Tabula Sonora 0.1.0
A native C++20 implementation of the Roland Sound Canvas VA synth voice
Loading...
Searching...
No Matches
ts::SequencePlayer Class Reference

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.
ToneGeneratorgenerator () 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.

Detailed Description

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.

Constructor & Destructor Documentation

◆ SequencePlayer() [1/2]

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.

◆ SequencePlayer() [2/2]

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.

Member Function Documentation

◆ from_file()

SequencePlayer ts::SequencePlayer::from_file ( ToneGenerator & generator,
const std::filesystem::path & path )
staticnodiscard

Reads a music file and creates a player for it.

Every format smf::load handles, loop points included.

◆ addressed_parts()

std::vector< int > ts::SequencePlayer::addressed_parts ( ) const
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.

◆ generator()

ToneGenerator & ts::SequencePlayer::generator ( )
inlinenodiscardnoexcept

The engine being driven.

◆ last_event_position()

std::int64_t ts::SequencePlayer::last_event_position ( ) const
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.

◆ position()

std::int64_t ts::SequencePlayer::position ( ) const
inlinenodiscardnoexcept

Current position in samples.

◆ loop()

const std::optional< smf::SongLoop > & ts::SequencePlayer::loop ( ) const
inlinenodiscardnoexcept

The file's loop points, when it declared any.

◆ set_loop_count()

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.

◆ loop_count()

int ts::SequencePlayer::loop_count ( ) const
inlinenodiscardnoexcept

◆ loops_played()

int ts::SequencePlayer::loops_played ( ) const
inlinenodiscardnoexcept

Completed passes over the loop body, counting up from zero on the initial pass.

◆ set_fade_seconds()

void ts::SequencePlayer::set_fade_seconds ( double seconds)

The post-loop fade length. Zero cuts at the loop end.

◆ at_end()

bool ts::SequencePlayer::at_end ( ) const
inlinenodiscardnoexcept

Whether every event has been dispatched and nothing is still sounding, or the post-loop fade has run to silence.

◆ render()

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.

◆ render_to_end()

RenderResult ts::SequencePlayer::render_to_end ( double tail_seconds = 2.2,
std::optional< double > end_seconds = std::nullopt )
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.

◆ seek()

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.


The documentation for this class was generated from the following file: