mirror of
https://github.com/tildearrow/furnace.git
synced 2024-12-03 09:47:26 +00:00
Add files via upload
This commit is contained in:
parent
2c9bad3b3d
commit
6c517292dd
32 changed files with 6550 additions and 0 deletions
22
extern/NSFplay/legacy/2413tone.h
vendored
Normal file
22
extern/NSFplay/legacy/2413tone.h
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
/* YM2413 tone by Mitsutaka Okazaki, 2020 */
|
||||
/* https://github.com/digital-sound-antiques/emu2413/blob/d2b9c8dfc5e84b7f8a535fdfee0149ac7bd84ca2/emu2413.c#L34
|
||||
*/
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0: Original
|
||||
0x71, 0x61, 0x1e, 0x17, 0xd0, 0x78, 0x00, 0x17, // 1: Violin
|
||||
0x13, 0x41, 0x1a, 0x0d, 0xd8, 0xf7, 0x23, 0x13, // 2: Guitar
|
||||
0x13, 0x01, 0x99, 0x00, 0xf2, 0xc4, 0x21, 0x23, // 3: Piano
|
||||
0x11, 0x61, 0x0e, 0x07, 0x8d, 0x64, 0x70, 0x27, // 4: Flute
|
||||
0x32, 0x21, 0x1e, 0x06, 0xe1, 0x76, 0x01, 0x28, // 5: Clarinet
|
||||
0x31, 0x22, 0x16, 0x05, 0xe0, 0x71, 0x00, 0x18, // 6: Oboe
|
||||
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x11, 0x07, // 7: Trumpet
|
||||
0x33, 0x21, 0x2d, 0x13, 0xb0, 0x70, 0x00, 0x07, // 8: Organ
|
||||
0x61, 0x61, 0x1b, 0x06, 0x64, 0x65, 0x10, 0x17, // 9: Horn
|
||||
0x41, 0x61, 0x0b, 0x18, 0x85, 0xf0, 0x81, 0x07, // A: Synthesizer
|
||||
0x33, 0x01, 0x83, 0x11, 0xea, 0xef, 0x10, 0x04, // B: Harpsichord
|
||||
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12, // C: Vibraphone
|
||||
0x61, 0x50, 0x0c, 0x05, 0xd2, 0xf5, 0x40, 0x42, // D: Synthsizer Bass
|
||||
0x01, 0x01, 0x55, 0x03, 0xe9, 0x90, 0x03, 0x02, // E: Acoustic Bass
|
||||
0x41, 0x41, 0x89, 0x03, 0xf1, 0xe4, 0xc0, 0x13, // F: Electric Guitar
|
||||
0x01, 0x01, 0x18, 0x0f, 0xdf, 0xf8, 0x6a, 0x6d, // R: Bass Drum (from VRC7)
|
||||
0x01, 0x01, 0x00, 0x00, 0xc8, 0xd8, 0xa7, 0x68, // R: High-Hat(M) / Snare Drum(C) (from VRC7)
|
||||
0x05, 0x01, 0x00, 0x00, 0xf8, 0xaa, 0x59, 0x55, // R: Tom-tom(M) / Top Cymbal(C) (from VRC7)
|
20
extern/NSFplay/legacy/281btone.h
vendored
Normal file
20
extern/NSFplay/legacy/281btone.h
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* YMF281B tone by Chabin (4/10/2004) */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x62,0x21,0x1a,0x07,0xf0,0x6f,0x00,0x16,
|
||||
0x00,0x10,0x44,0x02,0xf6,0xf4,0x54,0x23,
|
||||
0x03,0x01,0x97,0x04,0xf3,0xf3,0x13,0xf3,
|
||||
0x01,0x61,0x0a,0x0f,0xfa,0x64,0x70,0x17,
|
||||
0x22,0x21,0x1e,0x06,0xf0,0x76,0x00,0x28,
|
||||
0x00,0x61,0x8a,0x0e,0xc0,0x61,0x00,0x07,
|
||||
0x21,0x61,0x1b,0x07,0x84,0x80,0x17,0x17,
|
||||
0x37,0x32,0xc9,0x01,0x66,0x64,0x40,0x28,
|
||||
0x01,0x21,0x06,0x03,0xa5,0x71,0x51,0x07,
|
||||
0x06,0x11,0x5e,0x07,0xf3,0xf2,0xf6,0x11,
|
||||
0x00,0x20,0x18,0x06,0xf5,0xf3,0x20,0x26,
|
||||
0x97,0x41,0x20,0x07,0xff,0xf4,0x22,0x22,
|
||||
0x65,0x61,0x15,0x00,0xf7,0xf3,0x16,0xf4,
|
||||
0x01,0x31,0x0e,0x07,0xfa,0xf3,0xff,0xff,
|
||||
0x48,0x61,0x09,0x07,0xf1,0x94,0xf0,0xf5,
|
||||
0x07,0x21,0x14,0x00,0xee,0xf8,0xff,0xf8,
|
||||
0x01,0x31,0x00,0x00,0xf8,0xf7,0xf8,0xf7,
|
||||
0x25,0x11,0x00,0x00,0xf8,0xfa,0xf8,0x55,
|
363
extern/NSFplay/legacy/emu2149.c
vendored
Normal file
363
extern/NSFplay/legacy/emu2149.c
vendored
Normal file
|
@ -0,0 +1,363 @@
|
|||
/****************************************************************************
|
||||
|
||||
emu2149.c -- YM2149/AY-3-8910 emulator by Mitsutaka Okazaki 2001
|
||||
|
||||
2001 04-28 : Version 1.00beta -- 1st Beta Release.
|
||||
2001 08-14 : Version 1.10
|
||||
2001 10-03 : Version 1.11 -- Added PSG_set_quality().
|
||||
2002 03-02 : Version 1.12 -- Removed PSG_init & PSG_close.
|
||||
2002 10-13 : Version 1.14 -- Fixed the envelope unit.
|
||||
2003 09-19 : Version 1.15 -- Added PSG_setMask and PSG_toggleMask
|
||||
2004 01-11 : Version 1.16 -- Fixed an envelope problem where the envelope
|
||||
frequency register is written before key-on.
|
||||
|
||||
References:
|
||||
psg.vhd -- 2000 written by Kazuhiro Tsujikawa.
|
||||
s_fme7.c -- 1999,2000 written by Mamiya (NEZplug).
|
||||
ay8910.c -- 1998-2001 Author unknown (MAME).
|
||||
MSX-Datapack -- 1991 ASCII Corp.
|
||||
AY-3-8910 data sheet
|
||||
|
||||
*****************************************************************************/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "emu2149.h"
|
||||
|
||||
static e_uint32 voltbl[2][32] = {
|
||||
{0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09,
|
||||
0x0B, 0x0D, 0x0F, 0x12,
|
||||
0x16, 0x1A, 0x1F, 0x25, 0x2D, 0x35, 0x3F, 0x4C, 0x5A, 0x6A, 0x7F, 0x97,
|
||||
0xB4, 0xD6, 0xEB, 0xFF},
|
||||
{0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x05, 0x05, 0x07, 0x07,
|
||||
0x0B, 0x0B, 0x0F, 0x0F,
|
||||
0x16, 0x16, 0x1F, 0x1F, 0x2D, 0x2D, 0x3F, 0x3F, 0x5A, 0x5A, 0x7F, 0x7F,
|
||||
0xB4, 0xB4, 0xFF, 0xFF}
|
||||
};
|
||||
|
||||
#define GETA_BITS 24
|
||||
|
||||
static void
|
||||
internal_refresh (PSG * psg)
|
||||
{
|
||||
if (psg->quality)
|
||||
{
|
||||
psg->base_incr = 1 << GETA_BITS;
|
||||
psg->realstep = (e_uint32) ((1 << 31) / psg->rate);
|
||||
psg->psgstep = (e_uint32) ((1 << 31) / (psg->clk / 16));
|
||||
psg->psgtime = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
psg->base_incr =
|
||||
(e_uint32) ((double) psg->clk * (1 << GETA_BITS) / (16 * psg->rate));
|
||||
}
|
||||
}
|
||||
|
||||
EMU2149_API void
|
||||
PSG_set_rate (PSG * psg, e_uint32 r)
|
||||
{
|
||||
psg->rate = r ? r : 44100;
|
||||
internal_refresh (psg);
|
||||
}
|
||||
|
||||
EMU2149_API void
|
||||
PSG_set_quality (PSG * psg, e_uint32 q)
|
||||
{
|
||||
psg->quality = q;
|
||||
internal_refresh (psg);
|
||||
}
|
||||
|
||||
EMU2149_API PSG *
|
||||
PSG_new (e_uint32 c, e_uint32 r)
|
||||
{
|
||||
PSG *psg;
|
||||
|
||||
psg = (PSG *) malloc (sizeof (PSG));
|
||||
if (psg == NULL)
|
||||
return NULL;
|
||||
|
||||
PSG_setVolumeMode (psg, EMU2149_VOL_DEFAULT);
|
||||
psg->clk = c;
|
||||
psg->rate = r ? r : 44100;
|
||||
PSG_set_quality (psg, 0);
|
||||
|
||||
return psg;
|
||||
}
|
||||
|
||||
EMU2149_API void
|
||||
PSG_setVolumeMode (PSG * psg, int type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case 1:
|
||||
psg->voltbl = voltbl[EMU2149_VOL_YM2149];
|
||||
break;
|
||||
case 2:
|
||||
psg->voltbl = voltbl[EMU2149_VOL_AY_3_8910];
|
||||
break;
|
||||
default:
|
||||
psg->voltbl = voltbl[EMU2149_VOL_DEFAULT];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EMU2149_API e_uint32
|
||||
PSG_setMask (PSG *psg, e_uint32 mask)
|
||||
{
|
||||
e_uint32 ret = 0;
|
||||
if(psg)
|
||||
{
|
||||
ret = psg->mask;
|
||||
psg->mask = mask;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
EMU2149_API e_uint32
|
||||
PSG_toggleMask (PSG *psg, e_uint32 mask)
|
||||
{
|
||||
e_uint32 ret = 0;
|
||||
if(psg)
|
||||
{
|
||||
ret = psg->mask;
|
||||
psg->mask ^= mask;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
EMU2149_API void
|
||||
PSG_reset (PSG * psg)
|
||||
{
|
||||
int i;
|
||||
|
||||
psg->base_count = 0;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
psg->cout[i] = 0;
|
||||
psg->count[i] = 0x1000;
|
||||
psg->freq[i] = 0;
|
||||
psg->edge[i] = 0;
|
||||
psg->volume[i] = 0;
|
||||
}
|
||||
|
||||
psg->mask = 0;
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
psg->reg[i] = 0;
|
||||
psg->adr = 0;
|
||||
|
||||
psg->noise_seed = 0xffff;
|
||||
psg->noise_count = 0x40;
|
||||
psg->noise_freq = 0;
|
||||
|
||||
psg->env_volume = 0;
|
||||
psg->env_ptr = 0;
|
||||
psg->env_freq = 0;
|
||||
psg->env_count = 0;
|
||||
psg->env_pause = 1;
|
||||
|
||||
psg->out = 0;
|
||||
}
|
||||
|
||||
EMU2149_API void
|
||||
PSG_delete (PSG * psg)
|
||||
{
|
||||
free (psg);
|
||||
}
|
||||
|
||||
EMU2149_API e_uint8
|
||||
PSG_readIO (PSG * psg)
|
||||
{
|
||||
return (e_uint8) (psg->reg[psg->adr]);
|
||||
}
|
||||
|
||||
EMU2149_API e_uint8
|
||||
PSG_readReg (PSG * psg, e_uint32 reg)
|
||||
{
|
||||
return (e_uint8) (psg->reg[reg & 0x1f]);
|
||||
|
||||
}
|
||||
|
||||
EMU2149_API void
|
||||
PSG_writeIO (PSG * psg, e_uint32 adr, e_uint32 val)
|
||||
{
|
||||
if (adr & 1)
|
||||
PSG_writeReg (psg, psg->adr, val);
|
||||
else
|
||||
psg->adr = val & 0x1f;
|
||||
}
|
||||
|
||||
INLINE static e_int16
|
||||
calc (PSG * psg)
|
||||
{
|
||||
|
||||
int i, noise;
|
||||
e_uint32 incr;
|
||||
e_int32 mix = 0;
|
||||
|
||||
psg->base_count += psg->base_incr;
|
||||
incr = (psg->base_count >> GETA_BITS);
|
||||
psg->base_count &= (1 << GETA_BITS) - 1;
|
||||
|
||||
/* Envelope */
|
||||
psg->env_count += incr;
|
||||
while (psg->env_count>=0x10000 && psg->env_freq!=0)
|
||||
{
|
||||
if (!psg->env_pause)
|
||||
{
|
||||
if(psg->env_face)
|
||||
psg->env_ptr = (psg->env_ptr + 1) & 0x3f ;
|
||||
else
|
||||
psg->env_ptr = (psg->env_ptr + 0x3f) & 0x3f;
|
||||
}
|
||||
|
||||
if (psg->env_ptr & 0x20) /* if carry or borrow */
|
||||
{
|
||||
if (psg->env_continue)
|
||||
{
|
||||
if (psg->env_alternate^psg->env_hold) psg->env_face ^= 1;
|
||||
if (psg->env_hold) psg->env_pause = 1;
|
||||
psg->env_ptr = psg->env_face?0:0x1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
psg->env_pause = 1;
|
||||
psg->env_ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
psg->env_count -= psg->env_freq;
|
||||
}
|
||||
|
||||
/* Noise */
|
||||
psg->noise_count += incr;
|
||||
if (psg->noise_count & 0x40)
|
||||
{
|
||||
if (psg->noise_seed & 1)
|
||||
psg->noise_seed ^= 0x24000;
|
||||
psg->noise_seed >>= 1;
|
||||
psg->noise_count -= psg->noise_freq;
|
||||
}
|
||||
noise = psg->noise_seed & 1;
|
||||
|
||||
/* Tone */
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
psg->count[i] += incr;
|
||||
if (psg->count[i] & 0x1000)
|
||||
{
|
||||
if (psg->freq[i] > 1)
|
||||
{
|
||||
psg->edge[i] = !psg->edge[i];
|
||||
psg->count[i] -= psg->freq[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
psg->edge[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
psg->cout[i] = 0; // maintaining cout for stereo mix
|
||||
|
||||
if (psg->mask&PSG_MASK_CH(i))
|
||||
continue;
|
||||
|
||||
if ((psg->tmask[i] || psg->edge[i]) && (psg->nmask[i] || noise))
|
||||
{
|
||||
if (!(psg->volume[i] & 32))
|
||||
psg->cout[i] = psg->voltbl[psg->volume[i] & 31];
|
||||
else
|
||||
psg->cout[i] = psg->voltbl[psg->env_ptr];
|
||||
|
||||
mix += psg->cout[i];
|
||||
}
|
||||
}
|
||||
|
||||
return (e_int16) mix;
|
||||
}
|
||||
|
||||
EMU2149_API e_int16
|
||||
PSG_calc (PSG * psg)
|
||||
{
|
||||
if (!psg->quality)
|
||||
return (e_int16) (calc (psg) << 4);
|
||||
|
||||
/* Simple rate converter */
|
||||
while (psg->realstep > psg->psgtime)
|
||||
{
|
||||
psg->psgtime += psg->psgstep;
|
||||
psg->out += calc (psg);
|
||||
psg->out >>= 1;
|
||||
}
|
||||
|
||||
psg->psgtime = psg->psgtime - psg->realstep;
|
||||
|
||||
return (e_int16) (psg->out << 4);
|
||||
}
|
||||
|
||||
EMU2149_API void
|
||||
PSG_writeReg (PSG * psg, e_uint32 reg, e_uint32 val)
|
||||
{
|
||||
int c;
|
||||
|
||||
if (reg > 15) return;
|
||||
|
||||
psg->reg[reg] = (e_uint8) (val & 0xff);
|
||||
switch (reg)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
c = reg >> 1;
|
||||
psg->freq[c] = ((psg->reg[c * 2 + 1] & 15) << 8) + psg->reg[c * 2];
|
||||
break;
|
||||
|
||||
case 6:
|
||||
psg->noise_freq = (val == 0) ? 1 : ((val & 31) << 1);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
psg->tmask[0] = (val & 1);
|
||||
psg->tmask[1] = (val & 2);
|
||||
psg->tmask[2] = (val & 4);
|
||||
psg->nmask[0] = (val & 8);
|
||||
psg->nmask[1] = (val & 16);
|
||||
psg->nmask[2] = (val & 32);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
case 9:
|
||||
case 10:
|
||||
psg->volume[reg - 8] = val << 1;
|
||||
|
||||
break;
|
||||
|
||||
case 11:
|
||||
case 12:
|
||||
psg->env_freq = (psg->reg[12] << 8) + psg->reg[11];
|
||||
break;
|
||||
|
||||
case 13:
|
||||
psg->env_continue = (val >> 3) & 1;
|
||||
psg->env_attack = (val >> 2) & 1;
|
||||
psg->env_alternate = (val >> 1) & 1;
|
||||
psg->env_hold = val & 1;
|
||||
psg->env_face = psg->env_attack;
|
||||
psg->env_pause = 0;
|
||||
psg->env_count = 0x10000 - psg->env_freq;
|
||||
psg->env_ptr = psg->env_face?0:0x1f;
|
||||
break;
|
||||
|
||||
case 14:
|
||||
case 15:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
94
extern/NSFplay/legacy/emu2149.h
vendored
Normal file
94
extern/NSFplay/legacy/emu2149.h
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
/* emu2149.h */
|
||||
#ifndef _EMU2149_H_
|
||||
#define _EMU2149_H_
|
||||
#include "emutypes.h"
|
||||
|
||||
#ifdef EMU2149_DLL_EXPORTS
|
||||
#define EMU2149_API __declspec(dllexport)
|
||||
#elif EMU2149_DLL_IMPORTS
|
||||
#define EMU2149_API __declspec(dllimport)
|
||||
#else
|
||||
#define EMU2149_API
|
||||
#endif
|
||||
|
||||
#define EMU2149_VOL_DEFAULT 1
|
||||
#define EMU2149_VOL_YM2149 0
|
||||
#define EMU2149_VOL_AY_3_8910 1
|
||||
|
||||
#define PSG_MASK_CH(x) (1<<(x))
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef struct __PSG
|
||||
{
|
||||
|
||||
/* Volume Table */
|
||||
e_uint32 *voltbl;
|
||||
|
||||
e_uint8 reg[0x20];
|
||||
e_int32 out;
|
||||
e_int32 cout[3];
|
||||
|
||||
e_uint32 clk, rate, base_incr, quality;
|
||||
|
||||
e_uint32 count[3];
|
||||
e_uint32 volume[3];
|
||||
e_uint32 freq[3];
|
||||
e_uint32 edge[3];
|
||||
e_uint32 tmask[3];
|
||||
e_uint32 nmask[3];
|
||||
e_uint32 mask;
|
||||
|
||||
e_uint32 base_count;
|
||||
|
||||
e_uint32 env_volume;
|
||||
e_uint32 env_ptr;
|
||||
e_uint32 env_face;
|
||||
|
||||
e_uint32 env_continue;
|
||||
e_uint32 env_attack;
|
||||
e_uint32 env_alternate;
|
||||
e_uint32 env_hold;
|
||||
e_uint32 env_pause;
|
||||
e_uint32 env_reset;
|
||||
|
||||
e_uint32 env_freq;
|
||||
e_uint32 env_count;
|
||||
|
||||
e_uint32 noise_seed;
|
||||
e_uint32 noise_count;
|
||||
e_uint32 noise_freq;
|
||||
|
||||
/* rate converter */
|
||||
e_uint32 realstep;
|
||||
e_uint32 psgtime;
|
||||
e_uint32 psgstep;
|
||||
|
||||
/* I/O Ctrl */
|
||||
e_uint32 adr;
|
||||
|
||||
}
|
||||
PSG;
|
||||
|
||||
EMU2149_API void PSG_set_quality (PSG * psg, e_uint32 q);
|
||||
EMU2149_API void PSG_set_rate (PSG * psg, e_uint32 r);
|
||||
EMU2149_API PSG *PSG_new (e_uint32 clk, e_uint32 rate);
|
||||
EMU2149_API void PSG_reset (PSG *);
|
||||
EMU2149_API void PSG_delete (PSG *);
|
||||
EMU2149_API void PSG_writeReg (PSG *, e_uint32 reg, e_uint32 val);
|
||||
EMU2149_API void PSG_writeIO (PSG * psg, e_uint32 adr, e_uint32 val);
|
||||
EMU2149_API e_uint8 PSG_readReg (PSG * psg, e_uint32 reg);
|
||||
EMU2149_API e_uint8 PSG_readIO (PSG * psg);
|
||||
EMU2149_API e_int16 PSG_calc (PSG *);
|
||||
EMU2149_API void PSG_setVolumeMode (PSG * psg, int type);
|
||||
EMU2149_API e_uint32 PSG_setMask (PSG *, e_uint32 mask);
|
||||
EMU2149_API e_uint32 PSG_toggleMask (PSG *, e_uint32 mask);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
485
extern/NSFplay/legacy/emu2212.c
vendored
Normal file
485
extern/NSFplay/legacy/emu2212.c
vendored
Normal file
|
@ -0,0 +1,485 @@
|
|||
/****************************************************************************
|
||||
|
||||
emu2212.c -- S.C.C. emulator by Mitsutaka Okazaki 2001
|
||||
|
||||
2001 09-30 : Version 1.00
|
||||
2001 10-03 : Version 1.01 -- Added SCC_set_quality().
|
||||
2002 02-14 : Version 1.10 -- Added SCC_writeReg(), SCC_set_type().
|
||||
Fixed SCC_write().
|
||||
2002 02-17 : Version 1.11 -- Fixed SCC_write().
|
||||
2002 03-02 : Version 1.12 -- Removed SCC_init & SCC_close.
|
||||
2003 09-19 : Version 1.13 -- Added SCC_setMask() and SCC_toggleMask()
|
||||
2004 10-21 : Version 1.14 -- Fixed the problem where SCC+ is disabled.
|
||||
|
||||
Registar map for SCC_writeReg()
|
||||
|
||||
$00-1F : WaveTable CH.A
|
||||
$20-3F : WaveTable CH.B
|
||||
$40-5F : WaveTable CH.C
|
||||
$60-7F : WaveTable CH.D&E(SCC), CH.D(SCC+)
|
||||
$80-9F : WaveTable CH.E
|
||||
|
||||
$C0 : CH.A Freq(L)
|
||||
$C1 : CH.A Freq(H)
|
||||
$C2 : CH.B Freq(L)
|
||||
$C3 : CH.B Freq(H)
|
||||
$C4 : CH.C Freq(L)
|
||||
$C5 : CH.C Freq(H)
|
||||
$C6 : CH.D Freq(L)
|
||||
$C7 : CH.D Freq(H)
|
||||
$C8 : CH.E Freq(L)
|
||||
$C9 : CH.E Freq(H)
|
||||
|
||||
$D0 : CH.A Volume
|
||||
$D1 : CH.B Volume
|
||||
$D2 : CH.C Volume
|
||||
$D3 : CH.D Volume
|
||||
$D4 : CH.E Volume
|
||||
|
||||
$E0 : Bit0 = 0:SCC, 1:SCC+
|
||||
$E1 : CH mask
|
||||
$E2 : Extra Flags
|
||||
|
||||
*****************************************************************************/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "emu2212.h"
|
||||
|
||||
#define GETA_BITS 22
|
||||
|
||||
static void
|
||||
internal_refresh (SCC * scc)
|
||||
{
|
||||
if (scc->quality)
|
||||
{
|
||||
scc->base_incr = 2 << GETA_BITS;
|
||||
scc->realstep = (e_uint32) ((1 << 31) / scc->rate);
|
||||
scc->sccstep = (e_uint32) ((1 << 31) / (scc->clk / 2));
|
||||
scc->scctime = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
scc->base_incr = (e_uint32) ((double) scc->clk * (1 << GETA_BITS) / scc->rate);
|
||||
}
|
||||
}
|
||||
|
||||
EMU2212_API e_uint32
|
||||
SCC_setMask (SCC *scc, e_uint32 mask)
|
||||
{
|
||||
e_uint32 ret = 0;
|
||||
if(scc)
|
||||
{
|
||||
ret = scc->mask;
|
||||
scc->mask = mask;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
EMU2212_API e_uint32
|
||||
SCC_toggleMask (SCC *scc, e_uint32 mask)
|
||||
{
|
||||
e_uint32 ret = 0;
|
||||
if(scc)
|
||||
{
|
||||
ret = scc->mask;
|
||||
scc->mask ^= mask;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
EMU2212_API void
|
||||
SCC_set_quality (SCC * scc, e_uint32 q)
|
||||
{
|
||||
scc->quality = q;
|
||||
internal_refresh (scc);
|
||||
}
|
||||
|
||||
EMU2212_API void
|
||||
SCC_set_rate (SCC * scc, e_uint32 r)
|
||||
{
|
||||
scc->rate = r ? r : 44100;
|
||||
internal_refresh (scc);
|
||||
}
|
||||
|
||||
EMU2212_API SCC *
|
||||
SCC_new (e_uint32 c, e_uint32 r)
|
||||
{
|
||||
SCC *scc;
|
||||
|
||||
scc = (SCC *) malloc (sizeof (SCC));
|
||||
if (scc == NULL)
|
||||
return NULL;
|
||||
memset(scc, 0, sizeof (SCC));
|
||||
|
||||
scc->clk = c;
|
||||
scc->rate = r ? r : 44100;
|
||||
SCC_set_quality (scc, 0);
|
||||
scc->type = SCC_ENHANCED;
|
||||
return scc;
|
||||
}
|
||||
|
||||
EMU2212_API void
|
||||
SCC_reset (SCC * scc)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
if (scc == NULL)
|
||||
return;
|
||||
|
||||
scc->mode = 0;
|
||||
scc->active = 0;
|
||||
scc->base_adr = 0x9000;
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
{
|
||||
for (j = 0; j < 5; j++)
|
||||
scc->wave[i][j] = 0;
|
||||
scc->count[i] = 0;
|
||||
scc->freq[i] = 0;
|
||||
scc->phase[i] = 0;
|
||||
scc->volume[i] = 0;
|
||||
scc->offset[i] = 0;
|
||||
scc->rotate[i] = 0;
|
||||
}
|
||||
|
||||
memset(scc->reg,0,0x100-0xC0);
|
||||
|
||||
scc->mask = 0;
|
||||
|
||||
scc->ch_enable = 0xff;
|
||||
scc->ch_enable_next = 0xff;
|
||||
|
||||
scc->cycle_4bit = 0;
|
||||
scc->cycle_8bit = 0;
|
||||
scc->refresh = 0;
|
||||
|
||||
scc->out = 0;
|
||||
scc->prev = 0;
|
||||
scc->next = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
EMU2212_API void
|
||||
SCC_delete (SCC * scc)
|
||||
{
|
||||
if (scc != NULL)
|
||||
free (scc);
|
||||
}
|
||||
|
||||
INLINE static e_int16
|
||||
calc (SCC * scc)
|
||||
{
|
||||
int i;
|
||||
e_int32 mix = 0;
|
||||
|
||||
for (i = 0; i < 5; i++)
|
||||
{
|
||||
scc->count[i] = (scc->count[i] + scc->incr[i]);
|
||||
|
||||
if (scc->count[i] & (1 << (GETA_BITS + 5)))
|
||||
{
|
||||
scc->count[i] &= ((1 << (GETA_BITS + 5)) - 1);
|
||||
scc->offset[i] = (scc->offset[i] + 31) & scc->rotate[i];
|
||||
scc->ch_enable &= ~(1 << i);
|
||||
scc->ch_enable |= scc->ch_enable_next & (1 << i);
|
||||
}
|
||||
|
||||
if (scc->ch_enable & (1 << i))
|
||||
{
|
||||
scc->phase[i] = ((scc->count[i] >> (GETA_BITS)) + scc->offset[i]) & 0x1F;
|
||||
if(!(scc->mask&SCC_MASK_CH(i)))
|
||||
mix += ((((e_int8) (scc->wave[i][scc->phase[i]]) * (e_int8) scc->volume[i]))) >> 4;
|
||||
}
|
||||
}
|
||||
|
||||
return (e_int16) (mix << 4);
|
||||
}
|
||||
|
||||
EMU2212_API e_int16
|
||||
SCC_calc (SCC * scc)
|
||||
{
|
||||
if (!scc->quality)
|
||||
return calc (scc);
|
||||
|
||||
while (scc->realstep > scc->scctime)
|
||||
{
|
||||
scc->scctime += scc->sccstep;
|
||||
scc->prev = scc->next;
|
||||
scc->next = calc (scc);
|
||||
}
|
||||
|
||||
scc->scctime -= scc->realstep;
|
||||
scc->out = (e_int16) (((double) scc->next * (scc->sccstep - scc->scctime) + (double) scc->prev * scc->scctime) / scc->sccstep);
|
||||
|
||||
return (e_int16) (scc->out);
|
||||
}
|
||||
|
||||
EMU2212_API e_uint32
|
||||
SCC_readReg (SCC * scc, e_uint32 adr)
|
||||
{
|
||||
if (adr < 0xA0)
|
||||
return scc->wave[adr >> 5][adr & 0x1f];
|
||||
else if( 0xC0 < adr && adr < 0xF0 )
|
||||
return scc->reg[adr-0xC0];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMU2212_API void
|
||||
SCC_writeReg (SCC * scc, e_uint32 adr, e_uint32 val)
|
||||
{
|
||||
int ch;
|
||||
e_uint32 freq;
|
||||
|
||||
adr &= 0xFF;
|
||||
|
||||
if (adr < 0xA0)
|
||||
{
|
||||
ch = (adr & 0xF0) >> 5;
|
||||
if (!scc->rotate[ch])
|
||||
{
|
||||
scc->wave[ch][adr & 0x1F] = (e_int8) val;
|
||||
if (scc->mode == 0 && ch == 3)
|
||||
scc->wave[4][adr & 0x1F] = (e_int8) val;
|
||||
}
|
||||
}
|
||||
else if (0xC0 <= adr && adr <= 0xC9)
|
||||
{
|
||||
scc->reg[adr-0xC0] = val;
|
||||
ch = (adr & 0x0F) >> 1;
|
||||
if (adr & 1)
|
||||
scc->freq[ch] = ((val & 0xF) << 8) | (scc->freq[ch] & 0xFF);
|
||||
else
|
||||
scc->freq[ch] = (scc->freq[ch] & 0xF00) | (val & 0xFF);
|
||||
|
||||
if (scc->refresh)
|
||||
scc->count[ch] = 0;
|
||||
freq = scc->freq[ch];
|
||||
if (scc->cycle_8bit)
|
||||
freq &= 0xFF;
|
||||
if (scc->cycle_4bit)
|
||||
freq >>= 8;
|
||||
if (freq <= 8)
|
||||
scc->incr[ch] = 0;
|
||||
else
|
||||
scc->incr[ch] = scc->base_incr / (freq + 1);
|
||||
}
|
||||
else if (0xD0 <= adr && adr <= 0xD4)
|
||||
{
|
||||
scc->reg[adr-0xC0] = val;
|
||||
scc->volume[adr & 0x0F] = (e_uint8) (val & 0xF);
|
||||
}
|
||||
else if (adr == 0xE0)
|
||||
{
|
||||
scc->reg[adr-0xC0] = val;
|
||||
scc->mode = (e_uint8) val & 1;
|
||||
}
|
||||
else if (adr == 0xE1)
|
||||
{
|
||||
scc->reg[adr-0xC0] = val;
|
||||
scc->ch_enable_next = (e_uint8) val & 0x1F;
|
||||
}
|
||||
else if (adr == 0xE2)
|
||||
{
|
||||
scc->reg[adr-0xC0] = val;
|
||||
scc->cycle_4bit = val & 1;
|
||||
scc->cycle_8bit = val & 2;
|
||||
scc->refresh = val & 32;
|
||||
if (val & 64)
|
||||
for (ch = 0; ch < 5; ch++)
|
||||
scc->rotate[ch] = 0x1F;
|
||||
else
|
||||
for (ch = 0; ch < 5; ch++)
|
||||
scc->rotate[ch] = 0;
|
||||
if (val & 128)
|
||||
scc->rotate[3] = scc->rotate[4] = 0x1F;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
INLINE static void
|
||||
write_standard (SCC * scc, e_uint32 adr, e_uint32 val)
|
||||
{
|
||||
adr &= 0xFF;
|
||||
|
||||
if (adr < 0x80) /* wave */
|
||||
{
|
||||
SCC_writeReg (scc, adr, val);
|
||||
}
|
||||
else if (adr < 0x8A) /* freq */
|
||||
{
|
||||
SCC_writeReg (scc, adr + 0xC0 - 0x80, val);
|
||||
}
|
||||
else if (adr < 0x8F) /* volume */
|
||||
{
|
||||
SCC_writeReg (scc, adr + 0xD0 - 0x8A, val);
|
||||
}
|
||||
else if (adr == 0x8F) /* ch enable */
|
||||
{
|
||||
SCC_writeReg (scc, 0xE1, val);
|
||||
}
|
||||
else if (0xE0 <= adr) /* flags */
|
||||
{
|
||||
SCC_writeReg (scc, 0xE2, val);
|
||||
}
|
||||
}
|
||||
|
||||
INLINE static void
|
||||
write_enhanced (SCC * scc, e_uint32 adr, e_uint32 val)
|
||||
{
|
||||
adr &= 0xFF;
|
||||
|
||||
if (adr < 0xA0) /* wave */
|
||||
{
|
||||
SCC_writeReg (scc, adr, val);
|
||||
}
|
||||
else if (adr < 0xAA) /* freq */
|
||||
{
|
||||
SCC_writeReg (scc, adr + 0xC0 - 0xA0, val);
|
||||
}
|
||||
else if (adr < 0xAF) /* volume */
|
||||
{
|
||||
SCC_writeReg (scc, adr + 0xD0 - 0xAA, val);
|
||||
}
|
||||
else if (adr == 0xAF) /* ch enable */
|
||||
{
|
||||
SCC_writeReg (scc, 0xE1, val);
|
||||
}
|
||||
else if (0xC0 <= adr && adr <= 0xDF) /* flags */
|
||||
{
|
||||
SCC_writeReg (scc, 0xE2, val);
|
||||
}
|
||||
}
|
||||
|
||||
INLINE static e_uint32
|
||||
read_enhanced (SCC * scc, e_uint32 adr)
|
||||
{
|
||||
adr &= 0xFF;
|
||||
if (adr < 0xA0)
|
||||
return SCC_readReg (scc, adr);
|
||||
else if (adr < 0xAA)
|
||||
return SCC_readReg (scc, adr + 0xC0 - 0xA0);
|
||||
else if (adr < 0xAF)
|
||||
return SCC_readReg (scc, adr + 0xD0 - 0xAA);
|
||||
else if (adr == 0xAF)
|
||||
return SCC_readReg (scc, 0xE1);
|
||||
else if (0xC0 <= adr && adr <= 0xDF)
|
||||
return SCC_readReg (scc, 0xE2);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
INLINE static e_uint32
|
||||
read_standard (SCC * scc, e_uint32 adr)
|
||||
{
|
||||
adr &= 0xFF;
|
||||
if(adr<0x80)
|
||||
return SCC_readReg (scc, adr);
|
||||
else if (0xA0<=adr&&adr<=0xBF)
|
||||
return SCC_readReg (scc, 0x80+(adr&0x1F));
|
||||
else if (adr < 0x8A)
|
||||
return SCC_readReg (scc, adr + 0xC0 - 0x80);
|
||||
else if (adr < 0x8F)
|
||||
return SCC_readReg (scc, adr + 0xD0 - 0x8A);
|
||||
else if (adr == 0x8F)
|
||||
return SCC_readReg (scc, 0xE1);
|
||||
else if (0xE0 <= adr)
|
||||
return SCC_readReg (scc, 0xE2);
|
||||
else return 0;
|
||||
}
|
||||
|
||||
EMU2212_API e_uint32
|
||||
SCC_read (SCC * scc, e_uint32 adr)
|
||||
{
|
||||
if( scc->type == SCC_ENHANCED && (adr&0xFFFE) == 0xBFFE )
|
||||
return (scc->base_adr>>8)&0x20;
|
||||
|
||||
if( adr < scc->base_adr ) return 0;
|
||||
adr -= scc->base_adr;
|
||||
|
||||
if( adr == 0 )
|
||||
{
|
||||
if(scc->mode) return 0x80; else return 0x3F;
|
||||
}
|
||||
|
||||
if(!scc->active||adr<0x800||0x8FF<adr) return 0;
|
||||
|
||||
switch (scc->type)
|
||||
{
|
||||
case SCC_STANDARD:
|
||||
return read_standard (scc, adr);
|
||||
break;
|
||||
case SCC_ENHANCED:
|
||||
if(!scc->mode)
|
||||
return read_standard (scc, adr);
|
||||
else
|
||||
return read_enhanced (scc, adr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMU2212_API void
|
||||
SCC_write (SCC * scc, e_uint32 adr, e_uint32 val)
|
||||
{
|
||||
val = val & 0xFF;
|
||||
|
||||
if( scc->type == SCC_ENHANCED && (adr&0xFFFE) == 0xBFFE )
|
||||
{
|
||||
scc->base_adr = 0x9000 | ((val&0x20)<<8);
|
||||
return;
|
||||
}
|
||||
|
||||
if( adr < scc->base_adr ) return;
|
||||
adr -= scc->base_adr;
|
||||
|
||||
if(adr == 0)
|
||||
{
|
||||
if( val == 0x3F )
|
||||
{
|
||||
scc->mode = 0;
|
||||
scc->active = 1;
|
||||
}
|
||||
else if( val&0x80 && scc->type == SCC_ENHANCED)
|
||||
{
|
||||
scc->mode = 1;
|
||||
scc->active = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
scc->mode = 0;
|
||||
scc->active = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!scc->active||adr<0x800||0x8FF<adr) return;
|
||||
|
||||
switch (scc->type)
|
||||
{
|
||||
case SCC_STANDARD:
|
||||
write_standard (scc, adr, val);
|
||||
break;
|
||||
case SCC_ENHANCED:
|
||||
if(scc->mode)
|
||||
write_enhanced (scc, adr, val);
|
||||
else
|
||||
write_standard (scc, adr, val);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
EMU2212_API void
|
||||
SCC_set_type (SCC * scc, e_uint32 type)
|
||||
{
|
||||
scc->type = type;
|
||||
}
|
77
extern/NSFplay/legacy/emu2212.h
vendored
Normal file
77
extern/NSFplay/legacy/emu2212.h
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
#ifndef _EMU2212_H_
|
||||
#define _EMU2212_H_
|
||||
|
||||
#ifdef EMU2212_DLL_EXPORTS
|
||||
#define EMU2212_API __declspec(dllexport)
|
||||
#elif defined(EMU2212_DLL_IMPORTS)
|
||||
#define EMU2212_API __declspec(dllimport)
|
||||
#else
|
||||
#define EMU2212_API
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "emutypes.h"
|
||||
|
||||
#define SCC_STANDARD 0
|
||||
#define SCC_ENHANCED 1
|
||||
|
||||
#define SCC_MASK_CH(x) (1<<(x))
|
||||
|
||||
typedef struct __SCC {
|
||||
|
||||
e_uint32 clk, rate ,base_incr, quality ;
|
||||
|
||||
e_int32 out, prev, next;
|
||||
e_uint32 type ;
|
||||
e_uint32 mode ;
|
||||
e_uint32 active;
|
||||
e_uint32 base_adr;
|
||||
e_uint32 mask ;
|
||||
|
||||
e_uint32 realstep ;
|
||||
e_uint32 scctime ;
|
||||
e_uint32 sccstep ;
|
||||
|
||||
e_uint32 incr[5] ;
|
||||
|
||||
e_int8 wave[5][32] ;
|
||||
|
||||
e_uint32 count[5] ;
|
||||
e_uint32 freq[5] ;
|
||||
e_uint32 phase[5] ;
|
||||
e_uint32 volume[5] ;
|
||||
e_uint32 offset[5] ;
|
||||
e_uint8 reg[0x100-0xC0];
|
||||
|
||||
int ch_enable ;
|
||||
int ch_enable_next ;
|
||||
|
||||
int cycle_4bit ;
|
||||
int cycle_8bit ;
|
||||
int refresh ;
|
||||
int rotate[5] ;
|
||||
|
||||
} SCC ;
|
||||
|
||||
|
||||
EMU2212_API SCC *SCC_new(e_uint32 c, e_uint32 r) ;
|
||||
EMU2212_API void SCC_reset(SCC *scc) ;
|
||||
EMU2212_API void SCC_set_rate(SCC *scc, e_uint32 r);
|
||||
EMU2212_API void SCC_set_quality(SCC *scc, e_uint32 q) ;
|
||||
EMU2212_API void SCC_set_type(SCC *scc, e_uint32 type) ;
|
||||
EMU2212_API void SCC_delete(SCC *scc) ;
|
||||
EMU2212_API e_int16 SCC_calc(SCC *scc) ;
|
||||
EMU2212_API void SCC_write(SCC *scc, e_uint32 adr, e_uint32 val) ;
|
||||
EMU2212_API void SCC_writeReg(SCC *scc, e_uint32 adr, e_uint32 val) ;
|
||||
EMU2212_API e_uint32 SCC_read(SCC *scc, e_uint32 adr) ;
|
||||
EMU2212_API e_uint32 SCC_setMask(SCC *scc, e_uint32 adr) ;
|
||||
EMU2212_API e_uint32 SCC_toggleMask(SCC *scc, e_uint32 adr) ;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
1461
extern/NSFplay/legacy/emu2413.c
vendored
Normal file
1461
extern/NSFplay/legacy/emu2413.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
247
extern/NSFplay/legacy/emu2413.h
vendored
Normal file
247
extern/NSFplay/legacy/emu2413.h
vendored
Normal file
|
@ -0,0 +1,247 @@
|
|||
#ifndef _EMU2413_H_
|
||||
#define _EMU2413_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define OPLL_DEBUG 0
|
||||
|
||||
enum OPLL_TONE_ENUM {
|
||||
OPLL_VRC7_TONE = 0,
|
||||
OPLL_VRC7_RW_TONE,
|
||||
OPLL_VRC7_FT36_TONE,
|
||||
OPLL_VRC7_FT35_TONE,
|
||||
OPLL_VRC7_MO_TONE,
|
||||
OPLL_VRC7_KT2_TONE,
|
||||
OPLL_VRC7_KT1_TONE,
|
||||
OPLL_2413_TONE,
|
||||
OPLL_281B_TONE
|
||||
};
|
||||
|
||||
/* voice data */
|
||||
typedef struct __OPLL_PATCH {
|
||||
uint32_t TL, FB, EG, ML, AR, DR, SL, RR, KR, KL, AM, PM, WS;
|
||||
} OPLL_PATCH;
|
||||
|
||||
/* slot */
|
||||
typedef struct __OPLL_SLOT {
|
||||
uint8_t number;
|
||||
|
||||
/* type flags:
|
||||
* 000000SM
|
||||
* |+-- M: 0:modulator 1:carrier
|
||||
* +--- S: 0:normal 1:single slot mode (sd, tom, hh or cym)
|
||||
*/
|
||||
uint8_t type;
|
||||
|
||||
OPLL_PATCH *patch; /* voice parameter */
|
||||
|
||||
/* slot output */
|
||||
int32_t output[2]; /* output value, latest and previous. */
|
||||
|
||||
/* phase generator (pg) */
|
||||
uint16_t *wave_table; /* wave table */
|
||||
uint32_t pg_phase; /* pg phase */
|
||||
uint32_t pg_out; /* pg output, as index of wave table */
|
||||
uint8_t pg_keep; /* if 1, pg_phase is preserved when key-on */
|
||||
uint16_t blk_fnum; /* (block << 9) | f-number */
|
||||
uint16_t fnum; /* f-number (9 bits) */
|
||||
uint8_t blk; /* block (3 bits) */
|
||||
|
||||
/* envelope generator (eg) */
|
||||
uint8_t eg_state; /* current state */
|
||||
int32_t volume; /* current volume */
|
||||
uint8_t key_flag; /* key-on flag 1:on 0:off */
|
||||
uint8_t sus_flag; /* key-sus option 1:on 0:off */
|
||||
uint16_t tll; /* total level + key scale level*/
|
||||
uint8_t rks; /* key scale offset (rks) for eg speed */
|
||||
uint8_t eg_rate_h; /* eg speed rate high 4bits */
|
||||
uint8_t eg_rate_l; /* eg speed rate low 2bits */
|
||||
uint32_t eg_shift; /* shift for eg global counter, controls envelope speed */
|
||||
uint32_t eg_out; /* eg output */
|
||||
|
||||
uint32_t update_requests; /* flags to debounce update */
|
||||
|
||||
#if OPLL_DEBUG
|
||||
uint8_t last_eg_state;
|
||||
#endif
|
||||
} OPLL_SLOT;
|
||||
|
||||
/* mask */
|
||||
#define OPLL_MASK_CH(x) (1 << (x))
|
||||
#define OPLL_MASK_HH (1 << (9))
|
||||
#define OPLL_MASK_CYM (1 << (10))
|
||||
#define OPLL_MASK_TOM (1 << (11))
|
||||
#define OPLL_MASK_SD (1 << (12))
|
||||
#define OPLL_MASK_BD (1 << (13))
|
||||
#define OPLL_MASK_RHYTHM (OPLL_MASK_HH | OPLL_MASK_CYM | OPLL_MASK_TOM | OPLL_MASK_SD | OPLL_MASK_BD)
|
||||
|
||||
/* rate conveter */
|
||||
typedef struct __OPLL_RateConv {
|
||||
int ch;
|
||||
double timer;
|
||||
double f_ratio;
|
||||
int16_t *sinc_table;
|
||||
int16_t **buf;
|
||||
} OPLL_RateConv;
|
||||
|
||||
OPLL_RateConv *OPLL_RateConv_new(double f_inp, double f_out, int ch);
|
||||
void OPLL_RateConv_reset(OPLL_RateConv *conv);
|
||||
void OPLL_RateConv_putData(OPLL_RateConv *conv, int ch, int16_t data);
|
||||
int16_t OPLL_RateConv_getData(OPLL_RateConv *conv, int ch);
|
||||
void OPLL_RateConv_delete(OPLL_RateConv *conv);
|
||||
|
||||
typedef struct __OPLL {
|
||||
uint32_t clk;
|
||||
uint32_t rate;
|
||||
|
||||
uint8_t chip_type;
|
||||
|
||||
uint32_t adr;
|
||||
|
||||
uint32_t inp_step;
|
||||
uint32_t out_step;
|
||||
uint32_t out_time;
|
||||
|
||||
uint8_t reg[0x40];
|
||||
uint8_t test_flag;
|
||||
uint32_t slot_key_status;
|
||||
uint8_t rhythm_mode;
|
||||
|
||||
uint32_t eg_counter;
|
||||
|
||||
uint32_t pm_phase;
|
||||
int32_t am_phase;
|
||||
|
||||
uint8_t lfo_am;
|
||||
|
||||
uint32_t noise;
|
||||
uint8_t short_noise;
|
||||
|
||||
int32_t patch_number[9];
|
||||
OPLL_SLOT slot[18];
|
||||
OPLL_PATCH patch[19 * 2];
|
||||
|
||||
uint8_t pan[16];
|
||||
float pan_fine[16][2];
|
||||
|
||||
uint32_t mask;
|
||||
|
||||
/* channel output */
|
||||
/* 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym */
|
||||
int16_t ch_out[14];
|
||||
|
||||
int16_t mix_out[2];
|
||||
|
||||
OPLL_RateConv *conv;
|
||||
} OPLL;
|
||||
|
||||
OPLL *OPLL_new(uint32_t clk, uint32_t rate);
|
||||
void OPLL_delete(OPLL *);
|
||||
|
||||
void OPLL_reset(OPLL *);
|
||||
void OPLL_resetPatch(OPLL *, uint8_t);
|
||||
|
||||
/**
|
||||
* Set output wave sampling rate.
|
||||
* @param rate sampling rate. If clock / 72 (typically 49716 or 49715 at 3.58MHz) is set, the internal rate converter is
|
||||
* disabled.
|
||||
*/
|
||||
void OPLL_setRate(OPLL *opll, uint32_t rate);
|
||||
|
||||
/**
|
||||
* Set internal calcuration quality. Currently no effects, just for compatibility.
|
||||
* >= v1.0.0 always synthesizes internal output at clock/72 Hz.
|
||||
*/
|
||||
void OPLL_setQuality(OPLL *opll, uint8_t q);
|
||||
|
||||
/**
|
||||
* Set pan pot (extra function - not YM2413 chip feature)
|
||||
* @param ch 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym 14,15:reserved
|
||||
* @param pan 0:mute 1:right 2:left 3:center
|
||||
* ```
|
||||
* pan: 76543210
|
||||
* |+- bit 1: enable Left output
|
||||
* +-- bit 0: enable Right output
|
||||
* ```
|
||||
*/
|
||||
void OPLL_setPan(OPLL *opll, uint32_t ch, uint8_t pan);
|
||||
|
||||
/**
|
||||
* Set fine-grained panning
|
||||
* @param ch 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym 14,15:reserved
|
||||
* @param pan output strength of left/right channel.
|
||||
* pan[0]: left, pan[1]: right. pan[0]=pan[1]=1.0f for center.
|
||||
*/
|
||||
void OPLL_setPanFine(OPLL *opll, uint32_t ch, float pan[2]);
|
||||
|
||||
/**
|
||||
* Set chip type. If vrc7 is selected, r#14 is ignored.
|
||||
* This method not change the current ROM patch set.
|
||||
* To change ROM patch set, use OPLL_resetPatch.
|
||||
* @param type 0:YM2413 1:VRC7
|
||||
*/
|
||||
void OPLL_setChipType(OPLL *opll, uint8_t type);
|
||||
|
||||
void OPLL_writeIO(OPLL *opll, uint32_t reg, uint8_t val);
|
||||
void OPLL_writeReg(OPLL *opll, uint32_t reg, uint8_t val);
|
||||
|
||||
/**
|
||||
* Calculate one sample
|
||||
*/
|
||||
int16_t OPLL_calc(OPLL *opll);
|
||||
|
||||
/**
|
||||
* Calulate stereo sample
|
||||
*/
|
||||
void OPLL_calcStereo(OPLL *opll, int32_t out[2]);
|
||||
|
||||
void OPLL_setPatch(OPLL *, const uint8_t *dump);
|
||||
void OPLL_copyPatch(OPLL *, int32_t, OPLL_PATCH *);
|
||||
|
||||
/**
|
||||
* Force to refresh.
|
||||
* External program should call this function after updating patch parameters.
|
||||
*/
|
||||
void OPLL_forceRefresh(OPLL *);
|
||||
|
||||
void OPLL_dumpToPatch(const uint8_t *dump, OPLL_PATCH *patch);
|
||||
void OPLL_patchToDump(const OPLL_PATCH *patch, uint8_t *dump);
|
||||
void OPLL_getDefaultPatch(int32_t type, int32_t num, OPLL_PATCH *);
|
||||
|
||||
/**
|
||||
* Set channel mask
|
||||
* @param mask mask flag: OPLL_MASK_* can be used.
|
||||
* - bit 0..8: mask for ch 1 to 9 (OPLL_MASK_CH(i))
|
||||
* - bit 9: mask for Hi-Hat (OPLL_MASK_HH)
|
||||
* - bit 10: mask for Top-Cym (OPLL_MASK_CYM)
|
||||
* - bit 11: mask for Tom (OPLL_MASK_TOM)
|
||||
* - bit 12: mask for Snare Drum (OPLL_MASK_SD)
|
||||
* - bit 13: mask for Bass Drum (OPLL_MASK_BD)
|
||||
*/
|
||||
uint32_t OPLL_setMask(OPLL *, uint32_t mask);
|
||||
|
||||
/**
|
||||
* Toggler channel mask flag
|
||||
*/
|
||||
uint32_t OPLL_toggleMask(OPLL *, uint32_t mask);
|
||||
|
||||
/* for compatibility */
|
||||
#define OPLL_set_rate OPLL_setRate
|
||||
#define OPLL_set_quality OPLL_setQuality
|
||||
#define OPLL_set_pan OPLL_setPan
|
||||
#define OPLL_set_pan_fine OPLL_setPanFine
|
||||
#define OPLL_calc_stereo OPLL_calcStereo
|
||||
#define OPLL_reset_patch OPLL_resetPatch
|
||||
#define OPLL_dump2patch OPLL_dumpToPatch
|
||||
#define OPLL_patch2dump OPLL_patchToDump
|
||||
#define OPLL_setChipMode OPLL_setChipType
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
41
extern/NSFplay/legacy/emutypes.h
vendored
Normal file
41
extern/NSFplay/legacy/emutypes.h
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef _EMUTYPES_H_
|
||||
#define _EMUTYPES_H_
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define INLINE __forceinline
|
||||
#elif defined(__GNUC__)
|
||||
#define INLINE __inline__
|
||||
#elif defined(_MWERKS_)
|
||||
#define INLINE inline
|
||||
#else
|
||||
#define INLINE
|
||||
#endif
|
||||
|
||||
#if defined(EMU_DLL_IMPORTS)
|
||||
#define EMU2149_DLL_IMPORTS
|
||||
#define EMU2212_DLL_IMPORTS
|
||||
#define EMU2413_DLL_IMPORTS
|
||||
#define EMU8950_DLL_IMPORTS
|
||||
#define EMU76489_DLL_IMPORTS
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef unsigned int e_uint;
|
||||
typedef signed int e_int;
|
||||
|
||||
typedef unsigned char e_uint8 ;
|
||||
typedef signed char e_int8 ;
|
||||
|
||||
typedef unsigned short e_uint16 ;
|
||||
typedef signed short e_int16 ;
|
||||
|
||||
typedef unsigned int e_uint32 ;
|
||||
typedef signed int e_int32 ;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
20
extern/NSFplay/legacy/vrc7tone_ft35.h
vendored
Normal file
20
extern/NSFplay/legacy/vrc7tone_ft35.h
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
// patch set by Mitsutaka Okazaki used in FamiTracker 0.3.5 and prior (6/24/2001)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x33, 0x01, 0x09, 0x0e, 0x94, 0x90, 0x40, 0x01,
|
||||
0x13, 0x41, 0x0f, 0x0d, 0xce, 0xd3, 0x43, 0x13,
|
||||
0x01, 0x12, 0x1b, 0x06, 0xff, 0xd2, 0x00, 0x32,
|
||||
0x61, 0x61, 0x1b, 0x07, 0xaf, 0x63, 0x20, 0x28,
|
||||
0x22, 0x21, 0x1e, 0x06, 0xf0, 0x76, 0x08, 0x28,
|
||||
0x66, 0x21, 0x15, 0x00, 0x93, 0x94, 0x20, 0xf8,
|
||||
0x21, 0x61, 0x1c, 0x07, 0x82, 0x81, 0x10, 0x17,
|
||||
0x23, 0x21, 0x20, 0x1f, 0xc0, 0x71, 0x07, 0x47,
|
||||
0x25, 0x31, 0x26, 0x05, 0x64, 0x41, 0x18, 0xf8,
|
||||
0x17, 0x21, 0x28, 0x07, 0xff, 0x83, 0x02, 0xf8,
|
||||
0x97, 0x81, 0x25, 0x07, 0xcf, 0xc8, 0x02, 0x14,
|
||||
0x21, 0x21, 0x54, 0x0f, 0x80, 0x7f, 0x07, 0x07,
|
||||
0x01, 0x01, 0x56, 0x03, 0xd3, 0xb2, 0x43, 0x58,
|
||||
0x31, 0x21, 0x0c, 0x03, 0x82, 0xc0, 0x40, 0x07,
|
||||
0x21, 0x01, 0x0c, 0x03, 0xd4, 0xd3, 0x40, 0x84,
|
||||
0x04, 0x21, 0x28, 0x00, 0xdf, 0xf8, 0xff, 0xf8,
|
||||
0x23, 0x22, 0x00, 0x00, 0xa8, 0xf8, 0xf8, 0xf8,
|
||||
0x25, 0x18, 0x00, 0x00, 0xf8, 0xa9, 0xf8, 0x55,
|
21
extern/NSFplay/legacy/vrc7tone_ft36.h
vendored
Normal file
21
extern/NSFplay/legacy/vrc7tone_ft36.h
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// patch set by quietust (1/18/2004), used in FamiTracker 0.3.6
|
||||
// Source: http://nesdev.com/cgi-bin/wwwthreads/showpost.pl?Board=NESemdev&Number=1440
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0x21, 0x04, 0x06, 0x8D, 0xF2, 0x42, 0x17,
|
||||
0x13, 0x41, 0x05, 0x0E, 0x99, 0x96, 0x63, 0x12,
|
||||
0x31, 0x11, 0x10, 0x0A, 0xF0, 0x9C, 0x32, 0x02,
|
||||
0x21, 0x61, 0x1D, 0x07, 0x9F, 0x64, 0x20, 0x27,
|
||||
0x22, 0x21, 0x1E, 0x06, 0xF0, 0x76, 0x08, 0x28,
|
||||
0x02, 0x01, 0x06, 0x00, 0xF0, 0xF2, 0x03, 0x95,
|
||||
0x21, 0x61, 0x1C, 0x07, 0x82, 0x81, 0x16, 0x07,
|
||||
0x23, 0x21, 0x1A, 0x17, 0xEF, 0x82, 0x25, 0x15,
|
||||
0x25, 0x11, 0x1F, 0x00, 0x86, 0x41, 0x20, 0x11,
|
||||
0x85, 0x01, 0x1F, 0x0F, 0xE4, 0xA2, 0x11, 0x12,
|
||||
0x07, 0xC1, 0x2B, 0x45, 0xB4, 0xF1, 0x24, 0xF4,
|
||||
0x61, 0x23, 0x11, 0x06, 0x96, 0x96, 0x13, 0x16,
|
||||
0x01, 0x02, 0xD3, 0x05, 0x82, 0xA2, 0x31, 0x51,
|
||||
0x61, 0x22, 0x0D, 0x02, 0xC3, 0x7F, 0x24, 0x05,
|
||||
0x21, 0x62, 0x0E, 0x00, 0xA1, 0xA0, 0x44, 0x17,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
21
extern/NSFplay/legacy/vrc7tone_kt1.h
vendored
Normal file
21
extern/NSFplay/legacy/vrc7tone_kt1.h
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// patch set 1 by kevtris (11/14/1999)
|
||||
// http://kevtris.org/nes/vrcvii.txt
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x05, 0x03, 0x10, 0x06, 0x74, 0xA1, 0x13, 0xF4,
|
||||
0x05, 0x01, 0x16, 0x00, 0xF9, 0xA2, 0x15, 0xF5,
|
||||
0x01, 0x41, 0x11, 0x00, 0xA0, 0xA0, 0x83, 0x95,
|
||||
0x01, 0x41, 0x17, 0x00, 0x60, 0xF0, 0x83, 0x95,
|
||||
0x24, 0x41, 0x1F, 0x00, 0x50, 0xB0, 0x94, 0x94,
|
||||
0x05, 0x01, 0x0B, 0x04, 0x65, 0xA0, 0x54, 0x95,
|
||||
0x11, 0x41, 0x0E, 0x04, 0x70, 0xC7, 0x13, 0x10,
|
||||
0x02, 0x44, 0x16, 0x06, 0xE0, 0xE0, 0x31, 0x35,
|
||||
0x48, 0x22, 0x22, 0x07, 0x50, 0xA1, 0xA5, 0xF4,
|
||||
0x05, 0xA1, 0x18, 0x00, 0xA2, 0xA2, 0xF5, 0xF5,
|
||||
0x07, 0x81, 0x2B, 0x05, 0xA5, 0xA5, 0x03, 0x03,
|
||||
0x01, 0x41, 0x08, 0x08, 0xA0, 0xA0, 0x83, 0x95,
|
||||
0x21, 0x61, 0x12, 0x00, 0x93, 0x92, 0x74, 0x75,
|
||||
0x21, 0x62, 0x21, 0x00, 0x84, 0x85, 0x34, 0x15,
|
||||
0x21, 0x62, 0x0E, 0x00, 0xA1, 0xA0, 0x34, 0x15,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
21
extern/NSFplay/legacy/vrc7tone_kt2.h
vendored
Normal file
21
extern/NSFplay/legacy/vrc7tone_kt2.h
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// patch set 2 by kevtris (11/15/1999)
|
||||
// http://kevtris.org/nes/vrcvii.txt
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x31, 0x22, 0x23, 0x07, 0xF0, 0xF0, 0xE8, 0xF7,
|
||||
0x03, 0x31, 0x68, 0x05, 0xF2, 0x74, 0x79, 0x9C,
|
||||
0x01, 0x51, 0x72, 0x04, 0xF1, 0xD3, 0x9D, 0x8B,
|
||||
0x22, 0x61, 0x1B, 0x05, 0xC0, 0xA1, 0xF8, 0xE8,
|
||||
0x22, 0x61, 0x2C, 0x03, 0xD2, 0xA1, 0xA7, 0xE8,
|
||||
0x31, 0x22, 0xFA, 0x01, 0xF1, 0xF1, 0xF4, 0xEE,
|
||||
0x21, 0x61, 0x28, 0x06, 0xF1, 0xF1, 0xCE, 0x9B,
|
||||
0x27, 0x61, 0x60, 0x00, 0xF0, 0xF0, 0xFF, 0xFD,
|
||||
0x60, 0x21, 0x2B, 0x06, 0x85, 0xF1, 0x79, 0x9D,
|
||||
0x31, 0xA1, 0xFF, 0x0A, 0x53, 0x62, 0x5E, 0xAF,
|
||||
0x03, 0xA1, 0x70, 0x0F, 0xD4, 0xA3, 0x94, 0xBE,
|
||||
0x2B, 0x61, 0xE4, 0x07, 0xF6, 0x93, 0xBD, 0xAC,
|
||||
0x21, 0x63, 0xED, 0x07, 0x77, 0xF1, 0xC7, 0xE8,
|
||||
0x21, 0x61, 0x2A, 0x03, 0xF3, 0xE2, 0xB6, 0xD9,
|
||||
0x21, 0x63, 0x37, 0x03, 0xF3, 0xE2, 0xB6, 0xD9,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
20
extern/NSFplay/legacy/vrc7tone_mo.h
vendored
Normal file
20
extern/NSFplay/legacy/vrc7tone_mo.h
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* VRC7 TONES by okazaki@angel.ne.jp (4/10/2004) */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x33,0x01,0x09,0x0e,0x94,0x90,0x40,0x01,
|
||||
0x13,0x41,0x0f,0x0d,0xce,0xd3,0x43,0x13,
|
||||
0x01,0x12,0x1b,0x06,0xff,0xd2,0x00,0x32,
|
||||
0x61,0x61,0x1b,0x07,0xaf,0x63,0x20,0x28,
|
||||
0x22,0x21,0x1e,0x06,0xf0,0x76,0x08,0x28,
|
||||
0x66,0x21,0x15,0x00,0x93,0x94,0x20,0xf8,
|
||||
0x21,0x61,0x1c,0x07,0x82,0x81,0x10,0x17,
|
||||
0x23,0x21,0x20,0x1f,0xc0,0x71,0x07,0x47,
|
||||
0x25,0x31,0x26,0x05,0x64,0x41,0x18,0xf8,
|
||||
0x17,0x21,0x28,0x07,0xff,0x83,0x02,0xf8,
|
||||
0x97,0x81,0x25,0x07,0xcf,0xc8,0x02,0x14,
|
||||
0x21,0x21,0x54,0x0f,0x80,0x7f,0x07,0x07,
|
||||
0x01,0x01,0x56,0x03,0xd3,0xb2,0x43,0x58,
|
||||
0x31,0x21,0x0c,0x03,0x82,0xc0,0x40,0x07,
|
||||
0x21,0x01,0x0c,0x03,0xd4,0xd3,0x40,0x84,
|
||||
0x07,0x21,0x14,0x00,0xee,0xf8,0xff,0xf8,
|
||||
0x01,0x31,0x00,0x00,0xf8,0xf7,0xf8,0xf7,
|
||||
0x25,0x11,0x00,0x00,0xf8,0xfa,0xf8,0x55,
|
21
extern/NSFplay/legacy/vrc7tone_nuke.h
vendored
Normal file
21
extern/NSFplay/legacy/vrc7tone_nuke.h
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// patch set by Nuke.YKT (3/15/2019)
|
||||
// https://siliconpr0n.org/archive/doku.php?id=vendor:yamaha:opl2#ym2413_instruments
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0x21, 0x05, 0x06, 0xE8, 0x81, 0x42, 0x27,
|
||||
0x13, 0x41, 0x14, 0x0D, 0xD8, 0xF6, 0x23, 0x12,
|
||||
0x11, 0x11, 0x08, 0x08, 0xFA, 0xB2, 0x20, 0x12,
|
||||
0x31, 0x61, 0x0C, 0x07, 0xA8, 0x64, 0x61, 0x27,
|
||||
0x32, 0x21, 0x1E, 0x06, 0xE1, 0x76, 0x01, 0x28,
|
||||
0x02, 0x01, 0x06, 0x00, 0xA3, 0xE2, 0xF4, 0xF4,
|
||||
0x21, 0x61, 0x1D, 0x07, 0x82, 0x81, 0x11, 0x07,
|
||||
0x23, 0x21, 0x22, 0x17, 0xA2, 0x72, 0x01, 0x17,
|
||||
0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01,
|
||||
0xB5, 0x01, 0x0F, 0x0F, 0xA8, 0xA5, 0x51, 0x02,
|
||||
0x17, 0xC1, 0x24, 0x07, 0xF8, 0xF8, 0x22, 0x12,
|
||||
0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16,
|
||||
0x01, 0x02, 0xD3, 0x05, 0xC9, 0x95, 0x03, 0x02,
|
||||
0x61, 0x63, 0x0C, 0x00, 0x94, 0xC0, 0x33, 0xF6,
|
||||
0x21, 0x72, 0x0D, 0x00, 0xC1, 0xD5, 0x56, 0x06,
|
||||
0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D,
|
||||
0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x68,
|
||||
0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55,
|
21
extern/NSFplay/legacy/vrc7tone_rw.h
vendored
Normal file
21
extern/NSFplay/legacy/vrc7tone_rw.h
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// patch set by rainwarrior (8/01/2012)
|
||||
// http://forums.nesdev.com/viewtopic.php?f=6&t=9141
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0x21, 0x05, 0x06, 0xB8, 0x82, 0x42, 0x27,
|
||||
0x13, 0x41, 0x13, 0x0D, 0xD8, 0xD6, 0x23, 0x12,
|
||||
0x31, 0x11, 0x08, 0x08, 0xFA, 0x9A, 0x22, 0x02,
|
||||
0x31, 0x61, 0x18, 0x07, 0x78, 0x64, 0x30, 0x27,
|
||||
0x22, 0x21, 0x1E, 0x06, 0xF0, 0x76, 0x08, 0x28,
|
||||
0x02, 0x01, 0x06, 0x00, 0xF0, 0xF2, 0x03, 0xF5,
|
||||
0x21, 0x61, 0x1D, 0x07, 0x82, 0x81, 0x16, 0x07,
|
||||
0x23, 0x21, 0x1A, 0x17, 0xCF, 0x72, 0x25, 0x17,
|
||||
0x15, 0x11, 0x25, 0x00, 0x4F, 0x71, 0x00, 0x11,
|
||||
0x85, 0x01, 0x12, 0x0F, 0x99, 0xA2, 0x40, 0x02,
|
||||
0x07, 0xC1, 0x69, 0x07, 0xF3, 0xF5, 0xA7, 0x12,
|
||||
0x71, 0x23, 0x0D, 0x06, 0x66, 0x75, 0x23, 0x16,
|
||||
0x01, 0x02, 0xD3, 0x05, 0xA3, 0x92, 0xF7, 0x52,
|
||||
0x61, 0x63, 0x0C, 0x00, 0x94, 0xAF, 0x34, 0x06,
|
||||
0x21, 0x62, 0x0D, 0x00, 0xB1, 0xA0, 0x54, 0x17,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
400
extern/NSFplay/nes_apu.cpp
vendored
Normal file
400
extern/NSFplay/nes_apu.cpp
vendored
Normal file
|
@ -0,0 +1,400 @@
|
|||
//
|
||||
// NES 2A03
|
||||
//
|
||||
#include <assert.h>
|
||||
#include "nes_apu.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
void NES_APU::sweep_sqr (int i)
|
||||
{
|
||||
int shifted = freq[i] >> sweep_amount[i];
|
||||
if (i == 0 && sweep_mode[i]) shifted += 1;
|
||||
sfreq[i] = freq[i] + (sweep_mode[i] ? -shifted : shifted);
|
||||
//DEBUG_OUT("shifted[%d] = %d (%d >> %d)\n",i,shifted,freq[i],sweep_amount[i]);
|
||||
}
|
||||
|
||||
void NES_APU::FrameSequence(int s)
|
||||
{
|
||||
//DEBUG_OUT("FrameSequence(%d)\n",s);
|
||||
|
||||
if (s > 3) return; // no operation in step 4
|
||||
|
||||
// 240hz clock
|
||||
for (int i=0; i < 2; ++i)
|
||||
{
|
||||
bool divider = false;
|
||||
if (envelope_write[i])
|
||||
{
|
||||
envelope_write[i] = false;
|
||||
envelope_counter[i] = 15;
|
||||
envelope_div[i] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
++envelope_div[i];
|
||||
if (envelope_div[i] > envelope_div_period[i])
|
||||
{
|
||||
divider = true;
|
||||
envelope_div[i] = 0;
|
||||
}
|
||||
}
|
||||
if (divider)
|
||||
{
|
||||
if (envelope_loop[i] && envelope_counter[i] == 0)
|
||||
envelope_counter[i] = 15;
|
||||
else if (envelope_counter[i] > 0)
|
||||
--envelope_counter[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 120hz clock
|
||||
if ((s&1) == 0)
|
||||
for (int i=0; i < 2; ++i)
|
||||
{
|
||||
if (!envelope_loop[i] && (length_counter[i] > 0))
|
||||
--length_counter[i];
|
||||
|
||||
if (sweep_enable[i])
|
||||
{
|
||||
//DEBUG_OUT("Clock sweep: %d\n", i);
|
||||
|
||||
--sweep_div[i];
|
||||
if (sweep_div[i] <= 0)
|
||||
{
|
||||
sweep_sqr(i); // calculate new sweep target
|
||||
|
||||
//DEBUG_OUT("sweep_div[%d] (0/%d)\n",i,sweep_div_period[i]);
|
||||
//DEBUG_OUT("freq[%d]=%d > sfreq[%d]=%d\n",i,freq[i],i,sfreq[i]);
|
||||
|
||||
if (freq[i] >= 8 && sfreq[i] < 0x800 && sweep_amount[i] > 0) // update frequency if appropriate
|
||||
{
|
||||
freq[i] = sfreq[i] < 0 ? 0 : sfreq[i];
|
||||
}
|
||||
sweep_div[i] = sweep_div_period[i] + 1;
|
||||
|
||||
//DEBUG_OUT("freq[%d]=%d\n",i,freq[i]);
|
||||
}
|
||||
|
||||
if (sweep_write[i])
|
||||
{
|
||||
sweep_div[i] = sweep_div_period[i] + 1;
|
||||
sweep_write[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
INT32 NES_APU::calc_sqr (int i, UINT32 clocks)
|
||||
{
|
||||
static const INT16 sqrtbl[4][16] = {
|
||||
{0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0},
|
||||
{1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
};
|
||||
|
||||
scounter[i] -= clocks;
|
||||
while (scounter[i] < 0)
|
||||
{
|
||||
sphase[i] = (sphase[i] + 1) & 15;
|
||||
scounter[i] += freq[i] + 1;
|
||||
}
|
||||
|
||||
INT32 ret = 0;
|
||||
if (length_counter[i] > 0 &&
|
||||
freq[i] >= 8 &&
|
||||
sfreq[i] < 0x800
|
||||
)
|
||||
{
|
||||
int v = envelope_disable[i] ? volume[i] : envelope_counter[i];
|
||||
ret = sqrtbl[duty[i]][sphase[i]] ? v : 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool NES_APU::Read (UINT32 adr, UINT32 & val, UINT32 id)
|
||||
{
|
||||
if (0x4000 <= adr && adr < 0x4008)
|
||||
{
|
||||
val |= reg[adr&0x7];
|
||||
return true;
|
||||
}
|
||||
else if(adr==0x4015)
|
||||
{
|
||||
val |= (length_counter[1]?2:0)|(length_counter[0]?1:0);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void NES_APU::Tick (UINT32 clocks)
|
||||
{
|
||||
out[0] = calc_sqr(0, clocks);
|
||||
out[1] = calc_sqr(1, clocks);
|
||||
}
|
||||
|
||||
// 生成される波形の振幅は0-8191
|
||||
UINT32 NES_APU::Render (INT32 b[2])
|
||||
{
|
||||
out[0] = (mask & 1) ? 0 : out[0];
|
||||
out[1] = (mask & 2) ? 0 : out[1];
|
||||
|
||||
INT32 m[2];
|
||||
|
||||
if(option[OPT_NONLINEAR_MIXER])
|
||||
{
|
||||
INT32 voltage = square_table[out[0] + out[1]];
|
||||
m[0] = out[0] << 6;
|
||||
m[1] = out[1] << 6;
|
||||
INT32 ref = m[0] + m[1];
|
||||
if (ref > 0)
|
||||
{
|
||||
m[0] = (m[0] * voltage) / ref;
|
||||
m[1] = (m[1] * voltage) / ref;
|
||||
}
|
||||
else
|
||||
{
|
||||
m[0] = voltage;
|
||||
m[1] = voltage;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m[0] = (out[0] * square_linear) / 15;
|
||||
m[1] = (out[1] * square_linear) / 15;
|
||||
}
|
||||
|
||||
b[0] = m[0] * sm[0][0];
|
||||
b[0] += m[1] * sm[0][1];
|
||||
b[0] >>= 7;
|
||||
|
||||
b[1] = m[0] * sm[1][0];
|
||||
b[1] += m[1] * sm[1][1];
|
||||
b[1] >>= 7;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
NES_APU::NES_APU ()
|
||||
{
|
||||
SetClock (DEFAULT_CLOCK);
|
||||
SetRate (DEFAULT_RATE);
|
||||
option[OPT_UNMUTE_ON_RESET] = true;
|
||||
option[OPT_PHASE_REFRESH] = true;
|
||||
option[OPT_NONLINEAR_MIXER] = true;
|
||||
option[OPT_DUTY_SWAP] = false;
|
||||
option[OPT_NEGATE_SWEEP_INIT] = false;
|
||||
|
||||
square_table[0] = 0;
|
||||
for(int i=1;i<32;i++)
|
||||
square_table[i]=(INT32)((8192.0*95.88)/(8128.0/i+100));
|
||||
|
||||
square_linear = square_table[15]; // match linear scale to one full volume square of nonlinear
|
||||
|
||||
for(int c=0;c<2;++c)
|
||||
for(int t=0;t<2;++t)
|
||||
sm[c][t] = 128;
|
||||
}
|
||||
|
||||
NES_APU::~NES_APU ()
|
||||
{
|
||||
}
|
||||
|
||||
void NES_APU::Reset ()
|
||||
{
|
||||
int i;
|
||||
gclock = 0;
|
||||
mask = 0;
|
||||
|
||||
for (int i=0; i<2; ++i)
|
||||
{
|
||||
scounter[i] = 0;
|
||||
sphase[i] = 0;
|
||||
duty[i] = 0;
|
||||
volume[i] = 0;
|
||||
freq[i] = 0;
|
||||
sfreq[i] = 0;
|
||||
sweep_enable[i] = 0;
|
||||
sweep_mode[i] = 0;
|
||||
sweep_write[i] = 0;
|
||||
sweep_div_period[i] = 0;
|
||||
sweep_div[i] = 1;
|
||||
sweep_amount[i] = 0;
|
||||
envelope_disable[i] = 0;
|
||||
envelope_loop[i] = 0;
|
||||
envelope_write[i] = 0;
|
||||
envelope_div_period[i] = 0;
|
||||
envelope_div[0] = 0;
|
||||
envelope_counter[i] = 0;
|
||||
length_counter[i] = 0;
|
||||
enable[i] = 0;
|
||||
}
|
||||
|
||||
for (i = 0x4000; i < 0x4008; i++)
|
||||
Write (i, 0);
|
||||
|
||||
Write (0x4015, 0);
|
||||
if (option[OPT_UNMUTE_ON_RESET])
|
||||
Write (0x4015, 0x0f);
|
||||
if (option[OPT_NEGATE_SWEEP_INIT])
|
||||
{
|
||||
Write (0x4001, 0x08);
|
||||
Write (0x4005, 0x08);
|
||||
}
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
out[i] = 0;
|
||||
|
||||
SetRate(rate);
|
||||
}
|
||||
|
||||
void NES_APU::SetOption (int id, int val)
|
||||
{
|
||||
if(id<OPT_END) option[id] = val;
|
||||
}
|
||||
|
||||
void NES_APU::SetClock (double c)
|
||||
{
|
||||
clock = c;
|
||||
}
|
||||
|
||||
void NES_APU::SetRate (double r)
|
||||
{
|
||||
rate = r ? r : DEFAULT_RATE;
|
||||
}
|
||||
|
||||
void NES_APU::SetStereoMix(int trk, xgm::INT16 mixl, xgm::INT16 mixr)
|
||||
{
|
||||
if (trk < 0) return;
|
||||
if (trk > 1) return;
|
||||
sm[0][trk] = mixl;
|
||||
sm[1][trk] = mixr;
|
||||
}
|
||||
|
||||
ITrackInfo *NES_APU::GetTrackInfo(int trk)
|
||||
{
|
||||
trkinfo[trk]._freq = freq[trk];
|
||||
if(freq[trk])
|
||||
trkinfo[trk].freq = clock/16/(freq[trk] + 1);
|
||||
else
|
||||
trkinfo[trk].freq = 0;
|
||||
|
||||
trkinfo[trk].output = out[trk];
|
||||
trkinfo[trk].volume = volume[trk]+(envelope_disable[trk]?0:0x10)+(envelope_loop[trk]?0x20:0);
|
||||
trkinfo[trk].key =
|
||||
enable[trk] &&
|
||||
length_counter[trk] > 0 &&
|
||||
freq[trk] >= 8 &&
|
||||
sfreq[trk] < 0x800 &&
|
||||
(envelope_disable[trk] ? volume[trk] : (envelope_counter[trk] > 0));
|
||||
trkinfo[trk].tone = duty[trk];
|
||||
trkinfo[trk].max_volume = 15;
|
||||
return &trkinfo[trk];
|
||||
}
|
||||
|
||||
bool NES_APU::Write (UINT32 adr, UINT32 val, UINT32 id)
|
||||
{
|
||||
int ch;
|
||||
|
||||
static const UINT8 length_table[32] = {
|
||||
0x0A, 0xFE,
|
||||
0x14, 0x02,
|
||||
0x28, 0x04,
|
||||
0x50, 0x06,
|
||||
0xA0, 0x08,
|
||||
0x3C, 0x0A,
|
||||
0x0E, 0x0C,
|
||||
0x1A, 0x0E,
|
||||
0x0C, 0x10,
|
||||
0x18, 0x12,
|
||||
0x30, 0x14,
|
||||
0x60, 0x16,
|
||||
0xC0, 0x18,
|
||||
0x48, 0x1A,
|
||||
0x10, 0x1C,
|
||||
0x20, 0x1E
|
||||
};
|
||||
|
||||
if (0x4000 <= adr && adr < 0x4008)
|
||||
{
|
||||
//DEBUG_OUT("$%04X = %02X\n",adr,val);
|
||||
|
||||
adr &= 0xf;
|
||||
ch = adr >> 2;
|
||||
switch (adr)
|
||||
{
|
||||
case 0x0:
|
||||
case 0x4:
|
||||
volume[ch] = val & 15;
|
||||
envelope_disable[ch] = (val >> 4) & 1;
|
||||
envelope_loop[ch] = (val >> 5) & 1;
|
||||
envelope_div_period[ch] = (val & 15);
|
||||
duty[ch] = (val >> 6) & 3;
|
||||
if (option[OPT_DUTY_SWAP])
|
||||
{
|
||||
if (duty[ch] == 1) duty[ch] = 2;
|
||||
else if (duty[ch] == 2) duty[ch] = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1:
|
||||
case 0x5:
|
||||
sweep_enable[ch] = (val >> 7) & 1;
|
||||
sweep_div_period[ch] = (((val >> 4) & 7));
|
||||
sweep_mode[ch] = (val >> 3) & 1;
|
||||
sweep_amount[ch] = val & 7;
|
||||
sweep_write[ch] = true;
|
||||
sweep_sqr(ch);
|
||||
break;
|
||||
|
||||
case 0x2:
|
||||
case 0x6:
|
||||
freq[ch] = val | (freq[ch] & 0x700) ;
|
||||
sweep_sqr(ch);
|
||||
break;
|
||||
|
||||
case 0x3:
|
||||
case 0x7:
|
||||
freq[ch] = (freq[ch] & 0xFF) | ((val & 0x7) << 8) ;
|
||||
if (option[OPT_PHASE_REFRESH])
|
||||
sphase[ch] = 0;
|
||||
envelope_write[ch] = true;
|
||||
if (enable[ch])
|
||||
{
|
||||
length_counter[ch] = length_table[(val >> 3) & 0x1f];
|
||||
}
|
||||
sweep_sqr(ch);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
reg[adr] = val;
|
||||
return true;
|
||||
}
|
||||
else if (adr == 0x4015)
|
||||
{
|
||||
enable[0] = (val & 1) ? true : false;
|
||||
enable[1] = (val & 2) ? true : false;
|
||||
|
||||
if (!enable[0])
|
||||
length_counter[0] = 0;
|
||||
if (!enable[1])
|
||||
length_counter[1] = 0;
|
||||
|
||||
reg[adr-0x4000] = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4017 is handled in nes_dmc.cpp
|
||||
//else if (adr == 0x4017)
|
||||
//{
|
||||
//}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace xgm;
|
88
extern/NSFplay/nes_apu.h
vendored
Normal file
88
extern/NSFplay/nes_apu.h
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
#ifndef _NES_APU_H_
|
||||
#define _NES_APU_H_
|
||||
#include "../device.h"
|
||||
#include "nes_dmc.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
/** Upper half of APU **/
|
||||
class NES_APU : public ISoundChip
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
OPT_UNMUTE_ON_RESET=0,
|
||||
OPT_PHASE_REFRESH,
|
||||
OPT_NONLINEAR_MIXER,
|
||||
OPT_DUTY_SWAP,
|
||||
OPT_NEGATE_SWEEP_INIT,
|
||||
OPT_END };
|
||||
|
||||
enum
|
||||
{ SQR0_MASK = 1, SQR1_MASK = 2, };
|
||||
|
||||
protected:
|
||||
int option[OPT_END]; // ŠeŽíƒIƒvƒVƒ‡ƒ“
|
||||
int mask;
|
||||
INT32 sm[2][2];
|
||||
|
||||
UINT32 gclock;
|
||||
UINT8 reg[0x20];
|
||||
INT32 out[2];
|
||||
double rate, clock;
|
||||
|
||||
INT32 square_table[32]; // nonlinear mixer
|
||||
INT32 square_linear; // linear mix approximation
|
||||
|
||||
int scounter[2]; // frequency divider
|
||||
int sphase[2]; // phase counter
|
||||
|
||||
int duty[2];
|
||||
int volume[2];
|
||||
int freq[2];
|
||||
int sfreq[2];
|
||||
|
||||
bool sweep_enable[2];
|
||||
bool sweep_mode[2];
|
||||
bool sweep_write[2];
|
||||
int sweep_div_period[2];
|
||||
int sweep_div[2];
|
||||
int sweep_amount[2];
|
||||
|
||||
bool envelope_disable[2];
|
||||
bool envelope_loop[2];
|
||||
bool envelope_write[2];
|
||||
int envelope_div_period[2];
|
||||
int envelope_div[2];
|
||||
int envelope_counter[2];
|
||||
|
||||
int length_counter[2];
|
||||
|
||||
bool enable[2];
|
||||
|
||||
void sweep_sqr (int ch); // calculates target sweep frequency
|
||||
INT32 calc_sqr (int ch, UINT32 clocks);
|
||||
TrackInfoBasic trkinfo[2];
|
||||
|
||||
public:
|
||||
NES_APU ();
|
||||
~NES_APU ();
|
||||
|
||||
void FrameSequence(int s);
|
||||
|
||||
virtual void Reset ();
|
||||
virtual void Tick (UINT32 clocks);
|
||||
virtual UINT32 Render (INT32 b[2]);
|
||||
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
|
||||
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
|
||||
virtual void SetRate (double rate);
|
||||
virtual void SetClock (double clock);
|
||||
virtual void SetOption (int id, int b);
|
||||
virtual void SetMask(int m){ mask = m; }
|
||||
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
|
||||
virtual ITrackInfo *GetTrackInfo(int trk);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
771
extern/NSFplay/nes_dmc.cpp
vendored
Normal file
771
extern/NSFplay/nes_dmc.cpp
vendored
Normal file
|
@ -0,0 +1,771 @@
|
|||
#include "nes_dmc.h"
|
||||
#include "nes_apu.h"
|
||||
#include <cstdlib>
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
const UINT32 NES_DMC::wavlen_table[2][16] = {
|
||||
{ // NTSC
|
||||
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
|
||||
},
|
||||
{ // PAL
|
||||
4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778
|
||||
}};
|
||||
|
||||
const UINT32 NES_DMC::freq_table[2][16] = {
|
||||
{ // NTSC
|
||||
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54
|
||||
},
|
||||
{ // PAL
|
||||
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50
|
||||
}};
|
||||
|
||||
const UINT32 BITREVERSE[256] = {
|
||||
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
|
||||
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
|
||||
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
|
||||
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
|
||||
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
|
||||
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
|
||||
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
|
||||
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
|
||||
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
|
||||
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
|
||||
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
|
||||
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
|
||||
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
|
||||
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
|
||||
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
|
||||
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF,
|
||||
};
|
||||
|
||||
NES_DMC::NES_DMC () : GETA_BITS (20)
|
||||
{
|
||||
SetClock (DEFAULT_CLOCK);
|
||||
SetRate (DEFAULT_RATE);
|
||||
SetPal (false);
|
||||
option[OPT_ENABLE_4011] = 1;
|
||||
option[OPT_ENABLE_PNOISE] = 1;
|
||||
option[OPT_UNMUTE_ON_RESET] = 1;
|
||||
option[OPT_DPCM_ANTI_CLICK] = 0;
|
||||
option[OPT_NONLINEAR_MIXER] = 1;
|
||||
option[OPT_RANDOMIZE_NOISE] = 1;
|
||||
option[OPT_RANDOMIZE_TRI] = 1;
|
||||
option[OPT_TRI_MUTE] = 1;
|
||||
option[OPT_DPCM_REVERSE] = 0;
|
||||
tnd_table[0][0][0][0] = 0;
|
||||
tnd_table[1][0][0][0] = 0;
|
||||
|
||||
apu = NULL;
|
||||
frame_sequence_count = 0;
|
||||
frame_sequence_length = 7458;
|
||||
frame_sequence_steps = 4;
|
||||
|
||||
for(int c=0;c<2;++c)
|
||||
for(int t=0;t<3;++t)
|
||||
sm[c][t] = 128;
|
||||
}
|
||||
|
||||
|
||||
NES_DMC::~NES_DMC ()
|
||||
{
|
||||
}
|
||||
|
||||
void NES_DMC::SetStereoMix(int trk, xgm::INT16 mixl, xgm::INT16 mixr)
|
||||
{
|
||||
if (trk < 0) return;
|
||||
if (trk > 2) return;
|
||||
sm[0][trk] = mixl;
|
||||
sm[1][trk] = mixr;
|
||||
}
|
||||
|
||||
ITrackInfo *NES_DMC::GetTrackInfo(int trk)
|
||||
{
|
||||
switch(trk)
|
||||
{
|
||||
case 0:
|
||||
trkinfo[trk].max_volume = 255;
|
||||
trkinfo[0].key = (linear_counter>0 && length_counter[0]>0 && enable[0]);
|
||||
trkinfo[0].volume = 0;
|
||||
trkinfo[0]._freq = tri_freq;
|
||||
if(trkinfo[0]._freq)
|
||||
trkinfo[0].freq = clock/32/(trkinfo[0]._freq + 1);
|
||||
else
|
||||
trkinfo[0].freq = 0;
|
||||
trkinfo[0].tone = -1;
|
||||
trkinfo[0].output = out[0];
|
||||
break;
|
||||
case 1:
|
||||
trkinfo[1].max_volume = 15;
|
||||
trkinfo[1].volume = noise_volume+(envelope_disable?0:0x10)+(envelope_loop?0x20:0);
|
||||
trkinfo[1].key = length_counter[1]>0 && enable[1] &&
|
||||
(envelope_disable ? (noise_volume>0) : (envelope_counter>0));
|
||||
trkinfo[1]._freq = reg[0x400e - 0x4008]&0xF;
|
||||
trkinfo[1].freq = clock/double(wavlen_table[pal][trkinfo[1]._freq] * ((noise_tap&(1<<6)) ? 93 : 1));
|
||||
trkinfo[1].tone = noise_tap & (1<<6);
|
||||
trkinfo[1].output = out[1];
|
||||
break;
|
||||
case 2:
|
||||
trkinfo[2].max_volume = 127;
|
||||
trkinfo[2].volume = reg[0x4011 - 0x4008]&0x7F;
|
||||
trkinfo[2].key = dlength > 0;
|
||||
trkinfo[2]._freq = reg[0x4010 - 0x4008]&0xF;
|
||||
trkinfo[2].freq = clock/double(freq_table[pal][trkinfo[2]._freq]);
|
||||
trkinfo[2].tone = (0xc000|(adr_reg<<6));
|
||||
trkinfo[2].output = (damp<<1)|dac_lsb;
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
return &trkinfo[trk];
|
||||
}
|
||||
|
||||
void NES_DMC::FrameSequence(int s)
|
||||
{
|
||||
//DEBUG_OUT("FrameSequence: %d\n",s);
|
||||
|
||||
if (s > 3) return; // no operation in step 4
|
||||
|
||||
if (apu)
|
||||
{
|
||||
apu->FrameSequence(s);
|
||||
}
|
||||
|
||||
if (s == 0 && (frame_sequence_steps == 4))
|
||||
{
|
||||
if (frame_irq_enable) frame_irq = true;
|
||||
cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, frame_irq & frame_irq_enable);
|
||||
}
|
||||
|
||||
// 240hz clock
|
||||
{
|
||||
// triangle linear counter
|
||||
if (linear_counter_halt)
|
||||
{
|
||||
linear_counter = linear_counter_reload;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (linear_counter > 0) --linear_counter;
|
||||
}
|
||||
if (!linear_counter_control)
|
||||
{
|
||||
linear_counter_halt = false;
|
||||
}
|
||||
|
||||
// noise envelope
|
||||
bool divider = false;
|
||||
if (envelope_write)
|
||||
{
|
||||
envelope_write = false;
|
||||
envelope_counter = 15;
|
||||
envelope_div = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
++envelope_div;
|
||||
if (envelope_div > envelope_div_period)
|
||||
{
|
||||
divider = true;
|
||||
envelope_div = 0;
|
||||
}
|
||||
}
|
||||
if (divider)
|
||||
{
|
||||
if (envelope_loop && envelope_counter == 0)
|
||||
envelope_counter = 15;
|
||||
else if (envelope_counter > 0)
|
||||
--envelope_counter;
|
||||
}
|
||||
}
|
||||
|
||||
// 120hz clock
|
||||
if ((s&1) == 0)
|
||||
{
|
||||
// triangle length counter
|
||||
if (!linear_counter_control && (length_counter[0] > 0))
|
||||
--length_counter[0];
|
||||
|
||||
// noise length counter
|
||||
if (!envelope_loop && (length_counter[1] > 0))
|
||||
--length_counter[1];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 三角波チャンネルの計算 戻り値は0-15
|
||||
UINT32 NES_DMC::calc_tri (UINT32 clocks)
|
||||
{
|
||||
static UINT32 tritbl[32] =
|
||||
{
|
||||
15,14,13,12,11,10, 9, 8,
|
||||
7, 6, 5, 4, 3, 2, 1, 0,
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9,10,11,12,13,14,15,
|
||||
};
|
||||
|
||||
if (linear_counter > 0 && length_counter[0] > 0
|
||||
&& (!option[OPT_TRI_MUTE] || tri_freq > 0))
|
||||
{
|
||||
counter[0] -= clocks;
|
||||
while (counter[0] < 0)
|
||||
{
|
||||
tphase = (tphase + 1) & 31;
|
||||
counter[0] += (tri_freq + 1);
|
||||
}
|
||||
}
|
||||
|
||||
UINT32 ret = tritbl[tphase];
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ノイズチャンネルの計算 戻り値は0-127
|
||||
// 低サンプリングレートで合成するとエイリアスノイズが激しいので
|
||||
// ノイズだけはこの関数内で高クロック合成し、簡易なサンプリングレート
|
||||
// 変換を行っている。
|
||||
UINT32 NES_DMC::calc_noise(UINT32 clocks)
|
||||
{
|
||||
UINT32 env = envelope_disable ? noise_volume : envelope_counter;
|
||||
if (length_counter[1] < 1) env = 0;
|
||||
|
||||
UINT32 last = (noise & 0x4000) ? 0 : env;
|
||||
if (clocks < 1) return last;
|
||||
|
||||
// simple anti-aliasing (noise requires it, even when oversampling is off)
|
||||
UINT32 count = 0;
|
||||
UINT32 accum = counter[1] * last; // samples pending from previous calc
|
||||
UINT32 accum_clocks = counter[1];
|
||||
#ifdef _DEBUG
|
||||
INT32 start_clocks = counter[1];
|
||||
#endif
|
||||
if (counter[1] < 0) // only happens on startup when using the randomize noise option
|
||||
{
|
||||
accum = 0;
|
||||
accum_clocks = 0;
|
||||
}
|
||||
|
||||
counter[1] -= clocks;
|
||||
assert (nfreq > 0); // prevent infinite loop
|
||||
while (counter[1] < 0)
|
||||
{
|
||||
// tick the noise generator
|
||||
UINT32 feedback = (noise&1) ^ ((noise&noise_tap)?1:0);
|
||||
noise = (noise>>1) | (feedback<<14);
|
||||
|
||||
last = (noise & 0x4000) ? 0 : env;
|
||||
accum += (last * nfreq);
|
||||
counter[1] += nfreq;
|
||||
++count;
|
||||
accum_clocks += nfreq;
|
||||
}
|
||||
|
||||
if (count < 1) // no change over interval, don't anti-alias
|
||||
{
|
||||
return last;
|
||||
}
|
||||
|
||||
accum -= (last * counter[1]); // remove these samples which belong in the next calc
|
||||
accum_clocks -= counter[1];
|
||||
#ifdef _DEBUG
|
||||
if (start_clocks >= 0) assert(accum_clocks == clocks); // these should be equal
|
||||
#endif
|
||||
|
||||
UINT32 average = accum / accum_clocks;
|
||||
assert(average <= 15); // above this would indicate overflow
|
||||
return average;
|
||||
}
|
||||
|
||||
// Tick the DMC for the number of clocks, and return output counter;
|
||||
UINT32 NES_DMC::calc_dmc (UINT32 clocks)
|
||||
{
|
||||
counter[2] -= clocks;
|
||||
assert (dfreq > 0); // prevent infinite loop
|
||||
while (counter[2] < 0)
|
||||
{
|
||||
counter[2] += dfreq;
|
||||
|
||||
if ( data > 0x100 ) // data = 0x100 when shift register is empty
|
||||
{
|
||||
if (!empty)
|
||||
{
|
||||
if ((data & 1) && (damp < 63))
|
||||
damp++;
|
||||
else if (!(data & 1) && (0 < damp))
|
||||
damp--;
|
||||
}
|
||||
data >>=1;
|
||||
}
|
||||
|
||||
if ( data <= 0x100 ) // shift register is empty
|
||||
{
|
||||
if (dlength > 0)
|
||||
{
|
||||
memory->Read (daddress, data);
|
||||
cpu->StealCycles(4); // DMC read takes 3 or 4 CPU cycles, usually 4
|
||||
// (checking for the 3-cycle case would require sub-instruction emulation)
|
||||
data &= 0xFF; // read 8 bits
|
||||
if (option[OPT_DPCM_REVERSE]) data = BITREVERSE[data];
|
||||
data |= 0x10000; // use an extra bit to signal end of data
|
||||
empty = false;
|
||||
daddress = ((daddress+1)&0xFFFF)|0x8000 ;
|
||||
--dlength;
|
||||
if (dlength == 0)
|
||||
{
|
||||
if (mode & 1) // looped DPCM = auto-reload
|
||||
{
|
||||
daddress = ((adr_reg<<6)|0xC000);
|
||||
dlength = (len_reg<<4)+1;
|
||||
}
|
||||
else if (mode & 2) // IRQ and not looped
|
||||
{
|
||||
irq = true;
|
||||
cpu->UpdateIRQ(NES_CPU::IRQD_DMC, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
data = 0x10000; // DMC will do nothing
|
||||
empty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (damp<<1) + dac_lsb;
|
||||
}
|
||||
|
||||
void NES_DMC::TickFrameSequence (UINT32 clocks)
|
||||
{
|
||||
frame_sequence_count += clocks;
|
||||
while (frame_sequence_count > frame_sequence_length)
|
||||
{
|
||||
FrameSequence(frame_sequence_step);
|
||||
frame_sequence_count -= frame_sequence_length;
|
||||
++frame_sequence_step;
|
||||
if(frame_sequence_step >= frame_sequence_steps)
|
||||
frame_sequence_step = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NES_DMC::Tick (UINT32 clocks)
|
||||
{
|
||||
out[0] = calc_tri(clocks);
|
||||
out[1] = calc_noise(clocks);
|
||||
out[2] = calc_dmc(clocks);
|
||||
}
|
||||
|
||||
UINT32 NES_DMC::Render (INT32 b[2])
|
||||
{
|
||||
out[0] = (mask & 1) ? 0 : out[0];
|
||||
out[1] = (mask & 2) ? 0 : out[1];
|
||||
out[2] = (mask & 4) ? 0 : out[2];
|
||||
|
||||
INT32 m[3];
|
||||
m[0] = tnd_table[0][out[0]][0][0];
|
||||
m[1] = tnd_table[0][0][out[1]][0];
|
||||
m[2] = tnd_table[0][0][0][out[2]];
|
||||
|
||||
if (option[OPT_NONLINEAR_MIXER])
|
||||
{
|
||||
INT32 ref = m[0] + m[1] + m[2];
|
||||
INT32 voltage = tnd_table[1][out[0]][out[1]][out[2]];
|
||||
if (ref)
|
||||
{
|
||||
for (int i=0; i < 3; ++i)
|
||||
m[i] = (m[i] * voltage) / ref;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i=0; i < 3; ++i)
|
||||
m[i] = voltage;
|
||||
}
|
||||
}
|
||||
|
||||
// anti-click nullifies any 4011 write but preserves nonlinearity
|
||||
if (option[OPT_DPCM_ANTI_CLICK])
|
||||
{
|
||||
if (dmc_pop) // $4011 will cause pop this frame
|
||||
{
|
||||
// adjust offset to counteract pop
|
||||
dmc_pop_offset += dmc_pop_follow - m[2];
|
||||
dmc_pop = false;
|
||||
|
||||
// prevent overflow, keep headspace at edges
|
||||
const INT32 OFFSET_MAX = (1 << 30) - (4 << 16);
|
||||
if (dmc_pop_offset > OFFSET_MAX) dmc_pop_offset = OFFSET_MAX;
|
||||
if (dmc_pop_offset < -OFFSET_MAX) dmc_pop_offset = -OFFSET_MAX;
|
||||
}
|
||||
dmc_pop_follow = m[2]; // remember previous position
|
||||
|
||||
m[2] += dmc_pop_offset; // apply offset
|
||||
|
||||
// TODO implement this in a better way
|
||||
// roll off offset (not ideal, but prevents overflow)
|
||||
if (dmc_pop_offset > 0) --dmc_pop_offset;
|
||||
else if (dmc_pop_offset < 0) ++dmc_pop_offset;
|
||||
}
|
||||
|
||||
b[0] = m[0] * sm[0][0];
|
||||
b[0] += m[1] * sm[0][1];
|
||||
b[0] += m[2] * sm[0][2];
|
||||
b[0] >>= 7;
|
||||
|
||||
b[1] = m[0] * sm[1][0];
|
||||
b[1] += m[1] * sm[1][1];
|
||||
b[1] += m[2] * sm[1][2];
|
||||
b[1] >>= 7;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
void NES_DMC::SetClock (double c)
|
||||
{
|
||||
clock = c;
|
||||
}
|
||||
|
||||
void NES_DMC::SetRate (double r)
|
||||
{
|
||||
rate = (UINT32)(r?r:DEFAULT_RATE);
|
||||
}
|
||||
|
||||
void NES_DMC::SetPal (bool is_pal)
|
||||
{
|
||||
pal = (is_pal ? 1 : 0);
|
||||
// set CPU cycles in frame_sequence
|
||||
frame_sequence_length = is_pal ? 8314 : 7458;
|
||||
}
|
||||
|
||||
void NES_DMC::SetAPU (NES_APU* apu_)
|
||||
{
|
||||
apu = apu_;
|
||||
}
|
||||
|
||||
// Initializing TRI, NOISE, DPCM mixing table
|
||||
void NES_DMC::InitializeTNDTable(double wt, double wn, double wd) {
|
||||
|
||||
// volume adjusted by 0.95 based on empirical measurements
|
||||
const double MASTER = 8192.0 * 0.95;
|
||||
// truthfully, the nonlinear curve does not appear to match well
|
||||
// with my tests. Do more testing of the APU/DMC DAC later.
|
||||
// this value keeps the triangle consistent with measured levels,
|
||||
// but not necessarily the rest of this APU channel,
|
||||
// because of the lack of a good DAC model, currently.
|
||||
|
||||
{ // Linear Mixer
|
||||
for(int t=0; t<16 ; t++) {
|
||||
for(int n=0; n<16; n++) {
|
||||
for(int d=0; d<128; d++) {
|
||||
tnd_table[0][t][n][d] = (UINT32)(MASTER*(3.0*t+2.0*n+d)/208.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{ // Non-Linear Mixer
|
||||
tnd_table[1][0][0][0] = 0;
|
||||
for(int t=0; t<16 ; t++) {
|
||||
for(int n=0; n<16; n++) {
|
||||
for(int d=0; d<128; d++) {
|
||||
if(t!=0||n!=0||d!=0)
|
||||
tnd_table[1][t][n][d] = (UINT32)((MASTER*159.79)/(100.0+1.0/((double)t/wt+(double)n/wn+(double)d/wd)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NES_DMC::Reset ()
|
||||
{
|
||||
int i;
|
||||
mask = 0;
|
||||
|
||||
InitializeTNDTable(8227,12241,22638);
|
||||
|
||||
counter[0] = 0;
|
||||
counter[1] = 0;
|
||||
counter[2] = 0;
|
||||
tphase = 0;
|
||||
nfreq = wavlen_table[0][0];
|
||||
dfreq = freq_table[0][0];
|
||||
tri_freq = 0;
|
||||
linear_counter = 0;
|
||||
linear_counter_reload = 0;
|
||||
linear_counter_halt = 0;
|
||||
linear_counter_control = 0;
|
||||
noise_volume = 0;
|
||||
noise = 0;
|
||||
noise_tap = 0;
|
||||
envelope_loop = 0;
|
||||
envelope_disable = 0;
|
||||
envelope_write = 0;
|
||||
envelope_div_period = 0;
|
||||
envelope_div = 0;
|
||||
envelope_counter = 0;
|
||||
enable[0] = 0;
|
||||
enable[1] = 0;
|
||||
length_counter[0] = 0;
|
||||
length_counter[1] = 0;
|
||||
frame_irq = false;
|
||||
frame_irq_enable = false;
|
||||
frame_sequence_count = 0;
|
||||
frame_sequence_steps = 4;
|
||||
frame_sequence_step = 0;
|
||||
cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, false);
|
||||
|
||||
for (i = 0; i < 0x0F; i++)
|
||||
Write (0x4008 + i, 0);
|
||||
Write (0x4017, 0x40);
|
||||
|
||||
irq = false;
|
||||
Write (0x4015, 0x00);
|
||||
if (option[OPT_UNMUTE_ON_RESET])
|
||||
Write (0x4015, 0x0f);
|
||||
cpu->UpdateIRQ(NES_CPU::IRQD_DMC, false);
|
||||
|
||||
out[0] = out[1] = out[2] = 0;
|
||||
damp = 0;
|
||||
dmc_pop = false;
|
||||
dmc_pop_offset = 0;
|
||||
dmc_pop_follow = 0;
|
||||
dac_lsb = 0;
|
||||
data = 0x100;
|
||||
empty = true;
|
||||
adr_reg = 0;
|
||||
dlength = 0;
|
||||
len_reg = 0;
|
||||
daddress = 0;
|
||||
noise = 1;
|
||||
noise_tap = (1<<1);
|
||||
|
||||
if (option[OPT_RANDOMIZE_NOISE])
|
||||
{
|
||||
noise |= ::rand();
|
||||
counter[1] = -(rand() & 511);
|
||||
}
|
||||
if (option[OPT_RANDOMIZE_TRI])
|
||||
{
|
||||
tphase = ::rand() & 31;
|
||||
counter[0] = -(rand() & 2047);
|
||||
}
|
||||
|
||||
SetRate(rate);
|
||||
}
|
||||
|
||||
void NES_DMC::SetMemory (IDevice * r)
|
||||
{
|
||||
memory = r;
|
||||
}
|
||||
|
||||
void NES_DMC::SetOption (int id, int val)
|
||||
{
|
||||
if(id<OPT_END)
|
||||
{
|
||||
option[id] = val;
|
||||
if(id==OPT_NONLINEAR_MIXER)
|
||||
InitializeTNDTable(8227,12241,22638);
|
||||
}
|
||||
}
|
||||
|
||||
bool NES_DMC::Write (UINT32 adr, UINT32 val, UINT32 id)
|
||||
{
|
||||
static const UINT8 length_table[32] = {
|
||||
0x0A, 0xFE,
|
||||
0x14, 0x02,
|
||||
0x28, 0x04,
|
||||
0x50, 0x06,
|
||||
0xA0, 0x08,
|
||||
0x3C, 0x0A,
|
||||
0x0E, 0x0C,
|
||||
0x1A, 0x0E,
|
||||
0x0C, 0x10,
|
||||
0x18, 0x12,
|
||||
0x30, 0x14,
|
||||
0x60, 0x16,
|
||||
0xC0, 0x18,
|
||||
0x48, 0x1A,
|
||||
0x10, 0x1C,
|
||||
0x20, 0x1E
|
||||
};
|
||||
|
||||
if (adr == 0x4015)
|
||||
{
|
||||
enable[0] = (val & 4) ? true : false;
|
||||
enable[1] = (val & 8) ? true : false;
|
||||
|
||||
if (!enable[0])
|
||||
{
|
||||
length_counter[0] = 0;
|
||||
}
|
||||
if (!enable[1])
|
||||
{
|
||||
length_counter[1] = 0;
|
||||
}
|
||||
|
||||
if ((val & 16) && dlength == 0)
|
||||
{
|
||||
daddress = (0xC000 | (adr_reg << 6));
|
||||
dlength = (len_reg << 4) + 1;
|
||||
}
|
||||
else if (!(val & 16))
|
||||
{
|
||||
dlength = 0;
|
||||
}
|
||||
|
||||
irq = false;
|
||||
cpu->UpdateIRQ(NES_CPU::IRQD_DMC, false);
|
||||
|
||||
reg[adr-0x4008] = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (adr == 0x4017)
|
||||
{
|
||||
//DEBUG_OUT("4017 = %02X\n", val);
|
||||
frame_irq_enable = ((val & 0x40) != 0x40);
|
||||
if (frame_irq_enable) frame_irq = false;
|
||||
cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, false);
|
||||
|
||||
frame_sequence_count = 0;
|
||||
if (val & 0x80)
|
||||
{
|
||||
frame_sequence_steps = 5;
|
||||
frame_sequence_step = 0;
|
||||
FrameSequence(frame_sequence_step);
|
||||
++frame_sequence_step;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame_sequence_steps = 4;
|
||||
frame_sequence_step = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (adr<0x4008||0x4013<adr)
|
||||
return false;
|
||||
|
||||
reg[adr-0x4008] = val&0xff;
|
||||
|
||||
//DEBUG_OUT("$%04X %02X\n", adr, val);
|
||||
|
||||
switch (adr)
|
||||
{
|
||||
|
||||
// tri
|
||||
|
||||
case 0x4008:
|
||||
linear_counter_control = (val >> 7) & 1;
|
||||
linear_counter_reload = val & 0x7F;
|
||||
break;
|
||||
|
||||
case 0x4009:
|
||||
break;
|
||||
|
||||
case 0x400a:
|
||||
tri_freq = val | (tri_freq & 0x700) ;
|
||||
break;
|
||||
|
||||
case 0x400b:
|
||||
tri_freq = (tri_freq & 0xff) | ((val & 0x7) << 8) ;
|
||||
linear_counter_halt = true;
|
||||
if (enable[0])
|
||||
{
|
||||
length_counter[0] = length_table[(val >> 3) & 0x1f];
|
||||
}
|
||||
break;
|
||||
|
||||
// noise
|
||||
|
||||
case 0x400c:
|
||||
noise_volume = val & 15;
|
||||
envelope_div_period = val & 15;
|
||||
envelope_disable = (val >> 4) & 1;
|
||||
envelope_loop = (val >> 5) & 1;
|
||||
break;
|
||||
|
||||
case 0x400d:
|
||||
break;
|
||||
|
||||
case 0x400e:
|
||||
if (option[OPT_ENABLE_PNOISE])
|
||||
noise_tap = (val & 0x80) ? (1<<6) : (1<<1);
|
||||
else
|
||||
noise_tap = (1<<1);
|
||||
nfreq = wavlen_table[pal][val&15];
|
||||
break;
|
||||
|
||||
case 0x400f:
|
||||
if (enable[1])
|
||||
{
|
||||
length_counter[1] = length_table[(val >> 3) & 0x1f];
|
||||
}
|
||||
envelope_write = true;
|
||||
break;
|
||||
|
||||
// dmc
|
||||
|
||||
case 0x4010:
|
||||
mode = (val >> 6) & 3;
|
||||
if (!(mode & 2))
|
||||
{
|
||||
irq = false;
|
||||
cpu->UpdateIRQ(NES_CPU::IRQD_DMC, false);
|
||||
}
|
||||
dfreq = freq_table[pal][val&15];
|
||||
break;
|
||||
|
||||
case 0x4011:
|
||||
if (option[OPT_ENABLE_4011])
|
||||
{
|
||||
damp = (val >> 1) & 0x3f;
|
||||
dac_lsb = val & 1;
|
||||
dmc_pop = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x4012:
|
||||
adr_reg = val&0xff;
|
||||
// ここでdaddressは更新されない
|
||||
break;
|
||||
|
||||
case 0x4013:
|
||||
len_reg = val&0xff;
|
||||
// ここでlengthは更新されない
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NES_DMC::Read (UINT32 adr, UINT32 & val, UINT32 id)
|
||||
{
|
||||
if (adr == 0x4015)
|
||||
{
|
||||
val |=(irq ? 0x80 : 0)
|
||||
| (frame_irq ? 0x40 : 0)
|
||||
| ((dlength>0) ? 0x10 : 0)
|
||||
| (length_counter[1] ? 0x08 : 0)
|
||||
| (length_counter[0] ? 0x04 : 0)
|
||||
;
|
||||
|
||||
frame_irq = false;
|
||||
cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, false);
|
||||
return true;
|
||||
}
|
||||
else if (0x4008<=adr&&adr<=0x4014)
|
||||
{
|
||||
val |= reg[adr-0x4008];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// IRQ support requires CPU read access
|
||||
void NES_DMC::SetCPU(NES_CPU* cpu_)
|
||||
{
|
||||
cpu = cpu_;
|
||||
}
|
||||
} // namespace
|
129
extern/NSFplay/nes_dmc.h
vendored
Normal file
129
extern/NSFplay/nes_dmc.h
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
#ifndef _NES_DMC_H_
|
||||
#define _NES_DMC_H_
|
||||
|
||||
#include "../device.h"
|
||||
#include "../Audio/MedianFilter.h"
|
||||
#include "../CPU/nes_cpu.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
class NES_APU; // forward declaration
|
||||
|
||||
/** Bottom Half of APU **/
|
||||
class NES_DMC:public ISoundChip
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
OPT_ENABLE_4011=0,
|
||||
OPT_ENABLE_PNOISE,
|
||||
OPT_UNMUTE_ON_RESET,
|
||||
OPT_DPCM_ANTI_CLICK,
|
||||
OPT_NONLINEAR_MIXER,
|
||||
OPT_RANDOMIZE_NOISE,
|
||||
OPT_TRI_MUTE,
|
||||
OPT_RANDOMIZE_TRI,
|
||||
OPT_DPCM_REVERSE,
|
||||
OPT_END
|
||||
};
|
||||
protected:
|
||||
const int GETA_BITS;
|
||||
static const UINT32 freq_table[2][16];
|
||||
static const UINT32 wavlen_table[2][16];
|
||||
UINT32 tnd_table[2][16][16][128];
|
||||
|
||||
int option[OPT_END];
|
||||
int mask;
|
||||
INT32 sm[2][3];
|
||||
UINT8 reg[0x10];
|
||||
UINT32 len_reg;
|
||||
UINT32 adr_reg;
|
||||
IDevice *memory;
|
||||
UINT32 out[3];
|
||||
UINT32 daddress;
|
||||
UINT32 dlength;
|
||||
UINT32 data;
|
||||
bool empty;
|
||||
INT16 damp;
|
||||
int dac_lsb;
|
||||
bool dmc_pop;
|
||||
INT32 dmc_pop_offset;
|
||||
INT32 dmc_pop_follow;
|
||||
double clock;
|
||||
UINT32 rate;
|
||||
int pal;
|
||||
int mode;
|
||||
bool irq;
|
||||
|
||||
INT32 counter[3]; // frequency dividers
|
||||
int tphase; // triangle phase
|
||||
UINT32 nfreq; // noise frequency
|
||||
UINT32 dfreq; // DPCM frequency
|
||||
|
||||
UINT32 tri_freq;
|
||||
int linear_counter;
|
||||
int linear_counter_reload;
|
||||
bool linear_counter_halt;
|
||||
bool linear_counter_control;
|
||||
|
||||
int noise_volume;
|
||||
UINT32 noise, noise_tap;
|
||||
|
||||
// noise envelope
|
||||
bool envelope_loop;
|
||||
bool envelope_disable;
|
||||
bool envelope_write;
|
||||
int envelope_div_period;
|
||||
int envelope_div;
|
||||
int envelope_counter;
|
||||
|
||||
bool enable[2]; // tri/noise enable
|
||||
int length_counter[2]; // 0=tri, 1=noise
|
||||
|
||||
TrackInfoBasic trkinfo[3];
|
||||
|
||||
// frame sequencer
|
||||
NES_APU* apu; // apu is clocked by DMC's frame sequencer
|
||||
int frame_sequence_count; // current cycle count
|
||||
int frame_sequence_length; // CPU cycles per FrameSequence
|
||||
int frame_sequence_step; // current step of frame sequence
|
||||
int frame_sequence_steps; // 4/5 steps per frame
|
||||
bool frame_irq;
|
||||
bool frame_irq_enable;
|
||||
|
||||
NES_CPU* cpu; // IRQ needs CPU access
|
||||
|
||||
inline UINT32 calc_tri (UINT32 clocks);
|
||||
inline UINT32 calc_dmc (UINT32 clocks);
|
||||
inline UINT32 calc_noise (UINT32 clocks);
|
||||
|
||||
public:
|
||||
NES_DMC ();
|
||||
~NES_DMC ();
|
||||
|
||||
void InitializeTNDTable(double wt, double wn, double wd);
|
||||
void SetPal (bool is_pal);
|
||||
void SetAPU (NES_APU* apu_);
|
||||
void SetMemory (IDevice * r);
|
||||
void FrameSequence(int s);
|
||||
int GetDamp(){ return (damp<<1)|dac_lsb ; }
|
||||
void TickFrameSequence (UINT32 clocks);
|
||||
|
||||
virtual void Reset ();
|
||||
virtual void Tick (UINT32 clocks);
|
||||
virtual UINT32 Render (INT32 b[2]);
|
||||
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
|
||||
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
|
||||
virtual void SetRate (double rate);
|
||||
virtual void SetClock (double rate);
|
||||
virtual void SetOption (int, int);
|
||||
virtual void SetMask(int m){ mask = m; }
|
||||
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
|
||||
virtual ITrackInfo *GetTrackInfo(int trk);
|
||||
|
||||
void SetCPU(NES_CPU* cpu_);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
397
extern/NSFplay/nes_fds.cpp
vendored
Normal file
397
extern/NSFplay/nes_fds.cpp
vendored
Normal file
|
@ -0,0 +1,397 @@
|
|||
#include <cstring>
|
||||
#include "nes_fds.h"
|
||||
|
||||
namespace xgm {
|
||||
|
||||
const int RC_BITS = 12;
|
||||
|
||||
NES_FDS::NES_FDS ()
|
||||
{
|
||||
option[OPT_CUTOFF] = 2000;
|
||||
option[OPT_4085_RESET] = 0;
|
||||
option[OPT_WRITE_PROTECT] = 0; // not used here, see nsfplay.cpp
|
||||
|
||||
rc_k = 0;
|
||||
rc_l = (1<<RC_BITS);
|
||||
|
||||
SetClock (DEFAULT_CLOCK);
|
||||
SetRate (DEFAULT_RATE);
|
||||
sm[0] = 128;
|
||||
sm[1] = 128;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
NES_FDS::~NES_FDS ()
|
||||
{
|
||||
}
|
||||
|
||||
void NES_FDS::SetStereoMix(int trk, INT16 mixl, INT16 mixr)
|
||||
{
|
||||
if (trk < 0) return;
|
||||
if (trk > 1) return;
|
||||
sm[0] = mixl;
|
||||
sm[1] = mixr;
|
||||
}
|
||||
|
||||
ITrackInfo *NES_FDS::GetTrackInfo(int trk)
|
||||
{
|
||||
trkinfo.max_volume = 32;
|
||||
trkinfo.volume = last_vol;
|
||||
trkinfo.key = last_vol > 0;
|
||||
trkinfo._freq = last_freq;
|
||||
trkinfo.freq = (double(last_freq) * clock) / (65536.0 * 64.0);
|
||||
trkinfo.tone = env_out[EMOD];
|
||||
for(int i=0;i<64;i++)
|
||||
trkinfo.wave[i] = wave[TWAV][i];
|
||||
|
||||
return &trkinfo;
|
||||
}
|
||||
|
||||
void NES_FDS::SetClock (double c)
|
||||
{
|
||||
clock = c;
|
||||
}
|
||||
|
||||
void NES_FDS::SetRate (double r)
|
||||
{
|
||||
rate = r;
|
||||
|
||||
// configure lowpass filter
|
||||
double cutoff = double(option[OPT_CUTOFF]);
|
||||
double leak = 0.0;
|
||||
if (cutoff > 0)
|
||||
leak = ::exp(-2.0 * 3.14159 * cutoff / rate);
|
||||
rc_k = INT32(leak * double(1<<RC_BITS));
|
||||
rc_l = (1<<RC_BITS) - rc_k;
|
||||
}
|
||||
|
||||
void NES_FDS::SetOption (int id, int val)
|
||||
{
|
||||
if(id<OPT_END) option[id] = val;
|
||||
|
||||
// update cutoff immediately
|
||||
if (id == OPT_CUTOFF) SetRate(rate);
|
||||
}
|
||||
|
||||
void NES_FDS::Reset ()
|
||||
{
|
||||
master_io = true;
|
||||
master_vol = 0;
|
||||
last_freq = 0;
|
||||
last_vol = 0;
|
||||
|
||||
rc_accum = 0;
|
||||
|
||||
for (int i=0; i<2; ++i)
|
||||
{
|
||||
::memset(wave[i], 0, sizeof(wave[i]));
|
||||
freq[i] = 0;
|
||||
phase[i] = 0;
|
||||
}
|
||||
wav_write = false;
|
||||
wav_halt = true;
|
||||
env_halt = true;
|
||||
mod_halt = true;
|
||||
mod_pos = 0;
|
||||
mod_write_pos = 0;
|
||||
|
||||
for (int i=0; i<2; ++i)
|
||||
{
|
||||
env_mode[i] = false;
|
||||
env_disable[i] = true;
|
||||
env_timer[i] = 0;
|
||||
env_speed[i] = 0;
|
||||
env_out[i] = 0;
|
||||
}
|
||||
master_env_speed = 0xFF;
|
||||
|
||||
// NOTE: the FDS BIOS reset only does the following related to audio:
|
||||
// $4023 = $00
|
||||
// $4023 = $83 enables master_io
|
||||
// $4080 = $80 output volume = 0, envelope disabled
|
||||
// $408A = $E8 master envelope speed
|
||||
Write(0x4023, 0x00);
|
||||
Write(0x4023, 0x83);
|
||||
Write(0x4080, 0x80);
|
||||
Write(0x408A, 0xE8);
|
||||
|
||||
// reset other stuff
|
||||
Write(0x4082, 0x00); // wav freq 0
|
||||
Write(0x4083, 0x80); // wav disable
|
||||
Write(0x4084, 0x80); // mod strength 0
|
||||
Write(0x4085, 0x00); // mod position 0
|
||||
Write(0x4086, 0x00); // mod freq 0
|
||||
Write(0x4087, 0x80); // mod disable
|
||||
Write(0x4089, 0x00); // wav write disable, max global volume}
|
||||
}
|
||||
|
||||
void NES_FDS::Tick (UINT32 clocks)
|
||||
{
|
||||
// clock envelopes
|
||||
if (!env_halt && !wav_halt && (master_env_speed != 0))
|
||||
{
|
||||
for (int i=0; i<2; ++i)
|
||||
{
|
||||
if (!env_disable[i])
|
||||
{
|
||||
env_timer[i] += clocks;
|
||||
UINT32 period = ((env_speed[i]+1) * master_env_speed) << 3;
|
||||
while (env_timer[i] >= period)
|
||||
{
|
||||
// clock the envelope
|
||||
if (env_mode[i])
|
||||
{
|
||||
if (env_out[i] < 32) ++env_out[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (env_out[i] > 0 ) --env_out[i];
|
||||
}
|
||||
env_timer[i] -= period;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clock the mod table
|
||||
if (!mod_halt)
|
||||
{
|
||||
// advance phase, adjust for modulator
|
||||
UINT32 start_pos = phase[TMOD] >> 16;
|
||||
phase[TMOD] += (clocks * freq[TMOD]);
|
||||
UINT32 end_pos = phase[TMOD] >> 16;
|
||||
|
||||
// wrap the phase to the 64-step table (+ 16 bit accumulator)
|
||||
phase[TMOD] = phase[TMOD] & 0x3FFFFF;
|
||||
|
||||
// execute all clocked steps
|
||||
for (UINT32 p = start_pos; p < end_pos; ++p)
|
||||
{
|
||||
INT32 wv = wave[TMOD][p & 0x3F];
|
||||
if (wv == 4) // 4 resets mod position
|
||||
mod_pos = 0;
|
||||
else
|
||||
{
|
||||
const INT32 BIAS[8] = { 0, 1, 2, 4, 0, -4, -2, -1 };
|
||||
mod_pos += BIAS[wv];
|
||||
mod_pos &= 0x7F; // 7-bit clamp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clock the wav table
|
||||
if (!wav_halt)
|
||||
{
|
||||
// complex mod calculation
|
||||
INT32 mod = 0;
|
||||
if (env_out[EMOD] != 0) // skip if modulator off
|
||||
{
|
||||
// convert mod_pos to 7-bit signed
|
||||
INT32 pos = (mod_pos < 64) ? mod_pos : (mod_pos-128);
|
||||
|
||||
// multiply pos by gain,
|
||||
// shift off 4 bits but with odd "rounding" behaviour
|
||||
INT32 temp = pos * env_out[EMOD];
|
||||
INT32 rem = temp & 0x0F;
|
||||
temp >>= 4;
|
||||
if ((rem > 0) && ((temp & 0x80) == 0))
|
||||
{
|
||||
if (pos < 0) temp -= 1;
|
||||
else temp += 2;
|
||||
}
|
||||
|
||||
// wrap if range is exceeded
|
||||
while (temp >= 192) temp -= 256;
|
||||
while (temp < -64) temp += 256;
|
||||
|
||||
// multiply result by pitch,
|
||||
// shift off 6 bits, round to nearest
|
||||
temp = freq[TWAV] * temp;
|
||||
rem = temp & 0x3F;
|
||||
temp >>= 6;
|
||||
if (rem >= 32) temp += 1;
|
||||
|
||||
mod = temp;
|
||||
}
|
||||
|
||||
// advance wavetable position
|
||||
INT32 f = freq[TWAV] + mod;
|
||||
phase[TWAV] = phase[TWAV] + (clocks * f);
|
||||
phase[TWAV] = phase[TWAV] & 0x3FFFFF; // wrap
|
||||
|
||||
// store for trackinfo
|
||||
last_freq = f;
|
||||
}
|
||||
|
||||
// output volume caps at 32
|
||||
INT32 vol_out = env_out[EVOL];
|
||||
if (vol_out > 32) vol_out = 32;
|
||||
|
||||
// final output
|
||||
if (!wav_write)
|
||||
fout = wave[TWAV][(phase[TWAV]>>16)&0x3F] * vol_out;
|
||||
|
||||
// NOTE: during wav_halt, the unit still outputs (at phase 0)
|
||||
// and volume can affect it if the first sample is nonzero.
|
||||
// haven't worked out 100% of the conditions for volume to
|
||||
// effect (vol envelope does not seem to run, but am unsure)
|
||||
// but this implementation is very close to correct
|
||||
|
||||
// store for trackinfo
|
||||
last_vol = vol_out;
|
||||
}
|
||||
|
||||
UINT32 NES_FDS::Render (INT32 b[2])
|
||||
{
|
||||
// 8 bit approximation of master volume
|
||||
const double MASTER_VOL = 2.4 * 1223.0; // max FDS vol vs max APU square (arbitrarily 1223)
|
||||
const double MAX_OUT = 32.0f * 63.0f; // value that should map to master vol
|
||||
const INT32 MASTER[4] = {
|
||||
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 2.0f),
|
||||
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 3.0f),
|
||||
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 4.0f),
|
||||
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 5.0f) };
|
||||
|
||||
INT32 v = fout * MASTER[master_vol] >> 8;
|
||||
|
||||
// lowpass RC filter
|
||||
INT32 rc_out = ((rc_accum * rc_k) + (v * rc_l)) >> RC_BITS;
|
||||
rc_accum = rc_out;
|
||||
v = rc_out;
|
||||
|
||||
// output mix
|
||||
INT32 m = mask ? 0 : v;
|
||||
b[0] = (m * sm[0]) >> 7;
|
||||
b[1] = (m * sm[1]) >> 7;
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool NES_FDS::Write (UINT32 adr, UINT32 val, UINT32 id)
|
||||
{
|
||||
// $4023 master I/O enable/disable
|
||||
if (adr == 0x4023)
|
||||
{
|
||||
master_io = ((val & 2) != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!master_io)
|
||||
return false;
|
||||
if (adr < 0x4040 || adr > 0x408A)
|
||||
return false;
|
||||
|
||||
if (adr < 0x4080) // $4040-407F wave table write
|
||||
{
|
||||
if (wav_write)
|
||||
wave[TWAV][adr - 0x4040] = val & 0x3F;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (adr & 0x00FF)
|
||||
{
|
||||
case 0x80: // $4080 volume envelope
|
||||
env_disable[EVOL] = ((val & 0x80) != 0);
|
||||
env_mode[EVOL] = ((val & 0x40) != 0);
|
||||
env_timer[EVOL] = 0;
|
||||
env_speed[EVOL] = val & 0x3F;
|
||||
if (env_disable[EVOL])
|
||||
env_out[EVOL] = env_speed[EVOL];
|
||||
return true;
|
||||
case 0x81: // $4081 ---
|
||||
return false;
|
||||
case 0x82: // $4082 wave frequency low
|
||||
freq[TWAV] = (freq[TWAV] & 0xF00) | val;
|
||||
return true;
|
||||
case 0x83: // $4083 wave frequency high / enables
|
||||
freq[TWAV] = (freq[TWAV] & 0x0FF) | ((val & 0x0F) << 8);
|
||||
wav_halt = ((val & 0x80) != 0);
|
||||
env_halt = ((val & 0x40) != 0);
|
||||
if (wav_halt)
|
||||
phase[TWAV] = 0;
|
||||
if (env_halt)
|
||||
{
|
||||
env_timer[EMOD] = 0;
|
||||
env_timer[EVOL] = 0;
|
||||
}
|
||||
return true;
|
||||
case 0x84: // $4084 mod envelope
|
||||
env_disable[EMOD] = ((val & 0x80) != 0);
|
||||
env_mode[EMOD] = ((val & 0x40) != 0);
|
||||
env_timer[EMOD] = 0;
|
||||
env_speed[EMOD] = val & 0x3F;
|
||||
if (env_disable[EMOD])
|
||||
env_out[EMOD] = env_speed[EMOD];
|
||||
return true;
|
||||
case 0x85: // $4085 mod position
|
||||
mod_pos = val & 0x7F;
|
||||
// not hardware accurate., but prevents detune due to cycle inaccuracies
|
||||
// (notably in Bio Miracle Bokutte Upa)
|
||||
if (option[OPT_4085_RESET])
|
||||
phase[TMOD] = mod_write_pos << 16;
|
||||
return true;
|
||||
case 0x86: // $4086 mod frequency low
|
||||
freq[TMOD] = (freq[TMOD] & 0xF00) | val;
|
||||
return true;
|
||||
case 0x87: // $4087 mod frequency high / enable
|
||||
freq[TMOD] = (freq[TMOD] & 0x0FF) | ((val & 0x0F) << 8);
|
||||
mod_halt = ((val & 0x80) != 0);
|
||||
if (mod_halt)
|
||||
phase[TMOD] = phase[TMOD] & 0x3F0000; // reset accumulator phase
|
||||
return true;
|
||||
case 0x88: // $4088 mod table write
|
||||
if (mod_halt)
|
||||
{
|
||||
// writes to current playback position (there is no direct way to set phase)
|
||||
wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07;
|
||||
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
|
||||
wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07;
|
||||
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
|
||||
mod_write_pos = phase[TMOD] >> 16; // used by OPT_4085_RESET
|
||||
}
|
||||
return true;
|
||||
case 0x89: // $4089 wave write enable, master volume
|
||||
wav_write = ((val & 0x80) != 0);
|
||||
master_vol = val & 0x03;
|
||||
return true;
|
||||
case 0x8A: // $408A envelope speed
|
||||
master_env_speed = val;
|
||||
// haven't tested whether this register resets phase on hardware,
|
||||
// but this ensures my inplementation won't spam envelope clocks
|
||||
// if this value suddenly goes low.
|
||||
env_timer[EMOD] = 0;
|
||||
env_timer[EVOL] = 0;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NES_FDS::Read (UINT32 adr, UINT32 & val, UINT32 id)
|
||||
{
|
||||
if (adr >= 0x4040 && adr <= 0x407F)
|
||||
{
|
||||
// TODO: if wav_write is not enabled, the
|
||||
// read address may not be reliable? need
|
||||
// to test this on hardware.
|
||||
val = wave[TWAV][adr - 0x4040];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (adr == 0x4090) // $4090 read volume envelope
|
||||
{
|
||||
val = env_out[EVOL] | 0x40;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (adr == 0x4092) // $4092 read mod envelope
|
||||
{
|
||||
val = env_out[EMOD] | 0x40;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
83
extern/NSFplay/nes_fds.h
vendored
Normal file
83
extern/NSFplay/nes_fds.h
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
#ifndef _NES_FDS_H_
|
||||
#define _NES_FDS_H_
|
||||
#include "../device.h"
|
||||
|
||||
namespace xgm {
|
||||
|
||||
class TrackInfoFDS : public TrackInfoBasic
|
||||
{
|
||||
public:
|
||||
INT16 wave[64];
|
||||
virtual IDeviceInfo *Clone(){ return new TrackInfoFDS(*this); }
|
||||
};
|
||||
|
||||
class NES_FDS : public ISoundChip
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
OPT_CUTOFF=0,
|
||||
OPT_4085_RESET,
|
||||
OPT_WRITE_PROTECT,
|
||||
OPT_END
|
||||
};
|
||||
|
||||
protected:
|
||||
double rate, clock;
|
||||
int mask;
|
||||
INT32 sm[2]; // stereo mix
|
||||
INT32 fout; // current output
|
||||
TrackInfoFDS trkinfo;
|
||||
int option[OPT_END];
|
||||
|
||||
bool master_io;
|
||||
UINT32 master_vol;
|
||||
UINT32 last_freq; // for trackinfo
|
||||
UINT32 last_vol; // for trackinfo
|
||||
|
||||
// two wavetables
|
||||
enum { TMOD=0, TWAV=1 };
|
||||
INT32 wave[2][64];
|
||||
UINT32 freq[2];
|
||||
UINT32 phase[2];
|
||||
bool wav_write;
|
||||
bool wav_halt;
|
||||
bool env_halt;
|
||||
bool mod_halt;
|
||||
UINT32 mod_pos;
|
||||
UINT32 mod_write_pos;
|
||||
|
||||
// two ramp envelopes
|
||||
enum { EMOD=0, EVOL=1 };
|
||||
bool env_mode[2];
|
||||
bool env_disable[2];
|
||||
UINT32 env_timer[2];
|
||||
UINT32 env_speed[2];
|
||||
UINT32 env_out[2];
|
||||
UINT32 master_env_speed;
|
||||
|
||||
// 1-pole RC lowpass filter
|
||||
INT32 rc_accum;
|
||||
INT32 rc_k;
|
||||
INT32 rc_l;
|
||||
|
||||
public:
|
||||
NES_FDS ();
|
||||
virtual ~ NES_FDS ();
|
||||
|
||||
virtual void Reset ();
|
||||
virtual void Tick (UINT32 clocks);
|
||||
virtual UINT32 Render (INT32 b[2]);
|
||||
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
|
||||
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
|
||||
virtual void SetRate (double);
|
||||
virtual void SetClock (double);
|
||||
virtual void SetOption (int, int);
|
||||
virtual void SetMask(int m){ mask = m&1; }
|
||||
virtual void SetStereoMix (int trk, INT16 mixl, INT16 mixr);
|
||||
virtual ITrackInfo *GetTrackInfo(int trk);
|
||||
};
|
||||
|
||||
} // namespace xgm
|
||||
|
||||
#endif
|
186
extern/NSFplay/nes_fme7.cpp
vendored
Normal file
186
extern/NSFplay/nes_fme7.cpp
vendored
Normal file
|
@ -0,0 +1,186 @@
|
|||
#include "nes_fme7.h"
|
||||
|
||||
using namespace xgm;
|
||||
|
||||
const int DIVIDER = 8; // TODO this is not optimal, rewrite PSG output
|
||||
|
||||
NES_FME7::NES_FME7 ()
|
||||
{
|
||||
psg = PSG_new ((e_uint32)DEFAULT_CLOCK, DEFAULT_RATE);
|
||||
divider = 0;
|
||||
|
||||
for(int c=0;c<2;++c)
|
||||
for(int t=0;t<3;++t)
|
||||
sm[c][t] = 128;
|
||||
}
|
||||
|
||||
NES_FME7::~NES_FME7 ()
|
||||
{
|
||||
if (psg)
|
||||
PSG_delete (psg);
|
||||
}
|
||||
|
||||
void NES_FME7::SetClock (double c)
|
||||
{
|
||||
this->clock = c * 2.0;
|
||||
}
|
||||
|
||||
void NES_FME7::SetRate (double r)
|
||||
{
|
||||
//rate = r ? r : DEFAULT_RATE;
|
||||
rate = DEFAULT_CLOCK / double(DIVIDER); // TODO rewrite PSG to integrate with clock
|
||||
if (psg)
|
||||
PSG_set_rate (psg, (e_uint32)rate);
|
||||
}
|
||||
|
||||
void NES_FME7::SetOption (int id, int val)
|
||||
{
|
||||
if(id<OPT_END)
|
||||
{
|
||||
//option[id] = val;
|
||||
}
|
||||
}
|
||||
|
||||
void NES_FME7::Reset ()
|
||||
{
|
||||
for (int i=0; i<16; ++i) // blank all registers
|
||||
{
|
||||
Write(0xC000,i);
|
||||
Write(0xE000,0);
|
||||
}
|
||||
Write(0xC000,0x07); // disable all tones
|
||||
Write(0xE000,0x3F);
|
||||
|
||||
divider = 0;
|
||||
if (psg)
|
||||
PSG_reset (psg);
|
||||
}
|
||||
|
||||
bool NES_FME7::Write (xgm::UINT32 adr, xgm::UINT32 val, xgm::UINT32 id)
|
||||
{
|
||||
if (adr == 0xC000)
|
||||
{
|
||||
if (psg)
|
||||
PSG_writeIO (psg, 0, val);
|
||||
return true;
|
||||
}
|
||||
if (adr == 0xE000)
|
||||
{
|
||||
if (psg)
|
||||
PSG_writeIO (psg, 1, val);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NES_FME7::Read (xgm::UINT32 adr, xgm::UINT32 & val, xgm::UINT32 id)
|
||||
{
|
||||
// not sure why this was here - BS
|
||||
//if (psg)
|
||||
// val = PSG_readIO (psg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void NES_FME7::Tick (xgm::UINT32 clocks)
|
||||
{
|
||||
divider += clocks;
|
||||
while (divider >= DIVIDER)
|
||||
{
|
||||
divider -= DIVIDER;
|
||||
if (psg) PSG_calc(psg);
|
||||
}
|
||||
}
|
||||
|
||||
xgm::UINT32 NES_FME7::Render (xgm::INT32 b[2])
|
||||
{
|
||||
b[0] = b[1] = 0;
|
||||
|
||||
for (int i=0; i < 3; ++i)
|
||||
{
|
||||
// note negative polarity
|
||||
b[0] -= psg->cout[i] * sm[0][i];
|
||||
b[1] -= psg->cout[i] * sm[1][i];
|
||||
}
|
||||
b[0] >>= (7-4);
|
||||
b[1] >>= (7-4);
|
||||
|
||||
// master volume adjustment
|
||||
const INT32 MASTER = INT32(0.64 * 256.0);
|
||||
b[0] = (b[0] * MASTER) >> 8;
|
||||
b[1] = (b[1] * MASTER) >> 8;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
void NES_FME7::SetStereoMix(int trk, xgm::INT16 mixl, xgm::INT16 mixr)
|
||||
{
|
||||
if (trk < 0) return;
|
||||
if (trk > 2) return;
|
||||
sm[0][trk] = mixl;
|
||||
sm[1][trk] = mixr;
|
||||
}
|
||||
|
||||
ITrackInfo *NES_FME7::GetTrackInfo(int trk)
|
||||
{
|
||||
assert(trk<5);
|
||||
|
||||
if(psg)
|
||||
{
|
||||
if (trk<3)
|
||||
{
|
||||
trkinfo[trk]._freq = psg->freq[trk];
|
||||
if(psg->freq[trk])
|
||||
trkinfo[trk].freq = psg->clk/32.0/psg->freq[trk];
|
||||
else
|
||||
trkinfo[trk].freq = 0;
|
||||
|
||||
trkinfo[trk].output = psg->cout[trk];
|
||||
trkinfo[trk].max_volume = 15;
|
||||
trkinfo[trk].volume = psg->volume[trk] >> 1;
|
||||
//trkinfo[trk].key = (psg->cout[trk]>0)?true:false;
|
||||
trkinfo[trk].key = !(psg->tmask[trk]);
|
||||
trkinfo[trk].tone = (psg->tmask[trk]?2:0)+(psg->nmask[trk]?1:0);
|
||||
}
|
||||
else if (trk == 3) // envelope
|
||||
{
|
||||
trkinfo[trk]._freq = psg->env_freq;
|
||||
if(psg->env_freq)
|
||||
trkinfo[trk].freq = psg->clk/512.0/psg->env_freq;
|
||||
else
|
||||
trkinfo[trk].freq = 0;
|
||||
|
||||
if (psg->env_continue && psg->env_alternate && !psg->env_hold) // triangle wave
|
||||
{
|
||||
trkinfo[trk].freq *= 0.5f; // sounds an octave down
|
||||
}
|
||||
|
||||
trkinfo[trk].output = psg->voltbl[psg->env_ptr];
|
||||
trkinfo[trk].max_volume = 0;
|
||||
trkinfo[trk].volume = 0;
|
||||
trkinfo[trk].key = (((psg->volume[0]|psg->volume[1]|psg->volume[2])&32) != 0);
|
||||
trkinfo[trk].tone =
|
||||
(psg->env_continue ?8:0) |
|
||||
(psg->env_attack ?4:0) |
|
||||
(psg->env_alternate?2:0) |
|
||||
(psg->env_hold ?1:0) ;
|
||||
}
|
||||
else if (trk == 4) // noise
|
||||
{
|
||||
trkinfo[trk]._freq = psg->noise_freq >> 1;
|
||||
if(trkinfo[trk]._freq > 0)
|
||||
trkinfo[trk].freq = psg->clk/16.0/psg->noise_freq;
|
||||
else
|
||||
trkinfo[trk].freq = 0;
|
||||
|
||||
trkinfo[trk].output = psg->noise_seed & 1;
|
||||
trkinfo[trk].max_volume = 0;
|
||||
trkinfo[trk].volume = 0;
|
||||
//trkinfo[trk].key = ((psg->nmask[0]&psg->nmask[1]&psg->nmask[2]) == 0);
|
||||
trkinfo[trk].key = false;
|
||||
trkinfo[trk].tone = 0;
|
||||
}
|
||||
}
|
||||
return &trkinfo[trk];
|
||||
}
|
42
extern/NSFplay/nes_fme7.h
vendored
Normal file
42
extern/NSFplay/nes_fme7.h
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
#ifndef _NES_FME7_H_
|
||||
#define _NES_FME7_H_
|
||||
#include "../device.h"
|
||||
#include "legacy/emu2149.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
|
||||
class NES_FME7:public ISoundChip
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
OPT_END
|
||||
};
|
||||
protected:
|
||||
//int option[OPT_END];
|
||||
INT32 sm[2][3]; // stereo mix
|
||||
INT16 buf[2];
|
||||
PSG *psg;
|
||||
int divider; // clock divider
|
||||
double clock, rate;
|
||||
TrackInfoBasic trkinfo[5];
|
||||
public:
|
||||
NES_FME7 ();
|
||||
~NES_FME7 ();
|
||||
virtual void Reset ();
|
||||
virtual void Tick (UINT32 clocks);
|
||||
virtual UINT32 Render (INT32 b[2]);
|
||||
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
|
||||
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
|
||||
virtual void SetClock (double);
|
||||
virtual void SetRate (double);
|
||||
virtual void SetOption (int, int);
|
||||
virtual void SetMask (int m){ if(psg) PSG_setMask(psg,m); }
|
||||
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
|
||||
virtual ITrackInfo *GetTrackInfo(int trk);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
422
extern/NSFplay/nes_mmc5.cpp
vendored
Normal file
422
extern/NSFplay/nes_mmc5.cpp
vendored
Normal file
|
@ -0,0 +1,422 @@
|
|||
#include "nes_mmc5.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
|
||||
NES_MMC5::NES_MMC5 ()
|
||||
{
|
||||
cpu = NULL;
|
||||
SetClock (DEFAULT_CLOCK);
|
||||
SetRate (DEFAULT_RATE);
|
||||
option[OPT_NONLINEAR_MIXER] = true;
|
||||
option[OPT_PHASE_REFRESH] = true;
|
||||
frame_sequence_count = 0;
|
||||
|
||||
// square nonlinear mix, same as 2A03
|
||||
square_table[0] = 0;
|
||||
for(int i=1;i<32;i++)
|
||||
square_table[i]=(INT32)((8192.0*95.88)/(8128.0/i+100));
|
||||
|
||||
// 2A03 style nonlinear pcm mix with double the bits
|
||||
//pcm_table[0] = 0;
|
||||
//INT32 wd = 22638;
|
||||
//for(int d=1;d<256; ++d)
|
||||
// pcm_table[d] = (INT32)((8192.0*159.79)/(100.0+1.0/((double)d/wd)));
|
||||
|
||||
// linear pcm mix (actual hardware seems closer to this)
|
||||
pcm_table[0] = 0;
|
||||
double pcm_scale = 32.0;
|
||||
for (int d=1; d<256; ++d)
|
||||
pcm_table[d] = (INT32)(double(d) * pcm_scale);
|
||||
|
||||
// stereo mix
|
||||
for(int c=0;c<2;++c)
|
||||
for(int t=0;t<3;++t)
|
||||
sm[c][t] = 128;
|
||||
}
|
||||
|
||||
NES_MMC5::~NES_MMC5 ()
|
||||
{
|
||||
}
|
||||
|
||||
void NES_MMC5::Reset ()
|
||||
{
|
||||
int i;
|
||||
|
||||
scounter[0] = 0;
|
||||
scounter[1] = 0;
|
||||
sphase[0] = 0;
|
||||
sphase[1] = 0;
|
||||
|
||||
envelope_div[0] = 0;
|
||||
envelope_div[1] = 0;
|
||||
length_counter[0] = 0;
|
||||
length_counter[1] = 0;
|
||||
envelope_counter[0] = 0;
|
||||
envelope_counter[1] = 0;
|
||||
frame_sequence_count = 0;
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
Write (0x5000 + i, 0);
|
||||
|
||||
Write(0x5015, 0);
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
out[i] = 0;
|
||||
|
||||
mask = 0;
|
||||
pcm = 0; // PCM channel
|
||||
pcm_mode = false; // write mode
|
||||
|
||||
SetRate(rate);
|
||||
}
|
||||
|
||||
void NES_MMC5::SetOption (int id, int val)
|
||||
{
|
||||
if(id<OPT_END) option[id] = val;
|
||||
}
|
||||
|
||||
void NES_MMC5::SetClock (double c)
|
||||
{
|
||||
this->clock = c;
|
||||
}
|
||||
|
||||
void NES_MMC5::SetRate (double r)
|
||||
{
|
||||
rate = r ? r : DEFAULT_RATE;
|
||||
}
|
||||
|
||||
void NES_MMC5::FrameSequence ()
|
||||
{
|
||||
// 240hz clock
|
||||
for (int i=0; i < 2; ++i)
|
||||
{
|
||||
bool divider = false;
|
||||
if (envelope_write[i])
|
||||
{
|
||||
envelope_write[i] = false;
|
||||
envelope_counter[i] = 15;
|
||||
envelope_div[i] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
++envelope_div[i];
|
||||
if (envelope_div[i] > envelope_div_period[i])
|
||||
{
|
||||
divider = true;
|
||||
envelope_div[i] = 0;
|
||||
}
|
||||
}
|
||||
if (divider)
|
||||
{
|
||||
if (envelope_loop[i] && envelope_counter[i] == 0)
|
||||
envelope_counter[i] = 15;
|
||||
else if (envelope_counter[i] > 0)
|
||||
--envelope_counter[i];
|
||||
}
|
||||
}
|
||||
|
||||
// MMC5 length counter is clocked at 240hz, unlike 2A03
|
||||
for (int i=0; i < 2; ++i)
|
||||
{
|
||||
if (!envelope_loop[i] && (length_counter[i] > 0))
|
||||
--length_counter[i];
|
||||
}
|
||||
}
|
||||
|
||||
INT32 NES_MMC5::calc_sqr (int i, UINT32 clocks)
|
||||
{
|
||||
static const INT16 sqrtbl[4][16] = {
|
||||
{0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0},
|
||||
{1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
};
|
||||
|
||||
scounter[i] += clocks;
|
||||
while (scounter[i] > freq[i])
|
||||
{
|
||||
sphase[i] = (sphase[i] + 1) & 15;
|
||||
scounter[i] -= (freq[i] + 1);
|
||||
}
|
||||
|
||||
INT32 ret = 0;
|
||||
if (length_counter[i] > 0)
|
||||
{
|
||||
// note MMC5 does not silence the highest 8 frequencies like APU,
|
||||
// because this is done by the sweep unit.
|
||||
|
||||
int v = envelope_disable[i] ? volume[i] : envelope_counter[i];
|
||||
ret = sqrtbl[duty[i]][sphase[i]] ? v : 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void NES_MMC5::TickFrameSequence (UINT32 clocks)
|
||||
{
|
||||
frame_sequence_count += clocks;
|
||||
while (frame_sequence_count > 7458)
|
||||
{
|
||||
FrameSequence();
|
||||
frame_sequence_count -= 7458;
|
||||
}
|
||||
}
|
||||
|
||||
void NES_MMC5::Tick (UINT32 clocks)
|
||||
{
|
||||
out[0] = calc_sqr(0, clocks);
|
||||
out[1] = calc_sqr(1, clocks);
|
||||
out[2] = pcm;
|
||||
}
|
||||
|
||||
UINT32 NES_MMC5::Render (INT32 b[2])
|
||||
{
|
||||
out[0] = (mask & 1) ? 0 : out[0];
|
||||
out[1] = (mask & 2) ? 0 : out[1];
|
||||
out[2] = (mask & 4) ? 0 : out[2];
|
||||
|
||||
INT32 m[3];
|
||||
|
||||
if(option[OPT_NONLINEAR_MIXER])
|
||||
{
|
||||
// squares nonlinear
|
||||
INT32 voltage = square_table[out[0] + out[1]];
|
||||
m[0] = out[0] << 6;
|
||||
m[1] = out[1] << 6;
|
||||
INT32 ref = m[0] + m[1];
|
||||
if (ref > 0)
|
||||
{
|
||||
m[0] = (m[0] * voltage) / ref;
|
||||
m[1] = (m[1] * voltage) / ref;
|
||||
}
|
||||
else
|
||||
{
|
||||
m[0] = voltage;
|
||||
m[1] = voltage;
|
||||
}
|
||||
|
||||
// pcm nonlinear
|
||||
m[2] = pcm_table[out[2]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// squares
|
||||
m[0] = out[0] << 6;
|
||||
m[1] = out[1] << 6;
|
||||
|
||||
// pcm channel
|
||||
m[2] = out[2] << 5;
|
||||
}
|
||||
|
||||
// note polarity is flipped on output
|
||||
|
||||
b[0] = m[0] * -sm[0][0];
|
||||
b[0] += m[1] * -sm[0][1];
|
||||
b[0] += m[2] * -sm[0][2];
|
||||
b[0] >>= 7;
|
||||
|
||||
b[1] = m[0] * -sm[1][0];
|
||||
b[1] += m[1] * -sm[1][1];
|
||||
b[1] += m[2] * -sm[1][2];
|
||||
b[1] >>= 7;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool NES_MMC5::Write (UINT32 adr, UINT32 val, UINT32 id)
|
||||
{
|
||||
int ch;
|
||||
|
||||
static const UINT8 length_table[32] = {
|
||||
0x0A, 0xFE,
|
||||
0x14, 0x02,
|
||||
0x28, 0x04,
|
||||
0x50, 0x06,
|
||||
0xA0, 0x08,
|
||||
0x3C, 0x0A,
|
||||
0x0E, 0x0C,
|
||||
0x1A, 0x0E,
|
||||
0x0C, 0x10,
|
||||
0x18, 0x12,
|
||||
0x30, 0x14,
|
||||
0x60, 0x16,
|
||||
0xC0, 0x18,
|
||||
0x48, 0x1A,
|
||||
0x10, 0x1C,
|
||||
0x20, 0x1E
|
||||
};
|
||||
|
||||
if ((0x5c00 <= adr) && (adr < 0x5ff0))
|
||||
{
|
||||
ram[adr & 0x3ff] = val;
|
||||
return true;
|
||||
}
|
||||
else if ((0x5000 <= adr) && (adr < 0x5008))
|
||||
{
|
||||
reg[adr & 0x7] = val;
|
||||
}
|
||||
|
||||
switch (adr)
|
||||
{
|
||||
case 0x5000:
|
||||
case 0x5004:
|
||||
ch = (adr >> 2) & 1;
|
||||
volume[ch] = val & 15;
|
||||
envelope_disable[ch] = (val >> 4) & 1;
|
||||
envelope_loop[ch] = (val >> 5) & 1;
|
||||
envelope_div_period[ch] = (val & 15);
|
||||
duty[ch] = (val >> 6) & 3;
|
||||
break;
|
||||
|
||||
case 0x5002:
|
||||
case 0x5006:
|
||||
ch = (adr >> 2) & 1;
|
||||
freq[ch] = val + (freq[ch] & 0x700);
|
||||
if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch];
|
||||
break;
|
||||
|
||||
case 0x5003:
|
||||
case 0x5007:
|
||||
ch = (adr >> 2) & 1;
|
||||
freq[ch] = (freq[ch] & 0xff) + ((val & 7) << 8);
|
||||
if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch];
|
||||
// phase reset
|
||||
if (option[OPT_PHASE_REFRESH])
|
||||
sphase[ch] = 0;
|
||||
envelope_write[ch] = true;
|
||||
if (enable[ch])
|
||||
{
|
||||
length_counter[ch] = length_table[(val >> 3) & 0x1f];
|
||||
}
|
||||
break;
|
||||
|
||||
// PCM channel control
|
||||
case 0x5010:
|
||||
pcm_mode = ((val & 1) != 0); // 0 = write, 1 = read
|
||||
break;
|
||||
|
||||
// PCM channel control
|
||||
case 0x5011:
|
||||
if (!pcm_mode)
|
||||
{
|
||||
val &= 0xFF;
|
||||
if (val != 0) pcm = val;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x5015:
|
||||
enable[0] = (val & 1) ? true : false;
|
||||
enable[1] = (val & 2) ? true : false;
|
||||
if (!enable[0])
|
||||
length_counter[0] = 0;
|
||||
if (!enable[1])
|
||||
length_counter[1] = 0;
|
||||
break;
|
||||
|
||||
case 0x5205:
|
||||
mreg[0] = val;
|
||||
break;
|
||||
|
||||
case 0x5206:
|
||||
mreg[1] = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NES_MMC5::Read (UINT32 adr, UINT32 & val, UINT32 id)
|
||||
{
|
||||
// in PCM read mode, reads from $8000-$C000 automatically load the PCM output
|
||||
if (pcm_mode && (0x8000 <= adr) && (adr < 0xC000) && cpu)
|
||||
{
|
||||
pcm_mode = false; // prevent recursive entry
|
||||
UINT32 pcm_read;
|
||||
cpu->Read(adr, pcm_read);
|
||||
pcm_read &= 0xFF;
|
||||
if (pcm_read != 0)
|
||||
pcm = pcm_read;
|
||||
pcm_mode = true;
|
||||
}
|
||||
|
||||
if ((0x5000 <= adr) && (adr < 0x5008))
|
||||
{
|
||||
val = reg[adr&0x7];
|
||||
return true;
|
||||
}
|
||||
else if(adr == 0x5015)
|
||||
{
|
||||
val = (enable[1]?2:0)|(enable[0]?1:0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((0x5c00 <= adr) && (adr < 0x5ff0))
|
||||
{
|
||||
val = ram[adr & 0x3ff];
|
||||
return true;
|
||||
}
|
||||
else if (adr == 0x5205)
|
||||
{
|
||||
val = (mreg[0] * mreg[1]) & 0xff;
|
||||
return true;
|
||||
}
|
||||
else if (adr == 0x5206)
|
||||
{
|
||||
val = (mreg[0] * mreg[1]) >> 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void NES_MMC5::SetStereoMix(int trk, xgm::INT16 mixl, xgm::INT16 mixr)
|
||||
{
|
||||
if (trk < 0) return;
|
||||
if (trk > 2) return;
|
||||
sm[0][trk] = mixl;
|
||||
sm[1][trk] = mixr;
|
||||
}
|
||||
|
||||
ITrackInfo *NES_MMC5::GetTrackInfo(int trk)
|
||||
{
|
||||
assert(trk<3);
|
||||
|
||||
if (trk < 2) // square
|
||||
{
|
||||
trkinfo[trk]._freq = freq[trk];
|
||||
if(freq[trk])
|
||||
trkinfo[trk].freq = clock/16/(freq[trk] + 1);
|
||||
else
|
||||
trkinfo[trk].freq = 0;
|
||||
|
||||
trkinfo[trk].output = out[trk];
|
||||
trkinfo[trk].max_volume = 15;
|
||||
trkinfo[trk].volume = volume[trk]+(envelope_disable[trk]?0:0x10);
|
||||
trkinfo[trk].key = (envelope_disable[trk]?(volume[trk]>0): (envelope_counter[trk]>0));
|
||||
trkinfo[trk].tone = duty[trk];
|
||||
}
|
||||
else // pcm
|
||||
{
|
||||
trkinfo[trk]._freq = 0;
|
||||
trkinfo[trk].freq = 0;
|
||||
trkinfo[trk].output = out[2];
|
||||
trkinfo[trk].max_volume = 255;
|
||||
trkinfo[trk].volume = pcm;
|
||||
trkinfo[trk].key = 0;
|
||||
trkinfo[trk].tone = pcm_mode ? 1 : 0;
|
||||
}
|
||||
|
||||
return &trkinfo[trk];
|
||||
}
|
||||
|
||||
// pcm read mode requires CPU read access
|
||||
void NES_MMC5::SetCPU(NES_CPU* cpu_)
|
||||
{
|
||||
cpu = cpu_;
|
||||
}
|
||||
|
||||
}// namespace
|
74
extern/NSFplay/nes_mmc5.h
vendored
Normal file
74
extern/NSFplay/nes_mmc5.h
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
#ifndef _NES_MMC5_H_
|
||||
#define _NES_MMC5_H_
|
||||
#include "../device.h"
|
||||
#include "../CPU/nes_cpu.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
class NES_MMC5:public ISoundChip
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{ OPT_NONLINEAR_MIXER=0, OPT_PHASE_REFRESH, OPT_END };
|
||||
|
||||
protected:
|
||||
int option[OPT_END];
|
||||
int mask;
|
||||
INT32 sm[2][3]; // stereo panning
|
||||
UINT8 ram[0x6000 - 0x5c00];
|
||||
UINT8 reg[8];
|
||||
UINT8 mreg[2];
|
||||
UINT8 pcm; // PCM channel
|
||||
bool pcm_mode; // PCM channel
|
||||
NES_CPU* cpu; // PCM channel reads need CPU access
|
||||
|
||||
UINT32 scounter[2]; // frequency divider
|
||||
UINT32 sphase[2]; // phase counter
|
||||
|
||||
UINT32 duty[2];
|
||||
UINT32 volume[2];
|
||||
UINT32 freq[2];
|
||||
INT32 out[3];
|
||||
bool enable[2];
|
||||
|
||||
bool envelope_disable[2]; // エンベロープ有効フラグ
|
||||
bool envelope_loop[2]; // エンベロープループ
|
||||
bool envelope_write[2];
|
||||
int envelope_div_period[2];
|
||||
int envelope_div[2];
|
||||
int envelope_counter[2];
|
||||
|
||||
int length_counter[2];
|
||||
|
||||
int frame_sequence_count;
|
||||
|
||||
double clock, rate;
|
||||
INT32 calc_sqr (int i, UINT32 clocks);
|
||||
INT32 square_table[32];
|
||||
INT32 pcm_table[256];
|
||||
TrackInfoBasic trkinfo[3];
|
||||
public:
|
||||
NES_MMC5 ();
|
||||
~NES_MMC5 ();
|
||||
|
||||
void FrameSequence ();
|
||||
void TickFrameSequence (UINT32 clocks);
|
||||
|
||||
virtual void Reset ();
|
||||
virtual void Tick (UINT32 clocks);
|
||||
virtual UINT32 Render (INT32 b[2]);
|
||||
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
|
||||
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
|
||||
virtual void SetOption (int id, int b);
|
||||
virtual void SetClock (double);
|
||||
virtual void SetRate (double);
|
||||
virtual void SetMask (int m){ mask = m; }
|
||||
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
|
||||
virtual ITrackInfo *GetTrackInfo(int trk);
|
||||
|
||||
void SetCPU(NES_CPU* cpu_);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
367
extern/NSFplay/nes_n106.cpp
vendored
Normal file
367
extern/NSFplay/nes_n106.cpp
vendored
Normal file
|
@ -0,0 +1,367 @@
|
|||
#include <cstring>
|
||||
#include "nes_n106.h"
|
||||
|
||||
namespace xgm {
|
||||
|
||||
NES_N106::NES_N106 ()
|
||||
{
|
||||
option[OPT_SERIAL] = 0;
|
||||
option[OPT_PHASE_READ_ONLY] = 0;
|
||||
option[OPT_LIMIT_WAVELENGTH] = 0;
|
||||
SetClock (DEFAULT_CLOCK);
|
||||
SetRate (DEFAULT_RATE);
|
||||
for (int i=0; i < 8; ++i)
|
||||
{
|
||||
sm[0][i] = 128;
|
||||
sm[1][i] = 128;
|
||||
}
|
||||
Reset();
|
||||
}
|
||||
|
||||
NES_N106::~NES_N106 ()
|
||||
{
|
||||
}
|
||||
|
||||
void NES_N106::SetStereoMix (int trk, INT16 mixl, INT16 mixr)
|
||||
{
|
||||
if (trk < 0 || trk >= 8) return;
|
||||
trk = 7-trk; // displayed channels are inverted
|
||||
sm[0][trk] = mixl;
|
||||
sm[1][trk] = mixr;
|
||||
}
|
||||
|
||||
ITrackInfo *NES_N106::GetTrackInfo (int trk)
|
||||
{
|
||||
int channels = get_channels();
|
||||
int channel = 7-trk; // invert the track display
|
||||
|
||||
TrackInfoN106* t = &trkinfo[channel];
|
||||
|
||||
if (trk >= channels)
|
||||
{
|
||||
t->max_volume = 15;
|
||||
t->volume = 0;
|
||||
t->_freq = 0;
|
||||
t->wavelen = 0;
|
||||
t->tone = -1;
|
||||
t->output = 0;
|
||||
t->key = false;
|
||||
t->freq = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
t->max_volume = 15;
|
||||
t->volume = get_vol(channel);
|
||||
t->_freq = get_freq(channel);
|
||||
t->wavelen = get_len(channel);
|
||||
t->tone = get_off(channel);
|
||||
t->output = fout[channel];
|
||||
|
||||
t->key = (t->volume > 0) && (t->_freq > 0);
|
||||
t->freq = (double(t->_freq) * clock) / double(15 * 65536 * channels * t->wavelen);
|
||||
|
||||
for (int i=0; i < t->wavelen; ++i)
|
||||
t->wave[i] = get_sample((i+t->tone)&0xFF);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
void NES_N106::SetClock (double c)
|
||||
{
|
||||
clock = c;
|
||||
}
|
||||
|
||||
void NES_N106::SetRate (double r)
|
||||
{
|
||||
rate = r;
|
||||
}
|
||||
|
||||
void NES_N106::SetMask (int m)
|
||||
{
|
||||
// bit reverse the mask,
|
||||
// N163 waves are displayed in reverse order
|
||||
mask = 0
|
||||
| ((m & (1<<0)) ? (1<<7) : 0)
|
||||
| ((m & (1<<1)) ? (1<<6) : 0)
|
||||
| ((m & (1<<2)) ? (1<<5) : 0)
|
||||
| ((m & (1<<3)) ? (1<<4) : 0)
|
||||
| ((m & (1<<4)) ? (1<<3) : 0)
|
||||
| ((m & (1<<5)) ? (1<<2) : 0)
|
||||
| ((m & (1<<6)) ? (1<<1) : 0)
|
||||
| ((m & (1<<7)) ? (1<<0) : 0);
|
||||
}
|
||||
|
||||
void NES_N106::SetOption (int id, int val)
|
||||
{
|
||||
if (id<OPT_END) option[id] = val;
|
||||
}
|
||||
|
||||
void NES_N106::Reset ()
|
||||
{
|
||||
master_disable = false;
|
||||
::memset(reg, 0, sizeof(reg));
|
||||
reg_select = 0;
|
||||
reg_advance = false;
|
||||
tick_channel = 0;
|
||||
tick_clock = 0;
|
||||
render_channel = 0;
|
||||
render_clock = 0;
|
||||
render_subclock = 0;
|
||||
|
||||
for (int i=0; i<8; ++i) fout[i] = 0;
|
||||
|
||||
Write(0xE000, 0x00); // master disable off
|
||||
Write(0xF800, 0x80); // select $00 with auto-increment
|
||||
for (unsigned int i=0; i<0x80; ++i) // set all regs to 0
|
||||
{
|
||||
Write(0x4800, 0x00);
|
||||
}
|
||||
Write(0xF800, 0x00); // select $00 without auto-increment
|
||||
}
|
||||
|
||||
void NES_N106::Tick (UINT32 clocks)
|
||||
{
|
||||
if (master_disable) return;
|
||||
|
||||
int channels = get_channels();
|
||||
|
||||
tick_clock += clocks;
|
||||
render_clock += clocks; // keep render in sync
|
||||
while (tick_clock > 0)
|
||||
{
|
||||
int channel = 7-tick_channel;
|
||||
|
||||
UINT32 phase = get_phase(channel);
|
||||
UINT32 freq = get_freq(channel);
|
||||
UINT32 len = get_len(channel);
|
||||
UINT32 off = get_off(channel);
|
||||
INT32 vol = get_vol(channel);
|
||||
|
||||
// accumulate 24-bit phase
|
||||
phase = (phase + freq) & 0x00FFFFFF;
|
||||
|
||||
// wrap phase if wavelength exceeded
|
||||
UINT32 hilen = len << 16;
|
||||
while (phase >= hilen) phase -= hilen;
|
||||
|
||||
// write back phase
|
||||
set_phase(phase, channel);
|
||||
|
||||
// fetch sample (note: N163 output is centred at 8, and inverted w.r.t 2A03)
|
||||
INT32 sample = 8 - get_sample(((phase >> 16) + off) & 0xFF);
|
||||
fout[channel] = sample * vol;
|
||||
|
||||
// cycle to next channel every 15 clocks
|
||||
tick_clock -= 15;
|
||||
++tick_channel;
|
||||
if (tick_channel >= channels)
|
||||
tick_channel = 0;
|
||||
}
|
||||
}
|
||||
|
||||
UINT32 NES_N106::Render (INT32 b[2])
|
||||
{
|
||||
b[0] = 0;
|
||||
b[1] = 0;
|
||||
if (master_disable) return 2;
|
||||
|
||||
int channels = get_channels();
|
||||
|
||||
if (option[OPT_SERIAL]) // hardware accurate serial multiplexing
|
||||
{
|
||||
// this could be made more efficient than going clock-by-clock
|
||||
// but this way is simpler
|
||||
int clocks = render_clock;
|
||||
while (clocks > 0)
|
||||
{
|
||||
int c = 7-render_channel;
|
||||
if (0 == ((mask >> c) & 1))
|
||||
{
|
||||
b[0] += fout[c] * sm[0][c];
|
||||
b[1] += fout[c] * sm[1][c];
|
||||
}
|
||||
|
||||
++render_subclock;
|
||||
if (render_subclock >= 15) // each channel gets a 15-cycle slice
|
||||
{
|
||||
render_subclock = 0;
|
||||
++render_channel;
|
||||
if (render_channel >= channels)
|
||||
render_channel = 0;
|
||||
}
|
||||
--clocks;
|
||||
}
|
||||
|
||||
// increase output level by 1 bits (7 bits already added from sm)
|
||||
b[0] <<= 1;
|
||||
b[1] <<= 1;
|
||||
|
||||
// average the output
|
||||
if (render_clock > 0)
|
||||
{
|
||||
b[0] /= render_clock;
|
||||
b[1] /= render_clock;
|
||||
}
|
||||
render_clock = 0;
|
||||
}
|
||||
else // just mix all channels
|
||||
{
|
||||
for (int i = (8-channels); i<8; ++i)
|
||||
{
|
||||
if (0 == ((mask >> i) & 1))
|
||||
{
|
||||
b[0] += fout[i] * sm[0][i];
|
||||
b[1] += fout[i] * sm[1][i];
|
||||
}
|
||||
}
|
||||
|
||||
// mix together, increase output level by 8 bits, roll off 7 bits from sm
|
||||
INT32 MIX[9] = { 256/1, 256/1, 256/2, 256/3, 256/4, 256/5, 256/6, 256/6, 256/6 };
|
||||
b[0] = (b[0] * MIX[channels]) >> 7;
|
||||
b[1] = (b[1] * MIX[channels]) >> 7;
|
||||
// when approximating the serial multiplex as a straight mix, once the
|
||||
// multiplex frequency gets below the nyquist frequency an average mix
|
||||
// begins to sound too quiet. To approximate this effect, I don't attenuate
|
||||
// any further after 6 channels are active.
|
||||
}
|
||||
|
||||
// 8 bit approximation of master volume
|
||||
// max N163 vol vs max APU square
|
||||
// unfortunately, games have been measured as low as 3.4x and as high as 8.5x
|
||||
// with higher volumes on Erika, King of Kings, and Rolling Thunder
|
||||
// and lower volumes on others. Using 6.0x as a rough "one size fits all".
|
||||
const double MASTER_VOL = 6.0 * 1223.0;
|
||||
const double MAX_OUT = 15.0 * 15.0 * 256.0; // max digital value
|
||||
const INT32 GAIN = int((MASTER_VOL / MAX_OUT) * 256.0f);
|
||||
b[0] = (b[0] * GAIN) >> 8;
|
||||
b[1] = (b[1] * GAIN) >> 8;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool NES_N106::Write (UINT32 adr, UINT32 val, UINT32 id)
|
||||
{
|
||||
if (adr == 0xE000) // master disable
|
||||
{
|
||||
master_disable = ((val & 0x40) != 0);
|
||||
return true;
|
||||
}
|
||||
else if (adr == 0xF800) // register select
|
||||
{
|
||||
reg_select = (val & 0x7F);
|
||||
reg_advance = (val & 0x80) != 0;
|
||||
return true;
|
||||
}
|
||||
else if (adr == 0x4800) // register write
|
||||
{
|
||||
if (option[OPT_PHASE_READ_ONLY]) // old emulators didn't know phase was stored here
|
||||
{
|
||||
int c = 15 - (reg_select/8);
|
||||
int r = reg_select & 7;
|
||||
if (c < get_channels() &&
|
||||
(r == 1 ||
|
||||
r == 3 ||
|
||||
r == 5))
|
||||
{
|
||||
if (reg_advance)
|
||||
reg_select = (reg_select + 1) & 0x7F;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (option[OPT_LIMIT_WAVELENGTH]) // old emulators ignored top 3 bits of length
|
||||
{
|
||||
int c = 15 - (reg_select/8);
|
||||
int r = reg_select & 7;
|
||||
if (c < get_channels() && r == 4)
|
||||
{
|
||||
val |= 0xE0;
|
||||
}
|
||||
}
|
||||
reg[reg_select] = val;
|
||||
if (reg_advance)
|
||||
reg_select = (reg_select + 1) & 0x7F;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NES_N106::Read (UINT32 adr, UINT32 & val, UINT32 id)
|
||||
{
|
||||
if (adr == 0x4800) // register read
|
||||
{
|
||||
val = reg[reg_select];
|
||||
if (reg_advance)
|
||||
reg_select = (reg_select + 1) & 0x7F;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// register decoding/encoding functions
|
||||
//
|
||||
|
||||
inline UINT32 NES_N106::get_phase (int channel)
|
||||
{
|
||||
// 24-bit phase stored in channel regs 1/3/5
|
||||
channel = channel << 3;
|
||||
return (reg[0x41 + channel] )
|
||||
+ (reg[0x43 + channel] << 8 )
|
||||
+ (reg[0x45 + channel] << 16);
|
||||
}
|
||||
|
||||
inline UINT32 NES_N106::get_freq (int channel)
|
||||
{
|
||||
// 19-bit frequency stored in channel regs 0/2/4
|
||||
channel = channel << 3;
|
||||
return ( reg[0x40 + channel] )
|
||||
+ ( reg[0x42 + channel] << 8 )
|
||||
+ ((reg[0x44 + channel] & 0x03) << 16);
|
||||
}
|
||||
|
||||
inline UINT32 NES_N106::get_off (int channel)
|
||||
{
|
||||
// 8-bit offset stored in channel reg 6
|
||||
channel = channel << 3;
|
||||
return reg[0x46 + channel];
|
||||
}
|
||||
|
||||
inline UINT32 NES_N106::get_len (int channel)
|
||||
{
|
||||
// 6-bit<<3 length stored obscurely in channel reg 4
|
||||
channel = channel << 3;
|
||||
return 256 - (reg[0x44 + channel] & 0xFC);
|
||||
}
|
||||
|
||||
inline INT32 NES_N106::get_vol (int channel)
|
||||
{
|
||||
// 4-bit volume stored in channel reg 7
|
||||
channel = channel << 3;
|
||||
return reg[0x47 + channel] & 0x0F;
|
||||
}
|
||||
|
||||
inline INT32 NES_N106::get_sample (UINT32 index)
|
||||
{
|
||||
// every sample becomes 2 samples in regs
|
||||
return (index&1) ?
|
||||
((reg[index>>1] >> 4) & 0x0F) :
|
||||
( reg[index>>1] & 0x0F) ;
|
||||
}
|
||||
|
||||
inline int NES_N106::get_channels ()
|
||||
{
|
||||
// 3-bit channel count stored in reg 0x7F
|
||||
return ((reg[0x7F] >> 4) & 0x07) + 1;
|
||||
}
|
||||
|
||||
inline void NES_N106::set_phase (UINT32 phase, int channel)
|
||||
{
|
||||
// 24-bit phase stored in channel regs 1/3/5
|
||||
channel = channel << 3;
|
||||
reg[0x41 + channel] = phase & 0xFF;
|
||||
reg[0x43 + channel] = (phase >> 8 ) & 0xFF;
|
||||
reg[0x45 + channel] = (phase >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
} //namespace
|
74
extern/NSFplay/nes_n106.h
vendored
Normal file
74
extern/NSFplay/nes_n106.h
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
#ifndef _NES_N106_H_
|
||||
#define _NES_N106_H_
|
||||
#include "../device.h"
|
||||
|
||||
namespace xgm {
|
||||
|
||||
class TrackInfoN106 : public TrackInfoBasic
|
||||
{
|
||||
public:
|
||||
int wavelen;
|
||||
INT16 wave[256];
|
||||
virtual IDeviceInfo *Clone(){ return new TrackInfoN106(*this); }
|
||||
};
|
||||
|
||||
class NES_N106:public ISoundChip
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
OPT_SERIAL = 0,
|
||||
OPT_PHASE_READ_ONLY = 1,
|
||||
OPT_LIMIT_WAVELENGTH = 2,
|
||||
OPT_END
|
||||
};
|
||||
|
||||
protected:
|
||||
double rate, clock;
|
||||
int mask;
|
||||
INT32 sm[2][8]; // stereo mix
|
||||
INT32 fout[8]; // current output
|
||||
TrackInfoN106 trkinfo[8];
|
||||
int option[OPT_END];
|
||||
|
||||
bool master_disable;
|
||||
UINT32 reg[0x80]; // all state is contained here
|
||||
unsigned int reg_select;
|
||||
bool reg_advance;
|
||||
int tick_channel;
|
||||
int tick_clock;
|
||||
int render_channel;
|
||||
int render_clock;
|
||||
int render_subclock;
|
||||
|
||||
// convenience functions to interact with regs
|
||||
inline UINT32 get_phase (int channel);
|
||||
inline UINT32 get_freq (int channel);
|
||||
inline UINT32 get_off (int channel);
|
||||
inline UINT32 get_len (int channel);
|
||||
inline INT32 get_vol (int channel);
|
||||
inline INT32 get_sample (UINT32 index);
|
||||
inline int get_channels ();
|
||||
// for storing back the phase after modifying
|
||||
inline void set_phase (UINT32 phase, int channel);
|
||||
|
||||
public:
|
||||
NES_N106 ();
|
||||
~NES_N106 ();
|
||||
|
||||
virtual void Reset ();
|
||||
virtual void Tick (UINT32 clocks);
|
||||
virtual UINT32 Render (INT32 b[2]);
|
||||
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
|
||||
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
|
||||
virtual void SetRate (double);
|
||||
virtual void SetClock (double);
|
||||
virtual void SetOption (int, int);
|
||||
virtual void SetMask (int m);
|
||||
virtual void SetStereoMix (int trk, INT16 mixl, INT16 mixr);
|
||||
virtual ITrackInfo *GetTrackInfo(int trk);
|
||||
};
|
||||
|
||||
} // namespace xgm
|
||||
|
||||
#endif
|
264
extern/NSFplay/nes_vrc6.cpp
vendored
Normal file
264
extern/NSFplay/nes_vrc6.cpp
vendored
Normal file
|
@ -0,0 +1,264 @@
|
|||
#include "nes_vrc6.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
|
||||
NES_VRC6::NES_VRC6 ()
|
||||
{
|
||||
SetClock (DEFAULT_CLOCK);
|
||||
SetRate (DEFAULT_RATE);
|
||||
|
||||
halt = false;
|
||||
freq_shift = 0;
|
||||
|
||||
for(int c=0;c<2;++c)
|
||||
for(int t=0;t<3;++t)
|
||||
sm[c][t] = 128;
|
||||
}
|
||||
|
||||
NES_VRC6::~NES_VRC6 ()
|
||||
{
|
||||
}
|
||||
|
||||
void NES_VRC6::SetStereoMix(int trk, xgm::INT16 mixl, xgm::INT16 mixr)
|
||||
{
|
||||
if (trk < 0) return;
|
||||
if (trk > 2) return;
|
||||
sm[0][trk] = mixl;
|
||||
sm[1][trk] = mixr;
|
||||
}
|
||||
|
||||
ITrackInfo *NES_VRC6::GetTrackInfo(int trk)
|
||||
{
|
||||
if(trk<2)
|
||||
{
|
||||
trkinfo[trk].max_volume = 15;
|
||||
trkinfo[trk].volume = volume[trk];
|
||||
trkinfo[trk]._freq = freq2[trk];
|
||||
trkinfo[trk].freq = freq2[trk]?clock/16/(freq2[trk]+1):0;
|
||||
trkinfo[trk].tone = duty[trk];
|
||||
trkinfo[trk].key = (volume[trk]>0)&&enable[trk]&&!gate[trk];
|
||||
return &trkinfo[trk];
|
||||
}
|
||||
else if(trk==2)
|
||||
{
|
||||
trkinfo[2].max_volume = 255;
|
||||
trkinfo[2].volume = volume[2];
|
||||
trkinfo[2]._freq = freq2[2];
|
||||
trkinfo[2].freq = freq2[2]?clock/14/(freq2[2]+1):0;
|
||||
trkinfo[2].tone = -1;
|
||||
trkinfo[2].key = (enable[2]>0);
|
||||
return &trkinfo[2];
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void NES_VRC6::SetClock (double c)
|
||||
{
|
||||
clock = c;
|
||||
}
|
||||
|
||||
void NES_VRC6::SetRate (double r)
|
||||
{
|
||||
rate = r ? r : DEFAULT_RATE;
|
||||
}
|
||||
|
||||
void NES_VRC6::SetOption (int id, int val)
|
||||
{
|
||||
if(id<OPT_END)
|
||||
{
|
||||
//option[id] = val;
|
||||
}
|
||||
}
|
||||
|
||||
void NES_VRC6::Reset ()
|
||||
{
|
||||
Write (0x9003, 0);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Write (0x9000 + i, 0);
|
||||
Write (0xa000 + i, 0);
|
||||
Write (0xb000 + i, 0);
|
||||
}
|
||||
count14 = 0;
|
||||
mask = 0;
|
||||
counter[0] = 0;
|
||||
counter[1] = 0;
|
||||
counter[2] = 0;
|
||||
phase[0] = 0;
|
||||
phase[0] = 1;
|
||||
phase[0] = 2;
|
||||
}
|
||||
|
||||
INT16 NES_VRC6::calc_sqr (int i, UINT32 clocks)
|
||||
{
|
||||
static const INT16 sqrtbl[8][16] = {
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
};
|
||||
|
||||
if (!enable[i])
|
||||
return 0;
|
||||
|
||||
if (!halt)
|
||||
{
|
||||
counter[i] += clocks;
|
||||
while(counter[i] > freq2[i])
|
||||
{
|
||||
phase[i] = (phase[i] + 1) & 15;
|
||||
counter[i] -= (freq2[i] + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return (gate[i]
|
||||
|| sqrtbl[duty[i]][phase[i]])? volume[i] : 0;
|
||||
}
|
||||
|
||||
INT16 NES_VRC6::calc_saw (UINT32 clocks)
|
||||
{
|
||||
if (!enable[2])
|
||||
return 0;
|
||||
|
||||
if (!halt)
|
||||
{
|
||||
counter[2] += clocks;
|
||||
while(counter[2] > freq2[2])
|
||||
{
|
||||
counter[2] -= (freq2[2] + 1);
|
||||
|
||||
// accumulate saw
|
||||
++count14;
|
||||
if (count14 >= 14)
|
||||
{
|
||||
count14 = 0;
|
||||
phase[2] = 0;
|
||||
}
|
||||
else if (0 == (count14 & 1)) // only accumulate on even ticks
|
||||
{
|
||||
phase[2] = (phase[2] + volume[2]) & 0xFF; // note 8-bit wrapping behaviour
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only top 5 bits of saw are output
|
||||
return phase[2] >> 3;
|
||||
}
|
||||
|
||||
void NES_VRC6::Tick (UINT32 clocks)
|
||||
{
|
||||
out[0] = calc_sqr(0,clocks);
|
||||
out[1] = calc_sqr(1,clocks);
|
||||
out[2] = calc_saw(clocks);
|
||||
}
|
||||
|
||||
UINT32 NES_VRC6::Render (INT32 b[2])
|
||||
{
|
||||
INT32 m[3];
|
||||
m[0] = out[0];
|
||||
m[1] = out[1];
|
||||
m[2] = out[2];
|
||||
|
||||
// note: signal is inverted compared to 2A03
|
||||
|
||||
m[0] = (mask & 1) ? 0 : -m[0];
|
||||
m[1] = (mask & 2) ? 0 : -m[1];
|
||||
m[2] = (mask & 4) ? 0 : -m[2];
|
||||
|
||||
b[0] = m[0] * sm[0][0];
|
||||
b[0] += m[1] * sm[0][1];
|
||||
b[0] += m[2] * sm[0][2];
|
||||
//b[0] >>= (7 - 7);
|
||||
|
||||
b[1] = m[0] * sm[1][0];
|
||||
b[1] += m[1] * sm[1][1];
|
||||
b[1] += m[2] * sm[1][2];
|
||||
//b[1] >>= (7 - 7);
|
||||
|
||||
// master volume adjustment
|
||||
const INT32 MASTER = INT32(256.0 * 1223.0 / 1920.0);
|
||||
b[0] = (b[0] * MASTER) >> 8;
|
||||
b[1] = (b[1] * MASTER) >> 8;
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool NES_VRC6::Write (UINT32 adr, UINT32 val, UINT32 id)
|
||||
{
|
||||
int ch, cmap[4] = { 0, 0, 1, 2 };
|
||||
|
||||
switch (adr)
|
||||
{
|
||||
case 0x9000:
|
||||
case 0xa000:
|
||||
ch = cmap[(adr >> 12) & 3];
|
||||
volume[ch] = val & 15;
|
||||
duty[ch] = (val >> 4) & 7;
|
||||
gate[ch] = (val >> 7) & 1;
|
||||
break;
|
||||
case 0xb000:
|
||||
volume[2] = val & 63;
|
||||
break;
|
||||
|
||||
case 0x9001:
|
||||
case 0xa001:
|
||||
case 0xb001:
|
||||
ch = cmap[(adr >> 12) & 3];
|
||||
freq[ch] = (freq[ch] & 0xf00) | val;
|
||||
freq2[ch] = (freq[ch] >> freq_shift);
|
||||
if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch];
|
||||
break;
|
||||
|
||||
case 0x9002:
|
||||
case 0xa002:
|
||||
case 0xb002:
|
||||
ch = cmap[(adr >> 12) & 3];
|
||||
freq[ch] = ((val & 0xf) << 8) + (freq[ch] & 0xff);
|
||||
freq2[ch] = (freq[ch] >> freq_shift);
|
||||
if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch];
|
||||
if (!enable[ch]) // if enable is being turned on, phase should be reset
|
||||
{
|
||||
if (ch == 2)
|
||||
{
|
||||
count14 = 0; // reset saw
|
||||
}
|
||||
phase[ch] = 0;
|
||||
}
|
||||
enable[ch] = (val >> 7) & 1;
|
||||
break;
|
||||
|
||||
case 0x9003:
|
||||
halt = val & 1;
|
||||
freq_shift =
|
||||
(val & 4) ? 8 :
|
||||
(val & 2) ? 4 :
|
||||
0;
|
||||
freq2[0] = (freq[0] >> freq_shift);
|
||||
freq2[1] = (freq[1] >> freq_shift);
|
||||
freq2[2] = (freq[2] >> freq_shift);
|
||||
if (counter[0] > freq2[0]) counter[0] = freq2[0];
|
||||
if (counter[1] > freq2[1]) counter[1] = freq2[1];
|
||||
if (counter[2] > freq2[2]) counter[2] = freq2[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NES_VRC6::Read (UINT32 adr, UINT32 & val, UINT32 id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
56
extern/NSFplay/nes_vrc6.h
vendored
Normal file
56
extern/NSFplay/nes_vrc6.h
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
#ifndef _NES_VRC6_H_
|
||||
#define _NES_VRC6_H_
|
||||
#include "../device.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
|
||||
class NES_VRC6:public ISoundChip
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
OPT_END
|
||||
};
|
||||
protected:
|
||||
UINT32 counter[3]; // frequency divider
|
||||
UINT32 phase[3]; // phase counter
|
||||
UINT32 freq2[3]; // adjusted frequency
|
||||
int count14; // saw 14-stage counter
|
||||
|
||||
//int option[OPT_END];
|
||||
int mask;
|
||||
INT32 sm[2][3]; // stereo mix
|
||||
int duty[2];
|
||||
int volume[3];
|
||||
int enable[3];
|
||||
int gate[3];
|
||||
UINT32 freq[3];
|
||||
INT16 calc_sqr (int i, UINT32 clocks);
|
||||
INT16 calc_saw (UINT32 clocks);
|
||||
bool halt;
|
||||
int freq_shift;
|
||||
double clock, rate;
|
||||
INT32 out[3];
|
||||
TrackInfoBasic trkinfo[3];
|
||||
|
||||
public:
|
||||
NES_VRC6 ();
|
||||
~NES_VRC6 ();
|
||||
|
||||
virtual void Reset ();
|
||||
virtual void Tick (UINT32 clocks);
|
||||
virtual UINT32 Render (INT32 b[2]);
|
||||
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
|
||||
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
|
||||
virtual void SetClock (double);
|
||||
virtual void SetRate (double);
|
||||
virtual void SetOption (int, int);
|
||||
virtual void SetMask (int m){ mask = m; }
|
||||
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
|
||||
virtual ITrackInfo *GetTrackInfo(int trk);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
189
extern/NSFplay/nes_vrc7.cpp
vendored
Normal file
189
extern/NSFplay/nes_vrc7.cpp
vendored
Normal file
|
@ -0,0 +1,189 @@
|
|||
#include <cstring>
|
||||
#include "nes_vrc7.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
NES_VRC7::NES_VRC7 ()
|
||||
{
|
||||
use_all_channels = false;
|
||||
patch_set = OPLL_VRC7_TONE;
|
||||
patch_custom = NULL;
|
||||
divider = 0;
|
||||
|
||||
opll = OPLL_new ( 3579545, DEFAULT_RATE);
|
||||
OPLL_reset_patch (opll, patch_set);
|
||||
SetClock(DEFAULT_CLOCK);
|
||||
|
||||
for(int c=0;c<2;++c)
|
||||
//for(int t=0;t<6;++t)
|
||||
for(int t=0;t<9;++t) // HACK for YM2413 support
|
||||
sm[c][t] = 128;
|
||||
}
|
||||
|
||||
NES_VRC7::~NES_VRC7 ()
|
||||
{
|
||||
OPLL_delete (opll);
|
||||
}
|
||||
|
||||
void NES_VRC7::UseAllChannels(bool b)
|
||||
{
|
||||
use_all_channels = b;
|
||||
}
|
||||
|
||||
void NES_VRC7::SetPatchSet(int p)
|
||||
{
|
||||
patch_set = p;
|
||||
}
|
||||
|
||||
void NES_VRC7::SetPatchSetCustom (const UINT8* pset)
|
||||
{
|
||||
patch_custom = pset;
|
||||
}
|
||||
|
||||
void NES_VRC7::SetClock (double c)
|
||||
{
|
||||
clock = c / 36;
|
||||
}
|
||||
|
||||
void NES_VRC7::SetRate (double r)
|
||||
{
|
||||
//rate = r ? r : DEFAULT_RATE;
|
||||
(void)r; // rate is ignored
|
||||
rate = 49716;
|
||||
OPLL_set_quality(opll, 1); // quality always on (not really a CPU hog)
|
||||
OPLL_set_rate(opll,(uint32_t)rate);
|
||||
}
|
||||
|
||||
void NES_VRC7::SetOption (int id, int val)
|
||||
{
|
||||
if(id<OPT_END)
|
||||
{
|
||||
option[id] = val;
|
||||
}
|
||||
}
|
||||
|
||||
void NES_VRC7::Reset ()
|
||||
{
|
||||
for (int i=0; i < 0x40; ++i)
|
||||
{
|
||||
Write(0x9010,i);
|
||||
Write(0x9030,0);
|
||||
}
|
||||
|
||||
divider = 0;
|
||||
OPLL_reset_patch (opll, patch_set);
|
||||
if (patch_custom)
|
||||
{
|
||||
uint8_t dump[19 * 8];
|
||||
memcpy(dump, patch_custom, 16 * 8);
|
||||
memset(dump + 16 * 8, 0, 3 * 8);
|
||||
OPLL_setPatch(opll, dump);
|
||||
}
|
||||
OPLL_reset (opll);
|
||||
}
|
||||
|
||||
void NES_VRC7::SetStereoMix(int trk, xgm::INT16 mixl, xgm::INT16 mixr)
|
||||
{
|
||||
if (trk < 0) return;
|
||||
//if (trk > 5) return;
|
||||
if (trk > 8) return; // HACK YM2413
|
||||
sm[0][trk] = mixl;
|
||||
sm[1][trk] = mixr;
|
||||
}
|
||||
|
||||
ITrackInfo *NES_VRC7::GetTrackInfo(int trk)
|
||||
{
|
||||
//if(opll&&trk<6)
|
||||
if(opll&&trk<9) // HACK YM2413 (percussion mode isn't very diagnostic this way though)
|
||||
{
|
||||
trkinfo[trk].max_volume = 15;
|
||||
trkinfo[trk].volume = 15 - ((opll->reg[0x30+trk])&15);
|
||||
trkinfo[trk]._freq = opll->reg[0x10+trk]+((opll->reg[0x20+trk]&1)<<8);
|
||||
int blk = (opll->reg[0x20+trk]>>1)&7;
|
||||
trkinfo[trk].freq = clock*trkinfo[trk]._freq/(double)(0x80000>>blk);
|
||||
trkinfo[trk].tone = (opll->reg[0x30+trk]>>4)&15;
|
||||
//trkinfo[trk].key = (opll->reg[0x20+trk]&0x10)?true:false;
|
||||
trkinfo[trk].key = opll->slot_key_status & (3 << trk)?true:false;
|
||||
return &trkinfo[trk];
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool NES_VRC7::Write (UINT32 adr, UINT32 val, UINT32 id)
|
||||
{
|
||||
if (adr == 0x9010)
|
||||
{
|
||||
OPLL_writeIO (opll, 0, val);
|
||||
return true;
|
||||
}
|
||||
if (adr == 0x9030)
|
||||
{
|
||||
OPLL_writeIO (opll, 1, val);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NES_VRC7::Read (UINT32 adr, UINT32 & val, UINT32 id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void NES_VRC7::Tick (UINT32 clocks)
|
||||
{
|
||||
divider += clocks;
|
||||
while (divider >= 36)
|
||||
{
|
||||
divider -= 36;
|
||||
OPLL_calc(opll);
|
||||
}
|
||||
}
|
||||
|
||||
UINT32 NES_VRC7::Render (INT32 b[2])
|
||||
{
|
||||
b[0] = b[1] = 0;
|
||||
for (int i=0; i < 6; ++i)
|
||||
{
|
||||
INT32 val = (mask & (1<<i)) ? 0 : opll->ch_out[i] >> 4;
|
||||
b[0] += val * sm[0][i];
|
||||
b[1] += val * sm[1][i];
|
||||
}
|
||||
|
||||
// HACK for YM2413 support
|
||||
if (use_all_channels)
|
||||
{
|
||||
for (int i=6; i < 9; ++i)
|
||||
{
|
||||
if (mask & (1<<i)) continue;
|
||||
|
||||
INT32 val;
|
||||
if (opll->patch_number[i] > 15) // rhytm mode
|
||||
{
|
||||
if (i == 6) val = opll->ch_out[9]; // BD
|
||||
else if (i == 7) val = opll->ch_out[10] + opll->ch_out[11]; // HH&SD
|
||||
else val = opll->ch_out[12] + opll->ch_out[13]; // TOM&CYM
|
||||
/* (i == 8) is implied */
|
||||
}
|
||||
else
|
||||
{
|
||||
val = opll->ch_out[i];
|
||||
}
|
||||
val >>= 4;
|
||||
b[0] += val * sm[0][i];
|
||||
b[1] += val * sm[1][i];
|
||||
}
|
||||
}
|
||||
|
||||
b[0] >>= (7 - 4);
|
||||
b[1] >>= (7 - 4);
|
||||
|
||||
// master volume adjustment
|
||||
const INT32 MASTER = INT32(1.15 * 256.0);
|
||||
b[0] = (b[0] * MASTER) >> 8;
|
||||
b[1] = (b[1] * MASTER) >> 8;
|
||||
|
||||
return 2;
|
||||
}
|
||||
}
|
53
extern/NSFplay/nes_vrc7.h
vendored
Normal file
53
extern/NSFplay/nes_vrc7.h
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
#ifndef _NES_VRC7_H_
|
||||
#define _NES_VRC7_H_
|
||||
#include "../device.h"
|
||||
#include "legacy/emu2413.h"
|
||||
|
||||
namespace xgm
|
||||
{
|
||||
|
||||
class NES_VRC7 : public ISoundChip
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
OPT_OPLL=0,
|
||||
OPT_END
|
||||
};
|
||||
protected:
|
||||
int option[OPT_END];
|
||||
int mask;
|
||||
int patch_set;
|
||||
const UINT8* patch_custom;
|
||||
//INT32 sm[2][6]; // stereo mix
|
||||
INT32 sm[2][9]; // stereo mix temporary HACK to support YM2413
|
||||
INT16 buf[2];
|
||||
OPLL *opll;
|
||||
UINT32 divider; // clock divider
|
||||
double clock, rate;
|
||||
//TrackInfoBasic trkinfo[6];
|
||||
TrackInfoBasic trkinfo[9]; // HACK to support YM2413
|
||||
bool use_all_channels;
|
||||
public:
|
||||
NES_VRC7 ();
|
||||
~NES_VRC7 ();
|
||||
|
||||
virtual void Reset ();
|
||||
virtual void Tick (UINT32 clocks);
|
||||
virtual UINT32 Render (INT32 b[2]);
|
||||
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
|
||||
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
|
||||
virtual void UseAllChannels (bool b);
|
||||
virtual void SetPatchSet (int p);
|
||||
virtual void SetPatchSetCustom (const UINT8* pset);
|
||||
virtual void SetClock (double);
|
||||
virtual void SetRate (double);
|
||||
virtual void SetOption (int, int);
|
||||
virtual void SetMask (int m){ mask = m; if(opll) OPLL_setMask(opll, m); }
|
||||
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
|
||||
virtual ITrackInfo *GetTrackInfo(int trk);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue