Merge branch 'master' of https://github.com/tildearrow/furnace into sample_macro

This commit is contained in:
cam900 2022-09-18 00:11:53 +09:00
commit 0f5a400b29
140 changed files with 2646 additions and 1419 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ linuxbuild/
test/songs/
test/delta/
test/result/
test/assert_delta
android/.gradle/
android/app/build/
android/app/.cxx/

View File

@ -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/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_opm.cpp

View File

@ -22,7 +22,7 @@ bug fixes, improvements and several other things accepted.
the coding style is described here:
- indentation: two spaces
- indentation: two spaces. **strictly** spaces. do NOT use tabs.
- modified 1TBS style:
- no spaces in function calls
- spaces between arguments in function declarations

View File

@ -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
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)
- saves .dmf modules - both modern and legacy
- 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)
- bug/quirk implementation for increased playback accuracy through compatibility flags
- VGM export
@ -103,7 +103,7 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
- built-in visualizer in pattern view
- open-source under GPLv2 or later.
***
---
# quick references
- **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.
- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
***
---
# developer info
[![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.**
***
---
# frequently asked questions
> 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.
***
---
# footnotes
copyright (C) 2021-2022 tildearrow and contributors.

View File

@ -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
- "paste with instrument"
- "paste with instrument"
- FM operator muting
- FM operator swap
- bug fixes

View File

@ -15,8 +15,8 @@ android {
}
minSdkVersion 21
targetSdkVersion 26
versionCode 93
versionName "0.6pre1"
versionCode 113
versionName "dev113"
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.tildearrow.furnace"
android:versionCode="93"
android:versionName="0.6pre1"
android:versionCode="113"
android:versionName="dev113"
android:installLocation="auto">
<!-- OpenGL ES 2.0 -->
@ -83,13 +83,17 @@
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<!-- Drop file event -->
<!--
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:host="*"/>
<data android:mimeType="*/*"/>
<data android:pathPattern=".*\\.fur"/>
</intent-filter>
-->
</activity>
</application>

Binary file not shown.

Binary file not shown.

BIN
demos/ChaosTune.fur Normal file

Binary file not shown.

BIN
demos/FEDMS.fur Normal file

Binary file not shown.

BIN
demos/GEN Equinox Intro.fur Normal file

Binary file not shown.

BIN
demos/her11_veraedit.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/iji_tor.fur Normal file

Binary file not shown.

BIN
demos/lunacommdemo.fur Normal file

Binary file not shown.

2
extern/Nuked-OPN2 vendored

@ -1 +1 @@
Subproject commit 64704a443f8f6c1906ba26297092ea70fa1d45d7
Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250

6
extern/opm/opm.c vendored
View File

@ -1,5 +1,5 @@
/* Nuked OPM
* Copyright (C) 2020 Nuke.YKT
* Copyright (C) 2022 Nuke.YKT
*
* This file is part of Nuked OPM.
*
@ -21,7 +21,7 @@
* siliconpr0n.org(digshadow, John McMaster):
* YM2151 and other FM chip decaps and die shots.
*
* version: 0.9.2 beta
* version: 0.9.3 beta
*/
#include <string.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]);
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;
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.

View File

@ -64,7 +64,3 @@ In there, you can modify certain data pertaining to your sample, such as the:
- 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.
# 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,

View File

@ -1,5 +1,5 @@
# 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

View File

@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are:
- 114: Furnace dev114
- 113: Furnace dev113
- 112: Furnace dev112
- 111: Furnace dev111
- 110: Furnace dev110
@ -254,6 +256,7 @@ size | description
| - 0xc3: OPN CSM - 10 channels
| - 0xc4: PC-98 CSM - 20 channels
| - 0xc5: YM2610B CSM - 20 channels
| - 0xc6: MSM5232 - 8 channels
| - 0xde: YM2610B extended - 19 channels
| - 0xe0: QSound - 19 channels
| - 0xfd: Dummy System - 8 channels
@ -338,7 +341,8 @@ size | description
1 | broken initial position of porta after arp (>=101) or reserved
1 | SN periods under 8 are treated as 1 (>=108) 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**
2 | virtual tempo numerator 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 | ws
1 | ksr
12 | reserved
1 | operator enabled (>=114) or reserved
11 | reserved
--- | **Game Boy instrument data**
1 | volume
1 | direction

View File

@ -239,6 +239,10 @@ void DivEngine::setConf(String key, double 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) {
conf[key]=value;
}

View File

@ -147,37 +147,68 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
int nextOrder=-1;
int nextRow=0;
int effectVal=0;
int lastSuspectedLoopEnd=-1;
DivPattern* pat[DIV_MAX_CHANS];
unsigned char wsWalked[8192];
memset(wsWalked,0,8192);
for (int i=0; i<curSubSong->ordersLen; i++) {
for (int j=0; j<chans; j++) {
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
}
if (i>lastSuspectedLoopEnd) {
lastSuspectedLoopEnd=i;
}
for (int j=nextRow; j<curSubSong->patLen; j++) {
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 l=0; l<curPat[k].effectCols; l++) {
effectVal=pat[k]->data[j][5+(l<<1)];
if (effectVal<0) effectVal=0;
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
nextOrder=i+1;
nextRow=effectVal;
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)) {
nextOrder=i+1;
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) {
if (nextOrder==-1) {
if (nextOrder==-1 || song.jumpTreatment==0) {
nextOrder=effectVal;
nextRow=0;
if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) {
nextRow=0;
}
changingOrder=true;
}
}
}
}
wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7);
if (nextOrder!=-1) {
if (nextOrder<=i) {
loopOrder=nextOrder;
loopRow=nextRow;
loopEnd=i;
return;
}
i=nextOrder-1;
nextOrder=-1;
break;
@ -1659,6 +1690,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
speedAB=false;
playing=true;
skipping=true;
memset(walked,0,8192);
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->setSkipRegisterWrites(true);
while (playing && curOrder<goal) {
if (nextTick(preserveDrift)) {
@ -3440,9 +3472,8 @@ void DivEngine::setConsoleMode(bool enable) {
}
bool DivEngine::switchMaster() {
deinitAudioBackend();
quitDispatch();
initDispatch();
logI("switching output...");
deinitAudioBackend(true);
if (initAudioBackend()) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].setRates(got.rate);
@ -3586,6 +3617,7 @@ void DivEngine::quitDispatch() {
bool DivEngine::initAudioBackend() {
// load values
logI("initializing audio.");
if (audioEngine==DIV_AUDIO_NULL) {
if (getConfString("audioEngine","SDL")=="JACK") {
audioEngine=DIV_AUDIO_JACK;
@ -3691,8 +3723,9 @@ bool DivEngine::initAudioBackend() {
return true;
}
bool DivEngine::deinitAudioBackend() {
bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) {
if (output!=NULL) {
logI("closing audio output.");
output->quit();
if (output->midiIn) {
if (output->midiIn->isDeviceOpen()) {
@ -3709,7 +3742,9 @@ bool DivEngine::deinitAudioBackend() {
output->quitMidi();
delete output;
output=NULL;
//audioEngine=DIV_AUDIO_NULL;
if (dueToSwitchMaster) {
audioEngine=DIV_AUDIO_NULL;
}
}
return true;
}

View File

@ -46,9 +46,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev112"
#define DIV_ENGINE_VERSION 112
#define DIV_VERSION "dev114"
#define DIV_ENGINE_VERSION 114
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -358,6 +357,8 @@ class DivEngine {
double exportFadeOut;
std::map<String,String> conf;
std::deque<DivNoteEvent> pendingNotes;
// bitfield
unsigned char walked[8192];
bool isMuted[DIV_MAX_CHANS];
std::mutex isBusy, saveLock;
String configPath;
@ -453,7 +454,7 @@ class DivEngine {
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
bool initAudioBackend();
bool deinitAudioBackend();
bool deinitAudioBackend(bool dueToSwitchMaster=false);
void registerSystems();
void initSongWithDesc(const int* description);
@ -545,6 +546,7 @@ class DivEngine {
void setConf(String key, int value);
void setConf(String key, float value);
void setConf(String key, double value);
void setConf(String key, const char* value);
void setConf(String key, String value);
// calculate base frequency/period
@ -1074,6 +1076,7 @@ class DivEngine {
memset(reversePitchTable,0,4096*sizeof(int));
memset(pitchTable,0,4096*sizeof(int));
memset(sysDefs,0,256*sizeof(void*));
memset(walked,0,8192);
for (int i=0; i<256; i++) {
sysFileMapFur[i]=DIV_SYSTEM_NULL;

View File

@ -179,6 +179,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.brokenPortaArp=false;
ds.snNoLowPeriods=true;
ds.delayBehavior=0;
ds.jumpTreatment=2;
// 1.1 compat flags
if (ds.version>24) {
@ -1081,6 +1082,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<110) {
ds.delayBehavior=1;
}
if (ds.version<113) {
ds.jumpTreatment=1;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -1503,7 +1507,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else {
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();
}
}
@ -3751,7 +3760,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(song.brokenPortaArp);
w->writeC(song.snNoLowPeriods);
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);
}

View File

@ -150,9 +150,13 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
ins->type=DIV_INS_FM;
logD("instrument type is Arcade");
break;
case 9: // Neo Geo
ins->type=DIV_INS_FM;
logD("instrument type is Neo Geo");
break;
default:
logD("instrument type is unknown");
lastError="unknown instrument type!";
lastError=fmt::sprintf("unknown instrument type %d!",sys);
delete ins;
return;
break;
@ -171,11 +175,21 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
mode=reader.readC();
logD("instrument mode is %d",mode);
if (mode==0) {
if (version<11) {
ins->type=DIV_INS_STD;
if (ins->type==DIV_INS_FM) {
if (sys==9) {
ins->type=DIV_INS_AY;
} else {
ins->type=DIV_INS_STD;
}
}
} else {
ins->type=DIV_INS_FM;
if (sys==3 || sys==6) {
ins->type=DIV_INS_OPLL;
} else if (sys==1) {
ins->type=DIV_INS_OPL;
} else {
ins->type=DIV_INS_FM;
}
}
} else {
ins->type=DIV_INS_FM;
@ -232,12 +246,23 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
ins->fm.op[j].dvb=reader.readC();
ins->fm.op[j].dam=reader.readC();
} else {
ins->fm.op[j].rs=reader.readC();
ins->fm.op[j].dt=reader.readC();
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
ins->fm.op[j].dt&=15;
ins->fm.op[j].d2r=reader.readC();
ins->fm.op[j].ssgEnv=reader.readC();
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 {
ins->fm.op[j].rs=reader.readC();
ins->fm.op[j].dt=reader.readC();
ins->fm.op[j].dt2=ins->fm.op[j].dt>>4;
ins->fm.op[j].dt&=15;
ins->fm.op[j].d2r=reader.readC();
ins->fm.op[j].ssgEnv=reader.readC();
}
}
}
} else { // STD
@ -247,6 +272,9 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, St
if (version>5) {
for (int i=0; i<ins->std.volMacro.len; i++) {
ins->std.volMacro.val[i]=reader.readI();
if (ins->std.volMacro.val[i]>15 && sys==6) { // FDS
ins->type=DIV_INS_FDS;
}
}
} else {
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.dt = ((dtMul >> 4) & 0x7);
op.tl = totalLevel & 0x3F;
op.tl = totalLevel & 0x7F;
op.rs = ((arRateScale >> 6) & 0x3);
op.ar = arRateScale & 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.dt = ((dtMul >> 4) & 0x7));
total += (op.tl = totalLevel & 0x3F);
total += (op.tl = totalLevel & 0x7F);
total += (op.rs = ((arRateScale >> 6) & 0x3));
total += (op.ar = arRateScale & 0x1F);
total += (op.dr = drAmpEnable & 0x1F);

View File

@ -71,8 +71,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
w->writeC(op.ws);
w->writeC(op.ksr);
w->writeC(op.enable);
// reserved
for (int k=0; k<12; k++) {
for (int k=0; k<11; k++) {
w->writeC(0);
}
}
@ -716,8 +718,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
op.ws=reader.readC();
op.ksr=reader.readC();
if (version>=114) {
op.enable=reader.readC();
} else {
reader.readC();
}
// reserved
for (int k=0; k<12; k++) reader.readC();
for (int k=0; k<11; k++) reader.readC();
}
// GB

View File

@ -67,7 +67,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
w.addrOrVal=true;
}
}
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
@ -77,13 +77,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i];
}
if (o[0]<-32768) o[0]=-32768;
if (o[0]>32767) o[0]=32767;
if (o[1]<-32768) o[1]=-32768;
if (o[1]>32767) o[1]=32767;
bufL[h]=o[0];
bufR[h]=o[1];
}
@ -106,7 +106,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
delay=1;
}
}
fm_ymfm->generate(&out_ymfm);
for (int i=0; i<8; i++) {
@ -120,7 +120,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
os[1]=out_ymfm.data[1];
if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767;
bufL[h]=os[0];
bufR[h]=os[1];
}
@ -255,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) {
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));
}
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++) {
unsigned short baseAddr=chanOffs[i]|opOffs[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);
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
immWrite(0x08,0x78|i);
if (chan[i].keyOn || chan[i].opMaskChanged) {
immWrite(0x08,(chan[i].opMask<<3)|i);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
@ -370,6 +375,11 @@ int DivPlatformArcade::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
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);
@ -502,6 +512,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_LFO: {
if(c.value==0) {
rWrite(0x01,0x02);
}
else {
rWrite(0x01,0x00);
}
rWrite(0x18,c.value);
break;
}
@ -825,6 +841,8 @@ void DivPlatformArcade::reset() {
pmDepth=0x7f;
//rWrite(0x18,0x10);
immWrite(0x01,0x02); // LFO Off
immWrite(0x18,0x00); // LFO Freq Off
immWrite(0x19,amDepth);
immWrite(0x19,0x80|pmDepth);
//rWrite(0x1b,0x00);

View File

@ -43,9 +43,9 @@ class DivPlatformArcade: public DivPlatformOPM {
int freq, baseFreq, pitch, pitch2, note;
int ins;
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;
unsigned char chVolL, chVolR;
unsigned char chVolL, chVolR, opMask;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
@ -68,10 +68,12 @@ class DivPlatformArcade: public DivPlatformOPM {
portaPause(false),
furnacePCM(false),
hardReset(false),
opMaskChanged(false),
vol(0),
outVol(0),
chVolL(127),
chVolR(127) {}
chVolR(127),
opMask(15) {}
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];

View File

@ -347,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
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));
}
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++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
@ -479,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
if (i<6) immWrite(0x28,0xf0|konOffs[i]);
if (chan[i].keyOn || chan[i].opMaskChanged) {
if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
chan[i].opMaskChanged=false;
chan[i].keyOn=false;
}
}
@ -591,6 +596,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
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);

