Temporary workaround for accidently removed libs

This commit is contained in:
cam900 2022-09-17 00:05:54 +09:00
parent 52476ec1a6
commit bf2ec8f1c4
48 changed files with 8972 additions and 0 deletions

1
extern/vgsound_emu-modified vendored Submodule

@ -0,0 +1 @@
Subproject commit 7b988a6714ebf61e8a5fad5c9ccbda2b85853fe1

View file

@ -0,0 +1,154 @@
#
# License: Zlib
# see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
#
# Copyright holder(s): cam900
# Clang Format setting for vgsound_emu
#
---
BasedOnStyle: Microsoft
UseCRLF: true
IndentWidth: 4
ColumnLimit: 0
---
Language: Proto
DisableFormat: true
---
Language: TableGen
DisableFormat: true
---
Language: TextProto
DisableFormat: true
---
Language: Cpp
TabWidth: 4
UseTab: Always
AccessModifierOffset: 4
ColumnLimit: 100
AlignAfterOpenBracket: Align
AlignArrayOfStructures: Right
AlignConsecutiveAssignments:
Enabled: true
AcrossEmptyLines: true
AcrossComments: false
AlignCompound: true
PadOperators: true
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: true
AcrossComments: false
AlignCompound: true
PadOperators: true
AlignConsecutiveBitFields:
Enabled: true
AcrossEmptyLines: true
AcrossComments: false
AlignCompound: true
PadOperators: true
AlignEscapedNewlines: Right
AlignOperands: AlignAfterOperator
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: true
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: Inline
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BitFieldColonSpacing: Both
BreakBeforeBraces: Allman
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeComma
BreakStringLiterals: false
CompactNamespaces: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 2
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
EmptyLineAfterAccessModifier: Leave
EmptyLineBeforeAccessModifier: Always
FixNamespaceComments: true
IncludeBlocks: Regroup
IndentAccessModifiers: true
IndentCaseBlocks: true
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: AfterHash
IndentRequiresClause: true
IndentRequires: true
IndentWrappedFunctionNames: false
InsertBraces: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
PPIndentWidth: 1
PackConstructorInitializers: Never
PointerAlignment: Right
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: Microsoft
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RequiresClausePosition: OwnLine
SeparateDefinitionBlocks: Always
ShortNamespaceLines: 0
SortIncludes: CaseSensitive
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: After
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: Custom
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: false
AfterFunctionDeclarationName: false
AfterFunctionDefinitionName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
StatementAttributeLikeMacros: []

18
vgsound_emu-modified/.gitignore vendored Normal file
View file

@ -0,0 +1,18 @@
.vs/*
.vscode/*
node_modules/*
package.json
package-lock.json
build/*
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps

View file

@ -0,0 +1,17 @@
# Changelogs
## Important changes
### V 2.1.0 (2022-09-08)
Move source folder into vgsound_emu folder
CMake support
Move each readmes into README.md each folders
## Details
See [here](https://gitlab.com/cam900/vgsound_emu/-/commits/main).
### Previous changelogs
See [here](https://github.com/cam900/vgsound_emu/commits/main).

View file

@ -0,0 +1,161 @@
#
# License: Zlib
# see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
#
# Copyright holder(s): cam900
# CMake for vgsound_emu
#
cmake_minimum_required(VERSION 3.0)
project(vgsound_emu
VERSION 2.1.0
LANGUAGES CXX)
option(VGSOUND_EMU_ES5504 "Use ES5504 core" ON)
option(VGSOUND_EMU_ES5505 "Use ES5505 core" ON)
option(VGSOUND_EMU_ES5506 "Use ES5506 core" ON)
option(VGSOUND_EMU_K005289 "Use K005289 core" ON)
option(VGSOUND_EMU_K007232 "Use K007232 core" ON)
option(VGSOUND_EMU_K053260 "Use K053260 core" ON)
option(VGSOUND_EMU_MSM6295 "Use MSM6295 core" ON)
option(VGSOUND_EMU_NAMCO_163 "Use Namco 163 core" ON)
option(VGSOUND_EMU_SCC "Use SCC core" ON)
option(VGSOUND_EMU_VRCVI "Use VRC VI core" ON)
option(VGSOUND_EMU_X1_010 "Use X1-010 core" ON)
message(STATUS "Host: ${CMAKE_HOST_SYSTEM_NAME}, ${CMAKE_HOST_SYSTEM_PROCESSOR}")
message(STATUS "Target: ${CMAKE_SYSTEM_NAME}, ${CMAKE_SYSTEM_PROCESSOR}")
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "CMake version: ${CMAKE_VERSION}")
message(STATUS "Generator: ${CMAKE_GENERATOR}")
message(STATUS "Extra generator: ${CMAKE_EXTRA_GENERATOR}")
message(STATUS "Make program: ${CMAKE_MAKE_PROGRAM}")
set(CORE_SOURCE "")
set(EMU_SOURCE "")
# Core functions
list(APPEND CORE_SOURCE
vgsound_emu/src/core/util.hpp
)
# Dialogic ADPCM
if(VGSOUND_EMU_MSM6295)
list(APPEND CORE_SOURCE
vgsound_emu/src/core/vox/vox.cpp
vgsound_emu/src/core/vox/vox.hpp
)
message(STATUS "Using Dialogic ADPCM core")
endif()
# ES5504, ES5505, ES5506
if(VGSOUND_EMU_ES5504 OR VGSOUND_EMU_ES5505 OR VGSOUND_EMU_ES5506)
list(APPEND EMU_SOURCE
vgsound_emu/src/es550x/es550x.hpp
vgsound_emu/src/es550x/es550x.cpp
vgsound_emu/src/es550x/es550x_alu.cpp
vgsound_emu/src/es550x/es550x_filter.cpp
)
if(VGSOUND_EMU_ES5504)
list(APPEND EMU_SOURCE
vgsound_emu/src/es550x/es5504.hpp
vgsound_emu/src/es550x/es5504.cpp
)
message(STATUS "Using ES5504 core")
endif()
if(VGSOUND_EMU_ES5505)
list(APPEND EMU_SOURCE
vgsound_emu/src/es550x/es5505.hpp
vgsound_emu/src/es550x/es5505.cpp
)
message(STATUS "Using ES5505 core")
endif()
if(VGSOUND_EMU_ES5506)
list(APPEND EMU_SOURCE
vgsound_emu/src/es550x/es5506.hpp
vgsound_emu/src/es550x/es5506.cpp
)
message(STATUS "Using ES5506 core")
endif()
endif()
# K005289
if(VGSOUND_EMU_K005289)
list(APPEND EMU_SOURCE
vgsound_emu/src/k005289/k005289.hpp
vgsound_emu/src/k005289/k005289.cpp
)
message(STATUS "Using K005289 core")
endif()
# K007232
if(VGSOUND_EMU_K007232)
list(APPEND EMU_SOURCE
vgsound_emu/src/k007232/k007232.hpp
vgsound_emu/src/k007232/k007232.cpp
)
message(STATUS "Using K007232 core")
endif()
# K053260
if(VGSOUND_EMU_K053260)
list(APPEND EMU_SOURCE
vgsound_emu/src/k053260/k053260.hpp
vgsound_emu/src/k053260/k053260.cpp
)
message(STATUS "Using K053260 core")
endif()
# MSM6295
if(VGSOUND_EMU_MSM6295)
list(APPEND EMU_SOURCE
vgsound_emu/src/msm6295/msm6295.hpp
vgsound_emu/src/msm6295/msm6295.cpp
)
message(STATUS "Using MSM6295 core")
endif()
# Namco 163
if(VGSOUND_EMU_NAMCO_163)
list(APPEND EMU_SOURCE
vgsound_emu/src/n163/n163.hpp
vgsound_emu/src/n163/n163.cpp
)
message(STATUS "Using Namco 163 core")
endif()
# SCC
if(VGSOUND_EMU_SCC)
list(APPEND EMU_SOURCE
vgsound_emu/src/scc/scc.hpp
vgsound_emu/src/scc/scc.cpp
)
message(STATUS "Using SCC core")
endif()
# VRC VI
if(VGSOUND_EMU_VRCVI)
list(APPEND EMU_SOURCE
vgsound_emu/src/vrcvi/vrcvi.hpp
vgsound_emu/src/vrcvi/vrcvi.cpp
)
message(STATUS "Using VRC VI core")
endif()
# X1-010
if(VGSOUND_EMU_X1_010)
list(APPEND EMU_SOURCE
vgsound_emu/src/x1_010/x1_010.hpp
vgsound_emu/src/x1_010/x1_010.cpp
)
message(STATUS "Using X1-010 core")
endif()
add_library(vgsound_emu STATIC ${CORE_SOURCE} ${EMU_SOURCE})
target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
# target_compile_options(vgsound_emu PRIVATE -Wall -Werror)

View file

@ -0,0 +1,19 @@
zlib License
(C) 2022-present cam900 and contributors
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View file

@ -0,0 +1,7 @@
# modification disclaimer
this is a modified version of vgsound_emu emulation core library tailored for Furnace.
it should not and shall NOT be mistaken for the original, authentic or actual version and revision of the library.
you can get original software from [here](https://gitlab.com/cam900/vgsound_emu/).

View file

@ -0,0 +1,131 @@
# vgsound_emu V2 (modified)
This is a library of video game sound chip emulation cores. useful for emulators, chiptune trackers, or players.
This is a modified version of vgsound_emu, tailored for Furnace.
## Important
License is now changed to zlib license in vgsound_emu V2, now you must notify your all modifications.
but [vgsound_emu V1 (pre-V2)](https://gitlab.com/cam900/vgsound_emu/-/tree/V1) is still exists, and it's still distributed under [BSD-3-Clause license](https://spdx.org/licenses/BSD-3-Clause.html).([details](https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE))
## V2 revision changes
formatting codes with clang-format, Encapsulation for Maintenance, Fix GCC 12, Change license to zlib license for notify modifications in derived works from this cores.
## Changelog
See [here](https://gitlab.com/cam900/vgsound_emu/-/blob/main/CHANGELOG.md).
## License
This software is distributed under [zlib License](https://spdx.org/licenses/Zlib.html), unlike [vgsound_emu V1](https://gitlab.com/cam900/vgsound_emu/-/tree/V1)([standard BSD-3-Clause license](https://spdx.org/licenses/BSD-3-Clause.html)([details](https://gitlab.com/cam900/vgsound_emu/-/blob/V1/LICENSE))).
You must notify your modifications at all files you have modified!
See [here](https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE) for details.
## Folders
- vgsound_emu: base folder
- src: source codes for emulation cores
- core: core files used in most of emulation cores
- vox: Dialogic ADPCM core
- es550x: Ensoniq ES5504, ES5505, ES5506 PCM sound chip families, 25/32 voices with 16/4 stereo/6 stereo output channels
- k005289: Konami K005289, 2 timers
- k007232: Konami K007232, 2 PCM channels
- k053260: Konami K053260, 4 PCM or ADPCM channels with CPU to CPU communication feature
- msm6295: OKI MSM6295, 4 ADPCM channels
- n163: Namco 163, NES Mapper with up to 8 Wavetable channels
- scc: Konami SCC, MSX Mappers with 5 Wavetable channels
- vrcvi: Konami VRC VI, NES Mapper with 2 Pulse channels and 1 Sawtooth channel
- x1_010: Seta/Allumer X1-010, 16 Wavetable/PCM channels
- template: Template for sound emulation core
## Build instruction
### dependencies
- CMake
- git (for source repository management)
- MSVC or GCC or Clang (for compile)
### Clone repository
type the following on a terminal/console/shell/whatever:
```
git clone https://gitlab.com/cam900/vgsound_emu.git
cd vgsound_emu
```
### Compile
#### MSVC
type the following on a developer tools command prompt:
```
mkdir build
cd build
cmake ..
msbuild ALL_BUILD.vcxproj
```
#### MinGW, GCC, Clang with MakeFile
type the following on a terminal/console/shell/whatever:
```
mkdir build
cd build
cmake ..
make
```
### CMake options
To add an CMake option from the command line: ```cmake -D<Insert option name here>=<Value> ..```
You can add multiple option with CMake.
#### Available options
| Options | Available Value | Default | Descriptions |
| :-: | :-: | :-: | :-: |
| VGSOUND_EMU_ES5504 | ON/OFF | ON | Use ES5504 core |
| VGSOUND_EMU_ES5505 | ON/OFF | ON | Use ES5505 core |
| VGSOUND_EMU_ES5506 | ON/OFF | ON | Use ES5506 core |
| VGSOUND_EMU_K005289 | ON/OFF | ON | Use K005289 core |
| VGSOUND_EMU_K007232 | ON/OFF | ON | Use K007232 core |
| VGSOUND_EMU_K053260 | ON/OFF | ON | Use K053260 core |
| VGSOUND_EMU_MSM6295 | ON/OFF | ON | Use MSM6295 core |
| VGSOUND_EMU_NAMCO_163 | ON/OFF | ON | Use Namco 163 core |
| VGSOUND_EMU_SCC | ON/OFF | ON | Use SCC core |
| VGSOUND_EMU_VRCVI | ON/OFF | ON | Use VRC VI core |
| VGSOUND_EMU_X1_010 | ON/OFF | ON | Use X1-010 core |
### Link at another project
Copy this repository as submodule first, type the following on a terminal/console/shell/whatever:
```
git submodule add https://gitlab.com/cam900/vgsound_emu.git <Insert submodule path here>
```
Then, Add following options at your CMakeLists.txt file.
example:
```
add_subdirectory(<Insert submodule path here> [EXCLUDE_FROM_ALL])
...
target_include_directories(<Insert your project name here> SYSTEM PRIVATE <Insert submodule path here>)
target_link_libraries(<Insert your project name here> PRIVATE vgsound_emu)
```
## Contributors
- [cam900](https://gitlab.com/cam900)
- [Natt Akuma](https://github.com/akumanatt)
- [James Alan Nguyen](https://github.com/djtuBIG-MaliceX)
- [Laurens Holst](https://github.com/Grauw)

View file

@ -0,0 +1,263 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Various core utilities for vgsound_emu
*/
#ifndef _VGSOUND_EMU_SRC_CORE_UTIL_HPP
#define _VGSOUND_EMU_SRC_CORE_UTIL_HPP
#pragma once
#include <algorithm>
#include <array>
#include <cmath>
#include <iterator>
#include <memory>
#include <string>
#include <vector>
namespace vgsound_emu
{
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;
class vgsound_emu_core
{
public:
// constructors
vgsound_emu_core(std::string tag)
: m_tag(tag)
{
}
// getters
std::string tag() { return m_tag; }
protected:
const f64 PI = 3.1415926535897932384626433832795;
// std::clamp is only for C++17 or later; I use my own code
template<typename T>
T clamp(T in, T min, T max)
{
#if defined(_HAS_CXX17) && _HAS_CXX17
// just use std::clamp if C++17 or above
return std::clamp(in, min, max);
#else
// otherwise, use my own implementation of std::clamp
return std::min(std::max(in, min), max);
#endif
}
// 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 std::pow(10.0f, attenuation / 20.0f); }
private:
std::string m_tag = ""; // core tags
};
class vgsound_emu_mem_intf : public vgsound_emu_core
{
public:
// constructor
vgsound_emu_mem_intf()
: vgsound_emu_core("mem_intf")
{
}
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>
class clock_pulse_t : public vgsound_emu_core
{
private:
const T m_init_width = 1;
class edge_t : public vgsound_emu_core
{
private:
const u8 m_init_edge = 1;
public:
edge_t(u8 init_edge = 0)
: vgsound_emu_core("clock_pulse_edge")
, m_init_edge(init_edge)
, m_current(init_edge ^ 1)
, m_previous(init_edge)
, m_rising(0)
, m_falling(0)
, m_changed(0)
{
set(init_edge);
}
// internal states
void reset()
{
m_previous = m_init_edge;
m_current = m_init_edge ^ 1;
set(m_init_edge);
}
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;
}
// getters
inline bool current() { return m_current; }
inline bool rising() { return m_rising; }
inline bool falling() { return m_falling; }
inline bool changed() { return m_changed; }
private:
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
};
public:
clock_pulse_t(T init_width, u8 init_edge = 0)
: vgsound_emu_core("clock_pulse")
, m_init_width(init_width)
, m_edge(edge_t(init_edge & 1))
, m_width(init_width)
, m_width_latch(init_width)
, m_counter(init_width)
, m_cycle(0)
{
}
void reset(T init)
{
m_edge.reset();
m_width = m_width_latch = m_counter = init;
m_cycle = 0;
}
inline void reset() { reset(m_init_width); }
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;
}
inline void set_width(T width) { m_width = width; }
inline void set_width_latch(T width) { m_width_latch = width; }
// Accessors
inline bool current_edge() { return m_edge.current(); }
inline bool rising_edge() { return m_edge.rising(); }
inline bool falling_edge() { return m_edge.falling(); }
// getters
edge_t &edge() { return m_edge; }
inline T cycle() { return m_cycle; }
private:
edge_t m_edge;
T m_width = 1; // clock pulse width
T m_width_latch = 1; // clock pulse width latch
T m_counter = 1; // clock counter
T m_cycle = 0; // clock cycle
};
}; // namespace vgsound_emu
using namespace vgsound_emu;
#endif

View file

@ -0,0 +1,75 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Dialogic ADPCM core
*/
#include "vox.hpp"
// reset decoder
void vox_core::vox_decoder_t::decoder_state_t::reset()
{
m_index = 0;
m_step = 16;
}
// copy from source
void vox_core::vox_decoder_t::decoder_state_t::copy(decoder_state_t src)
{
m_index = src.index();
m_step = src.step();
}
// decode single nibble
void vox_core::vox_decoder_t::decoder_state_t::decode(u8 nibble)
{
const u8 delta = bitfield(nibble, 0, 3);
const 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 -= d;
}
else
{
m_step += d;
}
if (m_wraparound) // wraparound (MSM5205)
{
if (m_step < -2048)
{
m_step &= 0x7ff;
}
else if (m_step > 2047)
{
m_step |= ~0x7ff;
}
}
else
{
m_step = clamp<s32>(m_step, -2048, 2047);
}
// adjust step index
m_index = clamp(m_index + m_vox.m_index_table[delta], 0, 48);
}

View file

@ -0,0 +1,115 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Dialogic ADPCM core
*/
#ifndef _VGSOUND_EMU_SRC_CORE_VOX_VOX_HPP
#define _VGSOUND_EMU_SRC_CORE_VOX_VOX_HPP
#pragma once
#include "../util.hpp"
class vox_core : public vgsound_emu_core
{
protected:
class vox_decoder_t : public vgsound_emu_core
{
private:
class decoder_state_t : public vgsound_emu_core
{
public:
decoder_state_t(vox_core &vox, bool wraparound)
: vgsound_emu_core("vox_decoder_state")
, m_wraparound(wraparound)
, m_vox(vox)
, m_index(0)
, m_step(16)
{
}
// internal states
void reset();
void decode(u8 nibble);
// getters
s8 index() { return m_index; }
s32 step() { return m_step; }
decoder_state_t &operator=(decoder_state_t src)
{
copy(src);
return *this;
}
private:
const bool m_wraparound = false; // wraparound or clamp?
void copy(decoder_state_t src);
vox_core &m_vox;
s8 m_index = 0;
s32 m_step = 16;
};
public:
vox_decoder_t(vox_core &vox, bool wraparound)
: vgsound_emu_core("vox_decoder")
, m_curr(vox, wraparound)
, m_loop(vox, wraparound)
, m_loop_saved(false)
{
}
virtual void reset()
{
m_curr.reset();
m_loop.reset();
m_loop_saved = false;
}
void save()
{
if (!m_loop_saved)
{
m_loop = m_curr;
m_loop_saved = true;
}
}
void restore()
{
if (m_loop_saved)
{
m_curr = m_loop;
}
}
void decode(u8 nibble) { m_curr.decode(nibble); }
s32 step() { return m_curr.step(); }
private:
decoder_state_t m_curr;
decoder_state_t m_loop;
bool m_loop_saved = false;
};
const s8 m_index_table[8] = {-1, -1, -1, -1, 2, 4, 6, 8};
const 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};
public:
vox_core(std::string tag)
: vgsound_emu_core(tag)
{
}
};
#endif

View file

@ -0,0 +1,88 @@
# Ensoniq ES5504 DOCII, ES5505 OTIS, ES5506 OTTO
## Summary
- 32 voice of PCMs
- per-voice features:
- 4 pole, 4 mode filter (2 filter coefficient, 12 bit per them)
- max 16 output, 12 bit volume (ES5504)
- max 4 stereo output channels, 8 bit logarithmic volume per left/right output (ES5505)
- max 6 stereo output channels, 12 bit logarithmic volume per left/right output (ES5506)
- Hardware envelope for volume and filter (ES5506)
- 16 bit linear PCM
- 8 bit compressed PCM (ES5506)
- total accessible memory: 1 MWord (2MByte) per bank (ES5504, ES5505) or 2 MWord (4MByte) per bank (ES5506)
- CA flag is also usable for bank
- 1 bit BS flag for bank - 2 bank total (ES5505)
- 2 bit BS flag for bank - 4 bank total (ES5506)
## Source code
- es550x.hpp: Base header
- es550x.cpp: Emulation core for common shared features
- es550x_alu.cpp: Emulation core for shared ALU function
- es550x_filter.cpp: Emulation core for shared filter function
- es5504.hpp: ES5504 header
- es5504.cpp: Emulation core for ES5504 specific features
- es5505.hpp: ES5505 header
- es5505.cpp: Emulation core for ES5505 specific features
- es5506.hpp: ES5506 header
- es5506.cpp: Emulation core for ES5506 specific features
## Description
After ES5503 DOC's appeared, Ensoniq announces ES5504 DOC II, ES5505 OTIS, ES5506 OTTO.
These are not just PCM chip; but with built-in 4 pole filters and variable voice limits.
It can be trades higher sample rate and finer frequency and Tons of voices, or vice-versa.
These are mainly used with their synthesizers, musical stuffs. It's also mainly paired with ES5510 ESP/ES5511 ESP2 for post processing. ES5506 can be paired with itself, It's called Dual chip configuration and Both chips are can be shares same memory spaces.
ES5505 was also mainly used on Taito's early- to late-90s arcade hardware for their PCM sample based sound system, paired with ES5510 ESP for post processing. It's configuration is borrowed from Ensoniq's 32 Voice synths powered by these chips. It's difference is external logic to adds per-voice bankswitching looks like what Konami doing on K007232.
Atari Panther was will be use ES5505, but finally canceled.
Ensoniq's ISA Sound Card for PC, Soundscape used ES5506, "Elite" model has optional daughterboard with ES5510 for digital effects.
## Related chips
- ES5530 "OPUS" variant is 2-in-one chip with built-in ES5506 and Sequoia.
- ES5540 "OTTOFX" variant is ES5506 and ES5510 merged in single package.
- ES5548 "OTTO48" variant is used at late-90s ensoniq synths and musical instruments, 2 ES5506s are merged in single package, or with 48 voices in chip?
## Chip difference
### ES5504 to ES5505
- Total voice amount is expanded to 32, rather than 25.
- ADC and DAC is completely redesigned.
- it's has now voice-independent 10 bit and Sony/Burr-Brown format DAC.
- Output channel and Volume is changed to 16 mono to 4 stereo, 12 bit Analog to 8 bit Stereo digital, also Floating point-ish format and independent per left and right output.
- Channel 3 is can be Input/Output.
- Channel output is can be accessible at host for test purpose.
- Max sample memory is expanded to 2MWords (1MWords * 2 Banks)
### ES5505 to ES5506
- Frequency is more finer now: 11 bit fraction rather than 9 bit.
- Output channel and Volume is changed to 4 stereo to 6 stereo, 8 bit to 16 bit, but only 12 bit is used for calculation; 4 LSB is used for envelope ramping.
- Transwave flag is added - its helpful for transwave process, with interrupt per voices. Hardware envelope is added - K1, K2, Volume value is can be modified in run-time. also K1, K2 is expanded to 16 bit for finer envelope ramping.
- Filter calculation resolution is expanded to 18 bit.
- All channels are output, Serial output is now partially programmable.
- Max sample memory is expanded to 8MWords (2MWords * 4 Banks)
- Register format between these chips are incompatible.

View file

