Generative Techniques in MML¶
A Step-by-Step Tutorial¶
Creating music with procedural and algorithmic techniques is a powerful way to explore new sonic possibilities while minimizing repetitive coding. This tutorial walks you through increasingly sophisticated generative music techniques using MML's random() expressions, loops, and other tools.
What is Generative Music?¶
Generative music uses algorithms and randomness to create variations of musical patterns automatically. Rather than manually writing every note, you define rules and let the system generate them. This is useful for:
- Humanization: Adding natural variations to robotic MIDI patterns
- Exploration: Discovering new melodies and rhythms through algorithmic variation
- Efficiency: Creating complex polyrhythmic and layered patterns with minimal code
- Live Performance: Generating fresh variations in real-time during performances
- Ambient Music: Creating evolving soundscapes that never repeat exactly
The key insight is that controlled randomness within boundaries creates musicality, while pure randomness creates chaos. Throughout this tutorial, we'll learn to control randomness to stay musical.
Lesson 1: Your First Random Pattern - Simple Velocity Variation¶
Learning Objective: Understand how to use the random() function to add human-like dynamics to static patterns.
The Problem¶
When you write the same velocity over and over, MIDI sounds robotic:
[00:00.000]
- note_on 1.C4 80 0.5b
[00:00.480]
- note_on 1.D4 80 0.5b
[00:00.960]
- note_on 1.E4 80 0.5b
All notes have identical velocity (80), which sounds mechanical. Real musicians vary dynamics subtly.
The Solution¶
Use random(min, max) to generate values within a range:
---
title: "Lesson 1: Velocity Humanization"
tempo: 120
time_signature: [4, 4]
ppq: 480
---
# Basic robotic pattern
[00:00.000]
- marker "Robotic - No Variation"
- note_on 1.C4 80 0.5b
[00:00.480]
- note_on 1.D4 80 0.5b
[00:00.960]
- note_on 1.E4 80 0.5b
[00:01.440]
- note_on 1.F4 80 0.5b
# Humanized with velocity variation
[00:02.000]
- marker "Humanized - Velocity Variation"
- note_on 1.C4 random(75,85) 0.5b
[00:02.480]
- note_on 1.D4 random(75,85) 0.5b
[00:02.960]
- note_on 1.E4 random(75,85) 0.5b
[00:03.440]
- note_on 1.F4 random(75,85) 0.5b
# Larger variation range (more expressive)
[00:04.000]
- marker "Expressive - Wide Range"
- note_on 1.C4 random(60,100) 0.5b
[00:04.480]
- note_on 1.D4 random(60,100) 0.5b
[00:04.960]
- note_on 1.E4 random(60,100) 0.5b
[00:05.440]
- note_on 1.F4 random(60,100) 0.5b
How It Works¶
The random() function takes two parameters:
- Minimum value (first parameter)
- Maximum value (second parameter)
Each time a note with random() velocity is generated, it picks a random integer between those bounds. The key is choosing the right range:
- Subtle variation (±5):
random(75,85)- Professional, almost imperceptible - Moderate variation (±20):
random(60,100)- Natural, expressive - Wide variation (±30+):
random(50,110)- Very dynamic, less polished
Pro Tips¶
-
Preserve musical intent: If you want a downbeat accent, make it louder on average:
-
Keep ranges within 0-127: MIDI velocities range from 0 (silent) to 127 (loudest)
-
Combine with note durations: Variable velocity + variable timing creates extra humanity
Try It Yourself¶
Create a 4-bar pattern where:
- Notes on strong beats (1, 3) use random(85,100)
- Notes on weak beats (2, 4) use random(60,75)
- All notes are quarter notes (1b duration)
Save as lesson1_humanization.mmd and compile it to hear the difference!
Lesson 2: Random Note Selection - Creating Melodies¶
Learning Objective: Use randomness to generate melodic variations from a defined note range.
Building on Lesson 1¶
In Lesson 1, we varied velocity. Now we'll also vary the pitch itself while keeping timing fixed.
The Concept¶
Instead of playing the same note repeatedly, pick randomly from a range of valid notes:
---
title: "Lesson 2: Random Melody Generation"
tempo: 120
time_signature: [4, 4]
ppq: 480
---
# Static melody (boring)
[00:00.000]
- marker "Static Melody"
- note_on 1.C4 80 0.5b
[00:00.480]
- note_on 1.C4 80 0.5b
[00:00.960]
- note_on 1.C4 80 0.5b
[00:01.440]
- note_on 1.C4 80 0.5b
# Randomized within C major pentatonic scale
[00:02.000]
- marker "Random Pentatonic"
- note_on 1.random(C4,G4) random(75,90) 0.5b
[00:02.480]
- note_on 1.random(C4,G4) random(75,90) 0.5b
[00:02.960]
- note_on 1.random(C4,G4) random(75,90) 0.5b
[00:03.440]
- note_on 1.random(C4,G4) random(75,90) 0.5b
# Wider melodic range (2 octaves)
[00:04.000]
- marker "Wide Range Random"
- note_on 1.random(C4,C6) random(60,95) 0.5b
[00:04.480]
- note_on 1.random(C4,C6) random(60,95) 0.5b
[00:04.960]
- note_on 1.random(C4,C6) random(60,95) 0.5b
[00:05.440]
- note_on 1.random(C4,C6) random(60,95) 0.5b
# Using MIDI note numbers instead of note names
[00:06.000]
- marker "MIDI Numbers"
- note_on 1.random(60,84) random(75,90) 0.5b
[00:06.480]
- note_on 1.random(60,84) random(75,90) 0.5b
[00:06.960]
- note_on 1.random(60,84) random(75,90) 0.5b
[00:07.440]
- note_on 1.random(60,84) random(75,90) 0.5b
Understanding Ranges¶
When you write random(C4,G4), MMD picks a random note between C4 (MIDI 60) and G4 (MIDI 67).
Common useful ranges:
- Pentatonic scale (5 notes, pleasing): random(C4,G4) includes all white keys in that octave
- One octave: random(C4,C5)
- Two octaves: random(C4,C6)
- Narrow range (3-4 notes): random(C4,D#4)
Musical Scales and Random Selection¶
The random() function picks all notes in the range, including sharps/flats. If you want a specific scale (like C major without accidentals), you need to be selective:
# This includes ALL notes from C4 to G4 (including C#, D#, F#, etc.)
- note_on 1.random(C4,G4) 80 0.5b
# For true C major pentatonic (C,D,E,G,A), use @define with specific notes
@define PENT_NOTES ["C4", "D4", "E4", "G4", "A4"]
However, including all notes sometimes creates beautiful microtonal effects!
Try It Yourself¶
Create a 16-bar ambient drone where:
1. Each bar has 4 quarter notes
2. Notes are randomly selected from a range (e.g., C3 to G3)
3. Each note has humanized velocity: random(50,70)
4. At bars 9-16, expand the range upward: C3 to G4
Save as lesson2_random_melody.mmd and listen to how the melody "evolves" over time!
Lesson 3: CC Automation - Filter Sweeps¶
Learning Objective: Use randomness to create evolving parameter automation (like filter cutoff, resonance, reverb).
Beyond Note Randomness¶
In Lessons 1 & 2, we randomized notes and velocity. Now let's randomize effect parameters using Control Change (CC) messages combined with loops.
The Concept¶
Control Change (CC) messages automate synth parameters. CC#74 is commonly filter cutoff. Here's how to create an evolving filter sweep:
---
title: "Lesson 3: Random CC Automation"
tempo: 120
time_signature: [4, 4]
ppq: 480
---
# Static filter (no movement)
[00:00.000]
- marker "Static Filter"
- cc 1.74.64
- @loop 8 times every 1b
- note_on 1.C4 80 0.5b
- @end
# Humanized filter with subtle variation
[00:08.000]
- marker "Subtle Filter Variation"
@loop 8 times at [00:08.000] every 0.5b
- cc 1.74.random(62,66)
@end
# Large sweep (evolving filter)
[00:12.000]
- marker "Large Filter Sweep"
@loop 32 times at [00:12.000] every 0.125b
- cc 1.74.random(40,100)
@end
# Controlled sweep direction (closing filter)
[00:20.000]
- marker "Controlled Sweep"
- cc 1.74.100 # Start high
- note_on 1.C4 80 2b
[00:22.000]
- cc 1.74.80
[00:23.000]
- cc 1.74.60
[00:24.000]
- cc 1.74.40 # End low
Common CC Parameters¶
- CC#1: Modulation wheel (0-127)
- CC#7: Volume (0-127)
- CC#10: Pan (0=left, 64=center, 127=right)
- CC#11: Expression (0-127)
- CC#74: Filter cutoff on many synths (0-127)
- CC#91: Reverb amount (0-127)
- CC#93: Chorus amount (0-127)
Combining Notes + CC Randomness¶
The most musical approach combines random notes with evolving CC:
[00:00.000]
- marker "Pad with Evolving Filter"
- note_on 1.G3 random(60,75) 4b # Hold note for 4 beats
@loop 16 times at [00:00.000] every 0.25b
- cc 1.74.random(50,100) # Filter sweeps during note
@end
Try It Yourself¶
Create a 16-second pad where:
1. One note plays for the full 16 seconds: note_on 1.D3 random(70,80) 16s
2. Every 250ms, send a random CC#74 value: cc 1.74.random(40,100)
3. At the 8-second mark, change the range: cc 1.74.random(80,127) (open filter)
The effect should be an evolving, breathing pad!
Lesson 4: Combining Techniques - Layered Randomization¶
Learning Objective: Blend multiple random parameters to create complex, evolving musical structures.
Putting It All Together¶
Now we'll combine lessons 1-3: random velocity, random notes, and random CC values in the same composition.
Complex Example: Algorithmic Ambient Piece¶
---
title: "Lesson 4: Layered Randomization"
tempo: 90
time_signature: [4, 4]
ppq: 480
---
# Layer 1: Bass drone
[00:00.000]
- marker "Layer 1: Bass"
- note_on 1.G2 random(50,65) 8b # 8-beat bass note
@loop 32 times at [00:00.000] every 0.25b
- cc 1.7.random(100,110) # Volume automation (subtle)
@end
# Layer 2: Melodic counterpoint (different channel)
[00:00.000]
- marker "Layer 2: Melody"
@loop 16 times at [00:00.000] every 0.5b
- note_on 2.random(D4,A4) random(70,85) 0.4b
@end
# Layer 3: Filter on melody channel
[00:00.000]
@loop 64 times at [00:00.000] every 0.125b
- cc 2.74.random(40,100) # Rapid filter movement
@end
# Section B: More movement
[00:32.000]
- marker "Section B: Intensify"
# Faster melodic movement
[00:32.000]
@loop 32 times at [00:32.000] every 0.25b
- note_on 2.random(C4,G5) random(65,100) 0.2b
@end
# Faster filter modulation
[00:32.000]
@loop 128 times at [00:32.000] every 0.0625b
- cc 2.74.random(30,110)
@end
# Bass evolves with more energy
[00:32.000]
- note_on 1.F#2 random(60,80) 8b
@loop 64 times at [00:32.000] every 0.125b
- cc 1.7.random(90,127) # More dynamic volume
@end
# Coda: Return to stillness
[00:64.000]
- marker "Coda"
- note_on 1.G2 random(40,60) 16b
- note_on 2.G4 random(50,70) 16b
@loop 128 times at [00:64.000] every 0.125b
- cc 2.74.random(70,80) # Narrow filter (less movement)
@end
Design Principles for Layered Randomization¶
- Use different channels for different instruments (channel 1 = bass, channel 2 = melody)
- Vary the randomness ranges by section (quiet intro, intense middle, quiet outro)
- Use different
@loopintervals for each layer (bass every 0.25b, melody every 0.5b) - Add markers to structure your composition clearly
- Change ranges over time to create narrative (quieter → louder → quieter)
Try It Yourself¶
Create a 60-second piece with: 1. Bass layer (channel 1): 2-beat notes on G2, with volume automation every 0.5b 2. Chord layer (channel 3): 1-beat notes randomly chosen from G3/B3/D4, velocity 70-85 3. Lead layer (channel 2): 0.5-beat rapid melodies from D4-A5, with filter sweep via CC#74
Make sure each layer has a different loop frequency to create natural variation!
Lesson 5: Integration with Modulation - Advanced Techniques¶
Learning Objective: Understand how randomness integrates with MML's advanced features like @sweep, @loop offsets, and conditional expressions.
Beyond Basic Random()¶
The random() function is powerful, but MMD has other advanced tools that work beautifully with generative techniques.
@sweep + Randomization¶
The @sweep directive creates ramps. Combine it with randomization:
---
title: "Lesson 5: Advanced Modulation"
tempo: 120
time_signature: [4, 4]
ppq: 480
---
# Sweep creates smooth parameter changes
[00:00.000]
- marker "Controlled Sweep"
@sweep cc 1.74 from 30 to 100 over 8b
@end
# But with random note selection during the sweep
[00:00.000]
@loop 16 times at [00:00.000] every 0.5b
- note_on 1.random(C3,G4) random(70,90) 0.4b
@end
# Pitch bend sweep with random timing
[00:08.000]
- marker "Pitch Bend Modulation"
- note_on 1.C4 80 4b
@sweep pitch_bend 1 from -2000 to 2000 over 4b
@end
# During pitch bend, CC automation happens independently
[00:08.000]
@loop 32 times at [00:08.000] every 0.125b
- cc 1.1.random(0,127) # Mod wheel random movement
@end
Loop Offsets for Phasing Effects¶
Use loop counts and offsets to create phasing:
[00:00.000]
# Melody loop
@loop 16 times at [00:00.000] every 1b
- note_on 1.random(C4,G4) random(70,90) 0.5b
@end
# Same melody on channel 2, but offset by 0.5b
[00:00.250] # Half beat offset
@loop 16 times at [00:00.250] every 1b
- note_on 2.random(C4,G4) random(70,90) 0.5b
@end
This creates a beautiful "phase" effect where the two melodies align and diverge.
Variables in Generative Context¶
While random() doesn't require variables, using @define lets you control global parameters:
@define MIN_VEL 50
@define MAX_VEL 100
@define MIN_NOTE C3
@define MAX_NOTE G5
@define SWEEP_TIME 8b
[00:00.000]
- marker "Using Variables"
@loop 16 times at [00:00.000] every 0.5b
- note_on 1.random(${MIN_NOTE},${MAX_NOTE}) \
random(${MIN_VEL},${MAX_VEL}) 0.4b
@end
# Easy to adjust everything from the top!
Try It Yourself¶
Create a "pulsing pad" 30-second piece:
1. Use @sweep to gradually open a filter from 40 to 100 over 30 seconds on channel 1
2. During the 30 seconds, play random notes from C3-G4 with @loop every 0.5b
3. On channel 2, play the same notes but offset by 0.25b for a phasing effect
4. Add random volume automation (CC#7) on channel 1: cc 1.7.random(80,120)
This combines sweeps + randomness + phasing for a complex, evolving texture!
Practical Tips & Best Practices¶
Musicality Through Constraint¶
The secret to generative music is constraint. Pure randomness sounds chaotic. Musical randomness has rules:
✓ DO: - Use note ranges that are musically coherent (pentatonic, modal, diatonic) - Keep velocity ranges within 15-20 points of your "average" velocity - Use loops with musical subdivisions (beats, half-beats, eighth-notes) - Add markers to label sections and maintain narrative
✗ DON'T: - Randomize every single parameter (too chaotic) - Use velocity ranges wider than 0-127 - Loop with non-musical intervals (0.37b is awkward) - Create 10 hours of automation without structure
Performance Considerations¶
- MIDI limitations: Rapid CC changes (every 0.06b = 16th notes at 120 BPM) can overwhelm some devices
- CPU usage: Very long loops with many events can slow compilation
- Playback smoothness: Filter sweeps at 1ms intervals are fine; faster than that may cause jitter
Real-World Application: Humanizing Drum Machines¶
Here's how to take a rigid drum pattern and make it sound human:
@define KICK_VEL_MIN 95
@define KICK_VEL_MAX 115
@define SNARE_VEL_MIN 100
@define SNARE_VEL_MAX 125
@define HAT_VEL_MIN 40
@define HAT_VEL_MAX 60
[00:00.000]
- marker "Humanized Drums"
# 4-bar drum loop
@loop 4 times every 4b
# Bar: kick on 1,3 with variation
- note_on 10.C2 random(${KICK_VEL_MIN},${KICK_VEL_MAX}) 0.2b
[+0.5b]
- note_on 10.F#2 random(${HAT_VEL_MIN},${HAT_VEL_MAX}) 0.15b
[+0.25b]
- note_on 10.F#2 random(${HAT_VEL_MIN},${HAT_VEL_MAX}) 0.15b
[+0.75b]
- note_on 10.D2 random(${SNARE_VEL_MIN},${SNARE_VEL_MAX}) 0.2b
[+0.5b]
- note_on 10.F#2 random(${HAT_VEL_MIN},${HAT_VEL_MAX}) 0.15b
[+0.25b]
- note_on 10.F#2 random(${HAT_VEL_MIN},${HAT_VEL_MAX}) 0.15b
@end
Next Steps & Further Exploration¶
Recommended Reading¶
- Random Expressions Reference - Complete
random()syntax and limitations - Loops & Patterns Guide - Advanced
@looptechniques - Sweep & Automation Guide - Detailed
@sweepdocumentation
Example Files to Study¶
The examples/05_generative/ directory contains full working examples:
- random_humanization.mmd - Foundational velocity and note randomization (start here!)
- algorithmic_drums.mmd - Professional drum patterns with humanization
- random_cc_automation.mmd - CC sweeps and filter modulation
- generative_ambient.mmd - Complex layered ambient piece using all techniques
Exercises to Try¶
- Humanize a boring pattern: Take any static MIDI pattern and add 2-3 layers of randomization
- Create a 60-second generative piece: Use all 5 lessons in one composition
- Generative drum fills: Create fills that are mostly random but with structured breaks
- Modulation matrix: Randomize 4+ CC parameters simultaneously on different @loop intervals
Advanced Topics (Future Learning)¶
While not covered in this tutorial, MMD also supports:
- Conditional logic (
@if/@elif/@else) - Create branching generative patterns - Custom aliases - Create reusable generative "instrument definitions"
- REPL mode - Interactive generative experimentation (see
mml repl) - Computed values - Mathematical expressions beyond
random()
Troubleshooting¶
"My pattern sounds too random/chaotic"
→ Narrow your ranges. Use random(75,80) instead of random(0,127)
"All my notes are the same octave"
→ Expand your note range. Use random(C3,C6) instead of random(C4,C4)
"CC changes are too jerky"
→ Decrease loop interval. Use every 0.125b instead of every 1b
"My file won't compile"
→ Check that random() is only used in parameter positions (velocity, note, CC value), not timing
→ Ensure minimum < maximum in all random() calls
Summary¶
You've learned to:
- Lesson 1: Add velocity humanization with subtle ranges
- Lesson 2: Generate melodies by randomizing note selection
- Lesson 3: Automate effect parameters with random CC values
- Lesson 4: Layer multiple randomization sources for complex textures
- Lesson 5: Integrate randomization with advanced features like
@sweep
The key principle: Generative music uses randomness within musical constraints to create variation without chaos.
Compile any of the example files and listen to them multiple times - each playback will be slightly different, creating the feeling of a live, breathing performance. This is the power of generative techniques!
Happy creating! 🎵
Last updated: November 2025
See the examples/05_generative/ directory for runnable examples and more advanced techniques.