Instruments
An instrument turns the player’s timed events into sound for the tracks given to it: a MIDI synthesiser, a modelled guitar amplifier, sampled drums. The core plays the score: it turns it into a timeline of notes with every effect, tempo, the metronome and count-in, runs the transport, and mixes the instruments’ sound (volume, pan, mute, solo) before an audio-device service sends it to the system.
The instrument interface is interface 1.3 (OxiGuitar 0.9 and later). The player ignores instruments declared by a plugin built for an older interface.
How an instrument works
- Prepared, off the audio thread. When a song is opened or its sounds change, the core asks the instrument to get ready for the tracks it plays (
Instrument::prepare, given aPrepare: the sample rate, the largest piece it will be asked for, and each track’s program, tuning, capo and its track section’s settings). Load the sounds now, and touch their memory, so nothing waits later. It hands back aRenderer. - Rendered, on the audio thread. Each device callback is split at every event, and each piece goes to
Renderer::renderas aPiece:events()all happen on the piece’s first frame: a note starts (RtEventKind::NOTE_ON), ends (NOTE_OFF, the same note, byid), or every note stops at once (ALL_OFF: stop, seek, loop). A piece may have no frames, only events.- A note (
RtNote) carries what it means, not MIDI: its track’s place, key, velocity, string, sound (normal, dead, palm mute, harmonic of a kind), attack (picked, legato, tapped, slapped, popped), flags (ghost, let ring, fade in or out, grace, vibrato), and its start and end in the score’s seconds. Its pitch curve (bends, the whammy bar, slides) ispiece.pitch_of(¬e), andpiece.cents_at(¬e, frame)follows it frame by frame, whatever the speed. The curves are runs of one array,piece.pitch(), which stays put while anything sounds, so a renderer that follows a note across calls keeps the note (or itspitch_startandpitch_len) and finds its run again in the next call’s array (in the SDK of 0.9.0 and later). piece.track(slot)is a track’s left and right buffers, zeroed: add into them.time()andstep()give the score’s time at the first frame and its seconds per frame, which change with the speed. The pitch never does: playing slower re-times the notes.
- Released, when the core is done with it, off the audio thread: the renderer is dropped.
Two numbers describe it: latency(), the frames its sound comes late by (the cursor moves back by it), and resolution(), the most frames late a note may start (a synthesiser rendering in blocks of 8 starts notes on the next block: 7). Both default to 0.
The audio thread’s rules: in render, never allocate, lock, wait or make a system call. A device callback lasts a few milliseconds, and a late one is a click the listener hears. The contract suite checks each instrument: it prepares, a note starts within its resolution and not before, nothing is allocated while it renders (the SDK installs a counting allocator in every .oxp, so don’t set a #[global_allocator] of your own), and on Linux no system call is made while it renders. A lock no other thread holds makes no system call, so the suite can’t see that one: keep locks off the audio thread altogether.
Effects
An effect works on one track’s sound after its instrument, in the order the track lists them: NAM is the first. Declare it under effects (the same shape as an instrument, by class), and implement Effect::prepare, which hands back a Processor: process(left, right) changes the track’s buffers in place, under the same rules.
Tracks and classes
-
One instrument per track. Each track chooses its instrument, with one default for the rest.
-
Instrument classes. The manifest says which tracks an instrument suits, from a fixed set of classes the core derives from each track’s General MIDI program and drum flag:
Class Tracks frettedGuitars and basses with frets, banjo, sitar fretlessFretless bass, shamisen, koto bowedViolin, viola, cello, contrabass, string ensembles, fiddle keyboardPianos, organs, chromatic percussion voiceChoirs and voices windsBrass, reeds, pipes, bagpipe, shanai synthSynth basses, leads, pads and effects drumsDrum-kit tracks otherHarp, timpani, orchestra hit, kalimba, percussive and sound effects "instruments": [ { "id": "kit", "name": "Realistic drums", "plays": ["drums"] }, { "id": "synth", "name": "Everything but drums", "does_not_play": ["drums"] } ]playslists what it covers (absent: everything);does_not_playtakes classes out. The player offers an instrument only for the tracks it plays. -
Large data (sound sets, amplifier models) is files the user puts in a folder, such as the
soundfontsfolder besideplugins; OxiGuitar and our plugins download nothing (Mateus, 21 Oct 2026). What a plugin makes from them (a cache) goes in its data folder. The player’s design: docs/design/player.md.