@ -0,0 +1,456 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504 emulation core
*/
#include "es5504.hpp"
// Internal functions
void es5504_core::tick()
{
m_voice_update = false;
m_voice_end = false;
// /CAS, E
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
{
// /CAS
if (m_cas.tick())
{
// /CAS high, E low: get sample address
if (m_cas.falling_edge())
{
// /CAS low, E low: fetch sample
if (!m_e.current_edge())
{
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
}
// E
if (m_clkin.falling_edge()) // falling edge triggers E clock
{
if (m_e.tick())
{
m_intf.e_pin(m_e.current_edge());
if (m_e.rising_edge()) // Host access
{
m_host_intf.update_strobe();
voice_tick();
}
if (m_e.falling_edge()) // Voice memory
{
m_host_intf.clear_host_access();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
if (m_e.current_edge()) // Host interface
{
if (m_host_intf.host_access())
{
if (m_host_intf.rw() && (m_e.cycle() == 2)) // Read
{
m_hd = read(m_ha);
m_host_intf.clear_host_access();
}
else if ((!m_host_intf.rw()) && (m_e.cycle() == 2))
{ // Write
write(m_ha, m_hd);
}
}
}
else if (!m_e.current_edge())
{
if (m_e.cycle() == 2)
{
// reset host access state
m_hd = 0;
m_host_intf.clear_strobe();
}
}
}
}
}
// less cycle accurate, but less CPU heavy routine
void es5504_core::tick_perf()
{
m_voice_update = false;
m_voice_end = false;
// update
// falling edge
m_e.edge().set(false);
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
m_intf.e_pin(true);
m_host_intf.update_strobe();
// falling edge
m_e.edge().set(false);
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
m_intf.e_pin(true);
m_host_intf.update_strobe();
}
void es5504_core::voice_tick()
{
// Voice updates every 2 E clock cycle (= 1 CHSTRB cycle or 4 BCLK clock cycle)
m_voice_update = bitfield(m_voice_fetch++, 0);
if (m_voice_update)
{
// Update voice
m_voice[m_voice_cycle].tick(m_voice_cycle);
// Refresh output (Multiplexed analog output)
m_out[m_voice[m_voice_cycle].cr().ca()] = m_voice[m_voice_cycle].out();
if ((++m_voice_cycle) > std::min<u8>(24, m_active)) // ~ 25 voices
{
m_voice_end = true;
m_voice_cycle = 0;
}
m_voice_fetch = 0;
}
}
void es5504_core::voice_t::fetch(u8 voice, u8 cycle)
{
m_alu.set_sample(
cycle,
m_host.m_intf.read_sample(voice,
bitfield(m_cr.ca(), 0, 3),
bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer)));
}
void es5504_core::voice_t::tick(u8 voice)
{
m_out = 0;
// Filter execute
m_filter.tick(m_alu.interpolation());
if (m_alu.busy())
{
// Send to output
m_out = ((sign_ext<s32>(m_filter.o4_1(), 16) >> 3) * m_volume) >>
12; // Analog multiplied in real chip, 13/12 bit ladder DAC
// ALU execute
if (m_alu.tick())
{
m_alu.loop_exec();
}
// ADC check
adc_exec();
}
// Update IRQ
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
}
// ADC; Correct?
void es5504_core::voice_t::adc_exec()
{
if (m_cr.adc())
{
m_host.m_adc = m_host.m_intf.adc_r() & ~0x7;
}
}
void es5504_core::reset()
{
es550x_shared_core::reset();
for (auto &elem : m_voice)
{
elem.reset();
}
m_adc = 0;
std::fill(m_out.begin(), m_out.end(), 0);
}
void es5504_core::voice_t::reset()
{
es550x_shared_core::es550x_voice_t::reset();
m_volume = 0;
m_out = 0;
}
// Accessors
u16 es5504_core::host_r(u8 address)
{
if (!m_host_intf.host_access())
{
m_ha = address;
if (m_e.rising_edge())
{ // update directly
m_hd = read(m_ha, true);
}
else
{
m_host_intf.set_strobe(true);
}
}
return m_hd;
}
void es5504_core::host_w(u8 address, u16 data)
{
if (!m_host_intf.host_access())
{
m_ha = address;
m_hd = data;
if (m_e.rising_edge())
{ // update directly
write(m_ha, m_hd, true);
}
else
{
m_host_intf.set_strobe(false);
}
}
}
u16 es5504_core::read(u8 address, bool cpu_access) { return regs_r(m_page, address, cpu_access); }
void es5504_core::write(u8 address, u16 data, bool cpu_access)
{
regs_w(m_page, address, data, cpu_access);
}
u16 es5504_core::regs_r(u8 page, u8 address, bool cpu_access)
{
u16 ret = 0xffff;
address = bitfield(address, 0, 4); // 4 bit address for CPU access
if (address >= 12) // Global registers
{
switch (address)
{
case 12: // A/D (A to D Convert/Test)
ret = (ret & ~0xfffb) | (m_adc & 0xfffb);
break;
case 13: // ACT (Number of voices)
ret = (ret & ~0x1f) | bitfield(m_active, 0, 5);
break;
case 14: // IRQV (Interrupting voice vector)
ret = (ret & ~0x9f) | m_irqv.get();
if (cpu_access)
{
m_irqv.clear();
if (bool(bitfield(ret, 7)) != m_irqv.irqb())
{
m_voice[m_irqv.voice()].alu().irq_update(m_intf, m_irqv);
}
}
break;
case 15: // PAGE (Page select register)
ret = (ret & ~0x3f) | bitfield(m_page, 0, 6);
break;
}
}
else // Voice specific registers
{
const u8 voice = bitfield(page, 0, 5); // Voice select
if (voice < 25)
{
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 56
{
switch (address)
{
case 1: // O4(n-1) (Filter 4 Temp Register)
ret = v.filter().o4_1();
break;
case 2: // O3(n-2) (Filter 3 Temp Register #2)
ret = v.filter().o3_2();
break;
case 3: // O3(n-1) (Filter 3 Temp Register #1)
ret = v.filter().o3_1();
break;
case 4: // O2(n-2) (Filter 2 Temp Register #2)
ret = v.filter().o2_2();
break;
case 5: // O2(n-1) (Filter 2 Temp Register #1)
ret = v.filter().o2_1();
break;
case 6: // O1(n-1) (Filter 1 Temp Register)
ret = v.filter().o1_1();
break;
}
}
else // Page 0 - 24
{
switch (address)
{
case 0: // CR (Control Register)
ret = (ret & ~0xff) | (v.alu().stop() ? 0x01 : 0x00) |
(v.cr().adc() ? 0x04 : 0x00) | (v.alu().lpe() ? 0x08 : 0x00) |
(v.alu().ble() ? 0x10 : 0x00) | (v.alu().irqe() ? 0x20 : 0x00) |
(v.alu().dir() ? 0x40 : 0x00) | (v.alu().irq() ? 0x80 : 0x00);
break;
case 1: // FC (Frequency Control)
ret = (ret & ~0xfffe) | (v.alu().fc() << 1);
break;
case 2: // STRT-H (Loop Start Register High)
ret = (ret & ~0x1fff) | bitfield(v.alu().start(), 16, 13);
break;
case 3: // STRT-L (Loop Start Register Low)
ret = (ret & ~0xffe0) | (v.alu().start() & 0xffe0);
break;
case 4: // END-H (Loop End Register High)
ret = (ret & ~0x1fff) | bitfield(v.alu().end(), 16, 13);
break;
case 5: // END-L (Loop End Register Low)
ret = (ret & ~0xffe0) | (v.alu().end() & 0xffe0);
break;
case 6: // K2 (Filter Cutoff Coefficient #2)
ret = (ret & ~0xfff0) | (v.filter().k2() & 0xfff0);
break;
case 7: // K1 (Filter Cutoff Coefficient #1)
ret = (ret & ~0xfff0) | (v.filter().k1() & 0xfff0);
break;
case 8: // Volume
ret = (ret & ~0xfff0) | ((v.volume() << 4) & 0xfff0);
break;
case 9: // CA (Filter Config, Channel Assign)
ret = (ret & ~0x3f) | bitfield(v.cr().ca(), 0, 4) |
(bitfield(v.filter().lp(), 0, 2) << 4);
break;
case 10: // ACCH (Accumulator High)
ret = (ret & ~0x1fff) | bitfield(v.alu().accum(), 16, 13);
break;
case 11: // ACCL (Accumulator Low)
ret = bitfield(v.alu().accum(), 0, 16);
break;
}
}
}
}
return ret;
}
void es5504_core::regs_w(u8 page, u8 address, u16 data, bool cpu_access)
{
address = bitfield(address, 0, 4); // 4 bit address for CPU access
if (address >= 12) // Global registers
{
switch (address)
{
case 12: // A/D (A to D Convert/Test)
if (bitfield(m_adc, 0)) // Writam_ble ADC
{
m_adc = (m_adc & 7) | (data & ~7);
m_intf.adc_w(m_adc & ~7);
}
m_adc = (m_adc & ~3) | (data & 3);
break;
case 13: // ACT (Number of voices)
m_active = std::min<u8>(24, bitfield(data, 0, 5));
break;
case 14: // IRQV (Interrupting voice vector)
// Read only
break;
case 15: // PAGE (Page select register)
m_page = bitfield(data, 0, 6);
break;
}
}
else // Voice specific registers
{
const u8 voice = bitfield(page, 0, 5); // Voice select
if (voice < 25)
{
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 56
{
switch (address)
{
case 1: // O4(n-1) (Filter 4 Temp Register)
v.filter().set_o4_1(sign_ext<s32>(data, 16));
break;
case 2: // O3(n-2) (Filter 3 Temp Register #2)
v.filter().set_o3_2(sign_ext<s32>(data, 16));
break;
case 3: // O3(n-1) (Filter 3 Temp Register #1)
v.filter().set_o3_1(sign_ext<s32>(data, 16));
break;
case 4: // O2(n-2) (Filter 2 Temp Register #2)
v.filter().set_o2_2(sign_ext<s32>(data, 16));
break;
case 5: // O2(n-1) (Filter 2 Temp Register #1)
v.filter().set_o2_1(sign_ext<s32>(data, 16));
break;
case 6: // O1(n-1) (Filter 1 Temp Register)
v.filter().set_o1_1(sign_ext<s32>(data, 16));
break;
}
}
else // Page 0 - 24
{
switch (address)
{
case 0: // CR (Control Register)
v.alu().set_stop(bitfield(data, 0, 2));
v.cr().set_adc(bitfield(data, 2));
v.alu().set_lpe(bitfield(data, 3));
v.alu().set_ble(bitfield(data, 4));
v.alu().set_irqe(bitfield(data, 5));
v.alu().set_dir(bitfield(data, 6));
v.alu().set_irq(bitfield(data, 7));
break;
case 1: // FC (Frequency Control)
v.alu().set_fc(bitfield(data, 1, 15));
break;
case 2: // STRT-H (Loop Start Register High)
v.alu().set_start(bitfield<u32>(data, 0, 13) << 16, 0x1fff0000);
break;
case 3: // STRT-L (Loop Start Register Low)
v.alu().set_start(data & 0xffe0, 0xffe0);
break;
case 4: // END-H (Loop End Register High)
v.alu().set_end(bitfield<u32>(data, 0, 13) << 16, 0x1fff0000);
break;
case 5: // END-L (Loop End Register Low)
v.alu().set_end(data & 0xffe0, 0xffe0);
break;
case 6: // K2 (Filter Cutoff Coefficient #2)
v.filter().set_k2(data & 0xfff0);
break;
case 7: // K1 (Filter Cutoff Coefficient #1)
v.filter().set_k1(data & 0xfff0);
break;
case 8: // Volume
v.set_volume(bitfield(data, 4, 12));
break;
case 9: // CA (Filter Config, Channel Assign)
v.cr().set_ca(bitfield(data, 0, 4));
v.filter().set_lp(bitfield(data, 4, 2));
break;
case 10: // ACCH (Accumulator High)
v.alu().set_accum(bitfield<u32>(data, 0, 13) << 16, 0x1fff0000);
break;
case 11: // ACCL (Accumulator Low)
v.alu().set_accum(data, 0xffff);
break;
}
}
}
}
}

View file

@ -0,0 +1,117 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504 emulation core
*/
#ifndef _VGSOUND_EMU_SRC_ES5504_HPP
#define _VGSOUND_EMU_SRC_ES5504_HPP
#pragma once
#include "es550x.hpp"
// ES5504 specific
class es5504_core : public es550x_shared_core
{
private:
// es5504 voice classes
class voice_t : public es550x_voice_t
{
public:
// constructor
voice_t(es5504_core &host)
: es550x_voice_t("es5504_voice", 20, 9, false)
, m_host(host)
, m_volume(0)
, m_out(0)
{
}
// internal state
virtual void reset() override;
virtual void fetch(u8 voice, u8 cycle) override;
virtual void tick(u8 voice) override;
// setters
inline void set_volume(u16 volume) { m_volume = volume; }
// getters
inline u16 volume() { return m_volume; }
inline s32 out() { return m_out; }
private:
void adc_exec();
// registers
es5504_core &m_host;
u16 m_volume = 0; // 12 bit Volume
s32 m_out = 0; // channel outputs
};
public:
// constructor
es5504_core(es550x_intf &intf)
: es550x_shared_core("es5504", 25, intf)
, m_voice{*this, *this, *this, *this, *this, *this, *this, *this, *this,
*this, *this, *this, *this, *this, *this, *this, *this, *this,
*this, *this, *this, *this, *this, *this, *this}
, m_adc(0)
, m_out{0}
{
}
// host interface
u16 host_r(u8 address);
void host_w(u8 address, u16 data);
// internal state
virtual void reset() override;
virtual void tick() override;
// less cycle accurate, but also less cpu heavy update routine
void tick_perf();
// 16 analog output channels
inline s32 out(u8 ch) { return m_out[ch & 0xf]; }
//-----------------------------------------------------------------
//
// for preview/debug purpose only, not for serious emulators
//
//-----------------------------------------------------------------
// bypass chips host interface for debug purpose only
u16 read(u8 address, bool cpu_access = false);
void write(u8 address, u16 data, bool cpu_access = false);
u16 regs_r(u8 page, u8 address, bool cpu_access = false);
void regs_w(u8 page, u8 address, u16 data, bool cpu_access = false);
u16 regs_r(u8 page, u8 address)
{
u8 prev = m_page;
m_page = page;
u16 ret = read(address, false);
m_page = prev;
return ret;
}
// per-voice outputs
inline s32 voice_out(u8 voice) { return (voice < 25) ? m_voice[voice].out() : 0; }
protected:
virtual inline u8 max_voices() override { return 25; }
virtual void voice_tick() override;
private:
std::array<voice_t, 25> m_voice; // 25 voices
u16 m_adc = 0; // ADC register
std::array<s32, 16> m_out = {0}; // 16 channel outputs
};
#endif

View file

@ -0,0 +1,652 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5505 emulation core
*/
#include "es5505.hpp"
// Internal functions
void es5505_core::tick()
{
m_voice_update = false;
m_voice_end = false;
// CLKIN
if (m_clkin.tick())
{
// SERBCLK
if (m_clkin.edge().changed()) // BCLK is freely running clock
{
if (m_bclk.tick())
{
m_intf.bclk(m_bclk.current_edge());
// Serial output
if (m_bclk.falling_edge())
{
// SERLRCLK
if (m_lrclk.tick())
{
m_intf.lrclk(m_lrclk.current_edge());
}
}
// SERWCLK
if (m_lrclk.edge().changed())
{
m_wclk = 0;
}
if (m_bclk.falling_edge())
{
if (m_wclk == ((m_sermode.sony_bb()) ? 1 : 0))
{
if (m_lrclk.current_edge())
{
for (int i = 0; i < 4; i++)
{
// copy output
m_output[i] = m_output_temp[i];
m_output_latch[i] = m_ch[i];
m_output_temp[i].reset();
// clamp to 16 bit (upper 5 bits are overflow
// guard bits)
m_output_latch[i].clamp16();
// set signed
if (m_output_latch[i].left() < 0)
{
m_output_temp[i].set_left(-1);
}
if (m_output_latch[i].right() < 0)
{
m_output_temp[i].set_right(-1);
}
}
}
m_wclk_lr = m_lrclk.current_edge();
m_output_bit = 16;
}
s8 output_bit = --m_output_bit;
if (m_output_bit >= 0)
{
for (int i = 0; i < 4; i++)
{
if (m_wclk_lr)
{ // Right output
m_output_temp[i].serial_in(
m_wclk_lr,
bitfield(m_output_latch[i].right(), output_bit));
}
else
{ // Left output
m_output_temp[i].serial_in(
m_wclk_lr,
bitfield(m_output_latch[i].left(), output_bit));
}
}
}
m_wclk++;
}
}
}
// /CAS, E
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
{
// /CAS
if (m_cas.tick())
{
// /CAS high, E low: get sample address
if (m_cas.falling_edge())
{
// /CAS low, E low: fetch sample
if (!m_e.current_edge())
{
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
}
// E
if (m_e.tick())
{
m_intf.e_pin(m_e.current_edge());
if (m_e.rising_edge()) // Host access
{
m_host_intf.update_strobe();
voice_tick();
}
else if (m_e.falling_edge()) // Voice memory
{
m_host_intf.clear_host_access();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
if (m_e.current_edge()) // Host interface
{
if (m_host_intf.host_access())
{
if (m_host_intf.rw() && (m_e.cycle() == 2)) // Read
{
m_hd = read(m_ha);
m_host_intf.clear_host_access();
}
else if ((!m_host_intf.rw()) && (m_e.cycle() == 2))
{ // Write
write(m_ha, m_hd);
}
}
}
else if (!m_e.current_edge())
{
if (m_e.cycle() == 2)
{
// reset host access state
m_hd = 0;
m_host_intf.clear_strobe();
}
}
}
}
}
}
// less cycle accurate, but less CPU heavy routine
void es5505_core::tick_perf()
{
m_voice_update = false;
m_voice_end = false;
// output
for (int c = 0; c < 4; c++)
{
m_output[c] = m_ch[c];
}
// update
// falling edge
m_e.edge().set(false);
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
m_intf.e_pin(true);
m_host_intf.update_strobe();
// falling edge
m_e.edge().set(false);
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
m_intf.e_pin(true);
m_host_intf.update_strobe();
}
void es5505_core::voice_tick()
{
// Voice updates every 2 E clock cycle (or 4 BCLK clock cycle)
m_voice_update = bitfield(m_voice_fetch++, 0);
if (m_voice_update)
{
// Update voice
m_voice[m_voice_cycle].tick(m_voice_cycle);
// Refresh output
if ((++m_voice_cycle) > clamp<u8>(m_active, 7, 31)) // 8 ~ 32 voices
{
m_voice_end = true;
m_voice_cycle = 0;
for (auto &elem : m_ch)
{
elem.reset();
}
for (auto &elem : m_voice)
{
m_ch[bitfield(elem.cr().ca(), 0, 2)] += elem.ch();
elem.ch().reset();
}
}
m_voice_fetch = 0;
}
}
void es5505_core::voice_t::fetch(u8 voice, u8 cycle)
{
m_alu.set_sample(
cycle,
m_host.m_intf.read_sample(voice,
bitfield(m_cr.bs(), 0),
bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer)));
}
void es5505_core::voice_t::tick(u8 voice)
{
m_ch.reset();
// Filter execute
m_filter.tick(m_alu.interpolation());
if (m_alu.busy())
{
// Send to output
m_ch.set_left(volume_calc(m_lvol, sign_ext<s32>(m_filter.o4_1(), 16)));
m_ch.set_right(volume_calc(m_rvol, sign_ext<s32>(m_filter.o4_1(), 16)));
// ALU execute
if (m_alu.tick())
{
m_alu.loop_exec();
}
}
// Update IRQ
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
}
// volume calculation
s32 es5505_core::voice_t::volume_calc(u8 volume, s32 in)
{
u8 exponent = bitfield(volume, 4, 4);
u8 mantissa = bitfield(volume, 0, 4);
return exponent ? (in * s32(0x10 | mantissa)) >> (19 - exponent) : 0;
}
void es5505_core::reset()
{
es550x_shared_core::reset();
for (auto &elem : m_voice)
{
elem.reset();
}
m_sermode.reset();
m_bclk.reset();
m_lrclk.reset();
m_wclk = 0;
m_wclk_lr = false;
m_output_bit = 0;
for (auto &elem : m_ch)
{
elem.reset();
}
for (auto &elem : m_output)
{
elem.reset();
}
for (auto &elem : m_output_temp)
{
elem.reset();
}
for (auto &elem : m_output_latch)
{
elem.reset();
}
}
void es5505_core::voice_t::reset()
{
es550x_shared_core::es550x_voice_t::reset();
m_lvol = 0;
m_rvol = 0;
m_ch.reset();
}
// Accessors
u16 es5505_core::host_r(u8 address)
{
if (!m_host_intf.host_access())
{
m_ha = address;
if (m_e.rising_edge())
{ // update directly
m_hd = read(m_ha, true);
}
else
{
m_host_intf.set_strobe(true);
}
}
return m_hd;
}
void es5505_core::host_w(u8 address, u16 data)
{
if (!m_host_intf.host_access())
{
m_ha = address;
m_hd = data;
if (m_e.rising_edge())
{ // update directly
write(m_ha, m_hd, true);
}
else
{
m_host_intf.set_strobe(false);
}
}
}
u16 es5505_core::read(u8 address, bool cpu_access) { return regs_r(m_page, address, cpu_access); }
void es5505_core::write(u8 address, u16 data, bool cpu_access)
{
regs_w(m_page, address, data, cpu_access);
}
u16 es5505_core::regs_r(u8 page, u8 address, bool cpu_access)
{
u16 ret = 0xffff;
address = bitfield(address, 0, 4); // 4 bit address for CPU access
if (address >= 13) // Global registers
{
switch (address)
{
case 13: // ACT (Number of voices)
ret = (ret & ~0x1f) | bitfield(m_active, 0, 5);
break;
case 14: // IRQV (Interrupting voice vector)
ret = (ret & ~0x9f) | m_irqv.get();
if (cpu_access)
{
m_irqv.clear();
if (bool(bitfield(ret, 7)) != m_irqv.irqb())
{
m_voice[m_irqv.voice()].alu().irq_update(m_intf, m_irqv);
}
}
break;
case 15: // PAGE (Page select register)
ret = (ret & ~0x7f) | bitfield(m_page, 0, 7);
break;
}
}
else
{
if (bitfield(page, 6)) // Channel registers
{
switch (address)
{
case 0: // CH0L (Channel 0 Left)
case 2: // CH1L (Channel 1 Left)
case 4: // CH2L (Channel 2 Left)
if (!cpu_access)
{ // CPU can't read here
ret = m_ch[bitfield(address, 0, 2)].left();
}
break;
case 1: // CH0R (Channel 0 Right)
case 3: // CH1R (Channel 1 Right)
case 5: // CH2R (Channel 2 Right)
if (!cpu_access)
{ // CPU can't read here
ret = m_ch[bitfield(address, 0, 2)].right();
}
break;
case 6: // CH3L (Channel 3 Left)
if ((!cpu_access) || m_sermode.adc())
{
ret = m_ch[3].left();
}
break;
case 7: // CH3R (Channel 3 Right)
if ((!cpu_access) || m_sermode.adc())
{
ret = m_ch[3].right();
}
break;
case 8: // SERMODE (Serial Mode)
ret = (ret & ~0xf807) | (m_sermode.adc() ? 0x01 : 0x00) |
(m_sermode.test() ? 0x02 : 0x00) | (m_sermode.sony_bb() ? 0x04 : 0x00) |
(bitfield(m_sermode.msb(), 0, 5) << 11);
break;
case 9: // PAR (Port A/D Register)
ret = (ret & ~0x3f) | (m_intf.adc_r() & ~0x3f);
break;
}
}
else // Voice specific registers
{
const u8 voice = bitfield(page, 0, 5); // Voice select
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 63
{
switch (address)
{
case 1: // O4(n-1) (Filter 4 Temp Register)
ret = v.filter().o4_1();
break;
case 2: // O3(n-2) (Filter 3 Temp Register #2)
ret = v.filter().o3_2();
break;
case 3: // O3(n-1) (Filter 3 Temp Register #1)
ret = v.filter().o3_1();
break;
case 4: // O2(n-2) (Filter 2 Temp Register #2)
ret = v.filter().o2_2();
break;
case 5: // O2(n-1) (Filter 2 Temp Register #1)
ret = v.filter().o2_1();
break;
case 6: // O1(n-1) (Filter 1 Temp Register)
ret = v.filter().o1_1();
break;
}
}
else // Page 0 - 31
{
switch (address)
{
case 0: // CR (Control Register)
ret = (ret & ~0xfff) | (v.alu().stop() << 0) |
(bitfield(v.cr().bs(), 0) ? 0x04 : 0x00) |
(v.alu().lpe() ? 0x08 : 0x00) | (v.alu().ble() ? 0x10 : 0x00) |
(v.alu().irqe() ? 0x20 : 0x00) | (v.alu().dir() ? 0x40 : 0x00) |
(v.alu().irq() ? 0x80 : 0x00) | (bitfield(v.cr().ca(), 0, 2) << 8) |
(bitfield(v.filter().lp(), 0, 2) << 10);
break;
case 1: // FC (Frequency Control)
ret = (ret & ~0xfffe) | (bitfield(v.alu().fc(), 0, 15) << 1);
break;
case 2: // STRT-H (Loop Start Register High)
ret = (ret & ~0x1fff) | bitfield(v.alu().start(), 16, 13);
break;
case 3: // STRT-L (Loop Start Register Low)
ret = (ret & ~0xffe0) | (v.alu().start() & 0xffe0);
break;
case 4: // END-H (Loop End Register High)
ret = (ret & ~0x1fff) | bitfield(v.alu().end(), 16, 13);
break;
case 5: // END-L (Loop End Register Low)
ret = (ret & ~0xffe0) | (v.alu().end() & 0xffe0);
break;
case 6: // K2 (Filter Cutoff Coefficient #2)
ret = (ret & ~0xfff0) | (v.filter().k2() & 0xfff0);
break;
case 7: // K1 (Filter Cutoff Coefficient #1)
ret = (ret & ~0xfff0) | (v.filter().k1() & 0xfff0);
break;
case 8: // LVOL (Left Volume)
ret = (ret & ~0xff00) | ((v.lvol() << 8) & 0xff00);
break;
case 9: // RVOL (Right Volume)
ret = (ret & ~0xff00) | ((v.rvol() << 8) & 0xff00);
break;
case 10: // ACCH (Accumulator High)
ret = (ret & ~0x1fff) | bitfield(v.alu().accum(), 16, 13);
break;
case 11: // ACCL (Accumulator Low)
ret = bitfield(v.alu().accum(), 0, 16);
break;
}
}
}
}
return ret;
}
void es5505_core::regs_w(u8 page, u8 address, u16 data, bool cpu_access)
{
address = bitfield(address, 0, 4); // 4 bit address for CPU access
if (address >= 12) // Global registers
{
switch (address)
{
case 13: // ACT (Number of voices)
m_active = std::max<u8>(7, bitfield(data, 0, 5));
break;
case 14: // IRQV (Interrupting voice vector)
// Read only
break;
case 15: // PAGE (Page select register)
m_page = bitfield(data, 0, 7);
break;
}
}
else // Voice specific registers
{
if (bitfield(page, 6)) // Channel registers
{
switch (address)
{
case 0: // CH0L (Channel 0 Left)
if (m_sermode.test())
{
m_ch[0].set_left(data);
}
break;
case 1: // CH0R (Channel 0 Right)
if (m_sermode.test())
{
m_ch[0].set_right(data);
}
break;
case 2: // CH1L (Channel 1 Left)
if (m_sermode.test())
{
m_ch[1].set_left(data);
}
break;
case 3: // CH1R (Channel 1 Right)
if (m_sermode.test())
{
m_ch[1].set_right(data);
}
break;
case 4: // CH2L (Channel 2 Left)
if (m_sermode.test())
{
m_ch[2].set_left(data);
}
break;
case 5: // CH2R (Channel 2 Right)
if (m_sermode.test())
{
m_ch[2].set_right(data);
}
break;
case 6: // CH3L (Channel 3 Left)
if (m_sermode.test())
{
m_ch[3].set_left(data);
}
break;
case 7: // CH3R (Channel 3 Right)
if (m_sermode.test())
{
m_ch[3].set_right(data);
}
break;
case 8: // SERMODE (Serial Mode)
m_sermode.write(data);
break;
case 9: // PAR (Port A/D Register)
// Read only
break;
}
}
else // Voice specific registers
{
const u8 voice = bitfield(page, 0, 5); // Voice select
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 56
{
switch (address)
{
case 1: // O4(n-1) (Filter 4 Temp Register)
v.filter().set_o4_1(sign_ext<s32>(data, 16));
break;
case 2: // O3(n-2) (Filter 3 Temp Register #2)
v.filter().set_o3_2(sign_ext<s32>(data, 16));
break;
case 3: // O3(n-1) (Filter 3 Temp Register #1)
v.filter().set_o3_1(sign_ext<s32>(data, 16));
break;
case 4: // O2(n-2) (Filter 2 Temp Register #2)
v.filter().set_o2_2(sign_ext<s32>(data, 16));
break;
case 5: // O2(n-1) (Filter 2 Temp Register #1)
v.filter().set_o2_1(sign_ext<s32>(data, 16));
break;
case 6: // O1(n-1) (Filter 1 Temp Register)
v.filter().set_o1_1(sign_ext<s32>(data, 16));
break;
}
}
else // Page 0 - 24
{
switch (address)
{
case 0: // CR (Control Register)
v.alu().set_stop(bitfield(data, 0, 2));
v.cr().set_bs(bitfield(data, 2));
v.alu().set_lpe(bitfield(data, 3));
v.alu().set_ble(bitfield(data, 4));
v.alu().set_irqe(bitfield(data, 5));
v.alu().set_dir(bitfield(data, 6));
v.alu().set_irq(bitfield(data, 7));
v.cr().set_ca(bitfield(data, 8, 2));
v.filter().set_lp(bitfield(data, 10, 2));
break;
case 1: // FC (Frequency Control)
v.alu().set_fc(bitfield(data, 1, 15));
break;
case 2: // STRT-H (Loop Start Register High)
v.alu().set_start(bitfield<u32>(data, 0, 13) << 16, 0x1fff0000);
break;
case 3: // STRT-L (Loop Start Register Low)
v.alu().set_start(data & 0xffe0, 0xffe0);
break;
case 4: // END-H (Loop End Register High)
v.alu().set_end(bitfield<u32>(data, 0, 13) << 16, 0x1fff0000);
break;
case 5: // END-L (Loop End Register Low)
v.alu().set_end(data & 0xffe0, 0xffe0);
break;
case 6: // K2 (Filter Cutoff Coefficient #2)
v.filter().set_k2(data & 0xfff0);
break;
case 7: // K1 (Filter Cutoff Coefficient #1)
v.filter().set_k1(data & 0xfff0);
break;
case 8: // LVOL (Left Volume)
v.set_lvol(bitfield(data, 8, 8));
break;
case 9: // RVOL (Right Volume)
v.set_rvol(bitfield(data, 8, 8));
break;
case 10: // ACCH (Accumulator High)
v.alu().set_accum(bitfield<u32>(data, 0, 13) << 16, 0x1fff0000);
break;
case 11: // ACCL (Accumulator Low)
v.alu().set_accum(data, 0xffff);
break;
}
}
}
}
}

View file

@ -0,0 +1,304 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504 emulation core
*/
#ifndef _VGSOUND_EMU_SRC_ES5505_HPP
#define _VGSOUND_EMU_SRC_ES5505_HPP
#pragma once
#include "es550x.hpp"
// ES5505 specific
class es5505_core : public es550x_shared_core
{
private:
class output_t : public vgsound_emu_core
{
public:
output_t(s32 left = 0, s32 right = 0)
: vgsound_emu_core("es5505_output")
, m_left(left)
, m_right(right)
{
}
void reset()
{
m_left = 0;
m_right = 0;
};
inline s32 clamp16(s32 in) { return clamp(in, -0x8000, 0x7fff); }
inline void clamp16(output_t src)
{
m_left = clamp16(src.left());
m_right = clamp16(src.right());
}
inline void clamp16()
{
m_left = clamp16(m_left);
m_right = clamp16(m_right);
}
// setters
inline void set_left(s32 left) { m_left = left; }
inline void set_right(s32 right) { m_right = right; }
inline void serial_in(bool ch, u8 in)
{
if (ch) // Right output
{
m_right = (m_right << 1) | (in ? 1 : 0);
}
else // Left output
{
m_left = (m_left << 1) | (in ? 1 : 0);
}
}
// getters
inline u32 left() { return m_left; }
inline u32 right() { return m_right; }
output_t &operator+=(output_t &src)
{
m_left = clamp16(m_left + src.left());
m_right = clamp16(m_right + src.right());
return *this;
}
output_t &operator=(output_t src)
{
clamp16(src);
return *this;
}
output_t &operator=(s32 val)
{
m_left = m_right = clamp16(val);
return *this;
}
output_t &operator>>(s32 shift)
{
m_left >>= shift;
m_right >>= shift;
return *this;
}
private:
s32 m_left = 0;
s32 m_right = 0;
};
// es5505 voice classes
class voice_t : public es550x_voice_t
{
public:
// constructor
voice_t(es5505_core &host)
: es550x_voice_t("es5505_voice", 20, 9, false)
, m_host(host)
, m_lvol(0)
, m_rvol(0)
, m_ch(output_t())
{
}
// internal state
virtual void reset() override;
virtual void fetch(u8 voice, u8 cycle) override;
virtual void tick(u8 voice) override;
// setters
inline void set_lvol(u8 lvol) { m_lvol = lvol; }
inline void set_rvol(u8 rvol) { m_rvol = rvol; }
// getters
inline u8 lvol() { return m_lvol; }
inline u8 rvol() { return m_rvol; }
output_t &ch() { return m_ch; }
private:
s32 volume_calc(u8 volume, s32 in);
// registers
es5505_core &m_host;
u8 m_lvol = 0; // Left volume
u8 m_rvol = 0; // Right volume
output_t m_ch; // channel output
};
class sermode_t : public vgsound_emu_core
{
public:
sermode_t()
: vgsound_emu_core("es5505_sermode")
, m_adc(0)
, m_test(0)
, m_sony_bb(0)
, m_msb(0)
{
}
void reset()
{
m_adc = 0;
m_test = 0;
m_sony_bb = 0;
m_msb = 0;
}
// setters
void write(u16 data)
{
m_adc = (data >> 0) & 1;
m_test = (data >> 1) & 1;
m_sony_bb = (data >> 2) & 1;
m_msb = (data >> 11) & 0x1f;
}
void set_adc(bool adc) { m_adc = adc ? 1 : 0; }
void set_test(bool test) { m_test = test ? 1 : 0; }
void set_sony_bb(bool sony_bb) { m_sony_bb = sony_bb ? 1 : 0; }
void set_msb(u8 msb) { m_msb = msb & 0x1f; }
// getters
bool adc() { return m_adc; }
bool test() { return m_test; }
bool sony_bb() { return m_sony_bb; }
u8 msb() { return m_msb; }
private:
u8 m_adc : 1; // A/D
u8 m_test : 1; // Test
u8 m_sony_bb : 1; // Sony/BB format serial output
u8 m_msb : 5; // Serial output MSB
};
public:
// constructor
es5505_core(es550x_intf &intf)
: es550x_shared_core("es5505", 32, intf)
, m_voice{*this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this,
*this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this,
*this, *this, *this, *this, *this, *this, *this, *this, *this, *this}
, m_sermode(sermode_t())
, m_bclk(clock_pulse_t<s8>(4, 0))
, m_lrclk(clock_pulse_t<s8>(16, 1))
, m_wclk(0)
, m_wclk_lr(false)
, m_output_bit(0)
, m_ch{output_t()}
, m_output{output_t()}
, m_output_temp{output_t()}
, m_output_latch{output_t()}
{
}
// host interface
u16 host_r(u8 address);
void host_w(u8 address, u16 data);
// internal state
virtual void reset() override;
virtual void tick() override;
// less cycle accurate, but also less cpu heavy update routine
void tick_perf();
// clock outputs
inline bool bclk() { return m_bclk.current_edge(); }
inline bool bclk_rising_edge() { return m_bclk.rising_edge(); }
inline bool bclk_falling_edge() { return m_bclk.falling_edge(); }
// Input mode for Channel 3
inline void lin(s32 in)
{
if (m_sermode.adc())
{
m_ch[3].set_left(in);
}
}
inline void rin(s32 in)
{
if (m_sermode.adc())
{
m_ch[3].set_right(in);
}
}
// 4 stereo output channels
inline s32 lout(u8 ch) { return m_ch[ch & 0x3].left(); }
inline s32 rout(u8 ch) { return m_ch[ch & 0x3].right(); }
//-----------------------------------------------------------------
//
// for preview/debug purpose only, not for serious emulators
//
//-----------------------------------------------------------------
// bypass chips host interface for debug purpose only
u16 read(u8 address, bool cpu_access = false);
void write(u8 address, u16 data, bool cpu_access = false);
u16 regs_r(u8 page, u8 address, bool cpu_access = false);
void regs_w(u8 page, u8 address, u16 data, bool cpu_access = false);
u16 regs_r(u8 page, u8 address)
{
u8 prev = m_page;
m_page = page;
u16 ret = read(address, false);
m_page = prev;
return ret;
}
// per-voice outputs
inline s32 voice_lout(u8 voice) { return (voice < 32) ? m_voice[voice].ch().left() : 0; }
inline s32 voice_rout(u8 voice) { return (voice < 32) ? m_voice[voice].ch().right() : 0; }
protected:
virtual inline u8 max_voices() override { return 32; }
virtual void voice_tick() override;
private:
std::array<voice_t, 32> m_voice; // 32 voices
// Serial related stuffs
sermode_t m_sermode; // Serial mode register
clock_pulse_t<s8> m_bclk; // BCLK clock (CLKIN / 4), freely running clock
clock_pulse_t<s8> m_lrclk; // LRCLK
s16 m_wclk = 0; // WCLK
bool m_wclk_lr = false; // WCLK, L/R output select
s8 m_output_bit = 0; // Bit position in output
std::array<output_t, 4> m_ch; // 4 stereo output channels
std::array<output_t, 4> m_output; // Serial outputs
std::array<output_t, 4> m_output_temp; // temporary signal for serial output
std::array<output_t, 4> m_output_latch; // output latch
};
#endif

View file

@ -0,0 +1,870 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5506 emulation core
*/
#include "es5506.hpp"
// Internal functions
void es5506_core::tick()
{
m_voice_update = false;
m_voice_end = false;
// CLKIN
if (m_clkin.tick())
{
// BCLK
if (m_clkin.edge().changed() && (!m_mode.bclk_en())) // BCLK is freely running clock
{
if (m_bclk.tick())
{
m_intf.bclk(m_bclk.current_edge());
// Serial output
if (!m_mode.lrclk_en())
{
if (m_bclk.falling_edge())
{
// LRCLK
if (m_lrclk.tick())
{
m_intf.lrclk(m_lrclk.current_edge());
if (m_lrclk.rising_edge())
{
m_w_st_curr = m_w_st;
m_w_end_curr = m_w_end;
}
if (m_lrclk.falling_edge())
{ // update width
m_lrclk.set_width_latch(m_lr_end);
}
}
}
}
// WCLK
if (!m_mode.wclk_en())
{
if (!m_mode.lrclk_en())
{
if (m_lrclk.edge().changed())
{
m_wclk = 0;
}
}
if (m_bclk.falling_edge())
{
if (m_wclk == m_w_st_curr)
{
m_intf.wclk(true);
if (m_lrclk.current_edge())
{
for (int i = 0; i < 6; i++)
{
// copy output
m_output[i] = m_output_temp[i];
m_output_latch[i] = m_ch[i];
m_output_temp[i].reset();
// clamp to 20 bit (upper 3 bits are
// overflow guard bits)
m_output_latch[i].clamp20();
// set signed
if (m_output_latch[i].left() < 0)
{
m_output_temp[i].set_left(-1);
}
if (m_output_latch[i].right() < 0)
{
m_output_temp[i].set_right(-1);
}
}
}
m_wclk_lr = m_lrclk.current_edge();
m_output_bit = 20;
}
if (m_wclk < m_w_end_curr)
{
s8 output_bit = --m_output_bit;
if (m_output_bit >= 0)
{
for (int i = 0; i < 6; i++)
{
if (m_wclk_lr)
{
// Right output
m_output_temp[i].serial_in(
m_wclk_lr,
bitfield(m_output_latch[i].right(), output_bit));
}
else
{
// Left output
m_output_temp[i].serial_in(
m_wclk_lr,
bitfield(m_output_latch[i].left(), output_bit));
}
}
}
}
if (m_wclk == m_w_end_curr)
{
m_intf.wclk(false);
}
m_wclk++;
}
}
}
}
// /CAS, E
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
{
// /CAS
if (m_cas.tick())
{
// single OTTO master mode, /CAS high, E low: get sample address
// single OTTO early mode, /CAS falling, E high: get sample
// address
if (m_cas.falling_edge())
{
if (!m_e.current_edge())
{
// single OTTO master mode, /CAS low, E low: fetch
// sample
if (m_mode.master())
{
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
else if (m_e.current_edge())
{
// dual OTTO slave mode, /CAS low, E high: fetch sample
if (m_mode.dual() && (!m_mode.master()))
{ // Dual OTTO, slave mode
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
}
}
// E
if (m_e.tick())
{
m_intf.e_pin(m_e.current_edge());
if (m_e.rising_edge())
{
m_host_intf.update_strobe();
}
else if (m_e.falling_edge())
{
m_host_intf.clear_host_access();
voice_tick();
}
if (m_e.current_edge()) // Host interface
{
if (m_host_intf.host_access())
{
if (m_host_intf.rw() && (m_e.cycle() == 0)) // Read
{
m_hd = read(m_ha);
m_host_intf.clear_host_access();
}
else if ((!m_host_intf.rw()) && (m_e.cycle() == 2))
{ // Write
write(m_ha, m_hd);
}
}
}
else if (!m_e.current_edge())
{
if (m_e.cycle() == 2)
{
// reset host access state
m_hd = 0;
m_host_intf.clear_strobe();
}
}
}
}
}
}
// less cycle accurate, but less CPU heavy routine
void es5506_core::tick_perf()
{
m_voice_update = false;
m_voice_end = false;
// output
if (((!m_mode.lrclk_en()) && (!m_mode.bclk_en()) && (!m_mode.wclk_en())) && (m_w_st < m_w_end))
{
const int output_bits = 20 - (m_w_end - m_w_st);
if (output_bits < 20)
{
for (int c = 0; c < 6; c++)
{
m_output[c] = m_ch[c] >> output_bits;
}
}
}
else
{
for (int c = 0; c < 6; c++)
{
m_output[c] = 0;
}
}
// update
// falling edge
m_e.edge().set(false);
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
m_intf.e_pin(true);
m_host_intf.update_strobe();
// falling edge
m_e.edge().set(false);
m_intf.e_pin(false);
m_host_intf.clear_host_access();
m_host_intf.clear_strobe();
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.edge().set(true);
m_intf.e_pin(true);
m_host_intf.update_strobe();
}
void es5506_core::voice_tick()
{
// Voice updates every 2 E clock cycle (or 4 BCLK clock cycle)
m_voice_update = bitfield(m_voice_fetch++, 0);
if (m_voice_update)
{
// Update voice
m_voice[m_voice_cycle].tick(m_voice_cycle);
// Refresh output
if ((++m_voice_cycle) > clamp<u8>(m_active, 4, 31)) // 5 ~ 32 voices
{
m_voice_end = true;
m_voice_cycle = 0;
for (output_t &elem : m_ch)
{
elem.reset();
}
for (voice_t &elem : m_voice)
{
const u8 ca = bitfield<u8>(elem.cr().ca(), 0, 3);
if (ca < 6)
{
m_ch[ca] += elem.ch();
}
elem.ch().reset();
}
}
m_voice_fetch = 0;
}
}
void es5506_core::voice_t::fetch(u8 voice, u8 cycle)
{
m_alu.set_sample(
cycle,
m_host.m_intf.read_sample(voice,
m_cr.bs(),
bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer)));
if (m_cr.cmpd())
{ // Decompress (Upper 8 bit is used for compressed format)
m_alu.set_sample(cycle, decompress(bitfield(m_alu.sample(cycle), 8, 8)));
}
}
void es5506_core::voice_t::tick(u8 voice)
{
m_ch.reset();
// Filter execute
m_filter.tick(m_alu.interpolation());
if (m_alu.busy())
{
// Send to output
m_ch.set_left(volume_calc(m_lvol, sign_ext<s32>(m_filter.o4_1(), 16)));
m_ch.set_right(volume_calc(m_rvol, sign_ext<s32>(m_filter.o4_1(), 16)));
// ALU execute
if (m_alu.tick())
{
m_alu.loop_exec();
}
}
// Envelope
if (m_ecount != 0)
{
// Left and Right volume
if (bitfield(m_lvramp, 0, 8) != 0)
{
m_lvol = clamp<s32>(m_lvol + sign_ext<s32>(bitfield(m_lvramp, 0, 8), 8), 0, 0xffff);
}
if (bitfield(m_rvramp, 0, 8) != 0)
{
m_rvol = clamp<s32>(m_rvol + sign_ext<s32>(bitfield(m_rvramp, 0, 8), 8), 0, 0xffff);
}
// Filter coeffcient
if ((m_k1ramp.ramp() != 0) &&
((m_k1ramp.slow() == 0) || (bitfield(m_filtcount, 0, 3) == 0)))
{
m_filter.set_k1(
clamp<s32>(m_filter.k1() + sign_ext<s32>(m_k1ramp.ramp(), 8), 0, 0xffff));
}
if ((m_k2ramp.ramp() != 0) &&
((m_k2ramp.slow() == 0) || (bitfield(m_filtcount, 0, 3) == 0)))
{
m_filter.set_k2(
clamp<s32>(m_filter.k2() + sign_ext<s32>(m_k2ramp.ramp(), 8), 0, 0xffff));
}
m_ecount--;
}
m_filtcount = bitfield(m_filtcount + 1, 0, 3);
// Update IRQ
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
}
// Compressed format
s16 es5506_core::voice_t::decompress(u8 sample)
{
u8 exponent = bitfield(sample, 5, 3);
u8 mantissa = bitfield(sample, 0, 5);
return (exponent > 0)
? s16(((bitfield(mantissa, 4) ? 0x10 : ~0x1f) | bitfield(mantissa, 0, 4))
<< (4 + (exponent - 1)))
: s16(((bitfield(mantissa, 4) ? ~0xf : 0) | bitfield(mantissa, 0, 4)) << 4);
}
// volume calculation
s32 es5506_core::voice_t::volume_calc(u16 volume, s32 in)
{
u8 exponent = bitfield(volume, 12, 4);
u8 mantissa = bitfield(volume, 4, 8);
return (in * s32(0x100 | mantissa)) >> (20 - exponent);
}
void es5506_core::reset()
{
es550x_shared_core::reset();
for (auto &elem : m_voice)
{
elem.reset();
}
m_read_latch = 0xffffffff;
m_write_latch = 0xffffffff;
m_w_st = 0;
m_w_end = 0;
m_lr_end = 0;
m_w_st_curr = 0;
m_w_end_curr = 0;
m_mode.reset();
m_bclk.reset();
m_lrclk.reset(32);
m_wclk = 0;
m_wclk_lr = false;
m_output_bit = 0;
for (auto &elem : m_ch)
{
elem.reset();
}
for (auto &elem : m_output)
{
elem.reset();
}
for (auto &elem : m_output_temp)
{
elem.reset();
}
for (auto &elem : m_output_latch)
{
elem.reset();
}
}
void es5506_core::voice_t::reset()
{
es550x_shared_core::es550x_voice_t::reset();
m_lvol = 0;
m_rvol = 0;
m_lvramp = 0;
m_rvramp = 0;
m_ecount = 0;
m_k2ramp.reset();
m_k1ramp.reset();
m_filtcount = 0;
m_ch.reset();
m_mute = false;
}
// Accessors
u8 es5506_core::host_r(u8 address)
{
if (!m_host_intf.host_access())
{
m_ha = address;
if (m_e.rising_edge())
{ // update directly
m_hd = read(m_ha, true);
}
else
{
m_host_intf.set_strobe(true);
}
}
return m_hd;
}
void es5506_core::host_w(u8 address, u8 data)
{
if (!m_host_intf.host_access())
{
m_ha = address;
m_hd = data;
if (m_e.rising_edge())
{ // update directly
write(m_ha, m_hd, true);
}
else
{
m_host_intf.set_strobe(false);
}
}
}
u8 es5506_core::read(u8 address, bool cpu_access)
{
const u8 byte = bitfield(address, 0, 2); // byte select
const u8 shift = 24 - (byte << 3);
if (byte != 0)
{ // Return already latched register if not highest byte is accessing
return bitfield(m_read_latch, shift, 8);
}
address = bitfield(address, 2, 4); // 4 bit address for CPU access
// get read register
m_read_latch = regs_r(m_page, address, cpu_access);
return bitfield(m_read_latch, 24, 8);
}
void es5506_core::write(u8 address, u8 data, bool cpu_access)
{
const u8 byte = bitfield(address, 0, 2); // byte select
const u8 shift = 24 - (byte << 3);
address = bitfield(address, 2, 4); // 4 bit address for CPU access
// Update register latch
m_write_latch = (m_write_latch & ~(0xff << shift)) | (u32(data) << shift);
if (byte != 3)
{ // Wait until lowest byte is writed
return;
}
regs_w(m_page, address, m_write_latch, cpu_access);
// Reset latch
m_write_latch = 0;
}
u32 es5506_core::regs_r(u8 page, u8 address, bool cpu_access)
{
u32 read_latch = 0xffffffff;
// Global registers
if (address >= 13)
{
switch (address)
{
case 13: // POT (Pot A/D Register)
read_latch = (read_latch & ~0x3ff) | bitfield(m_intf.adc_r(), 0, 10);
break;
case 14: // IRQV (Interrupting voice vector)
read_latch = (read_latch & ~0x9f) | (m_irqv.irqb() ? 0x80 : 0) |
bitfield(m_irqv.voice(), 0, 5);
if (cpu_access)
{
m_irqv.clear();
if (bool(bitfield(read_latch, 7)) != m_irqv.irqb())
{
m_voice[m_irqv.voice()].irq_update(m_intf, m_irqv);
}
}
break;
case 15: // PAGE (Page select register)
read_latch = (read_latch & ~0x7f) | bitfield(m_page, 0, 7);
break;
}
}
else
{
// Channel registers are Write only
if (bitfield(page, 6))
{
if (!cpu_access) // CPU can't read here
{
switch (address)
{
case 0: // CH0L (Channel 0 Left)
case 2: // CH1L (Channel 1 Left)
case 4: // CH2L (Channel 2 Left)
case 6: // CH3L (Channel 3 Left)
case 8: // CH4L (Channel 4 Left)
case 10: // CH5L (Channel 5 Left)
read_latch = m_ch[bitfield(address, 1, 3)].left();
break;
case 1: // CH0R (Channel 0 Right)
case 3: // CH1R (Channel 1 Right)
case 5: // CH2R (Channel 2 Right)
case 7: // CH3R (Channel 3 Right)
case 9: // CH4R (Channel 4 Right)
case 11: // CH5R (Channel 5 Right)
read_latch = m_ch[bitfield(address, 1, 3)].right();
break;
}
}
}
else
{
const u8 voice = bitfield(page, 0, 5); // Voice select
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 63
{
switch (address)
{
case 0: // CR (Control Register)
read_latch = (read_latch & ~0xffff) | (v.alu().stop() << 0) |
(v.alu().lei() ? 0x0004 : 0x0000) | (v.alu().loop() << 3) |
(v.alu().irqe() ? 0x0020 : 0x0000) |
(v.alu().dir() ? 0x0040 : 0x0000) |
(v.alu().irq() ? 0x0080 : 0x0000) |
(bitfield(v.filter().lp(), 0, 2) << 8) | (v.cr().ca() << 10) |
(v.cr().cmpd() ? 0x2000 : 0x0000) | (v.cr().bs() << 14);
break;
case 1: // START (Loop Start Register)
read_latch = (read_latch & ~0xfffff800) | (v.alu().start() & 0xfffff800);
break;
case 2: // END (Loop End Register)
read_latch = (read_latch & ~0xffffff80) | (v.alu().end() & 0xffffff80);
break;
case 3: // ACCUM (Accumulator Register)
read_latch = v.alu().accum();
break;
case 4: // O4(n-1) (Filter 4 Temp Register)
if (cpu_access)
{
read_latch =
(read_latch & ~0x3ffff) | bitfield(v.filter().o4_1(), 0, 18);
}
else
{
read_latch = v.filter().o4_1();
}
break;
case 5: // O3(n-2) (Filter 3 Temp Register #2)
if (cpu_access)
{
read_latch =
(read_latch & ~0x3ffff) | bitfield(v.filter().o3_2(), 0, 18);
}
else
{
read_latch = v.filter().o3_2();
}
break;
case 6: // O3(n-1) (Filter 3 Temp Register #1)
if (cpu_access)
{
read_latch =
(read_latch & ~0x3ffff) | bitfield(v.filter().o3_1(), 0, 18);
}
else
{
read_latch = v.filter().o3_1();
}
break;
case 7: // O2(n-2) (Filter 2 Temp Register #2)
if (cpu_access)
{
read_latch =
(read_latch & ~0x3ffff) | bitfield(v.filter().o2_2(), 0, 18);
}
else
{
read_latch = v.filter().o2_2();
}
break;
case 8: // O2(n-1) (Filter 2 Temp Register #1)
if (cpu_access)
{
read_latch =
(read_latch & ~0x3ffff) | bitfield(v.filter().o2_1(), 0, 18);
}
else
{
read_latch = v.filter().o2_1();
}
break;
case 9: // O1(n-1) (Filter 1 Temp Register)
if (cpu_access)
{
read_latch =
(read_latch & ~0x3ffff) | bitfield(v.filter().o1_1(), 0, 18);
}
else
{
read_latch = v.filter().o1_1();
}
break;
case 10: // W_ST (Word Clock Start Register)
read_latch = (read_latch & ~0x7f) | bitfield(m_w_st, 0, 7);
break;
case 11: // W_END (Word Clock End Register)
read_latch = (read_latch & ~0x7f) | bitfield(m_w_end, 0, 7);
break;
case 12: // LR_END (Left/Right Clock End Register)
read_latch = (read_latch & ~0x7f) | bitfield(m_lr_end, 0, 7);
break;
}
}
else // Page 0 - 31
{
switch (address)
{
case 0: // CR (Control Register)
read_latch = (read_latch & ~0xffff) | (v.alu().stop() << 0) |
(v.alu().lei() ? 0x0004 : 0x0000) | (v.alu().loop() << 3) |
(v.alu().irqe() ? 0x0020 : 0x0000) |
(v.alu().dir() ? 0x0040 : 0x0000) |
(v.alu().irq() ? 0x0080 : 0x0000) |
(bitfield(v.filter().lp(), 0, 2) << 8) | (v.cr().ca() << 10) |
(v.cr().cmpd() ? 0x2000 : 0x0000) | (v.cr().bs() << 14);
break;
case 1: // FC (Frequency Control)
read_latch = (read_latch & ~0x1ffff) | bitfield(v.alu().fc(), 0, 17);
break;
case 2: // LVOL (Left Volume)
read_latch = (read_latch & ~0xffff) | bitfield(v.lvol(), 0, 16);
break;
case 3: // LVRAMP (Left Volume Ramp)
read_latch = (read_latch & ~0xff00) | (bitfield(v.lvramp(), 0, 8) << 8);
break;
case 4: // RVOL (Right Volume)
read_latch = (read_latch & ~0xffff) | bitfield(v.rvol(), 0, 16);
break;
case 5: // RVRAMP (Right Volume Ramp)
read_latch = (read_latch & ~0xff00) | (bitfield(v.rvramp(), 0, 8) << 8);
break;
case 6: // ECOUNT (Envelope Counter)
read_latch = (read_latch & ~0x01ff) | bitfield(v.ecount(), 0, 9);
break;
case 7: // K2 (Filter Cutoff Coefficient #2)
read_latch = (read_latch & ~0xffff) | bitfield(v.filter().k2(), 0, 16);
break;
case 8: // K2RAMP (Filter Cutoff Coefficient #2 Ramp)
read_latch = (read_latch & ~0xff01) |
(bitfield(v.k2ramp().ramp(), 0, 8) << 8) |
(v.k2ramp().slow() ? 0x0001 : 0x0000);
break;
case 9: // K1 (Filter Cutoff Coefficient #1)
read_latch = (read_latch & ~0xffff) | bitfield(v.filter().k1(), 0, 16);
break;
case 10: // K1RAMP (Filter Cutoff Coefficient #1 Ramp)
read_latch = (read_latch & ~0xff01) |
(bitfield(v.k1ramp().ramp(), 0, 8) << 8) |
(v.k1ramp().slow() ? 0x0001 : 0x0000);
break;
case 11: // ACT (Number of voices)
read_latch = (read_latch & ~0x1f) | bitfield(m_active, 0, 5);
break;
case 12: // MODE (Global Mode)
read_latch =
(read_latch & ~0x1f) | (m_mode.lrclk_en() ? 0x01 : 0x00) |
(m_mode.wclk_en() ? 0x02 : 0x00) | (m_mode.bclk_en() ? 0x04 : 0x00) |
(m_mode.master() ? 0x08 : 0x00) | (m_mode.dual() ? 0x10 : 0x00);
break;
}
}
}
}
return read_latch;
}
void es5506_core::regs_w(u8 page, u8 address, u32 data, bool cpu_access)
{
// Global registers
if (address >= 13)
{
switch (address)
{
case 13: // POT (Pot A/D Register)
// Read only
break;
case 14: // IRQV (Interrupting voice vector)
// Read only
break;
case 15: // PAGE (Page select register)
m_page = bitfield(data, 0, 7);
break;
}
}
else
{
// Channel registers are Write only, and for test purposes
if (bitfield(page, 6))
{
switch (address)
{
case 0: // CH0L (Channel 0 Left)
case 2: // CH1L (Channel 1 Left)
case 4: // CH2L (Channel 2 Left)
case 6: // CH3L (Channel 3 Left)
case 8: // CH4L (Channel 4 Left)
case 10: // CH5L (Channel 5 Left)
m_ch[bitfield(address, 1, 3)].set_left(
sign_ext<s32>(bitfield(data, 0, 23), 23));
break;
case 1: // CH0R (Channel 0 Right)
case 3: // CH1R (Channel 1 Right)
case 5: // CH2R (Channel 2 Right)
case 7: // CH3R (Channel 3 Right)
case 9: // CH4R (Channel 4 Right)
case 11: // CH5R (Channel 5 Right)
m_ch[bitfield(address, 1, 3)].set_right(
sign_ext<s32>(bitfield(data, 0, 23), 23));
break;
}
}
else
{
const u8 voice = bitfield(page, 0, 5); // Voice select
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 63
{
switch (address)
{
case 0: // CR (Control Register)
v.alu().set_stop(bitfield(data, 0, 2));
v.alu().set_lei(bitfield(data, 2));
v.alu().set_loop(bitfield(data, 3, 2));
v.alu().set_irqe(bitfield(data, 5));
v.alu().set_dir(bitfield(data, 6));
v.alu().set_irq(bitfield(data, 7));
v.filter().set_lp(bitfield(data, 8, 2));
v.cr().set_ca(std::min<u8>(5, bitfield(data, 10, 3)));
v.cr().set_cmpd(bitfield(data, 13));
v.cr().set_bs(bitfield(data, 14, 2));
break;
case 1: // START (Loop Start Register)
v.alu().set_start(data & 0xfffff800);
break;
case 2: // END (Loop End Register)
v.alu().set_end(data & 0xffffff80);
break;
case 3: // ACCUM (Accumulator Register)
v.alu().set_accum(data);
break;
case 4: // O4(n-1) (Filter 4 Temp Register)
v.filter().set_o4_1(sign_ext<s32>(bitfield(data, 0, 18), 18));
break;
case 5: // O3(n-2) (Filter 3 Temp Register #2)
v.filter().set_o3_2(sign_ext<s32>(bitfield(data, 0, 18), 18));
break;
case 6: // O3(n-1) (Filter 3 Temp Register #1)
v.filter().set_o3_1(sign_ext<s32>(bitfield(data, 0, 18), 18));
break;
case 7: // O2(n-2) (Filter 2 Temp Register #2)
v.filter().set_o2_2(sign_ext<s32>(bitfield(data, 0, 18), 18));
break;
case 8: // O2(n-1) (Filter 2 Temp Register #1)
v.filter().set_o2_1(sign_ext<s32>(bitfield(data, 0, 18), 18));
break;
case 9: // O1(n-1) (Filter 1 Temp Register)
v.filter().set_o1_1(sign_ext<s32>(bitfield(data, 0, 18), 18));
break;
case 10: // W_ST (Word Clock Start Register)
m_w_st = bitfield(data, 0, 7);
break;
case 11: // W_END (Word Clock End Register)
m_w_end = bitfield(data, 0, 7);
break;
case 12: // LR_END (Left/Right Clock End Register)
m_lr_end = bitfield(data, 0, 7);
m_lrclk.set_width(m_lr_end);
break;
}
}
else // Page 0 - 31
{
switch (address)
{
case 0: // CR (Control Register)
v.alu().set_stop(bitfield(data, 0, 2));
v.alu().set_lei(bitfield(data, 2));
v.alu().set_loop(bitfield(data, 3, 2));
v.alu().set_irqe(bitfield(data, 5));
v.alu().set_dir(bitfield(data, 6));
v.alu().set_irq(bitfield(data, 7));
v.filter().set_lp(bitfield(data, 8, 2));
v.cr().set_ca(std::min<u8>(5, bitfield(data, 10, 3)));
v.cr().set_cmpd(bitfield(data, 13));
v.cr().set_bs(bitfield(data, 14, 2));
break;
case 1: // FC (Frequency Control)
v.alu().set_fc(bitfield(data, 0, 17));
break;
case 2: // LVOL (Left Volume)
v.set_lvol(bitfield(data, 0, 16));
break;
case 3: // LVRAMP (Left Volume Ramp)
v.set_lvramp(bitfield(data, 8, 8));
break;
case 4: // RVOL (Right Volume)
v.set_rvol(bitfield(data, 0, 16));
break;
case 5: // RVRAMP (Right Volume Ramp)
v.set_rvramp(bitfield(data, 8, 8));
break;
case 6: // ECOUNT (Envelope Counter)
v.set_ecount(bitfield(data, 0, 9));
break;
case 7: // K2 (Filter Cutoff Coefficient #2)
v.filter().set_k2(bitfield(data, 0, 16));
break;
case 8: // K2RAMP (Filter Cutoff Coefficient #2 Ramp)
v.k2ramp().write(data);
break;
case 9: // K1 (Filter Cutoff Coefficient #1)
v.filter().set_k1(bitfield(data, 0, 16));
break;
case 10: // K1RAMP (Filter Cutoff Coefficient #1 Ramp)
v.k1ramp().write(data);
break;
case 11: // ACT (Number of voices)
m_active = std::max<u8>(4, bitfield(data, 0, 5));
break;
case 12: // MODE (Global Mode)
m_mode.write(data);
break;
}
}
}
}
}

View file

@ -0,0 +1,385 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5506 emulation core
*/
#ifndef _VGSOUND_EMU_SRC_ES5506_HPP
#define _VGSOUND_EMU_SRC_ES5506_HPP
#pragma once
#include "es550x.hpp"
// ES5506 specific
class es5506_core : public es550x_shared_core
{
private:
class output_t : public vgsound_emu_core
{
public:
output_t(s32 left = 0, s32 right = 0)
: vgsound_emu_core("es5506_output")
, m_left(left)
, m_right(right)
{
}
void reset()
{
m_left = 0;
m_right = 0;
};
inline s32 clamp20(s32 in) { return clamp(in, -0x80000, 0x7ffff); }
inline void clamp20(output_t src)
{
m_left = clamp20(src.left());
m_right = clamp20(src.right());
}
inline void clamp20()
{
m_left = clamp20(m_left);
m_right = clamp20(m_right);
}
// setters
inline void set_left(s32 left) { m_left = clamp20(left); }
inline void set_right(s32 right) { m_right = clamp20(right); }
void serial_in(bool ch, u8 in)
{
if (ch) // Right output
{
m_right = (m_right << 1) | (in ? 1 : 0);
}
else // Left output
{
m_left = (m_left << 1) | (in ? 1 : 0);
}
}
// getters
inline u32 left() { return m_left; }
inline u32 right() { return m_right; }
output_t &operator+=(output_t &src)
{
m_left = clamp20(m_left + src.left());
m_right = clamp20(m_right + src.right());
return *this;
}
output_t &operator=(output_t src)
{
clamp20(src);
return *this;
}
output_t &operator=(s32 val)
{
m_left = m_right = clamp20(val);
return *this;
}
output_t &operator>>(s32 shift)
{
m_left >>= shift;
m_right >>= shift;
return *this;
}
private:
s32 m_left = 0;
s32 m_right = 0;
};
// es5506 voice classes
class voice_t : public es550x_voice_t
{
private:
// es5506 Filter ramp class
class filter_ramp_t : public vgsound_emu_core
{
public:
filter_ramp_t()
: vgsound_emu_core("es5506_filter_ramp")
, m_slow(0)
, m_ramp(0)
{
}
void reset()
{
m_slow = 0;
m_ramp = 0;
};
// Setters
inline void write(u16 data)
{
m_slow = data & 1;
m_ramp = (data >> 8) & 0xff;
}
// Getters
inline bool slow() { return m_slow; }
inline u16 ramp() { return m_ramp; }
private:
u16 m_slow : 1; // Slow mode flag
u16 m_ramp = 8; // Ramp value
};
public:
// constructor
voice_t(es5506_core &host)
: es550x_voice_t("es5506_voice", 21, 11, true)
, m_host(host)
, m_lvol(0)
, m_rvol(0)
, m_lvramp(0)
, m_rvramp(0)
, m_ecount(0)
, m_k2ramp(filter_ramp_t())
, m_k1ramp(filter_ramp_t())
, m_filtcount(0)
, m_ch(output_t())
, m_mute(false)
{
}
// internal state
virtual void reset() override;
virtual void fetch(u8 voice, u8 cycle) override;
virtual void tick(u8 voice) override;
// Setters
inline void set_lvol(s32 lvol) { m_lvol = lvol; }
inline void set_rvol(s32 rvol) { m_rvol = rvol; }
inline void set_lvramp(s32 lvramp) { m_lvramp = lvramp; }
inline void set_rvramp(s32 rvramp) { m_rvramp = rvramp; }
inline void set_ecount(s16 ecount) { m_ecount = ecount; }
// Getters
inline s32 lvol() { return m_lvol; }
inline s32 rvol() { return m_rvol; }
inline s32 lvramp() { return m_lvramp; }
inline s32 rvramp() { return m_rvramp; }
inline s16 ecount() { return m_ecount; }
inline filter_ramp_t &k2ramp() { return m_k2ramp; }
inline filter_ramp_t &k1ramp() { return m_k1ramp; }
output_t &ch() { return m_ch; }
// for debug/preview only
inline void set_mute(bool mute) { m_mute = mute; }
inline s32 left_out() { return m_mute ? 0 : m_ch.left(); }
inline s32 right_out() { return m_mute ? 0 : m_ch.right(); }
private:
// accessors, getters, setters
s16 decompress(u8 sample);
s32 volume_calc(u16 volume, s32 in);
// registers
es5506_core &m_host;
// Volume register: 4 bit exponent, 8 bit mantissa
// 4 LSBs are used for fine control of ramp increment for hardware envelope
s32 m_lvol = 0; // Left volume
s32 m_rvol = 0; // Right volume
// Envelope
s32 m_lvramp = 0; // Left volume ramp
s32 m_rvramp = 0; // Righr volume ramp
s16 m_ecount = 0; // Envelope counter
filter_ramp_t m_k2ramp; // Filter coefficient 2 Ramp
filter_ramp_t m_k1ramp; // Filter coefficient 1 Ramp
u8 m_filtcount = 0; // Internal counter for slow mode
output_t m_ch; // channel output
bool m_mute = false; // mute flag (for debug purpose)
};
// 5 bit mode
class mode_t : public vgsound_emu_core
{
public:
mode_t()
: vgsound_emu_core("es5506_mode")
, m_lrclk_en(1)
, m_wclk_en(1)
, m_bclk_en(1)
, m_master(0)
, m_dual(0)
{
}
// internal states
void reset()
{
m_lrclk_en = 1;
m_wclk_en = 1;
m_bclk_en = 1;
m_master = 0;
m_dual = 0;
}
// accessors
void write(u8 data)
{
m_lrclk_en = (data >> 0) & 1;
m_wclk_en = (data >> 1) & 1;
m_bclk_en = (data >> 2) & 1;
m_master = (data >> 3) & 1;
m_dual = (data >> 4) & 1;
}
// getters
bool lrclk_en() { return m_lrclk_en; }
bool wclk_en() { return m_wclk_en; }
bool bclk_en() { return m_bclk_en; }
bool master() { return m_master; }
bool dual() { return m_dual; }
private:
u8 m_lrclk_en : 1; // Set LRCLK to output
u8 m_wclk_en : 1; // Set WCLK to output
u8 m_bclk_en : 1; // Set BCLK to output
u8 m_master : 1; // Set memory mode to master
u8 m_dual : 1; // Set dual chip config
};
public:
// constructor
es5506_core(es550x_intf &intf)
: es550x_shared_core("es5506", 32, intf)
, m_voice{*this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this,
*this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this,
*this, *this, *this, *this, *this, *this, *this, *this, *this, *this}
, m_read_latch(0)
, m_write_latch(0)
, m_w_st(0)
, m_w_end(0)
, m_lr_end(0)
, m_mode(mode_t())
, m_w_st_curr(0)
, m_w_end_curr(0)
, m_bclk(clock_pulse_t<s8>(4, 0))
, m_lrclk(clock_pulse_t<s8>(32, 1))
, m_wclk(0)
, m_wclk_lr(false)
, m_output_bit(0)
, m_ch{output_t()}
, m_output{output_t()}
, m_output_temp{output_t()}
, m_output_latch{output_t()}
{
}
// host interface
u8 host_r(u8 address);
void host_w(u8 address, u8 data);
// internal state
virtual void reset() override;
virtual void tick() override;
// less cycle accurate, but also less cpu heavy update routine
void tick_perf();
// clock outputs
inline bool bclk() { return m_bclk.current_edge(); }
inline bool bclk_rising_edge() { return m_bclk.rising_edge(); }
inline bool bclk_falling_edge() { return m_bclk.falling_edge(); }
// 6 stereo output channels
inline s32 lout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].left(); }
inline s32 rout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].right(); }
//-----------------------------------------------------------------
//
// for preview/debug purpose only, not for serious emulators
//
//-----------------------------------------------------------------
// bypass chips host interface for debug purpose only
u8 read(u8 address, bool cpu_access = false);
void write(u8 address, u8 data, bool cpu_access = false);
u32 regs_r(u8 page, u8 address, bool cpu_access = false);
void regs_w(u8 page, u8 address, u32 data, bool cpu_access = false);
u8 regs8_r(u8 page, u8 address)
{
u8 prev = m_page;
m_page = page;
u8 ret = read(address, false);
m_page = prev;
return ret;
}
inline void set_mute(u8 ch, bool mute) { m_voice[ch & 0x1f].set_mute(mute); }
// per-voice outputs
inline s32 voice_lout(u8 voice) { return (voice < 32) ? m_voice[voice].left_out() : 0; }
inline s32 voice_rout(u8 voice) { return (voice < 32) ? m_voice[voice].right_out() : 0; }
protected:
virtual inline u8 max_voices() override { return 32; }
virtual void voice_tick() override;
private:
std::array<voice_t, 32> m_voice; // 32 voices
// Host interfaces
u32 m_read_latch = 0; // 32 bit register latch for host read
u32 m_write_latch = 0; // 32 bit register latch for host write
// Serial register
u8 m_w_st = 0; // Word clock start register
u8 m_w_end = 0; // Word clock end register
u8 m_lr_end = 0; // Left/Right clock end register
mode_t m_mode; // Global mode
// Serial related stuffs
u8 m_w_st_curr = 0; // Word clock start, current status
u8 m_w_end_curr = 0; // Word clock end register
clock_pulse_t<s8> m_bclk; // BCLK clock (CLKIN / 4), freely running clock
clock_pulse_t<s8> m_lrclk; // LRCLK
s16 m_wclk = 0; // WCLK
bool m_wclk_lr = false; // WCLK, L/R output select
s8 m_output_bit = 0; // Bit position in output
std::array<output_t, 6> m_ch; // 6 stereo output channels
std::array<output_t, 6> m_output; // Serial outputs
std::array<output_t, 6> m_output_temp; // temporary signal for serial output
std::array<output_t, 6> m_output_latch; // output latch
};
#endif

View file

@ -0,0 +1,34 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 emulation core
*/
#include "es550x.hpp"
// Shared functions
void es550x_shared_core::reset()
{
m_host_intf.reset();
m_ha = 0;
m_hd = 0;
m_page = 0;
m_irqv.reset();
m_active = max_voices() - 1;
m_voice_cycle = 0;
m_voice_fetch = 0;
m_voice_update = false;
m_voice_end = false;
m_clkin.reset();
m_cas.reset();
m_e.reset();
}
void es550x_shared_core::es550x_voice_t::reset()
{
m_cr.reset();
m_alu.reset();
m_filter.reset();
}

View file

@ -0,0 +1,610 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 emulation core
*/
#ifndef _VGSOUND_EMU_SRC_ES550X_HPP
#define _VGSOUND_EMU_SRC_ES550X_HPP
#pragma once
#include "../core/util.hpp"
// ES5504/ES5505/ES5506 interface
class es550x_intf : public vgsound_emu_core
{
public:
es550x_intf()
: vgsound_emu_core("es550x_intf")
{
}
virtual void e_pin(bool state) {} // E output
virtual void bclk(bool state) {} // BCLK output (serial specific)
virtual void lrclk(bool state) {} // LRCLK output (serial specific)
virtual void wclk(bool state) {} // WCLK output (serial specific)
virtual void irqb(bool state) {} // irqb output
virtual u16 adc_r() { return 0; } // ADC input
virtual void adc_w(u16 data) {} // ADC output
virtual s16 read_sample(u8 voice, u8 bank, u32 address) { return 0; }
};
// Shared functions for ES5504/ES5505/ES5506
class es550x_shared_core : public vgsound_emu_core
{
friend class es550x_intf; // es550x specific memory interface
private:
const u8 m_max_voices = 32;
protected:
// Interrupt bits
class es550x_irq_t : public vgsound_emu_core
{
public:
es550x_irq_t()
: vgsound_emu_core("es550x_irq")
, m_voice(0)
, m_irqb(1)
{
}
void reset()
{
m_voice = 0;
m_irqb = 1;
}
// setter
void set(u8 index)
{
m_irqb = 0;
m_voice = index & 0x1f;
}
void clear()
{
m_irqb = 1;
m_voice = 0;
}
// getter
inline bool irqb() { return m_irqb; }
inline u8 voice() { return m_voice; }
inline u8 get() { return (m_irqb << 7) | (m_voice & 0x1f); }
u8 m_voice : 5;
u8 m_irqb : 1;
};
// Common voice class
class es550x_voice_t : public vgsound_emu_core
{
private:
// Common control bits
class es550x_control_t : public vgsound_emu_core
{
public:
es550x_control_t()
: vgsound_emu_core("es550x_voice_control")
, m_ca(0)
, m_adc(0)
, m_bs(0)
, m_cmpd(0)
{
}
void reset()
{
m_ca = 0;
m_adc = 0;
m_bs = 0;
m_cmpd = 0;
}
// setters
inline void set_ca(u8 ca) { m_ca = ca & 0xf; }
inline void set_adc(bool adc) { m_adc = adc ? 1 : 0; }
inline void set_bs(u8 bs) { m_bs = bs & 0x3; }
inline void set_cmpd(bool cmpd) { m_cmpd = cmpd ? 1 : 0; }
// getters
inline u8 ca() { return m_ca; }
inline bool adc() { return m_adc; }
inline u8 bs() { return m_bs; }
inline bool cmpd() { return m_cmpd; }
protected:
// Channel assign -
// 4 bit (16 channel or Bank) for ES5504
// 2 bit (4 stereo channels) for ES5505
// 3 bit (6 stereo channels) for ES5506
u8 m_ca : 4;
// ES5504 Specific
u8 m_adc : 1; // Start ADC
// ES5505/ES5506 Specific
u8 m_bs : 2; // Bank bit (1 bit for ES5505, 2 bit for ES5506)
u8 m_cmpd : 1; // Use compressed sample format (ES5506)
};
// Accumulator
class es550x_alu_t : public vgsound_emu_core
{
public:
es550x_alu_t(u8 integer, u8 fraction, bool transwave)
: vgsound_emu_core("es550x_voice_alu")
, m_integer(integer)
, m_fraction(fraction)
, m_total_bits(integer + fraction)
, m_accum_mask(
u32(std::min<u64>(~0, u64(u64(1) << u64(integer + fraction)) - 1)))
, m_transwave(transwave)
, m_fc(0)
, m_start(0)
, m_end(0)
, m_accum(0)
, m_sample({0})
{
}
// configurations
const u8 m_integer;
const u8 m_fraction;
const u8 m_total_bits;
const u32 m_accum_mask;
const bool m_transwave;
// internal states
void reset();
bool tick();
void loop_exec();
bool busy();
s32 interpolation();
u32 get_accum_integer();
void irq_exec(es550x_intf &intf, es550x_irq_t &irqv, u8 index);
void irq_update(es550x_intf &intf, es550x_irq_t &irqv)
{
intf.irqb(irqv.irqb() ? false : true);
}
// setters
inline void set_stop0(bool stop0) { m_cr.set_stop0(stop0); }
inline void set_stop1(bool stop1) { m_cr.set_stop1(stop1); }
inline void set_lpe(bool lpe) { m_cr.set_lpe(lpe); }
inline void set_ble(bool ble) { m_cr.set_ble(ble); }
inline void set_irqe(bool irqe) { m_cr.set_irqe(irqe); }
inline void set_dir(bool dir) { m_cr.set_dir(dir); }
inline void set_irq(bool irq) { m_cr.set_irq(irq); }
inline void set_lei(bool lei) { m_cr.set_lei(lei); }
inline void set_stop(u8 stop) { m_cr.set_stop(stop); }
inline void set_loop(u8 loop) { m_cr.set_loop(loop); }
inline void set_fc(u32 fc) { m_fc = fc; }
inline void set_start(u32 start, u32 mask = ~0)
{
m_start = (m_start & ~mask) | (start & mask);
}
inline void set_end(u32 end, u32 mask = ~0)
{
m_end = (m_end & ~mask) | (end & mask);
}
inline void set_accum(u32 accum, u32 mask = ~0)
{
m_accum = (m_accum & ~mask) | (accum & mask);
}
inline void set_sample(u8 slot, s32 sample) { m_sample[slot & 1] = sample; }
// getters
inline bool stop0() { return m_cr.stop0(); }
inline bool stop1() { return m_cr.stop1(); }
inline bool lpe() { return m_cr.lpe(); }
inline bool ble() { return m_cr.ble(); }
inline bool irqe() { return m_cr.irqe(); }
inline bool dir() { return m_cr.dir(); }
inline bool irq() { return m_cr.irq(); }
inline bool lei() { return m_cr.lei(); }
inline u8 stop() { return m_cr.stop(); }
inline u8 loop() { return m_cr.loop(); }
inline u32 fc() { return m_fc; }
inline u32 start() { return m_start; }
inline u32 end() { return m_end; }
inline u32 accum() { return m_accum; }
inline s32 sample(u8 slot) { return m_sample[slot & 1]; }
private:
class es550x_alu_cr_t : public vgsound_emu_core
{
public:
es550x_alu_cr_t()
: vgsound_emu_core("es550x_voice_alu_cr")
, m_stop0(0)
, m_stop1(0)
, m_lpe(0)
, m_ble(0)
, m_irqe(0)
, m_dir(0)
, m_irq(0)
, m_lei(0)
{
}
void reset()
{
m_stop0 = 0;
m_stop1 = 0;
m_lpe = 0;
m_ble = 0;
m_irqe = 0;
m_dir = 0;
m_irq = 0;
m_lei = 0;
}
// setters
inline void set_stop0(bool stop0) { m_stop0 = stop0 ? 1 : 0; }
inline void set_stop1(bool stop1) { m_stop1 = stop1 ? 1 : 0; }
inline void set_lpe(bool lpe) { m_lpe = lpe ? 1 : 0; }
inline void set_ble(bool ble) { m_ble = ble ? 1 : 0; }
inline void set_irqe(bool irqe) { m_irqe = irqe ? 1 : 0; }
inline void set_dir(bool dir) { m_dir = dir ? 1 : 0; }
inline void set_irq(bool irq) { m_irq = irq ? 1 : 0; }
inline void set_lei(bool lei) { m_lei = lei ? 1 : 0; }
inline void set_stop(u8 stop)
{
m_stop0 = (stop >> 0) & 1;
m_stop1 = (stop >> 1) & 1;
}
inline void set_loop(u8 loop)
{
m_lpe = (loop >> 0) & 1;
m_ble = (loop >> 1) & 1;
}
// getters
inline bool stop0() { return m_stop0; }
inline bool stop1() { return m_stop1; }
inline bool lpe() { return m_lpe; }
inline bool ble() { return m_ble; }
inline bool irqe() { return m_irqe; }
inline bool dir() { return m_dir; }
inline bool irq() { return m_irq; }
inline bool lei() { return m_lei; }
inline u8 stop() { return (m_stop0 << 0) | (m_stop1 << 1); }
inline u8 loop() { return (m_lpe << 0) | (m_ble << 1); }
private:
u8 m_stop0 : 1; // Stop with ALU
u8 m_stop1 : 1; // Stop with processor
u8 m_lpe : 1; // Loop enable
u8 m_ble : 1; // Bidirectional loop enable
u8 m_irqe : 1; // IRQ enable
u8 m_dir : 1; // Playback direction
u8 m_irq : 1; // IRQ bit
u8 m_lei : 1; // Loop end ignore (ES5506 specific)
};
es550x_alu_cr_t m_cr;
// Frequency -
// 6 integer, 9 fraction for ES5504/ES5505
// 6 integer, 11 fraction for ES5506
u32 m_fc = 0;
u32 m_start = 0; // Start register
u32 m_end = 0; // End register
// Accumulator -
// 20 integer, 9 fraction for ES5504/ES5505
// 21 integer, 11 fraction for ES5506
u32 m_accum = 0;
// Samples
std::array<s32, 2> m_sample = {0};
};
// Filter
class es550x_filter_t : public vgsound_emu_core
{
public:
es550x_filter_t()
: vgsound_emu_core("es550x_voice_filter")
, m_lp(0)
, m_k2(0)
, m_k1(0)
{
for (std::array<s32, 2> &elem : m_o)
{
std::fill(elem.begin(), elem.end(), 0);
}
}
void reset();
void tick(s32 in);
// setters
inline void set_lp(u8 lp) { m_lp = lp & 3; }
inline void set_k2(s32 k2) { m_k2 = k2; }
inline void set_k1(s32 k1) { m_k1 = k1; }
inline void set_o1_1(s32 o1_1) { m_o[1][0] = o1_1; }
inline void set_o2_1(s32 o2_1) { m_o[2][0] = o2_1; }
inline void set_o2_2(s32 o2_2) { m_o[2][1] = o2_2; }
inline void set_o3_1(s32 o3_1) { m_o[3][0] = o3_1; }
inline void set_o3_2(s32 o3_2) { m_o[3][1] = o3_2; }
inline void set_o4_1(s32 o4_1) { m_o[4][0] = o4_1; }
// getters
inline u8 lp() { return m_lp; }
inline s32 k2() { return m_k2; }
inline s32 k1() { return m_k1; }
inline s32 o1_1() { return m_o[1][0]; }
inline s32 o2_1() { return m_o[2][0]; }
inline s32 o2_2() { return m_o[2][1]; }
inline s32 o3_1() { return m_o[3][0]; }
inline s32 o3_2() { return m_o[3][1]; }
inline s32 o4_1() { return m_o[4][0]; }
private:
void lp_exec(s32 coeff, s32 in, s32 out);
void hp_exec(s32 coeff, s32 in, s32 out);
// Registers
u8 m_lp = 0; // Filter mode
// Filter coefficient registers
// 12 bit for filter calculation, 4
// LSBs are used for fine control of ramp increment for
// hardware envelope (ES5506)
s32 m_k2 = 0; // Filter coefficient 2
s32 m_k1 = 0; // Filter coefficient 1
// Filter storage registers
std::array<std::array<s32, 2>, 5> m_o;
};
public:
es550x_voice_t(std::string tag, u8 integer, u8 fraction, bool transwave)
: vgsound_emu_core(tag)
, m_cr(es550x_control_t())
, m_alu(integer, fraction, transwave)
, m_filter(es550x_filter_t())
{
}
// internal state
virtual void reset();
virtual void fetch(u8 voice, u8 cycle) = 0;
virtual void tick(u8 voice) = 0;
void irq_update(es550x_intf &intf, es550x_irq_t &irqv)
{
m_alu.irq_update(intf, irqv);
}
// Getters
es550x_control_t &cr() { return m_cr; }
es550x_alu_t &alu() { return m_alu; }
es550x_filter_t &filter() { return m_filter; }
protected:
es550x_control_t m_cr;
es550x_alu_t m_alu;
es550x_filter_t m_filter;
};
// Host interfaces
class host_interface_flag_t : public vgsound_emu_core
{
public:
host_interface_flag_t()
: vgsound_emu_core("es550x_host_interface_flag")
, m_host_access(0)
, m_host_access_strobe(0)
, m_rw(0)
, m_rw_strobe(0)
{
}
// Accessors
void reset()
{
m_host_access = 0;
m_host_access_strobe = 0;
m_rw = 0;
m_rw_strobe = 0;
}
// Setters
void set_strobe(bool rw)
{
m_rw_strobe = rw ? 1 : 0;
m_host_access_strobe = 1;
}
void clear_strobe() { m_host_access_strobe = 0; }
void clear_host_access() { m_host_access = 0; }
void update_strobe()
{
m_rw = m_rw_strobe;
m_host_access = m_host_access_strobe;
}
// Getters
bool host_access() { return m_host_access; }
bool rw() { return m_rw; }
private:
u8 m_host_access : 1; // Host access trigger
u8 m_host_access_strobe : 1; // Host access strobe
u8 m_rw : 1; // R/W state
u8 m_rw_strobe : 1; // R/W strobe
};
public:
// internal state
virtual void reset();
virtual void tick() {}
// clock outputs
inline bool _cas() { return m_cas.current_edge(); }
inline bool _cas_rising_edge() { return m_cas.rising_edge(); }
inline bool _cas_falling_edge() { return m_cas.falling_edge(); }
inline bool e() { return m_e.current_edge(); }
inline bool e_rising_edge() { return m_e.rising_edge(); }
inline bool e_falling_edge() { return m_e.falling_edge(); }
//-----------------------------------------------------------------
//
// for preview/debug purpose only, not for serious emulators
//
//-----------------------------------------------------------------
// voice cycle
inline u8 voice_cycle() { return m_voice_cycle; }
// voice update flag
inline bool voice_update() { return m_voice_update; }
inline bool voice_end() { return m_voice_end; }
protected:
// constructor
es550x_shared_core(std::string tag, const u8 voice, es550x_intf &intf)
: vgsound_emu_core(tag)
, m_max_voices(voice)
, m_intf(intf)
, m_host_intf(host_interface_flag_t())
, m_ha(0)
, m_hd(0)
, m_page(0)
, m_irqv(es550x_irq_t())
, m_active(m_max_voices - 1)
, m_voice_cycle(0)
, m_voice_fetch(0)
, m_voice_update(0)
, m_voice_end(0)
, m_clkin(clock_pulse_t<s8>(1, 0))
, m_cas(clock_pulse_t<s8>(2, 1))
, m_e(clock_pulse_t<s8>(4, 0))
{
}
// Constants
virtual inline u8 max_voices() { return m_max_voices; }
// Shared registers, functions
virtual void voice_tick() {} // voice tick
es550x_intf &m_intf; // es550x specific memory interface
host_interface_flag_t m_host_intf; // Host interface flag
u8 m_ha = 0; // Host address (4 bit)
u16 m_hd = 0; // Host data (16 bit for ES5504/ES5505, 8 bit for ES5506)
u8 m_page = 0; // Page
es550x_irq_t m_irqv; // Voice interrupt vector registers
// Internal states
u8 m_active = 31; // Activated voices
// -1, ~25 for ES5504,
// ~32 for ES5505/ES5506
u8 m_voice_cycle = 0; // Voice cycle
u8 m_voice_fetch = 0; // Voice fetch cycle
bool m_voice_update = false; // Voice update flag
bool m_voice_end = false; // End of one voice cycle flag
clock_pulse_t<s8> m_clkin; // CLKIN clock
clock_pulse_t<s8> m_cas; // /CAS clock (CLKIN / 4), falling edge of
// CLKIN trigger this clock
clock_pulse_t<s8> m_e; // E clock (CLKIN / 8),
// falling edge of CLKIN trigger this clock
};
#endif

View file

@ -0,0 +1,131 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 Shared Accumulator emulation core
*/
#include "es550x.hpp"
// Accumulator functions
void es550x_shared_core::es550x_voice_t::es550x_alu_t::reset()
{
m_cr.reset();
m_fc = 0;
m_start = 0;
m_end = 0;
m_accum = 0;
m_sample[0] = m_sample[1] = 0;
}
bool es550x_shared_core::es550x_voice_t::es550x_alu_t::busy() { return m_cr.stop() == 0; }
bool es550x_shared_core::es550x_voice_t::es550x_alu_t::tick()
{
if (m_cr.dir())
{
m_accum -= m_fc;
}
else
{
m_accum += m_fc;
}
m_accum &= m_accum_mask;
return ((!m_cr.lei()) &&
(((m_cr.dir()) && (m_accum < m_start)) || ((!m_cr.dir()) && (m_accum > m_end))))
? true
: false;
}
void es550x_shared_core::es550x_voice_t::es550x_alu_t::loop_exec()
{
if (m_cr.irqe())
{ // Set IRQ
m_cr.set_irq(true);
}
if (m_cr.dir()) // Reverse playback
{
if (m_cr.lpe()) // Loop enable
{
if (m_cr.ble()) // Bidirectional
{
m_cr.set_dir(false);
m_accum = m_start + (m_start - m_accum);
}
else
{ // Normal
m_accum = m_end - (m_start - m_accum);
}
}
else if (m_cr.ble() && m_transwave) // m_transwave
{
m_cr.set_loop(0);
m_cr.set_lei(true); // Loop end ignore
m_accum = m_end - (m_start - m_accum);
}
else
{ // Stop
m_cr.set_stop0(true);
}
}
else
{
if (m_cr.lpe()) // Loop enable
{
if (m_cr.ble()) // Bidirectional
{
m_cr.set_dir(true);
m_accum = m_end - (m_end - m_accum);
}
else
{ // Normal
m_accum = (m_accum - m_end) + m_start;
}
}
else if (m_cr.ble() && m_transwave) // m_transwave
{
m_cr.set_loop(0);
m_cr.set_lei(true); // Loop end ignore
m_accum = (m_accum - m_end) + m_start;
}
else
{ // Stop
m_cr.set_stop0(true);
}
}
}
s32 es550x_shared_core::es550x_voice_t::es550x_alu_t::interpolation()
{
// SF = S1 + ACCfr * (S2 - S1)
return m_sample[0] + ((bitfield<s32>(m_accum, std::max<s8>(0, m_fraction - 9), 9) *
(m_sample[1] - m_sample[0])) >>
9);
}
u32 es550x_shared_core::es550x_voice_t::es550x_alu_t::get_accum_integer()
{
return bitfield(m_accum, m_fraction, m_integer);
}
void es550x_shared_core::es550x_voice_t::es550x_alu_t::irq_exec(es550x_intf &intf,
es550x_irq_t &irqv,
u8 index)
{
const bool prev = irqv.irqb();
if (m_cr.irq())
{
if (irqv.irqb())
{
irqv.set(index);
m_cr.set_irq(false);
}
}
if (prev != irqv.irqb())
{
irq_update(intf, irqv);
}
}

View file

@ -0,0 +1,72 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 Shared Filter emulation core
*/
#include "es550x.hpp"
// Filter functions
void es550x_shared_core::es550x_voice_t::es550x_filter_t::reset()
{
m_lp = 0;
m_k2 = 0;
m_k1 = 0;
for (std::array<s32, 2> &elem : m_o)
{
std::fill(elem.begin(), elem.end(), 0);
}
}
void es550x_shared_core::es550x_voice_t::es550x_filter_t::tick(s32 in)
{
// set sample input
m_o[0][0] = in;
s32 coeff_k1 = s32(bitfield(m_k1, 4, 12)); // 12 MSB used
s32 coeff_k2 = s32(bitfield(m_k2, 4, 12)); // 12 MSB used
// First and second stage: LP/K1, LP/K1 Fixed
lp_exec(coeff_k1, 0, 1);
lp_exec(coeff_k1, 1, 2);
switch (m_lp)
{
case 0: // LP3 = 0, LP4 = 0: HP/K2, HP/K2
default:
hp_exec(coeff_k2, 2, 3);
hp_exec(coeff_k2, 3, 4);
break;
case 1: // LP3 = 0, LP4 = 1: HP/K2, LP/K1
lp_exec(coeff_k1, 2, 3);
hp_exec(coeff_k2, 3, 4);
break;
case 2: // LP3 = 1, LP4 = 0: LP/K2, LP/K2
lp_exec(coeff_k2, 2, 3);
lp_exec(coeff_k2, 3, 4);
break;
case 3: // LP3 = 1, LP4 = 1: LP/K2, LP/K1
lp_exec(coeff_k1, 2, 3);
lp_exec(coeff_k2, 3, 4);
break;
}
}
void es550x_shared_core::es550x_voice_t::es550x_filter_t::lp_exec(s32 coeff, s32 in, s32 out)
{
// Store previous filter data
m_o[out][1] = m_o[out][0];
// Yn = K*(Xn - Yn-1) + Yn-1
m_o[out][0] = ((coeff * (m_o[in][0] - m_o[out][0])) / 4096) + m_o[out][0];
}
void es550x_shared_core::es550x_voice_t::es550x_filter_t::hp_exec(s32 coeff, s32 in, s32 out)
{
// Store previous filter data
m_o[out][1] = m_o[out][0];
// Yn = Xn - Xn-1 + K*Yn-1
m_o[out][0] = m_o[in][0] - m_o[in][1] + ((coeff * m_o[out][0]) / 8192) + (m_o[out][0] / 2);
}

View file

@ -0,0 +1,23 @@
# Konami K005289
## Summary
- 2 12 bit timer
- Address generator
## Source code
- k005289.hpp: Base header
- k005289.cpp: Source emulation core
## Description
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)
```

View file

@ -0,0 +1,42 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Konami K005289 emulation core
*/
#include "k005289.hpp"
void k005289_core::tick()
{
for (timer_t &elem : m_timer)
{
elem.tick();
}
}
void k005289_core::reset()
{
for (timer_t &elem : m_timer)
{
elem.reset();
}
}
void k005289_core::timer_t::tick()
{
if (bitfield(++m_counter, 0, 12) == 0)
{
m_addr = bitfield(m_addr + 1, 0, 5);
m_counter = m_freq;
}
}
void k005289_core::timer_t::reset()
{
m_addr = 0;
m_pitch = 0;
m_freq = 0;
m_counter = 0;
}

View file

@ -0,0 +1,83 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Konami K005289 emulation core
*/
#ifndef _VGSOUND_EMU_SRC_K005289_HPP
#define _VGSOUND_EMU_SRC_K005289_HPP
#pragma once
#include "../core/util.hpp"
class k005289_core : public vgsound_emu_core
{
private:
// k005289 timer classes
class timer_t : public vgsound_emu_core
{
public:
timer_t()
: vgsound_emu_core("k005289_timer")
, m_addr(0)
, m_pitch(0)
, m_freq(0)
, m_counter(0)
{
}
// internal state
void reset();
void tick();
// accessors
// Replace current frequency to lastest loaded pitch
inline void update() { m_freq = m_pitch; }
// setters
// Load pitch data (address pin)
inline void load(u16 pitch) { m_pitch = pitch; }
// getters
inline u8 addr() { return m_addr; }
private:
// registers
u8 m_addr = 0; // external address pin
u16 m_pitch = 0; // pitch
u16 m_freq = 0; // current frequency
s16 m_counter = 0; // frequency counter
};
public:
// constructor
k005289_core()
: vgsound_emu_core("k005289")
, m_timer{timer_t()}
{
}
// internal state
void reset();
void tick();
// accessors
// TG1/2 pin
inline void update(int voice) { m_timer[voice & 1].update(); }
// setters
// LD1/2 pin, A0...11 pin
inline void load(int voice, u16 addr) { m_timer[voice & 1].load(addr); }
// getters
// 1QA...E/2QA...E pin
inline u8 addr(int voice) { return m_timer[voice & 1].addr(); }
private:
std::array<timer_t, 2> m_timer;
};
#endif

View file

@ -0,0 +1,75 @@
# Konami K007232
## Summary
- 2 voice PCM
- 7 bit unsigned, with end marker
- total accessible memory: 128 Kbyte per bank
- Per-voice bankswitchable via E clock
- External 8 bit I/O (usually for volume)
## Source code
- k007232.hpp: Base header
- k007232.cpp: Source emulation core
## Description
It's Konami's one of custom PCM sound chip, Used at their arcade hardware at mid-80s to early-90s.
It has 2 channel of PCM, these are has its own output pins...just 7 LSB of currently fetched data.
PCM Sample format is unique, 1 MSB is end marker and 7 LSB is actually output. (unsigned format)
The chip itself is DACless, so Sound output and mixing control needs external logics and sound DAC.
## Register layout
```
Address Bits R/W Description
7654 3210
0x0 xxxx xxxx W Channel 0 Pitch bit 0-7
0x1 --x- ---- W Channel 0 4 bit Frequency mode
---x ---- W Channel 0 8 bit Frequency mode
---- xxxx W Channel 0 Pitch bit 8-11
0x2 xxxx xxxx W Channel 0 Start address bit 0-7
0x3 xxxx xxxx W Channel 0 Start address bit 8-15
0x4 ---- ---x W Channel 0 Start address bit 16
0x5 R/W Channel 0 Key on trigger (R/W)
0x6 xxxx xxxx W Channel 1 Pitch bit 0-7
0x7 --x- ---- W Channel 1 4 bit Frequency mode
---x ---- W Channel 1 8 bit Frequency mode
---- xxxx W Channel 1 Pitch bit 8-11
0x8 xxxx xxxx W Channel 1 Start address bit 0-7
0x9 xxxx xxxx W Channel 1 Start address bit 8-15
0xa ---- ---x W Channel 1 Start address bit 16
0xb R/W Channel 1 Key on trigger (R/W)
0xc xxxx xxxx W External port write (w/SLEV pin, Usually for volume control)
0xd ---- --x- W Channel 1 Loop enable
---- ---x W Channel 0 Loop enable
```
## Frequency calculation
(Guesswork in 8/4 bit Frequency mode)
```
if 8 bit Frequency mode then
Frequency: Input clock / 4 * (256 - (Pitch bit 0 - 7))
else if 4 bit Frequency mode then
Frequency: Input clock / 4 * (16 - (Pitch bit 8 - 11))
else
Frequency: Input clock / 4 * (4096 - (Pitch bit 0 - 11))
```
## Reference
<https://github.com/furrtek/VGChips/tree/master/Konami/007232>

View file

@ -0,0 +1,169 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Konami K007232 core
*/
#include "k007232.hpp"
void k007232_core::tick()
{
for (int i = 0; i < 2; i++)
{
m_voice[i].tick(i);
}
}
void k007232_core::voice_t::tick(u8 ne)
{
if (m_busy)
{
const bool is4bit = bitfield(m_pitch, 13); // 4 bit frequency divider flag
const bool is8bit = bitfield(m_pitch, 12); // 8 bit frequency divider flag
// update counter
if (is4bit)
{
m_counter = (m_counter & ~0x0ff) | (bitfield(bitfield(m_counter, 0, 8) + 1, 0, 8) << 0);
m_counter = (m_counter & ~0xf00) | (bitfield(bitfield(m_counter, 8, 4) + 1, 0, 4) << 8);
}
else
{
m_counter++;
}
// handle counter carry
bool carry =
is8bit ? (bitfield(m_counter, 0, 8) == 0)
: (is4bit ? (bitfield(m_counter, 8, 4) == 0) : (bitfield(m_counter, 0, 12) == 0));
if (carry)
{
m_counter = bitfield(m_pitch, 0, 12);
if (is4bit) // 4 bit frequency has different behavior for address
{
m_addr = (m_addr & ~0x0000f) | (bitfield(bitfield(m_addr, 0, 4) + 1, 0, 4) << 0);
m_addr = (m_addr & ~0x000f0) | (bitfield(bitfield(m_addr, 4, 4) + 1, 0, 4) << 4);
m_addr = (m_addr & ~0x00f00) | (bitfield(bitfield(m_addr, 8, 4) + 1, 0, 4) << 8);
m_addr = (m_addr & ~0x1f000) | (bitfield(bitfield(m_addr, 12, 5) + 1, 0, 5) << 12);
}
else
{
m_addr = bitfield(m_addr + 1, 0, 17);
}
}
m_data = m_host.m_intf.read_sample(ne, bitfield(m_addr, 0, 17)); // fetch ROM
if (bitfield(m_data, 7)) // check end marker
{
if (m_loop)
{
m_addr = m_start;
}
else
{
m_busy = false;
}
}
m_out = s8(m_data) - 0x40; // send to output (ASD/BSD) pin
}
else
{
m_out = 0;
}
}
void k007232_core::write(u8 address, u8 data)
{
address &= 0xf; // 4 bit for CPU write
switch (address)
{
case 0x0:
case 0x1:
case 0x2:
case 0x3:
case 0x4:
case 0x5: // voice 0
case 0x6:
case 0x7:
case 0x8:
case 0x9:
case 0xa:
case 0xb: // voice 1
m_voice[(address / 6) & 1].write(address % 6, data);
break;
case 0xc: // external register with SLEV pin
m_intf.write_slev(data);
break;
case 0xd: // loop flag
m_voice[0].set_loop(bitfield(data, 0));
m_voice[1].set_loop(bitfield(data, 1));
break;
default: break;
}
m_reg[address] = data;
}
// write registers on each voices
void k007232_core::voice_t::write(u8 address, u8 data)
{
switch (address)
{
case 0: // pitch LSB
m_pitch = (m_pitch & ~0x00ff) | data;
break;
case 1: // pitch MSB, divider
m_pitch = (m_pitch & ~0x3f00) | (u16(bitfield(data, 0, 6)) << 8);
break;
case 2: // start address bit 0-7
m_start = (m_start & ~0x000ff) | data;
break;
case 3: // start address bit 8-15
m_start = (m_start & ~0x0ff00) | (u32(data) << 8);
break;
case 4: // start address bit 16
m_start = (m_start & ~0x10000) | (u32(bitfield(data, 16)) << 16);
break;
case 5: // keyon trigger
keyon();
break;
}
}
// key on trigger (write OR read 0x05/0x11 register)
void k007232_core::voice_t::keyon()
{
m_busy = true;
m_counter = bitfield(m_pitch, 0, 12);
m_addr = m_start;
}
// reset chip
void k007232_core::reset()
{
for (auto &elem : m_voice)
{
elem.reset();
}
m_intf.write_slev(0);
std::fill(m_reg.begin(), m_reg.end(), 0);
}
// reset voice
void k007232_core::voice_t::reset()
{
m_busy = false;
m_loop = false;
m_pitch = 0;
m_start = 0;
m_counter = 0;
m_addr = 0;
m_data = 0;
m_out = 0;
}

View file

@ -0,0 +1,115 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Konami K007232 core
*/
#ifndef _VGSOUND_EMU_SRC_K007232_HPP
#define _VGSOUND_EMU_SRC_K007232_HPP
#pragma once
#include "../core/util.hpp"
class k007232_intf : public vgsound_emu_core
{
public:
k007232_intf()
: vgsound_emu_core("k007232_intf")
{
}
// NE pin is executing voice number, and used for per-voice sample bank.
virtual u8 read_sample(u8 ne, u32 address) { return 0; }
// SLEV pin actived when 0x0c register accessed
virtual void write_slev(u8 out) {}
};
class k007232_core : public vgsound_emu_core
{
friend class k007232_intf; // k007232 specific interface
private:
class voice_t : public vgsound_emu_core
{
public:
// constructor
voice_t(k007232_core &host)
: vgsound_emu_core("k007232_voice")
, m_host(host)
, m_busy(false)
, m_loop(false)
, m_pitch(0)
, m_start(0)
, m_counter(0)
, m_addr(0)
, m_data(0)
, m_out(0)
{
}
// internal state
void reset();
void tick(u8 ne);
// accessors
void write(u8 address, u8 data);
void keyon();
// setters
inline void set_loop(bool loop) { m_loop = loop; }
// getters
inline s8 out() { return m_out; }
private:
// registers
k007232_core &m_host;
bool m_busy = false; // busy status
bool m_loop = false; // loop flag
u16 m_pitch = 0; // pitch, frequency divider
u32 m_start = 0; // start position when keyon or loop start position at
// when reach end marker if loop enabled
u16 m_counter = 0; // frequency counter
u32 m_addr = 0; // current address
u8 m_data = 0; // current data
s8 m_out = 0; // current output (7 bit unsigned)
};
public:
// constructor
k007232_core(k007232_intf &intf)
: vgsound_emu_core("k007232")
, m_voice{*this, *this}
, m_intf(intf)
, m_reg{0}
{
}
// host accessors
void keyon(u8 voice) { m_voice[voice & 1].keyon(); }
void write(u8 address, u8 data);
// internal state
void reset();
void tick();
// output for each voices, ASD/BSD pin
inline s32 output(u8 voice) { return m_voice[voice & 1].out(); }
// getters for debug, trackers, etc
inline u8 reg_r(u8 address) { return m_reg[address & 0xf]; }
private:
std::array<voice_t, 2> m_voice;
k007232_intf &m_intf; // common memory interface
std::array<u8, 16> m_reg = {0}; // register pool
};
#endif

View file

@ -0,0 +1,109 @@
# Konami K053260
## Summary
- 4 voice DPCM or PCM
- 8 bit signed PCM or 4 bit DPCM (unique type)
- total accessible memory: 2 MByte, 64 KByte per sample
- CPU to CPU Communication
- CPU can be accessible k053260 memory through voice register
- 2 Stereo sound input, 1 Stereo output (YM3012 DAC compatible)
## Source code
- k053260.hpp: Base header
- k053260.cpp: Source emulation core
## Description
It's one of Konami's custom PCM playback chip with CPU to CPU communication feature, and built in timer.
It's architecture is successed from K007232, but it features various enhancements:
it's expanded to 4 channels, Supports more memory space, 4 bit DPCM, Built in volume and stereo panning support, and Dual chip configurations.
There's 2 stereo inputs and single stereo output, Both format is YM3012 compatible.
## Register layout
```
Address Bits R/W Description
7654 3210
00...03 Communication Registers
00 xxxx xxxx R Answer from host CPU LSB
01 xxxx xxxx R Answer from host CPU MSB
02 xxxx xxxx W Reply to host CPU LSB
03 xxxx xxxx W Reply to host CPU MSB
08...0f Voice 0 Register
08 xxxx xxxx W Pitch bit 0-7
09 ---- xxxx W Pitch bit 8-11
0a xxxx xxxx W Source length bit 0-7 (byte wide)
0b xxxx xxxx W Source length bit 8-15 (byte wide)
0c xxxx xxxx W Start address/ROM readback base bit 0-7
0d xxxx xxxx W Start address/ROM readback base bit 8-15
0e ---x xxxx W Start address/ROM readback base bit 16-20
0f -xxx xxxx W Volume
10...17 Voice 1 Register
18...1f Voice 2 Register
20...27 Voice 3 Register
28 ---- x--- W Voice 3 Key on/off trigger
---- -x-- W Voice 2 Key on/off trigger
---- --x- W Voice 1 Key on/off trigger
---- ---x W Voice 0 Key on/off trigger
29 ---- x--- R Voice 3 busy
---- -x-- R Voice 2 busy
---- --x- R Voice 1 busy
---- ---x R Voice 0 busy
2a x--- ---- W Voice 3 source format
0--- ---- 8 bit signed PCM
1--- ---- 4 bit ADPCM
-x-- ---- W Voice 2 source format
--x- ---- W Voice 1 source format
---x ---- W Voice 0 source format
---- x--- W Voice 3 Loop enable
---- -x-- W Voice 2 Loop enable
---- --x- W Voice 1 Loop enable
---- ---x W Voice 0 Loop enable
2c --xx x--- W Voice 1 Pan angle in degrees*1
--00 0--- Mute
--00 1--- 0 degrees
--01 0--- 24 degrees
--01 1--- 35 degrees
--10 0--- 45 degrees
--10 1--- 55 degrees
--11 0--- 66 degrees
--11 1--- 90 degrees
---- -xxx W Voice 0 Pan angle in degrees*1
2d --xx x--- W Voice 3 Pan angle in degrees*1
---- -xxx W Voice 2 Pan angle in degrees*1
2e xxxx xxxx R ROM readback (use Voice 0 register)
2f ---- x--- W AUX2 input enable
---- -x-- W AUX1 input enable
---- --x- W Sound enable
---- ---x W ROM readbank enable
*1 Actually fomula unknown, Use floating point type until explained that.
```
## Frequency calculation
```
Frequency: Input clock / (4096 - Pitch)
```

View file

@ -0,0 +1,290 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Konami K053260 core
*/
#include "k053260.hpp"
void k053260_core::tick()
{
m_out[0] = m_out[1] = 0;
if (m_ctrl.sound_en())
{
for (int i = 0; i < 4; i++)
{
m_voice[i].tick(i);
m_out[0] += m_voice[i].out(0);
m_out[1] += m_voice[i].out(1);
}
}
// dac clock (YM3012 format)
u8 dac_clock = m_dac.clock();
if (bitfield(++dac_clock, 0, 4) == 0)
{
m_intf.write_int(m_dac.state());
u8 dac_state = m_dac.state();
if (bitfield(++dac_state, 0) == 0)
{
m_ym3012.tick(bitfield(dac_state, 1), m_out[bitfield(dac_state, 1) ^ 1]);
}
m_dac.set_state(bitfield(dac_state, 0, 2));
}
m_dac.set_clock(bitfield(dac_clock, 0, 4));
}
void k053260_core::voice_t::tick(u8 ne)
{
if (m_enable && m_busy)
{
bool update = false;
// update counter
if (bitfield(++m_counter, 0, 12) == 0)
{
if (m_bitpos < 8)
{
m_bitpos += 8;
m_addr = bitfield(m_addr + 1, 0, 21);
m_remain--;
}
if (m_adpcm)
{
m_bitpos -= 4;
update = true;
}
else
{
m_bitpos -= 8;
}
}
m_data = m_host.m_intf.read_sample(bitfield(m_addr, 0, 21)); // fetch ROM
if (update)
{
const u8 nibble = bitfield(m_data, m_bitpos & 4, 4); // get nibble from ROM
if (nibble)
{
m_adpcm_buf += bitfield(nibble, 3) ? s8(0x80 >> bitfield(nibble, 0, 3))
: (1 << bitfield(nibble - 1, 0, 3));
}
}
if (m_remain < 0) // check end flag
{
if (m_loop)
{
m_addr = m_start;
m_adpcm_buf = 0;
}
else
{
m_busy = false;
}
}
// calculate output
s32 output = m_adpcm ? m_adpcm_buf : sign_ext<s32>(m_data, 8) * s32(m_volume);
// use math for now; actually fomula unknown
m_out[0] = (m_pan >= 0) ? s32(output * cos(f64(m_pan) * PI / 180)) : 0;
m_out[1] = (m_pan >= 0) ? s32(output * sin(f64(m_pan) * PI / 180)) : 0;
}
else
{
m_out[0] = m_out[1] = 0;
}
}
u8 k053260_core::read(u8 address)
{
address &= 0x3f; // 6 bit for CPU read
switch (address)
{
case 0x0:
case 0x1: // Answer from host
return m_host2snd[address & 1];
break;
case 0x29: // Voice playing status
return (m_voice[0].busy() ? 0x1 : 0x0) | (m_voice[1].busy() ? 0x2 : 0x0) |
(m_voice[2].busy() ? 0x4 : 0x0) | (m_voice[3].busy() ? 0x8 : 0x0);
case 0x2e: // ROM readback
{
if (!m_ctrl.rom_read())
{
return 0xff;
}
const u32 rom_addr = m_voice[0].start() + m_voice[0].length();
m_voice[0].length_inc();
return m_intf.read_sample(rom_addr);
}
}
return 0xff;
}
void k053260_core::write(u8 address, u8 data)
{
address &= 0x3f; // 6 bit for CPU write
switch (address)
{
case 0x2:
case 0x3: // Reply to host
m_snd2host[address & 1] = data;
break;
case 0x08:
case 0x09:
case 0x0a:
case 0x0b:
case 0x0c:
case 0x0d:
case 0x0e:
case 0x0f: // voice 0
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17: // voice 1
case 0x18:
case 0x19:
case 0x1a:
case 0x1b:
case 0x1c:
case 0x1d:
case 0x1e:
case 0x1f: // voice 2
case 0x20:
case 0x21:
case 0x22:
case 0x23:
case 0x24:
case 0x25:
case 0x26:
case 0x27: // voice 3
m_voice[bitfield(address - 0x8, 3, 2)].write(bitfield(address, 0, 3), data);
break;
case 0x28: // keyon/off toggle
for (int i = 0; i < 4; i++)
{
if (bitfield(data, i) && (!m_voice[i].enable()))
{ // rising edge (keyon)
m_voice[i].keyon();
}
else if ((!bitfield(data, i)) && m_voice[i].enable())
{ // falling edge (keyoff)
m_voice[i].keyoff();
}
}
break;
case 0x2a: // loop/adpcm flag
for (int i = 0; i < 4; i++)
{
m_voice[i].set_loop(bitfield(data, i));
m_voice[i].set_adpcm(bitfield(data, i + 4));
}
break;
case 0x2c:
m_voice[0].set_pan(bitfield(data, 0, 3));
m_voice[1].set_pan(bitfield(data, 3, 3));
break;
case 0x2d:
m_voice[2].set_pan(bitfield(data, 0, 3));
m_voice[3].set_pan(bitfield(data, 3, 3));
break;
case 0x2f: m_ctrl.write(data); break;
default: break;
}
m_reg[address] = data;
}
// write registers on each voices
void k053260_core::voice_t::write(u8 address, u8 data)
{
switch (address)
{
case 0: // pitch LSB
m_pitch = (m_pitch & ~0x00ff) | data;
break;
case 1: // pitch MSB
m_pitch = (m_pitch & ~0x0f00) | (u16(bitfield(data, 0, 4)) << 8);
break;
case 2: // source length LSB
m_length = (m_length & ~0x000ff) | data;
break;
case 3: // source length MSB
m_length = (m_length & ~0x0ff00) | (u16(data) << 8);
break;
case 4: // start address bit 0-7
m_start = (m_start & ~0x0000ff) | data;
break;
case 5: // start address bit 8-15
m_start = (m_start & ~0x00ff00) | (u32(data) << 8);
break;
case 6: // start address bit 16-20
m_start = (m_start & ~0x1f0000) | (u32(bitfield(data, 16, 5)) << 16);
break;
case 7: // volume
m_volume = bitfield(data, 0, 7);
break;
}
}
// key on trigger
void k053260_core::voice_t::keyon()
{
m_enable = m_busy = 1;
m_counter = bitfield(m_pitch, 0, 12);
m_addr = m_start;
m_remain = m_length;
m_bitpos = 4;
m_adpcm_buf = 0;
std::fill(m_out.begin(), m_out.end(), 0);
}
// key off trigger
void k053260_core::voice_t::keyoff() { m_enable = m_busy = 0; }
// reset chip
void k053260_core::reset()
{
for (auto &elem : m_voice)
{
elem.reset();
}
m_intf.write_int(0);
std::fill(m_host2snd.begin(), m_host2snd.end(), 0);
std::fill(m_snd2host.begin(), m_snd2host.end(), 0);
m_ctrl.reset();
m_dac.reset();
std::fill(m_reg.begin(), m_reg.end(), 0);
std::fill(m_out.begin(), m_out.end(), 0);
}
// reset voice
void k053260_core::voice_t::reset()
{
m_enable = 0;
m_busy = 0;
m_loop = 0;
m_adpcm = 0;
m_pitch = 0;
m_start = 0;
m_length = 0;
m_volume = 0;
m_pan = -1;
m_counter = 0;
m_addr = 0;
m_remain = 0;
m_bitpos = 4;
m_data = 0;
m_adpcm_buf = 0;
m_out[0] = m_out[1] = 0;
}

View file

@ -0,0 +1,262 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Konami K053260 core
*/
#ifndef _VGSOUND_EMU_SRC_K053260_HPP
#define _VGSOUND_EMU_SRC_K053260_HPP
#pragma once
#include "../core/util.hpp"
class k053260_intf : public vgsound_emu_core
{
public:
k053260_intf()
: vgsound_emu_core("k053260_intf")
{
}
virtual u8 read_sample(u32 address) { return 0; } // sample fetch
virtual void write_int(u8 out) {} // timer interrupt
};
class k053260_core : public vgsound_emu_core
{
friend class k053260_intf; // k053260 specific interface
private:
const int pan_dir[8] = {-1, 0, 24, 35, 45, 55, 66, 90}; // pan direction
class voice_t : public vgsound_emu_core
{
public:
// constructor
voice_t(k053260_core &host)
: vgsound_emu_core("k053260_voice")
, m_host(host)
, m_enable(0)
, m_busy(0)
, m_loop(0)
, m_adpcm(0)
, m_pitch(0)
, m_start(0)
, m_length(0)
, m_volume(0)
, m_pan(-1)
, m_counter(0)
, m_addr(0)
, m_remain(0)
, m_bitpos(4)
, m_data(0)
, m_adpcm_buf(0)
, m_out{0}
{
}
// internal state
void reset();
void tick(u8 ne);
// accessors
void write(u8 address, u8 data);
void keyon();
void keyoff();
// setters
inline void set_enable(bool enable) { m_enable = enable ? 1 : 0; }
inline void set_busy(bool busy) { m_busy = busy ? 1 : 0; }
inline void set_loop(bool loop) { m_loop = loop ? 1 : 0; }
inline void set_adpcm(bool adpcm) { m_adpcm = adpcm ? 1 : 0; }
inline void length_inc() { m_length = (m_length + 1) & 0xffff; }
inline void set_pan(u8 pan) { m_pan = m_host.pan_dir[pan & 7]; }
// getters
inline bool enable() { return m_enable; }
inline bool busy() { return m_busy; }
inline u32 start() { return m_start; }
inline u16 length() { return m_length; }
inline s32 out(u8 ch) { return m_out[ch & 1]; }
private:
// registers
k053260_core &m_host;
u16 m_enable : 1; // enable flag
u16 m_busy : 1; // busy status
u16 m_loop : 1; // loop flag
u16 m_adpcm : 1; // ADPCM flag
u16 m_pitch : 12; // pitch
u32 m_start = 0; // start position
u16 m_length = 0; // source length
u8 m_volume = 0; // master volume
int m_pan = -1; // master pan
u16 m_counter = 0; // frequency counter
u32 m_addr = 0; // current address
s32 m_remain = 0; // remain for end sample
u8 m_bitpos = 4; // bit position for ADPCM decoding
u8 m_data = 0; // current data
s8 m_adpcm_buf = 0; // ADPCM buffer
std::array<s32, 2> m_out = {0}; // current output
};
class ctrl_t
{
public:
ctrl_t()
: m_rom_read(0)
, m_sound_en(0)
, m_input_en(0)
{
}
void reset()
{
m_rom_read = 0;
m_sound_en = 0;
m_input_en = 0;
}
void write(u8 data)
{
m_rom_read = (data >> 0) & 1;
m_sound_en = (data >> 1) & 1;
m_input_en = (data >> 2) & 3;
}
// getters
inline bool rom_read() { return m_rom_read; }
inline bool sound_en() { return m_sound_en; }
inline u8 input_en() { return m_input_en; }
private:
u8 m_rom_read : 1; // ROM readback
u8 m_sound_en : 1; // Sound enable
u8 m_input_en : 2; // Input enable
};
class ym3012_t
{
public:
ym3012_t()
: m_in{0}
, m_out{0}
{
}
void reset()
{
std::fill(m_in.begin(), m_in.end(), 0);
std::fill(m_out.begin(), m_out.end(), 0);
}
void tick(u8 ch, s32 in)
{
m_out[(ch & 1)] = m_in[(ch & 1)];
m_in[(ch & 1) ^ 1] = in;
}
private:
std::array<s32, 2> m_in = {0};
std::array<s32, 2> m_out = {0};
};
class dac_t
{
public:
dac_t()
: m_clock(0)
, m_state(0)
{
}
void reset()
{
m_clock = 0;
m_state = 0;
}
inline void set_clock(u8 clock) { m_clock = clock; }
inline void set_state(u8 state) { m_state = state; }
inline u8 clock() { return m_clock; }
inline u8 state() { return m_state; }
private:
u8 m_clock : 4; // DAC clock (16 clock)
u8 m_state : 2; // DAC state (4 state - SAM1, SAM2)
};
public:
// constructor
k053260_core(k053260_intf &intf)
: vgsound_emu_core("k053260")
, m_voice{*this, *this, *this, *this}
, m_intf(intf)
, m_host2snd{0}
, m_snd2host{0}
, m_ctrl(ctrl_t())
, m_ym3012(ym3012_t())
, m_dac(dac_t())
, m_reg{0}
, m_out{0}
{
}
// communications
inline u8 snd2host_r(u8 address) { return m_snd2host[address & 1]; }
inline void host2snd_w(u8 address, u8 data) { m_host2snd[address & 1] = data; }
// sound accessors
u8 read(u8 address);
void write(u8 address, u8 data);
// internal state
void reset();
void tick();
// getters for debug, trackers, etc
inline s32 output(u8 ch) { return m_out[ch & 1]; } // output for each channels
inline u8 reg_r(u8 address) { return m_reg[address & 0x3f]; }
inline s32 voice_out(u8 voice, u8 ch)
{
return (voice < 4) ? m_voice[voice].out(ch & 1) : 0;
}
private:
std::array<voice_t, 4> m_voice;
k053260_intf &m_intf; // common memory interface
std::array<u8, 2> m_host2snd = {0};
std::array<u8, 2> m_snd2host = {0};
ctrl_t m_ctrl; // chip control
ym3012_t m_ym3012; // YM3012 output
dac_t m_dac; // YM3012 interface
std::array<u8, 64> m_reg = {0}; // register pool
std::array<s32, 2> m_out = {0}; // stereo output
};
#endif

View file

@ -0,0 +1,67 @@
# OKI MSM6295
## Summary
- 4 voice Dialogic ADPCM
- total accessible memory: 256 KByte
- Clock divider via SS pin
## Source code
- msm6295.hpp: Base header
- msm6295.cpp: Source emulation core
### Dependencies
- vox.hpp: Dialogic ADPCM decoder header
- vox.cpp: Dialogic ADPCM decoder source
## Description
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
```

View file

@ -0,0 +1,179 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
OKI MSM6295 emulation core
*/
#include "msm6295.hpp"
void msm6295_core::tick()
{
if (m_counter < 4)
{
m_voice[m_counter].tick();
m_out_temp += m_voice[m_counter].out();
}
if ((++m_counter) >= (m_ss ? 5 : 4))
{
// command handler
if (m_command_pending)
{
if (bitfield(m_command, 7)) // play voice
{
if ((++m_clock) >= 15)
{
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].busy())
{
m_voice[i].set_command(m_command);
m_voice[i].set_volume(bitfield(m_next_command, 0, 4));
}
// voices aren't be playable simultaneously at once
break;
}
}
}
m_command = 0;
m_command_pending = false;
m_clock = 0;
}
}
else if (bitfield(m_next_command, 7)) // select phrase
{
if ((++m_clock) >= 15)
{
m_command = m_next_command;
m_command_pending = false;
m_clock = 0;
}
}
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].busy())
{
m_voice[i].set_command(m_next_command);
}
}
}
m_next_command &= ~0x78;
}
m_command_pending = false;
}
}
m_out = m_out_temp;
m_out_temp = 0;
m_counter = 0;
}
}
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_counter = 0;
m_out = 0;
m_out_temp = 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)
{
bool is_end = (m_command != 0); // suspend
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 = (step() * m_volume) >> 7; // scale out to 12 bit output
m_clock = 0;
}
}
}
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;
m_mute = false;
}
// accessors
u8 msm6295_core::busy_r()
{
return (m_voice[0].busy() ? 0x01 : 0x00) | (m_voice[1].busy() ? 0x02 : 0x00) |
(m_voice[2].busy() ? 0x04 : 0x00) | (m_voice[3].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,142 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
OKI MSM6295 emulation core
*/
#ifndef _VGSOUND_EMU_SRC_MSM6295_HPP
#define _VGSOUND_EMU_SRC_MSM6295_HPP
#pragma once
#include "../core/util.hpp"
#include "../core/vox/vox.hpp"
class msm6295_core : public vox_core
{
friend class vgsound_emu_mem_intf; // common memory interface
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 classes
class voice_t : vox_decoder_t
{
public:
// constructor
voice_t(msm6295_core &host)
: vox_decoder_t(host, false)
, m_host(host)
, m_clock(0)
, m_busy(false)
, m_command(0)
, m_addr(0)
, m_nibble(0)
, m_end(0)
, m_volume(0)
, m_out(0)
, m_mute(false)
{
}
// internal state
virtual void reset() override;
void tick();
// Setters
inline void set_command(u8 command) { m_command = command; }
inline void set_volume(s32 volume)
{
m_volume = (volume < 9) ? m_host.m_volume_table[volume] : 0;
}
inline void set_mute(bool mute) { m_mute = mute; }
// Getters
inline bool busy() { return m_busy; }
inline s32 out() { return m_mute ? 0 : m_out; }
private:
// accessors, getters, setters
// registers
msm6295_core &m_host; // host core
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
// for preview only
bool m_mute = false; // mute flag
};
public:
// constructor
msm6295_core(vgsound_emu_mem_intf &intf)
: vox_core("msm6295")
, m_voice{*this, *this, *this, *this}
, m_intf(intf)
, m_ss(false)
, m_command(0)
, m_next_command(0)
, m_command_pending(false)
, m_clock(0)
, m_counter(0)
, m_out(0)
, m_out_temp(0)
{
}
// accessors, getters, setters
u8 busy_r();
void command_w(u8 data);
inline void ss_w(bool ss) { m_ss = ss; } // SS pin
// internal state
void reset();
void tick();
inline s32 out() { return m_out; } // built in 12 bit DAC
// for preview
inline void voice_mute(u8 voice, bool mute)
{
if (voice < 4)
{
m_voice[voice].set_mute(mute);
}
}
inline s32 voice_out(u8 voice) { return (voice < 4) ? m_voice[voice].out() : 0; }
private:
std::array<voice_t, 4> m_voice;
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
u16 m_counter = 0; // another clock counter
s32 m_out = 0; // 12 bit output
s32 m_out_temp = 0; // temporary buffer of above
};
#endif

View file

@ -0,0 +1,88 @@
# Namco 163
## Summary
- 1 to 8 voice wavetable
- 4 bit unsigned
- each waveforms are can be placed to anywhere in internal RAM, and its size is can be variable.
- activated voice count can be changed any time, multiplexed output
## Source code
- n163.hpp: Base header
- n163.cpp: Source emulation core
## Description
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.
## Waveform format
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 calculation
```
Frequency: Pitch input * ((Input clock * 15 * Number of activated voices) / 65536)
```
## Technical notice
This core only outputs raw output from chip (or accumulated output, see below); any kind of off-chip stuff needs to implemented outside core.
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

View file

@ -0,0 +1,120 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Namco 163 Sound emulation core
*/
#include "n163.hpp"
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);
// get per-voice output
const s16 voice_out = (wave * volume);
m_voice_out[(m_voice_cycle >> 3) & 7] = voice_out;
// 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);
// 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
m_acc += voice_out;
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;
std::fill(m_ram.begin(), m_ram.end(), 0);
m_voice_cycle = 0x78;
m_addr_latch.reset();
m_out = 0;
m_acc = 0;
}
// accessor
void n163_core::addr_w(u8 data)
{
// 0xf800-0xffff Sound address, increment
m_addr_latch.write(data);
}
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_inc();
}
}
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_inc();
}
return ret;
}

View file

@ -0,0 +1,109 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Namco 163 Sound emulation core
*/
#ifndef _VGSOUND_EMU_SRC_N163_HPP
#define _VGSOUND_EMU_SRC_N163_HPP
#pragma once
#include "../core/util.hpp"
class n163_core : public vgsound_emu_core
{
private:
// Address latch
class addr_latch_t : public vgsound_emu_core
{
public:
addr_latch_t()
: vgsound_emu_core("namco_163_addr_latch")
, m_addr(0)
, m_incr(0)
{
}
void reset()
{
m_addr = 0;
m_incr = 0;
}
// accessors
inline void write(u8 data)
{
m_addr = (data >> 0) & 0x7f;
m_incr = (data >> 7) & 0x01;
}
inline void addr_inc() { m_addr = (m_addr + 1) & 0x7f; }
// getters
inline u8 addr() { return m_addr; }
inline bool incr() { return m_incr; }
private:
u8 m_addr : 7;
u8 m_incr : 1;
};
public:
n163_core()
: vgsound_emu_core("namco_163")
, m_disable(false)
, m_ram{0}
, m_voice_cycle(0x78)
, m_addr_latch(addr_latch_t())
, m_out(0)
, m_voice_out{0}
, m_multiplex(true)
, m_acc(0)
{
}
// accessors, getters, setters
void addr_w(u8 data);
void data_w(u8 data, bool cpu_access = false);
u8 data_r(bool cpu_access = false);
inline void set_disable(bool disable) { m_disable = disable; }
// internal state
void reset();
void tick();
// sound output pin
inline s16 out() { return m_out; }
// register pool
inline u8 reg(u8 addr) { return m_ram[addr & 0x7f]; }
inline void set_multiplex(bool multiplex = true) { m_multiplex = multiplex; }
// preview only
inline u8 voice_cycle() { return m_voice_cycle; }
inline s16 voice_out(u8 voice)
{
return (voice <= ((m_ram[0x7f] >> 4) & 7)) ? m_voice_out[7 - voice] : 0;
}
private:
bool m_disable = false;
std::array<u8, 0x80> m_ram = {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
std::array<s16, 8> m_voice_out = {0}; // per-voice output, for preview only
// demultiplex related
bool m_multiplex = true; // multiplex flag, but less noisy = inaccurate!
s16 m_acc = 0; // accumulated output
};
#endif

View file

@ -0,0 +1,314 @@
# Konami SCC
## Summary
- 5 voice wavetable
- 8 bit signed, 32 width long for each voice
- each voice has separated waveform space
- 4th and 5th voice shares waveform (K051649)
- MegaROM mapper (K051649) or MegaRAM mapper (K052539)
## Source code
- scc.hpp: Base header
- scc.cpp: Source emulation core
## Description
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.
## Register layout
### K051649
- 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
- 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
```
## 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)
```
## Reference
<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>

View file

@ -0,0 +1,461 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst
Konami SCC emulation core
*/
#include "scc.hpp"
// 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 (m_pitch >= 9) // or voice is halted
{
// update counter - Post decrement
const u16 temp = m_counter;
if (m_host.m_test.freq_4bit()) // 4 bit frequency mode
{
m_counter = (m_counter & ~0x0ff) | (bitfield(bitfield(m_counter, 0, 8) - 1, 0, 8) << 0);
m_counter = (m_counter & ~0xf00) | (bitfield(bitfield(m_counter, 8, 4) - 1, 0, 4) << 8);
}
else
{
m_counter = bitfield(m_counter - 1, 0, 12);
}
// handle counter carry
const 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)
{
m_addr = bitfield(m_addr + 1, 0, 5);
m_counter = m_pitch;
}
}
// get output
if (m_enable)
{
m_out = (m_wave[m_addr] * m_volume) >> 4; // scale to 11 bit digital output
}
else
{
m_out = 0;
}
}
void scc_core::reset()
{
for (auto &elem : m_voice)
{
elem.reset();
}
m_test.reset();
m_out = 0;
std::fill(m_reg.begin(), m_reg.end(), 0);
}
void scc_core::voice_t::reset()
{
std::fill(m_wave.begin(), m_wave.end(), 0);
m_enable = false;
m_pitch = 0;
m_volume = 0;
m_addr = 0;
m_counter = 0;
m_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].set_wave(wave_addr, data);
m_voice[4].set_wave(wave_addr, data);
}
else
{
m_voice[voice].set_wave(wave_addr, data);
}
}
else
{
m_voice[voice].set_wave(wave_addr, data);
}
}
void scc_core::freq_vol_enable_w(u8 address, u8 data)
{
// *0-*f Pitch, Volume, Enable
address = bitfield(address, 0, 4); // mask address to 4 bit
const u8 voice_freq = bitfield(address, 1, 3);
switch (address)
{
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].reset_addr();
}
m_voice[voice_freq].set_pitch(data, 0x0ff);
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].reset_addr();
}
m_voice[voice_freq].set_pitch(u16(bitfield(data, 0, 4)) << 8, 0xf00);
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[address - 0xa].set_volume(bitfield(data, 0, 4));
break;
case 0xf: // 0x*f Enable/Disable flag
m_voice[0].set_enable(bitfield(data, 0));
m_voice[1].set_enable(bitfield(data, 1));
m_voice[2].set_enable(bitfield(data, 2));
m_voice[3].set_enable(bitfield(data, 3));
m_voice[4].set_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.set_freq_4bit(bitfield(data, 0));
m_test.set_freq_8bit(bitfield(data, 1));
m_test.set_resetpos(bitfield(data, 5));
m_test.set_rotate(bitfield(data, 6));
m_test.set_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.set_freq_4bit(bitfield(data, 0));
m_test.set_freq_8bit(bitfield(data, 1));
m_test.set_resetpos(bitfield(data, 5));
m_test.set_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.set_freq_4bit(bitfield(data, 0));
m_test.set_freq_8bit(bitfield(data, 1));
m_test.set_resetpos(bitfield(data, 5));
m_test.set_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;
}
// Mapper
void k051649_core::reset()
{
k051649_scc_core::reset();
m_mapper.reset();
m_scc_enable = false;
}
void k052539_core::reset()
{
k052539_scc_core::reset();
m_mapper.reset();
m_scc_enable = false;
m_is_sccplus = false;
}
void k051649_core::k051649_mapper_t::reset()
{
m_bank[0] = 0;
m_bank[1] = 1;
m_bank[2] = 2;
m_bank[3] = 3;
}
void k052539_core::k052539_mapper_t::reset()
{
m_bank[0] = 0;
m_bank[1] = 1;
m_bank[2] = 2;
m_bank[3] = 3;
std::fill(m_ram_enable.begin(), m_ram_enable.end(), false);
}
// Mapper accessors
u8 k051649_core::read(u16 address)
{
if ((bitfield(address, 11, 5) == 0b10011) && m_scc_enable)
{
return scc_r(false, u8(address));
}
return m_intf.read_byte((u32(m_mapper.bank(bitfield(address, 13, 2) ^ 2)) << 13) |
bitfield(address, 0, 13));
}
u8 k052539_core::read(u16 address)
{
if ((bitfield(address, 11, 5) == 0b10011) && m_scc_enable && (!m_is_sccplus))
{
return scc_r(false, u8(address));
}
if ((bitfield(address, 11, 5) == 0b10111) && m_scc_enable && m_is_sccplus)
{
return scc_r(true, u8(address));
}
return m_intf.read_byte((u32(m_mapper.bank(bitfield(address, 13, 2) ^ 2)) << 13) |
bitfield(address, 0, 13));
}
void k051649_core::write(u16 address, u8 data)
{
const u16 bank = bitfield(address, 13, 2) ^ 2;
switch (bitfield(address, 11, 5))
{
case 0b01010: // 0x5000-0x57ff Bank 0
case 0b01110: // 0x7000-0x77ff Bank 1
case 0b10010: // 0x9000-0x97ff Bank 2
case 0b10110: // 0xb000-0xb7ff Bank 3
m_mapper.set_bank(bank, data);
m_scc_enable = (bitfield(m_mapper.bank(2), 0, 6) == 0x3f);
break;
case 0b10011: // 0x9800-9fff SCC
if (m_scc_enable)
{
scc_w(false, u8(address), data);
}
break;
}
}
void k052539_core::write(u16 address, u8 data)
{
u8 prev = 0;
bool update = false;
const u16 bank = bitfield(address, 13, 2) ^ 2;
const bool ram_enable = m_mapper.ram_enable(bank);
if (ram_enable)
{
m_intf.write_byte((u32(m_mapper.bank(bank)) << 13) | bitfield(address, 0, 13), data);
}
switch (bitfield(address, 11, 5))
{
case 0b01010: // 0x5000-0x57ff Bank 0
case 0b01110: // 0x7000-0x77ff Bank 1
case 0b10010: // 0x9000-0x97ff Bank 2
case 0b10110: // 0xb000-0xb7ff Bank 3
if (!ram_enable)
{
prev = m_mapper.bank(bank);
m_mapper.set_bank(bank, data);
update = prev ^ m_mapper.bank(bank);
}
break;
case 0b10011: // 0x9800-0x9fff SCC
if ((!ram_enable) && m_scc_enable && (!m_is_sccplus))
{
scc_w(false, u8(address), data);
}
break;
case 0b10111: // 0xb800-0xbfff SCC+, Mapper configuration
if (bitfield(address, 1, 10) == 0x3ff)
{
m_mapper.set_ram_enable(0, bitfield(data, 4) || bitfield(data, 0));
m_mapper.set_ram_enable(1, bitfield(data, 4) || bitfield(data, 1));
m_mapper.set_ram_enable(2, bitfield(data, 4) || bitfield(data, 2));
m_mapper.set_ram_enable(3, bitfield(data, 4));
prev = (m_is_sccplus ? 1 : 0);
m_is_sccplus = bitfield(data, 5);
update = prev ^ (m_is_sccplus ? 1 : 0);
}
else if ((!ram_enable) && m_scc_enable && m_is_sccplus)
{
scc_w(true, u8(address), data);
}
break;
}
if (update)
{
m_scc_enable =
m_is_sccplus ? bitfield(m_mapper.bank(3), 7) : (bitfield(m_mapper.bank(2), 0, 6) == 0x3f);
}
}

View file

@ -0,0 +1,320 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Contributor(s): Natt Akuma, James Alan Nguyen, Laurens Holst
Konami SCC emulation core
*/
#ifndef _VGSOUND_EMU_SRC_SCC_HPP
#define _VGSOUND_EMU_SRC_SCC_HPP
#pragma once
#include "../core/util.hpp"
// shared for SCCs
class scc_core : public vgsound_emu_core
{
private:
// classes
class voice_t : public vgsound_emu_core
{
public:
// constructor
voice_t(scc_core &host)
: vgsound_emu_core("scc_voice")
, m_host(host)
, m_wave{0}
, m_enable(false)
, m_pitch(0)
, m_volume(0)
, m_addr(0)
, m_counter(0)
, m_out(0)
{
}
// internal state
void reset();
void tick();
// accessors
inline void reset_addr() { m_addr = 0; }
// setters
inline void set_wave(u8 addr, s8 wave) { m_wave[addr & 0x1f] = wave; }
inline void set_enable(bool enable) { m_enable = enable; }
inline void set_pitch(u16 pitch, u16 mask = 0xfff)
{
m_pitch = (m_pitch & ~(mask & 0xfff)) | (pitch & (mask & 0xfff));
m_counter = m_pitch;
}
inline void set_volume(u8 volume) { m_volume = volume & 0xf; }
// getters
inline s8 wave(u8 addr) { return m_wave[addr & 0x1f]; }
inline u8 addr() { return m_addr; }
inline s32 out() { return m_out; }
private:
// registers
scc_core &m_host;
std::array<s8, 32> m_wave = {0}; // internal waveform
bool m_enable = false; // output enable flag
u16 m_pitch : 12; // pitch
u16 m_volume : 4; // volume
u8 m_addr = 0; // waveform pointer
u16 m_counter = 0; // frequency counter
s32 m_out = 0; // current output
};
class test_t : public vgsound_emu_core
{
public:
// constructor
test_t()
: vgsound_emu_core("scc_test")
, m_freq_4bit(0)
, m_freq_8bit(0)
, m_resetpos(0)
, m_rotate(0)
, m_rotate4(0)
{
}
void reset()
{
m_freq_4bit = 0;
m_freq_8bit = 0;
m_resetpos = 0;
m_rotate = 0;
m_rotate4 = 0;
}
// setters
inline void set_freq_4bit(bool freq_4bit) { m_freq_4bit = freq_4bit; }
inline void set_freq_8bit(bool freq_8bit) { m_freq_8bit = freq_8bit; }
inline void set_resetpos(bool resetpos) { m_resetpos = resetpos; }
inline void set_rotate(bool rotate) { m_rotate = rotate; }
inline void set_rotate4(bool rotate4) { m_rotate4 = rotate4; }
// getters
inline bool freq_4bit() { return m_freq_4bit; }
inline bool freq_8bit() { return m_freq_8bit; }
inline bool resetpos() { return m_resetpos; }
inline bool rotate() { return m_rotate; }
inline bool rotate4() { return m_rotate4; }
private:
u8 m_freq_4bit : 1; // 4 bit frequency
u8 m_freq_8bit : 1; // 8 bit frequency
u8 m_resetpos : 1; // reset counter after pitch writes
u8 m_rotate : 1; // rotate and write protect waveform for all channels
u8 m_rotate4 : 1; // same as above but for channel 4 only
};
public:
// constructor
scc_core(std::string tag)
: vgsound_emu_core(tag)
, m_voice{*this, *this, *this, *this, *this}
, m_test(test_t())
, m_out(0)
, m_reg{0}
{
}
// destructor
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
inline s32 out() { return m_out; } // output to DA0...DA10 pin
inline u8 reg(u8 address) { return m_reg[address]; }
// for preview
inline s32 voice_out(u8 voice) { return (voice < 5) ? m_voice[voice].out() : 0; }
protected:
// 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);
// internal values
std::array<voice_t, 5> m_voice; // 5 voices
test_t m_test; // test register
s32 m_out = 0; // output to DA0...10
std::array<u8, 256> m_reg = {0}; // register pool
};
// SCC core
class k051649_scc_core : public scc_core
{
public:
// constructor
k051649_scc_core(std::string tag = "k051649_scc")
: scc_core(tag)
{
}
// 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:
// constructor
k052539_scc_core(std::string tag = "k052539_scc")
: k051649_scc_core(tag)
{
}
// accessors
virtual u8 scc_r(bool is_sccplus, u8 address) override;
virtual void scc_w(bool is_sccplus, u8 address, u8 data) override;
};
// MegaROM Mapper with SCC
class k051649_core : public k051649_scc_core
{
friend class vgsound_emu_mem_intf; // for megaROM mapper
private:
// mapper classes
class k051649_mapper_t : public vgsound_emu_core
{
public:
k051649_mapper_t()
: vgsound_emu_core("k051649_mapper")
, m_bank{0, 1, 2, 3}
{
}
// internal state
void reset();
// setters
inline void set_bank(u8 slot, u8 bank) { m_bank[slot & 3] = bank; }
// getters
inline u8 bank(u8 slot) { return m_bank[slot & 3]; }
private:
// registers
u8 m_bank[4] = {0, 1, 2, 3};
};
public:
// constructor
k051649_core(vgsound_emu_mem_intf &intf)
: k051649_scc_core("k051649")
, m_intf(intf)
, m_mapper(k051649_mapper_t())
, m_scc_enable(false)
{
}
// accessors
u8 read(u16 address);
void write(u16 address, u8 data);
virtual void reset() override;
private:
vgsound_emu_mem_intf m_intf;
k051649_mapper_t m_mapper;
bool m_scc_enable = false;
};
// MegaRAM Mapper with SCC
class k052539_core : public k052539_scc_core
{
friend class vgsound_emu_mem_intf; // for megaRAM mapper
private:
// mapper classes
class k052539_mapper_t : public vgsound_emu_core
{
public:
k052539_mapper_t()
: vgsound_emu_core("k052539_mapper")
, m_bank{0, 1, 2, 3}
, m_ram_enable{false}
{
}
// internal state
void reset();
// setters
inline void set_bank(u8 slot, u8 bank) { m_bank[slot & 3] = bank; }
inline void set_ram_enable(u8 slot, bool ram_enable)
{
m_ram_enable[slot & 3] = ram_enable;
}
// getters
inline u8 bank(u8 slot) { return m_bank[slot & 3]; }
inline bool ram_enable(u8 slot) { return m_ram_enable[slot & 3]; }
private:
// registers
std::array<u8, 4> m_bank = {0, 1, 2, 3};
std::array<bool, 4> m_ram_enable = {false};
};
public:
// constructor
k052539_core(vgsound_emu_mem_intf &intf)
: k052539_scc_core("k052539")
, m_intf(intf)
, m_mapper(k052539_mapper_t())
, m_scc_enable(false)
, m_is_sccplus(false)
{
}
// accessors
u8 read(u16 address);
void write(u16 address, u8 data);
virtual void reset() override;
private:
vgsound_emu_mem_intf m_intf;
k052539_mapper_t m_mapper;
bool m_scc_enable = false;
bool m_is_sccplus = false;
};
#endif

View file

@ -0,0 +1,33 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): (Author name)
Template for sound emulation core
*/
#include "template.hpp"
void template_core::tick()
{
// tick per each clock
}
void template_core::reset()
{
// reset this chip
std::fill(m_array.begin(), m_array.end(), 0); // std::fill() for fill std::array, std::vector
}
/*
template voice function
void template_core::voice_t::tick()
{
// tick per each voice
}
void template_core::voice_t::reset()
{
// reset this voice
}
*/

View file

@ -0,0 +1,88 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): (Author name)
Template for sound emulation core, also guideline
*/
#ifndef _VGSOUND_EMU_SRC_TEMPLATE_HPP // _VGSOUND_EMU_ABSOLUTE_PATH_OF_THIS_FILE
#define _VGSOUND_EMU_SRC_TEMPLATE_HPP
#pragma once
#include "../core/util.hpp"
class template_core : public vgsound_emu_core
{
friend class vgsound_emu_mem_intf; // common memory interface if exists
private: // protected: if shares between inheritances
// place classes and local constants here if exists
// template voice classes
class voice_t : public vgsound_emu_core
{
public:
// constructor
voice_t(template_core &host)
: vgsound_emu_core("your_voice_tag_here")
, m_host(host)
, m_something(0)
{
}
// internal state
void reset();
void tick();
// accessors, getters, setters
// setters
void set_something(s32 something) { m_something = something; }
// getters
s32 something() { return m_something; }
private:
// registers
template_core &m_host;
s32 m_something = 0; // register
};
public:
// place constructor and destructor, getter and setter for local variables,
// off-chip interfaces, update routine here only if can't be local
// constructor
template_core(vgsound_emu_mem_intf &intf)
: vgsound_emu_core("your_core_tag_here")
// initialize all variables in constructor, because constructor is also executable
// anywhere, and it works as initializer.
, m_voice{*this}
, m_intf(intf)
, m_array{0}
, m_vector{0}
{
}
// accessors, getters, setters
// internal state
void reset();
void tick();
protected:
// place local variables and functions here if shares between inheritances
private:
// place local variables and functions here
std::array<voice_t, 1 /*number of voices*/> m_voice; // voice classes
vgsound_emu_mem_intf &m_intf; // common memory interface
std::array<u8 /*type*/, 8 /*size of array*/> m_array = {
0}; // std::array for static size array
std::vector<u8 /*type*/> m_vector = {0}; // std::vector for variable size array
};
#endif

View file

@ -0,0 +1,97 @@
# Konami VRC VI
## Summary
- 2 voice pulse wave
- 8 level duty or volume only mode
- 1 voice sawtooth wave
- Internal mapper and timer
## Source code
- vrcvi.hpp: Base header
- vrcvi.cpp: Source emulation core
## Description
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 calculation
```
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
```

View file

@ -0,0 +1,260 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Konami VRC VI sound emulation core
*/
#include "vrcvi.hpp"
void vrcvi_core::tick()
{
m_out = 0;
if (!m_control.halt()) // Halt flag
{
// tick per each clock
for (auto &elem : m_pulse)
{
m_out += elem.get_output(); // add 4 bit pulse output
}
m_out += m_sawtooth.get_output(); // add 5 bit sawtooth output
}
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;
}
bool vrcvi_core::alu_t::tick()
{
if (m_divider.enable())
{
const u16 temp = m_counter;
// post decrement
if (bitfield(m_host.m_control.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.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.shift(), 1)
? (bitfield(temp, 8, 4) == 0)
: (bitfield(m_host.m_control.shift(), 0) ? (bitfield(temp, 4, 8) == 0)
: (bitfield(temp, 0, 12) == 0));
if (carry)
{
m_counter = m_divider.divider();
}
return carry;
}
return false;
}
bool vrcvi_core::pulse_t::tick()
{
if (!m_divider.enable())
{
return false;
}
if (vrcvi_core::alu_t::tick())
{
m_cycle = bitfield(m_cycle + 1, 0, 4);
}
return m_control.mode() ? true : ((m_cycle > m_control.duty()) ? true : false);
}
bool vrcvi_core::sawtooth_t::tick()
{
if (!m_divider.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;
}
s8 vrcvi_core::pulse_t::get_output()
{
// add 4 bit pulse output
m_out = tick() ? m_control.volume() : 0;
return m_out;
}
s8 vrcvi_core::sawtooth_t::get_output()
{
// add 5 bit sawtooth output
m_out = tick() ? bitfield(m_accum, 3, 5) : 0;
return m_out;
}
void vrcvi_core::alu_t::reset()
{
m_divider.reset();
m_counter = 0;
m_cycle = 0;
m_out = 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.enable())
{
if (!m_timer_control.sync()) // scanline sync mode
{
m_prescaler -= 3;
if (m_prescaler <= 0)
{
m_prescaler += 341;
return true;
}
}
}
return (m_timer_control.enable() && m_timer_control.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.control().write(data);
break;
case 0x01: // Pitch LSB - 0x9001/0x9002 (Pulse 1), 0xa001/0xa002 (Pulse 2)
v.divider().write(false, data);
break;
case 0x02: // Pitch MSB, Enable/Disable - 0x9002/0x9001 (Pulse 1), 0xa002/0xa001 (Pulse 2)
v.divider().write(true, data);
if (!v.divider().enable())
{ // Reset duty cycle
v.clear_cycle();
}
break;
}
}
void vrcvi_core::saw_w(u8 address, u8 data)
{
switch (address)
{
case 0x00: // Sawtooth Accumulate - 0xb000
m_sawtooth.set_rate(bitfield(data, 0, 6));
break;
case 0x01: // Pitch LSB - 0xb001/0xb002 (Sawtooth)
m_sawtooth.divider().write(false, data);
break;
case 0x02: // Pitch MSB, Enable/Disable - 0xb002/0xb001 (Sawtooth)
m_sawtooth.divider().write(true, data);
if (!m_sawtooth.divider().enable())
{ // Reset accumulator
m_sawtooth.clear_accum();
}
break;
}
}
void vrcvi_core::timer_w(u8 address, u8 data)
{
switch (address)
{
case 0x00: // Timer latch - 0xf000
m_timer.set_counter_latch(data);
break;
case 0x01: // Timer control - 0xf001/0xf002
m_timer.timer_control_w(data);
break;
case 0x02: // IRQ Acknowledge - 0xf002/0xf001
m_timer.irq_ack();
break;
}
}
void vrcvi_core::control_w(u8 data)
{
// Global control - 0x9003
m_control.write(data);
}

View file

@ -0,0 +1,407 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Konami VRC VI sound emulation core
*/
#ifndef _VGSOUND_EMU_SRC_VRCVI_HPP
#define _VGSOUND_EMU_SRC_VRCVI_HPP
#pragma once
#include "../core/util.hpp"
class vrcvi_intf : public vgsound_emu_core
{
public:
vrcvi_intf()
: vgsound_emu_core("vrc_vi_intf")
{
}
virtual void irq_w(bool irq) {}
};
class vrcvi_core : public vgsound_emu_core
{
friend class vrcvi_intf;
private:
// Common ALU for sound channels
class alu_t : public vgsound_emu_core
{
private:
class divider_t : public vgsound_emu_core
{
public:
divider_t()
: vgsound_emu_core("vrc_vi_frequency_divider")
, m_divider(0)
, m_enable(0)
{
}
void reset()
{
m_divider = 0;
m_enable = 0;
}
void write(bool msb, u8 data);
// getters
inline u16 divider() { return m_divider; }
inline bool enable() { return m_enable; }
private:
u16 m_divider : 12; // divider (pitch)
u16 m_enable : 1; // channel disable flag
};
public:
alu_t(std::string tag, vrcvi_core &host)
: vgsound_emu_core(tag)
, m_host(host)
, m_divider(divider_t())
, m_counter(0)
, m_cycle(0)
, m_out(0)
{
}
virtual void reset();
virtual bool tick();
virtual s8 get_output()
{
m_out = 0;
return 0;
}
// accessors
inline void clear_cycle() { m_cycle = 0; }
// getters
divider_t &divider() { return m_divider; }
inline u16 counter() { return m_counter; }
inline u8 cycle() { return m_cycle; }
// for previwe/debug only
inline s8 out() { return m_out; }
protected:
vrcvi_core &m_host;
divider_t m_divider;
u16 m_counter = 0; // clock counter
u8 m_cycle = 0; // clock cycle
s8 m_out = 0; // output per channel
};
// 2 Pulse channels
class pulse_t : public alu_t
{
private:
// Control bits
class pulse_control_t
{
public:
pulse_control_t()
: m_mode(0)
, m_duty(0)
, m_volume(0)
{
}
void reset()
{
m_mode = 0;
m_duty = 0;
m_volume = 0;
}
// accessors
inline void write(u8 data)
{
m_mode = (data >> 7) & 0x1;
m_duty = (data >> 4) & 0x7;
m_volume = (data >> 0) & 0xf;
}
// getters
inline bool mode() { return m_mode; }
inline u8 duty() { return m_duty; }
inline u8 volume() { return m_volume; }
private:
u8 m_mode : 1; // duty toggle flag
u8 m_duty : 3; // 3 bit duty cycle
u8 m_volume : 4; // 4 bit volume
};
public:
pulse_t(vrcvi_core &host)
: alu_t("vrc_vi_pulse", host)
, m_control(pulse_control_t())
{
}
virtual void reset() override;
virtual bool tick() override;
virtual s8 get_output() override;
// getters
pulse_control_t &control() { return m_control; }
private:
pulse_control_t m_control;
};
// 1 Sawtooth channel
class sawtooth_t : public alu_t
{
public:
sawtooth_t(vrcvi_core &host)
: alu_t("vrc_vi_sawtooth", host)
, m_rate(0)
, m_accum(0)
{
}
virtual void reset() override;
virtual bool tick() override;
virtual s8 get_output() override;
// accessors
inline void clear_accum() { m_accum = 0; }
// setters
inline void set_rate(u8 rate) { m_rate = rate; }
// getters
inline u8 rate() { return m_rate; }
inline u8 accum() { return m_accum; }
private:
u8 m_rate = 0; // sawtooth accumulate rate
u8 m_accum = 0; // sawtooth accumulator, high 5 bit is accumulated to output
};
// Internal timer
class timer_t : public vgsound_emu_core
{
private:
// Control bits
class timer_control_t : public vgsound_emu_core
{
public:
timer_control_t()
: vgsound_emu_core("vrc_vi_timer_control")
, 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;
}
// accessors
inline void irq_set(bool irq) { m_irq_trigger = irq ? 1 : 0; }
// setters
inline void set_enable_ack(bool enable_ack)
{
m_enable_ack = enable_ack ? 1 : 0;
}
inline void set_enable(bool enable) { m_enable = enable ? 1 : 0; }
inline void set_sync(bool sync) { m_sync = sync ? 1 : 0; }
// getters
inline bool irq_trigger() { return m_irq_trigger; }
inline bool enable_ack() { return m_enable_ack; }
inline bool enable() { return m_enable; }
inline bool sync() { return m_sync; }
private:
u8 m_irq_trigger : 1;
u8 m_enable_ack : 1;
u8 m_enable : 1;
u8 m_sync : 1;
};
public:
timer_t(vrcvi_core &host)
: vgsound_emu_core("vrc_vi_timer")
, m_host(host)
, m_timer_control(timer_control_t())
, m_prescaler(341)
, m_counter(0)
, m_counter_latch(0)
{
}
void reset();
bool tick();
void counter_tick();
// IRQ update
void update() { m_host.m_intf.irq_w(m_timer_control.irq_trigger()); }
void irq_set()
{
if (!m_timer_control.irq_trigger())
{
m_timer_control.irq_set(true);
update();
}
}
void irq_clear()
{
if (m_timer_control.irq_trigger())
{
m_timer_control.irq_set(false);
update();
}
}
// accessors
void reset_counter()
{
m_counter = m_counter_latch;
m_prescaler = 341;
}
void timer_control_w(u8 data)
{
m_timer_control.set_enable_ack((data >> 0) & 1);
m_timer_control.set_enable((data >> 1) & 1);
m_timer_control.set_sync((data >> 2) & 1);
if (m_timer_control.enable())
{
reset_counter();
}
irq_clear();
}
void irq_ack()
{
irq_clear();
m_timer_control.set_enable(m_timer_control.enable_ack());
}
// setters
inline void set_counter_latch(u8 counter_latch) { m_counter_latch = counter_latch; }
// getters
timer_control_t &timer_control() { return m_timer_control; }
inline s16 prescaler() { return m_prescaler; }
inline u8 counter() { return m_counter; }
inline u8 counter_latch() { return m_counter_latch; }
private:
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
};
class global_control_t : public vgsound_emu_core
{
public:
global_control_t()
: vgsound_emu_core("vrc_vi_global_control")
, m_halt(0)
, m_shift(0)
{
}
void reset()
{
m_halt = 0;
m_shift = 0;
}
// accessors
inline void write(u8 data)
{
m_halt = (data >> 0) & 1;
m_shift = (data >> 1) & 3;
}
// getters
inline bool halt() { return m_halt; }
inline u8 shift() { return m_shift; }
private:
u8 m_halt : 1; // halt sound
u8 m_shift : 2; // 4/8 bit right shift
};
public:
// constructor
vrcvi_core(vrcvi_intf &intf)
: vgsound_emu_core("vrc_vi")
, m_intf(intf)
, m_pulse{*this, *this}
, m_sawtooth(*this)
, m_timer(*this)
, m_control(global_control_t())
, m_out(0)
{
}
// 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
inline s8 out() { return m_out; }
// for debug/preview only
inline s8 pulse_out(u8 pulse) { return (pulse < 2) ? m_pulse[pulse].out() : 0; }
inline s8 sawtooth_out() { return m_sawtooth.out(); }
private:
vrcvi_intf &m_intf;
std::array<pulse_t, 2> m_pulse; // 2 pulse channels
sawtooth_t m_sawtooth; // sawtooth channel
timer_t m_timer; // internal timer
global_control_t m_control; // control
s8 m_out = 0; // 6 bit output
};
#endif

