mirror of
https://github.com/tildearrow/furnace.git
synced 2024-12-04 02:07:27 +00:00
Merge branch 'tildearrow:master' into master
This commit is contained in:
commit
d0a86d7c2a
217 changed files with 22478 additions and 7294 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -187,6 +187,8 @@ jobs:
|
|||
export USE_WAE=ON
|
||||
export CMAKE_EXTRA_ARGS=()
|
||||
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
|
||||
# 1. Go to hell
|
||||
export USE_WAE=OFF
|
||||
CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}')
|
||||
|
||||
# Force static linking
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,3 +13,4 @@ test/songs/
|
|||
test/delta/
|
||||
test/result/
|
||||
.vs/
|
||||
CMakeSettings.json
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -19,6 +19,3 @@
|
|||
[submodule "extern/adpcm"]
|
||||
path = extern/adpcm
|
||||
url = https://github.com/superctr/adpcm
|
||||
[submodule "extern/Nuked-OPL3"]
|
||||
path = extern/Nuked-OPL3
|
||||
url = https://github.com/nukeykt/Nuked-OPL3.git
|
||||
|
|
|
@ -237,7 +237,7 @@ extern/adpcm/ymz_codec.c
|
|||
extern/Nuked-OPN2/ym3438.c
|
||||
extern/opm/opm.c
|
||||
extern/Nuked-OPLL/opll.c
|
||||
extern/Nuked-OPL3/opl3.c
|
||||
extern/opl/opl3.c
|
||||
src/engine/platform/sound/sn76496.cpp
|
||||
src/engine/platform/sound/ay8910.cpp
|
||||
src/engine/platform/sound/saa1099.cpp
|
||||
|
@ -250,6 +250,13 @@ src/engine/platform/sound/nes/mmc5.c
|
|||
src/engine/platform/sound/vera_psg.c
|
||||
src/engine/platform/sound/vera_pcm.c
|
||||
|
||||
src/engine/platform/sound/nes_nsfplay/nes_apu.cpp
|
||||
src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp
|
||||
src/engine/platform/sound/nes_nsfplay/nes_fds.cpp
|
||||
src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp
|
||||
src/engine/platform/sound/nes_nsfplay/nes_n106.cpp
|
||||
src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp
|
||||
|
||||
src/engine/platform/sound/c64/sid.cc
|
||||
src/engine/platform/sound/c64/voice.cc
|
||||
src/engine/platform/sound/c64/wave.cc
|
||||
|
@ -284,6 +291,8 @@ src/engine/platform/sound/x1_010/x1_010.cpp
|
|||
|
||||
src/engine/platform/sound/swan.cpp
|
||||
|
||||
src/engine/platform/sound/su.cpp
|
||||
|
||||
src/engine/platform/sound/k005289/k005289.cpp
|
||||
|
||||
src/engine/platform/sound/n163/n163.cpp
|
||||
|
@ -341,6 +350,7 @@ src/engine/platform/segapcm.cpp
|
|||
src/engine/platform/qsound.cpp
|
||||
src/engine/platform/x1_010.cpp
|
||||
src/engine/platform/lynx.cpp
|
||||
src/engine/platform/su.cpp
|
||||
src/engine/platform/swan.cpp
|
||||
src/engine/platform/vera.cpp
|
||||
src/engine/platform/bubsyswsg.cpp
|
||||
|
@ -386,6 +396,7 @@ src/gui/guiConst.cpp
|
|||
|
||||
src/gui/about.cpp
|
||||
src/gui/channels.cpp
|
||||
src/gui/chanOsc.cpp
|
||||
src/gui/compatFlags.cpp
|
||||
src/gui/cursor.cpp
|
||||
src/gui/dataList.cpp
|
||||
|
@ -393,6 +404,7 @@ src/gui/debugWindow.cpp
|
|||
src/gui/doAction.cpp
|
||||
src/gui/editing.cpp
|
||||
src/gui/editControls.cpp
|
||||
src/gui/effectList.cpp
|
||||
src/gui/insEdit.cpp
|
||||
src/gui/log.cpp
|
||||
src/gui/mixer.cpp
|
||||
|
|
|
@ -49,12 +49,27 @@ the coding style is described here:
|
|||
- `size_t` are 32-bit or 64-bit, depending on architecture.
|
||||
- in float/double operations, always use decimal and `f` if single-precision.
|
||||
- e.g. `1.0f` or `1.0` instead of `1`.
|
||||
- prefer `NULL` over `nullptr` or any other proprietary null.
|
||||
- don't use `auto` unless needed.
|
||||
- use `String` for `std::string` (this is typedef'd in ta-utils.h).
|
||||
- prefer using operator for String (std::string) comparisons (a=="").
|
||||
|
||||
some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style.
|
||||
|
||||
you don't have to follow this style. I will fix it after I accept your contribution.
|
||||
|
||||
additional guidelines:
|
||||
|
||||
- in general **strongly** avoid breaking compatibility.
|
||||
- do not touch loadFur/saveFur unless you know what you're doing!
|
||||
- new fields must be at the end of each block to ensure forward compatibility
|
||||
- likewise, the instrument read/write functions in DivInstrument have to be handled carefully
|
||||
- any change to the format requires a version bump (see `src/engine/engine.h`).
|
||||
- do not bump the version number under any circumstances!
|
||||
- if you are making major changes to the playback routine, make sure to test with older songs to ensure nothing breaks.
|
||||
- I will run a test suite to make sure this is the case.
|
||||
- if something breaks, you might want to add a compatibility flag (this requires changing the format though).
|
||||
|
||||
## Demo Songs
|
||||
|
||||
just put your demo song in `demos/`!
|
||||
|
|
|
@ -11,6 +11,8 @@ this is a multi-system chiptune tracker.
|
|||
|
||||
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
|
||||
|
||||
[see here](https://nightly.link/tildearrow/furnace/workflows/build/master) for unstable developer builds.
|
||||
|
||||
## features
|
||||
|
||||
- supports the following systems:
|
||||
|
@ -64,6 +66,8 @@ some people have provided packages for Unix/Unix-like distributions. here's a li
|
|||
|
||||
[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml)
|
||||
|
||||
if you can't download these artifacts (because GitHub requires you to be logged in), [go here](https://nightly.link/tildearrow/furnace/workflows/build/master) instead.
|
||||
|
||||
**NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building. please instead use Git as shown below.**
|
||||
|
||||
## dependencies
|
||||
|
|
32
TODO.md
Normal file
32
TODO.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
# to-do for 0.6pre1
|
||||
|
||||
- piano/input pad
|
||||
- note input via piano
|
||||
- input pad
|
||||
- settings
|
||||
- RF5C68 system
|
||||
- OPN system
|
||||
- OPNA system
|
||||
- ZX beeper system
|
||||
- Y8950 system
|
||||
- SCC/SCC+ system
|
||||
- maybe YMU759 ADPCM channel
|
||||
- ADPCM chips
|
||||
- Game Boy envelope macro/sequence
|
||||
- rewrite the system name detection function anyway
|
||||
- scroll instrument/wave/sample list when selecting item
|
||||
- unified data view
|
||||
- volume commands should work on Game Boy
|
||||
- macro editor menu
|
||||
- add another FM editor layout
|
||||
- try to find out why does VSlider not accept keyboard input
|
||||
- finish lock layout
|
||||
- if macros have release, note off should release them
|
||||
- add ability to select entire row when clicking on row number
|
||||
- store edit/followOrders/followPattern state in config
|
||||
- add ability to select a column by double clicking
|
||||
- add ability to move selection by dragging
|
||||
- Apply button in settings
|
||||
- find and replace
|
||||
- finish wave synth
|
||||
- add mono/poly note preview button
|
1
extern/Nuked-OPL3
vendored
1
extern/Nuked-OPL3
vendored
|
@ -1 +0,0 @@
|
|||
Subproject commit bb5c8d08a85779c42b75c79d7b84f365a1b93b66
|
8
extern/SAASound/src/SAADevice.cpp
vendored
8
extern/SAASound/src/SAADevice.cpp
vendored
|
@ -307,7 +307,7 @@ BYTE CSAADevice::_ReadData(void)
|
|||
}
|
||||
#endif
|
||||
|
||||
void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed)
|
||||
void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed, DivDispatchOscBuffer** oscBuf)
|
||||
{
|
||||
unsigned int temp_left, temp_right;
|
||||
unsigned int accum_left = 0, accum_right = 0;
|
||||
|
@ -316,21 +316,27 @@ void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& ri
|
|||
m_Noise0.Tick();
|
||||
m_Noise1.Tick();
|
||||
m_Amp0.TickAndOutputStereo(temp_left, temp_right);
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=(temp_left+temp_right)<<4;
|
||||
accum_left += temp_left;
|
||||
accum_right += temp_right;
|
||||
m_Amp1.TickAndOutputStereo(temp_left, temp_right);
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=(temp_left+temp_right)<<4;
|
||||
accum_left += temp_left;
|
||||
accum_right += temp_right;
|
||||
m_Amp2.TickAndOutputStereo(temp_left, temp_right);
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=(temp_left+temp_right)<<4;
|
||||
accum_left += temp_left;
|
||||
accum_right += temp_right;
|
||||
m_Amp3.TickAndOutputStereo(temp_left, temp_right);
|
||||
oscBuf[3]->data[oscBuf[3]->needle++]=(temp_left+temp_right)<<4;
|
||||
accum_left += temp_left;
|
||||
accum_right += temp_right;
|
||||
m_Amp4.TickAndOutputStereo(temp_left, temp_right);
|
||||
oscBuf[4]->data[oscBuf[4]->needle++]=(temp_left+temp_right)<<4;
|
||||
accum_left += temp_left;
|
||||
accum_right += temp_right;
|
||||
m_Amp5.TickAndOutputStereo(temp_left, temp_right);
|
||||
oscBuf[5]->data[oscBuf[5]->needle++]=(temp_left+temp_right)<<4;
|
||||
accum_left += temp_left;
|
||||
accum_right += temp_right;
|
||||
}
|
||||
|
|
2
extern/SAASound/src/SAADevice.h
vendored
2
extern/SAASound/src/SAADevice.h
vendored
|
@ -53,7 +53,7 @@ public:
|
|||
void _SetClockRate(unsigned int nClockRate);
|
||||
void _SetSampleRate(unsigned int nSampleRate);
|
||||
void _SetOversample(unsigned int nOversample);
|
||||
void _TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed);
|
||||
void _TickAndOutputStereo(unsigned int& left_mixed, unsigned int& right_mixed, DivDispatchOscBuffer** oscBuf);
|
||||
void _TickAndOutputSeparate(unsigned int& left_mixed, unsigned int& right_mixed,
|
||||
unsigned int& left0, unsigned int& right0,
|
||||
unsigned int& left1, unsigned int& right1,
|
||||
|
|
4
extern/SAASound/src/SAAImpl.cpp
vendored
4
extern/SAASound/src/SAAImpl.cpp
vendored
|
@ -298,7 +298,7 @@ void scale_for_output(unsigned int left_input, unsigned int right_input,
|
|||
*pBuffer++ = (right_output >> 8) & 0x00ff;
|
||||
}
|
||||
|
||||
void CSAASoundInternal::GenerateMany(BYTE* pBuffer, unsigned long nSamples)
|
||||
void CSAASoundInternal::GenerateMany(BYTE* pBuffer, unsigned long nSamples, DivDispatchOscBuffer** oscBuf)
|
||||
{
|
||||
unsigned int left_mixed, right_mixed;
|
||||
static double filterout_z1_left_mixed = 0, filterout_z1_right_mixed = 0;
|
||||
|
@ -376,7 +376,7 @@ void CSAASoundInternal::GenerateMany(BYTE* pBuffer, unsigned long nSamples)
|
|||
#endif
|
||||
while (nSamples--)
|
||||
{
|
||||
m_chip._TickAndOutputStereo(left_mixed, right_mixed);
|
||||
m_chip._TickAndOutputStereo(left_mixed, right_mixed, oscBuf);
|
||||
scale_for_output(left_mixed, right_mixed, oversample, m_bHighpass, nBoost, filterout_z1_left_mixed, filterout_z1_right_mixed, pBuffer);
|
||||
}
|
||||
|
||||
|
|
2
extern/SAASound/src/SAAImpl.h
vendored
2
extern/SAASound/src/SAAImpl.h
vendored
|
@ -68,7 +68,7 @@ public:
|
|||
unsigned short GetCurrentBytesPerSample(void);
|
||||
static unsigned short GetBytesPerSample(SAAPARAM uParam);
|
||||
|
||||
void GenerateMany(BYTE * pBuffer, unsigned long nSamples);
|
||||
void GenerateMany(BYTE * pBuffer, unsigned long nSamples, DivDispatchOscBuffer** oscBuf);
|
||||
|
||||
};
|
||||
|
||||
|
|
2
extern/SAASound/src/SAASndC.cpp
vendored
2
extern/SAASound/src/SAASndC.cpp
vendored
|
@ -81,7 +81,7 @@ unsigned long SAAAPI SAASNDGetSampleRate(SAAPARAM uParam)
|
|||
|
||||
void SAAAPI SAASNDGenerateMany(SAASND object, BYTE * pBuffer, unsigned long nSamples)
|
||||
{
|
||||
((LPCSAASOUND)(object))->GenerateMany(pBuffer, nSamples);
|
||||
((LPCSAASOUND)(object))->GenerateMany(pBuffer, nSamples, NULL);
|
||||
}
|
||||
|
||||
void SAAAPI SAASNDSetSampleRate(SAASND object, unsigned int nSampleRate)
|
||||
|
|
4
extern/SAASound/src/SAASound.h
vendored
4
extern/SAASound/src/SAASound.h
vendored
|
@ -61,6 +61,8 @@ typedef unsigned long SAAPARAM;
|
|||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "../../src/engine/dispatch.h"
|
||||
|
||||
class CSAASound
|
||||
{
|
||||
public:
|
||||
|
@ -79,7 +81,7 @@ public:
|
|||
virtual unsigned short GetCurrentBytesPerSample () = 0;
|
||||
static unsigned short GetBytesPerSample (SAAPARAM uParam);
|
||||
|
||||
virtual void GenerateMany (BYTE * pBuffer, unsigned long nSamples) = 0;
|
||||
virtual void GenerateMany (BYTE * pBuffer, unsigned long nSamples, DivDispatchOscBuffer** oscBuf) = 0;
|
||||
|
||||
virtual void SetClockRate(unsigned int nClockRate) = 0;
|
||||
virtual void SetSampleRate(unsigned int nSampleRate) = 0;
|
||||
|
|
2
extern/SAASound/src/types.h
vendored
2
extern/SAASound/src/types.h
vendored
|
@ -12,8 +12,10 @@
|
|||
defined(__arm__) || \
|
||||
(defined(__mips__) && defined(__MIPSEL__))
|
||||
#else
|
||||
#ifndef __BIG_ENDIAN
|
||||
#define __BIG_ENDIAN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef NULL
|
||||
|
|
39
extern/igfd/ImGuiFileDialog.cpp
vendored
39
extern/igfd/ImGuiFileDialog.cpp
vendored
|
@ -3295,7 +3295,8 @@ namespace IGFD
|
|||
const std::string& vFileName,
|
||||
const int& vCountSelectionMax,
|
||||
UserDatas vUserDatas,
|
||||
ImGuiFileDialogFlags vFlags)
|
||||
ImGuiFileDialogFlags vFlags,
|
||||
SelectFun vSelectFun)
|
||||
{
|
||||
if (prFileDialogInternal.puShowDialog) // if already opened, quit
|
||||
return;
|
||||
|
@ -3306,6 +3307,7 @@ namespace IGFD
|
|||
prFileDialogInternal.puDLGtitle = vTitle;
|
||||
prFileDialogInternal.puDLGuserDatas = vUserDatas;
|
||||
prFileDialogInternal.puDLGflags = vFlags;
|
||||
prFileDialogInternal.puDLGselFun = vSelectFun;
|
||||
prFileDialogInternal.puDLGoptionsPane = nullptr;
|
||||
prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f;
|
||||
prFileDialogInternal.puDLGmodal = false;
|
||||
|
@ -3335,7 +3337,8 @@ namespace IGFD
|
|||
const std::string& vFilePathName,
|
||||
const int& vCountSelectionMax,
|
||||
UserDatas vUserDatas,
|
||||
ImGuiFileDialogFlags vFlags)
|
||||
ImGuiFileDialogFlags vFlags,
|
||||
SelectFun vSelectFun)
|
||||
{
|
||||
if (prFileDialogInternal.puShowDialog) // if already opened, quit
|
||||
return;
|
||||
|
@ -3348,6 +3351,7 @@ namespace IGFD
|
|||
prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f;
|
||||
prFileDialogInternal.puDLGuserDatas = vUserDatas;
|
||||
prFileDialogInternal.puDLGflags = vFlags;
|
||||
prFileDialogInternal.puDLGselFun = vSelectFun;
|
||||
prFileDialogInternal.puDLGmodal = false;
|
||||
|
||||
auto ps = IGFD::Utils::ParsePathFileName(vFilePathName);
|
||||
|
@ -3390,7 +3394,8 @@ namespace IGFD
|
|||
const float& vSidePaneWidth,
|
||||
const int& vCountSelectionMax,
|
||||
UserDatas vUserDatas,
|
||||
ImGuiFileDialogFlags vFlags)
|
||||
ImGuiFileDialogFlags vFlags,
|
||||
SelectFun vSelectFun)
|
||||
{
|
||||
if (prFileDialogInternal.puShowDialog) // if already opened, quit
|
||||
return;
|
||||
|
@ -3401,6 +3406,7 @@ namespace IGFD
|
|||
prFileDialogInternal.puDLGtitle = vTitle;
|
||||
prFileDialogInternal.puDLGuserDatas = vUserDatas;
|
||||
prFileDialogInternal.puDLGflags = vFlags;
|
||||
prFileDialogInternal.puDLGselFun = vSelectFun;
|
||||
prFileDialogInternal.puDLGoptionsPane = vSidePane;
|
||||
prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth;
|
||||
prFileDialogInternal.puDLGmodal = false;
|
||||
|
@ -3435,7 +3441,8 @@ namespace IGFD
|
|||
const float& vSidePaneWidth,
|
||||
const int& vCountSelectionMax,
|
||||
UserDatas vUserDatas,
|
||||
ImGuiFileDialogFlags vFlags)
|
||||
ImGuiFileDialogFlags vFlags,
|
||||
SelectFun vSelectFun)
|
||||
{
|
||||
if (prFileDialogInternal.puShowDialog) // if already opened, quit
|
||||
return;
|
||||
|
@ -3448,6 +3455,7 @@ namespace IGFD
|
|||
prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth;
|
||||
prFileDialogInternal.puDLGuserDatas = vUserDatas;
|
||||
prFileDialogInternal.puDLGflags = vFlags;
|
||||
prFileDialogInternal.puDLGselFun = vSelectFun;
|
||||
prFileDialogInternal.puDLGmodal = false;
|
||||
|
||||
auto ps = IGFD::Utils::ParsePathFileName(vFilePathName);
|
||||
|
@ -3489,7 +3497,8 @@ namespace IGFD
|
|||
const std::string& vFileName,
|
||||
const int& vCountSelectionMax,
|
||||
UserDatas vUserDatas,
|
||||
ImGuiFileDialogFlags vFlags)
|
||||
ImGuiFileDialogFlags vFlags,
|
||||
SelectFun vSelectFun)
|
||||
{
|
||||
if (prFileDialogInternal.puShowDialog) // if already opened, quit
|
||||
return;
|
||||
|
@ -3497,7 +3506,7 @@ namespace IGFD
|
|||
OpenDialog(
|
||||
vKey, vTitle, vFilters,
|
||||
vPath, vFileName,
|
||||
vCountSelectionMax, vUserDatas, vFlags);
|
||||
vCountSelectionMax, vUserDatas, vFlags, vSelectFun);
|
||||
|
||||
prFileDialogInternal.puDLGmodal = true;
|
||||
}
|
||||
|
@ -3509,7 +3518,8 @@ namespace IGFD
|
|||
const std::string& vFilePathName,
|
||||
const int& vCountSelectionMax,
|
||||
UserDatas vUserDatas,
|
||||
ImGuiFileDialogFlags vFlags)
|
||||
ImGuiFileDialogFlags vFlags,
|
||||
SelectFun vSelectFun)
|
||||
{
|
||||
if (prFileDialogInternal.puShowDialog) // if already opened, quit
|
||||
return;
|
||||
|
@ -3517,7 +3527,7 @@ namespace IGFD
|
|||
OpenDialog(
|
||||
vKey, vTitle, vFilters,
|
||||
vFilePathName,
|
||||
vCountSelectionMax, vUserDatas, vFlags);
|
||||
vCountSelectionMax, vUserDatas, vFlags, vSelectFun);
|
||||
|
||||
prFileDialogInternal.puDLGmodal = true;
|
||||
}
|
||||
|
@ -3534,7 +3544,8 @@ namespace IGFD
|
|||
const float& vSidePaneWidth,
|
||||
const int& vCountSelectionMax,
|
||||
UserDatas vUserDatas,
|
||||
ImGuiFileDialogFlags vFlags)
|
||||
ImGuiFileDialogFlags vFlags,
|
||||
SelectFun vSelectFun)
|
||||
{
|
||||
if (prFileDialogInternal.puShowDialog) // if already opened, quit
|
||||
return;
|
||||
|
@ -3543,7 +3554,7 @@ namespace IGFD
|
|||
vKey, vTitle, vFilters,
|
||||
vPath, vFileName,
|
||||
vSidePane, vSidePaneWidth,
|
||||
vCountSelectionMax, vUserDatas, vFlags);
|
||||
vCountSelectionMax, vUserDatas, vFlags, vSelectFun);
|
||||
|
||||
prFileDialogInternal.puDLGmodal = true;
|
||||
}
|
||||
|
@ -3559,7 +3570,8 @@ namespace IGFD
|
|||
const float& vSidePaneWidth,
|
||||
const int& vCountSelectionMax,
|
||||
UserDatas vUserDatas,
|
||||
ImGuiFileDialogFlags vFlags)
|
||||
ImGuiFileDialogFlags vFlags,
|
||||
SelectFun vSelectFun)
|
||||
{
|
||||
if (prFileDialogInternal.puShowDialog) // if already opened, quit
|
||||
return;
|
||||
|
@ -3568,7 +3580,7 @@ namespace IGFD
|
|||
vKey, vTitle, vFilters,
|
||||
vFilePathName,
|
||||
vSidePane, vSidePaneWidth,
|
||||
vCountSelectionMax, vUserDatas, vFlags);
|
||||
vCountSelectionMax, vUserDatas, vFlags, vSelectFun);
|
||||
|
||||
prFileDialogInternal.puDLGmodal = true;
|
||||
}
|
||||
|
@ -3940,6 +3952,9 @@ namespace IGFD
|
|||
return 2;
|
||||
} else {
|
||||
fdi.SelectFileName(prFileDialogInternal, vInfos);
|
||||
if (prFileDialogInternal.puDLGselFun!=NULL) {
|
||||
prFileDialogInternal.puDLGselFun(GetFilePathName().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
26
extern/igfd/ImGuiFileDialog.h
vendored
26
extern/igfd/ImGuiFileDialog.h
vendored
|
@ -1080,6 +1080,7 @@ namespace IGFD
|
|||
|
||||
typedef void* UserDatas;
|
||||
typedef std::function<void(const char*, UserDatas, bool*)> PaneFun; // side pane function binding
|
||||
typedef std::function<void(const char*)> SelectFun; // click on file function binding
|
||||
class FileDialogInternal
|
||||
{
|
||||
public:
|
||||
|
@ -1103,6 +1104,7 @@ namespace IGFD
|
|||
ImGuiFileDialogFlags puDLGflags = ImGuiFileDialogFlags_None;
|
||||
UserDatas puDLGuserDatas = nullptr;
|
||||
PaneFun puDLGoptionsPane = nullptr;
|
||||
SelectFun puDLGselFun = nullptr;
|
||||
float puDLGoptionsPaneWidth = 0.0f;
|
||||
bool puDLGmodal = false;
|
||||
bool puNeedToExitDialog = false;
|
||||
|
@ -1155,7 +1157,8 @@ namespace IGFD
|
|||
const std::string& vFileName, // defaut file name
|
||||
const int& vCountSelectionMax = 1, // count selection max
|
||||
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
|
||||
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
|
||||
ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags
|
||||
SelectFun vSelectFun = nullptr); // function to be called on file click
|
||||
|
||||
void OpenDialog( // open simple dialog (path and filename are obtained from filePathName)
|
||||
const std::string& vKey, // key dialog
|
||||
|
@ -1164,7 +1167,8 @@ namespace IGFD
|
|||
const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName)
|
||||
const int& vCountSelectionMax = 1, // count selection max
|
||||
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
|
||||
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
|
||||
ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags
|
||||
SelectFun vSelectFun = nullptr); // function to be called on file click
|
||||
|
||||
// with pane
|
||||
void OpenDialog( // open dialog with custom right pane (path and fileName can be specified)
|
||||
|
@ -1177,7 +1181,8 @@ namespace IGFD
|
|||
const float& vSidePaneWidth = 250.0f, // side pane width
|
||||
const int& vCountSelectionMax = 1, // count selection max
|
||||
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
|
||||
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
|
||||
ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags
|
||||
SelectFun vSelectFun = nullptr); // function to be called on file click
|
||||
|
||||
void OpenDialog( // open dialog with custom right pane (path and filename are obtained from filePathName)
|
||||
const std::string& vKey, // key dialog
|
||||
|
@ -1188,7 +1193,8 @@ namespace IGFD
|
|||
const float& vSidePaneWidth = 250.0f, // side pane width
|
||||
const int& vCountSelectionMax = 1, // count selection max
|
||||
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
|
||||
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
|
||||
ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags
|
||||
SelectFun vSelectFun = nullptr); // function to be called on file click
|
||||
|
||||
// modal dialog
|
||||
void OpenModal( // open simple modal (path and fileName can be specified)
|
||||
|
@ -1199,7 +1205,8 @@ namespace IGFD
|
|||
const std::string& vFileName, // defaut file name
|
||||
const int& vCountSelectionMax = 1, // count selection max
|
||||
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
|
||||
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
|
||||
ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags
|
||||
SelectFun vSelectFun = nullptr); // function to be called on file click
|
||||
|
||||
void OpenModal( // open simple modal (path and fielname are obtained from filePathName)
|
||||
const std::string& vKey, // key dialog
|
||||
|
@ -1208,7 +1215,8 @@ namespace IGFD
|
|||
const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName)
|
||||
const int& vCountSelectionMax = 1, // count selection max
|
||||
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
|
||||
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
|
||||
ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags
|
||||
SelectFun vSelectFun = nullptr); // function to be called on file click
|
||||
|
||||
// with pane
|
||||
void OpenModal( // open modal with custom right pane (path and filename are obtained from filePathName)
|
||||
|
@ -1221,7 +1229,8 @@ namespace IGFD
|
|||
const float& vSidePaneWidth = 250.0f, // side pane width
|
||||
const int& vCountSelectionMax = 1, // count selection max
|
||||
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
|
||||
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
|
||||
ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags
|
||||
SelectFun vSelectFun = nullptr); // function to be called on file click
|
||||
|
||||
void OpenModal( // open modal with custom right pane (path and fielname are obtained from filePathName)
|
||||
const std::string& vKey, // key dialog
|
||||
|
@ -1232,7 +1241,8 @@ namespace IGFD
|
|||
const float& vSidePaneWidth = 250.0f, // side pane width
|
||||
const int& vCountSelectionMax = 1, // count selection max
|
||||
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
|
||||
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
|
||||
ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags
|
||||
SelectFun vSelectFun = nullptr); // function to be called on file click
|
||||
|
||||
// Display / Close dialog form
|
||||
bool Display( // Display the dialog. return true if a result was obtained (Ok or not)
|
||||
|
|
3
extern/imgui_patched/imgui_impl_sdl.cpp
vendored
3
extern/imgui_patched/imgui_impl_sdl.cpp
vendored
|
@ -300,6 +300,9 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
|
|||
{
|
||||
float wheel_x = (event->wheel.x > 0) ? 1.0f : (event->wheel.x < 0) ? -1.0f : 0.0f;
|
||||
float wheel_y = (event->wheel.y > 0) ? 1.0f : (event->wheel.y < 0) ? -1.0f : 0.0f;
|
||||
#ifdef __APPLE__
|
||||
wheel_x = -wheel_x;
|
||||
#endif
|
||||
io.AddMouseWheelEvent(wheel_x, wheel_y);
|
||||
return true;
|
||||
}
|
||||
|
|
12
extern/opl/.github/FUNDING.yml
vendored
Normal file
12
extern/opl/.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: nukeykt # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://www.buymeacoffee.com/nukeykt', 'https://paypal.me/nukeykt'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
504
extern/opl/LICENSE
vendored
Normal file
504
extern/opl/LICENSE
vendored
Normal file
|
@ -0,0 +1,504 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random
|
||||
Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
4
extern/opl/MODIFIED.md
vendored
Normal file
4
extern/opl/MODIFIED.md
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# modification disclaimer
|
||||
|
||||
this is a modified version of Nuked-OPL3 which implements channel muting in the core.
|
||||
see [this issue](https://github.com/tildearrow/furnace/issues/414) for more information.
|
1476
extern/opl/opl3.c
vendored
Normal file
1476
extern/opl/opl3.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
168
extern/opl/opl3.h
vendored
Normal file
168
extern/opl/opl3.h
vendored
Normal file
|
@ -0,0 +1,168 @@
|
|||
/* Nuked OPL3
|
||||
* Copyright (C) 2013-2020 Nuke.YKT
|
||||
*
|
||||
* This file is part of Nuked OPL3.
|
||||
*
|
||||
* Nuked OPL3 is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation, either version 2.1
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* Nuked OPL3 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Nuked OPL3. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
* Nuked OPL3 emulator.
|
||||
* Thanks:
|
||||
* MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh):
|
||||
* Feedback and Rhythm part calculation information.
|
||||
* forums.submarine.org.uk(carbon14, opl3):
|
||||
* Tremolo and phase generator calculation information.
|
||||
* OPLx decapsulated(Matthew Gambrell, Olli Niemitalo):
|
||||
* OPL2 ROMs.
|
||||
* siliconpr0n.org(John McMaster, digshadow):
|
||||
* YMF262 and VRC VII decaps and die shots.
|
||||
*
|
||||
* version: 1.8
|
||||
*/
|
||||
|
||||
#ifndef OPL_OPL3_H
|
||||
#define OPL_OPL3_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifndef OPL_ENABLE_STEREOEXT
|
||||
#define OPL_ENABLE_STEREOEXT 0
|
||||
#endif
|
||||
|
||||
#define OPL_WRITEBUF_SIZE 1024
|
||||
#define OPL_WRITEBUF_DELAY 2
|
||||
|
||||
typedef struct _opl3_slot opl3_slot;
|
||||
typedef struct _opl3_channel opl3_channel;
|
||||
typedef struct _opl3_chip opl3_chip;
|
||||
|
||||
struct _opl3_slot {
|
||||
opl3_channel *channel;
|
||||
opl3_chip *chip;
|
||||
int16_t out;
|
||||
int16_t fbmod;
|
||||
int16_t *mod;
|
||||
int16_t prout;
|
||||
uint16_t eg_rout;
|
||||
uint16_t eg_out;
|
||||
uint8_t eg_inc;
|
||||
uint8_t eg_gen;
|
||||
uint8_t eg_rate;
|
||||
uint8_t eg_ksl;
|
||||
uint8_t *trem;
|
||||
uint8_t reg_vib;
|
||||
uint8_t reg_type;
|
||||
uint8_t reg_ksr;
|
||||
uint8_t reg_mult;
|
||||
uint8_t reg_ksl;
|
||||
uint8_t reg_tl;
|
||||
uint8_t reg_ar;
|
||||
uint8_t reg_dr;
|
||||
uint8_t reg_sl;
|
||||
uint8_t reg_rr;
|
||||
uint8_t reg_wf;
|
||||
uint8_t key;
|
||||
uint32_t pg_reset;
|
||||
uint32_t pg_phase;
|
||||
uint16_t pg_phase_out;
|
||||
uint8_t slot_num;
|
||||
};
|
||||
|
||||
struct _opl3_channel {
|
||||
opl3_slot *slots[2];
|
||||
opl3_channel *pair;
|
||||
opl3_chip *chip;
|
||||
int16_t *out[4];
|
||||
|
||||
#if OPL_ENABLE_STEREOEXT
|
||||
int32_t leftpan;
|
||||
int32_t rightpan;
|
||||
#endif
|
||||
|
||||
uint8_t chtype;
|
||||
uint16_t f_num;
|
||||
uint8_t block;
|
||||
uint8_t fb;
|
||||
uint8_t con;
|
||||
uint8_t alg;
|
||||
uint8_t ksv;
|
||||
uint16_t cha, chb;
|
||||
uint8_t ch_num;
|
||||
uint8_t muted;
|
||||
};
|
||||
|
||||
typedef struct _opl3_writebuf {
|
||||
uint64_t time;
|
||||
uint16_t reg;
|
||||
uint8_t data;
|
||||
} opl3_writebuf;
|
||||
|
||||
struct _opl3_chip {
|
||||
opl3_channel channel[18];
|
||||
opl3_slot slot[36];
|
||||
uint16_t timer;
|
||||
uint64_t eg_timer;
|
||||
uint8_t eg_timerrem;
|
||||
uint8_t eg_state;
|
||||
uint8_t eg_add;
|
||||
uint8_t newm;
|
||||
uint8_t nts;
|
||||
uint8_t rhy;
|
||||
uint8_t vibpos;
|
||||
uint8_t vibshift;
|
||||
uint8_t tremolo;
|
||||
uint8_t tremolopos;
|
||||
uint8_t tremoloshift;
|
||||
uint32_t noise;
|
||||
int16_t zeromod;
|
||||
int32_t mixbuff[2];
|
||||
uint8_t rm_hh_bit2;
|
||||
uint8_t rm_hh_bit3;
|
||||
uint8_t rm_hh_bit7;
|
||||
uint8_t rm_hh_bit8;
|
||||
uint8_t rm_tc_bit3;
|
||||
uint8_t rm_tc_bit5;
|
||||
|
||||
#if OPL_ENABLE_STEREOEXT
|
||||
uint8_t stereoext;
|
||||
#endif
|
||||
|
||||
/* OPL3L */
|
||||
int32_t rateratio;
|
||||
int32_t samplecnt;
|
||||
int16_t oldsamples[2];
|
||||
int16_t samples[2];
|
||||
|
||||
uint64_t writebuf_samplecnt;
|
||||
uint32_t writebuf_cur;
|
||||
uint32_t writebuf_last;
|
||||
uint64_t writebuf_lasttime;
|
||||
opl3_writebuf writebuf[OPL_WRITEBUF_SIZE];
|
||||
};
|
||||
|
||||
void OPL3_Generate(opl3_chip *chip, int16_t *buf);
|
||||
void OPL3_GenerateResampled(opl3_chip *chip, int16_t *buf);
|
||||
void OPL3_Reset(opl3_chip *chip, uint32_t samplerate);
|
||||
void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v);
|
||||
void OPL3_WriteRegBuffered(opl3_chip *chip, uint16_t reg, uint8_t v);
|
||||
void OPL3_GenerateStream(opl3_chip *chip, int16_t *sndptr, uint32_t numsamples);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
8
extern/opm/opm.c
vendored
8
extern/opm/opm.c
vendored
|
@ -1256,6 +1256,14 @@ static inline void OPM_Mixer(opm_t *chip)
|
|||
}
|
||||
chip->mix[0] += chip->op_mix * chip->op_mixl;
|
||||
chip->mix[1] += chip->op_mix * chip->op_mixr;
|
||||
|
||||
if (slot<8) {
|
||||
chip->op_chmix[slot&7]=0;
|
||||
}
|
||||
chip->op_chmix[slot&7]+=chip->op_mix*(chip->op_mixl|chip->op_mixr);
|
||||
if (slot>=24) {
|
||||
chip->ch_out[slot&7]=chip->op_chmix[slot&7];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void OPM_Noise(opm_t *chip)
|
||||
|
|
2
extern/opm/opm.h
vendored
2
extern/opm/opm.h
vendored
|
@ -141,6 +141,7 @@ typedef struct {
|
|||
int16_t op_fb[2];
|
||||
uint8_t op_mixl;
|
||||
uint8_t op_mixr;
|
||||
uint16_t op_chmix[8];
|
||||
|
||||
// Mixer
|
||||
|
||||
|
@ -161,6 +162,7 @@ typedef struct {
|
|||
uint8_t smp_so;
|
||||
uint8_t smp_sh1;
|
||||
uint8_t smp_sh2;
|
||||
uint16_t ch_out[8];
|
||||
|
||||
// Noise
|
||||
uint32_t noise_lfsr;
|
||||
|
|
|
@ -14,6 +14,15 @@ however, effects are continuous, which means you only need to type it once and t
|
|||
- maximum tremolo depth is -60 volume steps.
|
||||
- `08xy`: set panning. `x` is the left channel and `y` is the right one.
|
||||
- not all systems support this effect.
|
||||
- `80xx`: set panning (linear). this effect behaves more like other trackers:
|
||||
- `00` is left.
|
||||
- `80` is center.
|
||||
- `FF` is right.
|
||||
- not all systems support this effect.
|
||||
- `81xx`: set volume of left channel (from `00` to `FF`).
|
||||
- not all systems support this effect.
|
||||
- `82xx`: set volume of right channel (from `00` to `FF`).
|
||||
- not all systems support this effect.
|
||||
- `09xx`: set speed 1.
|
||||
- `0Axy`: volume slide.
|
||||
- if `x` is 0 then this is a slide down.
|
||||
|
|
|
@ -6,8 +6,10 @@ in this very computer music trackers were born...
|
|||
|
||||
# effects
|
||||
|
||||
- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on.
|
||||
- `10xx`: change wave.
|
||||
- only works when "Mode" is set to "Wavetable" in the instrument.
|
||||
- `11xx`: toggle amplitude modulation with the next channel.
|
||||
- does not work on the last channel.
|
||||
- `12xx`: toggle period (frequency) modulation with the next channel.
|
||||
- does not work on the last channel.
|
||||
- does not work on the last channel.
|
||||
- `13xx`: toggle low-pass filter. `0` turns it off and `1` turns it on.
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
the console from Nintendo that plays Super Mario Bros. and helped revive the agonizing video game market in the US during mid-80s.
|
||||
|
||||
also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel
|
||||
also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel.
|
||||
|
||||
# effects
|
||||
|
||||
- `11xx`: write to delta modulation counter.
|
||||
- this may be used to attenuate the triangle and noise channels.
|
||||
- will not work if a sample is playing.
|
||||
- `12xx`: set duty cycle or noise mode of channel.
|
||||
- may be 0-3 for the pulse channels and 0-1 for the noise channel.
|
||||
- `13xy`: setup sweep up.
|
||||
|
@ -16,3 +19,7 @@ also known as Famicom. It is a five-channel PSG: first two channels play pulse w
|
|||
- `x` is the time.
|
||||
- `y` is the shift.
|
||||
- set to 0 to disable it.
|
||||
- `18xx`: set PCM channel mode.
|
||||
- `00`: PCM (software).
|
||||
- `01`: DPCM (hardware).
|
||||
- when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored.
|
|
@ -44,3 +44,37 @@ afterwards everyone moved to Windows and software mixed PCM streaming...
|
|||
- only in 4-op mode (OPL3).
|
||||
- `1Dxx`: set attack of operator 4.
|
||||
- only in 4-op mode (OPL3).
|
||||
- `2Axy`: set waveform of operator.
|
||||
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- only in OPL2 or higher.
|
||||
- `30xx`: enable envelope hard reset.
|
||||
- this works by inserting a quick release and tiny delay before a new note.
|
||||
- `50xy`: set AM of operator.
|
||||
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
|
||||
- `y` determines whether AM is on.
|
||||
- `51xy` set SL of operator.
|
||||
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `52xy` set RR of operator.
|
||||
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `53xy`: set VIB of operator.
|
||||
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
|
||||
- `y` determines whether VIB is on.
|
||||
- `54xy` set KSL of operator.
|
||||
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `55xy` set SUS of operator.
|
||||
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
|
||||
- `y` determines whether SUS is on.
|
||||
- `56xx`: set DR of all operators.
|
||||
- `57xx`: set DR of operator 1.
|
||||
- `58xx`: set DR of operator 2.
|
||||
- `59xx`: set DR of operator 3.
|
||||
- only in 4-op mode (OPL3).
|
||||
- `5Axx`: set DR of operator 4.
|
||||
- only in 4-op mode (OPL3).
|
||||
- `5Bxy`: set KSR of operator.
|
||||
- `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators".
|
||||
- `y` determines whether KSR is on.
|
|
@ -2,17 +2,17 @@
|
|||
|
||||
the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip, based on the Yamaha YM3812 (OPL2). thought OPL was downgraded enough? :p
|
||||
|
||||
OPLL spawned also a few derivative chips, the best known of these is:
|
||||
- the myth. the legend. THE VRC7. 6 channels, *rather interesting* instruments sound bank, no drums mode
|
||||
OPLL also spawned a few derivative chips, the best known of these is:
|
||||
- the famous Konami VRC7. used in the Japan-only video game Lagrange Point, it was **another** cost reduction on top of the OPLL! this time just 6 channels...
|
||||
- Yamaha YM2423, same chip as YM2413, just a different patch set
|
||||
- Yamaha YMF281, ditto
|
||||
- Yamaha YMF281, ditto
|
||||
|
||||
# technical specifications
|
||||
|
||||
the YM2413 is equipped with the following features:
|
||||
|
||||
- 9 channels of 2 operator FM synthesis
|
||||
- A drum/percussion mode, replacing the last 3 voices with 3 rhythm channels
|
||||
- A drum/percussion mode, replacing the last 3 voices with 5 rhythm channels
|
||||
- 1 user-definable patch (this patch can be changed throughout the course of the song)
|
||||
- 15 pre-defined patches which can all be used at the same time
|
||||
- Support for ADSR on both the modulator and the carrier
|
||||
|
@ -37,3 +37,27 @@ the YM2413 is equipped with the following features:
|
|||
- `19xx`: set attack of all operators.
|
||||
- `1Axx`: set attack of operator 1.
|
||||
- `1Bxx`: set attack of operator 2.
|
||||
- `50xy`: set AM of operator.
|
||||
- `x` is the operator (1-2). a value of 0 means "all operators".
|
||||
- `y` determines whether AM is on.
|
||||
- `51xy` set SL of operator.
|
||||
- `x` is the operator (1-2). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `52xy` set RR of operator.
|
||||
- `x` is the operator (1-2). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `53xy`: set VIB of operator.
|
||||
- `x` is the operator (1-2). a value of 0 means "all operators".
|
||||
- `y` determines whether VIB is on.
|
||||
- `54xy` set KSL of operator.
|
||||
- `x` is the operator (1-2). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `55xy` set EGT of operator.
|
||||
- `x` is the operator (1-2). a value of 0 means "all operators".
|
||||
- `y` determines whether EGT is on.
|
||||
- `56xx`: set DR of all operators.
|
||||
- `57xx`: set DR of operator 1.
|
||||
- `58xx`: set DR of operator 2.
|
||||
- `5Bxy`: set KSR of operator.
|
||||
- `x` is the operator (1-2). a value of 0 means "all operators".
|
||||
- `y` determines whether KSR is on.
|
103
papers/doc/7-systems/opz.md
Normal file
103
papers/doc/7-systems/opz.md
Normal file
|
@ -0,0 +1,103 @@
|
|||
# Yamaha OPZ (YM2414)
|
||||
|
||||
this is the YM2151's little-known successor, used in the Yamaha TX81Z and a few other Yamaha synthesizers. oh, and the Korg Z3 too.
|
||||
|
||||
it adds these features on top of the YM2151:
|
||||
- 8 waveforms (but they're different from the OPL ones)
|
||||
- per-channel (possibly) linear volume control separate from TL
|
||||
- increased multiplier precision (in 1/16ths)
|
||||
- 4-step envelope generator shift (minimum TL)
|
||||
- another LFO
|
||||
- no per-operator key on/off
|
||||
- fixed frequency mode per operator (kind of like OPN family's extended channel mode but with a bit less precision and for all 8 channels)
|
||||
- "reverb" effect (actually extends release)
|
||||
|
||||
unlike the YM2151, this chip is officially undocumented. very few efforts have been made to study the chip and document it...
|
||||
therefore emulation of this chip in Furnace is incomplete and uncertain.
|
||||
|
||||
no plans have been made for TX81Z MIDI passthrough, because:
|
||||
- Furnace works with register writes rather than MIDI commands
|
||||
- the MIDI protocol is slow (would not be enough).
|
||||
- the TX81Z is very slow to process a note on/off or parameter change event.
|
||||
- the TL range has been reduced to 0-99, but the chip goes from 0-127.
|
||||
|
||||
# effects
|
||||
|
||||
- `10xx`: set noise frequency of channel 8 operator 4. 00 disables noise while 01 to 20 enables it.
|
||||
- `11xx`: set feedback of channel.
|
||||
- `12xx`: set operator 1 level.
|
||||
- `13xx`: set operator 2 level.
|
||||
- `14xx`: set operator 3 level.
|
||||
- `15xx`: set operator 4 level.
|
||||
- `16xy`: set multiplier of operator.
|
||||
- `x` is the operator (1-4).
|
||||
- `y` is the mutliplier.
|
||||
- `17xx`: set LFO speed.
|
||||
- `18xx`: set LFO waveform. `xx` may be one of the following:
|
||||
- `00`: saw
|
||||
- `01`: square
|
||||
- `02`: triangle
|
||||
- `03`: noise
|
||||
- `19xx`: set attack of all operators.
|
||||
- `1Axx`: set attack of operator 1.
|
||||
- `1Bxx`: set attack of operator 2.
|
||||
- `1Cxx`: set attack of operator 3.
|
||||
- `1Dxx`: set attack of operator 4.
|
||||
- `28xy`: set reverb of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `2Axy`: set waveform of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `2Bxy`: set EG shift of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `2Cxy` set fine multiplier of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `2Fxx`: enable envelope hard reset.
|
||||
- this works by inserting a quick release and tiny delay before a new note.
|
||||
- `3xyy`: set fixed frequency of operator 1/2.
|
||||
- `x` is the block (0-7 for operator 1; 8-F for operator 2).
|
||||
- `y` is the frequency. fixed frequency mode will be disabled if this is less than 8.
|
||||
- the actual frequency is: `y*(2^x)`.
|
||||
- `4xyy`: set fixed frequency of operator 3/4.
|
||||
- `x` is the block (0-7 for operator 3; 8-F for operator 4).
|
||||
- `y` is the frequency. fixed frequency mode will be disabled if this is less than 8.
|
||||
- the actual frequency is: `y*(2^x)`.
|
||||
- `50xy`: set AM of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` determines whether AM is on.
|
||||
- `51xy` set SL of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `52xy` set RR of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `53xy` set DT of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value:
|
||||
- 0: +0
|
||||
- 1: +1
|
||||
- 2: +2
|
||||
- 3: +3
|
||||
- 4: -0
|
||||
- 5: -3
|
||||
- 6: -2
|
||||
- 7: -1
|
||||
- `54xy` set RS of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `55xy` set DT2 of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `56xx`: set DR of all operators.
|
||||
- `57xx`: set DR of operator 1.
|
||||
- `58xx`: set DR of operator 2.
|
||||
- `59xx`: set DR of operator 3.
|
||||
- `5Axx`: set DR of operator 4.
|
||||
- `5Bxx`: set D2R/SR of all operators.
|
||||
- `5Cxx`: set D2R/SR of operator 1.
|
||||
- `5Dxx`: set D2R/SR of operator 2.
|
||||
- `5Exx`: set D2R/SR of operator 3.
|
||||
- `5Fxx`: set D2R/SR of operator 4.
|
|
@ -12,7 +12,8 @@ There are also 3 ADPCM channels, however they cannot be used in Furnace yet. The
|
|||
|
||||
# effects
|
||||
|
||||
- `08xx`: Set panning. Valid range is 00-20. 00 for full left, 10 for center and 20 for full right. It is also possible to bypass the QSound algorithm by using the range 30-50.
|
||||
- `10xx`: Set echo feedback level. This effect will apply to all channels.
|
||||
- `11xx`: Set echo level.
|
||||
- `3xxx`: Set the length of the echo delay buffer.
|
||||
- `10xx`: set echo feedback level.
|
||||
- this effect will apply to all channels.
|
||||
- `11xx`: set echo level.
|
||||
- `12xx`: toggle QSound algorithm (on by default).
|
||||
- `3xxx`: set the length of the echo delay buffer.
|
||||
|
|
|
@ -26,3 +26,41 @@ it also was present on several pinball machines and synthesizers of the era, and
|
|||
- `1Bxx`: set attack of operator 2.
|
||||
- `1Cxx`: set attack of operator 3.
|
||||
- `1Dxx`: set attack of operator 4.
|
||||
- `30xx`: enable envelope hard reset.
|
||||
- this works by inserting a quick release and tiny delay before a new note.
|
||||
- `50xy`: set AM of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` determines whether AM is on.
|
||||
- `51xy` set SL of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `52xy` set RR of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `53xy` set DT of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value:
|
||||
- 0: +0
|
||||
- 1: +1
|
||||
- 2: +2
|
||||
- 3: +3
|
||||
- 4: -0
|
||||
- 5: -3
|
||||
- 6: -2
|
||||
- 7: -1
|
||||
- `54xy` set RS of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `55xy` set DT2 of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `56xx`: set DR of all operators.
|
||||
- `57xx`: set DR of operator 1.
|
||||
- `58xx`: set DR of operator 2.
|
||||
- `59xx`: set DR of operator 3.
|
||||
- `5Axx`: set DR of operator 4.
|
||||
- `5Bxx`: set D2R/SR of all operators.
|
||||
- `5Cxx`: set D2R/SR of operator 1.
|
||||
- `5Dxx`: set D2R/SR of operator 2.
|
||||
- `5Exx`: set D2R/SR of operator 3.
|
||||
- `5Fxx`: set D2R/SR of operator 4.
|
|
@ -57,3 +57,43 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different
|
|||
- `x` is the numerator.
|
||||
- `y` is the denominator.
|
||||
- if `x` or `y` are 0 this will disable auto-envelope mode.
|
||||
- `30xx`: enable envelope hard reset.
|
||||
- this works by inserting a quick release and tiny delay before a new note.
|
||||
- `50xy`: set AM of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` determines whether AM is on.
|
||||
- `51xy` set SL of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `52xy` set RR of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `53xy` set DT of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value:
|
||||
- 0: +0
|
||||
- 1: +1
|
||||
- 2: +2
|
||||
- 3: +3
|
||||
- 4: -0
|
||||
- 5: -3
|
||||
- 6: -2
|
||||
- 7: -1
|
||||
- `54xy` set RS of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `55xy` set SSG-EG of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value (0-8).
|
||||
- values between 0 and 7 set SSG-EG.
|
||||
- value 8 disables it.
|
||||
- `56xx`: set DR of all operators.
|
||||
- `57xx`: set DR of operator 1.
|
||||
- `58xx`: set DR of operator 2.
|
||||
- `59xx`: set DR of operator 3.
|
||||
- `5Axx`: set DR of operator 4.
|
||||
- `5Bxx`: set D2R/SR of all operators.
|
||||
- `5Cxx`: set D2R/SR of operator 1.
|
||||
- `5Dxx`: set D2R/SR of operator 2.
|
||||
- `5Exx`: set D2R/SR of operator 3.
|
||||
- `5Fxx`: set D2R/SR of operator 4.
|
|
@ -56,3 +56,43 @@ it is backward compatible with the original chip.
|
|||
- `x` is the numerator.
|
||||
- `y` is the denominator.
|
||||
- if `x` or `y` are 0 this will disable auto-envelope mode.
|
||||
- `30xx`: enable envelope hard reset.
|
||||
- this works by inserting a quick release and tiny delay before a new note.
|
||||
- `50xy`: set AM of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` determines whether AM is on.
|
||||
- `51xy` set SL of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `52xy` set RR of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `53xy` set DT of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value:
|
||||
- 0: +0
|
||||
- 1: +1
|
||||
- 2: +2
|
||||
- 3: +3
|
||||
- 4: -0
|
||||
- 5: -3
|
||||
- 6: -2
|
||||
- 7: -1
|
||||
- `54xy` set RS of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `55xy` set SSG-EG of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value (0-8).
|
||||
- values between 0 and 7 set SSG-EG.
|
||||
- value 8 disables it.
|
||||
- `56xx`: set DR of all operators.
|
||||
- `57xx`: set DR of operator 1.
|
||||
- `58xx`: set DR of operator 2.
|
||||
- `59xx`: set DR of operator 3.
|
||||
- `5Axx`: set DR of operator 4.
|
||||
- `5Bxx`: set D2R/SR of all operators.
|
||||
- `5Cxx`: set D2R/SR of operator 1.
|
||||
- `5Dxx`: set D2R/SR of operator 2.
|
||||
- `5Exx`: set D2R/SR of operator 3.
|
||||
- `5Fxx`: set D2R/SR of operator 4.
|
|
@ -25,3 +25,43 @@ one of two chips that powered the Sega Genesis. It is a six-channel, four-operat
|
|||
- `1Bxx`: set attack of operator 2.
|
||||
- `1Cxx`: set attack of operator 3.
|
||||
- `1Dxx`: set attack of operator 4.
|
||||
- `30xx`: enable envelope hard reset.
|
||||
- this works by inserting a quick release and tiny delay before a new note.
|
||||
- `50xy`: set AM of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` determines whether AM is on.
|
||||
- `51xy` set SL of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `52xy` set RR of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `53xy` set DT of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value:
|
||||
- 0: -3
|
||||
- 1: -2
|
||||
- 2: -1
|
||||
- 3: 0
|
||||
- 4: 1
|
||||
- 5: 2
|
||||
- 6: 3
|
||||
- 7: -0
|
||||
- `54xy` set RS of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value.
|
||||
- `55xy` set SSG-EG of operator.
|
||||
- `x` is the operator (1-4). a value of 0 means "all operators".
|
||||
- `y` is the value (0-8).
|
||||
- values between 0 and 7 set SSG-EG.
|
||||
- value 8 disables it.
|
||||
- `56xx`: set DR of all operators.
|
||||
- `57xx`: set DR of operator 1.
|
||||
- `58xx`: set DR of operator 2.
|
||||
- `59xx`: set DR of operator 3.
|
||||
- `5Axx`: set DR of operator 4.
|
||||
- `5Bxx`: set D2R/SR of all operators.
|
||||
- `5Cxx`: set D2R/SR of operator 1.
|
||||
- `5Dxx`: set D2R/SR of operator 2.
|
||||
- `5Exx`: set D2R/SR of operator 3.
|
||||
- `5Fxx`: set D2R/SR of operator 4.
|
137
papers/format.md
137
papers/format.md
|
@ -29,6 +29,16 @@ furthermore, an `or reserved` indicates this field is always present, but is res
|
|||
|
||||
the format versions are:
|
||||
|
||||
- 90: Furnace dev90
|
||||
- 89: Furnace dev89
|
||||
- 88: Furnace dev88
|
||||
- 87: Furnace dev87
|
||||
- 86: Furnace dev86
|
||||
- 85: Furnace dev85
|
||||
- 84: Furnace dev84
|
||||
- 83: Furnace dev83
|
||||
- 82: Furnace dev82
|
||||
- 81: Furnace dev81
|
||||
- 80: Furnace dev80
|
||||
- 79: Furnace dev79
|
||||
- 78: Furnace dev78
|
||||
|
@ -167,7 +177,7 @@ size | description
|
|||
| - 0x8f: OPL (YM3526) - 9 channels
|
||||
| - 0x90: OPL2 (YM3812) - 9 channels
|
||||
| - 0x91: OPL3 (YMF262) - 18 channels
|
||||
| - 0x92: MultiPCM - 24 channels
|
||||
| - 0x92: MultiPCM - 28 channels
|
||||
| - 0x93: Intel 8253 (beeper) - 1 channel
|
||||
| - 0x94: POKEY - 4 channels
|
||||
| - 0x95: RF5C68 - 8 channels
|
||||
|
@ -190,14 +200,26 @@ size | description
|
|||
| - 0xa6: Neo Geo extended (YM2610) - 17 channels
|
||||
| - 0xa7: OPLL drums (YM2413) - 11 channels
|
||||
| - 0xa8: Atari Lynx - 4 channels
|
||||
| - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels
|
||||
| - 0xa9: SegaPCM (for DefleMask compatibility) - 5 channels
|
||||
| - 0xaa: MSM6295 - 4 channels
|
||||
| - 0xab: MSM6258 - 1 channel
|
||||
| - 0xac: Commander X16 (VERA) - 17 channels
|
||||
| - 0xad: Bubble System WSG - 2 channels
|
||||
| - 0xae: OPL4 (YMF278B) - 42 channels
|
||||
| - 0xaf: OPL4 drums (YMF278B) - 44 channels
|
||||
| - 0xb0: Seta/Allumer X1-010 - 16 channels
|
||||
| - 0xb1: Ensoniq ES5506 - 32 channels
|
||||
| - 0xb2: Yamaha Y8950 - 10 channels
|
||||
| - 0xb3: Yamaha Y8950 drums - 12 channels
|
||||
| - 0xb4: Konami SCC+ - 5 channels
|
||||
| - 0xb5: tildearrow Sound Unit - 8 channels
|
||||
| - 0xb6: OPN extended - 9 channels
|
||||
| - 0xb7: PC-98 extended - 19 channels
|
||||
| - 0xde: YM2610B extended - 19 channels
|
||||
| - 0xe0: QSound - 19 channels
|
||||
| - 0xfd: Dummy System - 8 channels
|
||||
| - 0xfe: reserved for development
|
||||
| - 0xff: reserved for development
|
||||
| - (compound!) means that the system is composed of two or more chips,
|
||||
| and has to be flattened.
|
||||
32 | sound chip volumes
|
||||
|
@ -258,11 +280,30 @@ size | description
|
|||
1 | buggy portamento after slide (>=72) or reserved
|
||||
1 | new ins affects envelope (Game Boy) (>=72) or reserved
|
||||
1 | ExtCh channel state is shared (>=78) or reserved
|
||||
25 | reserved
|
||||
1 | ignore DAC mode change outside of intended channel (>=83) or reserved
|
||||
1 | E1xx and E2xx also take priority over Slide00 (>=83) or reserved
|
||||
1 | new Sega PCM (with macros and proper vol/pan) (>=84) or reserved
|
||||
1 | weird f-num/block-based chip pitch slides (>=85) or reserved
|
||||
1 | SN duty macro always resets phase (>=86) or reserved
|
||||
1 | pitch macro is linear (>=90) or reserved
|
||||
19 | reserved
|
||||
```
|
||||
|
||||
# instrument
|
||||
|
||||
notes:
|
||||
|
||||
- the entire instrument is stored, regardless of instrument type.
|
||||
- the macro range varies depending on the instrument type.
|
||||
- "macro open" indicates whether the macro is collapsed or not in the instrument editor.
|
||||
- FM operator order is:
|
||||
- 1/3/2/4 (internal order) for OPN, OPM, OPZ and OPL 4-op
|
||||
- 1/2/?/? (? = unused) for OPL 2-op and OPLL
|
||||
- meaning of extended macros varies depending on instrument type.
|
||||
- meaning of panning macros varies depending on instrument type:
|
||||
- for hard-panned chips (e.g. FM and Game Boy): left panning is 2-bit panning macro (left/right)
|
||||
- otherwise both left and right panning macros are used
|
||||
|
||||
```
|
||||
size | description
|
||||
-----|------------------------------------
|
||||
|
@ -271,17 +312,43 @@ size | description
|
|||
2 | format version (see header)
|
||||
1 | instrument type
|
||||
| - 0: standard
|
||||
| - 1: FM
|
||||
| - 1: FM (OPM/OPN)
|
||||
| - 2: Game Boy
|
||||
| - 3: C64
|
||||
| - 4: Amiga/sample
|
||||
| - 5: PC Engine
|
||||
| - 6: AY-3-8910
|
||||
| - 7: AY8930
|
||||
| - 8: TIA
|
||||
| - 9: SAA1099
|
||||
| - 10: VIC
|
||||
| - 11: PET
|
||||
| - 12: VRC6
|
||||
| - 13: OPLL
|
||||
| - 14: OPL
|
||||
| - 15: FDS
|
||||
| - 16: Virtual Boy
|
||||
| - 17: Namco 163
|
||||
| - 18: SCC
|
||||
| - 19: OPZ
|
||||
| - 20: POKEY
|
||||
| - 21: PC Speaker
|
||||
| - 22: WonderSwan
|
||||
| - 23: Lynx
|
||||
| - 24: VERA
|
||||
| - 25: X1-010
|
||||
| - 26: VRC6 (saw)
|
||||
| - 27: ES5506
|
||||
| - 28: MultiPCM
|
||||
| - 29: SNES
|
||||
| - 30: Sound Unit
|
||||
1 | reserved
|
||||
STR | instrument name
|
||||
--- | **FM instrument data**
|
||||
1 | alg
|
||||
1 | alg (SUS on OPLL)
|
||||
1 | feedback
|
||||
1 | fms
|
||||
1 | ams
|
||||
1 | fms (DC on OPLL)
|
||||
1 | ams (DM on OPLL)
|
||||
1 | operator count
|
||||
| - this is either 2 or 4, and is ignored on non-OPL systems.
|
||||
| - always read 4 ops regardless of this value.
|
||||
|
@ -303,10 +370,12 @@ size | description
|
|||
1 | dt
|
||||
1 | d2r
|
||||
1 | ssgEnv
|
||||
1 | dam
|
||||
1 | dvb
|
||||
1 | egt
|
||||
1 | ksl
|
||||
| - bit 4: on (EG-S on OPLL)
|
||||
| - bit 0-3: envelope type
|
||||
1 | dam (for YMU759 compat; REV on OPZ)
|
||||
1 | dvb (for YMU759 compat; FINE on OPZ)
|
||||
1 | egt (for YMU759 compat; FixedFreq on OPZ)
|
||||
1 | ksl (EGShift on OPZ)
|
||||
1 | sus
|
||||
1 | vib
|
||||
1 | ws
|
||||
|
@ -342,7 +411,11 @@ size | description
|
|||
1 | filter macro is absolute
|
||||
--- | **Amiga instrument data**
|
||||
2 | initial sample
|
||||
14 | reserved
|
||||
1 | mode (>=82) or reserved
|
||||
| - 0: sample
|
||||
| - 1: wavetable
|
||||
1 | wavetable length (-1) (>=82) or reserved
|
||||
12 | reserved
|
||||
--- | **standard instrument data**
|
||||
4 | volume macro length
|
||||
4 | arp macro length
|
||||
|
@ -365,9 +438,11 @@ size | description
|
|||
1 | reserved (>=17) or duty macro height (>=15) or reserved
|
||||
1 | reserved (>=17) or wave macro height (>=15) or reserved
|
||||
4?? | volume macro
|
||||
| - before version 87, if this is the C64 relative cutoff macro, its values were stored offset by 18.
|
||||
4?? | arp macro
|
||||
| - before version 31, this macro's values were stored offset by 12.
|
||||
4?? | duty macro
|
||||
| - before version 87, if this is the C64 relative duty macro, its values were stored offset by 12.
|
||||
4?? | wave macro
|
||||
4?? | pitch macro (>=17)
|
||||
4?? | extra 1 macro (>=17)
|
||||
|
@ -600,6 +675,39 @@ size | description
|
|||
1 | parameter 2
|
||||
1 | parameter 3
|
||||
1 | parameter 4
|
||||
--- | **additional macro mode flags** (>=84)
|
||||
1 | volume macro mode
|
||||
1 | duty macro mode
|
||||
1 | wave macro mode
|
||||
1 | pitch macro mode
|
||||
1 | extra 1 macro mode
|
||||
1 | extra 2 macro mode
|
||||
1 | extra 3 macro mode
|
||||
1 | alg macro mode
|
||||
1 | fb macro mode
|
||||
1 | fms macro mode
|
||||
1 | ams macro mode
|
||||
1 | left panning macro mode
|
||||
1 | right panning macro mode
|
||||
1 | phase reset macro mode
|
||||
1 | extra 4 macro mode
|
||||
1 | extra 5 macro mode
|
||||
1 | extra 6 macro mode
|
||||
1 | extra 7 macro mode
|
||||
1 | extra 8 macro mode
|
||||
--- | **extra C64 data** (>=89)
|
||||
1 | don't test/gate before new note
|
||||
--- | **MultiPCM data** (>=93)
|
||||
1 | attack rate
|
||||
1 | decay 1 rate
|
||||
1 | decay level
|
||||
1 | decay 2 rate
|
||||
1 | release rate
|
||||
1 | rate correction
|
||||
1 | lfo rate
|
||||
1 | vib depth
|
||||
1 | am depth
|
||||
23 | reserved
|
||||
```
|
||||
|
||||
# wavetable
|
||||
|
@ -675,15 +783,18 @@ size | description
|
|||
| - 10: A#
|
||||
| - 11: B
|
||||
| - 12: C (of next octave)
|
||||
| - this is actually a leftover of the .dmf format.
|
||||
| - 100: note off
|
||||
| - 100: note release
|
||||
| - 100: macro release
|
||||
| - octave
|
||||
| - this is an signed char stored in a short.
|
||||
| - therefore octave value 255 is actually octave -1.
|
||||
| - yep, another leftover of the .dmf format...
|
||||
| - instrument
|
||||
| - volume
|
||||
| - effect and effect data...
|
||||
| - effect and effect data (× effect columns)
|
||||
| - for note/octave, if both values are 0 then it means empty.
|
||||
| - for instrument, volume, effect and effect data, a value of -1 means empty.
|
||||
STR | pattern name (>=51)
|
||||
```
|
||||
|
|
|
@ -25,13 +25,13 @@
|
|||
|
||||
struct SampleRateChangeEvent {
|
||||
double rate;
|
||||
SampleRateChangeEvent(double r):
|
||||
explicit SampleRateChangeEvent(double r):
|
||||
rate(r) {}
|
||||
};
|
||||
|
||||
struct BufferSizeChangeEvent {
|
||||
unsigned int bufsize;
|
||||
BufferSizeChangeEvent(unsigned int bs):
|
||||
explicit BufferSizeChangeEvent(unsigned int bs):
|
||||
bufsize(bs) {}
|
||||
};
|
||||
|
||||
|
@ -183,6 +183,7 @@ class TAAudio {
|
|||
inBufs(NULL),
|
||||
outBufs(NULL),
|
||||
audioProcCallback(NULL),
|
||||
audioProcCallbackUser(NULL),
|
||||
sampleRateChanged(NULL),
|
||||
bufferSizeChanged(NULL),
|
||||
midiIn(NULL),
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#define _DISPATCH_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#define ONE_SEMITONE 2200
|
||||
|
@ -35,74 +36,95 @@
|
|||
// names as strings for the commands (and other debug stuff).
|
||||
//
|
||||
// if you miss it, the program will crash or misbehave at some point.
|
||||
//
|
||||
// the comments are: (arg1, arg2) -> val
|
||||
// not all commands have a return value
|
||||
enum DivDispatchCmds {
|
||||
DIV_CMD_NOTE_ON=0,
|
||||
DIV_CMD_NOTE_ON=0, // (note)
|
||||
DIV_CMD_NOTE_OFF,
|
||||
DIV_CMD_NOTE_OFF_ENV,
|
||||
DIV_CMD_ENV_RELEASE,
|
||||
DIV_CMD_INSTRUMENT,
|
||||
DIV_CMD_VOLUME,
|
||||
DIV_CMD_GET_VOLUME,
|
||||
DIV_CMD_GET_VOLMAX,
|
||||
DIV_CMD_NOTE_PORTA,
|
||||
DIV_CMD_PITCH,
|
||||
DIV_CMD_PANNING,
|
||||
DIV_CMD_LEGATO,
|
||||
DIV_CMD_PRE_PORTA,
|
||||
DIV_CMD_PRE_NOTE, // used in C64
|
||||
DIV_CMD_INSTRUMENT, // (ins, force)
|
||||
DIV_CMD_VOLUME, // (vol)
|
||||
DIV_CMD_GET_VOLUME, // () -> vol
|
||||
DIV_CMD_GET_VOLMAX, // () -> volMax
|
||||
DIV_CMD_NOTE_PORTA, // (target, speed) -> 2 if target reached
|
||||
DIV_CMD_PITCH, // (pitch)
|
||||
DIV_CMD_PANNING, // (left, right)
|
||||
DIV_CMD_LEGATO, // (note)
|
||||
DIV_CMD_PRE_PORTA, // (inPorta, isPortaOrSlide)
|
||||
DIV_CMD_PRE_NOTE, // used in C64 (note)
|
||||
|
||||
DIV_CMD_SAMPLE_MODE,
|
||||
DIV_CMD_SAMPLE_FREQ,
|
||||
DIV_CMD_SAMPLE_BANK,
|
||||
DIV_CMD_SAMPLE_POS,
|
||||
DIV_CMD_SAMPLE_MODE, // (enabled)
|
||||
DIV_CMD_SAMPLE_FREQ, // (frequency)
|
||||
DIV_CMD_SAMPLE_BANK, // (bank)
|
||||
DIV_CMD_SAMPLE_POS, // (pos)
|
||||
|
||||
DIV_CMD_FM_HARD_RESET,
|
||||
DIV_CMD_FM_LFO,
|
||||
DIV_CMD_FM_LFO_WAVE,
|
||||
DIV_CMD_FM_TL,
|
||||
DIV_CMD_FM_AR,
|
||||
DIV_CMD_FM_FB,
|
||||
DIV_CMD_FM_MULT,
|
||||
DIV_CMD_FM_EXTCH,
|
||||
DIV_CMD_FM_AM_DEPTH,
|
||||
DIV_CMD_FM_PM_DEPTH,
|
||||
DIV_CMD_FM_HARD_RESET, // (enabled)
|
||||
DIV_CMD_FM_LFO, // (speed)
|
||||
DIV_CMD_FM_LFO_WAVE, // (waveform)
|
||||
DIV_CMD_FM_TL, // (op, value)
|
||||
DIV_CMD_FM_AM, // (op, value)
|
||||
DIV_CMD_FM_AR, // (op, value)
|
||||
DIV_CMD_FM_DR, // (op, value)
|
||||
DIV_CMD_FM_SL, // (op, value)
|
||||
DIV_CMD_FM_D2R, // (op, value)
|
||||
DIV_CMD_FM_RR, // (op, value)
|
||||
DIV_CMD_FM_DT, // (op, value)
|
||||
DIV_CMD_FM_DT2, // (op, value)
|
||||
DIV_CMD_FM_RS, // (op, value)
|
||||
DIV_CMD_FM_KSR, // (op, value)
|
||||
DIV_CMD_FM_VIB, // (op, value)
|
||||
DIV_CMD_FM_SUS, // (op, value)
|
||||
DIV_CMD_FM_WS, // (op, value)
|
||||
DIV_CMD_FM_SSG, // (op, value)
|
||||
DIV_CMD_FM_REV, // (op, value)
|
||||
DIV_CMD_FM_EG_SHIFT, // (op, value)
|
||||
DIV_CMD_FM_FB, // (value)
|
||||
DIV_CMD_FM_MULT, // (op, value)
|
||||
DIV_CMD_FM_FINE, // (op, value)
|
||||
DIV_CMD_FM_FIXFREQ, // (op, value)
|
||||
DIV_CMD_FM_EXTCH, // (enabled)
|
||||
DIV_CMD_FM_AM_DEPTH, // (depth)
|
||||
DIV_CMD_FM_PM_DEPTH, // (depth)
|
||||
|
||||
DIV_CMD_GENESIS_LFO,
|
||||
DIV_CMD_GENESIS_LFO, // unused?
|
||||
|
||||
DIV_CMD_ARCADE_LFO,
|
||||
DIV_CMD_ARCADE_LFO, // unused?
|
||||
|
||||
DIV_CMD_STD_NOISE_FREQ,
|
||||
DIV_CMD_STD_NOISE_MODE,
|
||||
DIV_CMD_STD_NOISE_FREQ, // (freq)
|
||||
DIV_CMD_STD_NOISE_MODE, // (mode)
|
||||
|
||||
DIV_CMD_WAVE,
|
||||
DIV_CMD_WAVE, // (waveform)
|
||||
|
||||
DIV_CMD_GB_SWEEP_TIME,
|
||||
DIV_CMD_GB_SWEEP_DIR,
|
||||
DIV_CMD_GB_SWEEP_TIME, // (time)
|
||||
DIV_CMD_GB_SWEEP_DIR, // (direction)
|
||||
|
||||
DIV_CMD_PCE_LFO_MODE,
|
||||
DIV_CMD_PCE_LFO_SPEED,
|
||||
DIV_CMD_PCE_LFO_MODE, // (mode)
|
||||
DIV_CMD_PCE_LFO_SPEED, // (speed)
|
||||
|
||||
DIV_CMD_NES_SWEEP,
|
||||
DIV_CMD_NES_SWEEP, // (direction, value)
|
||||
DIV_CMD_NES_DMC, // (value)
|
||||
|
||||
DIV_CMD_C64_CUTOFF,
|
||||
DIV_CMD_C64_RESONANCE,
|
||||
DIV_CMD_C64_FILTER_MODE,
|
||||
DIV_CMD_C64_RESET_TIME,
|
||||
DIV_CMD_C64_RESET_MASK,
|
||||
DIV_CMD_C64_FILTER_RESET,
|
||||
DIV_CMD_C64_DUTY_RESET,
|
||||
DIV_CMD_C64_EXTENDED,
|
||||
DIV_CMD_C64_FINE_DUTY,
|
||||
DIV_CMD_C64_FINE_CUTOFF,
|
||||
DIV_CMD_C64_CUTOFF, // (value)
|
||||
DIV_CMD_C64_RESONANCE, // (value)
|
||||
DIV_CMD_C64_FILTER_MODE, // (value)
|
||||
DIV_CMD_C64_RESET_TIME, // (value)
|
||||
DIV_CMD_C64_RESET_MASK, // (mask)
|
||||
DIV_CMD_C64_FILTER_RESET, // (value)
|
||||
DIV_CMD_C64_DUTY_RESET, // (value)
|
||||
DIV_CMD_C64_EXTENDED, // (value)
|
||||
DIV_CMD_C64_FINE_DUTY, // (value)
|
||||
DIV_CMD_C64_FINE_CUTOFF, // (value)
|
||||
|
||||
DIV_CMD_AY_ENVELOPE_SET,
|
||||
DIV_CMD_AY_ENVELOPE_LOW,
|
||||
DIV_CMD_AY_ENVELOPE_HIGH,
|
||||
DIV_CMD_AY_ENVELOPE_SLIDE,
|
||||
DIV_CMD_AY_NOISE_MASK_AND,
|
||||
DIV_CMD_AY_NOISE_MASK_OR,
|
||||
DIV_CMD_AY_AUTO_ENVELOPE,
|
||||
DIV_CMD_AY_IO_WRITE,
|
||||
DIV_CMD_AY_NOISE_MASK_AND, // (value)
|
||||
DIV_CMD_AY_NOISE_MASK_OR, // (value)
|
||||
DIV_CMD_AY_AUTO_ENVELOPE, // (value)
|
||||
DIV_CMD_AY_IO_WRITE, // (port, value)
|
||||
DIV_CMD_AY_AUTO_PWM,
|
||||
|
||||
DIV_CMD_FDS_MOD_DEPTH,
|
||||
|
@ -111,17 +133,18 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_FDS_MOD_POS,
|
||||
DIV_CMD_FDS_MOD_WAVE,
|
||||
|
||||
DIV_CMD_SAA_ENVELOPE,
|
||||
DIV_CMD_SAA_ENVELOPE, // (value)
|
||||
|
||||
DIV_CMD_AMIGA_FILTER,
|
||||
DIV_CMD_AMIGA_AM,
|
||||
DIV_CMD_AMIGA_PM,
|
||||
DIV_CMD_AMIGA_FILTER, // (enabled)
|
||||
DIV_CMD_AMIGA_AM, // (enabled)
|
||||
DIV_CMD_AMIGA_PM, // (enabled)
|
||||
|
||||
DIV_CMD_LYNX_LFSR_LOAD,
|
||||
DIV_CMD_LYNX_LFSR_LOAD, // (value)
|
||||
|
||||
DIV_CMD_QSOUND_ECHO_FEEDBACK,
|
||||
DIV_CMD_QSOUND_ECHO_DELAY,
|
||||
DIV_CMD_QSOUND_ECHO_LEVEL,
|
||||
DIV_CMD_QSOUND_SURROUND,
|
||||
|
||||
DIV_CMD_X1_010_ENVELOPE_SHAPE,
|
||||
DIV_CMD_X1_010_ENVELOPE_ENABLE,
|
||||
|
@ -146,7 +169,7 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,
|
||||
DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,
|
||||
|
||||
DIV_ALWAYS_SET_VOLUME,
|
||||
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
|
||||
|
||||
DIV_CMD_MAX
|
||||
};
|
||||
|
@ -200,7 +223,26 @@ struct DivRegWrite {
|
|||
addr(a), val(v) {}
|
||||
};
|
||||
|
||||
struct DivDispatchOscBuffer {
|
||||
bool follow;
|
||||
unsigned int rate;
|
||||
unsigned short needle;
|
||||
unsigned short readNeedle;
|
||||
unsigned short followNeedle;
|
||||
short data[65536];
|
||||
|
||||
DivDispatchOscBuffer():
|
||||
follow(true),
|
||||
rate(65536),
|
||||
needle(0),
|
||||
readNeedle(0),
|
||||
followNeedle(0) {
|
||||
memset(data,0,65536*sizeof(short));
|
||||
}
|
||||
};
|
||||
|
||||
class DivEngine;
|
||||
class DivMacroInt;
|
||||
|
||||
class DivDispatch {
|
||||
protected:
|
||||
|
@ -214,11 +256,13 @@ class DivDispatch {
|
|||
/**
|
||||
* the rate the samples are provided.
|
||||
* the engine shall resample to the output rate.
|
||||
* you have to initialize this one during init() or setFlags().
|
||||
*/
|
||||
int rate;
|
||||
|
||||
/**
|
||||
* the actual chip's clock.
|
||||
* you have to initialize this one during init() or setFlags().
|
||||
*/
|
||||
int chipClock;
|
||||
|
||||
|
@ -245,14 +289,27 @@ class DivDispatch {
|
|||
|
||||
/**
|
||||
* ticks this dispatch.
|
||||
* @param sysTick whether the engine has ticked (if not then this may be a sub-tick used in low-latency mode).
|
||||
*/
|
||||
virtual void tick();
|
||||
virtual void tick(bool sysTick=true);
|
||||
|
||||
/**
|
||||
* get the state of a channel.
|
||||
* @return a pointer, or NULL.
|
||||
*/
|
||||
virtual void* getChanState(int chan);
|
||||
|
||||
/**
|
||||
* get the DivMacroInt of a chanmel.
|
||||
* @return a pointer, or NULL.
|
||||
*/
|
||||
virtual DivMacroInt* getChanMacroInt(int chan);
|
||||
|
||||
/**
|
||||
* get an oscilloscope buffer for a channel.
|
||||
* @return a pointer to a DivDispatchOscBuffer, or NULL if not supported.
|
||||
*/
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
|
||||
/**
|
||||
* get the register pool of this dispatch.
|
||||
|
@ -404,6 +461,26 @@ class DivDispatch {
|
|||
*/
|
||||
virtual const char** getRegisterSheet();
|
||||
|
||||
/**
|
||||
* Get sample memory buffer.
|
||||
*/
|
||||
virtual const void* getSampleMem(int index = 0);
|
||||
|
||||
/**
|
||||
* Get sample memory capacity.
|
||||
*/
|
||||
virtual size_t getSampleMemCapacity(int index = 0);
|
||||
|
||||
/**
|
||||
* Get sample memory usage.
|
||||
*/
|
||||
virtual size_t getSampleMemUsage(int index = 0);
|
||||
|
||||
/**
|
||||
* Render samples into sample memory.
|
||||
*/
|
||||
virtual void renderSamples();
|
||||
|
||||
/**
|
||||
* initialize this DivDispatch.
|
||||
* @param parent the parent DivEngine.
|
||||
|
@ -417,16 +494,35 @@ class DivDispatch {
|
|||
/**
|
||||
* quit the DivDispatch.
|
||||
*/
|
||||
virtual void quit();
|
||||
virtual void quit();
|
||||
|
||||
virtual ~DivDispatch();
|
||||
virtual ~DivDispatch();
|
||||
};
|
||||
|
||||
// pitch calculation:
|
||||
// - a DivDispatch usually contains four variables per channel:
|
||||
// - baseFreq: this changes on new notes, legato, arpeggio and slides.
|
||||
// - pitch: this changes with DIV_CMD_PITCH (E5xx/04xy).
|
||||
// - freq: this is the result of combining baseFreq and pitch using DivEngine::calcFreq().
|
||||
// - freqChanged: whether baseFreq and/or pitch have changed, and a frequency recalculation is required on the next tick.
|
||||
// - the following definitions will help you calculate baseFreq.
|
||||
// - to use them, define CHIP_DIVIDER and/or CHIP_FREQBASE in your code (not in the header though!).
|
||||
#define NOTE_PERIODIC(x) round(parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true))
|
||||
#define NOTE_PERIODIC_NOROUND(x) parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true)
|
||||
#define NOTE_FREQUENCY(x) parent->calcBaseFreq(chipClock,CHIP_FREQBASE,x,false)
|
||||
|
||||
// this is a special case definition. only use it for f-num/block-based chips.
|
||||
#define NOTE_FNUM_BLOCK(x,bits) parent->calcBaseFreqFNumBlock(chipClock,CHIP_FREQBASE,x,bits)
|
||||
|
||||
// these are here for convenience.
|
||||
// it is encouraged to use these, since you get an exact value this way.
|
||||
// - NTSC colorburst: 3.58MHz
|
||||
// - PAL colorburst: 4.43MHz
|
||||
#define COLOR_NTSC (315000000.0/88.0)
|
||||
#define COLOR_PAL (283.75*15625.0+25.0)
|
||||
|
||||
#define CLAMP_VAR(x,xMin,xMax) \
|
||||
if (x<xMin) x=xMin; \
|
||||
if (x>xMax) x=xMax;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "platform/qsound.h"
|
||||
#include "platform/vera.h"
|
||||
#include "platform/x1_010.h"
|
||||
#include "platform/su.h"
|
||||
#include "platform/swan.h"
|
||||
#include "platform/lynx.h"
|
||||
#include "platform/bubsyswsg.h"
|
||||
|
@ -188,6 +189,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
dispatch=new DivPlatformNES;
|
||||
((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCore",0)==1);
|
||||
break;
|
||||
case DIV_SYSTEM_C64_6581:
|
||||
dispatch=new DivPlatformC64;
|
||||
|
@ -226,6 +228,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_FDS:
|
||||
dispatch=new DivPlatformFDS;
|
||||
((DivPlatformFDS*)dispatch)->setNSFPlay(eng->getConfInt("fdsCore",0)==1);
|
||||
break;
|
||||
case DIV_SYSTEM_TIA:
|
||||
dispatch=new DivPlatformTIA;
|
||||
|
@ -311,6 +314,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_MMC5:
|
||||
dispatch=new DivPlatformMMC5;
|
||||
break;
|
||||
case DIV_SYSTEM_SOUND_UNIT:
|
||||
dispatch=new DivPlatformSoundUnit;
|
||||
break;
|
||||
case DIV_SYSTEM_DUMMY:
|
||||
dispatch=new DivPlatformDummy;
|
||||
break;
|
||||
default:
|
||||
logW("this system is not supported yet! using dummy platform.");
|
||||
dispatch=new DivPlatformDummy;
|
||||
|
|
|
@ -41,7 +41,7 @@ void process(void* u, float** in, float** out, int inChans, int outChans, unsign
|
|||
((DivEngine*)u)->nextBuf(in,out,inChans,outChans,size);
|
||||
}
|
||||
|
||||
const char* DivEngine::getEffectDesc(unsigned char effect, int chan) {
|
||||
const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNull) {
|
||||
switch (effect) {
|
||||
case 0x00:
|
||||
return "00xy: Arpeggio";
|
||||
|
@ -69,6 +69,12 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) {
|
|||
return "0Dxx: Jump to next pattern";
|
||||
case 0x0f:
|
||||
return "0Fxx: Set speed 2";
|
||||
case 0x80:
|
||||
return "80xx: Set panning (00: left; 80: center; FF: right)";
|
||||
case 0x81:
|
||||
return "81xx: Set panning (left channel)";
|
||||
case 0x82:
|
||||
return "82xx: Set panning (right channel)";
|
||||
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
||||
return "Cxxx: Set tick rate (hz)";
|
||||
case 0xe0:
|
||||
|
@ -116,14 +122,13 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) {
|
|||
default:
|
||||
if ((effect&0xf0)==0x90) {
|
||||
return "9xxx: Set sample offset*256";
|
||||
}
|
||||
else if (chan>=0 && chan<chans) {
|
||||
} else if (chan>=0 && chan<chans) {
|
||||
const char* ret=disCont[dispatchOfChan[chan]].dispatch->getEffectName(effect);
|
||||
if (ret!=NULL) return ret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return "Invalid effect";
|
||||
return notNull?"Invalid effect":NULL;
|
||||
}
|
||||
|
||||
void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
||||
|
@ -141,7 +146,7 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
|||
for (int j=nextRow; j<song.patLen; j++) {
|
||||
nextRow=0;
|
||||
for (int k=0; k<chans; k++) {
|
||||
for (int l=0; l<song.pat[k].effectRows; l++) {
|
||||
for (int l=0; l<song.pat[k].effectCols; l++) {
|
||||
effectVal=pat[k]->data[j][5+(l<<1)];
|
||||
if (effectVal<0) effectVal=0;
|
||||
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
|
||||
|
@ -358,6 +363,14 @@ void DivEngine::runExportThread() {
|
|||
for (int j=0; j<chans; j++) {
|
||||
bool mute=(j!=i);
|
||||
isMuted[j]=mute;
|
||||
}
|
||||
if (getChannelType(i)==5) {
|
||||
for (int j=i; j<chans; j++) {
|
||||
if (getChannelType(j)!=5) break;
|
||||
isMuted[j]=false;
|
||||
}
|
||||
}
|
||||
for (int j=0; j<chans; j++) {
|
||||
if (disCont[dispatchOfChan[j]].dispatch!=NULL) {
|
||||
disCont[dispatchOfChan[j]].dispatch->muteChannel(dispatchChanOfChan[j],isMuted[j]);
|
||||
}
|
||||
|
@ -385,6 +398,17 @@ void DivEngine::runExportThread() {
|
|||
if (sf_close(sf)!=0) {
|
||||
logE("could not close audio file!");
|
||||
}
|
||||
|
||||
if (getChannelType(i)==5) {
|
||||
i++;
|
||||
while (true) {
|
||||
if (i>=chans) break;
|
||||
if (getChannelType(i)!=5) break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
|
||||
if (stopExport) break;
|
||||
}
|
||||
exporting=false;
|
||||
|
||||
|
@ -412,12 +436,25 @@ void DivEngine::runExportThread() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
stopExport=false;
|
||||
}
|
||||
|
||||
bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode) {
|
||||
exportPath=path;
|
||||
exportMode=mode;
|
||||
if (exportMode!=DIV_EXPORT_MODE_ONE) {
|
||||
// remove extension
|
||||
String lowerCase=exportPath;
|
||||
for (char& i: lowerCase) {
|
||||
if (i>='A' && i<='Z') i+='a'-'A';
|
||||
}
|
||||
size_t extPos=lowerCase.rfind(".wav");
|
||||
if (extPos!=String::npos) {
|
||||
exportPath=exportPath.substr(0,extPos);
|
||||
}
|
||||
}
|
||||
exporting=true;
|
||||
stopExport=false;
|
||||
stop();
|
||||
repeatPattern=false;
|
||||
setOrder(0);
|
||||
|
@ -433,6 +470,7 @@ void DivEngine::waitAudioFile() {
|
|||
}
|
||||
|
||||
bool DivEngine::haltAudioFile() {
|
||||
stopExport=true;
|
||||
stop();
|
||||
return true;
|
||||
}
|
||||
|
@ -468,118 +506,107 @@ void DivEngine::renderSamples() {
|
|||
song.sample[i]->render();
|
||||
}
|
||||
|
||||
// step 2: allocate ADPCM-A samples
|
||||
if (adpcmAMem==NULL) adpcmAMem=new unsigned char[16777216];
|
||||
|
||||
size_t memPos=0;
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* s=song.sample[i];
|
||||
int paddedLen=(s->lengthA+255)&(~0xff);
|
||||
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
|
||||
memPos=(memPos+0xfffff)&0xf00000;
|
||||
// step 2: render samples to dispatch
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
if (disCont[i].dispatch!=NULL) {
|
||||
disCont[i].dispatch->renderSamples();
|
||||
}
|
||||
if (memPos>=16777216) {
|
||||
logW("out of ADPCM-A memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
if (memPos+paddedLen>=16777216) {
|
||||
memcpy(adpcmAMem+memPos,s->dataA,16777216-memPos);
|
||||
logW("out of ADPCM-A memory for sample %d!",i);
|
||||
} else {
|
||||
memcpy(adpcmAMem+memPos,s->dataA,paddedLen);
|
||||
}
|
||||
s->offA=memPos;
|
||||
memPos+=paddedLen;
|
||||
}
|
||||
adpcmAMemLen=memPos+256;
|
||||
}
|
||||
|
||||
// step 2: allocate ADPCM-B samples
|
||||
if (adpcmBMem==NULL) adpcmBMem=new unsigned char[16777216];
|
||||
|
||||
memPos=0;
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* s=song.sample[i];
|
||||
int paddedLen=(s->lengthB+255)&(~0xff);
|
||||
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
|
||||
memPos=(memPos+0xfffff)&0xf00000;
|
||||
String DivEngine::encodeSysDesc(std::vector<int>& desc) {
|
||||
String ret;
|
||||
if (desc[0]!=0) {
|
||||
int index=0;
|
||||
for (size_t i=0; i<desc.size(); i+=4) {
|
||||
ret+=fmt::sprintf("%d %d %d %d ",systemToFileFur((DivSystem)desc[i]),desc[i+1],desc[i+2],desc[i+3]);
|
||||
index++;
|
||||
if (index>=32) break;
|
||||
}
|
||||
if (memPos>=16777216) {
|
||||
logW("out of ADPCM-B memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
if (memPos+paddedLen>=16777216) {
|
||||
memcpy(adpcmBMem+memPos,s->dataB,16777216-memPos);
|
||||
logW("out of ADPCM-B memory for sample %d!",i);
|
||||
} else {
|
||||
memcpy(adpcmBMem+memPos,s->dataB,paddedLen);
|
||||
}
|
||||
s->offB=memPos;
|
||||
memPos+=paddedLen;
|
||||
}
|
||||
adpcmBMemLen=memPos+256;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// step 4: allocate qsound pcm samples
|
||||
if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216];
|
||||
memset(qsoundMem,0,16777216);
|
||||
std::vector<int> DivEngine::decodeSysDesc(String desc) {
|
||||
std::vector<int> ret;
|
||||
bool hasVal=false;
|
||||
bool negative=false;
|
||||
int val=0;
|
||||
int curStage=0;
|
||||
int sysID=0;
|
||||
int sysVol=0;
|
||||
int sysPan=0;
|
||||
int sysFlags=0;
|
||||
desc+=' '; // ha
|
||||
for (char i: desc) {
|
||||
switch (i) {
|
||||
case ' ':
|
||||
if (hasVal) {
|
||||
if (negative) val=-val;
|
||||
switch (curStage) {
|
||||
case 0:
|
||||
sysID=val;
|
||||
curStage++;
|
||||
break;
|
||||
case 1:
|
||||
sysVol=val;
|
||||
curStage++;
|
||||
break;
|
||||
case 2:
|
||||
sysPan=val;
|
||||
curStage++;
|
||||
break;
|
||||
case 3:
|
||||
sysFlags=val;
|
||||
|
||||
memPos=0;
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* s=song.sample[i];
|
||||
int length=s->length8;
|
||||
if (length>65536-16) {
|
||||
length=65536-16;
|
||||
if (systemFromFileFur(sysID)!=0) {
|
||||
if (sysVol<-128) sysVol=-128;
|
||||
if (sysVol>127) sysVol=127;
|
||||
if (sysPan<-128) sysPan=-128;
|
||||
if (sysPan>127) sysPan=127;
|
||||
ret.push_back(systemFromFileFur(sysID));
|
||||
ret.push_back(sysVol);
|
||||
ret.push_back(sysPan);
|
||||
ret.push_back(sysFlags);
|
||||
}
|
||||
|
||||
curStage=0;
|
||||
break;
|
||||
}
|
||||
hasVal=false;
|
||||
negative=false;
|
||||
val=0;
|
||||
}
|
||||
break;
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
val=(val*10)+(i-'0');
|
||||
hasVal=true;
|
||||
break;
|
||||
case '-':
|
||||
if (!hasVal) negative=true;
|
||||
break;
|
||||
}
|
||||
if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) {
|
||||
memPos=(memPos+0xffff)&0xff0000;
|
||||
}
|
||||
if (memPos>=16777216) {
|
||||
logW("out of QSound PCM memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
if (memPos+length>=16777216) {
|
||||
for (unsigned int i=0; i<16777216-(memPos+length); i++) {
|
||||
qsoundMem[(memPos+i)^0x8000]=s->data8[i];
|
||||
}
|
||||
logW("out of QSound PCM memory for sample %d!",i);
|
||||
} else {
|
||||
for (int i=0; i<length; i++) {
|
||||
qsoundMem[(memPos+i)^0x8000]=s->data8[i];
|
||||
}
|
||||
}
|
||||
s->offQSound=memPos^0x8000;
|
||||
memPos+=length+16;
|
||||
}
|
||||
qsoundMemLen=memPos+256;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// step 4: allocate x1-010 pcm samples
|
||||
if (x1_010Mem==NULL) x1_010Mem=new unsigned char[1048576];
|
||||
memset(x1_010Mem,0,1048576);
|
||||
|
||||
memPos=0;
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* s=song.sample[i];
|
||||
int paddedLen=(s->length8+4095)&(~0xfff);
|
||||
// fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!)
|
||||
if (paddedLen>131072) {
|
||||
paddedLen=131072;
|
||||
void DivEngine::initSongWithDesc(const int* description) {
|
||||
int chanCount=0;
|
||||
if (description[0]!=0) {
|
||||
int index=0;
|
||||
for (int i=0; description[i]; i+=4) {
|
||||
song.system[index]=(DivSystem)description[i];
|
||||
song.systemVol[index]=description[i+1];
|
||||
song.systemPan[index]=description[i+2];
|
||||
song.systemFlags[index]=description[i+3];
|
||||
index++;
|
||||
chanCount+=getChannelCount(song.system[index]);
|
||||
if (chanCount>=63) break;
|
||||
if (index>=32) break;
|
||||
}
|
||||
if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) {
|
||||
memPos=(memPos+0x1ffff)&0xfe0000;
|
||||
}
|
||||
if (memPos>=1048576) {
|
||||
logW("out of X1-010 memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
if (memPos+paddedLen>=1048576) {
|
||||
memcpy(x1_010Mem+memPos,s->data8,1048576-memPos);
|
||||
logW("out of X1-010 memory for sample %d!",i);
|
||||
} else {
|
||||
memcpy(x1_010Mem+memPos,s->data8,paddedLen);
|
||||
}
|
||||
s->offX1_010=memPos;
|
||||
memPos+=paddedLen;
|
||||
song.systemLen=index;
|
||||
}
|
||||
x1_010MemLen=memPos+256;
|
||||
}
|
||||
|
||||
void DivEngine::createNew(const int* description) {
|
||||
|
@ -589,33 +616,109 @@ void DivEngine::createNew(const int* description) {
|
|||
song.unload();
|
||||
song=DivSong();
|
||||
if (description!=NULL) {
|
||||
if (description[0]!=0) {
|
||||
int index=0;
|
||||
for (int i=0; description[i]; i+=4) {
|
||||
song.system[index]=(DivSystem)description[i];
|
||||
song.systemVol[index]=description[i+1];
|
||||
song.systemPan[index]=description[i+2];
|
||||
song.systemFlags[index]=description[i+3];
|
||||
index++;
|
||||
if (index>=32) break;
|
||||
}
|
||||
song.systemLen=index;
|
||||
}
|
||||
initSongWithDesc(description);
|
||||
}
|
||||
recalcChans();
|
||||
renderSamples();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
initDispatch();
|
||||
BUSY_BEGIN;
|
||||
renderSamples();
|
||||
reset();
|
||||
BUSY_END;
|
||||
}
|
||||
|
||||
void DivEngine::changeSystem(int index, DivSystem which) {
|
||||
void DivEngine::swapChannels(int src, int dest) {
|
||||
logV("swapping channel %d with %d",src,dest);
|
||||
if (src==dest) {
|
||||
logV("not swapping channels because it's the same channel!",src,dest);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0; i<256; i++) {
|
||||
song.orders.ord[dest][i]^=song.orders.ord[src][i];
|
||||
song.orders.ord[src][i]^=song.orders.ord[dest][i];
|
||||
song.orders.ord[dest][i]^=song.orders.ord[src][i];
|
||||
|
||||
DivPattern* prev=song.pat[src].data[i];
|
||||
song.pat[src].data[i]=song.pat[dest].data[i];
|
||||
song.pat[dest].data[i]=prev;
|
||||
}
|
||||
|
||||
song.pat[src].effectCols^=song.pat[dest].effectCols;
|
||||
song.pat[dest].effectCols^=song.pat[src].effectCols;
|
||||
song.pat[src].effectCols^=song.pat[dest].effectCols;
|
||||
|
||||
String prevChanName=song.chanName[src];
|
||||
String prevChanShortName=song.chanShortName[src];
|
||||
bool prevChanShow=song.chanShow[src];
|
||||
bool prevChanCollapse=song.chanCollapse[src];
|
||||
|
||||
song.chanName[src]=song.chanName[dest];
|
||||
song.chanShortName[src]=song.chanShortName[dest];
|
||||
song.chanShow[src]=song.chanShow[dest];
|
||||
song.chanCollapse[src]=song.chanCollapse[dest];
|
||||
song.chanName[dest]=prevChanName;
|
||||
song.chanShortName[dest]=prevChanShortName;
|
||||
song.chanShow[dest]=prevChanShow;
|
||||
song.chanCollapse[dest]=prevChanCollapse;
|
||||
}
|
||||
|
||||
void DivEngine::stompChannel(int ch) {
|
||||
logV("stomping channel %d",ch);
|
||||
for (int i=0; i<256; i++) {
|
||||
song.orders.ord[ch][i]=0;
|
||||
}
|
||||
song.pat[ch].wipePatterns();
|
||||
song.pat[ch].effectCols=1;
|
||||
song.chanName[ch]="";
|
||||
song.chanShortName[ch]="";
|
||||
song.chanShow[ch]=true;
|
||||
song.chanCollapse[ch]=false;
|
||||
}
|
||||
|
||||
void DivEngine::swapChannelsP(int src, int dest) {
|
||||
if (src<0 || src>=chans) return;
|
||||
if (dest<0 || dest>=chans) return;
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
swapChannels(src,dest);
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
}
|
||||
|
||||
void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
|
||||
int chanCount=chans;
|
||||
quitDispatch();
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
|
||||
if (!preserveOrder) {
|
||||
int firstChan=0;
|
||||
int chanMovement=getChannelCount(which)-getChannelCount(song.system[index]);
|
||||
while (dispatchOfChan[firstChan]!=index) firstChan++;
|
||||
int lastChan=firstChan+getChannelCount(song.system[index]);
|
||||
if (chanMovement!=0) {
|
||||
if (chanMovement>0) {
|
||||
// add channels
|
||||
for (int i=chanCount+chanMovement-1; i>=lastChan+chanMovement; i--) {
|
||||
swapChannels(i,i-chanMovement);
|
||||
}
|
||||
for (int i=lastChan; i<lastChan+chanMovement; i++) {
|
||||
stompChannel(i);
|
||||
}
|
||||
} else {
|
||||
// remove channels
|
||||
for (int i=lastChan+chanMovement; i<lastChan; i++) {
|
||||
stompChannel(i);
|
||||
}
|
||||
for (int i=lastChan+chanMovement; i<chanCount+chanMovement; i++) {
|
||||
swapChannels(i,i-chanMovement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
song.system[index]=which;
|
||||
song.systemFlags[index]=0;
|
||||
recalcChans();
|
||||
|
@ -656,7 +759,7 @@ bool DivEngine::addSystem(DivSystem which) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DivEngine::removeSystem(int index) {
|
||||
bool DivEngine::removeSystem(int index, bool preserveOrder) {
|
||||
if (song.systemLen<=1) {
|
||||
lastError="cannot remove the last one";
|
||||
return false;
|
||||
|
@ -665,13 +768,29 @@ bool DivEngine::removeSystem(int index) {
|
|||
lastError="invalid index";
|
||||
return false;
|
||||
}
|
||||
int chanCount=chans;
|
||||
quitDispatch();
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
|
||||
if (!preserveOrder) {
|
||||
int firstChan=0;
|
||||
while (dispatchOfChan[firstChan]!=index) firstChan++;
|
||||
for (int i=0; i<getChannelCount(song.system[index]); i++) {
|
||||
stompChannel(i+firstChan);
|
||||
}
|
||||
for (int i=firstChan+getChannelCount(song.system[index]); i<chanCount; i++) {
|
||||
swapChannels(i,i-getChannelCount(song.system[index]));
|
||||
}
|
||||
}
|
||||
|
||||
song.system[index]=DIV_SYSTEM_NULL;
|
||||
song.systemLen--;
|
||||
for (int i=index; i<song.systemLen; i++) {
|
||||
song.system[i]=song.system[i+1];
|
||||
song.systemVol[i]=song.systemVol[i+1];
|
||||
song.systemPan[i]=song.systemPan[i+1];
|
||||
song.systemFlags[i]=song.systemFlags[i+1];
|
||||
}
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
|
@ -706,8 +825,23 @@ String DivEngine::getWarnings() {
|
|||
return warnings;
|
||||
}
|
||||
|
||||
DivInstrument* DivEngine::getIns(int index) {
|
||||
if (index<0 || index>=song.insLen) return &song.nullIns;
|
||||
DivInstrument* DivEngine::getIns(int index, DivInstrumentType fallbackType) {
|
||||
if (index==-2 && tempIns!=NULL) {
|
||||
return tempIns;
|
||||
}
|
||||
if (index<0 || index>=song.insLen) {
|
||||
switch (fallbackType) {
|
||||
case DIV_INS_OPLL:
|
||||
return &song.nullInsOPLL;
|
||||
break;
|
||||
case DIV_INS_OPL:
|
||||
return &song.nullInsOPL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return &song.nullIns;
|
||||
}
|
||||
return song.ins[index];
|
||||
}
|
||||
|
||||
|
@ -727,6 +861,11 @@ DivSample* DivEngine::getSample(int index) {
|
|||
return song.sample[index];
|
||||
}
|
||||
|
||||
DivDispatch* DivEngine::getDispatch(int index) {
|
||||
if (index<0 || index>=song.systemLen) return NULL;
|
||||
return disCont[index].dispatch;
|
||||
}
|
||||
|
||||
void DivEngine::setLoops(int loops) {
|
||||
remainingLoops=loops;
|
||||
}
|
||||
|
@ -749,6 +888,16 @@ unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) {
|
|||
return disCont[sys].dispatch->getRegisterPool();
|
||||
}
|
||||
|
||||
DivMacroInt* DivEngine::getMacroInt(int chan) {
|
||||
if (chan<0 || chan>=chans) return NULL;
|
||||
return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) {
|
||||
if (chan<0 || chan>=chans) return NULL;
|
||||
return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]);
|
||||
}
|
||||
|
||||
void DivEngine::enableCommandStream(bool enable) {
|
||||
cmdStreamEnabled=enable;
|
||||
}
|
||||
|
@ -820,6 +969,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
|||
}
|
||||
if (!preserveDrift) {
|
||||
ticks=1;
|
||||
subticks=1;
|
||||
}
|
||||
skipping=false;
|
||||
cmdStream.clear();
|
||||
|
@ -842,21 +992,78 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri
|
|||
base*(divider/clock);
|
||||
}
|
||||
|
||||
int DivEngine::calcFreq(int base, int pitch, bool period, int octave) {
|
||||
unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) {
|
||||
int bf=calcBaseFreq(clock,divider,note,false);
|
||||
int block=note/12;
|
||||
if (block<0) block=0;
|
||||
if (block>7) block=7;
|
||||
bf>>=block;
|
||||
if (bf<0) bf=0;
|
||||
// octave boundaries
|
||||
while (bf>0 && bf<644 && block>0) {
|
||||
bf<<=1;
|
||||
block--;
|
||||
}
|
||||
if (bf>1288) {
|
||||
while (block<7) {
|
||||
bf>>=1;
|
||||
block++;
|
||||
}
|
||||
if (bf>((1<<bits)-1)) {
|
||||
bf=(1<<bits)-1;
|
||||
}
|
||||
}
|
||||
return bf|(block<<bits);
|
||||
}
|
||||
|
||||
int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2) {
|
||||
if (song.linearPitch) {
|
||||
// global pitch multiplier
|
||||
int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0));
|
||||
if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me
|
||||
if (song.pitchMacroIsLinear) {
|
||||
pitch+=pitch2;
|
||||
}
|
||||
pitch+=2048;
|
||||
if (pitch<0) pitch=0;
|
||||
if (pitch>4095) pitch=4095;
|
||||
return period?
|
||||
((base*(reversePitchTable[pitch]))/whatTheFuck):
|
||||
(((base*(pitchTable[pitch]))>>10)*whatTheFuck)/1024;
|
||||
int ret=period?
|
||||
((base*(reversePitchTable[pitch]))/whatTheFuck):
|
||||
(((base*(pitchTable[pitch]))>>10)*whatTheFuck)/1024;
|
||||
if (!song.pitchMacroIsLinear) {
|
||||
ret+=period?(-pitch2):pitch2;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return period?
|
||||
base-pitch:
|
||||
base+((pitch*octave)>>1);
|
||||
base-pitch-pitch2:
|
||||
base+((pitch*octave)>>1)+pitch2;
|
||||
}
|
||||
|
||||
int DivEngine::convertPanSplitToLinear(unsigned int val, unsigned char bits, int range) {
|
||||
int panL=val>>bits;
|
||||
int panR=val&((1<<bits)-1);
|
||||
int diff=panR-panL;
|
||||
float pan=0.5f;
|
||||
if (diff!=0) {
|
||||
pan=(1.0f+((float)diff/(float)MAX(panL,panR)))*0.5f;
|
||||
}
|
||||
return pan*range;
|
||||
}
|
||||
|
||||
int DivEngine::convertPanSplitToLinearLR(unsigned char left, unsigned char right, int range) {
|
||||
return convertPanSplitToLinear((left<<8)|right,8,range);
|
||||
}
|
||||
|
||||
unsigned int DivEngine::convertPanLinearToSplit(int val, unsigned char bits, int range) {
|
||||
if (val<0) val=0;
|
||||
if (val>range) val=range;
|
||||
int maxV=(1<<bits)-1;
|
||||
int panL=(((range-val)*maxV*2))/range;
|
||||
int panR=((val)*maxV*2)/range;
|
||||
if (panL>maxV) panL=maxV;
|
||||
if (panR>maxV) panR=maxV;
|
||||
return (panL<<bits)|panR;
|
||||
}
|
||||
|
||||
void DivEngine::play() {
|
||||
|
@ -964,8 +1171,10 @@ const char** DivEngine::getRegisterSheet(int sys) {
|
|||
}
|
||||
|
||||
void DivEngine::recalcChans() {
|
||||
bool isInsTypePossible[DIV_INS_MAX];
|
||||
chans=0;
|
||||
int chanIndex=0;
|
||||
memset(isInsTypePossible,0,DIV_INS_MAX*sizeof(bool));
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
int chanCount=getChannelCount(song.system[i]);
|
||||
chans+=chanCount;
|
||||
|
@ -974,8 +1183,25 @@ void DivEngine::recalcChans() {
|
|||
dispatchOfChan[chanIndex]=i;
|
||||
dispatchChanOfChan[chanIndex]=j;
|
||||
chanIndex++;
|
||||
|
||||
if (sysDefs[song.system[i]]!=NULL) {
|
||||
if (sysDefs[song.system[i]]->chanInsType[j][0]!=DIV_INS_NULL) {
|
||||
isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][0]]=true;
|
||||
}
|
||||
|
||||
if (sysDefs[song.system[i]]->chanInsType[j][1]!=DIV_INS_NULL) {
|
||||
isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][1]]=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
possibleInsTypes.clear();
|
||||
for (int i=0; i<DIV_INS_MAX; i++) {
|
||||
if (isInsTypePossible[i]) possibleInsTypes.push_back((DivInstrumentType)i);
|
||||
}
|
||||
|
||||
hasLoadedSomething=true;
|
||||
}
|
||||
|
||||
void DivEngine::reset() {
|
||||
|
@ -1263,8 +1489,22 @@ int DivEngine::addInstrument(int refChan) {
|
|||
BUSY_BEGIN;
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
int insCount=(int)song.ins.size();
|
||||
DivInstrumentType prefType=getPreferInsType(refChan);
|
||||
switch (prefType) {
|
||||
case DIV_INS_OPLL:
|
||||
*ins=song.nullInsOPLL;
|
||||
break;
|
||||
case DIV_INS_OPL:
|
||||
*ins=song.nullInsOPL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (sysOfChan[refChan]==DIV_SYSTEM_QSOUND) {
|
||||
*ins=song.nullInsQSound;
|
||||
}
|
||||
ins->name=fmt::sprintf("Instrument %d",insCount);
|
||||
ins->type=getPreferInsType(refChan);
|
||||
ins->type=prefType;
|
||||
saveLock.lock();
|
||||
song.ins.push_back(ins);
|
||||
song.insLen=insCount+1;
|
||||
|
@ -1283,6 +1523,15 @@ int DivEngine::addInstrumentPtr(DivInstrument* which) {
|
|||
return song.insLen;
|
||||
}
|
||||
|
||||
void DivEngine::loadTempIns(DivInstrument* which) {
|
||||
BUSY_BEGIN;
|
||||
if (tempIns==NULL) {
|
||||
tempIns=new DivInstrument;
|
||||
}
|
||||
*tempIns=*which;
|
||||
BUSY_END;
|
||||
}
|
||||
|
||||
void DivEngine::delInstrument(int index) {
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
|
@ -1336,6 +1585,10 @@ bool DivEngine::addWaveFromFile(const char* path) {
|
|||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
if (len==0) {
|
||||
fclose(f);
|
||||
return false;
|
||||
|
@ -1468,6 +1721,105 @@ int DivEngine::addSample() {
|
|||
|
||||
int DivEngine::addSampleFromFile(const char* path) {
|
||||
BUSY_BEGIN;
|
||||
warnings="";
|
||||
|
||||
const char* pathRedux=strrchr(path,DIR_SEPARATOR);
|
||||
if (pathRedux==NULL) {
|
||||
pathRedux=path;
|
||||
} else {
|
||||
pathRedux++;
|
||||
}
|
||||
String stripPath;
|
||||
const char* pathReduxEnd=strrchr(pathRedux,'.');
|
||||
if (pathReduxEnd==NULL) {
|
||||
stripPath=pathRedux;
|
||||
} else {
|
||||
for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) {
|
||||
stripPath+=*i;
|
||||
}
|
||||
}
|
||||
|
||||
const char* ext=strrchr(path,'.');
|
||||
if (ext!=NULL) {
|
||||
String extS;
|
||||
for (; *ext; ext++) {
|
||||
char i=*ext;
|
||||
if (i>='A' && i<='Z') {
|
||||
i+='a'-'A';
|
||||
}
|
||||
extS+=i;
|
||||
}
|
||||
if (extS==String(".dmc")) { // read as .dmc
|
||||
size_t len=0;
|
||||
DivSample* sample=new DivSample;
|
||||
int sampleCount=(int)song.sample.size();
|
||||
sample->name=stripPath;
|
||||
|
||||
FILE* f=fopen(path,"rb");
|
||||
if (f==NULL) {
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_END)<0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return -1;
|
||||
}
|
||||
|
||||
len=ftell(f);
|
||||
|
||||
if (len==0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="file is empty!";
|
||||
delete sample;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="file is invalid!";
|
||||
delete sample;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_SET)<0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sample->rate=33144;
|
||||
sample->centerRate=33144;
|
||||
sample->depth=1;
|
||||
sample->init(len*8);
|
||||
|
||||
if (fread(sample->dataDPCM,1,len,f)==0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return -1;
|
||||
}
|
||||
|
||||
saveLock.lock();
|
||||
song.sample.push_back(sample);
|
||||
song.sampleLen=sampleCount+1;
|
||||
saveLock.unlock();
|
||||
renderSamples();
|
||||
BUSY_END;
|
||||
return sampleCount;
|
||||
}
|
||||
}
|
||||
|
||||
SF_INFO si;
|
||||
SNDFILE* f=sf_open(path,SFM_READ,&si);
|
||||
if (f==NULL) {
|
||||
|
@ -1492,13 +1844,7 @@ int DivEngine::addSampleFromFile(const char* path) {
|
|||
}
|
||||
DivSample* sample=new DivSample;
|
||||
int sampleCount=(int)song.sample.size();
|
||||
const char* sName=strrchr(path,DIR_SEPARATOR);
|
||||
if (sName==NULL) {
|
||||
sName=path;
|
||||
} else {
|
||||
sName++;
|
||||
}
|
||||
sample->name=sName;
|
||||
sample->name=stripPath;
|
||||
|
||||
int index=0;
|
||||
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
|
||||
|
@ -1840,10 +2186,13 @@ void DivEngine::noteOff(int chan) {
|
|||
}
|
||||
|
||||
void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
|
||||
//if (ch<0 || ch>=chans) return;
|
||||
bool isViable[DIV_MAX_CHANS];
|
||||
bool canPlayAnyway=false;
|
||||
bool notInViableChannel=false;
|
||||
if (midiBaseChan<0) midiBaseChan=0;
|
||||
if (midiBaseChan>=chans) midiBaseChan=chans-1;
|
||||
int finalChan=midiBaseChan;
|
||||
int finalChanType=getChannelType(finalChan);
|
||||
|
||||
if (!playing) {
|
||||
reset();
|
||||
|
@ -1851,16 +2200,56 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
|
|||
playing=true;
|
||||
}
|
||||
|
||||
// 1. check which channels are viable for this instrument
|
||||
DivInstrument* insInst=getIns(ins);
|
||||
if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type) notInViableChannel=true;
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || getPreferInsSecondType(i)==insInst->type) {
|
||||
if (insInst->type==DIV_INS_OPL) {
|
||||
if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) {
|
||||
isViable[i]=true;
|
||||
canPlayAnyway=true;
|
||||
} else {
|
||||
isViable[i]=false;
|
||||
}
|
||||
} else {
|
||||
isViable[i]=true;
|
||||
canPlayAnyway=true;
|
||||
}
|
||||
} else {
|
||||
isViable[i]=false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canPlayAnyway) return;
|
||||
|
||||
// 2. find a free channel
|
||||
do {
|
||||
if ((ins==-1 || getPreferInsType(finalChan)==getIns(ins)->type) && chan[finalChan].midiNote==-1) {
|
||||
if (isViable[finalChan] && chan[finalChan].midiNote==-1 && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel)) {
|
||||
chan[finalChan].midiNote=note;
|
||||
chan[finalChan].midiAge=midiAgeCounter++;
|
||||
pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true));
|
||||
break;
|
||||
return;
|
||||
}
|
||||
if (++finalChan>=chans) {
|
||||
finalChan=0;
|
||||
}
|
||||
} while (finalChan!=midiBaseChan);
|
||||
|
||||
// 3. find the oldest channel
|
||||
int candidate=finalChan;
|
||||
do {
|
||||
if (isViable[finalChan] && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel) && chan[finalChan].midiAge<chan[candidate].midiAge) {
|
||||
candidate=finalChan;
|
||||
}
|
||||
if (++finalChan>=chans) {
|
||||
finalChan=0;
|
||||
}
|
||||
} while (finalChan!=midiBaseChan);
|
||||
|
||||
chan[candidate].midiNote=note;
|
||||
chan[candidate].midiAge=midiAgeCounter++;
|
||||
pendingNotes.push(DivNoteEvent(candidate,ins,note,vol,true));
|
||||
}
|
||||
|
||||
void DivEngine::autoNoteOff(int ch, int note, int vol) {
|
||||
|
@ -1878,6 +2267,20 @@ void DivEngine::autoNoteOff(int ch, int note, int vol) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivEngine::autoNoteOffAll() {
|
||||
if (!playing) {
|
||||
reset();
|
||||
freelance=true;
|
||||
playing=true;
|
||||
}
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (chan[i].midiNote!=-1) {
|
||||
pendingNotes.push(DivNoteEvent(i,-1,-1,-1,false));
|
||||
chan[i].midiNote=-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivEngine::setOrder(unsigned char order) {
|
||||
BUSY_BEGIN_SOFT;
|
||||
curOrder=order;
|
||||
|
@ -1939,6 +2342,10 @@ void DivEngine::setMetronome(bool enable) {
|
|||
metroAmp=0;
|
||||
}
|
||||
|
||||
void DivEngine::setMetronomeVol(float vol) {
|
||||
metroVol=vol;
|
||||
}
|
||||
|
||||
void DivEngine::setConsoleMode(bool enable) {
|
||||
consoleMode=enable;
|
||||
}
|
||||
|
@ -1959,6 +2366,7 @@ bool DivEngine::switchMaster() {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
renderSamples();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2110,6 +2518,12 @@ bool DivEngine::initAudioBackend() {
|
|||
|
||||
lowQuality=getConfInt("audioQuality",0);
|
||||
forceMono=getConfInt("forceMono",0);
|
||||
lowLatency=getConfInt("lowLatency",0);
|
||||
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
|
||||
if (metroVol<0.0f) metroVol=0.0f;
|
||||
if (metroVol>2.0f) metroVol=2.0f;
|
||||
|
||||
if (lowLatency) logI("using low latency mode.");
|
||||
|
||||
switch (audioEngine) {
|
||||
case DIV_AUDIO_JACK:
|
||||
|
@ -2212,6 +2626,9 @@ bool DivEngine::deinitAudioBackend() {
|
|||
#endif
|
||||
|
||||
bool DivEngine::init() {
|
||||
// register systems
|
||||
if (!systemsRegistered) registerSystems();
|
||||
|
||||
// init config
|
||||
#ifdef _WIN32
|
||||
configPath=getWinConfigPath();
|
||||
|
@ -2245,6 +2662,18 @@ bool DivEngine::init() {
|
|||
|
||||
loadConf();
|
||||
|
||||
// set default system preset
|
||||
if (!hasLoadedSomething) {
|
||||
logD("setting default preset");
|
||||
std::vector<int> preset=decodeSysDesc(getConfString("initialSys",""));
|
||||
logD("preset size %ld",preset.size());
|
||||
if (preset.size()>0 && (preset.size()&3)==0) {
|
||||
preset.push_back(0);
|
||||
initSongWithDesc(preset.data());
|
||||
}
|
||||
hasLoadedSomething=true;
|
||||
}
|
||||
|
||||
// init the rest of engine
|
||||
bool haveAudio=false;
|
||||
if (!initAudioBackend()) {
|
||||
|
@ -2283,6 +2712,7 @@ bool DivEngine::init() {
|
|||
oscBuf[1]=new float[32768];
|
||||
|
||||
initDispatch();
|
||||
renderSamples();
|
||||
reset();
|
||||
active=true;
|
||||
|
||||
|
|
|
@ -19,13 +19,16 @@
|
|||
|
||||
#ifndef _ENGINE_H
|
||||
#define _ENGINE_H
|
||||
#include "instrument.h"
|
||||
#include "song.h"
|
||||
#include "dispatch.h"
|
||||
#include "dataErrors.h"
|
||||
#include "safeWriter.h"
|
||||
#include "../audio/taAudio.h"
|
||||
#include "blip_buf.h"
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
|
@ -42,8 +45,8 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev81"
|
||||
#define DIV_ENGINE_VERSION 81
|
||||
#define DIV_VERSION "dev93"
|
||||
#define DIV_ENGINE_VERSION 93
|
||||
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
|
@ -82,11 +85,12 @@ struct DivChannelState {
|
|||
int delayOrder, delayRow, retrigSpeed, retrigTick;
|
||||
int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine;
|
||||
int tremoloDepth, tremoloRate, tremoloPos;
|
||||
unsigned char arp, arpStage, arpTicks;
|
||||
unsigned char arp, arpStage, arpTicks, panL, panR;
|
||||
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff;
|
||||
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit, resetArp;
|
||||
|
||||
int midiNote, curMidiNote, midiPitch;
|
||||
size_t midiAge;
|
||||
bool midiAftertouch;
|
||||
|
||||
DivChannelState():
|
||||
|
@ -116,6 +120,8 @@ struct DivChannelState {
|
|||
arp(0),
|
||||
arpStage(-1),
|
||||
arpTicks(1),
|
||||
panL(255),
|
||||
panR(255),
|
||||
doNote(false),
|
||||
legato(false),
|
||||
portaStop(false),
|
||||
|
@ -133,6 +139,7 @@ struct DivChannelState {
|
|||
midiNote(-1),
|
||||
curMidiNote(-1),
|
||||
midiPitch(-1),
|
||||
midiAge(0),
|
||||
midiAftertouch(false) {}
|
||||
};
|
||||
|
||||
|
@ -176,6 +183,94 @@ struct DivDispatchContainer {
|
|||
dcOffCompensation(false) {}
|
||||
};
|
||||
|
||||
typedef std::function<bool(int,unsigned char,unsigned char)> EffectProcess;
|
||||
|
||||
struct DivSysDef {
|
||||
const char* name;
|
||||
const char* nameJ;
|
||||
unsigned char id;
|
||||
unsigned char id_DMF;
|
||||
int channels;
|
||||
bool isFM, isSTD, isCompound;
|
||||
unsigned int vgmVersion;
|
||||
const char* chanNames[DIV_MAX_CHANS];
|
||||
const char* chanShortNames[DIV_MAX_CHANS];
|
||||
int chanTypes[DIV_MAX_CHANS];
|
||||
// 0: primary
|
||||
// 1: alternate (usually PCM)
|
||||
DivInstrumentType chanInsType[DIV_MAX_CHANS][2];
|
||||
EffectProcess effectFunc;
|
||||
EffectProcess postEffectFunc;
|
||||
DivSysDef(
|
||||
const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans,
|
||||
bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound,
|
||||
std::initializer_list<const char*> chNames,
|
||||
std::initializer_list<const char*> chShortNames,
|
||||
std::initializer_list<int> chTypes,
|
||||
std::initializer_list<DivInstrumentType> chInsType1,
|
||||
std::initializer_list<DivInstrumentType> chInsType2={},
|
||||
EffectProcess fxHandler=[](int,unsigned char,unsigned char) -> bool {return false;},
|
||||
EffectProcess postFxHandler=[](int,unsigned char,unsigned char) -> bool {return false;}):
|
||||
name(sysName),
|
||||
nameJ(sysNameJ),
|
||||
id(fileID),
|
||||
id_DMF(fileID_DMF),
|
||||
channels(chans),
|
||||
isFM(isFMChip),
|
||||
isSTD(isSTDChip),
|
||||
isCompound(compound),
|
||||
vgmVersion(vgmVer),
|
||||
effectFunc(fxHandler),
|
||||
postEffectFunc(postFxHandler) {
|
||||
memset(chanNames,0,DIV_MAX_CHANS*sizeof(void*));
|
||||
memset(chanShortNames,0,DIV_MAX_CHANS*sizeof(void*));
|
||||
memset(chanTypes,0,DIV_MAX_CHANS*sizeof(int));
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
chanInsType[i][0]=DIV_INS_NULL;
|
||||
chanInsType[i][1]=DIV_INS_NULL;
|
||||
}
|
||||
|
||||
int index=0;
|
||||
for (const char* i: chNames) {
|
||||
chanNames[index++]=i;
|
||||
if (index>=DIV_MAX_CHANS) break;
|
||||
}
|
||||
|
||||
index=0;
|
||||
for (const char* i: chShortNames) {
|
||||
chanShortNames[index++]=i;
|
||||
if (index>=DIV_MAX_CHANS) break;
|
||||
}
|
||||
|
||||
index=0;
|
||||
for (int i: chTypes) {
|
||||
chanTypes[index++]=i;
|
||||
if (index>=DIV_MAX_CHANS) break;
|
||||
}
|
||||
|
||||
index=0;
|
||||
for (DivInstrumentType i: chInsType1) {
|
||||
chanInsType[index++][0]=i;
|
||||
if (index>=DIV_MAX_CHANS) break;
|
||||
}
|
||||
|
||||
index=0;
|
||||
for (DivInstrumentType i: chInsType2) {
|
||||
chanInsType[index++][1]=i;
|
||||
if (index>=DIV_MAX_CHANS) break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum DivChanTypes {
|
||||
DIV_CH_FM=0,
|
||||
DIV_CH_PULSE=1,
|
||||
DIV_CH_NOISE=2,
|
||||
DIV_CH_WAVE=3,
|
||||
DIV_CH_PCM=4,
|
||||
DIV_CH_OP=5
|
||||
};
|
||||
|
||||
class DivEngine {
|
||||
DivDispatchContainer disCont[32];
|
||||
TAAudio* output;
|
||||
|
@ -194,6 +289,7 @@ class DivEngine {
|
|||
bool repeatPattern;
|
||||
bool metronome;
|
||||
bool exporting;
|
||||
bool stopExport;
|
||||
bool halted;
|
||||
bool forceMono;
|
||||
bool cmdStreamEnabled;
|
||||
|
@ -201,8 +297,11 @@ class DivEngine {
|
|||
bool firstTick;
|
||||
bool skipping;
|
||||
bool midiIsDirect;
|
||||
bool lowLatency;
|
||||
bool systemsRegistered;
|
||||
bool hasLoadedSomething;
|
||||
int softLockCount;
|
||||
int ticks, curRow, curOrder, remainingLoops, nextSpeed;
|
||||
int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed;
|
||||
double divider;
|
||||
int cycles;
|
||||
double clockDrift;
|
||||
|
@ -227,6 +326,10 @@ class DivEngine {
|
|||
std::vector<String> midiIns;
|
||||
std::vector<String> midiOuts;
|
||||
std::vector<DivCommand> cmdStream;
|
||||
std::vector<DivInstrumentType> possibleInsTypes;
|
||||
DivSysDef* sysDefs[256];
|
||||
DivSystem sysFileMapFur[256];
|
||||
DivSystem sysFileMapDMF[256];
|
||||
|
||||
struct SamplePreview {
|
||||
int sample;
|
||||
|
@ -242,6 +345,7 @@ class DivEngine {
|
|||
int reversePitchTable[4096];
|
||||
int pitchTable[4096];
|
||||
int midiBaseChan;
|
||||
size_t midiAgeCounter;
|
||||
|
||||
blip_buffer_t* samp_bb;
|
||||
size_t samp_bbInLen;
|
||||
|
@ -252,6 +356,7 @@ class DivEngine {
|
|||
size_t metroTickLen;
|
||||
float metroFreq, metroPos;
|
||||
float metroAmp;
|
||||
float metroVol;
|
||||
|
||||
size_t totalProcessed;
|
||||
|
||||
|
@ -268,7 +373,7 @@ class DivEngine {
|
|||
void nextRow();
|
||||
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond);
|
||||
// returns true if end of song.
|
||||
bool nextTick(bool noAccum=false);
|
||||
bool nextTick(bool noAccum=false, bool inhibitLowLat=false);
|
||||
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);
|
||||
bool perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal);
|
||||
void recalcChans();
|
||||
|
@ -278,21 +383,33 @@ class DivEngine {
|
|||
bool loadDMF(unsigned char* file, size_t len);
|
||||
bool loadFur(unsigned char* file, size_t len);
|
||||
bool loadMod(unsigned char* file, size_t len);
|
||||
bool loadFTM(unsigned char* file, size_t len);
|
||||
|
||||
void loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadVGI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadOPLI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadY12(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
|
||||
bool initAudioBackend();
|
||||
bool deinitAudioBackend();
|
||||
|
||||
void registerSystems();
|
||||
void initSongWithDesc(const int* description);
|
||||
|
||||
void exchangeIns(int one, int two);
|
||||
void swapChannels(int src, int dest);
|
||||
void stompChannel(int ch);
|
||||
|
||||
public:
|
||||
DivSong song;
|
||||
DivInstrument* tempIns;
|
||||
DivSystem sysOfChan[DIV_MAX_CHANS];
|
||||
int dispatchOfChan[DIV_MAX_CHANS];
|
||||
int dispatchChanOfChan[DIV_MAX_CHANS];
|
||||
|
@ -300,12 +417,18 @@ class DivEngine {
|
|||
float* oscBuf[2];
|
||||
float oscSize;
|
||||
int oscReadPos, oscWritePos;
|
||||
int tickMult;
|
||||
std::atomic<size_t> processTime;
|
||||
|
||||
void runExportThread();
|
||||
void nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size);
|
||||
DivInstrument* getIns(int index);
|
||||
DivInstrument* getIns(int index, DivInstrumentType fallbackType=DIV_INS_FM);
|
||||
DivWavetable* getWave(int index);
|
||||
DivSample* getSample(int index);
|
||||
DivDispatch* getDispatch(int index);
|
||||
// parse system setup description
|
||||
String encodeSysDesc(std::vector<int>& desc);
|
||||
std::vector<int> decodeSysDesc(String desc);
|
||||
// start fresh
|
||||
void createNew(const int* description);
|
||||
// load a file.
|
||||
|
@ -357,8 +480,16 @@ class DivEngine {
|
|||
// calculate base frequency/period
|
||||
double calcBaseFreq(double clock, double divider, int note, bool period);
|
||||
|
||||
// calculate base frequency in f-num/block format
|
||||
unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits);
|
||||
|
||||
// calculate frequency/period
|
||||
int calcFreq(int base, int pitch, bool period=false, int octave=0);
|
||||
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0);
|
||||
|
||||
// convert panning formats
|
||||
int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range);
|
||||
int convertPanSplitToLinearLR(unsigned char left, unsigned char right, int range);
|
||||
unsigned int convertPanLinearToSplit(int val, unsigned char bits, int range);
|
||||
|
||||
// find song loop position
|
||||
void walkSong(int& loopOrder, int& loopRow, int& loopEnd);
|
||||
|
@ -395,8 +526,11 @@ class DivEngine {
|
|||
// get channel count
|
||||
int getTotalChannelCount();
|
||||
|
||||
// get instrument types available for use
|
||||
std::vector<DivInstrumentType>& getPossibleInsTypes();
|
||||
|
||||
// get effect description
|
||||
const char* getEffectDesc(unsigned char effect, int chan);
|
||||
const char* getEffectDesc(unsigned char effect, int chan, bool notNull=false);
|
||||
|
||||
// get channel type
|
||||
// - 0: FM
|
||||
|
@ -410,15 +544,15 @@ class DivEngine {
|
|||
// get preferred instrument type
|
||||
DivInstrumentType getPreferInsType(int ch);
|
||||
|
||||
// get alternate instrument type
|
||||
DivInstrumentType getPreferInsSecondType(int ch);
|
||||
|
||||
// get song system name
|
||||
const char* getSongSystemName();
|
||||
String getSongSystemName(bool isMultiSystemAcceptable=true);
|
||||
|
||||
// get sys name
|
||||
const char* getSystemName(DivSystem sys);
|
||||
|
||||
// get sys chips
|
||||
const char* getSystemChips(DivSystem sys);
|
||||
|
||||
// get japanese system name
|
||||
const char* getSystemNameJ(DivSystem sys);
|
||||
|
||||
|
@ -512,6 +646,9 @@ class DivEngine {
|
|||
// if the returned vector is empty then there was an error.
|
||||
std::vector<DivInstrument*> instrumentFromFile(const char* path);
|
||||
|
||||
// load temporary instrument
|
||||
void loadTempIns(DivInstrument* which);
|
||||
|
||||
// delete instrument
|
||||
void delInstrument(int index);
|
||||
|
||||
|
@ -566,6 +703,7 @@ class DivEngine {
|
|||
|
||||
void autoNoteOn(int chan, int ins, int note, int vol=-1);
|
||||
void autoNoteOff(int chan, int note, int vol=-1);
|
||||
void autoNoteOffAll();
|
||||
|
||||
// go to order
|
||||
void setOrder(unsigned char order);
|
||||
|
@ -588,6 +726,12 @@ class DivEngine {
|
|||
// get register pool
|
||||
unsigned char* getRegisterPool(int sys, int& size, int& depth);
|
||||
|
||||
// get macro interpreter
|
||||
DivMacroInt* getMacroInt(int chan);
|
||||
|
||||
// get osc buffer
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
|
||||
// enable command stream dumping
|
||||
void enableCommandStream(bool enable);
|
||||
|
||||
|
@ -621,6 +765,9 @@ class DivEngine {
|
|||
// set metronome
|
||||
void setMetronome(bool enable);
|
||||
|
||||
// set metronome volume (1.0 = 100%)
|
||||
void setMetronomeVol(float vol);
|
||||
|
||||
// halt now
|
||||
void halt();
|
||||
|
||||
|
@ -642,14 +789,17 @@ class DivEngine {
|
|||
// public render samples
|
||||
void renderSamplesP();
|
||||
|
||||
// public swap channels
|
||||
void swapChannelsP(int src, int dest);
|
||||
|
||||
// change system
|
||||
void changeSystem(int index, DivSystem which);
|
||||
void changeSystem(int index, DivSystem which, bool preserveOrder=true);
|
||||
|
||||
// add system
|
||||
bool addSystem(DivSystem which);
|
||||
|
||||
// remove system
|
||||
bool removeSystem(int index);
|
||||
bool removeSystem(int index, bool preserveOrder=true);
|
||||
|
||||
// write to register on system
|
||||
void poke(int sys, unsigned int addr, unsigned short val);
|
||||
|
@ -697,25 +847,12 @@ class DivEngine {
|
|||
// quit dispatch
|
||||
void quitDispatch();
|
||||
|
||||
// initialize the engine. optionally provide an output file name.
|
||||
// initialize the engine.
|
||||
bool init();
|
||||
|
||||
// terminate the engine.
|
||||
bool quit();
|
||||
|
||||
unsigned char* adpcmAMem;
|
||||
size_t adpcmAMemLen;
|
||||
unsigned char* adpcmBMem;
|
||||
size_t adpcmBMemLen;
|
||||
unsigned char* qsoundMem;
|
||||
size_t qsoundMemLen;
|
||||
unsigned char* qsoundAMem;
|
||||
size_t qsoundAMemLen;
|
||||
unsigned char* dpcmMem;
|
||||
size_t dpcmMemLen;
|
||||
unsigned char* x1_010Mem;
|
||||
size_t x1_010MemLen;
|
||||
|
||||
DivEngine():
|
||||
output(NULL),
|
||||
exportThread(NULL),
|
||||
|
@ -731,6 +868,7 @@ class DivEngine {
|
|||
repeatPattern(false),
|
||||
metronome(false),
|
||||
exporting(false),
|
||||
stopExport(false),
|
||||
halted(false),
|
||||
forceMono(false),
|
||||
cmdStreamEnabled(false),
|
||||
|
@ -738,7 +876,11 @@ class DivEngine {
|
|||
firstTick(false),
|
||||
skipping(false),
|
||||
midiIsDirect(false),
|
||||
lowLatency(false),
|
||||
systemsRegistered(false),
|
||||
hasLoadedSomething(false),
|
||||
softLockCount(0),
|
||||
subticks(0),
|
||||
ticks(0),
|
||||
curRow(0),
|
||||
curOrder(0),
|
||||
|
@ -756,35 +898,50 @@ class DivEngine {
|
|||
totalCmds(0),
|
||||
lastCmds(0),
|
||||
cmdsPerSecond(0),
|
||||
globalPitch(0),
|
||||
extValue(0),
|
||||
speed1(3),
|
||||
speed2(3),
|
||||
view(DIV_STATUS_NOTHING),
|
||||
haltOn(DIV_HALT_NONE),
|
||||
audioEngine(DIV_AUDIO_NULL),
|
||||
exportMode(DIV_EXPORT_MODE_ONE),
|
||||
midiBaseChan(0),
|
||||
midiAgeCounter(0),
|
||||
samp_bb(NULL),
|
||||
samp_bbInLen(0),
|
||||
samp_temp(0),
|
||||
samp_prevSample(0),
|
||||
samp_bbIn(NULL),
|
||||
samp_bbOut(NULL),
|
||||
metroTick(NULL),
|
||||
metroTickLen(0),
|
||||
metroFreq(0),
|
||||
metroPos(0),
|
||||
metroAmp(0.0f),
|
||||
metroVol(1.0f),
|
||||
totalProcessed(0),
|
||||
tempIns(NULL),
|
||||
oscBuf{NULL,NULL},
|
||||
oscSize(1),
|
||||
oscReadPos(0),
|
||||
oscWritePos(0),
|
||||
adpcmAMem(NULL),
|
||||
adpcmAMemLen(0),
|
||||
adpcmBMem(NULL),
|
||||
adpcmBMemLen(0),
|
||||
qsoundMem(NULL),
|
||||
qsoundMemLen(0),
|
||||
qsoundAMem(NULL),
|
||||
qsoundAMemLen(0),
|
||||
dpcmMem(NULL),
|
||||
dpcmMemLen(0) {}
|
||||
tickMult(1),
|
||||
processTime(0) {
|
||||
memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool));
|
||||
memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool));
|
||||
memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int));
|
||||
memset(dispatchOfChan,0,DIV_MAX_CHANS*sizeof(int));
|
||||
memset(sysOfChan,0,DIV_MAX_CHANS*sizeof(int));
|
||||
memset(vibTable,0,64*sizeof(short));
|
||||
memset(reversePitchTable,0,4096*sizeof(int));
|
||||
memset(pitchTable,0,4096*sizeof(int));
|
||||
memset(sysDefs,0,256*sizeof(void*));
|
||||
|
||||
for (int i=0; i<256; i++) {
|
||||
sysFileMapFur[i]=DIV_SYSTEM_NULL;
|
||||
sysFileMapDMF[i]=DIV_SYSTEM_NULL;
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
#include "instrument.h"
|
||||
#include "song.h"
|
||||
#include <zlib.h>
|
||||
#include <fmt/printf.h>
|
||||
|
@ -26,6 +27,7 @@
|
|||
#define DIV_READ_SIZE 131072
|
||||
#define DIV_DMF_MAGIC ".DelekDefleMask."
|
||||
#define DIV_FUR_MAGIC "-Furnace module-"
|
||||
#define DIV_FTM_MAGIC "FamiTracker Module"
|
||||
|
||||
struct InflateBlock {
|
||||
unsigned char* buf;
|
||||
|
@ -42,6 +44,12 @@ struct InflateBlock {
|
|||
}
|
||||
};
|
||||
|
||||
struct NotZlibException {
|
||||
int what;
|
||||
NotZlibException(int w):
|
||||
what(w) {}
|
||||
};
|
||||
|
||||
static double samplePitches[11]={
|
||||
0.1666666666, 0.2, 0.25, 0.333333333, 0.5,
|
||||
1,
|
||||
|
@ -154,6 +162,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
ds.ignoreJumpAtEnd=true;
|
||||
ds.buggyPortaAfterSlide=true;
|
||||
ds.gbInsAffectsEnvelope=true;
|
||||
ds.ignoreDACModeOutsideIntendedChannel=false;
|
||||
ds.e1e2AlsoTakePriority=true;
|
||||
ds.fbPortaPause=true;
|
||||
ds.snDutyReset=true;
|
||||
|
||||
// 1.1 compat flags
|
||||
if (ds.version>24) {
|
||||
|
@ -491,9 +503,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
} else {
|
||||
ins->std.dutyMacro.val[j]=reader.readI();
|
||||
}
|
||||
if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) {
|
||||
/*if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) {
|
||||
ins->std.dutyMacro.val[j]=24;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
if (ins->std.dutyMacro.len>0) {
|
||||
ins->std.dutyMacro.open=true;
|
||||
|
@ -546,6 +558,16 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
ins->c64.bp=reader.readC();
|
||||
ins->c64.lp=reader.readC();
|
||||
ins->c64.ch3off=reader.readC();
|
||||
|
||||
// weird storage
|
||||
if (ins->c64.volIsCutoff) {
|
||||
for (int j=0; j<ins->std.volMacro.len; j++) {
|
||||
ins->std.volMacro.val[j]-=18;
|
||||
}
|
||||
}
|
||||
for (int j=0; j<ins->std.dutyMacro.len; j++) {
|
||||
ins->std.dutyMacro.val[j]-=12;
|
||||
}
|
||||
}
|
||||
|
||||
if (ds.system[0]==DIV_SYSTEM_GB && ds.version>0x11) {
|
||||
|
@ -617,14 +639,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
for (int i=0; i<getChannelCount(ds.system[0]); i++) {
|
||||
DivChannelData& chan=ds.pat[i];
|
||||
if (ds.version<0x0a) {
|
||||
chan.effectRows=1;
|
||||
chan.effectCols=1;
|
||||
} else {
|
||||
chan.effectRows=reader.readC();
|
||||
chan.effectCols=reader.readC();
|
||||
}
|
||||
logD("%d fx rows: %d",i,chan.effectRows);
|
||||
if (chan.effectRows>4 || chan.effectRows<1) {
|
||||
logE("invalid effect row count %d. are you sure everything is ok?",chan.effectRows);
|
||||
lastError="file is corrupt or unreadable at effect rows";
|
||||
logD("%d fx rows: %d",i,chan.effectCols);
|
||||
if (chan.effectCols>4 || chan.effectCols<1) {
|
||||
logE("invalid effect column count %d. are you sure everything is ok?",chan.effectCols);
|
||||
lastError="file is corrupt or unreadable at effect columns";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
@ -674,7 +696,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
pat->data[k][3]=(pat->data[k][3]&3)*5;
|
||||
}
|
||||
}
|
||||
for (int l=0; l<chan.effectRows; l++) {
|
||||
for (int l=0; l<chan.effectCols; l++) {
|
||||
// effect
|
||||
pat->data[k][4+(l<<1)]=reader.readS();
|
||||
pat->data[k][5+(l<<1)]=reader.readS();
|
||||
|
@ -879,12 +901,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
song.unload();
|
||||
song=ds;
|
||||
recalcChans();
|
||||
renderSamples();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
if (active) {
|
||||
initDispatch();
|
||||
syncReset();
|
||||
BUSY_BEGIN;
|
||||
renderSamples();
|
||||
reset();
|
||||
BUSY_END;
|
||||
}
|
||||
} catch (EndOfFileException& e) {
|
||||
logE("premature end of file!");
|
||||
|
@ -978,6 +1002,22 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
if (ds.version<78) {
|
||||
ds.sharedExtStat=false;
|
||||
}
|
||||
if (ds.version<83) {
|
||||
ds.ignoreDACModeOutsideIntendedChannel=true;
|
||||
ds.e1e2AlsoTakePriority=false;
|
||||
}
|
||||
if (ds.version<84) {
|
||||
ds.newSegaPCM=false;
|
||||
}
|
||||
if (ds.version<85) {
|
||||
ds.fbPortaPause=true;
|
||||
}
|
||||
if (ds.version<86) {
|
||||
ds.snDutyReset=true;
|
||||
}
|
||||
if (ds.version<90) {
|
||||
ds.pitchMacroIsLinear=false;
|
||||
}
|
||||
ds.isDMF=false;
|
||||
|
||||
reader.readS(); // reserved
|
||||
|
@ -1068,9 +1108,11 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
return false;
|
||||
}
|
||||
|
||||
logD("systems:");
|
||||
for (int i=0; i<32; i++) {
|
||||
unsigned char sysID=reader.readC();
|
||||
ds.system[i]=systemFromFileFur(sysID);
|
||||
logD("- %d: %.2x (%s)",i,sysID,getSystemName(ds.system[i]));
|
||||
if (sysID!=0 && systemToFileFur(ds.system[i])==0) {
|
||||
logE("unrecognized system ID %.2x",ds.system[i]);
|
||||
lastError=fmt::sprintf("unrecognized system ID %.2x!",ds.system[i]);
|
||||
|
@ -1256,10 +1298,10 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
}
|
||||
|
||||
for (int i=0; i<tchans; i++) {
|
||||
ds.pat[i].effectRows=reader.readC();
|
||||
if (ds.pat[i].effectRows<1 || ds.pat[i].effectRows>8) {
|
||||
logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectRows);
|
||||
lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectRows);
|
||||
ds.pat[i].effectCols=reader.readC();
|
||||
if (ds.pat[i].effectCols<1 || ds.pat[i].effectCols>8) {
|
||||
logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectCols);
|
||||
lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectCols);
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
@ -1274,6 +1316,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
ds.chanCollapse[i]=reader.readC();
|
||||
}
|
||||
|
||||
if (ds.version<92) {
|
||||
for (int i=0; i<tchans; i++) {
|
||||
if (ds.chanCollapse[i]>0) ds.chanCollapse[i]=3;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<tchans; i++) {
|
||||
ds.chanName[i]=reader.readString();
|
||||
}
|
||||
|
@ -1315,7 +1363,34 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
for (int i=0; i<25; i++) {
|
||||
if (ds.version>=83) {
|
||||
ds.ignoreDACModeOutsideIntendedChannel=reader.readC();
|
||||
ds.e1e2AlsoTakePriority=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
reader.readC();
|
||||
}
|
||||
if (ds.version>=84) {
|
||||
ds.newSegaPCM=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
if (ds.version>=85) {
|
||||
ds.fbPortaPause=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
if (ds.version>=86) {
|
||||
ds.snDutyReset=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
if (ds.version>=90) {
|
||||
ds.pitchMacroIsLinear=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
for (int i=0; i<19; i++) {
|
||||
reader.readC();
|
||||
}
|
||||
}
|
||||
|
@ -1508,7 +1583,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
pat->data[j][1]=reader.readS();
|
||||
pat->data[j][2]=reader.readS();
|
||||
pat->data[j][3]=reader.readS();
|
||||
for (int k=0; k<ds.pat[chan].effectRows; k++) {
|
||||
for (int k=0; k<ds.pat[chan].effectCols; k++) {
|
||||
pat->data[j][4+(k<<1)]=reader.readS();
|
||||
pat->data[j][5+(k<<1)]=reader.readS();
|
||||
}
|
||||
|
@ -1531,12 +1606,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
song.unload();
|
||||
song=ds;
|
||||
recalcChans();
|
||||
renderSamples();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
if (active) {
|
||||
initDispatch();
|
||||
syncReset();
|
||||
BUSY_BEGIN;
|
||||
renderSamples();
|
||||
reset();
|
||||
BUSY_END;
|
||||
}
|
||||
} catch (EndOfFileException& e) {
|
||||
logE("premature end of file!");
|
||||
|
@ -1548,8 +1625,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
||||
struct InvalidHeaderException {};
|
||||
bool success=false;
|
||||
|
@ -1585,7 +1660,10 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
logD("couldn't seek to 1080");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
reader.read(magic,4);
|
||||
if (reader.read(magic,4)!=4) {
|
||||
logD("the magic isn't complete");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0 || memcmp(magic,"M&K!",4)==0) {
|
||||
logD("detected a ProTracker module");
|
||||
chCount=4;
|
||||
|
@ -1606,20 +1684,26 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
logD("detected a Fast/TakeTracker module");
|
||||
chCount=((magic[0]-'0')*10)+(magic[1]-'0');
|
||||
} else {
|
||||
// TODO: Soundtracker MOD?
|
||||
insCount=15;
|
||||
throw InvalidHeaderException();
|
||||
logD("possibly a Soundtracker module");
|
||||
chCount=4;
|
||||
}
|
||||
|
||||
// song name
|
||||
reader.seek(0,SEEK_SET);
|
||||
if (!reader.seek(0,SEEK_SET)) {
|
||||
logD("couldn't seek to 0");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
ds.name=reader.readString(20);
|
||||
logI("%s",ds.name);
|
||||
|
||||
// samples
|
||||
logD("reading samples... (%d)",insCount);
|
||||
for (int i=0; i<insCount; i++) {
|
||||
DivSample* sample=new DivSample;
|
||||
sample->depth=8;
|
||||
sample->name=reader.readString(22);
|
||||
logD("%d: %s",i+1,sample->name);
|
||||
int slen=((unsigned short)reader.readS_BE())*2;
|
||||
sampLens[i]=slen;
|
||||
if (slen==2) slen=0;
|
||||
|
@ -1636,7 +1720,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
loopStart=0;
|
||||
loopLen=0;
|
||||
}
|
||||
if(loopLen>=2) {
|
||||
if (loopLen>=2) {
|
||||
if (loopEnd<slen) slen=loopEnd;
|
||||
sample->loopStart=loopStart;
|
||||
}
|
||||
|
@ -1647,7 +1731,21 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
|
||||
// orders
|
||||
ds.ordersLen=ordCount=reader.readC();
|
||||
reader.readC(); // restart position, unused
|
||||
if (ds.ordersLen<1 || ds.ordersLen>127) {
|
||||
logD("invalid order count!");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
unsigned char restartPos=reader.readC(); // restart position, unused
|
||||
logD("restart position byte: %.2x",restartPos);
|
||||
if (insCount==15) {
|
||||
if (restartPos>0x60 && restartPos<0x80) {
|
||||
logD("detected a Soundtracker module");
|
||||
} else {
|
||||
logD("no Soundtracker signature found");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
}
|
||||
|
||||
int patMax=0;
|
||||
for (int i=0; i<128; i++) {
|
||||
unsigned char pat=reader.readC();
|
||||
|
@ -1657,8 +1755,17 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: maybe change if this is a Soundtracker module?
|
||||
reader.seek(1084,SEEK_SET);
|
||||
if (insCount==15) {
|
||||
if (!reader.seek(600,SEEK_SET)) {
|
||||
logD("couldn't seek to 600");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
} else {
|
||||
if (!reader.seek(1084,SEEK_SET)) {
|
||||
logD("couldn't seek to 1084");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
}
|
||||
|
||||
// patterns
|
||||
ds.patLen=64;
|
||||
|
@ -1729,8 +1836,12 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
|
||||
// samples
|
||||
size_t pos=reader.tell();
|
||||
for (int i=0; i<31; i++) {
|
||||
reader.seek(pos,SEEK_SET);
|
||||
logD("reading sample data...");
|
||||
for (int i=0; i<insCount; i++) {
|
||||
if (!reader.seek(pos,SEEK_SET)) {
|
||||
logD("%d: couldn't seek to %d",i,pos);
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
reader.read(ds.sample[i]->data8,ds.sample[i]->samples);
|
||||
pos+=sampLens[i];
|
||||
}
|
||||
|
@ -1821,8 +1932,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
break;
|
||||
case 15: // set speed
|
||||
// TODO: somehow handle VBlank tunes
|
||||
// TODO: klisje is still broken, perhaps because there wasn't tempo set back then?
|
||||
if (fxVal>0x20) {
|
||||
// TODO: i am so sorry
|
||||
if (fxVal>0x20 && ds.name!="klisje paa klisje") {
|
||||
writeFxCol(0xf0,fxVal);
|
||||
} else {
|
||||
writeFxCol(0x09,fxVal);
|
||||
|
@ -1867,7 +1978,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
}
|
||||
}
|
||||
}
|
||||
ds.pat[ch].effectRows=fxCols;
|
||||
ds.pat[ch].effectCols=fxCols;
|
||||
}
|
||||
|
||||
ds.pal=false;
|
||||
|
@ -1884,7 +1995,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
ds.chanShortName[i]=fmt::sprintf("C%d",i+1);
|
||||
}
|
||||
for(int i=chCount; i<ds.systemLen*4; i++) {
|
||||
ds.pat[i].effectRows=1;
|
||||
ds.pat[i].effectCols=1;
|
||||
ds.chanShow[i]=false;
|
||||
}
|
||||
|
||||
|
@ -1904,12 +2015,14 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
song.unload();
|
||||
song=ds;
|
||||
recalcChans();
|
||||
renderSamples();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
if (active) {
|
||||
initDispatch();
|
||||
syncReset();
|
||||
BUSY_BEGIN;
|
||||
renderSamples();
|
||||
reset();
|
||||
BUSY_END;
|
||||
}
|
||||
success=true;
|
||||
} catch (EndOfFileException& e) {
|
||||
|
@ -1922,27 +2035,381 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
return success;
|
||||
}
|
||||
|
||||
#define CHECK_BLOCK_VERSION(x) \
|
||||
if (blockVersion>x) { \
|
||||
logE("incompatible block version %d for %s!",blockVersion,blockName); \
|
||||
lastError="incompatible block version"; \
|
||||
delete[] file; \
|
||||
return false; \
|
||||
}
|
||||
|
||||
bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||
SafeReader reader=SafeReader(file,len);
|
||||
warnings="";
|
||||
try {
|
||||
DivSong ds;
|
||||
String blockName;
|
||||
unsigned char expansions=0;
|
||||
unsigned int tchans=0;
|
||||
unsigned int n163Chans=0;
|
||||
bool hasSequence[256][8];
|
||||
unsigned char sequenceIndex[256][8];
|
||||
|
||||
memset(hasSequence,0,256*8*sizeof(bool));
|
||||
memset(sequenceIndex,0,256*8);
|
||||
|
||||
if (!reader.seek(18,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
ds.version=(unsigned short)reader.readI();
|
||||
logI("module version %d (0x%.4x)",ds.version,ds.version);
|
||||
|
||||
if (ds.version>0x0450) {
|
||||
logE("incompatible version %x!",ds.version);
|
||||
lastError="incompatible version";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
blockName=reader.readString(3);
|
||||
if (blockName=="END") {
|
||||
// end of module
|
||||
logD("end of data");
|
||||
break;
|
||||
}
|
||||
|
||||
// not the end
|
||||
reader.seek(-3,SEEK_CUR);
|
||||
blockName=reader.readString(16);
|
||||
unsigned int blockVersion=(unsigned int)reader.readI();
|
||||
unsigned int blockSize=(unsigned int)reader.readI();
|
||||
size_t blockStart=reader.tell();
|
||||
|
||||
logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize);
|
||||
if (blockName=="PARAMS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
unsigned int oldSpeedTempo=0;
|
||||
if (blockVersion<=1) {
|
||||
oldSpeedTempo=reader.readI();
|
||||
}
|
||||
if (blockVersion>=2) {
|
||||
expansions=reader.readC();
|
||||
}
|
||||
tchans=reader.readI();
|
||||
unsigned int pal=reader.readI();
|
||||
unsigned int customHz=reader.readI();
|
||||
unsigned int newVibrato=0;
|
||||
unsigned int speedSplitPoint=0;
|
||||
if (blockVersion>=3) {
|
||||
newVibrato=reader.readI();
|
||||
}
|
||||
if (blockVersion>=4) {
|
||||
ds.hilightA=reader.readI();
|
||||
ds.hilightB=reader.readI();
|
||||
}
|
||||
if (expansions&8) if (blockVersion>=5) { // N163 channels
|
||||
n163Chans=reader.readI();
|
||||
}
|
||||
if (blockVersion>=6) {
|
||||
speedSplitPoint=reader.readI();
|
||||
}
|
||||
|
||||
logV("old speed/tempo: %d",oldSpeedTempo);
|
||||
logV("expansions: %x",expansions);
|
||||
logV("channels: %d",tchans);
|
||||
logV("PAL: %d",pal);
|
||||
logV("custom Hz: %d",customHz);
|
||||
logV("new vibrato: %d",newVibrato);
|
||||
logV("N163 channels: %d",n163Chans);
|
||||
logV("highlight 1: %d",ds.hilightA);
|
||||
logV("highlight 2: %d",ds.hilightB);
|
||||
logV("split point: %d",speedSplitPoint);
|
||||
|
||||
if (customHz!=0) {
|
||||
ds.hz=customHz;
|
||||
}
|
||||
|
||||
// initialize channels
|
||||
int systemID=0;
|
||||
ds.system[systemID++]=DIV_SYSTEM_NES;
|
||||
if (expansions&1) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_VRC6;
|
||||
}
|
||||
if (expansions&2) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_VRC7;
|
||||
}
|
||||
if (expansions&4) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_FDS;
|
||||
}
|
||||
if (expansions&8) {
|
||||
ds.system[systemID++]=DIV_SYSTEM_MMC5;
|
||||
}
|
||||
if (expansions&16) {
|
||||
ds.system[systemID]=DIV_SYSTEM_N163;
|
||||
ds.systemFlags[systemID++]=n163Chans;
|
||||
}
|
||||
if (expansions&32) {
|
||||
ds.system[systemID]=DIV_SYSTEM_AY8910;
|
||||
ds.systemFlags[systemID++]=38; // Sunsoft 5B
|
||||
}
|
||||
ds.systemLen=systemID;
|
||||
|
||||
unsigned int calcChans=0;
|
||||
for (int i=0; i<ds.systemLen; i++) {
|
||||
calcChans+=getChannelCount(ds.system[i]);
|
||||
}
|
||||
if (calcChans!=tchans) {
|
||||
logE("channel counts do not match! %d != %d",tchans,calcChans);
|
||||
lastError="channel counts do not match";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (tchans>DIV_MAX_CHANS) {
|
||||
tchans=DIV_MAX_CHANS;
|
||||
logW("too many channels!");
|
||||
}
|
||||
} else if (blockName=="INFO") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
ds.name=reader.readString(32);
|
||||
ds.author=reader.readString(32);
|
||||
ds.copyright=reader.readString(32);
|
||||
} else if (blockName=="HEADER") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
unsigned char totalSongs=reader.readC();
|
||||
logV("%d songs:",totalSongs+1);
|
||||
for (int i=0; i<=totalSongs; i++) {
|
||||
String subSongName=reader.readString();
|
||||
logV("- %s",subSongName);
|
||||
}
|
||||
for (unsigned int i=0; i<tchans; i++) {
|
||||
unsigned char chID=reader.readC();
|
||||
logV("for channel ID %d",chID);
|
||||
for (int j=0; j<=totalSongs; j++) {
|
||||
unsigned char effectCols=reader.readC();
|
||||
if (j==0) {
|
||||
ds.pat[i].effectCols=effectCols+1;
|
||||
}
|
||||
logV("- song %d has %d effect columns",j,effectCols);
|
||||
}
|
||||
}
|
||||
} else if (blockName=="INSTRUMENTS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
ds.insLen=reader.readI();
|
||||
if (ds.insLen<0 || ds.insLen>256) {
|
||||
logE("too many instruments/out of range!");
|
||||
lastError="too many instruments/out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
ds.ins.push_back(ins);
|
||||
}
|
||||
|
||||
logV("instruments:");
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
unsigned int insIndex=reader.readI();
|
||||
if (insIndex>=ds.ins.size()) {
|
||||
logE("instrument index %d is out of range!",insIndex);
|
||||
lastError="instrument index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
DivInstrument* ins=ds.ins[insIndex];
|
||||
unsigned char insType=reader.readC();
|
||||
switch (insType) {
|
||||
case 1:
|
||||
ins->type=DIV_INS_STD;
|
||||
break;
|
||||
case 2: // TODO: tell VRC6 and VRC6 saw instruments apart
|
||||
ins->type=DIV_INS_VRC6;
|
||||
break;
|
||||
case 3:
|
||||
ins->type=DIV_INS_OPLL;
|
||||
break;
|
||||
case 4:
|
||||
ins->type=DIV_INS_FDS;
|
||||
break;
|
||||
case 5:
|
||||
ins->type=DIV_INS_N163;
|
||||
break;
|
||||
case 6: // 5B?
|
||||
ins->type=DIV_INS_AY;
|
||||
break;
|
||||
default: {
|
||||
logE("%d: invalid instrument type %d",insIndex,insType);
|
||||
lastError="invalid instrument type";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// instrument data
|
||||
switch (ins->type) {
|
||||
case DIV_INS_STD: {
|
||||
unsigned int totalSeqs=reader.readI();
|
||||
if (totalSeqs>5) {
|
||||
logE("%d: too many sequences!",insIndex);
|
||||
lastError="too many sequences";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int j=0; j<totalSeqs; j++) {
|
||||
hasSequence[insIndex][j]=reader.readC();
|
||||
sequenceIndex[insIndex][j]=reader.readC();
|
||||
}
|
||||
|
||||
const int dpcmNotes=(blockVersion>=2)?96:72;
|
||||
for (int j=0; j<dpcmNotes; j++) {
|
||||
ins->amiga.noteMap[j]=(short)((unsigned char)reader.readC())-1;
|
||||
ins->amiga.noteFreq[j]=(unsigned char)reader.readC();
|
||||
if (blockVersion>=6) {
|
||||
reader.readC(); // DMC value
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_INS_VRC6: {
|
||||
unsigned int totalSeqs=reader.readI();
|
||||
if (totalSeqs>4) {
|
||||
logE("%d: too many sequences!",insIndex);
|
||||
lastError="too many sequences";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int j=0; j<totalSeqs; j++) {
|
||||
hasSequence[insIndex][j]=reader.readC();
|
||||
sequenceIndex[insIndex][j]=reader.readC();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_INS_OPLL: {
|
||||
ins->fm.opllPreset=(unsigned int)reader.readI();
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
case DIV_INS_FDS: {
|
||||
DivWavetable* wave=new DivWavetable;
|
||||
wave->len=64;
|
||||
wave->max=64;
|
||||
for (int j=0; j<64; j++) {
|
||||
wave->data[j]=reader.readC();
|
||||
}
|
||||
ins->std.waveMacro.len=1;
|
||||
ins->std.waveMacro.val[0]=ds.wave.size();
|
||||
for (int j=0; j<32; j++) {
|
||||
ins->fds.modTable[j]=reader.readC()-3;
|
||||
}
|
||||
ins->fds.modSpeed=reader.readI();
|
||||
ins->fds.modDepth=reader.readI();
|
||||
reader.readI(); // this is delay. currently ignored. TODO.
|
||||
ds.wave.push_back(wave);
|
||||
|
||||
ins->std.volMacro.len=reader.readC();
|
||||
ins->std.volMacro.loop=reader.readI();
|
||||
ins->std.volMacro.rel=reader.readI();
|
||||
reader.readI(); // arp mode does not apply here
|
||||
for (int j=0; j<ins->std.volMacro.len; j++) {
|
||||
ins->std.volMacro.val[j]=reader.readC();
|
||||
}
|
||||
|
||||
ins->std.arpMacro.len=reader.readC();
|
||||
ins->std.arpMacro.loop=reader.readI();
|
||||
ins->std.arpMacro.rel=reader.readI();
|
||||
ins->std.arpMacro.mode=reader.readI();
|
||||
for (int j=0; j<ins->std.arpMacro.len; j++) {
|
||||
ins->std.arpMacro.val[j]=reader.readC();
|
||||
}
|
||||
|
||||
ins->std.pitchMacro.len=reader.readC();
|
||||
ins->std.pitchMacro.loop=reader.readI();
|
||||
ins->std.pitchMacro.rel=reader.readI();
|
||||
reader.readI(); // arp mode does not apply here
|
||||
for (int j=0; j<ins->std.pitchMacro.len; j++) {
|
||||
ins->std.pitchMacro.val[j]=reader.readC();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case DIV_INS_N163: {
|
||||
// TODO!
|
||||
break;
|
||||
}
|
||||
// TODO: 5B!
|
||||
default: {
|
||||
logE("%d: what's going on here?",insIndex);
|
||||
lastError="invalid instrument type";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// name
|
||||
ins->name=reader.readString((unsigned int)reader.readI());
|
||||
logV("- %d: %s",insIndex,ins->name);
|
||||
}
|
||||
} else if (blockName=="SEQUENCES") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
} else if (blockName=="FRAMES") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
} else if (blockName=="PATTERNS") {
|
||||
CHECK_BLOCK_VERSION(5);
|
||||
} else if (blockName=="DPCM SAMPLES") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
} else if (blockName=="SEQUENCES_VRC6") {
|
||||
// where are the 5B and FDS sequences?
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
} else if (blockName=="SEQUENCES_N163") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
} else if (blockName=="COMMENTS") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
} else {
|
||||
logE("block %s is unknown!",blockName);
|
||||
lastError="unknown block "+blockName;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((reader.tell()-blockStart)!=blockSize) {
|
||||
logE("block %s is incomplete!",blockName);
|
||||
lastError="incomplete block "+blockName;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (EndOfFileException& e) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
delete[] file;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivEngine::load(unsigned char* f, size_t slen) {
|
||||
unsigned char* file;
|
||||
size_t len;
|
||||
if (slen<16) {
|
||||
if (slen<18) {
|
||||
logE("too small!");
|
||||
lastError="file is too small";
|
||||
delete[] f;
|
||||
return false;
|
||||
}
|
||||
if (memcmp(f,DIV_DMF_MAGIC,16)!=0 && memcmp(f,DIV_FUR_MAGIC,16)!=0) {
|
||||
// try loading as a .mod first before trying to decompress
|
||||
// TODO: move to a different location?
|
||||
logD("loading as .mod...");
|
||||
if (loadMod(f,slen)) {
|
||||
delete[] f;
|
||||
return true;
|
||||
}
|
||||
|
||||
lastError="not a .mod song";
|
||||
logD("loading as zlib...");
|
||||
// try zlib
|
||||
|
||||
if (!systemsRegistered) registerSystems();
|
||||
|
||||
// step 1: try loading as a zlib-compressed file
|
||||
logD("trying zlib...");
|
||||
try {
|
||||
z_stream zl;
|
||||
memset(&zl,0,sizeof(z_stream));
|
||||
|
||||
|
@ -1956,14 +2423,13 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
|
|||
nextErr=inflateInit(&zl);
|
||||
if (nextErr!=Z_OK) {
|
||||
if (zl.msg==NULL) {
|
||||
logE("zlib error: unknown! %d",nextErr);
|
||||
logD("zlib error: unknown! %d",nextErr);
|
||||
} else {
|
||||
logE("zlib error: %s",zl.msg);
|
||||
logD("zlib error: %s",zl.msg);
|
||||
}
|
||||
inflateEnd(&zl);
|
||||
delete[] f;
|
||||
lastError="not a .dmf song";
|
||||
return false;
|
||||
lastError="not a .dmf/.fur song";
|
||||
throw NotZlibException(0);
|
||||
}
|
||||
|
||||
std::vector<InflateBlock*> blocks;
|
||||
|
@ -1975,18 +2441,17 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
|
|||
nextErr=inflate(&zl,Z_SYNC_FLUSH);
|
||||
if (nextErr!=Z_OK && nextErr!=Z_STREAM_END) {
|
||||
if (zl.msg==NULL) {
|
||||
logE("zlib error: unknown error! %d",nextErr);
|
||||
logD("zlib error: unknown error! %d",nextErr);
|
||||
lastError="unknown decompression error";
|
||||
} else {
|
||||
logE("zlib inflate: %s",zl.msg);
|
||||
logD("zlib inflate: %s",zl.msg);
|
||||
lastError=fmt::sprintf("decompression error: %s",zl.msg);
|
||||
}
|
||||
for (InflateBlock* i: blocks) delete i;
|
||||
blocks.clear();
|
||||
delete ib;
|
||||
inflateEnd(&zl);
|
||||
delete[] f;
|
||||
return false;
|
||||
throw NotZlibException(0);
|
||||
}
|
||||
ib->blockSize=ib->len-zl.avail_out;
|
||||
blocks.push_back(ib);
|
||||
|
@ -1997,16 +2462,15 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
|
|||
nextErr=inflateEnd(&zl);
|
||||
if (nextErr!=Z_OK) {
|
||||
if (zl.msg==NULL) {
|
||||
logE("zlib end error: unknown error! %d",nextErr);
|
||||
logD("zlib end error: unknown error! %d",nextErr);
|
||||
lastError="unknown decompression finish error";
|
||||
} else {
|
||||
logE("zlib end: %s",zl.msg);
|
||||
logD("zlib end: %s",zl.msg);
|
||||
lastError=fmt::sprintf("decompression finish error: %s",zl.msg);
|
||||
}
|
||||
for (InflateBlock* i: blocks) delete i;
|
||||
blocks.clear();
|
||||
delete[] f;
|
||||
return false;
|
||||
throw NotZlibException(0);
|
||||
}
|
||||
|
||||
size_t finalSize=0;
|
||||
|
@ -2015,12 +2479,11 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
|
|||
finalSize+=i->blockSize;
|
||||
}
|
||||
if (finalSize<1) {
|
||||
logE("compressed too small!");
|
||||
logD("compressed too small!");
|
||||
lastError="file too small";
|
||||
for (InflateBlock* i: blocks) delete i;
|
||||
blocks.clear();
|
||||
delete[] f;
|
||||
return false;
|
||||
throw NotZlibException(0);
|
||||
}
|
||||
file=new unsigned char[finalSize];
|
||||
for (InflateBlock* i: blocks) {
|
||||
|
@ -2031,16 +2494,28 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
|
|||
blocks.clear();
|
||||
len=finalSize;
|
||||
delete[] f;
|
||||
} else {
|
||||
logD("loading as uncompressed");
|
||||
file=(unsigned char*)f;
|
||||
} catch (NotZlibException& e) {
|
||||
logD("not zlib. loading as raw...");
|
||||
file=f;
|
||||
len=slen;
|
||||
}
|
||||
|
||||
// step 2: try loading as .fur or .dmf
|
||||
if (memcmp(file,DIV_DMF_MAGIC,16)==0) {
|
||||
return loadDMF(file,len);
|
||||
} else if (memcmp(file,DIV_FTM_MAGIC,18)==0) {
|
||||
return loadFTM(file,len);
|
||||
} else if (memcmp(file,DIV_FUR_MAGIC,16)==0) {
|
||||
return loadFur(file,len);
|
||||
}
|
||||
|
||||
// step 3: try loading as .mod
|
||||
if (loadMod(f,slen)) {
|
||||
delete[] f;
|
||||
return true;
|
||||
}
|
||||
|
||||
// step 4: not a valid file
|
||||
logE("not a valid module!");
|
||||
lastError="not a compatible song";
|
||||
delete[] file;
|
||||
|
@ -2188,7 +2663,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
}
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
w->writeC(song.pat[i].effectRows);
|
||||
w->writeC(song.pat[i].effectCols);
|
||||
}
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
|
@ -2219,7 +2694,13 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
w->writeC(song.buggyPortaAfterSlide);
|
||||
w->writeC(song.gbInsAffectsEnvelope);
|
||||
w->writeC(song.sharedExtStat);
|
||||
for (int i=0; i<25; i++) {
|
||||
w->writeC(song.ignoreDACModeOutsideIntendedChannel);
|
||||
w->writeC(song.e1e2AlsoTakePriority);
|
||||
w->writeC(song.newSegaPCM);
|
||||
w->writeC(song.fbPortaPause);
|
||||
w->writeC(song.snDutyReset);
|
||||
w->writeC(song.pitchMacroIsLinear);
|
||||
for (int i=0; i<19; i++) {
|
||||
w->writeC(0);
|
||||
}
|
||||
|
||||
|
@ -2273,7 +2754,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
w->writeS(pat->data[j][1]); // octave
|
||||
w->writeS(pat->data[j][2]); // instrument
|
||||
w->writeS(pat->data[j][3]); // volume
|
||||
w->write(&pat->data[j][4],2*song.pat[i>>16].effectRows*2); // effects
|
||||
w->write(&pat->data[j][4],2*song.pat[i>>16].effectCols*2); // effects
|
||||
}
|
||||
|
||||
w->writeString(pat->name,false);
|
||||
|
@ -2525,7 +3006,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
} else { // STD
|
||||
if (sys!=DIV_SYSTEM_GB) {
|
||||
w->writeC(i->std.volMacro.len);
|
||||
w->write(i->std.volMacro.val,4*i->std.volMacro.len);
|
||||
if ((sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) && i->c64.volIsCutoff) {
|
||||
for (int j=0; j<i->std.volMacro.len; j++) {
|
||||
w->writeI(i->std.volMacro.val[j]+18);
|
||||
}
|
||||
} else {
|
||||
w->write(i->std.volMacro.val,4*i->std.volMacro.len);
|
||||
}
|
||||
if (i->std.volMacro.len>0) {
|
||||
w->writeC(i->std.volMacro.loop);
|
||||
}
|
||||
|
@ -2545,7 +3032,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
w->writeC(i->std.arpMacro.mode);
|
||||
|
||||
w->writeC(i->std.dutyMacro.len);
|
||||
w->write(i->std.dutyMacro.val,4*i->std.dutyMacro.len);
|
||||
if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) {
|
||||
for (int j=0; j<i->std.dutyMacro.len; j++) {
|
||||
w->writeI(i->std.dutyMacro.val[j]+12);
|
||||
}
|
||||
} else {
|
||||
w->write(i->std.dutyMacro.val,4*i->std.dutyMacro.len);
|
||||
}
|
||||
if (i->std.dutyMacro.len>0) {
|
||||
w->writeC(i->std.dutyMacro.loop);
|
||||
}
|
||||
|
@ -2607,7 +3100,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
}
|
||||
|
||||
for (int i=0; i<getChannelCount(sys); i++) {
|
||||
w->writeC(song.pat[i].effectRows);
|
||||
w->writeC(song.pat[i].effectCols);
|
||||
|
||||
for (int j=0; j<song.ordersLen; j++) {
|
||||
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][j],false);
|
||||
|
@ -2615,7 +3108,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
w->writeS(pat->data[k][0]); // note
|
||||
w->writeS(pat->data[k][1]); // octave
|
||||
w->writeS(pat->data[k][3]); // volume
|
||||
w->write(&pat->data[k][4],2*song.pat[i].effectRows*2); // effects
|
||||
w->write(&pat->data[k][4],2*song.pat[i].effectCols*2); // effects
|
||||
w->writeS(pat->data[k][2]); // instrument
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -106,7 +106,9 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
|
||||
// Amiga
|
||||
w->writeS(amiga.initSample);
|
||||
for (int j=0; j<14; j++) { // reserved
|
||||
w->writeC(amiga.useWave);
|
||||
w->writeC(amiga.waveLen);
|
||||
for (int j=0; j<12; j++) { // reserved
|
||||
w->writeC(0);
|
||||
}
|
||||
|
||||
|
@ -236,40 +238,40 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentSTD::OpMacro& op=std.opMacros[i];
|
||||
for (int j=0; j<op.amMacro.len; j++) {
|
||||
w->writeC(op.amMacro.val[j]);
|
||||
w->writeC(op.amMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.arMacro.len; j++) {
|
||||
w->writeC(op.arMacro.val[j]);
|
||||
w->writeC(op.arMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.drMacro.len; j++) {
|
||||
w->writeC(op.drMacro.val[j]);
|
||||
w->writeC(op.drMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.multMacro.len; j++) {
|
||||
w->writeC(op.multMacro.val[j]);
|
||||
w->writeC(op.multMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.rrMacro.len; j++) {
|
||||
w->writeC(op.rrMacro.val[j]);
|
||||
w->writeC(op.rrMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.slMacro.len; j++) {
|
||||
w->writeC(op.slMacro.val[j]);
|
||||
w->writeC(op.slMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.tlMacro.len; j++) {
|
||||
w->writeC(op.tlMacro.val[j]);
|
||||
w->writeC(op.tlMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.dt2Macro.len; j++) {
|
||||
w->writeC(op.dt2Macro.val[j]);
|
||||
w->writeC(op.dt2Macro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.rsMacro.len; j++) {
|
||||
w->writeC(op.rsMacro.val[j]);
|
||||
w->writeC(op.rsMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.dtMacro.len; j++) {
|
||||
w->writeC(op.dtMacro.val[j]);
|
||||
w->writeC(op.dtMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.d2rMacro.len; j++) {
|
||||
w->writeC(op.d2rMacro.val[j]);
|
||||
w->writeC(op.d2rMacro.val[j]&0xff);
|
||||
}
|
||||
for (int j=0; j<op.ssgMacro.len; j++) {
|
||||
w->writeC(op.ssgMacro.val[j]);
|
||||
w->writeC(op.ssgMacro.val[j]&0xff);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,6 +482,44 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
w->writeC(ws.param2);
|
||||
w->writeC(ws.param3);
|
||||
w->writeC(ws.param4);
|
||||
|
||||
// other macro modes
|
||||
w->writeC(std.volMacro.mode);
|
||||
w->writeC(std.dutyMacro.mode);
|
||||
w->writeC(std.waveMacro.mode);
|
||||
w->writeC(std.pitchMacro.mode);
|
||||
w->writeC(std.ex1Macro.mode);
|
||||
w->writeC(std.ex2Macro.mode);
|
||||
w->writeC(std.ex3Macro.mode);
|
||||
w->writeC(std.algMacro.mode);
|
||||
w->writeC(std.fbMacro.mode);
|
||||
w->writeC(std.fmsMacro.mode);
|
||||
w->writeC(std.amsMacro.mode);
|
||||
w->writeC(std.panLMacro.mode);
|
||||
w->writeC(std.panRMacro.mode);
|
||||
w->writeC(std.phaseResetMacro.mode);
|
||||
w->writeC(std.ex4Macro.mode);
|
||||
w->writeC(std.ex5Macro.mode);
|
||||
w->writeC(std.ex6Macro.mode);
|
||||
w->writeC(std.ex7Macro.mode);
|
||||
w->writeC(std.ex8Macro.mode);
|
||||
|
||||
// C64 no test
|
||||
w->writeC(c64.noTest);
|
||||
|
||||
// MultiPCM
|
||||
w->writeC(multipcm.ar);
|
||||
w->writeC(multipcm.d1r);
|
||||
w->writeC(multipcm.dl);
|
||||
w->writeC(multipcm.d2r);
|
||||
w->writeC(multipcm.rr);
|
||||
w->writeC(multipcm.rc);
|
||||
w->writeC(multipcm.lfo);
|
||||
w->writeC(multipcm.vib);
|
||||
w->writeC(multipcm.am);
|
||||
for (int j=0; j<23; j++) { // reserved
|
||||
w->writeC(0);
|
||||
}
|
||||
}
|
||||
|
||||
DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
||||
|
@ -571,8 +611,15 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
|
||||
// Amiga
|
||||
amiga.initSample=reader.readS();
|
||||
if (version>=82) {
|
||||
amiga.useWave=reader.readC();
|
||||
amiga.waveLen=(unsigned char)reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
reader.readC();
|
||||
}
|
||||
// reserved
|
||||
for (int k=0; k<14; k++) reader.readC();
|
||||
for (int k=0; k<12; k++) reader.readC();
|
||||
|
||||
// standard
|
||||
std.volMacro.len=reader.readI();
|
||||
|
@ -609,6 +656,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
std.arpMacro.val[j]-=12;
|
||||
}
|
||||
}
|
||||
if (type==DIV_INS_C64 && version<87) {
|
||||
if (c64.volIsCutoff && !c64.filterIsAbs) for (int j=0; j<std.volMacro.len; j++) {
|
||||
std.volMacro.val[j]-=18;
|
||||
}
|
||||
if (!c64.dutyIsAbs) for (int j=0; j<std.dutyMacro.len; j++) {
|
||||
std.dutyMacro.val[j]-=12;
|
||||
}
|
||||
}
|
||||
if (version>=17) {
|
||||
reader.read(std.pitchMacro.val,4*std.pitchMacro.len);
|
||||
reader.read(std.ex1Macro.val,4*std.ex1Macro.len);
|
||||
|
@ -696,20 +751,45 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
op.ssgMacro.open=reader.readC();
|
||||
}
|
||||
|
||||
// FM macro low 8 bits
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentSTD::OpMacro& op=std.opMacros[i];
|
||||
reader.read(op.amMacro.val,op.amMacro.len);
|
||||
reader.read(op.arMacro.val,op.arMacro.len);
|
||||
reader.read(op.drMacro.val,op.drMacro.len);
|
||||
reader.read(op.multMacro.val,op.multMacro.len);
|
||||
reader.read(op.rrMacro.val,op.rrMacro.len);
|
||||
reader.read(op.slMacro.val,op.slMacro.len);
|
||||
reader.read(op.tlMacro.val,op.tlMacro.len);
|
||||
reader.read(op.dt2Macro.val,op.dt2Macro.len);
|
||||
reader.read(op.rsMacro.val,op.rsMacro.len);
|
||||
reader.read(op.dtMacro.val,op.dtMacro.len);
|
||||
reader.read(op.d2rMacro.val,op.d2rMacro.len);
|
||||
reader.read(op.ssgMacro.val,op.ssgMacro.len);
|
||||
for (int j=0; j<op.amMacro.len; j++) {
|
||||
op.amMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.arMacro.len; j++) {
|
||||
op.arMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.drMacro.len; j++) {
|
||||
op.drMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.multMacro.len; j++) {
|
||||
op.multMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.rrMacro.len; j++) {
|
||||
op.rrMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.slMacro.len; j++) {
|
||||
op.slMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.tlMacro.len; j++) {
|
||||
op.tlMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.dt2Macro.len; j++) {
|
||||
op.dt2Macro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.rsMacro.len; j++) {
|
||||
op.rsMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.dtMacro.len; j++) {
|
||||
op.dtMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.d2rMacro.len; j++) {
|
||||
op.d2rMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
for (int j=0; j<op.ssgMacro.len; j++) {
|
||||
op.ssgMacro.val[j]=(unsigned char)reader.readC();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -842,8 +922,8 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
reader.readC(); // reserved
|
||||
}
|
||||
|
||||
// more macros
|
||||
if (version>=76) {
|
||||
// more macros
|
||||
std.panLMacro.len=reader.readI();
|
||||
std.panRMacro.len=reader.readI();
|
||||
std.phaseResetMacro.len=reader.readI();
|
||||
|
@ -888,10 +968,8 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
reader.read(std.ex6Macro.val,4*std.ex6Macro.len);
|
||||
reader.read(std.ex7Macro.val,4*std.ex7Macro.len);
|
||||
reader.read(std.ex8Macro.val,4*std.ex8Macro.len);
|
||||
}
|
||||
|
||||
// FDS
|
||||
if (version>=76) {
|
||||
// FDS
|
||||
fds.modSpeed=reader.readI();
|
||||
fds.modDepth=reader.readI();
|
||||
fds.initModTableWithFirstWave=reader.readC();
|
||||
|
@ -921,6 +999,50 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
ws.param3=reader.readC();
|
||||
ws.param4=reader.readC();
|
||||
}
|
||||
|
||||
// other macro modes
|
||||
if (version>=84) {
|
||||
std.volMacro.mode=reader.readC();
|
||||
std.dutyMacro.mode=reader.readC();
|
||||
std.waveMacro.mode=reader.readC();
|
||||
std.pitchMacro.mode=reader.readC();
|
||||
std.ex1Macro.mode=reader.readC();
|
||||
std.ex2Macro.mode=reader.readC();
|
||||
std.ex3Macro.mode=reader.readC();
|
||||
std.algMacro.mode=reader.readC();
|
||||
std.fbMacro.mode=reader.readC();
|
||||
std.fmsMacro.mode=reader.readC();
|
||||
std.amsMacro.mode=reader.readC();
|
||||
std.panLMacro.mode=reader.readC();
|
||||
std.panRMacro.mode=reader.readC();
|
||||
std.phaseResetMacro.mode=reader.readC();
|
||||
std.ex4Macro.mode=reader.readC();
|
||||
std.ex5Macro.mode=reader.readC();
|
||||
std.ex6Macro.mode=reader.readC();
|
||||
std.ex7Macro.mode=reader.readC();
|
||||
std.ex8Macro.mode=reader.readC();
|
||||
}
|
||||
|
||||
// C64 no test
|
||||
if (version>=89) {
|
||||
c64.noTest=reader.readC();
|
||||
}
|
||||
|
||||
// MultiPCM
|
||||
if (version>=93) {
|
||||
multipcm.ar=reader.readC();
|
||||
multipcm.d1r=reader.readC();
|
||||
multipcm.dl=reader.readC();
|
||||
multipcm.d2r=reader.readC();
|
||||
multipcm.rr=reader.readC();
|
||||
multipcm.rc=reader.readC();
|
||||
multipcm.lfo=reader.readC();
|
||||
multipcm.vib=reader.readC();
|
||||
multipcm.am=reader.readC();
|
||||
// reserved
|
||||
for (int k=0; k<23; k++) reader.readC();
|
||||
}
|
||||
|
||||
return DIV_DATA_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,12 @@ enum DivInstrumentType: unsigned short {
|
|||
DIV_INS_VERA=24,
|
||||
DIV_INS_X1_010=25,
|
||||
DIV_INS_VRC6_SAW=26,
|
||||
DIV_INS_ES5506=27,
|
||||
DIV_INS_MULTIPCM=28,
|
||||
DIV_INS_SNES=29,
|
||||
DIV_INS_SU=30,
|
||||
DIV_INS_MAX,
|
||||
DIV_INS_NULL
|
||||
};
|
||||
|
||||
// FM operator structure:
|
||||
|
@ -161,7 +166,7 @@ struct DivInstrumentMacro {
|
|||
unsigned char len;
|
||||
signed char loop;
|
||||
signed char rel;
|
||||
DivInstrumentMacro(String n, bool initOpen=false):
|
||||
explicit DivInstrumentMacro(const String& n, bool initOpen=false):
|
||||
name(n),
|
||||
mode(0),
|
||||
open(initOpen),
|
||||
|
@ -260,7 +265,7 @@ struct DivInstrumentC64 {
|
|||
unsigned char a, d, s, r;
|
||||
unsigned short duty;
|
||||
unsigned char ringMod, oscSync;
|
||||
bool toFilter, volIsCutoff, initFilter, dutyIsAbs, filterIsAbs;
|
||||
bool toFilter, volIsCutoff, initFilter, dutyIsAbs, filterIsAbs, noTest;
|
||||
unsigned char res;
|
||||
unsigned short cut;
|
||||
bool hp, lp, bp, ch3off;
|
||||
|
@ -282,6 +287,7 @@ struct DivInstrumentC64 {
|
|||
initFilter(false),
|
||||
dutyIsAbs(false),
|
||||
filterIsAbs(false),
|
||||
noTest(false),
|
||||
res(0),
|
||||
cut(0),
|
||||
hp(false),
|
||||
|
@ -293,12 +299,16 @@ struct DivInstrumentC64 {
|
|||
struct DivInstrumentAmiga {
|
||||
short initSample;
|
||||
bool useNoteMap;
|
||||
bool useWave;
|
||||
unsigned char waveLen;
|
||||
int noteFreq[120];
|
||||
short noteMap[120];
|
||||
|
||||
DivInstrumentAmiga():
|
||||
initSample(0),
|
||||
useNoteMap(false) {
|
||||
useNoteMap(false),
|
||||
useWave(false),
|
||||
waveLen(31) {
|
||||
memset(noteMap,-1,120*sizeof(short));
|
||||
memset(noteFreq,0,120*sizeof(int));
|
||||
}
|
||||
|
@ -328,6 +338,16 @@ struct DivInstrumentFDS {
|
|||
}
|
||||
};
|
||||
|
||||
struct DivInstrumentMultiPCM {
|
||||
unsigned char ar, d1r, dl, d2r, rr, rc;
|
||||
unsigned char lfo, vib, am;
|
||||
|
||||
DivInstrumentMultiPCM():
|
||||
ar(15), d1r(15), dl(0), d2r(0), rr(15), rc(15),
|
||||
lfo(0), vib(0), am(0) {
|
||||
}
|
||||
};
|
||||
|
||||
enum DivWaveSynthEffects {
|
||||
DIV_WS_NONE=0,
|
||||
// one waveform effects
|
||||
|
@ -383,6 +403,7 @@ struct DivInstrument {
|
|||
DivInstrumentAmiga amiga;
|
||||
DivInstrumentN163 n163;
|
||||
DivInstrumentFDS fds;
|
||||
DivInstrumentMultiPCM multipcm;
|
||||
DivInstrumentWaveSynth ws;
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,15 +19,21 @@
|
|||
|
||||
#include "macroInt.h"
|
||||
#include "instrument.h"
|
||||
#include "engine.h"
|
||||
|
||||
void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released) {
|
||||
void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tick) {
|
||||
if (!tick) {
|
||||
had=false;
|
||||
return;
|
||||
}
|
||||
if (finished) {
|
||||
finished=false;
|
||||
}
|
||||
if (had!=has) {
|
||||
if (actualHad!=has) {
|
||||
finished=true;
|
||||
}
|
||||
had=has;
|
||||
actualHad=has;
|
||||
had=actualHad;
|
||||
if (has) {
|
||||
val=source.val[pos++];
|
||||
if (source.rel>=0 && pos>source.rel && !released) {
|
||||
|
@ -51,9 +57,17 @@ void DivMacroInt::next() {
|
|||
if (ins==NULL) return;
|
||||
// run macros
|
||||
// TODO: potentially get rid of list to avoid allocations
|
||||
subTick--;
|
||||
for (size_t i=0; i<macroListLen; i++) {
|
||||
if (macroList[i]!=NULL && macroSource[i]!=NULL) {
|
||||
macroList[i]->doMacro(*macroSource[i],released);
|
||||
macroList[i]->doMacro(*macroSource[i],released,subTick==0);
|
||||
}
|
||||
}
|
||||
if (subTick<=0) {
|
||||
if (e==NULL) {
|
||||
subTick=1;
|
||||
} else {
|
||||
subTick=e->tickMult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +76,10 @@ void DivMacroInt::release() {
|
|||
released=true;
|
||||
}
|
||||
|
||||
void DivMacroInt::setEngine(DivEngine* eng) {
|
||||
e=eng;
|
||||
}
|
||||
|
||||
#define ADD_MACRO(m,s) \
|
||||
macroList[macroListLen]=&m; \
|
||||
macroSource[macroListLen++]=&s;
|
||||
|
@ -73,6 +91,7 @@ void DivMacroInt::init(DivInstrument* which) {
|
|||
if (macroList[i]!=NULL) macroList[i]->init();
|
||||
}
|
||||
macroListLen=0;
|
||||
subTick=1;
|
||||
|
||||
released=false;
|
||||
|
||||
|
|
|
@ -22,18 +22,22 @@
|
|||
|
||||
#include "instrument.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
struct DivMacroStruct {
|
||||
int pos;
|
||||
int val;
|
||||
bool has, had, finished, will;
|
||||
bool has, had, actualHad, finished, will;
|
||||
unsigned int mode;
|
||||
void doMacro(DivInstrumentMacro& source, bool released);
|
||||
void doMacro(DivInstrumentMacro& source, bool released, bool tick);
|
||||
void init() {
|
||||
pos=mode=0;
|
||||
has=had=will=false;
|
||||
has=had=actualHad=will=false;
|
||||
// TODO: test whether this breaks anything?
|
||||
val=0;
|
||||
}
|
||||
void prepare(DivInstrumentMacro& source) {
|
||||
has=had=will=true;
|
||||
has=had=actualHad=will=true;
|
||||
mode=source.mode;
|
||||
}
|
||||
DivMacroStruct():
|
||||
|
@ -41,16 +45,19 @@ struct DivMacroStruct {
|
|||
val(0),
|
||||
has(false),
|
||||
had(false),
|
||||
actualHad(false),
|
||||
finished(false),
|
||||
will(false),
|
||||
mode(0) {}
|
||||
};
|
||||
|
||||
class DivMacroInt {
|
||||
DivEngine* e;
|
||||
DivInstrument* ins;
|
||||
DivMacroStruct* macroList[128];
|
||||
DivInstrumentMacro* macroSource[128];
|
||||
size_t macroListLen;
|
||||
int subTick;
|
||||
bool released;
|
||||
public:
|
||||
// common macro
|
||||
|
@ -100,6 +107,12 @@ class DivMacroInt {
|
|||
*/
|
||||
void next();
|
||||
|
||||
/**
|
||||
* set the engine.
|
||||
* @param the engine
|
||||
*/
|
||||
void setEngine(DivEngine* eng);
|
||||
|
||||
/**
|
||||
* initialize the macro interpreter.
|
||||
* @param which an instrument, or NULL.
|
||||
|
@ -113,8 +126,10 @@ class DivMacroInt {
|
|||
void notifyInsDeletion(DivInstrument* which);
|
||||
|
||||
DivMacroInt():
|
||||
e(NULL),
|
||||
ins(NULL),
|
||||
macroListLen(0),
|
||||
subTick(1),
|
||||
released(false),
|
||||
vol(),
|
||||
arp(),
|
||||
|
|
|
@ -130,6 +130,6 @@ SafeReader* DivPattern::compile(int len, int fxRows) {
|
|||
}
|
||||
|
||||
DivChannelData::DivChannelData():
|
||||
effectRows(1) {
|
||||
effectCols(1) {
|
||||
memset(data,0,256*sizeof(void*));
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ struct DivPattern {
|
|||
};
|
||||
|
||||
struct DivChannelData {
|
||||
unsigned char effectRows;
|
||||
unsigned char effectCols;
|
||||
// data goes as follows: data[ROW][TYPE]
|
||||
// TYPE is:
|
||||
// 0: note
|
||||
|
|
|
@ -22,13 +22,21 @@
|
|||
void DivDispatch::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
}
|
||||
|
||||
void DivDispatch::tick() {
|
||||
void DivDispatch::tick(bool sysTick) {
|
||||
}
|
||||
|
||||
void* DivDispatch::getChanState(int chan) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DivMacroInt* DivDispatch::getChanMacroInt(int chan) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivDispatch::getOscBuffer(int chan) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned char* DivDispatch::getRegisterPool() {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -133,6 +141,22 @@ const char** DivDispatch::getRegisterSheet() {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
const void* DivDispatch::getSampleMem(int index) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t DivDispatch::getSampleMemCapacity(int index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t DivDispatch::getSampleMemUsage(int index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DivDispatch::renderSamples() {
|
||||
|
||||
}
|
||||
|
||||
int DivDispatch::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -79,35 +79,51 @@ const char* DivPlatformAmiga::getEffectName(unsigned char effect) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#define writeAudDat(x) \
|
||||
chan[i].audDat=x; \
|
||||
if (i<3 && chan[i].useV) { \
|
||||
chan[i+1].outVol=(unsigned char)chan[i].audDat^0x80; \
|
||||
if (chan[i+1].outVol>64) chan[i+1].outVol=64; \
|
||||
} \
|
||||
if (i<3 && chan[i].useP) { \
|
||||
chan[i+1].freq=(unsigned char)chan[i].audDat^0x80; \
|
||||
if (chan[i+1].freq<AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER; \
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int outL, outR;
|
||||
static int outL, outR, output;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
outL=0;
|
||||
outR=0;
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
if (!chan[i].active) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
continue;
|
||||
}
|
||||
if (chan[i].useWave || (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen)) {
|
||||
chan[i].audSub-=AMIGA_DIVIDER;
|
||||
if (chan[i].audSub<0) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
if (s->samples>0) {
|
||||
chan[i].audDat=s->data8[chan[i].audPos++];
|
||||
if (i<3 && chan[i].useV) {
|
||||
chan[i+1].outVol=(unsigned char)chan[i].audDat^0x80;
|
||||
if (chan[i+1].outVol>64) chan[i+1].outVol=64;
|
||||
}
|
||||
if (i<3 && chan[i].useP) {
|
||||
chan[i+1].freq=(unsigned char)chan[i].audDat^0x80;
|
||||
if (chan[i+1].freq<AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER;
|
||||
}
|
||||
if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].audPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
if (chan[i].useWave) {
|
||||
writeAudDat(chan[i].ws.output[chan[i].audPos++]^0x80);
|
||||
if (chan[i].audPos>=(unsigned int)(chan[i].audLen<<1)) {
|
||||
chan[i].audPos=0;
|
||||
}
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
if (s->samples>0) {
|
||||
if (chan[i].audPos<s->samples) {
|
||||
writeAudDat(s->data8[chan[i].audPos++]);
|
||||
}
|
||||
if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].audPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
}
|
||||
/*if (chan[i].freq<124) {
|
||||
if (++chan[i].busClock>=512) {
|
||||
|
@ -126,13 +142,17 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
|
|||
}
|
||||
}
|
||||
if (!isMuted[i]) {
|
||||
output=chan[i].audDat*chan[i].outVol;
|
||||
if (i==0 || i==3) {
|
||||
outL+=((chan[i].audDat*chan[i].outVol)*sep1)>>7;
|
||||
outR+=((chan[i].audDat*chan[i].outVol)*sep2)>>7;
|
||||
outL+=(output*sep1)>>7;
|
||||
outR+=(output*sep2)>>7;
|
||||
} else {
|
||||
outL+=((chan[i].audDat*chan[i].outVol)*sep2)>>7;
|
||||
outR+=((chan[i].audDat*chan[i].outVol)*sep1)>>7;
|
||||
outL+=(output*sep2)>>7;
|
||||
outR+=(output*sep1)>>7;
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=output<<2;
|
||||
} else {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
}
|
||||
}
|
||||
filter[0][0]+=(filtConst*(outL-filter[0][0]))>>12;
|
||||
|
@ -144,14 +164,14 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::tick() {
|
||||
void DivPlatformAmiga::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=((chan[i].vol%65)*MIN(64,chan[i].std.vol.val))>>6;
|
||||
}
|
||||
double off=1.0;
|
||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
if (!chan[i].useWave && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
|
@ -174,21 +194,36 @@ void DivPlatformAmiga::tick() {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val) {
|
||||
if (chan[i].useWave && chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
chan[i].wave=chan[i].std.wave.val;
|
||||
chan[i].ws.changeWave1(chan[i].wave);
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].useWave && chan[i].active) {
|
||||
chan[i].ws.tick();
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].audPos=0;
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
if (chan[i].note>0x5d) chan[i].freq=0x01;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].keyOn) {
|
||||
if (chan[i].wave<0) {
|
||||
chan[i].wave=0;
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
}
|
||||
|
@ -202,21 +237,34 @@ void DivPlatformAmiga::tick() {
|
|||
int DivPlatformAmiga::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=8363.0/(double)s->centerRate;
|
||||
if (ins->amiga.useWave) {
|
||||
chan[c.chan].useWave=true;
|
||||
chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1;
|
||||
if (chan[c.chan].insChanged) {
|
||||
if (chan[c.chan].wave<0) {
|
||||
chan[c.chan].wave=0;
|
||||
chan[c.chan].ws.setWidth(chan[c.chan].audLen<<1);
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
chan[c.chan].useWave=false;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=8363.0/(double)s->centerRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value));
|
||||
}
|
||||
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].sample=-1;
|
||||
}
|
||||
if (chan[c.chan].setPos) {
|
||||
|
@ -231,14 +279,18 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (chan[c.chan].useWave) {
|
||||
chan[c.chan].ws.init(ins,chan[c.chan].audLen<<1,255,chan[c.chan].insChanged);
|
||||
}
|
||||
chan[c.chan].insChanged=false;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].sample=-1;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -247,6 +299,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
chan[c.chan].insChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
|
@ -268,14 +321,16 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
if (!chan[c.chan].useWave) break;
|
||||
chan[c.chan].wave=c.value;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
|
@ -307,7 +362,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
|
@ -315,18 +370,19 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
off=8363.0/(double)s->centerRate;
|
||||
}
|
||||
}
|
||||
chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0))));
|
||||
chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
if (chan[c.chan].useWave) break;
|
||||
chan[c.chan].audPos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
break;
|
||||
|
@ -370,9 +426,16 @@ void* DivPlatformAmiga::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformAmiga::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::reset() {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i]=DivPlatformAmiga::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].ws.setEngine(parent);
|
||||
chan[i].ws.init(NULL,32,255);
|
||||
filter[0][i]=0;
|
||||
filter[1][i]=0;
|
||||
}
|
||||
|
@ -397,7 +460,11 @@ void DivPlatformAmiga::notifyInsChange(int ins) {
|
|||
}
|
||||
|
||||
void DivPlatformAmiga::notifyWaveChange(int wave) {
|
||||
// TODO when wavetables are added
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].useWave && chan[i].wave==wave) {
|
||||
chan[i].ws.changeWave1(wave);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::notifyInsDeletion(void* ins) {
|
||||
|
@ -413,6 +480,9 @@ void DivPlatformAmiga::setFlags(unsigned int flags) {
|
|||
chipClock=COLOR_NTSC;
|
||||
}
|
||||
rate=chipClock/AMIGA_DIVIDER;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
sep1=((flags>>8)&127)+127;
|
||||
sep2=127-((flags>>8)&127);
|
||||
amigaModel=flags&2;
|
||||
|
@ -431,6 +501,7 @@ int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, unsigned int
|
|||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
isMuted[i]=false;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
@ -439,4 +510,7 @@ int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, unsigned int
|
|||
}
|
||||
|
||||
void DivPlatformAmiga::quit() {
|
||||
for (int i=0; i<4; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,33 +23,40 @@
|
|||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
|
||||
class DivPlatformAmiga: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch;
|
||||
int freq, baseFreq, pitch, pitch2;
|
||||
unsigned int audLoc;
|
||||
unsigned short audLen;
|
||||
unsigned int audPos;
|
||||
int audSub;
|
||||
signed char audDat;
|
||||
int sample, wave;
|
||||
unsigned char ins;
|
||||
int ins;
|
||||
int busClock;
|
||||
int note;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, useV, useP;
|
||||
signed char vol, outVol;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
audLoc(0),
|
||||
audLen(0),
|
||||
audPos(0),
|
||||
audSub(0),
|
||||
audDat(0),
|
||||
sample(-1),
|
||||
wave(0),
|
||||
wave(-1),
|
||||
ins(-1),
|
||||
busClock(0),
|
||||
note(0),
|
||||
|
@ -67,6 +74,7 @@ class DivPlatformAmiga: public DivDispatch {
|
|||
outVol(64) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
bool bypassLimits;
|
||||
bool amigaModel;
|
||||
|
@ -84,9 +92,10 @@ class DivPlatformAmiga: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
|
|
|
@ -135,6 +135,54 @@ const char* DivPlatformArcade::getEffectName(unsigned char effect) {
|
|||
case 0x30:
|
||||
return "30xx: Toggle hard envelope reset on new notes";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x59:
|
||||
return "59xx: Set decay of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5a:
|
||||
return "5Axx: Set decay of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxx: Set decay 2 of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x5c:
|
||||
return "5Cxx: Set decay 2 of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x5d:
|
||||
return "5Dxx: Set decay 2 of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x5e:
|
||||
return "5Exx: Set decay 2 of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5f:
|
||||
return "5Fxx: Set decay 2 of operator 4 (0 to 1F)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -143,23 +191,29 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
|
|||
static int o[2];
|
||||
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
if (!writes.empty() && !fm.write_busy) {
|
||||
QueuedWrite& w=writes.front();
|
||||
if (w.addrOrVal) {
|
||||
OPM_Write(&fm,1,w.val);
|
||||
regPool[w.addr&0xff]=w.val;
|
||||
//printf("write: %x = %.2x\n",w.addr,w.val);
|
||||
writes.pop();
|
||||
} else {
|
||||
OPM_Write(&fm,0,w.addr);
|
||||
w.addrOrVal=true;
|
||||
for (int i=0; i<8; i++) {
|
||||
if (!writes.empty() && !fm.write_busy) {
|
||||
QueuedWrite& w=writes.front();
|
||||
if (w.addrOrVal) {
|
||||
OPM_Write(&fm,1,w.val);
|
||||
regPool[w.addr&0xff]=w.val;
|
||||
//printf("write: %x = %.2x\n",w.addr,w.val);
|
||||
writes.pop();
|
||||
} else {
|
||||
OPM_Write(&fm,0,w.addr);
|
||||
w.addrOrVal=true;
|
||||
}
|
||||
}
|
||||
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,o,NULL,NULL,NULL);
|
||||
}
|
||||
|
||||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i];
|
||||
}
|
||||
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,o,NULL,NULL,NULL);
|
||||
|
||||
if (o[0]<-32768) o[0]=-32768;
|
||||
if (o[0]>32767) o[0]=32767;
|
||||
|
@ -175,6 +229,8 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
|
|||
void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int os[2];
|
||||
|
||||
ymfm::ym2151::fm_engine* fme=fm_ymfm->debug_engine();
|
||||
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
os[0]=0; os[1]=0;
|
||||
if (!writes.empty()) {
|
||||
|
@ -190,6 +246,10 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
|
|||
|
||||
fm_ymfm->generate(&out_ymfm);
|
||||
|
||||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1));
|
||||
}
|
||||
|
||||
os[0]=out_ymfm.data[0];
|
||||
if (os[0]<-32768) os[0]=-32768;
|
||||
if (os[0]>32767) os[0]=32767;
|
||||
|
@ -219,7 +279,7 @@ inline int hScale(int note) {
|
|||
return ((note/12)<<4)+(noteMap[note%12]);
|
||||
}
|
||||
|
||||
void DivPlatformArcade::tick() {
|
||||
void DivPlatformArcade::tick(bool sysTick) {
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i].std.next();
|
||||
|
||||
|
@ -264,6 +324,32 @@ void DivPlatformArcade::tick() {
|
|||
rWrite(0x1b,chan[i].std.wave.val&3);
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].chVolL=(chan[i].std.panL.val&2)>>1;
|
||||
chan[i].chVolR=chan[i].std.panL.val&1;
|
||||
if (isMuted[i]) {
|
||||
rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||
} else {
|
||||
rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|((chan[i].chVolL&1)<<6)|((chan[i].chVolR&1)<<7));
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.ex1.had) {
|
||||
amDepth=chan[i].std.ex1.val;
|
||||
immWrite(0x19,amDepth);
|
||||
|
@ -400,7 +486,7 @@ void DivPlatformArcade::tick() {
|
|||
|
||||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64;
|
||||
chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64+chan[i].pitch2;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>=(95<<6)) chan[i].freq=(95<<6)-1;
|
||||
immWrite(i+0x28,hScale(chan[i].freq>>6));
|
||||
|
@ -426,13 +512,13 @@ void DivPlatformArcade::muteChannel(int ch, bool mute) {
|
|||
int DivPlatformArcade::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
|
||||
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].state=ins->fm;
|
||||
}
|
||||
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (!chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
|
@ -517,8 +603,8 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
chan[c.chan].ins=c.value;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
chan[c.chan].chVolL=((c.value>>4)>0);
|
||||
chan[c.chan].chVolR=((c.value&15)>0);
|
||||
chan[c.chan].chVolL=(c.value>0);
|
||||
chan[c.chan].chVolR=(c.value2>0);
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
|
||||
} else {
|
||||
|
@ -612,6 +698,134 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RS: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AM: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SL: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DT2: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.dt2=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.dt2=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_D2R: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DT: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.dt=c.value&7;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.dt=c.value2&7;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AM_DEPTH: {
|
||||
amDepth=c.value;
|
||||
immWrite(0x19,amDepth);
|
||||
|
@ -699,6 +913,10 @@ void* DivPlatformArcade::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformArcade::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformArcade::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -729,6 +947,7 @@ void DivPlatformArcade::reset() {
|
|||
}
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i]=DivPlatformArcade::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].vol=0x7f;
|
||||
chan[i].outVol=0x7f;
|
||||
}
|
||||
|
@ -765,10 +984,9 @@ void DivPlatformArcade::setFlags(unsigned int flags) {
|
|||
chipClock=COLOR_NTSC;
|
||||
baseFreqOff=0;
|
||||
}
|
||||
if (useYMFM) {
|
||||
rate=chipClock/64;
|
||||
} else {
|
||||
rate=chipClock/8;
|
||||
rate=chipClock/64;
|
||||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -786,6 +1004,7 @@ int DivPlatformArcade::init(DivEngine* p, int channels, int sugRate, unsigned in
|
|||
skipRegisterWrites=false;
|
||||
for (int i=0; i<8; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
if (useYMFM) fm_ymfm=new ymfm::ym2151(iface);
|
||||
|
@ -795,6 +1014,9 @@ int DivPlatformArcade::init(DivEngine* p, int channels, int sugRate, unsigned in
|
|||
}
|
||||
|
||||
void DivPlatformArcade::quit() {
|
||||
for (int i=0; i<8; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
if (useYMFM) {
|
||||
delete fm_ymfm;
|
||||
}
|
||||
|
|
|
@ -36,18 +36,23 @@ class DivPlatformArcade: public DivDispatch {
|
|||
DivInstrumentFM state;
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset;
|
||||
int vol, outVol;
|
||||
unsigned char chVolL, chVolR;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freqH(0),
|
||||
freqL(0),
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
active(false),
|
||||
|
@ -65,6 +70,7 @@ class DivPlatformArcade: public DivDispatch {
|
|||
chVolR(127) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
|
@ -103,11 +109,12 @@ class DivPlatformArcade: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void notifyInsChange(int ins);
|
||||
void setFlags(unsigned int flags);
|
||||
|
|
|
@ -146,6 +146,12 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l
|
|||
bufR[i+start]=bufL[i+start];
|
||||
}
|
||||
}
|
||||
|
||||
for (int ch=0; ch<3; ch++) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=ayBuf[ch][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::updateOutSel(bool immediate) {
|
||||
|
@ -172,7 +178,7 @@ void DivPlatformAY8910::updateOutSel(bool immediate) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::tick() {
|
||||
void DivPlatformAY8910::tick(bool sysTick) {
|
||||
// PSG
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].std.next();
|
||||
|
@ -215,6 +221,21 @@ void DivPlatformAY8910::tick() {
|
|||
rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2));
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
oldWrites[0x08+i]=-1;
|
||||
oldWrites[0x0d]=-1;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.ex2.had) {
|
||||
ayEnvMode=chan[i].std.ex2.val;
|
||||
rWrite(0x0d,ayEnvMode);
|
||||
|
@ -230,7 +251,7 @@ void DivPlatformAY8910::tick() {
|
|||
if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
if (chan[i].keyOn) {
|
||||
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
|
@ -285,7 +306,7 @@ void DivPlatformAY8910::tick() {
|
|||
int DivPlatformAY8910::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AY);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -293,7 +314,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
} else if (intellivision && (chan[c.chan].psgMode&4)) {
|
||||
|
@ -306,7 +327,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -448,7 +469,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -485,6 +506,10 @@ void* DivPlatformAY8910::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformAY8910::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformAY8910::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -507,6 +532,7 @@ void DivPlatformAY8910::reset() {
|
|||
memset(regPool,0,16);
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=DivPlatformAY8910::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].vol=0x0f;
|
||||
}
|
||||
if (dumpWrites) {
|
||||
|
@ -599,6 +625,9 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
|
|||
break;
|
||||
}
|
||||
rate=chipClock/8;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
||||
if (ay!=NULL) delete ay;
|
||||
switch ((flags>>4)&3) {
|
||||
|
@ -634,6 +663,7 @@ int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned in
|
|||
skipRegisterWrites=false;
|
||||
for (int i=0; i<3; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
ay=NULL;
|
||||
setFlags(flags);
|
||||
|
@ -644,6 +674,9 @@ int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned in
|
|||
}
|
||||
|
||||
void DivPlatformAY8910::quit() {
|
||||
for (int i=0; i<3; i++) delete[] ayBuf[i];
|
||||
for (int i=0; i<3; i++) {
|
||||
delete oscBuf[i];
|
||||
delete[] ayBuf[i];
|
||||
}
|
||||
if (ay!=NULL) delete ay;
|
||||
}
|
||||
|
|
|
@ -32,14 +32,19 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
inline unsigned char regRemap(unsigned char reg) { return intellivision?AY8914RegRemap[reg&0x0f]:reg&0x0f; }
|
||||
struct Channel {
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, note, pitch;
|
||||
unsigned char ins, psgMode, autoEnvNum, autoEnvDen;
|
||||
int freq, baseFreq, note, pitch, pitch2;
|
||||
int ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
int vol, outVol;
|
||||
unsigned char pan;
|
||||
DivMacroInt std;
|
||||
Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {}
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), pitch2(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {}
|
||||
};
|
||||
Channel chan[3];
|
||||
bool isMuted[3];
|
||||
|
@ -51,6 +56,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
ay8910_device* ay;
|
||||
DivDispatchOscBuffer* oscBuf[3];
|
||||
unsigned char regPool[16];
|
||||
unsigned char lastBusy;
|
||||
|
||||
|
@ -85,12 +91,13 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void flushWrites();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setFlags(unsigned int flags);
|
||||
bool isStereo();
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
#define immWrite2(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#define CHIP_DIVIDER 8
|
||||
#define CHIP_DIVIDER 4
|
||||
|
||||
const char* regCheatSheetAY8930[]={
|
||||
"FreqL_A", "00",
|
||||
|
@ -61,6 +61,18 @@ const char* regCheatSheetAY8930[]={
|
|||
NULL
|
||||
};
|
||||
|
||||
void DivPlatformAY8930::immWrite(unsigned char a, unsigned char v) {
|
||||
if ((int)bank!=(a>>4)) {
|
||||
bank=a>>4;
|
||||
immWrite2(0x0d, 0xa0|(bank<<4)|ayEnvMode[0]);
|
||||
}
|
||||
if (a==0x0d) {
|
||||
immWrite2(0x0d,0xa0|(bank<<4)|(v&15));
|
||||
} else {
|
||||
immWrite2(a&15,v);
|
||||
}
|
||||
}
|
||||
|
||||
const char** DivPlatformAY8930::getRegisterSheet() {
|
||||
return regCheatSheetAY8930;
|
||||
}
|
||||
|
@ -123,18 +135,13 @@ void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t l
|
|||
}
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
if ((int)bank!=(w.addr>>4)) {
|
||||
bank=w.addr>>4;
|
||||
ay->address_w(0x0d);
|
||||
ay->data_w(0xa0|(bank<<4)|ayEnvMode[0]);
|
||||
}
|
||||
ay->address_w(w.addr&15);
|
||||
if (w.addr==0x0d) {
|
||||
ay->data_w(0xa0|(bank<<4)|(w.val&15));
|
||||
ay->address_w(w.addr);
|
||||
ay->data_w(w.val);
|
||||
if (w.addr!=0x0d && (regPool[0x0d]&0xf0)==0xb0) {
|
||||
regPool[(w.addr&0x0f)|0x10]=w.val;
|
||||
} else {
|
||||
ay->data_w(w.val);
|
||||
regPool[w.addr&0x0f]=w.val;
|
||||
}
|
||||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
ay->sound_stream_update(ayBuf,len);
|
||||
|
@ -149,6 +156,12 @@ void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t l
|
|||
bufR[i+start]=bufL[i+start];
|
||||
}
|
||||
}
|
||||
|
||||
for (int ch=0; ch<3; ch++) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=ayBuf[ch][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8930::updateOutSel(bool immediate) {
|
||||
|
@ -187,7 +200,7 @@ const unsigned char regMode[3]={
|
|||
0x0d, 0x14, 0x15
|
||||
};
|
||||
|
||||
void DivPlatformAY8930::tick() {
|
||||
void DivPlatformAY8930::tick(bool sysTick) {
|
||||
// PSG
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].std.next();
|
||||
|
@ -226,6 +239,21 @@ void DivPlatformAY8930::tick() {
|
|||
rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode&4)<<3));
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
oldWrites[0x08+i]=-1;
|
||||
oldWrites[regMode[i]]=-1;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.ex1.had) { // duty
|
||||
rWrite(0x16+i,chan[i].std.ex1.val);
|
||||
}
|
||||
|
@ -252,7 +280,7 @@ void DivPlatformAY8930::tick() {
|
|||
immWrite(0x1a,ayNoiseOr);
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
if (chan[i].keyOn) {
|
||||
if (chan[i].insChanged) {
|
||||
|
@ -309,7 +337,7 @@ void DivPlatformAY8930::tick() {
|
|||
int DivPlatformAY8930::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AY8930);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -317,7 +345,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
} else {
|
||||
|
@ -328,7 +356,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -473,7 +501,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY8930));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -508,6 +536,10 @@ void* DivPlatformAY8930::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformAY8930::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformAY8930::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -522,6 +554,7 @@ void DivPlatformAY8930::reset() {
|
|||
memset(regPool,0,32);
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=DivPlatformAY8930::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].vol=31;
|
||||
ayEnvPeriod[i]=0;
|
||||
ayEnvMode[i]=0;
|
||||
|
@ -612,7 +645,11 @@ void DivPlatformAY8930::setFlags(unsigned int flags) {
|
|||
chipClock=COLOR_NTSC/2.0;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/8;
|
||||
rate=chipClock/4;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
||||
stereo=flags>>6;
|
||||
}
|
||||
|
||||
|
@ -622,6 +659,7 @@ int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned in
|
|||
skipRegisterWrites=false;
|
||||
for (int i=0; i<3; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
ay=new ay8930_device(rate);
|
||||
|
@ -633,6 +671,9 @@ int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned in
|
|||
}
|
||||
|
||||
void DivPlatformAY8930::quit() {
|
||||
for (int i=0; i<3; i++) delete[] ayBuf[i];
|
||||
for (int i=0; i<3; i++) {
|
||||
delete oscBuf[i];
|
||||
delete[] ayBuf[i];
|
||||
}
|
||||
delete ay;
|
||||
}
|
||||
|
|
|
@ -28,14 +28,19 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
protected:
|
||||
struct Channel {
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, note, pitch;
|
||||
unsigned char ins, psgMode, autoEnvNum, autoEnvDen, duty;
|
||||
int freq, baseFreq, note, pitch, pitch2;
|
||||
int ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen, duty;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
int vol, outVol;
|
||||
unsigned char pan;
|
||||
DivMacroInt std;
|
||||
Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), duty(4), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(31), pan(3) {}
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), note(0), pitch(0), pitch2(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), duty(4), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(31), pan(3) {}
|
||||
};
|
||||
Channel chan[3];
|
||||
bool isMuted[3];
|
||||
|
@ -47,6 +52,7 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
ay8930_device* ay;
|
||||
DivDispatchOscBuffer* oscBuf[3];
|
||||
unsigned char regPool[32];
|
||||
unsigned char ayNoiseAnd, ayNoiseOr;
|
||||
bool bank;
|
||||
|
@ -67,6 +73,7 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
size_t ayBufLen;
|
||||
|
||||
void updateOutSel(bool immediate=false);
|
||||
void immWrite(unsigned char a, unsigned char v);
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
|
@ -74,11 +81,12 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setFlags(unsigned int flags);
|
||||
bool isStereo();
|
||||
|
|
|
@ -49,6 +49,7 @@ const char* DivPlatformBubSysWSG::getEffectName(unsigned char effect) {
|
|||
}
|
||||
|
||||
void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int chanOut=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
signed int out=0;
|
||||
// K005289 part
|
||||
|
@ -56,10 +57,20 @@ void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_
|
|||
|
||||
// Wavetable part
|
||||
for (int i=0; i<2; i++) {
|
||||
if (isMuted[i]) continue;
|
||||
out+=chan[i].waveROM[k005289->addr(i)]*(regPool[2+i]&0xf);
|
||||
if (isMuted[i]) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
continue;
|
||||
} else {
|
||||
chanOut=chan[i].waveROM[k005289->addr(i)]*(regPool[2+i]&0xf);
|
||||
out+=chanOut;
|
||||
if (writeOscBuf==0) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (++writeOscBuf>=64) writeOscBuf=0;
|
||||
|
||||
out<<=6; // scale output to 16 bit
|
||||
|
||||
if (out<-32768) out=-32768;
|
||||
|
@ -81,7 +92,7 @@ void DivPlatformBubSysWSG::updateWave(int ch) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformBubSysWSG::tick() {
|
||||
void DivPlatformBubSysWSG::tick(bool sysTick) {
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -110,14 +121,23 @@ void DivPlatformBubSysWSG::tick() {
|
|||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].active) {
|
||||
if (chan[i].ws.tick()) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SCC);
|
||||
chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
k005289->load(i,chan[i].freq);
|
||||
|
@ -139,7 +159,7 @@ void DivPlatformBubSysWSG::tick() {
|
|||
int DivPlatformBubSysWSG::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SCC);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -148,7 +168,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) {
|
|||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol);
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (chan[c.chan].wave<0) {
|
||||
chan[c.chan].wave=0;
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
|
@ -160,7 +180,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -225,7 +245,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -258,6 +278,10 @@ void* DivPlatformBubSysWSG::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformBubSysWSG::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformBubSysWSG::getRegisterPool() {
|
||||
return (unsigned char*)regPool;
|
||||
}
|
||||
|
@ -274,6 +298,7 @@ void DivPlatformBubSysWSG::reset() {
|
|||
memset(regPool,0,4*2);
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i]=DivPlatformBubSysWSG::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].ws.setEngine(parent);
|
||||
chan[i].ws.init(NULL,32,15,false);
|
||||
}
|
||||
|
@ -309,6 +334,9 @@ void DivPlatformBubSysWSG::notifyInsDeletion(void* ins) {
|
|||
void DivPlatformBubSysWSG::setFlags(unsigned int flags) {
|
||||
chipClock=COLOR_NTSC;
|
||||
rate=chipClock;
|
||||
for (int i=0; i<2; i++) {
|
||||
oscBuf[i]->rate=rate/64;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformBubSysWSG::poke(unsigned int addr, unsigned short val) {
|
||||
|
@ -323,8 +351,10 @@ int DivPlatformBubSysWSG::init(DivEngine* p, int channels, int sugRate, unsigned
|
|||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
writeOscBuf=0;
|
||||
for (int i=0; i<2; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
k005289=new k005289_core();
|
||||
|
@ -333,6 +363,9 @@ int DivPlatformBubSysWSG::init(DivEngine* p, int channels, int sugRate, unsigned
|
|||
}
|
||||
|
||||
void DivPlatformBubSysWSG::quit() {
|
||||
for (int i=0; i<2; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete k005289;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,17 +28,21 @@
|
|||
|
||||
class DivPlatformBubSysWSG: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol, wave;
|
||||
signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
active(false),
|
||||
|
@ -52,7 +56,9 @@ class DivPlatformBubSysWSG: public DivDispatch {
|
|||
wave(-1) {}
|
||||
};
|
||||
Channel chan[2];
|
||||
DivDispatchOscBuffer* oscBuf[2];
|
||||
bool isMuted[2];
|
||||
unsigned char writeOscBuf;
|
||||
|
||||
k005289_core* k005289;
|
||||
unsigned short regPool[4];
|
||||
|
@ -62,12 +68,13 @@ class DivPlatformBubSysWSG: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
int getRegisterPoolDepth();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
|
|
|
@ -112,6 +112,12 @@ void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
for (size_t i=start; i<start+len; i++) {
|
||||
sid.clock();
|
||||
bufL[i]=sid.output();
|
||||
if (++writeOscBuf>=8) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=sid.last_chan_out[0]>>5;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=sid.last_chan_out[1]>>5;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=sid.last_chan_out[2]>>5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,16 +128,16 @@ void DivPlatformC64::updateFilter() {
|
|||
rWrite(0x18,(filtControl<<4)|vol);
|
||||
}
|
||||
|
||||
void DivPlatformC64::tick() {
|
||||
void DivPlatformC64::tick(bool sysTick) {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64);
|
||||
if (ins->c64.volIsCutoff) {
|
||||
if (ins->c64.filterIsAbs) {
|
||||
filtCut=MIN(2047,chan[i].std.vol.val);
|
||||
} else {
|
||||
filtCut-=((signed char)chan[i].std.vol.val-18)*7;
|
||||
filtCut-=((signed char)chan[i].std.vol.val)*7;
|
||||
if (filtCut>2047) filtCut=2047;
|
||||
if (filtCut<0) filtCut=0;
|
||||
}
|
||||
|
@ -157,27 +163,39 @@ void DivPlatformC64::tick() {
|
|||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64);
|
||||
if (ins->c64.dutyIsAbs) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
} else {
|
||||
chan[i].duty-=((signed char)chan[i].std.duty.val-12)*4;
|
||||
chan[i].duty-=((signed char)chan[i].std.duty.val)*4;
|
||||
}
|
||||
rWrite(i*7+2,chan[i].duty&0xff);
|
||||
rWrite(i*7+3,chan[i].duty>>8);
|
||||
}
|
||||
if (chan[i].testWhen>0) {
|
||||
if (--chan[i].testWhen<1) {
|
||||
if (!chan[i].resetMask && !isMuted[i]) {
|
||||
rWrite(i*7+5,0);
|
||||
rWrite(i*7+6,0);
|
||||
rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|8|(chan[i].ring<<2)|(chan[i].sync<<1));
|
||||
if (sysTick) {
|
||||
if (chan[i].testWhen>0) {
|
||||
if (--chan[i].testWhen<1) {
|
||||
if (!chan[i].resetMask && !chan[i].inPorta) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64);
|
||||
rWrite(i*7+5,0);
|
||||
rWrite(i*7+6,0);
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|(ins->c64.noTest?0:8)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
chan[i].wave=chan[i].std.wave.val;
|
||||
rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active));
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active));
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.ex1.had) {
|
||||
filtControl=chan[i].std.ex1.val&15;
|
||||
|
@ -191,20 +209,25 @@ void DivPlatformC64::tick() {
|
|||
chan[i].sync=chan[i].std.ex3.val&1;
|
||||
chan[i].ring=chan[i].std.ex3.val&2;
|
||||
chan[i].freqChanged=true;
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active));
|
||||
}
|
||||
if (chan[i].std.ex4.had) {
|
||||
chan[i].test=chan[i].std.ex4.val&1;
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active));
|
||||
}
|
||||
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2);
|
||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));
|
||||
rWrite(i*7+6,(chan[i].sustain<<4)|(chan[i].release));
|
||||
rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|1);
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|1);
|
||||
}
|
||||
if (chan[i].keyOff && !isMuted[i]) {
|
||||
if (chan[i].keyOff) {
|
||||
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));
|
||||
rWrite(i*7+6,(chan[i].sustain<<4)|(chan[i].release));
|
||||
rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|0);
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|0);
|
||||
}
|
||||
rWrite(i*7,chan[i].freq&0xff);
|
||||
rWrite(i*7+1,chan[i].freq>>8);
|
||||
|
@ -218,7 +241,7 @@ void DivPlatformC64::tick() {
|
|||
int DivPlatformC64::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -226,6 +249,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].test=false;
|
||||
if (chan[c.chan].insChanged || chan[c.chan].resetDuty || ins->std.waveMacro.len>0) {
|
||||
chan[c.chan].duty=ins->c64.duty;
|
||||
rWrite(c.chan*7+2,chan[c.chan].duty&0xff);
|
||||
|
@ -252,14 +276,14 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].insChanged=false;
|
||||
}
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
//chan[c.chan].std.init(NULL);
|
||||
//chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].active=false;
|
||||
|
@ -330,7 +354,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
chan[c.chan].wave=c.value;
|
||||
rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active));
|
||||
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|(chan[c.chan].test<<3)|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active));
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
|
@ -339,8 +363,8 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) {
|
||||
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta || !chan[c.chan].inPorta) {
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_C64));
|
||||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
@ -378,7 +402,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_C64_FILTER_RESET:
|
||||
if (c.value&15) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64);
|
||||
if (ins->c64.initFilter) {
|
||||
filtCut=ins->c64.cut;
|
||||
updateFilter();
|
||||
|
@ -388,7 +412,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_C64_DUTY_RESET:
|
||||
if (c.value&15) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_C64);
|
||||
chan[c.chan].duty=ins->c64.duty;
|
||||
rWrite(c.chan*7+2,chan[c.chan].duty&0xff);
|
||||
rWrite(c.chan*7+3,chan[c.chan].duty>>8);
|
||||
|
@ -411,11 +435,11 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
break;
|
||||
case 4:
|
||||
chan[c.chan].ring=c.value;
|
||||
rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active));
|
||||
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|(chan[c.chan].test<<3)|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active));
|
||||
break;
|
||||
case 5:
|
||||
chan[c.chan].sync=c.value;
|
||||
rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active));
|
||||
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|(chan[c.chan].test<<3)|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active));
|
||||
break;
|
||||
case 6:
|
||||
filtControl&=7;
|
||||
|
@ -434,7 +458,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
|
||||
void DivPlatformC64::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
rWrite(ch*7+4,(isMuted[ch]?8:(chan[ch].wave<<4))|(chan[ch].ring<<2)|(chan[ch].sync<<1)|(int)(chan[ch].active));
|
||||
sid.set_is_muted(ch,mute);
|
||||
}
|
||||
|
||||
void DivPlatformC64::forceIns() {
|
||||
|
@ -467,6 +491,10 @@ void* DivPlatformC64::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformC64::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformC64::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -475,9 +503,14 @@ int DivPlatformC64::getRegisterPoolSize() {
|
|||
return 32;
|
||||
}
|
||||
|
||||
bool DivPlatformC64::getDCOffRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformC64::reset() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=DivPlatformC64::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
|
||||
sid.reset();
|
||||
|
@ -522,14 +555,19 @@ void DivPlatformC64::setFlags(unsigned int flags) {
|
|||
break;
|
||||
}
|
||||
chipClock=rate;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate/16;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
writeOscBuf=0;
|
||||
for (int i=0; i<3; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
||||
|
@ -539,6 +577,9 @@ int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
}
|
||||
|
||||
void DivPlatformC64::quit() {
|
||||
for (int i=0; i<3; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformC64::~DivPlatformC64() {
|
||||
|
|
|
@ -26,17 +26,22 @@
|
|||
|
||||
class DivPlatformC64: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, prevFreq, testWhen, note;
|
||||
unsigned char ins, sweep, wave, attack, decay, sustain, release;
|
||||
int freq, baseFreq, pitch, pitch2, prevFreq, testWhen, note, ins;
|
||||
unsigned char sweep, wave, attack, decay, sustain, release;
|
||||
short duty;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, filter;
|
||||
bool resetMask, resetFilter, resetDuty, ring, sync;
|
||||
bool resetMask, resetFilter, resetDuty, ring, sync, test;
|
||||
signed char vol, outVol;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
prevFreq(65535),
|
||||
testWhen(0),
|
||||
note(0),
|
||||
|
@ -61,12 +66,15 @@ class DivPlatformC64: public DivDispatch {
|
|||
resetDuty(false),
|
||||
ring(false),
|
||||
sync(false),
|
||||
test(false),
|
||||
vol(15) {}
|
||||
};
|
||||
Channel chan[3];
|
||||
DivDispatchOscBuffer* oscBuf[3];
|
||||
bool isMuted[3];
|
||||
|
||||
unsigned char filtControl, filtRes, vol;
|
||||
unsigned char writeOscBuf;
|
||||
int filtCut, resetTime;
|
||||
|
||||
SID sid;
|
||||
|
@ -79,14 +87,16 @@ class DivPlatformC64: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyInsChange(int ins);
|
||||
bool getDCOffRequired();
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
|
|
|
@ -22,15 +22,29 @@
|
|||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#define CHIP_FREQBASE 2048
|
||||
|
||||
void DivPlatformDummy::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int chanOut;
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
bufL[i]=0;
|
||||
int out=0;
|
||||
for (unsigned char j=0; j<chans; j++) {
|
||||
if (chan[j].active) {
|
||||
if (!isMuted[j]) bufL[i]+=(((signed short)chan[j].pos)*chan[j].amp*chan[j].vol)>>13;
|
||||
if (!isMuted[j]) {
|
||||
chanOut=(((signed short)chan[j].pos)*chan[j].amp*chan[j].vol)>>12;
|
||||
oscBuf[j]->data[oscBuf[j]->needle++]=chanOut;
|
||||
out+=chanOut;
|
||||
} else {
|
||||
oscBuf[j]->data[oscBuf[j]->needle++]=0;
|
||||
}
|
||||
chan[j].pos+=chan[j].freq;
|
||||
} else {
|
||||
oscBuf[j]->data[oscBuf[j]->needle++]=0;
|
||||
}
|
||||
}
|
||||
if (out<-32768) out=-32768;
|
||||
if (out>32767) out=32767;
|
||||
bufL[i]=out;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,10 +52,12 @@ void DivPlatformDummy::muteChannel(int ch, bool mute) {
|
|||
isMuted[ch]=mute;
|
||||
}
|
||||
|
||||
void DivPlatformDummy::tick() {
|
||||
void DivPlatformDummy::tick(bool sysTick) {
|
||||
for (unsigned char i=0; i<chans; i++) {
|
||||
chan[i].amp-=3;
|
||||
if (chan[i].amp<16) chan[i].amp=16;
|
||||
if (sysTick) {
|
||||
chan[i].amp-=7;
|
||||
if (chan[i].amp<15) chan[i].amp=15;
|
||||
}
|
||||
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freqChanged=false;
|
||||
|
@ -54,11 +70,15 @@ void* DivPlatformDummy::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformDummy::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
int DivPlatformDummy::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=65.6f*pow(2.0f,((float)c.value/12.0f));
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
|
@ -78,8 +98,28 @@ int DivPlatformDummy::dispatch(DivCommand c) {
|
|||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) return 2;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=65.6f*pow(2.0f,((float)c.value/12.0f));
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
@ -104,15 +144,23 @@ int DivPlatformDummy::init(DivEngine* p, int channels, int sugRate, unsigned int
|
|||
skipRegisterWrites=false;
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
isMuted[i]=false;
|
||||
if (i<channels) {
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
oscBuf[i]->rate=65536;
|
||||
}
|
||||
}
|
||||
rate=65536;
|
||||
chipClock=65536;
|
||||
chans=channels;
|
||||
reset();
|
||||
return channels;
|
||||
}
|
||||
|
||||
void DivPlatformDummy::quit() {
|
||||
for (int i=0; i<chans; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformDummy::~DivPlatformDummy() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,7 @@
|
|||
// used when a DivDispatch for a system is not found.
|
||||
class DivPlatformDummy: public DivDispatch {
|
||||
struct Channel {
|
||||
unsigned short freq, baseFreq;
|
||||
short pitch;
|
||||
int freq, baseFreq, pitch;
|
||||
unsigned short pos;
|
||||
bool active, freqChanged;
|
||||
unsigned char vol;
|
||||
|
@ -32,6 +31,7 @@ class DivPlatformDummy: public DivDispatch {
|
|||
Channel(): freq(0), baseFreq(0), pitch(0), pos(0), active(false), freqChanged(false), vol(0), amp(64) {}
|
||||
};
|
||||
Channel chan[128];
|
||||
DivDispatchOscBuffer* oscBuf[128];
|
||||
bool isMuted[128];
|
||||
unsigned char chans;
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
@ -40,8 +40,9 @@ class DivPlatformDummy: public DivDispatch {
|
|||
void muteChannel(int ch, bool mute);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
void reset();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformDummy();
|
||||
|
|
|
@ -20,11 +20,12 @@
|
|||
#include "fds.h"
|
||||
#include "sound/nes/cpu_inline.h"
|
||||
#include "../engine.h"
|
||||
#include "sound/nes_nsfplay/nes_fds.h"
|
||||
#include <math.h>
|
||||
|
||||
#define CHIP_FREQBASE 262144
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {fds_wr_mem(fds,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
const char* regCheatSheetFDS[]={
|
||||
"IOCtrl", "4023",
|
||||
|
@ -78,13 +79,49 @@ const char* DivPlatformFDS::getEffectName(unsigned char effect) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
void DivPlatformFDS::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
extcl_apu_tick_FDS(fds);
|
||||
int sample=isMuted[0]?0:fds->snd.main.output;
|
||||
if (sample>32767) sample=32767;
|
||||
if (sample<-32768) sample=-32768;
|
||||
bufL[i]=sample;
|
||||
if (++writeOscBuf>=32) {
|
||||
writeOscBuf=0;
|
||||
oscBuf->data[oscBuf->needle++]=sample<<1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformFDS::acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int out[2];
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
fds_NP->Tick(1);
|
||||
fds_NP->Render(out);
|
||||
int sample=isMuted[0]?0:(out[0]<<1);
|
||||
if (sample>32767) sample=32767;
|
||||
if (sample<-32768) sample=-32768;
|
||||
bufL[i]=sample;
|
||||
if (++writeOscBuf>=32) {
|
||||
writeOscBuf=0;
|
||||
oscBuf->data[oscBuf->needle++]=sample<<1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformFDS::doWrite(unsigned short addr, unsigned char data) {
|
||||
if (useNP) {
|
||||
fds_NP->Write(addr,data);
|
||||
} else {
|
||||
fds_wr_mem(fds,addr,data);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
if (useNP) {
|
||||
acquire_NSFPlay(bufL,bufR,start,len);
|
||||
} else {
|
||||
acquire_puNES(bufL,bufR,start,len);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +134,7 @@ void DivPlatformFDS::updateWave() {
|
|||
rWrite(0x4089,0);
|
||||
}
|
||||
|
||||
void DivPlatformFDS::tick() {
|
||||
void DivPlatformFDS::tick(bool sysTick) {
|
||||
for (int i=0; i<1; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -107,21 +144,11 @@ void DivPlatformFDS::tick() {
|
|||
rWrite(0x4080,0x80|chan[i].outVol);
|
||||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (i==3) { // noise
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=chan[i].std.arp.val;
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=chan[i].note+chan[i].std.arp.val;
|
||||
}
|
||||
if (chan[i].baseFreq>255) chan[i].baseFreq=255;
|
||||
if (chan[i].baseFreq<0) chan[i].baseFreq=0;
|
||||
} else {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
|
@ -155,6 +182,15 @@ void DivPlatformFDS::tick() {
|
|||
//if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].active) {
|
||||
if (ws.tick()) {
|
||||
updateWave();
|
||||
|
@ -184,7 +220,7 @@ void DivPlatformFDS::tick() {
|
|||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].keyOn) {
|
||||
|
@ -205,7 +241,7 @@ void DivPlatformFDS::tick() {
|
|||
int DivPlatformFDS::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FDS);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -248,7 +284,7 @@ int DivPlatformFDS::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (chan[c.chan].wave<0) {
|
||||
chan[c.chan].wave=0;
|
||||
ws.changeWave1(chan[c.chan].wave);
|
||||
|
@ -261,7 +297,7 @@ int DivPlatformFDS::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -365,7 +401,7 @@ int DivPlatformFDS::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FDS));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -397,6 +433,10 @@ void* DivPlatformFDS::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformFDS::getOscBuffer(int ch) {
|
||||
return oscBuf;
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformFDS::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -408,6 +448,7 @@ int DivPlatformFDS::getRegisterPoolSize() {
|
|||
void DivPlatformFDS::reset() {
|
||||
for (int i=0; i<1; i++) {
|
||||
chan[i]=DivPlatformFDS::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
ws.setEngine(parent);
|
||||
ws.init(NULL,64,63,false);
|
||||
|
@ -415,7 +456,11 @@ void DivPlatformFDS::reset() {
|
|||
addWrite(0xffffffff,0);
|
||||
}
|
||||
|
||||
fds_reset(fds);
|
||||
if (useNP) {
|
||||
fds_NP->Reset();
|
||||
} else {
|
||||
fds_reset(fds);
|
||||
}
|
||||
memset(regPool,0,128);
|
||||
|
||||
rWrite(0x4023,0);
|
||||
|
@ -427,6 +472,10 @@ bool DivPlatformFDS::keyOffAffectsArp(int ch) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformFDS::setNSFPlay(bool use) {
|
||||
useNP=use;
|
||||
}
|
||||
|
||||
void DivPlatformFDS::setFlags(unsigned int flags) {
|
||||
if (flags==2) { // Dendy
|
||||
rate=COLOR_PAL*2.0/5.0;
|
||||
|
@ -436,10 +485,15 @@ void DivPlatformFDS::setFlags(unsigned int flags) {
|
|||
rate=COLOR_NTSC/2.0;
|
||||
}
|
||||
chipClock=rate;
|
||||
oscBuf->rate=rate/32;
|
||||
if (useNP) {
|
||||
fds_NP->SetClock(rate);
|
||||
fds_NP->SetRate(rate);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformFDS::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<5; i++) {
|
||||
for (int i=0; i<1; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
@ -457,18 +511,29 @@ int DivPlatformFDS::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
apuType=flags;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
fds=new struct _fds;
|
||||
writeOscBuf=0;
|
||||
if (useNP) {
|
||||
fds_NP=new xgm::NES_FDS;
|
||||
} else {
|
||||
fds=new struct _fds;
|
||||
}
|
||||
oscBuf=new DivDispatchOscBuffer;
|
||||
for (int i=0; i<1; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
||||
reset();
|
||||
return 5;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformFDS::quit() {
|
||||
delete fds;
|
||||
delete oscBuf;
|
||||
if (useNP) {
|
||||
delete fds_NP;
|
||||
} else {
|
||||
delete fds;
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformFDS::~DivPlatformFDS() {
|
||||
|
|
|
@ -24,18 +24,25 @@
|
|||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
|
||||
#include "sound/nes_nsfplay/nes_fds.h"
|
||||
|
||||
class DivPlatformFDS: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, prevFreq, note, modFreq;
|
||||
unsigned char ins, duty, sweep, modDepth, modPos;
|
||||
int freq, baseFreq, pitch, pitch2, prevFreq, note, modFreq, ins;
|
||||
unsigned char duty, sweep, modDepth, modPos;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, modOn;
|
||||
signed char vol, outVol, wave;
|
||||
signed char modTable[32];
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
prevFreq(65535),
|
||||
note(0),
|
||||
modFreq(0),
|
||||
|
@ -59,27 +66,37 @@ class DivPlatformFDS: public DivDispatch {
|
|||
}
|
||||
};
|
||||
Channel chan[1];
|
||||
DivDispatchOscBuffer* oscBuf;
|
||||
bool isMuted[1];
|
||||
DivWaveSynth ws;
|
||||
unsigned char apuType;
|
||||
unsigned char writeOscBuf;
|
||||
bool useNP;
|
||||
struct _fds* fds;
|
||||
xgm::NES_FDS* fds_NP;
|
||||
unsigned char regPool[128];
|
||||
|
||||
void updateWave();
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
void doWrite(unsigned short addr, unsigned char data);
|
||||
void acquire_puNES(short* bufL, short* bufR, size_t start, size_t len);
|
||||
void acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len);
|
||||
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setNSFPlay(bool use);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
|
|
|
@ -87,6 +87,10 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
GB_advance_cycles(gb,16);
|
||||
bufL[i]=gb->apu_output.final_sample.left;
|
||||
bufR[i]=gb->apu_output.final_sample.right;
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +150,7 @@ static unsigned char noiseTable[256]={
|
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
void DivPlatformGB::tick() {
|
||||
void DivPlatformGB::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.arp.had) {
|
||||
|
@ -176,7 +180,7 @@ void DivPlatformGB::tick() {
|
|||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
|
||||
if (i!=2) {
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
} else {
|
||||
|
@ -192,6 +196,25 @@ void DivPlatformGB::tick() {
|
|||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.panL.had) {
|
||||
lastPan&=~(0x11<<i);
|
||||
lastPan|=((chan[i].std.panL.val&1)<<i)|((chan[i].std.panL.val&2)<<(i+3));
|
||||
rWrite(0x25,procMute());
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (i==2) {
|
||||
if (chan[i].active) {
|
||||
if (ws.tick()) {
|
||||
|
@ -207,15 +230,16 @@ void DivPlatformGB::tick() {
|
|||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
|
||||
if (i==3) { // noise
|
||||
int ntPos=chan[i].baseFreq;
|
||||
if (ntPos<0) ntPos=0;
|
||||
if (ntPos>255) ntPos=255;
|
||||
chan[i].freq=noiseTable[ntPos];
|
||||
} else {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
if (i==2) { // wave
|
||||
|
@ -255,7 +279,7 @@ void DivPlatformGB::muteChannel(int ch, bool mute) {
|
|||
int DivPlatformGB::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
if (c.chan==3) { // noise
|
||||
chan[c.chan].baseFreq=c.value;
|
||||
|
@ -267,7 +291,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (c.chan==2) {
|
||||
if (chan[c.chan].wave<0) {
|
||||
chan[c.chan].wave=0;
|
||||
|
@ -281,7 +305,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -292,7 +316,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
chan[c.chan].ins=c.value;
|
||||
chan[c.chan].insChanged=true;
|
||||
if (c.chan!=2) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB);
|
||||
chan[c.chan].vol=ins->gb.envVol;
|
||||
if (parent->song.gbInsAffectsEnvelope) {
|
||||
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
|
||||
|
@ -346,14 +370,16 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
chan[c.chan].duty=c.value;
|
||||
if (c.chan!=2) {
|
||||
chan[c.chan].freqChanged=true;
|
||||
rWrite(16+c.chan*5+1,((chan[c.chan].duty&3)<<6)|(63-(parent->getIns(chan[c.chan].ins)->gb.soundLen&63)));
|
||||
rWrite(16+c.chan*5+1,((chan[c.chan].duty&3)<<6)|(63-(parent->getIns(chan[c.chan].ins,DIV_INS_GB)->gb.soundLen&63)));
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
lastPan&=~(0x11<<c.chan);
|
||||
if (c.value==0) c.value=0x11;
|
||||
c.value=((c.value&15)>0)|(((c.value>>4)>0)<<4);
|
||||
lastPan|=c.value<<c.chan;
|
||||
int pan=0;
|
||||
if (c.value>0) pan|=0x10;
|
||||
if (c.value2>0) pan|=0x01;
|
||||
if (pan==0) pan=0x11;
|
||||
lastPan|=pan<<c.chan;
|
||||
rWrite(0x25,procMute());
|
||||
break;
|
||||
}
|
||||
|
@ -365,7 +391,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_GB));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -407,6 +433,10 @@ void* DivPlatformGB::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformGB::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformGB::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -418,6 +448,7 @@ int DivPlatformGB::getRegisterPoolSize() {
|
|||
void DivPlatformGB::reset() {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i]=DivPlatformGB::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
ws.setEngine(parent);
|
||||
ws.init(NULL,32,15,false);
|
||||
|
@ -472,20 +503,25 @@ void DivPlatformGB::poke(std::vector<DivRegWrite>& wlist) {
|
|||
}
|
||||
|
||||
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
chipClock=4194304;
|
||||
rate=chipClock/16;
|
||||
for (int i=0; i<4; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
chipClock=4194304;
|
||||
rate=chipClock/16;
|
||||
gb=new GB_gameboy_t;
|
||||
reset();
|
||||
return 4;
|
||||
}
|
||||
|
||||
void DivPlatformGB::quit() {
|
||||
for (int i=0; i<4; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete gb;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,15 +27,20 @@
|
|||
|
||||
class DivPlatformGB: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins, duty, sweep;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
unsigned char duty, sweep;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
duty(0),
|
||||
|
@ -52,6 +57,7 @@ class DivPlatformGB: public DivDispatch {
|
|||
wave(-1) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
unsigned char lastPan;
|
||||
DivWaveSynth ws;
|
||||
|
@ -66,11 +72,12 @@ class DivPlatformGB: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
void notifyInsChange(int ins);
|
||||
|
|
|
@ -77,6 +77,54 @@ const char* DivPlatformGenesis::getEffectName(unsigned char effect) {
|
|||
case 0x30:
|
||||
return "30xx: Toggle hard envelope reset on new notes";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x59:
|
||||
return "59xx: Set decay of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5a:
|
||||
return "5Axx: Set decay of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxx: Set decay 2 of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x5c:
|
||||
return "5Cxx: Set decay 2 of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x5d:
|
||||
return "5Dxx: Set decay 2 of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x5e:
|
||||
return "5Exx: Set decay 2 of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5f:
|
||||
return "5Fxx: Set decay 2 of operator 4 (0 to 1F)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -86,14 +134,22 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
|
|||
static int os[2];
|
||||
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
if (!dacReady) {
|
||||
dacDelay+=32000;
|
||||
if (dacDelay>=rate) {
|
||||
dacDelay-=rate;
|
||||
dacReady=true;
|
||||
}
|
||||
}
|
||||
if (dacMode && dacSample!=-1) {
|
||||
dacPeriod-=6;
|
||||
if (dacPeriod<1) {
|
||||
dacPeriod+=dacRate;
|
||||
if (dacPeriod>=rate) {
|
||||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->samples>0) {
|
||||
if (!isMuted[5]) {
|
||||
if (writes.size()<16) {
|
||||
if (dacReady && writes.size()<16) {
|
||||
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||
dacReady=false;
|
||||
}
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
|
@ -106,7 +162,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
|
|||
}
|
||||
}
|
||||
}
|
||||
dacPeriod+=MAX(40,dacRate);
|
||||
while (dacPeriod>=rate) dacPeriod-=rate;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
}
|
||||
|
@ -136,7 +192,8 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
|
|||
|
||||
OPN2_Clock(&fm,o); os[0]+=o[0]; os[1]+=o[1];
|
||||
//OPN2_Write(&fm,0,0);
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]<<7;
|
||||
}
|
||||
|
||||
os[0]=(os[0]<<5);
|
||||
if (os[0]<-32768) os[0]=-32768;
|
||||
|
@ -154,15 +211,25 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
|
|||
void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int os[2];
|
||||
|
||||
ymfm::ym2612::fm_engine* fme=fm_ymfm->debug_engine();
|
||||
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
if (!dacReady) {
|
||||
dacDelay+=32000;
|
||||
if (dacDelay>=rate) {
|
||||
dacDelay-=rate;
|
||||
dacReady=true;
|
||||
}
|
||||
}
|
||||
if (dacMode && dacSample!=-1) {
|
||||
dacPeriod-=24;
|
||||
if (dacPeriod<1) {
|
||||
dacPeriod+=dacRate;
|
||||
if (dacPeriod>=rate) {
|
||||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->samples>0) {
|
||||
if (!isMuted[5]) {
|
||||
if (writes.size()<16) {
|
||||
if (dacReady && writes.size()<16) {
|
||||
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||
dacReady=false;
|
||||
}
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
|
@ -175,7 +242,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
|
|||
}
|
||||
}
|
||||
}
|
||||
dacPeriod+=MAX(40,dacRate);
|
||||
while (dacPeriod>=rate) dacPeriod-=rate;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
}
|
||||
|
@ -200,6 +267,10 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
|
|||
os[0]=out_ymfm.data[0];
|
||||
os[1]=out_ymfm.data[1];
|
||||
//OPN2_Write(&fm,0,0);
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))<<6;
|
||||
}
|
||||
|
||||
if (os[0]<-32768) os[0]=-32768;
|
||||
if (os[0]>32767) os[0]=32767;
|
||||
|
@ -220,7 +291,7 @@ void DivPlatformGenesis::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformGenesis::tick() {
|
||||
void DivPlatformGenesis::tick(bool sysTick) {
|
||||
for (int i=0; i<6; i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
chan[i].std.next();
|
||||
|
@ -245,19 +316,40 @@ void DivPlatformGenesis::tick() {
|
|||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].pan=chan[i].std.panL.val&3;
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
}
|
||||
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.alg.had) {
|
||||
chan[i].state.alg=chan[i].std.alg.val;
|
||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||
|
@ -379,11 +471,20 @@ void DivPlatformGenesis::tick() {
|
|||
for (int i=0; i<6; i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq));
|
||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
||||
int freqt=toFreq(chan[i].freq);
|
||||
immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8);
|
||||
immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff);
|
||||
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2);
|
||||
int block=(chan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
chan[i].freq=(block<<11)|fNum;
|
||||
if (chan[i].freq>0x3fff) chan[i].freq=0x3fff;
|
||||
immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8);
|
||||
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
|
||||
if (chan[i].furnaceDac && dacMode) {
|
||||
double off=1.0;
|
||||
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||
|
@ -391,12 +492,13 @@ void DivPlatformGenesis::tick() {
|
|||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=8363.0/(double)s->centerRate;
|
||||
off=(double)s->centerRate/8363.0;
|
||||
}
|
||||
}
|
||||
dacRate=(1280000*1.25*off)/MAX(1,chan[i].baseFreq);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4)+chan[i].pitch2;
|
||||
dacRate=chan[i].freq*off;
|
||||
if (dacRate<1) dacRate=1;
|
||||
if (dumpWrites) addWrite(0xffff0001,1280000/dacRate);
|
||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
||||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
|
@ -407,47 +509,6 @@ void DivPlatformGenesis::tick() {
|
|||
}
|
||||
}
|
||||
|
||||
int DivPlatformGenesis::octave(int freq) {
|
||||
if (freq>=82432) {
|
||||
return 128;
|
||||
} else if (freq>=41216) {
|
||||
return 64;
|
||||
} else if (freq>=20608) {
|
||||
return 32;
|
||||
} else if (freq>=10304) {
|
||||
return 16;
|
||||
} else if (freq>=5152) {
|
||||
return 8;
|
||||
} else if (freq>=2576) {
|
||||
return 4;
|
||||
} else if (freq>=1288) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DivPlatformGenesis::toFreq(int freq) {
|
||||
if (freq>=82432) {
|
||||
return 0x3800|((freq>>7)&0x7ff);
|
||||
} else if (freq>=41216) {
|
||||
return 0x3000|((freq>>6)&0x7ff);
|
||||
} else if (freq>=20608) {
|
||||
return 0x2800|((freq>>5)&0x7ff);
|
||||
} else if (freq>=10304) {
|
||||
return 0x2000|((freq>>4)&0x7ff);
|
||||
} else if (freq>=5152) {
|
||||
return 0x1800|((freq>>3)&0x7ff);
|
||||
} else if (freq>=2576) {
|
||||
return 0x1000|((freq>>2)&0x7ff);
|
||||
} else if (freq>=1288) {
|
||||
return 0x800|((freq>>1)&0x7ff);
|
||||
} else {
|
||||
return freq&0x7ff;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformGenesis::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
for (int j=0; j<4; j++) {
|
||||
|
@ -469,7 +530,7 @@ void DivPlatformGenesis::muteChannel(int ch, bool mute) {
|
|||
int DivPlatformGenesis::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
|
||||
if (c.chan==5) {
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
dacMode=1;
|
||||
|
@ -493,8 +554,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
}
|
||||
dacPos=0;
|
||||
dacPeriod=0;
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].furnaceDac=true;
|
||||
} else { // compatible mode
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
|
@ -511,7 +574,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
}
|
||||
dacPos=0;
|
||||
dacPeriod=0;
|
||||
dacRate=1280000/MAX(1,parent->getSample(dacSample)->rate);
|
||||
dacRate=MAX(1,parent->getSample(dacSample)->rate);
|
||||
if (dumpWrites) addWrite(0xffff0001,parent->getSample(dacSample)->rate);
|
||||
chan[c.chan].furnaceDac=false;
|
||||
}
|
||||
|
@ -522,7 +585,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
chan[c.chan].state=ins->fm;
|
||||
}
|
||||
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (!chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
|
@ -559,7 +622,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
chan[c.chan].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
chan[c.chan].portaPause=false;
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -625,10 +688,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
chan[c.chan].ins=c.value;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (c.value==0) {
|
||||
if (c.value==0 && c.value2==0) {
|
||||
chan[c.chan].pan=3;
|
||||
} else {
|
||||
chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1);
|
||||
chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1);
|
||||
}
|
||||
rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4));
|
||||
break;
|
||||
|
@ -639,31 +702,65 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) {
|
||||
int destFreq=parent->calcBaseFreq(1,1,c.value2,false);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value*16;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value*16;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||
int newFreq;
|
||||
bool return2=false;
|
||||
if (chan[c.chan].portaPause) {
|
||||
chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq;
|
||||
}
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq+c.value;
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq);
|
||||
newFreq=chan[c.chan].baseFreq-c.value;
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
// check for octave boundary
|
||||
// what the heck!
|
||||
if (!chan[c.chan].portaPause) {
|
||||
if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) {
|
||||
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
|
||||
chan[c.chan].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
|
||||
chan[c.chan].portaPause=true;
|
||||
break;
|
||||
}
|
||||
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
|
||||
chan[c.chan].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
|
||||
chan[c.chan].portaPause=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
chan[c.chan].baseFreq=newFreq;
|
||||
chan[c.chan].portaPause=false;
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].baseFreq=newFreq;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
|
@ -682,7 +779,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
case DIV_CMD_LEGATO: {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
} else {
|
||||
chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
}
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
|
@ -727,7 +828,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
} else {
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.ar=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
|
@ -735,6 +836,134 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RS: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AM: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SL: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_D2R: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DT: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.dt=c.value&7;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.dt=c.value2&7;
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SSG: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
op.ssgEnv=8^(c.value2&15);
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
|
||||
op.ssgEnv=8^(c.value2&15);
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
@ -798,6 +1027,10 @@ void* DivPlatformGenesis::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformGenesis::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformGenesis::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -819,6 +1052,7 @@ void DivPlatformGenesis::reset() {
|
|||
}
|
||||
for (int i=0; i<10; i++) {
|
||||
chan[i]=DivPlatformGenesis::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].vol=0x7f;
|
||||
chan[i].outVol=0x7f;
|
||||
}
|
||||
|
@ -833,6 +1067,8 @@ void DivPlatformGenesis::reset() {
|
|||
dacPeriod=0;
|
||||
dacPos=0;
|
||||
dacRate=0;
|
||||
dacDelay=0;
|
||||
dacReady=true;
|
||||
dacSample=-1;
|
||||
sampleBank=0;
|
||||
lfoValue=8;
|
||||
|
@ -907,6 +1143,9 @@ void DivPlatformGenesis::setFlags(unsigned int flags) {
|
|||
} else {
|
||||
rate=chipClock/36;
|
||||
}
|
||||
for (int i=0; i<10; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
|
@ -916,6 +1155,7 @@ int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, unsigned i
|
|||
skipRegisterWrites=false;
|
||||
for (int i=0; i<10; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
fm_ymfm=NULL;
|
||||
setFlags(flags);
|
||||
|
@ -925,6 +1165,9 @@ int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, unsigned i
|
|||
}
|
||||
|
||||
void DivPlatformGenesis::quit() {
|
||||
for (int i=0; i<10; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
if (fm_ymfm!=NULL) delete fm_ymfm;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,17 +36,23 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
DivInstrumentFM state;
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
|
||||
int ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset;
|
||||
int vol, outVol;
|
||||
unsigned char pan;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freqH(0),
|
||||
freqL(0),
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
portaPauseFreq(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
active(false),
|
||||
|
@ -62,6 +68,7 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
pan(3) {}
|
||||
};
|
||||
Channel chan[10];
|
||||
DivDispatchOscBuffer* oscBuf[10];
|
||||
bool isMuted[10];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
|
@ -84,6 +91,8 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
int dacRate;
|
||||
unsigned int dacPos;
|
||||
int dacSample;
|
||||
int dacDelay;
|
||||
bool dacReady;
|
||||
unsigned char sampleBank;
|
||||
unsigned char lfoValue;
|
||||
|
||||
|
@ -93,9 +102,6 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
int octave(int freq);
|
||||
int toFreq(int freq);
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len);
|
||||
|
@ -105,11 +111,12 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
void setYMFM(bool use);
|
||||
|
|
|
@ -37,7 +37,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
int ordch=orderedOps[ch];
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins);
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
|
||||
if (opChan[ch].insChanged) {
|
||||
chan[2].state.alg=ins->fm.alg;
|
||||
|
@ -72,7 +72,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
opChan[ch].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
}
|
||||
|
@ -107,10 +107,10 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
opChan[ch].ins=c.value;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (c.value==0) {
|
||||
if (c.value==0 && c.value2==0) {
|
||||
opChan[ch].pan=3;
|
||||
} else {
|
||||
opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1);
|
||||
opChan[ch].pan=(c.value2>0)|((c.value>0)<<1);
|
||||
}
|
||||
if (parent->song.sharedExtStat) {
|
||||
for (int i=0; i<4; i++) {
|
||||
|
@ -127,40 +127,70 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||
int newFreq;
|
||||
bool return2=false;
|
||||
if (opChan[ch].portaPause) {
|
||||
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
|
||||
}
|
||||
if (destFreq>opChan[ch].baseFreq) {
|
||||
newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq);
|
||||
newFreq=opChan[ch].baseFreq+c.value;
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq);
|
||||
newFreq=opChan[ch].baseFreq-c.value;
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
// what the heck!
|
||||
if (!opChan[ch].portaPause) {
|
||||
if (octave(opChan[ch].baseFreq)!=octave(newFreq)) {
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
}
|
||||
opChan[ch].baseFreq=newFreq;
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
opChan[ch].baseFreq=newFreq;
|
||||
if (return2) return 2;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_MODE: {
|
||||
// ignored on extended channel 3 mode.
|
||||
// not ignored actually!
|
||||
if (!parent->song.ignoreDACModeOutsideIntendedChannel) {
|
||||
dacMode=c.value;
|
||||
rWrite(0x2b,c.value<<7);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
if (!parent->song.ignoreDACModeOutsideIntendedChannel) {
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_LEGATO: {
|
||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
|
@ -205,6 +235,134 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RS: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AM: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SL: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_D2R: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DT: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.dt=c.value&7;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.dt=c.value2&7;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SSG: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.ssgEnv=8^(c.value2&15);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.ssgEnv=8^(c.value2&15);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 127;
|
||||
break;
|
||||
|
@ -254,7 +412,7 @@ static int opChanOffsH[4]={
|
|||
0xad, 0xae, 0xac, 0xa6
|
||||
};
|
||||
|
||||
void DivPlatformGenesisExt::tick() {
|
||||
void DivPlatformGenesisExt::tick(bool sysTick) {
|
||||
if (extMode) {
|
||||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
|
@ -271,41 +429,26 @@ void DivPlatformGenesisExt::tick() {
|
|||
}
|
||||
}
|
||||
|
||||
DivPlatformGenesis::tick();
|
||||
DivPlatformGenesis::tick(sysTick);
|
||||
|
||||
bool writeNoteOn=false;
|
||||
unsigned char writeMask=2;
|
||||
if (extMode) for (int i=0; i<4; i++) {
|
||||
if (opChan[i].freqChanged) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch);
|
||||
if (opChan[i].freq>262143) opChan[i].freq=262143;
|
||||
if (opChan[i].freq>=82432) {
|
||||
opChan[i].freqH=((opChan[i].freq>>15)&7)|0x38;
|
||||
opChan[i].freqL=(opChan[i].freq>>7)&0xff;
|
||||
} else if (opChan[i].freq>=41216) {
|
||||
opChan[i].freqH=((opChan[i].freq>>14)&7)|0x30;
|
||||
opChan[i].freqL=(opChan[i].freq>>6)&0xff;
|
||||
} else if (opChan[i].freq>=20608) {
|
||||
opChan[i].freqH=((opChan[i].freq>>13)&7)|0x28;
|
||||
opChan[i].freqL=(opChan[i].freq>>5)&0xff;
|
||||
} else if (opChan[i].freq>=10304) {
|
||||
opChan[i].freqH=((opChan[i].freq>>12)&7)|0x20;
|
||||
opChan[i].freqL=(opChan[i].freq>>4)&0xff;
|
||||
} else if (opChan[i].freq>=5152) {
|
||||
opChan[i].freqH=((opChan[i].freq>>11)&7)|0x18;
|
||||
opChan[i].freqL=(opChan[i].freq>>3)&0xff;
|
||||
} else if (opChan[i].freq>=2576) {
|
||||
opChan[i].freqH=((opChan[i].freq>>10)&7)|0x10;
|
||||
opChan[i].freqL=(opChan[i].freq>>2)&0xff;
|
||||
} else if (opChan[i].freq>=1288) {
|
||||
opChan[i].freqH=((opChan[i].freq>>9)&7)|0x08;
|
||||
opChan[i].freqL=(opChan[i].freq>>1)&0xff;
|
||||
} else {
|
||||
opChan[i].freqH=(opChan[i].freq>>8)&7;
|
||||
opChan[i].freqL=opChan[i].freq&0xff;
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
immWrite(opChanOffsH[i],opChan[i].freqH);
|
||||
immWrite(opChanOffsL[i],opChan[i].freqL);
|
||||
opChan[i].freq=(block<<11)|fNum;
|
||||
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
|
||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||
}
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
if (opChan[i].keyOn) {
|
||||
|
@ -376,6 +519,12 @@ void* DivPlatformGenesisExt::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformGenesisExt::getOscBuffer(int ch) {
|
||||
if (ch>=6) return oscBuf[ch-3];
|
||||
if (ch<3) return oscBuf[ch];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformGenesisExt::reset() {
|
||||
DivPlatformGenesis::reset();
|
||||
|
||||
|
|
|
@ -25,13 +25,28 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
|||
struct OpChannel {
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch;
|
||||
unsigned char ins;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
||||
OpChannel():
|
||||
freqH(0),
|
||||
freqL(0),
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
portaPauseFreq(0),
|
||||
ins(-1),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
portaPause(false),
|
||||
vol(0),
|
||||
pan(3) {}
|
||||
};
|
||||
OpChannel opChan[4];
|
||||
bool isOpMuted[4];
|
||||
|
@ -39,9 +54,10 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
|||
public:
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
bool keyOffAffectsPorta(int ch);
|
||||
|
|
|
@ -142,10 +142,10 @@ const char* DivPlatformLynx::getEffectName(unsigned char effect) {
|
|||
}
|
||||
|
||||
void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
mikey->sampleAudio( bufL + start, bufR + start, len );
|
||||
mikey->sampleAudio( bufL + start, bufR + start, len, oscBuf );
|
||||
}
|
||||
|
||||
void DivPlatformLynx::tick() {
|
||||
void DivPlatformLynx::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -171,21 +171,45 @@ void DivPlatformLynx::tick() {
|
|||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].pan&=0x0f;
|
||||
chan[i].pan|=(chan[i].std.panL.val&15)<<4;
|
||||
}
|
||||
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[i].pan&=0xf0;
|
||||
chan[i].pan|=chan[i].std.panR.val&15;
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||
WRITE_ATTEN(i,chan[i].pan);
|
||||
}
|
||||
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
|
||||
if (chan[i].freqChanged) {
|
||||
if (chan[i].lfsr >= 0) {
|
||||
WRITE_LFSR(i, (chan[i].lfsr&0xff));
|
||||
WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4));
|
||||
chan[i].lfsr=-1;
|
||||
}
|
||||
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
WRITE_FEEDBACK(i, chan[i].duty.feedback);
|
||||
}
|
||||
WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7));
|
||||
WRITE_BACKUP( i, chan[i].fd.backup );
|
||||
}
|
||||
else if (chan[i].std.duty.had) {
|
||||
chan[i].freqChanged=false;
|
||||
} else if (chan[i].std.duty.had) {
|
||||
chan[i].duty = chan[i].std.duty.val;
|
||||
WRITE_FEEDBACK(i, chan[i].duty.feedback);
|
||||
WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7));
|
||||
|
@ -206,12 +230,12 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
WRITE_VOLUME(c.chan,(isMuted[c.chan]?0:(chan[c.chan].vol&127)));
|
||||
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY));
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
WRITE_VOLUME(c.chan, 0);
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_LYNX_LFSR_LOAD:
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -223,7 +247,7 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
chan[c.chan].ins=c.value;
|
||||
//chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
//chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY));
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
|
@ -235,7 +259,7 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
chan[c.chan].pan=c.value;
|
||||
chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4);
|
||||
WRITE_ATTEN(c.chan,chan[c.chan].pan);
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
|
@ -279,7 +303,7 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -318,6 +342,10 @@ void* DivPlatformLynx::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformLynx::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformLynx::getRegisterPool()
|
||||
{
|
||||
return const_cast<unsigned char*>( mikey->getRegisterPool() );
|
||||
|
@ -329,11 +357,11 @@ int DivPlatformLynx::getRegisterPoolSize()
|
|||
}
|
||||
|
||||
void DivPlatformLynx::reset() {
|
||||
|
||||
mikey = std::make_unique<Lynx::Mikey>( rate );
|
||||
mikey=std::make_unique<Lynx::Mikey>(rate);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i]= DivPlatformLynx::Channel();
|
||||
chan[i]=DivPlatformLynx::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
|
@ -374,16 +402,24 @@ int DivPlatformLynx::init(DivEngine* p, int channels, int sugRate, unsigned int
|
|||
|
||||
for (int i=0; i<4; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
|
||||
chipClock = 16000000;
|
||||
rate = chipClock/128;
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
||||
reset();
|
||||
return 4;
|
||||
}
|
||||
|
||||
void DivPlatformLynx::quit() {
|
||||
for (int i=0; i<4; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
mikey.reset();
|
||||
}
|
||||
|
||||
|
|
|
@ -44,16 +44,21 @@ class DivPlatformLynx: public DivDispatch {
|
|||
DivMacroInt std;
|
||||
MikeyFreqDiv fd;
|
||||
MikeyDuty duty;
|
||||
int baseFreq, pitch, note, actualNote, lfsr;
|
||||
unsigned char ins, pan;
|
||||
int baseFreq, pitch, pitch2, note, actualNote, lfsr, ins;
|
||||
unsigned char pan;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
std(),
|
||||
fd(0),
|
||||
duty(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
actualNote(0),
|
||||
lfsr(-1),
|
||||
|
@ -69,6 +74,7 @@ class DivPlatformLynx: public DivDispatch {
|
|||
outVol(127) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
std::unique_ptr<Lynx::Mikey> mikey;
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
@ -76,11 +82,12 @@ class DivPlatformLynx: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
|
|
|
@ -59,7 +59,7 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
if (dacPeriod>=rate) {
|
||||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->samples>0) {
|
||||
if (!isMuted[4]) {
|
||||
if (!isMuted[2]) {
|
||||
rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80));
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
|
@ -92,10 +92,17 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
if (sample>32767) sample=32767;
|
||||
if (sample<-32768) sample=-32768;
|
||||
bufL[i]=sample;
|
||||
|
||||
if (++writeOscBuf>=32) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output*10)<<7);
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output*10)<<7);
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output*2)<<6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformMMC5::tick() {
|
||||
void DivPlatformMMC5::tick(bool sysTick) {
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -123,8 +130,23 @@ void DivPlatformMMC5::tick() {
|
|||
chan[i].duty=chan[i].std.duty.val;
|
||||
rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6));
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].prevFreq=-1;
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].keyOn) {
|
||||
|
@ -149,18 +171,18 @@ void DivPlatformMMC5::tick() {
|
|||
}
|
||||
|
||||
// PCM
|
||||
if (chan[4].freqChanged) {
|
||||
chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false);
|
||||
if (chan[4].furnaceDac) {
|
||||
if (chan[2].freqChanged) {
|
||||
chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false);
|
||||
if (chan[2].furnaceDac) {
|
||||
double off=1.0;
|
||||
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(dacSample);
|
||||
off=(double)s->centerRate/8363.0;
|
||||
}
|
||||
dacRate=MIN(chan[4].freq*off,32000);
|
||||
dacRate=MIN(chan[2].freq*off,32000);
|
||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
||||
}
|
||||
chan[4].freqChanged=false;
|
||||
chan[2].freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +190,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
if (c.chan==2) { // PCM
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
dacSample=ins->amiga.initSample;
|
||||
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
|
||||
|
@ -218,7 +240,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
rWrite(0x5000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
|
@ -228,7 +250,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -282,6 +304,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
}
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
chan[c.chan].duty=c.value;
|
||||
rWrite(0x5000+c.chan*4,0x30|chan[c.chan].outVol|((chan[c.chan].duty&3)<<6));
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
|
@ -296,7 +319,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -327,6 +350,10 @@ void* DivPlatformMMC5::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformMMC5::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformMMC5::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -342,6 +369,7 @@ float DivPlatformMMC5::getPostAmp() {
|
|||
void DivPlatformMMC5::reset() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=DivPlatformMMC5::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
|
@ -373,6 +401,9 @@ void DivPlatformMMC5::setFlags(unsigned int flags) {
|
|||
rate=COLOR_NTSC/2.0;
|
||||
}
|
||||
chipClock=rate;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate/32;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformMMC5::notifyInsDeletion(void* ins) {
|
||||
|
@ -394,9 +425,11 @@ int DivPlatformMMC5::init(DivEngine* p, int channels, int sugRate, unsigned int
|
|||
apuType=flags;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
writeOscBuf=0;
|
||||
mmc5=new struct _mmc5;
|
||||
for (int i=0; i<3; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
//mmc5->muted[i]=false; // TODO
|
||||
}
|
||||
setFlags(flags);
|
||||
|
@ -407,6 +440,9 @@ int DivPlatformMMC5::init(DivEngine* p, int channels, int sugRate, unsigned int
|
|||
}
|
||||
|
||||
void DivPlatformMMC5::quit() {
|
||||
for (int i=0; i<3; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete mmc5;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,15 +25,20 @@
|
|||
|
||||
class DivPlatformMMC5: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, prevFreq, note;
|
||||
unsigned char ins, duty, sweep;
|
||||
int freq, baseFreq, pitch, pitch2, prevFreq, note, ins;
|
||||
unsigned char duty, sweep;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
prevFreq(65535),
|
||||
note(0),
|
||||
ins(-1),
|
||||
|
@ -52,12 +57,14 @@ class DivPlatformMMC5: public DivDispatch {
|
|||
wave(-1) {}
|
||||
};
|
||||
Channel chan[5];
|
||||
DivDispatchOscBuffer* oscBuf[3];
|
||||
bool isMuted[5];
|
||||
int dacPeriod, dacRate;
|
||||
unsigned int dacPos;
|
||||
int dacSample;
|
||||
unsigned char sampleBank;
|
||||
unsigned char apuType;
|
||||
unsigned char writeOscBuf;
|
||||
struct _mmc5* mmc5;
|
||||
unsigned char regPool[128];
|
||||
|
||||
|
@ -67,11 +74,12 @@ class DivPlatformMMC5: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
float getPostAmp();
|
||||
|
|
|
@ -161,6 +161,10 @@ void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
if (out<-32768) out=-32768;
|
||||
bufL[i]=bufR[i]=out;
|
||||
|
||||
if (n163.voice_cycle()==0x78) for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=n163.chan_out(i)<<7;
|
||||
}
|
||||
|
||||
// command queue
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
|
@ -171,36 +175,50 @@ void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformN163::updateWave(int wave, int pos, int len) {
|
||||
void DivPlatformN163::updateWave(int ch, int wave, int pos, int len) {
|
||||
len&=0xfc; // 4 nibble boundary
|
||||
DivWavetable* wt=parent->getWave(wave);
|
||||
for (int i=0; i<len; i++) {
|
||||
unsigned char addr=(pos+i); // address (nibble each)
|
||||
if (addr>=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area
|
||||
break;
|
||||
}
|
||||
unsigned char mask=(addr&1)?0xf0:0x0f;
|
||||
if (wt->max<1 || wt->len<1) {
|
||||
rWriteMask(addr>>1,0,mask);
|
||||
} else {
|
||||
int data=wt->data[i*wt->len/len]*15/wt->max;
|
||||
if (data<0) data=0;
|
||||
if (data>15) data=15;
|
||||
if (wave<0) {
|
||||
// load from wave synth
|
||||
for (int i=0; i<len; i++) {
|
||||
unsigned char addr=(pos+i); // address (nibble each)
|
||||
if (addr>=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area
|
||||
break;
|
||||
}
|
||||
unsigned char mask=(addr&1)?0xf0:0x0f;
|
||||
int data=chan[ch].ws.output[i];
|
||||
rWriteMask(addr>>1,(addr&1)?(data<<4):(data&0xf),mask);
|
||||
}
|
||||
} else {
|
||||
// load from custom
|
||||
DivWavetable* wt=parent->getWave(wave);
|
||||
for (int i=0; i<len; i++) {
|
||||
unsigned char addr=(pos+i); // address (nibble each)
|
||||
if (addr>=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area
|
||||
break;
|
||||
}
|
||||
unsigned char mask=(addr&1)?0xf0:0x0f;
|
||||
if (wt->max<1 || wt->len<1) {
|
||||
rWriteMask(addr>>1,0,mask);
|
||||
} else {
|
||||
int data=wt->data[i*wt->len/len]*15/wt->max;
|
||||
if (data<0) data=0;
|
||||
if (data>15) data=15;
|
||||
rWriteMask(addr>>1,(addr&1)?(data<<4):(data&0xf),mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformN163::updateWaveCh(int ch) {
|
||||
if (ch<=chanMax) {
|
||||
updateWave(chan[ch].wave,chan[ch].wavePos,chan[ch].waveLen);
|
||||
updateWave(ch,-1,chan[ch].wavePos,chan[ch].waveLen);
|
||||
if (chan[ch].active && !isMuted[ch]) {
|
||||
chan[ch].volumeChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformN163::tick() {
|
||||
void DivPlatformN163::tick(bool sysTick) {
|
||||
for (int i=0; i<=chanMax; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -241,14 +259,25 @@ void DivPlatformN163::tick() {
|
|||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val) {
|
||||
chan[i].wave=chan[i].std.wave.val;
|
||||
chan[i].ws.changeWave1(chan[i].wave);
|
||||
if (chan[i].waveMode&0x2) {
|
||||
chan[i].waveUpdated=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.ex1.had) {
|
||||
if (chan[i].waveLen!=(chan[i].std.ex1.val&0xfc)) {
|
||||
chan[i].waveLen=chan[i].std.ex1.val&0xfc;
|
||||
chan[i].ws.setWidth(chan[i].waveLen);
|
||||
if (chan[i].waveMode&0x2) {
|
||||
chan[i].waveUpdated=true;
|
||||
}
|
||||
|
@ -275,7 +304,7 @@ void DivPlatformN163::tick() {
|
|||
if (chan[i].loadWave!=chan[i].std.ex3.val) {
|
||||
chan[i].loadWave=chan[i].std.ex3.val;
|
||||
if (chan[i].loadMode&0x2) {
|
||||
updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc);
|
||||
updateWave(i,chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -296,7 +325,7 @@ void DivPlatformN163::tick() {
|
|||
if ((chan[i].loadMode&0x1)!=(chan[i].std.fms.val&0x1)) { // load now
|
||||
chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms.val&0x1);
|
||||
if (chan[i].loadMode&0x1) { // rising edge
|
||||
updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc);
|
||||
updateWave(i,chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -315,6 +344,11 @@ void DivPlatformN163::tick() {
|
|||
}
|
||||
chan[i].waveChanged=false;
|
||||
}
|
||||
if (chan[i].active) {
|
||||
if (chan[i].ws.tick()) {
|
||||
chan[i].waveUpdated=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].waveUpdated) {
|
||||
updateWaveCh(i);
|
||||
if (chan[i].active) {
|
||||
|
@ -323,7 +357,7 @@ void DivPlatformN163::tick() {
|
|||
chan[i].waveUpdated=false;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0);
|
||||
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2);
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
|
||||
if (chan[i].keyOn) {
|
||||
|
@ -350,14 +384,15 @@ void DivPlatformN163::tick() {
|
|||
int DivPlatformN163::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163);
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].wave=ins->n163.wave;
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
chan[c.chan].wavePos=ins->n163.wavePos;
|
||||
chan[c.chan].waveLen=ins->n163.waveLen;
|
||||
chan[c.chan].waveMode=ins->n163.waveMode;
|
||||
chan[c.chan].waveChanged=true;
|
||||
if (chan[c.chan].waveMode&0x3) {
|
||||
if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) {
|
||||
chan[c.chan].waveUpdated=true;
|
||||
}
|
||||
chan[c.chan].insChanged=false;
|
||||
|
@ -373,14 +408,15 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].volumeChanged=true;
|
||||
}
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
chan[c.chan].ws.init(ins,chan[c.chan].waveLen,15,chan[c.chan].insChanged);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
//chan[c.chan].std.init(NULL);
|
||||
//chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].active=false;
|
||||
|
@ -472,7 +508,7 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
case DIV_CMD_N163_WAVE_LOAD:
|
||||
chan[c.chan].loadWave=c.value;
|
||||
if (chan[c.chan].loadMode&0x2) { // load when every waveform changes
|
||||
updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen);
|
||||
updateWave(c.chan,chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_N163_WAVE_LOADPOS:
|
||||
|
@ -484,13 +520,13 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
case DIV_CMD_N163_WAVE_LOADMODE:
|
||||
chan[c.chan].loadMode=c.value&0x3;
|
||||
if (chan[c.chan].loadMode&0x1) { // load now
|
||||
updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen);
|
||||
updateWave(c.chan,chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_N163_GLOBAL_WAVE_LOAD:
|
||||
loadWave=c.value;
|
||||
if (loadMode&0x2) { // load when every waveform changes
|
||||
updateWave(loadWave,loadPos,loadLen);
|
||||
updateWave(c.chan,loadWave,loadPos,loadLen);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_N163_GLOBAL_WAVE_LOADPOS:
|
||||
|
@ -502,7 +538,7 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
case DIV_CMD_N163_GLOBAL_WAVE_LOADMODE:
|
||||
loadMode=c.value&0x3;
|
||||
if (loadMode&0x3) { // load now
|
||||
updateWave(loadWave,loadPos,loadLen);
|
||||
updateWave(c.chan,loadWave,loadPos,loadLen);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_N163_CHANNEL_LIMIT:
|
||||
|
@ -520,7 +556,7 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) {
|
||||
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_N163));
|
||||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
@ -562,6 +598,7 @@ void DivPlatformN163::notifyWaveChange(int wave) {
|
|||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].wave==wave) {
|
||||
if (chan[i].waveMode&0x2) {
|
||||
chan[i].ws.changeWave1(wave);
|
||||
chan[i].waveUpdated=true;
|
||||
}
|
||||
}
|
||||
|
@ -586,6 +623,10 @@ void* DivPlatformN163::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformN163::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformN163::getRegisterPool() {
|
||||
for (int i=0; i<128; i++) {
|
||||
regPool[i]=n163.reg(i);
|
||||
|
@ -601,6 +642,9 @@ void DivPlatformN163::reset() {
|
|||
while (!writes.empty()) writes.pop();
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i]=DivPlatformN163::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].ws.setEngine(parent);
|
||||
chan[i].ws.init(NULL,32,15,false);
|
||||
}
|
||||
|
||||
n163.reset();
|
||||
|
@ -642,6 +686,9 @@ void DivPlatformN163::setFlags(unsigned int flags) {
|
|||
rate/=15;
|
||||
n163.set_multiplex(multiplex);
|
||||
rWrite(0x7f,initChanMax<<4);
|
||||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->rate=rate/(initChanMax+1);
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
|
@ -650,6 +697,7 @@ int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int
|
|||
skipRegisterWrites=false;
|
||||
for (int i=0; i<8; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
||||
|
@ -659,6 +707,9 @@ int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int
|
|||
}
|
||||
|
||||
void DivPlatformN163::quit() {
|
||||
for (int i=0; i<8; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformN163::~DivPlatformN163() {
|
||||
|
|
|
@ -23,11 +23,12 @@
|
|||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
#include "sound/n163/n163.hpp"
|
||||
|
||||
class DivPlatformN163: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
short ins, wave, wavePos, waveLen;
|
||||
unsigned char waveMode;
|
||||
short loadWave, loadPos, loadLen;
|
||||
|
@ -35,10 +36,16 @@ class DivPlatformN163: public DivDispatch {
|
|||
bool active, insChanged, freqChanged, volumeChanged, waveChanged, waveUpdated, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol, resVol;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
wave(-1),
|
||||
|
@ -63,6 +70,7 @@ class DivPlatformN163: public DivDispatch {
|
|||
resVol(15) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
bool isMuted[8];
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
|
@ -79,7 +87,7 @@ class DivPlatformN163: public DivDispatch {
|
|||
|
||||
n163_core n163;
|
||||
unsigned char regPool[128];
|
||||
void updateWave(int wave, int pos, int len);
|
||||
void updateWave(int ch, int wave, int pos, int len);
|
||||
void updateWaveCh(int ch);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
|
@ -87,11 +95,12 @@ class DivPlatformN163: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyWaveChange(int wave);
|
||||
|
|
|
@ -20,14 +20,15 @@
|
|||
#include "nes.h"
|
||||
#include "sound/nes/cpu_inline.h"
|
||||
#include "../engine.h"
|
||||
#include <cstddef>
|
||||
#include "../../ta-log.h"
|
||||
#include <stddef.h>
|
||||
#include <math.h>
|
||||
|
||||
struct _nla_table nla_table;
|
||||
|
||||
#define CHIP_DIVIDER 16
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {apu_wr_reg(nes,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
const char* regCheatSheetNES[]={
|
||||
"S0Volume", "4000",
|
||||
|
@ -53,12 +54,19 @@ const char* regCheatSheetNES[]={
|
|||
NULL
|
||||
};
|
||||
|
||||
unsigned char _readDMC(void* user, unsigned short addr) {
|
||||
return ((DivPlatformNES*)user)->readDMC(addr);
|
||||
}
|
||||
|
||||
const char** DivPlatformNES::getRegisterSheet() {
|
||||
return regCheatSheetNES;
|
||||
}
|
||||
|
||||
const char* DivPlatformNES::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x11:
|
||||
return "Write to delta modulation counter (0 to 7F)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)";
|
||||
break;
|
||||
|
@ -68,50 +76,108 @@ const char* DivPlatformNES::getEffectName(unsigned char effect) {
|
|||
case 0x14:
|
||||
return "14xy: Sweep down (x: time; y: shift)";
|
||||
break;
|
||||
case 0x18:
|
||||
return "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
||||
if (useNP) {
|
||||
nes1_NP->Write(addr,data);
|
||||
nes2_NP->Write(addr,data);
|
||||
} else {
|
||||
apu_wr_reg(nes,addr,data);
|
||||
}
|
||||
}
|
||||
|
||||
#define doPCM \
|
||||
if (!dpcmMode && dacSample!=-1) { \
|
||||
dacPeriod+=dacRate; \
|
||||
if (dacPeriod>=rate) { \
|
||||
DivSample* s=parent->getSample(dacSample); \
|
||||
if (s->samples>0) { \
|
||||
if (!isMuted[4]) { \
|
||||
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \
|
||||
if (dacAntiClickOn && dacAntiClick<next) { \
|
||||
dacAntiClick+=8; \
|
||||
rWrite(0x4011,dacAntiClick); \
|
||||
} else { \
|
||||
dacAntiClickOn=false; \
|
||||
rWrite(0x4011,next); \
|
||||
} \
|
||||
} \
|
||||
if (++dacPos>=s->samples) { \
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { \
|
||||
dacPos=s->loopStart; \
|
||||
} else { \
|
||||
dacSample=-1; \
|
||||
} \
|
||||
} \
|
||||
dacPeriod-=rate; \
|
||||
} else { \
|
||||
dacSample=-1; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
void DivPlatformNES::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
if (dacSample!=-1) {
|
||||
dacPeriod+=dacRate;
|
||||
if (dacPeriod>=rate) {
|
||||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->samples>0) {
|
||||
if (!isMuted[4]) {
|
||||
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1;
|
||||
if (dacAntiClickOn && dacAntiClick<next) {
|
||||
dacAntiClick+=8;
|
||||
rWrite(0x4011,dacAntiClick);
|
||||
} else {
|
||||
dacAntiClickOn=false;
|
||||
rWrite(0x4011,next);
|
||||
}
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
}
|
||||
}
|
||||
dacPeriod-=rate;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
doPCM;
|
||||
|
||||
apu_tick(nes,NULL);
|
||||
nes->apu.odd_cycle=!nes->apu.odd_cycle;
|
||||
if (nes->apu.clocked) {
|
||||
nes->apu.clocked=false;
|
||||
}
|
||||
int sample=(pulse_output(nes)+tnd_output(nes));
|
||||
int sample=(pulse_output(nes)+tnd_output(nes))<<6;
|
||||
if (sample>32767) sample=32767;
|
||||
if (sample<-32768) sample=-32768;
|
||||
bufL[i]=sample;
|
||||
if (++writeOscBuf>=32) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=nes->S1.output<<11;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=nes->S2.output<<11;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=nes->TR.output<<11;
|
||||
oscBuf[3]->data[oscBuf[3]->needle++]=nes->NS.output<<11;
|
||||
oscBuf[4]->data[oscBuf[4]->needle++]=nes->DMC.output<<8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNES::acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int out1[2];
|
||||
int out2[2];
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
doPCM;
|
||||
|
||||
nes1_NP->Tick(1);
|
||||
nes2_NP->TickFrameSequence(1);
|
||||
nes2_NP->Tick(1);
|
||||
nes1_NP->Render(out1);
|
||||
nes2_NP->Render(out2);
|
||||
|
||||
int sample=(out1[0]+out1[1]+out2[0]+out2[1])<<1;
|
||||
if (sample>32767) sample=32767;
|
||||
if (sample<-32768) sample=-32768;
|
||||
bufL[i]=sample;
|
||||
if (++writeOscBuf>=32) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<11;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<11;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<11;
|
||||
oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<11;
|
||||
oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
if (useNP) {
|
||||
acquire_NSFPlay(bufL,bufR,start,len);
|
||||
} else {
|
||||
acquire_puNES(bufL,bufR,start,len);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,7 +206,26 @@ static unsigned char noiseTable[253]={
|
|||
15
|
||||
};
|
||||
|
||||
void DivPlatformNES::tick() {
|
||||
unsigned char DivPlatformNES::calcDPCMRate(int inRate) {
|
||||
if (inRate<4450) return 0;
|
||||
if (inRate<5000) return 1;
|
||||
if (inRate<5400) return 2;
|
||||
if (inRate<5900) return 3;
|
||||
if (inRate<6650) return 4;
|
||||
if (inRate<7450) return 5;
|
||||
if (inRate<8100) return 6;
|
||||
if (inRate<8800) return 7;
|
||||
if (inRate<10200) return 8;
|
||||
if (inRate<11700) return 9;
|
||||
if (inRate<13300) return 10;
|
||||
if (inRate<15900) return 11;
|
||||
if (inRate<18900) return 12;
|
||||
if (inRate<23500) return 13;
|
||||
if (inRate<29000) return 14;
|
||||
return 15;
|
||||
}
|
||||
|
||||
void DivPlatformNES::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -195,12 +280,27 @@ void DivPlatformNES::tick() {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].sweepChanged) {
|
||||
chan[i].sweepChanged=false;
|
||||
if (i==0) {
|
||||
//rWrite(16+i*5,chan[i].sweep);
|
||||
}
|
||||
}
|
||||
if (i<2) if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].prevFreq=-1;
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
if (i==3) { // noise
|
||||
int ntPos=chan[i].baseFreq;
|
||||
|
@ -208,13 +308,11 @@ void DivPlatformNES::tick() {
|
|||
if (ntPos>252) ntPos=252;
|
||||
chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]);
|
||||
} else {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
//rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
//rWrite(16+i*5+2,8);
|
||||
|
@ -243,7 +341,7 @@ void DivPlatformNES::tick() {
|
|||
}
|
||||
|
||||
// PCM
|
||||
if (chan[4].freqChanged) {
|
||||
if (chan[4].freqChanged || chan[4].keyOn) {
|
||||
chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false);
|
||||
if (chan[4].furnaceDac) {
|
||||
double off=1.0;
|
||||
|
@ -252,8 +350,27 @@ void DivPlatformNES::tick() {
|
|||
off=(double)s->centerRate/8363.0;
|
||||
}
|
||||
dacRate=MIN(chan[4].freq*off,32000);
|
||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
||||
if (chan[4].keyOn) {
|
||||
if (dpcmMode && !skipRegisterWrites && dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||
unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM;
|
||||
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
|
||||
if (dpcmLen>255) dpcmLen=255;
|
||||
// write DPCM
|
||||
rWrite(0x4015,15);
|
||||
rWrite(0x4010,calcDPCMRate(dacRate));
|
||||
rWrite(0x4012,(dpcmAddr>>6)&0xff);
|
||||
rWrite(0x4013,dpcmLen&0xff);
|
||||
rWrite(0x4015,31);
|
||||
dpcmBank=dpcmAddr>>14;
|
||||
}
|
||||
} else {
|
||||
if (dpcmMode) {
|
||||
rWrite(0x4010,calcDPCMRate(dacRate));
|
||||
}
|
||||
}
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
|
||||
}
|
||||
if (chan[4].keyOn) chan[4].keyOn=false;
|
||||
chan[4].freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
@ -262,20 +379,20 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
if (c.chan==4) { // PCM
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
dacSample=ins->amiga.initSample;
|
||||
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
|
||||
break;
|
||||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000,dacSample);
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
dacPos=0;
|
||||
dacPeriod=0;
|
||||
chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f));
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
|
@ -289,16 +406,28 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
dacSample=12*sampleBank+chan[c.chan].note%12;
|
||||
if (dacSample>=parent->song.sampleLen) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
|
||||
break;
|
||||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000,dacSample);
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
dacPos=0;
|
||||
dacPeriod=0;
|
||||
dacRate=parent->getSample(dacSample)->rate;
|
||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
|
||||
chan[c.chan].furnaceDac=false;
|
||||
if (dpcmMode && !skipRegisterWrites) {
|
||||
unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM;
|
||||
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
|
||||
if (dpcmLen>255) dpcmLen=255;
|
||||
// write DPCM
|
||||
rWrite(0x4015,15);
|
||||
rWrite(0x4010,calcDPCMRate(dacRate));
|
||||
rWrite(0x4012,(dpcmAddr>>6)&0xff);
|
||||
rWrite(0x4013,dpcmLen&0xff);
|
||||
rWrite(0x4015,31);
|
||||
dpcmBank=dpcmAddr>>14;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else if (c.chan==3) { // noise
|
||||
|
@ -316,7 +445,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
if (c.chan==2) {
|
||||
rWrite(0x4000+c.chan*4,0xff);
|
||||
} else {
|
||||
|
@ -327,10 +456,11 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
if (c.chan==4) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
if (dpcmMode && !skipRegisterWrites) rWrite(0x4015,15);
|
||||
}
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -364,7 +494,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_PERIODIC(c.value2);
|
||||
int destFreq=(c.chan==4)?(parent->calcBaseFreq(1,1,c.value2,false)):(NOTE_PERIODIC(c.value2));
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
|
@ -390,6 +520,8 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
chan[c.chan].duty=c.value;
|
||||
if (c.chan==3) { // noise
|
||||
chan[c.chan].freqChanged=true;
|
||||
} else if (c.chan<2) {
|
||||
rWrite(0x4000+c.chan*4,0x30|chan[c.chan].outVol|((chan[c.chan].duty&3)<<6));
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NES_SWEEP:
|
||||
|
@ -405,6 +537,19 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
}
|
||||
rWrite(0x4001+(c.chan*4),chan[c.chan].sweep);
|
||||
break;
|
||||
case DIV_CMD_NES_DMC:
|
||||
rWrite(0x4011,c.value&0x7f);
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
dpcmMode=c.value;
|
||||
if (dumpWrites && dpcmMode) addWrite(0xffff0002,0);
|
||||
dacSample=-1;
|
||||
rWrite(0x4015,15);
|
||||
rWrite(0x4010,0);
|
||||
rWrite(0x4012,0);
|
||||
rWrite(0x4013,0);
|
||||
rWrite(0x4015,31);
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
|
@ -413,13 +558,17 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
if (c.chan==3) break;
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
if (c.chan==4) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)),false);
|
||||
} else {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -437,7 +586,12 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
|
||||
void DivPlatformNES::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
nes->muted[ch]=mute;
|
||||
if (useNP) {
|
||||
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
|
||||
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
|
||||
} else {
|
||||
nes->muted[ch]=mute;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNES::forceIns() {
|
||||
|
@ -453,6 +607,10 @@ void* DivPlatformNES::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformNES::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformNES::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -462,12 +620,13 @@ int DivPlatformNES::getRegisterPoolSize() {
|
|||
}
|
||||
|
||||
float DivPlatformNES::getPostAmp() {
|
||||
return 128.0f;
|
||||
return 2.0f;
|
||||
}
|
||||
|
||||
void DivPlatformNES::reset() {
|
||||
for (int i=0; i<5; i++) {
|
||||
chan[i]=DivPlatformNES::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
|
@ -478,11 +637,20 @@ void DivPlatformNES::reset() {
|
|||
dacRate=0;
|
||||
dacSample=-1;
|
||||
sampleBank=0;
|
||||
dpcmBank=0;
|
||||
dpcmMode=false;
|
||||
|
||||
apu_turn_on(nes,apuType);
|
||||
if (useNP) {
|
||||
nes1_NP->Reset();
|
||||
nes2_NP->Reset();
|
||||
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
|
||||
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
|
||||
} else {
|
||||
apu_turn_on(nes,apuType);
|
||||
nes->apu.cpu_cycles=0;
|
||||
nes->apu.cpu_opcode_cycle=0;
|
||||
}
|
||||
memset(regPool,0,128);
|
||||
nes->apu.cpu_cycles=0;
|
||||
nes->apu.cpu_opcode_cycle=0;
|
||||
|
||||
rWrite(0x4015,0x1f);
|
||||
rWrite(0x4001,chan[0].sweep);
|
||||
|
@ -500,17 +668,26 @@ void DivPlatformNES::setFlags(unsigned int flags) {
|
|||
if (flags==2) { // Dendy
|
||||
rate=COLOR_PAL*2.0/5.0;
|
||||
apuType=2;
|
||||
nes->apu.type=apuType;
|
||||
} else if (flags==1) { // PAL
|
||||
rate=COLOR_PAL*3.0/8.0;
|
||||
apuType=1;
|
||||
nes->apu.type=apuType;
|
||||
} else { // NTSC
|
||||
rate=COLOR_NTSC/2.0;
|
||||
apuType=0;
|
||||
}
|
||||
if (useNP) {
|
||||
nes1_NP->SetClock(rate);
|
||||
nes1_NP->SetRate(rate);
|
||||
nes2_NP->SetClock(rate);
|
||||
nes2_NP->SetRate(rate);
|
||||
nes2_NP->SetPal(apuType==1);
|
||||
} else {
|
||||
nes->apu.type=apuType;
|
||||
}
|
||||
chipClock=rate;
|
||||
for (int i=0; i<5; i++) {
|
||||
oscBuf[i]->rate=rate/32;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNES::notifyInsDeletion(void* ins) {
|
||||
|
@ -527,25 +704,102 @@ void DivPlatformNES::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformNES::setNSFPlay(bool use) {
|
||||
useNP=use;
|
||||
}
|
||||
|
||||
unsigned char DivPlatformNES::readDMC(unsigned short addr) {
|
||||
return dpcmMem[(addr&0x3fff)|((dpcmBank&15)<<14)];
|
||||
}
|
||||
|
||||
const void* DivPlatformNES::getSampleMem(int index) {
|
||||
return index==0?dpcmMem:NULL;
|
||||
}
|
||||
|
||||
size_t DivPlatformNES::getSampleMemCapacity(int index) {
|
||||
return index==0?262144:0;
|
||||
}
|
||||
|
||||
size_t DivPlatformNES::getSampleMemUsage(int index) {
|
||||
return index==0?dpcmMemLen:0;
|
||||
}
|
||||
|
||||
void DivPlatformNES::renderSamples() {
|
||||
memset(dpcmMem,0,getSampleMemCapacity(0));
|
||||
|
||||
size_t memPos=0;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
unsigned int paddedLen=(s->lengthDPCM+63)&(~0x3f);
|
||||
logV("%d padded length: %d",i,paddedLen);
|
||||
if ((memPos&(~0x3fff))!=((memPos+paddedLen)&(~0x3fff))) {
|
||||
memPos=(memPos+0x3fff)&(~0x3fff);
|
||||
}
|
||||
if (paddedLen>4081) {
|
||||
paddedLen=4096;
|
||||
}
|
||||
if (memPos>=getSampleMemCapacity(0)) {
|
||||
logW("out of DPCM memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
if (memPos+paddedLen>=getSampleMemCapacity(0)) {
|
||||
memcpy(dpcmMem+memPos,s->dataDPCM,getSampleMemCapacity(0)-memPos);
|
||||
logW("out of DPCM memory for sample %d!",i);
|
||||
} else {
|
||||
memcpy(dpcmMem+memPos,s->dataDPCM,MIN(s->lengthDPCM,paddedLen));
|
||||
}
|
||||
s->offDPCM=memPos;
|
||||
memPos+=paddedLen;
|
||||
}
|
||||
dpcmMemLen=memPos;
|
||||
}
|
||||
|
||||
int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
apuType=flags;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
nes=new struct NESAPU;
|
||||
if (useNP) {
|
||||
nes1_NP=new xgm::NES_APU;
|
||||
nes1_NP->SetOption(xgm::NES_APU::OPT_NONLINEAR_MIXER,1);
|
||||
nes2_NP=new xgm::NES_DMC;
|
||||
nes2_NP->SetOption(xgm::NES_DMC::OPT_NONLINEAR_MIXER,1);
|
||||
nes2_NP->SetMemory([this](unsigned short addr, unsigned int& data) {
|
||||
data=readDMC(addr);
|
||||
});
|
||||
nes2_NP->SetAPU(nes1_NP);
|
||||
} else {
|
||||
nes=new struct NESAPU;
|
||||
nes->readDMC=_readDMC;
|
||||
nes->readDMCUser=this;
|
||||
}
|
||||
writeOscBuf=0;
|
||||
for (int i=0; i<5; i++) {
|
||||
isMuted[i]=false;
|
||||
nes->muted[i]=false;
|
||||
if (!useNP) nes->muted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
||||
dpcmMem=new unsigned char[262144];
|
||||
dpcmMemLen=0;
|
||||
dpcmBank=0;
|
||||
|
||||
init_nla_table(500,500);
|
||||
reset();
|
||||
return 5;
|
||||
}
|
||||
|
||||
void DivPlatformNES::quit() {
|
||||
delete nes;
|
||||
for (int i=0; i<5; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
if (useNP) {
|
||||
delete nes1_NP;
|
||||
delete nes2_NP;
|
||||
} else {
|
||||
delete nes;
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformNES::~DivPlatformNES() {
|
||||
|
|
|
@ -23,17 +23,24 @@
|
|||
#include "../dispatch.h"
|
||||
#include "../macroInt.h"
|
||||
|
||||
#include "sound/nes_nsfplay/nes_apu.h"
|
||||
|
||||
class DivPlatformNES: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, prevFreq, note;
|
||||
unsigned char ins, duty, sweep;
|
||||
int freq, baseFreq, pitch, pitch2, prevFreq, note, ins;
|
||||
unsigned char duty, sweep;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
prevFreq(65535),
|
||||
note(0),
|
||||
ins(-1),
|
||||
|
@ -52,36 +59,57 @@ class DivPlatformNES: public DivDispatch {
|
|||
wave(-1) {}
|
||||
};
|
||||
Channel chan[5];
|
||||
DivDispatchOscBuffer* oscBuf[5];
|
||||
bool isMuted[5];
|
||||
int dacPeriod, dacRate;
|
||||
unsigned int dacPos, dacAntiClick;
|
||||
int dacSample;
|
||||
unsigned char* dpcmMem;
|
||||
size_t dpcmMemLen;
|
||||
unsigned char dpcmBank;
|
||||
unsigned char sampleBank;
|
||||
unsigned char writeOscBuf;
|
||||
unsigned char apuType;
|
||||
bool dpcmMode;
|
||||
bool dacAntiClickOn;
|
||||
bool useNP;
|
||||
struct NESAPU* nes;
|
||||
xgm::NES_APU* nes1_NP;
|
||||
xgm::NES_DMC* nes2_NP;
|
||||
unsigned char regPool[128];
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
void doWrite(unsigned short addr, unsigned char data);
|
||||
unsigned char calcDPCMRate(int inRate);
|
||||
void acquire_puNES(short* bufL, short* bufR, size_t start, size_t len);
|
||||
void acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len);
|
||||
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
float getPostAmp();
|
||||
unsigned char readDMC(unsigned short addr);
|
||||
void setNSFPlay(bool use);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index);
|
||||
size_t getSampleMemCapacity(int index);
|
||||
size_t getSampleMemUsage(int index);
|
||||
void renderSamples();
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformNES();
|
||||
|
|
|
@ -52,6 +52,10 @@ const unsigned short chanMapOPL2Drums[20]={
|
|||
0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, N, N, N, N, N, N, N, N, N
|
||||
};
|
||||
|
||||
const unsigned char outChanMapOPL2[18]={
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N
|
||||
};
|
||||
|
||||
const unsigned char* slotsOPL2[4]={
|
||||
slotsOPL2i[0],
|
||||
slotsOPL2i[1],
|
||||
|
@ -88,6 +92,10 @@ const unsigned short chanMapOPL3Drums[20]={
|
|||
0, 3, 1, 4, 2, 5, 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, 0x106, 0x107, 0x108, 6, 7, 8, 8, 7
|
||||
};
|
||||
|
||||
const unsigned char outChanMapOPL3[18]={
|
||||
0, 3, 1, 4, 2, 5, 9, 12, 10, 13, 11, 14, 15, 16, 17, 6, 7, 8
|
||||
};
|
||||
|
||||
const unsigned char* slotsOPL3[4]={
|
||||
slotsOPL3i[0],
|
||||
slotsOPL3i[1],
|
||||
|
@ -189,6 +197,48 @@ const char* DivPlatformOPL::getEffectName(unsigned char effect) {
|
|||
case 0x1d:
|
||||
return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x2a:
|
||||
return "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 3 in OPL2 and 0 to 7 in OPL3)";
|
||||
break;
|
||||
case 0x30:
|
||||
return "30xx: Toggle hard envelope reset on new notes";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set vibrato (x: operator from 1 to 4 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set key scale level (x: operator from 1 to 4 (0 for all ops); y: level from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set envelope sustain (x: operator from 1 to 4 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x59:
|
||||
return "59xx: Set decay of operator 3 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x5a:
|
||||
return "5Axx: Set decay of operator 4 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 4 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -208,6 +258,20 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
}
|
||||
|
||||
OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1];
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
unsigned char ch=outChanMap[i];
|
||||
if (ch==255) continue;
|
||||
oscBuf[i]->data[oscBuf[i]->needle]=0;
|
||||
if (fm.channel[i].out[0]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0];
|
||||
}
|
||||
if (fm.channel[i].out[1]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1];
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle]<<=1;
|
||||
oscBuf[i]->needle++;
|
||||
}
|
||||
|
||||
if (os[0]<-32768) os[0]=-32768;
|
||||
if (os[0]>32767) os[0]=32767;
|
||||
|
@ -228,7 +292,7 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
//}
|
||||
}
|
||||
|
||||
void DivPlatformOPL::tick() {
|
||||
void DivPlatformOPL::tick(bool sysTick) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2;
|
||||
chan[i].std.next();
|
||||
|
@ -269,6 +333,26 @@ void DivPlatformOPL::tick() {
|
|||
}
|
||||
}
|
||||
|
||||
if (oplType==3 && chan[i].std.panL.had) {
|
||||
chan[i].pan=((chan[i].std.panL.val&1)<<1)|((chan[i].std.panL.val&2)>>1);
|
||||
}
|
||||
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.alg.had) {
|
||||
chan[i].state.alg=chan[i].std.alg.val;
|
||||
}
|
||||
|
@ -276,7 +360,7 @@ void DivPlatformOPL::tick() {
|
|||
chan[i].state.fb=chan[i].std.fb.val;
|
||||
}
|
||||
|
||||
if (chan[i].std.alg.had || chan[i].std.fb.had) {
|
||||
if (chan[i].std.alg.had || chan[i].std.fb.had || (oplType==3 && chan[i].std.panL.had)) {
|
||||
if (isMuted[i]) {
|
||||
rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1));
|
||||
if (ops==4) {
|
||||
|
@ -403,9 +487,9 @@ void DivPlatformOPL::tick() {
|
|||
bool updateDrums=false;
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq));
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
||||
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||
int freqt=toFreq(chan[i].freq);
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
chan[i].freqH=freqt>>8;
|
||||
chan[i].freqL=freqt&0xff;
|
||||
immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL);
|
||||
|
@ -480,6 +564,9 @@ int DivPlatformOPL::toFreq(int freq) {
|
|||
|
||||
void DivPlatformOPL::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
if (oplType<3 && ch<melodicChans) {
|
||||
fm.channel[outChanMap[ch]].muted=mute;
|
||||
}
|
||||
int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2;
|
||||
chan[ch].fourOp=(ops==4);
|
||||
update4OpMask=true;
|
||||
|
@ -521,19 +608,22 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
}
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPL);
|
||||
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].state=ins->fm;
|
||||
}
|
||||
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (!chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
if (chan[c.chan].insChanged) {
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
chan[c.chan].fourOp=(ops==4);
|
||||
if (chan[c.chan].fourOp) {
|
||||
chan[c.chan+1].macroInit(NULL);
|
||||
}
|
||||
update4OpMask=true;
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
|
@ -657,10 +747,10 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (oplType!=3) break;
|
||||
if (c.value==0) {
|
||||
if (c.value==0 && c.value2==0) {
|
||||
chan[c.chan].pan=3;
|
||||
} else {
|
||||
chan[c.chan].pan=(((c.value&15)>0)<<1)|((c.value>>4)>0);
|
||||
chan[c.chan].pan=(c.value>0)|((c.value2>0)<<1);
|
||||
}
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (isMuted[c.chan]) {
|
||||
|
@ -796,6 +886,222 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DR: {
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
op.dr=c.value2&15;
|
||||
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
|
||||
}
|
||||
} else {
|
||||
if (c.value>=ops) break;
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
|
||||
op.dr=c.value2&15;
|
||||
unsigned char slot=slots[c.value][c.chan];
|
||||
if (slot==255) break;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SL: {
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
op.sl=c.value2&15;
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
|
||||
}
|
||||
} else {
|
||||
if (c.value>=ops) break;
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
|
||||
op.sl=c.value2&15;
|
||||
unsigned char slot=slots[c.value][c.chan];
|
||||
if (slot==255) break;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RR: {
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
op.rr=c.value2&15;
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
|
||||
}
|
||||
} else {
|
||||
if (c.value>=ops) break;
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
|
||||
op.rr=c.value2&15;
|
||||
unsigned char slot=slots[c.value][c.chan];
|
||||
if (slot==255) break;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AM: {
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
op.am=c.value2&1;
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
}
|
||||
} else {
|
||||
if (c.value>=ops) break;
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
|
||||
op.am=c.value2&1;
|
||||
unsigned char slot=slots[c.value][c.chan];
|
||||
if (slot==255) break;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_VIB: {
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
op.vib=c.value2&1;
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
}
|
||||
} else {
|
||||
if (c.value>=ops) break;
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
|
||||
op.vib=c.value2&1;
|
||||
unsigned char slot=slots[c.value][c.chan];
|
||||
if (slot==255) break;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SUS: {
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
op.sus=c.value2&1;
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
}
|
||||
} else {
|
||||
if (c.value>=ops) break;
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
|
||||
op.sus=c.value2&1;
|
||||
unsigned char slot=slots[c.value][c.chan];
|
||||
if (slot==255) break;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_KSR: {
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
op.ksr=c.value2&1;
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
}
|
||||
} else {
|
||||
if (c.value>=ops) break;
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
|
||||
op.ksr=c.value2&1;
|
||||
unsigned char slot=slots[c.value][c.chan];
|
||||
if (slot==255) break;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_WS: {
|
||||
if (oplType<2) break;
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
op.ws=c.value2&7;
|
||||
rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3));
|
||||
}
|
||||
} else {
|
||||
if (c.value>=ops) break;
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
|
||||
op.ws=c.value2&7;
|
||||
unsigned char slot=slots[c.value][c.chan];
|
||||
if (slot==255) break;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RS: {
|
||||
if (oplType<2) break;
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
op.ksl=c.value2&3;
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
|
||||
} else {
|
||||
if (isOutputL[ops==4][chan[c.chan].state.alg][i]) {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[c.chan].outVol&0x3f))/63))|(op.ksl<<6));
|
||||
} else {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (c.value>=ops) break;
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
|
||||
op.ksl=c.value2&3;
|
||||
unsigned char slot=slots[c.value][c.chan];
|
||||
if (slot==255) break;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
|
||||
} else {
|
||||
if (isOutputL[ops==4][chan[c.chan].state.alg][c.value]) {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[c.chan].outVol&0x3f))/63))|(op.ksl<<6));
|
||||
} else {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_EXTCH: {
|
||||
if (!properDrumsSys) break;
|
||||
properDrums=c.value;
|
||||
|
@ -894,6 +1200,11 @@ void* DivPlatformOPL::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) {
|
||||
if (ch>=18) return NULL;
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformOPL::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -918,19 +1229,26 @@ void DivPlatformOPL::reset() {
|
|||
properDrums=properDrumsSys;
|
||||
if (oplType==3) {
|
||||
chanMap=properDrums?chanMapOPL3Drums:chanMapOPL3;
|
||||
outChanMap=outChanMapOPL3;
|
||||
melodicChans=properDrums?15:18;
|
||||
totalChans=properDrums?20:18;
|
||||
} else {
|
||||
chanMap=properDrums?chanMapOPL2Drums:chanMapOPL2;
|
||||
outChanMap=outChanMapOPL2;
|
||||
melodicChans=properDrums?6:9;
|
||||
totalChans=properDrums?11:9;
|
||||
}
|
||||
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
chan[i]=DivPlatformOPL::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].vol=0x3f;
|
||||
chan[i].outVol=0x3f;
|
||||
}
|
||||
|
||||
if (oplType<3) for (int i=0; i<melodicChans; i++) {
|
||||
fm.channel[outChanMap[i]].muted=isMuted[i];
|
||||
}
|
||||
|
||||
for (int i=0; i<512; i++) {
|
||||
oldWrites[i]=-1;
|
||||
|
@ -1010,6 +1328,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
|||
slotsDrums=slotsOPL2Drums;
|
||||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=drums?chanMapOPL2Drums:chanMapOPL2;
|
||||
outChanMap=outChanMapOPL2;
|
||||
chipFreqBase=9440540*0.25;
|
||||
chans=9;
|
||||
melodicChans=drums?6:9;
|
||||
|
@ -1020,6 +1339,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
|||
slotsDrums=slotsOPL3Drums;
|
||||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=drums?chanMapOPL3Drums:chanMapOPL3;
|
||||
outChanMap=outChanMapOPL3;
|
||||
chipFreqBase=9440540;
|
||||
chans=18;
|
||||
melodicChans=drums?15:18;
|
||||
|
@ -1072,6 +1392,10 @@ void DivPlatformOPL::setFlags(unsigned int flags) {
|
|||
rate=48000;
|
||||
chipClock=rate*288;
|
||||
}
|
||||
|
||||
for (int i=0; i<18; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
|
@ -1081,6 +1405,9 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
for (int i=0; i<20; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
for (int i=0; i<18; i++) {
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
||||
reset();
|
||||
|
@ -1088,6 +1415,9 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
}
|
||||
|
||||
void DivPlatformOPL::quit() {
|
||||
for (int i=0; i<18; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformOPL::~DivPlatformOPL() {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include "../dispatch.h"
|
||||
#include "../macroInt.h"
|
||||
#include <queue>
|
||||
#include "../../../extern/Nuked-OPL3/opl3.h"
|
||||
#include "../../../extern/opl/opl3.h"
|
||||
|
||||
class DivPlatformOPL: public DivDispatch {
|
||||
protected:
|
||||
|
@ -30,17 +30,21 @@ class DivPlatformOPL: public DivDispatch {
|
|||
DivInstrumentFM state;
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, fourOp;
|
||||
int vol, outVol;
|
||||
unsigned char pan;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freqH(0),
|
||||
freqL(0),
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
active(false),
|
||||
|
@ -58,6 +62,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
}
|
||||
};
|
||||
Channel chan[20];
|
||||
DivDispatchOscBuffer* oscBuf[18];
|
||||
bool isMuted[20];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
|
@ -71,6 +76,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
const unsigned char** slotsDrums;
|
||||
const unsigned char** slots;
|
||||
const unsigned short* chanMap;
|
||||
const unsigned char* outChanMap;
|
||||
double chipFreqBase;
|
||||
int delay, oplType, chans, melodicChans, totalChans;
|
||||
unsigned char lastBusy;
|
||||
|
@ -100,11 +106,12 @@ class DivPlatformOPL: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
void setYMFM(bool use);
|
||||
|
|
|
@ -55,6 +55,36 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) {
|
|||
case 0x1b:
|
||||
return "1Bxx: Set attack of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 2 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 2 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 2 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set vibrato (x: operator from 1 to 2 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set key scale level (x: operator from 1 to 2 (0 for all ops); y: level from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set envelope sustain (x: operator from 1 to 2 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 2 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -94,7 +124,10 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size
|
|||
OPLL_Clock(&fm,o);
|
||||
unsigned char nextOut=cycleMapOPLL[fm.cycles];
|
||||
if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) {
|
||||
oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6;
|
||||
os+=(o[0]+o[1]);
|
||||
} else {
|
||||
oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0;
|
||||
}
|
||||
}
|
||||
os*=50;
|
||||
|
@ -111,7 +144,7 @@ void DivPlatformOPLL::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
acquire_nuked(bufL,bufR,start,len);
|
||||
}
|
||||
|
||||
void DivPlatformOPLL::tick() {
|
||||
void DivPlatformOPLL::tick(bool sysTick) {
|
||||
for (int i=0; i<11; i++) {
|
||||
chan[i].std.next();
|
||||
|
||||
|
@ -145,6 +178,22 @@ void DivPlatformOPLL::tick() {
|
|||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].state.opllPreset==0) {
|
||||
if (chan[i].std.alg.had) { // SUS
|
||||
chan[i].state.alg=chan[i].std.alg.val;
|
||||
|
@ -251,9 +300,9 @@ void DivPlatformOPLL::tick() {
|
|||
|
||||
for (int i=0; i<11; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq));
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2);
|
||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
||||
int freqt=toFreq(chan[i].freq);
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
chan[i].freqL=freqt&0xff;
|
||||
if (i>=6 && properDrums) {
|
||||
immWrite(0x10+drumSlot[i],freqt&0xff);
|
||||
|
@ -355,12 +404,12 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
|
|||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPLL);
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].state=ins->fm;
|
||||
}
|
||||
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (!chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
|
@ -428,6 +477,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
|
|||
if (drums) {
|
||||
drums=false;
|
||||
immWrite(0x0e,0);
|
||||
drumState=0;
|
||||
}
|
||||
}
|
||||
if (c.chan<9) {
|
||||
|
@ -618,6 +668,150 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
|
|||
rWrite(0x05,(car.ar<<4)|(car.dr));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DR: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
|
||||
DivInstrumentFM::Operator& car=chan[c.chan].state.op[1];
|
||||
if (c.value<0) {
|
||||
mod.dr=c.value2&15;
|
||||
car.dr=c.value2&15;
|
||||
} else {
|
||||
if (c.value==0) {
|
||||
mod.dr=c.value2&15;
|
||||
} else {
|
||||
car.dr=c.value2&15;
|
||||
}
|
||||
}
|
||||
rWrite(0x04,(mod.ar<<4)|(mod.dr));
|
||||
rWrite(0x05,(car.ar<<4)|(car.dr));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SL: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
|
||||
DivInstrumentFM::Operator& car=chan[c.chan].state.op[1];
|
||||
if (c.value<0) {
|
||||
mod.sl=c.value2&15;
|
||||
car.sl=c.value2&15;
|
||||
} else {
|
||||
if (c.value==0) {
|
||||
mod.sl=c.value2&15;
|
||||
} else {
|
||||
car.sl=c.value2&15;
|
||||
}
|
||||
}
|
||||
rWrite(0x06,(mod.sl<<4)|(mod.rr));
|
||||
rWrite(0x07,(car.sl<<4)|(car.rr));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RR: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
|
||||
DivInstrumentFM::Operator& car=chan[c.chan].state.op[1];
|
||||
if (c.value<0) {
|
||||
mod.rr=c.value2&15;
|
||||
car.rr=c.value2&15;
|
||||
} else {
|
||||
if (c.value==0) {
|
||||
mod.rr=c.value2&15;
|
||||
} else {
|
||||
car.rr=c.value2&15;
|
||||
}
|
||||
}
|
||||
rWrite(0x06,(mod.sl<<4)|(mod.rr));
|
||||
rWrite(0x07,(car.sl<<4)|(car.rr));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AM: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
|
||||
DivInstrumentFM::Operator& car=chan[c.chan].state.op[1];
|
||||
if (c.value<0) {
|
||||
mod.am=c.value2&1;
|
||||
car.am=c.value2&1;
|
||||
} else {
|
||||
if (c.value==0) {
|
||||
mod.am=c.value2&1;
|
||||
} else {
|
||||
car.am=c.value2&1;
|
||||
}
|
||||
}
|
||||
rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult));
|
||||
rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_VIB: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
|
||||
DivInstrumentFM::Operator& car=chan[c.chan].state.op[1];
|
||||
if (c.value<0) {
|
||||
mod.vib=c.value2&1;
|
||||
car.vib=c.value2&1;
|
||||
} else {
|
||||
if (c.value==0) {
|
||||
mod.vib=c.value2&1;
|
||||
} else {
|
||||
car.vib=c.value2&1;
|
||||
}
|
||||
}
|
||||
rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult));
|
||||
rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_KSR: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
|
||||
DivInstrumentFM::Operator& car=chan[c.chan].state.op[1];
|
||||
if (c.value<0) {
|
||||
mod.ksr=c.value2&1;
|
||||
car.ksr=c.value2&1;
|
||||
} else {
|
||||
if (c.value==0) {
|
||||
mod.ksr=c.value2&1;
|
||||
} else {
|
||||
car.ksr=c.value2&1;
|
||||
}
|
||||
}
|
||||
rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult));
|
||||
rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SUS: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
|
||||
DivInstrumentFM::Operator& car=chan[c.chan].state.op[1];
|
||||
if (c.value<0) {
|
||||
mod.ssgEnv=c.value2?8:0;
|
||||
car.ssgEnv=c.value2?8:0;
|
||||
} else {
|
||||
if (c.value==0) {
|
||||
mod.ssgEnv=c.value2?8:0;
|
||||
} else {
|
||||
car.ssgEnv=c.value2?8:0;
|
||||
}
|
||||
}
|
||||
rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult));
|
||||
rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RS: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
|
||||
DivInstrumentFM::Operator& car=chan[c.chan].state.op[1];
|
||||
if (c.value<0) {
|
||||
mod.ksl=c.value2&3;
|
||||
car.ksl=c.value2&3;
|
||||
} else {
|
||||
if (c.value==0) {
|
||||
mod.ksl=c.value2&3;
|
||||
} else {
|
||||
car.ksl=c.value2&3;
|
||||
}
|
||||
}
|
||||
rWrite(0x02,(mod.ksl<<6)|(mod.tl&63));
|
||||
rWrite(0x03,(car.ksl<<6)|((chan[c.chan].state.fms&1)<<4)|((chan[c.chan].state.ams&1)<<3)|chan[c.chan].state.fb);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_EXTCH:
|
||||
if (!properDrumsSys) break;
|
||||
if ((int)properDrums==c.value) break;
|
||||
|
@ -715,6 +909,11 @@ void* DivPlatformOPLL::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformOPLL::getOscBuffer(int ch) {
|
||||
if (ch>=9) return NULL;
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformOPLL::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -750,6 +949,7 @@ void DivPlatformOPLL::reset() {
|
|||
}
|
||||
for (int i=0; i<11; i++) {
|
||||
chan[i]=DivPlatformOPLL::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].vol=15;
|
||||
chan[i].outVol=15;
|
||||
}
|
||||
|
@ -825,6 +1025,9 @@ void DivPlatformOPLL::setFlags(unsigned int flags) {
|
|||
}
|
||||
rate=chipClock/36;
|
||||
patchSet=flags>>4;
|
||||
for (int i=0; i<11; i++) {
|
||||
oscBuf[i]->rate=rate/2;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
|
@ -834,14 +1037,18 @@ int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int
|
|||
patchSet=0;
|
||||
for (int i=0; i<11; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
||||
reset();
|
||||
return 10;
|
||||
return 11;
|
||||
}
|
||||
|
||||
void DivPlatformOPLL::quit() {
|
||||
for (int i=0; i<11; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformOPLL::~DivPlatformOPLL() {
|
||||
|
|
|
@ -33,17 +33,21 @@ class DivPlatformOPLL: public DivDispatch {
|
|||
DivInstrumentFM state;
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta;
|
||||
int vol, outVol;
|
||||
unsigned char pan;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freqH(0),
|
||||
freqL(0),
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
active(false),
|
||||
|
@ -59,6 +63,7 @@ class DivPlatformOPLL: public DivDispatch {
|
|||
};
|
||||
Channel chan[11];
|
||||
bool isMuted[11];
|
||||
DivDispatchOscBuffer* oscBuf[11];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
|
@ -96,11 +101,12 @@ class DivPlatformOPLL: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setYMFM(bool use);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
|
|
|
@ -116,6 +116,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
pce->Update(24);
|
||||
pce->ResetTS(0);
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1;
|
||||
}
|
||||
|
||||
tempL[0]=(tempL[0]>>1)+(tempL[0]>>2);
|
||||
tempR[0]=(tempR[0]>>1)+(tempR[0]>>2);
|
||||
|
||||
|
@ -146,7 +150,7 @@ static unsigned char noiseFreq[12]={
|
|||
4,13,15,18,21,23,25,27,29,31,0,2
|
||||
};
|
||||
|
||||
void DivPlatformPCE::tick() {
|
||||
void DivPlatformPCE::tick(bool sysTick) {
|
||||
for (int i=0; i<6; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -196,14 +200,34 @@ void DivPlatformPCE::tick() {
|
|||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].pan&=0x0f;
|
||||
chan[i].pan|=(chan[i].std.panL.val&15)<<4;
|
||||
}
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[i].pan&=0xf0;
|
||||
chan[i].pan|=chan[i].std.panR.val&15;
|
||||
}
|
||||
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||
chWrite(i,0x05,isMuted[i]?0:chan[i].pan);
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].active) {
|
||||
if (chan[i].ws.tick()) {
|
||||
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
if (chan[i].furnaceDac) {
|
||||
double off=1.0;
|
||||
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
|
||||
|
@ -237,7 +261,7 @@ void DivPlatformPCE::tick() {
|
|||
int DivPlatformPCE::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE);
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
chan[c.chan].pcm=true;
|
||||
} else if (chan[c.chan].furnaceDac) {
|
||||
|
@ -265,7 +289,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
//chan[c.chan].keyOn=true;
|
||||
chan[c.chan].furnaceDac=true;
|
||||
} else {
|
||||
|
@ -302,7 +326,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chWrite(c.chan,0x04,0x80|chan[c.chan].vol);
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (chan[c.chan].wave<0) {
|
||||
chan[c.chan].wave=0;
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
|
@ -317,7 +341,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
chan[c.chan].pcm=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -404,7 +428,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
chan[c.chan].pan=c.value;
|
||||
chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4);
|
||||
chWrite(c.chan,0x05,isMuted[c.chan]?0:chan[c.chan].pan);
|
||||
break;
|
||||
}
|
||||
|
@ -415,7 +439,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -449,6 +473,10 @@ void* DivPlatformPCE::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformPCE::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformPCE::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -462,6 +490,7 @@ void DivPlatformPCE::reset() {
|
|||
memset(regPool,0,128);
|
||||
for (int i=0; i<6; i++) {
|
||||
chan[i]=DivPlatformPCE::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].ws.setEngine(parent);
|
||||
chan[i].ws.init(NULL,32,31,false);
|
||||
}
|
||||
|
@ -520,6 +549,9 @@ void DivPlatformPCE::setFlags(unsigned int flags) {
|
|||
chipClock=COLOR_NTSC;
|
||||
}
|
||||
rate=chipClock/12;
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPCE::poke(unsigned int addr, unsigned short val) {
|
||||
|
@ -536,6 +568,7 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
skipRegisterWrites=false;
|
||||
for (int i=0; i<6; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
pce=new PCE_PSG(tempL,tempR,PCE_PSG::REVISION_HUC6280A);
|
||||
|
@ -544,6 +577,9 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
}
|
||||
|
||||
void DivPlatformPCE::quit() {
|
||||
for (int i=0; i<6; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete pce;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,19 +28,24 @@
|
|||
|
||||
class DivPlatformPCE: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int dacPeriod, dacRate;
|
||||
unsigned int dacPos;
|
||||
int dacSample;
|
||||
unsigned char ins, pan;
|
||||
int dacSample, ins;
|
||||
unsigned char pan;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
dacPeriod(0),
|
||||
dacRate(0),
|
||||
|
@ -62,6 +67,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
wave(-1) {}
|
||||
};
|
||||
Channel chan[6];
|
||||
DivDispatchOscBuffer* oscBuf[6];
|
||||
bool isMuted[6];
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
|
@ -83,11 +89,12 @@ class DivPlatformPCE: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
|
|
|
@ -50,9 +50,11 @@ const float cut=0.05;
|
|||
const float reso=0.06;
|
||||
|
||||
void DivPlatformPCSpeaker::acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int out=0;
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
if (on) {
|
||||
pos-=PCSPKR_DIVIDER;
|
||||
if (pos>freq) pos=freq;
|
||||
while (pos<0) {
|
||||
if (freq<1) {
|
||||
pos=1;
|
||||
|
@ -60,9 +62,12 @@ void DivPlatformPCSpeaker::acquire_unfilt(short* bufL, short* bufR, size_t start
|
|||
pos+=freq;
|
||||
}
|
||||
}
|
||||
bufL[i]=(pos>(freq>>1) && !isMuted[0])?32767:0;
|
||||
out=(pos>(freq>>1) && !isMuted[0])?32767:0;
|
||||
bufL[i]=out;
|
||||
oscBuf->data[oscBuf->needle++]=out;
|
||||
} else {
|
||||
bufL[i]=0;
|
||||
oscBuf->data[oscBuf->needle++]=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +76,7 @@ void DivPlatformPCSpeaker::acquire_cone(short* bufL, short* bufR, size_t start,
|
|||
for (size_t i=start; i<start+len; i++) {
|
||||
if (on) {
|
||||
pos-=PCSPKR_DIVIDER;
|
||||
if (pos>freq) pos=freq;
|
||||
while (pos<0) {
|
||||
if (freq<1) {
|
||||
pos=1;
|
||||
|
@ -85,8 +91,10 @@ void DivPlatformPCSpeaker::acquire_cone(short* bufL, short* bufR, size_t start,
|
|||
if (out>1.0) out=1.0;
|
||||
if (out<-1.0) out=-1.0;
|
||||
bufL[i]=out*32767;
|
||||
oscBuf->data[oscBuf->needle++]=out*32767;
|
||||
} else {
|
||||
bufL[i]=0;
|
||||
oscBuf->data[oscBuf->needle++]=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +103,7 @@ void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start,
|
|||
for (size_t i=start; i<start+len; i++) {
|
||||
if (on) {
|
||||
pos-=PCSPKR_DIVIDER;
|
||||
if (pos>freq) pos=freq;
|
||||
while (pos<0) {
|
||||
if (freq<1) {
|
||||
pos=1;
|
||||
|
@ -109,8 +118,10 @@ void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start,
|
|||
if (out>1.0) out=1.0;
|
||||
if (out<-1.0) out=-1.0;
|
||||
bufL[i]=out*32767;
|
||||
oscBuf->data[oscBuf->needle++]=out*32767;
|
||||
} else {
|
||||
bufL[i]=0;
|
||||
oscBuf->data[oscBuf->needle++]=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,12 +148,28 @@ void DivPlatformPCSpeaker::beepFreq(int freq) {
|
|||
}
|
||||
|
||||
void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int out=0;
|
||||
if (lastOn!=on || lastFreq!=freq) {
|
||||
lastOn=on;
|
||||
lastFreq=freq;
|
||||
beepFreq((on && !isMuted[0])?freq:0);
|
||||
}
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
if (on) {
|
||||
pos-=PCSPKR_DIVIDER;
|
||||
if (pos>freq) pos=freq;
|
||||
while (pos<0) {
|
||||
if (freq<1) {
|
||||
pos=1;
|
||||
} else {
|
||||
pos+=freq;
|
||||
}
|
||||
}
|
||||
out=(pos>(freq>>1) && !isMuted[0])?32767:0;
|
||||
oscBuf->data[oscBuf->needle++]=out;
|
||||
} else {
|
||||
oscBuf->data[oscBuf->needle++]=0;
|
||||
}
|
||||
bufL[i]=0;
|
||||
}
|
||||
}
|
||||
|
@ -164,7 +191,7 @@ void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformPCSpeaker::tick() {
|
||||
void DivPlatformPCSpeaker::tick(bool sysTick) {
|
||||
for (int i=0; i<1; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -186,8 +213,17 @@ void DivPlatformPCSpeaker::tick() {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1;
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
if (chan[i].keyOn) {
|
||||
|
@ -214,12 +250,12 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER));
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -279,7 +315,7 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -309,6 +345,10 @@ void* DivPlatformPCSpeaker::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformPCSpeaker::getOscBuffer(int ch) {
|
||||
return oscBuf;
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformPCSpeaker::getRegisterPool() {
|
||||
if (on) {
|
||||
regPool[0]=freq;
|
||||
|
@ -327,6 +367,7 @@ int DivPlatformPCSpeaker::getRegisterPoolSize() {
|
|||
void DivPlatformPCSpeaker::reset() {
|
||||
for (int i=0; i<1; i++) {
|
||||
chan[i]=DivPlatformPCSpeaker::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
|
@ -366,6 +407,7 @@ void DivPlatformPCSpeaker::setFlags(unsigned int flags) {
|
|||
chipClock=COLOR_NTSC/3.0;
|
||||
rate=chipClock/PCSPKR_DIVIDER;
|
||||
speakerType=flags&3;
|
||||
oscBuf->rate=rate;
|
||||
}
|
||||
|
||||
void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) {
|
||||
|
@ -394,6 +436,7 @@ int DivPlatformPCSpeaker::init(DivEngine* p, int channels, int sugRate, unsigned
|
|||
for (int i=0; i<1; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
oscBuf=new DivDispatchOscBuffer;
|
||||
setFlags(flags);
|
||||
|
||||
reset();
|
||||
|
@ -407,6 +450,7 @@ void DivPlatformPCSpeaker::quit() {
|
|||
#ifdef __linux__
|
||||
if (beepFD>=0) close(beepFD);
|
||||
#endif
|
||||
delete oscBuf;
|
||||
}
|
||||
|
||||
DivPlatformPCSpeaker::~DivPlatformPCSpeaker() {
|
||||
|
|
|
@ -25,15 +25,20 @@
|
|||
|
||||
class DivPlatformPCSpeaker: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins, duty, sweep;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
unsigned char duty, sweep;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
duty(0),
|
||||
|
@ -51,6 +56,7 @@ class DivPlatformPCSpeaker: public DivDispatch {
|
|||
wave(-1) {}
|
||||
};
|
||||
Channel chan[1];
|
||||
DivDispatchOscBuffer* oscBuf;
|
||||
bool isMuted[1];
|
||||
bool on, flip, lastOn;
|
||||
int pos, speakerType, beepFD;
|
||||
|
@ -73,11 +79,12 @@ class DivPlatformPCSpeaker: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setFlags(unsigned int flags);
|
||||
|
|
|
@ -64,12 +64,14 @@ void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
}
|
||||
bufL[h]=chan.out;
|
||||
bufR[h]=chan.out;
|
||||
oscBuf->data[oscBuf->needle++]=chan.out;
|
||||
}
|
||||
} else {
|
||||
chan.out=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
bufL[h]=0;
|
||||
bufR[h]=0;
|
||||
oscBuf->data[oscBuf->needle++]=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +87,7 @@ void DivPlatformPET::writeOutVol() {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformPET::tick() {
|
||||
void DivPlatformPET::tick(bool sysTick) {
|
||||
chan.std.next();
|
||||
if (chan.std.vol.had) {
|
||||
chan.outVol=chan.std.vol.val&chan.vol;
|
||||
|
@ -112,8 +114,11 @@ void DivPlatformPET::tick() {
|
|||
rWrite(10,chan.wave);
|
||||
}
|
||||
}
|
||||
if (chan.std.pitch.had) {
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
|
||||
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true);
|
||||
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2);
|
||||
if (chan.freq>257) chan.freq=257;
|
||||
if (chan.freq<2) chan.freq=2;
|
||||
rWrite(8,chan.freq-2);
|
||||
|
@ -135,7 +140,7 @@ void DivPlatformPET::tick() {
|
|||
int DivPlatformPET::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan.ins);
|
||||
DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_PET);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan.baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan.freqChanged=true;
|
||||
|
@ -143,13 +148,13 @@ int DivPlatformPET::dispatch(DivCommand c) {
|
|||
}
|
||||
chan.active=true;
|
||||
chan.keyOn=true;
|
||||
chan.std.init(ins);
|
||||
chan.macroInit(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan.active=false;
|
||||
chan.keyOff=true;
|
||||
chan.std.init(NULL);
|
||||
chan.macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -210,7 +215,7 @@ int DivPlatformPET::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan.active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan.std.init(parent->getIns(chan.ins));
|
||||
if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_PET));
|
||||
}
|
||||
chan.inPorta=c.value;
|
||||
break;
|
||||
|
@ -241,6 +246,10 @@ void* DivPlatformPET::getChanState(int ch) {
|
|||
return &chan;
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformPET::getOscBuffer(int ch) {
|
||||
return oscBuf;
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformPET::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -252,6 +261,7 @@ int DivPlatformPET::getRegisterPoolSize() {
|
|||
void DivPlatformPET::reset() {
|
||||
memset(regPool,0,16);
|
||||
chan=Channel();
|
||||
chan.std.setEngine(parent);
|
||||
}
|
||||
|
||||
bool DivPlatformPET::isStereo() {
|
||||
|
@ -277,9 +287,15 @@ int DivPlatformPET::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
chipClock=1000000;
|
||||
rate=chipClock/SAMP_DIVIDER; // = 250000kHz
|
||||
isMuted=false;
|
||||
oscBuf=new DivDispatchOscBuffer;
|
||||
oscBuf->rate=rate;
|
||||
reset();
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformPET::quit() {
|
||||
delete oscBuf;
|
||||
}
|
||||
|
||||
DivPlatformPET::~DivPlatformPET() {
|
||||
}
|
||||
|
|
|
@ -25,18 +25,22 @@
|
|||
|
||||
class DivPlatformPET: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
int vol, outVol, wave;
|
||||
unsigned char sreg;
|
||||
int cnt;
|
||||
short out;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
active(false),
|
||||
|
@ -53,6 +57,7 @@ class DivPlatformPET: public DivDispatch {
|
|||
out(0) {}
|
||||
};
|
||||
Channel chan;
|
||||
DivDispatchOscBuffer* oscBuf;
|
||||
bool isMuted;
|
||||
|
||||
unsigned char regPool[16];
|
||||
|
@ -61,11 +66,12 @@ class DivPlatformPET: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void notifyInsDeletion(void* ins);
|
||||
bool isStereo();
|
||||
|
@ -74,6 +80,7 @@ class DivPlatformPET: public DivDispatch {
|
|||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformPET();
|
||||
private:
|
||||
void writeOutVol();
|
||||
|
|
|
@ -257,6 +257,9 @@ const char* DivPlatformQSound::getEffectName(unsigned char effect) {
|
|||
case 0x11:
|
||||
return "11xx: Set channel echo level (00 to FF)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Toggle QSound algorithm (0: disabled; 1: enabled)";
|
||||
break;
|
||||
default:
|
||||
if ((effect & 0xf0) == 0x30) {
|
||||
return "3xxx: Set echo delay buffer length (000 to AA5)";
|
||||
|
@ -265,16 +268,21 @@ const char* DivPlatformQSound::getEffectName(unsigned char effect) {
|
|||
return NULL;
|
||||
}
|
||||
void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
chip.rom_data = parent->qsoundMem;
|
||||
chip.rom_mask = 0xffffff;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
qsound_update(&chip);
|
||||
bufL[h]=chip.out[0];
|
||||
bufR[h]=chip.out[1];
|
||||
|
||||
for (int i=0; i<19; i++) {
|
||||
int data=chip.voice_output[i]<<2;
|
||||
if (data<-32768) data=-32768;
|
||||
if (data>32767) data=32767;
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformQSound::tick() {
|
||||
void DivPlatformQSound::tick(bool sysTick) {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -326,9 +334,27 @@ void DivPlatformQSound::tick() {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.panL.had) { // panning
|
||||
chan[i].panning=chan[i].std.panL.val+16;
|
||||
}
|
||||
if (chan[i].std.panR.had) { // surround
|
||||
chan[i].surround=chan[i].std.panR.val;
|
||||
}
|
||||
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||
immWrite(Q1_PAN+i,chan[i].panning+0x110+(chan[i].surround?0:0x30));
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false);
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
|
||||
|
@ -360,7 +386,7 @@ void DivPlatformQSound::tick() {
|
|||
int DivPlatformQSound::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
double off=1.0;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
|
@ -383,14 +409,14 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].sample=-1;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -420,7 +446,8 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
immWrite(Q1_PAN+c.chan, c.value + 0x110);
|
||||
chan[c.chan].panning=parent->convertPanSplitToLinearLR(c.value,c.value2,32);
|
||||
immWrite(Q1_PAN+c.chan,chan[c.chan].panning+0x110+(chan[c.chan].surround?0:0x30));
|
||||
break;
|
||||
case DIV_CMD_QSOUND_ECHO_LEVEL:
|
||||
immWrite(Q1_ECHO+c.chan, c.value << 7);
|
||||
|
@ -431,6 +458,10 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
case DIV_CMD_QSOUND_ECHO_DELAY:
|
||||
immWrite(Q1_ECHO_LENGTH, (c.value > 2725 ? 0xfff : 0xfff - (2725 - c.value)));
|
||||
break;
|
||||
case DIV_CMD_QSOUND_SURROUND:
|
||||
chan[c.chan].surround=c.value;
|
||||
immWrite(Q1_PAN+c.chan,chan[c.chan].panning+0x110+(chan[c.chan].surround?0:0x30));
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -484,7 +515,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
}
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -520,9 +551,14 @@ void* DivPlatformQSound::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformQSound::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
void DivPlatformQSound::reset() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i]=DivPlatformQSound::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
qsound_reset(&chip);
|
||||
while(!chip.ready_flag) {
|
||||
|
@ -600,23 +636,79 @@ int DivPlatformQSound::getRegisterPoolDepth() {
|
|||
return 16;
|
||||
}
|
||||
|
||||
const void* DivPlatformQSound::getSampleMem(int index) {
|
||||
return index == 0 ? sampleMem : NULL;
|
||||
}
|
||||
|
||||
size_t DivPlatformQSound::getSampleMemCapacity(int index) {
|
||||
return index == 0 ? 16777216 : 0;
|
||||
}
|
||||
|
||||
size_t DivPlatformQSound::getSampleMemUsage(int index) {
|
||||
return index == 0 ? sampleMemLen : 0;
|
||||
}
|
||||
|
||||
void DivPlatformQSound::renderSamples() {
|
||||
memset(sampleMem,0,getSampleMemCapacity());
|
||||
|
||||
size_t memPos=0;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
int length=s->length8;
|
||||
if (length>65536-16) {
|
||||
length=65536-16;
|
||||
}
|
||||
if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) {
|
||||
memPos=(memPos+0xffff)&0xff0000;
|
||||
}
|
||||
if (memPos>=getSampleMemCapacity()) {
|
||||
logW("out of QSound PCM memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
if (memPos+length>=getSampleMemCapacity()) {
|
||||
for (unsigned int i=0; i<getSampleMemCapacity()-(memPos+length); i++) {
|
||||
sampleMem[(memPos+i)^0x8000]=s->data8[i];
|
||||
}
|
||||
logW("out of QSound PCM memory for sample %d!",i);
|
||||
} else {
|
||||
for (int i=0; i<length; i++) {
|
||||
sampleMem[(memPos+i)^0x8000]=s->data8[i];
|
||||
}
|
||||
}
|
||||
s->offQSound=memPos^0x8000;
|
||||
memPos+=length+16;
|
||||
}
|
||||
sampleMemLen=memPos+256;
|
||||
}
|
||||
|
||||
int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
|
||||
//for (int i=0; i<16; i++) {
|
||||
// isMuted[i]=false;
|
||||
//}
|
||||
for (int i=0; i<19; i++) {
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
//isMuted[i]=false;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
||||
chipClock=60000000;
|
||||
rate = qsound_start(&chip, chipClock);
|
||||
chip.rom_data = (unsigned char*)&chip.rom_mask;
|
||||
chip.rom_mask = 0;
|
||||
sampleMem=new unsigned char[getSampleMemCapacity()];
|
||||
sampleMemLen=0;
|
||||
chip.rom_data=sampleMem;
|
||||
chip.rom_mask=0xffffff;
|
||||
reset();
|
||||
|
||||
for (int i=0; i<19; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
return 19;
|
||||
}
|
||||
|
||||
void DivPlatformQSound::quit() {
|
||||
delete[] sampleMem;
|
||||
for (int i=0; i<19; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,20 +27,24 @@
|
|||
|
||||
class DivPlatformQSound: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch;
|
||||
int freq, baseFreq, pitch, pitch2;
|
||||
unsigned short audLen;
|
||||
unsigned int audPos;
|
||||
int sample, wave;
|
||||
unsigned char ins;
|
||||
int sample, wave, ins;
|
||||
int note;
|
||||
int panning;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, surround;
|
||||
int vol, outVol;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
audLen(0),
|
||||
audPos(0),
|
||||
sample(-1),
|
||||
|
@ -53,13 +57,18 @@ class DivPlatformQSound: public DivDispatch {
|
|||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
useWave(false),
|
||||
surround(true),
|
||||
vol(255),
|
||||
outVol(255) {}
|
||||
};
|
||||
Channel chan[19];
|
||||
DivDispatchOscBuffer* oscBuf[19];
|
||||
int echoDelay;
|
||||
int echoFeedback;
|
||||
|
||||
unsigned char* sampleMem;
|
||||
size_t sampleMemLen;
|
||||
struct qsound_chip chip;
|
||||
unsigned short regPool[512];
|
||||
|
||||
|
@ -69,12 +78,13 @@ class DivPlatformQSound: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
int getRegisterPoolDepth();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
|
@ -86,6 +96,10 @@ class DivPlatformQSound: public DivDispatch {
|
|||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index = 0);
|
||||
size_t getSampleMemCapacity(int index = 0);
|
||||
size_t getSampleMemUsage(int index = 0);
|
||||
void renderSamples();
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
|
|
@ -86,7 +86,7 @@ void DivPlatformSAA1099::acquire_mame(short* bufL, short* bufR, size_t start, si
|
|||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
saa.sound_stream_update(saaBuf,len);
|
||||
saa.sound_stream_update(saaBuf,len,oscBuf);
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=saaBuf[0][i];
|
||||
bufR[i+start]=saaBuf[1][i];
|
||||
|
@ -107,7 +107,7 @@ void DivPlatformSAA1099::acquire_saaSound(short* bufL, short* bufR, size_t start
|
|||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
saa_saaSound->GenerateMany((unsigned char*)saaBuf[0],len);
|
||||
saa_saaSound->GenerateMany((unsigned char*)saaBuf[0],len,oscBuf);
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=saaBuf[0][i<<1];
|
||||
bufR[i+start]=saaBuf[0][1+(i<<1)];
|
||||
|
@ -132,7 +132,7 @@ inline unsigned char applyPan(unsigned char vol, unsigned char pan) {
|
|||
return ((vol*(pan>>4))/15)|(((vol*(pan&15))/15)<<4);
|
||||
}
|
||||
|
||||
void DivPlatformSAA1099::tick() {
|
||||
void DivPlatformSAA1099::tick(bool sysTick) {
|
||||
for (int i=0; i<6; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
@ -166,12 +166,41 @@ void DivPlatformSAA1099::tick() {
|
|||
if (chan[i].std.wave.had) {
|
||||
chan[i].psgMode=chan[i].std.wave.val&3;
|
||||
}
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].pan&=0x0f;
|
||||
chan[i].pan|=(chan[i].std.panL.val&15)<<4;
|
||||
}
|
||||
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[i].pan&=0xf0;
|
||||
chan[i].pan|=chan[i].std.panR.val&15;
|
||||
}
|
||||
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||
if (isMuted[i]) {
|
||||
rWrite(i,0);
|
||||
} else {
|
||||
if (chan[i].std.vol.had) {
|
||||
if (chan[i].active) rWrite(i,applyPan(chan[i].outVol&15,chan[i].pan));
|
||||
} else {
|
||||
if (chan[i].active) rWrite(i,applyPan(chan[i].vol&15,chan[i].pan));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.ex1.had) {
|
||||
saaEnv[i/3]=chan[i].std.ex1.val;
|
||||
rWrite(0x18+(i/3),saaEnv[i/3]);
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
if (chan[i].freq>=32768) {
|
||||
chan[i].freqH=7;
|
||||
|
@ -226,7 +255,7 @@ void DivPlatformSAA1099::tick() {
|
|||
int DivPlatformSAA1099::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SAA1099);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
@ -234,7 +263,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(c.chan,0);
|
||||
} else {
|
||||
|
@ -245,7 +274,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -302,7 +331,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_PANNING:
|
||||
chan[c.chan].pan=c.value;
|
||||
chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4);
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(c.chan,0);
|
||||
} else {
|
||||
|
@ -333,7 +362,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SAA1099));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -369,6 +398,10 @@ void* DivPlatformSAA1099::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformSAA1099::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformSAA1099::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -392,6 +425,7 @@ void DivPlatformSAA1099::reset() {
|
|||
}
|
||||
for (int i=0; i<6; i++) {
|
||||
chan[i]=DivPlatformSAA1099::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].vol=0x0f;
|
||||
}
|
||||
if (dumpWrites) {
|
||||
|
@ -455,6 +489,10 @@ void DivPlatformSAA1099::setFlags(unsigned int flags) {
|
|||
}
|
||||
rate=chipClock/32;
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
||||
switch (core) {
|
||||
case DIV_SAA_CORE_MAME:
|
||||
break;
|
||||
|
@ -486,6 +524,7 @@ int DivPlatformSAA1099::init(DivEngine* p, int channels, int sugRate, unsigned i
|
|||
saa_saaSound=NULL;
|
||||
for (int i=0; i<6; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
if (core==DIV_SAA_CORE_SAASOUND) {
|
||||
saa_saaSound=CreateCSAASound();
|
||||
|
@ -500,6 +539,9 @@ int DivPlatformSAA1099::init(DivEngine* p, int channels, int sugRate, unsigned i
|
|||
}
|
||||
|
||||
void DivPlatformSAA1099::quit() {
|
||||
for (int i=0; i<6; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
if (saa_saaSound!=NULL) {
|
||||
DestroyCSAASound(saa_saaSound);
|
||||
saa_saaSound=NULL;
|
||||
|
|
|
@ -35,16 +35,21 @@ class DivPlatformSAA1099: public DivDispatch {
|
|||
protected:
|
||||
struct Channel {
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins, psgMode;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
unsigned char psgMode;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
int vol, outVol;
|
||||
unsigned char pan;
|
||||
DivMacroInt std;
|
||||
Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), note(0), ins(-1), psgMode(1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(255) {}
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), note(0), ins(-1), psgMode(1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(255) {}
|
||||
};
|
||||
Channel chan[6];
|
||||
DivDispatchOscBuffer* oscBuf[6];
|
||||
bool isMuted[6];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
|
@ -86,11 +91,12 @@ class DivPlatformSAA1099: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setCore(DivSAACores core);
|
||||
void setFlags(unsigned int flags);
|
||||
|
|
|
@ -46,9 +46,11 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
DivSample* s=parent->getSample(chan[i].pcm.sample);
|
||||
if (s->samples<=0) {
|
||||
chan[i].pcm.sample=-1;
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
continue;
|
||||
}
|
||||
if (!isMuted[i]) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=s->data8[chan[i].pcm.pos>>8]*(chan[i].chVolL+chan[i].chVolR)>>1;
|
||||
pcmL+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolL);
|
||||
pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR);
|
||||
}
|
||||
|
@ -60,6 +62,8 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
chan[i].pcm.sample=-1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,13 +80,14 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformSegaPCM::tick() {
|
||||
void DivPlatformSegaPCM::tick(bool sysTick) {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.next();
|
||||
|
||||
if (chan[i].std.vol.had) {
|
||||
// TODO: fix
|
||||
/*if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
|
@ -99,6 +104,30 @@ void DivPlatformSegaPCM::tick() {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].chVolL=chan[i].std.panL.val&127;
|
||||
if (dumpWrites) {
|
||||
addWrite(0x10002+(i<<3),chan[i].chVolL);
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[i].chVolR=chan[i].std.panR.val&127;
|
||||
if (dumpWrites) {
|
||||
addWrite(0x10003+(i<<3),chan[i].chVolR);
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
/*if (chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].keyOff=false;
|
||||
}*/
|
||||
|
@ -113,7 +142,7 @@ void DivPlatformSegaPCM::tick() {
|
|||
DivSample* s=parent->getSample(chan[i].pcm.sample);
|
||||
off=(double)s->centerRate/8363.0;
|
||||
}
|
||||
chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250);
|
||||
chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250)+chan[i].pitch2;
|
||||
if (dumpWrites) {
|
||||
addWrite(0x10007+(i<<3),chan[i].pcm.freq);
|
||||
}
|
||||
|
@ -130,7 +159,7 @@ void DivPlatformSegaPCM::muteChannel(int ch, bool mute) {
|
|||
int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
if (skipRegisterWrites) break;
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
chan[c.chan].pcm.sample=ins->amiga.initSample;
|
||||
|
@ -139,12 +168,17 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
if (dumpWrites) {
|
||||
addWrite(0x10086+(c.chan<<3),3);
|
||||
}
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
}
|
||||
chan[c.chan].pcm.pos=0;
|
||||
chan[c.chan].baseFreq=(c.value<<6);
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=(c.value<<6);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].furnacePCM=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (dumpWrites) { // Sega PCM writes
|
||||
DivSample* s=parent->getSample(chan[c.chan].pcm.sample);
|
||||
addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
|
||||
|
@ -161,6 +195,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].macroInit(NULL);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
|
@ -202,6 +237,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].keyOff=true;
|
||||
|
@ -236,8 +272,8 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
chan[c.chan].ins=c.value;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
chan[c.chan].chVolL=(c.value>>4)|(((c.value>>4)>>1)<<4);
|
||||
chan[c.chan].chVolR=(c.value&15)|(((c.value&15)>>1)<<4);
|
||||
chan[c.chan].chVolL=c.value>>1;
|
||||
chan[c.chan].chVolR=c.value2>>1;
|
||||
if (dumpWrites) {
|
||||
addWrite(0x10002+(c.chan<<3),chan[c.chan].chVolL);
|
||||
addWrite(0x10003+(c.chan<<3),chan[c.chan].chVolR);
|
||||
|
@ -327,6 +363,10 @@ void* DivPlatformSegaPCM::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformSegaPCM::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
@ -348,6 +388,7 @@ void DivPlatformSegaPCM::reset() {
|
|||
memset(regPool,0,256);
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i]=DivPlatformSegaPCM::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].vol=0x7f;
|
||||
chan[i].outVol=0x7f;
|
||||
}
|
||||
|
@ -375,6 +416,9 @@ void DivPlatformSegaPCM::reset() {
|
|||
void DivPlatformSegaPCM::setFlags(unsigned int flags) {
|
||||
chipClock=8000000.0;
|
||||
rate=31250;
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
bool DivPlatformSegaPCM::isStereo() {
|
||||
|
@ -387,6 +431,7 @@ int DivPlatformSegaPCM::init(DivEngine* p, int channels, int sugRate, unsigned i
|
|||
skipRegisterWrites=false;
|
||||
for (int i=0; i<16; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
reset();
|
||||
|
@ -395,6 +440,9 @@ int DivPlatformSegaPCM::init(DivEngine* p, int channels, int sugRate, unsigned i
|
|||
}
|
||||
|
||||
void DivPlatformSegaPCM::quit() {
|
||||
for (int i=0; i<16; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformSegaPCM::~DivPlatformSegaPCM() {
|
||||
|
|
|
@ -29,8 +29,7 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
struct Channel {
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM;
|
||||
int vol, outVol;
|
||||
|
@ -43,9 +42,14 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
unsigned char freq;
|
||||
PCMChannel(): sample(-1), pos(0), len(0), freq(0) {}
|
||||
} pcm;
|
||||
Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), portaPause(false), furnacePCM(false), vol(0), outVol(0), chVolL(127), chVolR(127) {}
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), note(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), portaPause(false), furnacePCM(false), vol(0), outVol(0), chVolL(127), chVolR(127) {}
|
||||
};
|
||||
Channel chan[16];
|
||||
DivDispatchOscBuffer* oscBuf[16];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
|
@ -74,11 +78,12 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void notifyInsChange(int ins);
|
||||
void setFlags(unsigned int flags);
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
|
||||
#define rWrite(v) {if (!skipRegisterWrites) {sn->write(v); if (dumpWrites) {addWrite(0x200,v);}}}
|
||||
|
||||
#define CHIP_DIVIDER 64
|
||||
|
||||
const char* regCheatSheetSN[]={
|
||||
"DATA", "0",
|
||||
NULL
|
||||
|
@ -44,7 +42,16 @@ const char* DivPlatformSMS::getEffectName(unsigned char effect) {
|
|||
}
|
||||
|
||||
void DivPlatformSMS::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
sn->sound_stream_update(bufL+start,len);
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
sn->sound_stream_update(bufL+h,1);
|
||||
for (int i=0; i<4; i++) {
|
||||
if (isMuted[i]) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
} else {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=sn->get_channel_output(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformSMS::acquireOne() {
|
||||
|
@ -53,8 +60,10 @@ int DivPlatformSMS::acquireOne() {
|
|||
return v;
|
||||
}
|
||||
|
||||
void DivPlatformSMS::tick() {
|
||||
void DivPlatformSMS::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
int CHIP_DIVIDER=64;
|
||||
if (i==3 && isRealSN) CHIP_DIVIDER=60;
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15));
|
||||
|
@ -84,19 +93,38 @@ void DivPlatformSMS::tick() {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (i==3) if (chan[i].std.duty.had) {
|
||||
snNoiseMode=chan[i].std.duty.val;
|
||||
if (chan[i].std.duty.val<2) {
|
||||
chan[3].freqChanged=false;
|
||||
if (i==3) {
|
||||
if (chan[i].std.duty.had) {
|
||||
if (chan[i].std.duty.val!=snNoiseMode || parent->song.snDutyReset) {
|
||||
snNoiseMode=chan[i].std.duty.val;
|
||||
if (chan[i].std.duty.val<2) {
|
||||
chan[3].freqChanged=false;
|
||||
}
|
||||
updateSNMode=true;
|
||||
}
|
||||
}
|
||||
updateSNMode=true;
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
updateSNMode=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
for (int i=0; i<3; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2);
|
||||
if (chan[i].freq>1023) chan[i].freq=1023;
|
||||
if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
|
||||
if (chan[i].freq<8) chan[i].freq=1;
|
||||
//if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
|
||||
rWrite(0x80|i<<5|(chan[i].freq&15));
|
||||
rWrite(chan[i].freq>>4);
|
||||
// what?
|
||||
|
@ -108,8 +136,7 @@ void DivPlatformSMS::tick() {
|
|||
}
|
||||
}
|
||||
if (chan[3].freqChanged || updateSNMode) {
|
||||
// seems arbitrary huh?
|
||||
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch-1-(isRealSN?127:0),true);
|
||||
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2);
|
||||
if (chan[3].freq>1023) chan[3].freq=1023;
|
||||
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
|
||||
if (snNoiseMode&2) { // take period from channel 3
|
||||
|
@ -153,6 +180,8 @@ void DivPlatformSMS::tick() {
|
|||
}
|
||||
|
||||
int DivPlatformSMS::dispatch(DivCommand c) {
|
||||
int CHIP_DIVIDER=64;
|
||||
if (c.chan==3 && isRealSN) CHIP_DIVIDER=60;
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
|
@ -163,12 +192,12 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
|
||||
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
rWrite(0x9f|c.chan<<5);
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
@ -176,7 +205,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
chan[c.chan].ins=c.value;
|
||||
//chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
//chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
|
@ -232,7 +261,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
|
@ -267,9 +296,14 @@ void* DivPlatformSMS::getChanState(int ch) {
|
|||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformSMS::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
void DivPlatformSMS::reset() {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i]=DivPlatformSMS::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
|
@ -338,6 +372,9 @@ void DivPlatformSMS::setFlags(unsigned int flags) {
|
|||
break;
|
||||
}
|
||||
rate=chipClock/16;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
|
@ -348,6 +385,7 @@ int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
oldValue=0xff;
|
||||
for (int i=0; i<4; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
sn=NULL;
|
||||
setFlags(flags);
|
||||
|
@ -356,6 +394,9 @@ int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
}
|
||||
|
||||
void DivPlatformSMS::quit() {
|
||||
for (int i=0; i<4; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
if (sn!=NULL) delete sn;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,15 +26,19 @@
|
|||
|
||||
class DivPlatformSMS: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note, actualNote;
|
||||
unsigned char ins;
|
||||
int freq, baseFreq, pitch, pitch2, note, actualNote, ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
actualNote(0),
|
||||
ins(-1),
|
||||
|
@ -48,6 +52,7 @@ class DivPlatformSMS: public DivDispatch {
|
|||
outVol(15) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
unsigned char oldValue;
|
||||
unsigned char snNoiseMode;
|
||||
|
@ -61,9 +66,10 @@ class DivPlatformSMS: public DivDispatch {
|
|||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
bool keyOffAffectsPorta(int ch);
|
||||
|
|
|
@ -1064,7 +1064,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
|
|||
{
|
||||
tone = &m_tone[chan];
|
||||
const int period = std::max<int>(1,tone->period);
|
||||
tone->count += is_expanded_mode() ? 16 : 1;
|
||||
tone->count += is_expanded_mode() ? 16 : (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1;
|
||||
while (tone->count >= period)
|
||||
{
|
||||
tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f;
|
||||
|
@ -1080,7 +1080,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
|
|||
* channels.
|
||||
*/
|
||||
m_count_noise = 0;
|
||||
m_prescale_noise ^= 1;
|
||||
m_prescale_noise = (m_prescale_noise + 1) & ((m_feature & PSG_HAS_EXPANDED_MODE) ? 3 : 1);
|
||||
|
||||
if (!m_prescale_noise || is_expanded_mode()) // AY8930 noise generator rate is twice compares as compatibility mode
|
||||
{
|
||||
|
@ -1469,7 +1469,7 @@ ay8910_device::ay8910_device(device_type type, unsigned int clock,
|
|||
m_noise_latch(0),
|
||||
m_mode(0),
|
||||
m_env_step_mask((!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 0x0f : 0x1f),
|
||||
m_step( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 2 : 1),
|
||||
m_step( (feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY) ? 2 : 1),
|
||||
m_zero_is_off( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 1 : 0),
|
||||
m_par( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param),
|
||||
m_par_env( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param_env),
|
||||
|
@ -1502,7 +1502,7 @@ void ay8910_device::set_type(psg_type_t psg_type)
|
|||
else
|
||||
{
|
||||
m_env_step_mask = 0x1f;
|
||||
m_step = 1;
|
||||
m_step = (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1;
|
||||
m_zero_is_off = 0;
|
||||
m_par = &ym2149_param;
|
||||
m_par_env = &ym2149_param_env;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue