parent
98e9a4b28d
commit
43981eb59f
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue