復元・復旧サービス充実|(有)フロンティア・オンライン

復元・復旧サービス充実|(有)フロンティア・オンライン

【深淵オーディオ】(10)Core'1 Mission Command Layer:ProAudio仕様PopoDAC(USB DAC) 自作(DIY)

‹ 2026/01/04 ›

こんばんは。10回目、いよいよコア(核心)に迫って説明を始めます。

(・・・Coreだけで連載になりそうです、汗)


まず、最新のPopoDAC完成度は対Windows180%、対iPhone99.95%まで到達しました。


PopoDAC連載終結までの結論から申し上げますと、完成度はこのスペックで完了としました。


「0.05残ってますけど?」

ええ、これは記念に残しただけで、諦めたわけではありません。


深部までトレースを繰り返し、データと調整を繰り返して、理解を深めた結果、対iPhone100%を狙う意味を失いました。

それは後ほどにでも、どこかで解説ができればと思います。


PopoDAC Core Architecture(Paxa-spec.)

では早速、Core(本丸)をガツンと出します。



PopoDAC Core Architectureは、中央管制独立3相駆動x8モジュール型、相間完全ロックフリーで回転する仕組みになります。

各役割も完全にPAXA仕様で整然に配置しました。


(もはや、飛ばないわけがない仕様です)


PopoDAC Core Architecture構成リスト

構成リストで表すと次のようになります。


Mission Command Layer(ミッション指令レイヤー)

- Module 1: Startup and Igniter(起動・点火)


Guidance & Navigation Control(誘導・航法・制御)

- Module 2: Real Time Sensor(リアルタイム観測)

- Module 3: Timeline Adjuster(時間軸補正)

- Module 4: Feedback Uplinker(フィードバック上りリンク)


Telemetry Downlink Receiver(テレメトリ下り処理)

- Module 5: Data Telemetry Receiver(データ・テレメトリ)


Timeline Dynamics Processor(時間軸動力学プロセッサ)

- Module 6: Velocity Vector Stabilizer(速度ベクトル安定化)

- Module 7: Timeline Interpolation(時間軸補間)

- Module 8: Timeline Thruster(時間軸スラスター出力)


かっこよすぎですね。

これで、3レイヤーに分われた全8モジュールを積み上げて適切稼働すれば、MASTER TIMELINE(真の時間軸)に吸着させてSpeakerを鳴すことができます。


SOFとは?

では早速Module解説へ・・・、と、いきたところなのですが!

UACの回でひとつ、とても重要な話題を取り上げませんでしたので、ここで解説をします。


「Start Of Frame」これはUSBプロトコルクラスの心臓(鼓動)そのものです。

UAC1では1ms境界、UAC2では125us境界の絶対門番と思っておられる方が99%に達するかと思います。


ここで一旦、MASTER TIMELINEを正しく理解する前に、SOFの誤解を解いておきます。


これは恐らくこの連載中で一番重要で一番誤解しやすい話題です。


(耐えてここまで読み込んで来られた方、たまたま見に来ちゃった方、PCに興味がある方、そんな方々は大変ラッキー回♪です、汗)


では、図解を見てみてみましょう。


前段はPCからUSBメモリーにファイルを落とす作業です。

例えばWindowsは親切ですので、転送中の速度、転送バイト量が見られ、グラフまで付いています。


見覚えがあるグラフが書かれていますね。

転送は速度を波打ちながら時間ともにファイル終端まで転送を繰り返していきます。


この時、誰しも全人類が待っている答えは、「originalとcopyの転送結果データが一致しているか否かの一点のみ」となります。


そして、この波打つ動きはUSBに限らずUSBなら尚更当たりまえに常に発生している揺れとなりますが、これを気にして止まないという方は少ないかと思います。

寧ろそれよりも全体なコピー時間と結果を気にしますね。


さて、次は同じくUSB機器の一種USBDACをSpeaker出力した場合を考えてみます。

USB機器ですので、UACだけがUSBのジレンマから逃れる仕様にはそもそもなっておりません。

寧ろUSBの親規格に縛られ続けているのがUACです。


音楽ファイルをUAC1/2に関わらずホスト送信したとしても、実は転送中のデータは同じ揺らぎの中で時を進めます。

従って、I2S側に高精度MCLKを積んだとしても、ホスト側とTIMELINEがリンク(時ロック)していなければ、DACが奏でる音楽は実はまるで揺らいだ時の中を演奏していることになります。


リンクしない形で高精度MCLKを積んだ場合、ジッタが安定するという成果があったとしても、ロックしてませんので、楽曲の曲調全長は実はぐらぐら揺れているのです。

結局のところ、ジッタが少ない(ほぼない)ので、クリアに聞こえているだけで、音は揺れてますので、聞き疲れの大きな要因、音像ホラー、定位ズレを抱えたままとなります。

またこの状況を見過ごしている場合、条件が重なればチャンネルスワッピングを引き起こす因子になる可能性も含んでいます。


では絶対軸だと思ったSOFの正体はなんでしょうか?


遂行しているSOFがしてくれる唯一の保証は「インターバル毎に起こすフレーム境界とパケットの整合性」です。


1ms/125usの絶対門番と思われがちですが、実際には“境界の通知”であって、時間精度そのものを保証しているわけではないのですよねぇ。><

だから揺れます。


どうしてこんな事(仕様)になるのでしょう?


「作り込みが悪いだけじゃね?」

いいえ。