View file

@ -0,0 +1,97 @@
# Seta/Allumer X1-010
## Summary
- 16 voice wavetable or PCM
- 8 bit signed for both wavetable and PCM
- 128 width long waveform
- wavetable playback must be paired with envelope
- envelope shape is 4 bit stereo, 128 width long waveform
- waveform and envelope shape stored at each half area on RAM space
- total accessible memory for PCM: 1 MByte
## Source code
- x1_010.hpp: Base header
- x1_010.cpp: Source emulation core
## Description
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
### Voice registers (0000...007f)
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
5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers)
```
0008...000f Voice #1 Register
...
0078...007f Voice #15 Register
### Envelope shape data (0080...0fff)
Same format as volume; Each nibble is for each output
0080...00ff Envelope shape #1 data
0100...017f Envelope shape #2 data
...
0f80...0fff Envelope shape #31 data
### Waveform data (1000...1fff)
1000...107f Waveform #0 data
1080...10ff Waveform #1 data
...
1f80...1fff Waveform #31 data
## Frequency calculation
```
Wavetable, Divider Clear: Frequency value * (Input clock / 524288)
Wavetable, Divider Set: Frequency value * (Input clock / 1048576)
PCM, Divider Clear: Frequency value * (Input clock / 8192)
PCM, Divider Set: Frequency value * (Input clock / 16384)
Envelope: Envelope period * (Input clock / 524288) - Frequency divider not affected?
```
Frequency divider is higher precision or just right shift? needs verification.

View file

@ -0,0 +1,163 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Seta/Allumer X1-010 Emulation core
*/
#include "x1_010.hpp"
void x1_010_core::tick()
{
// reset output
m_out[0] = m_out[1] = 0;
for (voice_t &elem : m_voice)
{
elem.tick();
m_out[0] += elem.out(0);
m_out[1] += elem.out(1);
}
}
void x1_010_core::voice_t::tick()
{
m_out[0] = m_out[1] = 0;
if (m_flag.keyon())
{
if (m_flag.wavetable()) // Wavetable
{
// envelope, each nibble is for each output
u8 vol =
m_host.m_envelope[(bitfield(m_end_envshape, 0, 5) << 7) | bitfield(m_env_acc, 10, 7)];
m_vol_out[0] = bitfield(vol, 4, 4);
m_vol_out[1] = bitfield(vol, 0, 4);
m_env_acc += m_start_envfreq;
if (m_flag.env_oneshot() && bitfield(m_env_acc, 17))
{
m_flag.set_keyon(false);
}
else
{
m_env_acc = bitfield(m_env_acc, 0, 17);
}
// get wavetable data
m_data = m_host.m_wave[(bitfield(m_vol_wave, 0, 5) << 7) | bitfield(m_acc, 11, 7)];
m_acc = bitfield(m_acc + (m_freq << (1 - m_flag.div())), 0, 18);
}
else // PCM sample
{
// volume register, each nibble is for each output
m_vol_out[0] = bitfield(m_vol_wave, 4, 4);
m_vol_out[1] = bitfield(m_vol_wave, 0, 4);
// get PCM sample
m_data = m_host.m_intf.read_byte(bitfield(m_acc, 5, 20));
m_acc += u32(bitfield(m_freq, 0, 8)) << (1 - m_flag.div());
if ((m_acc >> 17) > u32(0xff ^ m_end_envshape))
{
m_flag.set_keyon(false);
}
}
m_out[0] = m_data * m_vol_out[0];
m_out[1] = m_data * m_vol_out[1];
}
}
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 (m_flag.div() << 7) | (m_flag.env_oneshot() << 2) | (m_flag.wavetable() << 1) |
(m_flag.keyon() << 0);
case 0x01: return m_vol_wave;
case 0x02: return bitfield(m_freq, 0, 8);
case 0x03: return bitfield(m_freq, 8, 8);
case 0x04: return m_start_envfreq;
case 0x05: return m_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 = m_flag.keyon();
m_flag.write(data);
if (!prev_keyon && m_flag.keyon()) // Key on
{
m_acc = m_flag.wavetable() ? 0 : (u32(m_start_envfreq) << 16);
m_env_acc = 0;
}
break;
}
case 0x01: m_vol_wave = data; break;
case 0x02: m_freq = (m_freq & 0xff00) | data; break;
case 0x03: m_freq = (m_freq & 0x00ff) | (u16(data) << 8); break;
case 0x04: m_start_envfreq = data; break;
case 0x05: m_end_envshape = data; break;
default: break;
}
}
void x1_010_core::voice_t::reset()
{
m_flag.reset();
m_vol_wave = 0;
m_freq = 0;
m_start_envfreq = 0;
m_end_envshape = 0;
m_acc = 0;
m_env_acc = 0;
m_data = 0;
m_vol_out.fill(0);
m_out.fill(0);
}
void x1_010_core::reset()
{
for (auto &elem : m_voice)
{
elem.reset();
}
m_envelope.fill(0);
m_wave.fill(0);
m_out.fill(0);
}

View file

@ -0,0 +1,179 @@
/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Seta/Allumer X1-010 Emulation core
*/
#ifndef _VGSOUND_EMU_SRC_X1_010_HPP
#define _VGSOUND_EMU_SRC_X1_010_HPP
#pragma once
#include "../core/util.hpp"
class x1_010_core : public vgsound_emu_core
{
friend class vgsound_emu_mem_intf;
private:
// 16 voices in chip
class voice_t : public vgsound_emu_core
{
private:
class flag_t : public vgsound_emu_core
{
public:
flag_t()
: vgsound_emu_core("x1_010_voice_flag")
, m_div(0)
, m_env_oneshot(0)
, m_wavetable(0)
, m_keyon(0)
{
}
// internal state
void reset()
{
m_div = 0;
m_env_oneshot = 0;
m_wavetable = 0;
m_keyon = 0;
}
// register accessor
inline void write(u8 data)
{
m_div = (data >> 7) & 1;
m_env_oneshot = (data >> 2) & 1;
m_wavetable = (data >> 1) & 1;
m_keyon = (data >> 0) & 1;
}
// Setters
inline void set_keyon(bool keyon) { m_keyon = keyon; }
// Getters
inline bool div() { return m_div; }
inline bool env_oneshot() { return m_env_oneshot; }
inline bool wavetable() { return m_wavetable; }
inline bool keyon() { return m_keyon; }
private:
u8 m_div : 1;
u8 m_env_oneshot : 1;
u8 m_wavetable : 1;
u8 m_keyon : 1;
};
public:
// constructor
voice_t(x1_010_core &host)
: vgsound_emu_core("x1_010_voice")
, m_host(host)
, m_flag(flag_t())
, m_vol_wave(0)
, m_freq(0)
, m_start_envfreq(0)
, m_end_envshape(0)
, m_acc(0)
, m_env_acc(0)
, m_data(0)
, m_vol_out{0}
, m_out{0}
{
}
// internal state
void reset();
void tick();
// register accessor
u8 reg_r(u8 offset);
void reg_w(u8 offset, u8 data);
// getters
inline s32 out(u8 ch) { return m_out[ch & 1]; }
private:
// host flag
x1_010_core &m_host;
// registers
flag_t m_flag;
u8 m_vol_wave = 0;
u16 m_freq = 0;
u8 m_start_envfreq = 0;
u8 m_end_envshape = 0;
// internal registers
u32 m_acc = 0;
u32 m_env_acc = 0;
s8 m_data = 0;
std::array<u8, 2> m_vol_out = {0};
// for preview only
std::array<s32, 2> m_out = {0};
};
public:
// constructor
x1_010_core(vgsound_emu_mem_intf &intf)
: vgsound_emu_core("x1_010")
, m_voice{*this,
*this,
*this,
*this,
*this,
*this,
*this,
*this,
*this,
*this,
*this,
*this,
*this,
*this,
*this,
*this}
, m_intf(intf)
, m_envelope{0}
, m_wave{0}
, m_out{0}
{
}
// register accessor
u8 ram_r(u16 offset);
void ram_w(u16 offset, u8 data);
// getters
inline s32 output(u8 ch) { return m_out[ch & 1]; }
// internal state
void reset();
void tick();
// for preview only
inline s32 voice_out(u8 voice, u8 ch)
{
return (voice < 16) ? m_voice[voice].out(ch & 1) : 0;
}
private:
std::array<voice_t, 16> m_voice;
vgsound_emu_mem_intf &m_intf;
// RAM
std::array<u8, 0x1000> m_envelope = {0};
std::array<u8, 0x1000> m_wave = {0};
// output data
std::array<s32, 2> m_out = {0};
};
#endif