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
to_wav_bytes() bytes[source]

Convert to WAV file bytes.

write(path: str) str[source]

Write audio to a WAV file. Returns the path.

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.

to_score()[source]

Convert to an accompy Score for pattern-based accompaniment.

Example

>>> cs = ChordSequence([("Dm7", 4.0), ("G7", 4.0)])
>>> score = cs.to_score()
>>> len(score)
2
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.

list_pairs() list[tuple[str, str]][source]

List all registered (source, target) type name pairs.

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

set_default(source_type: type, target_type: type, name: str) None[source]

Change the default converter for a type 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
at_tempo(tempo: int) float[source]

Return pattern duration in seconds at a given tempo.

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
to_bytes() bytes[source]

Get MIDI as bytes, converting from pretty_midi if needed.

to_pretty_midi()[source]

Get as pretty_midi.PrettyMIDI, converting from bytes if needed.

write(path: str) str[source]

Write MIDI to a file. Returns the path.

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']
available_styles() list[str][source]

List all available style names.

Returns:

List of style names

get_patterns(style: str) dict[str, list][source]

Get patterns for a style.

Parameters:

style – Style name (e.g., “swing”, “bossa”, “rock”)

Returns:

Dict with ‘drums’, ‘bass’, ‘comp’ keys containing pattern lists

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

set_chords(chords: Any) None[source]

Update the chord progression.

Parameters:

chords – Chord progression in any supported format (string, Score, list of tuples, etc.)

Example

>>> player = RealtimeAccompaniment()
>>> player.set_chords("| Dm7 | G7 | Cmaj7 |")
>>> player._score is not None
True
stop() None[source]

Stop playback (future implementation).

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']]
to_chord_sequence(*, tempo: int = 120)[source]

Convert Score to a ChordSequence (from the converter pipeline).

Each chord gets a duration proportional to its share of the bar.

Example

>>> score = Score.from_string("| Dm7 | G7 | C^7 |")
>>> cs = score.to_chord_sequence(tempo=160)
>>> len(cs)
3
class accompy.ScoreToWav(*args, **kwargs)[source]

Callable that renders a Score to a WAV file.

Any function matching this signature can be used as a score_to_wav engine in generate_wav() and generate_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

render_to_bytes(midi_path: Path, *, sample_rate: int = 44100) bytes[source]

Render a MIDI file to PCM bytes (for streaming).

Parameters:
  • midi_path – Path to MIDI file

  • sample_rate – Audio sample rate

Returns:

Raw PCM audio bytes

render_to_file(midi_path: Path, output_path: Path, *, sample_rate: int = 44100) Path[source]

Render a MIDI file to audio.

Parameters:
  • midi_path – Path to MIDI file

  • output_path – Where to save audio

  • sample_rate – Audio sample rate

Returns:

Path to the created audio file

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"). Run mma -Dg to list available grooves.

Parameters:
  • score – A Score with 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_wav callable to use a different backend — for instance one built with make_converter_engine().

Parameters:
  • score – A Score with 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_json(name: str)[source]

Read a resource file as JSON.

accompy.load_resource_lines(name: str) list[str][source]

Read a resource file as a list of non-empty stripped lines.

accompy.load_resource_text(name: str) str[source]

Read a resource file as text.

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 ScoreToWav engine from accompy’s converter pipeline.

This wraps the ChordSequence NoteSequence MidiData AudioData converter chain into the same interface that generate_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 ScoreToWav protocol.

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"). Run mma -Dg to list available grooves.

Parameters:
  • score – A Score with 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 a Score.

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_url first, then falls back to constructing a Tune directly (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_diagnostic_report()[source]

Print a comprehensive diagnostic report.

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_enhanceTrue for default Suno enhancement, False/None to 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 Score to a new key.

The spelling (sharp vs flat) is chosen automatically based on the target_key.

Parameters:
  • score – A Score instance.

  • 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!")