← All Stories
JMOVE·March 18, 2026Mar 18, 2026·20 min read

Inside @jmove/generator: The Algorithms Behind a Jazz Rhythm Section

Walking bass contour planning, Bill Evans rootless voicings with voice-leading minimization, stochastic drum comping with phrase continuity, 19 groove templates - the full architecture of an open source backing track engine, explained from the code

There is a philosophical question that sits underneath every piece of music software: who owns the knowledge? When a developer writes code that generates a walking bass line, they are encoding decades of musical tradition into functions and arrays. The rules that make a bass line sound like jazz - root on beat one, chord tones on two, passing tones on three, chromatic approach on four - did not come from a computer science textbook. They came from Paul Chambers, Ray Brown, Ron Carter, and every bass player who ever walked a blues in a smoky club at midnight.

So when I built the backing track generator for JMove, I knew from the beginning it had to be open source. The musical knowledge encoded in that code does not belong to me. It belongs to the tradition. I just wrote it down in TypeScript - zero runtime dependencies, pure functions in, MIDI events out. What follows is a deep walk through every algorithm in the package: walking bass, piano comping, drums, styles, and the test suite that holds it all together. If you play jazz and you write code, this is for you.

The Walking Bass: Contour-First Architecture

The walking bass generator operates on a principle I have never seen in any other open source implementation: it plans the line backward. Most generators pick beat one (root), then beat two (chord tone), then beat three (passing tone), and finally beat four (approach). The problem is that beat four is the most important note in a walking bass line - it is the chromatic half-step that pulls your ear into the next bar. If you build forward, beat four becomes whatever is left over. If you build backward, beat four is the first calculation, and beats two and three interpolate smoothly between the root and the approach.

The algorithm inside generateSwingMeasure works like this: beat one is always the chord root (strong harmonic anchor - the tests enforce this). Beat four is computed next as an approach tone to the next chord's root. Then beats two and three are interpolated to create a smooth contour between those two anchor points, dividing the span into thirds and selecting the nearest chord tone for beat two and the nearest scale tone for beat three.

Walking bass contour planning (backward):

  Beat 1: rootPitch = rootToMidi(chord.root)    // always the root
  Beat 4: approachTone(nextRoot, fromAbove, scaleTones, style)
  Beat 2: nearest chord tone to span/3 position
  Beat 3: passingTone(beat2, beat4, scaleTones, chordTones)

  Approach vocabulary per style (APPROACH_VOCAB):
    swing:    { chromatic: 0.75, diatonic: 0.15, doubleChrm: 0.10 }
    hardBop:  { chromatic: 0.70, diatonic: 0.15, doubleChrm: 0.15 }
    coolJazz: { chromatic: 0.55, diatonic: 0.35, doubleChrm: 0.10 }

  Double chromatic = two half-steps from same direction
  (e.g., Eb-D approaching C from above)

The approach tone vocabulary is style-dependent. Swing lines use 75% chromatic approaches - a half step, the strongest gravitational pull. Cool jazz loosens to 55% chromatic, 35% diatonic, giving the line a more relaxed, scalewise quality. Hard bop bumps double-chromatic approaches to 15%, because Art Blakey's bassists loved the extra tension of two half-steps sliding into the root. These are not arbitrary numbers. They come from listening to hundreds of walking bass recordings and counting.

Two-bar phrasing prevents the bass from becoming monotonous. The variable prevDirection tracks whether the previous bar walked up or down, and the algorithm biases the next bar to alternate. If the last bar ascended, the target octave for the next approach is nudged downward with 60% probability. This creates the undulating contour that real bassists produce instinctively - two bars up, two bars down, a natural breathing pattern across the form.

There is also an 8th-note enclosure figure that fires on 15% of beat fours. When it triggers, beat four splits into two eighth notes that surround the target from both sides - one half-step above and one below - before landing on the next root. Ron Carter does this constantly. Ray Brown does it at turnarounds. The probability is low enough that when it happens, it sounds like a decision, not a gimmick. The code guards against enclosures near the range boundaries (BASS_LOW = 28, BASS_HIGH = 55, which is E1 to G3) so the figure does not clip into inaudible territory.

Bossa and Latin styles get completely different bass engines. Bossa generates a root-fifth half-note pattern - the archetypal Jobim accompaniment. Latin generates a tumbao pattern with syncopated anticipations and the "and of two" kick that defines Afro-Cuban bass. The style parameter does not just change velocities or timing. It selects a different compositional strategy entirely.

Piano Comping: Bill Evans in a VOICINGS Table

In the late 1950s, Bill Evans did something that changed jazz piano permanently: he dropped the root from his chord voicings. In a trio setting, the bass player already has the root. The pianist does not need to double it. By removing the root, Evans freed up a voice for richer extensions - the ninth, the thirteenth, altered tones that give jazz harmony its characteristic shimmer. He developed two voicing types that became the standard for every jazz pianist who followed.

Type A starts with the third on the bottom and stacks upward: 3-5-7-9 for major sevenths, b3-5-b7-9 for minor sevenths, 3-5-b7-9 for dominant sevenths. Type B starts with the seventh on the bottom: 7-9-3-5 for major, b7-root-b3-5 for minor, b7-9-3-5 for dominant. The two types are designed so that when a ii-V-I progression moves through them, the voices barely move. A Dm7 Type A voicing shares most of its notes with a G7 Type B voicing. That is the entire point - smooth voice leading emerges from the interplay between the two types.

typescript
// From pianoComping.ts — Evans rootless voicing intervals
// Intervals are semitones from root, placed in PIANO_LOW..PIANO_HIGH (48-76)

const VOICINGS: Record<string, [VoicingTemplate, VoicingTemplate]> = {
  // Minor 7: A = b3-5-b7-9, B = b7-root-b3-5
  "m7": [
    { intervals: [3, 7, 10, 14], label: "A" },   // span=11
    { intervals: [10, 12, 15, 19], label: "B" },  // span=9
  ],
  // Dominant 7: A = 3-5-b7-9, B = b7-9-3-5
  "7": [
    { intervals: [4, 7, 10, 14], label: "A" },   // span=10
    { intervals: [10, 14, 16, 19], label: "B" },  // span=9
  ],
  // Major 7: A = 3-5-7-9, B = 7-9-3-5
  "maj7": [
    { intervals: [4, 7, 11, 14], label: "A" },   // span=10
    { intervals: [11, 14, 16, 19], label: "B" },  // span=8
  ],
  // ... 40+ chord qualities total, including 7alt, m7b5, dim7,
  // sus4, 7#9, 7b9, maj7#11, aug, and every iReal Pro quality
};

The VOICINGS table covers over 40 chord qualities - every symbol you will encounter in iReal Pro plus common variants. The intervals for 7alt are [4, 8, 10, 13] (3-#5-b7-b9), which is the exact voicing you hear on Wayne Shorter records when the harmony gets dark. The sus4 voicings use [5, 7, 10, 14] (4-5-b7-9) - critically, no major third, because a sus chord with a third is not suspended at all. This is the kind of detail that took me longer to get right than any algorithm.

Voice leading is where the Evans system comes alive. The function voiceLeadingDistance computes the total semitone motion between two voicings - it sorts both by pitch and sums the absolute differences. When the generator encounters a new chord, it builds both Type A and Type B voicings and picks whichever one is closer to the previous voicing. The result is comping that moves by half steps and common tones rather than jumping across the keyboard.

typescript
// Voice-leading distance minimization
function voiceLeadingDistance(a: number[], b: number[]): number {
  const sa = [...a].sort((x, y) => x - y);
  const sb = [...b].sort((x, y) => x - y);
  let total = 0;
  for (let i = 0; i < Math.min(sa.length, sb.length); i++) {
    total += Math.abs(sa[i] - sb[i]);
  }
  return total;
}

// Selection: always pick the voicing with less motion
const distA = voiceLeadingDistance(prevPitches, voicingA);
const distB = voiceLeadingDistance(prevPitches, voicingB);
const full = distB < distA ? voicingB : voicingA;

But Evans rootless voicings are only one of seven voicing strategies in the generator. The pickVoicing function selects based on style: modal jazz uses McCoy Tyner's quartal voicings 60% of the time (stacked perfect fourths - root, P4, P4, P4 - the sound of A Love Supreme). ECM pushes quartal to 70%. Fusion splits 50/50 between quartal and open voicings. Pat Metheny gets buildOpenVoicing, which translates guitar open-string chord shapes into wide piano intervals - 3-note spreads spanning 12-17 semitones with open fifths, the Lydian shimmer that defines Bright Size Life.

Alfa Mist's style gets the most complex voicing logic. He is a self-taught pianist who plays a Yamaha CP-73 through a Pigtronix Echolution delay, and his voicings do not follow textbook rules. The generator uses 45% cluster voicings (tight groupings spanning 5-8 semitones, like 9-b3-4-b7 for minor chords), 20% warm inversions (first/second inversion triads with a ninth on top - ear-based, not Evans-based), and 35% standard rootless for variety. His rhythm templates loop for 2-4 bars before switching, mimicking his hip-hop producer mentality of thinking in loops rather than linear comping. Grace notes fire on 20% of chords - a half-step approach from below on an inner voicing tone, 30ms before the main hit, the liquid Rhodes licks you hear on Antiphon.

Rhythm Templates: 19 Styles, Each a Personality

Every style in the generator carries its own set of rhythm templates - arrays of [beatOffset, durationMultiplier] pairs that define where the piano hits fall within a bar. This is not a single rhythm with different quantize settings. Each style has a completely separate vocabulary of rhythmic patterns, and the generator rotates through them with anti-repetition weighting.

typescript
// Swing piano: 8 patterns (Charleston, anticipation, sparse, syncopated...)
const SWING_RHYTHMS: RhythmHit[][] = [
  [[0, 1.5], [1.5, 0.5], [3, 0.8]],     // Charleston: beat 1 + "and" of 2
  [[0, 1.0], [1.5, 1.5], [3.5, 0.4]],    // Anticipation: stab before bar end
  [[0, 1.8], [2, 1.8]],                   // Sparse: beats 1 and 3
  // ...
];

// Bossa: 4 patterns (montuno, simplified, anticipated, off-beat)
// Latin: 4 patterns (tumbao, guajeo, driving off-beat)
// Fusion: 6 patterns (Rhodes stabs, Headhunters anticipation, Chick dotted-quarter)
// ECM: 6 patterns (whole-bar pads, Jarrett floating feel, sparse beat-3 entry)
// Modal: 5 patterns (McCoy percussive quartal stabs, off-beat 8ths)
// Alfa Mist: 8 patterns (grace-note approach, delay-pedal sparse, broken arpeggio)
// Holdsworth: 6 patterns (syncopated stabs, conversational call-and-response)
// Metheny: 5 patterns (sustained pads, light syncopation, ECM-adjacent)
// + 7 odd-meter sets: 5/4, 6/8, 7/8, 9/8, 6/4, 7/4, 11/8

The rhythm selection algorithm uses weighted randomization with anti-repetition. The two most recently used pattern indices get their selection probability reduced to 0.25 and 0.5 respectively. A density parameter biases toward sparser or busier patterns: patterns are sorted by hit count, and 70% of the time the algorithm selects from the region of the sorted list that matches the density setting. This means a density of 20 gravitates toward whole-note pads, while a density of 85 pulls toward Charleston and off-beat stabs.

Strum spread simulates the natural finger roll of a pianist playing a chord voicing. The strumSpread function takes a multi-pitch CompNote and splits it into individual pitches staggered by a gap calculated from the strumMs parameter. Bottom note first (ascending), with a velocity taper of -3 per voice so upper notes are slightly softer. Bossa gets 20ms strum, stride gets 0ms (simultaneous block chords), and the caller controls it per-preset. A bossa chord with four voices and 20ms total strum means each voice enters about 6.7ms after the previous one - subtle, but you feel the difference between a strummed chord and a robotic block.

The Drum Engine: Stochastic Comping with Phrase Continuity

The drum pattern generator was the hardest part to get right. Here is the fundamental problem: a real jazz drummer does not play a fixed pattern. Art Blakey's kick drum comps differently every single bar. But it is not random either - there are tendencies, favored positions, a vocabulary of rhythmic figures that recur over 2-4 bar phrases before morphing into something slightly different. The challenge is generating patterns that feel simultaneously unpredictable and coherent.

For nine of the nineteen styles - swing, hard bop, cool jazz, modal, ballad, contemporary jazz, ECM, Metheny, and Holdsworth - the kick and snare comping is fully stochastic. Instead of cycling through fixed pattern arrays, the generator uses per-beat probability tables. Each beat position has a probability of firing, a base velocity, and a ghost-note flag.

typescript
// Stochastic kick/snare comping for swing — from drumPatterns.ts
const SWING_STOCHASTIC: StochasticTable = {
  slots: {
    "0":    [{ drum: GM_DRUMS.KICK, probability: 0.85, velocity: 55 }],
    "0.67": [{ drum: GM_DRUMS.SNARE, probability: 0.08, velocity: 30, ghost: true }],
    "1":    [{ drum: GM_DRUMS.KICK, probability: 0.10, velocity: 45 }],
    "1.67": [{ drum: GM_DRUMS.SNARE, probability: 0.12, velocity: 30, ghost: true }],
    "2":    [{ drum: GM_DRUMS.KICK, probability: 0.30, velocity: 45 }],
    "2.67": [{ drum: GM_DRUMS.SNARE, probability: 0.15, velocity: 35, ghost: true }],
    "3":    [{ drum: GM_DRUMS.KICK, probability: 0.15, velocity: 45 }],
    "3.67": [{ drum: GM_DRUMS.SNARE, probability: 0.20, velocity: 35, ghost: true }],
  },
  minHits: 1,  // at least kick on 1
  maxHits: 4,  // never more than 4 comping events per bar
};

The phrase continuity system is what makes this sound human rather than dice-roll random. A CompingTendency object holds 2-3 "favored" beat positions and a barsRemaining counter set to 2-4 bars. During those bars, the favored positions get a 2x probability boost. When the counter expires, a new tendency is generated with different favored positions. This mimics how Philly Joe Jones might ride a particular kick pattern for three bars before shifting to something new - the pattern is not fixed, but the tendency persists long enough for the listener's ear to lock in.

The other ten styles use hand-crafted pattern arrays because their rhythmic vocabulary resists stochastic generation. Funk needs specific kick-snare interlocking patterns (kick on 1 and "and" of 2, snare on 2 and 4 with ghost notes on "e" of 1 and "a" of 3). Neo-soul needs J Dilla's broken hi-hat patterns with deliberate gaps. Alfa Mist needs snare flams (a grace note 30ms before the main hit, velocity 45 then 90) and sextuplet ghost cascades at beat positions like 0.17 and 0.67 that do not land on any standard grid. These textures are too specific to emerge from probability tables.

The GM Drum Map and Ghost Note Dynamics

The entire drum engine operates on the General MIDI drum map - 18 named pitches in the GM_DRUMS constant. This matters because GM is the one drum standard that every synthesizer, every DAW, every sound module built in the last thirty years understands. The generator does not need to know what sample library you are using. It outputs pitch 36 for kick, 38 for snare, 42 for closed hi-hat, 51 for ride, and the receiving end maps those to whatever sounds it has. The universality is the point.

typescript
// General MIDI drum map — 18 instruments
export const GM_DRUMS = {
  KICK: 36,           SNARE: 38,
  SIDE_STICK: 37,     CROSS_STICK: 37,
  SNARE_GHOST: 38,    // same pitch as snare, differentiated by velocity
  HI_HAT_CLOSED: 42,  HI_HAT_OPEN: 46,   HI_HAT_PEDAL: 44,
  RIDE: 51,           RIDE_BELL: 53,
  CRASH: 49,
  TOM_HIGH: 50,       TOM_MID: 47,
  TOM_LOW: 45,        TOM_FLOOR: 43,
  COWBELL: 56,        CLAVES: 75,         SHAKER: 70,
} as const;

Ghost notes are the secret language of jazz drumming. A ghost note is a snare hit so quiet it is more felt than heard - velocity 25-50 in the generator's scale, versus 85-100 for an accented hit. The humanizeVelocity function clamps ghost notes to a 35-50 range with 10-unit random variation, ensuring they stay in that subliminal zone. Full hits get a wider 12-unit variation window clamped between 45 and 127.

The difference between a drum pattern with ghost notes and one without is the difference between a groove and a click track. Listen to Steve Gadd playing "50 Ways to Leave Your Lover." The accented snare hits define the beat. The ghost notes between them define the feel. In the generator, funk patterns have explicit ghost snare hits at velocity 30-35 on the "e" and "a" of every beat. Neo-soul patterns push ghost velocities even lower - 25-30 - because Questlove plays so quietly between his accents that the ghosts are barely there, more a suggestion of rhythm than a statement of it. The Alfa Mist patterns go furthest, placing ghost snares at sextuplet positions (beats 0.17, 0.67, 2.17) - subdivisions that do not align with any standard 16th-note grid, creating the shimmering "raindrop" quality of Jas Kayser's hi-hat work.

Groove Templates: Where the Feel Lives

Groove is not randomness. This is one of the most misunderstood concepts in music programming. Adding random jitter to note timing does not create groove - it creates sloppiness. Real groove is structured micro-timing: specific instruments displaced by specific amounts on specific beats, consistently. The GrooVAE research out of the Magenta team confirmed what jazz musicians have always known: groove is a per-instrument, per-beat displacement pattern that repeats.

The grooveTemplates.ts file contains 19 GrooveTemplate objects, one per style. Each template defines bias (systematic offset in seconds) and jitter (random variation half-width) for nine elements: kick, snare, hi-hat, ride, crash, bass, bass-offbeat, piano, and piano-anticipation. Positive bias means behind the beat (laid back). Negative means ahead (pushing).

The neo-soul template is the most dramatic example. The kick bias is +0.010 seconds (10ms behind the beat), the snare bias is -0.005 seconds (5ms ahead), and the hi-hat has zero bias but 4ms of jitter. This is the J Dilla feel: the kick drags late, the snare pushes early, and each instrument is on its own private grid. The bass follows the kick (+8ms bias on downbeats, +10ms on offbeats), and the piano sits somewhere in between (+5ms). The result is a groove where nothing lines up on the grid but everything locks together in a way that makes your body move. Dilla learned this from listening to James Brown and Questlove, and it changed how an entire generation of producers understood time.

The Alfa Mist template takes the Dilla concept and adds Jas Kayser's quintuplet displacement. The hi-hat jitter is 6ms - wider than any other style - because Kayser's hi-hat patterns sit on a quintuplet grid that does not align with the 16th-note base. The snare pushes harder than neo-soul at -6ms, and the kick drags at +10ms with 5ms of jitter. The piano floats at +4ms with 5ms of jitter because the Rhodes delay pedal fills the gaps. Compare this to math rock, where every instrument has zero bias and 1-2ms of jitter: machine-tight precision, because that is what the style demands.

Swing Ratio: The Variable Nobody Talks About

The swingUtils.ts file contains two functions that control something most music software gets wrong: the relationship between tempo and swing feel. The function tempoSwingMultiplier returns a scaling factor that adjusts the swing ratio based on tempo. At 80 BPM, it returns 1.5 - heavy triplet swing, nearly a 3:1 ratio between the downbeat eighth and the upbeat eighth. At 120-180 BPM, it returns 1.0 - standard swing. Above 240 BPM, it returns 0.3 - nearly straight eighths. This is what real jazz musicians do: slow tempos get heavy swing because there is room for the upbeat to be late, while fast tempos straighten out because there simply is not time to displace the upbeat without the rhythm collapsing.

The second function, instrumentSwingFactor, adjusts swing per instrument. Drums get a factor of 1.0 (the ride swings hardest - it is the timekeeping reference). Piano gets 0.85 (slightly straighter, because the piano comps against the ride rather than matching it). Bass gets 0.70 (the straightest - walking bass lines that swing as hard as the ride sound drunk, not swinging). These ratios multiply together: a bass note at 80 BPM gets an effective swing of 0.70 * 1.5 = 1.05, while the same note at 200 BPM gets 0.70 * 0.8 = 0.56. The interdependence between tempo and instrument creates swing behavior that adjusts automatically to any tempo the user sets.

22 Style Presets: Parameterized Personalities

The STYLE_PRESETS array in stylePresets.ts contains 22 entries (19 pure styles plus 3 hybrid presets with per-instrument style overrides). Each preset bundles a style ID, a description, a parameter set (swingAmount, density, strumMs), and a tempo range. These are not vague labels. They are complete specifications.

Classic Swing: swingAmount 70, density 50, strumMs 20, tempo 120-180. Hard Bop Drive: swingAmount 80, density 70, strumMs 20, tempo 140-200. ECM Space: swingAmount 10, density 15, strumMs 0, tempo 50-90. Each number encodes a musical decision. ECM's density of 15 means the piano plays about 15% of available rhythmic positions - sparse, floating, lots of silence. Hard bop's 70 means the piano is busy, driving, filling space. The strumMs of 0 for ECM and ballad means chords hit simultaneously (block voicings), while bossa's 20ms creates that gentle finger roll.

The three hybrid presets demonstrate the system's flexibility. Fusion/ECM pairs fusion drums (16th hi-hats, syncopated kick) with ECM piano (quartal voicings, sustained pads, zero strum) - a combination inspired by 1970s European jazz-rock. Modal Funk pairs modal piano (McCoy Tyner quartal stabs) with funk drums (16th hi-hats, ghost snares). The instrumentStyles field lets each instrument draw from a different style's pattern set while sharing the same tempo and swing parameters.

Dynamic Arcs: Volume as Composition

The dynamicMultiplier function in swingUtils.ts implements per-style volume contours across the length of a chorus. This is something no one thinks about until they hear a computer play thirty-two bars of All The Things You Are at exactly the same volume for every measure. Real rhythm sections breathe. They start subdued, build through the middle of the form, peak near the climax, and pull back slightly at the end. The function takes a measure index and total measures and returns a velocity multiplier between roughly 0.60 and 1.05.

Each style gets a custom arc. Hard bop starts at 0.75 (Art Blakey's quiet opening), builds aggressively to 1.05 by 75% of the form, then tapers to 0.95 - a 30% dynamic range. ECM barely moves at all: 0.92 to 1.00 over the entire form, a 10% range - because Keith Jarrett trio recordings feel like a single sustained breath. Alfa Mist has the widest arc: 0.60 to 1.05, a 45% range, because his compositions build from whispered Rhodes chords to full-band intensity. The fusion arc includes a mid-form "reset" at 60% - dropping from 1.05 back to 0.95 before rebuilding - mimicking the mid-solo tension release that Weather Report used to devastating effect.

996 Tests: Musical Assertions

The generator package has 996 tests across 10 test files. The most interesting file is musicality.test.ts, which runs twenty real jazz standards through the generators and makes musical assertions about the output. These are not software tests. They are theory exams.

Does the bass hit root or fifth on beat one across all twenty standards? Does the piano always include the guide tones (third and seventh) that define a chord's quality? Does a dominant seventh voicing always contain the tritone between the third and the flat seventh? Does a minor seventh voicing always contain the flat third and flat seventh? Does the bass never play a major third on a minor chord's downbeat? Is the average voice-leading motion less than 4 semitones per voice across an entire standard?

typescript
// From musicality.test.ts — 20 real standards as test fixtures
const summertime = () => makeProgression([
  ["A", "m7"], ["A", "m7"], ["E", "7"], ["A", "m7"],
  ["D", "m7"], ["A", "m7"], ["E", "7"], ["A", "m7"],
], 120);

const giantSteps = () => makeProgression([
  ["B", "maj7"], ["D", "7"], ["G", "maj7"], ["Bb", "7"],
  ["Eb", "maj7"], ["A", "m7"], ["D", "7"], ["G", "maj7"],
  ["Bb", "7"], ["Eb", "maj7"], ["F#", "7"], ["B", "maj7"],
], 180);

// Assertions:
// "bass never plays major 3rd on minor chord beat 1"
// "bass never plays minor 3rd on major/dominant chord beat 1"
// "half-diminished (m7b5) always contains b3, b5, b7"
// "piano lowest > MIDI 48 always" (register separation from bass)

The twenty standards are deliberately diverse: Summertime (minor blues), Autumn Leaves (ii-V-I in major and minor), Giant Steps (Coltrane changes), So What (modal Dorian), Girl From Ipanema (bossa with key changes), Body and Soul (rich ballad harmony), Rhythm Changes bridge (dominant cycle), Satin Doll (turnarounds), Solar (minor key standard), Fly Me To The Moon (classic Great American Songbook). Each standard exercises a different corner of the harmonic engine. If the bass sounds wrong on Giant Steps, the approach-tone algorithm has a bug. If the piano sounds muddy on Body and Soul, the voicing tables need adjusting. The standards are the specification.

Zero Dependencies: A Deliberate Choice

The package.json for @jmove/generator has exactly zero runtime dependencies. Not one. The devDependencies list contains TypeScript, tsup (for bundling), vitest (for testing), and ESLint. That is it. The generator does not import Tone.js, does not use the Web Audio API, does not call any HTTP endpoint, does not require a browser environment, does not even need Node.js beyond the build step.

This is deliberate and it is non-negotiable. The generator is pure musical logic: chord events go in, MIDI events come out. Functions and arrays and arithmetic. It runs in a web worker, in a Node.js script, in a Deno runtime, in a React Native app, in an Electron desktop app. It does not know or care what turns its output into sound. That is the consuming application's job. The generator's job is to be correct, portable, and fast.

The zero-dependency architecture also means no supply chain risk, no breaking changes from upstream packages, no npm audit warnings, no version conflicts with your application's dependency tree. In a world where a single left-pad incident can take down thousands of builds, a package with zero dependencies is a package you can trust. The musical logic will never break because somebody published a bad patch to a transitive dependency three levels deep.

Why Open Source

I keep coming back to the same thought: the rules encoded in this generator do not belong to me. The walking bass algorithm is Paul Chambers and Ray Brown. The voicing tables are Bill Evans and McCoy Tyner. The groove templates are Tony Williams and Jack DeJohnette and J Dilla. The approach-tone ratios are something I measured by listening to hundreds of recordings, but the recordings were not mine. The tradition was not mine. I just wrote it down.

Open source is the only honest way to release this kind of work. If a jazz education student in Lagos or Jakarta or Sao Paulo wants to understand how voice leading works, they should be able to read the voiceLeadingDistance function and trace it with their own chord progression. If a developer in Berlin wants to build a practice app with a better UI than mine, they should be able to import the generator and not start from zero. If a music theory professor wants to show their class how chromatic approach tones work at different tempos, the tempoSwingMultiplier function should be right there, readable, forkable, improvable.

Jazz itself is an open source tradition. Every standard is a shared codebase that musicians fork, modify, and merge back into the collective repertoire. Charlie Parker took Gershwin's "I Got Rhythm" changes and wrote dozens of new melodies over them - the original API, different implementations. Coltrane took ii-V-I progressions and refactored them through major third cycles. Every jazz solo is a pull request on the original composition. The least I can do is release the code under MIT and let the tradition continue in a new medium.

Read the full story of why JMove was built Deep dive into the walking bass algorithm
Install @jmove/generator