npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

strudel-scalenav

v0.6.1

Published

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.

Downloads

2,132

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/[email protected]/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/[email protected]/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('[email protected]', 'your-password', 'Your Display Name')
// or (for returning users)
await signInWithEmail('[email protected]', '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

Hierarchical API (recommended)

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

Related


License

MIT © Nathan Turczan. See LICENSE.