もともとUSBの規格はPC側のRS232等ペリフェラルの次世代規格として策定されるに至っている以上、中心となるホストはマルチタスクで何でもやらせようぜ主義です。

高速化は待ち望んでいるが、かといってリアルタイム性最高主義じゃ、使える道具にならねぇということです。

つまり、ここが発端ですので、厳守すべきはパケット内容の信頼性が最優先ということになります。


そもそもUSBDACでは「リアルタイム性の追求ができにくいのでDSDで何とかならんか?!」という時代でもありますね。


それをあえて、実クロックのリアルタイム検波までして、TIMELINEに落とし込むという思想で設計されているのが、「PopoDAC Core Architecture」になります。


(ひえ~、くどくてごめんなさい、汗)


Mission Command Layer

では本筋に戻って・・・、Core初回は、MainLoopより、MCL(Mission Command Layer)の実装を紹介いたします。


Module1 Igniter

MCLで行う大事な仕事はModule1のひとつとなります。


ここでは単純に3相8モジュールの初期化と点火を行います。

(もちろんMCLには消化も必要ですが、そこは説明を省きます)


どんなタイミングで点火するのかと云いますと、USBホストよりALT1(UAC Output EP)を有効にしてくれ通知が来た際となります。

従って呼び出し元ファンクションは、'tud_audio_set_itf_cb'コールバックとなります。


i2s_prime_and_enable

static void i2s_prime_and_enable(i2s_chan_handle_t handle)

{

    // 監視パラメータ初期化

    g_ddc.ddc_ring_looses = 0;

    g_ddc.ddc_ring_wl = 0.0f;

    g_ddc.ddc_rx_size = 0,

    g_ddc.ddc_tx_size = 0,


    // PCNTカウンタ初期化

    normal_fscount(&g_cnt_ideal); // ノーマルポジションセット


    // Clock基準値初期化

    g_cnt_ideal.last_fb = (1 << 14),   // ★ Q14 の 1.0

    g_cnt_ideal.acc_q14 = 0,

    g_cnt_ideal.delta_fb_q = 0,

    g_cnt_ideal.source_clock_ratio = 1.0f,  // ★ ASRC 初期比率


    // I2S発射台準備

    g_ddc.i2s_starting  = true;

    g_ddc.i2s_start_ms  = xTaskGetTickCount();

    g_ddc.counter_warmup_rollout = false;

    g_ddc.i2s_src_locked = false;

    g_ddc.i2s_src_lockcount = 0;

    g_ddc.i2s_src_phase_ratio = 1.0f;

    g_ddc.i2s_src_smooth_ratio = 1.0f;

    g_ddc.i2s_src_phase = 0;

    g_ddc.i2s_src_in_idx = 0;

    g_ddc.i2s_src_prev_consumed = -1;

    g_ddc.i2s_pcnt_delta_hz = 0;


    // UAC発車準備

    g_ddc.uac_fb_inverval_count = 0;

    g_ddc.uac_fb_started = false;

    g_ddc.uac_fb_start_pending = true;   // ← ここが重要

    g_ddc.uac_fb_locked = false;

    g_ddc.uac_fb_lockcount = 0;

    g_ddc.uac_long_err_mHz = 0;


    // 前回再生までのバッファ残があればクリア

    rb_dropall(&g_audio_ring);


    // カウントタイマーリブート

    gptimer_stop(g_gptimer);

    gptimer_start(g_gptimer);


    // Now enable to start DMA

    i2s_channel_enable(handle);

}


どうでしょう、流れは綺麗で普通ですね。


3相のうち、最後に有効化するのがI2Sとなります。

そこまでの間に、Timer相(Guidance & Navigation Control)を初期化してプライムロールを走らせます。


UAC相(Telemetry Downlink Receiver)はTinyUSBが無休活動してますので、参照元コールバックを返却した後、速やかにバッファ提供を始めます。


それと何気なく書いてありますが、案外見落としなのは、「i2s_channel_enable」そのものの都度実施というところです。

つまり、I2S相(Timeline Dynamics Processor)は、UACのALT1(UAC Output EP)開閉シグナルに伴って、確実に開始・停止をしなくてはいけません。


これはどのようなUSBDACを自作したとしても、必須で行う必要があります。


「え?i2s_initで開けっ放しじゃダメなの?」

はい、ダメです。動くことは動きますが、それでもだめです。


ALT1の開閉シグナルって、言ってしまえばUACによるストリームの境界の通知です。


「ストリームの境界って何でしょうか?」

これは、定義と実装が100%あっているわけではありませんが、定義的には曲の変わり目だったり、Youtubeでいえば番組の切り替わりタイミングだったりです。


例えば2曲目に移行したのにALT1が開閉しなかったとすれば、次曲の揺れがそのまま次曲に伝わってしまいます。

そこでUSBホスト側は一度溜まったストレスを開放、初期化しして、次の曲の転送にかかる負担・負荷を下げます。


これはOS低層のドライバだけでなく表面層のアプリケーションにもかかわる仕事になりますので、アプリの個性や目的によって開閉の仕方に多少差があります。

従って曲当たり確実とは言い切れませんが、少なくともALT1を閉じたタイミングは、USB側は転送をリセットしたがっていると思ってください。


そのチャンスを使ってこちらもリセットするのが筋となります。

面倒でもI2S開閉はOutput EP開閉に連動しといたほうがよろしいということになります。


では、今回はModule1の説明まで、次回はUAC相(Telemetry Downlink Processing)をやります!

お楽しみに~♪