製作モジュール一覧

2026年8月24日月曜日

NuVCOに実験用UIを作る ― 増えてきた音作りのアイデアを試したい

前回までで、NuVCOは11 Voiceまで動作するようになり、Sine生成の処理時間についてもCycle Counterで測定し、LUT化まで行いました。

基本的なVCOを動かすこと以外にも、いろいろ試してみたいことが増えてきました。

例えば、現在考えているものには次のようなものがあります。

  • Detune Range
    Detuneの可変域を設定する。

  • Voice Spread
    Detuneの広がり方を設定する。

  • Pair Morph
    Sine → Triangle → Square → Sawtoothという一続きのMorphだけでなく、Sine → Square、Triangle → Sawtoothなど、2つの波形を選んでMorphする。

  • Voiceの初期phase
    複数Voiceを同じphaseから始めるか、Voiceごとにphaseをずらすかなどを設定する。波形の重なり方が変わるので、音の立ち上がりや厚みなどに違いが出る可能性がある。

  • Stereo / Pan
    MorphingやDetuneしたVoiceをL/Rへ振り分け、Stereoの広がりを持たせる。

  • Sub
    1 Octave下の波形を生成してMixする。

これらは私にとって未知の世界です。

名前や考え方は分かっていても、NuVCOで実際に音を出したときにどういう効果になるのかは、試してみないと分かりません。

最初のNuVCOの操作系

最初のNuVCOの設計では、

  • Tune POT

  • Wave POT

  • Detune POT

  • USER1 SW

  • USER2 SW

を使っていました。

Tune POTは基準周波数、Wave POTはSine → Triangle → Square → SawtoothのMorph、Detune POTは複数VoiceのDetune量を操作します。

USER1 SWではVoice数を、

1 / 2 / 3 / 5 / 7 / 9 / 11

と切り替え、USER2 SWではTune Rangeを切り替えるようにしました。

この方法で機能を増やしていく場合、機能ごとにPOTやSWを追加する必要があります。

部品や配線が増えるだけでなく、Nucleo-G431KB側もADCやGPIOとして自由に使えるpinには限りがあります。

UI用のI2Cを追加したときにも、pinの制約が実際に出てきました。

当初はI2C2を、

  • SCL: PA9

  • SDA: PF0

で使う予定でした。

ところがPF0が実機でうまく使えなかったため、SDAをPA8へ変更しました。

PA8は、それまでUSER1 SWのVoice数切り替えに割り当てていたpinです。

そのためI2Cを組み込んだ直後は、USER1 SWによるVoice数切り替えが使えなくなりました。

今はVoice切り替えをMCP23017側へ移しています。

このように、POTやSWを単純に増やしていく方法では、部品数だけでなくMCUのpin資源にも制約があります。

実験用UIを作る

まだNuVCOに搭載する音作り機能は絞れていません。

そこで、最終形のUIを決める前に、いろいろな機能を試すための実験用UIを用意しました。

現在の状態を確認するためにOLEDを使い、必要な設定値も表示します。

値の変更にはRotary Encoderを使います。

1個のPOTで複数のパラメータを切り替えて操作する方法もありますが、POTには物理的な位置があります。

例えば、あるパラメータを大きな値に設定してPOTが右側にある状態で別のパラメータへ切り替えると、POTの位置と新しいパラメータの値が一致しません。

Rotary Encoderなら絶対的な位置を持たないので、表示している現在値を基準に増減できます。

OLEDで現在の項目と値を表示し、Encoderで変更する方式なら、操作するパラメータが増えても主にFirmware側の変更で対応できます。

さらにMCP23017を使い、SwitchやLEDも追加できるようにしています。

NuVCO UI Expander

NuVCOの音作りを試すために作ったUI Expander Proto-A。OLED、Rotary Encoder、MCP23017、5個のSwitchとLEDを搭載しています。

現在は、OLED、Rotary Encoder、MCP23017、Switch、LEDを NuVCO UI Expander Proto-A にまとめています。

NuVCO本体とはAdapter基板を介して接続します。

このUIは最終形ではなく、Detune RangeやVoice Spreadなど、これから試してみたい機能を操作するための実験用です。

機能を増やしても、すぐに専用POTや専用SWを追加する必要はなく、Firmware上で項目を追加し、OLEDで状態を確認しながらEncoderやSwitchで操作できます。

次回は、この実験用UIで使っているOLED、Rotary Encoder、MCP23017をどのように試し、NuVCOへ組み込んでいったかを書こうと思います。

2026年8月16日日曜日

NuVCO 11 Voice Sineはなぜ壊れたのか ― Cycle Counterで処理時間を測る

前回は、STM32G431でNuVCOを11 Voice化したとき、Sineだけ波形が破綻した話を書きました。

前回の記事:
https://dad8893.blogspot.com/2026/08/nuvco11-voice-fpu.html

Triangle、Saw、Squareは11 Voiceでも動くのに、Sineだけは9 Voiceまでは正常で、11 Voiceにすると波形が崩れます。

当初はSineの生成にC標準ライブラリの sinf() を使っていました。

static float AppWaveform_GenerateSine(float phase) { return sinf(phase * two_pi); }

11 Voiceでは、この sinf() の計算が間に合っていないのではないか。

前回の記事ではそこまで書きました。

sinf() が重いならLUT化すれば良い、というのはすぐ思いつきます。しかし今回は、まずCycle Counterで実際の処理時間を測り、コンパイラの最適化でどこまで行けるか試しました。

Release相当の最適化を使うと一度は11 Voice Sineも正常に動作します。しかし、その後機能を追加していくと再び処理時間の限界に達しました。そこで最後に、SineをLUT化しています。

今回は、この 測定 → 最適化 → 再び限界 → LUT化 という流れを振り返ります。


2.667msという締切

NuVCOのPCM5102Aへのオーディオ出力は、公称48kHzです。

DMAでは256 frames分のバッファを用意し、その半分である128 framesごとに次のデータを生成しています。

つまり128 framesを再生する時間は、

128 / 48000 = 0.002666...

2.667ms です。

STM32G431は170MHzで動作しているので、CPU cycleにすると、

170,000,000 × 128 / 48,000 = 約453,333 cycles

となります。

この 453,333 cycles が重要です。

最初は「2.667msくらい時間がある」と考えていましたが、リアルタイムのオーディオ処理では、これは余裕時間ではありません。

次のDMA転送に間に合わせるための締切です。

128 framesの生成にこれ以上かかると、次に送るべきオーディオデータの準備が間に合わなくなります。


Cortex-M4のCycle Counterで測ってみる

STM32G431のCortex-M4には、DWT(Data Watchpoint and Trace)のCycle Counterがあります。

CPUが何cycle動いたかを数えるカウンタです。

初期化は簡単に書けばこのようなコードです。

CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;

初期化したら、測りたい処理の前後で DWT->CYCCNT を読みます。

start = DWT->CYCCNT; /* 128 framesを生成 */ elapsed = DWT->CYCCNT - start;

NuVCOではDMAのhalf/full callbackから呼ばれる128-frame生成処理の前後を測定しました。

さらに、

  • 最後のcycle数

  • 最小値

  • 最大値

  • 累積cycle数

  • 測定回数

  • 453,333 cyclesを超えた回数

を変数に保存するようにしました。

Cycle Counterの詳しい使い方はAIに質問してみましょう。


STM32CubeIDEのLive Expressionsで見る

測定値を見るのにはSTM32CubeIDEの Live Expressions を使いました。

Live Expressionsは最初は驚くと思います。MCUを動作させたまま、その内部にある変数をPCでほぼリアルタイムに観察出来ます。

NuVCOでは、

app_vco_active_voice_count app_audio_perf_max_cycles app_audio_perf_deadline_cycles app_audio_perf_overrun_count app_audio_perf_measurement_count

などを表示しておけば、音を出したままVoice数、最大処理時間、deadline、deadline超過回数などを確認できます。


今回のキャプチャ画面では、

Voice 11 Max cycles 168,563 Deadline cycles 453,333 Overrun 0 Measurement count 40,202

となっています。

現在のLUT版では、11 Voice Sineでもdeadlineまでかなり余裕があることが分かります。

※ この画像は、sinf() でサイン波を生成していたときのLive Expressions画面ではありません。サイン波生成にLUTを使った場合の測定値です。


Debug版で測る

まずSTM32CubeIDEの通常のDebug構成で測りました。

条件AverageMaxDeadline超過
9 Voice Sine約424,534 cycles448,349 cycles0
11 Voice Sine約500,839 cycles536,504 cycles15,892回
11 Voice Saw約199,000 cycles199,040 cycles0

11 Voice Sineは23,378回測定して、15,892回がdeadlineを超えています。

約68%です。

時間に直すと、

11 Voice Sine Average 500,839 / 170MHz ≒ 2.946ms 11 Voice Sine Max 536,504 / 170MHz ≒ 3.156ms

です。

締切は約2.667msですから、最大値だけがたまたま超えたわけではありません。

平均値の時点ですでに間に合っていません。

一方、9 Voice Sineの最大値は448,349 cycles、約2.637ms。

締切の453,333 cyclesまで、わずか約29μsしかありません。

9 Voiceで動いていたのも、かなりぎりぎりだったことが分かりました。


同じ11 VoiceでもSawは軽い

コンピューターにとって、Sineは計算量が多いですが、Sawはかなり簡単な計算で済みます。

Debug構成でも11 Voice Sawは約199,000 cycles、時間にすると約1.17msでした。

同じ11 Voiceなのに、Sineとは大きな差があります。

当時のSine生成では、1 Voiceにつき1 sampleごとに sinf() を1回呼んでいました。

128 frames × 11 Voiceなので、半バッファを1回生成する間に、

1,408回の sinf()

を呼ぶ計算になります。

STM32G431には単精度FPUがありますが、sinf() は「FPUのSIN命令を1回実行すれば終わり」という処理ではありません。

各sample、各Voiceで sinf() を大量に呼び出すSine生成処理が、リアルタイムオーディオの締切を超えていました。


DebugとReleaseでは速さがかなり違う

ここでもうひとつ重要なことが分かりました。

STM32CubeIDEのDebug構成では、C Compilerの最適化が、

-O0

になっています。

一方、NuVCOのRelease構成では、

-Os

です。

つまりDebugとReleaseでは、単に「デバッグ情報が入っているかどうか」だけでなく、コンパイラの最適化条件そのものが違います。

性能を測るときには、この差を無視できません。

実際、初期の11 Voice SineをRelease相当の -Os で測ると、

条件AverageMax
11 Voice Sine / Debug約500,839536,504
11 Voice Sine / ReleasePerf約305,747313,841

まで減りました。

Debug版だけを見て、「STM32G431では11 Voice Sineは無理」と判断するのは早かったわけです。


Releaseに近い性能のままDebugする「ReleasePerf」

しかし普通のRelease構成ではデバッグ情報がありません。

Cycle Counterの値をLive Expressionsから眺めたいので、NuVCOでは ReleasePerf というBuild Configurationを追加しました。

内容はシンプルです。

Base : Release C optimization : -Os C debug information: -g3 FPU : fpv4-sp-d16 Float ABI : hard

つまり、

Releaseの最適化を維持したまま、Cのデバッグ情報を残した性能測定用構成

です。

STM32CubeIDEで同じものを作る場合は、

  1. Projectを右クリック

  2. Build Configurations → Manage...

  3. Releaseを複製して ReleasePerf を作成

  4. C/C++ Build → Settings

  5. C CompilerのOptimizationを -Os

  6. Debug informationを -g3

  7. ReleasePerf用のELFを指定してDebug

という流れです。

NuVCOでは、

ReleasePerf/NuVCO-G431KB.elf

をデバッガから使っています。

これで、Releaseに近い最適化条件のコードを、ST-LinkとLive Expressionsを使いながら観察できます。


最適化したら解決……ではなかった

初期の11 Voice Sineは、ReleasePerfにすると最大313,841 cyclesまで下がり、453,333 cyclesの締切内に収まりました。

この時点では一度問題が解決しています。

しかし、その後NuVCOに機能を追加していくと、再びSineが限界へ近づいていきました。

Sine LUT化直前のReleasePerf測定がこちらです。

VoiceWaveMax cycles時間Overrun
1Sine69,122約0.407ms0
3Sine158,926約0.935ms0
5Sine236,074約1.389ms0
7Sine315,203約1.854ms0
9Sine392,026約2.306ms0
11Sine461,587約2.715ms146
11Triangle123,145約0.724ms0

条件はReleasePerf -Os、55Hz付近です。

Voice数を増やしていくと処理時間も増え、

9 Voiceまではdeadline内、11 Voiceで453,333 cyclesを超えました。

同じ11 VoiceでもTriangleは123,145 cyclesしか使っていません。

前回の記事で感じていた、

「11 Voice Sineだけ何かがおかしい」

が、cycle数としてはっきり見えました。

リアルタイム処理では、

一度性能測定して動いたから大丈夫

ではありません。

機能を追加すれば、それまであった処理時間の余裕を少しずつ削っていきます。

機能追加後にもう一度測る必要があります。


SineをLUT化する

そこで、整数DDSのころから使い慣れたSineのLUT化を行いました。

もちろん今回は整数の波形テーブルではなく、単精度浮動小数点数の波形テーブルです。足りない部分は計算で線形補間するという、少し贅沢なLUT化です。

NuVCOでは、

  • full-cycle 256分割

  • wrap用を含め257要素

  • float

  • Flashへ配置

  • 隣接2点を線形補間

という方法にしました。

LUT化後の11 Voice Sineは、当時の測定で、

Max cycles: 168,004 Overrun: 0

でした。

時間にすると、

168,004 / 170MHz ≒ 0.988ms

です。

LUT化直前の、

461,587 cycles ≒ 2.715ms

から、

168,004 cycles ≒ 0.988ms

まで減りました。

締切2.667msに対して、約1.68msの余裕があります。

今回、現在のFirmwareでもう一度Live Expressionsを表示してみたところ、11 Voice Sineで最大168,563 cyclesでした。

過去に記録していた168,004 cyclesとほぼ同じです。

LUT化で正常に動くようになっただけでなく、処理時間にかなり余裕を作ることができました。


実際に波形はどう壊れたのか

これは当時Analog Discovery 2で観測した11 Voice Sineの破綻波形です。

正弦波そのものは生成されていますが、波形途中に大きな不連続が現れています。

この状態では周期的な異音が発生し、Tune / Wave / Detune POTも反応しなくなりました。

デバッガを切断してLive Expressionsを表示しない状態でも11 Voice Sineの破綻は再現しました。したがって、Live Expressionsを表示していたこと自体が原因ではありません。

※ CH2に見える高周波成分は、11 Voice Sineの処理破綻そのものとは別の現象です。撮影時、オシロ測定点と並列になっているオーディオ出力をBehringer UMC404HDの入力へ接続しており、その状態で発生していました。今回注目しているのは、波形途中に現れる大きな不連続です。


今のNuVCO実験環境


現在はNuVCO Proto基板にNucleo-G431KBとPCM5102Aを載せ、ブレッドボード上にOLED、MCP23017、ロータリーエンコーダなどを接続して実験しています。

写真は今回のCycle Counter測定当時そのものではなく、現在の実験環境です。

機能を増やしていくにつれて、最初のPOTとUSERスイッチだけでは操作が難しくなってきました。

このUIの話は次回以降に書こうと思います。


Cycle Counterで壊れた理由が見えた

今回Cycle Counterを使ってみて良かったのは、

「たぶん sinf() が重い」

という推測を、

「128 framesを作るのに2.667ms以上かかっている」

という数字で確認できたことです。

11 Voice Sineでは締切を超える。

11 Voice SawやTriangleなら余裕がある。

Release最適化をすると速くなる。

それでも機能追加を続けると、また締切に近づく。

SineをLUT化すると大きく余裕が増える。

同じcycle数という尺度で比較すると、何が起きていたのかが分かりやすくなりました。

組み込みのリアルタイム処理では、「動いた/動かなかった」だけを見ていても原因が分からないことがあります。

今回のNuVCOでは、2.667msという締切を意識してCycle Counterで測ることで、11 Voice Sineが壊れた理由を数字で確認できました。

そしてもうひとつ、今回覚えておきたいのがBuild Configurationです。

STM32CubeIDEではDebugとReleaseだけを使う必要はありません。

Releaseをベースに最適化を残したBuild Configurationを作れば、実際の動作速度に近い状態でST-LinkとLive Expressionsを使って性能を観察できます。

今回作った ReleasePerf は、今後STM32で処理時間を調べるときにも使えそうです。

次は、11 Voice化やMorphing、Detuneなど実験したいことが増えた結果、POTとUSERスイッチだけでは操作しづらくなってきた話を書こうと思っています。

NuVCOを「VCOを作るプロジェクト」から、いろいろな音作りを実験できるVCOへ広げるため、OLEDやロータリーエンコーダを使ったUIを追加していきます。

2026年8月1日土曜日

NuVCOを11 Voice化したら波形が崩れた ― リングバッファとFPUを過信した話

前回の記事「NuVCO開発スタート ― STM32でデジタルVCOを作る」では、NuVCOを作り始めた経緯と、STM32G431、PCM5102Aを使ってデジタルVCOとして音を出すところまでを紹介しました。

今回は、その後にVoice数を増やしていったときの話です。

NuVCOでは、CPUで複数サンプルをまとめて計算し、DMAを使ってPCM5102Aへ連続転送しています。さらにSTM32G431にはFPUがあるため、サイン波も波形テーブルを使わず、sinf()でリアルタイムに計算していました。

この構成なら、かなり余裕があると思っていました。

実際、9 Voiceまでは動きました。しかし、サイン波を11 Voiceにすると波形が崩れ、POTにも反応しなくなりました。

1サンプルずつ処理する方法から、リングバッファへ


マイコンで波形を作ること自体は、今回が初めてではありません。

10年程前にはPSoCを使い、整数演算によるDDSを実装していました。位相を整数で加算し、その上位ビットを使って波形テーブルを参照する、よくある方式です。タイマー割り込みのたびに次の1サンプルを計算し、DACへ出力する。これまでの私にとって、マイコンで波形を作るとは、おおむねそのようなものでした。

PCM5102Aも今回初めて使ったわけではなく、以前から何度か使用しています。I2Sでデータを送れば、比較的簡単にきれいな音が出せる、使い慣れたオーディオDACです。

ただし、今回のNuVCOでは、これまでと少し違う方法を試しました。

従来のように、1サンプルを計算するたびにCPUが出力処理まで行うのではなく、複数のサンプルをバッファへまとめて生成し、そのバッファをDMAでPCM5102Aへ転送する方法です。

CPUで複数サンプルをまとめて計算し、DMAにまとめて送らせる。この構成を自分で実装するのは、今回が初めてでした。


NuVCO Proto-Aとブレッドボードによる実験風景。左側は測定に使用したAnalog Discovery 2です。

DMAが送っている間に、次の音を作る


現在のNuVCOでは、CPUが生成した音声データを、1サンプルずつPCM5102Aへ送り出しているわけではありません。

CPUは、これから再生する128 stereo frames分のデータを、まとめて音声バッファへ書き込みます。1 stereo frameは、左チャンネルと右チャンネルのサンプルを組み合わせたものです。

実際のI2S転送はDMAが担当します。DMAはCPUとは別にバッファからデータを読み出し、PCM5102Aへ順番に送り続けます。

音声バッファ全体には256 stereo frames分の領域があり、前半と後半の各128 stereo framesに分かれています。

  • DMAが前半を送信している間、CPUは次に必要なデータを準備する
  • 前半の送信が終わると、DMAは後半へ移る
  • CPUは、送信の終わった前半へ新しいデータを書き込む
  • 後半の送信が終わると、DMAは再び前半へ戻る
  • CPUは、送信の終わった後半へ新しいデータを書き込む

この動作を繰り返すことで、PCM5102Aへの出力を止めることなく、次の音声データを作り続けられます。

概念的にはリングバッファです。実装をもう少し正確に表すなら、Circular ModeのDMAでバッファの前半と後半を繰り返し使用する、ダブルバッファ相当の構成になっています。


DMAが一方の領域を送信している間に、CPUが空いた領域へ次の128 stereo framesを書き込みます。

NuVCOのサンプルレートは48kHzです。

1サンプルずつ処理する場合、約20.8マイクロ秒ごとに次のデータを用意し、その都度転送処理を行うことになります。

1 ÷ 48,000 ≒ 20.8マイクロ秒

一方、現在のNuVCOは、1回のコールバックで128 stereo framesをまとめて生成します。そのため、CPUは次の128 framesを約2.667ミリ秒以内に用意すればよいことになります。

128 ÷ 48,000 ≒ 2.667ミリ秒

もちろん、必要な波形サンプルの総数が減るわけではありません。48kHzであれば、最終的には1秒間に48,000 frames分を計算する必要があります。

それでも、I2Sへの細かな転送処理をDMAに任せ、128 frames単位でまとめて生成することで、CPUが転送のたびに呼び出される負担を減らせます。

CPUクロックそのものが速くなったわけではありませんが、波形生成へ使えるCPUサイクルには余裕ができました。感覚的には、リングバッファとDMAによって処理時間を稼いだ、という表現が近いと思います。

ここまでは、かなりうまくいきました。

マイコンで浮動小数点数を使うことへの抵抗


整数DDSは以前から使ってきましたが、マイコンのリアルタイム処理で浮動小数点数を多用することには、今でも少し抵抗があります。

浮動小数点演算は重い。マイコンでは、できるだけ整数演算や固定小数点演算を使う。そのような考え方が身についているからです。

ただし、NuVCOで使用しているSTM32G431は170MHzで動作し、単精度浮動小数点演算をハードウェアで処理するFPUを搭載しています。

余談ですが、私が最初に買ったDOS/V機はGateway2000のIntel 486DX / 100MHzでした。それまで使っていたNEC PC-9801 386 + FPU(コプロセッサ)よりずいぶん速く天国のように思えた記憶があります。

これだけの性能があり、しかもFPUまで載っているのなら、昔ながらの整数DDSにしなくても、浮動小数点数を使った素直な実装でかなりのところまで行けるのではないか。

そう考えました。

そこでNuVCOのサイン波は、最初から波形テーブルを参照するのではなく、浮動小数点数で位相を扱い、サンプルを生成するたびにsinf()で値を求めていました。

もちろん、FPUにサイン関数を直接計算する命令があるわけではありません。sinf()の内部では、数学ライブラリによるいくつもの演算が行われます。

それでもSTM32G431では、数Voice程度なら、48kHzのリアルタイム処理中に毎回sinf()を呼んでも実際に問題なく音が出ました。

一般的な小規模マイコンで同じことをすれば、かなり厳しいはずです。FPU付きのSTM32G431だからこそ可能になった、少々ぜいたくな実装です。

10年以上前から整数DDSを使ってきた身としては、波形テーブルを用意せず、その場で浮動小数点数のサイン波を計算しても普通に鳴ることには、少し感心しました。

DMAと音声バッファで転送処理を効率化し、FPUを使って波形をリアルタイムに計算する。

これならVoice数をさらに増やしても動くのではないか。

どこまで動くかとにかくやってみよう!という気持ちになりました。

最大11 Voiceまで増やしてみる


NuVCOにはDetune機能があり、少しずつ周波数をずらした複数のVoiceを重ねて出力できます。

当初は3 Voiceでしたが、その後、1、2、3、5、7、9、11 Voiceを切り替えられるように拡張しました。

Voice数を増やせば音の厚みも増します。その一方で、1 frameを生成するために必要な波形計算の回数も増えていきます。

1 Voiceなら、1 frameにつき1 Voice分です。11 Voiceなら、同じ時間内に11 Voice分の波形を計算して加算しなければなりません。

三角波、ノコギリ波、矩形波は、11 Voiceでも動作しました。サイン波も9 Voiceまでは動きました。

STM32G431は、思っていた以上に頑張ってくれます。


421Hz、Saw 100%、Voice数:9、Detune:+001で正常に動作している出力波形。わずかに周波数をずらした9本のノコギリ波を重ねているため、単独のノコギリ波とは異なる細かな段差が見えます。

ところが、サイン波を11 Voiceに切り替えると、様子がおかしくなりました。

オシロスコープに表示される波形が崩れ、各POTを回しても正常に反応しなくなります。

単に音が少し悪くなった、という程度ではありません。音声出力だけでなく、NuVCO全体の操作までまともに動かなくなりました。

最初は、11 Voice分の加算で振幅が大きくなりすぎたのか、どこかの変数がオーバーフローしたのか、それともDMAの使い方に問題があるのかと考えました。

しかし、サイン波以外は11 Voiceでも動きます。サイン波も9 Voiceまでは動きます。

サイン波だけが、11 Voiceにしたときに破綻する。

そうなると、怪しいのはsinf()の計算時間です。

2.667ミリ秒は「余裕」ではなく「締切」だった


リングバッファとDMAを使ったことで、1サンプルごとに転送処理を行う場合よりもCPU時間には余裕ができました。

しかし、CPUが好きなときに、好きなだけ時間をかけて音声データを作れるわけではありません。

DMAが現在の128 framesを送り終えるまでに、CPUは次の128 framesを完成させておく必要があります。

その時間が約2.667ミリ秒です。

もし計算が2.667ミリ秒を超えれば、DMAが次に送るべき音声データの準備が間に合いません。再生がこちらの計算を待ってくれることもありません。

DMAによって締切がなくなったのではなく、128 framesごとに約2.667ミリ秒という、はっきりした締切ができたわけです。

さらに、音声データの生成はDMAのHalf CompleteやFull Completeのコールバックから呼び出されています。ここでCPUを長時間占有すれば、メインループ側の処理も実行されにくくなります。

音声処理が間に合わなくなった結果、POTの読み取りや操作への反応まで止まったように見えたのではないか。

この時点では、まだ推測です。

FPUがあるから大丈夫だろう、という見込みは、完全に外れたわけではありません。数Voiceなら、本当にリアルタイムでサイン波を計算できた。

リングバッファとDMAによる効率化も、きちんと効果がありました。

しかし、どこまでも行けるわけではなかった。

リングバッファとDMAによって稼いだはずのCPU時間を、11 Voice分のsinf()が使い切ってしまった可能性があります。

では、実際にはどのくらい時間がかかっていたのでしょうか。

正常に動いた9 Voiceと、破綻した11 Voiceの間には、どのくらいの差があったのでしょうか。

次は感覚や予想ではなく、STM32G431のサイクルカウンタを使って、音声バッファの生成にかかる時間を実際に測ってみることにします。

 参考リンク


2026年7月15日水曜日

製作モジュール紹介サイトを公開しました

 


これまでBlogに掲載してきたDIYシンセ・モジュールを、見通しよくまとめるためのサイト「PNPN Manufactory」を公開しました。

現在はNuVCO、ArLFO、TLF01、Dual OTA VCAを掲載しています。今後も順次追加していきます。

製作過程や実験の詳しい記録はこれまでどおりこのBlogに掲載し、完成したものや開発中のモジュールはPNPN Manufactoryに整理していきます。

[製作モジュールを見る]

https://pnpn-mfg.com/modules/

2026年7月2日木曜日

NuVCO開発スタート ― STM32でデジタルVCOを作る

はじめに

しばらくごぶさたしておりました。

Arduinoを使った ArLFO を製作して以来、しばらくシンセDIYから離れていましたが、久しぶりに新しいモジュールを作り始めることにしました。

今回のテーマは STM32を使ったデジタルVCO です。


NuVCO Proto-B実装基板

NuVCOとは?

NuVCOは、STM32 Nucleoボードを使ったデジタルVCOです。

名前の NuNucleo の頭文字から取りました。また、"New" の意味も少し込めています。

これまでにもNucleoボードを使っていくつか音源を試作してきましたが、今回は本格的なEurorack向けVCOとして開発を進めます。

なぜデジタルVCOなのか

これまでにディスクリートVCOや3340を使ったアナログVCOをいくつか製作してきました。

アナログVCOには独特の揺らぎがあり、複数のVCOを重ねるだけでも温かく太い音になります。これはアナログならではの魅力です。

一方で、生成できる波形は比較的シンプルです。

もし、

  • SuperSawのような厚いDetune

  • デジタルシンセならではの複雑な波形

  • リアルタイムで変化するWave Morphing

こんな音をモジュラーシンセに持ち込めたら面白いのではないか。

そんな思いから、このプロジェクトを始めました。

もちろん、市販のデジタルVCOを買った方が安くて高性能でしょう。

それでも、自分で作る楽しさには抗えません。

開発目標

今回の目標はシンプルです。

  • STM32G431 + PCM5102AによるデジタルVCO

  • 16bit / 48kHzオーディオ

  • 1V/Oct入力対応

  • サイン波・三角波・ノコギリ波・矩形波

  • 将来的にはDetuneやWave Morphingも実装

デジタルならではの自由度を活かしながら、Eurorackの世界で気軽に使えるVCOを目指します。

ハードウェア

コアとなるマイコンは STM32G431 を搭載した Nucleo-32

オーディオDACには PCM5102A を使用しています。

どちらも比較的入手しやすく、性能も十分です。


Proto-B基板

基板を作る前は、ブレッドボード上でひたすら実験を繰り返していました。


ブレッドボードでの動作試験

現在ここまで動いています

現時点では次の機能が動作しています。

  • 正弦波

  • 三角波

  • ノコギリ波

  • 矩形波

  • Waveノブによる波形切り替え

  • Tuneノブによる周波数変更

  • 2 Voice Detune

ノコギリ波はこんな感じです。


1 Voice Saw

PCM5102Aには内部デジタルフィルタが入っているため、急峻な波形では少しリンギングが見られます。

今回はあえてDACの生の出力を観察しています。このクセをどう扱うかも今後の楽しみの一つです。

2 Voice Detuneも動き始めました。

まだ完成ではありませんが、「おっ、シンセらしい音になってきた」と感じられるところまで来ています。


2 Voice Detune

ソフトウェア

ソフトウェアはSTM32CubeMXとCubeIDEを使っています。

音声出力はI2S + DMA。

波形生成はDDS(位相アキュムレータ方式)で実装しました。

STM32G431にはFPUが搭載されているため、現在はWavetableを使わず浮動小数点演算でリアルタイム生成しています。

今後は処理速度や音質とのバランスを見ながら、アルゴリズムを改良していく予定です。

今後の予定

まだまだやりたいことはたくさんあります。

  • Wave Morphing

  • PolyBLEPによるアンチエイリアス

  • 1V/Octキャリブレーション

  • Voice数の切り替え

  • Proto-B基板での評価

デジタルVCOならではの自由度を活かして、少しずつ育てていこうと思っています。

おわりに

ようやく「音が出る」段階までたどり着きました。

ここからは音質や演奏性を磨きながら、本格的なEurorackモジュールへ仕上げていきます。

今後はDDSの実装やPolyBLEP、Detuneアルゴリズム、1V/Octの調整など、開発の過程も順番に紹介していく予定です。

お楽しみに。


USER SWで1 Voice、2 Voice Detune、3 Voice Detuneを切り替えた様子を動画にしました。Detune POTを回したときの音の変化も確認できます。


2025年2月26日水曜日

ArLFO Ver.1.2 BOOTHに出品しました。

 ArduinoとSPI DACを使ったArLFOの基板をBOOTHに出品しています。

こんなんなんぼあってもいいですからね。ぜひご検討ください。

https://pnpn-mfg.booth.pm/items/6591825




2025年2月12日水曜日

ArLFO Ver.1.2 Arduino(ATMega328P)とSPI DAC(MCP4921)を使ったLFOの製作

以前、テストしたArLFOのEurorackモジュールを製作しました。

はんだ付けしている様子をYoutubeにアップしてますので参考にしてください。

Arduinoのスケッチを書き込んだATMega328PとSPI DACのMCP4921を使ったLFOです。12bit DACでサンプリング周波数25kHzで動作させています。

オーディオ用DACに比べて低ビット、低レートですが、CV(モジュレーション信号)なので実用上問題ありません。MIDIが8bitデータ、分解能が1ms程度であることを考えるとむしろハイスペックと言えるかも知れません。また、出力段に4次多重帰還ローバス・フィルタを設けて量子化歪(信号波形のガタガタ)を低減しています。

ファームウェアはArduinoのスケッチなのでAVRライターなどは不要で、Arduino IDEからUSB-Serial変換モジュールを使ってプログラミング出来ます。デフォルトのファームウェアは性能を上げるために直接レジスタを操作しているので、書き換える際はAVRの知識が多少あった方がいいかもしれません。

ファームウェアはGithubで公開しています。

実装済基板

パネルに取付

出力波形


出力は以下6種類をプッシュスイッチで切り替えます。

  • サイン波
  • 三角波
  • 矩形波(パルス幅可変)
  • ノコギリ波(上昇)
  • ノコギリ波(下降)
  • サンプル&ホールド

サイン波

三角波

矩形波(パルス幅可変)

ノコギリ波(上昇)

ノコギリ波(下降)

サンプル&ホールド

Dual波形とSingle波形


ArLFOには2系統の出力があります。Singleは0V~+5V、Dualは-5V~+5Vです。受け側の入力電圧範囲に合わせて利用できます。


CH1:Dual CH2:Single

ローパス・フィルタ


DACの出力に4次多重帰還LPFを入れて量子化歪を除去しています。またLPFを掛けないDACの信号も出力できます。


CH1:LPF通過後 CH2:LPF通過前

LPFはカットオフ周波数6.25kHz、ベッセル特性で設計しています。LTSpiceでのシミュレーションは以下の通りです。

シミュレーション回路図

AC解析

過渡解析

1kHz/2Vp-p矩形波

ベッセル特性はフィルターの切れはいまいちですが、過渡特性が良く信号波形のリプルが少なく素直な特性になります。

出力周波数


出力周波数はLとHをトグルスイッチで切り替えます。

  • L:  0Hz~5Hz
  • H:  0Hz~50Hz

Hにすると可聴帯域の信号も出力できるので実際に耳で聞くこともできます。振幅が大きいので注意してください。最大周波数はスケッチ内の定数で変更できます。

回路について


回路図

回路図(アクティブフィルタ)

MCP4921の出力を直接アクティブフィルタに入力すると十分にドライブ出来ず、波形の一部がクリップしてしまいました。長年MCP492Xを使っているのですが、いつも何の気なしに出力にボルテージフォロワを入れていたので気づきませんでした。


MCP4921の出力を直接アクティブフィルタに入力すると波形がクリップする

このため、実際の回路ではDACの後段にボルテージフォロワによるバッファ(U3B)を入れています。前出のオシロの波形がクリップしていないのをご確認ください。

Q1の2SC1815はBLランクにします。YランクやGRランクだと増幅率が小さくインジケータLEDの明るさが暗く点滅するようになると思います。正規品の2SC1815は入手困難なのでUNISONIC製などセカンドソース品で十分です。

抵抗は、手持ちの関係で一部金属皮膜を使っていますが、今回の製作はすべてカーボンで十分です。また、フィルムコンデンサもマイラで大丈夫だと思います。

ファームウェアの書き込み


あらかじめATMega328PにArduinoのBootloaderを書き込んでおきます。


ファームウェアはGithubで公開しています。

ファームウェアに書き込むにはUSB-Serial変換モジュールをArLFOのJ7(ARDUINO_ISP)と接続します。

配線表

QIコネクタを使ってハーネスを作っておくと良いでしょう。


※光の加減でコネクタが白く映っていますが、黒いQIコネクタのハウジングです。

USB-Serial変換モジュールは、私はAliExpressのノンブランド品を使用しています。DTR、TXD、RXD、GNDの端子が出ていて、5V動作するものであれば大丈夫だと思います。FT232RLとCH340使用のもので動作確認しています。

ファームウェア書き込み手順

  1. USB-Serial変換モジュールをArLFOに接続
  2. USB-Serial変換モジュールをPCにUSBで接続
  3. ArLFOの電源投入
  4. Arduino IDEを起動
  5. Tools - Board - Arduino Uno
  6. Tools - Port - <USB-Serial変換モジュールのポート>

あとは普通のArudinoと同じようにスケッチをUploadします。

2025年2月7日金曜日

ArLFOはんだ付け 動画を公開


製作中のArduinoをSPI DACを使ったLFOのはんだ付けの様子を動画に撮ってみました。
Blog記事にするほどでもない細かいノウハウもちょこちょこ紹介しています。

初心者の頃、キットを買ったはいいもののどうやってはんだ付けして組み立てるのか良くわからなかったので、そういった方のお役に立てればと思います。

2025年1月2日木曜日

Arduino Writerの製作

こちらの基板をBOOTHで販売しております。

製作の目的

素のATMega328PをArduinoとして使うために、Arduino Unoを使ってBootloaderを書き込めます。簡単な回路なのでブレッドボードでも出来ますが、毎回ブレッドボードで回路を組むのが面倒なので専用の基板を製作しました。また、Bootloaderを書き込んだ後、USB-Serial(TTL)モジュールを使って、Arduinoのスケッチも書き込めるようにしました。


大きく2つに分けて使用方法を示します。

  1. ArduinoのBootloaderを書き込む
  2. Arduinoのスケッチを書き込む

Arduino IDEはVer.2.3.2を使って説明しますが他のバージョンでも大きく違うことはないと思います。Arduino UnoはATMega16U2の代わりにCH340を使った互換ボードでも動作確認しています。

1.ArduinoのBootloaderを書き込む


基本的な概念はArduinoの公式ページを参照してください。

Arduino UnoにProgramer用スケッチを書き込む


まずArduino UnoをBootloaderのProgrammerとして使うためのスケッチを書き込みます。

手順
  1. Arduino UnoとPCをUSB接続する
    Arudino Writer(この基板)とは接続しないでおきます
  2. PCでArduino IDEを起動し、File→Examples→11.ArduinoISP→ArduinoISPを開く
  3. Tools→Board→Arduino AVR Boards→Arduino Uno を選択
  4. Tools→Port→<Arduinoを接続しているPort> を選択
  5. Sketch→Upload、またはメニューバーの(→)アイコンでスケッチを書き込む
  6. IDEの右下にDone Uploading.とポップアップされるのを確認

これでArduino UnoをBootloader書き込み器として使う準備完了です。


Arudino Unoを使ってATMega328PにBootloaderを書き込む


配線図

ハーネスの作例

接続のようす

手順
  1. Arduino Writer(この基板)のZIFソケットにATMega328Pを挿入してレバーを倒す
    レバー側が1ピンです
  2. Arduino UnoとPCのUSB接続を外す
  3. Arduino UnoとArduino Writer(この基板)を接続する
  4. Arduino UnoとPCをUSB接続する
    Arduino Writer上のLEDチェックが走り、HTBT LEDがじんわりと点滅します
  5. PCでArduino IDEを起動
  6. Tools→Port→<Arduinoを接続しているPort> を選択
  7. Tools→Programmer→Arduino as ISP を選択
  8. Tools→BurnBootloader を選択
  9. IDEの右下にDone burning bootloaderとポップアップされるのを確認

これでATMega328PにArudinoのスケッチを書き込むためのBootloaderが書き込まれました。

2.Arduinoのスケッチを書き込む


スケッチの書き込みにはUSB Serial(TTL)モジュールを使用します。FT232RLとCH340のモジュールで動作確認しています。

テスト用にATMega328PにLチカのスケッチを書き込んでみます。

配線図

配線表

※TXDとRXDは互い違いになるように配線します
※CTSは配線しないでおきます
※VCCはUSB Serialモジュールで5Vに設定

配線用ハーネス

接続のようす

手順
  1. Arduino UnoとPCのUSB接続を外す
  2. Arduino UnoとArduino Writerの配線を外す
  3. USB SerialモジュールとArduino Writer(この基板)を接続する
  4. USB SerialモジュールをPCにUSB接続する
  5. PCでArduino IDEを起動
  6. File→Examples→01.Basics→Blinkを選択
  7. Tools→Board→AVR Arduino Board→Arduino Unoを選択
  8. Tools→Port→<USB SerialモジュールのPort> を選択
    Arduinoを接続していたPortと異なるで注意
  9. Sketch→Upload、またはメニューバーの(→)アイコンでスケッチを書き込む
  10. Done Uploading.とIDEの右下にポップアップされるのを確認

Arduino Writer(この基板)の緑のLEDが点滅したら成功です。

回路について


回路図

Bootloaderの書き込みはSPIを使います(D13~D10)。L_PROG、L_ERR、L_HTBTはProgramerとしてつかうArduino Unoからの出力を受けてLEDを点滅させます(D9~D7)。LEDの電流制限抵抗(R2、R3、R4)は実際に使うLEDの明るさで調整してください。私の使っているLEDは緑色だけ暗いのでR4は330Ωに変更しています。

U2のLM358はD13に接続するLEDをドライブしているだけです。D13はSPIのSCKとしても使うのでバッファリングして干渉を避けます。Arduino Unoでも同様の回路になっています。前述の理由でR5は330Ωに変更しています。

Sketchの書き込みにはSerial(D0、D1)とRESETを使います。DTRから0.1uFのコンデンサ(C5)介し、10kΩ(R1)でVCCにプルアップ必要があります。C5がハイパスフィルターとして働き、RESETピンに+5Vを超える電圧がかかります。このため、SBD(D4)でクランプして+5Vを超えないようにしています。


2024年12月31日火曜日

シンセ3VCOの解説をYoutubeにあげました

3340VCOとAnVCOの音出し動画を撮ってみました。

意外とディスクリートのAnVCOもいいものだと思いました。


2024年10月28日月曜日

MIDI - ステレオミニプラグ変換ケーブルの作成

シーケンサーのKORG SQ-64とドラムマシンのKORG drumlogueをMIDIケーブルで接続する場合、SQ-64側にMIDI-ステレオミニプラグ変換ケーブルが必要となります。Rolandから発売されていますが結構お値段がはり、何本か必要になりそうなので自作することにしました。

自作したMIDI - ステレオミニプラグ変換ケーブル

配線


Type-AとType-Bがあります。ステレオミニプラグのTipとRingへの接続が逆になります。


※コネクタのはんだ端子側から見た図

SQ-64の説明書にはType-Aを使うように書かれています。

普通のDIN 5PINのジャックで試験



DINコネクター5P中継ジャックで製作


使用したMIDI側の中継ジャックは マル信 MJ-52です。これも1個400円強と安いものではありませんが、信頼のマル信製です。

DINプラグの分解も分かりにくいものですが、このジャックもプラグと同じようにぐりぐりしてカバーを外す必要があります。


樹脂製のカバーと1つ内側のメタル製のパーツの1か所くぼんでいる部分に、精密ドライバー(-1.5程度)を差し込みぐりぐりしながらカバーを外していきます。カバーは柔らかいのである程度外れたら内部のパーツを引き抜きます。

メタル製のパーツは2分割で外せますが、外す時に位置関係をしっかり確認しておきます。

普通のDINジャックより端子の間隔が狭いので、はんだ付けの難易度はステレオミニプラグのはんだ付けと同程度かやや難しい程度です。


※導線がはみ出していたり良いはんだ付けとは言えません。この後ニッパーで整えましたが端子間が狭く本体が樹脂製なのではんだ付けには注意が必要です。

SQ-64とdrumlogueのMIDI接続


デフォルトのMIDI設定で、SQ-64のMIDI OUTからdrumlogueのMIDI INにつなぐと、SQ-64がマスターとなりテンポが同期します。SQ-64のDトラックで打ち込んだシーケンスでdrumlogueを鳴らすこともできます。

2024年10月21日月曜日

ArLFO Arduino(ATMega328P)とSPI DAC(MCP4922)を使ったLFOの構想

以前、Arduino NanoとMCP4922を使ったLFOを製作しました。

電池駆動のガジェットタイプのモジュールで、Eurorackでは多少使いにくく、基本コンセプトはそのままにEurorackモジュールとして製作することにしました。

踏襲する要件

  • Arduinoを使ってMCP4922を駆動
  • サイン波、三角波、ノコギリ波(上昇)、ノコギリ波(下降)、矩形波(PW可変)
  • サイン波はDDSで生成
  • MCP4922は12bit DACなので出力にフィルターを掛けデジタルノイズを低減する(定数見直し)
  • Sync入力で位相をリセット

新たに設ける要件

  • Eurorackモジュールとして製作する
  • Arduino nanoではなく、ATMega328Pをそのまま使ってArduino IDEでプログラミング可能とする
  • 出力はGND~+5Vの単電源波形と-5V~GND~+5V両電源波形を出力する
  • 出力周波数は最大20Hzと最大200Hzを切り替え
  • LFOの出力レベルインジケータLEDを付ける
  • Arduinoのスケッチでユーザー定義の波形を出力を可能とする

テスト基板の製作


フィルター以外を1枚の基板でテスト実装しました。

回路図

結果


Arduino IDEからプログラミング

USB Serial変換モジュールを使ってArduino IDEからプログラミングできました。

DTRは回路図のように10kΩで+5Vにプルアップ、DTRからAVRのRESET(1PIN)に0.1uFのコンデンサでカップリングする必要があります。TX、RXはAVR側とUSB Serialモジュール側とで入れ替えてTX→RX、RX→TXとします。

VCC(電源)はUSB Serial変換モジュールから供給するとややこしくなるので配線しないでおきます。

使ったUSB Serialモジュール


DTR、UART TX、UART RXが出力できるモジュールなら使えると思います。写真はCH340のモジュールですがFT232RLのモジュールでも同様にプログラミングできます。

Arduino IDEからATMega328PをプログラミングするにはあらかじめArduinoとして使うブートローダを書き込んでおく必要があります。Arduino Unoがあれば多少の追加投資で書き込みできます。「Arduino UNO のブートローダ書き込み」の「3 Arduino UNO をプログラマにして、Atmega328 に書き込む」を参考にしてください。

MCP4922の出力の両電源波形化

回路図のOPアンプU1Aの回路で、MCP4922のGND~+5Vの単電源波形を-5V~GND~+5Vの両電源波形に変換できました。

LTSpiceのシミュレーション結果を示します。



実際の回路の出力を示します。


出力フィルタの製作

以前8次MFB(多重帰還)LPF用の基板を作っていたので、小細工して4次用に実装しました。定数はベッセル特性、カットオフ周波数6.25kHz、4次MFBで設計しました。

シミュレーション結果

AC解析

過渡解析(@1kHz ±1V)

回路図

8次MFB用で単電源でも使える用にした基板パターンなので、ジャンパ線をはって不要な部分をバイパスしています。


測定結果

周波数特性

過渡応答 (@1kHz ±1V)


Core基板とFilter基板を接続


サイン波拡大画像


フィルターを掛けない場合(青色)階段状の波形です。製作したフィルターを掛けると(黄色)平滑化されます。

ノコギリ波(下降)トップ


オーバーシュートが出ています。アナログ発振器でもオーバーシュートは発生します。アナログ発振器の場合さらに急激なオーバーシュートになることが多いです。


ノコギリ波(上昇)ボトム


下側にもオーバーシュートが出ます。


矩形波トップ


一番なじみがあると思います。ベッセル特性の応答です。

矩形波ボトム


アンダーシュートがなくノイズが載っています。MCP4922からの出力の最小値はGNDなのでGNDのノイズでしょうか。