Adafruit_Learning_System_Gu.../Knobby_Sequencer/scales.py
2025-07-08 19:08:52 -07:00

165 lines
5.5 KiB
Python

# SPDX-FileCopyrightText: John Park for Adafruit 2025 Scales for Knobby MIDI Step Sequencer
# SPDX-License-Identifier: MIT
"""
Optimized Musical Scales Library for MIDI Step Sequencer
Contains single-octave scale patterns that can be expanded across any range
All base scales start from C (MIDI note 0 relative)
"""
# Base scale patterns - single octave starting from C (relative to 0)
BASE_SCALES = {
# Western scales
"pentatonic_major": [0, 2, 4, 7, 9], # C, D, E, G, A
"pentatonic_minor": [0, 3, 5, 7, 10], # C, Eb, F, G, Bb
"major": [0, 2, 4, 5, 7, 9, 11], # C, D, E, F, G, A, B (Ionian)
"dorian": [0, 2, 3, 5, 7, 9, 10], # C, D, Eb, F, G, A, Bb
"phrygian": [0, 1, 3, 5, 7, 8, 10], # C, Db, Eb, F, G, Ab, Bb
"lydian": [0, 2, 4, 6, 7, 9, 11], # C, D, E, F#, G, A, B
"mixolydian": [0, 2, 4, 5, 7, 9, 10], # C, D, E, F, G, A, Bb
"minor": [0, 2, 3, 5, 7, 8, 10], # C, D, Eb, F, G, Ab, Bb (Aeolian)
"locrian": [0, 1, 3, 5, 6, 8, 10], # C, Db, Eb, F, Gb, Ab, Bb
"harmonic_minor": [0, 2, 3, 5, 7, 8, 11], # C, D, Eb, F, G, Ab, B
"melodic_minor": [0, 2, 3, 5, 7, 9, 11], # C, D, Eb, F, G, A, B
"blues": [0, 3, 5, 6, 7, 10], # C, Eb, F, Gb, G, Bb
"chromatic": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], # All 12 notes
# Middle Eastern Maqams (approximated in 12-TET)
"maqam_rast": [0, 2, 4, 5, 7, 9, 10, 11], # C, D, E, F, G, A, Bb, B
"maqam_hijaz": [0, 1, 4, 5, 7, 8, 10], # C, Db, E, F, G, Ab, Bb
"maqam_bayati": [0, 1, 3, 5, 7, 8, 10], # C, Db, Eb, F, G, Ab, Bb
"maqam_saba": [0, 1, 2, 5, 7, 8, 10], # C, Db, D, F, G, Ab, Bb
"maqam_kurd": [0, 1, 3, 5, 7, 8, 10], # C, Db, Eb, F, G, Ab, Bb
# Indonesian Gamelan (approximated in 12-TET)
"slendro": [0, 2, 5, 7, 10], # C, D, F, G, Bb
"pelog_lima": [0, 1, 4, 7, 10], # C, Db, E, G, Bb
"pelog_tujuh": [0, 1, 3, 6, 7, 10, 11], # C, Db, Eb, F#, G, Bb, B
# Japanese Scales
"hirajoshi": [0, 2, 3, 7, 8], # C, D, Eb, G, Ab
"in_scale": [0, 1, 5, 7, 8], # C, Db, F, G, Ab
"yo_scale": [0, 2, 5, 7, 9], # C, D, F, G, A
# Indian Ragas (simplified 12-TET approximations)
"raga_bhairav": [0, 1, 4, 5, 7, 8, 11], # C, Db, E, F, G, Ab, B
"raga_yaman": [0, 2, 4, 6, 7, 9, 11], # C, D, E, F#, G, A, B
"raga_kafi": [0, 2, 3, 5, 7, 9, 10], # C, D, Eb, F, G, A, Bb
# Chinese Scales
"chinese_pentatonic": [0, 2, 4, 7, 9], # C, D, E, G, A
# African-influenced scales
"african_pentatonic": [0, 3, 5, 7, 10], # C, Eb, F, G, Bb
# Eastern European
"ee_scale": [0, 2, 3, 6, 7, 8, 11], # C, D, Eb, F#, G, Ab, B (Hungarian/Romani)
# Celtic
"irish_scale": [0, 2, 4, 5, 7, 9, 10] # C, D, E, F, G, A, Bb
}
def generate_scale(scale_name, start_note=24, end_note=95):
"""
Generate a full-range scale from a base pattern
Args:
scale_name (str): Name of the scale to generate
start_note (int): Starting MIDI note (default 24 = C1)
end_note (int): Ending MIDI note (default 95 = B6)
Returns:
list: List of MIDI note numbers, or None if scale not found
"""
if scale_name not in BASE_SCALES:
return None
pattern = BASE_SCALES[scale_name]
notes = []
# Start from the octave that contains or is below start_note
start_octave = (start_note // 12) * 12
# Generate notes across all octaves in range
octave = start_octave
while octave <= end_note:
for interval in pattern:
note = octave + interval
if start_note <= note <= end_note:
notes.append(note)
octave += 12
return notes
def get_scale(scale_name, start_note=24, end_note=95):
"""
Get a scale by name with specified range
Args:
scale_name (str): Name of the scale to retrieve
start_note (int): Starting MIDI note (default 24 = C1)
end_note (int): Ending MIDI note (default 95 = B6)
Returns:
list: List of MIDI note numbers, or None if scale not found
"""
return generate_scale(scale_name, start_note, end_note)
def list_scales():
"""
Get a list of all available scale names
Returns:
list: List of scale names
"""
return sorted(BASE_SCALES.keys())
def get_scale_pattern(scale_name):
"""
Get the base pattern for a scale (single octave, relative to 0)
Args:
scale_name (str): Name of the scale
Returns:
list: Base pattern or None if not found
"""
return BASE_SCALES.get(scale_name)
def get_scale_info():
"""
Get information about all scales organized by category
Returns:
dict: Dictionary with scale categories and descriptions
"""
return {
"Western": [
"pentatonic_major", "pentatonic_minor", "major", "dorian", "phrygian",
"lydian", "mixolydian", "minor", "locrian", "harmonic_minor",
"melodic_minor", "blues", "chromatic"
],
"Middle Eastern": [
"maqam_rast", "maqam_hijaz", "maqam_bayati", "maqam_saba", "maqam_kurd"
],
"Indonesian": [
"slendro", "pelog_lima", "pelog_tujuh"
],
"Japanese": [
"hirajoshi", "in_scale", "yo_scale"
],
"Indian": [
"raga_bhairav", "raga_yaman", "raga_kafi"
],
"Chinese": [
"chinese_pentatonic"
],
"African": [
"african_pentatonic"
],
"Eastern European": [
"ee_scale"
],
"Celtic": [
"irish_scale"
]
}