JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 62
  • Score
    100M100P100Q103342F
  • License MIT

Connect Strudel live-coding patterns to Scale Navigator ensembles. Sign in, join a live room, and sample the host's current scale, chord, and BPM as Strudel signals.

Package Exports

  • strudel-scalenav
  • strudel-scalenav/chords
  • strudel-scalenav/resolver
  • strudel-scalenav/scales
  • strudel-scalenav/visual

Readme

strudel-scalenav

Connect your Strudel live-coding patterns to Scale Navigator ensembles. Sign in, join a live room, and receive the host's current scale, chord, and BPM as live values you can sample in your Strudel patterns — so your algorithmic music stays harmonically locked to whoever's driving the ensemble.

  • Read-only client. Strudel users consume harmonic state; the host still drives.
  • Live. Updates stream via Firestore onSnapshot, so pattern output follows the host in real time.
  • Signed in. Google or email/password — your display name shows up in the ensemble alongside Dashboard users.
  • MIT-licensed, one-line import from the Strudel REPL.

Quick start

Paste this into strudel.cc:

const { signInWithGoogle, joinEnsemble, getCurrentUser } = await import('https://cdn.jsdelivr.net/npm/strudel-scalenav@0.6.1/dist/strudel-scalenav.js')
if (!getCurrentUser()) await signInWithGoogle()
const ens = await joinEnsemble('your-room-id')

n("0 2 4 <[6,8] [7,9]>")
  .scale(ens.strudelScale)
  .sound("piano")

Whenever the Scale Navigator host changes the scale or chord, your pattern reharmonizes on the next cycle — no re-evaluation needed.


Installation

In the Strudel REPL (the normal case)

const sn = await import('https://cdn.jsdelivr.net/npm/strudel-scalenav@0.6.1/dist/strudel-scalenav.js')

In a Vite / bundler project using @strudel/core

npm install strudel-scalenav firebase
import { joinEnsemble, signInWithGoogle } from 'strudel-scalenav'

You must have @strudel/core's signal available in global scope (the REPL does this for you; in a bundler project, expose it yourself: globalThis.signal = signal).


Signing in

Sign-in is required. Everyone who joins an ensemble shows up to the host by name. Pick one:

await signInWithGoogle()
// or
await signUpWithEmail('you@example.com', 'your-password', 'Your Display Name')
// or (for returning users)
await signInWithEmail('you@example.com', 'your-password')

Check if you're already signed in:

const user = getCurrentUser()
if (!user) await signInWithGoogle()

Joining an ensemble

You need a room ID. The host gets this when they create an ensemble in the Scale Navigator Dashboard or mobile app.

const ens = await joinEnsemble('nathan-jam-session')

Leave gracefully:

await ens.leave()

The ens API

The API is organized into clear namespaces: ens.scale.* and ens.chord.*.

ens.scale.*

Method Description
ens.scale.pitch(i) i-th scale note in octave 4
ens.scale.arp(n) Arpeggiate scale notes at n notes per cycle
ens.scale.block() All scale notes sounded together

ens.scale.thirds.*

Stack thirds from the scale root (index 0 of the scale). Uses the current scale root, not the chord root.

Method Description
ens.scale.thirds.pitch(i) i-th stacked third from scale root
ens.scale.thirds.arp(n) Arpeggiate thirds from scale root
ens.scale.thirds.block(count) Block chord of count stacked thirds

ens.chord.voicing.*

Access the host's original chord voicing (preserves octave placement).

Method Description
ens.chord.voicing.pitch(i) i-th voicing note (original octaves)
ens.chord.voicing.arp(n) Arpeggiate voicing at n notes per cycle
ens.chord.voicing.block() All voicing notes as block chord

ens.chord.closed.*

Close-position chord (all pitch classes in octave 4).

Method Description
ens.chord.closed.pitch(i) i-th chord note in close position
ens.chord.closed.arp(n) Arpeggiate closed position
ens.chord.closed.block() All closed position notes as block chord

ens.chord.thirds.*

Stack thirds from the chord root using the current scale. Continues up in octaves (9th is above 7th, not wrapped back down).

Method Description
ens.chord.thirds.pitch(i) i-th stacked third from chord root
ens.chord.thirds.arp(n) Arpeggiate thirds from chord root
ens.chord.thirds.block(count) Block chord of count stacked thirds

Stacked thirds index reference:

Index Interval Example (C major scale, chord root = C)
0 root C4
1 3rd E4
2 5th G4
3 7th B4
4 9th D5
5 11th F5
6 13th A5
7 root (2 oct) C6

All index methods support negative indices: -1 = last, -2 = second to last, etc.


Flat API (aliases)

For backwards compatibility and quick access, flat methods are also available. Prefer the hierarchical API for new code.

Flat Method Equivalent
ens.scalePitch(i) ens.scale.pitch(i)
ens.chordPitch(i) ens.chord.voicing.pitch(i)
ens.chordVoicingPitch(i) ens.chord.voicing.pitch(i)
ens.closedPitch(i) ens.chord.closed.pitch(i)
ens.chordClosedPitch(i) ens.chord.closed.pitch(i)
ens.stackThird(i) ens.chord.thirds.pitch(i)
ens.arpScale(n) ens.scale.arp(n)
ens.arp(n) ens.chord.voicing.arp(n)
ens.arpVoicing(n) ens.chord.voicing.arp(n)
ens.arpClosed(n) ens.chord.closed.arp(n)
ens.arpThirds(n) ens.chord.thirds.arp(n)
ens.block() ens.chord.voicing.block()
ens.blockVoicing() ens.chord.voicing.block()
ens.blockClosed() ens.chord.closed.block()
ens.blockThirds(count) ens.chord.thirds.block(count)

Scale data

Property Type Description
ens.scaleRoot signal (0–11) Pitch class of scale root
ens.scaleRootNote signal (MIDI) Scale root in octave 4 (playable)
ens.scalePitchClasses getter number[] Pitch classes 0–11
ens.scaleNotes getter number[] Scale notes in octave 4 (playable)
ens.strudelScale signal string For .scale(), e.g. "C:major"
ens.scaleName signal string Raw ID, e.g. "c_diatonic"
ens.scaleClass signal string e.g. "diatonic", "harmonic_minor"
ens.scaleRootName signal string e.g. "C", "F#"
ens.scalePretty getter string Pretty display: "E♭ Diatonic" or "[0,2] Whole Tone"

Chord data

Property Type Description
ens.chordRoot signal (0–11) Pitch class of chord root
ens.chordRootNote signal (MIDI) Chord root in octave 2 (bass)
ens.chordVoicing getter number[] Original voicing (MIDI notes)
ens.chordPitchClasses getter number[] Pitch classes 0–11
ens.chordClosed getter number[] Close position in octave 4 (playable)
ens.chordRootName signal string e.g. "A", "Db"
ens.chordType signal string e.g. "M7", "_13#9-110"
ens.chordNoteNames getter string[] e.g. ["G3", "Db4", "F#4"]
ens.chordPretty getter string Pretty display: "A♭ m7", "F♯ 13♯9"

Other

Property Type Description
ens.bpm signal Host's BPM
ens.hostName getter Who's hosting
ens.roomName getter Room display name
ens.roomId string Room ID you joined
ens.state getter Full internal state object
ens.leave() async Leave the ensemble gracefully

Visual helpers

Property/Method Type Description
ens.scaleColor getter string Current scale color (hex or rgb)
ens.showBadge(options?) function Display scale badge in REPL header

Options for showBadge: x (default 480), y (default 38), size (default 26), showText (default true)

note(ens.chord.voicing.arp(4)).s("piano")           // 4 notes per cycle
note(ens.chord.voicing.block()).s("piano").slow(2)  // block chord

Auth functions

Function Description
signInWithGoogle() Google popup sign-in
signInWithEmail(email, password) Email sign-in (returning users)
signUpWithEmail(email, password, displayName) Create new account
signOut() Sign out
getCurrentUser() Get current user or null
onAuthChange(callback) Listen for auth state changes
setDisplayName(name) Update your display name

Pattern recipes

Play in the host's scale

n("0 2 4 <[6,8] [7,9]>")
  .scale(ens.strudelScale)
  .sound("piano")

Arpeggiate the chord voicing

note(ens.chord.voicing.arp(4)).sound("piano")

Arpeggiate stacked thirds (1-3-5-7...)

note(ens.chord.thirds.arp(4)).sound("piano")

Chord with bass note

stack(
  note(ens.chordRootNote).slow(2),   // bass
  note(ens.chord.voicing.arp(4))     // arpeggio
).sound("piano")

Block chords

note(ens.chord.voicing.block())
  .struct("x ~ x ~ x x ~ x")
  .sound("piano")

Block chord of stacked thirds (7th chord)

note(ens.chord.thirds.block(4))  // root, 3rd, 5th, 7th
  .struct("x ~ x ~")
  .sound("piano")

Generative melody

n(irand(8).segment(8))
  .scale(ens.strudelScale)
  .sound("piano")

Arpeggiate the scale itself

note(ens.scale.arp(8)).sound("piano")

Sync to host's BPM

note(ens.chord.voicing.arp(4))
  .sound("piano")
  .cpm(ens.bpm.div(4))

Display scale badge

ens.showBadge()  // Shows polygon + scale/chord names in header

n("0 2 4 6")
  .scale(ens.strudelScale)
  .sound("piano")

The badge automatically positions to the right of "REPL (warm)" and updates when scale/chord changes.


Caveats

  • Scale → Strudel mapping. All 7 pressing scale classes map to Strudel equivalents: diatonicmajor, harmonic_minorharmonic minor, harmonic_majorharmonic major, acousticlydian dominant, whole_tonewhole tone, octatonicdiminished, hexatonicaugmented.
  • Read-only. This package never writes changes back to the room.
  • Auth required. Anonymous access isn't enabled.

Development

git clone https://github.com/nathanturczan/strudel-scalenav
cd strudel-scalenav
npm install
npm run build

Publishing

npm run build
npm publish


License

MIT © Nathan Turczan. See LICENSE.