"""
Real-time accompaniment support (foundation).
This module provides the foundation for real-time accompaniment playback,
separating event scheduling from synthesis. This enables integration with
real-time audio systems like hum/pyo in the future.
NOTE: This is scaffolding for future work. Current implementation focuses on
event generation infrastructure. Real-time audio synthesis integration
with hum/pyo is planned for a future release.
"""
from __future__ import annotations
from typing import Iterator, Callable, Optional, Any
from .base import Score, AccompanimentConfig, MidiEvent, ensure_score
from .renderers.midi import generate_midi_events
[docs]
class RealtimeAccompaniment:
"""
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 # doctest: +SKIP
>>> def on_event(event: MidiEvent): # doctest: +SKIP
... # Send MIDI event to synth in real-time
... synth.send_note(event.note, event.velocity, event.duration)
>>> player = RealtimeAccompaniment(config, on_event=on_event) # doctest: +SKIP
>>> player.play() # doctest: +SKIP
"""
def __init__(
self,
config: Optional[AccompanimentConfig] = None,
*,
on_event: Optional[Callable[[MidiEvent], None]] = None,
):
"""
Initialize real-time accompaniment player.
Args:
config: Accompaniment configuration (tempo, style, etc.)
on_event: Optional callback for each MIDI event (for real-time playback)
"""
self.config = config or AccompanimentConfig()
self._on_event = on_event
self._score: Optional[Score] = None
[docs]
def set_chords(self, chords: Any) -> None:
"""
Update the chord progression.
Args:
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
"""
self._score = ensure_score(chords)
[docs]
def events(self) -> Iterator[MidiEvent]:
"""
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
"""
if self._score is None:
return
# Get dependencies (with defaults)
from .patterns import get_pattern_registry
from .chord_resolution import get_chord_resolver
pattern_source = self.config.pattern_source or get_pattern_registry()
chord_resolver = self.config.chord_resolver or get_chord_resolver()
# Generate and yield events
for event in generate_midi_events(
self._score,
self.config,
pattern_source=pattern_source,
chord_resolver=chord_resolver,
):
if self._on_event:
self._on_event(event)
yield event
[docs]
def play(self) -> None:
"""
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
"""
raise NotImplementedError(
"Real-time playback is planned for a future release.\n"
"This will integrate with the hum package for real-time synthesis.\n"
"For now, use generate_accompaniment() for file-based output."
)
[docs]
def stop(self) -> None:
"""
Stop playback (future implementation).
Raises:
NotImplementedError: Real-time playback not yet implemented
"""
raise NotImplementedError(
"Real-time playback control is planned for a future release."
)
# Future integration points for hum/pyo:
#
# class PyoRealtimePlayer(RealtimeAccompaniment):
# """Real-time player using Pyo synthesis (future)."""
#
# def __init__(self, config=None):
# super().__init__(config)
# from hum.pyo_util import Synth
# # Initialize synth with appropriate settings
# self.synth = Synth(...)
#
# def play(self):
# """Start real-time playback using Pyo."""
# # Schedule MIDI events in real-time
# # Use event times to sync with audio clock
# for event in self.events():
# self.synth.schedule_note(
# event.note,
# event.velocity,
# event.duration,
# at_time=event.time
# )