mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-30 08:23:01 +00:00
Temporary workaround for accidently removed libs
This commit is contained in:
parent
52476ec1a6
commit
bf2ec8f1c4
48 changed files with 8972 additions and 0 deletions
1
extern/vgsound_emu-modified
vendored
Submodule
1
extern/vgsound_emu-modified
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 7b988a6714ebf61e8a5fad5c9ccbda2b85853fe1
|
154
vgsound_emu-modified/.clang-format
Normal file
154
vgsound_emu-modified/.clang-format
Normal 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
18
vgsound_emu-modified/.gitignore
vendored
Normal 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
|
17
vgsound_emu-modified/CHANGELOG.md
Normal file
17
vgsound_emu-modified/CHANGELOG.md
Normal 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).
|
161
vgsound_emu-modified/CMakeLists.txt
Normal file
161
vgsound_emu-modified/CMakeLists.txt
Normal 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)
|
19
vgsound_emu-modified/LICENSE
Normal file
19
vgsound_emu-modified/LICENSE
Normal 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.
|
7
vgsound_emu-modified/MODIFIED.md
Normal file
7
vgsound_emu-modified/MODIFIED.md
Normal 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/).
|
131
vgsound_emu-modified/README.md
Normal file
131
vgsound_emu-modified/README.md
Normal 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)
|
263
vgsound_emu-modified/vgsound_emu/src/core/util.hpp
Normal file
263
vgsound_emu-modified/vgsound_emu/src/core/util.hpp
Normal 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
|
75
vgsound_emu-modified/vgsound_emu/src/core/vox/vox.cpp
Normal file
75
vgsound_emu-modified/vgsound_emu/src/core/vox/vox.cpp
Normal 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);
|
||||||
|
}
|
115
vgsound_emu-modified/vgsound_emu/src/core/vox/vox.hpp
Normal file
115
vgsound_emu-modified/vgsound_emu/src/core/vox/vox.hpp
Normal 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
|
88
vgsound_emu-modified/vgsound_emu/src/es550x/README.md
Normal file
88
vgsound_emu-modified/vgsound_emu/src/es550x/README.md
Normal 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.
|
456
vgsound_emu-modified/vgsound_emu/src/es550x/es5504.cpp
Normal file
456
vgsound_emu-modified/vgsound_emu/src/es550x/es5504.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
117
vgsound_emu-modified/vgsound_emu/src/es550x/es5504.hpp
Normal file
117
vgsound_emu-modified/vgsound_emu/src/es550x/es5504.hpp
Normal 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
|
652
vgsound_emu-modified/vgsound_emu/src/es550x/es5505.cpp
Normal file
652
vgsound_emu-modified/vgsound_emu/src/es550x/es5505.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
304
vgsound_emu-modified/vgsound_emu/src/es550x/es5505.hpp
Normal file
304
vgsound_emu-modified/vgsound_emu/src/es550x/es5505.hpp
Normal 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
|
870
vgsound_emu-modified/vgsound_emu/src/es550x/es5506.cpp
Normal file
870
vgsound_emu-modified/vgsound_emu/src/es550x/es5506.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
385
vgsound_emu-modified/vgsound_emu/src/es550x/es5506.hpp
Normal file
385
vgsound_emu-modified/vgsound_emu/src/es550x/es5506.hpp
Normal 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
|
34
vgsound_emu-modified/vgsound_emu/src/es550x/es550x.cpp
Normal file
34
vgsound_emu-modified/vgsound_emu/src/es550x/es550x.cpp
Normal 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();
|
||||||
|
}
|
610
vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp
Normal file
610
vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp
Normal 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
|
131
vgsound_emu-modified/vgsound_emu/src/es550x/es550x_alu.cpp
Normal file
131
vgsound_emu-modified/vgsound_emu/src/es550x/es550x_alu.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
23
vgsound_emu-modified/vgsound_emu/src/k005289/README.md
Normal file
23
vgsound_emu-modified/vgsound_emu/src/k005289/README.md
Normal 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)
|
||||||
|
```
|
42
vgsound_emu-modified/vgsound_emu/src/k005289/k005289.cpp
Normal file
42
vgsound_emu-modified/vgsound_emu/src/k005289/k005289.cpp
Normal 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;
|
||||||
|
}
|
83
vgsound_emu-modified/vgsound_emu/src/k005289/k005289.hpp
Normal file
83
vgsound_emu-modified/vgsound_emu/src/k005289/k005289.hpp
Normal 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
|
75
vgsound_emu-modified/vgsound_emu/src/k007232/README.md
Normal file
75
vgsound_emu-modified/vgsound_emu/src/k007232/README.md
Normal 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>
|
169
vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp
Normal file
169
vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp
Normal 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;
|
||||||
|
}
|
115
vgsound_emu-modified/vgsound_emu/src/k007232/k007232.hpp
Normal file
115
vgsound_emu-modified/vgsound_emu/src/k007232/k007232.hpp
Normal 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
|
109
vgsound_emu-modified/vgsound_emu/src/k053260/README.md
Normal file
109
vgsound_emu-modified/vgsound_emu/src/k053260/README.md
Normal 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)
|
||||||
|
```
|
290
vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp
Normal file
290
vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp
Normal 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;
|
||||||
|
}
|
262
vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp
Normal file
262
vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp
Normal 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
|
67
vgsound_emu-modified/vgsound_emu/src/msm6295/README.md
Normal file
67
vgsound_emu-modified/vgsound_emu/src/msm6295/README.md
Normal 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
|
||||||
|
```
|
179
vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.cpp
Normal file
179
vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
142
vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.hpp
Normal file
142
vgsound_emu-modified/vgsound_emu/src/msm6295/msm6295.hpp
Normal 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
|
88
vgsound_emu-modified/vgsound_emu/src/n163/README.md
Normal file
88
vgsound_emu-modified/vgsound_emu/src/n163/README.md
Normal 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
|
120
vgsound_emu-modified/vgsound_emu/src/n163/n163.cpp
Normal file
120
vgsound_emu-modified/vgsound_emu/src/n163/n163.cpp
Normal 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;
|
||||||
|
}
|
109
vgsound_emu-modified/vgsound_emu/src/n163/n163.hpp
Normal file
109
vgsound_emu-modified/vgsound_emu/src/n163/n163.hpp
Normal 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
|
314
vgsound_emu-modified/vgsound_emu/src/scc/README.md
Normal file
314
vgsound_emu-modified/vgsound_emu/src/scc/README.md
Normal 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>
|
461
vgsound_emu-modified/vgsound_emu/src/scc/scc.cpp
Normal file
461
vgsound_emu-modified/vgsound_emu/src/scc/scc.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
320
vgsound_emu-modified/vgsound_emu/src/scc/scc.hpp
Normal file
320
vgsound_emu-modified/vgsound_emu/src/scc/scc.hpp
Normal 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
|
33
vgsound_emu-modified/vgsound_emu/src/template/template.cpp
Normal file
33
vgsound_emu-modified/vgsound_emu/src/template/template.cpp
Normal 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
|
||||||
|
}
|
||||||
|
*/
|
88
vgsound_emu-modified/vgsound_emu/src/template/template.hpp
Normal file
88
vgsound_emu-modified/vgsound_emu/src/template/template.hpp
Normal 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
|
97
vgsound_emu-modified/vgsound_emu/src/vrcvi/README.md
Normal file
97
vgsound_emu-modified/vgsound_emu/src/vrcvi/README.md
Normal 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
|
||||||
|
```
|
260
vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.cpp
Normal file
260
vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.cpp
Normal 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);
|
||||||
|
}
|
407
vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.hpp
Normal file
407
vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.hpp
Normal 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 ÷r() { 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
|
97
vgsound_emu-modified/vgsound_emu/src/x1_010/README.md
Normal file
97
vgsound_emu-modified/vgsound_emu/src/x1_010/README.md
Normal 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.
|
163
vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.cpp
Normal file
163
vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.cpp
Normal 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);
|
||||||
|
}
|
179
vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.hpp
Normal file
179
vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.hpp
Normal 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
|
Loading…
Reference in a new issue