preliminary YM2610 (OPNA) support

This commit is contained in:
wbcbz7 2024-10-21 03:18:51 +07:00
parent d4e88aebea
commit cb509877aa
46 changed files with 20248 additions and 2202 deletions

View file

@ -1,172 +0,0 @@
/* Nuked OPL3
* Copyright (C) 2013-2020 Nuke.YKT
*
* This file is part of Nuked OPL3.
*
* Nuked OPL3 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 2.1
* of the License, or (at your option) any later version.
*
* Nuked OPL3 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Nuked OPL3. If not, see <https://www.gnu.org/licenses/>.
* Nuked OPL3 emulator.
* Thanks:
* MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh):
* Feedback and Rhythm part calculation information.
* forums.submarine.org.uk(carbon14, opl3):
* Tremolo and phase generator calculation information.
* OPLx decapsulated(Matthew Gambrell, Olli Niemitalo):
* OPL2 ROMs.
* siliconpr0n.org(John McMaster, digshadow):
* YMF262 and VRC VII decaps and die shots.
*
* version: 1.8
*/
#ifndef OPL_OPL3_H
#define OPL_OPL3_H
#ifdef __cplusplus
extern "C" {
#endif
#include <inttypes.h>
#ifndef OPL_ENABLE_STEREOEXT
#define OPL_ENABLE_STEREOEXT 0
#endif
#define OPL_WRITEBUF_SIZE 1024
#define OPL_WRITEBUF_DELAY 2
typedef struct _opl3_slot opl3_slot;
typedef struct _opl3_channel opl3_channel;
typedef struct _opl3_chip opl3_chip;
struct _opl3_slot {
opl3_channel *channel;
opl3_chip *chip;
int16_t out;
int16_t fbmod;
int16_t *mod;
int16_t prout;
uint16_t eg_rout;
uint16_t eg_out;
uint8_t eg_inc;
uint8_t eg_gen;
uint8_t eg_rate;
uint8_t eg_ksl;
uint8_t *trem;
uint8_t reg_vib;
uint8_t reg_type;
uint8_t reg_ksr;
uint8_t reg_mult;
uint8_t reg_ksl;
uint8_t reg_tl;
uint8_t reg_ar;
uint8_t reg_dr;
uint8_t reg_sl;
uint8_t reg_rr;
uint8_t reg_wf;
uint8_t key;
uint32_t pg_reset;
uint32_t pg_phase;
uint16_t pg_phase_out;
uint8_t slot_num;
};
struct _opl3_channel {
opl3_slot *slotz[2];/*Don't use "slots" keyword to avoid conflict with Qt applications*/
opl3_channel *pair;
opl3_chip *chip;
int16_t *out[4];
#if OPL_ENABLE_STEREOEXT
int32_t leftpan;
int32_t rightpan;
#endif
uint8_t chtype;
uint16_t f_num;
uint8_t block;
uint8_t fb;
uint8_t con;
uint8_t alg;
uint8_t ksv;
uint16_t cha, chb;
uint16_t chc, chd;
uint8_t ch_num;
};
typedef struct _opl3_writebuf {
uint64_t time;
uint16_t reg;
uint8_t data;
} opl3_writebuf;
struct _opl3_chip {
opl3_channel channel[18];
opl3_slot slot[36];
uint16_t timer;
uint64_t eg_timer;
uint8_t eg_timerrem;
uint8_t eg_state;
uint8_t eg_add;
uint8_t newm;
uint8_t nts;
uint8_t rhy;
uint8_t vibpos;
uint8_t vibshift;
uint8_t tremolo;
uint8_t tremolopos;
uint8_t tremoloshift;
uint32_t noise;
int16_t zeromod;
int32_t mixbuff[4];
uint8_t rm_hh_bit2;
uint8_t rm_hh_bit3;
uint8_t rm_hh_bit7;
uint8_t rm_hh_bit8;
uint8_t rm_tc_bit3;
uint8_t rm_tc_bit5;
#if OPL_ENABLE_STEREOEXT
uint8_t stereoext;
#endif
/* OPL3L */
int32_t rateratio;
int32_t samplecnt;
int16_t oldsamples[4];
int16_t samples[4];
uint64_t writebuf_samplecnt;
uint32_t writebuf_cur;
uint32_t writebuf_last;
uint64_t writebuf_lasttime;
opl3_writebuf writebuf[OPL_WRITEBUF_SIZE];
};
void OPL3_Generate(opl3_chip *chip, int16_t *buf);
void OPL3_GenerateResampled(opl3_chip *chip, int16_t *buf);
void OPL3_Reset(opl3_chip *chip, uint32_t samplerate);
void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v);
void OPL3_WriteRegBuffered(opl3_chip *chip, uint16_t reg, uint8_t v);
void OPL3_GenerateStream(opl3_chip *chip, int16_t *sndptr, uint32_t numsamples);
void OPL3_Generate4Ch(opl3_chip *chip, int16_t *buf4);
void OPL3_Generate4ChResampled(opl3_chip *chip, int16_t *buf4);
void OPL3_Generate4ChStream(opl3_chip *chip, int16_t *sndptr1, int16_t *sndptr2, uint32_t numsamples);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,543 @@
/*
This data is derived from the chip's output - internal ROM can't be read.
It was verified, using real YM2608, that this ADPCM stream produces 100% correct output signal.
*/
const unsigned char YM2608_ADPCM_ROM[0x2000] = {
/* Source: 01BD.ROM */
/* Length: 448 / 0x000001C0 */
0x88,0x08,0x08,0x08,0x00,0x88,0x16,0x76,0x99,0xB8,0x22,0x3A,0x84,0x3C,0xB1,0x54,
0x10,0xA9,0x98,0x32,0x80,0x33,0x9A,0xA7,0x4A,0xB4,0x58,0xBC,0x15,0x29,0x8A,0x97,
0x9B,0x44,0xAC,0x80,0x12,0xDE,0x13,0x1B,0xC0,0x58,0xC8,0x11,0x0A,0xA2,0x1A,0xA0,
0x00,0x98,0x0B,0x93,0x9E,0x92,0x0A,0x88,0xBE,0x14,0x1B,0x98,0x08,0xA1,0x4A,0xC1,
0x30,0xD9,0x33,0x98,0x10,0x89,0x17,0x1A,0x82,0x29,0x37,0x0C,0x83,0x50,0x9A,0x24,
0x1A,0x83,0x10,0x23,0x19,0xB3,0x72,0x8A,0x16,0x10,0x0A,0x93,0x70,0x99,0x23,0x99,
0x02,0x20,0x91,0x18,0x02,0x41,0xAB,0x24,0x18,0x81,0x99,0x4A,0xE8,0x28,0x9A,0x99,
0xA1,0x2F,0xA8,0x9D,0x90,0x08,0xCC,0xA3,0x1D,0xCA,0x82,0x0B,0xD8,0x08,0xB9,0x09,
0xBC,0xB8,0x00,0xBE,0x90,0x1B,0xCA,0x00,0x9B,0x8A,0xA8,0x91,0x0F,0xB3,0x3D,0xB8,
0x31,0x0B,0xA5,0x0A,0x11,0xA1,0x48,0x92,0x10,0x50,0x91,0x30,0x23,0x09,0x37,0x39,
0xA2,0x72,0x89,0x92,0x30,0x83,0x1C,0x96,0x28,0xB9,0x24,0x8C,0xA1,0x31,0xAD,0xA9,
0x13,0x9C,0xBA,0xA8,0x0B,0xBF,0xB8,0x9B,0xCA,0x88,0xDB,0xB8,0x19,0xFC,0x92,0x0A,
0xBA,0x89,0xAB,0xB8,0xAB,0xD8,0x08,0xAD,0xBA,0x33,0x9D,0xAA,0x83,0x3A,0xC0,0x40,
0xB9,0x15,0x39,0xA2,0x52,0x89,0x02,0x63,0x88,0x13,0x23,0x03,0x52,0x02,0x54,0x00,
0x11,0x23,0x23,0x35,0x20,0x01,0x44,0x41,0x80,0x24,0x40,0xA9,0x45,0x19,0x81,0x12,
0x81,0x02,0x11,0x21,0x19,0x02,0x61,0x8A,0x13,0x3A,0x10,0x12,0x23,0x8B,0x37,0x18,
0x91,0x24,0x10,0x81,0x34,0x20,0x05,0x32,0x82,0x53,0x20,0x14,0x33,0x31,0x34,0x52,
0x00,0x43,0x32,0x13,0x52,0x22,0x13,0x52,0x11,0x43,0x11,0x32,0x32,0x32,0x22,0x02,
0x13,0x12,0x89,0x22,0x19,0x81,0x81,0x08,0xA8,0x08,0x8B,0x90,0x1B,0xBA,0x8A,0x9B,
0xB9,0x89,0xCA,0xB9,0xAB,0xCA,0x9B,0xCA,0xB9,0xAB,0xDA,0x99,0xAC,0xBB,0x9B,0xAC,
0xAA,0xBA,0xAC,0xAB,0x9A,0xAA,0xAA,0xBA,0xB8,0xA9,0xBA,0x99,0xA9,0x9A,0xA0,0x8A,
0xA9,0x08,0x8A,0xA9,0x00,0x99,0x89,0x88,0x98,0x08,0x99,0x00,0x89,0x80,0x08,0x98,
0x00,0x88,0x88,0x80,0x90,0x80,0x90,0x80,0x81,0x99,0x08,0x88,0x99,0x09,0x00,0x1A,
0xA8,0x10,0x9A,0x88,0x08,0x0A,0x8A,0x89,0x99,0xA8,0x98,0xA9,0x99,0x99,0xA9,0x99,
0xAA,0x8A,0xAA,0x9B,0x8A,0x9A,0xA9,0x9A,0xBA,0x99,0x9A,0xAA,0x99,0x89,0xA9,0x99,
0x98,0x9A,0x98,0x88,0x09,0x89,0x09,0x08,0x08,0x09,0x18,0x18,0x00,0x12,0x00,0x11,
0x11,0x11,0x12,0x12,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,0x32,0x31,0x32,0x31,
0x32,0x32,0x21,0x31,0x21,0x32,0x21,0x12,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
/* Source: 02SD.ROM */
/* Length: 640 / 0x00000280 */
0x0A,0xDC,0x14,0x0B,0xBA,0xBC,0x01,0x0F,0xF5,0x2F,0x87,0x19,0xC9,0x24,0x1B,0xA1,
0x31,0x99,0x90,0x32,0x32,0xFE,0x83,0x48,0xA8,0xA9,0x23,0x19,0xBC,0x91,0x02,0x41,
0xDE,0x81,0x28,0xA8,0x0A,0xB1,0x72,0xDA,0x23,0xBC,0x04,0x19,0xB8,0x21,0x8A,0x03,
0x29,0xBA,0x14,0x21,0x0B,0xC0,0x43,0x08,0x91,0x50,0x93,0x0F,0x86,0x1A,0x91,0x18,
0x21,0xCB,0x27,0x0A,0xA1,0x42,0x8C,0xA9,0x21,0x10,0x08,0xAB,0x94,0x2A,0xDA,0x02,
0x8B,0x91,0x09,0x98,0xAE,0x80,0xA9,0x02,0x0A,0xE9,0x21,0xBB,0x15,0x20,0xBE,0x92,
0x42,0x09,0xA9,0x11,0x34,0x08,0x12,0x0A,0x27,0x29,0xA1,0x52,0x12,0x8E,0x92,0x28,
0x92,0x2B,0xD1,0x23,0xBF,0x81,0x10,0x99,0xA8,0x0A,0xC4,0x3B,0xB9,0xB0,0x00,0x62,
0xCF,0x92,0x29,0x92,0x2B,0xB1,0x1C,0xB2,0x72,0xAA,0x88,0x11,0x18,0x80,0x13,0x9E,
0x03,0x18,0xB0,0x60,0xA1,0x28,0x88,0x08,0x04,0x10,0x8F,0x96,0x19,0x90,0x01,0x09,
0xC8,0x50,0x91,0x8A,0x01,0xAB,0x03,0x50,0xBA,0x9D,0x93,0x68,0xBA,0x80,0x22,0xCB,
0x41,0xBC,0x92,0x60,0xB9,0x1A,0x95,0x4A,0xC8,0x20,0x88,0x33,0xAC,0x92,0x38,0x83,
0x09,0x80,0x16,0x09,0x29,0xD0,0x54,0x8C,0xA2,0x28,0x91,0x89,0x93,0x60,0xCD,0x85,
0x1B,0xA1,0x49,0x90,0x8A,0x80,0x34,0x0C,0xC9,0x14,0x19,0x98,0xA0,0x40,0xA9,0x21,
0xD9,0x34,0x0A,0xA9,0x10,0x23,0xCB,0x25,0xAA,0x25,0x9B,0x13,0xCD,0x16,0x09,0xA0,
0x80,0x01,0x19,0x90,0x88,0x21,0xAC,0x33,0x8B,0xD8,0x27,0x3B,0xB8,0x81,0x31,0x80,
0xAF,0x97,0x0A,0x82,0x0A,0xA0,0x21,0x89,0x8A,0xA2,0x32,0x8D,0xBB,0x87,0x19,0x21,
0xC9,0xBC,0x45,0x09,0x90,0x09,0xA1,0x24,0x1A,0xD0,0x10,0x08,0x11,0xA9,0x21,0xE8,
0x60,0xA9,0x14,0x0C,0xD1,0x32,0xAB,0x04,0x0C,0x81,0x90,0x29,0x83,0x9B,0x01,0x8F,
0x97,0x0B,0x82,0x18,0x88,0xBA,0x06,0x39,0xC8,0x23,0xBC,0x04,0x09,0x92,0x08,0x1A,
0xBB,0x74,0x8C,0x81,0x18,0x81,0x9D,0x83,0x41,0xCD,0x81,0x40,0x9A,0x90,0x10,0x12,
0x9C,0xA1,0x68,0xD8,0x33,0x9C,0x91,0x01,0x12,0xBE,0x02,0x09,0x12,0x99,0x9A,0x36,
0x0A,0xB0,0x30,0x88,0xA3,0x2D,0x12,0xBC,0x03,0x3A,0x11,0xBD,0x08,0xC8,0x62,0x80,
0x8B,0xD8,0x23,0x38,0xF9,0x12,0x08,0x99,0x91,0x21,0x99,0x85,0x2F,0xB2,0x30,0x90,
0x88,0xD9,0x53,0xAC,0x82,0x19,0x91,0x20,0xCC,0x96,0x29,0xC9,0x24,0x89,0x80,0x99,
0x12,0x08,0x18,0x88,0x99,0x23,0xAB,0x73,0xCB,0x33,0x9F,0x04,0x2B,0xB1,0x08,0x03,
0x1B,0xC9,0x21,0x32,0xFA,0x33,0xDB,0x02,0x33,0xAE,0xB9,0x54,0x8B,0xA1,0x20,0x89,
0x90,0x11,0x88,0x09,0x98,0x23,0xBE,0x37,0x8D,0x81,0x20,0xAA,0x34,0xBB,0x13,0x18,
0xB9,0x40,0xB1,0x18,0x83,0x8E,0xB2,0x72,0xBC,0x82,0x30,0xA9,0x9A,0x24,0x8B,0x27,
0x0E,0x91,0x20,0x90,0x08,0xB0,0x32,0xB9,0x21,0xB0,0xAC,0x45,0x9A,0xA1,0x50,0xA9,
0x80,0x0A,0x26,0x9B,0x11,0xBB,0x23,0x71,0xCB,0x12,0x10,0xB8,0x40,0xA9,0xA5,0x39,
0xC0,0x30,0xB2,0x20,0xAA,0xBA,0x76,0x1C,0xC1,0x48,0x98,0x80,0x18,0x81,0xAA,0x23,
0x9C,0xA2,0x32,0xAC,0x9A,0x43,0x9C,0x12,0xAD,0x82,0x72,0xBC,0x00,0x82,0x39,0xD1,
0x3A,0xB8,0x35,0x9B,0x10,0x40,0xF9,0x22,0x0A,0xC0,0x51,0xB9,0x82,0x18,0x98,0xA3,
0x79,0xD0,0x20,0x88,0x09,0x01,0x99,0x82,0x11,0x38,0xFC,0x33,0x09,0xC8,0x40,0xA9,
0x11,0x29,0xAA,0x94,0x3A,0xC2,0x4A,0xC0,0x89,0x52,0xBC,0x11,0x08,0x09,0xB8,0x71,
0xA9,0x08,0xA8,0x62,0x8D,0x92,0x10,0x00,0x9E,0x94,0x38,0xBA,0x13,0x88,0x90,0x4A,
0xE2,0x30,0xBA,0x02,0x00,0x19,0xD9,0x62,0xBB,0x04,0x0B,0xA3,0x68,0xB9,0x21,0x88,
0x9D,0x04,0x10,0x8C,0xC8,0x62,0x99,0xAA,0x24,0x1A,0x80,0x9A,0x14,0x9B,0x26,0x8C,
0x92,0x30,0xB9,0x09,0xA3,0x71,0xBB,0x10,0x19,0x82,0x39,0xDB,0x02,0x44,0x9F,0x10,
/* Source: 04TOP.ROM */
/* Length: 5952 / 0x00001740 */
0x07,0xFF,0x7C,0x3C,0x31,0xC6,0xC4,0xBB,0x7F,0x7F,0x7B,0x82,0x8A,0x4D,0x5F,0x7C,
0x3E,0x44,0xD2,0xB3,0xA0,0x19,0x1B,0x6C,0x81,0x28,0xC4,0xA1,0x1C,0x4B,0x18,0x00,
0x2A,0xA2,0x0A,0x7C,0x2A,0x00,0x01,0x89,0x98,0x48,0x8A,0x3C,0x28,0x2A,0x5B,0x3E,
0x3A,0x1A,0x3B,0x3D,0x4B,0x3B,0x4A,0x08,0x2A,0x1A,0x2C,0x4A,0x3B,0x82,0x99,0x3C,
0x5D,0x29,0x2B,0x39,0x0B,0x23,0xAB,0x1A,0x4C,0x79,0xA3,0x01,0xC1,0x2A,0x0A,0x38,
0xA7,0xB9,0x12,0x1F,0x29,0x08,0x82,0xA1,0x08,0xA9,0x42,0xAA,0x95,0xB3,0x90,0x81,
0x09,0xD4,0x1A,0x80,0x1B,0x07,0xB8,0x12,0x8E,0x49,0x81,0x92,0xD3,0x90,0xA1,0x2A,
0x02,0xE1,0xA3,0x99,0x02,0xB3,0x94,0xB3,0xB0,0xF4,0x98,0x93,0x90,0x13,0xE1,0x81,
0x99,0x38,0x91,0xA6,0xD3,0x99,0x94,0xC1,0x83,0xB1,0x92,0x98,0x49,0xC4,0xB2,0xA4,
0xA3,0xD0,0x1A,0x30,0xBA,0x59,0x02,0xD4,0xA0,0xA4,0xA2,0x8A,0x01,0x00,0xB7,0xA8,
0x18,0x2A,0x2B,0x1E,0x23,0xC8,0x1A,0x00,0x39,0xA0,0x18,0x92,0x4F,0x2D,0x5A,0x10,
0x89,0x81,0x2A,0x8B,0x6A,0x02,0x09,0xB3,0x8D,0x48,0x1B,0x80,0x19,0x34,0xF8,0x29,
0x0A,0x7B,0x2A,0x28,0x81,0x0C,0x02,0x1E,0x29,0x09,0x12,0xC2,0x94,0xE1,0x18,0x98,
0x02,0xC4,0x89,0x91,0x1A,0x20,0xA9,0x02,0x1B,0x48,0x8E,0x20,0x88,0x2D,0x08,0x59,
0x1B,0x02,0xA3,0xB1,0x8A,0x1E,0x58,0x80,0xC2,0xB6,0x88,0x91,0x88,0x11,0xA1,0xA3,
0xE2,0x01,0xB0,0x19,0x11,0x09,0xF4,0x88,0x09,0x88,0x19,0x89,0x12,0xF1,0x2A,0x28,
0x8C,0x25,0x99,0xA4,0x98,0x39,0xA1,0x00,0xD0,0x58,0xAA,0x59,0x01,0x0C,0x00,0x2B,
0x00,0x08,0x89,0x6B,0x69,0x90,0x01,0x90,0x98,0x12,0xB3,0xF3,0xA0,0x89,0x02,0x3B,
0x0C,0x50,0xA9,0x4E,0x6B,0x19,0x28,0x09,0xA2,0x08,0x2F,0x20,0x88,0x92,0x8A,0x11,
0xC4,0x93,0xF1,0x18,0x88,0x11,0xF2,0x80,0x92,0xA8,0x02,0xA8,0xB7,0xB3,0xA3,0xA0,
0x88,0x1A,0x40,0xE2,0x91,0x19,0x88,0x18,0x91,0x83,0xC1,0xB5,0x92,0xA9,0xC6,0x90,
0x01,0xC2,0x81,0x98,0x03,0xF0,0x00,0x2C,0x2A,0x92,0x2C,0x83,0x1F,0x3A,0x29,0x00,
0xB8,0x70,0xAB,0x69,0x18,0x89,0x10,0x0D,0x12,0x0B,0x88,0x4A,0x3A,0x9B,0x70,0xA8,
0x28,0x2F,0x2A,0x3A,0x1B,0x85,0x88,0x8B,0x6A,0x29,0x00,0x91,0x91,0x1B,0x7C,0x29,
0x01,0x88,0x90,0x19,0x2B,0x2B,0x00,0x39,0xA8,0x5E,0x21,0x89,0x91,0x09,0x3A,0x6F,
0x2A,0x18,0x18,0x8B,0x50,0x89,0x2B,0x19,0x49,0x88,0x29,0xF5,0x89,0x08,0x09,0x12,
0xAA,0x15,0xB0,0x82,0xAC,0x38,0x00,0x3F,0x81,0x10,0xB0,0x49,0xA2,0x81,0x3A,0xC8,
0x87,0x90,0xC4,0xA3,0x99,0x19,0x83,0xE1,0x84,0xE2,0xA2,0x90,0x80,0x93,0xB5,0xC4,
0xB3,0xA1,0x0A,0x18,0x92,0xC4,0xA0,0x93,0x0C,0x3A,0x18,0x01,0x1E,0x20,0xB1,0x82,
0x8C,0x03,0xB5,0x2E,0x82,0x19,0xB2,0x1B,0x1B,0x6B,0x4C,0x19,0x12,0x8B,0x5A,0x11,
0x0C,0x3A,0x2C,0x18,0x3D,0x08,0x2A,0x5C,0x18,0x00,0x88,0x3D,0x29,0x80,0x2A,0x09,
0x00,0x7A,0x0A,0x10,0x0B,0x69,0x98,0x10,0x81,0x3F,0x00,0x18,0x19,0x91,0xB7,0x9A,
0x28,0x8A,0x48,0x92,0xF3,0xA2,0x88,0x98,0x87,0xA1,0x88,0x80,0x81,0x95,0xD1,0xA3,
0x1B,0x1C,0x39,0x10,0xA1,0x2A,0x0B,0x7A,0x4B,0x80,0x13,0xC1,0xD1,0x2B,0x2A,0x85,
0xB2,0xA2,0x93,0xB2,0xD3,0x80,0xD1,0x18,0x08,0x08,0xB7,0x98,0x81,0x3F,0x01,0x88,
0x01,0xE2,0x00,0x9A,0x59,0x08,0x10,0xC3,0x99,0x84,0xA9,0xA5,0x91,0x91,0x91,0x80,
0xB5,0x94,0xC0,0x01,0x98,0x09,0x84,0xB0,0x80,0x7A,0x08,0x18,0x90,0xA8,0x6A,0x1C,
0x39,0x2A,0xB7,0x98,0x19,0x10,0x2A,0xA1,0x10,0xBD,0x39,0x18,0x2D,0x39,0x3F,0x10,
0x3F,0x01,0x09,0x19,0x0A,0x38,0x8C,0x40,0xB3,0xB4,0x93,0xAD,0x20,0x2B,0xD4,0x81,
0xC3,0xB0,0x39,0xA0,0x23,0xD8,0x04,0xB1,0x9B,0xA7,0x1A,0x92,0x08,0xA5,0x88,0x81,
0xE2,0x01,0xB8,0x01,0x81,0xC1,0xC7,0x90,0x92,0x80,0xA1,0x97,0xA0,0xA2,0x82,0xB8,
0x18,0x00,0x9C,0x78,0x98,0x83,0x0B,0x0B,0x32,0x7D,0x19,0x10,0xA1,0x19,0x09,0x0A,
0x78,0xA8,0x10,0x1B,0x29,0x29,0x1A,0x14,0x2F,0x88,0x4A,0x1B,0x10,0x10,0xAB,0x79,
0x0D,0x49,0x18,0xA0,0x02,0x1F,0x19,0x3A,0x2B,0x11,0x8A,0x88,0x79,0x8A,0x20,0x49,
0x9B,0x58,0x0B,0x28,0x18,0xA9,0x3A,0x7D,0x00,0x29,0x88,0x82,0x3D,0x1A,0x38,0xBA,
0x15,0x09,0xAA,0x51,0x8B,0x83,0x3C,0x8A,0x58,0x1B,0xB5,0x01,0xBB,0x50,0x19,0x99,
0x24,0xCA,0x21,0x1B,0xA2,0x87,0xA8,0xB1,0x68,0xA1,0xA6,0xA2,0xA8,0x29,0x8B,0x24,
0xB4,0xE2,0x92,0x8A,0x00,0x19,0x93,0xB5,0xB4,0xB1,0x81,0xB1,0x03,0x9A,0x82,0xA7,
0x90,0xD6,0xA0,0x80,0x1B,0x29,0x01,0xA4,0xE1,0x18,0x0A,0x2A,0x29,0x92,0xC7,0xA8,
0x81,0x19,0x89,0x30,0x10,0xE0,0x30,0xB8,0x10,0x0C,0x1A,0x79,0x1B,0xA7,0x80,0xA0,
0x00,0x0B,0x28,0x18,0xB1,0x85,0x1E,0x00,0x20,0xA9,0x18,0x18,0x1C,0x13,0xBC,0x15,
0x99,0x2E,0x12,0x00,0xE1,0x00,0x0B,0x3B,0x21,0x90,0x06,0xC9,0x2A,0x49,0x0A,0x18,
0x20,0xD1,0x3C,0x08,0x00,0x83,0xC9,0x41,0x8E,0x18,0x08,0x02,0xA0,0x09,0xA4,0x7B,
0x90,0x19,0x2A,0x10,0x2A,0xA8,0x71,0xBA,0x10,0x4A,0x0E,0x22,0xB2,0xB2,0x1B,0x8C,
0x78,0x1A,0xB5,0x93,0xA9,0x1B,0x49,0x19,0x29,0xA3,0xC6,0x88,0xAA,0x32,0x0D,0x1B,
0x22,0x08,0xC2,0x18,0xB9,0x79,0x3F,0x01,0x10,0xA9,0x84,0x1C,0x09,0x21,0xB0,0xA7,
0x0A,0x99,0x50,0x0C,0x81,0x28,0x8B,0x48,0x2E,0x00,0x08,0x99,0x38,0x5B,0x88,0x14,
0xA9,0x08,0x11,0xAA,0x72,0xC1,0xB3,0x09,0x8A,0x05,0x91,0xF2,0x81,0xA1,0x09,0x02,
0xF2,0x92,0x99,0x1A,0x49,0x80,0xC5,0x90,0x90,0x18,0x09,0x12,0xA1,0xF2,0x81,0x98,
0xC6,0x91,0xA0,0x11,0xA0,0x94,0xB4,0xF2,0x81,0x8B,0x03,0x80,0xD2,0x93,0xA8,0x88,
0x69,0xA0,0x03,0xB8,0x88,0x32,0xBC,0x97,0x80,0xB1,0x3B,0x1A,0xA6,0x00,0xD1,0x01,
0x0B,0x3B,0x30,0x9B,0x31,0x3E,0x92,0x19,0x8A,0xD3,0x5C,0x1B,0x41,0xA0,0x93,0xA2,
0xAF,0x39,0x4C,0x01,0x92,0xA8,0x81,0x3C,0x0D,0x78,0x98,0x00,0x19,0x0A,0x20,0x2D,
0x29,0x3C,0x1B,0x48,0x88,0x99,0x7A,0x2D,0x29,0x2A,0x82,0x80,0xA8,0x49,0x3E,0x19,
0x11,0x98,0x82,0x9A,0x3B,0x28,0x2F,0x20,0x4C,0x90,0x29,0x19,0x9A,0x7A,0x29,0x28,
0x98,0x88,0x33,0xCD,0x11,0x3A,0xC1,0xA4,0xA0,0xC4,0x82,0xC8,0x50,0x98,0xB2,0x21,
0xC0,0xB6,0x98,0x82,0x80,0x9C,0x23,0x00,0xF8,0x30,0xA8,0x1A,0x68,0xA8,0x86,0x9A,
0x01,0x2A,0x0A,0x97,0x91,0xC1,0x18,0x89,0x02,0x83,0xE0,0x01,0x8B,0x29,0x30,0xE2,
0x91,0x0B,0x18,0x3B,0x1C,0x11,0x28,0xAC,0x78,0x80,0x93,0x91,0xA9,0x49,0x8B,0x87,
0x90,0x99,0x3D,0x5A,0x81,0x08,0xA1,0x11,0x2F,0x1A,0x21,0x9B,0x15,0xA2,0xB0,0x11,
0xC0,0x91,0x5B,0x98,0x24,0xA2,0xF2,0x92,0x8B,0x6A,0x18,0x81,0xB5,0xB1,0x88,0x4C,
0x00,0x00,0xA4,0xC1,0x2B,0x1A,0x59,0x0A,0x02,0x80,0x1E,0x02,0x08,0xB3,0x80,0x9A,
0x23,0xB8,0xF2,0x84,0xAB,0x01,0x48,0x90,0xA7,0x90,0x0A,0x29,0x09,0x95,0x99,0xA0,
0x59,0x2B,0x00,0x97,0xB0,0x29,0x89,0x2A,0x03,0xD0,0xB7,0x1B,0x81,0x00,0xA6,0xB1,
0x90,0x09,0x48,0xC0,0x11,0x00,0x8A,0x00,0x5B,0x83,0x9A,0x18,0x2F,0x3C,0x18,0x11,
0xA9,0x04,0x1A,0x4F,0x01,0x98,0x81,0x09,0x09,0x4A,0x18,0xB4,0xA2,0x0B,0x59,0x90,
0x3B,0x49,0xBC,0x40,0x6A,0x88,0x3A,0x08,0x3E,0x3A,0x80,0x93,0xB0,0xE1,0x5A,0x00,
0xA4,0xB3,0xE3,0x90,0x0D,0x38,0x09,0x82,0xC4,0xA1,0xB1,0x4C,0x18,0x10,0x91,0xB2,
0x13,0xEA,0x34,0x99,0x88,0xA6,0x89,0x92,0x91,0xC1,0x20,0xB2,0xC2,0x86,0xD2,0xB3,
0x80,0xB2,0x08,0x09,0x87,0x91,0xC0,0x11,0x89,0x90,0x28,0xB9,0x79,0x19,0xA4,0x82,
0xD0,0x03,0x0C,0xA3,0xA5,0xB2,0xB2,0x1B,0x29,0x13,0xF1,0xB4,0x81,0x9D,0x38,0x00,
0xC4,0xA1,0x89,0x59,0x1A,0x81,0xA4,0xA9,0x1C,0x6A,0x19,0x02,0xB1,0x1A,0x4A,0x0B,
0x78,0x89,0x81,0x1C,0x2A,0x29,0x4A,0xA3,0x3E,0x1C,0x49,0x1A,0x08,0x21,0xAE,0x28,
0x4B,0x19,0x20,0x8C,0x10,0x3A,0xAB,0x26,0x8B,0x18,0x59,0x99,0x13,0xA2,0xAB,0x79,
0x2F,0x18,0x10,0xB2,0x80,0x1B,0x4D,0x5A,0x80,0x82,0x98,0x81,0x80,0x09,0xA5,0x90,
0x91,0x03,0xC2,0xE2,0x81,0xA8,0x82,0x09,0xC6,0xA3,0xB1,0x08,0x5B,0x08,0x05,0xD1,
0xA2,0x89,0x2A,0x28,0x91,0xA6,0x88,0xB0,0x49,0x80,0x09,0x08,0x88,0x07,0xB8,0x05,
0x99,0x81,0x88,0x18,0xE2,0x00,0xC3,0x18,0x0D,0x10,0x30,0xD0,0x93,0x8A,0x09,0x10,
0x2F,0x11,0x90,0xA1,0x20,0x9B,0xB1,0x73,0xC8,0x94,0x98,0x3B,0x01,0x0C,0x30,0x19,
0xF8,0x12,0x90,0xBA,0x78,0x0A,0x11,0x98,0xA0,0x79,0x8A,0x30,0x2B,0xC2,0x11,0x0D,
0x09,0x7A,0x00,0x82,0xB9,0x01,0x7A,0x89,0x21,0x09,0xA1,0x0A,0x7C,0x10,0x88,0xB5,
0x88,0x0A,0x2B,0x69,0x1A,0x10,0xA0,0x5B,0x19,0x1A,0x10,0x19,0x1A,0x6C,0x20,0x90,
0xA5,0x98,0x1B,0x0A,0x69,0x82,0xD1,0x18,0x09,0x19,0x2A,0x93,0xD4,0x9A,0x01,0x49,
0xA2,0xA2,0x82,0xD8,0x22,0xAA,0x97,0xA9,0x2D,0x38,0x2A,0xB6,0x80,0x90,0x0A,0x3C,
0x82,0x94,0xB8,0x21,0x0E,0x2A,0x22,0xB8,0x00,0x4F,0x2B,0x3A,0x81,0xA1,0x29,0x2C,
0x6A,0x13,0xD1,0xA2,0x98,0x28,0x0C,0x01,0xD5,0x08,0xA9,0x31,0xB3,0xB0,0xA7,0xB0,
0x29,0x1B,0x87,0xA2,0xA1,0xB2,0x4A,0x89,0x11,0xC3,0xF3,0x98,0x08,0x03,0xA0,0xA3,
0xC5,0x90,0xB3,0xB5,0xB4,0xB8,0x02,0x91,0x91,0xD3,0xA4,0xC1,0x1B,0x82,0x28,0xA4,
0xD1,0x94,0x8A,0x28,0x08,0x03,0xE0,0x80,0xD4,0x90,0x91,0xA1,0x3B,0x3D,0x02,0xE4,
0xA1,0x92,0x89,0x1A,0x4B,0x95,0xB3,0x90,0x99,0x6A,0x0A,0x30,0xA1,0x93,0xA6,0xA9,
0x85,0x8B,0x82,0x10,0xB1,0xA3,0x94,0xF8,0x38,0x9A,0x30,0x1A,0x8B,0xA7,0x89,0x01,
0x5B,0x19,0x18,0x11,0xF0,0x18,0x1C,0x39,0x19,0x0C,0x12,0x1C,0x2A,0x7B,0x3A,0x88,
0x2B,0x18,0x2B,0x5C,0x20,0x92,0x8D,0x38,0x8A,0x3A,0x5B,0x2E,0x3A,0x2B,0x10,0x12,
0xBB,0x6A,0x4D,0x18,0x10,0xB1,0x81,0x2A,0x8B,0x79,0x80,0x01,0x0A,0x09,0x5B,0x2D,
0x84,0x8A,0x08,0x02,0xA2,0x91,0x82,0xE8,0x50,0x9B,0x85,0xA3,0xB0,0xA3,0x1B,0x02,
0x18,0xF3,0xA2,0x88,0xAB,0x53,0xD1,0xB4,0xA3,0x09,0x09,0x18,0xD4,0x08,0xB0,0x09,
0x58,0xD1,0x82,0x89,0x81,0x1A,0x18,0x05,0xB9,0xC3,0x30,0xC0,0x95,0x80,0xC3,0x89,
0x89,0x13,0x88,0xF2,0x93,0x0E,0x18,0x01,0x92,0xA5,0xB8,0x2A,0x39,0xAA,0x33,0x9A,
0xB1,0x11,0xF5,0xA1,0xA1,0x0A,0x50,0xB8,0x03,0xC4,0xA0,0x4E,0x29,0x10,0x88,0xC2,
0x1A,0x39,0x1D,0x28,0x98,0x94,0x0E,0x10,0x2A,0x3C,0x02,0x2D,0x1B,0x4B,0x3B,0x49,
0x19,0xA9,0x48,0x2F,0x29,0x10,0x89,0x02,0x0C,0x10,0x09,0xB9,0x70,0x1B,0x8A,0x50,
0xA8,0x2B,0x49,0x89,0x69,0x88,0x95,0x89,0x90,0x92,0x4C,0x19,0x82,0xC1,0x01,0x80,
0xA0,0x2B,0x7A,0x81,0x10,0xC2,0xB7,0x98,0x88,0x19,0x2C,0x03,0xB1,0xA4,0xA1,0x0C,
0x3B,0x78,0x88,0x85,0xB1,0xA0,0x1B,0x3A,0x4A,0x08,0x94,0x81,0xF1,0x80,0x00,0x0C,
0x59,0x09,0x18,0x90,0xA6,0x92,0x8C,0x1A,0x79,0x92,0xA8,0x00,0x81,0x2E,0x2A,0x13,
0xA2,0xB0,0xA5,0x88,0x88,0x89,0x11,0x19,0xA0,0xF3,0x82,0xB0,0x83,0x5F,0x2A,0x01,
0xA1,0x94,0xB0,0x09,0x78,0x98,0xA3,0xA6,0xA0,0x91,0x80,0x93,0x98,0xC1,0x12,0x18,
0xC9,0x17,0xA0,0xA0,0x1A,0x21,0x80,0x99,0xD4,0x30,0x9D,0x00,0x10,0x2F,0x08,0x1C,
0x21,0x08,0xB4,0xC3,0x2B,0xA9,0x52,0xD2,0xA3,0xD1,0x09,0x10,0x8B,0x24,0x92,0xD1,
0x80,0x19,0xA0,0x2C,0x12,0x49,0xAA,0xB6,0x95,0xB8,0x08,0x3A,0x2B,0x01,0xF3,0xB3,
0x0B,0x09,0x79,0x18,0xA2,0xA4,0xA0,0x18,0x0C,0x20,0x08,0xA9,0x16,0x0C,0x00,0x1B,
0x08,0x2B,0x7B,0x01,0x01,0xB9,0x59,0x19,0x8B,0x45,0xA8,0x80,0x0C,0x1A,0x41,0x1E,
0x00,0x28,0xA8,0x5A,0x00,0xC1,0x49,0x99,0x21,0x1D,0x08,0x85,0x99,0x95,0x89,0x90,
0x11,0x90,0xD1,0x28,0xB2,0xA7,0x99,0x81,0x02,0xAC,0x13,0x81,0xB2,0xA6,0xA9,0x28,
0x1C,0xB1,0x33,0xD1,0xC1,0x58,0xA8,0x14,0xB0,0xB7,0x91,0xA0,0x82,0x89,0xC2,0x28,
0xA1,0xB2,0x49,0xD2,0x94,0xC8,0x12,0x80,0x99,0x85,0x08,0xD3,0x09,0xA2,0xB3,0x1E,
0x08,0x21,0xB9,0x23,0xB4,0xAB,0x41,0xAC,0x87,0x09,0xA2,0xC5,0x0B,0x2A,0x5A,0x91,
0x20,0x9A,0x89,0x78,0x9B,0x31,0x89,0x80,0x29,0x0A,0xB7,0x3C,0x98,0x48,0x1D,0x00,
0x01,0xB0,0x20,0x2F,0x29,0x4A,0x89,0x94,0x1C,0x88,0x28,0x2B,0x10,0x88,0x9A,0x71,
0x9A,0x08,0x4A,0x2F,0x18,0x2B,0x18,0x02,0xA8,0x4B,0x7A,0x99,0x48,0x80,0xA8,0x20,
0x1D,0x40,0xA8,0x10,0x08,0xA8,0xC5,0x88,0xC2,0x18,0x88,0x2A,0x12,0xF3,0x82,0xD8,
0x20,0x0A,0x09,0xA6,0x98,0x04,0xB9,0x11,0x18,0xC3,0xE1,0x29,0xA1,0x11,0xC1,0x03,
0xE2,0x9A,0x33,0xA9,0xB5,0x98,0x92,0xA1,0x02,0xF8,0x21,0xA8,0x10,0x02,0xC1,0xB7,
0x1B,0x90,0x5B,0x3C,0x83,0x93,0xE0,0x19,0x1A,0x11,0x11,0xF1,0x92,0x89,0x19,0x2C,
0x2C,0x41,0x99,0x92,0x90,0x3F,0x18,0x4B,0x00,0x08,0xD2,0x01,0xB2,0xAA,0x78,0x09,
0x01,0x91,0xA2,0x98,0x2F,0x3A,0x2C,0x01,0x00,0x93,0xE0,0x28,0x2C,0x2B,0x01,0x12,
0xE1,0x80,0xB3,0x3D,0x3A,0x0A,0x50,0x98,0xC2,0xA0,0x11,0xAA,0x30,0x87,0x90,0xC2,
0x29,0x88,0x38,0xC8,0xB5,0x90,0xBA,0x70,0x1A,0x02,0x94,0xD0,0x80,0x1A,0x82,0xA6,
0xB0,0x91,0x18,0xB3,0x00,0x13,0xF1,0xA2,0xC1,0x82,0xB0,0x00,0x15,0x0B,0xD3,0x02,
0xA8,0x91,0x2B,0x1F,0x49,0x88,0xA6,0x80,0x88,0x08,0x1B,0xA5,0x80,0xB9,0x06,0x0B,
0x90,0x21,0x9D,0x48,0x18,0xA0,0x15,0xC9,0x82,0x2B,0x1A,0x42,0x9A,0xC4,0x39,0xBC,
0x69,0x00,0xA0,0x29,0x8C,0x39,0x59,0x08,0x09,0x49,0xA9,0x6B,0x81,0x00,0x98,0xB0,
0x68,0x3D,0x81,0x88,0x18,0x19,0x1D,0x12,0x80,0xB2,0x3A,0x3F,0x85,0x92,0xD0,0x00,
0x0A,0x19,0x12,0xF1,0x02,0x9B,0x19,0x40,0xB9,0x11,0x02,0xF2,0x1A,0x08,0x94,0x0A,
0xC2,0x83,0x0B,0xB4,0xA4,0xC0,0x32,0xD8,0x86,0x98,0x90,0x95,0x89,0xA3,0x83,0xC2,
0x92,0xE1,0x92,0x82,0xD9,0x03,0x08,0xA9,0x85,0x92,0xA2,0x80,0xE0,0x30,0x8B,0xB3,
0x87,0x89,0x90,0x83,0xA0,0x08,0x92,0x93,0x3E,0xAB,0x43,0x89,0xE3,0x80,0x83,0x2F,
0x00,0xA3,0x80,0xC9,0x22,0x3F,0x08,0x81,0x0B,0x33,0x9A,0xA3,0x7B,0x0C,0x29,0x4A,
0x1B,0x21,0xAA,0x70,0x1B,0x0D,0x48,0x1A,0x81,0x88,0xB1,0x39,0x3F,0x08,0x58,0xA0,
0x81,0x1A,0x1A,0x2B,0x6D,0x11,0x0A,0x91,0x01,0x1A,0x98,0x5A,0x0C,0x03,0xB1,0x84,
0xA3,0xAD,0x58,0x2A,0xA1,0x84,0xB1,0xA0,0x5C,0x2B,0x13,0xA8,0x95,0x83,0xE8,0x10,
0x81,0xB0,0x00,0xC2,0x96,0xA0,0x91,0x00,0x2C,0x90,0x30,0xF2,0x80,0xA8,0x39,0x21,
0xC1,0x03,0xAC,0x39,0x7C,0x29,0x91,0x1A,0x00,0x19,0x2C,0x3A,0x93,0xB0,0x29,0x8F,
0x28,0x02,0x93,0xF3,0xA9,0x01,0x03,0xE0,0x08,0x09,0x1D,0x58,0xA1,0x83,0xA9,0x6B,
0x2A,0x3C,0x21,0x89,0xC2,0x2C,0x4B,0x8A,0x50,0x81,0x98,0xA8,0x32,0x0C,0x8E,0x24,
0x0B,0x1A,0x81,0x92,0xA1,0x4F,0x18,0x3A,0x0A,0xB4,0x18,0x2E,0x39,0x82,0x19,0xD3,
0xD0,0x28,0x1B,0x11,0x98,0x07,0xAA,0x28,0x00,0x88,0xB4,0x89,0x1B,0x1F,0x22,0x00,
0xB3,0xC9,0x33,0xAB,0x2B,0xB5,0x48,0x98,0x98,0xA7,0x10,0xD2,0xC1,0x23,0xCA,0x93,
0xC6,0x80,0xA1,0x88,0x02,0x89,0xE2,0x09,0x38,0xBA,0x40,0x89,0x21,0xD8,0x49,0x10,
0x8D,0x02,0x90,0xC3,0x9A,0x24,0x89,0x08,0x84,0xA5,0x9C,0x10,0x11,0x9C,0x88,0x30,
0x3C,0xA1,0x94,0x58,0x8C,0x0B,0x69,0x29,0x9A,0x81,0x12,0x2B,0x8B,0x79,0x94,0xB0,
0xC1,0x84,0xC2,0x99,0x25,0x99,0x11,0xA2,0x93,0xE4,0x99,0x80,0x0A,0x00,0x10,0xB7,
0xB0,0x31,0xBA,0x3C,0x21,0xB3,0xF1,0x18,0xA0,0x2A,0x20,0xA3,0x06,0xE8,0x28,0xA1,
0xB4,0x08,0x0B,0x11,0x4B,0xB7,0x90,0xA5,0x98,0x3D,0x19,0x02,0xA1,0xC4,0xB2,0x19,
0x28,0xC0,0xA5,0x92,0xB1,0xA3,0x0A,0x0A,0x08,0x2B,0x70,0xC4,0xB3,0x00,0xBC,0x4B,
0x39,0x12,0xE3,0xA0,0x00,0x3F,0x18,0x29,0x94,0xD1,0x19,0x09,0x00,0xA1,0x83,0x99,
0x9B,0x35,0x80,0xC4,0xB1,0x6A,0x1A,0x1C,0x29,0x38,0x0E,0x19,0x5A,0x1A,0x82,0x8A,
0x59,0x2A,0x2E,0x20,0x88,0xA8,0x3A,0x38,0x3D,0x00,0xB3,0x29,0xAD,0x49,0x10,0x0C,
0x01,0x01,0xA3,0x8F,0x85,0x09,0x1B,0x88,0x10,0xA3,0xD2,0x90,0x3C,0x5C,0x39,0x03,
0xD1,0xA0,0x00,0x2A,0x0B,0x04,0xA7,0x90,0xA0,0x11,0x90,0x99,0x83,0xB4,0xB1,0xF1,
0x84,0x88,0x90,0x18,0x18,0xD3,0xD2,0xB3,0xA0,0x1A,0x21,0xA7,0xB2,0xB3,0x92,0x9A,
0x22,0xB9,0x28,0x38,0xBD,0x87,0x2A,0xB1,0x13,0x0D,0x0A,0x38,0xC9,0x24,0xC0,0x19,
0x23,0x0F,0x01,0x88,0xC0,0x2A,0x82,0x18,0x28,0xF0,0x18,0x2A,0x29,0x4B,0x35,0xB8,
0xA3,0x9D,0x18,0x1B,0x40,0x00,0x9A,0x5C,0x3A,0x09,0x2F,0x38,0x8A,0x3B,0x3B,0x11,
0x5C,0x19,0x2B,0x4A,0x08,0x0A,0x3D,0x20,0x4F,0x3A,0x19,0x2A,0x18,0x4D,0x1B,0x3A,
0x11,0x0D,0x3A,0x3C,0x4B,0x93,0x81,0xAA,0x6B,0x4A,0x18,0x00,0xC3,0xC3,0x9A,0x59,
0x2A,0x1B,0xA7,0xA1,0x81,0x88,0x88,0x58,0xB2,0xB1,0x2B,0x83,0xD4,0x81,0x08,0x0F,
0x00,0x20,0xC2,0xE2,0x80,0x08,0x1C,0x29,0x04,0xB1,0xA2,0x01,0x1C,0x91,0x00,0x0C,
0x49,0xB0,0x43,0xF2,0x99,0x39,0x3F,0x00,0x81,0x94,0xC1,0x09,0x1A,0x69,0x90,0x80,
0x94,0xAA,0x20,0x2A,0x91,0xB1,0x39,0x7A,0x38,0xD1,0x10,0x8A,0x8C,0x5A,0x01,0xB5,
0x98,0x80,0x2A,0x0B,0x32,0x92,0xF1,0x81,0x9A,0x23,0x8A,0xA3,0xB7,0x09,0x03,0x08,
0xD0,0x94,0x9A,0x09,0x01,0x93,0xB7,0xC2,0x8C,0x3A,0x83,0x99,0x05,0xA0,0x0B,0x29,
0x93,0xE5,0x80,0x89,0x38,0x90,0x8A,0xD7,0xA1,0x19,0x1B,0x48,0x98,0x92,0xC3,0xA1,
0x09,0x3F,0x02,0x0C,0x22,0xC3,0xB2,0xA1,0x01,0x9F,0x4A,0x01,0xA3,0xD3,0xB0,0x28,
0x3F,0x29,0x20,0xA2,0xC2,0xB1,0x08,0x5A,0x98,0x13,0xD2,0xC1,0x01,0xB2,0x80,0x3D,
0x03,0xC1,0x89,0x96,0x90,0x90,0x3A,0x1A,0x9A,0x32,0xB6,0xA2,0x8E,0x4A,0x28,0x8A,
0x84,0xA2,0x8A,0x2D,0x49,0x09,0x88,0x18,0x30,0x9D,0x2C,0x23,0xB1,0x0C,0x92,0x2D,
0x39,0x82,0xC4,0x2E,0x10,0x1A,0x10,0xB9,0x48,0x19,0x39,0xBA,0x34,0xDA,0x2D,0x48,
0x1A,0xA6,0x98,0x83,0x9A,0x1D,0x38,0x04,0xD0,0x18,0x90,0x2C,0x11,0x93,0xD3,0x9A,
0x11,0x08,0x82,0xF1,0x01,0xA0,0x2A,0x93,0xD3,0xB4,0xB8,0x82,0x2F,0x11,0xA3,0xB3,
0xA8,0x3B,0x09,0x23,0x96,0xC8,0x3B,0x3F,0x93,0x82,0xA1,0x90,0x3F,0x28,0x81,0xD1,
0x93,0x08,0x2D,0x18,0x91,0xB3,0xB5,0x98,0x2A,0x2B,0x84,0xB1,0x5B,0x8A,0x31,0x18,
0x80,0x8B,0x7E,0x39,0x2B,0x02,0xC1,0x8B,0x6C,0x49,0x09,0x10,0xA1,0x08,0x01,0x0C,
0x20,0xA1,0x09,0x4F,0x18,0x00,0x01,0xA0,0x5C,0x1B,0x5B,0x10,0x92,0x90,0x2B,0x5A,
0x3D,0x18,0x91,0x19,0x98,0x2D,0x39,0x89,0x2D,0x3A,0x48,0x2C,0x11,0xB5,0x9A,0x19,
0x5B,0x28,0x90,0x95,0x98,0x89,0x2B,0x40,0x08,0x90,0xF3,0x0A,0x08,0xA6,0x80,0x91,
0xB2,0xA0,0x02,0xF2,0xA1,0xB7,0x89,0x81,0x82,0x91,0xB1,0x21,0xAB,0x32,0xE9,0x04,
0xA2,0x8D,0x12,0x91,0xA3,0xA3,0xD2,0x8B,0x39,0xD1,0x84,0xE2,0x90,0x00,0x2B,0x29,
0xA3,0xD4,0xA1,0x91,0x1D,0x5A,0x08,0x19,0x11,0x99,0x08,0x18,0x49,0x0F,0x18,0x10,
0x82,0xF1,0x00,0x89,0x2F,0x3A,0x01,0xB3,0xC2,0x81,0x3F,0x29,0x08,0x10,0xA1,0xA1,
0x3B,0x5D,0x19,0x28,0x0B,0x38,0x82,0x91,0x19,0xBD,0x3B,0x7A,0x80,0x12,0xB3,0xE0,
0x0B,0x6A,0x01,0x88,0xA4,0x08,0x0B,0x08,0x59,0x80,0x80,0x1D,0x49,0x89,0x00,0x84,
0x99,0x1A,0x2B,0x32,0xE3,0xB4,0xA9,0x3A,0x99,0x31,0xE3,0xAA,0x58,0x3B,0x88,0x95,
0xC0,0x18,0x4A,0x09,0x30,0xF2,0xA3,0x1C,0x1B,0x49,0x00,0xD3,0xB2,0xA0,0x18,0x11,
0x92,0xD3,0xB2,0x91,0x80,0xE7,0xA1,0x91,0x98,0x19,0x22,0xC2,0xD2,0x18,0x8D,0x3B,
0x10,0xA5,0x91,0x98,0x02,0x3E,0x80,0x01,0x90,0xAA,0x13,0xF1,0x02,0xD1,0x08,0x19,
0x49,0xB4,0x91,0xB4,0x99,0x2A,0x0C,0x32,0xC0,0x05,0x88,0x0B,0x80,0x2C,0x81,0x10,
0x0B,0x51,0xA9,0x19,0x05,0xBF,0x28,0x20,0xE1,0x90,0x80,0x28,0x19,0x08,0x26,0xB1,
0xA1,0x18,0x88,0x2A,0xF0,0x12,0x8A,0xB3,0x14,0x1B,0xD4,0xD8,0x10,0x08,0x8A,0x17,
0xA0,0x98,0x2B,0x3A,0x29,0x48,0xA4,0x99,0x0E,0x4A,0x12,0x8B,0x31,0x8B,0x4E,0x1A,
0x11,0xB5,0x89,0x91,0x29,0x89,0xC2,0x97,0x90,0x0A,0x19,0x11,0x91,0xC1,0xD5,0x08,
0x89,0x20,0x91,0xB1,0x1A,0x2D,0x18,0x29,0xD2,0x3B,0x3E,0x3A,0x2A,0x90,0x82,0x1C,
0x49,0x3B,0x93,0xB6,0xC8,0x4C,0x02,0x91,0x93,0xF2,0x88,0x2D,0x28,0x81,0x82,0xC1,
0x89,0x2D,0x6B,0x19,0x82,0x80,0x18,0x8B,0x39,0x39,0xC8,0x3A,0x6A,0x0A,0x22,0xD2,
0x09,0x2C,0x1A,0x68,0x92,0xE2,0x89,0x2A,0x2A,0x30,0xC2,0xA3,0xB4,0x1D,0x2A,0x09,
0x93,0x18,0xF2,0x89,0x28,0xB3,0x01,0x8F,0x18,0x11,0xA1,0x93,0x90,0xD1,0x7A,0x20,
0xC3,0xA2,0xA8,0x88,0x1D,0x28,0xA5,0xA2,0xA2,0x0B,0x29,0x2B,0x87,0xC1,0x80,0x0A,
0x19,0x01,0x12,0xF1,0x10,0x80,0x0A,0x18,0x08,0x2F,0x4A,0x02,0x89,0x1B,0x29,0x5D,
0x4C,0x08,0x82,0xA1,0x0A,0x3A,0x4B,0x29,0xC6,0xC3,0x09,0x09,0x88,0x39,0x98,0x82,
0xA5,0x1A,0x30,0x11,0xBD,0x3F,0x12,0x8B,0x28,0xC3,0x88,0x3F,0x2B,0x3B,0x48,0xA1,
0x80,0x8A,0x4D,0x39,0x01,0x93,0xA2,0xF1,0x19,0x19,0x0A,0x02,0xB2,0x8B,0x24,0xD2,
0x4B,0x12,0xC8,0x2E,0x10,0xB5,0x89,0x01,0x09,0x1C,0x2A,0x03,0xD4,0x91,0x98,0x99,
0x11,0x2B,0xE4,0x00,0x00,0x01,0xE0,0xA5,0x89,0x99,0x31,0x18,0xD0,0xB7,0x98,0x18,
0x0A,0x10,0x94,0xC2,0x90,0x18,0x00,0x99,0x87,0xA0,0x90,0x2A,0x3C,0x02,0xB8,0xC1,
0x79,0x1A,0x20,0x08,0xA1,0xD2,0x1C,0x29,0x03,0xD1,0x29,0x99,0x2C,0x50,0xB3,0xD1,
0x08,0x09,0x3C,0x10,0x04,0xB2,0x0D,0x2B,0x59,0x80,0x90,0x01,0x0F,0x3A,0x18,0x01,
0xA2,0x9B,0x5B,0x3D,0x81,0x03,0xD2,0x98,0x59,0x90,0x81,0x92,0xB4,0x8B,0x1B,0x40,
0xB2,0xB5,0x08,0x4B,0x01,0x09,0xD1,0x91,0x8B,0x7A,0x10,0xB3,0xC3,0x99,0x49,0x1A,
0x29,0xB5,0xA2,0xAB,0x40,0x81,0x19,0xB7,0xB0,0x20,0x2B,0xD4,0x88,0xA1,0x91,0x3C,
0x82,0x37,0xD3,0xB1,0x8A,0x1B,0x30,0xB3,0xF4,0xA1,0x91,0x09,0x10,0x03,0xD0,0x83,
0xA9,0x8F,0x10,0x01,0x90,0x18,0x80,0x20,0x2B,0xF1,0x28,0x99,0x2A,0x41,0xF0,0x12,
0xAA,0x83,0x82,0xD1,0xC1,0x08,0x89,0x59,0x09,0x83,0x87,0xB0,0x2A,0x4D,0x18,0x09,
0x19,0xB3,0x4B,0x3F,0x39,0x19,0x09,0x01,0x89,0x03,0x1F,0x00,0x1A,0x0B,0x10,0x68,
0xA0,0x18,0x8C,0x6A,0x09,0x08,0x97,0xA1,0x81,0x1B,0x2B,0x4C,0x03,0xB4,0xA8,0x92,
0x4B,0x3C,0xA1,0x81,0x95,0xA8,0x81,0x12,0xBB,0x92,0x45,0xB9,0x93,0xF4,0x88,0x0A,
0x2D,0x28,0x00,0xA3,0xA3,0x8A,0x3F,0x48,0xB1,0x92,0xB4,0xA8,0x30,0x80,0xD3,0x80,
0xD1,0x19,0x3B,0xC4,0x81,0xC1,0x29,0x0D,0x20,0x13,0xC8,0xB4,0x4C,0x09,0x00,0x82,
0xC2,0x3B,0x0D,0x30,0x0B,0x12,0xF0,0x1B,0x20,0x0A,0xA6,0x80,0x0A,0x4A,0x4A,0x80,
0x94,0xB1,0x2E,0x3B,0x1A,0x10,0x93,0x10,0x4C,0x3D,0x08,0x82,0xC9,0x19,0x6A,0x2B,
0x38,0xD1,0x08,0x19,0x2A,0x5A,0x82,0xB1,0x8D,0x29,0x78,0x09,0x82,0x0A,0x2C,0x1B,
0x19,0x41,0xB8,0x8C,0x79,0x2B,0x11,0x88,0x82,0x91,0xDC,0x28,0x11,0xB0,0x11,0x18,
0xC9,0x62,0xA1,0x91,0x98,0x3B,0x3A,0xB0,0xF4,0x01,0xC0,0x29,0x39,0xF8,0x95,0x91,
0x88,0x88,0x91,0x03,0xA1,0xE2,0x18,0x82,0xD1,0xA2,0xD1,0x80,0x19,0x20,0x83,0xB1,
0xE3,0x80,0x91,0x4D,0x1A,0x03,0xB2,0x09,0x18,0xD1,0x19,0x09,0x92,0xA6,0xA0,0xB6,
0xB2,0x8B,0x38,0x10,0x42,0xD3,0xD0,0xA8,0x20,0x2C,0x10,0x01,0xB1,0xB4,0xAB,0x5B,
0x79,0x80,0x10,0x1A,0xA8,0x3D,0x18,0x20,0xB3,0x8F,0x18,0x01,0x00,0x09,0xF3,0x89,
0x69,0x88,0x81,0x91,0x08,0xE1,0x1A,0x08,0x11,0x81,0x1E,0x29,0xA0,0x01,0x00,0x90,
0x3E,0x7B,0x18,0x82,0xC3,0xA1,0x2A,0x2C,0x5B,0x81,0xA5,0x90,0x81,0x00,0x0B,0x1A,
0x1C,0x2C,0x32,0xC0,0xF3,0x80,0x2D,0x2A,0x10,0x02,0xE4,0xC1,0x89,0x4A,0x09,0x01,
0x03,0xD2,0x98,0x2A,0x39,0x8A,0x89,0x26,0xB1,0xB2,0x12,0xC0,0x0A,0x5A,0x18,0x98,
0xF3,0x92,0x99,0x99,0x79,0x01,0xB5,0xA1,0x80,0x80,0x90,0x83,0xA0,0xE2,0x81,0x29,
0x93,0x8A,0x0A,0x6A,0x1F,0x18,0x02,0xC8,0x01,0x19,0x3B,0x4A,0x98,0x17,0xA8,0x0D,
0x38,0xA1,0x91,0x10,0xA2,0x2B,0x4C,0xA6,0x81,0xBA,0x21,0x4C,0x80,0x21,0xD1,0x92,
0x2C,0x08,0x30,0x9F,0x93,0x2A,0x89,0x03,0x8B,0x87,0x0A,0x0D,0x12,0x98,0xA4,0x93,
0xBB,0x59,0x18,0xA1,0x32,0xE9,0x84,0x08,0x8A,0x02,0xA1,0x91,0x4B,0xB4,0x20,0x88,
0xF0,0x3A,0x1A,0x88,0x87,0xB1,0x92,0x0A,0x08,0x6B,0x83,0xC3,0x91,0xC0,0x2B,0x79,
0x08,0x8A,0x84,0xA0,0x89,0x40,0x1B,0xA1,0x39,0x98,0x17,0xC2,0xA2,0x12,0xCD,0x20,
0x89,0x92,0x25,0xB0,0x2D,0x3A,0x8B,0x58,0x2A,0xA0,0x4C,0x08,0x30,0xAE,0x82,0x59,
0x89,0x1A,0x10,0xC2,0x18,0x2C,0x40,0x1E,0x01,0xA3,0x8A,0x81,0x2C,0x29,0x29,0xA9,
0x13,0x51,0xAD,0x12,0x89,0x8F,0x18,0x2C,0x39,0x00,0xC1,0x10,0x3C,0x2A,0x41,0xC8,
0xA2,0x91,0x0A,0x6C,0x10,0x12,0x88,0xE8,0x30,0x91,0x81,0xD8,0x01,0x1B,0x0D,0x07,
0x00,0xA8,0x92,0x0A,0x28,0xD2,0xC3,0x02,0xAA,0x94,0x81,0xB4,0xB3,0x1A,0x0B,0x13,
0xF9,0x16,0xA1,0x8A,0x59,0x19,0x02,0xC1,0x91,0x8B,0x3D,0x18,0x3B,0xA4,0x94,0x80,
0x99,0x88,0x1C,0x79,0x0A,0x02,0x03,0xF8,0x90,0x39,0x5B,0x19,0x02,0xC3,0x90,0xBB,
0x58,0x6A,0x09,0x02,0x89,0x91,0x88,0x1A,0x69,0x8A,0x19,0x15,0xA0,0xA2,0x00,0x9A,
0x6B,0x49,0x88,0xA3,0x92,0xBB,0x6B,0x3D,0x38,0x01,0x98,0x91,0x3F,0x09,0x18,0x20,
0x90,0x80,0xAC,0x70,0x91,0x9B,0x51,0x09,0x88,0x99,0x14,0x8B,0x98,0x83,0x79,0xA0,
0x99,0x13,0x01,0x19,0xE0,0x83,0x0B,0xB0,0x0C,0x31,0x95,0xB5,0xC2,0x8A,0x39,0x20,
0x80,0x39,0xF3,0xB1,0x10,0x88,0x5E,0x18,0x94,0xA1,0x88,0xA1,0x98,0x15,0xAA,0x39,
0xD4,0x84,0xC0,0xA2,0xA2,0x0C,0x81,0x86,0xB5,0xA1,0xB1,0x14,0x1B,0xB1,0x02,0x92,
0xC3,0xE0,0x88,0x11,0xAA,0x69,0x18,0x81,0xA3,0xB0,0x01,0xBF,0x2A,0x31,0x93,0xF1,
0x00,0x89,0x18,0x19,0x11,0xD3,0xE0,0x10,0x18,0xB1,0x18,0x24,0x9A,0x2B,0xA4,0xC0,
0xB0,0x31,0x6C,0x19,0xB4,0x12,0xA8,0xEA,0x58,0x10,0x8B,0x93,0x82,0x88,0x9A,0x41,
0x10,0xC3,0xEA,0x41,0xA9,0x9C,0x34,0xA1,0x2A,0x79,0xA2,0x01,0xA8,0xB3,0x28,0xCC,
0x41,0x9A,0xB3,0x4B,0xB3,0x27,0x8B,0x83,0x2B,0x2F,0x08,0x28,0xB2,0x80,0x2C,0x30,
0x5E,0x09,0x12,0x9B,0x09,0x22,0x5B,0x19,0x8A,0x11,0x59,0x99,0xA4,0x32,0xCD,0x18,
0x08,0x10,0x85,0xB3,0xB4,0x1E,0x88,0x28,0x8A,0x11,0x09,0xC0,0x79,0x80,0x91,0x3B,
0x80,0x10,0x0F,0x01,0x80,0x91,0x19,0x3D,0x92,0x28,0xA8,0x37,0x9A,0x0A,0x3A,0x8A,
0x45,0xA9,0xA4,0x00,0xAA,0x09,0x3D,0x59,0x20,0xE1,0x08,0x98,0x90,0x59,0x10,0x09,
0xA3,0xC3,0x93,0x99,0x2B,0x69,0x11,0xD1,0xB1,0xA4,0x91,0x3C,0x89,0x83,0xF0,0x10,
0x91,0xA1,0x89,0x59,0x05,0x99,0x93,0x94,0xC8,0x08,0x0A,0x09,0x17,0xB1,0x83,0xC1,
0x91,0x40,0xA2,0xC2,0x98,0xC3,0xBA,0x28,0x23,0x0F,0x80,0x50,0xB8,0x19,0x10,0x96,
0x98,0x8C,0x05,0x98,0x19,0x29,0x2B,0x3B,0x0A,0xE2,0x01,0x0F,0x3C,0x38,0x08,0x09,
0x81,0x4A,0x6C,0x08,0x00,0x88,0x98,0x38,0x2C,0x5A,0x1B,0x20,0x1A,0x39,0xB0,0x09,
0xCB,0x5B,0x49,0x09,0x71,0x00,0xC1,0x0E,0x08,0x38,0x0C,0x02,0x10,0x0E,0x10,0x8A,
0x48,0x19,0x90,0x92,0x0D,0xA3,0x98,0x3B,0x79,0x19,0x01,0x10,0xE1,0x80,0x19,0x2B,
0x10,0xF2,0x02,0xAB,0x84,0x9A,0x29,0xB4,0x80,0x92,0x03,0x88,0x95,0xD0,0x03,0x90,
0xA0,0xC7,0xA1,0xB0,0xA2,0x02,0x18,0xB5,0xD4,0x01,0xC0,0x08,0xA2,0x93,0xA8,0xA0,
0xC3,0x20,0xF3,0x90,0x00,0xD5,0x08,0x89,0xA5,0x80,0xA0,0x81,0x82,0xC2,0x09,0xD1,
0x13,0xCB,0x03,0x84,0x91,0xE1,0x1B,0x12,0x08,0xAB,0x87,0x18,0xAB,0x58,0x89,0x28,
0x81,0xC9,0x33,0xA9,0x80,0x2E,0x20,0x83,0xB9,0x20,0x3B,0x9E,0x7A,0x08,0x81,0x18,
0x0B,0x88,0x79,0x80,0x8B,0x00,0x12,0x0E,0x89,0x51,0x1B,0x81,0xA0,0x3A,0x01,0xAF,
0x11,0x28,0xBA,0x35,0x98,0x88,0x52,0xC0,0x83,0x2F,0xA9,0x11,0x0A,0x19,0x25,0xD0,
0x30,0x9C,0x08,0x21,0x98,0x81,0x2A,0xF3,0x2A,0x80,0xB6,0x2B,0x08,0x93,0xE9,0x02,
0x81,0x8C,0x21,0x00,0xA6,0xA9,0x94,0x01,0x8F,0x80,0x94,0x98,0x93,0xB4,0x00,0x08,
0xC0,0x14,0x98,0xB3,0xB4,0xC1,0x09,0x18,0xA7,0x00,0xA3,0xC8,0x0A,0x3C,0x19,0x96,
0x83,0xC1,0x99,0x19,0x4A,0x85,0x80,0xC1,0x91,0x99,0x90,0x2A,0x17,0x95,0x99,0x88,
0x12,0xAE,0x39,0x08,0x92,0x84,0xB0,0xA8,0x79,0x09,0x19,0x01,0xB2,0xA3,0x8F,0x28,
0x2B,0xA2,0x40,0x82,0xA0,0x4C,0xA9,0x39,0x8D,0x81,0x70,0x88,0xA0,0x1A,0x49,0x2D,
0x1A,0x26,0xA8,0x98,0x08,0x29,0x0B,0x12,0x96,0xB1,0xB2,0x3A,0x13,0x9B,0x60,0xA0,
0x88,0xB2,0x34,0xEA,0x1A,0x2A,0x79,0x98,0x10,0x04,0x8C,0x1C,0x81,0x04,0x8C,0x83,
0x19,0x2F,0x81,0x93,0x98,0x10,0x08,0x30,0x2A,0xFA,0x05,0x08,0x2A,0x89,0x91,0xA3,
0xFA,0x11,0x11,0x00,0x8C,0x04,0x8A,0x2A,0xB5,0x10,0xA9,0xC2,0x3D,0x1B,0x32,0x04,
0x0A,0x1A,0x09,0x40,0x1F,0x92,0x1D,0x2A,0x91,0x10,0x30,0x2F,0x0B,0x68,0x99,0xA2,
0x92,0x88,0x78,0xA9,0x20,0x28,0xE2,0x92,0x1A,0x99,0x4B,0x19,0x22,0xA1,0xE2,0x21,
0x2F,0x98,0x29,0x18,0x91,0x08,0xB0,0x79,0x1A,0x82,0x3B,0xB1,0xA7,0x8A,0xB3,0x98,
0x5B,0x23,0xCA,0x42,0x83,0xF0,0x90,0x18,0x98,0x08,0xB4,0x20,0xA3,0xC0,0x43,0xD8,
0x80,0x81,0xA3,0x99,0xD9,0xA7,0x19,0x90,0x10,0x05,0xB1,0x8B,0x02,0xA4,0xBD,0x23,
0x93,0x8A,0x99,0x4B,0x03,0xC1,0xF8,0x38,0x09,0x2B,0x14,0xD0,0x03,0x8A,0x2A,0x39,
0xB9,0x97,0x90,0xAA,0x50,0x01,0x99,0x51,0xD1,0x09,0x1A,0xB5,0x00,0x8B,0x93,0x08,
0x98,0x11,0xF9,0x85,0x2B,0x08,0x96,0x89,0x90,0x2A,0x12,0x4A,0xD8,0x85,0x2B,0x0E,
0x10,0x00,0x01,0xB1,0x9B,0x69,0x1A,0x90,0x40,0xB8,0x01,0x08,0x0A,0x2C,0x09,0x14,
0x4B,0xE2,0x82,0x88,0xB1,0x78,0x0A,0x01,0xC2,0x93,0x19,0xCE,0x20,0x3C,0x82,0xB4,
0x1B,0x20,0x8C,0x3B,0x29,0xAB,0x86,0x23,0xD8,0x81,0x9A,0x5A,0x49,0xB0,0x16,0xA0,
0xB0,0x28,0x1B,0x13,0x93,0xE4,0xA2,0xA9,0x08,0x5A,0xB3,0x12,0xC1,0xE1,0x10,0x88,
0x01,0x0C,0x92,0x08,0x89,0xB7,0x88,0x81,0x10,0x9A,0x17,0xA0,0xB0,0x13,0x99,0xE0,
0x39,0x31,0xD2,0xB2,0x80,0x0B,0x2D,0x49,0x80,0x01,0xB0,0x06,0x09,0x0C,0x3A,0x69,
0xA0,0x08,0xB2,0xA1,0x69,0x2B,0x5A,0x81,0x92,0xBA,0x21,0xB1,0x7D,0x10,0x80,0x08,
0x88,0x82,0x32,0x0D,0xB0,0x1A,0x1C,0x21,0x94,0xA9,0x58,0xB9,0x5A,0x4A,0xA0,0x13,
0xA9,0x80,0x7C,0x00,0x20,0x8A,0x04,0x0C,0x00,0x82,0x2A,0xB2,0xAC,0x4B,0x69,0xA0,
0xA6,0x81,0x9B,0x19,0x38,0x8B,0x17,0xB2,0x81,0x2A,0xBB,0x94,0x29,0xA2,0x15,0xBA,
0x97,0xA3,0xB9,0x79,0x01,0xB2,0x02,0xF1,0x90,0x0A,0x29,0x11,0x88,0xE5,0xA0,0x81,
0x19,0x91,0x90,0x28,0xB3,0x14,0xD0,0xB5,0x91,0x9A,0x29,0x0B,0x07,0xA2,0xB3,0x01,
0x9D,0x28,0x41,0xD0,0x91,0x90,0x82,0x1A,0xA8,0x44,0x9A,0xA9,0x21,0xE3,0xA9,0x4B,
0x19,0x78,0x89,0x83,0xA3,0xB9,0x5A,0x3D,0x80,0x82,0xA2,0xA0,0x6C,0x10,0x20,0x8B,
0x93,0x8B,0x0E,0x33,0xA9,0xB1,0x68,0x8A,0x31,0xAC,0x94,0xB4,0x8B,0x32,0x0B,0xB4,
0x81,0x91,0x1D,0x33,0xD9,0x31,0xE1,0x8B,0x3B,0x30,0x12,0x49,0xD2,0x8E,0x29,0x18,
0x8A,0x92,0x02,0xAA,0x59,0x1C,0x32,0x88,0x01,0x23,0xFB,0x83,0x29,0xDA,0x59,0x01,
0x81,0x92,0xE1,0x18,0x8A,0x1D,0x30,0x93,0xF1,0x00,0x01,0x0B,0x39,0x92,0x89,0xA0,
0x11,0x5B,0xE0,0x82,0x09,0x13,0xAA,0xB4,0x16,0xD8,0x91,0x2A,0x29,0x84,0x1B,0xC5,
0x98,0x98,0x31,0x98,0x99,0x17,0xA9,0x20,0x92,0xC3,0x18,0x9D,0x20,0x3D,0x89,0x94,
0xA2,0x1C,0x5C,0x29,0x39,0xA0,0xB3,0x00,0x0C,0x4C,0x48,0x92,0x0A,0x91,0x85,0x9A,
0x01,0x82,0x1F,0x10,0x99,0x15,0xC1,0xA0,0x39,0x1A,0x1D,0x85,0xB4,0x90,0x1A,0x2A,
0x4B,0x01,0xB2,0x93,0xBE,0x12,0x83,0xC9,0x18,0x09,0x20,0x78,0xF1,0x08,0x19,0x88,
0x3A,0x83,0xB3,0xA9,0x93,0x7A,0x0A,0x96,0x98,0x00,0xA8,0x3A,0x30,0x92,0xF2,0x9B,
0x3D,0x38,0x92,0x92,0xC3,0xB8,0x6B,0x29,0x01,0x01,0xB2,0x2F,0x09,0x19,0x18,0x01,
0x3B,0x7B,0x10,0xA1,0x90,0x39,0x0F,0x38,0x0A,0xB5,0xA4,0x89,0x8B,0x6A,0x2B,0x12,
0xC8,0x90,0x40,0x2A,0x9E,0x22,0x88,0x18,0x09,0x3A,0xC3,0xE8,0x09,0x59,0x08,0x12,
0x94,0xD0,0x1A,0x2C,0x38,0x00,0xA1,0x83,0xE8,0x08,0x3A,0x08,0x10,0x9E,0x83,0x1D,
0x92,0x19,0x2C,0x39,0x3B,0x59,0x04,0xE1,0x80,0x08,0x8D,0x21,0x81,0xB2,0xB2,0x02,
0x99,0x91,0xA4,0xD6,0x98,0x99,0x03,0x80,0x98,0xA7,0x91,0x09,0xA1,0xB2,0xB3,0xE1,
0x12,0x92,0xB1,0x81,0x06,0x99,0x0A,0x23,0xC4,0xB1,0xF2,0x89,0x19,0x3A,0x94,0x82,
0xE0,0x89,0x38,0x0B,0xA4,0xA5,0x80,0x80,0x8C,0x34,0xB9,0xA9,0x23,0x13,0xB9,0xC1,
0xC7,0x1B,0x89,0x10,0x20,0x11,0xE3,0xA8,0x4B,0x0B,0x40,0x91,0x90,0x1B,0x5F,0x2A,
0x18,0x82,0x91,0x0B,0x4A,0x28,0xCA,0x40,0x80,0x5B,0x2C,0x13,0xB0,0x8A,0xA9,0x5A,
0x58,0x89,0x82,0x88,0x2E,0x3B,0x31,0xA1,0x9B,0x01,0x7A,0x2C,0x01,0x91,0x93,0x3F,
0x88,0x39,0x10,0xF1,0x91,0x8B,0x48,0x0A,0x12,0xE3,0xA8,0x18,0x28,0x92,0x97,0x98,
0x99,0x19,0xA1,0x11,0xB6,0x88,0x3B,0x10,0xD3,0xC3,0xA1,0x2A,0x8A,0x49,0x04,0xF1,
0x91,0x02,0x8A,0x89,0x04,0xF1,0x98,0x80,0x18,0x12,0xE3,0x81,0x98,0x80,0x01,0xB3,
0xF2,0x99,0x12,0x2A,0xB5,0xB3,0x92,0xAA,0x19,0x50,0xB2,0xC3,0x92,0xD0,0x2B,0x68,
0x93,0x99,0xC0,0x2C,0x3E,0x80,0x20,0x08,0x93,0x0D,0x2A,0x31,0x8D,0x02,0x2B,0x91,
0x08,0x0A,0x03,0x2C,0x3C,0x52,0xB9,0xA0,0x12,0xBF,0x3A,0x29,0x01,0x88,0xC0,0x6A,
0x3C,0x0A,0x49,0x18,0x0B,0x39,0x2B,0x69,0x0A,0x84,0x2A,0x2A,0x1C,0x2A,0xC3,0x8C,
0x19,0x50,0x09,0x91,0xA7,0x8D,0x18,0x1A,0x28,0x00,0xA0,0x94,0x10,0x1F,0x20,0x90,
0x8A,0x12,0xD0,0x1A,0x5A,0x81,0x04,0xBC,0x23,0x10,0xE0,0x90,0x90,0x18,0x1A,0xA6,
0x12,0xB1,0xD0,0x4A,0x08,0x82,0x92,0xB6,0x9A,0x0A,0x12,0x88,0xC3,0xC5,0x8A,0x89,
0x20,0xB5,0x93,0x0B,0x18,0x00,0x09,0xF2,0x88,0x2A,0x4A,0x08,0x05,0xB2,0xA9,0x3B,
0x5D,0x28,0xA4,0xB1,0x00,0x19,0x19,0x7A,0xA3,0xB3,0x0A,0x90,0xA1,0xC4,0x80,0xBA,
0x50,0x13,0xC1,0xC2,0x9A,0x2A,0x7B,0x28,0x84,0xC1,0x09,0x3B,0x4E,0x20,0x91,0xA1,
0x18,0xAB,0x79,0x10,0xB4,0x08,0x9A,0x11,0x2B,0xF0,0x93,0xAA,0x01,0x6A,0x01,0x93,
0x80,0xB8,0x2A,0x5B,0x10,0x80,0x89,0x4A,0x5B,0x92,0x15,0xB2,0xA0,0x2F,0x19,0x93,
0xB8,0x95,0x80,0x1C,0x21,0xA9,0x02,0x0B,0xA0,0x5A,0x18,0x98,0x39,0x1B,0x68,0x00,
0x91,0x91,0x9C,0x39,0x3E,0x18,0x84,0xB3,0x9B,0x7A,0x08,0x18,0x0A,0xB5,0x91,0x0B,
0x28,0x39,0x19,0x90,0x0A,0x50,0xAC,0x11,0x01,0xAB,0x88,0x52,0x1B,0x83,0xC4,0xA2,
0x9A,0xAB,0x03,0x90,0x19,0x93,0x81,0x08,0x92,0x9A,0x68,0x98,0x19,0x39,0xC1,0x92,
0x8A,0x38,0x4E,0x02,0xB1,0x90,0xC3,0x18,0x2B,0x04,0xC3,0xD2,0x91,0x90,0x81,0x89,
0x13,0xF1,0x88,0x93,0xA2,0x00,0x91,0xC0,0x5B,0x21,0x99,0x93,0x06,0x9A,0x1B,0x48,
0x99,0xB7,0x90,0x89,0x18,0x1B,0x11,0xA4,0xB2,0x81,0x9A,0x08,0x97,0x98,0x91,0x10,
0xB8,0x06,0xA2,0xA0,0x29,0x2B,0x21,0xC2,0xD1,0x10,0x1A,0x4A,0x29,0xF1,0x98,0x29,
0x1B,0x31,0x10,0xA0,0xA1,0x1D,0x5A,0x29,0xB2,0x82,0xA8,0x0F,0x28,0x21,0x09,0x91,
0x82,0x4D,0x10,0xA3,0xB0,0x89,0x4C,0x39,0xA0,0xA4,0xA1,0x89,0x1E,0x28,0x29,0xA3,
0xC3,0x2D,0x19,0x01,0x49,0x01,0x9B,0x0C,0x21,0xC2,0xA2,0x93,0x7C,0x2A,0x10,0x90,
/* Source: 08HH.ROM */
/* Length: 384 / 0x00000180 */
0x75,0xF2,0xAB,0x7D,0x7E,0x5C,0x3B,0x4B,0x3C,0x4D,0x4A,0x02,0xB3,0xC5,0xE7,0xE3,
0x92,0xB3,0xC4,0xB3,0xC3,0x8A,0x3B,0x5D,0x5C,0x3A,0x84,0xC2,0x91,0xA4,0xE7,0xF7,
0xF7,0xF4,0xA1,0x1B,0x49,0xA5,0xB1,0x1E,0x7F,0x5A,0x00,0x89,0x39,0xB7,0xA8,0x3D,
0x4A,0x84,0xE7,0xF7,0xE2,0x2D,0x4C,0x3A,0x4E,0x7D,0x04,0xB0,0x2D,0x4B,0x10,0x80,
0xA3,0x99,0x10,0x0E,0x59,0x93,0xC4,0xB1,0x81,0xC4,0xA2,0xB2,0x88,0x08,0x3F,0x3B,
0x28,0xA6,0xC3,0xA2,0xA2,0xC5,0xC1,0x3F,0x7E,0x39,0x81,0x93,0xC2,0xA3,0xE5,0xD2,
0x80,0x93,0xB8,0x6D,0x49,0x82,0xD4,0xA1,0x90,0x01,0xA0,0x09,0x04,0xE3,0xB2,0x91,
0xB7,0xB3,0xA8,0x2A,0x03,0xF3,0xA1,0x92,0xC5,0xC3,0xB2,0x0B,0x30,0xB3,0x8E,0x6D,
0x4A,0x01,0xB4,0xB4,0xC4,0xC3,0x99,0x3B,0x12,0xE3,0xA1,0x88,0x82,0xB4,0x9A,0x5C,
0x3A,0x18,0x93,0xC3,0xB3,0xB4,0xA8,0x19,0x04,0xF3,0xA8,0x3B,0x10,0xA2,0x88,0xA5,
0xB2,0x0B,0x6D,0x4B,0x10,0x91,0x89,0x3C,0x18,0x18,0xA6,0xC4,0xC3,0x98,0x19,0x2B,
0x20,0x91,0xA0,0x4E,0x28,0x93,0xB3,0xC2,0x92,0xA9,0x5A,0x96,0xC4,0xC2,0x09,0x01,
0xC4,0xA1,0x92,0xC4,0xA1,0x89,0x10,0xA3,0xA1,0x90,0x1C,0x5A,0x01,0xC5,0xA1,0x92,
0xD4,0xB3,0xC4,0xC4,0xC3,0xA1,0x88,0x1A,0x28,0x89,0x3C,0x3A,0x3D,0x29,0x00,0x93,
0xB0,0x3D,0x28,0x80,0x91,0x82,0xE3,0x99,0x2A,0x11,0xD6,0xC3,0x99,0x29,0x82,0xC4,
0xC3,0xA1,0x0A,0x3B,0x3D,0x3A,0x02,0xC3,0xA2,0x99,0x3B,0x2C,0x7C,0x28,0x81,0xA3,
0xB2,0xA3,0xB1,0x08,0x1A,0x3C,0x18,0x2E,0x4C,0x39,0xA5,0xB3,0xB4,0xC2,0x88,0x08,
0x19,0x0A,0x49,0xB7,0xB3,0xA2,0xA1,0x92,0xA1,0x93,0xB1,0x0C,0x7D,0x39,0x93,0xB3,
0xB1,0x1A,0x19,0x5D,0x28,0xA6,0xC4,0xB2,0x90,0x09,0x2A,0x18,0x1B,0x5B,0x28,0x88,
0x2C,0x29,0x82,0xA0,0x18,0x91,0x2D,0x29,0x2B,0x5C,0x4C,0x3B,0x4C,0x28,0x80,0x92,
0x90,0x09,0x2B,0x28,0x1D,0x6B,0x11,0xC5,0xB2,0x0B,0x39,0x09,0x4D,0x28,0x88,0x00,
0x1B,0x28,0x94,0xE3,0xA0,0x1A,0x28,0xB5,0xB4,0xB3,0xB2,0x93,0xE2,0x91,0x92,0xD4,
0xA0,0x1B,0x4A,0x01,0xA1,0x88,0x2D,0x5C,0x3B,0x28,0x08,0x93,0xD4,0xB2,0x91,0xB4,
0xA0,0x3E,0x3B,0x4B,0x3B,0x29,0x08,0x93,0x9B,0x7B,0x3A,0x19,0x00,0x80,0x80,0xA0,
/* Source: 10TOM.ROM */
/* Length: 640 / 0x00000280 */
0x77,0x27,0x87,0x01,0x2D,0x4F,0xC3,0xC1,0x92,0x91,0x89,0x59,0x83,0x1A,0x32,0xC2,
0x95,0xB1,0x81,0x88,0x81,0x4A,0x3D,0x11,0x9E,0x0B,0x88,0x0C,0x18,0x3B,0x11,0x11,
0x91,0x00,0xA0,0xE2,0x0A,0x48,0x13,0x24,0x81,0x48,0x1B,0x39,0x1C,0x83,0x84,0xA1,
0xD1,0x8E,0x8A,0x0B,0xC0,0x98,0x92,0xB8,0x39,0x90,0x10,0x92,0xF0,0xB5,0x88,0x32,
0x49,0x51,0x21,0x03,0x82,0x10,0x8A,0x7A,0x09,0x00,0xA2,0xCA,0x1B,0xCC,0x1C,0xB9,
0x8E,0x89,0x89,0xA1,0x89,0x92,0x29,0x11,0x60,0x40,0x14,0x22,0x32,0x78,0x40,0x01,
0x02,0x90,0x81,0xAB,0x0B,0x00,0xAF,0x99,0xCC,0xAB,0xDA,0xA9,0x99,0x1B,0x30,0x14,
0x92,0x22,0x19,0x68,0x32,0x14,0x26,0x13,0x23,0x23,0x20,0x12,0x9A,0xA8,0xB9,0xFA,
0xAA,0xCA,0xCC,0x0C,0xA8,0xAE,0x88,0xB9,0x88,0xA0,0x02,0x21,0x50,0x43,0x03,0x81,
0x2A,0x11,0x34,0x63,0x24,0x33,0x22,0x38,0x8B,0xEA,0xAE,0x99,0xA0,0x90,0x82,0x00,
0x89,0xBF,0x8A,0xE8,0xA9,0x90,0x01,0x12,0x13,0x12,0x08,0xA9,0xAA,0xC9,0x22,0x63,
0x63,0x12,0x44,0x00,0x10,0x88,0x9C,0x98,0xA1,0x85,0x03,0x32,0x36,0x80,0x89,0xDB,
0xDB,0xBB,0xB9,0xBA,0x01,0x81,0x28,0x19,0xCB,0xFA,0xBC,0x09,0x13,0x37,0x34,0x34,
0x23,0x31,0x20,0x10,0x00,0x00,0x28,0x38,0x10,0x88,0xEC,0x8D,0xCB,0xBC,0xCC,0xBB,
0xBB,0xC9,0x99,0x00,0x00,0x33,0x11,0x22,0x81,0x07,0x41,0x54,0x34,0x34,0x22,0x31,
0x00,0x88,0x9A,0x9B,0x98,0xAB,0x8E,0x9B,0xBD,0x9C,0xBC,0xBB,0xDA,0xAA,0xA9,0x99,
0x18,0x38,0x60,0x20,0x31,0x13,0x13,0x51,0x14,0x31,0x53,0x33,0x35,0x22,0x01,0x8A,
0x9C,0xA9,0xCA,0xC9,0xA8,0x00,0x10,0x81,0x9C,0x9E,0xAB,0xCC,0xAB,0xBA,0x98,0x30,
0x52,0x03,0x81,0x08,0x9C,0xAC,0xAC,0x18,0x11,0x03,0x51,0x61,0x41,0x31,0x31,0x02,
0x01,0x20,0x24,0x43,0x44,0x40,0x30,0x10,0xBC,0xBE,0xCB,0xDB,0xAB,0xBA,0x99,0x98,
0x99,0xAA,0xBD,0xAA,0xC8,0x90,0x11,0x53,0x37,0x23,0x43,0x34,0x33,0x33,0x33,0x11,
0x28,0x00,0x19,0xA9,0x9A,0xCB,0xCE,0xBB,0xEB,0xBC,0xBB,0xCA,0xBA,0xA8,0x88,0x11,
0x12,0x21,0x20,0x22,0x26,0x26,0x23,0x23,0x43,0x24,0x22,0x32,0x20,0x31,0x81,0x9A,
0xBC,0xBC,0xCB,0xBD,0x9A,0xA9,0x90,0x98,0xBA,0xCC,0xCB,0xBC,0x8B,0x88,0x22,0x35,
0x23,0x12,0x99,0x8B,0xAA,0xAA,0x89,0x82,0x93,0x31,0x42,0x23,0x23,0x21,0x32,0x11,
0x20,0x13,0x13,0x24,0x24,0x24,0x22,0x11,0x8A,0x9E,0xAC,0xAC,0xAA,0xBA,0xAA,0xAB,
0xBD,0xBC,0xCB,0xCB,0xA9,0xA8,0x91,0x12,0x44,0x43,0x44,0x34,0x34,0x42,0x33,0x42,
0x21,0x11,0x11,0x88,0x80,0xAA,0x0B,0xAC,0xCB,0xEC,0xAC,0xBA,0xCA,0xAB,0x9A,0x99,
0x80,0x91,0x09,0x08,0x10,0x22,0x44,0x43,0x44,0x33,0x43,0x22,0x13,0x21,0x22,0x20,
0x09,0x88,0xB9,0xC8,0xBB,0xAB,0xAB,0xA9,0xA9,0x9B,0x9B,0x99,0x90,0x90,0x00,0x81,
0x00,0x08,0x09,0x8A,0x9A,0xAA,0xA9,0xA9,0x99,0x90,0x80,0x01,0x80,0x00,0x09,0x31,
0x32,0x44,0x33,0x43,0x34,0x33,0x24,0x22,0x23,0x12,0x10,0x09,0x9B,0xAB,0xCA,0xCC,
0xBB,0xCB,0xDA,0xCA,0xAB,0xCA,0xAB,0xA9,0xA8,0x92,0x12,0x43,0x53,0x35,0x23,0x33,
0x43,0x43,0x52,0x22,0x22,0x21,0x01,0x09,0x89,0xA9,0xBB,0xBD,0xBC,0xCB,0xDA,0xAB,
0xAB,0xAB,0xAA,0xA9,0x99,0xA8,0x09,0x01,0x11,0x34,0x25,0x23,0x33,0x51,0x22,0x31,
0x12,0x20,0x21,0x12,0x10,0x80,0x99,0x9A,0x99,0x99,0x88,0x08,0x00,0x88,0xA9,0x99,
0x99,0x80,0x80,0x10,0x01,0x00,0x9A,0xAA,0xBB,0xBA,0xBA,0xA9,0x99,0x99,0x89,0x99,
0x99,0x00,0x01,0x33,0x35,0x24,0x23,0x34,0x23,0x33,0x34,0x33,0x43,0x32,0x21,0x88,
0xAB,0xBD,0xBB,0xDB,0xAB,0xBA,0xBB,0xDA,0xBB,0xCB,0xBB,0xBC,0xA8,0x90,0x01,0x12,
0x23,0x43,0x53,0x34,0x34,0x39,0x80,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,
/* Source: 20RIM.ROM */
/* Length: 128 / 0x00000080 */
0x0F,0xFF,0x73,0x8E,0x71,0xCD,0x00,0x49,0x10,0x90,0x21,0x49,0xA0,0xDB,0x02,0x3A,
0xE3,0x0A,0x50,0x98,0xC0,0x59,0xA2,0x99,0x09,0x22,0xA2,0x80,0x10,0xA8,0x5B,0xD2,
0x88,0x21,0x09,0x96,0xA8,0x10,0x0A,0xE0,0x08,0x48,0x19,0xAB,0x52,0xA8,0x92,0x0C,
0x03,0x19,0xE2,0x0A,0x12,0xC2,0x81,0x1E,0x01,0xD0,0x48,0x88,0x98,0x01,0x49,0x91,
0xAA,0x2C,0x25,0x89,0x88,0xB5,0x81,0xA2,0x9A,0x12,0x9E,0x38,0x3B,0x81,0x9B,0x59,
0x01,0x93,0xCA,0x4A,0x21,0xA0,0x3D,0x0A,0x39,0x3D,0x12,0xA8,0x3F,0x18,0x01,0x92,
0x1C,0x00,0xB2,0x48,0xB9,0x94,0xA3,0x19,0x4F,0x19,0xB2,0x32,0x90,0xBA,0x01,0xE6,
0x91,0x80,0xC1,0xA4,0x2A,0x08,0xA1,0xB1,0x25,0xD2,0x88,0x99,0x21,0x80,0x88,0x80,
};

View file

@ -33,9 +33,12 @@ struct VGMHeader {
uint32_t dataOffset; // relative from this field offset - use it for data fetching!
uint32_t unused[6];
uint32_t unused[3];
uint32_t YM2203_Clock;
uint32_t YM2608_Clock;
uint32_t unused_2[1];
uint32_t YM3812_Clock;
uint32_t unused_2[2];
uint32_t unused_3[2];
uint32_t YMF262_Clock;
};
@ -82,6 +85,7 @@ enum class VGM_Stream_Opcode : uint8_t {
PCM_RAW_WRITE = 0x68, // 0x68 ... : PCM RAM write : see below
AY_WRITE = 0xa0, // 0xA0 aa dd : AY8910, write value dd to register aa
YM2203_CHIP2_WRITE = 0xA5, // 0xA5 aa dd : YM2203 chip 2, write value dd to register aa
RF5C68_WRITE = 0xb0, // 0xB0 aa dd : RF5C68, write value dd to register aa
RF5C164_WRITE = 0xb1, // 0xB1 aa dd : RF5C164, write value dd to register aa
PWM_WRITE = 0xb2, // 0xB2 ad dd : PWM, write value ddd to register a(d is MSB, dd is LSB)

View file

@ -146,16 +146,19 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="opl3.c" />
<ClCompile Include="opmplay.cpp" />
<ClCompile Include="ymfm\src\ymfm_adpcm.cpp" />
<ClCompile Include="ymfm\src\ymfm_misc.cpp" />
<ClCompile Include="ymfm\src\ymfm_opn.cpp" />
<ClCompile Include="ymfm\src\ymfm_pcm.cpp" />
<ClCompile Include="ymfm\src\ymfm_ssg.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\opl3.h" />
<ClInclude Include="include\portaudio.h" />
<ClInclude Include="include\vgm.h" />
<ClInclude Include="opmfile.h" />
<ClInclude Include="opmplay.h" />
<ClInclude Include="wavehead.h" />
<ClInclude Include="ymfm\src\ymfm.h" />
<ClInclude Include="ymfm\src\ymfm_opn.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View file

@ -13,16 +13,31 @@
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Исходные файлы\ymfm">
<UniqueIdentifier>{138d41e0-de89-403a-801c-cd6e8c729114}</UniqueIdentifier>
</Filter>
<Filter Include="Файлы заголовков\ymfm">
<UniqueIdentifier>{9101f88f-74cd-4172-9731-8e247f8dd520}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<Filter>Исходные файлы</Filter>
</ClCompile>
<ClCompile Include="opl3.c">
<Filter>Исходные файлы</Filter>
<ClCompile Include="ymfm\src\ymfm_opn.cpp">
<Filter>Исходные файлы\ymfm</Filter>
</ClCompile>
<ClCompile Include="opmplay.cpp">
<Filter>Исходные файлы</Filter>
<ClCompile Include="ymfm\src\ymfm_misc.cpp">
<Filter>Исходные файлы\ymfm</Filter>
</ClCompile>
<ClCompile Include="ymfm\src\ymfm_pcm.cpp">
<Filter>Исходные файлы\ymfm</Filter>
</ClCompile>
<ClCompile Include="ymfm\src\ymfm_ssg.cpp">
<Filter>Исходные файлы\ymfm</Filter>
</ClCompile>
<ClCompile Include="ymfm\src\ymfm_adpcm.cpp">
<Filter>Исходные файлы\ymfm</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
@ -35,14 +50,14 @@
<ClInclude Include="include\vgm.h">
<Filter>Файлы заголовков</Filter>
</ClInclude>
<ClInclude Include="include\opl3.h">
<Filter>Файлы заголовков</Filter>
</ClInclude>
<ClInclude Include="opmplay.h">
<Filter>Файлы заголовков</Filter>
</ClInclude>
<ClInclude Include="opmfile.h">
<Filter>Файлы заголовков</Filter>
</ClInclude>
<ClInclude Include="ymfm\src\ymfm_opn.h">
<Filter>Файлы заголовков\ymfm</Filter>
</ClInclude>
<ClInclude Include="ymfm\src\ymfm.h">
<Filter>Файлы заголовков\ymfm</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -7,22 +7,27 @@
#include <math.h>
#include <vector>
#include <fstream>
#include <queue>
#include "wavehead.h"
#include "opmplay.h"
#include "opl3.h"
#include "vgm.h"
#include "rss.h" // YM2608 internal ADPCM-A ROM dump
#include "ymfm/src/ymfm_opn.h"
#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN
#define NOMINMAX
#include <Windows.h>
const uint32_t SAMPLE_RATE = 44100;
enum {
CHANNELS = 2,
FRAMES_PER_BUFFER = 512,
MAX_FRAMES_PER_BUFFER = 4096,
};
const double OPNA_CLOCK_RATE = 7987000;
PaStreamParameters outputParameters;
PaStream* stream;
@ -43,8 +48,84 @@ struct opm_context_t {
int32_t delay_count;
};
struct vgm_context_t {
std::vector<uint8_t> vgmfile;
std::vector<uint8_t>::iterator vgmfile_it;
VGMHeader* header;
uint32_t loop_pos;
uint32_t start, end; // offsets
// delay count
int32_t delay_count;
// rescaler for 44100hz delays
double rescaler;
};
vgm_context_t vgmctx;
opm_context_t opmctx;
opl3_chip opl3;
class opna_interface_t : public ymfm::ymfm_interface {
public:
opna_interface_t() {};
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address) {
switch (type) {
case ymfm::ACCESS_ADPCM_A: return YM2608_ADPCM_ROM[address & 0x1FFF];
default: return 0;
}
}
};
// register write queue
class opnx_register_queue_t {
private:
uint64_t write_delay;
uint64_t clock;
struct reg_entry_t {
uint64_t clock;
int chip, reg, data;
};
std::queue<reg_entry_t> reg_queue;
ymfm::ym2608* chip;
public:
opnx_register_queue_t() : opnx_register_queue_t(nullptr) {}
opnx_register_queue_t(ymfm::ym2608* _chip) : clock(0), write_delay(0), chip(_chip) {}
void set_chip(ymfm::ym2608* _chip) { chip = _chip; }
void reset() {
clock = 0;
write_delay = 0;
while (!reg_queue.empty()) reg_queue.pop(); // flush queue
}
void add(int chip, int reg, int data, uint64_t delay) {
reg_entry_t r;
r.clock = clock + write_delay;
r.chip = chip;
r.reg = reg;
r.data = data;
reg_queue.push(r);
write_delay += delay;
}
void pop_clock() {
if (!reg_queue.empty() && (reg_queue.front().clock <= clock)) {
auto &r = reg_queue.front();
if (r.chip == 1 && chip) {
chip->write_address_hi(r.reg);
chip->write_data_hi(r.data);
}
else if (chip) {
chip->write_address(r.reg);
chip->write_data(r.data);
}
reg_queue.pop();
}
clock++; write_delay = 0;
}
};
opna_interface_t opna_interface;
ymfm::ym2608 *opnachip;
opnx_register_queue_t opna_regqueue;
ymfm::ym2608::output_data opna_out[MAX_FRAMES_PER_BUFFER];
// ------------------
@ -151,11 +232,88 @@ void console_done() {
SetConsoleActiveScreenBuffer(console.hStdout);
}
// ----------------------------
int vgm_parse_frame() {
auto it = vgmctx.vgmfile_it;
while (it < vgmctx.vgmfile.begin() + vgmctx.end) {
// flush delays
vgmctx.delay_count = 0;
// first, do OPL cases
switch ((VGM_Stream_Opcode)*it) {
case VGM_Stream_Opcode::YM2608_PORT1_WRITE: {
// get register and data
int reg = *(it + 1), data = *(it + 2);
opna_regqueue.add(1, reg, data, 4);
//opnachip->write(2, reg);
//opnachip->write(3, data);
break;
}
case VGM_Stream_Opcode::YM2608_PORT0_WRITE: {
// get register and data
int reg = *(it + 1), data = *(it + 2);
opna_regqueue.add(0, reg, data, 4);
//opnachip->write(0, reg);
//opnachip->write(1, data);
break;
}
default: break;
}
// then parse everything else
if (((*it >= 0x30) && (*it <= 0x3F)) || (*it == 0x4F) || (*it == 0x50)) it += 2; else
if (((*it >= 0x40) && (*it <= 0x4E)) || ((*it >= 0x51) && (*it <= 0x5F)) ||
((*it >= 0xA0) && (*it <= 0xAF)) || ((*it >= 0xB0) && (*it <= 0xBF))) it += 3; else
if (((*it >= 0xC0) && (*it <= 0xCF)) || ((*it >= 0xD0) && (*it <= 0xDF))) it += 4; else
if (((*it >= 0x70) && (*it <= 0x7F))) {
// accumulate short delays
vgmctx.delay_count += (*it) - 0x70; it++;
} else
switch ((VGM_Stream_Opcode)*it) {
case VGM_Stream_Opcode::DELAY_LONG:
vgmctx.delay_count = (uint32_t)((*(it + 1)) | (*(it + 2) << 8));
it += 3;
break;
case VGM_Stream_Opcode::DELAY_60HZ:
vgmctx.delay_count = 735U;
it += 1;
break;
case VGM_Stream_Opcode::DELAY_50HZ:
vgmctx.delay_count = 882U;
it += 1;
break;
case VGM_Stream_Opcode::END_OF_DATA:
it = vgmctx.vgmfile.begin() + vgmctx.end; // hack but should work
break;
case VGM_Stream_Opcode::DATA_BLOCK:
// skip data block
{
uint32_t datasize = (*(it + 3)) | (*(it + 4) << 8) | (*(it + 5) << 16) | (*(it + 6) << 24);
it += datasize + 7;
}
break;
default:
printf("unknown opcode %02X at offset %X!\n", *it, it - vgmctx.vgmfile.begin());
return 1;
break;
}
if (vgmctx.delay_count > 0) {
vgmctx.delay_count *= vgmctx.rescaler;
break;
}
}
// save current pointer
vgmctx.vgmfile_it = it;
return 0;
}
// -------------------
// opl3 synth render
// synth render
int synth_render(int16_t* buffer, uint32_t num_samples) {
int samples_to_render = num_samples;
#if 0
while (samples_to_render > 0) {
if (samples_to_render < opmctx.delay_count) {
OPL3_GenerateStream(&opl3, buffer, samples_to_render);
@ -173,12 +331,44 @@ int synth_render(int16_t* buffer, uint32_t num_samples) {
opmctx.delay_count = (44100.0 / ((double)0x1234DD / opmctx.opm.header.frame_rate));
}
}
#endif
while (samples_to_render > 0) {
if (samples_to_render < vgmctx.delay_count) {
for (int i = 0; i < samples_to_render; i++) {
opna_regqueue.pop_clock();
opnachip->generate(opna_out + i, 1);
opna_out[i].clamp16();
*(buffer + 0) = 0.8*opna_out[i].data[0] + 0.2* opna_out[i].data[2]; // mix FM and SSG
*(buffer + 1) = 0.8*opna_out[i].data[1] + 0.2* opna_out[i].data[2];
buffer += 2;
}
vgmctx.delay_count -= samples_to_render;
break;
}
else {
// calculate new delay
for (int i = 0; i < vgmctx.delay_count; i++) {
opna_regqueue.pop_clock();
opnachip->generate(opna_out + i, 1);
opna_out[i].clamp16();
*(buffer + 0) = 0.8*opna_out[i].data[0] + 0.2* opna_out[i].data[2]; // mix FM and SSG
*(buffer + 1) = 0.8*opna_out[i].data[1] + 0.2* opna_out[i].data[2];
buffer += 2;
}
samples_to_render -= vgmctx.delay_count;
// parse VGM stream
vgm_parse_frame();
}
}
return 0;
}
int pa_init() {
int pa_init(double sample_rate) {
PaError err;
// init portaudio
@ -199,7 +389,7 @@ int pa_init() {
&stream,
NULL, /* no input */
&outputParameters,
SAMPLE_RATE,
sample_rate,
FRAMES_PER_BUFFER,
0, /* we won't output out of range samples so don't bother clipping them */
NULL, /* no callback, use blocking API */
@ -235,10 +425,27 @@ int main(int argc, char* argv[])
{
bool render_to_wave = (argc >= 3);
OPL3_Reset(&opl3, SAMPLE_RATE);
//OPL3_Reset(&opl3, SAMPLE_RATE);
opnachip = new ymfm::ym2608(opna_interface);
if(opnachip == nullptr) {
printf("error: unable to init ymfm!\n");
return 1;
}
opnachip->reset();
opnachip->set_fidelity(ymfm::OPN_FIDELITY_MIN);
uint32_t sample_rate = opnachip->sample_rate(OPNA_CLOCK_RATE);
vgmctx.rescaler = ((double)sample_rate / 44100.0);
printf("sample rate - %d hz\n", sample_rate);
if (!render_to_wave) pa_init();
console_open();
opna_regqueue.reset();
opna_regqueue.set_chip(opnachip);
if (!render_to_wave) {
if (pa_init(sample_rate) != 0) {
printf("error: unable to init PortAudio!\n");
return 1;
}
}
FILE* f = fopen(argv[1], "rb");
if (f == NULL) {
@ -246,6 +453,7 @@ int main(int argc, char* argv[])
return 1;
}
#if 0
opmctx.io.type = OPMPLAY_IO_FILE;
opmctx.io.io = f;
@ -264,7 +472,39 @@ int main(int argc, char* argv[])
return 1;
};
opmctx.delay_count = 0;
#else
// open VGM file, ready to parse
std::ifstream infile(argv[1], std::ios::in | std::ios::binary);
infile.unsetf(std::ios::skipws);
// get filesize
infile.seekg(0, std::ios::end);
uint64_t fsize = infile.tellg();
infile.seekg(0, std::ios::beg);
// read whole file
vgmctx.vgmfile.reserve(fsize);
vgmctx.vgmfile.insert(vgmctx.vgmfile.begin(), std::istream_iterator<uint8_t>(infile), std::istream_iterator<uint8_t>());
// get header
vgmctx.header = reinterpret_cast<VGMHeader*>(vgmctx.vgmfile.data());
// check header
if (memcmp(vgmctx.header->id, "Vgm\x20", sizeof(vgmctx.header->id)) != 0) {
printf("not a vaild VGM file!\n");
return 1;
}
// parse basic VGM structure
printf("VGM %d.%d file found\n", (vgmctx.header->version >> 8) & 0xFF, vgmctx.header->version & 0xFF);
if (vgmctx.header->loopOffset != 0) vgmctx.loop_pos = vgmctx.header->loopOffset + offsetof(VGMHeader, loopOffset);
vgmctx.end = vgmctx.header->eofOffset + offsetof(VGMHeader, eofOffset);
vgmctx.start = ((vgmctx.header->version < 0x150) ? 0x40 : vgmctx.header->dataOffset + offsetof(VGMHeader, dataOffset));
vgmctx.vgmfile_it = vgmctx.vgmfile.begin() + vgmctx.start;
vgmctx.delay_count = 0;
#endif
console_open();
std::vector<int16_t> wavedata;
int ff_pos = 0, ff_counter = 0;
@ -301,9 +541,9 @@ int main(int argc, char* argv[])
memcpy(&fmtHeader.id, "fmt ", sizeof(fmtHeader.id));
fmtHeader.size = sizeof(fmtHeader) - 8;
fmtHeader.wFormatTag = 1; // plain uncompressed PCM
fmtHeader.nSamplesPerSec = SAMPLE_RATE;
fmtHeader.nSamplesPerSec = sample_rate;
fmtHeader.nBlockAlign = CHANNELS;
fmtHeader.nAvgBytesPerSec = SAMPLE_RATE * CHANNELS;
fmtHeader.nAvgBytesPerSec = sample_rate * CHANNELS;
fmtHeader.nChannels = CHANNELS;
fmtHeader.wBitsPerSample = 8;

File diff suppressed because it is too large Load diff

View file

@ -4,21 +4,26 @@
#pragma pack(push, 1)
enum {
OPM_FORMAT_VERSION = 0x0002
OPM_FORMAT_VERSION = 0x0010
};
struct opm_header_stream_desc_t {
//uint16_t ptr; // offset to data stream in paragraphs (bytes*16)
uint16_t size; // stream data size in bytes
uint16_t size; // stream data size in bytes (max. 65520 bytes)
};
enum {
OPM_FLAG_CHIP_OPL2 = (0 << 0),
OPM_FLAG_CHIP_OPL3 = (1 << 0),
OPM_CHIPTYPE_OPL = (0 << 0),
OPM_CHIPTYPE_OPN = (1 << 0),
};
OPM_FLAG_CHIP_TYPE = (3 << 0),
enum {
OPM_FLAG_CHIP_OPN = (0 << 0),
OPM_FLAG_CHIP_OPN_DUAL = (1 << 0),
OPM_FLAG_CHIP_OPNA = (2 << 0),
OPM_FLAG_CHIP_OPN3 = (3 << 0),
OPM_FLAG_PERCUSSION_MODE = (1 << 2),
OPM_FLAG_CHIP_TYPE = (3 << 0),
};
struct opm_header_t {
@ -30,16 +35,19 @@ struct opm_header_t {
};
uint16_t v;
} version;
uint8_t chip_type; // see above
uint8_t reserved0; //
uint32_t clock_rate; // hz, integer
uint16_t frame_rate; // hz, 8.8 fixedpoint
uint16_t flags; // see above
uint16_t frame_rate; // [hz] = 0x1234dd/frame_rate
uint8_t callstack_depth; // reserved, 0 at this moment
uint8_t channels; // must be either 9 or 18!
uint32_t channel_mask; // used channel mask, LSB = ch 0
uint8_t streams; // including control streams
uint32_t stream_mask; // used channel mask, LSB = ch 0
// opm_header_stream_desc_t stream[opm_header_t::channels + 1]; // first is control stream
// opm_header_stream_desc_t stream[opm_header_t::channels]; // first is control stream
};
// OPM v0 stream data:
// OPM v0 stream data, stream-independent commands
enum {
OPM_STREAM_END_FRAME = 0xFF, // end of frame, next channel
OPM_STREAM_END = 0xFE, // end of stream, stop here or loop to OPM_STREAM_LOOP stream point
@ -48,9 +56,6 @@ enum {
OPM_STREAM_SET_FRAME_RATE = 0xFB, // word rate (as in opm_header_t::frame_rate)
OPM_STREAM_LOOP = 0xFA, // set loop point here
OPM_4OP_TRIGGER = 0xF4, // 4-op mode on/off
OPM_KEY_TRIGGER = 0xF0, // set key on/off + optionally end of frame
// delay commands
OPM_STREAM_DELAY_INT32 = 0xF9, // dword delay
OPM_STREAM_DELAY_INT16 = 0xF8, // word delay
@ -59,45 +64,101 @@ enum {
// back reference
OPM_STREAM_BACKREF = 0xE0, // E0..EF - word backrefpos (12 bit), byte frames
// setter commands
OPM_SET_OPERATOR = 0x00, // 00..7F - set operator parameters
OPM_SET_FREQ_FB_VOL = 0x80, // 80..BF - set frequency, feedback and total level
// control register set
OPM_CTRL_KEY_PERC = 0x00, // 00..7F - set key on/off for percussion, end of frame
OPM_CTRL_REG_SET = 0x80, // 80..BF - set control registers
// flags
OPM_4OP_OFF = (0 << 0),
OPM_4OP_ON = (1 << 0),
OPM_KEY_OFF = (0 << 0),
OPM_KEY_ON = (1 << 0),
OPM_KEY_END_OF_FRAME = (1 << 1),
OPM_SET_VOLUME_END_OF_FRAME = (1 << 7),
OPM_CMD00_SET_MULT = (1 << 0),
OPM_CMD00_SET_TL = (1 << 1),
OPM_CMD00_SET_AD = (1 << 2),
OPM_CMD00_SET_SR = (1 << 3),
OPM_CMD00_SET_WAVEFORM = (1 << 4),
OPM_CMD00_SELECT_OPERATOR = (1 << 5),
OPM_CMD00_END_OF_FRAME = (1 << 6),
OPM_CMD80_SET_TL0 = (1 << 0),
OPM_CMD80_SET_TL1 = (1 << 1),
OPM_CMD80_SET_FEEDBACK = (1 << 2),
OPM_CMD80_SET_FREQ = (1 << 3),
OPM_CMD80_SET_KEYBLOCK = (1 << 4),
OPM_CMD80_END_OF_FRAME = (1 << 5),
OPM_CTRL_SET_REG01 = (1 << 0),
OPM_CTRL_SET_REG08 = (1 << 1),
OPM_CTRL_SET_REG105 = (1 << 2),
OPM_CTRL_SET_REG104 = (1 << 3),
OPM_CTRL_SET_REGBD = (1 << 4),
};
// OPN control stream commands
enum {
OPM_CTRL_EXTCH3 = 0x00, // 00..7F - ext. CH3 op1-3 frequency
OPM_CTRL_TIMER_CSM = 0x80, // 80..9F - set timer/CSM/LFO frequency
OPM_CTRL_RHYTHM = 0xA0, // A0..BF - rhythm control
OPM_CTRL_CMD80_REG24 = (1 << 0),
OPM_CTRL_CMD80_REG25 = (1 << 1),
OPM_CTRL_CMD80_REG27 = (1 << 2),
OPM_CTRL_CMD80_REG22 = (1 << 3),
OPM_CTRL_CMD80_EOF = (1 << 4),
OPM_CTRL_EXTCH3_OP1_LOW = (1 << 0),
OPM_CTRL_EXTCH3_OP1_HIGH = (1 << 1),
OPM_CTRL_EXTCH3_OP2_LOW = (1 << 2),
OPM_CTRL_EXTCH3_OP2_HIGH = (1 << 3),
OPM_CTRL_EXTCH3_OP3_LOW = (1 << 4),
OPM_CTRL_EXTCH3_OP3_HIGH = (1 << 5),
OPM_CTRL_EXTCH3_EOF = (1 << 6),
OPM_CTRL_CMDA0_REG_MASK = (0x0F << 0),
OPM_CTRL_CMDA0_EOF = (1 << 4),
};
// OPN FM stream commands
enum {
OPM_FM_ADSR = 0x00, // 00..3F - set ADSR
OPM_FM_MUL_TL_EG = 0x40, // 40..7F - set MULT/TL/SSG-EG
OPM_FM_FREQ_FB_PAN = 0x80, // 80..9F - set frequency/feedback/panning
OPM_FM_KEY = 0xA0, // A0..BF - key on/off
OPM_FM_CMD00_REG50 = (1 << 0),
OPM_FM_CMD00_REG60 = (1 << 1),
OPM_FM_CMD00_REG70 = (1 << 2),
OPM_FM_CMD00_REG80 = (1 << 3),
OPM_FM_CMD00_OP_SHIFT = 4,
OPM_FM_CMD00_OP_MASK = (3 << OPM_FM_CMD00_OP_SHIFT),
OPM_FM_CMD40_REG30 = (1 << 0),
OPM_FM_CMD40_REG40 = (1 << 1),
OPM_FM_CMD40_REG90 = (1 << 2),
OPM_FM_CMD40_EOF = (1 << 3),
OPM_FM_CMD40_OP_SHIFT = OPM_FM_CMD00_OP_SHIFT,
OPM_FM_CMD40_OP_MASK = OPM_FM_CMD00_OP_MASK,
OPM_FM_CMD80_REGA0 = (1 << 0),
OPM_FM_CMD80_REGA4 = (1 << 1),
OPM_FM_CMD80_REGB0 = (1 << 2),
OPM_FM_CMD80_REGB4 = (1 << 3),
OPM_FM_CMD80_EOF = (1 << 4),
OPM_FM_CMDA0_OP_SHIFT = 0,
OPM_FM_CMDA0_OP_MASK = (0x0F << OPM_FM_CMDA0_OP_SHIFT),
OPM_FM_CMDA0_EOF = (1 << 4),
};
// OPN SSG tone stream commands (shared with AY chip type)
enum {
OPM_AYTONE_REGS = 0x00, // 00..7F - set volume and period low
OPM_AYTONE_PERIOD = 0x80, // 80..BF - set period
OPM_AYTONE_MASK = 0xF0, // F0..F7 - set tone/noise mask
OPM_AYTONE_CMD00_VOLUME_MASK = (0x1F << 0),
OPM_AYTONE_CMD00_PERIOD_LOW = (1 << 5),
OPM_AYTONE_CMD00_EOF = (1 << 6),
OPM_AYTONE_CMD80_PERIOD_HIGH = (0xF << 0),
OPM_AYTONE_CMD80_PERIOD_LOW = (1 << 4),
OPM_AYTONE_CMD80_EOF = (1 << 5),
OPM_AYTONE_MASK_TONE = (1 << 0),
OPM_AYTONE_MASK_NOISE = (1 << 1),
OPM_AYTONE_MASK_EOF = (1 << 2),
};
// OPN SSG envelope/noise stream commands (shared with AY chip type)
enum {
OPM_AYENV_REGS = 0x00, // 00..7F - set noise and period low
OPM_AYENV_ENVTYPE = 0x80, // 80..BF - set env type and period low
OPM_AYENV_PERIOD_FULL = 0xF0, // F0..F7 - set full envelope period
OPM_AYENV_CMD00_NOISE_MASK = (0x1F << 0),
OPM_AYENV_CMD00_PERIOD_LOW = (1 << 5),
OPM_AYENV_CMD00_EOF = (1 << 6),
OPM_AYENV_CMD80_ENV_TYPE = (0xF << 0),
OPM_AYENV_CMD80_PERIOD_LOW = (1 << 4),
OPM_AYENV_CMD80_EOF = (1 << 5),
OPM_AYENV_CMDF0_PERIOD_LOW = (1 << 0),
OPM_AYENV_CMDF0_PERIOD_HIGH = (1 << 1),
OPM_AYENV_CMDF0_EOF = (1 << 2),
};
#pragma pack(pop)

View file

@ -1,6 +1,6 @@
#pragma once
#include <stdint.h>
#include "opl3.h"
//#include "opl3.h"
#include "opmfile.h"
// OPMPlay setup defines
@ -114,13 +114,13 @@ struct opmplay_context_t {
uint8_t _4op; // used to track 4op mode changes
// opl chip context
opl3_chip* chip;
//opl3_chip* chip;
};
// init context
int opmplay_init(opmplay_context_t* ctx, opl3_chip *chip);
int opmplay_init(opmplay_context_t* ctx);// , opl3_chip* chip);
// free context
int opmplay_free(opmplay_context_t* ctx);

View file

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = tab
insert_final_newline = true
tab_width = 4
trim_trailing_whitespace = true

40
oplplay/lxmplay/ymfm/.gitignore vendored Normal file
View file

@ -0,0 +1,40 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Symbol/intermediate files
*.pdb
*.ilk
# VS Code stuff
.vs/
reference/

View file

@ -0,0 +1,282 @@
# ymfm: One FM core to rule them all
The ymfm emulator ws written from the ground-up using the analysis and deduction by Nemesis as a starting point, particularly in [this thread](https://gendev.spritesmind.net/forum/viewtopic.php?f=24&t=386).
The core assumption is that these details apply to all FM variants unless otherwise proven incorrect.
The fine details of this implementation have also been cross-checked against Nemesis' implementation in his [Exodus emulator](https://www.exodusemulator.com/), as well as Alexey Khokholov's ["Nuked" implementations](https://github.com/nukeykt/Nuked-OPN2) based off die shots.
Operator and channel summing/mixing code for OPM and OPN is largely based off of research done by [David Viens](https://twitter.com/plgDavid) and Hubert Lamontagne.
## Families
The Yamaha FM chips can be broadly categoried into families:
* OPM (YM2151)
* OPP (YM2164)
* OPN (YM2203)
* OPNA/OPNB/OPN2 (YM2608, YM2610, YM2610B, YM2612, YM3438, YMF276, YMF288)
* OPL (YM3526)
* OPL2 (YM3812)
* OPLL (YM2413, YM2423, YMF281, DS1001, and others)
* OPL3 (YMF262, YMF289B)
* OPL4 (YMF278)
Additionally, several lesser-documented variants exist exclusively in the employ of Yamaha synthesizers:
* OPQ (YM3608)
* OPZ (YM2414)
All of these families are very closely related, and the ymfm engine is designed to be universal to work across all of
these families.
Of course, each variant has its own register maps, features, and implementation details which need to be sorted out.
Thus, each significant variant listed above is represented by a register class.
The register class contains:
* constants describing core parameters and features
* mappers between operators and channels
* generic fetchers that return normalized values across families
* family-specific implementations of LFO and phase calculations
## Family History
This history outlines the progress of adding/removing features across the three main families (OPM, OPN, OPL):
OPM started it all off, featuring:
* 8 FM channels, 4 operators each
* LFO and noise support
* Stereo output
OPM -> OPN changes:
* Reduced to 3 FM channels, 4 operators each
* Removed LFO and noise support
* Mono output
* Integrated AY-8910 compatible PSG
* Added SSG-EG envelope mode
* Added multi-frequency mode: ch. 3 operators can have separate frequencies
* Software controlled clock divider
OPN -> OPNA changes:
* Increased to 6 FM channels, 4 operators each
* Added back (a cut-down) LFO
* Stereo output again
* Removed software controlled divider on later versions (OPNB/OPN2)
* Removed PSG on OPN2 models
OPNA -> OPL changes:
* Increased to 9 FM channels, but only 2 operators each
* Even more simplified LFO
* Mono output
* Removed PSG
* Removed SSG-EG envelope modes
* Removed multi-frequency modes
* Fixed clock divider
* Built-in ryhthm generation
OPL -> OPL2 changes:
* Added 4 selectable waveforms
OPL2 -> OPLL changes:
* Vastly simplified register map
* 15 built-in instruments, plus built-in rhythm instruments
* 1 user-controlled instrument
OPL2 -> OPL3 changes:
* Increased to 18 FM channels, 2 operators each
* 4 output channels
* Increased to 8 selectable waveforms
* 6 channels can be configured to use 4 operators
## Channels and Operators
The polyphony of a given chip is determined by the number of channels it supports.
This number ranges from as low as 3 to as high as 18.
Each channel has either 2 or 4 operators that can be combined in a myriad of ways.
On most chips the number of operators per channel is fixed; however, some later OPL chips allow this to be toggled between 2 and 4 at runtime.
The base ymfm engine class maintains an array of channels and operators, while the relationship between the two is described by the register class.
## Registers
Registers on the Yamaha chips are generally write-only, and can be divided into three distinct categories:
* system-wide registers
* channel-specific registers
* operator-specific registers
For maximum flexibility, most parameters can be configured at the operator level, with channel-level registers controlling details such as how to combine the operators into the final output.
System-wide registers are used to control chip-wide modes and manage onboard timer functions.
Note that since registers are write-only, some ymfm register classes will use "holes" in the register space to store additional values that may be needed.
## Attenuation
Most of the computations of the FM engines are done in terms of attenuation, and thus are logarithmic in nature.
The maximum resolution used internally is 12 bits, as returned by the sin table:
Bit | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
----|----|----|----|----|----|----|-----|------|-------|--------|---------|---------
dB | -96| -48| -24| -12| -6| -3| -1.5| -0.75| -0.375| -0.1875| -0.09375| -0.046875
The envelope generator internally uses 10 bits:
Bit | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
----|----|----|----|----|----|-----|------|-------|--------|---------|
dB | -48| -24| -12| -6| -3| -1.5| -0.75| -0.375| -0.1875| -0.09375|
Total level for operators is usually represented by 7 bits:
Bit | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
----|----|----|----|----|----|-----|------|
dB | -48| -24| -12| -6| -3| -1.5| -0.75|
Sustain level in the envelope generator is usually represented by 4 bits:
Bit | 3 | 2 | 1 | 0 |
----|----|----|----|----|
dB | -24| -12| -6| -3|
## Status and Timers
Generically, all chips (except OPLL) support two timers that can be programmed to fire and signal IRQs.
These timers also set bits in the status register.
The behavior of these bits is shared across all implementations, even if the exact bit positions shift (this is controlled by constants in the registers class).
In addition, several chips incorporate ADPCM decoders which also may set bits in the same status register.
For this reason, it is possible to control various bits in the status register via the `set_reset_status()` function directly.
Any active bits that are set and which are not masked (mask is controlled by `set_irq_mask()`), lead to an IRQ being signalled.
Thus, it is possible for the chip-specific implementations to set the mask and control the status register bits such that IRQs are signalled via the same mechanism as timer signals.
In addition, the OPM and OPN families have a "busy" flag, which is set after each write, indicating that another write should not be performed.
Historically, the duration of this flag was constant and had nothing to do with the internals of the chip.
However, since the details can potentially vary chip-to-chip, it is the chip's responsibility to insert the busy flag into the status before returning it to the caller.
## Clocking
Each of the Yamaha chips works by cycling through all operators one at a time.
Thus, the effective output rate of the chips is related to the input clock divided by the number of operators.
In addition, the input clock is prescaled by an amount.
Generally, this is a fixed value, though some early OPN chips allow this to be selected at runtime from a small
number of values.
## Channel Frequencies
One major difference between OPM and later families is in how frequencies are specified.
OPM specifies frequency via a 3-bit 'block' (aka octave), combined with a 4-bit 'key code' (note number) and a 6-bit 'key fraction'.
The key code and fraction are converted on the chip into an x.11 fixed-point value and then shifted by the block to produce the final step value for the phase.
Later families, on the other hand, specify frequencies via a 3-bit 'block' just as on OPM, but combined with a 9-12-bit 'frequency number' or 'fnum', which is directly shifted by the block to produce the step value.
So essentially, later chips make the user do the conversion from note value to phase increment, while OPM is programmed in a more 'musical' way, specifying notes and cents.
Internally, this is abstracted away into a 'block_freq' value, which is a 16-bit value containing the block and frequency info concatenated together as follows:
* OPM: `[3-bit block]:[4-bit keycode]:[6-bit fraction] = 13 bits total`
* OPZ: `[3-bit block]:[12-bit fnum] = 15 bits total`
* OPN: `[3-bit block]:[11-bit fnum] 0 = 15 bits total`
* OPL: `[3-bit block]:[10-bit fnum]:00 = 15 bits total`
* OPLL: `[3-bit block]:[ 9-bit fnum]:000 = 15 bits total`
The register classes handle the raw format directly and convert it into a phase increment which can be used by the generic engine.
## Low Frequency Oscillator (LFO)
The LFO engines are different in several key ways.
The OPM LFO engine is fairly intricate.
It has a 4.4 floating-point rate which allows for a huge range of frequencies, and can select between four different waveforms (sawtooth, square, triangle, or noise).
Separate 7-bit depth controls for AM and PM control the amount of modulation applied in each case.
This global LFO value is then further controlled at the channel level by a 2-bit AM sensitivity and a 3-bit PM sensitivity, and each operator has a 1-bit AM on/off switch.
For OPN the LFO engine was removed entirely, but a limited version was put back in OPNA and later chips.
This stripped-down version offered only a 3-bit rate setting (versus the 4.4 floating-point rate in OPN), and no
global depth control.
It did bring back the channel-level sensitivity controls and the operator-level on/off control.
For OPL, the LFO is simplified again, with AM and PM running at fixed frequencies, and simple enable flags at the operator level for each controlling their application.
## Differences Between Families
The table below provides some high level functional differences between the differnet families:
subfamily: | OPM | OPN | OPNA | OPL | OPL2 | OPLL | OPL3 |
------------:|:------:|:------:|:------:|:------:|:------:|:------:|:------:|
outputs: | 2 | 1 | 2 | 1 | 1 | 1 | 4 |
channels: | 8 | 3 | 6 | 9 | 9 | 9 | 18 |
operators: | 32 | 12 | 24 | 18 | 18 | 18 | 36 |
waveforms: | 1 | 1 | 1 | 1 | 4 | 2 | 8 |
instruments: | no | no | no | yes | yes | yes | yes |
ryhthm: | no | no | no | no | no | yes | no |
dynamic ops: | no | no | no | no | no | no | yes |
prescale: | 2 | 2/3/6 | 2/3/6 | 4 | 4 | 4 | 8 |
EG divider: | 3 | 3 | 3 | 1 | 1 | 1 | 1 |
EG DP: | no | no | no | no | no | yes | no |
EG SSG: | no | yes | yes | no | no | no | no |
mod delay: | no | no | no | yes | yes | yes? | no |
CSM: | yes | ch 2 | ch 2 | yes | yes | yes | no |
LFO: | yes | no | yes | yes | yes | yes | yes |
noise: | yes | no | no | no | no | no | no |
* Outputs represents the number of output channels: 1=mono, 2=stereo, 4=stereo+.
* Channels represents the number of independent FM channels.
* Operators represents the number of operators, or "slots" which are assembled into the channels.
* Waveforms represents the number of different sine-derived waveforms available.
* Instruments indicates whether the family has built-in instruments.
* Rhythm indicates whether the family has a built-in rhythm
* Dynamic ops indicates whether it is possible to switch between 2-operator and 4-operator modes dynamically.
* Prescale specifies the default clock divider; some chips allow this to be controlled via register writes.
* EG divider represents the divider applied to the envelope generator clock.
* EG DP indicates whether the envelope generator includes a DP (depress?) phase at the beginning of each key on.
* SSG EG indicates whether the envelope generator has SSG-style support.
* Mod delay indicates whether the connection to the first modulator's input is delayed by 1 sample.
* CSM indicates whether CSM mode is supported, triggered by timer A.
* LFO indicates whether LFO is supported.
* Noise indicates whether one of the operators can be replaced with a noise source.
## Chip Specifics
While OPM is its own thing, the OPN and OPL families have quite a few specific
implementations, with many differing details beyond the core FM parts. Here are
some details on the OPN family:
chip ID: | YM2203 | YM2608 | YMF288 | YM2610 | YM2610B | YM2612 | YM3438 | YMF276 |
---------:|:------:|:------:|:------:|:------:|:-------:|:------:|:------:|:------:|
aka: | OPN | OPNA | OPN3L | OPNB | OPNB2 | OPN2 | OPN2C | OPN2L |
FM: | 3 | 6 | 6 | 4 | 6 | 6 | 6 | 6 |
AY-8910: | 3 | 1 | 1 | 1 | 1 | - | - | - |
ADPCM-A: | - | 6 int | 6 int | 6 ext | 6 ext | - | - | - |
ADPCM-B: | - | 1 ext | - | 1 ext | 1 ext | - | - | - |
DAC: | no | no | no | no | no | yes | yes | yes |
output: | 10.3fp | 16-bit | 16-bit | 16-bit | 16-bit | 9-bit | 9-bit | 16-bit |
summing: | adder | adder | adder | adder | adder | muxer | muxer | adder |
* FM represents the number of FM channels available.
* AY-8910 represents the number of AY-8910-compatible outputs.
* ADPCM-A represents the number of internal/external ADPCM-A channels present.
* ADPCM-B represents the number of internal/external ADPCM-B channels present.
* DAC indicates if a directly-accessible DAC output exists, replacing one channel.
* Output indicates the output format to the final DAC.
* Summing indicates whether channels are added or time divided in the output.
OPL has a similar trove of chip variants:
chip ID: | YM3526 | Y8950 | YM3812 | YM2413 | YMF262 | YMF289B | YMF278B |
------------:|:------:|:-------:|:------:|:------:|:------:|:-------:|:-------:|
aka: | OPL |MSX-AUDIO| OPL2 | OPLL | OPL3 | OPL3L | OPL4 |
FM: | 9 | 9 | 9 | 9 | 18 | 18 | 18 |
ADPCM-B: | - | 1 ext | - | - | - | - | - |
wavetable: | - | - | - | - | - | - | 24 |
instruments: | no | no | no | yes | no | no | no |
output: | 10.3fp | 10.3fp | 10.3fp | 9-bit | 16-bit | 16-bit | 16-bit |
summing: | adder | adder | adder | muxer | adder | adder | adder |
* FM represents the number of FM channels available.
* ADPCM-B represents the number of external ADPCM-B channels present.
* Wavetable indicates the number of wavetable channels present.
* Instruments indicates that the chip has built-in instrument selection.
* Output indicates the output format to the final DAC.
* Summing indicates whether channels are added or time divided in the output.
There are several close variants of the YM2413 with different sets of built-in instruments.
These include the YM2423, YMF281, and DS1001 (aka Konami VRC7).

View file

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2021, Aaron Giles
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,125 @@
# ymfm
<div style='text-align:center;margin:auto'>
<img src='https://aarongiles.com/img/icon-ymfm.png' width='128px'>
</div>
[ymfm](https://github.com/aaronsgiles/ymfm) is a collection of BSD-licensed Yamaha FM sound cores (OPM, OPN, OPL, and others), written by [Aaron Giles](https://aarongiles.com)
## Supported environments
This code should compile cleanly in any environment that has C++14 support.
It has been tested on gcc, clang, and Microsoft Visual C++ 2019.
## Supported chip families
Currently, support is present for the following chips (organized by header file):
* ymfm_misc.h:
* YM2149 (SSG) [1983: MSX; Atari ST]
* ymfm_opm.h:
* YM2151 (OPM) [1983: Sharp X1, X68000; MSX; synths: DX21, DX27, DX100]
* YM2164 (OPP) [1985: FB-01 MIDI Expander; IBM Music Feature Card; MSX; synths: Korg DS-8, 707]
* ymfm_opn.h:
* YM2203 (OPN) [1984: NEC PC-88, PC-98, NEC PC-6001mkII SR, PC-6601 SR]
* YM2608 (OPNA) [1985: NEC PC-88, PC-98]
* YM2610 (OPNB) [1987: Neo Geo]
* YM2610B (OPNB2)
* YM2612 (OPN2) [1988: Sega Mega Drive/Genesis; FM Towns]
* YM3438 (OPN2C)
* YMF276 (OPN2L)
* YMF288 (OPN3L) [1995: NEC PC-98]
* ymfm_opl.h:
* YM3526 (OPL) [1984: C64 SFX Sound Expander]
* Y8950 (MSX-Audio) [1984: MSX]
* YM3812 (OPL2) [1985: AdLib, Sound Blaster; synths: some Portasound keyboards]
* YMF262 (OPL3) [1988: Sound Blaster Pro 2.0, SB16]
* YMF289B (OPL3L)
* YMF278B (OPL4) [1993: MSX Moonsound cartridge]
* YM2413 (OPLL) [1986: Sega Master System, Mark III; MSX; synths: Portasound PSS-140, PSS-170, PSS-270]
* YM2423 (OPLL-X)
* YMF281 (OPLLP)
* DS1001 (Konami 053982/VRC7) [1991: Famicom cartridge Lagrange Point]
* ymfm_opq.h:
* YM3806 (OPQ) [synths: PSR-60/70]
* ymfm_opz.h:
* YM2414 (OPZ) [1987: synths: TX81Z, DX11, YS200; Korg Z3 guitar synth]
There are some obviously-related chips that also are on my horizon but have no implementation as yet:
* YMW-258-F 'GEW8' (aka Sega 315-5560 aka Sega Multi-PCM)
* YMF271 (OPX)
* YM21280 (OPS) / YM21290 (EGS) [synths: DX7, DX1, DX5, DX9, TX7, TX216, TX416, TX816]
* OPK?
## History
These cores were originally written during the summer and fall of 2020 as part of the [MAME](https://mamedev.org/) project.
As such, their design started off heavily based on how MAME works.
The OPM/OPN cores first appeared in MAME 0.230.
The OPL cores were added in MAME 0.231.
A further rewrite to abstract MAME dependencies is planned for MAME 0.232.
The goal was threefold:
1. provide BSD-licensed emulation cores that are more compatible with MAME's core licensing
1. modernize and unify the code around a common implementation of shared features
1. improve accuracy where possible based on discoveries made by others
## Accuracy
The goal of these cores is not 100% digital accuracy.
To achieve that would require full emulation of the pipelines, which would make the code extremely difficult to comprehend.
It would also make it much harder to share common implementations of features, or to add support for less well-known chip types.
If you want that level of accuracy, there are [several](https://github.com/nukeykt/Nuked-OPN2) [decap-based](https://github.com/nukeykt/Nuked-OPM) [emulation cores](https://github.com/nukeykt/Nuked-OPLL) out there.
Instead, the main goals are:
1. Extremely high (audibly indistinguishable) accuracy
1. Reasonable performance
1. Clean design with readable code
1. Clear documentation of the various chips
## General approach
Check out the [examples directory](https://github.com/aaronsgiles/ymfm/tree/main/examples) for some example usage patterns.
I'm not a big fan of makefiles for simple things, so instructions on how to compile each example are provided at the top.
# IMPORTANT
As of May 2021, the interface to these is still a bit in flux.
Be prepared when syncing with upstream to make some adjustments.
### Clocking
The general philosophy of the emulators provided here is that they are clock-independent.
Much like the actual chips, you (the consumer) control the clock; the chips themselves have no idea what time it is.
They just tick forward each time you ask them to.
The way you move things along is via the `generate()` function, which ticks the internal system forward one or more samples, and writes out an array out chip-specific `output_data`.
But what, exactly, is a "sample", and how long is it?
This is where the external clock comes in.
Most of the Yamaha chips are externally clocked in the MHz range.
They then divide that clock by a factor (sometimes dynamically controllable), and then the internal operators are pipelined to further divide the clock.
For example, the YM2151 internally divides the clock by 2, and has 32 operators to iterate through.
Thus, for a nominal input lock of 3.58MHz, you end up at around a 55.9kHz sample rate.
Fortunately, all the chip implementations can compute this for you; just pass the raw external clock value to the `sample_rate()` method and it will hand you back the output sample rate you want.
Then call `generate()` that many times per second to output the results.
But what if I want to output at a "normal" rate, like 44.1kHz?
Sorry, you'll have to rate convert as needed.
### Reading and Writing
To read or write to the chips, you can call the `read()` and `write()` methods.
The offset provided corresponds to the addressing input lines in a (hopefully) logical way.
For reads, almost all chips have a status register, which you can read via `read_status()`.
Some chips have a data port that can be read via `read_data()`.
And chips with extended addressing may also have `read_status_hi()` and `read_data_hi()`.
For writes, almost all chips have an address register and a data register, and so you can reliably count on there being a `write_address()` and `write_data()` method as well.
If the chip supports extended addressing, it may also have `write_address_hi()` and `write_data_hi()`.

View file

@ -0,0 +1,114 @@
//
// Simple program that touches all the existing cores to help ensure
// that everything builds cleanly.
//
// Compile with:
//
// g++ --std=c++14 -I../../src buildall.cpp ../../src/ymfm_misc.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_opq.cpp ../../src/ymfm_opz.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_pcm.cpp ../../src/ymfm_ssg.cpp -o buildall.exe
//
// or:
//
// clang --std=c++14 -I../../src buildall.cpp ../../src/ymfm_misc.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_opq.cpp ../../src/ymfm_opz.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_pcm.cpp ../../src/ymfm_ssg.cpp -o buildall.exe
//
// or:
//
// cl -I..\..\src buildall.cpp ..\..\src\ymfm_misc.cpp ..\..\src\ymfm_opl.cpp ..\..\src\ymfm_opm.cpp ..\..\src\ymfm_opn.cpp ..\..\src\ymfm_opq.cpp ..\..\src\ymfm_opz.cpp ..\..\src\ymfm_adpcm.cpp ..\..\src\ymfm_pcm.cpp ..\..\src\ymfm_ssg.cpp /Od /Zi /std:c++14 /EHsc
//
#include <vector>
#include "ymfm_misc.h"
#include "ymfm_opl.h"
#include "ymfm_opm.h"
#include "ymfm_opn.h"
#include "ymfm_opq.h"
#include "ymfm_opz.h"
//-------------------------------------------------
// main - program entry point
//-------------------------------------------------
template<typename ChipType>
class chip_wrapper : public ymfm::ymfm_interface
{
public:
chip_wrapper() :
m_chip(*this)
{
// reset
m_chip.reset();
// save/restore
std::vector<uint8_t> buffer;
{
ymfm::ymfm_saved_state saver(buffer, true);
m_chip.save_restore(saver);
}
{
ymfm::ymfm_saved_state restorer(buffer, false);
m_chip.save_restore(restorer);
}
// dummy read/write
m_chip.read(0);
m_chip.write(0, 0);
// generate
typename ChipType::output_data output[20];
m_chip.generate(&output[0], ymfm::array_size(output));
}
private:
ChipType m_chip;
};
//-------------------------------------------------
// main - program entry point
//-------------------------------------------------
int main(int argc, char *argv[])
{
// just keep adding chip variants here as they are implemented
// ymfm_misc.h:
chip_wrapper<ymfm::ym2149> test2149;
// ymfm_opl.h:
chip_wrapper<ymfm::ym3526> test3526;
chip_wrapper<ymfm::y8950> test8950;
chip_wrapper<ymfm::ym3812> test3812;
chip_wrapper<ymfm::ymf262> test262;
chip_wrapper<ymfm::ymf289b> test289b;
chip_wrapper<ymfm::ymf278b> test278b;
chip_wrapper<ymfm::ym2413> test2413;
chip_wrapper<ymfm::ym2423> test2423;
chip_wrapper<ymfm::ymf281> test281;
chip_wrapper<ymfm::ds1001> test1001;
// ymfm_opm.h:
chip_wrapper<ymfm::ym2151> test2151;
chip_wrapper<ymfm::ym2164> test2164;
// ymfm_opn.h:
chip_wrapper<ymfm::ym2203> test2203;
chip_wrapper<ymfm::ym2608> test2608;
chip_wrapper<ymfm::ymf288> test288;
chip_wrapper<ymfm::ym2610> test2610;
chip_wrapper<ymfm::ym2610b> test2610b;
chip_wrapper<ymfm::ym2612> test2612;
chip_wrapper<ymfm::ym3438> test3438;
chip_wrapper<ymfm::ymf276> test276;
// ymfm_opq.h:
chip_wrapper<ymfm::ym3806> test3806;
chip_wrapper<ymfm::ym3533> test3533;
// ymfm_opz.h:
chip_wrapper<ymfm::ym2414> test2414;
printf("Done\n");
return 0;
}

View file

@ -0,0 +1 @@
test

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
/*
* em_inflate.h - fast in-memory inflate (gzip/zlib decompressor) definitions
*
* Copyright (C) 2019 Emmanuel Marty
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef _EM_INFLATE_H
#define _EM_INFLATE_H
#include <sys/types.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Inflate gzip or zlib data
*
* @param pCompressedData pointer to start of zlib data
* @param nCompressedDataSize size of zlib data, in bytes
* @param pOutData pointer to start of decompression buffer
* @param nMaxOutDataSize maximum size of decompression buffer, in bytes
*
* @return number of bytes decompressed, or -1 in case of an error
*/
size_t em_inflate(const void *pCompressedData, size_t nCompressedDataSize, unsigned char *pOutData, size_t nMaxOutDataSize);
#ifdef __cplusplus
}
#endif
#endif /* _EM_INFLATE_H */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,566 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_H
#define YMFM_H
#pragma once
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <array>
#include <memory>
#include <string>
#include <vector>
namespace ymfm
{
//*********************************************************
// DEBUGGING
//*********************************************************
class debug
{
public:
// masks to help isolate specific channels
static constexpr uint32_t GLOBAL_FM_CHANNEL_MASK = 0xffffffff;
static constexpr uint32_t GLOBAL_ADPCM_A_CHANNEL_MASK = 0xffffffff;
static constexpr uint32_t GLOBAL_ADPCM_B_CHANNEL_MASK = 0xffffffff;
static constexpr uint32_t GLOBAL_PCM_CHANNEL_MASK = 0xffffffff;
// types of logging
static constexpr bool LOG_FM_WRITES = false;
static constexpr bool LOG_KEYON_EVENTS = false;
static constexpr bool LOG_UNEXPECTED_READ_WRITES = false;
// helpers to write based on the log type
template<typename... Params> static void log_fm_write(Params &&... args) { if (LOG_FM_WRITES) log(args...); }
template<typename... Params> static void log_keyon(Params &&... args) { if (LOG_KEYON_EVENTS) log(args...); }
template<typename... Params> static void log_unexpected_read_write(Params &&... args) { if (LOG_UNEXPECTED_READ_WRITES) log(args...); }
// downstream helper to output log data; defaults to printf
template<typename... Params> static void log(Params &&... args) { printf(args...); }
};
//*********************************************************
// GLOBAL HELPERS
//*********************************************************
//-------------------------------------------------
// bitfield - extract a bitfield from the given
// value, starting at bit 'start' for a length of
// 'length' bits
//-------------------------------------------------
inline uint32_t bitfield(uint32_t value, int start, int length = 1)
{
return (value >> start) & ((1 << length) - 1);
}
//-------------------------------------------------
// clamp - clamp between the minimum and maximum
// values provided
//-------------------------------------------------
inline int32_t clamp(int32_t value, int32_t minval, int32_t maxval)
{
if (value < minval)
return minval;
if (value > maxval)
return maxval;
return value;
}
//-------------------------------------------------
// count_leading_zeros - return the number of
// leading zeros in a 32-bit value; CPU-optimized
// versions for various architectures are included
// below
//-------------------------------------------------
#if defined(__GNUC__)
inline uint8_t count_leading_zeros(uint32_t value)
{
if (value == 0)
return 32;
return __builtin_clz(value);
}
#elif defined(_MSC_VER)
inline uint8_t count_leading_zeros(uint32_t value)
{
unsigned long index;
return _BitScanReverse(&index, value) ? uint8_t(31U - index) : 32U;
}
#else
inline uint8_t count_leading_zeros(uint32_t value)
{
if (value == 0)
return 32;
uint8_t count;
for (count = 0; int32_t(value) >= 0; count++)
value <<= 1;
return count;
}
#endif
// Many of the Yamaha FM chips emit a floating-point value, which is sent to
// a DAC for processing. The exact format of this floating-point value is
// documented below. This description only makes sense if the "internal"
// format treats sign as 1=positive and 0=negative, so the helpers below
// presume that.
//
// Internal OPx data 16-bit signed data Exp Sign Mantissa
// ================= ================= === ==== ========
// 1 1xxxxxxxx------ -> 0 1xxxxxxxx------ -> 111 1 1xxxxxxx
// 1 01xxxxxxxx----- -> 0 01xxxxxxxx----- -> 110 1 1xxxxxxx
// 1 001xxxxxxxx---- -> 0 001xxxxxxxx---- -> 101 1 1xxxxxxx
// 1 0001xxxxxxxx--- -> 0 0001xxxxxxxx--- -> 100 1 1xxxxxxx
// 1 00001xxxxxxxx-- -> 0 00001xxxxxxxx-- -> 011 1 1xxxxxxx
// 1 000001xxxxxxxx- -> 0 000001xxxxxxxx- -> 010 1 1xxxxxxx
// 1 000000xxxxxxxxx -> 0 000000xxxxxxxxx -> 001 1 xxxxxxxx
// 0 111111xxxxxxxxx -> 1 111111xxxxxxxxx -> 001 0 xxxxxxxx
// 0 111110xxxxxxxx- -> 1 111110xxxxxxxx- -> 010 0 0xxxxxxx
// 0 11110xxxxxxxx-- -> 1 11110xxxxxxxx-- -> 011 0 0xxxxxxx
// 0 1110xxxxxxxx--- -> 1 1110xxxxxxxx--- -> 100 0 0xxxxxxx
// 0 110xxxxxxxx---- -> 1 110xxxxxxxx---- -> 101 0 0xxxxxxx
// 0 10xxxxxxxx----- -> 1 10xxxxxxxx----- -> 110 0 0xxxxxxx
// 0 0xxxxxxxx------ -> 1 0xxxxxxxx------ -> 111 0 0xxxxxxx
//-------------------------------------------------
// encode_fp - given a 32-bit signed input value
// convert it to a signed 3.10 floating-point
// value
//-------------------------------------------------
inline int16_t encode_fp(int32_t value)
{
// handle overflows first
if (value < -32768)
return (7 << 10) | 0x000;
if (value > 32767)
return (7 << 10) | 0x3ff;
// we need to count the number of leading sign bits after the sign
// we can use count_leading_zeros if we invert negative values
int32_t scanvalue = value ^ (int32_t(value) >> 31);
// exponent is related to the number of leading bits starting from bit 14
int exponent = 7 - count_leading_zeros(scanvalue << 17);
// smallest exponent value allowed is 1
exponent = std::max(exponent, 1);
// mantissa
int32_t mantissa = value >> (exponent - 1);
// assemble into final form, inverting the sign
return ((exponent << 10) | (mantissa & 0x3ff)) ^ 0x200;
}
//-------------------------------------------------
// decode_fp - given a 3.10 floating-point value,
// convert it to a signed 16-bit value
//-------------------------------------------------
inline int16_t decode_fp(int16_t value)
{
// invert the sign and the exponent
value ^= 0x1e00;
// shift mantissa up to 16 bits then apply inverted exponent
return int16_t(value << 6) >> bitfield(value, 10, 3);
}
//-------------------------------------------------
// roundtrip_fp - compute the result of a round
// trip through the encode/decode process above
//-------------------------------------------------
inline int16_t roundtrip_fp(int32_t value)
{
// handle overflows first
if (value < -32768)
return -32768;
if (value > 32767)
return 32767;
// we need to count the number of leading sign bits after the sign
// we can use count_leading_zeros if we invert negative values
int32_t scanvalue = value ^ (int32_t(value) >> 31);
// exponent is related to the number of leading bits starting from bit 14
int exponent = 7 - count_leading_zeros(scanvalue << 17);
// smallest exponent value allowed is 1
exponent = std::max(exponent, 1);
// apply the shift back and forth to zero out bits that are lost
exponent -= 1;
int32_t mask = (1 << exponent) - 1;
return value & ~mask;
}
//*********************************************************
// HELPER CLASSES
//*********************************************************
// various envelope states
enum envelope_state : uint32_t
{
EG_DEPRESS = 0, // OPLL only; set EG_HAS_DEPRESS to enable
EG_ATTACK = 1,
EG_DECAY = 2,
EG_SUSTAIN = 3,
EG_RELEASE = 4,
EG_REVERB = 5, // OPQ/OPZ only; set EG_HAS_REVERB to enable
EG_STATES = 6
};
// external I/O access classes
enum access_class : uint32_t
{
ACCESS_IO = 0,
ACCESS_ADPCM_A,
ACCESS_ADPCM_B,
ACCESS_PCM,
ACCESS_CLASSES
};
//*********************************************************
// HELPER CLASSES
//*********************************************************
// ======================> ymfm_output
// struct containing an array of output values
template<int NumOutputs>
struct ymfm_output
{
// clear all outputs to 0
ymfm_output &clear()
{
for (uint32_t index = 0; index < NumOutputs; index++)
data[index] = 0;
return *this;
}
// clamp all outputs to a 16-bit signed value
ymfm_output &clamp16()
{
for (uint32_t index = 0; index < NumOutputs; index++)
data[index] = clamp(data[index], -32768, 32767);
return *this;
}
// run each output value through the floating-point processor
ymfm_output &roundtrip_fp()
{
for (uint32_t index = 0; index < NumOutputs; index++)
data[index] = ymfm::roundtrip_fp(data[index]);
return *this;
}
// internal state
int32_t data[NumOutputs];
};
// ======================> ymfm_wavfile
// this class is a debugging helper that accumulates data and writes it to wav files
template<int Channels>
class ymfm_wavfile
{
public:
// construction
ymfm_wavfile(uint32_t samplerate = 44100) :
m_samplerate(samplerate)
{
}
// configuration
ymfm_wavfile &set_index(uint32_t index) { m_index = index; return *this; }
ymfm_wavfile &set_samplerate(uint32_t samplerate) { m_samplerate = samplerate; return *this; }
// destruction
~ymfm_wavfile()
{
if (!m_buffer.empty())
{
// create file
char name[20];
snprintf(&name[0], sizeof(name), "wavlog-%02d.wav", m_index);
FILE *out = fopen(name, "wb");
// make the wav file header
uint8_t header[44];
memcpy(&header[0], "RIFF", 4);
*(uint32_t *)&header[4] = m_buffer.size() * 2 + 44 - 8;
memcpy(&header[8], "WAVE", 4);
memcpy(&header[12], "fmt ", 4);
*(uint32_t *)&header[16] = 16;
*(uint16_t *)&header[20] = 1;
*(uint16_t *)&header[22] = Channels;
*(uint32_t *)&header[24] = m_samplerate;
*(uint32_t *)&header[28] = m_samplerate * 2 * Channels;
*(uint16_t *)&header[32] = 2 * Channels;
*(uint16_t *)&header[34] = 16;
memcpy(&header[36], "data", 4);
*(uint32_t *)&header[40] = m_buffer.size() * 2 + 44 - 44;
// write header then data
fwrite(&header[0], 1, sizeof(header), out);
fwrite(&m_buffer[0], 2, m_buffer.size(), out);
fclose(out);
}
}
// add data to the file
template<int Outputs>
void add(ymfm_output<Outputs> output)
{
int16_t sum[Channels] = { 0 };
for (int index = 0; index < Outputs; index++)
sum[index % Channels] += output.data[index];
for (int index = 0; index < Channels; index++)
m_buffer.push_back(sum[index]);
}
// add data to the file, using a reference
template<int Outputs>
void add(ymfm_output<Outputs> output, ymfm_output<Outputs> const &ref)
{
int16_t sum[Channels] = { 0 };
for (int index = 0; index < Outputs; index++)
sum[index % Channels] += output.data[index] - ref.data[index];
for (int index = 0; index < Channels; index++)
m_buffer.push_back(sum[index]);
}
private:
// internal state
uint32_t m_index;
uint32_t m_samplerate;
std::vector<int16_t> m_buffer;
};
// ======================> ymfm_saved_state
// this class contains a managed vector of bytes that is used to save and
// restore state
class ymfm_saved_state
{
public:
// construction
ymfm_saved_state(std::vector<uint8_t> &buffer, bool saving) :
m_buffer(buffer),
m_offset(saving ? -1 : 0)
{
if (saving)
buffer.resize(0);
}
// are we saving or restoring?
bool saving() const { return (m_offset < 0); }
// generic save/restore
template<typename DataType>
void save_restore(DataType &data)
{
if (saving())
save(data);
else
restore(data);
}
public:
// save data to the buffer
void save(bool &data) { write(data ? 1 : 0); }
void save(int8_t &data) { write(data); }
void save(uint8_t &data) { write(data); }
void save(int16_t &data) { write(uint8_t(data)).write(data >> 8); }
void save(uint16_t &data) { write(uint8_t(data)).write(data >> 8); }
void save(int32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); }
void save(uint32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); }
void save(envelope_state &data) { write(uint8_t(data)); }
template<typename DataType, int Count>
void save(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) save(data[index]); }
// restore data from the buffer
void restore(bool &data) { data = read() ? true : false; }
void restore(int8_t &data) { data = read(); }
void restore(uint8_t &data) { data = read(); }
void restore(int16_t &data) { data = read(); data |= read() << 8; }
void restore(uint16_t &data) { data = read(); data |= read() << 8; }
void restore(int32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; }
void restore(uint32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; }
void restore(envelope_state &data) { data = envelope_state(read()); }
template<typename DataType, int Count>
void restore(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) restore(data[index]); }
// internal helper
ymfm_saved_state &write(uint8_t data) { m_buffer.push_back(data); return *this; }
uint8_t read() { return (m_offset < int32_t(m_buffer.size())) ? m_buffer[m_offset++] : 0; }
// internal state
std::vector<uint8_t> &m_buffer;
int32_t m_offset;
};
//*********************************************************
// INTERFACE CLASSES
//*********************************************************
// ======================> ymfm_engine_callbacks
// this class represents functions in the engine that the ymfm_interface
// needs to be able to call; it is represented here as a separate interface
// that is independent of the actual engine implementation
class ymfm_engine_callbacks
{
public:
virtual ~ymfm_engine_callbacks() = default;
// timer callback; called by the interface when a timer fires
virtual void engine_timer_expired(uint32_t tnum) = 0;
// check interrupts; called by the interface after synchronization
virtual void engine_check_interrupts() = 0;
// mode register write; called by the interface after synchronization
virtual void engine_mode_write(uint8_t data) = 0;
};
// ======================> ymfm_interface
// this class represents the interface between the fm_engine and the outside
// world; it provides hooks for timers, synchronization, and I/O
class ymfm_interface
{
// the engine is our friend
template<typename RegisterType> friend class fm_engine_base;
public:
virtual ~ymfm_interface() = default;
// the following functions must be implemented by any derived classes; the
// default implementations are sufficient for some minimal operation, but will
// likely need to be overridden to integrate with the outside world; they are
// all prefixed with ymfm_ to reduce the likelihood of namespace collisions
//
// timing and synchronizaton
//
// the chip implementation calls this when a write happens to the mode
// register, which could affect timers and interrupts; our responsibility
// is to ensure the system is up to date before calling the engine's
// engine_mode_write() method
virtual void ymfm_sync_mode_write(uint8_t data) { m_engine->engine_mode_write(data); }
// the chip implementation calls this when the chip's status has changed,
// which may affect the interrupt state; our responsibility is to ensure
// the system is up to date before calling the engine's
// engine_check_interrupts() method
virtual void ymfm_sync_check_interrupts() { m_engine->engine_check_interrupts(); }
// the chip implementation calls this when one of the two internal timers
// has changed state; our responsibility is to arrange to call the engine's
// engine_timer_expired() method after the provided number of clocks; if
// duration_in_clocks is negative, we should cancel any outstanding timers
virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) { }
// the chip implementation calls this to indicate that the chip should be
// considered in a busy state until the given number of clocks has passed;
// our responsibility is to compute and remember the ending time based on
// the chip's clock for later checking
virtual void ymfm_set_busy_end(uint32_t clocks) { }
// the chip implementation calls this to see if the chip is still currently
// is a busy state, as specified by a previous call to ymfm_set_busy_end();
// our responsibility is to compare the current time against the previously
// noted busy end time and return true if we haven't yet passed it
virtual bool ymfm_is_busy() { return false; }
//
// I/O functions
//
// the chip implementation calls this when the state of the IRQ signal has
// changed due to a status change; our responsibility is to respond as
// needed to the change in IRQ state, signaling any consumers
virtual void ymfm_update_irq(bool asserted) { }
// the chip implementation calls this whenever data is read from outside
// of the chip; our responsibility is to provide the data requested
virtual uint8_t ymfm_external_read(access_class type, uint32_t address) { return 0; }
// the chip implementation calls this whenever data is written outside
// of the chip; our responsibility is to pass the written data on to any consumers
virtual void ymfm_external_write(access_class type, uint32_t address, uint8_t data) { }
protected:
// pointer to engine callbacks -- this is set directly by the engine at
// construction time
ymfm_engine_callbacks *m_engine;
};
}
#endif // YMFM_H

View file

@ -0,0 +1,807 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ymfm_adpcm.h"
namespace ymfm
{
//*********************************************************
// ADPCM "A" REGISTERS
//*********************************************************
//-------------------------------------------------
// reset - reset the register state
//-------------------------------------------------
void adpcm_a_registers::reset()
{
std::fill_n(&m_regdata[0], REGISTERS, 0);
// initialize the pans to on by default, and max instrument volume;
// some neogeo homebrews (for example ffeast) rely on this
m_regdata[0x08] = m_regdata[0x09] = m_regdata[0x0a] =
m_regdata[0x0b] = m_regdata[0x0c] = m_regdata[0x0d] = 0xdf;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void adpcm_a_registers::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_regdata);
}
//*********************************************************
// ADPCM "A" CHANNEL
//*********************************************************
//-------------------------------------------------
// adpcm_a_channel - constructor
//-------------------------------------------------
adpcm_a_channel::adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift) :
m_choffs(choffs),
m_address_shift(addrshift),
m_playing(0),
m_curnibble(0),
m_curbyte(0),
m_curaddress(0),
m_accumulator(0),
m_step_index(0),
m_regs(owner.regs()),
m_owner(owner)
{
}
//-------------------------------------------------
// reset - reset the channel state
//-------------------------------------------------
void adpcm_a_channel::reset()
{
m_playing = 0;
m_curnibble = 0;
m_curbyte = 0;
m_curaddress = 0;
m_accumulator = 0;
m_step_index = 0;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void adpcm_a_channel::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_playing);
state.save_restore(m_curnibble);
state.save_restore(m_curbyte);
state.save_restore(m_curaddress);
state.save_restore(m_accumulator);
state.save_restore(m_step_index);
}
//-------------------------------------------------
// keyonoff - signal key on/off
//-------------------------------------------------
void adpcm_a_channel::keyonoff(bool on)
{
// QUESTION: repeated key ons restart the sample?
m_playing = on;
if (m_playing)
{
m_curaddress = m_regs.ch_start(m_choffs) << m_address_shift;
m_curnibble = 0;
m_curbyte = 0;
m_accumulator = 0;
m_step_index = 0;
// don't log masked channels
if (((debug::GLOBAL_ADPCM_A_CHANNEL_MASK >> m_choffs) & 1) != 0)
debug::log_keyon("KeyOn ADPCM-A%d: pan=%d%d start=%04X end=%04X level=%02X\n",
m_choffs,
m_regs.ch_pan_left(m_choffs),
m_regs.ch_pan_right(m_choffs),
m_regs.ch_start(m_choffs),
m_regs.ch_end(m_choffs),
m_regs.ch_instrument_level(m_choffs));
}
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
bool adpcm_a_channel::clock()
{
// if not playing, just output 0
if (m_playing == 0)
{
m_accumulator = 0;
return false;
}
// if we're about to read nibble 0, fetch the data
uint8_t data;
if (m_curnibble == 0)
{
// stop when we hit the end address; apparently only low 20 bits are used for
// comparison on the YM2610: this affects sample playback in some games, for
// example twinspri character select screen music will skip some samples if
// this is not correct
//
// note also: end address is inclusive, so wait until we are about to fetch
// the sample just after the end before stopping; this is needed for nitd's
// jump sound, for example
uint32_t end = (m_regs.ch_end(m_choffs) + 1) << m_address_shift;
if (((m_curaddress ^ end) & 0xfffff) == 0)
{
m_playing = m_accumulator = 0;
return true;
}
m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_A, m_curaddress++);
data = m_curbyte >> 4;
m_curnibble = 1;
}
// otherwise just extract from the previosuly-fetched byte
else
{
data = m_curbyte & 0xf;
m_curnibble = 0;
}
// compute the ADPCM delta
static uint16_t const s_steps[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
};
int32_t delta = (2 * bitfield(data, 0, 3) + 1) * s_steps[m_step_index] / 8;
if (bitfield(data, 3))
delta = -delta;
// the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205)
m_accumulator = (m_accumulator + delta) & 0xfff;
// adjust ADPCM step
static int8_t const s_step_inc[8] = { -1, -1, -1, -1, 2, 5, 7, 9 };
m_step_index = clamp(m_step_index + s_step_inc[bitfield(data, 0, 3)], 0, 48);
return false;
}
//-------------------------------------------------
// output - return the computed output value, with
// panning applied
//-------------------------------------------------
template<int NumOutputs>
void adpcm_a_channel::output(ymfm_output<NumOutputs> &output) const
{
// volume combines instrument and total levels
int vol = (m_regs.ch_instrument_level(m_choffs) ^ 0x1f) + (m_regs.total_level() ^ 0x3f);
// if combined is maximum, don't add to outputs
if (vol >= 63)
return;
// convert into a shift and a multiplier
// QUESTION: verify this from other sources
int8_t mul = 15 - (vol & 7);
uint8_t shift = 4 + 1 + (vol >> 3);
// m_accumulator is a 12-bit value; shift up to sign-extend;
// the downshift is incorporated into 'shift'
int16_t value = ((int16_t(m_accumulator << 4) * mul) >> shift) & ~3;
// apply to left/right as appropriate
if (NumOutputs == 1 || m_regs.ch_pan_left(m_choffs))
output.data[0] += value;
if (NumOutputs > 1 && m_regs.ch_pan_right(m_choffs))
output.data[1] += value;
}
//*********************************************************
// ADPCM "A" ENGINE
//*********************************************************
//-------------------------------------------------
// adpcm_a_engine - constructor
//-------------------------------------------------
adpcm_a_engine::adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift) :
m_intf(intf)
{
// create the channels
for (int chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum] = std::make_unique<adpcm_a_channel>(*this, chnum, addrshift);
}
//-------------------------------------------------
// reset - reset the engine state
//-------------------------------------------------
void adpcm_a_engine::reset()
{
// reset register state
m_regs.reset();
// reset each channel
for (auto &chan : m_channel)
chan->reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void adpcm_a_engine::save_restore(ymfm_saved_state &state)
{
// save register state
m_regs.save_restore(state);
// save channel state
for (int chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum]->save_restore(state);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
uint32_t adpcm_a_engine::clock(uint32_t chanmask)
{
// clock each channel, setting a bit in result if it finished
uint32_t result = 0;
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
if (m_channel[chnum]->clock())
result |= 1 << chnum;
// return the bitmask of completed samples
return result;
}
//-------------------------------------------------
// update - master update function
//-------------------------------------------------
template<int NumOutputs>
void adpcm_a_engine::output(ymfm_output<NumOutputs> &output, uint32_t chanmask)
{
// mask out some channels for debug purposes
chanmask &= debug::GLOBAL_ADPCM_A_CHANNEL_MASK;
// compute the output of each channel
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
m_channel[chnum]->output(output);
}
template void adpcm_a_engine::output<1>(ymfm_output<1> &output, uint32_t chanmask);
template void adpcm_a_engine::output<2>(ymfm_output<2> &output, uint32_t chanmask);
//-------------------------------------------------
// write - handle writes to the ADPCM-A registers
//-------------------------------------------------
void adpcm_a_engine::write(uint32_t regnum, uint8_t data)
{
// store the raw value to the register array;
// most writes are passive, consumed only when needed
m_regs.write(regnum, data);
// actively handle writes to the control register
if (regnum == 0x00)
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(data, chnum))
m_channel[chnum]->keyonoff(bitfield(~data, 7));
}
//*********************************************************
// ADPCM "B" REGISTERS
//*********************************************************
//-------------------------------------------------
// reset - reset the register state
//-------------------------------------------------
void adpcm_b_registers::reset()
{
std::fill_n(&m_regdata[0], REGISTERS, 0);
// default limit to wide open
m_regdata[0x0c] = m_regdata[0x0d] = 0xff;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void adpcm_b_registers::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_regdata);
}
//*********************************************************
// ADPCM "B" CHANNEL
//*********************************************************
//-------------------------------------------------
// adpcm_b_channel - constructor
//-------------------------------------------------
adpcm_b_channel::adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift) :
m_address_shift(addrshift),
m_status(STATUS_BRDY),
m_curnibble(0),
m_curbyte(0),
m_dummy_read(0),
m_position(0),
m_curaddress(0),
m_accumulator(0),
m_prev_accum(0),
m_adpcm_step(STEP_MIN),
m_regs(owner.regs()),
m_owner(owner)
{
}
//-------------------------------------------------
// reset - reset the channel state
//-------------------------------------------------
void adpcm_b_channel::reset()
{
m_status = STATUS_BRDY;
m_curnibble = 0;
m_curbyte = 0;
m_dummy_read = 0;
m_position = 0;
m_curaddress = 0;
m_accumulator = 0;
m_prev_accum = 0;
m_adpcm_step = STEP_MIN;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void adpcm_b_channel::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_status);
state.save_restore(m_curnibble);
state.save_restore(m_curbyte);
state.save_restore(m_dummy_read);
state.save_restore(m_position);
state.save_restore(m_curaddress);
state.save_restore(m_accumulator);
state.save_restore(m_prev_accum);
state.save_restore(m_adpcm_step);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
void adpcm_b_channel::clock()
{
// only process if active and not recording (which we don't support)
if (!m_regs.execute() || m_regs.record() || (m_status & STATUS_PLAYING) == 0)
{
m_status &= ~STATUS_PLAYING;
return;
}
// otherwise, advance the step
uint32_t position = m_position + m_regs.delta_n();
m_position = uint16_t(position);
if (position < 0x10000)
return;
// if we're about to process nibble 0, fetch sample
if (m_curnibble == 0)
{
// playing from RAM/ROM
if (m_regs.external())
m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress);
}
// extract the nibble from our current byte
uint8_t data = uint8_t(m_curbyte << (4 * m_curnibble)) >> 4;
m_curnibble ^= 1;
// we just processed the last nibble
if (m_curnibble == 0)
{
// if playing from RAM/ROM, check the end/limit address or advance
if (m_regs.external())
{
// handle the sample end, either repeating or stopping
if (at_end())
{
// if repeating, go back to the start
if (m_regs.repeat())
load_start();
// otherwise, done; set the EOS bit
else
{
m_accumulator = 0;
m_prev_accum = 0;
m_status = (m_status & ~STATUS_PLAYING) | STATUS_EOS;
debug::log_keyon("%s\n", "ADPCM EOS");
return;
}
}
// wrap at the limit address
else if (at_limit())
m_curaddress = 0;
// otherwise, advance the current address
else
{
m_curaddress++;
m_curaddress &= 0xffffff;
}
}
// if CPU-driven, copy the next byte and request more
else
{
m_curbyte = m_regs.cpudata();
m_status |= STATUS_BRDY;
}
}
// remember previous value for interpolation
m_prev_accum = m_accumulator;
// forecast to next forecast: 1/8, 3/8, 5/8, 7/8, 9/8, 11/8, 13/8, 15/8
int32_t delta = (2 * bitfield(data, 0, 3) + 1) * m_adpcm_step / 8;
if (bitfield(data, 3))
delta = -delta;
// add and clamp to 16 bits
m_accumulator = clamp(m_accumulator + delta, -32768, 32767);
// scale the ADPCM step: 0.9, 0.9, 0.9, 0.9, 1.2, 1.6, 2.0, 2.4
static uint8_t const s_step_scale[8] = { 57, 57, 57, 57, 77, 102, 128, 153 };
m_adpcm_step = clamp((m_adpcm_step * s_step_scale[bitfield(data, 0, 3)]) / 64, STEP_MIN, STEP_MAX);
}
//-------------------------------------------------
// output - return the computed output value, with
// panning applied
//-------------------------------------------------
template<int NumOutputs>
void adpcm_b_channel::output(ymfm_output<NumOutputs> &output, uint32_t rshift) const
{
// mask out some channels for debug purposes
if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) == 0)
return;
// do a linear interpolation between samples
int32_t result = (m_prev_accum * int32_t((m_position ^ 0xffff) + 1) + m_accumulator * int32_t(m_position)) >> 16;
// apply volume (level) in a linear fashion and reduce
result = (result * int32_t(m_regs.level())) >> (8 + rshift);
// apply to left/right
if (NumOutputs == 1 || m_regs.pan_left())
output.data[0] += result;
if (NumOutputs > 1 && m_regs.pan_right())
output.data[1] += result;
}
//-------------------------------------------------
// read - handle special register reads
//-------------------------------------------------
uint8_t adpcm_b_channel::read(uint32_t regnum)
{
uint8_t result = 0;
// register 8 reads over the bus under some conditions
if (regnum == 0x08 && !m_regs.execute() && !m_regs.record() && m_regs.external())
{
// two dummy reads are consumed first
if (m_dummy_read != 0)
{
load_start();
m_dummy_read--;
}
// read the data
else
{
// read from outside of the chip
result = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++);
// did we hit the end? if so, signal EOS
if (at_end())
{
m_status = STATUS_EOS | STATUS_BRDY;
debug::log_keyon("%s\n", "ADPCM EOS");
}
else
{
// signal ready
m_status = STATUS_BRDY;
}
// wrap at the limit address
if (at_limit())
m_curaddress = 0;
}
}
return result;
}
//-------------------------------------------------
// write - handle special register writes
//-------------------------------------------------
void adpcm_b_channel::write(uint32_t regnum, uint8_t value)
{
// register 0 can do a reset; also use writes here to reset the
// dummy read counter
if (regnum == 0x00)
{
if (m_regs.execute())
{
load_start();
// don't log masked channels
if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) != 0)
debug::log_keyon("KeyOn ADPCM-B: rep=%d spk=%d pan=%d%d dac=%d 8b=%d rom=%d ext=%d rec=%d start=%04X end=%04X pre=%04X dn=%04X lvl=%02X lim=%04X\n",
m_regs.repeat(),
m_regs.speaker(),
m_regs.pan_left(),
m_regs.pan_right(),
m_regs.dac_enable(),
m_regs.dram_8bit(),
m_regs.rom_ram(),
m_regs.external(),
m_regs.record(),
m_regs.start(),
m_regs.end(),
m_regs.prescale(),
m_regs.delta_n(),
m_regs.level(),
m_regs.limit());
}
else
m_status &= ~STATUS_EOS;
if (m_regs.resetflag())
reset();
if (m_regs.external())
m_dummy_read = 2;
}
// register 8 writes over the bus under some conditions
else if (regnum == 0x08)
{
// if writing from the CPU during execute, clear the ready flag
if (m_regs.execute() && !m_regs.record() && !m_regs.external())
m_status &= ~STATUS_BRDY;
// if writing during "record", pass through as data
else if (!m_regs.execute() && m_regs.record() && m_regs.external())
{
// clear out dummy reads and set start address
if (m_dummy_read != 0)
{
load_start();
m_dummy_read = 0;
}
// did we hit the end? if so, signal EOS
if (at_end())
{
debug::log_keyon("%s\n", "ADPCM EOS");
m_status = STATUS_EOS | STATUS_BRDY;
}
// otherwise, write the data and signal ready
else
{
m_owner.intf().ymfm_external_write(ACCESS_ADPCM_B, m_curaddress++, value);
m_status = STATUS_BRDY;
}
}
}
}
//-------------------------------------------------
// address_shift - compute the current address
// shift amount based on register settings
//-------------------------------------------------
uint32_t adpcm_b_channel::address_shift() const
{
// if a constant address shift, just provide that
if (m_address_shift != 0)
return m_address_shift;
// if ROM or 8-bit DRAM, shift is 5 bits
if (m_regs.rom_ram())
return 5;
if (m_regs.dram_8bit())
return 5;
// otherwise, shift is 2 bits
return 2;
}
//-------------------------------------------------
// load_start - load the start address and
// initialize the state
//-------------------------------------------------
void adpcm_b_channel::load_start()
{
m_status = (m_status & ~STATUS_EOS) | STATUS_PLAYING;
m_curaddress = m_regs.external() ? (m_regs.start() << address_shift()) : 0;
m_curnibble = 0;
m_curbyte = 0;
m_position = 0;
m_accumulator = 0;
m_prev_accum = 0;
m_adpcm_step = STEP_MIN;
}
//*********************************************************
// ADPCM "B" ENGINE
//*********************************************************
//-------------------------------------------------
// adpcm_b_engine - constructor
//-------------------------------------------------
adpcm_b_engine::adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift) :
m_intf(intf)
{
// create the channel (only one supported for now, but leaving possibilities open)
m_channel = std::make_unique<adpcm_b_channel>(*this, addrshift);
}
//-------------------------------------------------
// reset - reset the engine state
//-------------------------------------------------
void adpcm_b_engine::reset()
{
// reset registers
m_regs.reset();
// reset each channel
m_channel->reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void adpcm_b_engine::save_restore(ymfm_saved_state &state)
{
// save our state
m_regs.save_restore(state);
// save channel state
m_channel->save_restore(state);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
void adpcm_b_engine::clock()
{
// clock each channel, setting a bit in result if it finished
m_channel->clock();
}
//-------------------------------------------------
// output - master output function
//-------------------------------------------------
template<int NumOutputs>
void adpcm_b_engine::output(ymfm_output<NumOutputs> &output, uint32_t rshift)
{
// compute the output of each channel
m_channel->output(output, rshift);
}
template void adpcm_b_engine::output<1>(ymfm_output<1> &output, uint32_t rshift);
template void adpcm_b_engine::output<2>(ymfm_output<2> &output, uint32_t rshift);
//-------------------------------------------------
// write - handle writes to the ADPCM-B registers
//-------------------------------------------------
void adpcm_b_engine::write(uint32_t regnum, uint8_t data)
{
// store the raw value to the register array;
// most writes are passive, consumed only when needed
m_regs.write(regnum, data);
// let the channel handle any special writes
m_channel->write(regnum, data);
}
}

View file

@ -0,0 +1,411 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_ADPCM_H
#define YMFM_ADPCM_H
#pragma once
#include "ymfm.h"
namespace ymfm
{
//*********************************************************
// INTERFACE CLASSES
//*********************************************************
// forward declarations
class adpcm_a_engine;
class adpcm_b_engine;
// ======================> adpcm_a_registers
//
// ADPCM-A register map:
//
// System-wide registers:
// 00 x------- Dump (disable=1) or keyon (0) control
// --xxxxxx Mask of channels to dump or keyon
// 01 --xxxxxx Total level
// 02 xxxxxxxx Test register
// 08-0D x------- Pan left
// -x------ Pan right
// ---xxxxx Instrument level
// 10-15 xxxxxxxx Start address (low)
// 18-1D xxxxxxxx Start address (high)
// 20-25 xxxxxxxx End address (low)
// 28-2D xxxxxxxx End address (high)
//
class adpcm_a_registers
{
public:
// constants
static constexpr uint32_t OUTPUTS = 2;
static constexpr uint32_t CHANNELS = 6;
static constexpr uint32_t REGISTERS = 0x30;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
// constructor
adpcm_a_registers() { }
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
return chnum;
}
// direct read/write access
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
// system-wide registers
uint32_t dump() const { return bitfield(m_regdata[0x00], 7); }
uint32_t dump_mask() const { return bitfield(m_regdata[0x00], 0, 6); }
uint32_t total_level() const { return bitfield(m_regdata[0x01], 0, 6); }
uint32_t test() const { return m_regdata[0x02]; }
// per-channel registers
uint32_t ch_pan_left(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 7); }
uint32_t ch_pan_right(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 6); }
uint32_t ch_instrument_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 0, 5); }
uint32_t ch_start(uint32_t choffs) const { return m_regdata[choffs + 0x10] | (m_regdata[choffs + 0x18] << 8); }
uint32_t ch_end(uint32_t choffs) const { return m_regdata[choffs + 0x20] | (m_regdata[choffs + 0x28] << 8); }
// per-channel writes
void write_start(uint32_t choffs, uint32_t address)
{
write(choffs + 0x10, address);
write(choffs + 0x18, address >> 8);
}
void write_end(uint32_t choffs, uint32_t address)
{
write(choffs + 0x20, address);
write(choffs + 0x28, address >> 8);
}
private:
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> adpcm_a_channel
class adpcm_a_channel
{
public:
// constructor
adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift);
// reset the channel state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// signal key on/off
void keyonoff(bool on);
// master clockingfunction
bool clock();
// return the computed output value, with panning applied
template<int NumOutputs>
void output(ymfm_output<NumOutputs> &output) const;
private:
// internal state
uint32_t const m_choffs; // channel offset
uint32_t const m_address_shift; // address bits shift-left
uint32_t m_playing; // currently playing?
uint32_t m_curnibble; // index of the current nibble
uint32_t m_curbyte; // current byte of data
uint32_t m_curaddress; // current address
int32_t m_accumulator; // accumulator
int32_t m_step_index; // index in the stepping table
adpcm_a_registers &m_regs; // reference to registers
adpcm_a_engine &m_owner; // reference to our owner
};
// ======================> adpcm_a_engine
class adpcm_a_engine
{
public:
static constexpr int CHANNELS = adpcm_a_registers::CHANNELS;
// constructor
adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift);
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
uint32_t clock(uint32_t chanmask);
// compute sum of channel outputs
template<int NumOutputs>
void output(ymfm_output<NumOutputs> &output, uint32_t chanmask);
// write to the ADPCM-A registers
void write(uint32_t regnum, uint8_t data);
// set the start/end address for a channel (for hardcoded YM2608 percussion)
void set_start_end(uint8_t chnum, uint16_t start, uint16_t end)
{
uint32_t choffs = adpcm_a_registers::channel_offset(chnum);
m_regs.write_start(choffs, start);
m_regs.write_end(choffs, end);
}
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
adpcm_a_registers &regs() { return m_regs; }
private:
// internal state
ymfm_interface &m_intf; // reference to the interface
std::unique_ptr<adpcm_a_channel> m_channel[CHANNELS]; // array of channels
adpcm_a_registers m_regs; // registers
};
// ======================> adpcm_b_registers
//
// ADPCM-B register map:
//
// System-wide registers:
// 00 x------- Start of synthesis/analysis
// -x------ Record
// --x----- External/manual driving
// ---x---- Repeat playback
// ----x--- Speaker off
// -------x Reset
// 01 x------- Pan left
// -x------ Pan right
// ----x--- Start conversion
// -----x-- DAC enable
// ------x- DRAM access (1=8-bit granularity; 0=1-bit)
// -------x RAM/ROM (1=ROM, 0=RAM)
// 02 xxxxxxxx Start address (low)
// 03 xxxxxxxx Start address (high)
// 04 xxxxxxxx End address (low)
// 05 xxxxxxxx End address (high)
// 06 xxxxxxxx Prescale value (low)
// 07 -----xxx Prescale value (high)
// 08 xxxxxxxx CPU data/buffer
// 09 xxxxxxxx Delta-N frequency scale (low)
// 0a xxxxxxxx Delta-N frequency scale (high)
// 0b xxxxxxxx Level control
// 0c xxxxxxxx Limit address (low)
// 0d xxxxxxxx Limit address (high)
// 0e xxxxxxxx DAC data [YM2608/10]
// 0f xxxxxxxx PCM data [YM2608/10]
// 0e xxxxxxxx DAC data high [Y8950]
// 0f xx------ DAC data low [Y8950]
// 10 -----xxx DAC data exponent [Y8950]
//
class adpcm_b_registers
{
public:
// constants
static constexpr uint32_t REGISTERS = 0x11;
// constructor
adpcm_b_registers() { }
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// direct read/write access
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
// system-wide registers
uint32_t execute() const { return bitfield(m_regdata[0x00], 7); }
uint32_t record() const { return bitfield(m_regdata[0x00], 6); }
uint32_t external() const { return bitfield(m_regdata[0x00], 5); }
uint32_t repeat() const { return bitfield(m_regdata[0x00], 4); }
uint32_t speaker() const { return bitfield(m_regdata[0x00], 3); }
uint32_t resetflag() const { return bitfield(m_regdata[0x00], 0); }
uint32_t pan_left() const { return bitfield(m_regdata[0x01], 7); }
uint32_t pan_right() const { return bitfield(m_regdata[0x01], 6); }
uint32_t start_conversion() const { return bitfield(m_regdata[0x01], 3); }
uint32_t dac_enable() const { return bitfield(m_regdata[0x01], 2); }
uint32_t dram_8bit() const { return bitfield(m_regdata[0x01], 1); }
uint32_t rom_ram() const { return bitfield(m_regdata[0x01], 0); }
uint32_t start() const { return m_regdata[0x02] | (m_regdata[0x03] << 8); }
uint32_t end() const { return m_regdata[0x04] | (m_regdata[0x05] << 8); }
uint32_t prescale() const { return m_regdata[0x06] | (bitfield(m_regdata[0x07], 0, 3) << 8); }
uint32_t cpudata() const { return m_regdata[0x08]; }
uint32_t delta_n() const { return m_regdata[0x09] | (m_regdata[0x0a] << 8); }
uint32_t level() const { return m_regdata[0x0b]; }
uint32_t limit() const { return m_regdata[0x0c] | (m_regdata[0x0d] << 8); }
uint32_t dac() const { return m_regdata[0x0e]; }
uint32_t pcm() const { return m_regdata[0x0f]; }
private:
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> adpcm_b_channel
class adpcm_b_channel
{
static constexpr int32_t STEP_MIN = 127;
static constexpr int32_t STEP_MAX = 24576;
public:
static constexpr uint8_t STATUS_EOS = 0x01;
static constexpr uint8_t STATUS_BRDY = 0x02;
static constexpr uint8_t STATUS_PLAYING = 0x04;
// constructor
adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift);
// reset the channel state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// signal key on/off
void keyonoff(bool on);
// master clocking function
void clock();
// return the computed output value, with panning applied
template<int NumOutputs>
void output(ymfm_output<NumOutputs> &output, uint32_t rshift) const;
// return the status register
uint8_t status() const { return m_status; }
// handle special register reads
uint8_t read(uint32_t regnum);
// handle special register writes
void write(uint32_t regnum, uint8_t value);
private:
// helper - return the current address shift
uint32_t address_shift() const;
// load the start address
void load_start();
// limit checker; stops at the last byte of the chunk described by address_shift()
bool at_limit() const { return (m_curaddress == (((m_regs.limit() + 1) << address_shift()) - 1)); }
// end checker; stops at the last byte of the chunk described by address_shift()
bool at_end() const { return (m_curaddress == (((m_regs.end() + 1) << address_shift()) - 1)); }
// internal state
uint32_t const m_address_shift; // address bits shift-left
uint32_t m_status; // currently playing?
uint32_t m_curnibble; // index of the current nibble
uint32_t m_curbyte; // current byte of data
uint32_t m_dummy_read; // dummy read tracker
uint32_t m_position; // current fractional position
uint32_t m_curaddress; // current address
int32_t m_accumulator; // accumulator
int32_t m_prev_accum; // previous accumulator (for linear interp)
int32_t m_adpcm_step; // next forecast
adpcm_b_registers &m_regs; // reference to registers
adpcm_b_engine &m_owner; // reference to our owner
};
// ======================> adpcm_b_engine
class adpcm_b_engine
{
public:
// constructor
adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift = 0);
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
void clock();
// compute sum of channel outputs
template<int NumOutputs>
void output(ymfm_output<NumOutputs> &output, uint32_t rshift);
// read from the ADPCM-B registers
uint32_t read(uint32_t regnum) { return m_channel->read(regnum); }
// write to the ADPCM-B registers
void write(uint32_t regnum, uint8_t data);
// status
uint8_t status() const { return m_channel->status(); }
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
adpcm_b_registers &regs() { return m_regs; }
private:
// internal state
ymfm_interface &m_intf; // reference to our interface
std::unique_ptr<adpcm_b_channel> m_channel; // channel pointer
adpcm_b_registers m_regs; // registers
};
}
#endif // YMFM_ADPCM_H

View file

@ -0,0 +1,463 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_FM_H
#define YMFM_FM_H
#pragma once
#define YMFM_DEBUG_LOG_WAVFILES (0)
namespace ymfm
{
//*********************************************************
// GLOBAL ENUMERATORS
//*********************************************************
// three different keyon sources; actual keyon is an OR over all of these
enum keyon_type : uint32_t
{
KEYON_NORMAL = 0,
KEYON_RHYTHM = 1,
KEYON_CSM = 2
};
//*********************************************************
// CORE IMPLEMENTATION
//*********************************************************
// ======================> opdata_cache
// this class holds data that is computed once at the start of clocking
// and remains static during subsequent sound generation
struct opdata_cache
{
// set phase_step to this value to recalculate it each sample; needed
// in the case of PM LFO changes
static constexpr uint32_t PHASE_STEP_DYNAMIC = 1;
uint16_t const *waveform; // base of sine table
uint32_t phase_step; // phase step, or PHASE_STEP_DYNAMIC if PM is active
uint32_t total_level; // total level * 8 + KSL
uint32_t block_freq; // raw block frequency value (used to compute phase_step)
int32_t detune; // detuning value (used to compute phase_step)
uint32_t multiple; // multiple value (x.1, used to compute phase_step)
uint32_t eg_sustain; // sustain level, shifted up to envelope values
uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR
uint8_t eg_shift = 0; // envelope shift amount
};
// ======================> fm_registers_base
// base class for family-specific register classes; this provides a few
// constants, common defaults, and helpers, but mostly each derived class is
// responsible for defining all commonly-called methods
class fm_registers_base
{
public:
// this value is returned from the write() function for rhythm channels
static constexpr uint32_t RHYTHM_CHANNEL = 0xff;
// this is the size of a full sin waveform
static constexpr uint32_t WAVEFORM_LENGTH = 0x400;
//
// the following constants need to be defined per family:
// uint32_t OUTPUTS: The number of outputs exposed (1-4)
// uint32_t CHANNELS: The number of channels on the chip
// uint32_t ALL_CHANNELS: A bitmask of all channels
// uint32_t OPERATORS: The number of operators on the chip
// uint32_t WAVEFORMS: The number of waveforms offered
// uint32_t REGISTERS: The number of 8-bit registers allocated
// uint32_t DEFAULT_PRESCALE: The starting clock prescale
// uint32_t EG_CLOCK_DIVIDER: The clock divider of the envelope generator
// uint32_t CSM_TRIGGER_MASK: Mask of channels to trigger in CSM mode
// uint32_t REG_MODE: The address of the "mode" register controlling timers
// uint8_t STATUS_TIMERA: Status bit to set when timer A fires
// uint8_t STATUS_TIMERB: Status bit to set when tiemr B fires
// uint8_t STATUS_BUSY: Status bit to set when the chip is busy
// uint8_t STATUS_IRQ: Status bit to set when an IRQ is signalled
//
// the following constants are uncommon:
// bool DYNAMIC_OPS: True if ops/channel can be changed at runtime (OPL3+)
// bool EG_HAS_DEPRESS: True if the chip has a DP ("depress"?) envelope stage (OPLL)
// bool EG_HAS_REVERB: True if the chip has a faux reverb envelope stage (OPQ/OPZ)
// bool EG_HAS_SSG: True if the chip has SSG envelope support (OPN)
// bool MODULATOR_DELAY: True if the modulator is delayed by 1 sample (OPL pre-OPL3)
//
static constexpr bool DYNAMIC_OPS = false;
static constexpr bool EG_HAS_DEPRESS = false;
static constexpr bool EG_HAS_REVERB = false;
static constexpr bool EG_HAS_SSG = false;
static constexpr bool MODULATOR_DELAY = false;
// system-wide register defaults
uint32_t status_mask() const { return 0; } // OPL only
uint32_t irq_reset() const { return 0; } // OPL only
uint32_t noise_enable() const { return 0; } // OPM only
uint32_t rhythm_enable() const { return 0; } // OPL only
// per-operator register defaults
uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return 0; } // OPN(A) only
uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return 0; } // OPN(A) only
protected:
// helper to encode four operator numbers into a 32-bit value in the
// operator maps for each register class
static constexpr uint32_t operator_list(uint8_t o1 = 0xff, uint8_t o2 = 0xff, uint8_t o3 = 0xff, uint8_t o4 = 0xff)
{
return o1 | (o2 << 8) | (o3 << 16) | (o4 << 24);
}
// helper to apply KSR to the raw ADSR rate, ignoring ksr if the
// raw value is 0, and clamping to 63
static constexpr uint32_t effective_rate(uint32_t rawrate, uint32_t ksr)
{
return (rawrate == 0) ? 0 : std::min<uint32_t>(rawrate + ksr, 63);
}
};
//*********************************************************
// CORE ENGINE CLASSES
//*********************************************************
// forward declarations
template<class RegisterType> class fm_engine_base;
// ======================> fm_operator
// fm_operator represents an FM operator (or "slot" in FM parlance), which
// produces an output sine wave modulated by an envelope
template<class RegisterType>
class fm_operator
{
// "quiet" value, used to optimize when we can skip doing work
static constexpr uint32_t EG_QUIET = 0x380;
public:
// constructor
fm_operator(fm_engine_base<RegisterType> &owner, uint32_t opoffs);
// save/restore
void save_restore(ymfm_saved_state &state);
// reset the operator state
void reset();
// return the operator/channel offset
uint32_t opoffs() const { return m_opoffs; }
uint32_t choffs() const { return m_choffs; }
// set the current channel
void set_choffs(uint32_t choffs) { m_choffs = choffs; }
// prepare prior to clocking
bool prepare();
// master clocking function
void clock(uint32_t env_counter, int32_t lfo_raw_pm);
// return the current phase value
uint32_t phase() const { return m_phase >> 10; }
// compute operator volume
int32_t compute_volume(uint32_t phase, uint32_t am_offset) const;
// compute volume for the OPM noise channel
int32_t compute_noise_volume(uint32_t am_offset) const;
// key state control
void keyonoff(uint32_t on, keyon_type type);
// return a reference to our registers
RegisterType &regs() const { return m_regs; }
// simple getters for debugging
envelope_state debug_eg_state() const { return m_env_state; }
uint16_t debug_eg_attenuation() const { return m_env_attenuation; }
uint8_t debug_ssg_inverted() const { return m_ssg_inverted; }
opdata_cache &debug_cache() { return m_cache; }
private:
// start the attack phase
void start_attack(bool is_restart = false);
// start the release phase
void start_release();
// clock phases
void clock_keystate(uint32_t keystate);
void clock_ssg_eg_state();
void clock_envelope(uint32_t env_counter);
void clock_phase(int32_t lfo_raw_pm);
// return effective attenuation of the envelope
uint32_t envelope_attenuation(uint32_t am_offset) const;
// internal state
uint32_t m_choffs; // channel offset in registers
uint32_t m_opoffs; // operator offset in registers
uint32_t m_phase; // current phase value (10.10 format)
uint16_t m_env_attenuation; // computed envelope attenuation (4.6 format)
envelope_state m_env_state; // current envelope state
uint8_t m_ssg_inverted; // non-zero if the output should be inverted (bit 0)
uint8_t m_key_state; // current key state: on or off (bit 0)
uint8_t m_keyon_live; // live key on state (bit 0 = direct, bit 1 = rhythm, bit 2 = CSM)
opdata_cache m_cache; // cached values for performance
RegisterType &m_regs; // direct reference to registers
fm_engine_base<RegisterType> &m_owner; // reference to the owning engine
};
// ======================> fm_channel
// fm_channel represents an FM channel which combines the output of 2 or 4
// operators into a final result
template<class RegisterType>
class fm_channel
{
using output_data = ymfm_output<RegisterType::OUTPUTS>;
public:
// constructor
fm_channel(fm_engine_base<RegisterType> &owner, uint32_t choffs);
// save/restore
void save_restore(ymfm_saved_state &state);
// reset the channel state
void reset();
// return the channel offset
uint32_t choffs() const { return m_choffs; }
// assign operators
void assign(uint32_t index, fm_operator<RegisterType> *op)
{
assert(index < m_op.size());
m_op[index] = op;
if (op != nullptr)
op->set_choffs(m_choffs);
}
// signal key on/off to our operators
void keyonoff(uint32_t states, keyon_type type, uint32_t chnum);
// prepare prior to clocking
bool prepare();
// master clocking function
void clock(uint32_t env_counter, int32_t lfo_raw_pm);
// specific 2-operator and 4-operator output handlers
void output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const;
void output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const;
// compute the special OPL rhythm channel outputs
void output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const;
void output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const;
void output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const;
// are we a 4-operator channel or a 2-operator one?
bool is4op() const
{
if (RegisterType::DYNAMIC_OPS)
return (m_op[2] != nullptr);
return (RegisterType::OPERATORS / RegisterType::CHANNELS == 4);
}
// return a reference to our registers
RegisterType &regs() const { return m_regs; }
// simple getters for debugging
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_op[index]; }
private:
// helper to add values to the outputs based on channel enables
void add_to_output(uint32_t choffs, output_data &output, int32_t value) const
{
// create these constants to appease overzealous compilers checking array
// bounds in unreachable code (looking at you, clang)
constexpr int out0_index = 0;
constexpr int out1_index = 1 % RegisterType::OUTPUTS;
constexpr int out2_index = 2 % RegisterType::OUTPUTS;
constexpr int out3_index = 3 % RegisterType::OUTPUTS;
if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs))
output.data[out0_index] += value;
if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs))
output.data[out1_index] += value;
if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs))
output.data[out2_index] += value;
if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs))
output.data[out3_index] += value;
}
// internal state
uint32_t m_choffs; // channel offset in registers
int16_t m_feedback[2]; // feedback memory for operator 1
mutable int16_t m_feedback_in; // next input value for op 1 feedback (set in output)
std::array<fm_operator<RegisterType> *, 4> m_op; // up to 4 operators
RegisterType &m_regs; // direct reference to registers
fm_engine_base<RegisterType> &m_owner; // reference to the owning engine
};
// ======================> fm_engine_base
// fm_engine_base represents a set of operators and channels which together
// form a Yamaha FM core; chips that implement other engines (ADPCM, wavetable,
// etc) take this output and combine it with the others externally
template<class RegisterType>
class fm_engine_base : public ymfm_engine_callbacks
{
public:
// expose some constants from the registers
static constexpr uint32_t OUTPUTS = RegisterType::OUTPUTS;
static constexpr uint32_t CHANNELS = RegisterType::CHANNELS;
static constexpr uint32_t ALL_CHANNELS = RegisterType::ALL_CHANNELS;
static constexpr uint32_t OPERATORS = RegisterType::OPERATORS;
// also expose status flags for consumers that inject additional bits
static constexpr uint8_t STATUS_TIMERA = RegisterType::STATUS_TIMERA;
static constexpr uint8_t STATUS_TIMERB = RegisterType::STATUS_TIMERB;
static constexpr uint8_t STATUS_BUSY = RegisterType::STATUS_BUSY;
static constexpr uint8_t STATUS_IRQ = RegisterType::STATUS_IRQ;
// expose the correct output class
using output_data = ymfm_output<OUTPUTS>;
// constructor
fm_engine_base(ymfm_interface &intf);
// save/restore
void save_restore(ymfm_saved_state &state);
// reset the overall state
void reset();
// master clocking function
uint32_t clock(uint32_t chanmask);
// compute sum of channel outputs
void output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const;
// write to the OPN registers
void write(uint16_t regnum, uint8_t data);
// return the current status
uint8_t status() const;
// set/reset bits in the status register, updating the IRQ status
uint8_t set_reset_status(uint8_t set, uint8_t reset)
{
m_status = (m_status | set) & ~(reset | STATUS_BUSY);
m_intf.ymfm_sync_check_interrupts();
return m_status & ~m_regs.status_mask();
}
// set the IRQ mask
void set_irq_mask(uint8_t mask) { m_irq_mask = mask; m_intf.ymfm_sync_check_interrupts(); }
// return the current clock prescale
uint32_t clock_prescale() const { return m_clock_prescale; }
// set prescale factor (2/3/6)
void set_clock_prescale(uint32_t prescale) { m_clock_prescale = prescale; }
// compute sample rate
uint32_t sample_rate(uint32_t baseclock) const
{
#if (YMFM_DEBUG_LOG_WAVFILES)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
m_wavfile[chnum].set_samplerate(baseclock / (m_clock_prescale * OPERATORS));
#endif
return baseclock / (m_clock_prescale * OPERATORS);
}
// return the owning device
ymfm_interface &intf() const { return m_intf; }
// return a reference to our registers
RegisterType &regs() { return m_regs; }
// invalidate any caches
void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; }
// simple getters for debugging
fm_channel<RegisterType> *debug_channel(uint32_t index) const { return m_channel[index].get(); }
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_operator[index].get(); }
public:
// timer callback; called by the interface when a timer fires
virtual void engine_timer_expired(uint32_t tnum) override;
// check interrupts; called by the interface after synchronization
virtual void engine_check_interrupts() override;
// mode register write; called by the interface after synchronization
virtual void engine_mode_write(uint8_t data) override;
protected:
// assign the current set of operators to channels
void assign_operators();
// update the state of the given timer
void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks);
// internal state
ymfm_interface &m_intf; // reference to the system interface
uint32_t m_env_counter; // envelope counter; low 2 bits are sub-counter
uint8_t m_status; // current status register
uint8_t m_clock_prescale; // prescale factor (2/3/6)
uint8_t m_irq_mask; // mask of which bits signal IRQs
uint8_t m_irq_state; // current IRQ state
uint8_t m_timer_running[2]; // current timer running state
uint8_t m_total_clocks; // low 8 bits of the total number of clocks processed
uint32_t m_active_channels; // mask of active channels (computed by prepare)
uint32_t m_modified_channels; // mask of channels that have been modified
uint32_t m_prepare_count; // counter to do periodic prepare sweeps
RegisterType m_regs; // register accessor
std::unique_ptr<fm_channel<RegisterType>> m_channel[CHANNELS]; // channel pointers
std::unique_ptr<fm_operator<RegisterType>> m_operator[OPERATORS]; // operator pointers
#if (YMFM_DEBUG_LOG_WAVFILES)
mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging
#endif
};
}
#endif // YMFM_FM_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,175 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ymfm_misc.h"
namespace ymfm
{
//*********************************************************
// YM2149
//*********************************************************
//-------------------------------------------------
// ym2149 - constructor
//-------------------------------------------------
ym2149::ym2149(ymfm_interface &intf) :
m_address(0),
m_ssg(intf)
{
}
//-------------------------------------------------
// reset - reset the system
//-------------------------------------------------
void ym2149::reset()
{
// reset the engines
m_ssg.reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void ym2149::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_address);
m_ssg.save_restore(state);
}
//-------------------------------------------------
// read_data - read the data register
//-------------------------------------------------
uint8_t ym2149::read_data()
{
return m_ssg.read(m_address & 0x0f);
}
//-------------------------------------------------
// read - handle a read from the device
//-------------------------------------------------
uint8_t ym2149::read(uint32_t offset)
{
uint8_t result = 0xff;
switch (offset & 3) // BC2,BC1
{
case 0: // inactive
break;
case 1: // address
break;
case 2: // inactive
break;
case 3: // read
result = read_data();
break;
}
return result;
}
//-------------------------------------------------
// write_address - handle a write to the address
// register
//-------------------------------------------------
void ym2149::write_address(uint8_t data)
{
// just set the address
m_address = data;
}
//-------------------------------------------------
// write - handle a write to the register
// interface
//-------------------------------------------------
void ym2149::write_data(uint8_t data)
{
m_ssg.write(m_address & 0x0f, data);
}
//-------------------------------------------------
// write - handle a write to the register
// interface
//-------------------------------------------------
void ym2149::write(uint32_t offset, uint8_t data)
{
switch (offset & 3) // BC2,BC1
{
case 0: // address
write_address(data);
break;
case 1: // inactive
break;
case 2: // write
write_data(data);
break;
case 3: // address
write_address(data);
break;
}
}
//-------------------------------------------------
// generate - generate samples of SSG sound
//-------------------------------------------------
void ym2149::generate(output_data *output, uint32_t numsamples)
{
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
{
// clock the SSG
m_ssg.clock();
// YM2149 keeps the three SSG outputs independent
m_ssg.output(*output);
}
}
}

View file

@ -0,0 +1,93 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_MISC_H
#define YMFM_MISC_H
#pragma once
#include "ymfm.h"
#include "ymfm_adpcm.h"
#include "ymfm_ssg.h"
namespace ymfm
{
//*********************************************************
// SSG IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym2149
// ym2149 is just an SSG with no FM part, but we expose FM-like parts so that it
// integrates smoothly with everything else; they just don't do anything
class ym2149
{
public:
static constexpr uint32_t OUTPUTS = ssg_engine::OUTPUTS;
static constexpr uint32_t SSG_OUTPUTS = ssg_engine::OUTPUTS;
using output_data = ymfm_output<OUTPUTS>;
// constructor
ym2149(ymfm_interface &intf);
// configuration
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return input_clock / ssg_engine::CLOCK_DIVIDER / 8; }
// read access
uint8_t read_data();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint8_t m_address; // address register
ssg_engine m_ssg; // SSG engine
};
}
#endif // YMFM_MISC_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,902 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_OPL_H
#define YMFM_OPL_H
#pragma once
#include "ymfm.h"
#include "ymfm_adpcm.h"
#include "ymfm_fm.h"
#include "ymfm_pcm.h"
namespace ymfm
{
//*********************************************************
// REGISTER CLASSES
//*********************************************************
// ======================> opl_registers_base
//
// OPL/OPL2/OPL3/OPL4 register map:
//
// System-wide registers:
// 01 xxxxxxxx Test register
// --x----- Enable OPL compatibility mode [OPL2 only] (1 = enable)
// 02 xxxxxxxx Timer A value (4 * OPN)
// 03 xxxxxxxx Timer B value
// 04 x------- RST
// -x------ Mask timer A
// --x----- Mask timer B
// ------x- Load timer B
// -------x Load timer A
// 08 x------- CSM mode [OPL/OPL2 only]
// -x------ Note select
// BD x------- AM depth
// -x------ PM depth
// --x----- Rhythm enable
// ---x---- Bass drum key on
// ----x--- Snare drum key on
// -----x-- Tom key on
// ------x- Top cymbal key on
// -------x High hat key on
// 101 --xxxxxx Test register 2 [OPL3 only]
// 104 --x----- Channel 6 4-operator mode [OPL3 only]
// ---x---- Channel 5 4-operator mode [OPL3 only]
// ----x--- Channel 4 4-operator mode [OPL3 only]
// -----x-- Channel 3 4-operator mode [OPL3 only]
// ------x- Channel 2 4-operator mode [OPL3 only]
// -------x Channel 1 4-operator mode [OPL3 only]
// 105 -------x New [OPL3 only]
// ------x- New2 [OPL4 only]
//
// Per-channel registers (channel in address bits 0-3)
// Note that all these apply to address+100 as well on OPL3+
// A0-A8 xxxxxxxx F-number (low 8 bits)
// B0-B8 --x----- Key on
// ---xxx-- Block (octvate, 0-7)
// ------xx F-number (high two bits)
// C0-C8 x------- CHD output (to DO0 pin) [OPL3+ only]
// -x------ CHC output (to DO0 pin) [OPL3+ only]
// --x----- CHB output (mixed right, to DO2 pin) [OPL3+ only]
// ---x---- CHA output (mixed left, to DO2 pin) [OPL3+ only]
// ----xxx- Feedback level for operator 1 (0-7)
// -------x Operator connection algorithm
//
// Per-operator registers (operator in bits 0-5)
// Note that all these apply to address+100 as well on OPL3+
// 20-35 x------- AM enable
// -x------ PM enable (VIB)
// --x----- EG type
// ---x---- Key scale rate
// ----xxxx Multiple value (0-15)
// 40-55 xx------ Key scale level (0-3)
// --xxxxxx Total level (0-63)
// 60-75 xxxx---- Attack rate (0-15)
// ----xxxx Decay rate (0-15)
// 80-95 xxxx---- Sustain level (0-15)
// ----xxxx Release rate (0-15)
// E0-F5 ------xx Wave select (0-3) [OPL2 only]
// -----xxx Wave select (0-7) [OPL3+ only]
//
template<int Revision>
class opl_registers_base : public fm_registers_base
{
static constexpr bool IsOpl2 = (Revision == 2);
static constexpr bool IsOpl2Plus = (Revision >= 2);
static constexpr bool IsOpl3Plus = (Revision >= 3);
static constexpr bool IsOpl4Plus = (Revision >= 4);
public:
// constants
static constexpr uint32_t OUTPUTS = IsOpl3Plus ? 4 : 1;
static constexpr uint32_t CHANNELS = IsOpl3Plus ? 18 : 9;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
static constexpr uint32_t OPERATORS = CHANNELS * 2;
static constexpr uint32_t WAVEFORMS = IsOpl3Plus ? 8 : (IsOpl2Plus ? 4 : 1);
static constexpr uint32_t REGISTERS = IsOpl3Plus ? 0x200 : 0x100;
static constexpr uint32_t REG_MODE = 0x04;
static constexpr uint32_t DEFAULT_PRESCALE = IsOpl4Plus ? 19 : (IsOpl3Plus ? 8 : 4);
static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
static constexpr bool DYNAMIC_OPS = IsOpl3Plus;
static constexpr bool MODULATOR_DELAY = !IsOpl3Plus;
static constexpr uint8_t STATUS_TIMERA = 0x40;
static constexpr uint8_t STATUS_TIMERB = 0x20;
static constexpr uint8_t STATUS_BUSY = 0;
static constexpr uint8_t STATUS_IRQ = 0x80;
// constructor
opl_registers_base();
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
if (!IsOpl3Plus)
return chnum;
else
return (chnum % 9) + 0x100 * (chnum / 9);
}
// map operator number to register offset
static constexpr uint32_t operator_offset(uint32_t opnum)
{
assert(opnum < OPERATORS);
if (!IsOpl3Plus)
return opnum + 2 * (opnum / 6);
else
return (opnum % 18) + 2 * ((opnum % 18) / 6) + 0x100 * (opnum / 18);
}
// return an array of operator indices for each channel
struct operator_mapping { uint32_t chan[CHANNELS]; };
void operator_map(operator_mapping &dest) const;
// OPL4 apparently can read back FM registers?
uint8_t read(uint16_t index) const { return m_regdata[index]; }
// handle writes to the register array
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
// clock the noise and LFO, if present, returning LFO PM value
int32_t clock_noise_and_lfo();
// reset the LFO
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
// return the AM offset from LFO for the given channel
// on OPL this is just a fixed value
uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
// return LFO/noise states
uint32_t noise_state() const { return m_noise_lfsr >> 23; }
// caching helpers
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
// compute the phase step, given a PM value
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
// log a key-on event
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
// system-wide registers
uint32_t test() const { return byte(0x01, 0, 8); }
uint32_t waveform_enable() const { return IsOpl2 ? byte(0x01, 5, 1) : (IsOpl3Plus ? 1 : 0); }
uint32_t timer_a_value() const { return byte(0x02, 0, 8) * 4; } // 8->10 bits
uint32_t timer_b_value() const { return byte(0x03, 0, 8); }
uint32_t status_mask() const { return byte(0x04, 0, 8) & 0x78; }
uint32_t irq_reset() const { return byte(0x04, 7, 1); }
uint32_t reset_timer_b() const { return byte(0x04, 7, 1) | byte(0x04, 5, 1); }
uint32_t reset_timer_a() const { return byte(0x04, 7, 1) | byte(0x04, 6, 1); }
uint32_t enable_timer_b() const { return 1; }
uint32_t enable_timer_a() const { return 1; }
uint32_t load_timer_b() const { return byte(0x04, 1, 1); }
uint32_t load_timer_a() const { return byte(0x04, 0, 1); }
uint32_t csm() const { return IsOpl3Plus ? 0 : byte(0x08, 7, 1); }
uint32_t note_select() const { return byte(0x08, 6, 1); }
uint32_t lfo_am_depth() const { return byte(0xbd, 7, 1); }
uint32_t lfo_pm_depth() const { return byte(0xbd, 6, 1); }
uint32_t rhythm_enable() const { return byte(0xbd, 5, 1); }
uint32_t rhythm_keyon() const { return byte(0xbd, 4, 0); }
uint32_t newflag() const { return IsOpl3Plus ? byte(0x105, 0, 1) : 0; }
uint32_t new2flag() const { return IsOpl4Plus ? byte(0x105, 1, 1) : 0; }
uint32_t fourop_enable() const { return IsOpl3Plus ? byte(0x104, 0, 6) : 0; }
// per-channel registers
uint32_t ch_block_freq(uint32_t choffs) const { return word(0xb0, 0, 5, 0xa0, 0, 8, choffs); }
uint32_t ch_feedback(uint32_t choffs) const { return byte(0xc0, 1, 3, choffs); }
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xc0, 0, 1, choffs) | (IsOpl3Plus ? (8 | (byte(0xc3, 0, 1, choffs) << 1)) : 0); }
uint32_t ch_output_any(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 4) : 1; }
uint32_t ch_output_0(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 1) : 1; }
uint32_t ch_output_1(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 5, 1) : (IsOpl3Plus ? 1 : 0); }
uint32_t ch_output_2(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 6, 1) : 0; }
uint32_t ch_output_3(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 7, 1) : 0; }
// per-operator registers
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0x20, 7, 1, opoffs); }
uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return byte(0x20, 6, 1, opoffs); }
uint32_t op_eg_sustain(uint32_t opoffs) const { return byte(0x20, 5, 1, opoffs); }
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x20, 4, 1, opoffs); }
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x20, 0, 4, opoffs); }
uint32_t op_ksl(uint32_t opoffs) const { uint32_t temp = byte(0x40, 6, 2, opoffs); return bitfield(temp, 1) | (bitfield(temp, 0) << 1); }
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); }
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x60, 4, 4, opoffs); }
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 4, opoffs); }
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); }
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); }
uint32_t op_waveform(uint32_t opoffs) const { return IsOpl2Plus ? byte(0xe0, 0, newflag() ? 3 : 2, opoffs) : 0; }
protected:
// return a bitfield extracted from a byte
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
{
return bitfield(m_regdata[offset + extra_offset], start, count);
}
// return a bitfield extracted from a pair of bytes, MSBs listed first
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
{
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
}
// helper to determine if the this channel is an active rhythm channel
bool is_rhythm(uint32_t choffs) const
{
return rhythm_enable() && (choffs >= 6 && choffs <= 8);
}
// internal state
uint16_t m_lfo_am_counter; // LFO AM counter
uint16_t m_lfo_pm_counter; // LFO PM counter
uint32_t m_noise_lfsr; // noise LFSR state
uint8_t m_lfo_am; // current LFO AM value
uint8_t m_regdata[REGISTERS]; // register data
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
};
using opl_registers = opl_registers_base<1>;
using opl2_registers = opl_registers_base<2>;
using opl3_registers = opl_registers_base<3>;
using opl4_registers = opl_registers_base<4>;
// ======================> opll_registers
//
// OPLL register map:
//
// System-wide registers:
// 0E --x----- Rhythm enable
// ---x---- Bass drum key on
// ----x--- Snare drum key on
// -----x-- Tom key on
// ------x- Top cymbal key on
// -------x High hat key on
// 0F xxxxxxxx Test register
//
// Per-channel registers (channel in address bits 0-3)
// 10-18 xxxxxxxx F-number (low 8 bits)
// 20-28 --x----- Sustain on
// ---x---- Key on
// --- xxx- Block (octvate, 0-7)
// -------x F-number (high bit)
// 30-38 xxxx---- Instrument selection
// ----xxxx Volume
//
// User instrument registers (for carrier, modulator operators)
// 00-01 x------- AM enable
// -x------ PM enable (VIB)
// --x----- EG type
// ---x---- Key scale rate
// ----xxxx Multiple value (0-15)
// 02 xx------ Key scale level (carrier, 0-3)
// --xxxxxx Total level (modulator, 0-63)
// 03 xx------ Key scale level (modulator, 0-3)
// ---x---- Rectified wave (carrier)
// ----x--- Rectified wave (modulator)
// -----xxx Feedback level for operator 1 (0-7)
// 04-05 xxxx---- Attack rate (0-15)
// ----xxxx Decay rate (0-15)
// 06-07 xxxx---- Sustain level (0-15)
// ----xxxx Release rate (0-15)
//
// Internal (fake) registers:
// 40-48 xxxxxxxx Current instrument base address
// 4E-5F xxxxxxxx Current instrument base address + operator slot (0/1)
// 70-FF xxxxxxxx Data for instruments (1-16 plus 3 drums)
//
class opll_registers : public fm_registers_base
{
public:
static constexpr uint32_t OUTPUTS = 2;
static constexpr uint32_t CHANNELS = 9;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
static constexpr uint32_t OPERATORS = CHANNELS * 2;
static constexpr uint32_t WAVEFORMS = 2;
static constexpr uint32_t REGISTERS = 0x40;
static constexpr uint32_t REG_MODE = 0x3f;
static constexpr uint32_t DEFAULT_PRESCALE = 4;
static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
static constexpr uint32_t CSM_TRIGGER_MASK = 0;
static constexpr bool EG_HAS_DEPRESS = true;
static constexpr bool MODULATOR_DELAY = true;
static constexpr uint8_t STATUS_TIMERA = 0;
static constexpr uint8_t STATUS_TIMERB = 0;
static constexpr uint8_t STATUS_BUSY = 0;
static constexpr uint8_t STATUS_IRQ = 0;
// OPLL-specific constants
static constexpr uint32_t INSTDATA_SIZE = 0x90;
// constructor
opll_registers();
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
return chnum;
}
// map operator number to register offset
static constexpr uint32_t operator_offset(uint32_t opnum)
{
assert(opnum < OPERATORS);
return opnum;
}
// return an array of operator indices for each channel
struct operator_mapping { uint32_t chan[CHANNELS]; };
void operator_map(operator_mapping &dest) const;
// read a register value
uint8_t read(uint16_t index) const { return m_regdata[index]; }
// handle writes to the register array
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
// clock the noise and LFO, if present, returning LFO PM value
int32_t clock_noise_and_lfo();
// reset the LFO
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
// return the AM offset from LFO for the given channel
// on OPL this is just a fixed value
uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
// return LFO/noise states
uint32_t noise_state() const { return m_noise_lfsr >> 23; }
// caching helpers
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
// compute the phase step, given a PM value
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
// log a key-on event
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
// set the instrument data
void set_instrument_data(uint8_t const *data)
{
std::copy_n(data, INSTDATA_SIZE, &m_instdata[0]);
}
// system-wide registers
uint32_t rhythm_enable() const { return byte(0x0e, 5, 1); }
uint32_t rhythm_keyon() const { return byte(0x0e, 4, 0); }
uint32_t test() const { return byte(0x0f, 0, 8); }
uint32_t waveform_enable() const { return 1; }
uint32_t timer_a_value() const { return 0; }
uint32_t timer_b_value() const { return 0; }
uint32_t status_mask() const { return 0; }
uint32_t irq_reset() const { return 0; }
uint32_t reset_timer_b() const { return 0; }
uint32_t reset_timer_a() const { return 0; }
uint32_t enable_timer_b() const { return 0; }
uint32_t enable_timer_a() const { return 0; }
uint32_t load_timer_b() const { return 0; }
uint32_t load_timer_a() const { return 0; }
uint32_t csm() const { return 0; }
// per-channel registers
uint32_t ch_block_freq(uint32_t choffs) const { return word(0x20, 0, 4, 0x10, 0, 8, choffs); }
uint32_t ch_sustain(uint32_t choffs) const { return byte(0x20, 5, 1, choffs); }
uint32_t ch_total_level(uint32_t choffs) const { return instchbyte(0x02, 0, 6, choffs); }
uint32_t ch_feedback(uint32_t choffs) const { return instchbyte(0x03, 0, 3, choffs); }
uint32_t ch_algorithm(uint32_t choffs) const { return 0; }
uint32_t ch_instrument(uint32_t choffs) const { return byte(0x30, 4, 4, choffs); }
uint32_t ch_output_any(uint32_t choffs) const { return 1; }
uint32_t ch_output_0(uint32_t choffs) const { return !is_rhythm(choffs); }
uint32_t ch_output_1(uint32_t choffs) const { return is_rhythm(choffs); }
uint32_t ch_output_2(uint32_t choffs) const { return 0; }
uint32_t ch_output_3(uint32_t choffs) const { return 0; }
// per-operator registers
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return instopbyte(0x00, 7, 1, opoffs); }
uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return instopbyte(0x00, 6, 1, opoffs); }
uint32_t op_eg_sustain(uint32_t opoffs) const { return instopbyte(0x00, 5, 1, opoffs); }
uint32_t op_ksr(uint32_t opoffs) const { return instopbyte(0x00, 4, 1, opoffs); }
uint32_t op_multiple(uint32_t opoffs) const { return instopbyte(0x00, 0, 4, opoffs); }
uint32_t op_ksl(uint32_t opoffs) const { return instopbyte(0x02, 6, 2, opoffs); }
uint32_t op_waveform(uint32_t opoffs) const { return instchbyte(0x03, 3 + bitfield(opoffs, 0), 1, opoffs >> 1); }
uint32_t op_attack_rate(uint32_t opoffs) const { return instopbyte(0x04, 4, 4, opoffs); }
uint32_t op_decay_rate(uint32_t opoffs) const { return instopbyte(0x04, 0, 4, opoffs); }
uint32_t op_sustain_level(uint32_t opoffs) const { return instopbyte(0x06, 4, 4, opoffs); }
uint32_t op_release_rate(uint32_t opoffs) const { return instopbyte(0x06, 0, 4, opoffs); }
uint32_t op_volume(uint32_t opoffs) const { return byte(0x30, 4 * bitfield(~opoffs, 0), 4, opoffs >> 1); }
private:
// return a bitfield extracted from a byte
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
{
return bitfield(m_regdata[offset + extra_offset], start, count);
}
// return a bitfield extracted from a pair of bytes, MSBs listed first
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
{
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
}
// helpers to read from instrument channel/operator data
uint32_t instchbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t choffs) const { return bitfield(m_chinst[choffs][offset], start, count); }
uint32_t instopbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t opoffs) const { return bitfield(m_opinst[opoffs][offset], start, count); }
// helper to determine if the this channel is an active rhythm channel
bool is_rhythm(uint32_t choffs) const
{
return rhythm_enable() && choffs >= 6;
}
// internal state
uint16_t m_lfo_am_counter; // LFO AM counter
uint16_t m_lfo_pm_counter; // LFO PM counter
uint32_t m_noise_lfsr; // noise LFSR state
uint8_t m_lfo_am; // current LFO AM value
uint8_t const *m_chinst[CHANNELS]; // pointer to instrument data for each channel
uint8_t const *m_opinst[OPERATORS]; // pointer to instrument data for each operator
uint8_t m_regdata[REGISTERS]; // register data
uint8_t m_instdata[INSTDATA_SIZE]; // instrument data
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
};
//*********************************************************
// OPL IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym3526
class ym3526
{
public:
using fm_engine = fm_engine_base<opl_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
// constructor
ym3526(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint8_t m_address; // address register
fm_engine m_fm; // core FM engine
};
// ======================> y8950
class y8950
{
public:
using fm_engine = fm_engine_base<opl_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x01;
static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08;
static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x10;
static constexpr uint8_t ALL_IRQS = STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_EOS | fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB;
// constructor
y8950(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint8_t m_address; // address register
uint8_t m_io_ddr; // data direction register for I/O
fm_engine m_fm; // core FM engine
adpcm_b_engine m_adpcm_b; // ADPCM-B engine
};
//*********************************************************
// OPL2 IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym3812
class ym3812
{
public:
using fm_engine = fm_engine_base<opl2_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
// constructor
ym3812(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint8_t m_address; // address register
fm_engine m_fm; // core FM engine
};
//*********************************************************
// OPL3 IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ymf262
class ymf262
{
public:
using fm_engine = fm_engine_base<opl3_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
// constructor
ymf262(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint16_t m_address; // address register
fm_engine m_fm; // core FM engine
};
// ======================> ymf289b
class ymf289b
{
static constexpr uint8_t STATUS_BUSY_FLAGS = 0x05;
public:
using fm_engine = fm_engine_base<opl3_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = 2;
// constructor
ymf289b(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal helpers
bool ymf289b_mode() { return ((m_fm.regs().read(0x105) & 0x04) != 0); }
// internal state
uint16_t m_address; // address register
fm_engine m_fm; // core FM engine
};
//*********************************************************
// OPL4 IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ymf278b
class ymf278b
{
// Using the nominal datasheet frequency of 33.868MHz, the output of the
// chip will be clock/768 = 44.1kHz. However, the FM engine is clocked
// internally at clock/(19*36), or 49.515kHz, so the FM output needs to
// be downsampled. We treat this as needing to clock the FM engine an
// extra tick every few samples. The exact ratio is 768/(19*36) or
// 768/684 = 192/171. So if we always clock the FM once, we'll have
// 192/171 - 1 = 21/171 left. Thus we count 21 for each sample and when
// it gets above 171, we tick an extra time.
static constexpr uint32_t FM_EXTRA_SAMPLE_THRESH = 171;
static constexpr uint32_t FM_EXTRA_SAMPLE_STEP = 192 - FM_EXTRA_SAMPLE_THRESH;
public:
using fm_engine = fm_engine_base<opl4_registers>;
static constexpr uint32_t OUTPUTS = 6;
using output_data = ymfm_output<OUTPUTS>;
static constexpr uint8_t STATUS_BUSY = 0x01;
static constexpr uint8_t STATUS_LD = 0x02;
// constructor
ymf278b(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return input_clock / 768; }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data_pcm();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write_address_pcm(uint8_t data);
void write_data_pcm(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint16_t m_address; // address register
uint32_t m_fm_pos; // FM resampling position
uint32_t m_load_remaining; // how many more samples until LD flag clears
bool m_next_status_id; // flag to track which status ID to return
fm_engine m_fm; // core FM engine
pcm_engine m_pcm; // core PCM engine
};
//*********************************************************
// OPLL IMPLEMENTATION CLASSES
//*********************************************************
// ======================> opll_base
class opll_base
{
public:
using fm_engine = fm_engine_base<opll_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
// constructor
opll_base(ymfm_interface &intf, uint8_t const *data);
// configuration
void set_instrument_data(uint8_t const *data) { m_fm.regs().set_instrument_data(data); }
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access -- doesn't really have any, but provide these for consistency
uint8_t read_status() { return 0x00; }
uint8_t read(uint32_t offset) { return 0x00; }
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint8_t m_address; // address register
fm_engine m_fm; // core FM engine
};
// ======================> ym2413
class ym2413 : public opll_base
{
public:
// constructor
ym2413(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
private:
// internal state
static uint8_t const s_default_instruments[];
};
// ======================> ym2413
class ym2423 : public opll_base
{
public:
// constructor
ym2423(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
private:
// internal state
static uint8_t const s_default_instruments[];
};
// ======================> ymf281
class ymf281 : public opll_base
{
public:
// constructor
ymf281(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
private:
// internal state
static uint8_t const s_default_instruments[];
};
// ======================> ds1001
class ds1001 : public opll_base
{
public:
// constructor
ds1001(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
private:
// internal state
static uint8_t const s_default_instruments[];
};
}
#endif // YMFM_OPL_H

View file

@ -0,0 +1,539 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ymfm_opm.h"
#include "ymfm_fm.ipp"
namespace ymfm
{
//*********************************************************
// OPM REGISTERS
//*********************************************************
//-------------------------------------------------
// opm_registers - constructor
//-------------------------------------------------
opm_registers::opm_registers() :
m_lfo_counter(0),
m_noise_lfsr(1),
m_noise_counter(0),
m_noise_state(0),
m_noise_lfo(0),
m_lfo_am(0)
{
// create the waveforms
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
// create the LFO waveforms; AM in the low 8 bits, PM in the upper 8
// waveforms are adjusted to match the pictures in the application manual
for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++)
{
// waveform 0 is a sawtooth
uint8_t am = index ^ 0xff;
uint8_t pm = index;
m_lfo_waveform[0][index] = am | (pm << 8);
// waveform 1 is a square wave
am = bitfield(index, 7) ? 0 : 0xff;
pm = am ^ 0x80;
m_lfo_waveform[1][index] = am | (pm << 8);
// waveform 2 is a triangle wave
am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1);
pm = bitfield(index, 6) ? am : ~am;
m_lfo_waveform[2][index] = am | (pm << 8);
// waveform 3 is noise; it is filled in dynamically
m_lfo_waveform[3][index] = 0;
}
}
//-------------------------------------------------
// reset - reset to initial state
//-------------------------------------------------
void opm_registers::reset()
{
std::fill_n(&m_regdata[0], REGISTERS, 0);
// enable output on both channels by default
m_regdata[0x20] = m_regdata[0x21] = m_regdata[0x22] = m_regdata[0x23] = 0xc0;
m_regdata[0x24] = m_regdata[0x25] = m_regdata[0x26] = m_regdata[0x27] = 0xc0;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void opm_registers::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_lfo_counter);
state.save_restore(m_lfo_am);
state.save_restore(m_noise_lfsr);
state.save_restore(m_noise_counter);
state.save_restore(m_noise_state);
state.save_restore(m_noise_lfo);
state.save_restore(m_regdata);
}
//-------------------------------------------------
// operator_map - return an array of operator
// indices for each channel; for OPM this is fixed
//-------------------------------------------------
void opm_registers::operator_map(operator_mapping &dest) const
{
// Note that the channel index order is 0,2,1,3, so we bitswap the index.
//
// This is because the order in the map is:
// carrier 1, carrier 2, modulator 1, modulator 2
//
// But when wiring up the connections, the more natural order is:
// carrier 1, modulator 1, carrier 2, modulator 2
static const operator_mapping s_fixed_map =
{ {
operator_list( 0, 16, 8, 24 ), // Channel 0 operators
operator_list( 1, 17, 9, 25 ), // Channel 1 operators
operator_list( 2, 18, 10, 26 ), // Channel 2 operators
operator_list( 3, 19, 11, 27 ), // Channel 3 operators
operator_list( 4, 20, 12, 28 ), // Channel 4 operators
operator_list( 5, 21, 13, 29 ), // Channel 5 operators
operator_list( 6, 22, 14, 30 ), // Channel 6 operators
operator_list( 7, 23, 15, 31 ), // Channel 7 operators
} };
dest = s_fixed_map;
}
//-------------------------------------------------
// write - handle writes to the register array
//-------------------------------------------------
bool opm_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
{
assert(index < REGISTERS);
// LFO AM/PM depth are written to the same register (0x19);
// redirect the PM depth to an unused neighbor (0x1a)
if (index == 0x19)
m_regdata[index + bitfield(data, 7)] = data;
else if (index != 0x1a)
m_regdata[index] = data;
// handle writes to the key on index
if (index == 0x08)
{
channel = bitfield(data, 0, 3);
opmask = bitfield(data, 3, 4);
return true;
}
return false;
}
//-------------------------------------------------
// clock_noise_and_lfo - clock the noise and LFO,
// handling clock division, depth, and waveform
// computations
//-------------------------------------------------
int32_t opm_registers::clock_noise_and_lfo()
{
// base noise frequency is measured at 2x 1/2 FM frequency; this
// means each tick counts as two steps against the noise counter
uint32_t freq = noise_frequency();
for (int rep = 0; rep < 2; rep++)
{
// evidence seems to suggest the LFSR is clocked continually and just
// sampled at the noise frequency for output purposes; note that the
// low 8 bits are the most recent 8 bits of history while bits 8-24
// contain the 17 bit LFSR state
m_noise_lfsr <<= 1;
m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1;
// compare against the frequency and latch when we exceed it
if (m_noise_counter++ >= freq)
{
m_noise_counter = 0;
m_noise_state = bitfield(m_noise_lfsr, 17);
}
}
// treat the rate as a 4.4 floating-point step value with implied
// leading 1; this matches exactly the frequencies in the application
// manual, though it might not be implemented exactly this way on chip
uint32_t rate = lfo_rate();
m_lfo_counter += (0x10 | bitfield(rate, 0, 4)) << bitfield(rate, 4, 4);
// bit 1 of the test register is officially undocumented but has been
// discovered to hold the LFO in reset while active
if (lfo_reset())
m_lfo_counter = 0;
// now pull out the non-fractional LFO value
uint32_t lfo = bitfield(m_lfo_counter, 22, 8);
// fill in the noise entry 1 ahead of our current position; this
// ensures the current value remains stable for a full LFO clock
// and effectively latches the running value when the LFO advances
uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8);
m_lfo_waveform[3][(lfo + 1) & 0xff] = lfo_noise | (lfo_noise << 8);
// fetch the AM/PM values based on the waveform; AM is unsigned and
// encoded in the low 8 bits, while PM signed and encoded in the upper
// 8 bits
int32_t ampm = m_lfo_waveform[lfo_waveform()][lfo];
// apply depth to the AM value and store for later
m_lfo_am = ((ampm & 0xff) * lfo_am_depth()) >> 7;
// apply depth to the PM value and return it
return ((ampm >> 8) * int32_t(lfo_pm_depth())) >> 7;
}
//-------------------------------------------------
// lfo_am_offset - return the AM offset from LFO
// for the given channel
//-------------------------------------------------
uint32_t opm_registers::lfo_am_offset(uint32_t choffs) const
{
// OPM maps AM quite differently from OPN
// shift value for AM sensitivity is [*, 0, 1, 2],
// mapping to values of [0, 23.9, 47.8, and 95.6dB]
uint32_t am_sensitivity = ch_lfo_am_sens(choffs);
if (am_sensitivity == 0)
return 0;
// QUESTION: see OPN note below for the dB range mapping; it applies
// here as well
// raw LFO AM value on OPM is 0-FF, which is already a factor of 2
// larger than the OPN below, putting our staring point at 2x theirs;
// this works out since our minimum is 2x their maximum
return m_lfo_am << (am_sensitivity - 1);
}
//-------------------------------------------------
// cache_operator_data - fill the operator cache
// with prefetched data
//-------------------------------------------------
void opm_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
{
// set up the easy stuff
cache.waveform = &m_waveform[0][0];
// get frequency from the channel
uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
// compute the keycode: block_freq is:
//
// BBBCCCCFFFFFF
// ^^^^^
//
// the 5-bit keycode is just the top 5 bits (block + top 2 bits
// of the key code)
uint32_t keycode = bitfield(block_freq, 8, 5);
// detune adjustment
cache.detune = detune_adjustment(op_detune(opoffs), keycode);
// multiple value, as an x.1 value (0 means 0.5)
cache.multiple = op_multiple(opoffs) * 2;
if (cache.multiple == 0)
cache.multiple = 1;
// phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
// block_freq, detune, and multiple, so compute it after we've done those
if (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0)
cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
else
cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
// total level, scaled by 8
cache.total_level = op_total_level(opoffs) << 3;
// 4-bit sustain level, but 15 means 31 so effectively 5 bits
cache.eg_sustain = op_sustain_level(opoffs);
cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
cache.eg_sustain <<= 5;
// determine KSR adjustment for enevlope rates
uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
}
//-------------------------------------------------
// compute_phase_step - compute the phase step
//-------------------------------------------------
uint32_t opm_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
{
// OPM logic is rather unique here, due to extra detune
// and the use of key codes (not to be confused with keycode)
// start with coarse detune delta; table uses cents value from
// manual, converted into 1/64ths
static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 };
int32_t delta = s_detune2_delta[op_detune2(opoffs)];
// add in the PM delta
uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
if (pm_sensitivity != 0)
{
// raw PM value is -127..128 which is +/- 200 cents
// manual gives these magnitudes in cents:
// 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700
// this roughly corresponds to shifting the 200-cent value:
// 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2
if (pm_sensitivity < 6)
delta += lfo_raw_pm >> (6 - pm_sensitivity);
else
delta += uint32_t(lfo_raw_pm) << (pm_sensitivity - 5);
}
// apply delta and convert to a frequency number
uint32_t phase_step = opm_key_code_to_phase_step(cache.block_freq, delta);
// apply detune based on the keycode
phase_step += cache.detune;
// apply frequency multiplier (which is cached as an x.1 value)
return (phase_step * cache.multiple) >> 1;
}
//-------------------------------------------------
// log_keyon - log a key-on event
//-------------------------------------------------
std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
{
uint32_t chnum = choffs;
uint32_t opnum = opoffs;
char buffer[256];
int end = 0;
end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
chnum, opnum,
ch_block_freq(choffs),
op_detune2(opoffs),
op_detune(opoffs),
ch_feedback(choffs),
ch_algorithm(choffs),
op_multiple(opoffs),
op_total_level(opoffs),
op_ksr(opoffs),
op_attack_rate(opoffs),
op_decay_rate(opoffs),
op_sustain_rate(opoffs),
op_release_rate(opoffs),
op_sustain_level(opoffs),
ch_output_0(choffs) ? 'L' : '-',
ch_output_1(choffs) ? 'R' : '-');
bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
if (am)
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0);
if (pm)
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
if (am || pm)
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
if (noise_enable() && opoffs == 31)
end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1");
return buffer;
}
//*********************************************************
// YM2151
//*********************************************************
//-------------------------------------------------
// ym2151 - constructor
//-------------------------------------------------
ym2151::ym2151(ymfm_interface &intf, opm_variant variant) :
m_variant(variant),
m_address(0),
m_fm(intf)
{
}
//-------------------------------------------------
// reset - reset the system
//-------------------------------------------------
void ym2151::reset()
{
// reset the engines
m_fm.reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void ym2151::save_restore(ymfm_saved_state &state)
{
m_fm.save_restore(state);
state.save_restore(m_address);
}
//-------------------------------------------------
// read_status - read the status register
//-------------------------------------------------
uint8_t ym2151::read_status()
{
uint8_t result = m_fm.status();
if (m_fm.intf().ymfm_is_busy())
result |= fm_engine::STATUS_BUSY;
return result;
}
//-------------------------------------------------
// read - handle a read from the device
//-------------------------------------------------
uint8_t ym2151::read(uint32_t offset)
{
uint8_t result = 0xff;
switch (offset & 1)
{
case 0: // data port (unused)
debug::log_unexpected_read_write("Unexpected read from YM2151 offset %d\n", offset & 3);
break;
case 1: // status port, YM2203 compatible
result = read_status();
break;
}
return result;
}
//-------------------------------------------------
// write_address - handle a write to the address
// register
//-------------------------------------------------
void ym2151::write_address(uint8_t data)
{
// just set the address
m_address = data;
}
//-------------------------------------------------
// write - handle a write to the register
// interface
//-------------------------------------------------
void ym2151::write_data(uint8_t data)
{
// write the FM register
m_fm.write(m_address, data);
// special cases
if (m_address == 0x1b)
{
// writes to register 0x1B send the upper 2 bits to the output lines
m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6);
}
// mark busy for a bit
m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
}
//-------------------------------------------------
// write - handle a write to the register
// interface
//-------------------------------------------------
void ym2151::write(uint32_t offset, uint8_t data)
{
switch (offset & 1)
{
case 0: // address port
write_address(data);
break;
case 1: // data port
write_data(data);
break;
}
}
//-------------------------------------------------
// generate - generate one sample of sound
//-------------------------------------------------
void ym2151::generate(output_data *output, uint32_t numsamples)
{
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
{
// clock the system
m_fm.clock(fm_engine::ALL_CHANNELS);
// update the FM content; OPM is full 14-bit with no intermediate clipping
m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
// YM2151 uses an external DAC (YM3012) with mantissa/exponent format
// convert to 10.3 floating point value and back to simulate truncation
output->roundtrip_fp();
}
}
}

View file

@ -0,0 +1,322 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_OPM_H
#define YMFM_OPM_H
#pragma once
#include "ymfm.h"
#include "ymfm_fm.h"
namespace ymfm
{
//*********************************************************
// REGISTER CLASSES
//*********************************************************
// ======================> opm_registers
//
// OPM register map:
//
// System-wide registers:
// 01 xxxxxx-x Test register
// ------x- LFO reset
// 08 -x------ Key on/off operator 4
// --x----- Key on/off operator 3
// ---x---- Key on/off operator 2
// ----x--- Key on/off operator 1
// -----xxx Channel select
// 0F x------- Noise enable
// ---xxxxx Noise frequency
// 10 xxxxxxxx Timer A value (upper 8 bits)
// 11 ------xx Timer A value (lower 2 bits)
// 12 xxxxxxxx Timer B value
// 14 x------- CSM mode
// --x----- Reset timer B
// ---x---- Reset timer A
// ----x--- Enable timer B
// -----x-- Enable timer A
// ------x- Load timer B
// -------x Load timer A
// 18 xxxxxxxx LFO frequency
// 19 0xxxxxxx AM LFO depth
// 1xxxxxxx PM LFO depth
// 1B xx------ CT (2 output data lines)
// ------xx LFO waveform
//
// Per-channel registers (channel in address bits 0-2)
// 20-27 x------- Pan right
// -x------ Pan left
// --xxx--- Feedback level for operator 1 (0-7)
// -----xxx Operator connection algorithm (0-7)
// 28-2F -xxxxxxx Key code
// 30-37 xxxxxx-- Key fraction
// 38-3F -xxx---- LFO PM sensitivity
// ------xx LFO AM shift
//
// Per-operator registers (channel in address bits 0-2, operator in bits 3-4)
// 40-5F -xxx---- Detune value (0-7)
// ----xxxx Multiple value (0-15)
// 60-7F -xxxxxxx Total level (0-127)
// 80-9F xx------ Key scale rate (0-3)
// ---xxxxx Attack rate (0-31)
// A0-BF x------- LFO AM enable
// ---xxxxx Decay rate (0-31)
// C0-DF xx------ Detune 2 value (0-3)
// ---xxxxx Sustain rate (0-31)
// E0-FF xxxx---- Sustain level (0-15)
// ----xxxx Release rate (0-15)
//
// Internal (fake) registers:
// 1A -xxxxxxx PM depth
//
class opm_registers : public fm_registers_base
{
// LFO waveforms are 256 entries long
static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256;
public:
// constants
static constexpr uint32_t OUTPUTS = 2;
static constexpr uint32_t CHANNELS = 8;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
static constexpr uint32_t OPERATORS = CHANNELS * 4;
static constexpr uint32_t WAVEFORMS = 1;
static constexpr uint32_t REGISTERS = 0x100;
static constexpr uint32_t DEFAULT_PRESCALE = 2;
static constexpr uint32_t EG_CLOCK_DIVIDER = 3;
static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
static constexpr uint32_t REG_MODE = 0x14;
static constexpr uint8_t STATUS_TIMERA = 0x01;
static constexpr uint8_t STATUS_TIMERB = 0x02;
static constexpr uint8_t STATUS_BUSY = 0x80;
static constexpr uint8_t STATUS_IRQ = 0;
// constructor
opm_registers();
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
return chnum;
}
// map operator number to register offset
static constexpr uint32_t operator_offset(uint32_t opnum)
{
assert(opnum < OPERATORS);
return opnum;
}
// return an array of operator indices for each channel
struct operator_mapping { uint32_t chan[CHANNELS]; };
void operator_map(operator_mapping &dest) const;
// handle writes to the register array
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
// clock the noise and LFO, if present, returning LFO PM value
int32_t clock_noise_and_lfo();
// return the AM offset from LFO for the given channel
uint32_t lfo_am_offset(uint32_t choffs) const;
// return the current noise state, gated by the noise clock
uint32_t noise_state() const { return m_noise_state; }
// caching helpers
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
// compute the phase step, given a PM value
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
// log a key-on event
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
// system-wide registers
uint32_t test() const { return byte(0x01, 0, 8); }
uint32_t lfo_reset() const { return byte(0x01, 1, 1); }
uint32_t noise_frequency() const { return byte(0x0f, 0, 5) ^ 0x1f; }
uint32_t noise_enable() const { return byte(0x0f, 7, 1); }
uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); }
uint32_t timer_b_value() const { return byte(0x12, 0, 8); }
uint32_t csm() const { return byte(0x14, 7, 1); }
uint32_t reset_timer_b() const { return byte(0x14, 5, 1); }
uint32_t reset_timer_a() const { return byte(0x14, 4, 1); }
uint32_t enable_timer_b() const { return byte(0x14, 3, 1); }
uint32_t enable_timer_a() const { return byte(0x14, 2, 1); }
uint32_t load_timer_b() const { return byte(0x14, 1, 1); }
uint32_t load_timer_a() const { return byte(0x14, 0, 1); }
uint32_t lfo_rate() const { return byte(0x18, 0, 8); }
uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); }
uint32_t lfo_pm_depth() const { return byte(0x1a, 0, 7); }
uint32_t output_bits() const { return byte(0x1b, 6, 2); }
uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); }
// per-channel registers
uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 6, 2, choffs); }
uint32_t ch_output_0(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); }
uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs); }
uint32_t ch_output_2(uint32_t choffs) const { return 0; }
uint32_t ch_output_3(uint32_t choffs) const { return 0; }
uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); }
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); }
uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); }
uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); }
uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); }
// per-operator registers
uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); }
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); }
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); }
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); }
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); }
uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); }
uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); }
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); }
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); }
protected:
// return a bitfield extracted from a byte
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
{
return bitfield(m_regdata[offset + extra_offset], start, count);
}
// return a bitfield extracted from a pair of bytes, MSBs listed first
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
{
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
}
// internal state
uint32_t m_lfo_counter; // LFO counter
uint32_t m_noise_lfsr; // noise LFSR state
uint8_t m_noise_counter; // noise counter
uint8_t m_noise_state; // latched noise state
uint8_t m_noise_lfo; // latched LFO noise value
uint8_t m_lfo_am; // current LFO AM value
uint8_t m_regdata[REGISTERS]; // register data
int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
};
//*********************************************************
// OPM IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym2151
class ym2151
{
public:
using fm_engine = fm_engine_base<opm_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
// constructor
ym2151(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2151) { }
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// variants
enum opm_variant
{
VARIANT_YM2151,
VARIANT_YM2164
};
// internal constructor
ym2151(ymfm_interface &intf, opm_variant variant);
// internal state
opm_variant m_variant; // chip variant
uint8_t m_address; // address register
fm_engine m_fm; // core FM engine
};
//*********************************************************
// OPP IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym2164
// the YM2164 is almost 100% functionally identical to the YM2151, except
// it apparently has some mystery registers in the 00-07 range, and timer
// B's frequency is half that of the 2151
class ym2164 : public ym2151
{
public:
// constructor
ym2164(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2164) { }
};
}
#endif // YMFM_OPM_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,802 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_OPN_H
#define YMFM_OPN_H
#pragma once
#include "ymfm.h"
#include "ymfm_adpcm.h"
#include "ymfm_fm.h"
#include "ymfm_ssg.h"
namespace ymfm
{
//*********************************************************
// REGISTER CLASSES
//*********************************************************
// ======================> opn_registers_base
//
// OPN register map:
//
// System-wide registers:
// 21 xxxxxxxx Test register
// 22 ----x--- LFO enable [OPNA+ only]
// -----xxx LFO rate [OPNA+ only]
// 24 xxxxxxxx Timer A value (upper 8 bits)
// 25 ------xx Timer A value (lower 2 bits)
// 26 xxxxxxxx Timer B value
// 27 xx------ CSM/Multi-frequency mode for channel #2
// --x----- Reset timer B
// ---x---- Reset timer A
// ----x--- Enable timer B
// -----x-- Enable timer A
// ------x- Load timer B
// -------x Load timer A
// 28 x------- Key on/off operator 4
// -x------ Key on/off operator 3
// --x----- Key on/off operator 2
// ---x---- Key on/off operator 1
// ------xx Channel select
//
// Per-channel registers (channel in address bits 0-1)
// Note that all these apply to address+100 as well on OPNA+
// A0-A3 xxxxxxxx Frequency number lower 8 bits
// A4-A7 --xxx--- Block (0-7)
// -----xxx Frequency number upper 3 bits
// B0-B3 --xxx--- Feedback level for operator 1 (0-7)
// -----xxx Operator connection algorithm (0-7)
// B4-B7 x------- Pan left [OPNA]
// -x------ Pan right [OPNA]
// --xx---- LFO AM shift (0-3) [OPNA+ only]
// -----xxx LFO PM depth (0-7) [OPNA+ only]
//
// Per-operator registers (channel in address bits 0-1, operator in bits 2-3)
// Note that all these apply to address+100 as well on OPNA+
// 30-3F -xxx---- Detune value (0-7)
// ----xxxx Multiple value (0-15)
// 40-4F -xxxxxxx Total level (0-127)
// 50-5F xx------ Key scale rate (0-3)
// ---xxxxx Attack rate (0-31)
// 60-6F x------- LFO AM enable [OPNA]
// ---xxxxx Decay rate (0-31)
// 70-7F ---xxxxx Sustain rate (0-31)
// 80-8F xxxx---- Sustain level (0-15)
// ----xxxx Release rate (0-15)
// 90-9F ----x--- SSG-EG enable
// -----xxx SSG-EG envelope (0-7)
//
// Special multi-frequency registers (channel implicitly #2; operator in address bits 0-1)
// A8-AB xxxxxxxx Frequency number lower 8 bits
// AC-AF --xxx--- Block (0-7)
// -----xxx Frequency number upper 3 bits
//
// Internal (fake) registers:
// B8-BB --xxxxxx Latched frequency number upper bits (from A4-A7)
// BC-BF --xxxxxx Latched frequency number upper bits (from AC-AF)
//
template<bool IsOpnA>
class opn_registers_base : public fm_registers_base
{
public:
// constants
static constexpr uint32_t OUTPUTS = IsOpnA ? 2 : 1;
static constexpr uint32_t CHANNELS = IsOpnA ? 6 : 3;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
static constexpr uint32_t OPERATORS = CHANNELS * 4;
static constexpr uint32_t WAVEFORMS = 1;
static constexpr uint32_t REGISTERS = IsOpnA ? 0x200 : 0x100;
static constexpr uint32_t REG_MODE = 0x27;
static constexpr uint32_t DEFAULT_PRESCALE = 6;
static constexpr uint32_t EG_CLOCK_DIVIDER = 3;
static constexpr bool EG_HAS_SSG = true;
static constexpr bool MODULATOR_DELAY = false;
static constexpr uint32_t CSM_TRIGGER_MASK = 1 << 2;
static constexpr uint8_t STATUS_TIMERA = 0x01;
static constexpr uint8_t STATUS_TIMERB = 0x02;
static constexpr uint8_t STATUS_BUSY = 0x80;
static constexpr uint8_t STATUS_IRQ = 0;
// constructor
opn_registers_base();
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
if (!IsOpnA)
return chnum;
else
return (chnum % 3) + 0x100 * (chnum / 3);
}
// map operator number to register offset
static constexpr uint32_t operator_offset(uint32_t opnum)
{
assert(opnum < OPERATORS);
if (!IsOpnA)
return opnum + opnum / 3;
else
return (opnum % 12) + ((opnum % 12) / 3) + 0x100 * (opnum / 12);
}
// return an array of operator indices for each channel
struct operator_mapping { uint32_t chan[CHANNELS]; };
void operator_map(operator_mapping &dest) const;
// read a register value
uint8_t read(uint16_t index) const { return m_regdata[index]; }
// handle writes to the register array
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
// clock the noise and LFO, if present, returning LFO PM value
int32_t clock_noise_and_lfo();
// reset the LFO
void reset_lfo() { m_lfo_counter = 0; }
// return the AM offset from LFO for the given channel
uint32_t lfo_am_offset(uint32_t choffs) const;
// return LFO/noise states
uint32_t noise_state() const { return 0; }
// caching helpers
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
// compute the phase step, given a PM value
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
// log a key-on event
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
// system-wide registers
uint32_t test() const { return byte(0x21, 0, 8); }
uint32_t lfo_enable() const { return IsOpnA ? byte(0x22, 3, 1) : 0; }
uint32_t lfo_rate() const { return IsOpnA ? byte(0x22, 0, 3) : 0; }
uint32_t timer_a_value() const { return word(0x24, 0, 8, 0x25, 0, 2); }
uint32_t timer_b_value() const { return byte(0x26, 0, 8); }
uint32_t csm() const { return (byte(0x27, 6, 2) == 2); }
uint32_t multi_freq() const { return (byte(0x27, 6, 2) != 0); }
uint32_t reset_timer_b() const { return byte(0x27, 5, 1); }
uint32_t reset_timer_a() const { return byte(0x27, 4, 1); }
uint32_t enable_timer_b() const { return byte(0x27, 3, 1); }
uint32_t enable_timer_a() const { return byte(0x27, 2, 1); }
uint32_t load_timer_b() const { return byte(0x27, 1, 1); }
uint32_t load_timer_a() const { return byte(0x27, 0, 1); }
uint32_t multi_block_freq(uint32_t num) const { return word(0xac, 0, 6, 0xa8, 0, 8, num); }
// per-channel registers
uint32_t ch_block_freq(uint32_t choffs) const { return word(0xa4, 0, 6, 0xa0, 0, 8, choffs); }
uint32_t ch_feedback(uint32_t choffs) const { return byte(0xb0, 3, 3, choffs); }
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xb0, 0, 3, choffs); }
uint32_t ch_output_any(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 6, 2, choffs) : 1; }
uint32_t ch_output_0(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 7, 1, choffs) : 1; }
uint32_t ch_output_1(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 6, 1, choffs) : 0; }
uint32_t ch_output_2(uint32_t choffs) const { return 0; }
uint32_t ch_output_3(uint32_t choffs) const { return 0; }
uint32_t ch_lfo_am_sens(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 4, 2, choffs) : 0; }
uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 0, 3, choffs) : 0; }
// per-operator registers
uint32_t op_detune(uint32_t opoffs) const { return byte(0x30, 4, 3, opoffs); }
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x30, 0, 4, opoffs); }
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 7, opoffs); }
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x50, 6, 2, opoffs); }
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x50, 0, 5, opoffs); }
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 5, opoffs); }
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return IsOpnA ? byte(0x60, 7, 1, opoffs) : 0; }
uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0x70, 0, 5, opoffs); }
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); }
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); }
uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return byte(0x90, 3, 1, opoffs); }
uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return byte(0x90, 0, 3, opoffs); }
protected:
// return a bitfield extracted from a byte
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
{
return bitfield(m_regdata[offset + extra_offset], start, count);
}
// return a bitfield extracted from a pair of bytes, MSBs listed first
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
{
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
}
// internal state
uint32_t m_lfo_counter; // LFO counter
uint8_t m_lfo_am; // current LFO AM value
uint8_t m_regdata[REGISTERS]; // register data
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
};
using opn_registers = opn_registers_base<false>;
using opna_registers = opn_registers_base<true>;
//*********************************************************
// OPN IMPLEMENTATION CLASSES
//*********************************************************
// A note about prescaling and sample rates.
//
// YM2203, YM2608, and YM2610 contain an onboard SSG (basically, a YM2149).
// In order to properly generate sound at fully fidelity, the output sample
// rate of the YM2149 must be input_clock / 8. This is much higher than the
// FM needs, but in the interest of keeping things simple, the OPN generate
// functions will output at the higher rate and just replicate the last FM
// sample as many times as needed.
//
// To make things even more complicated, the YM2203 and YM2608 allow for
// software-controlled prescaling, which affects the FM and SSG clocks in
// different ways. There are three settings: divide by 6/4 (FM/SSG); divide
// by 3/2; and divide by 2/1.
//
// Thus, the minimum output sample rate needed by each part of the chip
// varies with the prescale as follows:
//
// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 -----
// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
// 6 /72 /16 /144 /32 /144 /32
// 3 /36 /8 /72 /16
// 2 /24 /4 /48 /8
//
// If we standardized on the fastest SSG rate, we'd end up with the following
// (ratios are output_samples:source_samples):
//
// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 -----
// rate = clock/4 rate = clock/8 rate = clock/16
// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
// 6 18:1 4:1 18:1 4:1 9:1 2:1
// 3 9:1 2:1 9:1 2:1
// 2 6:1 1:1 6:1 1:1
//
// However, that's a pretty big performance hit for minimal gain. Going to
// the other extreme, we could standardize on the fastest FM rate, but then
// at least one prescale case (3) requires the FM to be smeared across two
// output samples:
//
// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 -----
// rate = clock/24 rate = clock/48 rate = clock/144
// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
// 6 3:1 2:3 3:1 2:3 1:1 2:9
// 3 1.5:1 1:3 1.5:1 1:3
// 2 1:1 1:6 1:1 1:6
//
// Stepping back one factor of 2 addresses that issue:
//
// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 -----
// rate = clock/12 rate = clock/24 rate = clock/144
// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
// 6 6:1 4:3 6:1 4:3 1:1 2:9
// 3 3:1 2:3 3:1 2:3
// 2 2:1 1:3 2:1 1:3
//
// This gives us three levels of output fidelity:
// OPN_FIDELITY_MAX -- highest sample rate, using fastest SSG rate
// OPN_FIDELITY_MIN -- lowest sample rate, using fastest FM rate
// OPN_FIDELITY_MED -- medium sample rate such that FM is never smeared
//
// At the maximum clocks for YM2203/YM2608 (4Mhz/8MHz), these rates will
// end up as:
// OPN_FIDELITY_MAX = 1000kHz
// OPN_FIDELITY_MIN = 166kHz
// OPN_FIEDLITY_MED = 333kHz
// ======================> opn_fidelity
enum opn_fidelity : uint8_t
{
OPN_FIDELITY_MAX,
OPN_FIDELITY_MIN,
OPN_FIDELITY_MED,
OPN_FIDELITY_DEFAULT = OPN_FIDELITY_MAX
};
// ======================> ssg_resampler
template<typename OutputType, int FirstOutput, bool MixTo1>
class ssg_resampler
{
private:
// helper to add the last computed value to the sums, applying the given scale
void add_last(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale = 1);
// helper to clock a new value and then add it to the sums, applying the given scale
void clock_and_add(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale = 1);
// helper to write the sums to the appropriate outputs, applying the given
// divisor to the final result
void write_to_output(OutputType *output, int32_t sum0, int32_t sum1, int32_t sum2, int32_t divisor = 1);
public:
// constructor
ssg_resampler(ssg_engine &ssg);
// save/restore
void save_restore(ymfm_saved_state &state);
// get the current sample index
uint32_t sampindex() const { return m_sampindex; }
// configure the ratio
void configure(uint8_t outsamples, uint8_t srcsamples);
// resample
void resample(OutputType *output, uint32_t numsamples)
{
(this->*m_resampler)(output, numsamples);
}
private:
// resample SSG output to the target at a rate of 1 SSG sample
// to every n output samples
template<int Multiplier>
void resample_n_1(OutputType *output, uint32_t numsamples);
// resample SSG output to the target at a rate of n SSG samples
// to every 1 output sample
template<int Divisor>
void resample_1_n(OutputType *output, uint32_t numsamples);
// resample SSG output to the target at a rate of 9 SSG samples
// to every 2 output samples
void resample_2_9(OutputType *output, uint32_t numsamples);
// resample SSG output to the target at a rate of 3 SSG samples
// to every 1 output sample
void resample_1_3(OutputType *output, uint32_t numsamples);
// resample SSG output to the target at a rate of 3 SSG samples
// to every 2 output samples
void resample_2_3(OutputType *output, uint32_t numsamples);
// resample SSG output to the target at a rate of 3 SSG samples
// to every 4 output samples
void resample_4_3(OutputType *output, uint32_t numsamples);
// no-op resampler
void resample_nop(OutputType *output, uint32_t numsamples);
// define a pointer type
using resample_func = void (ssg_resampler::*)(OutputType *output, uint32_t numsamples);
// internal state
ssg_engine &m_ssg;
uint32_t m_sampindex;
resample_func m_resampler;
ssg_engine::output_data m_last;
};
// ======================> ym2203
class ym2203
{
public:
using fm_engine = fm_engine_base<opn_registers>;
static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS;
static constexpr uint32_t SSG_OUTPUTS = ssg_engine::OUTPUTS;
static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS;
using output_data = ymfm_output<OUTPUTS>;
// constructor
ym2203(ymfm_interface &intf);
// configuration
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(m_fm.clock_prescale()); }
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const
{
switch (m_fidelity)
{
case OPN_FIDELITY_MIN: return input_clock / 24;
case OPN_FIDELITY_MED: return input_clock / 12;
default:
case OPN_FIDELITY_MAX: return input_clock / 4;
}
}
uint32_t ssg_effective_clock(uint32_t input_clock) const { uint32_t scale = m_fm.clock_prescale() * 2 / 3; return input_clock * 2 / scale; }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal helpers
void update_prescale(uint8_t prescale);
void clock_fm();
// internal state
opn_fidelity m_fidelity; // configured fidelity
uint8_t m_address; // address register
uint8_t m_fm_samples_per_output; // how many samples to repeat
fm_engine::output_data m_last_fm; // last FM output
fm_engine m_fm; // core FM engine
ssg_engine m_ssg; // SSG engine
ssg_resampler<output_data, 1, false> m_ssg_resampler; // SSG resampler helper
};
//*********************************************************
// OPNA IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym2608
class ym2608
{
static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x04;
static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08;
static constexpr uint8_t STATUS_ADPCM_B_ZERO = 0x10;
static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x20;
public:
using fm_engine = fm_engine_base<opna_registers>;
static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS;
static constexpr uint32_t SSG_OUTPUTS = 1;
static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS;
using output_data = ymfm_output<OUTPUTS>;
// constructor
ym2608(ymfm_interface &intf);
// configuration
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(m_fm.clock_prescale()); }
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const
{
switch (m_fidelity)
{
case OPN_FIDELITY_MIN: return input_clock / 48;
case OPN_FIDELITY_MED: return input_clock / 24;
default:
case OPN_FIDELITY_MAX: return input_clock / 8;
}
}
uint32_t ssg_effective_clock(uint32_t input_clock) const { uint32_t scale = m_fm.clock_prescale() * 2 / 3; return input_clock / scale; }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data();
uint8_t read_status_hi();
uint8_t read_data_hi();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write_data_hi(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal helpers
void update_prescale(uint8_t prescale);
void clock_fm_and_adpcm();
// internal state
opn_fidelity m_fidelity; // configured fidelity
uint16_t m_address; // address register
uint8_t m_fm_samples_per_output; // how many samples to repeat
uint8_t m_irq_enable; // IRQ enable register
uint8_t m_flag_control; // flag control register
fm_engine::output_data m_last_fm; // last FM output
fm_engine m_fm; // core FM engine
ssg_engine m_ssg; // SSG engine
ssg_resampler<output_data, 2, true> m_ssg_resampler; // SSG resampler helper
adpcm_a_engine m_adpcm_a; // ADPCM-A engine
adpcm_b_engine m_adpcm_b; // ADPCM-B engine
};
// ======================> ymf288
class ymf288
{
public:
using fm_engine = fm_engine_base<opna_registers>;
static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS;
static constexpr uint32_t SSG_OUTPUTS = 1;
static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS;
using output_data = ymfm_output<OUTPUTS>;
// constructor
ymf288(ymfm_interface &intf);
// configuration
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(); }
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const
{
switch (m_fidelity)
{
case OPN_FIDELITY_MIN: return input_clock / 144;
case OPN_FIDELITY_MED: return input_clock / 144;
default:
case OPN_FIDELITY_MAX: return input_clock / 16;
}
}
uint32_t ssg_effective_clock(uint32_t input_clock) const { return input_clock / 4; }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data();
uint8_t read_status_hi();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write_data_hi(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal helpers
bool ymf288_mode() { return ((m_fm.regs().read(0x20) & 0x02) != 0); }
void update_prescale();
void clock_fm_and_adpcm();
// internal state
opn_fidelity m_fidelity; // configured fidelity
uint16_t m_address; // address register
uint8_t m_fm_samples_per_output; // how many samples to repeat
uint8_t m_irq_enable; // IRQ enable register
uint8_t m_flag_control; // flag control register
fm_engine::output_data m_last_fm; // last FM output
fm_engine m_fm; // core FM engine
ssg_engine m_ssg; // SSG engine
ssg_resampler<output_data, 2, true> m_ssg_resampler; // SSG resampler helper
adpcm_a_engine m_adpcm_a; // ADPCM-A engine
};
// ======================> ym2610/ym2610b
class ym2610
{
static constexpr uint8_t EOS_FLAGS_MASK = 0xbf;
public:
using fm_engine = fm_engine_base<opna_registers>;
static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS;
static constexpr uint32_t SSG_OUTPUTS = 1;
static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS;
using output_data = ymfm_output<OUTPUTS>;
// constructor
ym2610(ymfm_interface &intf, uint8_t channel_mask = 0x36);
// configuration
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(); }
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const
{
switch (m_fidelity)
{
case OPN_FIDELITY_MIN: return input_clock / 144;
case OPN_FIDELITY_MED: return input_clock / 144;
default:
case OPN_FIDELITY_MAX: return input_clock / 16;
}
}
uint32_t ssg_effective_clock(uint32_t input_clock) const { return input_clock / 4; }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data();
uint8_t read_status_hi();
uint8_t read_data_hi();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write_data_hi(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal helpers
void update_prescale();
void clock_fm_and_adpcm();
// internal state
opn_fidelity m_fidelity; // configured fidelity
uint16_t m_address; // address register
uint8_t const m_fm_mask; // FM channel mask
uint8_t m_fm_samples_per_output; // how many samples to repeat
uint8_t m_eos_status; // end-of-sample signals
uint8_t m_flag_mask; // flag mask control
fm_engine::output_data m_last_fm; // last FM output
fm_engine m_fm; // core FM engine
ssg_engine m_ssg; // core FM engine
ssg_resampler<output_data, 2, true> m_ssg_resampler; // SSG resampler helper
adpcm_a_engine m_adpcm_a; // ADPCM-A engine
adpcm_b_engine m_adpcm_b; // ADPCM-B engine
};
class ym2610b : public ym2610
{
public:
// constructor
ym2610b(ymfm_interface &intf) : ym2610(intf, 0x3f) { }
};
// ======================> ym2612
class ym2612
{
public:
using fm_engine = fm_engine_base<opna_registers>;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
using output_data = fm_engine::output_data;
// constructor
ym2612(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write_data_hi(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// simulate the DAC discontinuity
constexpr int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 3) : (value + 4); }
// internal state
uint16_t m_address; // address register
uint16_t m_dac_data; // 9-bit DAC data
uint8_t m_dac_enable; // DAC enabled?
fm_engine m_fm; // core FM engine
};
// ======================> ym3438
class ym3438 : public ym2612
{
public:
ym3438(ymfm_interface &intf) : ym2612(intf) { }
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
};
// ======================> ymf276
class ymf276 : public ym2612
{
public:
ymf276(ymfm_interface &intf) : ym2612(intf) { }
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples);
};
}
#endif // YMFM_OPN_H

View file

@ -0,0 +1,480 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ymfm_opq.h"
#include "ymfm_fm.ipp"
#define TEMPORARY_DEBUG_PRINTS (0)
//
// OPQ (aka YM3806/YM3533)
//
// This chip is not officially documented as far as I know. What I have
// comes from Jari Kangas' work on reverse engineering the PSR70:
//
// https://github.com/JKN0/PSR70-reverse
//
// OPQ appears be bsaically a mixture of OPM and OPN.
//
namespace ymfm
{
//*********************************************************
// OPQ SPECIFICS
//*********************************************************
//-------------------------------------------------
// opq_registers - constructor
//-------------------------------------------------
opq_registers::opq_registers() :
m_lfo_counter(0),
m_lfo_am(0)
{
// create the waveforms
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
uint16_t zeroval = m_waveform[0][0];
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index];
}
//-------------------------------------------------
// reset - reset to initial state
//-------------------------------------------------
void opq_registers::reset()
{
std::fill_n(&m_regdata[0], REGISTERS, 0);
// enable output on both channels by default
m_regdata[0x10] = m_regdata[0x11] = m_regdata[0x12] = m_regdata[0x13] = 0xc0;
m_regdata[0x14] = m_regdata[0x15] = m_regdata[0x16] = m_regdata[0x17] = 0xc0;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void opq_registers::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_lfo_counter);
state.save_restore(m_lfo_am);
state.save_restore(m_regdata);
}
//-------------------------------------------------
// operator_map - return an array of operator
// indices for each channel; for OPM this is fixed
//-------------------------------------------------
void opq_registers::operator_map(operator_mapping &dest) const
{
// seems like the operators are not swizzled like they are on OPM/OPN?
static const operator_mapping s_fixed_map =
{ {
operator_list( 0, 8, 16, 24 ), // Channel 0 operators
operator_list( 1, 9, 17, 25 ), // Channel 1 operators
operator_list( 2, 10, 18, 26 ), // Channel 2 operators
operator_list( 3, 11, 19, 27 ), // Channel 3 operators
operator_list( 4, 12, 20, 28 ), // Channel 4 operators
operator_list( 5, 13, 21, 29 ), // Channel 5 operators
operator_list( 6, 14, 22, 30 ), // Channel 6 operators
operator_list( 7, 15, 23, 31 ), // Channel 7 operators
} };
dest = s_fixed_map;
}
//-------------------------------------------------
// write - handle writes to the register array
//-------------------------------------------------
bool opq_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
{
assert(index < REGISTERS);
// detune/multiple share a register based on the MSB of what is written
// remap the multiple values to 100-11F
if ((index & 0xe0) == 0x40 && bitfield(data, 7) != 0)
index += 0xc0;
m_regdata[index] = data;
// handle writes to the key on index
if (index == 0x05)
{
channel = bitfield(data, 0, 3);
opmask = bitfield(data, 3, 4);
return true;
}
return false;
}
//-------------------------------------------------
// clock_noise_and_lfo - clock the noise and LFO,
// handling clock division, depth, and waveform
// computations
//-------------------------------------------------
int32_t opq_registers::clock_noise_and_lfo()
{
// OPQ LFO is not well-understood, but the enable and rate values
// look a lot like OPN, so we'll crib from there as a starting point
// if LFO not enabled (not present on OPN), quick exit with 0s
if (!lfo_enable())
{
m_lfo_counter = 0;
m_lfo_am = 0;
return 0;
}
// this table is based on converting the frequencies in the applications
// manual to clock dividers, based on the assumption of a 7-bit LFO value
static uint8_t const lfo_max_count[8] = { 109, 78, 72, 68, 63, 45, 9, 6 };
uint32_t subcount = uint8_t(m_lfo_counter++);
// when we cross the divider count, add enough to zero it and cause an
// increment at bit 8; the 7-bit value lives from bits 8-14
if (subcount >= lfo_max_count[lfo_rate()])
m_lfo_counter += 0x101 - subcount;
// AM value is 7 bits, staring at bit 8; grab the low 6 directly
m_lfo_am = bitfield(m_lfo_counter, 8, 6);
// first half of the AM period (bit 6 == 0) is inverted
if (bitfield(m_lfo_counter, 8+6) == 0)
m_lfo_am ^= 0x3f;
// PM value is 5 bits, starting at bit 10; grab the low 3 directly
int32_t pm = bitfield(m_lfo_counter, 10, 3);
// PM is reflected based on bit 3
if (bitfield(m_lfo_counter, 10+3))
pm ^= 7;
// PM is negated based on bit 4
return bitfield(m_lfo_counter, 10+4) ? -pm : pm;
}
//-------------------------------------------------
// lfo_am_offset - return the AM offset from LFO
// for the given channel
//-------------------------------------------------
uint32_t opq_registers::lfo_am_offset(uint32_t choffs) const
{
// OPM maps AM quite differently from OPN
// shift value for AM sensitivity is [*, 0, 1, 2],
// mapping to values of [0, 23.9, 47.8, and 95.6dB]
uint32_t am_sensitivity = ch_lfo_am_sens(choffs);
if (am_sensitivity == 0)
return 0;
// QUESTION: see OPN note below for the dB range mapping; it applies
// here as well
// raw LFO AM value on OPM is 0-FF, which is already a factor of 2
// larger than the OPN below, putting our staring point at 2x theirs;
// this works out since our minimum is 2x their maximum
return m_lfo_am << (am_sensitivity - 1);
}
//-------------------------------------------------
// cache_operator_data - fill the operator cache
// with prefetched data
//-------------------------------------------------
void opq_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
{
// set up the easy stuff
cache.waveform = &m_waveform[op_waveform(opoffs)][0];
// get frequency from the appropriate registers
uint32_t block_freq = cache.block_freq = (opoffs & 8) ? ch_block_freq_24(choffs) : ch_block_freq_13(choffs);
// compute the keycode: block_freq is:
//
// BBBFFFFFFFFFFFF
// ^^^^???
//
// keycode is not understood, so just guessing it is like OPN:
// the 5-bit keycode uses the top 4 bits plus a magic formula
// for the final bit
uint32_t keycode = bitfield(block_freq, 11, 4) << 1;
// lowest bit is determined by a mix of next lower FNUM bits
// according to this equation from the YM2608 manual:
//
// (F11 & (F10 | F9 | F8)) | (!F11 & F10 & F9 & F8)
//
// for speed, we just look it up in a 16-bit constant
keycode |= bitfield(0xfe80, bitfield(block_freq, 8, 4));
// detune adjustment: the detune values supported by the OPQ are
// a much larger range (6 bits vs 3 bits) compared to any other
// known FM chip; based on experiments, it seems that the extra
// bits provide a bigger detune range rather than finer control,
// so until we get true measurements just assemble a net detune
// value by summing smaller detunes
int32_t detune = int32_t(op_detune(opoffs)) - 0x20;
int32_t abs_detune = std::abs(detune);
int32_t adjust = (abs_detune / 3) * detune_adjustment(3, keycode) + detune_adjustment(abs_detune % 3, keycode);
cache.detune = (detune >= 0) ? adjust : -adjust;
// multiple value, as an x.1 value (0 means 0.5)
static const uint8_t s_multiple_map[16] = { 1,2,4,6,8,10,12,14,16,18,20,24,30,32,34,36 };
cache.multiple = s_multiple_map[op_multiple(opoffs)];
// phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
// block_freq, detune, and multiple, so compute it after we've done those
if (lfo_enable() == 0 || ch_lfo_pm_sens(choffs) == 0)
cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
else
cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
// total level, scaled by 8
cache.total_level = op_total_level(opoffs) << 3;
// 4-bit sustain level, but 15 means 31 so effectively 5 bits
cache.eg_sustain = op_sustain_level(opoffs);
cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
cache.eg_sustain <<= 5;
// determine KSR adjustment for enevlope rates
uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
cache.eg_rate[EG_REVERB] = (ch_reverb(choffs) != 0) ? 5*4 : cache.eg_rate[EG_RELEASE];
cache.eg_shift = 0;
}
//-------------------------------------------------
// compute_phase_step - compute the phase step
//-------------------------------------------------
uint32_t opq_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
{
// OPN phase calculation has only a single detune parameter
// and uses FNUMs instead of keycodes
// extract frequency number (low 12 bits of block_freq)
uint32_t fnum = bitfield(cache.block_freq, 0, 12);
// if there's a non-zero PM sensitivity, compute the adjustment
uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
if (pm_sensitivity != 0)
{
// apply the phase adjustment based on the upper 7 bits
// of FNUM and the PM depth parameters
fnum += opn_lfo_pm_phase_adjustment(bitfield(cache.block_freq, 5, 7), pm_sensitivity, lfo_raw_pm);
// keep fnum to 12 bits
fnum &= 0xfff;
}
// apply block shift to compute phase step
uint32_t block = bitfield(cache.block_freq, 12, 3);
uint32_t phase_step = (fnum << block) >> 2;
// apply detune based on the keycode
phase_step += cache.detune;
// clamp to 17 bits in case detune overflows
// QUESTION: is this specific to the YM2612/3438?
phase_step &= 0x1ffff;
// apply frequency multiplier (which is cached as an x.1 value)
return (phase_step * cache.multiple) >> 1;
}
//-------------------------------------------------
// log_keyon - log a key-on event
//-------------------------------------------------
std::string opq_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
{
uint32_t chnum = choffs;
uint32_t opnum = opoffs;
char buffer[256];
int end = 0;
end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt=%+2d fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
chnum, opnum,
(opoffs & 1) ? ch_block_freq_24(choffs) : ch_block_freq_13(choffs),
int32_t(op_detune(opoffs)) - 0x20,
ch_feedback(choffs),
ch_algorithm(choffs),
op_multiple(opoffs),
op_total_level(opoffs),
op_ksr(opoffs),
op_attack_rate(opoffs),
op_decay_rate(opoffs),
op_sustain_rate(opoffs),
op_release_rate(opoffs),
op_sustain_level(opoffs),
ch_output_0(choffs) ? 'L' : '-',
ch_output_1(choffs) ? 'R' : '-');
bool am = (lfo_enable() && op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0);
if (am)
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", ch_lfo_am_sens(choffs));
bool pm = (lfo_enable() && ch_lfo_pm_sens(choffs) != 0);
if (pm)
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", ch_lfo_pm_sens(choffs));
if (am || pm)
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X", lfo_rate());
if (ch_reverb(choffs))
end += snprintf(&buffer[end], sizeof(buffer) - end, " reverb");
return buffer;
}
//*********************************************************
// YM3806
//*********************************************************
//-------------------------------------------------
// ym3806 - constructor
//-------------------------------------------------
ym3806::ym3806(ymfm_interface &intf) :
m_fm(intf)
{
}
//-------------------------------------------------
// reset - reset the system
//-------------------------------------------------
void ym3806::reset()
{
// reset the engines
m_fm.reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void ym3806::save_restore(ymfm_saved_state &state)
{
m_fm.save_restore(state);
}
//-------------------------------------------------
// read_status - read the status register
//-------------------------------------------------
uint8_t ym3806::read_status()
{
uint8_t result = m_fm.status();
if (m_fm.intf().ymfm_is_busy())
result |= fm_engine::STATUS_BUSY;
return result;
}
//-------------------------------------------------
// read - handle a read from the device
//-------------------------------------------------
uint8_t ym3806::read(uint32_t offset)
{
uint8_t result = 0xff;
switch (offset)
{
case 0: // status port
result = read_status();
break;
default: // unknown
debug::log_unexpected_read_write("Unexpected read from YM3806 offset %02X\n", offset);
break;
}
if (TEMPORARY_DEBUG_PRINTS && offset != 0) printf("Read %02X = %02X\n", offset, result);
return result;
}
//-------------------------------------------------
// write - handle a write to the register
// interface
//-------------------------------------------------
void ym3806::write(uint32_t offset, uint8_t data)
{
if (TEMPORARY_DEBUG_PRINTS && (offset != 3 || data != 0x71)) printf("Write %02X = %02X\n", offset, data);
// write the FM register
m_fm.write(offset, data);
}
//-------------------------------------------------
// generate - generate one sample of sound
//-------------------------------------------------
void ym3806::generate(output_data *output, uint32_t numsamples)
{
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
{
// clock the system
m_fm.clock(fm_engine::ALL_CHANNELS);
// update the FM content; YM3806 is full 14-bit with no intermediate clipping
m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
// YM3608 appears to go through a YM3012 DAC, which means we want to apply
// the FP truncation logic to the outputs
output->roundtrip_fp();
}
}
}

View file

@ -0,0 +1,293 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_OPQ_H
#define YMFM_OPQ_H
#pragma once
#include "ymfm.h"
#include "ymfm_fm.h"
namespace ymfm
{
//*********************************************************
// REGISTER CLASSES
//*********************************************************
// ======================> opq_registers
//
// OPQ register map:
//
// System-wide registers:
// 03 xxxxxxxx Timer control (unknown; 0x71 causes interrupts at ~10ms)
// 04 ----x--- LFO disable
// -----xxx LFO frequency (0=~4Hz, 6=~10Hz, 7=~47Hz)
// 05 -x------ Key on/off operator 4
// --x----- Key on/off operator 3
// ---x---- Key on/off operator 2
// ----x--- Key on/off operator 1
// -----xxx Channel select
//
// Per-channel registers (channel in address bits 0-2)
// 10-17 x------- Pan right
// -x------ Pan left
// --xxx--- Feedback level for operator 1 (0-7)
// -----xxx Operator connection algorithm (0-7)
// 18-1F x------- Reverb
// -xxx---- PM sensitivity
// ------xx AM shift
// 20-27 -xxx---- Block (0-7), Operator 2 & 4
// ----xxxx Frequency number upper 4 bits, Operator 2 & 4
// 28-2F -xxx---- Block (0-7), Operator 1 & 3
// ----xxxx Frequency number upper 4 bits, Operator 1 & 3
// 30-37 xxxxxxxx Frequency number lower 8 bits, Operator 2 & 4
// 38-3F xxxxxxxx Frequency number lower 8 bits, Operator 1 & 3
//
// Per-operator registers (channel in address bits 0-2, operator in bits 3-4)
// 40-5F 0-xxxxxx Detune value (0-63)
// 1---xxxx Multiple value (0-15)
// 60-7F -xxxxxxx Total level (0-127)
// 80-9F xx------ Key scale rate (0-3)
// ---xxxxx Attack rate (0-31)
// A0-BF x------- LFO AM enable, retrigger disable
// x------ Waveform select
// ---xxxxx Decay rate (0-31)
// C0-DF ---xxxxx Sustain rate (0-31)
// E0-FF xxxx---- Sustain level (0-15)
// ----xxxx Release rate (0-15)
//
// Diffs from OPM:
// - 2 frequencies/channel
// - retrigger disable
// - 2 waveforms
// - uses FNUM
// - reverb behavior
// - larger detune range
//
// Questions:
// - timer information is pretty light
// - how does echo work?
// -
class opq_registers : public fm_registers_base
{
public:
// constants
static constexpr uint32_t OUTPUTS = 2;
static constexpr uint32_t CHANNELS = 8;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
static constexpr uint32_t OPERATORS = CHANNELS * 4;
static constexpr uint32_t WAVEFORMS = 2;
static constexpr uint32_t REGISTERS = 0x120;
static constexpr uint32_t REG_MODE = 0x03;
static constexpr uint32_t DEFAULT_PRESCALE = 2;
static constexpr uint32_t EG_CLOCK_DIVIDER = 3;
static constexpr bool EG_HAS_REVERB = true;
static constexpr bool MODULATOR_DELAY = false;
static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
static constexpr uint8_t STATUS_TIMERA = 0;
static constexpr uint8_t STATUS_TIMERB = 0x04;
static constexpr uint8_t STATUS_BUSY = 0x80;
static constexpr uint8_t STATUS_IRQ = 0;
// constructor
opq_registers();
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
return chnum;
}
// map operator number to register offset
static constexpr uint32_t operator_offset(uint32_t opnum)
{
assert(opnum < OPERATORS);
return opnum;
}
// return an array of operator indices for each channel
struct operator_mapping { uint32_t chan[CHANNELS]; };
void operator_map(operator_mapping &dest) const;
// handle writes to the register array
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
// clock the noise and LFO, if present, returning LFO PM value
int32_t clock_noise_and_lfo();
// reset the LFO
void reset_lfo() { m_lfo_counter = 0; }
// return the AM offset from LFO for the given channel
uint32_t lfo_am_offset(uint32_t choffs) const;
// return the current noise state, gated by the noise clock
uint32_t noise_state() const { return 0; }
// caching helpers
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
// compute the phase step, given a PM value
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
// log a key-on event
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
// system-wide registers
uint32_t timer_a_value() const { return 0; }
uint32_t timer_b_value() const { return byte(0x03, 2, 6) | 0xc0; } // ???
uint32_t csm() const { return 0; }
uint32_t reset_timer_b() const { return byte(0x03, 0, 1); } // ???
uint32_t reset_timer_a() const { return 0; }
uint32_t enable_timer_b() const { return byte(0x03, 0, 1); } // ???
uint32_t enable_timer_a() const { return 0; }
uint32_t load_timer_b() const { return byte(0x03, 0, 1); } // ???
uint32_t load_timer_a() const { return 0; }
uint32_t lfo_enable() const { return byte(0x04, 3, 1) ^ 1; }
uint32_t lfo_rate() const { return byte(0x04, 0, 3); }
// per-channel registers
uint32_t ch_output_any(uint32_t choffs) const { return byte(0x10, 6, 2, choffs); }
uint32_t ch_output_0(uint32_t choffs) const { return byte(0x10, 6, 1, choffs); }
uint32_t ch_output_1(uint32_t choffs) const { return byte(0x10, 7, 1, choffs); }
uint32_t ch_output_2(uint32_t choffs) const { return 0; }
uint32_t ch_output_3(uint32_t choffs) const { return 0; }
uint32_t ch_feedback(uint32_t choffs) const { return byte(0x10, 3, 3, choffs); }
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x10, 0, 3, choffs); }
uint32_t ch_reverb(uint32_t choffs) const { return byte(0x18, 7, 1, choffs); }
uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x18, 4, 3, choffs); }
uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x18, 0, 2, choffs); }
uint32_t ch_block_freq_24(uint32_t choffs) const { return word(0x20, 0, 7, 0x30, 0, 8, choffs); }
uint32_t ch_block_freq_13(uint32_t choffs) const { return word(0x28, 0, 7, 0x38, 0, 8, choffs); }
// per-operator registers
uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); }
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); }
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); }
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); }
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); }
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); }
uint32_t op_waveform(uint32_t opoffs) const { return byte(0xa0, 6, 1, opoffs); }
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); }
uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); }
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); }
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); }
protected:
// return a bitfield extracted from a byte
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
{
return bitfield(m_regdata[offset + extra_offset], start, count);
}
// return a bitfield extracted from a pair of bytes, MSBs listed first
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
{
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
}
// internal state
uint32_t m_lfo_counter; // LFO counter
uint8_t m_lfo_am; // current LFO AM value
uint8_t m_regdata[REGISTERS]; // register data
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
};
//*********************************************************
// IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym3806
class ym3806
{
public:
using fm_engine = fm_engine_base<opq_registers>;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
using output_data = fm_engine::output_data;
// constructor
ym3806(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data) { /* not supported; only direct writes */ }
void write_data(uint8_t data) { /* not supported; only direct writes */ }
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
fm_engine m_fm; // core FM engine
};
// ======================> ym3533
class ym3533 : public ym3806
{
public:
// constructor
ym3533(ymfm_interface &intf) :
ym3806(intf) { }
};
}
#endif // YMFM_OPQ_H

View file

@ -0,0 +1,290 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_OPX_H
#define YMFM_OPX_H
#pragma once
#include "ymfm.h"
#include "ymfm_fm.h"
namespace ymfm
{
//*********************************************************
// REGISTER CLASSES
//*********************************************************
// ======================> opx_registers
//
// OPX register map:
//
// System-wide registers:
//
// Per-channel registers (channel in address bits 0-2)
//
// Per-operator registers (4 banks):
// 00-0F x------- Enable
// -xxxx--- EXT out
// -------x Key on
// 10-1F xxxxxxxx LFO frequency
// 20-2F xx------ AM sensitivity (0-3)
// --xxx--- PM sensitivity (0-7)
// ------xx LFO waveform (0=disable, 1=saw, 2=
// 30-3F -xxx---- Detune (0-7)
// ----xxxx Multiple (0-15)
// 40-4F -xxxxxxx Total level (0-127)
// 50-5F xxx----- Key scale (0-7)
// ---xxxxx Attack rate (0-31)
// 60-6F ---xxxxx Decay rate (0-31)
// 70-7F ---xxxxx Sustain rate (0-31)
// 80-8F xxxx---- Sustain level (0-15)
// ----xxxx Release rate (0-15)
// 90-9F xxxxxxxx Frequency number (low 8 bits)
// A0-AF xxxx---- Block (0-15)
// ----xxxx Frequency number (high 4 bits)
// B0-BF x------- Acc on
// -xxx---- Feedback level (0-7)
// -----xxx Waveform (0-7, 7=PCM)
// C0-CF ----xxxx Algorithm (0-15)
// D0-DF xxxx---- CH0 level (0-15)
// ----xxxx CH1 level (0-15)
// E0-EF xxxx---- CH2 level (0-15)
// ----xxxx CH3 level (0-15)
//
class opx_registers : public fm_registers_base
{
// LFO waveforms are 256 entries long
static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256;
public:
// constants
static constexpr uint32_t OUTPUTS = 8;
static constexpr uint32_t CHANNELS = 24;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
static constexpr uint32_t OPERATORS = CHANNELS * 2;
static constexpr uint32_t WAVEFORMS = 8;
static constexpr uint32_t REGISTERS = 0x800;
static constexpr uint32_t DEFAULT_PRESCALE = 8;
static constexpr uint32_t EG_CLOCK_DIVIDER = 2;
static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
static constexpr uint32_t REG_MODE = 0x14;
static constexpr uint8_t STATUS_TIMERA = 0x01;
static constexpr uint8_t STATUS_TIMERB = 0x02;
static constexpr uint8_t STATUS_BUSY = 0x80;
static constexpr uint8_t STATUS_IRQ = 0;
// constructor
opz_registers();
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
return chnum;
}
// map operator number to register offset
static constexpr uint32_t operator_offset(uint32_t opnum)
{
assert(opnum < OPERATORS);
return opnum;
}
// return an array of operator indices for each channel
struct operator_mapping { uint32_t chan[CHANNELS]; };
void operator_map(operator_mapping &dest) const;
// handle writes to the register array
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
// clock the noise and LFO, if present, returning LFO PM value
int32_t clock_noise_and_lfo();
// return the AM offset from LFO for the given channel
uint32_t lfo_am_offset(uint32_t choffs) const;
// return the current noise state, gated by the noise clock
uint32_t noise_state() const { return m_noise_state; }
// caching helpers
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
// compute the phase step, given a PM value
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
// log a key-on event
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
// system-wide registers
uint32_t noise_frequency() const { return byte(0x0f, 0, 5); }
uint32_t noise_enable() const { return byte(0x0f, 7, 1); }
uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); }
uint32_t timer_b_value() const { return byte(0x12, 0, 8); }
uint32_t csm() const { return byte(0x14, 7, 1); }
uint32_t reset_timer_b() const { return byte(0x14, 5, 1); }
uint32_t reset_timer_a() const { return byte(0x14, 4, 1); }
uint32_t enable_timer_b() const { return byte(0x14, 3, 1); }
uint32_t enable_timer_a() const { return byte(0x14, 2, 1); }
uint32_t load_timer_b() const { return byte(0x14, 1, 1); }
uint32_t load_timer_a() const { return byte(0x14, 0, 1); }
uint32_t lfo2_pm_depth() const { return byte(0x148, 0, 7); } // fake
uint32_t lfo2_rate() const { return byte(0x16, 0, 8); }
uint32_t lfo2_am_depth() const { return byte(0x17, 0, 7); }
uint32_t lfo_rate() const { return byte(0x18, 0, 8); }
uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); }
uint32_t lfo_pm_depth() const { return byte(0x149, 0, 7); } // fake
uint32_t output_bits() const { return byte(0x1b, 6, 2); }
uint32_t lfo2_sync() const { return byte(0x1b, 5, 1); }
uint32_t lfo_sync() const { return byte(0x1b, 4, 1); }
uint32_t lfo2_waveform() const { return byte(0x1b, 2, 2); }
uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); }
// per-channel registers
uint32_t ch_volume(uint32_t choffs) const { return byte(0x00, 0, 8, choffs); }
uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); }
uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs); }
uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); }
uint32_t ch_output_2(uint32_t choffs) const { return 0; }
uint32_t ch_output_3(uint32_t choffs) const { return 0; }
uint32_t ch_key_on(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); }
uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); }
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); }
uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); }
uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); }
uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); }
uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x140, 4, 3, choffs); } // fake
uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x140, 0, 2, choffs); } // fake
// per-operator registers
uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
uint32_t op_fix_range(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
uint32_t op_fix_frequency(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
uint32_t op_waveform(uint32_t opoffs) const { return byte(0x100, 4, 3, opoffs); } // fake
uint32_t op_fine(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } // fake
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); }
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); }
uint32_t op_fix_mode(uint32_t opoffs) const { return byte(0x80, 5, 1, opoffs); }
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); }
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); }
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); }
uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); }
uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); }
uint32_t op_eg_shift(uint32_t opoffs) const { return byte(0x120, 6, 2, opoffs); } // fake
uint32_t op_reverb_rate(uint32_t opoffs) const { return byte(0x120, 0, 3, opoffs); } // fake
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); }
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); }
protected:
// return a bitfield extracted from a byte
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
{
return bitfield(m_regdata[offset + extra_offset], start, count);
}
// return a bitfield extracted from a pair of bytes, MSBs listed first
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
{
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
}
// internal state
uint32_t m_lfo_counter[2]; // LFO counter
uint32_t m_noise_lfsr; // noise LFSR state
uint8_t m_noise_counter; // noise counter
uint8_t m_noise_state; // latched noise state
uint8_t m_noise_lfo; // latched LFO noise value
uint8_t m_lfo_am[2]; // current LFO AM value
uint8_t m_regdata[REGISTERS]; // register data
uint16_t m_phase_substep[OPERATORS]; // phase substep for fixed frequency
int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
};
//*********************************************************
// IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym2414
class ym2414
{
public:
using fm_engine = fm_engine_base<opz_registers>;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
using output_data = fm_engine::output_data;
// constructor
ym2414(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint8_t m_address; // address register
fm_engine m_fm; // core FM engine
};
}
#endif // YMFM_OPZ_H

View file

@ -0,0 +1,808 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ymfm_opz.h"
#include "ymfm_fm.ipp"
#define TEMPORARY_DEBUG_PRINTS (0)
//
// OPZ (aka YM2414)
//
// This chip is not officially documented as far as I know. What I have
// comes from this site:
//
// http://sr4.sakura.ne.jp/fmsound/opz.html
//
// and from reading the TX81Z operator manual, which describes how a number
// of these new features work.
//
// OPZ appears be bsaically OPM with a bunch of extra features.
//
// For starters, there are two LFO generators. I have presumed that they
// operate identically since identical parameters are offered for each. I
// have also presumed the effects are additive between them. The LFOs on
// the OPZ have an extra "sync" option which apparently causes the LFO to
// reset whenever a key on is received.
//
// At the channel level, there is an additional 8-bit volume control. This
// might work as an addition to total level, or some other way. Completely
// unknown, and unimplemented.
//
// At the operator level, there are a number of extra features. First, there
// are 8 different waveforms to choose from. These are different than the
// waveforms introduced in the OPL2 and later chips.
//
// Second, there is an additional "reverb" stage added to the envelope
// generator, which kicks in when the envelope reaches -18dB. It specifies
// a slower decay rate to produce a sort of faux reverb effect.
//
// The envelope generator also supports a 2-bit shift value, which can be
// used to reduce the effect of the envelope attenuation.
//
// OPZ supports a "fixed frequency" mode for each operator, with a 3-bit
// range and 4-bit frequency value, plus a 1-bit enable. Not sure how that
// works at all, so it's not implemented.
//
// There are also several mystery fields in the operators which I have no
// clue about: "fine" (4 bits), "eg_shift" (2 bits), and "rev" (3 bits).
// eg_shift is some kind of envelope generator effect, but how it works is
// unknown.
//
// Also, according to the site above, the panning controls are changed from
// OPM, with a "mono" bit and only one control bit for the right channel.
// Current implementation is just a guess.
//
namespace ymfm
{
//*********************************************************
// OPZ REGISTERS
//*********************************************************
//-------------------------------------------------
// opz_registers - constructor
//-------------------------------------------------
opz_registers::opz_registers() :
m_lfo_counter{ 0, 0 },
m_noise_lfsr(1),
m_noise_counter(0),
m_noise_state(0),
m_noise_lfo(0),
m_lfo_am{ 0, 0 }
{
// create the waveforms
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
// we only have the diagrams to judge from, but suspecting waveform 1 (and
// derived waveforms) are sin^2, based on OPX description of similar wave-
// forms; since our sin table is logarithmic, this ends up just being
// 2*existing value
uint16_t zeroval = m_waveform[0][0];
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[1][index] = std::min<uint16_t>(2 * (m_waveform[0][index] & 0x7fff), zeroval) | (bitfield(index, 9) << 15);
// remaining waveforms are just derivations of the 2 main ones
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
{
m_waveform[2][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index];
m_waveform[3][index] = bitfield(index, 9) ? zeroval : m_waveform[1][index];
m_waveform[4][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index * 2];
m_waveform[5][index] = bitfield(index, 9) ? zeroval : m_waveform[1][index * 2];
m_waveform[6][index] = bitfield(index, 9) ? zeroval : m_waveform[0][(index * 2) & 0x1ff];
m_waveform[7][index] = bitfield(index, 9) ? zeroval : m_waveform[1][(index * 2) & 0x1ff];
}
// create the LFO waveforms; AM in the low 8 bits, PM in the upper 8
// waveforms are adjusted to match the pictures in the application manual
for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++)
{
// waveform 0 is a sawtooth
uint8_t am = index ^ 0xff;
uint8_t pm = index;
m_lfo_waveform[0][index] = am | (pm << 8);
// waveform 1 is a square wave
am = bitfield(index, 7) ? 0 : 0xff;
pm = am ^ 0x80;
m_lfo_waveform[1][index] = am | (pm << 8);
// waveform 2 is a triangle wave
am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1);
pm = bitfield(index, 6) ? am : ~am;
m_lfo_waveform[2][index] = am | (pm << 8);
// waveform 3 is noise; it is filled in dynamically
}
}
//-------------------------------------------------
// reset - reset to initial state
//-------------------------------------------------
void opz_registers::reset()
{
std::fill_n(&m_regdata[0], REGISTERS, 0);
// enable output on both channels by default
m_regdata[0x30] = m_regdata[0x31] = m_regdata[0x32] = m_regdata[0x33] = 0x01;
m_regdata[0x34] = m_regdata[0x35] = m_regdata[0x36] = m_regdata[0x37] = 0x01;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void opz_registers::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_lfo_counter);
state.save_restore(m_lfo_am);
state.save_restore(m_noise_lfsr);
state.save_restore(m_noise_counter);
state.save_restore(m_noise_state);
state.save_restore(m_noise_lfo);
state.save_restore(m_regdata);
state.save_restore(m_phase_substep);
}
//-------------------------------------------------
// operator_map - return an array of operator
// indices for each channel; for OPZ this is fixed
//-------------------------------------------------
void opz_registers::operator_map(operator_mapping &dest) const
{
// Note that the channel index order is 0,2,1,3, so we bitswap the index.
//
// This is because the order in the map is:
// carrier 1, carrier 2, modulator 1, modulator 2
//
// But when wiring up the connections, the more natural order is:
// carrier 1, modulator 1, carrier 2, modulator 2
static const operator_mapping s_fixed_map =
{ {
operator_list( 0, 16, 8, 24 ), // Channel 0 operators
operator_list( 1, 17, 9, 25 ), // Channel 1 operators
operator_list( 2, 18, 10, 26 ), // Channel 2 operators
operator_list( 3, 19, 11, 27 ), // Channel 3 operators
operator_list( 4, 20, 12, 28 ), // Channel 4 operators
operator_list( 5, 21, 13, 29 ), // Channel 5 operators
operator_list( 6, 22, 14, 30 ), // Channel 6 operators
operator_list( 7, 23, 15, 31 ), // Channel 7 operators
} };
dest = s_fixed_map;
}
//-------------------------------------------------
// write - handle writes to the register array
//-------------------------------------------------
bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
{
assert(index < REGISTERS);
// special mappings:
// 0x16 -> 0x188 if bit 7 is set
// 0x19 -> 0x189 if bit 7 is set
// 0x38..0x3F -> 0x180..0x187 if bit 7 is set
// 0x40..0x5F -> 0x100..0x11F if bit 7 is set
// 0xC0..0xDF -> 0x120..0x13F if bit 5 is set
if (index == 0x17 && bitfield(data, 7) != 0)
m_regdata[0x188] = data;
else if (index == 0x19 && bitfield(data, 7) != 0)
m_regdata[0x189] = data;
else if ((index & 0xf8) == 0x38 && bitfield(data, 7) != 0)
m_regdata[0x180 + (index & 7)] = data;
else if ((index & 0xe0) == 0x40 && bitfield(data, 7) != 0)
m_regdata[0x100 + (index & 0x1f)] = data;
else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0)
m_regdata[0x120 + (index & 0x1f)] = data;
else if (index < 0x100)
m_regdata[index] = data;
// preset writes restore some values from a preset memory; not sure
// how this really works but the TX81Z will overwrite the sustain level/
// release rate register and the envelope shift/reverb rate register to
// dampen sound, then write the preset number to register 8 to restore them
if (index == 0x08)
{
int chan = bitfield(data, 0, 3);
if (TEMPORARY_DEBUG_PRINTS)
printf("Loading preset %d\n", chan);
m_regdata[0xe0 + chan + 0] = m_regdata[0x140 + chan + 0];
m_regdata[0xe0 + chan + 8] = m_regdata[0x140 + chan + 8];
m_regdata[0xe0 + chan + 16] = m_regdata[0x140 + chan + 16];
m_regdata[0xe0 + chan + 24] = m_regdata[0x140 + chan + 24];
m_regdata[0x120 + chan + 0] = m_regdata[0x160 + chan + 0];
m_regdata[0x120 + chan + 8] = m_regdata[0x160 + chan + 8];
m_regdata[0x120 + chan + 16] = m_regdata[0x160 + chan + 16];
m_regdata[0x120 + chan + 24] = m_regdata[0x160 + chan + 24];
}
// store the presets under some unknown condition; the pattern of writes
// when setting a new preset is:
//
// 08 (0-7), 80-9F, A0-BF, C0-DF, C0-DF (alt), 20-27, 40-5F, 40-5F (alt),
// C0-DF (alt -- again?), 38-3F, 1B, 18, E0-FF
//
// So it writes 0-7 to 08 to either reset all presets or to indicate
// that we're going to be loading them. Immediately after all the writes
// above, the very next write will be temporary values to blow away the
// values loaded into E0-FF, so somehow it also knows that anything after
// that point is not part of the preset.
//
// For now, try using the 40-5F (alt) writes as flags that presets are
// being loaded until the E0-FF writes happen.
bool is_setting_preset = (bitfield(m_regdata[0x100 + (index & 0x1f)], 7) != 0);
if (is_setting_preset)
{
if ((index & 0xe0) == 0xe0)
{
m_regdata[0x140 + (index & 0x1f)] = data;
m_regdata[0x100 + (index & 0x1f)] &= 0x7f;
}
else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0)
m_regdata[0x160 + (index & 0x1f)] = data;
}
// handle writes to the key on index
if ((index & 0xf8) == 0x20 && bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3))
{
channel = bitfield(index, 0, 3);
opmask = ch_key_on(channel) ? 0xf : 0;
// according to the TX81Z manual, the sync option causes the LFOs
// to reset at each note on
if (opmask != 0)
{
if (lfo_sync())
m_lfo_counter[0] = 0;
if (lfo2_sync())
m_lfo_counter[1] = 0;
}
return true;
}
return false;
}
//-------------------------------------------------
// clock_noise_and_lfo - clock the noise and LFO,
// handling clock division, depth, and waveform
// computations
//-------------------------------------------------
int32_t opz_registers::clock_noise_and_lfo()
{
// base noise frequency is measured at 2x 1/2 FM frequency; this
// means each tick counts as two steps against the noise counter
uint32_t freq = noise_frequency();
for (int rep = 0; rep < 2; rep++)
{
// evidence seems to suggest the LFSR is clocked continually and just
// sampled at the noise frequency for output purposes; note that the
// low 8 bits are the most recent 8 bits of history while bits 8-24
// contain the 17 bit LFSR state
m_noise_lfsr <<= 1;
m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1;
// compare against the frequency and latch when we exceed it
if (m_noise_counter++ >= freq)
{
m_noise_counter = 0;
m_noise_state = bitfield(m_noise_lfsr, 17);
}
}
// treat the rate as a 4.4 floating-point step value with implied
// leading 1; this matches exactly the frequencies in the application
// manual, though it might not be implemented exactly this way on chip
uint32_t rate0 = lfo_rate();
uint32_t rate1 = lfo2_rate();
m_lfo_counter[0] += (0x10 | bitfield(rate0, 0, 4)) << bitfield(rate0, 4, 4);
m_lfo_counter[1] += (0x10 | bitfield(rate1, 0, 4)) << bitfield(rate1, 4, 4);
uint32_t lfo0 = bitfield(m_lfo_counter[0], 22, 8);
uint32_t lfo1 = bitfield(m_lfo_counter[1], 22, 8);
// fill in the noise entry 1 ahead of our current position; this
// ensures the current value remains stable for a full LFO clock
// and effectively latches the running value when the LFO advances
uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8);
m_lfo_waveform[3][(lfo0 + 1) & 0xff] = lfo_noise | (lfo_noise << 8);
m_lfo_waveform[3][(lfo1 + 1) & 0xff] = lfo_noise | (lfo_noise << 8);
// fetch the AM/PM values based on the waveform; AM is unsigned and
// encoded in the low 8 bits, while PM signed and encoded in the upper
// 8 bits
int32_t ampm0 = m_lfo_waveform[lfo_waveform()][lfo0];
int32_t ampm1 = m_lfo_waveform[lfo2_waveform()][lfo1];
// apply depth to the AM values and store for later
m_lfo_am[0] = ((ampm0 & 0xff) * lfo_am_depth()) >> 7;
m_lfo_am[1] = ((ampm1 & 0xff) * lfo2_am_depth()) >> 7;
// apply depth to the PM values and return them combined into two
int32_t pm0 = ((ampm0 >> 8) * int32_t(lfo_pm_depth())) >> 7;
int32_t pm1 = ((ampm1 >> 8) * int32_t(lfo2_pm_depth())) >> 7;
return (pm0 & 0xff) | (pm1 << 8);
}
//-------------------------------------------------
// lfo_am_offset - return the AM offset from LFO
// for the given channel
//-------------------------------------------------
uint32_t opz_registers::lfo_am_offset(uint32_t choffs) const
{
// not sure how this works for real, but just adding the two
// AM LFOs together
uint32_t result = 0;
// shift value for AM sensitivity is [*, 0, 1, 2],
// mapping to values of [0, 23.9, 47.8, and 95.6dB]
uint32_t am_sensitivity = ch_lfo_am_sens(choffs);
if (am_sensitivity != 0)
result = m_lfo_am[0] << (am_sensitivity - 1);
// QUESTION: see OPN note below for the dB range mapping; it applies
// here as well
// raw LFO AM value on OPZ is 0-FF, which is already a factor of 2
// larger than the OPN below, putting our staring point at 2x theirs;
// this works out since our minimum is 2x their maximum
uint32_t am_sensitivity2 = ch_lfo2_am_sens(choffs);
if (am_sensitivity2 != 0)
result += m_lfo_am[1] << (am_sensitivity2 - 1);
return result;
}
//-------------------------------------------------
// cache_operator_data - fill the operator cache
// with prefetched data
//-------------------------------------------------
void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
{
// TODO: how does fixed frequency mode work? appears to be enabled by
// op_fix_mode(), and controlled by op_fix_range(), op_fix_frequency()
// TODO: what is op_rev()?
// set up the easy stuff
cache.waveform = &m_waveform[op_waveform(opoffs)][0];
// get frequency from the channel
uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
// compute the keycode: block_freq is:
//
// BBBCCCCFFFFFF
// ^^^^^
//
// the 5-bit keycode is just the top 5 bits (block + top 2 bits
// of the key code)
uint32_t keycode = bitfield(block_freq, 8, 5);
// detune adjustment
cache.detune = detune_adjustment(op_detune(opoffs), keycode);
// multiple value, as an x.4 value (0 means 0.5)
// the "fine" control provides the fractional bits
cache.multiple = op_multiple(opoffs) << 4;
if (cache.multiple == 0)
cache.multiple = 0x08;
cache.multiple |= op_fine(opoffs);
// phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
// block_freq, detune, and multiple, so compute it after we've done those;
// note that fix frequency mode is also treated as dynamic
if (!op_fix_mode(opoffs) && (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0) && (lfo2_pm_depth() == 0 || ch_lfo2_pm_sens(choffs) == 0))
cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
else
cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
// total level, scaled by 8
// TODO: how does ch_volume() fit into this?
cache.total_level = op_total_level(opoffs) << 3;
// 4-bit sustain level, but 15 means 31 so effectively 5 bits
cache.eg_sustain = op_sustain_level(opoffs);
cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
cache.eg_sustain <<= 5;
// determine KSR adjustment for enevlope rates
uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
cache.eg_rate[EG_REVERB] = cache.eg_rate[EG_RELEASE];
uint32_t reverb = op_reverb_rate(opoffs);
if (reverb != 0)
cache.eg_rate[EG_REVERB] = std::min<uint32_t>(effective_rate(reverb * 4 + 2, ksrval), cache.eg_rate[EG_REVERB]);
// set the envelope shift; TX81Z manual says operator 1 shift is fixed at "off"
cache.eg_shift = ((opoffs & 0x18) == 0) ? 0 : op_eg_shift(opoffs);
}
//-------------------------------------------------
// compute_phase_step - compute the phase step
//-------------------------------------------------
uint32_t opz_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
{
// OPZ has a fixed frequency mode; it is unclear whether the
// detune and multiple parameters affect things
uint32_t phase_step;
if (op_fix_mode(opoffs))
{
// the baseline frequency in hz comes from the fix frequency and fine
// registers, which can specify values 8-255Hz in 1Hz increments; that
// value is then shifted up by the 3-bit range
uint32_t freq = op_fix_frequency(opoffs) << 4;
if (freq == 0)
freq = 8;
freq |= op_fine(opoffs);
freq <<= op_fix_range(opoffs);
// there is not enough resolution in the plain phase step to track the
// full range of frequencies, so we keep a per-operator sub step with an
// additional 12 bits of resolution; this calculation gives us, for
// example, a frequency of 8.0009Hz when 8Hz is requested
uint32_t substep = m_phase_substep[opoffs];
substep += 75 * freq;
phase_step = substep >> 12;
m_phase_substep[opoffs] = substep & 0xfff;
// detune/multiple occupy the same space as fix_range/fix_frequency so
// don't apply them in addition
return phase_step;
}
else
{
// start with coarse detune delta; table uses cents value from
// manual, converted into 1/64ths
static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 };
int32_t delta = s_detune2_delta[op_detune2(opoffs)];
// add in the PM deltas
uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
if (pm_sensitivity != 0)
{
// raw PM value is -127..128 which is +/- 200 cents
// manual gives these magnitudes in cents:
// 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700
// this roughly corresponds to shifting the 200-cent value:
// 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2
if (pm_sensitivity < 6)
delta += int8_t(lfo_raw_pm) >> (6 - pm_sensitivity);
else
delta += int8_t(lfo_raw_pm) << (pm_sensitivity - 5);
}
uint32_t pm_sensitivity2 = ch_lfo2_pm_sens(choffs);
if (pm_sensitivity2 != 0)
{
// raw PM value is -127..128 which is +/- 200 cents
// manual gives these magnitudes in cents:
// 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700
// this roughly corresponds to shifting the 200-cent value:
// 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2
if (pm_sensitivity2 < 6)
delta += int8_t(lfo_raw_pm >> 8) >> (6 - pm_sensitivity2);
else
delta += int8_t(lfo_raw_pm >> 8) << (pm_sensitivity2 - 5);
}
// apply delta and convert to a frequency number; this translation is
// the same as OPM so just re-use that helper
phase_step = opm_key_code_to_phase_step(cache.block_freq, delta);
// apply detune based on the keycode
phase_step += cache.detune;
// apply frequency multiplier (which is cached as an x.4 value)
return (phase_step * cache.multiple) >> 4;
}
}
//-------------------------------------------------
// log_keyon - log a key-on event
//-------------------------------------------------
std::string opz_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
{
uint32_t chnum = choffs;
uint32_t opnum = opoffs;
char buffer[256];
int end = 0;
end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u", chnum, opnum);
if (op_fix_mode(opoffs))
end += snprintf(&buffer[end], sizeof(buffer) - end, " fixfreq=%X fine=%X shift=%X", op_fix_frequency(opoffs), op_fine(opoffs), op_fix_range(opoffs));
else
end += snprintf(&buffer[end], sizeof(buffer) - end, " freq=%04X dt2=%u fine=%X", ch_block_freq(choffs), op_detune2(opoffs), op_fine(opoffs));
end += snprintf(&buffer[end], sizeof(buffer) - end, " dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
op_detune(opoffs),
ch_feedback(choffs),
ch_algorithm(choffs),
op_multiple(opoffs),
op_total_level(opoffs),
op_ksr(opoffs),
op_attack_rate(opoffs),
op_decay_rate(opoffs),
op_sustain_rate(opoffs),
op_release_rate(opoffs),
op_sustain_level(opoffs),
ch_output_0(choffs) ? 'L' : '-',
ch_output_1(choffs) ? 'R' : '-');
if (op_eg_shift(opoffs) != 0)
end += snprintf(&buffer[end], sizeof(buffer) - end, " egshift=%u", op_eg_shift(opoffs));
bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
if (am)
end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0);
if (pm)
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
if (am || pm)
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
bool am2 = (lfo2_am_depth() != 0 && ch_lfo2_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
if (am2)
end += snprintf(&buffer[end], sizeof(buffer) - end, " am2=%u/%02X", ch_lfo2_am_sens(choffs), lfo2_am_depth());
bool pm2 = (lfo2_pm_depth() != 0 && ch_lfo2_pm_sens(choffs) != 0);
if (pm2)
end += snprintf(&buffer[end], sizeof(buffer) - end, " pm2=%u/%02X", ch_lfo2_pm_sens(choffs), lfo2_pm_depth());
if (am2 || pm2)
end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo2=%02X/%c", lfo2_rate(), "WQTN"[lfo2_waveform()]);
if (op_reverb_rate(opoffs) != 0)
end += snprintf(&buffer[end], sizeof(buffer) - end, " rev=%u", op_reverb_rate(opoffs));
if (op_waveform(opoffs) != 0)
end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=%u", op_waveform(opoffs));
if (noise_enable() && opoffs == 31)
end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1");
return buffer;
}
//*********************************************************
// YM2414
//*********************************************************
//-------------------------------------------------
// ym2414 - constructor
//-------------------------------------------------
ym2414::ym2414(ymfm_interface &intf) :
m_address(0),
m_fm(intf)
{
}
//-------------------------------------------------
// reset - reset the system
//-------------------------------------------------
void ym2414::reset()
{
// reset the engines
m_fm.reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void ym2414::save_restore(ymfm_saved_state &state)
{
m_fm.save_restore(state);
state.save_restore(m_address);
}
//-------------------------------------------------
// read_status - read the status register
//-------------------------------------------------
uint8_t ym2414::read_status()
{
uint8_t result = m_fm.status();
if (m_fm.intf().ymfm_is_busy())
result |= fm_engine::STATUS_BUSY;
return result;
}
//-------------------------------------------------
// read - handle a read from the device
//-------------------------------------------------
uint8_t ym2414::read(uint32_t offset)
{
uint8_t result = 0xff;
switch (offset & 1)
{
case 0: // data port (unused)
debug::log_unexpected_read_write("Unexpected read from YM2414 offset %d\n", offset & 3);
break;
case 1: // status port, YM2203 compatible
result = read_status();
break;
}
return result;
}
//-------------------------------------------------
// write_address - handle a write to the address
// register
//-------------------------------------------------
void ym2414::write_address(uint8_t data)
{
// just set the address
m_address = data;
}
//-------------------------------------------------
// write - handle a write to the register
// interface
//-------------------------------------------------
void ym2414::write_data(uint8_t data)
{
// write the FM register
m_fm.write(m_address, data);
if (TEMPORARY_DEBUG_PRINTS)
{
switch (m_address & 0xe0)
{
case 0x00:
printf("CTL %02X = %02X\n", m_address, data);
break;
case 0x20:
switch (m_address & 0xf8)
{
case 0x20: printf("R/FBL/ALG %d = %02X\n", m_address & 7, data); break;
case 0x28: printf("KC %d = %02X\n", m_address & 7, data); break;
case 0x30: printf("KF/M %d = %02X\n", m_address & 7, data); break;
case 0x38: printf("PMS/AMS %d = %02X\n", m_address & 7, data); break;
}
break;
case 0x40:
if (bitfield(data, 7) == 0)
printf("DT1/MUL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
else
printf("OW/FINE %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
break;
case 0x60:
printf("TL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
break;
case 0x80:
printf("KRS/FIX/AR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
break;
case 0xa0:
printf("A/D1R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
break;
case 0xc0:
if (bitfield(data, 5) == 0)
printf("DT2/D2R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
else
printf("EGS/REV %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
break;
case 0xe0:
printf("D1L/RR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
break;
}
}
// special cases
if (m_address == 0x1b)
{
// writes to register 0x1B send the upper 2 bits to the output lines
m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6);
}
// mark busy for a bit
m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
}
//-------------------------------------------------
// write - handle a write to the register
// interface
//-------------------------------------------------
void ym2414::write(uint32_t offset, uint8_t data)
{
switch (offset & 1)
{
case 0: // address port
write_address(data);
break;
case 1: // data port
write_data(data);
break;
}
}
//-------------------------------------------------
// generate - generate one sample of sound
//-------------------------------------------------
void ym2414::generate(output_data *output, uint32_t numsamples)
{
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
{
// clock the system
m_fm.clock(fm_engine::ALL_CHANNELS);
// update the FM content; YM2414 is full 14-bit with no intermediate clipping
m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
// unsure about YM2414 outputs; assume it is like YM2151
output->roundtrip_fp();
}
}
}

View file

@ -0,0 +1,332 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_OPZ_H
#define YMFM_OPZ_H
#pragma once
#include "ymfm.h"
#include "ymfm_fm.h"
namespace ymfm
{
//*********************************************************
// REGISTER CLASSES
//*********************************************************
// ======================> opz_registers
//
// OPZ register map:
//
// System-wide registers:
// 08 -----xxx Load preset (not sure how it gets saved)
// 0F x------- Noise enable
// ---xxxxx Noise frequency
// 10 xxxxxxxx Timer A value (upper 8 bits)
// 11 ------xx Timer A value (lower 2 bits)
// 12 xxxxxxxx Timer B value
// 14 x------- CSM mode
// --x----- Reset timer B
// ---x---- Reset timer A
// ----x--- Enable timer B
// -----x-- Enable timer A
// ------x- Load timer B
// -------x Load timer A
// 16 xxxxxxxx LFO #2 frequency
// 17 0xxxxxxx AM LFO #2 depth
// 1xxxxxxx PM LFO #2 depth
// 18 xxxxxxxx LFO frequency
// 19 0xxxxxxx AM LFO depth
// 1xxxxxxx PM LFO depth
// 1B xx------ CT (2 output data lines)
// --x----- LFO #2 sync
// ---x---- LFO sync
// ----xx-- LFO #2 waveform
// ------xx LFO waveform
//
// Per-channel registers (channel in address bits 0-2)
// 00-07 xxxxxxxx Channel volume
// 20-27 x------- Pan right
// -x------ Key on (0)/off(1)
// --xxx--- Feedback level for operator 1 (0-7)
// -----xxx Operator connection algorithm (0-7)
// 28-2F -xxxxxxx Key code
// 30-37 xxxxxx-- Key fraction
// -------x Mono? mode
// 38-3F 0xxx---- LFO PM sensitivity
// -----0xx LFO AM shift
// 1xxx---- LFO #2 PM sensitivity
// -----1xx LFO #2 AM shift
//
// Per-operator registers (channel in address bits 0-2, operator in bits 3-4)
// 40-5F 0xxx---- Detune value (0-7)
// 0---xxxx Multiple value (0-15)
// 0xxx---- Fix range (0-15)
// 0---xxxx Fix frequency (0-15)
// 1xxx---- Oscillator waveform (0-7)
// 1---xxxx Fine? (0-15)
// 60-7F -xxxxxxx Total level (0-127)
// 80-9F xx------ Key scale rate (0-3)
// --x----- Fix frequency mode
// ---xxxxx Attack rate (0-31)
// A0-BF x------- LFO AM enable
// ---xxxxx Decay rate (0-31)
// C0-DF xx0----- Detune 2 value (0-3)
// --0xxxxx Sustain rate (0-31)
// xx1----- Envelope generator shift? (0-3)
// --1--xxx Rev? (0-7)
// E0-FF xxxx---- Sustain level (0-15)
// ----xxxx Release rate (0-15)
//
// Internal (fake) registers:
// 100-11F -xxx---- Oscillator waveform (0-7)
// ----xxxx Fine? (0-15)
// 120-13F xx------ Envelope generator shift (0-3)
// -----xxx Reverb rate (0-7)
// 140-15F xxxx---- Preset sustain level (0-15)
// ----xxxx Preset release rate (0-15)
// 160-17F xx------ Envelope generator shift (0-3)
// -----xxx Reverb rate (0-7)
// 180-187 -xxx---- LFO #2 PM sensitivity
// ---- xxx LFO #2 AM shift
// 188 -xxxxxxx LFO #2 PM depth
// 189 -xxxxxxx LFO PM depth
//
class opz_registers : public fm_registers_base
{
// LFO waveforms are 256 entries long
static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256;
public:
// constants
static constexpr uint32_t OUTPUTS = 2;
static constexpr uint32_t CHANNELS = 8;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
static constexpr uint32_t OPERATORS = CHANNELS * 4;
static constexpr uint32_t WAVEFORMS = 8;
static constexpr uint32_t REGISTERS = 0x190;
static constexpr uint32_t DEFAULT_PRESCALE = 2;
static constexpr uint32_t EG_CLOCK_DIVIDER = 3;
static constexpr bool EG_HAS_REVERB = true;
static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
static constexpr uint32_t REG_MODE = 0x14;
static constexpr uint8_t STATUS_TIMERA = 0x01;
static constexpr uint8_t STATUS_TIMERB = 0x02;
static constexpr uint8_t STATUS_BUSY = 0x80;
static constexpr uint8_t STATUS_IRQ = 0;
// constructor
opz_registers();
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
return chnum;
}
// map operator number to register offset
static constexpr uint32_t operator_offset(uint32_t opnum)
{
assert(opnum < OPERATORS);
return opnum;
}
// return an array of operator indices for each channel
struct operator_mapping { uint32_t chan[CHANNELS]; };
void operator_map(operator_mapping &dest) const;
// handle writes to the register array
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
// clock the noise and LFO, if present, returning LFO PM value
int32_t clock_noise_and_lfo();
// return the AM offset from LFO for the given channel
uint32_t lfo_am_offset(uint32_t choffs) const;
// return the current noise state, gated by the noise clock
uint32_t noise_state() const { return m_noise_state; }
// caching helpers
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
// compute the phase step, given a PM value
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
// log a key-on event
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
// system-wide registers
uint32_t noise_frequency() const { return byte(0x0f, 0, 5); }
uint32_t noise_enable() const { return byte(0x0f, 7, 1); }
uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); }
uint32_t timer_b_value() const { return byte(0x12, 0, 8); }
uint32_t csm() const { return byte(0x14, 7, 1); }
uint32_t reset_timer_b() const { return byte(0x14, 5, 1); }
uint32_t reset_timer_a() const { return byte(0x14, 4, 1); }
uint32_t enable_timer_b() const { return byte(0x14, 3, 1); }
uint32_t enable_timer_a() const { return byte(0x14, 2, 1); }
uint32_t load_timer_b() const { return byte(0x14, 1, 1); }
uint32_t load_timer_a() const { return byte(0x14, 0, 1); }
uint32_t lfo2_pm_depth() const { return byte(0x188, 0, 7); } // fake
uint32_t lfo2_rate() const { return byte(0x16, 0, 8); }
uint32_t lfo2_am_depth() const { return byte(0x17, 0, 7); }
uint32_t lfo_rate() const { return byte(0x18, 0, 8); }
uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); }
uint32_t lfo_pm_depth() const { return byte(0x189, 0, 7); } // fake
uint32_t output_bits() const { return byte(0x1b, 6, 2); }
uint32_t lfo2_sync() const { return byte(0x1b, 5, 1); }
uint32_t lfo_sync() const { return byte(0x1b, 4, 1); }
uint32_t lfo2_waveform() const { return byte(0x1b, 2, 2); }
uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); }
// per-channel registers
uint32_t ch_volume(uint32_t choffs) const { return byte(0x00, 0, 8, choffs); }
uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); }
uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs); }
uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); }
uint32_t ch_output_2(uint32_t choffs) const { return 0; }
uint32_t ch_output_3(uint32_t choffs) const { return 0; }
uint32_t ch_key_on(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); }
uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); }
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); }
uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); }
uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); }
uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); }
uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x180, 4, 3, choffs); } // fake
uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x180, 0, 2, choffs); } // fake
// per-operator registers
uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
uint32_t op_fix_range(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
uint32_t op_fix_frequency(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
uint32_t op_waveform(uint32_t opoffs) const { return byte(0x100, 4, 3, opoffs); } // fake
uint32_t op_fine(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } // fake
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); }
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); }
uint32_t op_fix_mode(uint32_t opoffs) const { return byte(0x80, 5, 1, opoffs); }
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); }
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); }
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); }
uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); }
uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); }
uint32_t op_eg_shift(uint32_t opoffs) const { return byte(0x120, 6, 2, opoffs); } // fake
uint32_t op_reverb_rate(uint32_t opoffs) const { return byte(0x120, 0, 3, opoffs); } // fake
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); }
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); }
protected:
// return a bitfield extracted from a byte
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
{
return bitfield(m_regdata[offset + extra_offset], start, count);
}
// return a bitfield extracted from a pair of bytes, MSBs listed first
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
{
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
}
// internal state
uint32_t m_lfo_counter[2]; // LFO counter
uint32_t m_noise_lfsr; // noise LFSR state
uint8_t m_noise_counter; // noise counter
uint8_t m_noise_state; // latched noise state
uint8_t m_noise_lfo; // latched LFO noise value
uint8_t m_lfo_am[2]; // current LFO AM value
uint8_t m_regdata[REGISTERS]; // register data
uint16_t m_phase_substep[OPERATORS]; // phase substep for fixed frequency
int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
};
//*********************************************************
// IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym2414
class ym2414
{
public:
using fm_engine = fm_engine_base<opz_registers>;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
using output_data = fm_engine::output_data;
// constructor
ym2414(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint8_t m_address; // address register
fm_engine m_fm; // core FM engine
};
}
#endif // YMFM_OPZ_H

View file

@ -0,0 +1,714 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ymfm_pcm.h"
#include "ymfm_fm.h"
#include "ymfm_fm.ipp"
namespace ymfm
{
//*********************************************************
// PCM REGISTERS
//*********************************************************
//-------------------------------------------------
// reset - reset the register state
//-------------------------------------------------
void pcm_registers::reset()
{
std::fill_n(&m_regdata[0], REGISTERS, 0);
m_regdata[0xf8] = 0x1b;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void pcm_registers::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_regdata);
}
//-------------------------------------------------
// cache_channel_data - update the cache with
// data from the registers
//-------------------------------------------------
void pcm_registers::cache_channel_data(uint32_t choffs, pcm_cache &cache)
{
// compute step from octave and fnumber; the math here implies
// a .18 fraction but .16 should be perfectly fine
int32_t octave = int8_t(ch_octave(choffs) << 4) >> 4;
uint32_t fnum = ch_fnumber(choffs);
cache.step = ((0x400 | fnum) << (octave + 7)) >> 2;
// total level is computed as a .10 value for interpolation
cache.total_level = ch_total_level(choffs) << 10;
// compute panning values in terms of envelope attenuation
int32_t panpot = int8_t(ch_panpot(choffs) << 4) >> 4;
if (panpot >= 0)
{
cache.pan_left = (panpot == 7) ? 0x3ff : 0x20 * panpot;
cache.pan_right = 0;
}
else if (panpot >= -7)
{
cache.pan_left = 0;
cache.pan_right = (panpot == -7) ? 0x3ff : -0x20 * panpot;
}
else
cache.pan_left = cache.pan_right = 0x3ff;
// determine the LFO stepping value; this how much to add to a running
// x.18 value for the LFO; steps were derived from frequencies in the
// manual and come out very close with these values
static const uint8_t s_lfo_steps[8] = { 1, 12, 19, 25, 31, 35, 37, 42 };
cache.lfo_step = s_lfo_steps[ch_lfo_speed(choffs)];
// AM LFO depth values, derived from the manual; note each has at most
// 2 bits to make the "multiply" easy in hardware
static const uint8_t s_am_depth[8] = { 0, 0x14, 0x20, 0x28, 0x30, 0x40, 0x50, 0x80 };
cache.am_depth = s_am_depth[ch_am_depth(choffs)];
// PM LFO depth values; these are converted from the manual's cents values
// into f-numbers; the computations come out quite cleanly so pretty sure
// these are correct
static const uint8_t s_pm_depth[8] = { 0, 2, 3, 4, 6, 12, 24, 48 };
cache.pm_depth = s_pm_depth[ch_vibrato(choffs)];
// 4-bit sustain level, but 15 means 31 so effectively 5 bits
cache.eg_sustain = ch_sustain_level(choffs);
cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
cache.eg_sustain <<= 5;
// compute the key scaling correction factor; 15 means don't do any correction
int32_t correction = ch_rate_correction(choffs);
if (correction == 15)
correction = 0;
else
correction = (octave + correction) * 2 + bitfield(fnum, 9);
// compute the envelope generator rates
cache.eg_rate[EG_ATTACK] = effective_rate(ch_attack_rate(choffs), correction);
cache.eg_rate[EG_DECAY] = effective_rate(ch_decay_rate(choffs), correction);
cache.eg_rate[EG_SUSTAIN] = effective_rate(ch_sustain_rate(choffs), correction);
cache.eg_rate[EG_RELEASE] = effective_rate(ch_release_rate(choffs), correction);
cache.eg_rate[EG_REVERB] = 5;
// if damping is on, override some things; essentially decay at a hardcoded
// rate of 48 until -12db (0x80), then at maximum rate for the rest
if (ch_damp(choffs) != 0)
{
cache.eg_rate[EG_DECAY] = 48;
cache.eg_rate[EG_SUSTAIN] = 63;
cache.eg_rate[EG_RELEASE] = 63;
cache.eg_sustain = 0x80;
}
}
//-------------------------------------------------
// effective_rate - return the effective rate,
// clamping and applying corrections as needed
//-------------------------------------------------
uint32_t pcm_registers::effective_rate(uint32_t raw, uint32_t correction)
{
// raw rates of 0 and 15 just pin to min/max
if (raw == 0)
return 0;
if (raw == 15)
return 63;
// otherwise add the correction and clamp to range
return clamp(raw * 4 + correction, 0, 63);
}
//*********************************************************
// PCM CHANNEL
//*********************************************************
//-------------------------------------------------
// pcm_channel - constructor
//-------------------------------------------------
pcm_channel::pcm_channel(pcm_engine &owner, uint32_t choffs) :
m_choffs(choffs),
m_baseaddr(0),
m_endpos(0),
m_looppos(0),
m_curpos(0),
m_nextpos(0),
m_lfo_counter(0),
m_eg_state(EG_RELEASE),
m_env_attenuation(0x3ff),
m_total_level(0x7f << 10),
m_format(0),
m_key_state(0),
m_regs(owner.regs()),
m_owner(owner)
{
}
//-------------------------------------------------
// reset - reset the channel state
//-------------------------------------------------
void pcm_channel::reset()
{
m_baseaddr = 0;
m_endpos = 0;
m_looppos = 0;
m_curpos = 0;
m_nextpos = 0;
m_lfo_counter = 0;
m_eg_state = EG_RELEASE;
m_env_attenuation = 0x3ff;
m_total_level = 0x7f << 10;
m_format = 0;
m_key_state = 0;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void pcm_channel::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_baseaddr);
state.save_restore(m_endpos);
state.save_restore(m_looppos);
state.save_restore(m_curpos);
state.save_restore(m_nextpos);
state.save_restore(m_lfo_counter);
state.save_restore(m_eg_state);
state.save_restore(m_env_attenuation);
state.save_restore(m_total_level);
state.save_restore(m_format);
state.save_restore(m_key_state);
}
//-------------------------------------------------
// prepare - prepare for clocking
//-------------------------------------------------
bool pcm_channel::prepare()
{
// cache the data
m_regs.cache_channel_data(m_choffs, m_cache);
// clock the key state
if ((m_key_state & KEY_PENDING) != 0)
{
uint8_t oldstate = m_key_state;
m_key_state = (m_key_state >> 1) & KEY_ON;
if (((oldstate ^ m_key_state) & KEY_ON) != 0)
{
if ((m_key_state & KEY_ON) != 0)
start_attack();
else
start_release();
}
}
// set the total level directly if not interpolating
if (m_regs.ch_level_direct(m_choffs))
m_total_level = m_cache.total_level;
// we're active until we're quiet after the release
return (m_eg_state < EG_RELEASE || m_env_attenuation < EG_QUIET);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
void pcm_channel::clock(uint32_t env_counter)
{
// clock the LFO, which is an x.18 value incremented based on the
// LFO speed value
m_lfo_counter += m_cache.lfo_step;
// clock the envelope
clock_envelope(env_counter);
// determine the step after applying vibrato
uint32_t step = m_cache.step;
if (m_cache.pm_depth != 0)
{
// shift the LFO by 1/4 cycle for PM so that it starts at 0
uint32_t lfo_shifted = m_lfo_counter + (1 << 16);
int32_t lfo_value = bitfield(lfo_shifted, 10, 7);
if (bitfield(lfo_shifted, 17) != 0)
lfo_value ^= 0x7f;
lfo_value -= 0x40;
step += (lfo_value * int32_t(m_cache.pm_depth)) >> 7;
}
// advance the sample step and loop as needed
m_curpos = m_nextpos;
m_nextpos = m_curpos + step;
if (m_nextpos >= m_endpos)
m_nextpos += m_looppos - m_endpos;
// interpolate total level if needed
if (m_total_level != m_cache.total_level)
{
// max->min volume takes 156.4ms, or pretty close to 19/1024 per 44.1kHz sample
// min->max volume is half that, so advance by 38/1024 per sample
if (m_total_level < m_cache.total_level)
m_total_level = std::min<int32_t>(m_total_level + 19, m_cache.total_level);
else
m_total_level = std::max<int32_t>(m_total_level - 38, m_cache.total_level);
}
}
//-------------------------------------------------
// output - return the computed output value, with
// panning applied
//-------------------------------------------------
void pcm_channel::output(output_data &output) const
{
// early out if the envelope is effectively off
uint32_t envelope = m_env_attenuation;
if (envelope > EG_QUIET)
return;
// add in LFO AM modulation
if (m_cache.am_depth != 0)
{
uint32_t lfo_value = bitfield(m_lfo_counter, 10, 7);
if (bitfield(m_lfo_counter, 17) != 0)
lfo_value ^= 0x7f;
envelope += (lfo_value * m_cache.am_depth) >> 7;
}
// add in the current interpolated total level value, which is a .10
// value shifted left by 2
envelope += m_total_level >> 8;
// add in panning effect and clamp
uint32_t lenv = std::min<uint32_t>(envelope + m_cache.pan_left, 0x3ff);
uint32_t renv = std::min<uint32_t>(envelope + m_cache.pan_right, 0x3ff);
// convert to volume as a .11 fraction
int32_t lvol = attenuation_to_volume(lenv << 2);
int32_t rvol = attenuation_to_volume(renv << 2);
// fetch current sample and add
int16_t sample = fetch_sample();
uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2;
output.data[outnum + 0] += (lvol * sample) >> 15;
output.data[outnum + 1] += (rvol * sample) >> 15;
}
//-------------------------------------------------
// keyonoff - signal key on/off
//-------------------------------------------------
void pcm_channel::keyonoff(bool on)
{
// mark the key state as pending
m_key_state |= KEY_PENDING | (on ? KEY_PENDING_ON : 0);
// don't log masked channels
if ((m_key_state & (KEY_PENDING_ON | KEY_ON)) == KEY_PENDING_ON && ((debug::GLOBAL_PCM_CHANNEL_MASK >> m_choffs) & 1) != 0)
{
debug::log_keyon("KeyOn PCM-%02d: num=%3d oct=%2d fnum=%03X level=%02X%c ADSR=%X/%X/%X/%X SL=%X",
m_choffs,
m_regs.ch_wave_table_num(m_choffs),
int8_t(m_regs.ch_octave(m_choffs) << 4) >> 4,
m_regs.ch_fnumber(m_choffs),
m_regs.ch_total_level(m_choffs),
m_regs.ch_level_direct(m_choffs) ? '!' : '/',
m_regs.ch_attack_rate(m_choffs),
m_regs.ch_decay_rate(m_choffs),
m_regs.ch_sustain_rate(m_choffs),
m_regs.ch_release_rate(m_choffs),
m_regs.ch_sustain_level(m_choffs));
if (m_regs.ch_rate_correction(m_choffs) != 15)
debug::log_keyon(" RC=%X", m_regs.ch_rate_correction(m_choffs));
if (m_regs.ch_pseudo_reverb(m_choffs) != 0)
debug::log_keyon(" %s", "REV");
if (m_regs.ch_damp(m_choffs) != 0)
debug::log_keyon(" %s", "DAMP");
if (m_regs.ch_vibrato(m_choffs) != 0 || m_regs.ch_am_depth(m_choffs) != 0)
{
if (m_regs.ch_vibrato(m_choffs) != 0)
debug::log_keyon(" VIB=%d", m_regs.ch_vibrato(m_choffs));
if (m_regs.ch_am_depth(m_choffs) != 0)
debug::log_keyon(" AM=%d", m_regs.ch_am_depth(m_choffs));
debug::log_keyon(" LFO=%d", m_regs.ch_lfo_speed(m_choffs));
}
debug::log_keyon("%s", "\n");
}
}
//-------------------------------------------------
// load_wavetable - load a wavetable by fetching
// its data from external memory
//-------------------------------------------------
void pcm_channel::load_wavetable()
{
// determine the address of the wave table header
uint32_t wavnum = m_regs.ch_wave_table_num(m_choffs);
uint32_t wavheader = 12 * wavnum;
// above 384 it may be in a different bank
if (wavnum >= 384)
{
uint32_t bank = m_regs.wave_table_header();
if (bank != 0)
wavheader = 512*1024 * bank + (wavnum - 384) * 12;
}
// fetch the 22-bit base address and 2-bit format
uint8_t byte = read_pcm(wavheader + 0);
m_format = bitfield(byte, 6, 2);
m_baseaddr = bitfield(byte, 0, 6) << 16;
m_baseaddr |= read_pcm(wavheader + 1) << 8;
m_baseaddr |= read_pcm(wavheader + 2) << 0;
// fetch the 16-bit loop position
m_looppos = read_pcm(wavheader + 3) << 8;
m_looppos |= read_pcm(wavheader + 4);
m_looppos <<= 16;
// fetch the 16-bit end position, which is stored as a negative value
// for some reason that is unclear
m_endpos = read_pcm(wavheader + 5) << 8;
m_endpos |= read_pcm(wavheader + 6);
m_endpos = -int32_t(m_endpos) << 16;
// remaining data values set registers
m_owner.write(0x80 + m_choffs, read_pcm(wavheader + 7));
m_owner.write(0x98 + m_choffs, read_pcm(wavheader + 8));
m_owner.write(0xb0 + m_choffs, read_pcm(wavheader + 9));
m_owner.write(0xc8 + m_choffs, read_pcm(wavheader + 10));
m_owner.write(0xe0 + m_choffs, read_pcm(wavheader + 11));
// reset the envelope so we don't continue playing mid-sample from previous key ons
m_env_attenuation = 0x3ff;
}
//-------------------------------------------------
// read_pcm - read a byte from the external PCM
// memory interface
//-------------------------------------------------
uint8_t pcm_channel::read_pcm(uint32_t address) const
{
return m_owner.intf().ymfm_external_read(ACCESS_PCM, address);
}
//-------------------------------------------------
// start_attack - start the attack phase
//-------------------------------------------------
void pcm_channel::start_attack()
{
// don't change anything if already in attack state
if (m_eg_state == EG_ATTACK)
return;
m_eg_state = EG_ATTACK;
// reset the LFO if requested
if (m_regs.ch_lfo_reset(m_choffs))
m_lfo_counter = 0;
// if the attack rate == 63 then immediately go to max attenuation
if (m_cache.eg_rate[EG_ATTACK] == 63)
m_env_attenuation = 0;
// reset the positions
m_curpos = m_nextpos = 0;
}
//-------------------------------------------------
// start_release - start the release phase
//-------------------------------------------------
void pcm_channel::start_release()
{
// don't change anything if already in release or reverb state
if (m_eg_state >= EG_RELEASE)
return;
m_eg_state = EG_RELEASE;
}
//-------------------------------------------------
// clock_envelope - clock the envelope generator
//-------------------------------------------------
void pcm_channel::clock_envelope(uint32_t env_counter)
{
// handle attack->decay transitions
if (m_eg_state == EG_ATTACK && m_env_attenuation == 0)
m_eg_state = EG_DECAY;
// handle decay->sustain transitions
if (m_eg_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain)
m_eg_state = EG_SUSTAIN;
// fetch the appropriate 6-bit rate value from the cache
uint32_t rate = m_cache.eg_rate[m_eg_state];
// compute the rate shift value; this is the shift needed to
// apply to the env_counter such that it becomes a 5.11 fixed
// point number
uint32_t rate_shift = rate >> 2;
env_counter <<= rate_shift;
// see if the fractional part is 0; if not, it's not time to clock
if (bitfield(env_counter, 0, 11) != 0)
return;
// determine the increment based on the non-fractional part of env_counter
uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3);
uint32_t increment = attenuation_increment(rate, relevant_bits);
// attack is the only one that increases
if (m_eg_state == EG_ATTACK)
m_env_attenuation += (~m_env_attenuation * increment) >> 4;
// all other cases are similar
else
{
// apply the increment
m_env_attenuation += increment;
// clamp the final attenuation
if (m_env_attenuation >= 0x400)
m_env_attenuation = 0x3ff;
// transition to reverb at -18dB if enabled
if (m_env_attenuation >= 0xc0 && m_eg_state < EG_REVERB && m_regs.ch_pseudo_reverb(m_choffs))
m_eg_state = EG_REVERB;
}
}
//-------------------------------------------------
// fetch_sample - fetch a sample at the current
// position
//-------------------------------------------------
int16_t pcm_channel::fetch_sample() const
{
uint32_t addr = m_baseaddr;
uint32_t pos = m_curpos >> 16;
// 8-bit PCM: shift up by 8
if (m_format == 0)
return read_pcm(addr + pos) << 8;
// 16-bit PCM: assemble from 2 halves
if (m_format == 2)
{
addr += pos * 2;
return (read_pcm(addr) << 8) | read_pcm(addr + 1);
}
// 12-bit PCM: assemble out of half of 3 bytes
addr += (pos / 2) * 3;
if ((pos & 1) == 0)
return (read_pcm(addr + 0) << 8) | ((read_pcm(addr + 1) << 4) & 0xf0);
else
return (read_pcm(addr + 2) << 8) | ((read_pcm(addr + 1) << 0) & 0xf0);
}
//*********************************************************
// PCM ENGINE
//*********************************************************
//-------------------------------------------------
// pcm_engine - constructor
//-------------------------------------------------
pcm_engine::pcm_engine(ymfm_interface &intf) :
m_intf(intf),
m_env_counter(0),
m_modified_channels(ALL_CHANNELS),
m_active_channels(ALL_CHANNELS)
{
// create the channels
for (int chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum] = std::make_unique<pcm_channel>(*this, chnum);
}
//-------------------------------------------------
// reset - reset the engine state
//-------------------------------------------------
void pcm_engine::reset()
{
// reset register state
m_regs.reset();
// reset each channel
for (auto &chan : m_channel)
chan->reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void pcm_engine::save_restore(ymfm_saved_state &state)
{
// save our data
state.save_restore(m_env_counter);
// save channel state
for (int chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum]->save_restore(state);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
void pcm_engine::clock(uint32_t chanmask)
{
// if something was modified, prepare
// also prepare every 4k samples to catch ending notes
if (m_modified_channels != 0 || m_prepare_count++ >= 4096)
{
// call each channel to prepare
m_active_channels = 0;
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
if (m_channel[chnum]->prepare())
m_active_channels |= 1 << chnum;
// reset the modified channels and prepare count
m_modified_channels = m_prepare_count = 0;
}
// increment the envelope counter; the envelope generator
// only clocks every other sample in order to make the PCM
// envelopes line up with the FM envelopes (after taking into
// account the different FM sampling rate)
m_env_counter++;
// now update the state of all the channels and operators
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
m_channel[chnum]->clock(m_env_counter >> 1);
}
//-------------------------------------------------
// update - master update function
//-------------------------------------------------
void pcm_engine::output(output_data &output, uint32_t chanmask)
{
// mask out some channels for debug purposes
chanmask &= debug::GLOBAL_PCM_CHANNEL_MASK;
// compute the output of each channel
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
m_channel[chnum]->output(output);
}
//-------------------------------------------------
// read - handle reads from the PCM registers
//-------------------------------------------------
uint8_t pcm_engine::read(uint32_t regnum)
{
// handle reads from the data register
if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
return m_intf.ymfm_external_read(ACCESS_PCM, m_regs.memory_address_autoinc());
return m_regs.read(regnum);
}
//-------------------------------------------------
// write - handle writes to the PCM registers
//-------------------------------------------------
void pcm_engine::write(uint32_t regnum, uint8_t data)
{
// handle reads to the data register
if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
{
m_intf.ymfm_external_write(ACCESS_PCM, m_regs.memory_address_autoinc(), data);
return;
}
// for now just mark all channels as modified
m_modified_channels = ALL_CHANNELS;
// most writes are passive, consumed only when needed
m_regs.write(regnum, data);
// however, process keyons immediately
if (regnum >= 0x68 && regnum <= 0x7f)
m_channel[regnum - 0x68]->keyonoff(bitfield(data, 7));
// and also wavetable writes
else if (regnum >= 0x08 && regnum <= 0x1f)
m_channel[regnum - 0x08]->load_wavetable();
}
}

View file

@ -0,0 +1,347 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_PCM_H
#define YMFM_PCM_H
#pragma once
#include "ymfm.h"
namespace ymfm
{
/*
Note to self: Sega "Multi-PCM" is almost identical to this
28 channels
Writes:
00 = data reg, causes write
01 = target slot = data - (data / 8)
02 = address (clamped to 7)
Slot data (registers with ADSR/KSR seem to be inaccessible):
0: xxxx---- panpot
1: xxxxxxxx wavetable low
2: xxxxxx-- pitch low
-------x wavetable high
3: xxxx---- octave
----xxxx pitch hi
4: x------- key on
5: xxxxxxx- total level
-------x level direct (0=interpolate)
6: --xxx--- LFO frequency
-----xxx PM sensitivity
7: -----xxx AM sensitivity
Sample data:
+00: start hi
+01: start mid
+02: start low
+03: loop hi
+04: loop low
+05: -end hi
+06: -end low
+07: vibrato (reg 6)
+08: attack/decay
+09: sustain level/rate
+0A: ksr/release
+0B: LFO amplitude (reg 7)
*/
//*********************************************************
// INTERFACE CLASSES
//*********************************************************
class pcm_engine;
// ======================> pcm_cache
// this class holds data that is computed once at the start of clocking
// and remains static during subsequent sound generation
struct pcm_cache
{
uint32_t step; // sample position step, as a .16 value
uint32_t total_level; // target total level, as a .10 value
uint32_t pan_left; // left panning attenuation
uint32_t pan_right; // right panning attenuation
uint32_t eg_sustain; // sustain level, shifted up to envelope values
uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR
uint8_t lfo_step; // stepping value for LFO
uint8_t am_depth; // scale value for AM LFO
uint8_t pm_depth; // scale value for PM LFO
};
// ======================> pcm_registers
//
// PCM register map:
//
// System-wide registers:
// 00-01 xxxxxxxx LSI Test
// 02 -------x Memory access mode (0=sound gen, 1=read/write)
// ------x- Memory type (0=ROM, 1=ROM+SRAM)
// ---xxx-- Wave table header
// xxx----- Device ID (=1 for YMF278B)
// 03 --xxxxxx Memory address high
// 04 xxxxxxxx Memory address mid
// 05 xxxxxxxx Memory address low
// 06 xxxxxxxx Memory data
// F8 --xxx--- Mix control (FM_R)
// -----xxx Mix control (FM_L)
// F9 --xxx--- Mix control (PCM_R)
// -----xxx Mix control (PCM_L)
//
// Channel-specific registers:
// 08-1F xxxxxxxx Wave table number low
// 20-37 -------x Wave table number high
// xxxxxxx- F-number low
// 38-4F -----xxx F-number high
// ----x--- Pseudo-reverb
// xxxx---- Octave
// 50-67 xxxxxxx- Total level
// -------x Level direct
// 68-7F x------- Key on
// -x------ Damp
// --x----- LFO reset
// ---x---- Output channel
// ----xxxx Panpot
// 80-97 --xxx--- LFO speed
// -----xxx Vibrato
// 98-AF xxxx---- Attack rate
// ----xxxx Decay rate
// B0-C7 xxxx---- Sustain level
// ----xxxx Sustain rate
// C8-DF xxxx---- Rate correction
// ----xxxx Release rate
// E0-F7 -----xxx AM depth
class pcm_registers
{
public:
// constants
static constexpr uint32_t OUTPUTS = 4;
static constexpr uint32_t CHANNELS = 24;
static constexpr uint32_t REGISTERS = 0x100;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
// constructor
pcm_registers() { }
// save/restore
void save_restore(ymfm_saved_state &state);
// reset to initial state
void reset();
// update cache information
void cache_channel_data(uint32_t choffs, pcm_cache &cache);
// direct read/write access
uint8_t read(uint32_t index ) { return m_regdata[index]; }
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
// system-wide registers
uint32_t memory_access_mode() const { return bitfield(m_regdata[0x02], 0); }
uint32_t memory_type() const { return bitfield(m_regdata[0x02], 1); }
uint32_t wave_table_header() const { return bitfield(m_regdata[0x02], 2, 3); }
uint32_t device_id() const { return bitfield(m_regdata[0x02], 5, 3); }
uint32_t memory_address() const { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; }
uint32_t memory_data() const { return m_regdata[0x06]; }
uint32_t mix_fm_r() const { return bitfield(m_regdata[0xf8], 3, 3); }
uint32_t mix_fm_l() const { return bitfield(m_regdata[0xf8], 0, 3); }
uint32_t mix_pcm_r() const { return bitfield(m_regdata[0xf9], 3, 3); }
uint32_t mix_pcm_l() const { return bitfield(m_regdata[0xf9], 0, 3); }
// per-channel registers
uint32_t ch_wave_table_num(uint32_t choffs) const { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); }
uint32_t ch_fnumber(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); }
uint32_t ch_pseudo_reverb(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 3); }
uint32_t ch_octave(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 4, 4); }
uint32_t ch_total_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 1, 7); }
uint32_t ch_level_direct(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 0); }
uint32_t ch_keyon(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 7); }
uint32_t ch_damp(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 6); }
uint32_t ch_lfo_reset(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 5); }
uint32_t ch_output_channel(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 4); }
uint32_t ch_panpot(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 0, 4); }
uint32_t ch_lfo_speed(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 3, 3); }
uint32_t ch_vibrato(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 0, 3); }
uint32_t ch_attack_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 4, 4); }
uint32_t ch_decay_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 0, 4); }
uint32_t ch_sustain_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 4, 4); }
uint32_t ch_sustain_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 0, 4); }
uint32_t ch_rate_correction(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 4, 4); }
uint32_t ch_release_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 0, 4); }
uint32_t ch_am_depth(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xe0], 0, 3); }
// return the memory address and increment it
uint32_t memory_address_autoinc()
{
uint32_t result = memory_address();
uint32_t newval = result + 1;
m_regdata[0x05] = newval >> 0;
m_regdata[0x04] = newval >> 8;
m_regdata[0x03] = (newval >> 16) & 0x3f;
return result;
}
private:
// internal helpers
uint32_t effective_rate(uint32_t raw, uint32_t correction);
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> pcm_channel
class pcm_channel
{
static constexpr uint8_t KEY_ON = 0x01;
static constexpr uint8_t KEY_PENDING_ON = 0x02;
static constexpr uint8_t KEY_PENDING = 0x04;
// "quiet" value, used to optimize when we can skip doing working
static constexpr uint32_t EG_QUIET = 0x200;
public:
using output_data = ymfm_output<pcm_registers::OUTPUTS>;
// constructor
pcm_channel(pcm_engine &owner, uint32_t choffs);
// save/restore
void save_restore(ymfm_saved_state &state);
// reset the channel state
void reset();
// return the channel offset
uint32_t choffs() const { return m_choffs; }
// prepare prior to clocking
bool prepare();
// master clocking function
void clock(uint32_t env_counter);
// return the computed output value, with panning applied
void output(output_data &output) const;
// signal key on/off
void keyonoff(bool on);
// load a new wavetable entry
void load_wavetable();
private:
// internal helpers
void start_attack();
void start_release();
void clock_envelope(uint32_t env_counter);
int16_t fetch_sample() const;
uint8_t read_pcm(uint32_t address) const;
// internal state
uint32_t const m_choffs; // channel offset
uint32_t m_baseaddr; // base address
uint32_t m_endpos; // ending position
uint32_t m_looppos; // loop position
uint32_t m_curpos; // current position
uint32_t m_nextpos; // next position
uint32_t m_lfo_counter; // LFO counter
envelope_state m_eg_state; // envelope state
uint16_t m_env_attenuation; // computed envelope attenuation
uint32_t m_total_level; // total level with as 7.10 for interp
uint8_t m_format; // sample format
uint8_t m_key_state; // current key state
pcm_cache m_cache; // cached data
pcm_registers &m_regs; // reference to registers
pcm_engine &m_owner; // reference to our owner
};
// ======================> pcm_engine
class pcm_engine
{
public:
static constexpr int OUTPUTS = pcm_registers::OUTPUTS;
static constexpr int CHANNELS = pcm_registers::CHANNELS;
static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS;
using output_data = pcm_channel::output_data;
// constructor
pcm_engine(ymfm_interface &intf);
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
void clock(uint32_t chanmask);
// compute sum of channel outputs
void output(output_data &output, uint32_t chanmask);
// read from the PCM registers
uint8_t read(uint32_t regnum);
// write to the PCM registers
void write(uint32_t regnum, uint8_t data);
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
pcm_registers &regs() { return m_regs; }
private:
// internal state
ymfm_interface &m_intf; // reference to the interface
uint32_t m_env_counter; // envelope counter
uint32_t m_modified_channels; // bitmask of modified channels
uint32_t m_active_channels; // bitmask of active channels
uint32_t m_prepare_count; // counter to do periodic prepare sweeps
std::unique_ptr<pcm_channel> m_channel[CHANNELS]; // array of channels
pcm_registers m_regs; // registers
};
}
#endif // YMFM_PCM_H

View file

@ -0,0 +1,279 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ymfm_ssg.h"
namespace ymfm
{
//*********************************************************
// SSG REGISTERS
//*********************************************************
//-------------------------------------------------
// reset - reset the register state
//-------------------------------------------------
void ssg_registers::reset()
{
std::fill_n(&m_regdata[0], REGISTERS, 0);
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void ssg_registers::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_regdata);
}
//*********************************************************
// SSG ENGINE
//*********************************************************
//-------------------------------------------------
// ssg_engine - constructor
//-------------------------------------------------
ssg_engine::ssg_engine(ymfm_interface &intf) :
m_intf(intf),
m_tone_count{ 0,0,0 },
m_tone_state{ 0,0,0 },
m_envelope_count(0),
m_envelope_state(0),
m_noise_count(0),
m_noise_state(1),
m_override(nullptr)
{
}
//-------------------------------------------------
// reset - reset the engine state
//-------------------------------------------------
void ssg_engine::reset()
{
// defer to the override if present
if (m_override != nullptr)
return m_override->ssg_reset();
// reset register state
m_regs.reset();
// reset engine state
for (int chan = 0; chan < 3; chan++)
{
m_tone_count[chan] = 0;
m_tone_state[chan] = 0;
}
m_envelope_count = 0;
m_envelope_state = 0;
m_noise_count = 0;
m_noise_state = 1;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void ssg_engine::save_restore(ymfm_saved_state &state)
{
// save register state
m_regs.save_restore(state);
// save engine state
state.save_restore(m_tone_count);
state.save_restore(m_tone_state);
state.save_restore(m_envelope_count);
state.save_restore(m_envelope_state);
state.save_restore(m_noise_count);
state.save_restore(m_noise_state);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
void ssg_engine::clock()
{
// clock tones; tone period units are clock/16 but since we run at clock/8
// that works out for us to toggle the state (50% duty cycle) at twice the
// programmed period
for (int chan = 0; chan < 3; chan++)
{
m_tone_count[chan]++;
if (m_tone_count[chan] >= m_regs.ch_tone_period(chan))
{
m_tone_state[chan] ^= 1;
m_tone_count[chan] = 0;
}
}
// clock noise; noise period units are clock/16 but since we run at clock/8,
// our counter needs a right shift prior to compare; note that a period of 0
// should produce an indentical result to a period of 1, so add a special
// check against that case
m_noise_count++;
if ((m_noise_count >> 1) >= m_regs.noise_period() && m_noise_count != 1)
{
m_noise_state ^= (bitfield(m_noise_state, 0) ^ bitfield(m_noise_state, 3)) << 17;
m_noise_state >>= 1;
m_noise_count = 0;
}
// clock envelope; envelope period units are clock/8 (manual says clock/256
// but that's for all 32 steps)
m_envelope_count++;
if (m_envelope_count >= m_regs.envelope_period())
{
m_envelope_state++;
m_envelope_count = 0;
}
}
//-------------------------------------------------
// output - output the current state
//-------------------------------------------------
void ssg_engine::output(output_data &output)
{
// volume to amplitude table, taken from MAME's implementation but biased
// so that 0 == 0
static int16_t const s_amplitudes[32] =
{
0, 32, 78, 141, 178, 222, 262, 306,
369, 441, 509, 585, 701, 836, 965, 1112,
1334, 1595, 1853, 2146, 2576, 3081, 3576, 4135,
5000, 6006, 7023, 8155, 9963,11976,14132,16382
};
// compute the envelope volume
uint32_t envelope_volume;
if ((m_regs.envelope_hold() | (m_regs.envelope_continue() ^ 1)) && m_envelope_state >= 32)
{
m_envelope_state = 32;
envelope_volume = ((m_regs.envelope_attack() ^ m_regs.envelope_alternate()) & m_regs.envelope_continue()) ? 31 : 0;
}
else
{
uint32_t attack = m_regs.envelope_attack();
if (m_regs.envelope_alternate())
attack ^= bitfield(m_envelope_state, 5);
envelope_volume = (m_envelope_state & 31) ^ (attack ? 0 : 31);
}
// iterate over channels
for (int chan = 0; chan < 3; chan++)
{
// noise depends on the noise state, which is the LSB of m_noise_state
uint32_t noise_on = m_regs.ch_noise_enable_n(chan) | m_noise_state;
// tone depends on the current tone state
uint32_t tone_on = m_regs.ch_tone_enable_n(chan) | m_tone_state[chan];
// if neither tone nor noise enabled, return 0
uint32_t volume;
if ((noise_on & tone_on) == 0)
volume = 0;
// if the envelope is enabled, use its amplitude
else if (m_regs.ch_envelope_enable(chan))
volume = envelope_volume;
// otherwise, scale the tone amplitude up to match envelope values
// according to the datasheet, amplitude 15 maps to envelope 31
else
{
volume = m_regs.ch_amplitude(chan) * 2;
if (volume != 0)
volume |= 1;
}
// convert to amplitude
output.data[chan] = s_amplitudes[volume];
}
}
//-------------------------------------------------
// read - handle reads from the SSG registers
//-------------------------------------------------
uint8_t ssg_engine::read(uint32_t regnum)
{
// defer to the override if present
if (m_override != nullptr)
return m_override->ssg_read(regnum);
// read from the I/O ports call the handlers if they are configured for input
if (regnum == 0x0e && !m_regs.io_a_out())
return m_intf.ymfm_external_read(ACCESS_IO, 0);
else if (regnum == 0x0f && !m_regs.io_b_out())
return m_intf.ymfm_external_read(ACCESS_IO, 1);
// otherwise just return the register value
return m_regs.read(regnum);
}
//-------------------------------------------------
// write - handle writes to the SSG registers
//-------------------------------------------------
void ssg_engine::write(uint32_t regnum, uint8_t data)
{
// defer to the override if present
if (m_override != nullptr)
return m_override->ssg_write(regnum, data);
// store the raw value to the register array;
// most writes are passive, consumed only when needed
m_regs.write(regnum, data);
// writes to the envelope shape register reset the state
if (regnum == 0x0d)
m_envelope_state = 0;
// writes to the I/O ports call the handlers if they are configured for output
else if (regnum == 0x0e && m_regs.io_a_out())
m_intf.ymfm_external_write(ACCESS_IO, 0, data);
else if (regnum == 0x0f && m_regs.io_b_out())
m_intf.ymfm_external_write(ACCESS_IO, 1, data);
}
}

View file

@ -0,0 +1,207 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_SSG_H
#define YMFM_SSG_H
#pragma once
#include "ymfm.h"
namespace ymfm
{
//*********************************************************
// OVERRIDE INTERFACE
//*********************************************************
// ======================> ssg_override
// this class describes a simple interface to allow the internal SSG to be
// overridden with another implementation
class ssg_override
{
public:
virtual ~ssg_override() = default;
// reset our status
virtual void ssg_reset() = 0;
// read/write to the SSG registers
virtual uint8_t ssg_read(uint32_t regnum) = 0;
virtual void ssg_write(uint32_t regnum, uint8_t data) = 0;
// notification when the prescale has changed
virtual void ssg_prescale_changed() = 0;
};
//*********************************************************
// REGISTER CLASS
//*********************************************************
// ======================> ssg_registers
//
// SSG register map:
//
// System-wide registers:
// 06 ---xxxxx Noise period
// 07 x------- I/O B in(0) or out(1)
// -x------ I/O A in(0) or out(1)
// --x----- Noise enable(0) or disable(1) for channel C
// ---x---- Noise enable(0) or disable(1) for channel B
// ----x--- Noise enable(0) or disable(1) for channel A
// -----x-- Tone enable(0) or disable(1) for channel C
// ------x- Tone enable(0) or disable(1) for channel B
// -------x Tone enable(0) or disable(1) for channel A
// 0B xxxxxxxx Envelope period fine
// 0C xxxxxxxx Envelope period coarse
// 0D ----x--- Envelope shape: continue
// -----x-- Envelope shape: attack/decay
// ------x- Envelope shape: alternate
// -------x Envelope shape: hold
// 0E xxxxxxxx 8-bit parallel I/O port A
// 0F xxxxxxxx 8-bit parallel I/O port B
//
// Per-channel registers:
// 00,02,04 xxxxxxxx Tone period (fine) for channel A,B,C
// 01,03,05 ----xxxx Tone period (coarse) for channel A,B,C
// 08,09,0A ---x---- Mode: fixed(0) or variable(1) for channel A,B,C
// ----xxxx Amplitude for channel A,B,C
//
class ssg_registers
{
public:
// constants
static constexpr uint32_t OUTPUTS = 3;
static constexpr uint32_t CHANNELS = 3;
static constexpr uint32_t REGISTERS = 0x10;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
// constructor
ssg_registers() { }
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// direct read/write access
uint8_t read(uint32_t index) { return m_regdata[index]; }
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
// system-wide registers
uint32_t noise_period() const { return bitfield(m_regdata[0x06], 0, 5); }
uint32_t io_b_out() const { return bitfield(m_regdata[0x07], 7); }
uint32_t io_a_out() const { return bitfield(m_regdata[0x07], 6); }
uint32_t envelope_period() const { return m_regdata[0x0b] | (m_regdata[0x0c] << 8); }
uint32_t envelope_continue() const { return bitfield(m_regdata[0x0d], 3); }
uint32_t envelope_attack() const { return bitfield(m_regdata[0x0d], 2); }
uint32_t envelope_alternate() const { return bitfield(m_regdata[0x0d], 1); }
uint32_t envelope_hold() const { return bitfield(m_regdata[0x0d], 0); }
uint32_t io_a_data() const { return m_regdata[0x0e]; }
uint32_t io_b_data() const { return m_regdata[0x0f]; }
// per-channel registers
uint32_t ch_noise_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 3 + choffs); }
uint32_t ch_tone_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 0 + choffs); }
uint32_t ch_tone_period(uint32_t choffs) const { return m_regdata[0x00 + 2 * choffs] | (bitfield(m_regdata[0x01 + 2 * choffs], 0, 4) << 8); }
uint32_t ch_envelope_enable(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 4); }
uint32_t ch_amplitude(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 0, 4); }
private:
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> ssg_engine
class ssg_engine
{
public:
static constexpr int OUTPUTS = ssg_registers::OUTPUTS;
static constexpr int CHANNELS = ssg_registers::CHANNELS;
static constexpr int CLOCK_DIVIDER = 8;
using output_data = ymfm_output<OUTPUTS>;
// constructor
ssg_engine(ymfm_interface &intf);
// configure an override
void override(ssg_override &override) { m_override = &override; }
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
void clock();
// compute sum of channel outputs
void output(output_data &output);
// read/write to the SSG registers
uint8_t read(uint32_t regnum);
void write(uint32_t regnum, uint8_t data);
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
ssg_registers &regs() { return m_regs; }
// true if we are overridden
bool overridden() const { return (m_override != nullptr); }
// indicate the prescale has changed
void prescale_changed() { if (m_override != nullptr) m_override->ssg_prescale_changed(); }
private:
// internal state
ymfm_interface &m_intf; // reference to the interface
uint32_t m_tone_count[3]; // current tone counter
uint32_t m_tone_state[3]; // current tone state
uint32_t m_envelope_count; // envelope counter
uint32_t m_envelope_state; // envelope state
uint32_t m_noise_count; // current noise counter
uint32_t m_noise_state; // current noise state
ssg_registers m_regs; // registers
ssg_override *m_override; // override interface
};
}
#endif // YMFM_SSG_H

View file

@ -163,7 +163,7 @@ void opm_compress(opm_convert_context_t* ctx) {
break;
};
for (int ch = 0; ch < ctx->max_channels+1; ch++) {
for (int ch = 0; ch < ctx->max_channels; ch++) {
if (ctx->flags.verbosity >= 2) {
printf("\n");
}

View file

@ -19,57 +19,85 @@ struct opm_control_track_t {
ñáîðñèòü key on â 0, çàòåì ïîñòàâèòü â 1
*/
enum {
OPM_MULT = (1 << 0),
OPM_KSL_TL = (1 << 1),
OPM_AD = (1 << 2),
OPM_SR = (1 << 3),
OPM_WAVEFORM = (1 << 4),
OPM_OP1_SHIFT = 5,
enum : uint64_t {
// control stream
OPM_REC_REG24 = (1 << 0),
OPM_REC_REG25 = (1 << 1),
OPM_REC_REG27 = (1 << 2),
OPM_REC_REG22 = (1 << 3),
OPM_REC_EXTCH3_OP1_LOW = (1 << 4),
OPM_REC_EXTCH3_OP1_HIGH = (1 << 5),
OPM_REC_EXTCH3_OP2_LOW = (1 << 6),
OPM_REC_EXTCH3_OP2_HIGH = (1 << 7),
OPM_REC_EXTCH3_OP3_LOW = (1 << 8),
OPM_REC_EXTCH3_OP3_HIGH = (1 << 9),
OPM_REC_RHYTHM_SHIFT = 10,
OPM_FEEDBACK = (1 << 10),
OPM_FNUM = (1 << 11),
OPM_BLOCK = (1 << 12),
OPM_KEY = (1 << 13),
// FM stream
OPM_REC_REG30 = (1 << 0),
OPM_REC_REG40 = (1 << 1),
OPM_REC_REG50 = (1 << 2),
OPM_REC_REG60 = (1 << 3),
OPM_REC_REG70 = (1 << 4),
OPM_REC_REG80 = (1 << 5),
OPM_REC_REG90 = (1 << 6),
OPM_KEYPERC = (1 << 14),
OPM_REG_01 = (1 << 15),
OPM_REG_08 = (1 << 16),
OPM_REG_105 = (1 << 17),
OPM_REG_104 = (1 << 18),
OPM_REG_BD = (1 << 19),
OPM_REC_REG30_IDX = 0,
OPM_REC_REG40_IDX = 1,
OPM_REC_REG50_IDX = 2,
OPM_REC_REG60_IDX = 3,
OPM_REC_REG70_IDX = 4,
OPM_REC_REG80_IDX = 5,
OPM_REC_REG90_IDX = 6,
OPM_4OP = (1 << 20),
OPM_REC_OP_SHIFTMUL = 7,
// opm_serialize_channel_stream() tweaks
OPM_TL0 = (1 << 8),
OPM_TL1 = (1 << 9),
OPM_REC_CHREG_SHIFT = OPM_REC_OP_SHIFTMUL*4,
OPM_REC_REGA0 = (1ULL << (OPM_REC_CHREG_SHIFT + 0)),
OPM_REC_REGA4 = (1ULL << (OPM_REC_CHREG_SHIFT + 1)),
OPM_REC_REGB0 = (1ULL << (OPM_REC_CHREG_SHIFT + 2)),
OPM_REC_REGB4 = (1ULL << (OPM_REC_CHREG_SHIFT + 3)),
OPM_REC_KEY = (1ULL << (OPM_REC_CHREG_SHIFT + 4)),
// SSG tone stream
OPM_REC_AYTONE_VOLUME = (1 << 0),
OPM_REC_AYTONE_PERIOD_LOW = (1 << 1),
OPM_REC_AYTONE_PERIOD_HIGH = (1 << 2),
OPM_REC_AYTONE_MASK = (1 << 3),
// SSG env/noise stream
OPM_REC_AYENV_NOISE = (1 << 0),
OPM_REC_AYENV_PERIOD_LOW = (1 << 1),
OPM_REC_AYENV_PERIOD_HIGH = (1 << 2),
OPM_REC_AYENV_ENVTYPE = (1 << 3),
};
struct opm_frame_record {
int flags;
uint64_t flags; // yeah!
// opl channel stream only
struct {
int mult;
int ksl_tl;
int ad;
int sr;
int waveform;
} op[2];
int fnum;
int block;
int feedback;
int key;
int _4op;
// control stream only
int keyperc;
int reg_01;
int reg_08;
int reg_BD;
int reg_104;
int reg_105;
union {
struct {
// control channel
int reg24, reg25, reg27, reg22;
struct {
int freq[2][4];
} extch3;
int rhythm[16];
};
struct {
// fm channel
int opdata[4][9-3+1]; // regs 30..90
int fnum, block, fb, pan, key;
};
struct {
// SSG tone channel
int volume, mask, period_low, period_hi;
} aytone;
struct {
// SSG envelope/noise channel
int noise, envtype, period_low, period_hi;
} ayenv;
};
};
enum {
@ -148,6 +176,11 @@ struct opm_convert_context_t {
// chip type
uint32_t chip_type; // OPM_FLAG_CHIP_*
uint32_t max_channels;
uint32_t ctrl_chidx;
uint32_t fm_chidx;
uint32_t ssg_chidx;
uint32_t rhy_chidx;
uint32_t dual_chidx;
bool percussion_mode;
// conversion flags
@ -155,6 +188,7 @@ struct opm_convert_context_t {
int compress_level;
int max_stack_depth;
int verbosity;
bool optimize_vgm;
} flags;
// frame rate stuff

View file

@ -4,21 +4,26 @@
#pragma pack(push, 1)
enum {
OPM_FORMAT_VERSION = 0x0002
OPM_FORMAT_VERSION = 0x0010
};
struct opm_header_stream_desc_t {
//uint16_t ptr; // offset to data stream in paragraphs (bytes*16)
uint16_t size; // stream data size in bytes
uint16_t size; // stream data size in bytes (max. 65520 bytes)
};
enum {
OPM_FLAG_CHIP_OPL2 = (0 << 0),
OPM_FLAG_CHIP_OPL3 = (1 << 0),
OPM_CHIPTYPE_OPL = (0 << 0),
OPM_CHIPTYPE_OPN = (1 << 0),
};
OPM_FLAG_CHIP_TYPE = (3 << 0),
enum {
OPM_FLAG_CHIP_OPN = (0 << 0),
OPM_FLAG_CHIP_OPN_DUAL = (1 << 0),
OPM_FLAG_CHIP_OPNA = (2 << 0),
OPM_FLAG_CHIP_OPN3 = (3 << 0),
OPM_FLAG_PERCUSSION_MODE = (1 << 2),
OPM_FLAG_CHIP_TYPE = (3 << 0),
};
struct opm_header_t {
@ -30,16 +35,19 @@ struct opm_header_t {
};
uint16_t v;
} version;
uint8_t chip_type; // see above
uint8_t reserved0; //
uint32_t clock_rate; // hz, integer
uint16_t frame_rate; // hz, 8.8 fixedpoint
uint16_t flags; // see above
uint16_t frame_rate; // [hz] = 0x1234dd/frame_rate
uint8_t callstack_depth; // reserved, 0 at this moment
uint8_t channels; // must be either 9 or 18!
uint32_t channel_mask; // used channel mask, LSB = ch 0
uint8_t streams; // including control streams
uint32_t stream_mask; // used channel mask, LSB = ch 0
// opm_header_stream_desc_t stream[opm_header_t::channels + 1]; // first is control stream
// opm_header_stream_desc_t stream[opm_header_t::channels]; // first is control stream
};
// OPM v0 stream data:
// OPM v0 stream data, stream-independent commands
enum {
OPM_STREAM_END_FRAME = 0xFF, // end of frame, next channel
OPM_STREAM_END = 0xFE, // end of stream, stop here or loop to OPM_STREAM_LOOP stream point
@ -48,9 +56,6 @@ enum {
OPM_STREAM_SET_FRAME_RATE = 0xFB, // word rate (as in opm_header_t::frame_rate)
OPM_STREAM_LOOP = 0xFA, // set loop point here
OPM_4OP_TRIGGER = 0xF4, // 4-op mode on/off
OPM_KEY_TRIGGER = 0xF0, // set key on/off + optionally end of frame
// delay commands
OPM_STREAM_DELAY_INT32 = 0xF9, // dword delay
OPM_STREAM_DELAY_INT16 = 0xF8, // word delay
@ -59,45 +64,117 @@ enum {
// back reference
OPM_STREAM_BACKREF = 0xE0, // E0..EF - word backrefpos (12 bit), byte frames
// setter commands
OPM_SET_OPERATOR = 0x00, // 00..7F - set operator parameters
OPM_SET_FREQ_FB_VOL = 0x80, // 80..BF - set frequency, feedback and total level
// control register set
OPM_CTRL_KEY_PERC = 0x00, // 00..7F - set key on/off for percussion, end of frame
OPM_CTRL_REG_SET = 0x80, // 80..BF - set control registers
// flags
OPM_4OP_OFF = (0 << 0),
OPM_4OP_ON = (1 << 0),
OPM_KEY_OFF = (0 << 0),
OPM_KEY_ON = (1 << 0),
OPM_KEY_END_OF_FRAME = (1 << 1),
OPM_SET_VOLUME_END_OF_FRAME = (1 << 7),
OPM_CMD00_SET_MULT = (1 << 0),
OPM_CMD00_SET_TL = (1 << 1),
OPM_CMD00_SET_AD = (1 << 2),
OPM_CMD00_SET_SR = (1 << 3),
OPM_CMD00_SET_WAVEFORM = (1 << 4),
OPM_CMD00_SELECT_OPERATOR = (1 << 5),
OPM_CMD00_END_OF_FRAME = (1 << 6),
OPM_CMD80_SET_TL0 = (1 << 0),
OPM_CMD80_SET_TL1 = (1 << 1),
OPM_CMD80_SET_FEEDBACK = (1 << 2),
OPM_CMD80_SET_FREQ = (1 << 3),
OPM_CMD80_SET_KEYBLOCK = (1 << 4),
OPM_CMD80_END_OF_FRAME = (1 << 5),
OPM_CTRL_SET_REG01 = (1 << 0),
OPM_CTRL_SET_REG08 = (1 << 1),
OPM_CTRL_SET_REG105 = (1 << 2),
OPM_CTRL_SET_REG104 = (1 << 3),
OPM_CTRL_SET_REGBD = (1 << 4),
};
// OPN control stream commands
enum {
OPM_CTRL_EXTCH3 = 0x00, // 00..7F - ext. CH3 op1-3 frequency
OPM_CTRL_TIMER_CSM = 0x80, // 80..9F - set timer/CSM/LFO frequency
OPM_CTRL_RHYTHM = 0xA0, // A0..BF - rhythm control
OPM_CTRL_CMD80_REG24 = (1 << 0),
OPM_CTRL_CMD80_REG25 = (1 << 1),
OPM_CTRL_CMD80_REG27 = (1 << 2),
OPM_CTRL_CMD80_REG22 = (1 << 3),
OPM_CTRL_CMD80_EOF = (1 << 4),
OPM_CTRL_EXTCH3_OP1_LOW = (1 << 0),
OPM_CTRL_EXTCH3_OP1_HIGH = (1 << 1),
OPM_CTRL_EXTCH3_OP2_LOW = (1 << 2),
OPM_CTRL_EXTCH3_OP2_HIGH = (1 << 3),
OPM_CTRL_EXTCH3_OP3_LOW = (1 << 4),
OPM_CTRL_EXTCH3_OP3_HIGH = (1 << 5),
OPM_CTRL_EXTCH3_EOF = (1 << 6),
OPM_CTRL_CMDA0_REG_MASK = (0x0F << 0),
OPM_CTRL_CMDA0_EOF = (1 << 4),
};
// OPN FM stream commands
enum {
OPM_FM_ADSR = 0x00, // 00..3F - set ADSR
OPM_FM_MUL_TL_EG = 0x40, // 40..7F - set MULT/TL/SSG-EG
OPM_FM_FREQ_FB_PAN = 0x80, // 80..9F - set frequency/feedback/panning
OPM_FM_KEY = 0xA0, // A0..BF - key on/off
OPM_FM_CMD00_REG50 = (1 << 0),
OPM_FM_CMD00_REG60 = (1 << 1),
OPM_FM_CMD00_REG70 = (1 << 2),
OPM_FM_CMD00_REG80 = (1 << 3),
OPM_FM_CMD00_OP_SHIFT = 4,
OPM_FM_CMD00_OP_MASK = (3 << OPM_FM_CMD00_OP_SHIFT),
OPM_FM_CMD40_REG30 = (1 << 0),
OPM_FM_CMD40_REG40 = (1 << 1),
OPM_FM_CMD40_REG90 = (1 << 2),
OPM_FM_CMD40_EOF = (1 << 3),
OPM_FM_CMD40_OP_SHIFT = OPM_FM_CMD00_OP_SHIFT,
OPM_FM_CMD40_OP_MASK = OPM_FM_CMD00_OP_MASK,
OPM_FM_CMD80_REGA0 = (1 << 0),
OPM_FM_CMD80_REGA4 = (1 << 1),
OPM_FM_CMD80_REGB0 = (1 << 2),
OPM_FM_CMD80_REGB4 = (1 << 3),
OPM_FM_CMD80_EOF = (1 << 4),
OPM_FM_CMDA0_OP_SHIFT = 0,
OPM_FM_CMDA0_OP_MASK = (0x0F << OPM_FM_CMDA0_OP_SHIFT),
OPM_FM_CMDA0_EOF = (1 << 4),
};
// OPNA rhythm channel stream
enum {
OPN_RHYTHM_KEY = 0x00, // 00..7F - key on
OPN_RHYTHM_REGS1 = 0x80, // 80..9F - write reg set 1
OPN_RHYTHM_REGS2 = 0xA0, // A0..BF - write reg set 2
OPN_RHYTHM_CMD80_REG10 = (1 << 0),
OPN_RHYTHM_CMD80_REG11 = (1 << 1),
OPN_RHYTHM_CMD80_REG18 = (1 << 2),
OPN_RHYTHM_CMD80_REG19 = (1 << 3),
OPN_RHYTHM_CMDA0_REG1A = (1 << 0),
OPN_RHYTHM_CMDA0_REG1B = (1 << 1),
OPN_RHYTHM_CMDA0_REG1C = (1 << 2),
OPN_RHYTHM_CMDA0_REG1D = (1 << 3),
};
// OPN SSG tone stream commands (shared with AY chip type)
enum {
OPM_AYTONE_REGS = 0x00, // 00..7F - set volume and period low
OPM_AYTONE_PERIOD = 0x80, // 80..BF - set period
OPM_AYTONE_MASK = 0xF0, // F0..F7 - set tone/noise mask
OPM_AYTONE_CMD00_VOLUME_MASK = (0x1F << 0),
OPM_AYTONE_CMD00_PERIOD_LOW = (1 << 5),
OPM_AYTONE_CMD00_EOF = (1 << 6),
OPM_AYTONE_CMD80_PERIOD_HIGH = (0xF << 0),
OPM_AYTONE_CMD80_PERIOD_LOW = (1 << 4),
OPM_AYTONE_CMD80_EOF = (1 << 5),
OPM_AYTONE_MASK_TONE = (1 << 0),
OPM_AYTONE_MASK_NOISE = (1 << 1),
OPM_AYTONE_MASK_EOF = (1 << 2),
};
// OPN SSG envelope/noise stream commands (shared with AY chip type)
enum {
OPM_AYENV_REGS = 0x00, // 00..7F - set noise and period low
OPM_AYENV_ENVTYPE = 0x80, // 80..BF - set env type and period low
OPM_AYENV_PERIOD_FULL = 0xF0, // F0..F7 - set full envelope period
OPM_AYENV_CMD00_NOISE_MASK = (0x1F << 0),
OPM_AYENV_CMD00_PERIOD_LOW = (1 << 5),
OPM_AYENV_CMD00_EOF = (1 << 6),
OPM_AYENV_CMD80_ENV_TYPE = (0xF << 0),
OPM_AYENV_CMD80_PERIOD_LOW = (1 << 4),
OPM_AYENV_CMD80_EOF = (1 << 5),
OPM_AYENV_CMDF0_PERIOD_LOW = (1 << 0),
OPM_AYENV_CMDF0_PERIOD_HIGH = (1 << 1),
OPM_AYENV_CMDF0_EOF = (1 << 2),
};
#pragma pack(pop)

View file

@ -33,9 +33,12 @@ struct VGMHeader {
uint32_t dataOffset; // relative from this field offset - use it for data fetching!
uint32_t unused[6];
uint32_t unused[3];
uint32_t YM2203_Clock;
uint32_t YM2608_Clock;
uint32_t unused_2[1];
uint32_t YM3812_Clock;
uint32_t unused_2[2];
uint32_t unused_3[2];
uint32_t YMF262_Clock;
};
@ -82,6 +85,7 @@ enum class VGM_Stream_Opcode : uint8_t {
PCM_RAW_WRITE = 0x68, // 0x68 ... : PCM RAM write : see below
AY_WRITE = 0xa0, // 0xA0 aa dd : AY8910, write value dd to register aa
YM2203_CHIP2_WRITE = 0xA5, // 0xA5 aa dd : YM2203 chip 2, write value dd to register aa
RF5C68_WRITE = 0xb0, // 0xB0 aa dd : RF5C68, write value dd to register aa
RF5C164_WRITE = 0xb1, // 0xB1 aa dd : RF5C164, write value dd to register aa
PWM_WRITE = 0xb2, // 0xB2 ad dd : PWM, write value ddd to register a(d is MSB, dd is LSB)

File diff suppressed because it is too large Load diff