Merge branch 'master' of https://github.com/tildearrow/furnace into sample_macro
This commit is contained in:
commit
0f5a400b29
|
@ -14,6 +14,7 @@ linuxbuild/
|
||||||
test/songs/
|
test/songs/
|
||||||
test/delta/
|
test/delta/
|
||||||
test/result/
|
test/result/
|
||||||
|
test/assert_delta
|
||||||
android/.gradle/
|
android/.gradle/
|
||||||
android/app/build/
|
android/app/build/
|
||||||
android/app/.cxx/
|
android/app/.cxx/
|
||||||
|
|
|
@ -386,7 +386,8 @@ src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
|
||||||
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
|
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
|
||||||
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
|
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/tia/TIASnd.cpp
|
src/engine/platform/sound/tia/AudioChannel.cpp
|
||||||
|
src/engine/platform/sound/tia/Audio.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
||||||
src/engine/platform/sound/ymfm/ymfm_opm.cpp
|
src/engine/platform/sound/ymfm/ymfm_opm.cpp
|
||||||
|
|
|
@ -22,7 +22,7 @@ bug fixes, improvements and several other things accepted.
|
||||||
|
|
||||||
the coding style is described here:
|
the coding style is described here:
|
||||||
|
|
||||||
- indentation: two spaces
|
- indentation: two spaces. **strictly** spaces. do NOT use tabs.
|
||||||
- modified 1TBS style:
|
- modified 1TBS style:
|
||||||
- no spaces in function calls
|
- no spaces in function calls
|
||||||
- spaces between arguments in function declarations
|
- spaces between arguments in function declarations
|
||||||
|
|
12
README.md
12
README.md
|
@ -6,7 +6,7 @@ the biggest multi-system chiptune tracker ever made!
|
||||||
|
|
||||||
[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions)
|
[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions)
|
||||||
|
|
||||||
***
|
---
|
||||||
## downloads
|
## downloads
|
||||||
|
|
||||||
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
|
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
|
||||||
|
@ -74,7 +74,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
|
||||||
- loads .dmf modules from all versions (beta 1 to 1.1.3)
|
- loads .dmf modules from all versions (beta 1 to 1.1.3)
|
||||||
- saves .dmf modules - both modern and legacy
|
- saves .dmf modules - both modern and legacy
|
||||||
- Furnace doubles as a module downgrader
|
- Furnace doubles as a module downgrader
|
||||||
- loads .dmp instruments and .dmw wavetables as well
|
- loads/saves .dmp instruments and .dmw wavetables as well
|
||||||
- clean-room design (guesswork and ABX tests only, no decompilation involved)
|
- clean-room design (guesswork and ABX tests only, no decompilation involved)
|
||||||
- bug/quirk implementation for increased playback accuracy through compatibility flags
|
- bug/quirk implementation for increased playback accuracy through compatibility flags
|
||||||
- VGM export
|
- VGM export
|
||||||
|
@ -103,7 +103,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
|
||||||
- built-in visualizer in pattern view
|
- built-in visualizer in pattern view
|
||||||
- open-source under GPLv2 or later.
|
- open-source under GPLv2 or later.
|
||||||
|
|
||||||
***
|
---
|
||||||
# quick references
|
# quick references
|
||||||
|
|
||||||
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z).
|
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z).
|
||||||
|
@ -119,7 +119,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li
|
||||||
- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608.
|
- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608.
|
||||||
- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
|
- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
|
||||||
|
|
||||||
***
|
---
|
||||||
# developer info
|
# developer info
|
||||||
|
|
||||||
[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml)
|
[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml)
|
||||||
|
@ -228,7 +228,7 @@ this will play a compatible file and enable the commands view.
|
||||||
|
|
||||||
**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.**
|
**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.**
|
||||||
|
|
||||||
***
|
---
|
||||||
# frequently asked questions
|
# frequently asked questions
|
||||||
|
|
||||||
> woah! 50 sound chips?! I can't believe it!
|
> woah! 50 sound chips?! I can't believe it!
|
||||||
|
@ -274,7 +274,7 @@ the DefleMask format has several limitations. save in Furnace song format instea
|
||||||
|
|
||||||
right click on the channel name.
|
right click on the channel name.
|
||||||
|
|
||||||
***
|
---
|
||||||
# footnotes
|
# footnotes
|
||||||
|
|
||||||
copyright (C) 2021-2022 tildearrow and contributors.
|
copyright (C) 2021-2022 tildearrow and contributors.
|
||||||
|
|
6
TODO.md
6
TODO.md
|
@ -1,5 +1,7 @@
|
||||||
# to-do for 0.6pre1.5-0.6pre2
|
# to-do for 0.6pre1.5
|
||||||
|
|
||||||
- volume commands should work on Game Boy
|
|
||||||
- stereo separation control for AY
|
- stereo separation control for AY
|
||||||
- "paste with instrument"
|
- "paste with instrument"
|
||||||
|
- FM operator muting
|
||||||
|
- FM operator swap
|
||||||
|
- bug fixes
|
||||||
|
|
|
@ -15,8 +15,8 @@ android {
|
||||||
}
|
}
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 93
|
versionCode 113
|
||||||
versionName "0.6pre1"
|
versionName "dev113"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"
|
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.tildearrow.furnace"
|
package="org.tildearrow.furnace"
|
||||||
android:versionCode="93"
|
android:versionCode="113"
|
||||||
android:versionName="0.6pre1"
|
android:versionName="dev113"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
||||||
<!-- OpenGL ES 2.0 -->
|
<!-- OpenGL ES 2.0 -->
|
||||||
|
@ -83,13 +83,17 @@
|
||||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- Drop file event -->
|
<!-- Drop file event -->
|
||||||
<!--
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="file"/>
|
||||||
|
<data android:scheme="content"/>
|
||||||
|
<data android:host="*"/>
|
||||||
<data android:mimeType="*/*"/>
|
<data android:mimeType="*/*"/>
|
||||||
|
<data android:pathPattern=".*\\.fur"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
-->
|
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
||||||
Subproject commit 64704a443f8f6c1906ba26297092ea70fa1d45d7
|
Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250
|
|
@ -1,5 +1,5 @@
|
||||||
/* Nuked OPM
|
/* Nuked OPM
|
||||||
* Copyright (C) 2020 Nuke.YKT
|
* Copyright (C) 2022 Nuke.YKT
|
||||||
*
|
*
|
||||||
* This file is part of Nuked OPM.
|
* This file is part of Nuked OPM.
|
||||||
*
|
*
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
* siliconpr0n.org(digshadow, John McMaster):
|
* siliconpr0n.org(digshadow, John McMaster):
|
||||||
* YM2151 and other FM chip decaps and die shots.
|
* YM2151 and other FM chip decaps and die shots.
|
||||||
*
|
*
|
||||||
* version: 0.9.2 beta
|
* version: 0.9.3 beta
|
||||||
*/
|
*/
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
@ -651,7 +651,7 @@ static inline void OPM_EnvelopePhase4(opm_t *chip)
|
||||||
chip->eg_instantattack = chip->eg_ratemax[1] && (kon || !chip->eg_ratemax[1]);
|
chip->eg_instantattack = chip->eg_ratemax[1] && (kon || !chip->eg_ratemax[1]);
|
||||||
|
|
||||||
eg_off = (chip->eg_level[slot] & 0x3f0) == 0x3f0;
|
eg_off = (chip->eg_level[slot] & 0x3f0) == 0x3f0;
|
||||||
slreach = (chip->eg_level[slot] >> 5) == chip->eg_sl[1];
|
slreach = (chip->eg_level[slot] >> 4) == (chip->eg_sl[1] << 1);
|
||||||
eg_zero = chip->eg_level[slot] == 0;
|
eg_zero = chip->eg_level[slot] == 0;
|
||||||
|
|
||||||
chip->eg_mute = eg_off && chip->eg_state[slot] != eg_num_attack && !kon;
|
chip->eg_mute = eg_off && chip->eg_state[slot] != eg_num_attack && !kon;
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -64,7 +64,3 @@ In there, you can modify certain data pertaining to your sample, such as the:
|
||||||
- and many more.
|
- and many more.
|
||||||
|
|
||||||
The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text.
|
The changes you make will be applied as soon as you've committed them to your sample, but they can be undoed and redoed, just like text.
|
||||||
|
|
||||||
# tips
|
|
||||||
|
|
||||||
if you have a sample you wanna use that is about 44100 or anything over 32000Hz, downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original audio file,
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# tildearrow Sound Unit
|
# tildearrow Sound Unit
|
||||||
This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB of sample data. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed.
|
This is a fantasy sound chip, used in the specs2 fantasy computer designed by tildearrow. It includes native support for sample playback, but with only 8KB or 64KB of sample data, depending on the configuration used. Since 0.6pre1, this sound chip is no longer hidden by default and can be accessed through the module creation screen and can be added or removed.
|
||||||
|
|
||||||
# effects
|
# effects
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
|
||||||
|
|
||||||
the format versions are:
|
the format versions are:
|
||||||
|
|
||||||
|
- 114: Furnace dev114
|
||||||
|
- 113: Furnace dev113
|
||||||
- 112: Furnace dev112
|
- 112: Furnace dev112
|
||||||
- 111: Furnace dev111
|
- 111: Furnace dev111
|
||||||
- 110: Furnace dev110
|
- 110: Furnace dev110
|
||||||
|
@ -254,6 +256,7 @@ size | description
|
||||||
| - 0xc3: OPN CSM - 10 channels
|
| - 0xc3: OPN CSM - 10 channels
|
||||||
| - 0xc4: PC-98 CSM - 20 channels
|
| - 0xc4: PC-98 CSM - 20 channels
|
||||||
| - 0xc5: YM2610B CSM - 20 channels
|
| - 0xc5: YM2610B CSM - 20 channels
|
||||||
|
| - 0xc6: MSM5232 - 8 channels
|
||||||
| - 0xde: YM2610B extended - 19 channels
|
| - 0xde: YM2610B extended - 19 channels
|
||||||
| - 0xe0: QSound - 19 channels
|
| - 0xe0: QSound - 19 channels
|
||||||
| - 0xfd: Dummy System - 8 channels
|
| - 0xfd: Dummy System - 8 channels
|
||||||
|
@ -338,7 +341,8 @@ size | description
|
||||||
1 | broken initial position of porta after arp (>=101) or reserved
|
1 | broken initial position of porta after arp (>=101) or reserved
|
||||||
1 | SN periods under 8 are treated as 1 (>=108) or reserved
|
1 | SN periods under 8 are treated as 1 (>=108) or reserved
|
||||||
1 | cut/delay effect policy (>=110) or reserved
|
1 | cut/delay effect policy (>=110) or reserved
|
||||||
5 | reserved
|
1 | 0B/0D effect treatment (>=113) or reserved
|
||||||
|
4 | reserved
|
||||||
--- | **virtual tempo data**
|
--- | **virtual tempo data**
|
||||||
2 | virtual tempo numerator of first song (>=96) or reserved
|
2 | virtual tempo numerator of first song (>=96) or reserved
|
||||||
2 | virtual tempo denominator of first song (>=96) or reserved
|
2 | virtual tempo denominator of first song (>=96) or reserved
|
||||||
|
@ -494,7 +498,8 @@ size | description
|
||||||
1 | vib
|
1 | vib
|
||||||
1 | ws
|
1 | ws
|
||||||
1 | ksr
|
1 | ksr
|
||||||
12 | reserved
|
1 | operator enabled (>=114) or reserved
|
||||||
|
11 | reserved
|
||||||
--- | **Game Boy instrument data**
|
--- | **Game Boy instrument data**
|
||||||
1 | volume
|
1 | volume
|
||||||
1 | direction
|
1 | direction
|
||||||
|
|
|
@ -239,6 +239,10 @@ void DivEngine::setConf(String key, double value) {
|
||||||
conf[key]=fmt::sprintf("%f",value);
|
conf[key]=fmt::sprintf("%f",value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DivEngine::setConf(String key, const char* value) {
|
||||||
|
conf[key]=String(value);
|
||||||
|
}
|
||||||
|
|
||||||
void DivEngine::setConf(String key, String value) {
|
void DivEngine::setConf(String key, String value) {
|
||||||
conf[key]=value;
|
conf[key]=value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,37 +147,68 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
||||||
int nextOrder=-1;
|
int nextOrder=-1;
|
||||||
int nextRow=0;
|
int nextRow=0;
|
||||||
int effectVal=0;
|
int effectVal=0;
|
||||||
|
int lastSuspectedLoopEnd=-1;
|
||||||
DivPattern* pat[DIV_MAX_CHANS];
|
DivPattern* pat[DIV_MAX_CHANS];
|
||||||
|
unsigned char wsWalked[8192];
|
||||||
|
memset(wsWalked,0,8192);
|
||||||
for (int i=0; i<curSubSong->ordersLen; i++) {
|
for (int i=0; i<curSubSong->ordersLen; i++) {
|
||||||
for (int j=0; j<chans; j++) {
|
for (int j=0; j<chans; j++) {
|
||||||
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
|
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
|
||||||
}
|
}
|
||||||
|
if (i>lastSuspectedLoopEnd) {
|
||||||
|
lastSuspectedLoopEnd=i;
|
||||||
|
}
|
||||||
for (int j=nextRow; j<curSubSong->patLen; j++) {
|
for (int j=nextRow; j<curSubSong->patLen; j++) {
|
||||||
nextRow=0;
|
nextRow=0;
|
||||||
|
bool changingOrder=false;
|
||||||
|
bool jumpingOrder=false;
|
||||||
|
if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) {
|
||||||
|
loopOrder=i;
|
||||||
|
loopRow=j;
|
||||||
|
loopEnd=lastSuspectedLoopEnd;
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (int k=0; k<chans; k++) {
|
for (int k=0; k<chans; k++) {
|
||||||
for (int l=0; l<curPat[k].effectCols; l++) {
|
for (int l=0; l<curPat[k].effectCols; l++) {
|
||||||
effectVal=pat[k]->data[j][5+(l<<1)];
|
effectVal=pat[k]->data[j][5+(l<<1)];
|
||||||
if (effectVal<0) effectVal=0;
|
if (effectVal<0) effectVal=0;
|
||||||
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
|
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
|
||||||
|
if (song.jumpTreatment==2) {
|
||||||
|
if ((i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||||
|
nextOrder=i+1;
|
||||||
|
nextRow=effectVal;
|
||||||
|
jumpingOrder=true;
|
||||||
|
}
|
||||||
|
} else if (song.jumpTreatment==1) {
|
||||||
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||||
nextOrder=i+1;
|
nextOrder=i+1;
|
||||||
nextRow=effectVal;
|
nextRow=effectVal;
|
||||||
|
jumpingOrder=true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||||
|
if (!changingOrder) {
|
||||||
|
nextOrder=i+1;
|
||||||
|
}
|
||||||
|
jumpingOrder=true;
|
||||||
|
nextRow=effectVal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (pat[k]->data[j][4+(l<<1)]==0x0b) {
|
} else if (pat[k]->data[j][4+(l<<1)]==0x0b) {
|
||||||
if (nextOrder==-1) {
|
if (nextOrder==-1 || song.jumpTreatment==0) {
|
||||||
nextOrder=effectVal;
|
nextOrder=effectVal;
|
||||||
|
if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) {
|
||||||
nextRow=0;
|
nextRow=0;
|
||||||
}
|
}
|
||||||
|
changingOrder=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7);
|
||||||
|
|
||||||
if (nextOrder!=-1) {
|
if (nextOrder!=-1) {
|
||||||
if (nextOrder<=i) {
|
|
||||||
loopOrder=nextOrder;
|
|
||||||
loopRow=nextRow;
|
|
||||||
loopEnd=i;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
i=nextOrder-1;
|
i=nextOrder-1;
|
||||||
nextOrder=-1;
|
nextOrder=-1;
|
||||||
break;
|
break;
|
||||||
|
@ -1659,6 +1690,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
||||||
speedAB=false;
|
speedAB=false;
|
||||||
playing=true;
|
playing=true;
|
||||||
skipping=true;
|
skipping=true;
|
||||||
|
memset(walked,0,8192);
|
||||||
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
|
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
|
||||||
while (playing && curOrder<goal) {
|
while (playing && curOrder<goal) {
|
||||||
if (nextTick(preserveDrift)) {
|
if (nextTick(preserveDrift)) {
|
||||||
|
@ -3440,9 +3472,8 @@ void DivEngine::setConsoleMode(bool enable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivEngine::switchMaster() {
|
bool DivEngine::switchMaster() {
|
||||||
deinitAudioBackend();
|
logI("switching output...");
|
||||||
quitDispatch();
|
deinitAudioBackend(true);
|
||||||
initDispatch();
|
|
||||||
if (initAudioBackend()) {
|
if (initAudioBackend()) {
|
||||||
for (int i=0; i<song.systemLen; i++) {
|
for (int i=0; i<song.systemLen; i++) {
|
||||||
disCont[i].setRates(got.rate);
|
disCont[i].setRates(got.rate);
|
||||||
|
@ -3586,6 +3617,7 @@ void DivEngine::quitDispatch() {
|
||||||
|
|
||||||
bool DivEngine::initAudioBackend() {
|
bool DivEngine::initAudioBackend() {
|
||||||
// load values
|
// load values
|
||||||
|
logI("initializing audio.");
|
||||||
if (audioEngine==DIV_AUDIO_NULL) {
|
if (audioEngine==DIV_AUDIO_NULL) {
|
||||||
if (getConfString("audioEngine","SDL")=="JACK") {
|
if (getConfString("audioEngine","SDL")=="JACK") {
|
||||||
audioEngine=DIV_AUDIO_JACK;
|
audioEngine=DIV_AUDIO_JACK;
|
||||||
|
@ -3691,8 +3723,9 @@ bool DivEngine::initAudioBackend() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivEngine::deinitAudioBackend() {
|
bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) {
|
||||||
if (output!=NULL) {
|
if (output!=NULL) {
|
||||||
|
logI("closing audio output.");
|
||||||
output->quit();
|
output->quit();
|
||||||
if (output->midiIn) {
|
if (output->midiIn) {
|
||||||
if (output->midiIn->isDeviceOpen()) {
|
if (output->midiIn->isDeviceOpen()) {
|
||||||
|
@ -3709,7 +3742,9 @@ bool DivEngine::deinitAudioBackend() {
|
||||||
output->quitMidi();
|
output->quitMidi();
|
||||||
delete output;
|
delete output;
|
||||||
output=NULL;
|
output=NULL;
|
||||||
//audioEngine=DIV_AUDIO_NULL;
|
if (dueToSwitchMaster) {
|
||||||
|
audioEngine=DIV_AUDIO_NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,8 @@
|
||||||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||||
|
|
||||||
#define DIV_VERSION "dev112"
|
#define DIV_VERSION "dev114"
|
||||||
#define DIV_ENGINE_VERSION 112
|
#define DIV_ENGINE_VERSION 114
|
||||||
|
|
||||||
// for imports
|
// for imports
|
||||||
#define DIV_VERSION_MOD 0xff01
|
#define DIV_VERSION_MOD 0xff01
|
||||||
#define DIV_VERSION_FC 0xff02
|
#define DIV_VERSION_FC 0xff02
|
||||||
|
@ -358,6 +357,8 @@ class DivEngine {
|
||||||
double exportFadeOut;
|
double exportFadeOut;
|
||||||
std::map<String,String> conf;
|
std::map<String,String> conf;
|
||||||
std::deque<DivNoteEvent> pendingNotes;
|
std::deque<DivNoteEvent> pendingNotes;
|
||||||
|
// bitfield
|
||||||
|
unsigned char walked[8192];
|
||||||
bool isMuted[DIV_MAX_CHANS];
|
bool isMuted[DIV_MAX_CHANS];
|
||||||
std::mutex isBusy, saveLock;
|
std::mutex isBusy, saveLock;
|
||||||
String configPath;
|
String configPath;
|
||||||
|
@ -453,7 +454,7 @@ class DivEngine {
|
||||||
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
|
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
|
||||||
|
|
||||||
bool initAudioBackend();
|
bool initAudioBackend();
|
||||||
bool deinitAudioBackend();
|
bool deinitAudioBackend(bool dueToSwitchMaster=false);
|
||||||
|
|
||||||
void registerSystems();
|
void registerSystems();
|
||||||
void initSongWithDesc(const int* description);
|
void initSongWithDesc(const int* description);
|
||||||
|
@ -545,6 +546,7 @@ class DivEngine {
|
||||||
void setConf(String key, int value);
|
void setConf(String key, int value);
|
||||||
void setConf(String key, float value);
|
void setConf(String key, float value);
|
||||||
void setConf(String key, double value);
|
void setConf(String key, double value);
|
||||||
|
void setConf(String key, const char* value);
|
||||||
void setConf(String key, String value);
|
void setConf(String key, String value);
|
||||||
|
|
||||||
// calculate base frequency/period
|
// calculate base frequency/period
|
||||||
|
@ -1074,6 +1076,7 @@ class DivEngine {
|
||||||
memset(reversePitchTable,0,4096*sizeof(int));
|
memset(reversePitchTable,0,4096*sizeof(int));
|
||||||
memset(pitchTable,0,4096*sizeof(int));
|
memset(pitchTable,0,4096*sizeof(int));
|
||||||
memset(sysDefs,0,256*sizeof(void*));
|
memset(sysDefs,0,256*sizeof(void*));
|
||||||
|
memset(walked,0,8192);
|
||||||
|
|
||||||
for (int i=0; i<256; i++) {
|
for (int i=0; i<256; i++) {
|
||||||
sysFileMapFur[i]=DIV_SYSTEM_NULL;
|
sysFileMapFur[i]=DIV_SYSTEM_NULL;
|
||||||
|
|
|
@ -179,6 +179,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
||||||
ds.brokenPortaArp=false;
|
ds.brokenPortaArp=false;
|
||||||
ds.snNoLowPeriods=true;
|
ds.snNoLowPeriods=true;
|
||||||
ds.delayBehavior=0;
|
ds.delayBehavior=0;
|
||||||
|
ds.jumpTreatment=2;
|
||||||
|
|
||||||
// 1.1 compat flags
|
// 1.1 compat flags
|
||||||
if (ds.version>24) {
|
if (ds.version>24) {
|
||||||
|
@ -1081,6 +1082,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
if (ds.version<110) {
|
if (ds.version<110) {
|
||||||
ds.delayBehavior=1;
|
ds.delayBehavior=1;
|
||||||
}
|
}
|
||||||
|
if (ds.version<113) {
|
||||||
|
ds.jumpTreatment=1;
|
||||||
|
}
|
||||||
ds.isDMF=false;
|
ds.isDMF=false;
|
||||||
|
|
||||||
reader.readS(); // reserved
|
reader.readS(); // reserved
|
||||||
|
@ -1503,7 +1507,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
||||||
} else {
|
} else {
|
||||||
reader.readC();
|
reader.readC();
|
||||||
}
|
}
|
||||||
for (int i=0; i<5; i++) {
|
if (ds.version>=113) {
|
||||||
|
ds.jumpTreatment=reader.readC();
|
||||||
|
} else {
|
||||||
|
reader.readC();
|
||||||
|
}
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
reader.readC();
|
reader.readC();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3751,7 +3760,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
||||||
w->writeC(song.brokenPortaArp);
|
w->writeC(song.brokenPortaArp);
|
||||||
w->writeC(song.snNoLowPeriods);
|
w->writeC(song.snNoLowPeriods);
|
||||||
w->writeC(song.delayBehavior);
|
w->writeC(song.delayBehavior);
|
||||||
for (int i=0; i<5; i++) {
|
w->writeC(song.jumpTreatment);
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
w->writeC(0);
|
w->writeC(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -150,9 +150,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
ins->type=DIV_INS_FM;
|
ins->type=DIV_INS_FM;
|
||||||
logD("instrument type is Arcade");
|
logD("instrument type is Arcade");
|
||||||
break;
|
break;
|
||||||
|
case 9: // Neo Geo
|
||||||
|
ins->type=DIV_INS_FM;
|
||||||
|
logD("instrument type is Neo Geo");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
logD("instrument type is unknown");
|
logD("instrument type is unknown");
|
||||||
lastError="unknown instrument type!";
|
lastError=fmt::sprintf("unknown instrument type %d!",sys);
|
||||||
delete ins;
|
delete ins;
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
@ -171,12 +175,22 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
mode=reader.readC();
|
mode=reader.readC();
|
||||||
logD("instrument mode is %d",mode);
|
logD("instrument mode is %d",mode);
|
||||||
if (mode==0) {
|
if (mode==0) {
|
||||||
if (version<11) {
|
if (ins->type==DIV_INS_FM) {
|
||||||
|
if (sys==9) {
|
||||||
|
ins->type=DIV_INS_AY;
|
||||||
|
} else {
|
||||||
ins->type=DIV_INS_STD;
|
ins->type=DIV_INS_STD;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sys==3 || sys==6) {
|
||||||
|
ins->type=DIV_INS_OPLL;
|
||||||
|
} else if (sys==1) {
|
||||||
|
ins->type=DIV_INS_OPL;
|
||||||
} else {
|
} else {
|
||||||
ins->type=DIV_INS_FM;
|
ins->type=DIV_INS_FM;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ins->type=DIV_INS_FM;
|
ins->type=DIV_INS_FM;
|
||||||
}
|
}
|
||||||
|
@ -231,6 +245,16 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
ins->fm.op[j].ksr=reader.readC();
|
ins->fm.op[j].ksr=reader.readC();
|
||||||
ins->fm.op[j].dvb=reader.readC();
|
ins->fm.op[j].dvb=reader.readC();
|
||||||
ins->fm.op[j].dam=reader.readC();
|
ins->fm.op[j].dam=reader.readC();
|
||||||
|
} else {
|
||||||
|
if (sys==3 || sys==6) { // OPLL/VRC7
|
||||||
|
ins->fm.op[j].ksr=reader.readC()?1:0;
|
||||||
|
ins->fm.op[j].vib=reader.readC();
|
||||||
|
if (j==0) {
|
||||||
|
ins->fm.opllPreset=ins->fm.op[j].vib>>4;
|
||||||
|
}
|
||||||
|
ins->fm.op[j].vib=ins->fm.op[j].vib?1:0;
|
||||||
|
ins->fm.op[j].ksl=reader.readC()?1:0;
|
||||||
|
ins->fm.op[j].ssgEnv=reader.readC();
|
||||||
} else {
|
} else {
|
||||||
ins->fm.op[j].rs=reader.readC();
|
ins->fm.op[j].rs=reader.readC();
|
||||||
ins->fm.op[j].dt=reader.readC();
|
ins->fm.op[j].dt=reader.readC();
|
||||||
|
@ -240,6 +264,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
ins->fm.op[j].ssgEnv=reader.readC();
|
ins->fm.op[j].ssgEnv=reader.readC();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else { // STD
|
} else { // STD
|
||||||
logD("reading STD data...");
|
logD("reading STD data...");
|
||||||
if (ins->type!=DIV_INS_GB) {
|
if (ins->type!=DIV_INS_GB) {
|
||||||
|
@ -247,6 +272,9 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
|
||||||
if (version>5) {
|
if (version>5) {
|
||||||
for (int i=0; i<ins->std.volMacro.len; i++) {
|
for (int i=0; i<ins->std.volMacro.len; i++) {
|
||||||
ins->std.volMacro.val[i]=reader.readI();
|
ins->std.volMacro.val[i]=reader.readI();
|
||||||
|
if (ins->std.volMacro.val[i]>15 && sys==6) { // FDS
|
||||||
|
ins->type=DIV_INS_FDS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i=0; i<ins->std.volMacro.len; i++) {
|
for (int i=0; i<ins->std.volMacro.len; i++) {
|
||||||
|
@ -805,7 +833,7 @@ void DivEngine::loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, S
|
||||||
|
|
||||||
op.mult = dtMul & 0xF;
|
op.mult = dtMul & 0xF;
|
||||||
op.dt = ((dtMul >> 4) & 0x7);
|
op.dt = ((dtMul >> 4) & 0x7);
|
||||||
op.tl = totalLevel & 0x3F;
|
op.tl = totalLevel & 0x7F;
|
||||||
op.rs = ((arRateScale >> 6) & 0x3);
|
op.rs = ((arRateScale >> 6) & 0x3);
|
||||||
op.ar = arRateScale & 0x1F;
|
op.ar = arRateScale & 0x1F;
|
||||||
op.dr = drAmpEnable & 0x1F;
|
op.dr = drAmpEnable & 0x1F;
|
||||||
|
@ -1643,7 +1671,7 @@ void DivEngine::loadWOPN(SafeReader& reader, std::vector<DivInstrument*>& ret, S
|
||||||
|
|
||||||
total += (op.mult = dtMul & 0xF);
|
total += (op.mult = dtMul & 0xF);
|
||||||
total += (op.dt = ((dtMul >> 4) & 0x7));
|
total += (op.dt = ((dtMul >> 4) & 0x7));
|
||||||
total += (op.tl = totalLevel & 0x3F);
|
total += (op.tl = totalLevel & 0x7F);
|
||||||
total += (op.rs = ((arRateScale >> 6) & 0x3));
|
total += (op.rs = ((arRateScale >> 6) & 0x3));
|
||||||
total += (op.ar = arRateScale & 0x1F);
|
total += (op.ar = arRateScale & 0x1F);
|
||||||
total += (op.dr = drAmpEnable & 0x1F);
|
total += (op.dr = drAmpEnable & 0x1F);
|
||||||
|
|
|
@ -71,8 +71,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
||||||
w->writeC(op.ws);
|
w->writeC(op.ws);
|
||||||
w->writeC(op.ksr);
|
w->writeC(op.ksr);
|
||||||
|
|
||||||
|
w->writeC(op.enable);
|
||||||
|
|
||||||
// reserved
|
// reserved
|
||||||
for (int k=0; k<12; k++) {
|
for (int k=0; k<11; k++) {
|
||||||
w->writeC(0);
|
w->writeC(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -716,8 +718,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
||||||
op.ws=reader.readC();
|
op.ws=reader.readC();
|
||||||
op.ksr=reader.readC();
|
op.ksr=reader.readC();
|
||||||
|
|
||||||
|
if (version>=114) {
|
||||||
|
op.enable=reader.readC();
|
||||||
|
} else {
|
||||||
|
reader.readC();
|
||||||
|
}
|
||||||
|
|
||||||
// reserved
|
// reserved
|
||||||
for (int k=0; k<12; k++) reader.readC();
|
for (int k=0; k<11; k++) reader.readC();
|
||||||
}
|
}
|
||||||
|
|
||||||
// GB
|
// GB
|
||||||
|
|
|
@ -255,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) {
|
||||||
chan[i].state.ams=chan[i].std.ams.val;
|
chan[i].state.ams=chan[i].std.ams.val;
|
||||||
rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3));
|
rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3));
|
||||||
}
|
}
|
||||||
|
if (chan[i].std.ex4.had && chan[i].active) {
|
||||||
|
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||||
|
chan[i].opMaskChanged=true;
|
||||||
|
}
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
@ -347,8 +351,9 @@ void DivPlatformArcade::tick(bool sysTick) {
|
||||||
immWrite(i+0x30,chan[i].freq<<2);
|
immWrite(i+0x30,chan[i].freq<<2);
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||||
immWrite(0x08,0x78|i);
|
immWrite(0x08,(chan[i].opMask<<3)|i);
|
||||||
|
chan[i].opMaskChanged=false;
|
||||||
chan[i].keyOn=false;
|
chan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,6 +375,11 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
||||||
|
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
chan[c.chan].state=ins->fm;
|
chan[c.chan].state=ins->fm;
|
||||||
|
chan[c.chan].opMask=
|
||||||
|
(chan[c.chan].state.op[0].enable?1:0)|
|
||||||
|
(chan[c.chan].state.op[2].enable?2:0)|
|
||||||
|
(chan[c.chan].state.op[1].enable?4:0)|
|
||||||
|
(chan[c.chan].state.op[3].enable?8:0);
|
||||||
}
|
}
|
||||||
|
|
||||||
chan[c.chan].macroInit(ins);
|
chan[c.chan].macroInit(ins);
|
||||||
|
@ -502,6 +512,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_FM_LFO: {
|
case DIV_CMD_FM_LFO: {
|
||||||
|
if(c.value==0) {
|
||||||
|
rWrite(0x01,0x02);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rWrite(0x01,0x00);
|
||||||
|
}
|
||||||
rWrite(0x18,c.value);
|
rWrite(0x18,c.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -825,6 +841,8 @@ void DivPlatformArcade::reset() {
|
||||||
pmDepth=0x7f;
|
pmDepth=0x7f;
|
||||||
|
|
||||||
//rWrite(0x18,0x10);
|
//rWrite(0x18,0x10);
|
||||||
|
immWrite(0x01,0x02); // LFO Off
|
||||||
|
immWrite(0x18,0x00); // LFO Freq Off
|
||||||
immWrite(0x19,amDepth);
|
immWrite(0x19,amDepth);
|
||||||
immWrite(0x19,0x80|pmDepth);
|
immWrite(0x19,0x80|pmDepth);
|
||||||
//rWrite(0x1b,0x00);
|
//rWrite(0x1b,0x00);
|
||||||
|
|
|
@ -43,9 +43,9 @@ class DivPlatformArcade: public DivPlatformOPM {
|
||||||
int freq, baseFreq, pitch, pitch2, note;
|
int freq, baseFreq, pitch, pitch2, note;
|
||||||
int ins;
|
int ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset, opMaskChanged;
|
||||||
int vol, outVol;
|
int vol, outVol;
|
||||||
unsigned char chVolL, chVolR;
|
unsigned char chVolL, chVolR, opMask;
|
||||||
void macroInit(DivInstrument* which) {
|
void macroInit(DivInstrument* which) {
|
||||||
std.init(which);
|
std.init(which);
|
||||||
pitch2=0;
|
pitch2=0;
|
||||||
|
@ -68,10 +68,12 @@ class DivPlatformArcade: public DivPlatformOPM {
|
||||||
portaPause(false),
|
portaPause(false),
|
||||||
furnacePCM(false),
|
furnacePCM(false),
|
||||||
hardReset(false),
|
hardReset(false),
|
||||||
|
opMaskChanged(false),
|
||||||
vol(0),
|
vol(0),
|
||||||
outVol(0),
|
outVol(0),
|
||||||
chVolL(127),
|
chVolL(127),
|
||||||
chVolR(127) {}
|
chVolR(127),
|
||||||
|
opMask(15) {}
|
||||||
};
|
};
|
||||||
Channel chan[8];
|
Channel chan[8];
|
||||||
DivDispatchOscBuffer* oscBuf[8];
|
DivDispatchOscBuffer* oscBuf[8];
|
||||||
|
|
|
@ -347,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
||||||
chan[i].state.ams=chan[i].std.ams.val;
|
chan[i].state.ams=chan[i].std.ams.val;
|
||||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||||
}
|
}
|
||||||
|
if (chan[i].std.ex4.had && chan[i].active) {
|
||||||
|
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||||
|
chan[i].opMaskChanged=true;
|
||||||
|
}
|
||||||
for (int j=0; j<4; j++) {
|
for (int j=0; j<4; j++) {
|
||||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||||
|
@ -479,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
chan[i].freqChanged=false;
|
chan[i].freqChanged=false;
|
||||||
}
|
}
|
||||||
if (chan[i].keyOn) {
|
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||||
if (i<6) immWrite(0x28,0xf0|konOffs[i]);
|
if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||||
|
chan[i].opMaskChanged=false;
|
||||||
chan[i].keyOn=false;
|
chan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -591,6 +596,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
||||||
|
|
||||||
if (chan[c.chan].insChanged) {
|
if (chan[c.chan].insChanged) {
|
||||||
chan[c.chan].state=ins->fm;
|
chan[c.chan].state=ins->fm;
|
||||||
|
chan[c.chan].opMask=
|
||||||
|
(chan[c.chan].state.op[0].enable?1:0)|
|
||||||
|
(chan[c.chan].state.op[2].enable?2:0)|
|
||||||
|
(chan[c.chan].state.op[1].enable?4:0)|
|
||||||
|
(chan[c.chan].state.op[3].enable?8:0);
|
||||||
}
|
}
|
||||||
|
|
||||||
chan[c.chan].macroInit(ins);
|
chan[c.chan].macroInit(ins);
|
||||||
|
|
|
@ -45,9 +45,9 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
|
||||||
int ins;
|
int ins;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset, opMaskChanged;
|
||||||
int vol, outVol;
|
int vol, outVol;
|
||||||
unsigned char pan;
|
unsigned char pan, opMask;
|
||||||
|
|
||||||
bool dacMode;
|
bool dacMode;
|
||||||
int dacPeriod;
|
int dacPeriod;
|
||||||
|
@ -82,9 +82,11 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
||||||
furnaceDac(false),
|
furnaceDac(false),
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
hardReset(false),
|
hardReset(false),
|
||||||
|
opMaskChanged(false),
|
||||||
vol(0),
|
vol(0),
|
||||||
outVol(0),
|
outVol(0),
|
||||||
pan(3),
|
pan(3),
|
||||||
|
opMask(15),
|
||||||
dacMode(false),
|
dacMode(false),
|
||||||
dacPeriod(0),
|
dacPeriod(0),
|
||||||
dacRate(0),
|
dacRate(0),
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#define CHIP_DIVIDER fmDivBase
|
#define CHIP_DIVIDER fmDivBase
|
||||||
|
|
||||||
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
||||||
|
#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3])
|
||||||
|
|
||||||
int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||||
if (c.chan<2) {
|
if (c.chan<2) {
|
||||||
|
@ -69,10 +70,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||||
rWrite(baseAddr+0x70,op.d2r&31);
|
rWrite(baseAddr+0x70,op.d2r&31);
|
||||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||||
|
opChan[ch].mask=op.enable;
|
||||||
}
|
}
|
||||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||||
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
|
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
|
||||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||||
}
|
}
|
||||||
opChan[ch].insChanged=false;
|
opChan[ch].insChanged=false;
|
||||||
|
|
||||||
|
@ -123,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||||
opChan[i].pan=opChan[ch].pan;
|
opChan[i].pan=opChan[ch].pan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_PITCH: {
|
case DIV_CMD_PITCH: {
|
||||||
|
@ -397,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
|
||||||
rWrite(baseAddr+0x40,op.tl);
|
rWrite(baseAddr+0x40,op.tl);
|
||||||
immWrite(baseAddr+0x40,op.tl);
|
immWrite(baseAddr+0x40,op.tl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int opChanOffsL[4]={
|
static int opChanOffsL[4]={
|
||||||
|
@ -412,7 +416,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
||||||
bool writeSomething=false;
|
bool writeSomething=false;
|
||||||
unsigned char writeMask=2;
|
unsigned char writeMask=2;
|
||||||
for (int i=0; i<4; i++) {
|
for (int i=0; i<4; i++) {
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||||
writeSomething=true;
|
writeSomething=true;
|
||||||
writeMask&=~(1<<(4+i));
|
writeMask&=~(1<<(4+i));
|
||||||
|
@ -459,10 +463,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
||||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||||
}
|
}
|
||||||
writeMask|=opChan[i].active<<(4+i);
|
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||||
if (opChan[i].keyOn) {
|
if (opChan[i].keyOn) {
|
||||||
writeNoteOn=true;
|
writeNoteOn=true;
|
||||||
|
if (opChan[i].mask) {
|
||||||
writeMask|=1<<(4+i);
|
writeMask|=1<<(4+i);
|
||||||
|
}
|
||||||
opChan[i].keyOn=false;
|
opChan[i].keyOn=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -544,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() {
|
||||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||||
}
|
}
|
||||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||||
|
if (i==2) {
|
||||||
|
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||||
|
} else {
|
||||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||||
|
}
|
||||||
if (chan[i].active) {
|
if (chan[i].active) {
|
||||||
chan[i].keyOn=true;
|
chan[i].keyOn=true;
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
|
|
|
@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
||||||
unsigned char freqH, freqL;
|
unsigned char freqH, freqL;
|
||||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||||
signed char konCycles;
|
signed char konCycles;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||||
int vol;
|
int vol;
|
||||||
unsigned char pan;
|
unsigned char pan;
|
||||||
OpChannel():
|
OpChannel():
|
||||||
|
@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
||||||
keyOff(false),
|
keyOff(false),
|
||||||
portaPause(false),
|
portaPause(false),
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
|
mask(true),
|
||||||
vol(0),
|
vol(0),
|
||||||
pan(3) {}
|
pan(3) {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -642,6 +642,9 @@ void DivPlatformN163::setFlags(unsigned int flags) {
|
||||||
for (int i=0; i<8; i++) {
|
for (int i=0; i<8; i++) {
|
||||||
oscBuf[i]->rate=rate/(initChanMax+1);
|
oscBuf[i]->rate=rate/(initChanMax+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// needed to make sure changing channel count won't trigger glitches
|
||||||
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
|
|
|
@ -249,9 +249,11 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
||||||
if (os[1]>32767) os[1]=32767;
|
if (os[1]>32767) os[1]=32767;
|
||||||
|
|
||||||
bufL[h]=os[0];
|
bufL[h]=os[0];
|
||||||
|
if (oplType==3 || oplType==759) {
|
||||||
bufR[h]=os[1];
|
bufR[h]=os[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
//if (useYMFM) {
|
//if (useYMFM) {
|
||||||
|
@ -1531,7 +1533,7 @@ void DivPlatformOPL::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivPlatformOPL::isStereo() {
|
bool DivPlatformOPL::isStereo() {
|
||||||
return true;
|
return (oplType==3 || oplType==759);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivPlatformOPL::keyOffAffectsArp(int ch) {
|
bool DivPlatformOPL::keyOffAffectsArp(int ch) {
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// SSSS tt lll lll
|
||||||
|
// SS SS tt ll ll
|
||||||
|
// SS tttttt eeee ll ll aaaa
|
||||||
|
// SSSS tt ee ee ll ll aa
|
||||||
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
||||||
|
// SS SS tt ee ll ll aa aa
|
||||||
|
// SSSS ttt eeeee llll llll aaaaa
|
||||||
|
//
|
||||||
|
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
|
||||||
|
// and the Stella Team
|
||||||
|
//
|
||||||
|
// See the file "License.txt" for information on usage and redistribution of
|
||||||
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include "Audio.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr double R_MAX = 30.;
|
||||||
|
constexpr double R = 1.;
|
||||||
|
|
||||||
|
short mixingTableEntry(unsigned char v, unsigned char vMax)
|
||||||
|
{
|
||||||
|
return static_cast<short>(
|
||||||
|
floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TIA {
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Audio::Audio()
|
||||||
|
{
|
||||||
|
for (unsigned char i = 0; i <= 0x1e; ++i) myMixingTableSum[i] = mixingTableEntry(i, 0x1e);
|
||||||
|
for (unsigned char i = 0; i <= 0x0f; ++i) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f);
|
||||||
|
|
||||||
|
reset(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::reset(bool st)
|
||||||
|
{
|
||||||
|
myCounter = 0;
|
||||||
|
mySampleIndex = 0;
|
||||||
|
stereo = st;
|
||||||
|
|
||||||
|
myCurrentSample[0]=0;
|
||||||
|
myCurrentSample[1]=0;
|
||||||
|
|
||||||
|
myChannelOut[0]=0;
|
||||||
|
myChannelOut[1]=0;
|
||||||
|
|
||||||
|
myChannel0.reset();
|
||||||
|
myChannel1.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::tick()
|
||||||
|
{
|
||||||
|
switch (myCounter) {
|
||||||
|
case 9:
|
||||||
|
case 81:
|
||||||
|
myChannel0.phase0();
|
||||||
|
myChannel1.phase0();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 37:
|
||||||
|
case 149:
|
||||||
|
phase1();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++myCounter == 228) myCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::write(unsigned char addr, unsigned char val) {
|
||||||
|
switch (addr&0x3f) {
|
||||||
|
case 0x15:
|
||||||
|
myChannel0.audc(val);
|
||||||
|
break;
|
||||||
|
case 0x16:
|
||||||
|
myChannel1.audc(val);
|
||||||
|
break;
|
||||||
|
case 0x17:
|
||||||
|
myChannel0.audf(val);
|
||||||
|
break;
|
||||||
|
case 0x18:
|
||||||
|
myChannel1.audf(val);
|
||||||
|
break;
|
||||||
|
case 0x19:
|
||||||
|
myChannel0.audv(val);
|
||||||
|
break;
|
||||||
|
case 0x1a:
|
||||||
|
myChannel1.audv(val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::phase1()
|
||||||
|
{
|
||||||
|
unsigned char sample0 = myChannel0.phase1();
|
||||||
|
unsigned char sample1 = myChannel1.phase1();
|
||||||
|
|
||||||
|
addSample(sample0, sample1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void Audio::addSample(unsigned char sample0, unsigned char sample1)
|
||||||
|
{
|
||||||
|
if(stereo) {
|
||||||
|
myCurrentSample[0] = myMixingTableIndividual[sample0];
|
||||||
|
myCurrentSample[1] = myMixingTableIndividual[sample1];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
myCurrentSample[0] = myMixingTableSum[sample0 + sample1];
|
||||||
|
}
|
||||||
|
|
||||||
|
myChannelOut[0] = myMixingTableIndividual[sample0];
|
||||||
|
myChannelOut[1] = myMixingTableIndividual[sample1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
AudioChannel& Audio::channel0()
|
||||||
|
{
|
||||||
|
return myChannel0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
AudioChannel& Audio::channel1()
|
||||||
|
{
|
||||||
|
return myChannel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// SSSS tt lll lll
|
||||||
|
// SS SS tt ll ll
|
||||||
|
// SS tttttt eeee ll ll aaaa
|
||||||
|
// SSSS tt ee ee ll ll aa
|
||||||
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
||||||
|
// SS SS tt ee ll ll aa aa
|
||||||
|
// SSSS ttt eeeee llll llll aaaaa
|
||||||
|
//
|
||||||
|
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
|
||||||
|
// and the Stella Team
|
||||||
|
//
|
||||||
|
// See the file "License.txt" for information on usage and redistribution of
|
||||||
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#ifndef TIA_AUDIO_HXX
|
||||||
|
#define TIA_AUDIO_HXX
|
||||||
|
|
||||||
|
#include "AudioChannel.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace TIA {
|
||||||
|
class Audio
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Audio();
|
||||||
|
|
||||||
|
void reset(bool stereo);
|
||||||
|
|
||||||
|
void tick();
|
||||||
|
|
||||||
|
void write(unsigned char addr, unsigned char val);
|
||||||
|
|
||||||
|
AudioChannel& channel0();
|
||||||
|
|
||||||
|
AudioChannel& channel1();
|
||||||
|
|
||||||
|
short myCurrentSample[2];
|
||||||
|
short myChannelOut[2];
|
||||||
|
|
||||||
|
private:
|
||||||
|
void phase1();
|
||||||
|
void addSample(unsigned char sample0, unsigned char sample1);
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned char myCounter{0};
|
||||||
|
|
||||||
|
AudioChannel myChannel0;
|
||||||
|
AudioChannel myChannel1;
|
||||||
|
|
||||||
|
bool stereo;
|
||||||
|
|
||||||
|
std::array<short, 0x1e + 1> myMixingTableSum;
|
||||||
|
std::array<short, 0x0f + 1> myMixingTableIndividual;
|
||||||
|
|
||||||
|
unsigned int mySampleIndex{0};
|
||||||
|
#ifdef GUI_SUPPORT
|
||||||
|
bool myRewindMode{false};
|
||||||
|
mutable ByteArray mySamples;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
Audio(const Audio&) = delete;
|
||||||
|
Audio(Audio&&) = delete;
|
||||||
|
Audio& operator=(const Audio&) = delete;
|
||||||
|
Audio& operator=(Audio&&) = delete;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TIA_AUDIO_HXX
|
|
@ -0,0 +1,140 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// SSSS tt lll lll
|
||||||
|
// SS SS tt ll ll
|
||||||
|
// SS tttttt eeee ll ll aaaa
|
||||||
|
// SSSS tt ee ee ll ll aa
|
||||||
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
||||||
|
// SS SS tt ee ll ll aa aa
|
||||||
|
// SSSS ttt eeeee llll llll aaaaa
|
||||||
|
//
|
||||||
|
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
|
||||||
|
// and the Stella Team
|
||||||
|
//
|
||||||
|
// See the file "License.txt" for information on usage and redistribution of
|
||||||
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#include "AudioChannel.h"
|
||||||
|
|
||||||
|
namespace TIA {
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::reset()
|
||||||
|
{
|
||||||
|
myAudc = myAudv = myAudf = 0;
|
||||||
|
myClockEnable = myNoiseFeedback = myNoiseCounterBit4 = myPulseCounterHold = false;
|
||||||
|
myDivCounter = myPulseCounter = myNoiseCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::phase0()
|
||||||
|
{
|
||||||
|
if (myClockEnable) {
|
||||||
|
myNoiseCounterBit4 = myNoiseCounter & 0x01;
|
||||||
|
|
||||||
|
switch (myAudc & 0x03) {
|
||||||
|
case 0x00:
|
||||||
|
case 0x01:
|
||||||
|
myPulseCounterHold = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x02:
|
||||||
|
myPulseCounterHold = (myNoiseCounter & 0x1e) != 0x02;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x03:
|
||||||
|
myPulseCounterHold = !myNoiseCounterBit4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (myAudc & 0x03) {
|
||||||
|
case 0x00:
|
||||||
|
myNoiseFeedback =
|
||||||
|
((myPulseCounter ^ myNoiseCounter) & 0x01) ||
|
||||||
|
!(myNoiseCounter || (myPulseCounter != 0x0a)) ||
|
||||||
|
!(myAudc & 0x0c);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
myNoiseFeedback =
|
||||||
|
(((myNoiseCounter & 0x04) ? 1 : 0) ^ (myNoiseCounter & 0x01)) ||
|
||||||
|
myNoiseCounter == 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
myClockEnable = myDivCounter == myAudf;
|
||||||
|
|
||||||
|
if (myDivCounter == myAudf || myDivCounter == 0x1f) {
|
||||||
|
myDivCounter = 0;
|
||||||
|
} else {
|
||||||
|
++myDivCounter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
unsigned char AudioChannel::phase1()
|
||||||
|
{
|
||||||
|
if (myClockEnable) {
|
||||||
|
bool pulseFeedback = false;
|
||||||
|
switch (myAudc >> 2) {
|
||||||
|
case 0x00:
|
||||||
|
pulseFeedback =
|
||||||
|
(((myPulseCounter & 0x02) ? 1 : 0) ^ (myPulseCounter & 0x01)) &&
|
||||||
|
(myPulseCounter != 0x0a) &&
|
||||||
|
(myAudc & 0x03);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01:
|
||||||
|
pulseFeedback = !(myPulseCounter & 0x08);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x02:
|
||||||
|
pulseFeedback = !myNoiseCounterBit4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x03:
|
||||||
|
pulseFeedback = !((myPulseCounter & 0x02) || !(myPulseCounter & 0x0e));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
myNoiseCounter >>= 1;
|
||||||
|
if (myNoiseFeedback) {
|
||||||
|
myNoiseCounter |= 0x10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!myPulseCounterHold) {
|
||||||
|
myPulseCounter = ~(myPulseCounter >> 1) & 0x07;
|
||||||
|
|
||||||
|
if (pulseFeedback) {
|
||||||
|
myPulseCounter |= 0x08;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (myPulseCounter & 0x01) * myAudv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::audc(unsigned char value)
|
||||||
|
{
|
||||||
|
myAudc = value & 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::audv(unsigned char value)
|
||||||
|
{
|
||||||
|
myAudv = value & 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void AudioChannel::audf(unsigned char value)
|
||||||
|
{
|
||||||
|
myAudf = value & 0x1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// SSSS tt lll lll
|
||||||
|
// SS SS tt ll ll
|
||||||
|
// SS tttttt eeee ll ll aaaa
|
||||||
|
// SSSS tt ee ee ll ll aa
|
||||||
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
||||||
|
// SS SS tt ee ll ll aa aa
|
||||||
|
// SSSS ttt eeeee llll llll aaaaa
|
||||||
|
//
|
||||||
|
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
|
||||||
|
// and the Stella Team
|
||||||
|
//
|
||||||
|
// See the file "License.txt" for information on usage and redistribution of
|
||||||
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
#ifndef TIA_AUDIO_CHANNEL_HXX
|
||||||
|
#define TIA_AUDIO_CHANNEL_HXX
|
||||||
|
|
||||||
|
namespace TIA {
|
||||||
|
class AudioChannel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AudioChannel() = default;
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void phase0();
|
||||||
|
|
||||||
|
unsigned char phase1();
|
||||||
|
|
||||||
|
void audc(unsigned char value);
|
||||||
|
|
||||||
|
void audf(unsigned char value);
|
||||||
|
|
||||||
|
void audv(unsigned char value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned char myAudc{0};
|
||||||
|
unsigned char myAudv{0};
|
||||||
|
unsigned char myAudf{0};
|
||||||
|
|
||||||
|
bool myClockEnable{false};
|
||||||
|
bool myNoiseFeedback{false};
|
||||||
|
bool myNoiseCounterBit4{false};
|
||||||
|
bool myPulseCounterHold{false};
|
||||||
|
|
||||||
|
unsigned char myDivCounter{0};
|
||||||
|
unsigned char myPulseCounter{0};
|
||||||
|
unsigned char myNoiseCounter{0};
|
||||||
|
|
||||||
|
private:
|
||||||
|
AudioChannel(const AudioChannel&) = delete;
|
||||||
|
AudioChannel(AudioChannel&&) = delete;
|
||||||
|
AudioChannel& operator=(const AudioChannel&) = delete;
|
||||||
|
AudioChannel& operator=(AudioChannel&&) = delete;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TIA_AUDIO_CHANNEL_HXX
|
|
@ -1,377 +0,0 @@
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// SSSS tt lll lll
|
|
||||||
// SS SS tt ll ll
|
|
||||||
// SS tttttt eeee ll ll aaaa
|
|
||||||
// SSSS tt ee ee ll ll aa
|
|
||||||
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
||||||
// SS SS tt ee ll ll aa aa
|
|
||||||
// SSSS ttt eeeee llll llll aaaaa
|
|
||||||
//
|
|
||||||
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
|
|
||||||
// and the Stella Team
|
|
||||||
//
|
|
||||||
// See the file "License.txt" for information on usage and redistribution of
|
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
||||||
//
|
|
||||||
// $Id: TIASnd.cxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
#include "TIATables.h"
|
|
||||||
#include "TIASnd.h"
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
TIASound::TIASound(int outputFrequency)
|
|
||||||
: myChannelMode(Hardware2Stereo),
|
|
||||||
myOutputFrequency(outputFrequency),
|
|
||||||
myVolumePercentage(100)
|
|
||||||
{
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::reset()
|
|
||||||
{
|
|
||||||
// Fill the polynomials
|
|
||||||
polyInit(Bit4, 4, 4, 3);
|
|
||||||
polyInit(Bit5, 5, 5, 3);
|
|
||||||
polyInit(Bit9, 9, 9, 5);
|
|
||||||
|
|
||||||
// Initialize instance variables
|
|
||||||
for(int chan = 0; chan <= 1; ++chan)
|
|
||||||
{
|
|
||||||
myVolume[chan] = 0;
|
|
||||||
myDivNCnt[chan] = 0;
|
|
||||||
myDivNMax[chan] = 0;
|
|
||||||
myDiv3Cnt[chan] = 3;
|
|
||||||
myAUDC[chan] = 0;
|
|
||||||
myAUDF[chan] = 0;
|
|
||||||
myAUDV[chan] = 0;
|
|
||||||
myP4[chan] = 0;
|
|
||||||
myP5[chan] = 0;
|
|
||||||
myP9[chan] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::outputFrequency(int freq)
|
|
||||||
{
|
|
||||||
myOutputFrequency = freq;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
std::string TIASound::channels(unsigned int hardware, bool stereo)
|
|
||||||
{
|
|
||||||
if(hardware == 1)
|
|
||||||
myChannelMode = Hardware1;
|
|
||||||
else
|
|
||||||
myChannelMode = stereo ? Hardware2Stereo : Hardware2Mono;
|
|
||||||
|
|
||||||
switch(myChannelMode)
|
|
||||||
{
|
|
||||||
case Hardware1: return "Hardware1";
|
|
||||||
case Hardware2Mono: return "Hardware2Mono";
|
|
||||||
case Hardware2Stereo: return "Hardware2Stereo";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::set(unsigned short address, unsigned char value)
|
|
||||||
{
|
|
||||||
int chan = ~address & 0x1;
|
|
||||||
switch(address)
|
|
||||||
{
|
|
||||||
case TIARegister::AUDC0:
|
|
||||||
case TIARegister::AUDC1:
|
|
||||||
myAUDC[chan] = value & 0x0f;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TIARegister::AUDF0:
|
|
||||||
case TIARegister::AUDF1:
|
|
||||||
myAUDF[chan] = value & 0x1f;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TIARegister::AUDV0:
|
|
||||||
case TIARegister::AUDV1:
|
|
||||||
myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned short newVal = 0;
|
|
||||||
|
|
||||||
// An AUDC value of 0 is a special case
|
|
||||||
if (myAUDC[chan] == SET_TO_1 || myAUDC[chan] == POLY5_POLY5)
|
|
||||||
{
|
|
||||||
// Indicate the clock is zero so no processing will occur,
|
|
||||||
// and set the output to the selected volume
|
|
||||||
newVal = 0;
|
|
||||||
myVolume[chan] = (myAUDV[chan] * myVolumePercentage) / 100;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Otherwise calculate the 'divide by N' value
|
|
||||||
newVal = myAUDF[chan] + 1;
|
|
||||||
|
|
||||||
// If bits 2 & 3 are set, then multiply the 'div by n' count by 3
|
|
||||||
if((myAUDC[chan] & DIV3_MASK) == DIV3_MASK && myAUDC[chan] != POLY5_DIV3)
|
|
||||||
newVal *= 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only reset those channels that have changed
|
|
||||||
if(newVal != myDivNMax[chan])
|
|
||||||
{
|
|
||||||
// Reset the divide by n counters
|
|
||||||
myDivNMax[chan] = newVal;
|
|
||||||
|
|
||||||
// If the channel is now volume only or was volume only,
|
|
||||||
// reset the counter (otherwise let it complete the previous)
|
|
||||||
if ((myDivNCnt[chan] == 0) || (newVal == 0))
|
|
||||||
myDivNCnt[chan] = newVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
unsigned char TIASound::get(unsigned short address) const
|
|
||||||
{
|
|
||||||
switch(address)
|
|
||||||
{
|
|
||||||
case TIARegister::AUDC0: return myAUDC[0];
|
|
||||||
case TIARegister::AUDC1: return myAUDC[1];
|
|
||||||
case TIARegister::AUDF0: return myAUDF[0];
|
|
||||||
case TIARegister::AUDF1: return myAUDF[1];
|
|
||||||
case TIARegister::AUDV0: return myAUDV[0] >> AUDV_SHIFT;
|
|
||||||
case TIARegister::AUDV1: return myAUDV[1] >> AUDV_SHIFT;
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::volume(unsigned int percent)
|
|
||||||
{
|
|
||||||
if(percent <= 100)
|
|
||||||
myVolumePercentage = percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf)
|
|
||||||
{
|
|
||||||
// Make temporary local copy
|
|
||||||
unsigned char audc0 = myAUDC[0], audc1 = myAUDC[1];
|
|
||||||
unsigned char p5_0 = myP5[0], p5_1 = myP5[1];
|
|
||||||
unsigned char div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1];
|
|
||||||
short v0 = myVolume[0], v1 = myVolume[1];
|
|
||||||
|
|
||||||
// Take external volume into account
|
|
||||||
short audv0 = (myAUDV[0] * myVolumePercentage) / 100,
|
|
||||||
audv1 = (myAUDV[1] * myVolumePercentage) / 100;
|
|
||||||
|
|
||||||
// Loop until the sample buffer is full
|
|
||||||
while(samples > 0)
|
|
||||||
{
|
|
||||||
// Process channel 0
|
|
||||||
if (div_n_cnt0 > 1)
|
|
||||||
{
|
|
||||||
div_n_cnt0--;
|
|
||||||
}
|
|
||||||
else if (div_n_cnt0 == 1)
|
|
||||||
{
|
|
||||||
int prev_bit5 = Bit5[p5_0];
|
|
||||||
div_n_cnt0 = myDivNMax[0];
|
|
||||||
|
|
||||||
// The P5 counter has multiple uses, so we increment it here
|
|
||||||
p5_0++;
|
|
||||||
if (p5_0 == POLY5_SIZE)
|
|
||||||
p5_0 = 0;
|
|
||||||
|
|
||||||
// Check clock modifier for clock tick
|
|
||||||
if ((audc0 & 0x02) == 0 ||
|
|
||||||
((audc0 & 0x01) == 0 && Div31[p5_0]) ||
|
|
||||||
((audc0 & 0x01) == 1 && Bit5[p5_0]) ||
|
|
||||||
((audc0 & 0x0f) == POLY5_DIV3 && Bit5[p5_0] != prev_bit5))
|
|
||||||
{
|
|
||||||
if (audc0 & 0x04) // Pure modified clock selected
|
|
||||||
{
|
|
||||||
if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
|
|
||||||
{
|
|
||||||
if ( Bit5[p5_0] != prev_bit5 )
|
|
||||||
{
|
|
||||||
myDiv3Cnt[0]--;
|
|
||||||
if ( !myDiv3Cnt[0] )
|
|
||||||
{
|
|
||||||
myDiv3Cnt[0] = 3;
|
|
||||||
v0 = v0 ? 0 : audv0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the output was set turn it off, else turn it on
|
|
||||||
v0 = v0 ? 0 : audv0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (audc0 & 0x08) // Check for p5/p9
|
|
||||||
{
|
|
||||||
if (audc0 == POLY9) // Check for poly9
|
|
||||||
{
|
|
||||||
// Increase the poly9 counter
|
|
||||||
myP9[0]++;
|
|
||||||
if (myP9[0] == POLY9_SIZE)
|
|
||||||
myP9[0] = 0;
|
|
||||||
|
|
||||||
v0 = Bit9[myP9[0]] ? audv0 : 0;
|
|
||||||
}
|
|
||||||
else if ( audc0 & 0x02 )
|
|
||||||
{
|
|
||||||
v0 = (v0 || audc0 & 0x01) ? 0 : audv0;
|
|
||||||
}
|
|
||||||
else // Must be poly5
|
|
||||||
{
|
|
||||||
v0 = Bit5[p5_0] ? audv0 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // Poly4 is the only remaining option
|
|
||||||
{
|
|
||||||
// Increase the poly4 counter
|
|
||||||
myP4[0]++;
|
|
||||||
if (myP4[0] == POLY4_SIZE)
|
|
||||||
myP4[0] = 0;
|
|
||||||
|
|
||||||
v0 = Bit4[myP4[0]] ? audv0 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process channel 1
|
|
||||||
if (div_n_cnt1 > 1)
|
|
||||||
{
|
|
||||||
div_n_cnt1--;
|
|
||||||
}
|
|
||||||
else if (div_n_cnt1 == 1)
|
|
||||||
{
|
|
||||||
int prev_bit5 = Bit5[p5_1];
|
|
||||||
|
|
||||||
div_n_cnt1 = myDivNMax[1];
|
|
||||||
|
|
||||||
// The P5 counter has multiple uses, so we increment it here
|
|
||||||
p5_1++;
|
|
||||||
if (p5_1 == POLY5_SIZE)
|
|
||||||
p5_1 = 0;
|
|
||||||
|
|
||||||
// Check clock modifier for clock tick
|
|
||||||
if ((audc1 & 0x02) == 0 ||
|
|
||||||
((audc1 & 0x01) == 0 && Div31[p5_1]) ||
|
|
||||||
((audc1 & 0x01) == 1 && Bit5[p5_1]) ||
|
|
||||||
((audc1 & 0x0f) == POLY5_DIV3 && Bit5[p5_1] != prev_bit5))
|
|
||||||
{
|
|
||||||
if (audc1 & 0x04) // Pure modified clock selected
|
|
||||||
{
|
|
||||||
if ((audc1 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
|
|
||||||
{
|
|
||||||
if ( Bit5[p5_1] != prev_bit5 )
|
|
||||||
{
|
|
||||||
myDiv3Cnt[1]--;
|
|
||||||
if ( ! myDiv3Cnt[1] )
|
|
||||||
{
|
|
||||||
myDiv3Cnt[1] = 3;
|
|
||||||
v1 = v1 ? 0 : audv1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the output was set turn it off, else turn it on
|
|
||||||
v1 = v1 ? 0 : audv1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (audc1 & 0x08) // Check for p5/p9
|
|
||||||
{
|
|
||||||
if (audc1 == POLY9) // Check for poly9
|
|
||||||
{
|
|
||||||
// Increase the poly9 counter
|
|
||||||
myP9[1]++;
|
|
||||||
if (myP9[1] == POLY9_SIZE)
|
|
||||||
myP9[1] = 0;
|
|
||||||
|
|
||||||
v1 = Bit9[myP9[1]] ? audv1 : 0;
|
|
||||||
}
|
|
||||||
else if ( audc1 & 0x02 )
|
|
||||||
{
|
|
||||||
v1 = (v1 || audc1 & 0x01) ? 0 : audv1;
|
|
||||||
}
|
|
||||||
else // Must be poly5
|
|
||||||
{
|
|
||||||
v1 = Bit5[p5_1] ? audv1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // Poly4 is the only remaining option
|
|
||||||
{
|
|
||||||
// Increase the poly4 counter
|
|
||||||
myP4[1]++;
|
|
||||||
if (myP4[1] == POLY4_SIZE)
|
|
||||||
myP4[1] = 0;
|
|
||||||
|
|
||||||
v1 = Bit4[myP4[1]] ? audv1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
short byte = v0 + v1;
|
|
||||||
switch(myChannelMode)
|
|
||||||
{
|
|
||||||
case Hardware2Mono: // mono sampling with 2 hardware channels
|
|
||||||
*(buffer++) = byte;
|
|
||||||
*(buffer++) = byte;
|
|
||||||
samples--;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Hardware2Stereo: // stereo sampling with 2 hardware channels
|
|
||||||
*(buffer++) = v0;
|
|
||||||
*(buffer++) = v1;
|
|
||||||
samples--;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Hardware1: // mono/stereo sampling with only 1 hardware channel
|
|
||||||
*(buffer++) = (v0 + v1) >> 1;
|
|
||||||
samples--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oscBuf!=NULL) {
|
|
||||||
oscBuf[0]->data[oscBuf[0]->needle++]=v0;
|
|
||||||
oscBuf[1]->data[oscBuf[1]->needle++]=v1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save for next round
|
|
||||||
myP5[0] = p5_0;
|
|
||||||
myP5[1] = p5_1;
|
|
||||||
myVolume[0] = v0;
|
|
||||||
myVolume[1] = v1;
|
|
||||||
myDivNCnt[0] = div_n_cnt0;
|
|
||||||
myDivNCnt[1] = div_n_cnt1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void TIASound::polyInit(unsigned char* poly, int size, int f0, int f1)
|
|
||||||
{
|
|
||||||
int mask = (1 << size) - 1, x = mask;
|
|
||||||
|
|
||||||
for(int i = 0; i < mask; i++)
|
|
||||||
{
|
|
||||||
int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01;
|
|
||||||
int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01;
|
|
||||||
poly[i] = x & 1;
|
|
||||||
// calculate next bit
|
|
||||||
x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
const unsigned char TIASound::Div31[POLY5_SIZE] = {
|
|
||||||
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
};
|
|
|
@ -1,186 +0,0 @@
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// SSSS tt lll lll
|
|
||||||
// SS SS tt ll ll
|
|
||||||
// SS tttttt eeee ll ll aaaa
|
|
||||||
// SSSS tt ee ee ll ll aa
|
|
||||||
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
||||||
// SS SS tt ee ll ll aa aa
|
|
||||||
// SSSS ttt eeeee llll llll aaaaa
|
|
||||||
//
|
|
||||||
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
|
|
||||||
// and the Stella Team
|
|
||||||
//
|
|
||||||
// See the file "License.txt" for information on usage and redistribution of
|
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
||||||
//
|
|
||||||
// $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
#ifndef TIASOUND_HXX
|
|
||||||
#define TIASOUND_HXX
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include "../../../dispatch.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
This class implements a fairly accurate emulation of the TIA sound
|
|
||||||
hardware. This class uses code/ideas from z26 and MESS.
|
|
||||||
|
|
||||||
Currently, the sound generation routines work at 31400Hz only.
|
|
||||||
Resampling can be done by passing in a different output frequency.
|
|
||||||
|
|
||||||
@author Bradford W. Mott, Stephen Anthony, z26 and MESS teams
|
|
||||||
@version $Id: TIASnd.hxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
*/
|
|
||||||
class TIASound
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
Create a new TIA Sound object using the specified output frequency
|
|
||||||
*/
|
|
||||||
TIASound(int outputFrequency = 31400);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
Reset the sound emulation to its power-on state
|
|
||||||
*/
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
/**
|
|
||||||
Set the frequency output samples should be generated at
|
|
||||||
*/
|
|
||||||
void outputFrequency(int freq);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Selects the number of audio channels per sample. There are two factors
|
|
||||||
to consider: hardware capability and desired mixing.
|
|
||||||
|
|
||||||
@param hardware The number of channels supported by the sound system
|
|
||||||
@param stereo Whether to output the internal sound signals into 1
|
|
||||||
or 2 channels
|
|
||||||
|
|
||||||
@return Status of the channel configuration used
|
|
||||||
*/
|
|
||||||
std::string channels(unsigned int hardware, bool stereo);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
Sets the specified sound register to the given value
|
|
||||||
|
|
||||||
@param address Register address
|
|
||||||
@param value Value to store in the register
|
|
||||||
*/
|
|
||||||
void set(unsigned short address, unsigned char value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Gets the specified sound register's value
|
|
||||||
|
|
||||||
@param address Register address
|
|
||||||
*/
|
|
||||||
unsigned char get(unsigned short address) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create sound samples based on the current sound register settings
|
|
||||||
in the specified buffer. NOTE: If channels is set to stereo then
|
|
||||||
the buffer will need to be twice as long as the number of samples.
|
|
||||||
|
|
||||||
@param buffer The location to store generated samples
|
|
||||||
@param samples The number of samples to generate
|
|
||||||
*/
|
|
||||||
void process(short* buffer, unsigned int samples, DivDispatchOscBuffer** oscBuf=NULL);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Set the volume of the samples created (0-100)
|
|
||||||
*/
|
|
||||||
void volume(unsigned int percent);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void polyInit(unsigned char* poly, int size, int f0, int f1);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Definitions for AUDCx (15, 16)
|
|
||||||
enum AUDCxRegister
|
|
||||||
{
|
|
||||||
SET_TO_1 = 0x00, // 0000
|
|
||||||
POLY4 = 0x01, // 0001
|
|
||||||
DIV31_POLY4 = 0x02, // 0010
|
|
||||||
POLY5_POLY4 = 0x03, // 0011
|
|
||||||
PURE1 = 0x04, // 0100
|
|
||||||
PURE2 = 0x05, // 0101
|
|
||||||
DIV31_PURE = 0x06, // 0110
|
|
||||||
POLY5_2 = 0x07, // 0111
|
|
||||||
POLY9 = 0x08, // 1000
|
|
||||||
POLY5 = 0x09, // 1001
|
|
||||||
DIV31_POLY5 = 0x0a, // 1010
|
|
||||||
POLY5_POLY5 = 0x0b, // 1011
|
|
||||||
DIV3_PURE = 0x0c, // 1100
|
|
||||||
DIV3_PURE2 = 0x0d, // 1101
|
|
||||||
DIV93_PURE = 0x0e, // 1110
|
|
||||||
POLY5_DIV3 = 0x0f // 1111
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
|
||||||
POLY4_SIZE = 0x000f,
|
|
||||||
POLY5_SIZE = 0x001f,
|
|
||||||
POLY9_SIZE = 0x01ff,
|
|
||||||
DIV3_MASK = 0x0c,
|
|
||||||
AUDV_SHIFT = 10 // shift 2 positions for AUDV,
|
|
||||||
// then another 8 for 16-bit sound
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ChannelMode {
|
|
||||||
Hardware2Mono, // mono sampling with 2 hardware channels
|
|
||||||
Hardware2Stereo, // stereo sampling with 2 hardware channels
|
|
||||||
Hardware1 // mono/stereo sampling with only 1 hardware channel
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Structures to hold the 6 tia sound control bytes
|
|
||||||
unsigned char myAUDC[2]; // AUDCx (15, 16)
|
|
||||||
unsigned char myAUDF[2]; // AUDFx (17, 18)
|
|
||||||
short myAUDV[2]; // AUDVx (19, 1A)
|
|
||||||
|
|
||||||
short myVolume[2]; // Last output volume for each channel
|
|
||||||
|
|
||||||
unsigned char myP4[2]; // Position pointer for the 4-bit POLY array
|
|
||||||
unsigned char myP5[2]; // Position pointer for the 5-bit POLY array
|
|
||||||
unsigned short myP9[2]; // Position pointer for the 9-bit POLY array
|
|
||||||
|
|
||||||
unsigned char myDivNCnt[2]; // Divide by n counter. one for each channel
|
|
||||||
unsigned char myDivNMax[2]; // Divide by n maximum, one for each channel
|
|
||||||
unsigned char myDiv3Cnt[2]; // Div 3 counter, used for POLY5_DIV3 mode
|
|
||||||
|
|
||||||
ChannelMode myChannelMode;
|
|
||||||
int myOutputFrequency;
|
|
||||||
unsigned int myVolumePercentage;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Initialize the bit patterns for the polynomials (at runtime).
|
|
||||||
|
|
||||||
The 4bit and 5bit patterns are the identical ones used in the tia chip.
|
|
||||||
Though the patterns could be packed with 8 bits per byte, using only a
|
|
||||||
single bit per byte keeps the math simple, which is important for
|
|
||||||
efficient processing.
|
|
||||||
*/
|
|
||||||
unsigned char Bit4[POLY4_SIZE];
|
|
||||||
unsigned char Bit5[POLY5_SIZE];
|
|
||||||
unsigned char Bit9[POLY9_SIZE];
|
|
||||||
|
|
||||||
/*
|
|
||||||
The 'Div by 31' counter is treated as another polynomial because of
|
|
||||||
the way it operates. It does not have a 50% duty cycle, but instead
|
|
||||||
has a 13:18 ratio (of course, 13+18 = 31). This could also be
|
|
||||||
implemented by using counters.
|
|
||||||
*/
|
|
||||||
static const unsigned char Div31[POLY5_SIZE];
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Following constructors and assignment operators not supported
|
|
||||||
TIASound(const TIASound&) = delete;
|
|
||||||
TIASound(TIASound&&) = delete;
|
|
||||||
TIASound& operator=(const TIASound&) = delete;
|
|
||||||
TIASound& operator=(TIASound&&) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,219 +0,0 @@
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// SSSS tt lll lll
|
|
||||||
// SS SS tt ll ll
|
|
||||||
// SS tttttt eeee ll ll aaaa
|
|
||||||
// SSSS tt ee ee ll ll aa
|
|
||||||
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
||||||
// SS SS tt ee ll ll aa aa
|
|
||||||
// SSSS ttt eeeee llll llll aaaaa
|
|
||||||
//
|
|
||||||
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
|
|
||||||
// and the Stella Team
|
|
||||||
//
|
|
||||||
// See the file "License.txt" for information on usage and redistribution of
|
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
||||||
//
|
|
||||||
// $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
#ifndef TIA_TABLES_HXX
|
|
||||||
#define TIA_TABLES_HXX
|
|
||||||
|
|
||||||
enum TIABit {
|
|
||||||
P0Bit = 0x01, // Bit for Player 0
|
|
||||||
M0Bit = 0x02, // Bit for Missle 0
|
|
||||||
P1Bit = 0x04, // Bit for Player 1
|
|
||||||
M1Bit = 0x08, // Bit for Missle 1
|
|
||||||
BLBit = 0x10, // Bit for Ball
|
|
||||||
PFBit = 0x20, // Bit for Playfield
|
|
||||||
ScoreBit = 0x40, // Bit for Playfield score mode
|
|
||||||
PriorityBit = 0x80 // Bit for Playfield priority
|
|
||||||
};
|
|
||||||
|
|
||||||
enum TIAColor {
|
|
||||||
BKColor = 0, // Color index for Background
|
|
||||||
PFColor = 1, // Color index for Playfield
|
|
||||||
P0Color = 2, // Color index for Player 0
|
|
||||||
P1Color = 3, // Color index for Player 1
|
|
||||||
M0Color = 4, // Color index for Missle 0
|
|
||||||
M1Color = 5, // Color index for Missle 1
|
|
||||||
BLColor = 6, // Color index for Ball
|
|
||||||
HBLANKColor = 7 // Color index for HMove blank area
|
|
||||||
};
|
|
||||||
|
|
||||||
enum CollisionBit
|
|
||||||
{
|
|
||||||
Cx_M0P1 = 1 << 0, // Missle0 - Player1 collision
|
|
||||||
Cx_M0P0 = 1 << 1, // Missle0 - Player0 collision
|
|
||||||
Cx_M1P0 = 1 << 2, // Missle1 - Player0 collision
|
|
||||||
Cx_M1P1 = 1 << 3, // Missle1 - Player1 collision
|
|
||||||
Cx_P0PF = 1 << 4, // Player0 - Playfield collision
|
|
||||||
Cx_P0BL = 1 << 5, // Player0 - Ball collision
|
|
||||||
Cx_P1PF = 1 << 6, // Player1 - Playfield collision
|
|
||||||
Cx_P1BL = 1 << 7, // Player1 - Ball collision
|
|
||||||
Cx_M0PF = 1 << 8, // Missle0 - Playfield collision
|
|
||||||
Cx_M0BL = 1 << 9, // Missle0 - Ball collision
|
|
||||||
Cx_M1PF = 1 << 10, // Missle1 - Playfield collision
|
|
||||||
Cx_M1BL = 1 << 11, // Missle1 - Ball collision
|
|
||||||
Cx_BLPF = 1 << 12, // Ball - Playfield collision
|
|
||||||
Cx_P0P1 = 1 << 13, // Player0 - Player1 collision
|
|
||||||
Cx_M0M1 = 1 << 14 // Missle0 - Missle1 collision
|
|
||||||
};
|
|
||||||
|
|
||||||
// TIA Write/Read register names
|
|
||||||
enum TIARegister {
|
|
||||||
VSYNC = 0x00, // Write: vertical sync set-clear (D1)
|
|
||||||
VBLANK = 0x01, // Write: vertical blank set-clear (D7-6,D1)
|
|
||||||
WSYNC = 0x02, // Write: wait for leading edge of hrz. blank (strobe)
|
|
||||||
RSYNC = 0x03, // Write: reset hrz. sync counter (strobe)
|
|
||||||
NUSIZ0 = 0x04, // Write: number-size player-missle 0 (D5-0)
|
|
||||||
NUSIZ1 = 0x05, // Write: number-size player-missle 1 (D5-0)
|
|
||||||
COLUP0 = 0x06, // Write: color-lum player 0 (D7-1)
|
|
||||||
COLUP1 = 0x07, // Write: color-lum player 1 (D7-1)
|
|
||||||
COLUPF = 0x08, // Write: color-lum playfield (D7-1)
|
|
||||||
COLUBK = 0x09, // Write: color-lum background (D7-1)
|
|
||||||
CTRLPF = 0x0a, // Write: cntrl playfield ballsize & coll. (D5-4,D2-0)
|
|
||||||
REFP0 = 0x0b, // Write: reflect player 0 (D3)
|
|
||||||
REFP1 = 0x0c, // Write: reflect player 1 (D3)
|
|
||||||
PF0 = 0x0d, // Write: playfield register byte 0 (D7-4)
|
|
||||||
PF1 = 0x0e, // Write: playfield register byte 1 (D7-0)
|
|
||||||
PF2 = 0x0f, // Write: playfield register byte 2 (D7-0)
|
|
||||||
RESP0 = 0x10, // Write: reset player 0 (strobe)
|
|
||||||
RESP1 = 0x11, // Write: reset player 1 (strobe)
|
|
||||||
RESM0 = 0x12, // Write: reset missle 0 (strobe)
|
|
||||||
RESM1 = 0x13, // Write: reset missle 1 (strobe)
|
|
||||||
RESBL = 0x14, // Write: reset ball (strobe)
|
|
||||||
AUDC0 = 0x15, // Write: audio control 0 (D3-0)
|
|
||||||
AUDC1 = 0x16, // Write: audio control 1 (D4-0)
|
|
||||||
AUDF0 = 0x17, // Write: audio frequency 0 (D4-0)
|
|
||||||
AUDF1 = 0x18, // Write: audio frequency 1 (D3-0)
|
|
||||||
AUDV0 = 0x19, // Write: audio volume 0 (D3-0)
|
|
||||||
AUDV1 = 0x1a, // Write: audio volume 1 (D3-0)
|
|
||||||
GRP0 = 0x1b, // Write: graphics player 0 (D7-0)
|
|
||||||
GRP1 = 0x1c, // Write: graphics player 1 (D7-0)
|
|
||||||
ENAM0 = 0x1d, // Write: graphics (enable) missle 0 (D1)
|
|
||||||
ENAM1 = 0x1e, // Write: graphics (enable) missle 1 (D1)
|
|
||||||
ENABL = 0x1f, // Write: graphics (enable) ball (D1)
|
|
||||||
HMP0 = 0x20, // Write: horizontal motion player 0 (D7-4)
|
|
||||||
HMP1 = 0x21, // Write: horizontal motion player 1 (D7-4)
|
|
||||||
HMM0 = 0x22, // Write: horizontal motion missle 0 (D7-4)
|
|
||||||
HMM1 = 0x23, // Write: horizontal motion missle 1 (D7-4)
|
|
||||||
HMBL = 0x24, // Write: horizontal motion ball (D7-4)
|
|
||||||
VDELP0 = 0x25, // Write: vertical delay player 0 (D0)
|
|
||||||
VDELP1 = 0x26, // Write: vertical delay player 1 (D0)
|
|
||||||
VDELBL = 0x27, // Write: vertical delay ball (D0)
|
|
||||||
RESMP0 = 0x28, // Write: reset missle 0 to player 0 (D1)
|
|
||||||
RESMP1 = 0x29, // Write: reset missle 1 to player 1 (D1)
|
|
||||||
HMOVE = 0x2a, // Write: apply horizontal motion (strobe)
|
|
||||||
HMCLR = 0x2b, // Write: clear horizontal motion registers (strobe)
|
|
||||||
CXCLR = 0x2c, // Write: clear collision latches (strobe)
|
|
||||||
|
|
||||||
CXM0P = 0x00, // Read collision: D7=(M0,P1); D6=(M0,P0)
|
|
||||||
CXM1P = 0x01, // Read collision: D7=(M1,P0); D6=(M1,P1)
|
|
||||||
CXP0FB = 0x02, // Read collision: D7=(P0,PF); D6=(P0,BL)
|
|
||||||
CXP1FB = 0x03, // Read collision: D7=(P1,PF); D6=(P1,BL)
|
|
||||||
CXM0FB = 0x04, // Read collision: D7=(M0,PF); D6=(M0,BL)
|
|
||||||
CXM1FB = 0x05, // Read collision: D7=(M1,PF); D6=(M1,BL)
|
|
||||||
CXBLPF = 0x06, // Read collision: D7=(BL,PF); D6=(unused)
|
|
||||||
CXPPMM = 0x07, // Read collision: D7=(P0,P1); D6=(M0,M1)
|
|
||||||
INPT0 = 0x08, // Read pot port: D7
|
|
||||||
INPT1 = 0x09, // Read pot port: D7
|
|
||||||
INPT2 = 0x0a, // Read pot port: D7
|
|
||||||
INPT3 = 0x0b, // Read pot port: D7
|
|
||||||
INPT4 = 0x0c, // Read P1 joystick trigger: D7
|
|
||||||
INPT5 = 0x0d // Read P2 joystick trigger: D7
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
The TIA class uses some static tables that aren't dependent on the actual
|
|
||||||
TIA state. For code organization, it's better to place that functionality
|
|
||||||
here.
|
|
||||||
|
|
||||||
@author Stephen Anthony
|
|
||||||
@version $Id: TIATables.hxx 3239 2015-12-29 19:22:46Z stephena $
|
|
||||||
*/
|
|
||||||
class TIATables
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
Compute all static tables used by the TIA
|
|
||||||
*/
|
|
||||||
static void computeAllTables();
|
|
||||||
|
|
||||||
// Player mask table
|
|
||||||
// [suppress mode][nusiz][pixel]
|
|
||||||
static unsigned char PxMask[2][8][320];
|
|
||||||
|
|
||||||
// Missle mask table (entries are true or false)
|
|
||||||
// [number][size][pixel]
|
|
||||||
// There are actually only 4 possible size combinations on a real system
|
|
||||||
// The fifth size is used for simulating the starfield effect in
|
|
||||||
// Cosmic Ark and Stay Frosty
|
|
||||||
static unsigned char MxMask[8][5][320];
|
|
||||||
|
|
||||||
// Ball mask table (entries are true or false)
|
|
||||||
// [size][pixel]
|
|
||||||
static unsigned char BLMask[4][320];
|
|
||||||
|
|
||||||
// Playfield mask table for reflected and non-reflected playfields
|
|
||||||
// [reflect, pixel]
|
|
||||||
static unsigned int PFMask[2][160];
|
|
||||||
|
|
||||||
// A mask table which can be used when an object is disabled
|
|
||||||
static unsigned char DisabledMask[640];
|
|
||||||
|
|
||||||
// Used to set the collision register to the correct value
|
|
||||||
static unsigned short CollisionMask[64];
|
|
||||||
|
|
||||||
// Indicates the update delay associated with poking at a TIA address
|
|
||||||
static const short PokeDelay[64];
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// Used to convert value written in a motion register into
|
|
||||||
// its internal representation
|
|
||||||
static const int CompleteMotion[76][16];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Indicates if HMOVE blanks should occur for the corresponding cycle
|
|
||||||
static const bool HMOVEBlankEnableCycles[76];
|
|
||||||
|
|
||||||
// Used to reflect a players graphics
|
|
||||||
static unsigned char GRPReflect[256];
|
|
||||||
|
|
||||||
// Indicates if player is being reset during delay, display or other times
|
|
||||||
// [nusiz][old pixel][new pixel]
|
|
||||||
static signed char PxPosResetWhen[8][160][160];
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Compute the collision decode table
|
|
||||||
static void buildCollisionMaskTable();
|
|
||||||
|
|
||||||
// Compute the player mask table
|
|
||||||
static void buildPxMaskTable();
|
|
||||||
|
|
||||||
// Compute the missle mask table
|
|
||||||
static void buildMxMaskTable();
|
|
||||||
|
|
||||||
// Compute the ball mask table
|
|
||||||
static void buildBLMaskTable();
|
|
||||||
|
|
||||||
// Compute playfield mask table
|
|
||||||
static void buildPFMaskTable();
|
|
||||||
|
|
||||||
// Compute the player reflect table
|
|
||||||
static void buildGRPReflectTable();
|
|
||||||
|
|
||||||
// Compute the player position reset when table
|
|
||||||
static void buildPxPosResetWhenTable();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Following constructors and assignment operators not supported
|
|
||||||
TIATables() = delete;
|
|
||||||
TIATables(const TIATables&) = delete;
|
|
||||||
TIATables(TIATables&&) = delete;
|
|
||||||
TIATables& operator=(const TIATables&) = delete;
|
|
||||||
TIATables& operator=(TIATables&&) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -292,6 +292,10 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3
|
||||||
|
|
||||||
// note from tildearrow:
|
// note from tildearrow:
|
||||||
// - are you kidding? I have to write to this "load preset" register before keying on?
|
// - are you kidding? I have to write to this "load preset" register before keying on?
|
||||||
|
// another note from tildearrow:
|
||||||
|
// - see https://github.com/110-kenichi/ymfm/blob/main/src/ymfm_opz.cpp
|
||||||
|
// - is 0x08 the actual key on register just like OPM?
|
||||||
|
// - if so then what's bit 5?
|
||||||
if ((index & 0xf8) == 0x20 /*&& bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)*/)
|
if ((index & 0xf8) == 0x20 /*&& bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)*/)
|
||||||
{
|
{
|
||||||
channel = bitfield(index, 0, 3);
|
channel = bitfield(index, 0, 3);
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#define rWrite(a,v) if (!skipRegisterWrites) {tia.set(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} }
|
#define rWrite(a,v) if (!skipRegisterWrites) {tia.write(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||||
|
|
||||||
const char* regCheatSheetTIA[]={
|
const char* regCheatSheetTIA[]={
|
||||||
"AUDC0", "15",
|
"AUDC0", "15",
|
||||||
|
@ -39,7 +39,22 @@ const char** DivPlatformTIA::getRegisterSheet() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
void DivPlatformTIA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
tia.process(bufL+start,len,oscBuf);
|
for (size_t h=start; h<start+len; h++) {
|
||||||
|
tia.tick();
|
||||||
|
if (mixingType==2) {
|
||||||
|
bufL[h]=tia.myCurrentSample[0];
|
||||||
|
bufR[h]=tia.myCurrentSample[1];
|
||||||
|
} else if (mixingType==1) {
|
||||||
|
bufL[h]=(tia.myCurrentSample[0]+tia.myCurrentSample[1])>>1;
|
||||||
|
} else {
|
||||||
|
bufL[h]=tia.myCurrentSample[0];
|
||||||
|
}
|
||||||
|
if (++chanOscCounter>=114) {
|
||||||
|
chanOscCounter=0;
|
||||||
|
oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0];
|
||||||
|
oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pitch) {
|
unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pitch) {
|
||||||
|
@ -300,7 +315,7 @@ int DivPlatformTIA::getRegisterPoolSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivPlatformTIA::reset() {
|
void DivPlatformTIA::reset() {
|
||||||
tia.reset();
|
tia.reset(mixingType);
|
||||||
memset(regPool,0,16);
|
memset(regPool,0,16);
|
||||||
for (int i=0; i<2; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
chan[i]=DivPlatformTIA::Channel();
|
chan[i]=DivPlatformTIA::Channel();
|
||||||
|
@ -309,8 +324,12 @@ void DivPlatformTIA::reset() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float DivPlatformTIA::getPostAmp() {
|
||||||
|
return 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
bool DivPlatformTIA::isStereo() {
|
bool DivPlatformTIA::isStereo() {
|
||||||
return false;
|
return (mixingType==2);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DivPlatformTIA::keyOffAffectsArp(int ch) {
|
bool DivPlatformTIA::keyOffAffectsArp(int ch) {
|
||||||
|
@ -333,25 +352,28 @@ void DivPlatformTIA::poke(std::vector<DivRegWrite>& wlist) {
|
||||||
|
|
||||||
void DivPlatformTIA::setFlags(unsigned int flags) {
|
void DivPlatformTIA::setFlags(unsigned int flags) {
|
||||||
if (flags&1) {
|
if (flags&1) {
|
||||||
rate=31250;
|
rate=COLOR_PAL*4.0/5.0;
|
||||||
} else {
|
} else {
|
||||||
rate=31468;
|
rate=COLOR_NTSC;
|
||||||
}
|
}
|
||||||
chipClock=rate;
|
chipClock=rate;
|
||||||
|
mixingType=(flags>>1)&3;
|
||||||
for (int i=0; i<2; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
oscBuf[i]->rate=rate;
|
oscBuf[i]->rate=rate/114;
|
||||||
}
|
}
|
||||||
|
tia.reset(mixingType);
|
||||||
}
|
}
|
||||||
|
|
||||||
int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
int DivPlatformTIA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
parent=p;
|
parent=p;
|
||||||
dumpWrites=false;
|
dumpWrites=false;
|
||||||
skipRegisterWrites=false;
|
skipRegisterWrites=false;
|
||||||
|
mixingType=0;
|
||||||
|
chanOscCounter=0;
|
||||||
for (int i=0; i<2; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
isMuted[i]=false;
|
isMuted[i]=false;
|
||||||
oscBuf[i]=new DivDispatchOscBuffer;
|
oscBuf[i]=new DivDispatchOscBuffer;
|
||||||
}
|
}
|
||||||
tia.channels(1,false);
|
|
||||||
setFlags(flags);
|
setFlags(flags);
|
||||||
reset();
|
reset();
|
||||||
return 2;
|
return 2;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue