Propagated jazz improvisation for walking bass lines​
​By William Wei
In jazz, a bass line, typically played by low-pitched instruments such as the double bass or piano, serves as a foundational accompaniment for all other melodies and chords in the song. A walking bass line generally consists of a sustained sequence of quarter notes played on the beat (4 notes per measure in 4/4 time). The objective of this project was to create a module that, given a bass line as input, could improvise a continuation of its preceding measures. The workflow of this project consisted of three main modules: note identification, chord (progression) construction, and improvisation. This project was able to successfully emulate a bass line by following a set of rules to create compatible melody. However, we have yet to explore other aspects of music, such as rhythm and dynamics, that would add musicality to the improvisation.

Introduction and Music Theory

In jazz, players typically follow a set of chord progressions and melodies to play a song. Each chord in a progression has a key and a quality, where every quality corresponds with a particular scale. For instance, the C Major chord is in the key of "C", and has a quality that corresponds with the Major scale. Thus, to play "over a chord", a player extracts notes, or degrees, from the chord's scale to form a melody compatible with that chord.
Plays a list of notes in "SoundNotePitch" notation (Each note lasts 0.5 seconds and is played with Piano):
In[]:=
playNotes[notes_List]:=Audio[Sound[SoundNote[#,1/2,"Bass"]&/@notes]]

2-5-1 (ii-V-I) Progression

The 2-5-1 progression exists as somewhat of a staple in not just jazz, but most music genres. Known as the most "popular progression", it is a succession of chords that, in fifths, descends from the second, fifth and then root using minor, dominant, and major qualities, respectively. For instance, a 2-5-1 progression in B flat would consist of a C-minor-7, F-dominant-7, and B-flat-major-7 chord.
2-5-1 Progression in B-flat Major:
In[]:=
n[key_,tmin_,tmax_]:={key,{tmin,tmax}}​​notes={n["C",0,1],n["Eb",0,1],n["G",0,1],n["Bb",0,1],n["F",1,2],n["A",1,2],n["C",1,2],n["Eb",1,2],n["Bb",2,3],n["D",2,3],n["F",2,3],n["A",2,3]};​​Sound[{"Cello"}~Join~(SoundNote@@@notes)];

Scales

The three basic scales in the 2-5-1 progression are minor, dominant, and major. These scales are distinguished by their third and seventh degrees, where the minor has both degrees lowered a half-step, the dominant has only the seventh lowered, and the major has none lowered.
Scales as functions of keys (minor, dominant, major):
In[]:=
min[key_]:=key+{0,2,3,5,7,9,10,12}​​dom[key_]:=key+{0,2,4,5,7,9,10,12}​​maj[key_]:=key+{0,2,4,5,7,9,11,12}

4 Rules: Bass Line Improvisation Technique

Bass line improvisation in this project follows four basic rules to construct a measure of "jazz". Given a chord progression, in our case, the 2-5-1, we assume that each chord is played over one measure. For that given measure, each of the four quarter notes is chosen by extracting a note from the scale that the chord corresponds with.
◼
  • First Beat: first (root) degree
  • ◼
  • Second Beat: third or seventh degree
  • ◼
  • Third Beat: fifth degree
  • ◼
  • Fourth Beat: leading tone
  • The leading tone is a half-step above or below next note, which is also the root of the next chord in the progression.

    Note Identification

    The first step of our workflow was to identify each note being played in the sample.
    ​This was achieved by using the “PitchRecognize” function.
    Input
    This audio sample was generated using the "4 Rules" to fit our chord construction module.
    2-5-1 Progression in B-flat Major:
    In[]:=
    audioInput=CloudImport["https://www.wolframcloud.com/obj/b6cef90d-b161-4b8e-b221-6d5bcc480ab4"];

    Pitch Recognition

    Using “PitchRecognize” on the audio sample, the following section returns a list of integers that represents music notes with “SoundNotePitch”, a format for converting notes to integers.
    Identifying notes:
    In[]:=
    identifyNote[audio_]:=​​Module[{ts,anom,filter,timeNote},​​ts=PitchRecognize[audioInput,"SoundNotePitch"]["Path"];​​anom=DeleteAnomalies[DeleteCases[ts,{_,Missing[]}]];​​filter=SplitBy[anom,#[[2]]&];​​timeNote=Table[{Ceiling[filter[[i,-1,1]]-filter[[i,1,1]],.25],filter[[i,1,2]]},{i,Length@filter}];​​Select[timeNote,#[[1]]>=0.4&][[All,2]]​​]
    Applying function onto audio sample:
    In[]:=
    identifyNote[audioInput]
    Out[]=
    {0,3,7,6,5,15,12,-1,-2,9,5}

    Chord Construction

    The second step of our workflow was to construct a chord progression from the identified notes.
    ​This was achieved by manually generating the notes of all possible 2-5-1 progressions, and matching the identified notes to a generated sequence.

    Possibilities of a Progression

    This section focuses on generating all possible notes in one progression (following the "4 Rules"). The different permutations are derived from the random selections occurring on the second and fourth beat of the measure.
    Given a scale, this function generates all combinations of the first three notes (used in "predictAll" function).
    Note that the fourth note, or the leading tone, is not added, as this function handles only one chord of the progression.
    In[]:=
    fThreeAll[scale_List]:={scale[[1]],scale[[#]],scale[[5]]}&/@{3,7}
    Given a progression, this function generates all possible combinations of notes:
    In[]:=
    predictAll[p_]:=​​Module[{fThreeNotes,lastNote},​​fThreeNotes=fThreeAll[#[[1]]@#[[2]]]&/@p;​​lastNote=Table[Flatten[Table[{Append[x,fThreeNotes[[i+1,1,1]]+#]&/@{-1,1}},{x,fThreeNotes[[i]]}],2],{i,Length[fThreeNotes]-1}];​​Tuples[Append[lastNote,fThreeNotes[[-1]]]]​​]

    Matching Progressions

    This section focuses on generates all possible 2-5-1 progressions. Once generated, the identified notes from the previous module are matched to a chord progression.
    Creates an association of all possible 2-5-1 progressions in 70 different keys (using "predictAll"):
    In[]:=
    allProg=Table[{{min,i+2},{dom,i+7},{maj,i}},{i,-35,35}];​​allProgAssoc=<|Table[(Flatten[i]->#),{i,predictAll[#]}]&/@allProg|>;
    Checks if identified notes exist as a chord progression; returns chord progression if true:
    In[]:=
    prog=allProgAssoc[identifyNote[audioInput]]
    Out[]=
    {{min,0},{dom,5},{maj,-2}}

    Improvisation

    The third step of our workflow was to improvise a section using the constructed progression.
    ​This was achieved by implementing the "4 Rules" into code. The functions in this section are similar to the ones in the previous module that generate all possible progressions.
    Generates first three notes in measure of a chord (used in "improv" function):
    In[]:=
    fThree[n_List]:={n[[1]],n[[RandomChoice[{3,7}]]],n[[5]]}
    Improvises a sequence given a progression:
    In[]:=
    improv[p_]:=​​Module[{fThreeNotes,lastNote,n},​​fThreeNotes=fThree[#[[1]]@#[[2]]]&/@p;​​lastNote=Table[Insert[fThreeNotes[[i]],fThreeNotes[[i+1]][[1]]+RandomChoice[{1,-1}],-1],{i,1,Length[fThreeNotes]-1}];​​n=Flatten[Insert[lastNote,fThreeNotes[[-1]],-1]];​​Insert[n,n[[1]]+RandomChoice[{-1,1}],1]​​]
    Improvisation:
    In[]:=
    improvisation=playNotes[improv[prog]];

    Result

    Joining together the original sample and the improvisation, the melodic compatibility is very apparent.
    Input sample + Improvised section (Bass):
    In[]:=
    result=AudioJoin[audioInput,improvisation];
    Cool visualisation of result:
    In[]:=
    ListPlot{PitchRecognize[result,"SoundNotePitch"],PitchRecognize[audioInput,"SoundNotePitch"]},
    options
    
    Out[]=

    Future Directions

    Extensions

    Although the original objective of this project was achieved, the improvisation lacks musical aspects such as dynamics and rhythm. Incorporating these aspects into an improvisation would create a more cohesive and musical accompaniment. Moreover, to broaden the application of this program, other instruments such as drums or the guitar could be incorporated into the sound to create a complete backing track.

    Shortcomings

    Note Identification

    In this project, pitch recognition was treated as a black box, as the "PitchRecognize" function was used to perform this task. However, this function was susceptible to errors in imperfect circumstances; such instances include lower-pitched audio and varying dynamics throughout the sample.

    Chord Construction

    A major shortcoming of this module was its overall inflexibility. It assumes that the sample follows the "4 Rules" of improvisation, and that the chord progression will always be a 2-5-1, as it generates the possibilities for only the 2-5-1. Structurally, this module needs to be more generic, and less strict; because it identifies progressions using pure matching, it does not allow any margin of error in the Note identification module. Future works could implement machine learning for this module; this would provide more accountability for Note Identification error.

    References

    [1] “Musical Scales and Modes in Every Key" - https://demonstrations.wolfram.com/MusicalScalesAndModesInEveryKey​
    [2] “Four rules for writing a walking bass line" - https://www.youtube.com/watch?v=h8IvkxQWf28