Update vgsound_emu library

This commit is contained in:
cam900 2022-09-16 23:48:06 +09:00
parent 9a3c81d90a
commit b461ffe411
28 changed files with 78 additions and 2705 deletions

View file

@ -97,6 +97,7 @@ else()
endif()
list(APPEND DEPENDENCIES_INCLUDE_DIRS "extern/SAASound/include")
list(APPEND DEPENDENCIES_INCLUDE_DIRS "extern/vgsound_emu-modified")
find_package(Threads REQUIRED)
list(APPEND DEPENDENCIES_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
@ -319,6 +320,31 @@ extern/SAASound/src/SAANoise.cpp
extern/SAASound/src/SAASndC.cpp
extern/SAASound/src/SAASound.cpp
extern/vgsound_emu-modified/vgsound_emu/src/core/vox/vox.cpp
extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.cpp
extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_alu.cpp
extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x_filter.cpp
extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.cpp
extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.cpp
extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.cpp
extern/vgsound_emu-modified/vgsound_emu/src/k005289/k005289.cpp
extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp
extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp
extern/vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.cpp
extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.cpp
extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.cpp
extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.cpp
extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.cpp
extern/adpcm/bs_codec.c
extern/adpcm/oki_codec.c
extern/adpcm/yma_codec.c
@ -398,28 +424,17 @@ src/engine/platform/sound/lynx/Mikey.cpp
src/engine/platform/sound/qsound.c
src/engine/platform/sound/x1_010/x1_010.cpp
src/engine/platform/sound/swan.cpp
src/engine/platform/sound/su.cpp
src/engine/platform/sound/k005289/k005289.cpp
src/engine/platform/sound/n163/n163.cpp
src/engine/platform/sound/vic20sound.c
src/engine/platform/sound/vrcvi/vrcvi.cpp
src/engine/platform/sound/scc/scc.cpp
src/engine/platform/sound/ymz280b.cpp
src/engine/platform/sound/rf5c68.cpp
src/engine/platform/sound/oki/okim6258.cpp
src/engine/platform/sound/oki/msm6295.cpp
src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp

View file

@ -44,7 +44,7 @@ void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_
for (size_t h=start; h<start+len; h++) {
signed int out=0;
// K005289 part
k005289->tick();
k005289.tick();
// Wavetable part
for (int i=0; i<2; i++) {
@ -52,7 +52,7 @@ void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_
oscBuf[i]->data[oscBuf[i]->needle++]=0;
continue;
} else {
chanOut=chan[i].waveROM[k005289->addr(i)]*(regPool[2+i]&0xf);
chanOut=chan[i].waveROM[k005289.addr(i)]*(regPool[2+i]&0xf);
out+=chanOut;
if (writeOscBuf==0) {
oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<7;
@ -122,9 +122,9 @@ void DivPlatformBubSysWSG::tick(bool sysTick) {
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>4095) chan[i].freq=4095;
k005289->load(i,chan[i].freq);
k005289.load(i,chan[i].freq);
rWrite(i,chan[i].freq);
k005289->update(i);
k005289.update(i);
if (chan[i].keyOn) {
// ???
}
@ -295,7 +295,7 @@ void DivPlatformBubSysWSG::reset() {
if (dumpWrites) {
addWrite(0xffffffff,0);
}
k005289->reset();
k005289.reset();
}
bool DivPlatformBubSysWSG::isStereo() {
@ -347,7 +347,6 @@ int DivPlatformBubSysWSG::init(DivEngine* p, int channels, int sugRate, unsigned
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
k005289=new k005289_core();
reset();
return 2;
}
@ -356,7 +355,6 @@ void DivPlatformBubSysWSG::quit() {
for (int i=0; i<2; i++) {
delete oscBuf[i];
}
delete k005289;
}
DivPlatformBubSysWSG::~DivPlatformBubSysWSG() {

View file

@ -24,7 +24,7 @@
#include <queue>
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/k005289/k005289.hpp"
#include "vgsound_emu/src/k005289/k005289.hpp"
class DivPlatformBubSysWSG: public DivDispatch {
struct Channel {
@ -60,7 +60,7 @@ class DivPlatformBubSysWSG: public DivDispatch {
bool isMuted[2];
unsigned char writeOscBuf;
k005289_core* k005289;
k005289_core k005289;
unsigned short regPool[4];
void updateWave(int ch);
friend void putDispatchChan(void*,int,int);

View file

@ -79,7 +79,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
updateOsc=0;
// TODO: per-channel osc
for (int i=0; i<4; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=msm.m_voice[i].m_muted?0:(msm.m_voice[i].m_out<<6);
oscBuf[i]->data[oscBuf[i]->needle++]=msm.voice_out(i)<<6;
}
}
}
@ -113,7 +113,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
rWriteDelay(0,(8<<c.chan),60); // turn off
rWriteDelay(0,(8<<c.chan),180); // turn off
rWrite(0,0x80|chan[c.chan].sample); // set phrase
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
} else {
@ -128,7 +128,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
}
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);
chan[c.chan].sample=12*sampleBank+c.value%12;
rWriteDelay(0,(8<<c.chan),60); // turn off
rWriteDelay(0,(8<<c.chan),180); // turn off
rWrite(0,0x80|chan[c.chan].sample); // set phrase
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
}
@ -138,14 +138,14 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
rWriteDelay(0,(8<<c.chan),60); // turn off
rWriteDelay(0,(8<<c.chan),180); // turn off
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
rWriteDelay(0,(8<<c.chan),60); // turn off
rWriteDelay(0,(8<<c.chan),180); // turn off
chan[c.chan].std.release();
break;
case DIV_CMD_ENV_RELEASE:
@ -206,7 +206,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
void DivPlatformMSM6295::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
msm.m_voice[ch].m_muted=mute;
msm.voice_mute(ch,mute);
}
void DivPlatformMSM6295::forceIns() {
@ -387,7 +387,7 @@ void DivPlatformMSM6295::setFlags(unsigned int flags) {
chipClock=COLOR_NTSC/3.0;
break;
}
rate=chipClock/3;
rate=chipClock;
for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate/22;
}

View file

@ -22,7 +22,7 @@
#include "../dispatch.h"
#include "../macroInt.h"
#include <queue>
#include "sound/oki/msm6295.hpp"
#include "vgsound_emu/src/msm6295/msm6295.hpp"
class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf {
protected:
@ -57,7 +57,7 @@ class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf {
unsigned short addr;
unsigned char val;
unsigned short delay;
QueuedWrite(unsigned short a, unsigned char v, unsigned short d=32):
QueuedWrite(unsigned short a, unsigned char v, unsigned short d=96):
addr(a),
val(v),
delay(d) {}

View file

@ -117,7 +117,7 @@ void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len
bufL[i]=bufR[i]=out;
if (n163.voice_cycle()==0x78) for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=n163.chan_out(i)<<7;
oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<7;
}
// command queue

View file

@ -24,7 +24,7 @@
#include <queue>
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/n163/n163.hpp"
#include "vgsound_emu/src/n163/n163.hpp"
class DivPlatformN163: public DivDispatch {
struct Channel {

View file

@ -89,7 +89,7 @@ void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len)
bufL[h]=bufR[h]=out;
for (int i=0; i<5; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=scc->chan_out(i)<<7;
oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<7;
}
}
}

View file

@ -24,7 +24,7 @@
#include <queue>
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/scc/scc.hpp"
#include "vgsound_emu/src/scc/scc.hpp"
class DivPlatformSCC: public DivDispatch {
struct Channel {

View file

@ -1,46 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900
Konami K005289 emulation core
This chip is used at infamous Konami Bubble System, for part of Wavetable sound generator.
But seriously, It is just to 2 internal 12 bit timer and address generators, rather than sound generator.
Everything except for internal counter and address are done by external logic, the chip is only has external address, frequency registers and its update pins.
Frequency calculation: Input clock / (4096 - Pitch input)
*/
#include "k005289.hpp"
void k005289_core::tick()
{
for (auto & elem : m_voice)
elem.tick();
}
void k005289_core::reset()
{
for (auto & elem : m_voice)
elem.reset();
}
void k005289_core::voice_t::tick()
{
if (bitfield(++counter, 0, 12) == 0)
{
addr = bitfield(addr + 1, 0, 5);
counter = freq;
}
}
void k005289_core::voice_t::reset()
{
addr = 0;
pitch = 0;
freq = 0;
counter = 0;
}

View file

@ -1,68 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900
Konami K005289 emulation core
See k005289.cpp for more info.
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_K005289_HPP
#define _VGSOUND_EMU_K005289_HPP
#pragma once
namespace k005289
{
typedef unsigned char u8;
typedef unsigned short u16;
typedef signed short s16;
// get bitfield, bitfield(input, position, len)
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
}
using namespace k005289;
class k005289_core
{
public:
// accessors, getters, setters
u8 addr(int voice) { return m_voice[voice & 1].addr; } // 1QA...E/2QA...E pin
void load(int voice, u16 addr) { m_voice[voice & 1].load(addr); } // LD1/2 pin, A0...11 pin
void update(int voice) { m_voice[voice & 1].update(); } // TG1/2 pin
// internal state
void reset();
void tick();
private:
// k005289 voice structs
struct voice_t
{
// internal state
void reset();
void tick();
// accessors, getters, setters
void load(u16 addr) { pitch = addr; } // Load pitch data (address pin)
void update() { freq = pitch; } // Replace current frequency to lastest loaded pitch
// registers
u8 addr = 0; // external address pin
u16 pitch = 0; // pitch
u16 freq = 0; // current frequency
s16 counter = 0; // frequency counter
};
voice_t m_voice[2];
};
#endif

View file

@ -1,167 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow
Namco 163 Sound emulation core
This chip is one of NES mapper with sound expansion, This one is by Namco.
It has 1 to 8 wavetable channels, All channel registers and waveforms are stored to internal RAM.
4 bit Waveforms are freely allocatable, and its length is variables; its can be stores many short waveforms or few long waveforms in RAM.
But waveforms are needs to squash, reallocate to avoid conflict with channel register area, each channel register size is 8 bytes per channels.
Sound output is time division multiplexed, it's can be captured only single channels output at once. in reason, More activated channels are less sound quality.
Sound register layout
Address Bit Description
7654 3210
78-7f Channel 0
78 xxxx xxxx Channel 0 Pitch input bit 0-7
79 xxxx xxxx Channel 0 Accumulator bit 0-7*
7a xxxx xxxx Channel 0 Pitch input bit 8-15
7b xxxx xxxx Channel 0 Accumulator bit 8-15*
7c xxxx xx-- Channel 0 Waveform length, 256 - (x * 4)
---- --xx Channel 0 Pitch input bit 16-17
7d xxxx xxxx Channel 0 Accumulator bit 16-23*
7e xxxx xxxx Channel 0 Waveform base offset
xxxx xxx- RAM byte (0 to 127)
---- ---x RAM nibble
---- ---0 Low nibble
---- ---1 High nibble
7f ---- xxxx Channel 0 Volume
7f Number of active channels
7f -xxx ---- Number of active channels
-000 ---- Channel 0 activated
-001 ---- Channel 1 activated
-010 ---- Channel 2 activated
...
-110 ---- Channel 6 activated
-111 ---- Channel 7 activated
70-77 Channel 1 (Optional if activated)
68-6f Channel 2 (Optional if activated)
...
48-4f Channel 6 (Optional if activated)
40-47 Channel 7 (Optional if activated)
Rest of RAM area are for 4 bit Waveform and/or scratchpad.
Each waveform byte has 2 nibbles packed, fetches LSB first, MSB next.
---- xxxx 4 bit waveform, LSB
xxxx ---- Same as above, MSB
Waveform address: Waveform base offset + Bit 16 to 23 of Accumulator, 1 LSB of result is nibble select, 7 MSB of result is Byte address in RAM.
Frequency formula:
Frequency: Pitch input * ((Input clock * 15 * Number of activated voices) / 65536)
There's to way for reduce N163 noises: reduce channel limit and demultiplex
- Channel limit is runtime changeable and it makes some usable effects.
- Demultiplex is used for "non-ear destroyable" emulators, but less hardware accurate. (when LPF and RF filter is not considered)
This core is support both, You can choose output behavior
*/
#include "n163.hpp"
#include <string.h>
void n163_core::tick()
{
if (m_multiplex)
m_out = 0;
// 0xe000-0xe7ff Disable sound bits (bit 6, bit 0 to 5 are CPU ROM Bank 0x8000-0x9fff select.)
if (m_disable)
{
if (!m_multiplex)
m_out = 0;
return;
}
// tick per each clock
const u32 freq = m_ram[m_voice_cycle + 0] | (u32(m_ram[m_voice_cycle + 2]) << 8) | (bitfield<u32>(m_ram[m_voice_cycle + 4], 0, 2) << 16); // 18 bit frequency
u32 accum = m_ram[m_voice_cycle + 1] | (u32(m_ram[m_voice_cycle + 3]) << 8) | ( u32(m_ram[m_voice_cycle + 5]) << 16); // 24 bit accumulator
const u16 length = 256 - (m_ram[m_voice_cycle + 4] & 0xfc);
const u8 addr = m_ram[m_voice_cycle + 6] + bitfield(accum, 16, 8);
const s16 wave = (bitfield(m_ram[bitfield(addr, 1, 7)], bitfield(addr, 0) << 2, 4) - 8);
const s16 volume = bitfield(m_ram[m_voice_cycle + 7], 0, 4);
// accumulate address
accum = bitfield(accum + freq, 0, 24);
if (bitfield(accum, 16, 8) >= length)
accum = bitfield(accum, 0, 18);
// writeback to register
m_ram[m_voice_cycle + 1] = bitfield(accum, 0, 8);
m_ram[m_voice_cycle + 3] = bitfield(accum, 8, 8);
m_ram[m_voice_cycle + 5] = bitfield(accum, 16, 8);
const u8 prev_voice_cycle = m_voice_cycle;
// update voice cycle
bool flush = m_multiplex ? true : false;
m_voice_cycle -= 0x8;
if (m_voice_cycle < (0x78 - (bitfield(m_ram[0x7f], 4, 3) << 3)))
{
if (!m_multiplex)
flush = true;
m_voice_cycle = 0x78;
}
// output 4 bit waveform and volume, multiplexed
const u8 chan_index = ((0x78-prev_voice_cycle)>>3)&7;
m_ch_out[chan_index]=wave * volume;
m_acc += m_ch_out[chan_index];
if (flush)
{
m_out = m_acc / (m_multiplex ? 1 : (bitfield(m_ram[0x7f], 4, 3) + 1));
m_acc = 0;
}
}
void n163_core::reset()
{
// reset this chip
m_disable = false;
m_multiplex = true;
memset(m_ram,0,sizeof(m_ram));
m_voice_cycle = 0x78;
m_addr_latch.reset();
m_out = 0;
m_acc = 0;
memset(m_ch_out,0,sizeof(m_ch_out));
}
// accessor
void n163_core::addr_w(u8 data)
{
// 0xf800-0xffff Sound address, increment
m_addr_latch.addr = bitfield(data, 0, 7);
m_addr_latch.incr = bitfield(data, 7);
}
void n163_core::data_w(u8 data, bool cpu_access)
{
// 0x4800-0x4fff Sound data write
m_ram[m_addr_latch.addr] = data;
// address latch increment
if (cpu_access && m_addr_latch.incr)
m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7);
}
u8 n163_core::data_r(bool cpu_access)
{
// 0x4800-0x4fff Sound data read
const u8 ret = m_ram[m_addr_latch.addr];
// address latch increment
if (cpu_access && m_addr_latch.incr)
m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7);
return ret;
}

View file

@ -1,90 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow
Namco 163 Sound emulation core
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_N163_HPP
#define _VGSOUND_EMU_N163_HPP
#pragma once
namespace n163
{
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef signed short s16;
// get bitfield, bitfield(input, position, len)
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
};
using namespace n163;
class n163_core
{
public:
// accessors, getters, setters
void addr_w(u8 data);
void data_w(u8 data, bool cpu_access = false);
u8 data_r(bool cpu_access = false);
void set_disable(bool disable) { m_disable = disable; }
// internal state
void reset();
void tick();
// sound output pin
s16 out() { return m_out; }
// get channel output
s16 chan_out(u8 ch) { return m_ch_out[ch]; }
// get voice cycle
u8 voice_cycle() { return m_voice_cycle; }
// register pool
u8 reg(u8 addr) { return m_ram[addr & 0x7f]; }
void set_multiplex(bool multiplex = true) { m_multiplex = multiplex; }
private:
// Address latch
struct addr_latch_t
{
addr_latch_t()
: addr(0)
, incr(0)
{ }
void reset()
{
addr = 0;
incr = 0;
}
u8 addr : 7;
u8 incr : 1;
};
bool m_disable = false;
u8 m_ram[0x80] = {0}; // internal 128 byte RAM
u8 m_voice_cycle = 0x78; // Voice cycle for processing
addr_latch_t m_addr_latch; // address latch
s16 m_out = 0; // output
s16 m_ch_out[8] = {0}; // per channel output
// demultiplex related
bool m_multiplex = true; // multiplex flag, but less noisy = inaccurate!
s16 m_acc = 0; // accumulated output
};
#endif

View file

@ -1,215 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow
OKI MSM6295 emulation core
It is 4 channel ADPCM playback chip from OKI semiconductor.
It was becomes de facto standard for ADPCM playback in arcade machine, due to cost performance.
The chip itself is pretty barebone: there is no "register" in chip.
It can't control volume and pitch in currently playing channels, only stopable them.
And volume is must be determined at playback start command.
Command format:
Playback command (2 byte):
Byte Bit Description
76543210
0 1xxxxxxx Phrase select (Header stored in ROM)
1 x000---- Play channel 4
0x00---- Play channel 3
00x0---- Play channel 2
000x---- Play channel 1
----xxxx Volume
----0000 0.0dB
----0001 -3.2dB
----0010 -6.0dB
----0011 -9.2dB
----0100 -12.0dB
----0101 -14.5dB
----0110 -18.0dB
----0111 -20.5dB
----1000 -24.0dB
Suspend command (1 byte):
Byte Bit Description
76543210
0 0x------ Suspend channel 4
0-x----- Suspend channel 3
0--x---- Suspend channel 2
0---x--- Suspend channel 1
Frequency calculation:
if (SS) then
Frequency = Input clock / 165
else then
Frequency = Input clock / 132
*/
#include "msm6295.hpp"
#define CORE_DIVIDER 3
#define CHANNEL_DELAY (15/CORE_DIVIDER)
#define MASTER_DELAY (33/CORE_DIVIDER)
void msm6295_core::tick()
{
// command handler
if (m_command_pending)
{
if (bitfield(m_command, 7)) // play voice
{
if ((++m_clock) >= ((CHANNEL_DELAY * (m_ss ? 5 : 4))))
{
m_clock = 0;
if (bitfield(m_next_command, 4, 4) != 0)
{
for (int i = 0; i < 4; i++)
{
if (bitfield(m_next_command, 4 + i))
{
if (!m_voice[i].m_busy)
{
m_voice[i].m_command = m_command;
m_voice[i].m_volume = (bitfield(m_next_command, 0, 4) < 9) ? m_volume_table[std::min<u8>(8, bitfield(m_next_command, 0, 4))] : 0;
}
break; // voices aren't be playable simultaneously at once
}
}
}
m_command = 0;
m_command_pending = false;
}
}
else if (bitfield(m_next_command, 7)) // select phrase
{
if ((++m_clock) >= ((CHANNEL_DELAY * (m_ss ? 5 : 4))))
{
m_clock = 0;
m_command = m_next_command;
m_command_pending = false;
}
}
else
{
if (bitfield(m_next_command, 3, 4) != 0) // suspend voices
{
for (int i = 0; i < 4; i++)
{
if (bitfield(m_next_command, 3 + i))
{
if (m_voice[i].m_busy)
m_voice[i].m_command = m_next_command;
}
}
m_next_command &= ~0x78;
}
m_command_pending = false;
}
}
m_out = 0;
for (int i = 0; i < 4; i++)
{
m_voice[i].tick();
if (!m_voice[i].m_muted) m_out += m_voice[i].m_out;
}
}
void msm6295_core::reset()
{
for (auto & elem : m_voice)
elem.reset();
m_command = 0;
m_next_command = 0;
m_command_pending = false;
m_clock = 0;
m_out = 0;
}
void msm6295_core::voice_t::tick()
{
if (!m_busy)
{
if (bitfield(m_command, 7))
{
// get phrase header (stored in data memory)
const u32 phrase = bitfield(m_command, 0, 7) << 3;
// Start address
m_addr = (bitfield(m_host.m_intf.read_byte(phrase | 0), 0, 2) << 16)
| (m_host.m_intf.read_byte(phrase | 1) << 8)
| (m_host.m_intf.read_byte(phrase | 2) << 0);
// End address
m_end = (bitfield(m_host.m_intf.read_byte(phrase | 3), 0, 2) << 16)
| (m_host.m_intf.read_byte(phrase | 4) << 8)
| (m_host.m_intf.read_byte(phrase | 5) << 0);
m_nibble = 4; // MSB first, LSB second
m_command = 0;
m_busy = true;
vox_decoder_t::reset();
}
m_out = 0;
}
else
{
// playback
if ((++m_clock) >= ((MASTER_DELAY * (m_host.m_ss ? 5 : 4))))
{
m_clock = 0;
bool is_end = (m_command != 0);
m_curr.decode(bitfield(m_host.m_intf.read_byte(m_addr), m_nibble, 4));
if (m_nibble <= 0)
{
m_nibble = 4;
if (++m_addr > m_end)
is_end = true;
}
else
m_nibble -= 4;
if (is_end)
{
m_command = 0;
m_busy = false;
}
m_out = (out() * m_volume) >> 7; // scale out to 12 bit output
}
}
}
void msm6295_core::voice_t::reset()
{
vox_decoder_t::reset();
m_clock = 0;
m_busy = false;
m_command = 0;
m_addr = 0;
m_nibble = 0;
m_end = 0;
m_volume = 0;
m_out = 0;
}
// accessors
u8 msm6295_core::busy_r()
{
return (m_voice[0].m_busy ? 0x01 : 0x00)
| (m_voice[1].m_busy ? 0x02 : 0x00)
| (m_voice[2].m_busy ? 0x04 : 0x00)
| (m_voice[3].m_busy ? 0x08 : 0x00);
}
void msm6295_core::command_w(u8 data)
{
if (!m_command_pending)
{
m_next_command = data;
m_command_pending = true;
}
}

View file

@ -1,95 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow
OKI MSM6295 emulation core
See msm6295.cpp for more info.
*/
#include "util.hpp"
#include "vox.hpp"
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_MSM6295_HPP
#define _VGSOUND_EMU_MSM6295_HPP
#pragma once
class msm6295_core : public vox_core
{
friend class vgsound_emu_mem_intf; // common memory interface
public:
// constructor
msm6295_core(vgsound_emu_mem_intf &intf)
: m_voice{{*this,*this},{*this,*this},{*this,*this},{*this,*this}}
, m_intf(intf)
{
}
// accessors, getters, setters
u8 busy_r();
void command_w(u8 data);
void ss_w(bool ss) { m_ss = ss; } // SS pin
// internal state
void reset();
void tick();
s32 out() { return m_out; } // built in 12 bit DAC
private:
// Internal volume table, 9 step
const s32 m_volume_table[9] = {
32/* 0.0dB */,
22/* -3.2dB */,
16/* -6.0dB */,
11/* -9.2dB */,
8/* -12.0dB */,
6/* -14.5dB */,
4/* -18.0dB */,
3/* -20.5dB */,
2/* -24.0dB */ }; // scale out to 5 bit for optimization
public:
// msm6295 voice structs
struct voice_t : vox_decoder_t
{
// constructor
voice_t(vox_core &vox, msm6295_core &host)
: vox_decoder_t(vox)
, m_host(host)
{};
// internal state
virtual void reset() override;
void tick();
// accessors, getters, setters
// registers
msm6295_core &m_host;
u16 m_clock = 0; // clock counter
bool m_busy = false; // busy status
bool m_muted = false; // muted
u8 m_command = 0; // current command
u32 m_addr = 0; // current address
s8 m_nibble = 0; // current nibble
u32 m_end = 0; // end address
s32 m_volume = 0; // volume
s32 m_out = 0; // output
};
voice_t m_voice[4];
private:
vgsound_emu_mem_intf &m_intf; // common memory interface
bool m_ss = false; // SS pin controls divider, input clock / 33 * (SS ? 5 : 4)
u8 m_command = 0; // Command byte
u8 m_next_command = 0; // Next command
bool m_command_pending = false; // command pending flag
u16 m_clock = 0; // clock counter
s32 m_out = 0; // 12 bit output
};
#endif

View file

@ -1,159 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow
Various core utilities for vgsound_emu
*/
#include <algorithm>
#include <memory>
#include <math.h>
#ifndef _VGSOUND_EMU_CORE_UTIL_HPP
#define _VGSOUND_EMU_CORE_UTIL_HPP
#pragma once
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef signed char s8;
typedef signed short s16;
typedef signed int s32;
typedef signed long long s64;
typedef float f32;
typedef double f64;
const f64 PI = 3.1415926535897932384626433832795;
// get bitfield, bitfield(input, position, len)
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
// get sign extended value, sign_ext<type>(input, len)
template<typename T> T sign_ext(T in, u8 len)
{
len = std::max<u8>(0, (8 * sizeof(T)) - len);
return T(T(in) << len) >> len;
}
// convert attenuation decibel value to gain
inline f32 dB_to_gain(f32 attenuation)
{
return powf(10.0f, attenuation / 20.0f);
}
class vgsound_emu_mem_intf
{
public:
virtual u8 read_byte(u32 address) { return 0; }
virtual u16 read_word(u32 address) { return 0; }
virtual u32 read_dword(u32 address) { return 0; }
virtual u64 read_qword(u32 address) { return 0; }
virtual void write_byte(u32 address, u8 data) { }
virtual void write_word(u32 address, u16 data) { }
virtual void write_dword(u32 address, u32 data) { }
virtual void write_qword(u32 address, u64 data) { }
};
template<typename T, T InitWidth, u8 InitEdge = 0>
struct clock_pulse_t
{
void reset(T init = InitWidth)
{
m_edge.reset();
m_width = m_width_latch = m_counter = init;
m_cycle = 0;
}
bool tick(T width = 0)
{
bool carry = ((--m_counter) <= 0);
if (carry)
{
if (!width)
m_width = m_width_latch;
else
m_width = width; // reset width
m_counter = m_width;
m_cycle = 0;
}
else
m_cycle++;
m_edge.tick(carry);
return carry;
}
void set_width(T width) { m_width = width; }
void set_width_latch(T width) { m_width_latch = width; }
// Accessors
bool current_edge() { return m_edge.m_current; }
bool rising_edge() { return m_edge.m_rising; }
bool falling_edge() { return m_edge.m_rising; }
T cycle() { return m_cycle; }
struct edge_t
{
edge_t()
: m_current(InitEdge ^ 1)
, m_previous(InitEdge)
, m_rising(0)
, m_falling(0)
, m_changed(0)
{
set(InitEdge);
}
void tick(bool toggle)
{
u8 current = m_current;
if (toggle)
current ^= 1;
set(current);
}
void set(u8 edge)
{
edge &= 1;
m_rising = m_falling = m_changed = 0;
if (m_current != edge)
{
m_changed = 1;
if (m_current && (!edge))
m_falling = 1;
else if ((!m_current) && edge)
m_rising = 1;
m_current = edge;
}
m_previous = m_current;
}
void reset()
{
m_previous = InitEdge;
m_current = InitEdge ^ 1;
set(InitEdge);
}
u8 m_current : 1; // current edge
u8 m_previous : 1; // previous edge
u8 m_rising : 1; // rising edge
u8 m_falling : 1; // falling edge
u8 m_changed : 1; // changed flag
};
edge_t m_edge;
T m_width = InitWidth; // clock pulse width
T m_width_latch = InitWidth; // clock pulse width latch
T m_counter = InitWidth; // clock counter
T m_cycle = 0; // clock cycle
};
#endif

View file

@ -1,116 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: tildearrow
Dialogic ADPCM core
*/
#include "util.hpp"
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_CORE_VOX_HPP
#define _VGSOUND_EMU_CORE_VOX_HPP
#pragma once
#define MODIFIED_CLAMP(x,xMin,xMax) (std::min(std::max((x),(xMin)),(xMax)))
class vox_core
{
protected:
struct vox_decoder_t
{
vox_decoder_t(vox_core &vox)
: m_curr(vox)
, m_loop(vox)
{ };
virtual void reset()
{
m_curr.reset();
m_loop.reset();
m_loop_saved = false;
}
void save()
{
if (!m_loop_saved)
{
m_loop.copy(m_curr);
m_loop_saved = true;
}
}
void restore()
{
if (m_loop_saved)
m_curr.copy(m_loop);
}
s32 out() { return m_curr.m_step; }
struct decoder_state_t
{
decoder_state_t(vox_core &vox)
: m_vox(vox)
{ };
void reset()
{
m_index = 0;
m_step = 16;
}
void copy(decoder_state_t src)
{
m_index = src.m_index;
m_step = src.m_step;
}
void decode(u8 nibble)
{
const u8 delta = bitfield(nibble, 0, 3);
s16 ss = m_vox.m_step_table[m_index]; // ss(n)
// d(n) = (ss(n) * B2) + ((ss(n) / 2) * B1) + ((ss(n) / 4) * B0) + (ss(n) / 8)
s16 d = ss >> 3;
if (bitfield(delta, 2))
d += ss;
if (bitfield(delta, 1))
d += (ss >> 1);
if (bitfield(delta, 0))
d += (ss >> 2);
// if (B3 = 1) then d(n) = d(n) * (-1) X(n) = X(n-1) * d(n)
if (bitfield(nibble, 3))
m_step = std::max(m_step - d, -2048);
else
m_step = std::min(m_step + d, 2047);
// adjust step index
m_index = MODIFIED_CLAMP(m_index + m_vox.m_index_table[delta], 0, 48);
}
vox_core &m_vox;
s8 m_index = 0;
s32 m_step = 16;
};
decoder_state_t m_curr;
decoder_state_t m_loop;
bool m_loop_saved = false;
};
s8 m_index_table[8] = {-1, -1, -1, -1, 2, 4, 6, 8};
s32 m_step_table[49] = {
16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143,
157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449,
494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552
};
};
#endif

View file

@ -1,617 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst
Modifiers and Contributors for Furnace: Natt Akuma, tildearrow, Grauw
Konami SCC emulation core
Konami SCC means "Sound Creative Chip", it's actually MSX MegaROM/RAM Mapper with 5 channel Wavetable sound generator.
It was first appeared at 1987, F-1 Spirit and Nemesis 2/Gradius 2 for MSX. then several MSX cartridges used that until 1990, Metal Gear 2: Solid Snake.
Even after MSX is discontinued, it was still used at some low-end arcade and amusement hardwares.
and some Third-party MSX utilities still support this due to its market shares.
There's 2 SCC types:
K051649 (or simply known as SCC)
This chip is used for MSX MegaROM Mapper, some arcade machines.
Channel 4 and 5 must be share waveform, other channels has its own waveforms.
K052539 (also known as SCC+)
This chip is used for MSX MegaRAM Mapper (Konami Sound Cartridges for Snatcher/SD Snatcher).
All channels can be has its own waveforms, and also has backward compatibility mode with K051649.
Based on:
https://www.msx.org/wiki/MegaROM_Mappers
https://www.msx.org/wiki/Konami_051649
https://www.msx.org/wiki/Konami_052539
http://bifi.msxnet.org/msxnet/tech/scc
http://bifi.msxnet.org/msxnet/tech/soundcartridge
K051649 Register Layout
--------------------------------------------------------------------
4000-bfff MegaROM Mapper
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
4000-5fff xxxx xxxx R Bank page 0
c000-dfff mirror of 4000-5fff
6000-7fff xxxx xxxx R Bank page 1
e000-ffff mirror of 6000-7fff
8000-9fff xxxx xxxx R Bank page 2
0000-1fff mirror of 8000-9fff
a000-bfff xxxx xxxx R Bank page 3
2000-3fff mirror of a000-bfff
--------------------------------------------------------------------
5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
5000 --xx xxxx W Bank select, Page 0
5001-57ff Mirror of 5000
7000 --xx xxxx W Bank select, Page 1
7001-77ff Mirror of 7000
9000 --xx xxxx W Bank select, Page 2
--11 1111 W SCC Enable
9001-97ff Mirror of 9000
b000 --xx xxxx W Bank select, Page 3
b001-b7ff Mirror of b000
--------------------------------------------------------------------
9800-9fff SCC register
--------------------------------------------------------------------
9800-987f Waveform
Address Bit R/W Description
7654 3210
9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed)
9820-983f xxxx xxxx R/W Channel 1 ""
9840-985f xxxx xxxx R/W Channel 2 ""
9860-987f xxxx xxxx R/W Channel 3/4 ""
9880-9889 Pitch
9880 xxxx xxxx W Channel 0 Pitch LSB
9881 ---- xxxx W Channel 0 Pitch MSB
9882 xxxx xxxx W Channel 1 Pitch LSB
9883 ---- xxxx W Channel 1 Pitch MSB
9884 xxxx xxxx W Channel 2 Pitch LSB
9885 ---- xxxx W Channel 2 Pitch MSB
9886 xxxx xxxx W Channel 3 Pitch LSB
9887 ---- xxxx W Channel 3 Pitch MSB
9888 xxxx xxxx W Channel 4 Pitch LSB
9889 ---- xxxx W Channel 4 Pitch MSB
9888-988e Volume
988a ---- xxxx W Channel 0 Volume
988b ---- xxxx W Channel 1 Volume
988c ---- xxxx W Channel 2 Volume
988d ---- xxxx W Channel 3 Volume
988e ---- xxxx W Channel 4 Volume
988f ---x ---- W Channel 4 Output enable/disable flag
---- x--- W Channel 3 Output enable/disable flag
---- -x-- W Channel 2 Output enable/disable flag
---- --x- W Channel 1 Output enable/disable flag
---- ---x W Channel 0 Output enable/disable flag
9890-989f Mirror of 9880-988f
98a0-98bf xxxx xxxx R Channel 4 Waveform
98e0 x--- ---- W Waveform rotate flag for channel 4
-x-- ---- W Waveform rotate flag for all channels
--x- ---- W Reset waveform position after pitch writes
---- --x- W 8 bit frequency
---- --0x W 4 bit frequency
98e1-98ff Mirror of 98e0
9900-9fff Mirror of 9800-98ff
--------------------------------------------------------------------
K052539 Register Layout
--------------------------------------------------------------------
4000-bfff MegaRAM Mapper
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
4000-5fff xxxx xxxx R/W Bank page 0
c000-dfff xxxx xxxx R/W ""
6000-7fff xxxx xxxx R/W Bank page 1
e000-ffff xxxx xxxx R/W ""
8000-9fff xxxx xxxx R/W Bank page 2
0000-1fff xxxx xxxx R/W ""
a000-bfff xxxx xxxx R/W Bank page 3
2000-3fff xxxx xxxx R/W ""
--------------------------------------------------------------------
5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
5000 xxxx xxxx W Bank select, Page 0
5001-57ff Mirror of 5000
7000 xxxx xxxx W Bank select, Page 1
7001-77ff Mirror of 7000
9000 xxxx xxxx W Bank select, Page 2
--11 1111 W SCC Enable (SCC Compatible mode)
9001-97ff Mirror of 9000
b000 xxxx xxxx W Bank select, Page 3
1--- ---- W SCC+ Enable (SCC+ mode)
b001-b7ff Mirror of b000
--------------------------------------------------------------------
bffe-bfff Mapper configuration
--------------------------------------------------------------------
Address Bit R/W Description
7654 3210
bffe --x- ---- W SCC operation mode
--0- ---- W SCC Compatible mode
--1- ---- W SCC+ mode
---x ---- W RAM write/Bank select toggle for all Bank pages
---0 ---- W Bank select enable
---1 ---- W RAM write enable
---0 -x-- W RAM write/Bank select toggle for Bank page 2
---0 --x- W RAM write/Bank select toggle for Bank page 1
---0 ---x W RAM write/Bank select toggle for Bank page 0
bfff Mirror of bffe
--------------------------------------------------------------------
9800-9fff SCC Compatible mode register
--------------------------------------------------------------------
9800-987f Waveform
Address Bit R/W Description
7654 3210
9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed)
9820-983f xxxx xxxx R/W Channel 1 ""
9840-985f xxxx xxxx R/W Channel 2 ""
9860-987f xxxx xxxx R/W Channel 3/4 ""
9880-9889 Pitch
9880 xxxx xxxx W Channel 0 Pitch LSB
9881 ---- xxxx W Channel 0 Pitch MSB
9882 xxxx xxxx W Channel 1 Pitch LSB
9883 ---- xxxx W Channel 1 Pitch MSB
9884 xxxx xxxx W Channel 2 Pitch LSB
9885 ---- xxxx W Channel 2 Pitch MSB
9886 xxxx xxxx W Channel 3 Pitch LSB
9887 ---- xxxx W Channel 3 Pitch MSB
9888 xxxx xxxx W Channel 4 Pitch LSB
9889 ---- xxxx W Channel 4 Pitch MSB
9888-988e Volume
988a ---- xxxx W Channel 0 Volume
988b ---- xxxx W Channel 1 Volume
988c ---- xxxx W Channel 2 Volume
988d ---- xxxx W Channel 3 Volume
988e ---- xxxx W Channel 4 Volume
988f ---x ---- W Channel 4 Output enable/disable flag
---- x--- W Channel 3 Output enable/disable flag
---- -x-- W Channel 2 Output enable/disable flag
---- --x- W Channel 1 Output enable/disable flag
---- ---x W Channel 0 Output enable/disable flag
9890-989f Mirror of 9880-988f
98a0-98bf xxxx xxxx R Channel 4 Waveform
98c0 -x-- ---- W Waveform rotate flag for all channels
--x- ---- W Reset waveform position after pitch writes
---- --x- W 8 bit frequency
---- --0x W 4 bit frequency
98c1-98df Mirror of 98c0
9900-9fff Mirror of 9800-98ff
--------------------------------------------------------------------
b800-bfff SCC+ mode register
--------------------------------------------------------------------
b800-b89f Waveform
Address Bit R/W Description
7654 3210
b800-b81f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed)
b820-b83f xxxx xxxx R/W Channel 1 ""
b840-b85f xxxx xxxx R/W Channel 2 ""
b860-b87f xxxx xxxx R/W Channel 3 ""
b880-b89f xxxx xxxx R/W Channel 3 ""
b8a0-b8a9 Pitch
b8a0 xxxx xxxx W Channel 0 Pitch LSB
b8a1 ---- xxxx W Channel 0 Pitch MSB
b8a2 xxxx xxxx W Channel 1 Pitch LSB
b8a3 ---- xxxx W Channel 1 Pitch MSB
b8a4 xxxx xxxx W Channel 2 Pitch LSB
b8a5 ---- xxxx W Channel 2 Pitch MSB
b8a6 xxxx xxxx W Channel 3 Pitch LSB
b8a7 ---- xxxx W Channel 3 Pitch MSB
b8a8 xxxx xxxx W Channel 4 Pitch LSB
b8a9 ---- xxxx W Channel 4 Pitch MSB
b8a8-b8ae Volume
b8aa ---- xxxx W Channel 0 Volume
b8ab ---- xxxx W Channel 1 Volume
b8ac ---- xxxx W Channel 2 Volume
b8ad ---- xxxx W Channel 3 Volume
b8ae ---- xxxx W Channel 4 Volume
b8af ---x ---- W Channel 4 Output enable/disable flag
---- x--- W Channel 3 Output enable/disable flag
---- -x-- W Channel 2 Output enable/disable flag
---- --x- W Channel 1 Output enable/disable flag
---- ---x W Channel 0 Output enable/disable flag
b8b0-b8bf Mirror of b8a0-b8af
b8c0 -x-- ---- W Waveform rotate flag for all channels
--x- ---- W Reset waveform position after pitch writes
---- --x- W 8 bit frequency
---- --0x W 4 bit frequency
b8c1-b8df Mirror of b8c0
b900-bfff Mirror of b800-b8ff
--------------------------------------------------------------------
SCC Frequency calculation:
if 8 bit frequency then
Frequency = Input clock / ((bit 0 to 7 of Pitch input) + 1)
else if 4 bit frequency then
Frequency = Input clock / ((bit 8 to 11 of Pitch input) + 1)
else
Frequency = Input clock / (Pitch input + 1)
*/
#include "scc.hpp"
#include <string.h>
// shared SCC features
void scc_core::tick()
{
m_out = 0;
for (auto & elem : m_voice)
{
elem.tick();
m_out += elem.out;
}
}
void scc_core::voice_t::tick()
{
if (pitch >= 9) // or voice is halted
{
// update counter - Post decrement
u16 temp = counter;
if (m_host.m_test.freq_4bit) // 4 bit frequency mode
{
counter = (counter & ~0x0ff) | (bitfield(bitfield(counter, 0, 8) - 1, 0, 8) << 0);
counter = (counter & ~0xf00) | (bitfield(bitfield(counter, 8, 4) - 1, 0, 4) << 8);
}
else
counter = bitfield(counter - 1, 0, 12);
// handle counter carry
bool carry = m_host.m_test.freq_8bit ? (bitfield(temp, 0, 8) == 0) :
(m_host.m_test.freq_4bit ? (bitfield(temp, 8, 4) == 0) :
(bitfield(temp, 0, 12) == 0));
if (carry)
{
addr = bitfield(addr + 1, 0, 5);
counter = pitch;
}
}
// get output
if (enable)
out = (wave[addr] * volume) >> 4; // scale to 11 bit digital output
else
out = 0;
}
void scc_core::reset()
{
for (auto & elem : m_voice)
elem.reset();
m_test.reset();
m_out = 0;
memset(m_reg,0,sizeof(m_reg));
}
void scc_core::voice_t::reset()
{
memset(wave,0,sizeof(wave));
enable = false;
pitch = 0;
volume = 0;
addr = 0;
counter = 0;
out = 0;
}
// SCC accessors
u8 scc_core::wave_r(bool is_sccplus, u8 address)
{
u8 ret = 0xff;
const u8 voice = bitfield(address, 5, 3);
if (voice > 4)
return ret;
u8 wave_addr = bitfield(address, 0, 5);
if (m_test.rotate) // rotate flag
wave_addr = bitfield(wave_addr + m_voice[voice].addr, 0, 5);
if (!is_sccplus)
{
if (voice == 3) // rotate voice 3~4 flag
{
if (m_test.rotate4 || m_test.rotate) // rotate flag
wave_addr = bitfield(bitfield(address, 0, 5) + m_voice[3 + m_test.rotate].addr, 0, 5);
}
}
ret = m_voice[voice].wave[wave_addr];
return ret;
}
void scc_core::wave_w(bool is_sccplus, u8 address, u8 data)
{
if (m_test.rotate) // write protected
return;
const u8 voice = bitfield(address, 5, 3);
if (voice > 4)
return;
const u8 wave_addr = bitfield(address, 0, 5);
if (!is_sccplus)
{
if (((voice >= 3) && m_test.rotate4) || (voice >= 4)) // Ignore if write protected, or voice 4
return;
if (voice >= 3) // voice 3, 4 shares waveform
{
m_voice[3].wave[wave_addr] = data;
m_voice[4].wave[wave_addr] = data;
}
else
m_voice[voice].wave[wave_addr] = data;
}
else
m_voice[voice].wave[wave_addr] = data;
}
void scc_core::freq_vol_enable_w(u8 address, u8 data)
{
const u8 voice_freq = bitfield(address, 1, 3);
const u8 voice_reg = bitfield(address, 0, 4);
// *0-*f Pitch, Volume, Enable
switch (voice_reg)
{
case 0x0: // 0x*0 Voice 0 Pitch LSB
case 0x2: // 0x*2 Voice 1 Pitch LSB
case 0x4: // 0x*4 Voice 2 Pitch LSB
case 0x6: // 0x*6 Voice 3 Pitch LSB
case 0x8: // 0x*8 Voice 4 Pitch LSB
if (m_test.resetpos) // Reset address
m_voice[voice_freq].addr = 0;
m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0x0ff) | data;
m_voice[voice_freq].counter = m_voice[voice_freq].pitch;
break;
case 0x1: // 0x*1 Voice 0 Pitch MSB
case 0x3: // 0x*3 Voice 1 Pitch MSB
case 0x5: // 0x*5 Voice 2 Pitch MSB
case 0x7: // 0x*7 Voice 3 Pitch MSB
case 0x9: // 0x*9 Voice 4 Pitch MSB
if (m_test.resetpos) // Reset address
m_voice[voice_freq].addr = 0;
m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0xf00) | (u16(bitfield(data, 0, 4)) << 8);
m_voice[voice_freq].counter = m_voice[voice_freq].pitch;
break;
case 0xa: // 0x*a Voice 0 Volume
case 0xb: // 0x*b Voice 1 Volume
case 0xc: // 0x*c Voice 2 Volume
case 0xd: // 0x*d Voice 3 Volume
case 0xe: // 0x*e Voice 4 Volume
m_voice[voice_reg - 0xa].volume = bitfield(data, 0, 4);
break;
case 0xf: // 0x*f Enable/Disable flag
m_voice[0].enable = bitfield(data, 0);
m_voice[1].enable = bitfield(data, 1);
m_voice[2].enable = bitfield(data, 2);
m_voice[3].enable = bitfield(data, 3);
m_voice[4].enable = bitfield(data, 4);
break;
}
}
void k051649_scc_core::scc_w(bool is_sccplus, u8 address, u8 data)
{
const u8 voice = bitfield(address, 5, 3);
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3/4 Waveform
wave_w(false, address, data);
break;
case 0b100: // 0x80-0x9f Pitch, Volume, Enable
freq_vol_enable_w(address, data);
break;
case 0b111: // 0xe0-0xff Test register
m_test.freq_4bit = bitfield(data, 0);
m_test.freq_8bit = bitfield(data, 1);
m_test.resetpos = bitfield(data, 5);
m_test.rotate = bitfield(data, 6);
m_test.rotate4 = bitfield(data, 7);
break;
}
m_reg[address] = data;
}
void k052539_scc_core::scc_w(bool is_sccplus, u8 address, u8 data)
{
const u8 voice = bitfield(address, 5, 3);
if (is_sccplus)
{
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3 Waveform
case 0b100: // 0x80-0x9f Voice 4 Waveform
wave_w(true, address, data);
break;
case 0b101: // 0xa0-0xbf Pitch, Volume, Enable
freq_vol_enable_w(address, data);
break;
case 0b110: // 0xc0-0xdf Test register
m_test.freq_4bit = bitfield(data, 0);
m_test.freq_8bit = bitfield(data, 1);
m_test.resetpos = bitfield(data, 5);
m_test.rotate = bitfield(data, 6);
break;
default:
break;
}
}
else
{
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3/4 Waveform
wave_w(false, address, data);
break;
case 0b100: // 0x80-0x9f Pitch, Volume, Enable
freq_vol_enable_w(address, data);
break;
case 0b110: // 0xc0-0xdf Test register
m_test.freq_4bit = bitfield(data, 0);
m_test.freq_8bit = bitfield(data, 1);
m_test.resetpos = bitfield(data, 5);
m_test.rotate = bitfield(data, 6);
break;
default:
break;
}
}
m_reg[address] = data;
}
u8 k051649_scc_core::scc_r(bool is_sccplus, u8 address)
{
const u8 voice = bitfield(address, 5, 3);
const u8 wave = bitfield(address, 0, 5);
u8 ret = 0xff;
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3 Waveform
case 0b101: // 0xa0-0xbf Voice 4 Waveform
ret = wave_r(false, (std::min<u8>(4, voice) << 5) | wave);
break;
}
return ret;
}
u8 k052539_scc_core::scc_r(bool is_sccplus, u8 address)
{
const u8 voice = bitfield(address, 5, 3);
const u8 wave = bitfield(address, 0, 5);
u8 ret = 0xff;
if (is_sccplus)
{
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3 Waveform
case 0b100: // 0x80-0x9f Voice 4 Waveform
ret = wave_r(true, address);
break;
}
}
else
{
switch (voice)
{
case 0b000: // 0x00-0x1f Voice 0 Waveform
case 0b001: // 0x20-0x3f Voice 1 Waveform
case 0b010: // 0x40-0x5f Voice 2 Waveform
case 0b011: // 0x60-0x7f Voice 3 Waveform
case 0b101: // 0xa0-0xbf Voice 4 Waveform
ret = wave_r(false, (std::min<u8>(4, voice) << 5) | wave);
break;
}
}
return ret;
}

