File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / adpcm.cpp
Revision 1.1.1.8 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:59 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
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,
};

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.