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

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

【深淵オーディオ】(12)Core'3 Guidance & Navigation Control:ProAudio仕様PopoDAC(USB DAC) 自作(DIY)

‹ 2026/01/07 ›

こんにちは。12回、Core'3は、GNC相(Guidance & Navigation Control、旧称Timer相)の解説、いよいよ真の心臓'鼓動'の回です!


「待ってました!?」

きっと、ここまで読み進められて来られた方には待望の回になりますね。^^


で、その前に、PopoDACの完成度に進展がありました!

な、ななんと!対Windows/対Mac完成度200%、対iPhone完成度100%に到達しました。^^


(いや~、我ながらびっくり感動)


ちなみに、ここで言う完成度100%超とは、「MASTER TIMELINEにきちんと乗ったうえで、さらに機器ごとの個性を引き出し、“味付け調整”できる段階」に到達していることを意味しています)



そして残っていた100%未満のiPhone 0.05%ズレは、実はiPhone側ではなく・・・。

Lightning Camera Adapter(互換品)が原因でした。汗


Lightning Camera Adapterを交換し、iPhoneは既にPopoDAC MASTER TIMELINE上で踊れることが分かりました。^^

(いや~、iPhoneともカッチリ手を結んだうえで再生してる確認ができてよかったです)


GNC相

まず、GNC相をPaxa的に一言で表しますと、それは探査機の出力制御・姿勢制御をするための超正確&精密なセンシングと探査機側への補正フィードバックを行う稼働部となります。


出力はI2S相が担当しますが、事前条件としてGNCでDDCとUACとが正しい制御下の元にありませんと、スラスターをかけても探査機は正しい方向正しい速度で進んでくれません。

加えて、PopoDACのI2S相出力は量子(ひと粒)単位のスラスター出力であり、単純なバケツリレー(パケットリレー)ではありません。


例えば、求める音質(サンプリング分解能)が、サンプルレート96KHzだったとしましょう。


「その中の量子ひと粒が求めるTIMELINE上の正しいステートポジション(着地点)はどこになるでしょうか?」

1ms上の景色で大きく言えば「1/(96000/1000)=約1/96ミリ秒の中のある一点」になります。


勿論、これはMCLK_MULTIによってBCLK分解能が上がれば1/96より更に細かい範囲に着地点が広がります。

そうなのです、PopoDAC DCCが求めるTIMELINE上の分解能は、超高精度である必要があります。


そして、ESP32-S3/IDFの新しいPLL分周(XYZ分周)が精密に時を刻め、PCNTを実測できることは先の回で実証した通りです。

ここでは、96KHzの奥、更に微細な時の狭に割込み、1/96以上の刻みを手に入れ、この精密さでUSBホストと協調したTIMELINEを作り出していきます!^^


モジュール構成

GNC相のモジュール構成は次の3パートとなります。


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

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

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


次はこの3パートによって作り出だされるストーリーを図で見てみます。

UAC端に届くSOFの揺れをDDC-GNC相で計測・フィードバックし吸収緩和、PLLで刻んだ真のMCLKを元に真の着地点をI2S相へ通知する。

この3パートが適切に仕事をすれば、PopoDAC DDCは真のMASTER TIMELINE(MTL)を手に入れ、UACとI2Sの双方を操る「時の操者」になります。


SCK(MCLK)ターゲット

さて、ここで一旦、PCM5102Aのデータシートに戻り、PopoDACがターゲットとするMCLK(DAC側SCK)を選定し直してみます。



48KHzでは256fS~1024fS、96KHzでは256fS~512fS、192KHzは256fSと、PCM5102Aの限界点まで引き出せる範囲で選択できそうです。

(これでフル稼働できれば大満足ですね、^^)


PCNT-BCLKカウンタへ

「ん?96KHz512fS(49.152MHz)ってPCNTじゃ無理じゃね?」

はい、その通りで、素直にMCLKをPCNTで数えようとすると検出能力が20数MHzまでなので足りません。

でも、ここはカウント対象をMCLKから“分周後のBCLK”に切り替えることで克服できます。汗


はい、MCLKとMCLK_MULTI実値さえおさえていれば、PCNTのカウント対象をMCLK分周のBCLKに変更することでこの問題は回避できます。


init_monitor改

具体的には、pcnt_config_tの観察ピンを変えるだけとなります。

(もちろんカウンタ数がBCLK由来であることを忘れずに実測fSを求める必要があります)

    pcnt_config_t pcnt_config = {

        .pulse_gpio_num = PIN_MONITOR, // BCKを入力、MCLKだとちょいともたない

        .ctrl_gpio_num = PCNT_PIN_NOT_USED,

        .channel = PCNT_CHANNEL_0,

        .unit = SYNCLK_PCNT_UNIT,

        .pos_mode = PCNT_COUNT_INC,

        .neg_mode = /*PCNT_COUNT_INC*/PCNT_COUNT_DIS, // ネガティブ側もカウントする(カウント開始がPかNかでカウント数に誤差がでるため)

        .lctrl_mode = PCNT_MODE_KEEP,

        .hctrl_mode = PCNT_MODE_KEEP,

        .counter_h_lim = INT16_MAX,

        .counter_l_lim = 0,

    };


本来は、neg_modeも計測対象にしたいのですが、カウント数リミットと照らし合わせ、ここではポジティブカウントのみに留めました。


「そうすると、BCLKとPCNTが真に正しくても実測±2精度程には揺れるけど、1/96で許せる範囲?」

はい、大丈夫です。

±2カウントの揺れは、今回の1/96秒スケールで見れば十分に許容範囲の誤差であり、TIMELINE設計上の要件を満たしています。

そして、勿論、これを念頭に入れてGNCを組み上げる必要があります。


Module 2: RTS(Real Time Sensor)

RTSの仕事は単純明快です。

精度の高いTimer間隔の下、正確にBCLKを検波カウントするのみとなります。


留意点はたった一つ、一度ALT1が開いたらALT1が閉じるまで、決してカウンタをリセットしないという点のみです。

つまり、ひたすらpcnt_get_counter_valueを取り続けます。


「あれ?カウント許容数がint16範囲しかありません、それは無理です!」

いえ、無理ではありません。

その状況下でやり続ける必要があります。


「計測毎にカウンタリセットしたっていいじゃないのですか?」

いいえ、100%ダメです。


前述のストーリー図で示した通り、ひとつのMTL(SOFに変わる真の刻み)は前のMTLと整合して初めて、正当性が導きだせます。

都度、カウンタをリセットしてしまうと、MTLの“前後関係”が失われ、時間軸が分断されてしまうためです。


そして、幸いにしてPCNTカウンターはカウント最大の次はカウント最小に戻ります。

つまり、次カウントまでに2周目に入らないパルス速度であれば、途切れと揺れ双方が生じない計測ができます。


この制約を乗り越えるため、PopoDACではMCLKとMCLK_MULTIを一対で重要視し、破綻のないプリセットを定義・選択する仕様になっています。


Module 3: TLA(TimeLine Adjuster)

TLAは、GNC相の中で最も慎重かつ丁寧、そして精密であり柔軟な演算を必要とするパートになります。


実際には、RTSで得られた真のカウント、過去のカウントを鑑み、Feedbackでホストに促すべきfS値を割り出します。

つまりSOFとMTLの揺らぎを考慮した上、ホストがMTL上に乗るための一手を打つ、実質的な決め手となります。


この揺らぎは、単純ではありません。

都度発生している揺れ、長期的な影響を与える揺れ、これらが複雑に混ざり合っています。


そして、揺れの短・長期、大きさの違いは、ホストOSだけではなくアプリのUAC動作挙動によって、個々の個性によって生まれます。

TLAではUACの揺れの根源を捉えるところまで、実装を深める必要があります。

Module 4: FUL(Feedback UpLinker)

FULで行う仕事はGNC相で最もシンプルなものとなります。


TLAで得られた次の一手fSをUAC1.0/2.0の規格に合わせた数値として、USBホストへFeedbackすればよいだけです。

つまり、UAC1.0/2.0 FS(FullSpeed)であれば10.14固定小数点、UAC2.0 HS(HighSpeed)では16.16固定小数点になります。


フィードバック送信間隔は、基本的にはtud_descriptor_configuration_cbで返答した’Feedback Endpoint Descriptor’項目のbIntervalであれば良いです。

(基本的にはです・・・、USBホスト側個性もありますので、ひと工夫したほうが良い場合もあります)

(そして・・・、iPhoneを視野に入れる場合、ひと工夫はMUSTの可能性のほうが高いです、汗)


さて、GNC相ではこの3つのパートをストレスなく実行し、MASTER TIMELINE上にSOFを吸着させれば、役目は完遂しています。

そして、ここで得られる状況ステータスはCCCにより引き上げられ、I2Sへと受け継がれます。


実装例

ではGNC相の実装例の紹介に入ります。


PopoDACではGNC相はGPTimer起点となりますので、実装は発動部と実働部に分かれます。


発動部

bool IRAM_ATTR monitor_task_gpt_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)

{

    // 1ms ごとに「やれ」のフラグを立てるだけ

    if (g_app_started) {

        BaseType_t xHigherPriorityTaskWoken = pdFALSE;


        vTaskNotifyGiveFromISR(g_monitor_task_handle, &xHigherPriorityTaskWoken);


        if (xHigherPriorityTaskWoken) {

            portYIELD_FROM_ISR();  // ISR終了直後にタスク実行

        }

        //g_monitor_tick = true;

    }

    return true;

}


発動部monitor_task_gpt_isrはとてもシンプルです。

ソースコメントの通り、やってくれ指令一発のみとなります。^^


実働部

void monitor_task_gpt(void *param)

{

    ESP_LOGI(TAG, "monitor_task_gpt enter");


    // 起動完了まで適当に待つ

    while (!g_app_started) {

        vTaskDelay(pdMS_TO_TICKS(1));

    }


    while (1) {


        // GPTimer ISR からの通知を待つ(無限待ち)

        // → 1 回の通知 = 1ms 分の処理

        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);


        // 全IOが稼働し始めるまでは何もしない

        if (g_app_started) 

        {

            // RTS実施

            g_ddc.counter_warmup_rollout = get_fscount(&g_cnt_ideal);


            // TLA(デルタ算出)

            fscount_to_fb_q(&g_ddc, &g_cnt_ideal);


            // FB Lock

            if (g_ddc.uac_fb_start_pending)

            {

                g_ddc.uac_fb_started = true;

                g_ddc.uac_fb_start_pending = false;

            }


            // FUL

            uint32_t fb_to_send = g_cnt_ideal.last_fb;

            if (!g_ddc.uac_fb_locked) {

                fb_to_send = ((g_ddc.uac_quality.sample_rate * (1u << 14) 500) / 1000);   // ← VLCのリセット甘さ対応、嘘通知

            }

            uac_send_feedback(fb_to_send, 0);

        }

    }

}


実働部monitor_task_gptは、RTS、TLA、FULまでを1手ごとに進めるのみ。

ここも綺麗です。^^


実際のところRTSでは小数点演算はありませんので、発動部で実施することもできます。

ですが、その場合はTLA、FULとの距離感があり、折角計算したfSに余計な時差を生むことになりますので、実働部へ整然と並べるが良いでしょう。


Feedbackタイミング

そして、実装で最も重要な留意点は、Feedbackのタイミングとなります。


「AUDIO_SOFでFB送信が正解じゃないの?」

答えはYES、NOのどちらのケースもありえます。

そして、PopoDACではNOです。


今一度、GNCの役割を思い出してください。

PopoDAC GNCは真のMTLを構築するのが主目的であり、SOFは吸着する側となります。


つまり、Feedbackは “SOFに合わせる”のではなく、“SOFをMTLへ吸着させるための一手”といえます。

そして、MTL内でFeedback通知を行うことこそが、その一手を最大効果に導きます。


アプリケーション誤差

こうして仕上がったGNCを作動しますと、MTLの影響力はUSBホストの実行アプリケーションにまで広がります。


例えばWindows上のWinampは素直な作りでUSBドライバの低IOまで丁寧な操作をしている、一方でVLCはUSBに直接触れていないせいかALT1閉時に次曲へ繋がるゴミを残すことがある。

また、Youtubeなどのストリーム配信系では、タイトルの切替、停止再開時などでもALT1の開閉を行わないケースが多い。

などなど、アプリケーション固有の癖が見えてきます。


これらの癖は、PopoDACの次曲の再生へも影響を及ぼすこともあります。

そのため、分かる範囲でアプリケーション誤差を吸収する手立てを入れることも可能となります。


ではこれにてGNC相の解説を終わります。

次回は、音質上極めて重要なI2S相の話となります。

そうです、I2Sスラスター出力の話です!!

これはもう見逃せません。^^


お楽しみに♪