|
Tabula Sonora 0.1.0
A native C++20 implementation of the Roland Sound Canvas VA synth voice
|
Drives a running engine from a MIDI event list. More...
#include <tabulasonora/sequence_player.hpp>
Public Member Functions | |
| SequencePlayer (ToneGenerator &generator, std::vector< MidiEvent > events) | |
| Creates a player over an engine and an event list ordered by position. | |
| SequencePlayer (ToneGenerator &generator, smf::Song song) | |
| Creates a player over an engine and a parsed song, keeping its loop points. | |
| std::vector< int > | addressed_parts () const |
| Which parts the file actually addresses, as port * 16 + channel. | |
| ToneGenerator & | generator () noexcept |
| The engine being driven. | |
| std::int64_t | last_event_position () const noexcept |
| Position of the final event of any kind. | |
| std::int64_t | position () const noexcept |
| Current position in samples. | |
| const std::optional< smf::SongLoop > & | loop () const noexcept |
| The file's loop points, when it declared any. | |
| void | set_loop_count (int count) |
| How many times the looped section should play, counting the first pass. | |
| int | loop_count () const noexcept |
| int | loops_played () const noexcept |
| Completed passes over the loop body, counting up from zero on the initial pass. | |
| void | set_fade_seconds (double seconds) |
| The post-loop fade length. Zero cuts at the loop end. | |
| bool | at_end () const noexcept |
| Whether every event has been dispatched and nothing is still sounding, or the post-loop fade has run to silence. | |
| void | render (std::span< float > left, std::span< float > right) |
| Renders audio, dispatching every event that falls inside it. | |
| RenderResult | render_to_end (double tail_seconds=2.2, std::optional< double > end_seconds=std::nullopt) |
| Streams the whole file into memory, from wherever the player currently is. | |
| void | seek (std::int64_t sample) |
| Jumps to a position, leaving the engine in the state the file would have put it in. | |
Static Public Member Functions | |
| static SequencePlayer | from_file (ToneGenerator &generator, const std::filesystem::path &path) |
| Reads a music file and creates a player for it. | |
Drives a running engine from a MIDI event list.
The engine itself has no notion of a file: this is what turns one into the stream of events it consumes, dispatching each at the render block it falls in.
| ts::SequencePlayer::SequencePlayer | ( | ToneGenerator & | generator, |
| std::vector< MidiEvent > | events ) |
Creates a player over an engine and an event list ordered by position.
Both must outlive the player.
| ts::SequencePlayer::SequencePlayer | ( | ToneGenerator & | generator, |
| smf::Song | song ) |
Creates a player over an engine and a parsed song, keeping its loop points.
The loop points only matter once set_loop_count asks for looping; the default remains a single straight play-through.
|
staticnodiscard |
Reads a music file and creates a player for it.
Every format smf::load handles, loop points included.
|
nodiscard |
Which parts the file actually addresses, as port * 16 + channel.
A mixer wants the channels a file uses, not sixteen rows of which four are silent – and a four-port file has sixty-four to choose from, so showing them all would be worse than useless. Computed once from the event list, so it is what the file contains rather than what has been reached so far.
|
inlinenodiscardnoexcept |
The engine being driven.
|
inlinenodiscardnoexcept |
Position of the final event of any kind.
Not the same as the last note: a file commonly closes with controller or meta traffic after the music stops, and stopping at the last note clips the tail.
|
inlinenodiscardnoexcept |
Current position in samples.
|
inlinenodiscardnoexcept |
The file's loop points, when it declared any.
| void ts::SequencePlayer::set_loop_count | ( | int | count | ) |
How many times the looped section should play, counting the first pass.
The vocabulary is the reference sequencer's: 0 or 1 plays the song once straight through, which is the default; -1 loops forever; N >= 2 targets N play-throughs of the loop body, after which the music keeps looping under a fade to silence rather than trailing off mid-phrase. A file with no loop points loops whole under -1 and plays straight through under any finite count. Asking for a count at or below the play-through already reached starts the fade immediately; switching to -1 cancels a pending fade.
|
inlinenodiscardnoexcept |
|
inlinenodiscardnoexcept |
Completed passes over the loop body, counting up from zero on the initial pass.
| void ts::SequencePlayer::set_fade_seconds | ( | double | seconds | ) |
The post-loop fade length. Zero cuts at the loop end.
|
inlinenodiscardnoexcept |
Whether every event has been dispatched and nothing is still sounding, or the post-loop fade has run to silence.
| void ts::SequencePlayer::render | ( | std::span< float > | left, |
| std::span< float > | right ) |
Renders audio, dispatching every event that falls inside it.
When looping is engaged this is also where the jumps happen: a soft loop – one whose end the file marked explicitly – rewinds with an all-notes-off and nothing else, because whatever must change at the jump is written inside the loop body and re-fires on its own; a hard loop's end was inferred, so the jump replays state the way a seek does.
Throws std::invalid_argument if the two channels differ in length.
|
nodiscard |
Streams the whole file into memory, from wherever the player currently is.
The length is computed the same way the offline renderer computes it, so the two line up sample for sample. A finite loop count extends it to cover the passes and the fade; an infinite one is ignored here, because a buffer cannot hold forever.
| void ts::SequencePlayer::seek | ( | std::int64_t | sample | ) |
Jumps to a position, leaving the engine in the state the file would have put it in.
Every event up to that point is replayed except the notes themselves, so program changes, bank selects, controllers and the GS effect selections all arrive — a seek into the middle of a song sounds the way playing up to that point would, without the notes in between.
A manual seek also cancels any post-loop fade and restarts the loop counter.