Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Spinel x Raspberry Pi Pico 2でシンセサイザーを作ってみた #nag...

Avatar for Ryo Ishigaki Ryo Ishigaki
September 16, 2026

Spinel x Raspberry Pi Pico 2でシンセサイザーを作ってみた #nagoyark05

名古屋Ruby会議05 LT https://regional.rubykaigi.org/nagoya05/ #nagoyark05

※実際のSPMS-1ライブ演奏は、都合により舞台袖で行いました。PC用のEditorアプリが使えず、PRMC-1だけで演奏しました。

スライド中のURL:
- Ryo Ishigaki https://x.com/risgk
- Ruby x Arduinoでシンセサイザーを作ってみた https://gist.github.com/risgk/0db52ea683530652d933
- PRA32-U2 https://github.com/risgk/digital-synth-pra32-u2
- 『ラズベリー・パイPico/Pico W攻略本』 https://interface.cqpub.co.jp/magazine/2023pico/
- MIDI Controller PRMC-1 https://github.com/risgk/midi_controller_prmc1
- Making a MIDI controller device with PicoRuby/R2P2 https://speakerdeck.com/risgk/r2p2-rubykaigi-2025-lt
- Supplement to “Making a MIDI controller device with PicoRuby/R2P2” https://speakerdeck.com/risgk/r2p2-rubykaigi-2025-shi-hou-mian-qiang-hui-oto
- Spinel https://github.com/matz/spinel
- MIDI Synthesizer SPMS-1 https://github.com/risgk/midi_synthesizer_spms1
- 『Pythonではじめる音のプログラミング』 https://www.ohmsha.co.jp/book/9784274228995.html
- Complex Nonlinearities Episode 4: Nonlinear Biquad Filters https://jatinchowdhury18.medium.com/complex-nonlinearities-episode-4-nonlinear-biquad-filters-ae6b3f23cb0e

Avatar for Ryo Ishigaki

Ryo Ishigaki

September 16, 2026

More Decks by Ryo Ishigaki

Other Decks in Programming

