Voice

class paeonia.Voice(bars=None, *, default_tonality=None, tonality_plan=None, name=None)[source]

Bases: object

An ordered collection of bars forming one musical part.

A voice owns its bar list but does not copy the individual, mutable Bar objects. Its default tonality provides a fallback tonal context, while a TonalityPlan can override that context at selected bar indices. These tonal fields are metadata; construction does not alter pitches or bar-level tonalities.

Parameters:
  • bars (Iterable[Bar] | None) – Initial bars. The iterable is materialized into a fresh list.

  • default_tonality (Tonality | None) – Fallback tonal context for bars without a more specific assignment.

  • tonality_plan (TonalityPlan | Mapping[int, Tonality] | None) – Tonality changes keyed by zero-based bar index. Mappings are converted to an immutable TonalityPlan.

  • name (str | None) – Optional display name for the voice.

Raises:
  • TypeError – If a bar, tonality, plan, plan value, or name has the wrong type.

  • ValueError – If the tonality plan contains an invalid bar index.

tonality_at(bar_index, *, inherited=None, inherited_plan=None)[source]

Resolve the effective tonality at a bar index.

Resolution proceeds from the most local context to the least local: a bar override, the voice’s tonal plan, its default tonality, an inherited plan, and finally the inherited default. Plan changes remain active after their change index.

Parameters:
  • bar_index (int) – Zero-based index of a bar in this voice.

  • inherited (Tonality | None) – Default tonality inherited from a containing score.

  • inherited_plan (TonalityPlan | None) – Score-level tonal changes indexed by bar position.

Returns:

Resolved tonality, or None if no level defines one.

Return type:

Tonality | None

Raises:
  • TypeError – If an index or inherited tonal context has the wrong type.

  • IndexError – If bar_index does not identify a bar in this voice.

apply_tonality(target, source=None, *, inherited=None, inherited_plan=None, chromatic='preserve_alteration', degree_policy='error')[source]

Reinterpret every bar in a single target tonality.

Source tonalities are resolved independently for each bar. An explicit source argument has highest priority, followed by the bar’s own tonality, this voice’s plan, this voice’s default tonality, an inherited plan, and finally the inherited fallback. Plan changes persist from their change index onward according to TonalityPlan.at().

The operation is immutable. Each transformed bar has its tonality cleared because the returned voice stores target as its default tonality. The returned voice also has an empty tonality plan and keeps this voice’s name. Pitches, rests, chords, durations, velocity, and tie metadata otherwise follow Bar.apply_tonality().

Parameters:
  • target (Tonality) – Tonality in which every transformed pitch is realized.

  • source (Tonality | None) – Optional source tonality forced across every bar. When omitted, the per-bar precedence chain described above is used.

  • inherited (Tonality | None) – Lowest-priority tonal context inherited from a containing object.

  • inherited_plan (TonalityPlan | None) – Inherited tonal changes indexed by this voice’s bar positions.

  • chromatic (str) – Chromatic analysis policy passed to Bar.apply_tonality(): "preserve_alteration", "error", or "nearest".

  • degree_policy (str) – Policy for differing source and target degree counts, passed to Bar.apply_tonality(): "error" or "wrap".

Returns:

A new voice expressed in target.

Return type:

Voice

Raises:
  • TypeError – If a supplied tonal context or plan has the wrong type.

  • ValueError – If a policy is unknown, no source tonality can be resolved for a bar, or a bar cannot be mapped under the selected policies.

span()[source]

Return the voice’s total duration in whole-note units.

The result is the sum of every bar’s span. Rests contribute their durations like pitched notes, and an empty voice has a span of zero.

Returns:

Exact total duration across all bars.

Return type:

Fraction

bar_spans()[source]

Return each bar’s duration in voice order.

Empty bars contribute Fraction(0). An empty voice therefore returns an empty tuple.

Returns:

Exact duration of each bar in whole-note units.

Return type:

tuple[Fraction, …]

add_bar(bar)[source]

Add a new bar to this voice.

Parameters:

bar (Bar) – A Bar object

Return type:

None

fill(notes)[source]

Replace this voice’s bars with events from notes.

Existing bars provide the finite rhythmic structure and explicit bar tonalities. Events are consumed in order, split and tied where they cross bar boundaries, and consecutive rests are combined within each bar. The voice’s default tonality, tonal plan, and name are unchanged.

The operation mutates this voice by replacing its bar entries, but it does not mutate any existing Bar. Retaining the underlying bar sequence also allows a voice obtained from a score slice to update the corresponding bars in the original score. Filling is atomic: if event validation or exhaustion fails, this voice remains unchanged.

Parameters:

notes (Iterable[Note]) – Finite or infinite source of notes, chords, and rests.

Returns:

This modified voice, allowing method chaining.

Return type:

Voice

Raises:
  • TypeError – If the source produces an object that is not a note.

  • ValueError – If the source ends before all existing bars are filled.

to_midi(tpb=480)[source]

Return MIDI messages for the complete voice.

Conversion is delegated to midi.voice_to_midi_messages(). That renderer is responsible for carrying the playback offset from one bar to the next so events remain correctly ordered across bar boundaries.

Parameters:

tpb (int, default=480) – MIDI ticks per beat.

Returns:

MIDI messages emitted by the voice renderer.

Return type:

list

to_lilypond(*, inherited=None, inherited_plan=None)[source]

Return LilyPond notation for the voice in its score context.

Rendering is delegated to lilypond.voice_to_lilypond(). The renderer walks bars in order, resolves the effective tonality at each bar using the voice and inherited score contexts, and emits a key signature only when that resolved tonality changes.

Parameters:
  • inherited (Tonality | None) – Default tonality inherited from the containing score.

  • inherited_plan (TonalityPlan | None) – Score-level tonal changes indexed by bar position.

Returns:

LilyPond notation emitted by the voice renderer.

Return type:

str

show()[source]

Render and display this voice, then return it.

Display is delegated to playback.show_voice(). Returning the voice supports fluent use in notebooks.

Returns:

This voice, unchanged.

Return type:

Voice

play(tpb=480, autoplay=False)[source]

Render and play this voice, then return it.

Playback is delegated to playback.play_voice().

Parameters:
  • tpb (int, default=480) – MIDI ticks per beat.

  • autoplay (bool, default=False) – Whether the playback helper should start playback immediately.

Returns:

This voice, unchanged.

Return type:

Voice