Creates algorithmic music composition systems using procedural generation, Markov chains, L-systems, and neural approaches for ambient, adaptive, and experimental music.
import random
from collections import defaultdict
class MarkovMelodyGenerator:
"""Generate melodies using Markov chains"""
def __init__(self, order=2):
self.order = order
self.transitions = defaultdict(list)
def train(self, melodies):
"""Learn from existing melodies (lists of MIDI notes)"""
for melody in melodies:
for i in range(len(melody) - self.order):
state = tuple(melody[i:i + self.order])
next_note = melody[i + self.order]
self.transitions[state].append(next_note)
def generate(self, length, seed=None):
"""Generate a new melody"""
if seed is None:
seed = random.choice(list(self.transitions.keys()))
melody = list(seed)
for _ in range(length - self.order):
state = tuple(melody[-self.order:])
if state in self.transitions:
next_note = random.choice(self.transitions[state])
else:
# Fallback: random from all possible next notes
next_note = random.choice(
[n for notes in self.transitions.values() for n in notes]
)
melody.append(next_note)
return melody
Weighted Random Selection
def weighted_choice(options, weights):
"""Select with custom probability distribution"""
total = sum(weights)
r = random.uniform(0, total)
cumulative = 0
for option, weight in zip(options, weights):
cumulative += weight
if r <= cumulative:
return option
return options[-1]
# Scale degree probabilities (tendency tones)
scale_weights = {
1: 0.20, # Tonic - stable, common
2: 0.10, # Supertonic - passing
3: 0.15, # Mediant - stable
4: 0.10, # Subdominant - tendency to 3
5: 0.20, # Dominant - stable
6: 0.10, # Submediant - relative major/minor
7: 0.05, # Leading tone - strong tendency
8: 0.10 # Octave
}
def generate_scale_melody(length, key='C', scale='major'):
degrees = list(scale_weights.keys())
weights = list(scale_weights.values())
melody = [weighted_choice(degrees, weights) for _ in range(length)]
return [degree_to_midi(d, key, scale) for d in melody]
Grammar-Based Generation
L-System for Musical Structure
class MusicalLSystem:
"""L-system for generating musical phrases"""
def __init__(self):
self.rules = {
'A': 'AB', # Antecedent expands to Antecedent + Bridge
'B': 'CA', # Bridge expands to Consequent + Antecedent
'C': 'DC', # Consequent expands to Development + Consequent
'D': 'A' # Development returns to Antecedent
}
self.interpretations = {
'A': self._phrase_a,
'B': self._phrase_b,
'C': self._phrase_c,
'D': self._phrase_d
}
def generate_structure(self, axiom='A', iterations=4):
"""Generate formal structure"""
result = axiom
for _ in range(iterations):
result = ''.join(self.rules.get(c, c) for c in result)
return result
def realize(self, structure):
"""Convert structure to musical phrases"""
phrases = []
for symbol in structure:
if symbol in self.interpretations:
phrases.append(self.interpretations[symbol]())
return phrases
def _phrase_a(self):
# Antecedent: tension-building phrase
return generate_phrase(contour='ascending', cadence='half')
def _phrase_b(self):
# Bridge: transitional material
return generate_phrase(contour='static', cadence='deceptive')
def _phrase_c(self):
# Consequent: resolution phrase
return generate_phrase(contour='descending', cadence='authentic')
def _phrase_d(self):
# Development: variation material
return generate_phrase(contour='varied', cadence='half')
Generative Grammar for Rhythm
class RhythmGrammar:
"""Context-free grammar for rhythm generation"""
def __init__(self):
# Non-terminal symbols with production rules
self.rules = {
'MEASURE': [
['HALF', 'HALF'],
['QUARTER', 'QUARTER', 'QUARTER', 'QUARTER'],
['DOTTED_HALF', 'QUARTER'],
['BEAT', 'BEAT', 'BEAT', 'BEAT']
],
'HALF': [
['QUARTER', 'QUARTER'],
['half']
],
'QUARTER': [
['EIGHTH', 'EIGHTH'],
['quarter'],
['SIXTEENTH', 'SIXTEENTH', 'EIGHTH']
],
'BEAT': [
['quarter'],
['EIGHTH', 'EIGHTH'],
['TRIPLET']
],
'EIGHTH': [
['eighth'],
['SIXTEENTH', 'SIXTEENTH']
],
'TRIPLET': [
['triplet', 'triplet', 'triplet']
],
'SIXTEENTH': [
['sixteenth']
]
}
def generate(self, symbol='MEASURE'):
"""Recursively expand grammar"""
if symbol not in self.rules:
return [symbol] # Terminal symbol
# Choose random production rule
production = random.choice(self.rules[symbol])
result = []
for s in production:
result.extend(self.generate(s))
return result
Constraint-Based Harmony
Voice Leading Rules
class HarmonyGenerator:
"""Generate chord progressions with voice leading constraints"""
def __init__(self, key='C', mode='major'):
self.key = key
self.mode = mode
self.chord_vocabulary = self._build_chords()
def generate_progression(self, length=8):
"""Generate progression satisfying constraints"""
progression = [self._tonic_chord()] # Start on tonic
for _ in range(length - 2):
current = progression[-1]
candidates = self._valid_next_chords(current)
next_chord = self._select_chord(candidates, current)
progression.append(next_chord)
# End with cadence
progression.append(self._dominant_chord())
progression.append(self._tonic_chord())
return progression
def _valid_next_chords(self, current):
"""Filter chords by voice leading constraints"""
candidates = []
for chord in self.chord_vocabulary:
if self._check_voice_leading(current, chord):
candidates.append(chord)
return candidates
def _check_voice_leading(self, chord1, chord2):
"""Check voice leading rules"""
# No parallel fifths
if self._has_parallel_fifths(chord1, chord2):
return False
# No parallel octaves
if self._has_parallel_octaves(chord1, chord2):
return False
# Resolve tendency tones
if not self._resolves_tendencies(chord1, chord2):
return False
# Limit voice movement
if self._excessive_movement(chord1, chord2):
return False
return True