Transcript

  1. 自己紹介 • Ryo Ishigaki (石垣 良) • https://x.com/risgk • Hamamatsu.rb

    (ハママツルビー) メン バー • “Ruby誕生の地は物理的には浜松市” • 組込みソフトウェア開発者 • プロジェクター、切削加工機、音響 機器など • 趣味で、主にマイコンで動くシンセサイ ザーを作っています • 名古屋Ruby会議への前回の参加は03 (2017年) 4/23
  2. シンセサイザー作りの始まり • 浜松は「楽器のまち」として知られてい ます • 私がシンセサイザー(シンセ)作りを始 めたキッカケのひとつは、浜松に引っ越 したことです • 2014年、私は最初のシンセVRA8のプロ

    トタイプをRubyで作り、Arduino UNO Rev3で動くようにC++に移植しました • Ruby x Arduinoでシンセサイザーを 作ってみた(2015年、浜松Ruby会議 01) https://gist.github.com/risgk/0db 52ea683530652d933 5/23
  3. シンセサイザー作りと演奏 • ISGK Instruments名義で、主にC++で年に1 台ほどシンセを作り、Maker Faireなどのイベ ントで展示しています • PRA32-U2(Raspberry Pi

    Pico 2 用) https://github.com/risgk/digital-synthpra32-u2 • シンセ製作記事を書いたこともあります • 『ラズベリー・パイPico/Pico W攻略本』 https://interface.cqpub.co.jp/magazine /2023pico/ • 少しだけ、ハードウェアシンセを使った演奏の 「マシンライブ」を行っています • シンセカイリアル(Yasushi.Kさん主催) • RubyKaigi 2025 LT • HR Music Arena(RubyKaigi 2026) 6/23
  4. MIDI Controller PRMC-1(2025年) • PicoRubyで作ったライブ用MIDIコントローラ、 RubyKaigi 2025 LTでtype-0を発表 • https://github.com/risgk/midi_controll

    er_prmc1 • https://speakerdeck.com/risgk/r2p2rubykaigi-2025-lt • やっと、mrubyファミリーを使った作品を 作れました! • RubyKaigi 2025 事後勉強会 otoでは、今後の テーマとして「Rubyのコードで音響合成する シンセをマイコンで動かす」をあげました • https://speakerdeck.com/risgk/r2p2rubykaigi-2025-shi-hou-mian-qianghui-oto • Rubyで演奏するのもたのしいですが、私 も久しぶりでRubyでゼロから音を作りた くなりました!(マイコンも使って!) 7/23
  5. Raspberry Pi Pico 2 マイコンボード Arduino UNO Rev3 Raspberry Pi

    Pico 2 発売年 2012年 2024年 価格 約3,000円(互換品は数百円~) 約1,000円 マイコン ATmega328P RP2350A CPUコア 8ビット AVR 16 MHz 1コア 32ビット ARM Cortex-M33 150MHz 2コア FPU(浮動小数点演算ユニット) なし あり(単精度、倍精度) フラッシュメモリ、SRAM 32 KB、2 KB 4 MB、520 KB 使用可能なRuby処理系 基本的に無し PicoRuby、mruby、mruby/c • Pico 2は現在最も広く使われているボードのひとつで、現在のPicoRubyの公式ボード • Arduino UNO Rev3よりも遥かに強力! 浮動小数点演算も高速! 8/23
  6. 試行錯誤 • その1:Raspberry Pi Pico 2とPicoRuby(当時のVMはmruby/c)を使っ て、Rubyのコードで音を合成するシンセサイザーを作ってみる • リアルタイムなデジタル信号処理(DSP)は厳しかったです… •

    「高速UART出力で波形を作る」アイデアを検討しましたが、お蔵入り • その2:PicoRubyデバイスから「MIDIで自由に制御できる」ノードベース のオーディオシステムを(C++で)作ってみる • Web Audio APIなどが参考になります • 実現できそうですが、やりたかった「Rubyで音のプログラミング」か らは遠ざかる感じが… 9/23
  7. Spinel • RubyKaigi 2026最終日、4月24日に公開 された、RubyのAOTコンパイラ • https://github.com/matz/spinel • Rubyのソースコードをスタンドアロンの ネイティブ実行ファイルにコンパイル

    • プログラム全体の型推論を行い、最適化 されたCコードを生成することで、CRuby を大幅に上回る高速化を実現 • Spinelが生成したCコードなら、マイ コンでリアルタイム信号処理が可能 なハズ!(ランタイムはマイコン用 に改造) Ruby (.rb) | v C source (.c) | v Native binary 10/23
  8. MIDI Synthesizer SPMS-1 (type-0) • SpinelとRaspberry Pi Pico 2で作った、減 算方式の単音シンセサイザー

    • https://github.com/risgk/midi_synt hesizer_spms1 • バイナリファイル(UF2)も同梱 • 48kHz/24bit、レイテンシ約4ms • USB/UART MIDIで制御される音源モ ジュール • I2S DACはPico Audio Pack(または GY-PCM5102) • 信号処理コードはすべてRuby! • MIDI入力やDAC出力はC++(ArduinoPicoコア環境) • SpinelランタイムはC言語 11/23
  9. MIDI (Musical Instrument Digital Interface) PicoRubyで作ったMIDI Controller • 電子楽器をつなぐ、40年以上使わ れている共通規格

    • 「どう演奏するか」を数バイトの MIDIメッセージで送信 • Note On/Off:ある高さの鍵盤 をある速さで押した/離した • Control Change(CC):音色 パラメータの調整値 • 物理層は31.25 kbaudのUARTで、 今ではUSB MIDIも一般的 PRMC-1 (type-8) ↓ UART MIDI USB MIDI Interface ↓ USB MIDI SPMS-1 (type-0) Editor –– Web App ↓ USB MIDI Spinelで作ったMIDI Synthesizer SPMS-1 (type-0) 12/23
  10. spms1_type0.ino(Arduinoスケッチ)より • 最適化オプション指定、コードをSRAMに配置 #pragma GCC optimize ("O3") • CPUコア0(MIDI受信、デバッグプリント) …

    … #pragma GCC section text=".time_critical" } • CPUコア1(SPMS-1のmain関数を処理) void setup1() { void loop() { void setup() { … } USB_MIDI.read(); … SPMS1_DEBUG_PRINT_SERIAL.print( void loop1() { g_debug_measurement_min_us); Spms1_main(0, NULL); } … } 15/23
  11. sp_runtime.h(改造版)より #define main __attribute__((section(".time_critical"), flatten)) Spms1_main #pragma GCC optimize("single-precision-constant") #pragma

    GCC optimize ("O3") … #pragma GCC diagnostic ignored "-Wunused-parameter" … #define double float • 7月18日のコミット 5af61ae を改造 #define sin(x) sinf(x) • 約10ファイルで約300行の修正 #define cos(x) cosf(x) • Regexp、Thread、Fiberは使えません … • Spinelが生成するmain関数をリネーム //static inline mrb_int sp_process_ppid(void) { • コードをSRAMに配置 // return (mrb_int)getppid(); • doubleを単精度floatに置き換え //} 16/23
  12. spms1_main.rbより・その1 module Spms1 module C ffi_func :set_midi_note_on_pitch, [:uint8, :uint8], :void

    ffi_func :get_midi_note_on_pitch, [:uint8], :uint8 • FFI(Foreign Function Interface)で Cの関数を呼べます … ffi_func :set_midi_cc_value, [:uint8, :uint8, :uint8], :void ffi_func :get_midi_cc_value, [:uint8, :uint8], :uint8 ffi_func :start_audio, [], :void ffi_func :stop_audio, [], :void ffi_func :write_to_audio_buffer, [:float, :float], :void ffi_func :start_debug_measure, [], :void ffi_func :stop_debug_measure, [], :void … end end 17/23
  13. spms1_main.rbより・その2 signals = Array.new(SIGNALS_SIZE, 0.0) • Float値0.0を指定して配列生成することで、型推論が働き、 GCの対象になるヒープメモリ割当を回避できます • GCが作動したら、信号処理はほぼ破綻します!

    … while i < AUDIO_BUFFER_WORDS … while slot < MODULES_SIZE module_id = active_modules[slot] … • 1サンプル(1/48000秒)毎に、アクティブなモジュールを順番に処理 case module_id … when MODULE_OSC_1 • source_osc_1_modなどの入力ソース設定をMIDIで変更できます • 処理順が後のモジュールの出力信号も、1サンプル遅延しますが入力にできます signals[SIGNAL_OSC_1_OUTPUT] = osc_1.process(signals[source_osc_1_pitch], signals[source_osc_1_mod]) when MODULE_FILTER_1 signals[SIGNAL_FILTER_1_OUTPUT] = filter_1.process(signals[source_filter_1_audio], signals[source_filter_1_mod]) when MODULE_AMP_1 signals[SIGNAL_AMP_1_OUTPUT] = amp_1.process(signals[source_amp_1_audio], signals[source_amp_1_mod]) end 18/23
  14. spms1_main.c(AOTコンパイル結果)より else if ((_t312 == cst_MODULE_OSC_1)) { #line 620 "spms1_main.rb"

    mrb_float _t314 = sp_FloatArray_get(lv_signals, lv_source_osc_1_pitch); mrb_float _t315 = sp_FloatArray_get(lv_signals, lv_source_osc_1_mod); sp_FloatArray_set(lv_signals, cst_SIGNAL_OSC_1_OUTPUT, sp_Osc_process((sp_Osc *)lv_osc_1, _t314, _t315)); } else if ((_t312 == cst_MODULE_FILTER_1)) { • クラス毎に別々の処理関数にコンパイルされています • Rubyの配列アクセスでは長さチェックが入るので、徹底的に最適化 したCコードの性能に追い付くのは難しそうです … } else if ((_t312 == cst_MODULE_AMP_1)) { • Spinelを意識した最適化も、Claude CodeなどのAIに相談できます • ただし、Spinelのバージョンでポイントは変わるでしょう #line 624 "spms1_main.rb" mrb_float _t318 = sp_FloatArray_get(lv_signals, lv_source_amp_1_audio); mrb_float _t319 = sp_FloatArray_get(lv_signals, lv_source_amp_1_mod); sp_FloatArray_set(lv_signals, cst_SIGNAL_AMP_1_OUTPUT, sp_Amp_process((sp_Amp *)lv_amp_1, _t318, _t319)); } 19/23
  15. spms1_osc.rb(オシレータクラス)より def process(pitch_input = 0.0, modulation_input = 0.0) … naive_saw1

    = -2.0 * @phase + 1.0 blep1 = poly_blep(@phase, current_dt, current_dt_inv) saw1 = naive_saw1 + blep1 … output = saw1 - (saw2 * @current_waveform) @phase += current_dt @phase -= (@phase < 1.0) ? 0.0 : 1.0 … end • 波形の角を丸めてエイリアスノイズを低減するPolyBLEP法でノコギリ波を生成 • 参考:青木直史『Pythonではじめる音のプログラミング』オーム社 (2022年)https://www.ohmsha.co.jp/book/9784274228995.html • 何らかのアンチエイリアシングを行わないと、高い音でノイズが目立ちます! • ノコギリ波の減算で矩形波を生成 • 処理時間が安定するように、Rubyコードはなるべく 条件分岐なし(ブランチレス)のマシンコードにな りやすいように調整しています 20/23
  16. spms1_filter.rb(フィルタクラス)より def process(audio_input = 0.0, modulation_input = 0.0) … if

    @sample_counter == 0 update_coefficients end … # Transposed Direct Form II (TDF-II) biquad implementation with soft clipping. audio_output = @z1 + @b0 * driven_input @z1 = soft_clip(@z2 + @b1 * driven_input - @a1 * audio_output) @z2 = soft_clip(@b2 * driven_input - @a2 * audio_output) … clip_output(audio_output) end • オーディオ処理でよく使われる、バイクアッド(biquad)フィルタ実装です • さらに、Jatin Chowdhuryさんのnonlinear biquad手法で、フィードバック成分や 出力をソフトクリップ(飽和)させています • Complex Nonlinearities Episode 4: Nonlinear Biquad Filters https://jatinchowdhury18.medium.com/complex-nonlinearities-episode-4nonlinear-biquad-filters-ae6b3f23cb0e • フィルタ係数の更新はサインやコサインを使っていて負荷が高いので、1/12000秒 毎に処理しています(制御レート処理) 21/23
  17. まとめと今後 • Rubyコードを使った音のプログラミングはたのしかったです! • 私にとって初めてのfloat演算シンセでした • SPMS-1の開発を続けます • type-1以降での機能追加を考えています •

    パッチエディタやWeb Audio API環境も作りたいです • 開発しながら、Ruby、Spinelでオーディオ処理する意味を考えていきます • Spinelの改善ネタも集めていきます 22/23