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

A dependency-free Standard MIDI File reader. More...

Classes

struct  SongLoop
 Loop points a file declared, in samples on the render-block grid. More...
struct  Song
 A parsed file: the event list plus what the container knew beyond the events. More...

Functions

Song load (const std::filesystem::path &path, int sample_rate=32000)
 Reads a music file, with its loop points.
Song load (std::span< const std::uint8_t > data, int sample_rate=32000, std::string_view name={})
 Parses a music file held in memory, with its loop points.
std::vector< MidiEventread (const std::filesystem::path &path, int sample_rate=32000)
 Reads a music file, ordered by position — load without the loop points.
std::vector< MidiEventparse (std::span< const std::uint8_t > data, int sample_rate=32000)
 Parses a music file held in memory, ordered by position — load without the loop points.
std::int64_t quantise (double samples) noexcept
 Rounds a sample position onto the render-block grid.

Variables

constexpr int block_grid = 32
 Samples per render block — the grid events are applied on.
constexpr int default_tempo = 500'000
 Default tempo when a file sets none: 120 bpm, in microseconds per quarter note.

Detailed Description

A dependency-free Standard MIDI File reader.

Handles formats 0, 1 and 2, running status, system-exclusive messages, and the tempo map. Track ticks are converted to seconds through that map and then to samples, and finally quantised to the render-block grid — the granularity at which the engine actually applies events. Quantising here is what lets an offline render line up sample-for-sample with the real engine's own output.

Meta events other than tempo are consumed here or dropped: port tags pick the port scheme, markers feed the loop scanners, and none of them reach the event list.

Function Documentation

◆ load() [1/2]

Song ts::smf::load ( const std::filesystem::path & path,
int sample_rate = 32000 )
nodiscard

Reads a music file, with its loop points.

Not only Standard MIDI Files: the formats formats::to_smf recognises — RMID, MIDS, MUS, XMI, GMF, HMP, HMI, XMF, and (by file name) LDS — are converted first, so every caller understands them. Files carrying EMIDI track designations (CC 110) are filtered for a General MIDI receiver: tracks authored exclusively for some other synthesizer are dropped, as playing them doubles every voice.

◆ load() [2/2]

Song ts::smf::load ( std::span< const std::uint8_t > data,
int sample_rate = 32000,
std::string_view name = {} )
nodiscard

Parses a music file held in memory, with its loop points.

name is the file name the data came from, when the caller knows it; only LDS detection uses it. Throws std::runtime_error if the file is malformed or uses SMPTE timing.

◆ read()

std::vector< MidiEvent > ts::smf::read ( const std::filesystem::path & path,
int sample_rate = 32000 )
nodiscard

Reads a music file, ordered by position — load without the loop points.

◆ parse()

std::vector< MidiEvent > ts::smf::parse ( std::span< const std::uint8_t > data,
int sample_rate = 32000 )
nodiscard

Parses a music file held in memory, ordered by position — load without the loop points.

Throws std::runtime_error if the file is malformed or uses SMPTE timing.

◆ quantise()

std::int64_t ts::smf::quantise ( double samples)
nodiscardnoexcept

Rounds a sample position onto the render-block grid.

The rounding is to nearest with ties to even, matching .NET's Math.Round, because that is what the reference build uses. C's round and llround go half-away-from-zero instead.

Measured: for this expression the choice does not matter. Over 400,000 half-sample positions the two modes disagree at every one of the 100,000 ties and the resulting block is the same every time — the two candidates differ by one, and the floor onto a 32-sample grid absorbs that unless they straddle a multiple of 32, which a tie can never do. nearbyint is kept anyway so the expression matches the original exactly rather than by an argument that could stop holding.

The same is not true of Math.Round where no grid follows it — the delay's tap lengths are the case to watch.

Variable Documentation

◆ block_grid

int ts::smf::block_grid = 32
inlineconstexpr

Samples per render block — the grid events are applied on.

◆ default_tempo

int ts::smf::default_tempo = 500'000
inlineconstexpr

Default tempo when a file sets none: 120 bpm, in microseconds per quarter note.