View file

@ -1,139 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst
Modifiers and Contributors for Furnace: Natt Akuma, tildearrow, Grauw
Konami SCC emulation core
See scc.cpp for more info.
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_SCC_HPP
#define _VGSOUND_EMU_SCC_HPP
#pragma once
namespace scc
{
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned int u32;
typedef signed int s32;
// get bitfield, bitfield(input, position, len)
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
}
using namespace scc;
// shared for SCCs
class scc_core
{
public:
// constructor
scc_core()
: m_voice{*this,*this,*this,*this,*this}
{};
virtual ~scc_core(){};
// accessors
virtual u8 scc_r(bool is_sccplus, u8 address) = 0;
virtual void scc_w(bool is_sccplus, u8 address, u8 data) = 0;
// internal state
virtual void reset();
void tick();
// getters
s32 out() { return m_out; } // output to DA0...DA10 pin
s32 chan_out(u8 ch) { return m_voice[ch].out; }
u8 reg(u8 address) { return m_reg[address]; }
protected:
// voice structs
struct voice_t
{
// constructor
voice_t(scc_core &host) : m_host(host) {};
// internal state
void reset();
void tick();
// registers
scc_core &m_host;
s8 wave[32] = {0}; // internal waveform
bool enable = false; // output enable flag
u16 pitch = 0; // pitch
u8 volume = 0; // volume
u8 addr = 0; // waveform pointer
u16 counter = 0; // frequency counter
s32 out = 0; // current output
};
voice_t m_voice[5]; // 5 voices
// accessor
u8 wave_r(bool is_sccplus, u8 address);
void wave_w(bool is_sccplus, u8 address, u8 data);
void freq_vol_enable_w(u8 address, u8 data);
struct test_t
{
// constructor
test_t()
: freq_4bit(0)
, freq_8bit(0)
, resetpos(0)
, rotate(0)
, rotate4(0)
{ };
void reset()
{
freq_4bit = 0;
freq_8bit = 0;
resetpos = 0;
rotate = 0;
rotate4 = 0;
}
u8 freq_4bit : 1; // 4 bit frequency
u8 freq_8bit : 1; // 8 bit frequency
u8 resetpos : 1; // reset counter after pitch writes
u8 rotate : 1; // rotate and write protect waveform for all channels
u8 rotate4 : 1; // same as above but for channel 4 only
};
test_t m_test; // test register
s32 m_out = 0; // output to DA0...10
u8 m_reg[256] = {0}; // register pool
};
// SCC core
class k051649_scc_core : public scc_core
{
public:
// accessors
virtual u8 scc_r(bool is_sccplus, u8 address) override;
virtual void scc_w(bool is_sccplus, u8 address, u8 data) override;
};
class k052539_scc_core : public k051649_scc_core
{
public:
// accessors
virtual u8 scc_r(bool is_sccplus, u8 address) override;
virtual void scc_w(bool is_sccplus, u8 address, u8 data) override;
};
#endif

View file

@ -1,324 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow
Konami VRC VI sound emulation core
It's one of NES mapper with built-in sound chip, and also one of 2 Konami VRCs with this feature. (rest one has OPLL derivatives.)
It's also DACless like other sound chip and mapper-with-sound manufactured by konami,
the Chips 6 bit digital sound output is needs converted to analog sound output when you it want to make some sounds, or send to sound mixer.
Its are used for Akumajou Densetsu (Japan release of Castlevania III), Madara, Esper Dream 2.
The chip is installed in 351951 PCB and 351949A PCB.
351951 PCB is used exclusivly for Akumajou Densetsu, Small board has VRC VI, PRG and CHR ROM.
- It's configuration also calls VRC6a, iNES mapper 024.
351949A PCB is for Last 2 titles with VRC VI, Bigger board has VRC VI, PRG and CHR ROM, and Battery Backed 8K x 8 bit SRAM.
- Additionally, It's PRG A0 and A1 bit to VRC VI input is swapped, compare to above.
- It's configuration also calls VRC6b, iNES mapper 026.
The chip itself has 053328, 053329, 053330 Revision, but Its difference between revision is unknown.
Like other mappers for NES, It has internal timer - Its timer can be sync with scanline like other Konami mapper in this era.
Register layout (Sound and Timer only; 351951 PCB case, 351949A swaps xxx1 and xxx2):
Address Bits Description
7654 3210
9000-9002 Pulse 1
9000 x--- ---- Pulse 1 Duty ignore
-xxx ---- Pulse 1 Duty cycle
---- xxxx Pulse 1 Volume
9001 xxxx xxxx Pulse 1 Pitch bit 0-7
9002 x--- ---- Pulse 1 Enable
---- xxxx Pulse 1 Pitch bit 8-11
9003 Sound control
9003 ---- -x-- 4 bit Frequency mode
---- -0x- 8 bit Frequency mode
---- ---x Halt
a000-a002 Pulse 2
a000 x--- ---- Pulse 2 Duty ignore
-xxx ---- Pulse 2 Duty cycle
---- xxxx Pulse 2 Volume
a001 xxxx xxxx Pulse 2 Pitch bit 0-7
a002 x--- ---- Pulse 2 Enable
---- xxxx Pulse 2 Pitch bit 8-11
b000-b002 Sawtooth
b000 --xx xxxx Sawtooth Accumulate Rate
b001 xxxx xxxx Sawtooth Pitch bit 0-7
b002 x--- ---- Sawtooth Enable
---- xxxx Sawtooth Pitch bit 8-11
f000-f002 IRQ Timer
f000 xxxx xxxx IRQ Timer latch
f001 ---- -0-- Sync with scanline
---- --x- Enable timer
---- ---x Enable timer after IRQ Acknowledge
f002 ---- ---- IRQ Acknowledge
Frequency calculations:
if 4 bit Frequency Mode then
Frequency: Input clock / (bit 8 to 11 of Pitch + 1)
end else if 8 bit Frequency Mode then
Frequency: Input clock / (bit 4 to 11 of Pitch + 1)
end else then
Frequency: Input clock / (Pitch + 1)
end
*/
#include "vrcvi.hpp"
#include <string.h>
void vrcvi_core::tick()
{
m_out = 0;
if (!m_control.m_halt) // Halt flag
{
// tick per each clock
int elemIndex=0;
for (auto & elem : m_pulse)
{
if (elem.tick()) {
m_out += elem.m_control.m_volume; // add 4 bit pulse output
m_ch_out[elemIndex]=elem.m_control.m_volume;
} else {
m_ch_out[elemIndex]=0;
}
elemIndex++;
}
if (m_sawtooth.tick()) {
m_out += bitfield(m_sawtooth.m_accum, 3, 5); // add 5 bit sawtooth output
m_ch_out[2]=bitfield(m_sawtooth.m_accum, 3, 5);
} else {
m_ch_out[2]=0;
}
}
if (m_timer.tick())
m_timer.counter_tick();
}
void vrcvi_core::reset()
{
for (auto & elem : m_pulse)
elem.reset();
m_sawtooth.reset();
m_timer.reset();
m_control.reset();
m_out = 0;
memset(m_ch_out,0,sizeof(m_ch_out));
}
bool vrcvi_core::alu_t::tick()
{
if (m_divider.m_enable)
{
const u16 temp = m_counter;
// post decrement
if (bitfield(m_host.m_control.m_shift, 1))
{
m_counter = (m_counter & 0x0ff) | (bitfield(bitfield(m_counter, 8, 4) - 1, 0, 4) << 8);
m_counter = (m_counter & 0xf00) | (bitfield(bitfield(m_counter, 0, 8) - 1, 0, 8) << 0);
}
else if (bitfield(m_host.m_control.m_shift, 0))
{
m_counter = (m_counter & 0x00f) | (bitfield(bitfield(m_counter, 4, 8) - 1, 0, 8) << 4);
m_counter = (m_counter & 0xff0) | (bitfield(bitfield(m_counter, 0, 4) - 1, 0, 4) << 0);
}
else
m_counter = bitfield(bitfield(m_counter, 0, 12) - 1, 0, 12);
// carry handling
bool carry = bitfield(m_host.m_control.m_shift, 1) ? (bitfield(temp, 8, 4) == 0) :
(bitfield(m_host.m_control.m_shift, 0) ? (bitfield(temp, 4, 8) == 0) :
(bitfield(temp, 0, 12) == 0));
if (carry)
m_counter = m_divider.m_divider;
return carry;
}
return false;
}
bool vrcvi_core::pulse_t::tick()
{
if (!m_divider.m_enable)
return false;
if (vrcvi_core::alu_t::tick())
m_cycle = bitfield(m_cycle + 1, 0, 4);
return m_control.m_mode ? true : ((m_cycle > m_control.m_duty) ? true : false);
}
bool vrcvi_core::sawtooth_t::tick()
{
if (!m_divider.m_enable)
return false;
if (vrcvi_core::alu_t::tick())
{
if (bitfield(m_cycle++, 0)) // Even step only
m_accum += m_rate;
if (m_cycle >= 14) // Reset accumulator at every 14 cycles
{
m_accum = 0;
m_cycle = 0;
}
}
return (m_accum == 0) ? false : true;
}
void vrcvi_core::alu_t::reset()
{
m_divider.reset();
m_counter = 0;
m_cycle = 0;
}
void vrcvi_core::pulse_t::reset()
{
vrcvi_core::alu_t::reset();
m_control.reset();
}
void vrcvi_core::sawtooth_t::reset()
{
vrcvi_core::alu_t::reset();
m_rate = 0;
m_accum = 0;
}
bool vrcvi_core::timer_t::tick()
{
if (m_timer_control.m_enable)
{
if (!m_timer_control.m_sync) // scanline sync mode
{
m_prescaler -= 3;
if (m_prescaler <= 0)
{
m_prescaler += 341;
return true;
}
}
}
return (m_timer_control.m_enable && m_timer_control.m_sync) ? true : false;
}
void vrcvi_core::timer_t::counter_tick()
{
if (bitfield(++m_counter, 0, 8) == 0)
{
m_counter = m_counter_latch;
irq_set();
}
}
void vrcvi_core::timer_t::reset()
{
m_timer_control.reset();
m_prescaler = 341;
m_counter = m_counter_latch = 0;
irq_clear();
}
// Accessors
void vrcvi_core::alu_t::divider_t::write(bool msb, u8 data)
{
if (msb)
{
m_divider = (m_divider & ~0xf00) | (bitfield<u32>(data, 0, 4) << 8);
m_enable = bitfield(data, 7);
}
else
m_divider = (m_divider & ~0x0ff) | data;
}
void vrcvi_core::pulse_w(u8 voice, u8 address, u8 data)
{
pulse_t &v = m_pulse[voice];
switch (address)
{
case 0x00: // Control - 0x9000 (Pulse 1), 0xa000 (Pulse 2)
v.m_control.m_mode = bitfield(data, 7);
v.m_control.m_duty = bitfield(data, 4, 3);
v.m_control.m_volume = bitfield(data, 0, 4);
break;
case 0x01: // Pitch LSB - 0x9001/0x9002 (Pulse 1), 0xa001/0xa002 (Pulse 2)
v.m_divider.write(false, data);
break;
case 0x02: // Pitch MSB, Enable/Disable - 0x9002/0x9001 (Pulse 1), 0xa002/0xa001 (Pulse 2)
v.m_divider.write(true, data);
if (!v.m_divider.m_enable) // Reset duty cycle
v.m_cycle = 0;
break;
}
}
void vrcvi_core::saw_w(u8 address, u8 data)
{
switch (address)
{
case 0x00: // Sawtooth Accumulate - 0xb000
m_sawtooth.m_rate = bitfield(data, 0, 6);
break;
case 0x01: // Pitch LSB - 0xb001/0xb002 (Sawtooth)
m_sawtooth.m_divider.write(false, data);
break;
case 0x02: // Pitch MSB, Enable/Disable - 0xb002/0xb001 (Sawtooth)
m_sawtooth.m_divider.write(true, data);
if (!m_sawtooth.m_divider.m_enable) // Reset accumulator
m_sawtooth.m_accum = 0;
break;
}
}
void vrcvi_core::timer_w(u8 address, u8 data)
{
switch (address)
{
case 0x00: // Timer latch - 0xf000
m_timer.m_counter_latch = data;
break;
case 0x01: // Timer control - 0xf001/0xf002
m_timer.m_timer_control.m_sync = bitfield(data, 2);
m_timer.m_timer_control.m_enable = bitfield(data, 1);
m_timer.m_timer_control.m_enable_ack = bitfield(data, 0);
if (m_timer.m_timer_control.m_enable)
{
m_timer.m_counter = m_timer.m_counter_latch;
m_timer.m_prescaler = 341;
}
m_timer.irq_clear();
break;
case 0x02: // IRQ Acknowledge - 0xf002/0xf001
m_timer.irq_clear();
m_timer.m_timer_control.m_enable = m_timer.m_timer_control.m_enable_ack;
break;
}
}
void vrcvi_core::control_w(u8 data)
{
// Global control - 0x9003
m_control.m_halt = bitfield(data, 0);
m_control.m_shift = bitfield(data, 1, 2);
}

