File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / ssg.cpp
Revision 1.1.1.3 (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) 2024 nono project
// Licensed under nono-license.txt
//

//
// SSG (YM2149)
//

#include "ssg.h"
#include "monitor.h"

// コンストラクタ
SSGDevice::SSGDevice()
	: inherited(OBJ_SSG)
{
	monitor = gMonitorManager->Regist(ID_MONITOR_SSG, this);
	monitor->SetCallback(&SSGDevice::MonitorScreen);
	monitor->SetSize(45, 12);
}

// デストラクタ
SSGDevice::~SSGDevice()
{
}

// リセット
void
SSGDevice::ResetHard(bool poweron)
{
}

// A0 でアドレスポートとデータポートを分けるみたいなのではなく、
// 謎の BDIR と BC1 を R/W と A0 につないであるっぽい感じなので、
// トラップみたいな配置になっている。(紙資料も間違えている…)
//
// BDIR BC1        ->	XP I/O
//  0    0  Inactive
//  0    1  Read		0x83 R
//  1    0  Write		0x82 W
//  1    1  Address		0x83 W

busdata
SSGDevice::Read1(uint32 addr)
{
	uint32 offset = addr & 1;
	uint32 data;

	if (offset == 0) {
		data = 0xff;	// ?
	} else {
		// 読み込み
		data = reg.Get(regno);
		putlog(3, "R%x -> $%02x", regno, data);
	}

	return data;
}

busdata
SSGDevice::Write1(uint32 addr, uint32 data)
{
	uint32 offset = addr & 1;

	if (offset == 0) {
		// データ書き込み
		putlog(2, "R%x <- $%02x", regno, data);
		switch (regno) {
		 case 0x0:	// Frequency of channel A (Low)
			reg.freq[0] &= 0xf00;
			reg.freq[0] |= data;
			break;
		 case 0x1:	// Frequency of channel A (High)
			reg.freq[0] &= 0x0ff;
			reg.freq[0] |= data << 8;
			break;
		 case 0x2:	// Frequency of channel B (Low)
			reg.freq[1] &= 0xf00;
			reg.freq[1] |= data;
			break;
		 case 0x3:	// Frequency of channel B (High)
			reg.freq[1] &= 0x0ff;
			reg.freq[1] |= data << 8;
			break;
		 case 0x4:	// Frequency of channel C (Low)
			reg.freq[2] &= 0xf00;
			reg.freq[2] |= data;
			break;
		 case 0x5:	// Frequency of channel C (High)
			reg.freq[2] &= 0x0ff;
			reg.freq[2] |= data << 8;
			break;

		 case 0x6:	// Frequency of noise
			reg.noise_freq = data & 0x1f;
			break;

		 case 0x7:	// I/O port and mixer settings
			reg.settings = data;
			break;

		 case 0x8:	// Level of channel A
			reg.ampmode[0] = (data & 0x10);
			reg.amplevel[0] = data & 0x0f;
			break;
		 case 0x9:	// Level of channel B
			reg.ampmode[1] = (data & 0x10);
			reg.amplevel[1] = data & 0x0f;
			break;
		 case 0xa:	// Level of channel C
			reg.ampmode[2] = (data & 0x10);
			reg.amplevel[2] = data & 0x0f;
			break;

		 case 0xb:	// Frequency of envelope (Low)
			reg.envelope_freq &= 0xff00;
			reg.envelope_freq |= data;
			break;
		 case 0xc:	// Frequency of envelope (Hight)
			reg.envelope_freq &= 0x00ff;
			reg.envelope_freq |= data << 8;
			break;

		 case 0xd:	// Shape of envelope
			reg.envelope_shape = data & 0x0f;
			break;

		 case 0xe:	// Data of I/O port A
			reg.dataA = data;
			break;
		 case 0xf:	// Data of I/O port B
			reg.dataB = data;
			break;

		 default:
			assertmsg(false, "regno=$%x", regno);
			break;
		}
	} else {
		// アドレス書き込み
		regno = data & 0x0f;
		putlog(4, "reg <- $%x", regno);
	}

	return 0;
}

busdata
SSGDevice::Peek1(uint32 addr)
{
	return reg.Get(regno);
}

void
SSGDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	// 012345678901234567890
	// R1/R0 Freq ChA  $xxx f=
	// R3/R2 Freq ChB  $xxx
	// R5/R4 Freq ChC  $xxx
	// R6    FreqNoise  $xx
	// R7    Settings   $xx(IB IA NC NB NA TC TB TA)
	// R8    Level A    $xx
	// R9    Level B    $xx
	// Ra    Level C    $xx
	// Rc/Rb EnvFreq  $xxxx
	// Rd    EnvShape   $xx
	// Re    Data A     $xx
	// Rf    Data A     $xx

	uint f;
	int y = 0;

	screen.Clear();

	SSG tmp = reg;

	for (int i = 0; i < 3; i++) {
		if (__predict_false(tmp.freq[i] == 0)) {
			f = 0;
		} else {
			f = fMaster / (16 * tmp.freq[i]);
		}
		screen.Print(0, y, "R%u/R%u Freq Ch%c  $%03x f=%u",
			i * 2 + 1, i * 2, 'A' + i, tmp.freq[i], f);
		y++;
	}

	if (__predict_false(tmp.noise_freq == 0)) {
		f = 0;
	} else {
		f = fMaster / (16 * tmp.noise_freq);
	}
	screen.Print(0, y++, "R6    FreqNoise  $%02x f=%u", tmp.noise_freq, f);

	screen.Print(0, y, "R7    Settings   $%02x(", tmp.settings);
	screen.Puts(21, y, TA::OnOff(tmp.settings & 0x80), "IB");
	screen.Puts(24, y, TA::OnOff(tmp.settings & 0x40), "IA");
	screen.Puts(27, y, TA::OnOff(tmp.settings & 0x20), "NC");
	screen.Puts(30, y, TA::OnOff(tmp.settings & 0x10), "NB");
	screen.Puts(33, y, TA::OnOff(tmp.settings & 0x08), "NA");
	screen.Puts(36, y, TA::OnOff(tmp.settings & 0x04), "TA");
	screen.Puts(39, y, TA::OnOff(tmp.settings & 0x02), "TA");
	screen.Puts(42, y, TA::OnOff(tmp.settings & 0x01), "TA");
	screen.Putc(44, y, ')');
	y++;

	for (int i = 0; i < 3; i++) {
		screen.Print(0, y++, "R%x    Level %c    $%02x",
			8 + i, 'A' + i, tmp.GetLevelReg(i));
	}

	if (__predict_false(tmp.envelope_freq == 0)) {
		f = 0;
	} else {
		f = fMaster / (256 * tmp.envelope_freq);
	}
	screen.Print(0, y++, "Rc/Rb EnvFreq  $%04x f=%u", tmp.envelope_freq, f);

	// モニタはソースコードのマルチバイト文字(UTF-8) を SJIS に変換する所は
	// (まだ?)用意してないので、こっちで SJIS 文字列を用意する必要がある。
	#define H	"\x81\x50"
	#define L	"\x81\x51"
	#define U	"\x81\x5e"
	#define D	"\x81\x5f"
	static const char *shape8[8] = {
		D D D D D,	// "\\\\\"
		D L L L L,	// "\____"
		D U D U D,	// "\/\/\"
		D H H H H,	// "\ ̄ ̄ ̄ ̄"
		U U U U U,	// "/////"
		U H H H H,	// "/ ̄ ̄ ̄ ̄"
		U D U D U,	// "/\/\/"
		U L L L L,	// "/____"
	};
	const char *shape;
	uint es = tmp.envelope_shape;
	if (es >= 8) {
		shape = shape8[es - 8];
	} else if (es >= 4) {
		shape = shape8[7];
	} else {
		shape = shape8[1];
	}
	screen.Print(0, y++, "Rd    EnvShape   $%02x %s",
		tmp.envelope_shape, shape);
	screen.Print(0, y++, "Re    Data A     $%02x", tmp.dataA);
	screen.Print(0, y++, "Rf    Data B     $%02x", tmp.dataB);
}

// レジスタの内容を副作用なく返す。
uint32
SSG::Get(uint n) const
{
	switch (n) {
	 case 0x0:
		return freq[0] & 0xff;
	 case 0x1:
		return freq[0] >> 8;
	 case 0x2:
		return freq[1] & 0xff;
	 case 0x3:
		return freq[1] >> 8;
	 case 0x4:
		return freq[2] & 0xff;
	 case 0x5:
		return freq[2] >> 8;
	 case 0x6:
		return noise_freq;
	 case 0x7:
		return settings;
	 case 0x8:
		return GetLevelReg(0);
	 case 0x9:
		return GetLevelReg(1);
	 case 0xa:
		return GetLevelReg(2);
	 case 0xb:
		return envelope_freq & 0xff;
	 case 0xc:
		return envelope_freq >> 8;
	 case 0xd:
		return envelope_shape;
	 case 0xe:
		return dataA;
	 case 0xf:
		return dataB;
	 default:
		__unreachable();
	}
}

unix.superglobalmegacorp.com

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