Extract X1-010 core from submodule
This commit is contained in:
parent
6c432bc42e
commit
66eb40e55e
|
@ -19,10 +19,6 @@
|
|||
[submodule "extern/adpcm"]
|
||||
path = extern/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"]
|
||||
path = extern/Nuked-OPL3
|
||||
url = https://github.com/nukeykt/Nuked-OPL3.git
|
||||
|
|
|
@ -228,8 +228,6 @@ extern/opm/opm.c
|
|||
extern/Nuked-OPLL/opll.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/ay8910.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/x1_010/x1_010.cpp
|
||||
|
||||
src/engine/platform/sound/swan.cpp
|
||||
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 3f8b5c5c6b996588afec7e4ce7469abccf16ecbe
|
|
@ -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;
|
||||
}
|
|
@ -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 "../engine.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:
|
||||
DivEngine* parent;
|
||||
int sampleBank;
|
||||
|
|
Loading…
Reference in New Issue