View file

@ -1,242 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow
Konami VRC VI sound emulation core
See vrcvi.cpp to more infos.
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_VRCVI_HPP
#define _VGSOUND_EMU_VRCVI_HPP
#pragma once
namespace vrcvi
{
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef signed char s8;
typedef signed short s16;
// get bitfield, bitfield(input, position, len)
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
};
class vrcvi_intf
{
public:
virtual void irq_w(bool irq) { }
};
using namespace vrcvi;
class vrcvi_core
{
public:
friend class vrcvi_intf;
// constructor
vrcvi_core(vrcvi_intf &intf)
: m_pulse{*this,*this}
, m_sawtooth(*this)
, m_timer(*this)
, m_intf(intf)
{
}
// accessors, getters, setters
void pulse_w(u8 voice, u8 address, u8 data);
void saw_w(u8 address, u8 data);
void timer_w(u8 address, u8 data);
void control_w(u8 data);
// internal state
void reset();
void tick();
// 6 bit output
s8 out() { return m_out; }
// channel output
s16 chan_out(u8 ch) { return m_ch_out[ch]; }
private:
// Common ALU for sound channels
struct alu_t
{
alu_t(vrcvi_core &host)
: m_host(host)
{ };
virtual void reset();
virtual bool tick();
struct divider_t
{
divider_t()
: m_divider(0)
, m_enable(0)
{ };
void reset()
{
m_divider = 0;
m_enable = 0;
}
void write(bool msb, u8 data);
u16 m_divider : 12; // divider (pitch)
u16 m_enable : 1; // channel enable flag
};
vrcvi_core &m_host;
divider_t m_divider;
u16 m_counter = 0; // clock counter
u8 m_cycle = 0; // clock cycle
};
// 2 Pulse channels
struct pulse_t : alu_t
{
pulse_t(vrcvi_core &host)
: alu_t(host)
{ };
virtual void reset() override;
virtual bool tick() override;
// Control bits
struct pulse_control_t
{
pulse_control_t()
: m_mode(0)
, m_duty(0)
, m_volume(0)
{ };
void reset()
{
m_mode = 0;
m_duty = 0;
m_volume = 0;
}
u8 m_mode : 1; // duty toggle flag
u8 m_duty : 3; // 3 bit duty cycle
u8 m_volume : 4; // 4 bit volume
};
pulse_control_t m_control;
};
// 1 Sawtooth channel
struct sawtooth_t : alu_t
{
sawtooth_t(vrcvi_core &host)
: alu_t(host)
{ };
virtual void reset() override;
virtual bool tick() override;
u8 m_rate = 0; // sawtooth accumulate rate
u8 m_accum = 0; // sawtooth accumulator, high 5 bit is accumulated to output
};
// Internal timer
struct timer_t
{
timer_t(vrcvi_core &host)
: m_host(host)
{ };
void reset();
bool tick();
void counter_tick();
// IRQ update
void update() { m_host.m_intf.irq_w(m_timer_control.m_irq_trigger); }
void irq_set()
{
if (!m_timer_control.m_irq_trigger)
{
m_timer_control.m_irq_trigger = 1;
update();
}
}
void irq_clear()
{
if (m_timer_control.m_irq_trigger)
{
m_timer_control.m_irq_trigger = 0;
update();
}
}
// Control bits
struct timer_control_t
{
timer_control_t()
: m_irq_trigger(0)
, m_enable_ack(0)
, m_enable(0)
, m_sync(0)
{ };
void reset()
{
m_irq_trigger = 0;
m_enable_ack = 0;
m_enable = 0;
m_sync = 0;
}
u8 m_irq_trigger : 1;
u8 m_enable_ack : 1;
u8 m_enable : 1;
u8 m_sync : 1;
};
vrcvi_core &m_host; // host core
timer_control_t m_timer_control; // timer control bits
s16 m_prescaler = 341; // prescaler
u8 m_counter = 0; // clock counter
u8 m_counter_latch = 0; // clock counter latch
};
struct global_control_t
{
global_control_t()
: m_halt(0)
, m_shift(0)
{ };
void reset()
{
m_halt = 0;
m_shift = 0;
}
u8 m_halt : 1; // halt sound
u8 m_shift : 2; // 4/8 bit right shift
};
pulse_t m_pulse[2]; // 2 pulse channels
sawtooth_t m_sawtooth; // sawtooth channel
timer_t m_timer; // internal timer
global_control_t m_control; // control
vrcvi_intf &m_intf;
s8 m_out = 0; // 6 bit output
s8 m_ch_out[3] = {0}; // per-channel output
};
#endif

View file

@ -1,225 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow
Seta/Allumer X1-010 Emulation core
the chip has 16 voices, all voices can be switchable to Wavetable or PCM sample playback mode.
It has also 2 output channels, but no known hardware using this feature for stereo sound.
Wavetable needs to paired with envelope, it's always enabled and similar as AY PSG's one
but its shape is stored at RAM.
PCM volume is stored by each register.
Both volume is 4bit per output.
Everything except PCM sample is stored at paired 8 bit RAM.
RAM layout (common case: Address bit 12 is swapped when RAM is shared with CPU)
-----------------------------
0000...007f Voice Registers
0000...0007 Voice 0 Register
Address Bits Description
7654 3210
0 x--- ---- Frequency divider*
---- -x-- Envelope one-shot mode
---- --x- Sound format
---- --0- PCM
---- --1- Wavetable
---- ---x Keyon/off
PCM case:
1 xxxx xxxx Volume (Each nibble is for each output)
2 xxxx xxxx Frequency*
4 xxxx xxxx Start address / 4096
5 xxxx xxxx 0x100 - (End address / 4096)
Wavetable case:
1 ---x xxxx Wavetable data select
2 xxxx xxxx Frequency LSB*
3 xxxx xxxx "" MSB
4 xxxx xxxx Envelope period (.10 fixed point, Low 8 bit)
5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers)
0008...000f Voice 1 Register
...
0078...007f Voice 15 Register
-----------------------------
0080...0fff Envelope shape data (Same as volume; Each nibble is for each output)
0080...00ff Envelope shape data 1
0100...017f Envelope shape data 2
...
0f80...0fff Envelope shape data 31
-----------------------------
1000...1fff Wavetable data
1000...107f Wavetable data 0
1080...10ff Wavetable data 1
...
1f80...1fff Wavetable data 31
-----------------------------
* Frequency is 4.4 fixed point for PCM,
6.10 for Wavetable.
Frequency divider is higher precision or just right shift?
needs verification.
*/
#include "x1_010.hpp"
void x1_010_core::tick()
{
// reset output
m_out[0] = m_out[1] = 0;
for (int i = 0; i < 16; i++)
{
voice_t &v = m_voice[i];
v.tick();
m_out[0] += v.data * v.vol_out[0];
m_out[1] += v.data * v.vol_out[1];
}
}
void x1_010_core::voice_t::tick()
{
data = vol_out[0] = vol_out[1] = 0;
if (flag.keyon)
{
if (flag.wavetable) // Wavetable
{
// envelope, each nibble is for each output
u8 vol = m_host.m_envelope[(bitfield(end_envshape, 0, 5) << 7) | bitfield(env_acc, 10, 7)];
vol_out[0] = bitfield(vol, 4, 4);
vol_out[1] = bitfield(vol, 0, 4);
env_acc += start_envfreq;
if (flag.env_oneshot && bitfield(env_acc, 17))
flag.keyon = false;
else
env_acc = bitfield(env_acc, 0, 17);
// get wavetable data
data = m_host.m_wave[(bitfield(vol_wave, 0, 5) << 7) | bitfield(acc, 10, 7)];
acc = bitfield(acc + (freq >> flag.div), 0, 17);
}
else // PCM sample
{
// volume register, each nibble is for each output
vol_out[0] = bitfield(vol_wave, 4, 4);
vol_out[1] = bitfield(vol_wave, 0, 4);
// get PCM sample
data = m_host.m_intf.read_byte(bitfield(acc, 4, 20));
acc += bitfield(freq, 0, 8) >> flag.div;
if ((acc >> 16) > (0xff ^ end_envshape))
flag.keyon = false;
}
}
}
u8 x1_010_core::ram_r(u16 offset)
{
if (offset & 0x1000) // wavetable data
return m_wave[offset & 0xfff];
else if (offset & 0xf80) // envelope shape data
return m_envelope[offset & 0xfff];
else // channel register
return m_voice[bitfield(offset, 3, 4)].reg_r(offset & 0x7);
}
void x1_010_core::ram_w(u16 offset, u8 data)
{
if (offset & 0x1000) // wavetable data
m_wave[offset & 0xfff] = data;
else if (offset & 0xf80) // envelope shape data
m_envelope[offset & 0xfff] = data;
else // channel register
m_voice[bitfield(offset, 3, 4)].reg_w(offset & 0x7, data);
}
u8 x1_010_core::voice_t::reg_r(u8 offset)
{
switch (offset & 0x7)
{
case 0x00: return (flag.div << 7)
| (flag.env_oneshot << 2)
| (flag.wavetable << 1)
| (flag.keyon << 0);
case 0x01: return vol_wave;
case 0x02: return bitfield(freq, 0, 8);
case 0x03: return bitfield(freq, 8, 8);
case 0x04: return start_envfreq;
case 0x05: return end_envshape;
default: break;
}
return 0;
}
void x1_010_core::voice_t::reg_w(u8 offset, u8 data)
{
switch (offset & 0x7)
{
case 0x00:
{
const bool prev_keyon = flag.keyon;
flag.div = bitfield(data, 7);
flag.env_oneshot = bitfield(data, 2);
flag.wavetable = bitfield(data, 1);
flag.keyon = bitfield(data, 0);
if (!prev_keyon && flag.keyon) // Key on
{
acc = flag.wavetable ? 0 : (u32(start_envfreq) << 16);
env_acc = 0;
}
break;
}
case 0x01:
vol_wave = data;
break;
case 0x02:
freq = (freq & 0xff00) | data;
break;
case 0x03:
freq = (freq & 0x00ff) | (u16(data) << 8);
break;
case 0x04:
start_envfreq = data;
break;
case 0x05:
end_envshape = data;
break;
default:
break;
}
}
void x1_010_core::voice_t::reset()
{
flag.reset();
vol_wave = 0;
freq = 0;
start_envfreq = 0;
end_envshape = 0;
acc = 0;
env_acc = 0;
data = 0;
vol_out[0] = vol_out[1] = 0;
}
void x1_010_core::reset()
{
for (auto & elem : m_voice)
elem.reset();
std::fill_n(&m_envelope[0], 0x1000, 0);
std::fill_n(&m_wave[0], 0x1000, 0);
m_out[0] = m_out[1] = 0;
}

View file

@ -1,134 +0,0 @@
/*
License: BSD-3-Clause
see https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE for more details
Copyright holder(s): cam900
Modifiers and Contributors for Furnace: cam900, tildearrow
Seta/Allumer X1-010 Emulation core
See x1_010.cpp for more info.
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_X1_010_HPP
#define _VGSOUND_EMU_X1_010_HPP
#pragma once
namespace x1_010
{
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef signed char s8;
typedef signed int s32;
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
}
using namespace x1_010;
class x1_010_mem_intf
{
public:
virtual u8 read_byte(u32 address) { return 0; }
};
using namespace x1_010;
class x1_010_core
{
friend class x1_010_mem_intf;
public:
// constructor
x1_010_core(x1_010_mem_intf &intf)
: m_voice{*this,*this,*this,*this,
*this,*this,*this,*this,
*this,*this,*this,*this,
*this,*this,*this,*this}
, m_intf(intf)
{
m_envelope = std::make_unique<u8[]>(0x1000);
m_wave = std::make_unique<u8[]>(0x1000);
std::fill_n(&m_envelope[0], 0x1000, 0);
std::fill_n(&m_wave[0], 0x1000, 0);
}
// register accessor
u8 ram_r(u16 offset);
void ram_w(u16 offset, u8 data);
// getters
s32 output(u8 channel) { return m_out[channel & 1]; }
s32 chan_out(u8 channel) { return (m_voice[channel].data * (m_voice[channel].vol_out[0]+m_voice[channel].vol_out[1]))<<2; }
// internal state
void reset();
void tick();
private:
// 16 voices in chip
struct voice_t
{
// constructor
voice_t(x1_010_core &host) : m_host(host) {}
// internal state
void reset();
void tick();
// register accessor
u8 reg_r(u8 offset);
void reg_w(u8 offset, u8 data);
// registers
x1_010_core &m_host;
struct flag_t
{
u8 div : 1;
u8 env_oneshot : 1;
u8 wavetable : 1;
u8 keyon : 1;
void reset()
{
div = 0;
env_oneshot = 0;
wavetable = 0;
keyon = 0;
}
flag_t()
: div(0)
, env_oneshot(0)
, wavetable(0)
, keyon(0)
{ }
};
flag_t flag;
u8 vol_wave = 0;
u16 freq = 0;
u8 start_envfreq = 0;
u8 end_envshape = 0;
// internal registers
u32 acc = 0;
u32 env_acc = 0;
s8 data = 0;
u8 vol_out[2] = {0};
};
voice_t m_voice[16];
// RAM
std::unique_ptr<u8[]> m_envelope = nullptr;
std::unique_ptr<u8[]> m_wave = nullptr;
// output data
s32 m_out[2] = {0};
x1_010_mem_intf &m_intf;
};
#endif

View file

@ -86,9 +86,10 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len
// Oscilloscope buffer part
if (++writeOscBuf>=32) {
writeOscBuf=0;
for (int i=0; i<3; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.chan_out(i)<<10;
for (int i=0; i<2; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<10;
}
oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<10;
}
// Command part
@ -195,7 +196,7 @@ void DivPlatformVRC6::tick(bool sysTick) {
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOff) {
chWrite(i,2,0);
} else {
} else if (chan[i].active) {
chWrite(i,1,chan[i].freq&0xff);
chWrite(i,2,0x80|((chan[i].freq>>8)&0xf));
}

View file

@ -23,10 +23,10 @@
#include <queue>
#include "../dispatch.h"
#include "../macroInt.h"
#include "sound/vrcvi/vrcvi.hpp"
#include "vgsound_emu/src/vrcvi/vrcvi.hpp"
class DivPlatformVRC6: public DivDispatch {
class DivPlatformVRC6: public DivDispatch, public vrcvi_intf {
struct Channel {
int freq, baseFreq, pitch, pitch2, note;
int dacPeriod, dacRate, dacOut;
@ -75,7 +75,6 @@ class DivPlatformVRC6: public DivDispatch {
std::queue<QueuedWrite> writes;
unsigned char sampleBank;
unsigned char writeOscBuf;
vrcvi_intf intf;
vrcvi_core vrc6;
unsigned char regPool[13];
@ -101,7 +100,7 @@ class DivPlatformVRC6: public DivDispatch {
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
DivPlatformVRC6() : vrc6(intf) {};
DivPlatformVRC6() : vrc6(*this) {};
~DivPlatformVRC6();
};

View file

@ -23,9 +23,9 @@
#include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v;
#define rWrite(a,v) if (!skipRegisterWrites) { x1_010->ram_w(a,v); if (dumpWrites) { addWrite(a,v); } }
#define rWrite(a,v) if (!skipRegisterWrites) { x1_010.ram_w(a,v); if (dumpWrites) { addWrite(a,v); } }
#define chRead(c,a) x1_010->ram_r((c<<3)|(a&7))
#define chRead(c,a) x1_010.ram_r((c<<3)|(a&7))
#define chWrite(c,a,v) rWrite((c<<3)|(a&7),v)
#define waveWrite(c,a,v) rWrite(0x1000|(chan[c].waveBank<<11)|(c<<7)|(a&0x7f),(v-128)&0xff)
#define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol)
@ -207,10 +207,10 @@ const char** DivPlatformX1_010::getRegisterSheet() {
void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t h=start; h<start+len; h++) {
x1_010->tick();
x1_010.tick();
signed int tempL=x1_010->output(0);
signed int tempR=x1_010->output(1);
signed int tempL=x1_010.output(0);
signed int tempR=x1_010.output(1);
if (tempL<-32768) tempL=-32768;
if (tempL>32767) tempL=32767;
@ -222,11 +222,18 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l
bufR[h]=stereo?tempR:bufL[h];
for (int i=0; i<16; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=x1_010->chan_out(i);
oscBuf[i]->data[oscBuf[i]->needle++]=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))>>1;
}
}
}
u8 DivPlatformX1_010::read_byte(u32 address) {
if ((sampleMem!=NULL) && (address<getSampleMemCapacity())) {
return sampleMem[address];
}
return 0;
}
double DivPlatformX1_010::NoteX1_010(int ch, int note) {
if (chan[ch].pcm) { // PCM note
double off=8192.0;
@ -811,7 +818,7 @@ DivDispatchOscBuffer* DivPlatformX1_010::getOscBuffer(int ch) {
unsigned char* DivPlatformX1_010::getRegisterPool() {
for (int i=0; i<0x2000; i++) {
regPool[i]=x1_010->ram_r(i);
regPool[i]=x1_010.ram_r(i);
}
return regPool;
}
@ -829,7 +836,7 @@ void DivPlatformX1_010::reset() {
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,128,255,false);
}
x1_010->reset();
x1_010.reset();
sampleBank=0;
// set per-channel initial panning
for (int i=0; i<16; i++) {
@ -942,9 +949,7 @@ int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned in
setFlags(flags);
sampleMem=new unsigned char[getSampleMemCapacity()];
sampleMemLen=0;
intf.memory=sampleMem;
x1_010=new x1_010_core(intf);
x1_010->reset();
x1_010.reset();
reset();
return 16;
}
@ -953,7 +958,6 @@ void DivPlatformX1_010::quit() {
for (int i=0; i<16; i++) {
delete oscBuf[i];
}
delete x1_010;
delete[] sampleMem;
}

View file

@ -24,20 +24,9 @@
#include "../engine.h"
#include "../macroInt.h"
#include "../waveSynth.h"
#include "sound/x1_010/x1_010.hpp"
#include "vgsound_emu/src/x1_010/x1_010.hpp"
class DivX1_010Interface: public x1_010_mem_intf {
public:
unsigned char* memory;
int sampleBank;
virtual u8 read_byte(u32 address) override {
if (memory==NULL) return 0;
return memory[address & 0xfffff];
}
DivX1_010Interface(): memory(NULL), sampleBank(0) {}
};
class DivPlatformX1_010: public DivDispatch {
class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf {
struct Channel {
struct Envelope {
struct EnvFlag {
@ -118,14 +107,14 @@ class DivPlatformX1_010: public DivDispatch {
unsigned char* sampleMem;
size_t sampleMemLen;
unsigned char sampleBank;
DivX1_010Interface intf;
x1_010_core* x1_010;
x1_010_core x1_010;
unsigned char regPool[0x2000];
double NoteX1_010(int ch, int note);
void updateWave(int ch);
void updateEnvelope(int ch);
friend void putDispatchChan(void*,int,int);
public:
u8 read_byte(u32 address);
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
@ -151,6 +140,10 @@ class DivPlatformX1_010: public DivDispatch {
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
DivPlatformX1_010():
DivDispatch(),
vgsound_emu_mem_intf(),
x1_010(*this) {}
~DivPlatformX1_010();
};

View file

@ -183,7 +183,7 @@ TAParamResult pVersion(String) {
printf("- reSID by Dag Lem (GPLv2)\n");
printf("- reSIDfp by Dag Lem, Antti Lankila and Leandro Nini (GPLv2)\n");
printf("- Stella by Stella Team (GPLv2)\n");
printf("- vgsound_emu (first version) by cam900 (BSD 3-clause)\n");
printf("- vgsound_emu (second version) by cam900 (zlib)\n");
return TA_PARAM_QUIT;
}