Skip to content

Generators API Reference

The audiomancer.generators module provides pattern generation and synth evolution functionality.

Overview

generators

Pattern generation and evolution for audiomancer.

Provides interfaces for generating patterns with Magenta and evolving them through mutation and crossover.

Synth Evolution: - generate_synth: Generate new SynthDefs from templates or descriptions - mutate_synth: Mutate existing synths with genetic operations - breed_synths: Crossover two synths to create hybrids - Lineage tracking: Track evolutionary history and ratings

Example

Pattern generation:

from audiomancer.generators import PatternGenerator, EvolutionEngine generator = PatternGenerator() pattern = generator.generate_drums(style="techno", bars=4, bpm=125) engine = EvolutionEngine() mutated = engine.mutate_pattern(pattern.id, amount=0.3)

Synth evolution:

from audiomancer.generators import generate_synth, mutate_synth synth = generate_synth("acid bass with filter sweep", category="bass") variant = mutate_synth(synth, amount=0.5, seed=42) print(variant.mutation_log) ['Saw → Pulse', 'Added LFO modulation to cutoff']

__all__ = ['EvolutionEngine', 'PatternGenerator', 'PatternMetadata', 'SynthLineage', 'generate_synth', 'mutate_synth', 'breed_synths', 'GeneratedSynth', 'LineageTracker', 'SynthRecord', 'record_synth', 'rate_synth', 'get_synth_lineage', 'get_top_rated', 'get_generation_stats'] module-attribute

EvolutionEngine

Bases: Protocol

Interface for evolving patterns and synths through mutation and crossover.

Implements genetic algorithm-style evolution with lineage tracking.

crossover_patterns(pattern_id_1: str, pattern_id_2: str, seed: Optional[int] = None) -> PatternMetadata

Crossover two patterns to create hybrid.

Algorithm: 1. Split each pattern at random bar boundary 2. Combine first half of pattern 1 with second half of pattern 2 3. Quantize and adjust timing to maintain coherence

Parameters:

Name Type Description Default
pattern_id_1 str

First parent pattern

required
pattern_id_2 str

Second parent pattern

required
seed Optional[int]

Random seed for deterministic testing

None

Returns:

Type Description
PatternMetadata

New hybrid pattern with both parents in lineage

Raises:

Type Description
PatternNotFoundError

If either pattern not found

IncompatiblePatternsError

If patterns have different time signatures

Example

engine = EvolutionEngine() hybrid = engine.crossover_patterns( ... "ptrn_abc123", ... "ptrn_def456", ... seed=42, ... ) hybrid['parent_ids']["ptrn_abc123", "ptrn_def456"] hybrid['generation_method'] "crossover"

evolve_population(population: list[str], generations: int = 5, mutation_rate: float = 0.3, crossover_rate: float = 0.2, seed: Optional[int] = None) -> list[PatternMetadata]

Evolve a population of patterns over multiple generations.

Genetic algorithm: 1. Rank population by fitness (user ratings) 2. Select top 50% as parents 3. Mutate parents (mutation_rate probability) 4. Crossover random parent pairs (crossover_rate probability) 5. Repeat for N generations

Parameters:

Name Type Description Default
population list[str]

List of pattern IDs to evolve

required
generations int

Number of evolution cycles

5
mutation_rate float

Probability of mutation (0.0-1.0)

0.3
crossover_rate float

Probability of crossover (0.0-1.0)

0.2
seed Optional[int]

Random seed for deterministic testing

None

Returns:

Type Description
list[PatternMetadata]

Final population (patterns from all generations)

Example

initial = ["ptrn_1", "ptrn_2", "ptrn_3", "ptrn_4"] final_pop = engine.evolve_population( ... population=initial, ... generations=5, ... mutation_rate=0.3, ... crossover_rate=0.2, ... seed=42, ... ) len(final_pop) > len(initial) True

get_lineage(item_id: str) -> list[str]

Get full ancestry chain for pattern or synth.

Recursively traces parent_ids to root.

Parameters:

Name Type Description Default
item_id str

Pattern or synth ID

required

Returns:

Type Description
list[str]

List of ancestor IDs from oldest to newest

Example
Pattern with 3 generations:
ptrn_root → ptrn_gen1 → ptrn_gen2 → ptrn_gen3

lineage = engine.get_lineage("ptrn_gen3") lineage ["ptrn_root", "ptrn_gen1", "ptrn_gen2", "ptrn_gen3"]

mutate_pattern(pattern_id: str, amount: float = 0.3, seed: Optional[int] = None) -> PatternMetadata

Mutate pattern with deterministic randomness.

Mutation types (probability based on amount): 1. Shift timing: ±50ms per note (amount * 0.3 probability) 2. Swap notes: exchange two notes (amount * 0.2) 3. Change velocity: ±20% (amount * 0.3) 4. Add note: insert on empty step (amount * 0.1) 5. Remove note: delete random note (amount * 0.1)

Parameters:

Name Type Description Default
pattern_id str

Pattern to mutate

required
amount float

Mutation strength (0.0-1.0, higher = more drastic)

0.3
seed Optional[int]

Random seed for deterministic testing

None

Returns:

Type Description
PatternMetadata

New mutated pattern with lineage tracking

Raises:

Type Description
PatternNotFoundError

If pattern_id does not exist

Example

engine = EvolutionEngine() original = pattern_store.get("ptrn_abc123") mutated = engine.mutate_pattern( ... "ptrn_abc123", ... amount=0.5, ... seed=42, # Deterministic for testing ... ) mutated['parent_ids']["ptrn_abc123"] mutated['mutation_amount'] 0.5 mutated['generation_method'] "mutated"

mutate_synth(synth_id: str, amount: float = 0.3, seed: Optional[int] = None) -> SynthLineage

Mutate SynthDef parameters.

Mutation types: 1. Adjust control defaults: ±20% of range 2. Swap oscillator types: Saw ↔ Pulse ↔ Sine 3. Add/remove effects: reverb, distortion, delay 4. Adjust envelope times: ±30%

Parameters:

Name Type Description Default
synth_id str

SynthDef to mutate

required
amount float

Mutation strength (0.0-1.0)

0.3
seed Optional[int]

Random seed for deterministic testing

None

Returns:

Type Description
SynthLineage

New mutated SynthDef with lineage

Raises:

Type Description
SynthNotFoundError

If synth_id does not exist

InvalidSynthError

If mutated synth fails validation with sclang

Example

engine = EvolutionEngine() mutated = engine.mutate_synth( ... "synt_tb303", ... amount=0.4, ... seed=42, ... ) mutated['parent_ids']["synt_tb303"] mutated['mutation_log'] [ "Increased cutoff default: 1200 → 1440", "Changed oscillator: Saw → Pulse", ]

rank_by_rating(pattern_type: Optional[Literal['drums', 'melody', 'bass']] = None, limit: int = 10) -> list[PatternMetadata]

Get highest-rated patterns.

Useful for selecting best candidates for further evolution.

Parameters:

Name Type Description Default
pattern_type Optional[Literal['drums', 'melody', 'bass']]

Filter by type (None = all types)

None
limit int

Maximum results

10

Returns:

Type Description
list[PatternMetadata]

Patterns sorted by user_rating descending

Example

top_drums = engine.rank_by_rating( ... pattern_type="drums", ... limit=5, ... ) top_drums[0]['user_rating'] 5

validate_mutated_synth(source_code: str) -> bool

Validate SynthDef by compiling with sclang.

Ensures mutated SynthDef is syntactically valid.

Parameters:

Name Type Description Default
source_code str

SuperCollider code to validate

required

Returns:

Type Description
bool

True if valid, False if compilation fails

Example

code = "SynthDef(\test, { Out.ar(0, SinOsc.ar(440)); })" engine.validate_mutated_synth(code) True bad_code = "SynthDef(\test, { invalid syntax )" engine.validate_mutated_synth(bad_code) False

GeneratedSynth dataclass

A generated or mutated SynthDef.

Attributes:

Name Type Description
name str

SynthDef name (unique identifier)

source_code str

Full SuperCollider .scd code

controls list[SynthControl]

List of control parameters

parent_ids Optional[list[str]]

Parent synth IDs (empty if generated from scratch)

generation_method str

How synth was created (generated/mutation/crossover)

mutation_log Optional[list[str]]

Human-readable list of mutations applied

category Optional[str]

Inferred category (bass, lead, pad, drum, fx)

Example

synth = GeneratedSynth( ... name="tb303_evolved_1", ... source_code="SynthDef(...)", ... controls=[SynthControl("cutoff", 1500.0)], ... parent_ids=["tb303"], ... generation_method="mutation", ... mutation_log=["Saw → Pulse", "cutoff: 1200 → 1500"], ... )

LineageTracker

Tracks synth evolution lineage and fitness scores.

Stores synth records in a JSON file for persistence across sessions.

Attributes:

Name Type Description
db_path

Path to lineage database file

Example

tracker = LineageTracker() tracker.record_synth("tb303_m1", ["tb303"], ["Saw → Pulse"]) lineage = tracker.get_lineage("tb303_m1") lineage["ancestors"]['tb303']

__init__(db_path: Optional[Path] = None)

Initialize lineage tracker.

Parameters:

Name Type Description Default
db_path Optional[Path]

Path to lineage database (defaults to ~/.audiomancer/lineage.json)

None

get_generation_stats() -> Dict[str, Any]

Get statistics about evolutionary generations.

Returns:

Type Description
Dict[str, Any]

Dictionary with generation counts and depth metrics

Example

tracker = LineageTracker() stats = tracker.get_generation_stats() stats { 'total_synths': 42, 'original_synths': 5, 'mutated_synths': 28, 'crossover_synths': 9, 'max_generation': 7, 'avg_generation': 3.2, 'avg_rating': 4.1, }

get_lineage(synth_id: str) -> Dict[str, Any]

Get full ancestry and descendants for a synth.

Parameters:

Name Type Description Default
synth_id str

Synth to trace

required

Returns:

Type Description
Dict[str, Any]

Dictionary with ancestors, descendants, and generation info

Example

tracker = LineageTracker() lineage = tracker.get_lineage("tb303_m2") lineage { 'synth_id': 'tb303_m2', 'ancestors': ['tb303', 'tb303_m1'], 'descendants': ['tb303_m3', 'tb303_m4'], 'generation': 2, 'family_tree': { 'tb303': { 'tb303_m1': { 'tb303_m2': {} } } } }

get_top_rated(limit: int = 10, min_rating: Optional[int] = None) -> List[Dict[str, Any]]

Get highest-rated synths.

Parameters:

Name Type Description Default
limit int

Maximum number of results

10
min_rating Optional[int]

Minimum rating threshold (1-5)

None

Returns:

Type Description
List[Dict[str, Any]]

List of synth records sorted by rating (descending)

Example

tracker = LineageTracker() top = tracker.get_top_rated(limit=5, min_rating=4) top[0]['user_rating'] 5

rate_synth(synth_id: str, score: int, notes: Optional[str] = None) -> None

Record user rating for a synth.

Parameters:

Name Type Description Default
synth_id str

Synth to rate

required
score int

Rating 1-5

required
notes Optional[str]

Optional feedback text

None

Raises:

Type Description
ValueError

If score not in range 1-5

KeyError

If synth_id not found

Example

tracker = LineageTracker() tracker.rate_synth("tb303_m1", 5, "Perfect acid sound!")

record_synth(synth_id: str, name: str, parent_ids: List[str], generation_method: str, mutation_log: List[str]) -> None

Record a new synth in the lineage database.

Parameters:

Name Type Description Default
synth_id str

Unique synth identifier

required
name str

SynthDef name

required
parent_ids List[str]

List of parent synth IDs

required
generation_method str

Creation method (generated/mutation/crossover)

required
mutation_log List[str]

List of mutations applied

required
Example

tracker = LineageTracker() tracker.record_synth( ... "synt_m1", ... "tb303_m1", ... ["tb303"], ... "mutation", ... ["Saw → Pulse", "cutoff: 1200 → 1440"] ... )

PatternGenerator

Bases: Protocol

Interface for generating musical patterns using Magenta.

Supports drum and melody generation with style and scale constraints.

download_models() -> None

Download all required Magenta models (~500MB total).

Downloads to ~/.local/share/audiomancer/models/

Raises:

Type Description
IOError

If download fails

Example

generator = PatternGenerator() generator.download_models()

Downloads drums_rnn.mag, melody_rnn.mag

generate_bass(key: str = 'C', scale: Literal['major', 'minor', 'pentatonic', 'chromatic'] = 'minor', bars: int = 4, bpm: float = 120.0, timeout: int = 30) -> PatternMetadata

Generate bass line pattern.

Similar to melody but constrained to lower register.

Parameters:

Name Type Description Default
key str

Root note

'C'
scale Literal['major', 'minor', 'pentatonic', 'chromatic']

Scale type

'minor'
bars int

Number of bars

4
bpm float

Tempo in BPM

120.0
timeout int

Maximum generation time (seconds)

30

Returns:

Type Description
PatternMetadata

Generated bass pattern

Raises:

Type Description
InferenceTimeoutError

If generation exceeds timeout

ModelLoadError

If Magenta not available

Example

bass = generator.generate_bass( ... key="F#", ... scale="minor", ... bars=8, ... bpm=140, ... ) bass['type'] "bass"

generate_drums(style: Literal['basic', 'techno', 'house', 'dnb'] = 'basic', bars: int = 4, bpm: float = 120.0, timeout: int = 30) -> PatternMetadata

Generate drum pattern using Magenta DrumsRNN.

Styles: - basic: 4-on-floor kick, snare on 2&4 - techno: 16th hat variations, offbeat kicks - house: shuffle groove, open hats - dnb: fast breaks, syncopation

Parameters:

Name Type Description Default
style Literal['basic', 'techno', 'house', 'dnb']

Generation style

'basic'
bars int

Number of bars to generate

4
bpm float

Tempo in BPM

120.0
timeout int

Maximum generation time (seconds)

30

Returns:

Type Description
PatternMetadata

Generated pattern with MIDI data

Raises:

Type Description
InferenceTimeoutError

If generation exceeds timeout

ModelLoadError

If Magenta not available

Example

generator = PatternGenerator() pattern = generator.generate_drums( ... style="techno", ... bars=4, ... bpm=125, ... timeout=30, ... ) pattern['type'] "drums" pattern['bpm'] 125.0 pattern['parent_ids'][]

generate_melody(key: str = 'C', scale: Literal['major', 'minor', 'pentatonic', 'chromatic'] = 'minor', bars: int = 4, bpm: float = 120.0, timeout: int = 30) -> PatternMetadata

Generate melody using Magenta MelodyRNN.

Scales: - major: C D E F G A B (Ionian) - minor: C D Eb F G Ab Bb (Natural minor/Aeolian) - pentatonic: C D F G A (Minor pentatonic) - chromatic: All 12 notes

Parameters:

Name Type Description Default
key str

Root note (e.g., "C", "F#", "Bb")

'C'
scale Literal['major', 'minor', 'pentatonic', 'chromatic']

Scale type

'minor'
bars int

Number of bars to generate

4
bpm float

Tempo in BPM

120.0
timeout int

Maximum generation time (seconds)

30

Returns:

Type Description
PatternMetadata

Generated melody pattern

Raises:

Type Description
InferenceTimeoutError

If generation exceeds timeout

ModelLoadError

If Magenta not available

Example

generator = PatternGenerator() melody = generator.generate_melody( ... key="C", ... scale="minor", ... bars=4, ... bpm=120, ... ) melody['key'] "C" melody['scale'] "minor"

load_model(model_name: Literal['drums_rnn', 'melody_rnn']) -> None

Load and cache Magenta model.

Downloads model if not present. Caches in memory for reuse.

Parameters:

Name Type Description Default
model_name Literal['drums_rnn', 'melody_rnn']

Model to load

required

Raises:

Type Description
ModelLoadError

If download or loading fails

ImportError

If Magenta/TensorFlow not installed

Example

generator = PatternGenerator() generator.load_model("drums_rnn") generator.load_model("melody_rnn")

PatternMetadata

Bases: TypedDict

Metadata for a generated pattern.

Stores MIDI data and conversion outputs for patterns.

Example

pattern = PatternMetadata( ... id="ptrn_abc12345", ... type="drums", ... midi_data='{"tracks": [...], "tempo": 120}', ... tidal_code="d1 $ sound "bd ~ sn ~"", ... sc_code="Pbind(\instrument, \default, ...)", ... style="techno", ... key="C", ... scale="minor", ... bpm=125.0, ... bars=4, ... parent_ids=["ptrn_xyz789"], ... generation_method="mutated", ... mutation_amount=0.3, ... user_rating=4, ... user_notes="Good groove, needs variation", ... created_at=datetime.now(), ... )

SynthLineage

Bases: TypedDict

Lineage tracking for evolved SynthDefs.

Records mutation history and parent relationships.

Example

lineage = SynthLineage( ... id="synt_evolved1", ... name="tb303_evolved_1", ... source_code="SynthDef(\tb303_evolved_1, { ... })", ... parent_ids=["synt_tb303"], ... generation_method="mutated", ... mutation_log=[ ... "Increased cutoff default: 1200 → 1500", ... "Added distortion: 0 → 0.3", ... ], ... user_rating=5, ... user_notes="Perfect acid sound", ... created_at=datetime.now(), ... )

SynthRecord dataclass

Record of a synth in the lineage database.

Attributes:

Name Type Description
id str

Unique synth identifier

name str

SynthDef name

parent_ids List[str]

List of parent synth IDs

generation_method str

How synth was created

mutation_log List[str]

List of applied mutations

user_rating Optional[int]

User rating 1-5 (None if not rated)

user_notes Optional[str]

User feedback text

created_at Optional[datetime]

Timestamp of creation

Example

record = SynthRecord( ... id="synt_abc123", ... name="tb303_evolved_1", ... parent_ids=["tb303"], ... generation_method="mutation", ... mutation_log=["Saw → Pulse"], ... user_rating=5, ... user_notes="Amazing acid sound", ... created_at=datetime.now(), ... )

breed_synths(synth_a: SynthDefInfo, synth_b: SynthDefInfo, seed: Optional[int] = None) -> GeneratedSynth

Crossover two SynthDefs to create a hybrid child.

Crossover strategy: 1. Take oscillator section from parent A 2. Take filter section from parent B 3. Randomly mix effects from both parents 4. Combine unique controls from both parents 5. Average numeric parameter defaults

Parameters:

Name Type Description Default
synth_a SynthDefInfo

First parent SynthDef

required
synth_b SynthDefInfo

Second parent SynthDef

required
seed Optional[int]

Random seed for deterministic testing

None

Returns:

Type Description
GeneratedSynth

GeneratedSynth combining features of both parents

Raises:

Type Description
SynthDefError

If crossover produces invalid code

Example

tb303 = parse_synthdef(Path("synths/tb303.scd")) juno = parse_synthdef(Path("synths/juno_pad.scd")) child = breed_synths(tb303, juno, seed=42) child.name 'tb303_x_juno_pad' child.parent_ids ['tb303', 'juno_pad'] "Saw" in child.source_code # From tb303 True

generate_synth(description: str, base_synth: Optional[SynthDefInfo] = None, category: Literal['bass', 'lead', 'pad', 'drum', 'fx'] = 'bass') -> GeneratedSynth

Generate a new SynthDef from description.

If base_synth provided, uses it as starting point and applies transformations based on description keywords. Otherwise generates from category template.

Description keywords: - "acid": Adds resonant filter with envelope - "warm": Uses saw wave with low-pass filter - "bright": Uses pulse wave with high cutoff - "filtered": Adds MoogFF or RLPF - "distorted": Adds distortion/saturation - "sweep": Adds LFO modulation to filter

Parameters:

Name Type Description Default
description str

Natural language description of desired sound

required
base_synth Optional[SynthDefInfo]

Optional existing synth to modify

None
category Literal['bass', 'lead', 'pad', 'drum', 'fx']

Sound category (bass, lead, pad, drum, fx)

'bass'

Returns:

Type Description
GeneratedSynth

GeneratedSynth with source code and metadata

Raises:

Type Description
SynthDefError

If template cannot be loaded or customized

Example

synth = generate_synth("warm acid bass with filter sweep", category="bass") synth.name 'warm_acid_bass_001' "MoogFF" in synth.source_code True "LFO" in synth.source_code # For sweep True

get_generation_stats() -> Dict[str, Any]

Get statistics about evolutionary generations.

See LineageTracker.get_generation_stats for details.

get_synth_lineage(synth_id: str) -> Dict[str, Any]

Get full ancestry and descendants for a synth.

See LineageTracker.get_lineage for details.

get_top_rated(limit: int = 10, min_rating: Optional[int] = None) -> List[Dict[str, Any]]

Get highest-rated synths.

See LineageTracker.get_top_rated for details.

mutate_synth(synth: SynthDefInfo, amount: float = 0.3, seed: Optional[int] = None) -> GeneratedSynth

Create a variation of a SynthDef through mutation.

Mutation operations (probability based on amount parameter): 1. Swap oscillator UGen (e.g., Saw → Pulse) - amount * 0.4 2. Swap filter UGen (e.g., LPF → MoogFF) - amount * 0.3 3. Add modulation (LFO to parameter) - amount * 0.3 4. Add distortion effect - amount * 0.2 5. Modify parameter defaults - amount * 0.5 6. Modify envelope shape - amount * 0.2

All mutations preserve synth structure (SynthDef, Out, envelope).

Parameters:

Name Type Description Default
synth SynthDefInfo

Source SynthDef to mutate

required
amount float

Mutation strength (0.0-1.0, higher = more drastic changes)

0.3
seed Optional[int]

Random seed for deterministic testing

None

Returns:

Type Description
GeneratedSynth

GeneratedSynth with mutations applied and logged

Raises:

Type Description
SynthDefError

If mutation produces invalid SuperCollider code

Example

from audiomancer.analyzers.synthdef import parse_synthdef tb303 = parse_synthdef(Path("synths/tb303.scd")) variant = mutate_synth(tb303, amount=0.5, seed=42) variant.name 'tb303_m427' variant.mutation_log ['Saw → Pulse', 'Added LFO modulation to cutoff', 'cutoff: 1200.0 → 1440.0'] variant.parent_ids ['tb303']

rate_synth(synth_id: str, score: int, notes: Optional[str] = None) -> None

Record user rating for a synth.

See LineageTracker.rate_synth for details.

record_synth(synth_id: str, name: str, parent_ids: List[str], generation_method: str, mutation_log: List[str]) -> None

Record a new synth in the lineage database.

See LineageTracker.record_synth for details.