accompy
accompy - Generate accompaniment audio from chord charts.
Generate backing tracks with bass, drums, piano from chord progressions, similar to iReal Pro.
Example
>>> from accompy import generate_accompaniment, Score
>>>
>>> # Simple usage
>>> path = generate_accompaniment("| C | Am | F | G |", style="bossa", tempo=120)
>>>
>>> # With Score object for more control
>>> score = Score.from_string("| Dm7 | G7 | C^7 | A7b9 |", title="ii-V-I")
>>> path = generate_accompaniment(score, style="swing", tempo=160, repeats=4)
Available styles: swing, bossa, rock, ballad, funk, latin, waltz, blues
- Converter Pipeline:
>>> from accompy import converter, ChordSequence, MidiData, convert >>> # List available converters for a given step >>> converter.list_converters(ChordSequence, MidiData) >>> # Convert using the default or a named converter >>> midi = convert(chord_seq, MidiData) >>> midi = convert(chord_seq, MidiData, via="midiutil")
- Advanced Usage (Extensibility):
>>> # Register custom patterns >>> from accompy import get_pattern_registry >>> registry = get_pattern_registry() >>> # registry['my_style'] = {'drums': [...], 'bass': [...], 'comp': [...]} >>> >>> # Use custom chord resolver >>> from accompy import set_chord_resolver >>> # set_chord_resolver(my_custom_resolver) >>> >>> # Access protocol definitions for custom implementations >>> from accompy.protocols import ChordResolver, PatternSource, SynthesizerBackend
- class accompy.AccompanimentConfig(style: Literal['swing', 'bossa', 'rock', 'ballad', 'funk', 'latin', 'waltz', 'blues']='swing', tempo: int = 120, repeats: int = 2, instruments: dict[str, bool]=<factory>, volumes: dict[str, float]=<factory>, soundfont: Path | None = None, sample_rate: int = 44100, output_format: Literal['wav', 'mp3', 'flac', 'midi']='wav', chord_resolver: Any | None = None, pattern_source: Any | None = None, synthesis_backend: Any | None = None)[source]
Configuration for accompaniment generation.
This is the single source of truth for all configuration options, including dependency injection hooks for extensibility.
- style
Musical style (swing, bossa, rock, etc.)
- Type:
Literal[‘swing’, ‘bossa’, ‘rock’, ‘ballad’, ‘funk’, ‘latin’, ‘waltz’, ‘blues’]
- tempo
Beats per minute
- Type:
int
- repeats
Number of times to play through the form
- Type:
int
- instruments
Which instruments to include
- Type:
dict[str, bool]
- volumes
Relative volume for each instrument (0.0-1.0)
- Type:
dict[str, float]
- soundfont
Path to SoundFont file for synthesis
- Type:
pathlib.Path | None
- sample_rate
Audio sample rate
- Type:
int
- output_format
Output file format
- Type:
Literal[‘wav’, ‘mp3’, ‘flac’, ‘midi’]
- chord_resolver
Optional custom chord resolution function
- Type:
Any | None
- pattern_source
Optional custom pattern provider
- Type:
Any | None
- synthesis_backend
Optional custom synthesis backend
- Type:
Any | None
- with_overrides(**kwargs) AccompanimentConfig[source]
Create a new config with specified overrides.
This enables immutable updates to configuration.
Example
>>> config = AccompanimentConfig(tempo=120) >>> fast_config = config.with_overrides(tempo=180) >>> config.tempo, fast_config.tempo (120, 180)
- class accompy.AudioData(waveform: ndarray, sr: int = 44100)[source]
Container for audio data — numpy array + sample rate.
>>> import numpy as np >>> ad = AudioData(waveform=np.zeros(44100), sr=44100) >>> ad.duration_seconds 1.0
- class accompy.BassPattern(name: str, notes: Sequence[NoteEvent])[source]
A bass pattern template.
Uses pitch_offset in NoteEvent to specify intervals from the chord root. The actual pitches are determined when the pattern is applied to specific chords.
- class accompy.ChordEvent(symbol: str, beats: int = 4)[source]
A chord at a specific position in the progression.
Example
>>> event = ChordEvent("Dm7", beats=4) >>> event.symbol 'D-7'
- class accompy.ChordResolver(*args, **kwargs)[source]
Convert chord symbols to MIDI note numbers.
- Example implementation:
>>> def my_resolver(symbol: str) -> list[int]: ... # Return MIDI notes for chord ... return [60, 64, 67] # C major
- class accompy.ChordSequence(chords: list[tuple[str, float]], title: str = '', key: str = 'C', tempo: int = 120, time_signature: tuple[int, int] = (4, 4))[source]
Ordered sequence of (chord_symbol, duration_beats) pairs with metadata.
This is the canonical internal representation of a chord progression.
>>> cs = ChordSequence([("Dm7", 4.0), ("G7", 4.0), ("Cmaj7", 8.0)]) >>> len(cs) 3 >>> cs[0] ('Dm7', 4.0) >>> cs.total_beats 16.0
- property durations: list[float]
Just the durations, without symbols.
- property symbols: list[str]
Just the chord symbols, without durations.
- class accompy.CompingPattern(name: str, hits: Sequence[tuple[float, float, int]])[source]
A piano/guitar comping (accompaniment) pattern.
- name
Pattern identifier
- Type:
str
- hits
Sequence of (beat, duration, velocity) tuples
- Type:
Sequence[tuple[float, float, int]]
- class accompy.ConverterRegistry[source]
Registry mapping (source_type, target_type) to named converter functions.
Supports multiple converters for the same type pair, distinguished by name. The first registered converter becomes the default.
>>> reg = ConverterRegistry() >>> reg.register(str, int, int, name='builtin') >>> reg[str, int]('42') 42 >>> reg.list_converters(str, int) ['builtin']
- get(source_type: type, target_type: type, name: str | None = None) Callable[source]
Get a specific named converter, or the default if name is None.
- list_converters(source_type: type, target_type: type) list[str][source]
List available converter names for a type pair.
- register(source_type: type, target_type: type, func: Callable, *, name: str = '', is_default: bool = False) None[source]
Register a converter function.
- Parameters:
source_type – The input type
target_type – The output type
func – The converter function (source -> target)
name – Name for this converter (defaults to func.__name__)
is_default – If True, make this the default converter for this pair
- class accompy.DrumHit(beat: float, drum: int, velocity: int)[source]
A single drum hit in a pattern.
- beat
Beat position (0-based within measure)
- Type:
float
- drum
MIDI note number for the drum sound
- Type:
int
- velocity
Hit velocity (0-127)
- Type:
int
- class accompy.DrumPattern(name: str, beats_per_bar: int, hits: Sequence[DrumHit])[source]
A drum pattern for one or more measures.
Example
>>> pattern = DrumPattern("rock", 4, [DrumHit(0, KICK, 100)]) >>> pattern.beats_per_bar 4
- class accompy.MidiData(bytes_: bytes | None = None, pretty_midi_obj: Any = None, tempo: int = 120, time_signature: tuple[int, int] = (4, 4))[source]
Container for MIDI data — either as bytes or as a pretty_midi object.
Wraps MIDI content so converters have a uniform interface regardless of which MIDI library produced the data.
>>> import io >>> md = MidiData(bytes_=b'MThd...', tempo=120) >>> md.has_bytes True
- class accompy.MidiEvent(time: float, channel: int, note: int, velocity: int, duration: float)[source]
A single MIDI event.
Used for event-based MIDI generation that enables both batch file creation and future real-time streaming.
- time
Event time in beats
- Type:
float
- channel
MIDI channel (0-15)
- Type:
int
- note
MIDI note number (0-127)
- Type:
int
- velocity
Note velocity (0-127)
- Type:
int
- duration
Note duration in beats
- Type:
float
- class accompy.NoteEvent(beat: float, pitch_offset: int, duration: float, velocity: int)[source]
A melodic note event for bass or other instruments.
- beat
Beat position within measure
- Type:
float
- pitch_offset
Offset from chord root in semitones (0=root, 7=5th, etc.)
- Type:
int
- duration
Note duration in beats
- Type:
float
- velocity
Note velocity (0-127)
- Type:
int
- class accompy.NoteSequence(notes: list[tuple[list[int], float]], tempo: int = 120, time_signature: tuple[int, int] = (4, 4))[source]
Ordered sequence of (midi_notes, duration_beats) with metadata.
Represents resolved chords — chord symbols have been converted to concrete MIDI note numbers.
>>> ns = NoteSequence([([60, 64, 67], 4.0), ([62, 65, 69], 4.0)]) >>> ns[0] ([60, 64, 67], 4.0)
- class accompy.PatternSource(*args, **kwargs)[source]
Provides musical patterns for a given style.
Example
>>> class MyPatternSource: ... def get_patterns(self, style: str) -> dict: ... return {'drums': [...], 'bass': [...], 'comp': [...]} ... def available_styles(self) -> list[str]: ... return ['swing', 'bossa']
- class accompy.RealtimeAccompaniment(config: AccompanimentConfig | None = None, *, on_event: Callable[[MidiEvent], None] | None = None)[source]
Real-time accompaniment player (foundation for future work).
This class separates event scheduling from synthesis, enabling integration with real-time audio systems. Current implementation generates events; future versions will integrate with hum/pyo for actual audio synthesis.
- Example (current usage):
>>> from accompy import AccompanimentConfig >>> config = AccompanimentConfig(tempo=120, style='swing') >>> player = RealtimeAccompaniment(config) >>> player.set_chords([('Dm7', 4), ('G7', 4), ('Cmaj7', 8)]) >>> events_iter = player.events() # Get event iterator >>> # Future: for event in events_iter: synth.play(event.note, event.velocity)
- Future usage (with hum integration):
>>> from hum.pyo_util import Synth >>> def on_event(event: MidiEvent): ... # Send MIDI event to synth in real-time ... synth.send_note(event.note, event.velocity, event.duration) >>> player = RealtimeAccompaniment(config, on_event=on_event) >>> player.play()
- events() Iterator[MidiEvent][source]
Generate events for current chord progression.
- Yields:
MidiEvent objects in chronological order
Example
>>> player = RealtimeAccompaniment() >>> player.set_chords([('C', 4)]) >>> events = list(player.events()) >>> len(events) > 0 True
- play() None[source]
Play the accompaniment (future implementation).
This will integrate with a real-time synthesis backend (hum/pyo) to actually play audio. Current implementation is a placeholder.
- Raises:
NotImplementedError – Real-time playback not yet implemented
- class accompy.Score(measures: list[list[str]], title: str = 'Untitled', composer: str = '', key: str = 'C', time_signature: tuple[int, int] = (4, 4))[source]
A musical score containing chord events and metadata.
This is the domain model for chord progressions. It provides a unified representation regardless of input format (string, iReal URL, tuples, etc.).
Example
>>> score = Score.from_string("| C | Am | F | G |", time_signature=(4, 4)) >>> list(score.measures) [['C'], ['A-'], ['F'], ['G']]
- classmethod from_ireal_url(url: str) Score[source]
Parse an iReal Pro URL into a Score.
- Example::
url = “irealb://Autumn%20Leaves=…” score = Score.from_ireal_url(url)
- classmethod from_string(chord_string: str, *, title: str = 'Untitled', key: str = 'C', time_signature: tuple[int, int] = (4, 4)) Score[source]
Parse a chord string into a Score.
Supports formats: - Simple: “C Am F G” (space-separated, one chord per bar) - Bar lines: “| C | Am | F | G |" - Multi-chord bars: "| C Am | F G |” (chords split evenly) - iReal-style: “C-7 F7 | Bb^7 | Eh7 A7b9 |”
Example
>>> Score.from_string("| Dm7 | G7 | C^7 | % |").measures # % means repeat [['D-7'], ['G7'], ['C^7'], ['C^7']]
- class accompy.ScoreToWav(*args, **kwargs)[source]
Callable that renders a
Scoreto a WAV file.Any function matching this signature can be used as a
score_to_wavengine ingenerate_wav()andgenerate_variations().
- class accompy.SynthesizerBackend(*args, **kwargs)[source]
Audio synthesis backend.
Simpler protocol than AudioRenderer, focused just on synthesis. Used by the synthesis module.
- classmethod is_available() bool[source]
Check if this backend’s dependencies are installed.
- Returns:
True if the backend can be used
- accompy.apply_skeleton(cs, skeleton: str | tuple | Sequence) ChordSequence[source]
Apply a rhythmic skeleton to a chord sequence.
The skeleton defines strike positions within each measure. Each strike plays whatever chord is active at that beat position. If a strike spans a chord boundary within a measure, it is split so the chord change is respected.
- Parameters:
cs – A ChordSequence (from
accompy.converters).skeleton – Skeleton key, name, style, or duration tuple.
- Returns:
A new ChordSequence with chords expanded according to the skeleton.
Example
>>> from accompy.converters import ChordSequence >>> cs = ChordSequence([("Dm7", 2.0), ("G7", 2.0)]) >>> result = apply_skeleton(cs, "tresillo") >>> [(s, d) for s, d in result] [('Dm7', 1.5), ('Dm7', 0.5), ('G7', 1.0), ('G7', 1.0)]
- accompy.check_dependencies() dict[str, bool][source]
Check which dependencies are available.
- Returns:
Dict mapping dependency name to availability status
Example
>>> deps = check_dependencies() >>> 'midiutil' in deps True
- accompy.chord_to_notes(symbol: str) list[int][source]
Convert chord symbol to MIDI notes using the current default resolver.
This is the main entry point for chord resolution in accompy.
- Parameters:
symbol – Chord symbol (e.g., “Dm7”, “G7”)
- Returns:
List of MIDI note numbers
Example
>>> notes = chord_to_notes("C") >>> len(notes) > 0 True
- accompy.chords_to_audio(chords: str | ChordSequence, *, resolver: str | None = None, midi_gen: str | None = None, audio_renderer: str | None = None, soundfont: str | None = None, tempo: int = 120, sr: int = 44100, output_path: str | None = None) AudioData[source]
Convert chord string to audio.
This is the complete pipeline: parse -> resolve -> MIDI -> audio.
- Parameters:
chords – Chord string or ChordSequence
resolver – Chord resolver (‘pychord’, ‘music21’, ‘mingus’, ‘tonal’)
midi_gen – MIDI generator (‘pretty_midi’, ‘midiutil’, ‘mido’)
audio_renderer – Audio renderer (‘pretty_midi’, ‘fluidsynth’, ‘tonal’)
soundfont – Path to SoundFont file
tempo – BPM
sr – Sample rate
output_path – Optional path to write WAV file
- Returns:
AudioData with waveform and sample rate
>>> audio = chords_to_audio("| C | Am | F | G |") >>> audio.write("output.wav")
- accompy.chords_to_midi(chords: str | ChordSequence, *, resolver: str | None = None, midi_gen: str | None = None, tempo: int = 120, output_path: str | None = None) MidiData[source]
Convert chord string to MIDI data.
- Parameters:
chords – Chord string or ChordSequence
resolver – Resolver name (‘pychord’, ‘music21’, ‘mingus’, ‘tonal’)
midi_gen – MIDI generator name (‘pretty_midi’, ‘midiutil’, ‘mido’)
tempo – BPM
output_path – Optional path to write MIDI file
- Returns:
MidiData object
>>> md = chords_to_midi("| C | Am | F | G |") >>> md.to_bytes()[:4] b'MThd'
- accompy.chords_to_notes(chords: str | ChordSequence, *, resolver: str | None = None, tempo: int = 120) NoteSequence[source]
Convert chord string or ChordSequence to resolved MIDI notes.
- Parameters:
chords – Chord string or ChordSequence
resolver – Resolver name (‘pychord’, ‘music21’, ‘mingus’, ‘tonal’)
tempo – BPM (used if chords is a string)
- Returns:
NoteSequence with MIDI note numbers
>>> ns = chords_to_notes("| C | Am |") >>> len(ns) 2 >>> all(0 <= n <= 127 for notes, _ in ns for n in notes) True
- accompy.chords_to_sequence(chords: str, *, parser: str | None = None, tempo: int = 120, title: str = '', key: str = 'C', time_signature: tuple[int, int] = (4, 4)) ChordSequence[source]
Parse a chord string into a ChordSequence.
- Parameters:
chords – Chord string in any supported format
parser – Parser name (‘auto_detect’, ‘plain_text’, ‘chordpro’, ‘musicgen_chord’)
tempo – BPM (default 120)
title – Song title
key – Key signature
time_signature – Time signature tuple
- Returns:
ChordSequence with parsed chords
>>> cs = chords_to_sequence("| Dm7 | G7 | Cmaj7 |", tempo=140) >>> cs.symbols ['Dm7', 'G7', 'Cmaj7'] >>> cs.tempo 140
- accompy.convert(source: Any, target_type: type, *, via: str | None = None) Any[source]
Convert source to target_type using the registered converter.
- Parameters:
source – The source data
target_type – The desired output type
via – Optional converter name (uses default if None)
- Returns:
Converted data of target_type
Example:
>>> # After converters are registered: >>> # audio = convert(chord_seq, AudioData)
- accompy.diagnose_issues() List[Tuple[str, str, str]][source]
Diagnose common setup issues and provide solutions.
- Returns:
List of (issue, description, solution) tuples
Example:
from accompy.setup_utils import diagnose_issues for issue, desc, solution in diagnose_issues(): print(f"{issue}: {desc}") print(f"Solution: {solution}")
- accompy.ensure_score(chords: Any, *, title: str = 'Untitled', key: str = 'C', time_signature: tuple[int, int] = (4, 4)) Score[source]
Coerce common chord-progression formats into a Score.
Supported inputs: - Score: returned as-is - str: chord string (e.g. “| C | Am | F | G |”) OR iReal URL (irealbook://…) - Iterable[tuple[str, int|float]]: list of (chord, beats) like in accompany - Iterable[str]: chord symbols, one per bar - list[list[str]]: already-parsed measures
Notes: - Score.measures in accompy does not encode per-chord durations within a bar.
For (chord, beats) inputs, durations not equal to whole bars are approximated by grouping chords into bars.
Examples
>>> ensure_score("| C | Am | F | G |", time_signature=(4, 4)).measures [['C'], ['A-'], ['F'], ['G']] >>> ensure_score([("F#m7b5", 4), ("B7", 4), ("Em", 8)], key="E").measures[:3] [['F#h7'], ['B7'], ['E-']]
- accompy.file_to_audio(filepath: str, *, output_path: str | None = None, n_repeats: int = 1, transpose: int = 0, resolver: str | None = None, midi_gen: str | None = None, audio_renderer: str | None = None, soundfont: str | None = None, tempo: int | None = None, sr: int = 44100) AudioData[source]
Convert an iReal Pro HTML/URL file to audio.
Handles: - iReal Pro HTML files (exported from the app) - iReal Pro URL strings - Plain chord text files
Supports repeating the progression and transposing.
- Parameters:
filepath – Path to an iReal HTML file, or a chord text file
output_path – Where to write audio. If None, uses filepath with .wav extension
n_repeats – Number of times to repeat the progression (default 1)
transpose – Semitones to transpose (positive=up, negative=down)
resolver – Chord resolver backend
midi_gen – MIDI generator backend
audio_renderer – Audio renderer backend
soundfont – Path to SoundFont file
tempo – Override BPM (None = use file’s tempo or 120)
sr – Sample rate
- Returns:
AudioData
Example:
>>> file_to_audio("/path/to/song.html") >>> file_to_audio("/path/to/song.html", n_repeats=40) >>> file_to_audio("/path/to/song.html", transpose=5)
- accompy.file_to_midi(filepath: str, *, output_path: str | None = None, n_repeats: int = 1, transpose: int = 0, resolver: str | None = None, midi_gen: str | None = None, tempo: int | None = None) MidiData[source]
Convert an iReal Pro HTML/URL file to MIDI.
Same as file_to_audio but outputs MIDI instead.
- Parameters:
filepath – Path to an iReal HTML file, or a chord text file
output_path – Where to write MIDI. If None, uses filepath with .mid extension
n_repeats – Number of times to repeat the progression
transpose – Semitones to transpose
resolver – Chord resolver backend
midi_gen – MIDI generator backend
tempo – Override BPM
- Returns:
MidiData
- accompy.generate_accompaniment(chords: Any, *, style: Literal['swing', 'bossa', 'rock', 'ballad', 'funk', 'latin', 'waltz', 'blues'] = 'swing', tempo: int = 120, repeats: int = 1, output_path: str | Path | None = None, output_format: Literal['wav', 'mp3', 'flac', 'midi', 'mid'] | None = None, config: AccompanimentConfig | None = None, use_mma: bool = True, backend: Literal['auto', 'mma', 'builtin'] | None = None, autoplay: bool = False) Path[source]
Generate an accompaniment audio file from a chord progression.
This is the main entry point for accompy. It generates backing tracks with bass, drums, and piano from chord progressions.
- Parameters:
chords – Chord progression (string, Score, list of tuples, iReal URL)
style – Musical style (swing, bossa, rock, ballad, funk, latin, waltz, blues)
tempo – Tempo in BPM
repeats – Number of times to repeat the progression
output_path – Where to save the file (None = temp file)
output_format – Output format (wav, mp3, flac, midi)
config – Full configuration object (overrides other params if provided)
use_mma – If True and MMA available, use MMA backend
backend – Explicitly select backend (‘auto’, ‘mma’, ‘builtin’)
autoplay – If True, automatically play the generated audio
- Returns:
Path to the generated audio/MIDI file
Example
>>> from accompy import generate_accompaniment >>> path = generate_accompaniment("| C | Am | F | G |", style="bossa", tempo=140) >>> print(f"Generated: {path}")
Note
Requires FluidSynth and a SoundFont for audio rendering. For MIDI-only output, use output_format=”midi”.
- accompy.generate_mma_wav(score: Score, output_path: str | Path, *, groove: str = 'Swing', tempo: int = 120, repeats: int = 1) Path
Render a Score to WAV using MMA (Musical MIDI Accompaniment).
Accepts any valid MMA groove name (e.g.
"Bebop","GypsyJazz"). Runmma -Dgto list available grooves.- Parameters:
score – A
Scorewith chord measures.output_path – Destination WAV file path.
groove – MMA groove name (case-sensitive).
tempo – Tempo in BPM.
repeats – Number of times to repeat the progression.
- Returns:
Path to the generated WAV file.
- Raises:
RuntimeError – If MMA is not installed or the groove is invalid.
- accompy.generate_variations(score: Score, output_dir: str | Path, *, keys: Sequence[str] = ('C',), tempos: Sequence[int] = (120,), grooves: Sequence[str] = ('Swing',), repeats: int = 1, filename_template: str = '{title}_{key}_{tempo}bpm_{groove}.wav', score_to_wav: ScoreToWav | None = None) list[Path][source]
Batch-generate WAV files for many key / tempo / groove combinations.
Produces one WAV for each
(key, tempo, groove)triple (cycled from the shortest sequences). Useful for creating practice backing-track collections.- Parameters:
score – Base
Score(will be transposed per key).output_dir – Directory for output files.
keys – Keys to transpose to.
tempos – Tempos in BPM to cycle through.
grooves – Groove / style names to cycle through.
repeats – Number of repeats per file.
filename_template – Template for filenames. Placeholders:
{title},{key},{tempo},{groove}.score_to_wav – Engine callable. Defaults to
mma_score_to_wav().
- Returns:
List of Paths to successfully generated files.
Example:
>>> from accompy import Score, generate_variations >>> score = Score.from_string("| C6 | Do | C6/E | Fo |", key="C") >>> generate_variations( ... score, "/tmp/variations", ... keys=["C", "G", "F"], ... tempos=[100, 120], ... grooves=["Swing", "BossaNova"], ... ) >>> # With a custom engine: >>> from accompy.tools import make_converter_engine >>> engine = make_converter_engine(audio_renderer="fluidsynth") >>> generate_variations( ... score, "/tmp/variations", ... keys=["C", "G"], ... tempos=[100, 120], ... grooves=["Swing"], ... score_to_wav=engine, ... )
- accompy.generate_wav(score: Score, output_path: str | Path, *, groove: str = 'Swing', tempo: int = 120, repeats: int = 1, score_to_wav: ScoreToWav | None = None) Path[source]
Generate a WAV file from a Score using a pluggable engine.
By default uses MMA (Musical MIDI Accompaniment). Pass a custom
score_to_wavcallable to use a different backend — for instance one built withmake_converter_engine().- Parameters:
score – A
Scorewith chord measures.output_path – Destination WAV file path.
groove – Groove / style name (interpretation depends on the engine).
tempo – Tempo in BPM.
repeats – Number of times to repeat the progression.
score_to_wav – Engine callable. Defaults to
mma_score_to_wav().
- Returns:
Path to the generated WAV file.
Example:
>>> from accompy import Score, generate_wav >>> score = Score.from_string("| Dm7 | G7 | C^7 |") >>> generate_wav(score, "/tmp/test.wav", groove="BossaNova", tempo=140) PosixPath('/tmp/test.wav') >>> # With a custom engine: >>> from accompy.tools import make_converter_engine >>> engine = make_converter_engine(audio_renderer="fluidsynth") >>> generate_wav(score, "/tmp/test.wav", score_to_wav=engine, tempo=140)
- accompy.get_app_folder(*, folder_kind: str = 'data') Path[source]
Return the app directory for folder_kind, creating it if needed.
- accompy.get_artifact_dir(kind: str) Path
Return (and create) an artifact sub-directory for kind.
- accompy.get_chord_resolver() Callable[[str], list[int]][source]
Get the current default chord resolver.
- Returns:
The active chord resolution function
Example
>>> resolver = get_chord_resolver() >>> notes = resolver("C") >>> len(notes) > 0 True
- accompy.get_config(name: str) Path
Return a config file path, seeding from package data if missing.
- accompy.get_pattern_registry() PatternRegistry[source]
Get the global pattern registry, initializing if needed.
- Returns:
The global PatternRegistry instance
Example
>>> registry = get_pattern_registry() >>> 'swing' in registry True
- accompy.get_patterns(style: str) dict[source]
Get all patterns for a given style.
- Returns:
Dict with ‘drums’, ‘bass’, ‘comp’ keys containing pattern lists
Example
>>> patterns = get_patterns("bossa") >>> drums = patterns["drums"][0] >>> drums.name 'bossa'
- accompy.get_resource(name: str) Path
Return a user resource path, seeding from package data if missing.
- accompy.list_available_converters() dict[str, list[str]][source]
List all available converters organized by pipeline stage.
- Returns:
Dict mapping stage names to lists of converter names.
>>> info = list_available_converters() >>> 'parsers' in info True >>> 'resolvers' in info True
- accompy.list_skeletons(*, beats_per_measure: float | None = None, style: str | None = None) list[str][source]
List available skeleton keys, optionally filtered.
- Parameters:
beats_per_measure – Filter to skeletons matching this measure length.
style – Filter to skeletons associated with this style.
- Returns:
List of skeleton key strings.
Examples
>>> "tresillo" in list_skeletons() True >>> all(RHYTHMIC_SKELETONS[k]["beats_per_measure"] == 3 ... for k in list_skeletons(beats_per_measure=3)) True
- accompy.load_resource_lines(name: str) list[str][source]
Read a resource file as a list of non-empty stripped lines.
- accompy.make_converter_engine(*, resolver: str | None = None, midi_gen: str | None = None, audio_renderer: str | None = None, soundfont: str | None = None, sr: int = 44100) ScoreToWav[source]
Create a
ScoreToWavengine from accompy’s converter pipeline.This wraps the
ChordSequence → NoteSequence → MidiData → AudioDataconverter chain into the same interface thatgenerate_wav()expects, so you can swap it in place of the default MMA engine.- Parameters:
resolver – Chord resolver name (e.g.
"pychord","tonal").midi_gen – MIDI generator name (e.g.
"pretty_midi","midiutil").audio_renderer – Audio renderer name (e.g.
"fluidsynth","pretty_midi").soundfont – Path to a SoundFont file (for FluidSynth-based renderers).
sr – Sample rate.
- Returns:
A callable matching the
ScoreToWavprotocol.
Example:
>>> engine = make_converter_engine(audio_renderer="fluidsynth") >>> generate_wav(score, "/tmp/out.wav", score_to_wav=engine, tempo=120)
- accompy.midi_to_audio(midi_data: MidiData, *, audio_renderer: str | None = None, soundfont: str | None = None, sr: int = 44100, output_path: str | None = None) AudioData[source]
Convert MidiData to audio.
- Parameters:
midi_data – MidiData object
audio_renderer – Audio renderer name
soundfont – Path to SoundFont file
sr – Sample rate
output_path – Optional path to write WAV file
- Returns:
AudioData
>>> audio = midi_to_audio(some_midi_data)
- accompy.mma_score_to_wav(score: Score, output_path: str | Path, *, groove: str = 'Swing', tempo: int = 120, repeats: int = 1) Path[source]
Render a Score to WAV using MMA (Musical MIDI Accompaniment).
Accepts any valid MMA groove name (e.g.
"Bebop","GypsyJazz"). Runmma -Dgto list available grooves.- Parameters:
score – A
Scorewith chord measures.output_path – Destination WAV file path.
groove – MMA groove name (case-sensitive).
tempo – Tempo in BPM.
repeats – Number of times to repeat the progression.
- Returns:
Path to the generated WAV file.
- Raises:
RuntimeError – If MMA is not installed or the groove is invalid.
- accompy.parse_ireal_html(html_path: str) Score[source]
Extract a Score from an iReal Pro HTML export file.
iReal Pro can export songs as HTML files containing an
irealb://link. This function reads the file, extracts that link, and parses it into aScore.- Parameters:
html_path – Path to the HTML file exported from iReal Pro.
- Returns:
A Score with measures, title, key, and time signature.
- Raises:
FileNotFoundError – If html_path does not exist.
ValueError – If no iReal URL is found in the HTML.
Example:
>>> score = parse_ireal_html("/path/to/song.html") >>> score.title 'Autumn Leaves'
- accompy.parse_ireal_url(url: str)[source]
Parse an iReal Pro URL into a Score object.
Tries pyRealParser’s
parse_ireal_urlfirst, then falls back to constructing aTunedirectly (handles URLs with empty==fields), and finally to a no-dependency best-effort parser.
- accompy.play_audio(audio_path: str | Path) bool[source]
Play an audio file using the system’s default audio player.
- Parameters:
audio_path – Path to the audio file
- Returns:
True if playback started successfully, False otherwise
Example
>>> from accompy import play_audio >>> play_audio("/path/to/audio.wav")
- accompy.print_setup_instructions()[source]
Print installation instructions for missing dependencies.
- accompy.register_skeleton(key: str, pattern: tuple, *, name: str = '', beats_per_measure: float | None = None, styles: list[str] | None = None) None[source]
Register a custom rhythmic skeleton.
- Parameters:
key – Unique string key for the skeleton.
pattern – Tuple of beat durations.
name – Human-readable name (defaults to key).
beats_per_measure – Measure length in beats (defaults to sum of pattern).
styles – List of associated style strings.
Example
>>> register_skeleton("my_groove", (1, 0.5, 0.5, 2), name="My Groove") >>> resolve_skeleton("my_groove") (1, 0.5, 0.5, 2)
- accompy.register_style(style: str, drums: list, bass: list, comp: list) None[source]
Register a custom style with the global registry.
- Parameters:
style – Style name
drums – List of DrumPattern objects
bass – List of BassPattern objects
comp – List of CompingPattern objects
Example
>>> registry = get_pattern_registry() >>> register_style('my_funk', [my_drum_pattern], [my_bass_pattern], [])
- accompy.render_chords(chords, *, rhythmic_skeleton: str | tuple[float, ...] = 'whole_note', bpm: int = 100, transpose: int = 0, n_loops: int | None = None, max_seconds: float | None = None, ai_enhance: bool | Callable | None = True, suno_mode: str = 'cover', prompt_template: str = '{genre} backing track with {instruments}', genre: str = 'jazz', instruments: list[str] | None = None, audio_weight: float = 0.99, style_weight: float = 0.51, weirdness: float = 0.0, model: str = '', instrumental: bool = True, wait_for_completion: bool = True, poll_interval: float = 15.0, timeout: float = 600.0, chords_to_midi_audio: Callable | None = None, audio_to_enhanced_audio: Callable | None = None, midi_store: MutableMapping | None = None, midi_audio_store: MutableMapping | None = None, enhanced_audio_store: MutableMapping | None = None, resolver: str | None = None, midi_gen: str | None = None, audio_renderer: str | None = None, soundfont: str | None = None, sr: int = 44100) str[source]
Render chords to a high-quality audio file, optionally AI-enhanced.
- Parameters:
chords – Chord input — string (
"| Dm7 | G7 | C^7 |"), iReal URL,ChordSequence,Score, or list of(chord, beats)tuples.rhythmic_skeleton – Restrike pattern within each measure.
bpm – Tempo in beats per minute.
transpose – Semitones to transpose (positive=up, negative=down).
n_loops – Explicit number of loops. Mutually exclusive with max_seconds.
max_seconds – Target maximum duration; computes loop count automatically. Defaults to 210 (3.5 min) when neither n_loops nor max_seconds is given.
ai_enhance –
Truefor default Suno enhancement,False/Noneto skip, or a callable(audio_path, prompt, **kw) → bytes.suno_mode –
"cover"(default, re-generates in style) or"extend".prompt_template – Template with
{genre}and{instruments}placeholders.genre – Genre/style tags for the AI prompt.
instruments – Instrument list for the AI prompt. Defaults to
["piano", "bass", "drums"].audio_weight – How much the source audio influences AI output (0–1).
style_weight – How much the style prompt influences AI output (0–1).
weirdness – Creative deviation for cover mode (0–1).
model – Suno model version (e.g.
"V4_5").instrumental – If True, generate without vocals.
wait_for_completion – If True, poll until AI generation is ready.
poll_interval – Seconds between AI status checks.
timeout – Max seconds to wait for AI completion.
chords_to_midi_audio – Override for the MIDI audio rendering step. Callable:
(ChordSequence, **kw) → AudioData.audio_to_enhanced_audio – Override for the AI enhancement step. Callable:
(audio_path, prompt, **kw) → bytes.midi_store – Optional store for MIDI files.
None= don’t persist MIDI.midi_audio_store – Store for rendered MIDI audio.
None= default file store under~/.local/share/accompy/artifacts/midi_audio/.enhanced_audio_store – Store for AI-enhanced audio.
None= default file store under~/.local/share/accompy/artifacts/enhanced_audio/.resolver – Chord resolver backend name.
midi_gen – MIDI generator backend name.
audio_renderer – Audio renderer backend name.
soundfont – Path to a SoundFont file.
sr – Sample rate for MIDI audio rendering.
- Returns:
Filesystem path to the final audio file.
- accompy.render_chords_batch(configs: Iterable[dict], **shared_kwargs) list[str][source]
Run
render_chords()for each config dict.Each dict in configs is merged with shared_kwargs (per-config values take priority over shared defaults).
- Parameters:
configs – Iterable of dicts, each containing keyword arguments for
render_chords().**shared_kwargs – Default arguments applied to every config.
- Returns:
List of filesystem paths to the final audio files.
Example:
>>> from itertools import product >>> configs = [ ... dict(chords="| Dm7 | G7 | C^7 |", genre=g, bpm=b) ... for g, b in product(["jazz", "lofi"], [100, 120]) ... ] >>> paths = render_chords_batch(configs, ai_enhance=False)
- accompy.resolve_skeleton(skeleton: str | tuple | Sequence) tuple[source]
Resolve a skeleton specification to a tuple of durations.
- Accepts:
A tuple or list of numbers (pass-through)
A skeleton key (e.g.,
"tresillo","whole_note")A skeleton name, case-insensitive (e.g.,
"Tresillo")A style string (e.g.,
"reggae") — returns the first match
- Returns:
Tuple of beat durations summing to the measure length.
- Raises:
KeyError – If the skeleton cannot be resolved.
Examples
>>> resolve_skeleton("whole_note") (4,) >>> resolve_skeleton("tresillo") (1.5, 1.5, 1) >>> resolve_skeleton((2, 2)) (2, 2) >>> resolve_skeleton("Dotted half + quarter") (3, 1)
- accompy.rhythm_to_audio(chords: str | ChordSequence, *, skeleton: str | tuple[float, ...] = 'whole_note', resolver: str | None = None, midi_gen: str | None = None, audio_renderer: str | None = None, soundfont: str | None = None, tempo: int = 120, sr: int = 44100, output_path: str | None = None) AudioData[source]
Convert chords to audio using a rhythmic skeleton for restrike timing.
Same as
rhythm_to_midi()but renders all the way to audio.- Parameters:
chords – Chord string or ChordSequence.
skeleton – Skeleton key, name, style, or duration tuple.
resolver – Chord resolver name.
midi_gen – MIDI generator name.
audio_renderer – Audio renderer name.
soundfont – Path to SoundFont file.
tempo – BPM.
sr – Sample rate.
output_path – Optional path to write WAV file.
- Returns:
AudioData with waveform and sample rate.
Example
>>> audio = rhythm_to_audio("| C | Am |", skeleton="half_notes")
- accompy.rhythm_to_midi(chords: str | ChordSequence, *, skeleton: str | tuple[float, ...] = 'whole_note', resolver: str | None = None, midi_gen: str | None = None, tempo: int = 120, output_path: str | None = None) MidiData[source]
Convert chords to MIDI using a rhythmic skeleton for restrike timing.
The skeleton defines when chords are struck within each measure. Each chord is sustained for the skeleton’s duration at that beat position.
- Parameters:
chords – Chord string (e.g.,
"| Dm7 | G7 | Cmaj7 |") or ChordSequence.skeleton – Skeleton key, name, style, or duration tuple. Defaults to
"whole_note"(one strike per measure).resolver – Chord resolver name (e.g.,
"pychord","tonal").midi_gen – MIDI generator name (e.g.,
"pretty_midi","midiutil").tempo – BPM (used if chords is a string).
output_path – Optional path to write MIDI file.
- Returns:
MidiData object.
Example
>>> md = rhythm_to_midi("| C | Am | F | G |", skeleton="tresillo") >>> md.to_bytes()[:4] b'MThd'
- accompy.set_chord_resolver(resolver: Callable[[str], list[int]]) None[source]
Set the default chord resolver.
This enables global customization of chord-to-notes resolution.
- Parameters:
resolver – A function that takes a chord symbol (str) and returns MIDI notes (list[int])
Example
>>> def my_resolver(symbol: str) -> list[int]: ... # Custom chord voicing logic ... return [60, 64, 67] # C major triad >>> set_chord_resolver(my_resolver)
- accompy.setup_soundfont(force: bool = False) bool[source]
Download and configure a SoundFont file.
- Parameters:
force – If True, download even if a SoundFont already exists
- Returns:
True if successful, False otherwise
Example:
from accompy.setup_utils import setup_soundfont if setup_soundfont(): print("SoundFont configured successfully!")
- accompy.tonal_resolver(symbol: str, *, transpose: int = -12) list[int][source]
Resolve chord symbol to MIDI notes using the tonal package (default).
The tonal package is lightweight and designed for music theory operations. It anchors chord roots around C4=60. We apply a default -12 semitone transpose to voice chords closer to C3=48 for better bass/piano range.
- Parameters:
symbol – Chord symbol (e.g., “Dm7”, “G7”, “Cmaj7”)
transpose – Semitone offset to apply (default: -12)
- Returns:
List of MIDI note numbers
Example
>>> notes = tonal_resolver("Cmaj7") >>> len(notes) > 0 True
Note
Requires: pip install tonal See: https://github.com/thorwhalen/tonal
- accompy.transpose_chord(chord: str, semitones: int, *, use_flat: bool | None = None) str[source]
Transpose a chord symbol by semitones.
Handles slash chords (e.g.
"C6/E").- Parameters:
chord – Chord symbol like
"Am7","C6/E","G#o".semitones – Number of semitones.
use_flat – Force flat/sharp spelling (see
transpose_note()).
Example
>>> transpose_chord("Am7", 2) 'Bm7' >>> transpose_chord("C6/E", 5) 'F6/A' >>> transpose_chord("G#o", -2, use_flat=True) 'Gbo'
- accompy.transpose_note(name: str, semitones: int, *, use_flat: bool | None = None) str[source]
Transpose a single note name by semitones.
- Parameters:
name – Note name like
"C","Eb","F#".semitones – Number of semitones (positive = up, negative = down).
use_flat – Force flat (True) or sharp (False) spelling.
None(default) uses flats for downward transposition.
Example
>>> transpose_note("C", 5) 'F' >>> transpose_note("A", -2) 'G' >>> transpose_note("C", 1, use_flat=True) 'Db'
- accompy.transpose_score(score, target_key: str) Score[source]
Transpose a
Scoreto a new key.The spelling (sharp vs flat) is chosen automatically based on the target_key.
- Parameters:
score – A
Scoreinstance.target_key – Target key, e.g.
"Eb","G","F#".
- Returns:
A new Score in the target key with an updated title.
Example
>>> from accompy import Score >>> s = Score.from_string("| C | Am | F | G |", key="C") >>> t = transpose_score(s, "G") >>> [m[0] for m in t.measures] ['G', 'E-', 'C', 'D']
- accompy.verify_and_setup(interactive: bool = True, auto_fix: bool = False) Dict[str, bool][source]
Verify all dependencies and optionally auto-configure.
- Parameters:
interactive – If True, prompt user for permission before making changes
auto_fix – If True and interactive=False, automatically fix issues without prompting
- Returns:
Dict mapping dependency name to whether it’s available
Example
>>> from accompy.setup_utils import verify_and_setup >>> status = verify_and_setup(interactive=False) >>> if all(status.values()): ... print("Ready to use!")