View File

@ -45,9 +45,9 @@ class DivPlatformGenesis: public DivPlatformOPN {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
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;
unsigned char pan;
unsigned char pan, opMask;
bool dacMode;
int dacPeriod;
@ -82,9 +82,11 @@ class DivPlatformGenesis: public DivPlatformOPN {
furnaceDac(false),
inPorta(false),
hardReset(false),
opMaskChanged(false),
vol(0),
outVol(0),
pan(3),
opMask(15),
dacMode(false),
dacPeriod(0),
dacRate(0),

View File

@ -25,6 +25,7 @@
#define CHIP_DIVIDER fmDivBase
#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) {
if (c.chan<2) {
@ -69,10 +70,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
opChan[ch].mask=op.enable;
}
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]+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;
@ -123,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
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;
}
case DIV_CMD_PITCH: {
@ -397,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
rWrite(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]={
@ -412,7 +416,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
bool writeSomething=false;
unsigned char writeMask=2;
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) {
writeSomething=true;
writeMask&=~(1<<(4+i));
@ -459,10 +463,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
immWrite(opChanOffsH[i],opChan[i].freq>>8);
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) {
writeNoteOn=true;
writeMask|=1<<(4+i);
if (opChan[i].mask) {
writeMask|=1<<(4+i);
}
opChan[i].keyOn=false;
}
}
@ -544,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() {
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_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
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));
}
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;

View File

@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
int vol;
unsigned char pan;
OpChannel():
@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
keyOff(false),
portaPause(false),
inPorta(false),
mask(true),
vol(0),
pan(3) {}
};

View File

@ -642,6 +642,9 @@ void DivPlatformN163::setFlags(unsigned int flags) {
for (int i=0; i<8; i++) {
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) {

View File

@ -249,7 +249,9 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
if (os[1]>32767) os[1]=32767;
bufL[h]=os[0];
bufR[h]=os[1];
if (oplType==3 || oplType==759) {
bufR[h]=os[1];
}
}
}
@ -1531,7 +1533,7 @@ void DivPlatformOPL::reset() {
}
bool DivPlatformOPL::isStereo() {
return true;
return (oplType==3 || oplType==759);
}
bool DivPlatformOPL::keyOffAffectsArp(int ch) {

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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
};

View File

@ -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

View File

@ -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

View File

@ -292,6 +292,10 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3
// note from tildearrow:
// - 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)*/)
{
channel = bitfield(index, 0, 3);

View File

@ -22,7 +22,7 @@
#include <string.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[]={
"AUDC0", "15",
@ -39,7 +39,22 @@ const char** DivPlatformTIA::getRegisterSheet() {
}
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) {
@ -300,7 +315,7 @@ int DivPlatformTIA::getRegisterPoolSize() {
}
void DivPlatformTIA::reset() {
tia.reset();
tia.reset(mixingType);
memset(regPool,0,16);
for (int i=0; i<2; i++) {
chan[i]=DivPlatformTIA::Channel();
@ -309,8 +324,12 @@ void DivPlatformTIA::reset() {
}
}
float DivPlatformTIA::getPostAmp() {
return 0.5f;
}
bool DivPlatformTIA::isStereo() {
return false;
return (mixingType==2);
}
bool DivPlatformTIA::keyOffAffectsArp(int ch) {
@ -333,25 +352,28 @@ void DivPlatformTIA::poke(std::vector<DivRegWrite>& wlist) {
void DivPlatformTIA::setFlags(unsigned int flags) {
if (flags&1) {
rate=31250;
rate=COLOR_PAL*4.0/5.0;
} else {
rate=31468;
rate=COLOR_NTSC;
}
chipClock=rate;
mixingType=(flags>>1)&3;
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) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
mixingType=0;
chanOscCounter=0;
for (int i=0; i<2; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
tia.channels(1,false);
setFlags(flags);
reset();
return 2;

Some files were not shown because too many files have changed in this diff Show More