bring up MSM6295 core

vgsound_emu by cam900
This commit is contained in:
tildearrow 2022-05-22 18:06:56 -05:00
parent 98e9a4b28d
commit 43981eb59f
5 changed files with 574 additions and 0 deletions

View File

@ -324,6 +324,7 @@ 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

@ -0,0 +1,209 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/main/LICENSE for more details
Copyright holder(s): cam900
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"
void msm6295_core::tick()
{
// command handler
if (m_command_pending)
{
if (bitfield(m_command, 7)) // play voice
{
if ((++m_clock) >= ((15 * (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) >= ((15 * (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();
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) >= ((33 * (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

@ -0,0 +1,91 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/main/LICENSE for more details
Copyright holder(s): cam900
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
// 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
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];
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

@ -0,0 +1,158 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/main/LICENSE for more details
Copyright holders: cam900
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
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

@ -0,0 +1,115 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/blob/main/LICENSE for more details
Copyright holder(s): cam900
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