mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-04 20:05:05 +00:00
Extract X1-010 core from submodule
This commit is contained in:
parent
6c432bc42e
commit
66eb40e55e
6 changed files with 355 additions and 9 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -19,10 +19,6 @@
|
||||||
[submodule "extern/adpcm"]
|
[submodule "extern/adpcm"]
|
||||||
path = extern/adpcm
|
path = extern/adpcm
|
||||||
url = https://github.com/superctr/adpcm
|
url = https://github.com/superctr/adpcm
|
||||||
[submodule "extern/cam900_vgsound_emu"]
|
|
||||||
path = extern/cam900_vgsound_emu
|
|
||||||
url = https://github.com/cam900/vgsound_emu
|
|
||||||
branch = main
|
|
||||||
[submodule "extern/Nuked-OPL3"]
|
[submodule "extern/Nuked-OPL3"]
|
||||||
path = extern/Nuked-OPL3
|
path = extern/Nuked-OPL3
|
||||||
url = https://github.com/nukeykt/Nuked-OPL3.git
|
url = https://github.com/nukeykt/Nuked-OPL3.git
|
||||||
|
|
|
@ -228,8 +228,6 @@ extern/opm/opm.c
|
||||||
extern/Nuked-OPLL/opll.c
|
extern/Nuked-OPLL/opll.c
|
||||||
extern/Nuked-OPL3/opl3.c
|
extern/Nuked-OPL3/opl3.c
|
||||||
|
|
||||||
extern/cam900_vgsound_emu/x1_010/x1_010.cpp
|
|
||||||
|
|
||||||
src/engine/platform/sound/sn76496.cpp
|
src/engine/platform/sound/sn76496.cpp
|
||||||
src/engine/platform/sound/ay8910.cpp
|
src/engine/platform/sound/ay8910.cpp
|
||||||
src/engine/platform/sound/saa1099.cpp
|
src/engine/platform/sound/saa1099.cpp
|
||||||
|
@ -268,6 +266,8 @@ src/engine/platform/sound/lynx/Mikey.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/qsound.c
|
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/swan.cpp
|
||||||
|
|
||||||
src/engine/platform/ym2610Interface.cpp
|
src/engine/platform/ym2610Interface.cpp
|
||||||
|
|
1
extern/cam900_vgsound_emu
vendored
1
extern/cam900_vgsound_emu
vendored
|
@ -1 +0,0 @@
|
||||||
Subproject commit 3f8b5c5c6b996588afec7e4ce7469abccf16ecbe
|
|
224
src/engine/platform/sound/x1_010/x1_010.cpp
Normal file
224
src/engine/platform/sound/x1_010/x1_010.cpp
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
/*
|
||||||
|
License: BSD-3-Clause
|
||||||
|
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||||
|
|
||||||
|
Copyright holders: cam900
|
||||||
|
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 (int i = 0; i < 16; i++)
|
||||||
|
m_voice[i].reset();
|
||||||
|
|
||||||
|
std::fill_n(&m_envelope[0], 0x1000, 0);
|
||||||
|
std::fill_n(&m_wave[0], 0x1000, 0);
|
||||||
|
m_out[0] = m_out[1] = 0;
|
||||||
|
}
|
127
src/engine/platform/sound/x1_010/x1_010.hpp
Normal file
127
src/engine/platform/sound/x1_010/x1_010.hpp
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
License: BSD-3-Clause
|
||||||
|
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||||
|
|
||||||
|
Copyright holders: cam900
|
||||||
|
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
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
class x1_010_mem_intf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual u8 read_byte(u32 address) { return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
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]; }
|
||||||
|
|
||||||
|
// 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
|
|
@ -24,9 +24,9 @@
|
||||||
#include "../dispatch.h"
|
#include "../dispatch.h"
|
||||||
#include "../engine.h"
|
#include "../engine.h"
|
||||||
#include "../macroInt.h"
|
#include "../macroInt.h"
|
||||||
#include "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp"
|
#include "sound/x1_010/x1_010.hpp"
|
||||||
|
|
||||||
class DivX1_010Interface: public vgsound_emu_mem_intf {
|
class DivX1_010Interface: public x1_010_mem_intf {
|
||||||
public:
|
public:
|
||||||
DivEngine* parent;
|
DivEngine* parent;
|
||||||
int sampleBank;
|
int sampleBank;
|
||||||
|
|
Loading…
Reference in a new issue