Skip to content

Instantly share code, notes, and snippets.

@kmorrill
Last active February 2, 2025 16:26
Show Gist options
  • Save kmorrill/121a664160dede43afa06d5373201a27 to your computer and use it in GitHub Desktop.
Save kmorrill/121a664160dede43afa06d5373201a27 to your computer and use it in GitHub Desktop.
Mozaic OP-XY Melody Generator
@Description
Melodic Generator v2.0
Uses four knobs to control melodic generation:
- Melodic Contour: Smooth ↔ Angular intervals
- Rhythm: Steady ↔ Syncopated patterns
- Register: Narrow ↔ Wide pitch range
- Harmony: Sparse ↔ Rich note choices
Adjust any knob to generate a new melody.
@End
@OnLoad
// Initialize layout and labels
ShowLayout 4
SetShortName {MelGen}
LabelKnobs {Melodic Generator}
LabelKnob 0, {Contour}
LabelKnob 1, {Rhythm}
LabelKnob 2, {Register}
LabelKnob 3, {Harmony}
// Initialize musical parameters
SetMetroPPQN 4
PresetScale {Major}
SetRootNote 0 // C
// Default parameters
if Unassigned init
init = YES
baseNote = 60 // Middle C
lastNote = baseNote
currentStep = 0
// Set initial knob values
SetKnobValue 0, 64 // Contour
SetKnobValue 1, 64 // Rhythm
SetKnobValue 2, 64 // Register
SetKnobValue 3, 64 // Harmony
// Initialize arrays for sequence storage
FillArray melodyPitches, -1, 64
FillArray melodyDurations, 4, 64 // Default to quarter notes
endif
Call @GenerateMelody
@End
@OnMetroPulse
if HostRunning
Call @PlayCurrentStep
endif
@End
@PlayCurrentStep
if melodyPitches[currentStep] > 0 // Normal note
// Stop any currently playing note
SendMIDICC 0, 123, 0 // All notes off
// Play new note with its duration
noteToPlay = melodyPitches[currentStep]
noteDuration = melodyDurations[currentStep]
SendMIDINoteOn 0, noteToPlay, 100
SendMIDINoteOff 0, noteToPlay, 0, (QuarterNote/4) * noteDuration
endif
// Skip if it's -1 (rest) or -2 (continuation of previous note)
// Advance sequence
inc currentStep
if currentStep >= 64
currentStep = 0
endif
@End
@OnKnobChange
Log {=== Knob }, LastKnob, { changed to }, GetKnobValue LastKnob
Call @GenerateMelody
@End
@GenerateMelody
// Get current knob values
contourVal = GetKnobValue 0
rhythmVal = GetKnobValue 1
registerVal = GetKnobValue 2
harmonyVal = GetKnobValue 3
Log {=== New Melody Generation ===}
Log {Controls:}
Log { Contour: }, contourVal
Log { Rhythm: }, rhythmVal
Log { Register: }, registerVal
Log { Harmony: }, harmonyVal
currentStep = 0
lastNote = baseNote
step = 0
// Pre-analyze rhythm characteristics
if rhythmVal < 33
Log {Pattern: Steady rhythm}
elseif rhythmVal < 66
Log {Pattern: Mixed rhythm}
else
Log {Pattern: Complex rhythm}
endif
registerRange = Round (TranslateScale registerVal, 0, 127, 3, 24)
Log {Range: }, (NoteName (baseNote - registerRange)), { to }, (NoteName (baseNote + registerRange))
while step < 64
// Determine rhythm first
rhythmValue = TranslateScale rhythmVal, 0, 127, 0, 100
// Duration patterns based on rhythm value
duration = 4 // Default duration
if rhythmValue < 33
// Steady rhythm - mostly quarter notes
duration = 4
restChance = 10
elseif rhythmValue < 66
// Mixed rhythm - eighths and quarters with some rests
durationChoice = Random 0, 100
if durationChoice < 50
duration = 4 // quarter
elseif durationChoice < 80
duration = 2 // eighth
else
duration = 6 // dotted quarter
endif
restChance = 25
else
// Complex rhythm - varied lengths and more rests
durationChoice = Random 0, 100
if durationChoice < 30
duration = 1 // sixteenth
elseif durationChoice < 50
duration = 2 // eighth
elseif durationChoice < 70
duration = 3 // dotted eighth
elseif durationChoice < 85
duration = 4 // quarter
else
duration = 6 // dotted quarter
endif
restChance = 40
endif
// Handle rests - check BEFORE storing the note
if Random 0, 100 < restChance
for i = 0 to duration - 1
if (step + i) < 64
melodyPitches[step + i] = -1
melodyDurations[step + i] = 1
endif
endfor
step = step + duration
Continue
endif
// Determine interval based on contour
// Ensure at least some movement even at low contour values
minStep = 1 // Minimum step size
maxStep = Round (TranslateScale contourVal, 0, 127, 2, 12) // Max interval range
// Generate interval with bias towards smaller intervals at low contour values
if contourVal < 30
interval = Random -2, 2 // Small steps
if interval = 0
interval = 1 // Ensure some movement
endif
elseif contourVal < 70
interval = Random -maxStep, maxStep
// Bias towards smaller intervals
if Abs interval > 3 and Random 0, 100 < 70
interval = interval / 2
endif
else
interval = Random -maxStep, maxStep
// Allow larger jumps more frequently
endif
// Calculate new note
newNote = lastNote + interval
// Apply register constraints
registerRange = Round (TranslateScale registerVal, 0, 127, 3, 24)
rangeMin = baseNote - registerRange
rangeMax = baseNote + registerRange
newNote = Clip newNote, rangeMin, rangeMax
// Quantize to scale based on harmony setting
harmonyThreshold = TranslateScale harmonyVal, 0, 127, 95, 20
if Random 0, 100 > harmonyThreshold
newNote = ScaleQuantize newNote
else
// Add chromatic notes for tension when harmony is high
if Random 0, 100 < 50
newNote = newNote + 1
endif
endif
// Store note and duration
if step < 64
melodyPitches[step] = newNote
// Fill duration slots with -2 to mark them as part of a longer note
for i = 1 to duration - 1
if (step + i) < 64
melodyPitches[step + i] = -2
melodyDurations[step + i] = 0
endif
endfor
melodyDurations[step] = duration
lastNote = newNote
endif
step = step + duration
endwhile
// Log the complete melody
Log {=== Melody Sequence ===}
melodyStep = 0
while melodyStep < 64
if melodyPitches[melodyStep] = -1
Log {Step }, melodyStep, {: Rest (}, melodyDurations[melodyStep], {)}
elseif melodyPitches[melodyStep] = -2
Log {Step }, melodyStep, {: Note continuation}
else
Log {Step }, melodyStep, {: }, (NoteName melodyPitches[melodyStep]), { (}, melodyDurations[melodyStep], {)}
endif
inc melodyStep
endwhile
// Count notes and rests
quarterNotes = 0
eighthNotes = 0
sixteenthNotes = 0
dottedNotes = 0
rests = 0
for i = 0 to 63
if melodyPitches[i] = -1
inc rests
elseif melodyPitches[i] > 0 // Don't count continuations (-2)
if melodyDurations[i] = 4
inc quarterNotes
elseif melodyDurations[i] = 2
inc eighthNotes
elseif melodyDurations[i] = 1
inc sixteenthNotes
else
inc dottedNotes
endif
endif
endfor
Log {=== Pattern Stats ===}
Log {Quarter: }, quarterNotes, { Eighth: }, eighthNotes, { 16th: }, sixteenthNotes, { Dotted: }, dottedNotes
Log {Notes: }, (64 - rests), { Rests: }, rests
Log {==================}
@End
@OnHostStart
currentStep = 0
@End
@OnHostStop
SendMIDICC 0, 123, 0 // All notes off
@End
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment