No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Zel9278 007f77eb82 SoundFont notes sound at least 10 ms, and black MIDI mixes faster
A SoundFont envelope starts with about a millisecond each of delay and attack,
so a note let go within it began its release from silence and was never heard
(ced lagtester.mid fell silent from 19 s). As FluidSynth's min-note-length, a
note-off or a restrike that soon lets that press go 10 ms after it began.

With every voice kept that long, a full voice pool was too slow: the mixer reads
in 32.32 fixed point instead of waiting on f64 rounding each frame, takes its
peak from the mixed quantum, and the voices a press may take are sorted once a
quantum (those let go since go first) instead of searched for every press.
Adds bench_soundfont.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QkNuMpEL54aNwaEQ7JQ3mP
2026-10-22 17:48:52 +09:00
examples CSynth v2 on its own, from midi-blocks-rs 2026-10-21 18:50:08 +09:00
src SoundFont notes sound at least 10 ms, and black MIDI mixes faster 2026-10-22 17:48:52 +09:00
.gitignore CSynth v2 on its own, from midi-blocks-rs 2026-10-21 18:50:08 +09:00
Cargo.lock CSynth v2 on its own, from midi-blocks-rs 2026-10-21 18:50:08 +09:00
Cargo.toml CSynth v2 on its own, from midi-blocks-rs 2026-10-21 18:50:08 +09:00
LICENSE CSynth v2 on its own, from midi-blocks-rs 2026-10-21 18:50:08 +09:00
LICENSE-CHROMIUM CSynth v2 on its own, from midi-blocks-rs 2026-10-21 18:50:08 +09:00
README.md SoundFont notes sound at least 10 ms, and black MIDI mixes faster 2026-10-22 17:48:52 +09:00

csynth

CSynth v2、midi-blocks-rs の MIDI Blocks と MIDI Keys のピアノを単体のクレートにしたもの。ブラウザ版 MPP のピアノ (piano-processor AudioWorklet と limiter-processor) を移植して育てた。

  • MPP のピアノのサンプル (piano-samples-ogg.bin) か、SF2 の SoundFont を鳴らす
  • SoundFont の音符は最低 10 ms 鳴らす (FluidSynth の synth.min-note-length と同じ)。それより早いノートオフや、同じ鍵の押し直しは、押してから 10 ms 後まで待って離す (そのあいだ次の押下は横で鳴る)。SoundFont のエンベロープは遅延と立ち上がりが最低でも 1 ms ずつあるので、黒 MIDI の数サンプルしかない音符は、そのまま離すと音量 0 から余韻に入って何も聞こえない
  • Web Audio と同じく 128 フレーム (QUANTUM) ずつ描画し、音符はストリームのフレーム単位で予約する
  • ベロシティは 2 乗、ボイスが足りないときは静かなものから譲る、ボイス数に応じた自動ゲイン
  • 黒 MIDI 向けに軽くしてある。SoundFont の波形を読む位置は 32.32 の固定小数点で進め、ボイスが足りないときに譲らせる候補は 1 クォンタムに 1 回だけ並べる (押下ごとに全ボイスを探さない)。SoundFont で 1 秒に 52 万回押して 1500 ボイス鳴らすのが 1 コアの 4 割ほど。BENCH_SF2=a.sf2 cargo test --release bench_soundfont -- --ignored --nocapture で測れる
  • チャンネルのボリューム / エクスプレッション / パン / モジュレーション / ピッチベンド (RPN 0 の幅) / プログラムチェンジ
  • 出力 (cpal など) は持たない。描画した音をどこに出すかは使う側が決める
  • ログは log クレートに出す (ロガーは使う側で)

使い方

[dependencies]
csynth = { git = "https://git.c30.life/ced/csynth-v2" }
# サンプルのダウンロード (ureq) が要らなければ
# csynth = { git = "https://git.c30.life/ced/csynth-v2", default-features = false }
use csynth::{PianoProcessor, QUANTUM, output_limiter, velocity_gain};

let rate = 48_000;
let mut piano = PianoProcessor::with_fonts(vec![csynth::Font::load(std::path::Path::new("piano.sf2"))?], rate);
let mut limiter = output_limiter(rate);
// 時刻はストリームのフレーム
piano.press_on_channel(0.0, 60, velocity_gain(100.0 / 127.0), 0, 0);
piano.release_on_channel(f64::from(rate), 60, 0, 0);

let (mut left, mut right) = ([0.0; QUANTUM], [0.0; QUANTUM]);
piano.process(&mut left, &mut right);
let mut interleaved: Vec<f32> = left.iter().zip(&right).flat_map(|(&l, &r)| [l, r]).collect();
limiter.process(&mut interleaved);

MPP のサンプルなら find_bundle(server, cache_path("アプリ名").as_deref(), &|status| ...) で探して (MPP_WEB_SAMPLES、作業フォルダや実行ファイルの近く、キャッシュ、なければサーバーから)、decode_bundle で出力のレートに変換して PianoProcessor::new に渡す。

動く例: cargo run --release --example render -- out.wav [piano.sf2] で、アルペジオと和音を WAV に書き出す。

feature

feature 既定 内容
download 有効 サンプルが手元になければ MPP のサーバーからダウンロードする (ureq)

ライセンス

MIT (LICENSE)。src/sinc_resampler.rs は Blink の SincResampler の移植なので、Chromium の BSD ライセンス (LICENSE-CHROMIUM) にも従う。