Following on from my piano key scale finder project, I’ve been working on something to help me remember my bass frets.
This project uses a string of addressable LEDs, and can be configured to zig-zag up and down the strings or across the fretboard.
The string tuning can be specified, along with:
- scale mode (colour root and scale notes separarely)
- interval mode (colour/bright root note and intervals from it differently)
- note mode (colour notes in scale to a pre-defined colour language - I use the colour of the fretboard stickers I have been using)
There is a huge range of scales available.
This was put together with a lot of assistance from Claude Code. Any optimisations welcome, as would be an adaptation to guitar if it interests anyone.
/*
============================================================
LED FRETBOARD SCALE HIGHLIGHTER - Pixelblaze
Bass fretboard. A position lights only when its note is in
the chosen scale. THREE display modes choose how lit notes
are coloured.
============================================================
Note numbers (E-based: open low string E = 0):
0 1 2 3 4 5 6 7 8 9 10 11
E F F# G G# A A# B C C# D D#
HOW IT WORKS:
decode(index) -> physical LED index to (string, fret)
isInScale() -> single gate; every mode respects it
setScale(id) -> activates the chosen scale/chord
mode -> picks colouring for lit notes
CHANGING SCALE OR CHORD:
Set scaleId to the ID next to the name in the library below.
That is all.
ADDING A SCALE OR CHORD:
1. Find the setScale() function below.
2. Copy any existing line as a template.
3. Change the id number to the next available (currently 84),
list your intervals using mark(), and add a name comment.
4. Add the same ID and name to the comment table above setScale().
Example -- adding Augmented triad as ID 68:
else if (id == 68) { mark(0);mark(4);mark(8); } // Augmented triad
*/
// ---- PHYSICAL BOARD -------------------------------------------------
// 4 strings x 22 fret-positions (0..21, position 0 = open/nut). 4*22 = 88.
var NUM_STRINGS = 4;
var NUM_FRETS = 22;
var NUM_USED = NUM_STRINGS * NUM_FRETS; // 88 used of 100 LEDs
// ---- WIRING LAYOUT --------------------------------------------------
// wiringMode 0 = per-string serpentine 1 = per-fret serpentine
// reverseChain = 1 if physical LED 0 is at the far end
export var wiringMode = 0;
export var reverseChain = 1;
// ---- TUNING: open-string note per string, in WIRING string order ----
// E-based note assignment:
// 0 1 2 3 4 5 6 7 8 9 10 11
// E F F# G G# A A# B C C# D D#
// Standard 4-string bass low->high E A D G = 0 5 10 3.
// If string 0 is the HIGH string, reverse this: [3, 10, 5, 0].
var openNotes = [0, 5, 10, 3];
// ====================================================================
// MODE
// 0 = SCALE root vs other colours (rootColorHue / otherColorHue)
// 1 = INTERVAL colour per interval-above-root; root/other ignored
// 2 = NOTES colour per absolute note (E-based); root/other ignored
// ====================================================================
export var mode = 2;
// ====================================================================
// SCALE / CHORD LIBRARY
//
// ID Name Intervals (semitones from root)
// 0 Major (Ionian) 0, 2, 4, 5, 7, 9, 11
// 1 Dorian 0, 2, 3, 5, 7, 9, 10
// 2 Phrygian 0, 1, 3, 5, 7, 8, 10
// 3 Lydian 0, 2, 4, 6, 7, 9, 11
// 4 Mixolydian 0, 2, 4, 5, 7, 9, 10
// 5 Natural minor (Aeolian) 0, 2, 3, 5, 7, 8, 10
// 6 Locrian 0, 1, 3, 5, 6, 8, 10
// 7 Harmonic minor 0, 2, 3, 5, 7, 8, 11
// 8 Melodic minor 0, 2, 3, 5, 7, 9, 11
// 9 Harmonic major 0, 2, 4, 5, 7, 8, 11
// 10 Major pentatonic 0, 2, 4, 7, 9 (= 6/9 chord tones)
// 11 Minor pentatonic 0, 3, 5, 7, 10
// 12 Minor blues 0, 3, 5, 6, 7, 10
// 13 Major blues 0, 2, 3, 4, 7, 9
// 14 Bebop dominant 0, 2, 4, 5, 7, 9, 10, 11
// 15 Bebop major 0, 2, 4, 5, 7, 8, 9, 11
// 16 Bebop minor 0, 2, 3, 4, 5, 7, 9, 10
// 17 Hungarian minor (Gypsy minor) 0, 2, 3, 6, 7, 8, 11
// 18 Hungarian major 0, 3, 4, 6, 7, 9, 10
// 19 Double harmonic (Byzantine) 0, 1, 4, 5, 7, 8, 11
// 20 Phrygian dominant 0, 1, 4, 5, 7, 8, 10
// 21 Neapolitan minor 0, 1, 3, 5, 7, 8, 11
// 22 Neapolitan major 0, 1, 3, 5, 7, 9, 11
// 23 Lydian dominant 0, 2, 4, 6, 7, 9, 10
// 24 Mixolydian b6 0, 2, 4, 5, 7, 8, 10
// 25 Locrian natural-2 0, 2, 3, 5, 6, 8, 10
// 26 Altered / Super Locrian 0, 1, 3, 4, 6, 8, 10
// 27 Lydian augmented 0, 2, 4, 6, 8, 9, 11
// 28 Dorian b2 0, 1, 3, 5, 7, 9, 10
// 29 Ukrainian Dorian 0, 2, 3, 6, 7, 9, 10
// 30 Lydian #2 0, 3, 4, 6, 7, 9, 11
// 31 Hirajoshi 0, 2, 3, 7, 8 (Kostka/Payne form; variants exist)
// 32 Iwato 0, 1, 5, 6, 10
// 33 In Sen 0, 1, 5, 7, 10
// 34 Yo 0, 2, 5, 7, 9 (Japanese; ~ gamelan slendro)
// 35 Marwa 0, 1, 4, 6, 7, 9, 11
// 36 Purvi 0, 1, 4, 6, 7, 8, 11
// 37 Todi 0, 1, 3, 6, 7, 8, 11
// 38 Pelog approx 0, 1, 3, 7, 8 (rough 12-TET stand-in)
// 39 Prometheus (Scriabin) 0, 2, 4, 6, 9, 10
// 40 Enigmatic 0, 1, 4, 6, 8, 10, 11
// 41 Egyptian (suspended pent.) 0, 2, 5, 7, 10
// 42 Whole tone 0, 2, 4, 6, 8, 10
// 43 Diminished (whole-half) 0, 2, 3, 5, 6, 8, 9, 11
// 44 Diminished (half-whole) 0, 1, 3, 4, 6, 7, 9, 10
// 45 Augmented scale 0, 3, 4, 7, 8, 11
// 46 Chromatic 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
// 47 Messiaen 3 0, 2, 3, 4, 6, 7, 8, 10, 11
// 48 Messiaen 4 0, 1, 2, 5, 6, 7, 8, 11
// 49 Messiaen 5 0, 1, 5, 6, 7, 11
// 50 Messiaen 6 0, 2, 4, 5, 6, 8, 10, 11
// 51 Messiaen 7 0, 1, 2, 3, 5, 6, 7, 8, 9, 11
// 52 Persian 0, 1, 4, 5, 6, 8, 11
// 53 Algerian 0, 2, 3, 5, 6, 7, 8, 11
// 54 Petrushka / tritone 0, 1, 4, 6, 7, 10
// 68 Bebop minor (natural) 0, 2, 3, 5, 7, 8, 10, 11
// 69 Spanish 8-tone 0, 1, 3, 4, 5, 6, 8, 10
// ---- Chords: set rootNote to the chord root ----
// 55 Major triad 0, 4, 7
// 56 Minor triad 0, 3, 7
// 57 Diminished triad 0, 3, 6
// 58 Augmented triad 0, 4, 8
// 59 Sus2 0, 2, 7
// 60 Sus4 0, 5, 7
// 61 Major 6 0, 4, 7, 9
// 62 Minor 6 0, 3, 7, 9
// 63 Dominant 7 0, 4, 7, 10
// 64 Major 7 0, 4, 7, 11
// 65 Minor 7 0, 3, 7, 10
// 66 Half-dim (m7b5) 0, 3, 6, 10
// 67 Diminished 7 0, 3, 6, 9
// 70 Power chord (5) 0, 7
// 71 Add9 (add2) 0, 2, 4, 7
// 72 Minor add9 0, 2, 3, 7
// 73 Minor-major 7 0, 3, 7, 11
// 74 Dominant 7b5 0, 4, 6, 10
// 75 Dominant 7#5 (aug7) 0, 4, 8, 10
// 76 Major 7#11 0, 4, 6, 7, 11
// 77 Dominant 9 0, 2, 4, 7, 10 (= dominant/Mixolydian pentatonic)
// 78 Major 9 0, 2, 4, 7, 11
// 79 Minor 9 0, 2, 3, 7, 10
// 80 Dominant 7b9 0, 1, 4, 7, 10
// 81 Dominant 7#9 0, 3, 4, 7, 10
// 82 Dominant 13 0, 2, 4, 7, 9, 10
// 83 Quartal (stacked 4ths) 0, 5, 10
// ====================================================================
export var scaleId = 46;
var selectedScale = array(12); // selectedScale[d]=1 if interval d is active
// mark() and setScale() are internal -- you only edit the mark() calls below.
function mark(d) { selectedScale[d] = 1; }
function setScale(id) {
var d = 0;
for (d = 0; d < 12; d++) { selectedScale[d] = 0; }
// ---- Diatonic modes ----
if (id == 0) { mark(0);mark(2);mark(4);mark(5);mark(7);mark(9);mark(11); } // Major (Ionian)
else if (id == 1) { mark(0);mark(2);mark(3);mark(5);mark(7);mark(9);mark(10); } // Dorian
else if (id == 2) { mark(0);mark(1);mark(3);mark(5);mark(7);mark(8);mark(10); } // Phrygian
else if (id == 3) { mark(0);mark(2);mark(4);mark(6);mark(7);mark(9);mark(11); } // Lydian
else if (id == 4) { mark(0);mark(2);mark(4);mark(5);mark(7);mark(9);mark(10); } // Mixolydian
else if (id == 5) { mark(0);mark(2);mark(3);mark(5);mark(7);mark(8);mark(10); } // Natural minor
else if (id == 6) { mark(0);mark(1);mark(3);mark(5);mark(6);mark(8);mark(10); } // Locrian
// ---- Other 7-note ----
else if (id == 7) { mark(0);mark(2);mark(3);mark(5);mark(7);mark(8);mark(11); } // Harmonic minor
else if (id == 8) { mark(0);mark(2);mark(3);mark(5);mark(7);mark(9);mark(11); } // Melodic minor
else if (id == 9) { mark(0);mark(2);mark(4);mark(5);mark(7);mark(8);mark(11); } // Harmonic major
// ---- Pentatonic / blues ----
else if (id == 10) { mark(0);mark(2);mark(4);mark(7);mark(9); } // Major pentatonic
else if (id == 11) { mark(0);mark(3);mark(5);mark(7);mark(10); } // Minor pentatonic
else if (id == 12) { mark(0);mark(3);mark(5);mark(6);mark(7);mark(10); } // Minor blues
else if (id == 13) { mark(0);mark(2);mark(3);mark(4);mark(7);mark(9); } // Major blues
// ---- Bebop ----
else if (id == 14) { mark(0);mark(2);mark(4);mark(5);mark(7);mark(9);mark(10);mark(11); } // Bebop dominant
else if (id == 15) { mark(0);mark(2);mark(4);mark(5);mark(7);mark(8);mark(9);mark(11); } // Bebop major
else if (id == 16) { mark(0);mark(2);mark(3);mark(4);mark(5);mark(7);mark(9);mark(10); } // Bebop minor
// ---- Gypsy / exotic ----
else if (id == 17) { mark(0);mark(2);mark(3);mark(6);mark(7);mark(8);mark(11); } // Hungarian minor
else if (id == 18) { mark(0);mark(3);mark(4);mark(6);mark(7);mark(9);mark(10); } // Hungarian major
else if (id == 19) { mark(0);mark(1);mark(4);mark(5);mark(7);mark(8);mark(11); } // Double harmonic
else if (id == 20) { mark(0);mark(1);mark(4);mark(5);mark(7);mark(8);mark(10); } // Phrygian dominant
else if (id == 21) { mark(0);mark(1);mark(3);mark(5);mark(7);mark(8);mark(11); } // Neapolitan minor
else if (id == 22) { mark(0);mark(1);mark(3);mark(5);mark(7);mark(9);mark(11); } // Neapolitan major
// ---- Melodic minor modes ----
else if (id == 23) { mark(0);mark(2);mark(4);mark(6);mark(7);mark(9);mark(10); } // Lydian dominant
else if (id == 24) { mark(0);mark(2);mark(4);mark(5);mark(7);mark(8);mark(10); } // Mixolydian b6
else if (id == 25) { mark(0);mark(2);mark(3);mark(5);mark(6);mark(8);mark(10); } // Locrian natural-2
else if (id == 26) { mark(0);mark(1);mark(3);mark(4);mark(6);mark(8);mark(10); } // Altered
else if (id == 27) { mark(0);mark(2);mark(4);mark(6);mark(8);mark(9);mark(11); } // Lydian augmented
else if (id == 28) { mark(0);mark(1);mark(3);mark(5);mark(7);mark(9);mark(10); } // Dorian b2
// ---- Harmonic minor modes ----
else if (id == 29) { mark(0);mark(2);mark(3);mark(6);mark(7);mark(9);mark(10); } // Ukrainian Dorian
else if (id == 30) { mark(0);mark(3);mark(4);mark(6);mark(7);mark(9);mark(11); } // Lydian #2
// ---- Japanese pentatonic ----
else if (id == 31) { mark(0);mark(2);mark(3);mark(7);mark(8); } // Hirajoshi
else if (id == 32) { mark(0);mark(1);mark(5);mark(6);mark(10); } // Iwato
else if (id == 33) { mark(0);mark(1);mark(5);mark(7);mark(10); } // In Sen
else if (id == 34) { mark(0);mark(2);mark(5);mark(7);mark(9); } // Yo
// ---- Indian thaats / gamelan ----
else if (id == 35) { mark(0);mark(1);mark(4);mark(6);mark(7);mark(9);mark(11); } // Marwa
else if (id == 36) { mark(0);mark(1);mark(4);mark(6);mark(7);mark(8);mark(11); } // Purvi
else if (id == 37) { mark(0);mark(1);mark(3);mark(6);mark(7);mark(8);mark(11); } // Todi
else if (id == 38) { mark(0);mark(1);mark(3);mark(7);mark(8); } // Pelog approx
// ---- Other exotic ----
else if (id == 39) { mark(0);mark(2);mark(4);mark(6);mark(9);mark(10); } // Prometheus
else if (id == 40) { mark(0);mark(1);mark(4);mark(6);mark(8);mark(10);mark(11); } // Enigmatic
else if (id == 41) { mark(0);mark(2);mark(5);mark(7);mark(10); } // Egyptian
// ---- Symmetric ----
else if (id == 42) { mark(0);mark(2);mark(4);mark(6);mark(8);mark(10); } // Whole tone
else if (id == 43) { mark(0);mark(2);mark(3);mark(5);mark(6);mark(8);mark(9);mark(11); } // Diminished (W-H)
else if (id == 44) { mark(0);mark(1);mark(3);mark(4);mark(6);mark(7);mark(9);mark(10); } // Diminished (H-W)
else if (id == 45) { mark(0);mark(3);mark(4);mark(7);mark(8);mark(11); } // Augmented scale
else if (id == 46) { mark(0);mark(1);mark(2);mark(3);mark(4);mark(5);mark(6);mark(7);mark(8);mark(9);mark(10);mark(11); } // Chromatic
// ---- Messiaen modes of limited transposition ----
else if (id == 47) { mark(0);mark(2);mark(3);mark(4);mark(6);mark(7);mark(8);mark(10);mark(11); } // Messiaen 3
else if (id == 48) { mark(0);mark(1);mark(2);mark(5);mark(6);mark(7);mark(8);mark(11); } // Messiaen 4
else if (id == 49) { mark(0);mark(1);mark(5);mark(6);mark(7);mark(11); } // Messiaen 5
else if (id == 50) { mark(0);mark(2);mark(4);mark(5);mark(6);mark(8);mark(10);mark(11); } // Messiaen 6
else if (id == 51) { mark(0);mark(1);mark(2);mark(3);mark(5);mark(6);mark(7);mark(8);mark(9);mark(11); } // Messiaen 7
// ---- More named exotics ----
else if (id == 52) { mark(0);mark(1);mark(4);mark(5);mark(6);mark(8);mark(11); } // Persian
else if (id == 53) { mark(0);mark(2);mark(3);mark(5);mark(6);mark(7);mark(8);mark(11); } // Algerian
else if (id == 54) { mark(0);mark(1);mark(4);mark(6);mark(7);mark(10); } // Petrushka / tritone
// ---- Chords ----
else if (id == 55) { mark(0);mark(4);mark(7); } // Major triad
else if (id == 56) { mark(0);mark(3);mark(7); } // Minor triad
else if (id == 57) { mark(0);mark(3);mark(6); } // Diminished triad
else if (id == 58) { mark(0);mark(4);mark(8); } // Augmented triad
else if (id == 59) { mark(0);mark(2);mark(7); } // Sus2
else if (id == 60) { mark(0);mark(5);mark(7); } // Sus4
else if (id == 61) { mark(0);mark(4);mark(7);mark(9); } // Major 6
else if (id == 62) { mark(0);mark(3);mark(7);mark(9); } // Minor 6
else if (id == 63) { mark(0);mark(4);mark(7);mark(10); } // Dominant 7
else if (id == 64) { mark(0);mark(4);mark(7);mark(11); } // Major 7
else if (id == 65) { mark(0);mark(3);mark(7);mark(10); } // Minor 7
else if (id == 66) { mark(0);mark(3);mark(6);mark(10); } // Half-dim (m7b5)
else if (id == 67) { mark(0);mark(3);mark(6);mark(9); } // Diminished 7
else if (id == 68) { mark(0);mark(2);mark(3);mark(5);mark(7);mark(8);mark(10);mark(11); } // Bebop minor (natural)
else if (id == 69) { mark(0);mark(1);mark(3);mark(4);mark(5);mark(6);mark(8);mark(10); } // Spanish 8-tone
else if (id == 70) { mark(0);mark(7); } // Power chord (5)
else if (id == 71) { mark(0);mark(2);mark(4);mark(7); } // Add9 (add2)
else if (id == 72) { mark(0);mark(2);mark(3);mark(7); } // Minor add9
else if (id == 73) { mark(0);mark(3);mark(7);mark(11); } // Minor-major 7
else if (id == 74) { mark(0);mark(4);mark(6);mark(10); } // Dominant 7b5
else if (id == 75) { mark(0);mark(4);mark(8);mark(10); } // Dominant 7#5 (aug7)
else if (id == 76) { mark(0);mark(4);mark(6);mark(7);mark(11); } // Major 7#11
else if (id == 77) { mark(0);mark(2);mark(4);mark(7);mark(10); } // Dominant 9
else if (id == 78) { mark(0);mark(2);mark(4);mark(7);mark(11); } // Major 9
else if (id == 79) { mark(0);mark(2);mark(3);mark(7);mark(10); } // Minor 9
else if (id == 80) { mark(0);mark(1);mark(4);mark(7);mark(10); } // Dominant 7b9
else if (id == 81) { mark(0);mark(3);mark(4);mark(7);mark(10); } // Dominant 7#9
else if (id == 82) { mark(0);mark(2);mark(4);mark(7);mark(9);mark(10); } // Dominant 13
else if (id == 83) { mark(0);mark(5);mark(10); } // Quartal (stacked 4ths)
}
// ---- ROOT NOTE ------------------------------------------------------
export var rootNote = 0; // 0 = E
export function isInScale(note) {
var diff = (note - rootNote + 12) % 12;
return selectedScale[diff];
}
// --- Colour reference table ------------------------------------------
// 0.00=red
// 0.08=orange
// 0.13=yellow
// 0.3=green
// 0.42=cyan
// 0.50=sky
// 0.58=blue
// 0.67=indigo
// 0.75=violet
// 0.83=magenta
// 0.92=pink
// 1.00=red
// ---- MODE 0: SCALE colours (root vs other) --------------------------
export var rootColorHue = 0.0;
export var rootColorSat = 1.0;
export var rootBrightness = 0.6;
export var otherColorHue = 0.06;
export var otherColorSat = 1.0;
export var otherBrightness = 0.4;
// ---- MODE 1: INTERVAL colour language -------------------------------
// Indexed by interval above root. A constant colour per interval; only
// shows where the scale lights it. Edit to taste.
// idx: 0=P1/8ve 1=m2 2=M2 3=m3 4=M3 5=P4 6=TT 7=P5 8=m6 9=M6 10=m7 11=M7
// 0.00=red 0.08=orange 0.13=yellow 0.3=green 0.42=cyan 0.50=sky 0.58=blue 0.67=indigo 0.75=violet 0.83=magenta 0.92=pink 1.00=red
var intervalHue = [0.00, 0.08, 0.13, 0.25, 0.33, 0.45, 0.55, 0.66, 0.75, 0.83, 0.90, 0.96];
var intervalSat = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
var intervalBright = [0.70, 0.40, 0.40, 0.50, 0.50, 0.50, 0.40, 0.60, 0.40, 0.50, 0.40, 0.40];
// ---- MODE 2: NOTE colour language -----------------------------------
// Indexed by absolute note (E-based). A constant colour per pitch.
// idx: 0=E 1=F 2=F# 3=G 4=G# 5=A 6=A# 7=B 8=C 9=C# 10=D 11=D#
// 0.00=red 0.08=orange 0.13=yellow 0.3=green 0.42=cyan 0.50=sky 0.58=blue 0.67=indigo 0.75=violet 0.83=magenta 0.92=pink 1.00=red
var noteHue = [0.33, 0.75, 0.13, 0.02, 0.79, 0.90, 0.75, 0.7, 0.0, 0.62, 0.5, 0.35];
var noteSat = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
var noteBright = [0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50, 0.50];
// ---- INTERVAL BRIGHTNESS FLAG ---------------------------------------
// When useIntervalBrightness = 1, modes 0 and 2 borrow brightness from
// intervalBright[diff] instead of their own brightness values.
// Colours and saturation still come from the active mode.
export var useIntervalBrightness = 0;
// ---- DECODE: physical LED index -> (gString, gFret) -----------------
var gString = 0;
var gFret = 0;
function decode(index) {
var i = reverseChain ? (NUM_USED - 1 - index) : index;
var pos = 0;
if (wiringMode == 0) {
gString = floor(i / NUM_FRETS);
pos = i % NUM_FRETS;
gFret = (gString % 2 == 0) ? pos : (NUM_FRETS - 1 - pos);
} else {
gFret = floor(i / NUM_STRINGS);
pos = i % NUM_STRINGS;
gString = (gFret % 2 == 0) ? pos : (NUM_STRINGS - 1 - pos);
}
}
// ---- BEFORE RENDER --------------------------------------------------
export function beforeRender(delta) {
setScale(scaleId);
}
// ---- RENDER ---------------------------------------------------------
export function render(index) {
if (index >= NUM_USED) { hsv(0, 0, 0); return; }
decode(index);
var note = (openNotes[gString] + gFret) % 12;
if (isInScale(note)) {
var diff = (note - rootNote + 12) % 12;
var bright = 0;
if (mode == 1) {
hsv(intervalHue[diff], intervalSat[diff], intervalBright[diff]);
} else if (mode == 2) {
bright = useIntervalBrightness ? intervalBright[diff] : noteBright[note];
hsv(noteHue[note], noteSat[note], bright);
} else {
if (diff == 0) {
bright = useIntervalBrightness ? intervalBright[0] : rootBrightness;
hsv(rootColorHue, rootColorSat, bright);
} else {
bright = useIntervalBrightness ? intervalBright[diff] : otherBrightness;
hsv(otherColorHue, otherColorSat, bright);
}
}
} else {
hsv(0, 0, 0);
}
}



