|
|
nono 1.7.0
//
// nono
// Copyright (C) 2022 nono project
// Licensed under nono-license.txt
//
//
// 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 "event.h"
#include "mainbus.h"
#include "monitor.h"
#include "scheduler.h"
#include "sound.h"
// コンストラクタ
ADPCMDevice::ADPCMDevice()
: inherited(OBJ_ADPCM)
{
monitor = gMonitorManager->Regist(ID_MONITOR_ADPCM, this);
monitor->SetCallback(&ADPCMDevice::MonitorScreen);
monitor->SetSize(40, 7);
}
// デストラクタ
ADPCMDevice::~ADPCMDevice()
{
}
// 初期化
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)
{
StopPlay();
stepidx = 0;
xprev = 0;
// ?
clkMHz = 8;
// div は PPI の初期化でセットされるはず。
div = 512;
// リセットからコマンド受け付けまで 16 msec (MSM6258.pdf)
}
busdata
ADPCMDevice::ReadPort(uint32 offset)
{
busdata data;
switch (offset) {
case 0: // STAT
data = GetSTAT();
putlog(1, "STAT -> $%02x", data.Data());
break;
case 1: // DATA
putlog(0, "Read $%06x (NOT IMPLEMENTED)",
GetMainbusDevice()->GetPaddr());
data = databuf;
break;
default:
VMPANIC("corrupted offset=%u", offset);
}
// InsideOut p.135
const busdata wait = busdata::Wait(18 * 40_nsec);
data |= wait;
data |= BusData::Size1;
return data;
}
busdata
ADPCMDevice::WritePort(uint32 offset, uint32 data)
{
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)) {
StartPlay();
}
if ((data & STOP)) {
StopPlay();
}
break;
case 1: // DATA
WriteData(data);
break;
default:
VMPANIC("corrupted offset=%u", offset);
}
// InsideOut p.135
const busdata wait = busdata::Wait(22 * 40_nsec);
busdata r = wait;
r |= BusData::Size1;
return r;
}
busdata
ADPCMDevice::PeekPort(uint32 offset)
{
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::MonitorScreen(Monitor *, TextScreen& screen)
{
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;
xprev = 0;
sound->StartPlay(track);
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)
{
if (playing) {
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;
}
// パンアウト R を設定する。PPI から呼ばれる。
void
ADPCMDevice::SetPanOutR(bool on)
{
panout_R = on;
}
// サンプリングレートを設定する。PPI から呼ばれる。
void
ADPCMDevice::SetRate(uint32 rate)
{
assert(rate < 4);
// %11 は %01 と同じになる (Inside p295)
if (rate == 3) {
rate = 1;
}
div = 1024 - rate * 256;
CalcRate();
}
// クロック切り替え。OPM から呼ばれる。
// CT1 = %0 で 8MHz、%1 で 4MHz。
void
ADPCMDevice::SetClock(bool ct1)
{
if (ct1) {
clkMHz = 4;
} else {
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,
};
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.