| Crates.io | vibelang-sfz |
| lib.rs | vibelang-sfz |
| version | 0.3.0 |
| created_at | 2025-12-03 10:14:52.035459+00 |
| updated_at | 2026-01-12 17:57:16.659769+00 |
| description | SFZ instrument support for VibeLang |
| homepage | |
| repository | https://github.com/trusch/vibelang |
| max_upload_size | |
| id | 1963765 |
| size | 268,490 |
SFZ (Sound Font Zip) instrument support for VibeLang.
vibelang-sfz provides complete SFZ instrument loading, parsing, and playback for VibeLang. It enables using sampled instruments defined in the industry-standard SFZ format.
Key features:
nom parserpitch_keycenter┌─────────────────────────────────────────┐
│ load_sfz("path") │
│ (Rhai API entry) │
└────────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Parser │
│ (nom-based SFZ file parsing) │
└────────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ SfzInstrument │
│ (regions, opcodes, samples) │
└────────────────┬────────────────────────┘
│
┌───────┴───────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────────┐
│ Region │ │ SynthDef │
│ Matching │ │ Generation │
└─────────────┘ └─────────────────┘
parser/ - SFZ File ParsingFull SFZ format parsing with nom:
use vibelang_sfz::parser::parse_sfz;
let sfz_content = std::fs::read_to_string("instrument.sfz")?;
let parsed = parse_sfz(&sfz_content)?;
Supported sections:
<control> - Global settings<global> - Default values for all regions<group> - Group-level defaults<region> - Individual sample regionstypes/ - Core Data Structuresuse vibelang_sfz::types::{SfzInstrument, SfzRegion, SfzRegionOpcodes};
pub struct SfzInstrument {
pub name: String,
pub regions: Vec<SfzRegion>,
}
pub struct SfzRegion {
pub sample: String,
pub opcodes: SfzRegionOpcodes,
pub buf_num: Option<i32>, // Allocated buffer
pub buf_frames: Option<u32>, // Sample length
pub buf_sample_rate: Option<f32>,
}
Supported opcodes:
lokey, hikey, pitch_keycenter - Pitch mappinglovel, hivel - Velocity layersloop_mode - no_loop, one_shot, loop_continuoustrigger - attack, release, first, legatogroup, off_by - Voice stealingseq_length, seq_position - Round robintune, transpose - Fine tuningvolume, pan - Level controlampeg_* - Envelope parametersloader/ - Instrument LoadingBackend-agnostic loading with callback for buffer allocation:
use vibelang_sfz::loader::load_sfz_instrument;
let instrument = load_sfz_instrument(
"piano.sfz",
|sample_path| {
// Allocate buffer and return buf_num
Ok(allocate_buffer(sample_path)?)
}
)?;
region_matcher/ - Sample Selectionuse vibelang_sfz::region_matcher::{find_matching_regions, RoundRobinState};
let mut rr_state = RoundRobinState::new();
// Find regions for note 60, velocity 100, attack trigger
let regions = find_matching_regions(
&instrument,
60, // MIDI note
100, // Velocity
TriggerMode::Attack,
&mut rr_state,
);
synthdef/ - SynthDef GenerationCreates PlayBuf-based synthdefs for SFZ regions:
use vibelang_sfz::synthdef::create_sfz_synthdefs;
let synthdefs = create_sfz_synthdefs(&instrument)?;
// Returns one synthdef per region with correct pitch handling
Pitch calculation:
rate = target_freq / sample_root_freq
= midi_to_freq(note) / midi_to_freq(pitch_keycenter)
// Load an SFZ instrument
let piano = load_sfz("piano.sfz");
// Create a voice using it
let keys = voice("keys").sfz(piano).gain(db(-6));
// Use in melodies
melody("chords")
.on(keys)
.notes("C4 E4 G4 - | - - - - |")
.start();
Missing pitch_keycenter - Some SFZ files don't specify pitch_keycenter, causing pitch issues. Workaround: Edit the SFZ file or use filename inference.
NOTE_OFF handling - Melodies don't auto-schedule NOTE_OFF, which can cause SFZ voices to hang. See CLAUDE.md for workarounds.
Opcode coverage - Not all SFZ opcodes are implemented. Focus is on the most common ones.
MIT OR Apache-2.0