--- nono/vm/adpcm.cpp 2026/04/29 17:05:13 1.1.1.1 +++ nono/vm/adpcm.cpp 2026/04/29 17:05:59 1.1.1.8 @@ -8,162 +8,311 @@ // ADPCM (MSM6258V) // +// ミキサー周波数はこれらがすべて割り切れるよう 62500 [Hz] (= 16 usec) +// にする。 +// +// clk div freq mixerfreq に対する倍率 +// ---- ---- -------- --- +// 8MHz 512 15625 4 +// 8MHz 768 10416.67 6 +// 8MHz 1024 7812.5 8 +// 4MHz 512 〃 +// 4MHz 768 5208.33 12 +// 4MHz 1024 3906.25 16 + +// ある 16 usec * (freq による倍率) の時間内での書き込みは、このサンプル +// の代表値。1期間内に1回しか来ないはず。 +// かつ、ブロックは 40 msec で割り切れる時間ごとに区切られているため、 +// 前回の書き込み位置の次、ではなく、現在時刻に応じた位置に書き出す。 + #include "adpcm.h" #include "dmac.h" -#include "mpu.h" -#include "pio.h" +#include "event.h" +#include "mainbus.h" +#include "monitor.h" #include "scheduler.h" - -// グローバル参照用 -ADPCMDevice *gADPCM; +#include "sound.h" // コンストラクタ ADPCMDevice::ADPCMDevice() - : inherited("ADPCM") + : inherited(OBJ_ADPCM) { - devaddr = baseaddr; - devlen = 0x2000; - - event.func = ToEventCallback(&ADPCMDevice::EventCallback); - event.Regist("ADPCM"); + monitor = gMonitorManager->Regist(ID_MONITOR_ADPCM, this); + monitor->SetCallback(&ADPCMDevice::MonitorScreen); + monitor->SetSize(40, 7); } // デストラクタ ADPCMDevice::~ADPCMDevice() { - gADPCM = NULL; +} + +// 初期化 +bool +ADPCMDevice::Init() +{ + dmac = GetDMACDevice(); + + sound = GetSoundRenderer(); + track = sound->RegistTrack(this); + if (track == NULL) { + // エラーメッセージは表示済み。 + return false; + } + + mixer_frame_tsec = 1_sec / sound->GetMixerFreq(); + + auto evman = GetEventManager(); + event = evman->Regist(this, + ToEventCallback(&ADPCMDevice::EventCallback), + "ADPCM"); + + return true; } // リセット void ADPCMDevice::ResetHard(bool poweron) { - playing = false; - dack = false; + StopPlay(); + + stepidx = 0; + xprev = 0; // ? - clk = 8000'000; + clkMHz = 8; + // div は PPI の初期化でセットされるはず。 div = 512; + + // リセットからコマンド受け付けまで 16 msec (MSM6258.pdf) } -uint64 -ADPCMDevice::Read(uint32 offset) +busdata +ADPCMDevice::ReadPort(uint32 offset) { - uint8 data; - - gMPU->AddCycle(18); // InsideOut p.135 + busdata data; switch (offset) { case 0: // STAT - data = 0x40; - if (playing) { - data |= STAT_PLAY; - } - putlog(1, "STAT -> $%02x", data); + data = GetSTAT(); + putlog(1, "STAT -> $%02x", data.Data()); break; case 1: // DATA - putlog(0, "Read $%06x (NOT IMPLEMENTED)", gMPU->GetPaddr()); - data = 0xff; + putlog(0, "Read $%06x (NOT IMPLEMENTED)", + GetMainbusDevice()->GetPaddr()); + data = databuf; break; default: - __unreachable(); + VMPANIC("corrupted offset=%u", offset); } + // InsideOut p.135 + const busdata wait = busdata::Wait(18 * 40_nsec); + data |= wait; + + data |= BusData::Size1; return data; } -uint64 -ADPCMDevice::Write(uint32 offset, uint32 data) +busdata +ADPCMDevice::WritePort(uint32 offset, uint32 data) { - gMPU->AddCycle(22); // InsideOut p.135 - switch (offset) { case 0: // CMD putlog(1, "CMD <- $%02x", data); + // XXX 同時に立ってるとどうなる? if ((data & REC_START)) { putlog(0, "Start recording (NOT IMPLEMENTED)"); } if ((data & PLAY_START)) { - putlog(0, "Start playing (NOT IMPLEMENTED)"); StartPlay(); } if ((data & STOP)) { - putlog(0, "Stop (NOT IMPLEMENTED)"); - playing = false; + StopPlay(); } break; case 1: // DATA - // 未実装だけど再生中の書き込みは来るのは分かっているので抑えておく - if ((playing && loglevel >= 2) || playing == false) { - putlogn("DATA <- $%02x (NOT IMPLEMENTED)", data); - } + WriteData(data); break; default: - __unreachable(); + VMPANIC("corrupted offset=%u", offset); } - return 0; + // InsideOut p.135 + const busdata wait = busdata::Wait(22 * 40_nsec); + + busdata r = wait; + r |= BusData::Size1; + return r; } -uint64 -ADPCMDevice::Peek(uint32 offset) +busdata +ADPCMDevice::PeekPort(uint32 offset) { - // XXX 未実装 - return 0xff; + switch (offset) { + case 0: // STAT + return GetSTAT(); + + case 1: // DATA + return databuf; + + default: + VMPANIC("corrupted offset=%d", offset); + } +} + +bool +ADPCMDevice::PokePort(uint32 offset, uint32 data) +{ + return false; } -// 再生開始 void -ADPCMDevice::StartPlay() +ADPCMDevice::MonitorScreen(Monitor *, TextScreen& screen) { - // PPI で設定されているサンプリングレートをここで読み出す。 - // XXX パンは未対応 - switch (gPPI->GetADPCMRate()) { - case 0: - div = 1024; - break; - case 1: - case 3: // %11 は %01 と同じになる (Inside p295) - div = 768; - break; - case 2: - div = 512; - break; - default: - __unreachable(); + int y; + + // Address: $e92000 + // + // Mode: Recording + // Freq: 15625 Hz (clk=8MHz div=1024) + // Pan : L R + + screen.Clear(); + y = 0; + + screen.Puts(0, y++, "Address: $e92000"); + y++; + screen.Puts(0, y, "Mode:"); + if (playing) { + screen.Puts(6, y, "Playing"); + } else { + screen.Puts(6, y, "Stopped"); + } + y++; + + screen.Print(0, y, "Freq: %u Hz", freq); + screen.Print(15, y, "(clk=%uMHz div=%u)", clkMHz, div); + y++; + + screen.Print(0, y, "Pan :"); + screen.Putc(6, y, 'L' | TA::OnOff(panout_L)); + screen.Putc(8, y, 'R' | TA::OnOff(panout_R)); + y++; + y++; + + screen.Print(0, y, "Internal: Xprev=%-5d StepIdx=%d", xprev, stepidx); +} + +// STAT レジスタの内容を返す。 +uint32 +ADPCMDevice::GetSTAT() const +{ + uint32 data; + + data = 0x40; + if (playing) { + data |= STAT_PLAY; } + return data; +} + +// 再生開始。 +void +ADPCMDevice::StartPlay() +{ + putlog(1, "Start playing %uHz", freq); playing = true; - gDMAC->AssertREQ(3); + xprev = 0; + sound->StartPlay(track); - event.time = div / clk; - gScheduler->StartEvent(event); + dmac->AssertREQ(3); + + event->time = guest_frame_tsec * 2; + scheduler->StartEvent(event); +} + +// 再生停止。 +void +ADPCMDevice::StopPlay() +{ + if (playing) { + putlog(1, "Stop playing"); + } + playing = false; + + // REQ はどうなる? + + scheduler->StopEvent(event); + + sound->StopPlay(track); +} + +// データ書き込み。 +void +ADPCMDevice::WriteData(uint32 data) +{ + if (__predict_false(playing == false)) { + // 非再生中の書き込み? + putlog(1, "DATA <- $%02x (Not Playing)", data); + } else { + putlog(2, "DATA <- $%02x", data); + } + databuf = data; } // イベント void -ADPCMDevice::EventCallback(Event& ev) +ADPCMDevice::EventCallback(Event *ev) { - // XXX 適当 if (playing) { - gDMAC->AssertREQ(3); + DecodeADPCM(databuf); + + // 空いたので REQ を上げる。 + dmac->AssertREQ(3); } + + event->time = guest_frame_tsec * 2; + scheduler->StartEvent(event); +} + +// DACK 信号をアサートする (DMAC から呼ばれる)。 +// 実際には PEDEC がやってるはず?。 +// ここではデータの受け渡しが完了したはずなのでその REQ を下げるだけ。 +void +ADPCMDevice::AssertDACK() +{ + dmac->NegateREQ(3); +} + +// パンアウト L を設定する。PPI から呼ばれる。 +void +ADPCMDevice::SetPanOutL(bool on) +{ + panout_L = on; } -// DACK 信号をアサートする (DMAC から呼ばれる) +// パンアウト R を設定する。PPI から呼ばれる。 void -ADPCMDevice::AssertDACK(bool tc_) +ADPCMDevice::SetPanOutR(bool on) { - dack = true; - // DRQ を下げる - gDMAC->NegateREQ(0); + panout_R = on; } -// DACK 信号をネゲートする (DMAC から呼ばれる) +// サンプリングレートを設定する。PPI から呼ばれる。 void -ADPCMDevice::NegateDACK() +ADPCMDevice::SetRate(uint32 rate) { - dack = false; + assert(rate < 4); + + // %11 は %01 と同じになる (Inside p295) + if (rate == 3) { + rate = 1; + } + + div = 1024 - rate * 256; + CalcRate(); } // クロック切り替え。OPM から呼ばれる。 @@ -172,8 +321,201 @@ void ADPCMDevice::SetClock(bool ct1) { if (ct1) { - clk = 4000'000; + clkMHz = 4; } else { - clk = 8000'000; + clkMHz = 8; + } + CalcRate(); +} + +// div と clk から必要なパラメータを計算する。 +void +ADPCMDevice::CalcRate() +{ + // 1フレームにかかる時間。15625 Hz なら 64 usec。 + guest_frame_tsec = usec_to_tsec(div / clkMHz); + + // 周波数。主に表示用。 + freq = clkMHz * 1000'000U / div; + + // アップスケール用の倍率。15625Hz なら 4倍。 + scale_factor = guest_frame_tsec / mixer_frame_tsec; + + // イベント名に載せておく。 + event->SetName(string_format("ADPCM %u Hz", freq)); +} + +// ADPCM ストリームの1バイトをデコードする。(再生) +void +ADPCMDevice::DecodeADPCM(uint32 data) +{ + int16 s1 = adpcm2pcm_step(data & 0x0f); + int16 s2 = adpcm2pcm_step(data >> 4); + + uint64 vtime = scheduler->GetVirtTime(); + + // 1サンプル目。 + WritePCM(vtime, s1); + // 2サンプル目。 + WritePCM(vtime + guest_frame_tsec, s2); +} + +// vtime に相当する位置に PCM データを書き込む。 +// その際、値は pcm0 から target までを線形補間し、 +// target を次の pcm0 とする。 +void +ADPCMDevice::WritePCM(uint64 vtime, int16 target) +{ + uint64 vseq = vtime / Sound::BLK_TSEC; + uint64 voff = vtime % Sound::BLK_TSEC; + + // 40msec バッファでは 1バイト (2サンプル) がバッファをまたぐ事があるため、 + // サンプルごとに行う必要がある。 + int16 *blkbuf = track->AllocSeq(vseq); + assert(blkbuf); + + // このフレームがミキサーバッファのどの位置か。 + uint32 mixer_frame_idx = + voff / guest_frame_tsec * Sound::NCHAN * scale_factor; + int16 *d = blkbuf + mixer_frame_idx; + + // ここから scale_factor 個で pcm0 から target になるよう線形補間する。 + int16 delta = (target - pcm0) / scale_factor; + int16 val; + + val = pcm0; + for (uint i = 0; i < scale_factor; i++) { + val += delta; + *d++ = __predict_true(panout_L) ? val : 0; + *d++ = __predict_true(panout_R) ? val : 0; + } + pcm0 = target; +} + +// エンコード (PCM -> ADPCM) 側は録音方向なので使ってない。 +// +// let Bit3 = Bit2 = Bit1 = Bit0 = 0 +// if (d(n) < 0) +// Bit3 = 1 +// d(n) = ABS(d(n)) +// if (d(n) >= ss(n)) +// Bit2 = 1; d(n) = d(n) - ss(n) +// if (d(n) >= ss(n) / 2) +// Bit1 = 1; d(n) = d(n) - ss(n) / 2 +// if (d(n) >= ss(n) / 4) +// Bit0 = 1 +// L(n) = (0b1000 * Bit3) + (0b100 * Bit2) + (0b10 + Bit1) + Bit0 +inline uint32 +ADPCMDevice::adpcm_encode(int32 dn) +{ + uint32 Ln = 0; + + int ss = adpcm_stepsize[stepidx]; + + if (dn < 0) { + Ln = 0x8; + dn = -dn; + } + if (dn >= ss) { + Ln |= 0x4; + dn -= ss; } + ss /= 2; + if (dn >= ss) { + Ln |= 0x2; + dn -= ss; + } + ss /= 2; + if (dn >= ss) { + Ln |= 0x1; + } + return Ln; } + +// d(n) = (ss(n) * Bit2) + (ss(n) / 2 * Bit1) + (ss(n) / 4 * Bit0) + (ss(n) / 8) +// if (Bit3 = 1) +// d(n) = d(n) * (-1) +// X(n) = X(n-1) + d(n) +inline int32 +ADPCMDevice::adpcm_decode(uint32 data, int32 xn) +{ + int b3 = (data & 8) ? 1 : 0; + int b2 = (data & 4) ? 1 : 0; + int b1 = (data & 2) ? 1 : 0; + int b0 = (data & 1); + + int32 ss = adpcm_stepsize[stepidx]; + int32 dn = (ss * b2) + (ss / 2 * b1) + (ss / 4 * b0) + ss / 8; + if (b3) { + dn = -dn; + } + + xn += dn; + + // Saturate in 12 bits. + if (__predict_false(xn > 2047)) { + xn = 2047; + } else if (__predict_false(xn < -2047)) { + xn = -2047; + } + + return xn; +} + +inline void +ADPCMDevice::adpcm_calcstep(uint32 data) +{ + stepidx += adpcm_stepadj[data]; + if (__predict_false(stepidx < 0)) { + stepidx = 0; + } else if (__predict_false(stepidx > 48)) { + stepidx = 48; + } +} + +uint32 +ADPCMDevice::pcm2adpcm_step(int16 pcm) +{ + // Input of this algorithm is 12 bit. + int32 xn = pcm / 16; + int32 dn = xn - xprev; + uint32 Ln = adpcm_encode(dn); + + // next はこの Ln をデコードして PCM にした値 + xprev = adpcm_decode(Ln, xprev); + adpcm_calcstep(Ln); + + return Ln; +} + +int16 +ADPCMDevice::adpcm2pcm_step(uint32 data) +{ + int32 xn = adpcm_decode(data, xprev); + + // next + xprev = xn; + adpcm_calcstep(data); + + // このアルゴリズムの出力は(下詰めで) 12 bit。 + // ただし MSM6258 の出力 DAC は 10 bit 分しかない。 + xn /= 4; + // その 10 bit を 16 bit PCM にする。 + xn *= 64; + return xn; +} + +/*static*/ const int +ADPCMDevice::adpcm_stepadj[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +}; + +/*static*/ const int32 +ADPCMDevice::adpcm_stepsize[49] = { + 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, + 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, + 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, + 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, +};