Merge branch 'master' into inf2

This commit is contained in:
tildearrow 2025-11-10 13:34:44 -05:00
commit 419cc04c00
93 changed files with 1573 additions and 2448 deletions

View file

@ -795,6 +795,7 @@ src/engine/fileOpsSample.cpp
src/engine/filePlayer.cpp
src/engine/filter.cpp
src/engine/instrument.cpp
src/engine/legacySample.cpp
src/engine/macroInt.cpp
src/engine/pattern.cpp
src/engine/pitchTable.cpp

View file

@ -35,6 +35,8 @@ the default layout of Furnace is depicted below.
- [oscilloscope](../8-advanced/osc.md)
- [oscilloscope (per channel)](../8-advanced/chanosc.md)
- [oscilloscope (X-Y)](../8-advanced/xyosc.md)
- [tuner](../8-advanced/tuner.md)
- [spectrum](../8-advanced/spectrum.md)
- [clock](../8-advanced/clock.md)
- [grooves](../8-advanced/grooves.md)
- [log viewer](../8-advanced/log-viewer.md)
@ -45,4 +47,6 @@ the default layout of Furnace is depicted below.
## other topics
- [piano/input pad](../8-advanced/piano.md)
- [reference music player](../8-advanced/refPlayer.md)
- [multi-ins setup](../8-advanced/multi-ins.md)
- [settings](settings.md)

View file

@ -135,8 +135,6 @@ in previous versions of Furnace a `9xxx` effect existed which set the sample pos
## other
- `EBxx`: **Set LEGACY sample mode bank.** selects sample bank. used only for compatibility.
- does not apply on Amiga.
- `EExx`: **Send external command.**
- this effect is currently incomplete.
- `F5xx`: **Disable macro.**

View file

@ -50,15 +50,6 @@ the simplest path to using a sample is:
- click the "Create instrument from sample" button (upload icon, to the left of "Zoom").
- use the created instrument in the track.
## compatible sample mode (LEGACY)
**use of this mode is discouraged in favor of Sample type instruments.**
effect `17xx` enables/disables compatible sample mode where supported (e.g. on Sega Genesis or PC Engine).
in this mode, samples are mapped to notes in an octave from C to B, allowing you to use up to 12 samples.
if you need to use more samples, you may change the sample bank using effect `EBxx`.
## notes
due to limitations in some of those sound chips, some restrictions exist:

View file

@ -18,8 +18,6 @@ furthermore, it has some PCM and LFO!
- `03`: LFO enabled, shift 8.
- when LFO is enabled, channel 2 is muted and its output is passed to channel 1's frequency.
- `13xx`: **set LFO speed.**
- `17xx`: **toggle LEGACY sample mode.**
- **this effect exists only for compatibility reasons! its use is NOT recommended. use Sample type instruments instead.**
## info

View file

@ -15,8 +15,6 @@ Furnace supports this routine for PCM playback, but it consumes a lot of CPU tim
these effects only are effective in the pulse channels.
- `12xx`: **set duty cycle.** range is `0` to `7`.
- `17xx`: **toggle LEGACY sample mode.**
- **this effect exists only for compatibility reasons! its use is NOT recommended. use Sample type instruments instead.**
## info

View file

@ -17,8 +17,6 @@ this chip was the inspiration for Organya/PxTone (the former being used in a wel
- `10xx`: **change wave.**
- `11xx`: **change envelope shape.** also wavetable.
- `17xx`: **toggle LEGACY sample mode.**
- **this effect exists only for compatibility reasons! its use is NOT recommended. use Sample type instruments instead.**
- `20xx`: **set PCM frequency.** range is `1` to `FF`.
- PCM frequency formula: `step * (clock / 8192)`.
- range is 1.95KHz to 498KHz if the chip clock is 16MHz.

View file

@ -16,9 +16,6 @@ Furnace also offers DualPCM, a Z80 driver that splits channel 6 into two individ
- `16xy`: **set multiplier of operator.**
- `x` is the operator (1-4).
- `y` is the new MULT value.
- `17xx`: **toggle LEGACY sample mode.**
- this only works on channel 6.
- **this effect exists only for compatibility reasons! its use is NOT recommended. use Sample type instruments instead.**
- `18xx`: **toggle extended channel 3 mode.**
- 0 disables it and 1 enables it.
- only in extended channel 3 chip.

View file

@ -19,6 +19,8 @@ as listed in the "Window" menu:
- [oscilloscope](../8-advanced/osc.md)
- [oscilloscope (per-channel)](../8-advanced/chanosc.md)
- [oscilloscope (X-Y)](../8-advanced/xyosc.md)
- [tuner](../8-advanced/tuner.md)
- [spectrum](../8-advanced/spectrum.md)
- volume meter
- tempo
- [clock](../8-advanced/clock.md)
@ -29,6 +31,8 @@ as listed in the "Window" menu:
- [statistics](../8-advanced/stats.md)
- [memory composition](../8-advanced/memory-composition.md)
- [piano/input pad](../8-advanced/piano.md)
- [reference music player](../8-advanced/refPlayer.md)
- [multi-ins setup](../8-advanced/multi-ins.md)
other:

View file

@ -4,9 +4,9 @@ the "Mixer" dialog provides options for overall sound mixing.
## "Mixer" tab
![mixer dialog on mixer tab](mixer-mixer.png)
![mixer dialog on mixer tab](mixer-mixer.png) <!--new image(s) needed!-->
"Master Volume" controls the overall mix.
**Master Volume** controls the overall mix.
each chip has several options:
- **Invert**: flips the output wave.
@ -14,6 +14,15 @@ each chip has several options:
- **Panning**: left-right sound control.
- **Front/Rear**: front-read sound control. only useful for setups with four or more speakers.
### Mixer-specific settings <!--in settings-->
- **Mixer layout**: how the mixer is shown.
- **Horizontal**: arranges the "chips"<!--reword!--> horizontally, with vertical volume sliders.
- **Vertical**: arranges the "chips"<!--ditto--> vertically, with horizontal volume sliders.
- **Mixer style**:
- **No volume meters**: does not show per-chip volume meters.
- **Volume meters separate**: shows per-chip volume meters below or to the right (depending on the layout) of the volume slider.
- **Volume meters in volume sliders**: shows per-chip volume meters inside the volume sliders.
## "Patchbay" tab
![mixer dialog on patchbay tab](mixer-patchbay.png)

View file

@ -0,0 +1,6 @@
# reference music player
the Music Player window is a small audio player that can synchronize with the tracker playback.
<!--IMAGE
![reference music player window]()-->
<!--TODO: the rest-->

View file

@ -0,0 +1,23 @@
# spectrum
the Spectrum window shows the frequency spectrum of the audio output.
<!--IMAGE
![spectrum window]()-->
<!--somehow make this info work-->
the X scale is in Hertz, and the Y scale is in decibels.
clicking the threeline <!--REPLACE--> button in the top left corner open its configuration.
- **Mono**: whether to mix all the output for a single graph.
- **Bins**: number of bins for the FFT calculation.
---
- **X Zoom**: how much to zoom the X axis.
- **X Offset**: how much to move the X axis.
---
- **Y Offset**: how much to move the Y axis down.
---
- **Show X Grid**: whether to show the X axis grid lines.
- **Show Y Grid**: whether to show the Y axis grid lines.
- **Show X Scale**: whether to show the X axis scale.
- **Show Y Scale**: whether to show the Y axis scale.

6
doc/8-advanced/tuner.md Normal file
View file

@ -0,0 +1,6 @@
# tuner
the Tuner window shows the approximate frequency and note of the audio output.
<!-- IMAGE
![tuner window]()-->

View file

@ -130,8 +130,6 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
return _("E9xy: Quick legato down (x: time; y: semitones)");
case 0xea:
return _("EAxx: Legato");
case 0xeb:
return _("EBxx: Set LEGACY sample mode bank");
case 0xec:
return _("ECxx: Note cut");
case 0xed:
@ -264,6 +262,19 @@ double DivEngine::benchmarkSeek() {
return tAvg;
}
double DivEngine::benchmarkWalk() {
std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now();
// benchmark
calcSongTimestamps();
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
double t=(double)(std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count())/1000000.0;
printf("[RESULT] %fs\n",t);
return t;
}
void DivEngine::notifyInsChange(int ins) {
BUSY_BEGIN;
for (int i=0; i<song.systemLen; i++) {

View file

@ -55,8 +55,8 @@ class DivWorkPool;
#define DIV_UNSTABLE
#define DIV_VERSION "dev238"
#define DIV_ENGINE_VERSION 238
#define DIV_VERSION "dev239"
#define DIV_ENGINE_VERSION 239
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -695,6 +695,10 @@ class DivEngine {
// change song (UNSAFE)
void changeSong(size_t songIndex);
// convert legacy sample mode to normal
// returns whether conversion occurred
bool convertLegacySampleMode();
void swapSystemUnsafe(int src, int dest, bool preserveOrder=true);
// move an asset
@ -821,6 +825,7 @@ class DivEngine {
// benchmark (returns time in seconds)
double benchmarkPlayback();
double benchmarkSeek();
double benchmarkWalk();
// returns the minimum VGM version which may carry the specified system, or 0 if none.
int minVGMVersion(DivSystem which);

View file

@ -934,10 +934,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
sample->name="";
}
logD("%d name %s (%d)",i,sample->name.c_str(),length);
sample->rate=22050;
sample->centerRate=22050;
if (ds.version>=0x0b) {
sample->rate=fileToDivRate(reader.readC());
sample->centerRate=sample->rate;
sample->centerRate=fileToDivRate(reader.readC());
pitch=reader.readC();
vol=reader.readC();
@ -947,8 +946,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
}
if (ds.version<=0x08) {
sample->rate=ymuSampleRate*400;
sample->centerRate=ymuSampleRate*400;
}
sample->legacyRate=sample->centerRate;
if (ds.version>0x15) {
sample->depth=(DivSampleDepth)reader.readC();
if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) {
@ -1176,6 +1176,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
song=ds;
changeSong(0);
recalcChans();
// always convert to normal sample mode (I have no idea how will I do export)
convertLegacySampleMode();
saveLock.unlock();
BUSY_END;
if (active) {
@ -1644,8 +1646,140 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
short note, octave;
w->writeC(curPat[i].effectCols);
bool convertSampleUsage=false;
bool alwaysConvert=false;
switch (sys) {
case DIV_SYSTEM_GENESIS:
if (i==5) convertSampleUsage=true;
break;
case DIV_SYSTEM_GENESIS_EXT:
if (i==8) convertSampleUsage=true;
break;
case DIV_SYSTEM_PCE:
convertSampleUsage=true;
break;
case DIV_SYSTEM_NES:
if (i==4) {
convertSampleUsage=true;
alwaysConvert=true;
}
break;
case DIV_SYSTEM_ARCADE:
if (i>=8) {
convertSampleUsage=true;
alwaysConvert=true;
}
break;
case DIV_SYSTEM_YM2610:
if (i>=7) {
convertSampleUsage=true;
alwaysConvert=true;
}
break;
case DIV_SYSTEM_YM2610_EXT:
if (i>=10) {
convertSampleUsage=true;
alwaysConvert=true;
}
break;
default:
break;
}
for (int j=0; j<curSubSong->ordersLen; j++) {
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false);
// we make a copy in order to convert Furnace sample mode to Defle one
DivPattern* origPat=curPat[i].getPattern(curOrders->ord[i][j],false);
DivPattern* pat=new DivPattern;
origPat->copyOn(pat);
if (convertSampleUsage) {
int convIns=-1;
bool isConverting=false;
for (int k=0; k<curSubSong->patLen; k++) {
int insert17xx=-1;
int insertEBxx=-1;
if (pat->newData[k][DIV_PAT_INS]!=-1) {
if (pat->newData[k][DIV_PAT_INS]>=0 && pat->newData[k][DIV_PAT_INS]<song.insLen) {
convIns=pat->newData[k][DIV_PAT_INS];
} else {
convIns=-1;
}
bool willBeConverting=false;
if (convIns>=0 && convIns<song.insLen) {
DivInstrument* convInsInst=song.ins[convIns];
if (convInsInst->type==DIV_INS_AMIGA ||
convInsInst->type==DIV_INS_ADPCMA ||
convInsInst->type==DIV_INS_SEGAPCM ||
(convInsInst->type==DIV_INS_PCE && convInsInst->amiga.useSample)) {
willBeConverting=true;
}
}
if (isConverting!=willBeConverting) {
if (!alwaysConvert) {
if (willBeConverting) {
insert17xx=1;
} else {
insert17xx=0;
}
}
isConverting=willBeConverting;
}
if (isConverting || alwaysConvert) {
pat->newData[k][DIV_PAT_INS]=-1;
}
}
if (pat->newData[k][DIV_PAT_NOTE]!=-1 && pat->newData[k][DIV_PAT_NOTE]!=DIV_NOTE_OFF && pat->newData[k][DIV_PAT_NOTE]!=DIV_NOTE_REL && pat->newData[k][DIV_PAT_NOTE]!=DIV_MACRO_REL) {
if (isConverting || alwaysConvert) {
if (convIns>=0 && convIns<song.insLen) {
DivInstrument* convInsInst=song.ins[convIns];
if (convInsInst->amiga.useNoteMap) {
int mapTarget=pat->newData[k][DIV_PAT_NOTE]-60;
if (mapTarget<0) mapTarget=0;
if (mapTarget>119) mapTarget=119;
insertEBxx=convInsInst->amiga.noteMap[mapTarget].map/12;
pat->newData[k][DIV_PAT_NOTE]=(12*(pat->newData[k][DIV_PAT_NOTE]/12))+(convInsInst->amiga.noteMap[mapTarget].map%12);
} else {
insertEBxx=convInsInst->amiga.initSample/12;
pat->newData[k][DIV_PAT_NOTE]=(12*(pat->newData[k][DIV_PAT_NOTE]/12))+(convInsInst->amiga.initSample%12);
}
}
}
}
if (insert17xx!=-1) {
int freeSlot=0;
logV("insert 17xx at %d:[%d]:%d (%d)",i,j,k,insert17xx);
for (int l=0; l<curPat[i].effectCols; l++) {
if (pat->newData[k][DIV_PAT_FX(l)]==-1) {
freeSlot=l;
break;
}
}
pat->newData[k][DIV_PAT_FX(freeSlot)]=0x17;
pat->newData[k][DIV_PAT_FXVAL(freeSlot)]=insert17xx;
}
if (insertEBxx!=-1) {
int freeSlot=1;
for (int l=0; l<curPat[i].effectCols; l++) {
if (pat->newData[k][DIV_PAT_FX(l)]==-1) {
freeSlot=l;
break;
}
}
pat->newData[k][DIV_PAT_FX(freeSlot)]=0xeb;
pat->newData[k][DIV_PAT_FXVAL(freeSlot)]=insertEBxx;
}
}
}
for (int k=0; k<curSubSong->patLen; k++) {
if (pat->newData[k][DIV_PAT_NOTE]==DIV_NOTE_REL || pat->newData[k][DIV_PAT_NOTE]==DIV_MACRO_REL) {
w->writeS(100);
@ -1669,6 +1803,8 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
#endif
w->writeS(pat->newData[k][DIV_PAT_INS]); // instrument
}
delete pat;
}
}
@ -1680,7 +1816,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
for (DivSample* i: song.sample) {
w->writeI(i->samples);
w->writeString(i->name,true);
w->writeC(divToFileRate(i->rate));
w->writeC(divToFileRate(i->centerRate));
w->writeC(5);
w->writeC(50);
// i'm too lazy to deal with .dmf's weird way of storing 8-bit samples

View file

@ -1986,7 +1986,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
DivSample* sample = ds.sample[index];
sample->rate = 33144;
sample->centerRate = 33144;
sample->depth = DIV_SAMPLE_DEPTH_1BIT_DPCM;

View file

@ -2188,6 +2188,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
song=ds;
changeSong(0);
recalcChans();
// removal of legacy sample mode
if (song.version<239) {
if (convertLegacySampleMode()) {
addWarning("Furnace no longer supports legacy sample mode. your song has been converted.");
}
}
saveLock.unlock();
BUSY_END;
if (active) {

View file

@ -112,8 +112,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
if (slen==2) slen=0;
signed char fineTune=reader.readC()&0x0f;
if (fineTune>=8) fineTune-=16;
sample->rate=(int)(pow(2.0,(double)fineTune/96.0)*8363.0);
sample->centerRate=sample->rate;
sample->centerRate=(int)(pow(2.0,(double)fineTune/96.0)*8363.0);
defaultVols[i]=reader.readC();
int loopStart=reader.readS_BE()*2;
int loopLen=reader.readS_BE()*2;

View file

@ -75,7 +75,6 @@ void DivEngine::loadP(SafeReader& reader, std::vector<DivSample*>& ret, String&
{
DivSample* s = new DivSample;
s->rate = P_SAMPLE_RATE;
s->centerRate = P_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_VOX;
@ -121,4 +120,4 @@ void DivEngine::loadP(SafeReader& reader, std::vector<DivSample*>& ret, String&
lastError=_("premature end of file");
logE("premature end of file");
}
}
}

View file

@ -110,7 +110,6 @@ void DivEngine::loadP86(SafeReader& reader, std::vector<DivSample*>& ret, String
{
DivSample* s = new DivSample;
s->rate = P86_SAMPLE_RATE;
s->centerRate = P86_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_8BIT;
s->init(headers[i].sample_length); //byte per sample
@ -139,4 +138,4 @@ void DivEngine::loadP86(SafeReader& reader, std::vector<DivSample*>& ret, String
lastError=_("premature end of file");
logE("premature end of file");
}
}
}

View file

@ -69,7 +69,6 @@ void DivEngine::loadPDX(SafeReader& reader, std::vector<DivSample*>& ret, String
{
DivSample* s = new DivSample;
s->rate = PDX_SAMPLE_RATE;
s->centerRate = PDX_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_VOX;
s->init(headers[i].sample_length * 2);
@ -98,4 +97,4 @@ void DivEngine::loadPDX(SafeReader& reader, std::vector<DivSample*>& ret, String
lastError=_("premature end of file");
logE("premature end of file");
}
}
}

View file

@ -109,7 +109,6 @@ void DivEngine::loadPPC(SafeReader& reader, std::vector<DivSample*>& ret, String
{
DivSample* s = new DivSample;
s->rate = PPC_SAMPLE_RATE;
s->centerRate = PPC_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_ADPCM_B;
s->init((headers[i].end_pointer - headers[i].start_pointer) * 32 * 2);
@ -139,4 +138,4 @@ void DivEngine::loadPPC(SafeReader& reader, std::vector<DivSample*>& ret, String
lastError=_("premature end of file");
logE("premature end of file");
}
}
}

View file

@ -90,7 +90,6 @@ void DivEngine::loadPPS(SafeReader& reader, std::vector<DivSample*>& ret, String
{
DivSample* s = new DivSample;
s->rate = PPS_SAMPLE_RATE;
s->centerRate = PPS_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_4BIT;
s->init(headers[i].sample_length*2); // bytes->samples

View file

@ -125,7 +125,6 @@ void DivEngine::loadPVI(SafeReader& reader, std::vector<DivSample*>& ret, String
{
DivSample* s = new DivSample;
s->rate = PVI_SAMPLE_RATE;
s->centerRate = PVI_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_ADPCM_B;
s->init((headers[i].end_pointer - headers[i].start_pointer) * 32 * 2);
@ -155,4 +154,4 @@ void DivEngine::loadPVI(SafeReader& reader, std::vector<DivSample*>& ret, String
lastError=_("premature end of file");
logE("premature end of file");
}
}
}

View file

@ -113,7 +113,6 @@ void DivEngine::loadPZI(SafeReader& reader, std::vector<DivSample*>& ret, String
{
DivSample* s = new DivSample;
s->rate = headers[i].sample_rate;
s->centerRate = headers[i].sample_rate;
s->depth = DIV_SAMPLE_DEPTH_8BIT;
s->init(headers[i].sample_length); //byte per sample

View file

@ -262,7 +262,6 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) {
w->writeText(fmt::sprintf("- data length: %d\n",sample->getCurBufLen()));
w->writeText(fmt::sprintf("- samples: %d\n",sample->samples));
w->writeText(fmt::sprintf("- rate: %d\n",sample->centerRate));
w->writeText(fmt::sprintf("- compat rate: %d\n",sample->rate));
w->writeText(fmt::sprintf("- loop: %s\n",trueFalse[sample->loop?1:0]));
if (sample->loop) {
w->writeText(fmt::sprintf(" - start: %d\n",sample->loopStart));

View file

@ -213,12 +213,10 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
}
if (extS==".dmc") {
sample->rate=33144;
sample->centerRate=33144;
sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM;
sample->init(len*8);
} else if (extS==".brr") {
sample->rate=32000;
sample->centerRate=32000;
sample->depth=DIV_SAMPLE_DEPTH_BRR;
sample->init(16*(len/9));
@ -400,9 +398,6 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
delete[] (float*)buf;
}
sample->rate=si.samplerate;
if (sample->rate<4000) sample->rate=4000;
if (sample->rate>96000) sample->rate=96000;
sample->centerRate=si.samplerate;
SF_INSTRUMENT inst;
@ -563,7 +558,6 @@ DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth,
return NULL;
}
sample->rate=rate;
sample->centerRate=rate;
sample->depth=depth;
sample->init(samples);

View file

@ -886,7 +886,7 @@ size_t DivInstrument::writeFeatureLS(SafeWriter* w, std::vector<int>& list, cons
if (list.empty()) return 0;
FEATURE_BEGIN("SL");
FEATURE_BEGIN("LS");
w->writeS(list.size());
@ -935,7 +935,7 @@ size_t DivInstrument::writeFeatureLW(SafeWriter* w, std::vector<int>& list, cons
if (list.empty()) return 0;
FEATURE_BEGIN("WL");
FEATURE_BEGIN("LW");
w->writeS(list.size());
@ -2832,7 +2832,7 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b
} else if (memcmp(featCode,"S3",2)==0) { // SID3
readFeatureS3(reader,version);
} else {
if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) {
if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0) || (memcmp(featCode,"LS",2)==0) || (memcmp(featCode,"LW",2)==0))) {
// nothing
} else {
logW("unknown feature code %c%c!",featCode[0],featCode[1]);

389
src/engine/legacySample.cpp Normal file
View file

@ -0,0 +1,389 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
// from this point onwards, a mess.
#include "engine.h"
#include <fmt/printf.h>
bool DivEngine::convertLegacySampleMode() {
logD("converting legacy sample mode...");
int legacyInsInit=-1;
// quit if we don't have space for a legacy sample instrument
if (song.ins.size()>254) {
logW("no space left on instrument list!");
return false;
}
// check which samples are used by instruments so we don't touch them
bool* isUsedByIns=new bool[MAX(1,song.sample.size())];
memset(isUsedByIns,0,MAX(1,song.sample.size())*sizeof(bool));
for (DivInstrument* ins: song.ins) {
bool canUse=false;
if (ins->type==DIV_INS_MSM6258 ||
ins->type==DIV_INS_MSM6295 ||
ins->type==DIV_INS_ADPCMA ||
ins->type==DIV_INS_ADPCMB ||
ins->type==DIV_INS_SEGAPCM ||
ins->type==DIV_INS_QSOUND ||
ins->type==DIV_INS_YMZ280B ||
ins->type==DIV_INS_RF5C68 ||
ins->type==DIV_INS_AMIGA ||
ins->type==DIV_INS_MULTIPCM ||
ins->type==DIV_INS_SNES ||
ins->type==DIV_INS_ES5506 ||
ins->type==DIV_INS_K007232 ||
ins->type==DIV_INS_GA20 ||
ins->type==DIV_INS_K053260 ||
ins->type==DIV_INS_C140 ||
ins->type==DIV_INS_C219 ||
ins->type==DIV_INS_NDS ||
ins->type==DIV_INS_GBA_DMA ||
ins->type==DIV_INS_GBA_MINMOD ||
ins->type==DIV_INS_NES ||
ins->type==DIV_INS_SUPERVISION) {
canUse=true;
}
if (ins->type==DIV_INS_SU ||
ins->type==DIV_INS_AY ||
ins->type==DIV_INS_AY8930 ||
ins->type==DIV_INS_MIKEY ||
ins->type==DIV_INS_PCE ||
ins->type==DIV_INS_X1_010 ||
ins->type==DIV_INS_SWAN ||
ins->type==DIV_INS_VRC6) {
if (ins->amiga.useSample) canUse=true;
}
if (canUse) {
if (ins->amiga.useNoteMap) {
for (int i=0; i<120; i++) {
if (ins->amiga.noteMap[i].map>=0 && ins->amiga.noteMap[i].map<(int)song.sample.size()) {
isUsedByIns[ins->amiga.noteMap[i].map]=true;
}
}
} else {
if (ins->amiga.initSample>=0 && ins->amiga.initSample<(int)song.sample.size()) {
isUsedByIns[ins->amiga.initSample]=true;
}
}
}
}
auto initSampleInsIfNeeded=[this,&legacyInsInit]() {
if (legacyInsInit==-1) {
legacyInsInit=(int)song.ins.size();
for (size_t bank=0; bank<(11+song.sample.size())/12; bank++) {
DivInstrument* ins=new DivInstrument;
ins->type=DIV_INS_AMIGA;
ins->amiga.useNoteMap=true;
if (bank==0) {
ins->name="Legacy Samples";
} else {
ins->name=fmt::sprintf("Legacy Samples (bank %d)",(int)bank);
}
for (int i=0; i<120; i++) {
ins->amiga.noteMap[i].freq=48; // C-4
ins->amiga.noteMap[i].map=12*bank+(i%12);
}
song.ins.push_back(ins);
if (song.ins.size()>=256) break;
}
song.insLen=song.ins.size();
checkAssetDir(song.insDir,song.ins.size());
}
};
for (DivSubSong* h: song.subsong) {
for (int i=0; i<chans; i++) {
// 0: sample off
// 1: legacy mode
// 2: normal mode
unsigned char sampleMode=0;
unsigned char sampleBank=0;
DivInstrumentType preferredInsType=DIV_INS_AMIGA;
DivInstrumentType preferredInsType2=DIV_INS_AMIGA;
bool noteOffDisablesSampleMode=false;
bool hasLegacyToggle=false;
switch (sysOfChan[i]) {
case DIV_SYSTEM_NES:
case DIV_SYSTEM_5E01:
// NES PCM channel (on by default)
if (dispatchChanOfChan[i]!=4) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_NES;
break;
case DIV_SYSTEM_MMC5:
// MMC5 PCM channel
if (dispatchChanOfChan[i]!=2) {
continue;
}
sampleMode=1;
break;
case DIV_SYSTEM_YM2612:
// YM2612 DAC channel
if (dispatchChanOfChan[i]!=5) {
continue;
}
hasLegacyToggle=true;
break;
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_CSM:
// YM2612 DAC channel
if (dispatchChanOfChan[i]!=8) {
continue;
}
hasLegacyToggle=true;
break;
case DIV_SYSTEM_PCE:
case DIV_SYSTEM_X1_010:
// any channel can be DAC'd
noteOffDisablesSampleMode=true;
hasLegacyToggle=true;
break;
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
// any channel can be DAC'd
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
// Neo Geo CD ADPCM channels
if (dispatchChanOfChan[i]<7) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT:
// Neo Geo CD ADPCM channels
if (dispatchChanOfChan[i]<10) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2612_DUALPCM:
// DualPCM DAC
if (dispatchChanOfChan[i]<5) {
continue;
}
sampleMode=1;
hasLegacyToggle=true;
break;
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
// DualPCM DAC
if (dispatchChanOfChan[i]<8 || dispatchChanOfChan[i]>9) {
continue;
}
sampleMode=1;
hasLegacyToggle=true;
break;
case DIV_SYSTEM_YM2610_CSM:
// Neo Geo CD ADPCM channels
if (dispatchChanOfChan[i]<11) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2610B:
// ADPCM channels
if (dispatchChanOfChan[i]<9) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2610B_EXT:
// ADPCM channels
if (dispatchChanOfChan[i]<12) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2610B_CSM:
// ADPCM channels
if (dispatchChanOfChan[i]<13) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMA;
preferredInsType2=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2608:
// ADPCM channel
if (dispatchChanOfChan[i]!=15) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2608_EXT:
// ADPCM channel
if (dispatchChanOfChan[i]!=18) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_YM2608_CSM:
// ADPCM channel
if (dispatchChanOfChan[i]!=19) {
continue;
}
sampleMode=1;
preferredInsType=DIV_INS_ADPCMB;
break;
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
// all channels can play back samples
sampleMode=1;
preferredInsType=DIV_INS_SEGAPCM;
break;
case DIV_SYSTEM_MSM6258:
sampleMode=1;
preferredInsType=DIV_INS_MSM6258;
break;
case DIV_SYSTEM_MSM6295:
sampleMode=1;
preferredInsType=DIV_INS_MSM6295;
break;
case DIV_SYSTEM_Y8950:
// Y8950 ADPCM
if (dispatchChanOfChan[i]!=9) {
continue;
}
sampleMode=1;
break;
case DIV_SYSTEM_Y8950_DRUMS:
// Y8950 ADPCM
if (dispatchChanOfChan[i]!=11) {
continue;
}
sampleMode=1;
break;
case DIV_SYSTEM_SWAN:
// PCM channel
if (dispatchChanOfChan[i]!=1) {
continue;
}
noteOffDisablesSampleMode=true;
hasLegacyToggle=true;
break;
case DIV_SYSTEM_VRC6:
// pulse DAC mode
if (dispatchChanOfChan[i]>=2) {
continue;
}
hasLegacyToggle=true;
break;
default: // not a chip with convertible channels
continue;
}
logV("- channel %d",i);
bool didThisPattern[DIV_MAX_PATTERNS];
memset(didThisPattern,0,DIV_MAX_PATTERNS*sizeof(bool));
for (int j=0; j<h->ordersLen; j++) {
int patIndex=h->orders.ord[i][j];
if (didThisPattern[patIndex]) continue;
didThisPattern[patIndex]=true;
DivPattern* p=h->pat[i].data[patIndex];
if (p==NULL) continue;
for (int k=0; k<h->patLen; k++) {
// check for legacy mode toggle and sample bank changes
for (int l=0; l<h->pat[i].effectCols; l++) {
int fxVal=p->newData[k][DIV_PAT_FXVAL(l)];
if (fxVal<0) fxVal=0;
switch (p->newData[k][DIV_PAT_FX(l)]) {
case 0x17: // set legacy sample mode
if (hasLegacyToggle) {
if (fxVal==0) {
sampleMode=0;
} else {
sampleMode=1;
}
// clear effect
p->newData[k][DIV_PAT_FX(l)]=-1;
p->newData[k][DIV_PAT_FXVAL(l)]=-1;
}
break;
case 0xeb: // set sample bank
sampleBank=fxVal;
// clear effect
p->newData[k][DIV_PAT_FX(l)]=-1;
p->newData[k][DIV_PAT_FXVAL(l)]=-1;
logV("change bank to %d",sampleBank);
break;
}
}
// check for instrument changes
if (p->newData[k][DIV_PAT_INS]!=-1) {
DivInstrument* ins=getIns(p->newData[k][DIV_PAT_INS]);
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample || ins->type==preferredInsType || ins->type==preferredInsType2) {
sampleMode=2;
}
}
if (p->newData[k][DIV_PAT_NOTE]!=-1 &&
p->newData[k][DIV_PAT_NOTE]!=DIV_NOTE_OFF &&
p->newData[k][DIV_PAT_NOTE]!=DIV_NOTE_REL &&
p->newData[k][DIV_PAT_NOTE]!=DIV_MACRO_REL) {
// we've got a note
if (sampleMode==1) {
initSampleInsIfNeeded();
p->newData[k][DIV_PAT_INS]=MIN(0xff,legacyInsInit+sampleBank);
int involvedSample=12*sampleBank+(p->newData[k][DIV_PAT_NOTE]%12);
if (involvedSample>=0 && involvedSample<song.sampleLen) {
if (!isUsedByIns[involvedSample]) {
DivSample* sample=song.sample[involvedSample];
sample->centerRate=sample->legacyRate;
}
}
}
} else if (p->newData[k][DIV_PAT_NOTE]==DIV_NOTE_OFF && noteOffDisablesSampleMode) {
sampleMode=0;
}
}
}
}
}
delete[] isUsedByIns;
return (legacyInsInit!=-1);
}

View file

@ -598,7 +598,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].dac.furnaceDAC) {
if (chan[i].curPSGMode.val&8) {
double off=1.0;
if (chan[i].dac.sample>=0 && chan[i].dac.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].dac.sample);
@ -677,76 +677,49 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AY);
if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->amiga.useSample)) {
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
chan[c.chan].nextPSGMode.val|=8;
} else if (chan[c.chan].dac.furnaceDAC) {
} else {
chan[c.chan].nextPSGMode.val&=~8;
}
if (chan[c.chan].nextPSGMode.val&8) {
if (skipRegisterWrites) break;
if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->amiga.useSample)) {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1;
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) {
rWrite(0x08+c.chan,0);
//addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
}
}
if (chan[c.chan].dac.setPos) {
chan[c.chan].dac.setPos=false;
} else {
chan[c.chan].dac.pos=0;
}
chan[c.chan].dac.period=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
//chan[c.chan].keyOn=true;
chan[c.chan].dac.furnaceDAC=true;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1;
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
chan[c.chan].dac.sample=12*sampleBank+chan[c.chan].note%12;
if (chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1;
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
//if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
}
if (chan[c.chan].dac.setPos) {
chan[c.chan].dac.setPos=false;
} else {
chan[c.chan].dac.pos=0;
}
chan[c.chan].dac.period=0;
chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*2048;
if (dumpWrites) {
rWrite(0x08+c.chan,0);
//addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate);
//addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
}
chan[c.chan].dac.furnaceDAC=false;
}
if (chan[c.chan].dac.setPos) {
chan[c.chan].dac.setPos=false;
} else {
chan[c.chan].dac.pos=0;
}
chan[c.chan].dac.period=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
//chan[c.chan].keyOn=true;
chan[c.chan].curPSGMode.val&=~8;
chan[c.chan].curPSGMode.val|=chan[c.chan].nextPSGMode.val&8;
break;
@ -950,12 +923,6 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
chan[c.chan].curPSGMode.val|=chan[c.chan].nextPSGMode.val&8;
}
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].dac.pos=c.value;
chan[c.chan].dac.setPos=true;
@ -1086,7 +1053,6 @@ void DivPlatformAY8910::reset() {
pendingWrites[i]=-1;
}
sampleBank=0;
ayEnvPeriod=0;
ayEnvMode=0;
ayEnvSlide=0;

View file

@ -65,7 +65,7 @@ class DivPlatformAY8910: public DivDispatch {
struct DAC {
int sample, rate, period, pos, out;
bool furnaceDAC, setPos;
bool setPos;
DAC():
sample(-1),
@ -73,7 +73,6 @@ class DivPlatformAY8910: public DivDispatch {
period(0),
pos(0),
out(0),
furnaceDAC(false),
setPos(false) {}
} dac;
@ -121,7 +120,6 @@ class DivPlatformAY8910: public DivDispatch {
unsigned char regPool[16];
unsigned char lastBusy;
unsigned char sampleBank;
unsigned char stereoSep;
unsigned char selCore;

View file

@ -402,7 +402,7 @@ void DivPlatformAY8930::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].dac.furnaceDAC) {
if (chan[i].curPSGMode.val&8) {
double off=1.0;
if (chan[i].dac.sample>=0 && chan[i].dac.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].dac.sample);
@ -507,74 +507,47 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AY8930);
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
chan[c.chan].nextPSGMode.val|=8;
} else if (chan[c.chan].dac.furnaceDAC) {
} else {
chan[c.chan].nextPSGMode.val&=~8;
}
if (chan[c.chan].nextPSGMode.val&8) {
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) {
rWrite(0x08+c.chan,0);
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
}
}
if (chan[c.chan].dac.setPos) {
chan[c.chan].dac.setPos=false;
} else {
chan[c.chan].dac.pos=0;
}
chan[c.chan].dac.period=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
//chan[c.chan].keyOn=true;
chan[c.chan].dac.furnaceDAC=true;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
chan[c.chan].dac.sample=12*sampleBank+chan[c.chan].note%12;
if (chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
}
if (chan[c.chan].dac.setPos) {
chan[c.chan].dac.setPos=false;
} else {
chan[c.chan].dac.pos=0;
}
chan[c.chan].dac.period=0;
chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*4096;
if (dumpWrites) {
rWrite(0x08+c.chan,0);
addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate);
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
}
chan[c.chan].dac.furnaceDAC=false;
}
if (chan[c.chan].dac.setPos) {
chan[c.chan].dac.setPos=false;
} else {
chan[c.chan].dac.pos=0;
}
chan[c.chan].dac.period=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
//chan[c.chan].keyOn=true;
chan[c.chan].curPSGMode.val&=~8;
chan[c.chan].curPSGMode.val|=chan[c.chan].nextPSGMode.val&8;
break;
@ -772,12 +745,6 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
chan[c.chan].curPSGMode.val|=chan[c.chan].nextPSGMode.val&8;
}
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].dac.pos=c.value;
chan[c.chan].dac.setPos=true;
@ -895,7 +862,6 @@ void DivPlatformAY8930::reset() {
pendingWrites[i]=-1;
}
sampleBank=0;
ayNoiseAnd=2;
ayNoiseOr=0;
delay=0;

View file

@ -65,7 +65,7 @@ class DivPlatformAY8930: public DivDispatch {
struct DAC {
int sample, rate, period, pos, out;
bool furnaceDAC, setPos;
bool setPos;
DAC():
sample(-1),
@ -73,7 +73,6 @@ class DivPlatformAY8930: public DivDispatch {
period(0),
pos(0),
out(0),
furnaceDAC(false),
setPos(false) {}
} dac;
@ -111,8 +110,6 @@ class DivPlatformAY8930: public DivDispatch {
unsigned char stereoSep;
bool bank;
unsigned char sampleBank;
int delay;
int lastOut[2];

View file

@ -120,7 +120,6 @@ class DivPlatformOPN: public DivPlatformFMBase {
struct OPNChannel: public FMChannel {
unsigned char psgMode, autoEnvNum, autoEnvDen;
bool furnacePCM;
int sample, macroVolMul;
OPNChannel():
@ -128,7 +127,6 @@ class DivPlatformOPN: public DivPlatformFMBase {
psgMode(1),
autoEnvNum(0),
autoEnvDen(0),
furnacePCM(false),
sample(-1),
macroVolMul(255) {}
};

View file

@ -681,7 +681,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
if (chan[i].std.vol.had) {
int inVol=chan[i].std.vol.val;
if (chan[i].furnaceDac && inVol>0) {
if (inVol>0) {
inVol+=63;
}
chan[i].outVol=VOL_SCALE_LOG_BROKEN(chan[i].vol,MIN(127,inVol),127);
@ -700,7 +700,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
}
if (i>=5 && chan[i].furnaceDac && chan[i].dacMode) {
if (i>=5 && chan[i].dacMode) {
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
@ -720,7 +720,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
}
if (i>=5 && chan[i].furnaceDac) {
if (i>=5 && chan[i].dacMode) {
if (chan[i].std.panL.had) {
chan[5].pan&=1;
chan[5].pan|=chan[i].std.panL.val?2:0;
@ -752,7 +752,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
if (i>=5 && chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1 && chan[i].furnaceDac) {
if (chan[i].std.phaseReset.val==1 && chan[i].dacMode) {
chan[i].dacPos=0;
}
}
@ -915,7 +915,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff);
hardResetElapsed+=2;
}
if (chan[i].furnaceDac && chan[i].dacMode) {
if (chan[i].dacMode) {
double off=1.0;
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].dacSample);
@ -1051,15 +1051,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].dacMode=1;
rWrite(0x2b,1<<7);
} else if (chan[c.chan].furnaceDac) {
} else {
chan[c.chan].dacMode=0;
rWrite(0x2b,0<<7);
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
} else if (!chan[c.chan].dacMode) {
rWrite(0x2b,0<<7);
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
}
}
if (c.chan>=5 && chan[c.chan].dacMode) {
@ -1097,7 +1093,6 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
}
chan[c.chan].furnaceDac=true;
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
@ -1108,25 +1103,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
//chan[c.chan].keyOn=true;
chan[c.chan].active=true;
} else { // compatible mode
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
chan[c.chan].dacSample=12*chan[c.chan].sampleBank+chan[c.chan].note%12;
if (chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
break;
} else {
rWrite(0x2b,1<<7);
if (dumpWrites) addWrite(0xffff0000,chan[c.chan].dacSample);
}
chan[c.chan].dacPos=0;
chan[c.chan].dacPeriod=0;
chan[c.chan].dacRate=MAX(1,parent->getSample(chan[c.chan].dacSample)->rate);
if (dumpWrites) addWrite(0xffff0001,parent->getSample(chan[c.chan].dacSample)->rate);
chan[c.chan].furnaceDac=false;
assert(false && "LEGACY SAMPLE MODE!!!");
}
break;
}
@ -1269,7 +1246,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
}
break;
}
if (c.chan>=5 && chan[c.chan].furnaceDac && chan[c.chan].dacMode) {
if (c.chan>=5 && chan[c.chan].dacMode) {
int destFreq=parent->calcBaseFreq(1,1,c.value2+chan[c.chan].sampleNoteDelta,false);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
@ -1301,13 +1278,6 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
rWrite(0x2b,c.value<<7);
break;
}
case DIV_CMD_SAMPLE_BANK:
if (c.chan<5) c.chan=5;
chan[c.chan].sampleBank=c.value;
if (chan[c.chan].sampleBank>(parent->song.sample.size()/12)) {
chan[c.chan].sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_DIR: {
if (c.chan<5) c.chan=5;
chan[c.chan].dacDirection=c.value;
@ -1323,7 +1293,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
case DIV_CMD_LEGATO: {
if (c.chan==csmChan) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
} else if (c.chan>=5 && chan[c.chan].furnaceDac && chan[c.chan].dacMode) {
} else if (c.chan>=5 && chan[c.chan].dacMode) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+chan[c.chan].sampleNoteDelta,false);
} else {
if (chan[c.chan].insChanged) {

View file

@ -37,7 +37,6 @@ class DivPlatformGenesis: public DivPlatformOPN {
};
struct Channel: public FMChannelStereo {
bool furnaceDac;
bool dacMode;
int dacPeriod;
int dacRate;
@ -46,11 +45,9 @@ class DivPlatformGenesis: public DivPlatformOPN {
int dacDelay;
bool dacDirection;
bool setPos;
unsigned char sampleBank;
signed char dacOutput;
Channel():
FMChannelStereo(),
furnaceDac(false),
dacMode(false),
dacPeriod(0),
dacRate(0),
@ -59,7 +56,6 @@ class DivPlatformGenesis: public DivPlatformOPN {
dacDelay(0),
dacDirection(false),
setPos(false),
sampleBank(0),
dacOutput(0) {}
};
Channel chan[10];

View file

@ -203,14 +203,6 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_SAMPLE_BANK:
if (!parent->song.ignoreDACModeOutsideIntendedChannel) {
chan[5].sampleBank=c.value;
if (chan[5].sampleBank>(parent->song.sample.size()/12)) {
chan[5].sampleBank=parent->song.sample.size()/12;
}
}
break;
case DIV_CMD_LEGATO: {
if (opChan[ch].insChanged) {
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);

View file

@ -171,15 +171,13 @@ void DivPlatformMMC5::tick(bool sysTick) {
// PCM
if (chan[2].freqChanged) {
chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,chan[2].fixedArp?chan[2].baseNoteOverride:chan[2].arpOff,chan[2].fixedArp,false,0,chan[2].pitch2,1,1);
if (chan[2].furnaceDac) {
double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(dacSample);
off=(double)s->centerRate/parent->getCenterRate();
}
dacRate=MIN(chan[2].freq*off,32000);
if (dumpWrites) addWrite(0xffff0001,dacRate);
double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(dacSample);
off=(double)s->centerRate/parent->getCenterRate();
}
dacRate=MIN(chan[2].freq*off,32000);
if (dumpWrites) addWrite(0xffff0001,dacRate);
chan[2].freqChanged=false;
}
}
@ -189,59 +187,35 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON:
if (c.chan==2) { // PCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
if (ins->type==DIV_INS_AMIGA) {
if (c.value!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000,dacSample);
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
dacPos=0;
}
dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].furnaceDac=true;
} else {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
dacSample=12*sampleBank+chan[c.chan].note%12;
if (dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000,dacSample);
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
dacPos=0;
}
dacPeriod=0;
dacRate=parent->getSample(dacSample)->rate;
if (dumpWrites) addWrite(0xffff0001,dacRate);
chan[c.chan].furnaceDac=false;
if (c.value!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000,dacSample);
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
dacPos=0;
}
dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
break;
} else {
if (c.value!=DIV_NOTE_NULL) {
@ -323,12 +297,6 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
chan[c.chan].duty=c.value;
rWrite(0x5000+c.chan*4,0x30|(chan[c.chan].active?chan[c.chan].outVol:0)|((chan[c.chan].duty&3)<<6));
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_POS:
if (c.chan!=2) break;
dacPos=c.value;
@ -422,7 +390,6 @@ void DivPlatformMMC5::reset() {
dacPos=0;
dacRate=0;
dacSample=-1;
sampleBank=0;
map_init_MMC5(mmc5);
memset(regPool,0,128);

View file

@ -26,14 +26,13 @@ class DivPlatformMMC5: public DivDispatch {
struct Channel: public SharedChannel<signed char> {
int prevFreq;
unsigned char duty, sweep;
bool sweepChanged, furnaceDac, setPos;
bool sweepChanged, setPos;
Channel():
SharedChannel<signed char>(15),
prevFreq(65535),
duty(0),
sweep(8),
sweepChanged(false),
furnaceDac(false),
setPos(false) {}
};
Channel chan[5];
@ -49,7 +48,6 @@ class DivPlatformMMC5: public DivDispatch {
int dacPeriod, dacRate;
unsigned int dacPos;
int dacSample;
unsigned char sampleBank;
unsigned char writeOscBuf;
struct _mmc5* mmc5;
unsigned char regPool[128];

View file

@ -162,39 +162,18 @@ int DivPlatformMSM6258::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
if (ins->type==DIV_INS_MSM6258 || ins->type==DIV_INS_AMIGA) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (c.value!=DIV_NOTE_NULL) sample=ins->amiga.getSample(c.value);
samplePos=0;
if (sample>=0 && sample<parent->song.sampleLen) {
//DivSample* s=parent->getSample(chan[c.chan].sample);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)<0 || (12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
}
if (c.value!=DIV_NOTE_NULL) sample=ins->amiga.getSample(c.value);
samplePos=0;
if (sample>=0 && sample<parent->song.sampleLen) {
//DivSample* s=parent->getSample(chan[c.chan].sample);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);
sample=12*sampleBank+c.value%12;
samplePos=0;
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
}
@ -238,12 +217,6 @@ int DivPlatformMSM6258::dispatch(DivCommand c) {
case DIV_CMD_NOTE_PORTA: {
return 2;
}
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_FREQ:
rateSel=c.value&3;
rWrite(12,rateSel);
@ -360,7 +333,6 @@ void DivPlatformMSM6258::reset() {
chan[i].outVol=8;
}
sampleBank=0;
sample=-1;
samplePos=0;

View file

@ -27,12 +27,10 @@
class DivPlatformMSM6258: public DivDispatch {
protected:
struct Channel: public SharedChannel<int> {
bool furnacePCM;
int sample;
unsigned char pan;
Channel():
SharedChannel<int>(8),
furnacePCM(false),
sample(-1),
pan(3) {}
};
@ -48,7 +46,7 @@ class DivPlatformMSM6258: public DivDispatch {
FixedQueue<QueuedWrite,256> writes;
okim6258_device* msm;
unsigned char sampleBank, msmPan, msmDivider, rateSel, msmClock, clockSel;
unsigned char msmPan, msmDivider, rateSel, msmClock, clockSel;
signed char msmDividerCount, msmClockCount;
bool updateSampleFreq;
bool variableRate;

View file

@ -161,40 +161,19 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
if (ins->type==DIV_INS_MSM6295 || ins->type==DIV_INS_AMIGA) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
//DivSample* s=parent->getSample(chan[c.chan].sample);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
rWriteDelay(0,(8<<c.chan),180); // turn off
setPhrase(c.chan);
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
} else {
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)<0 || (12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
}
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
//DivSample* s=parent->getSample(chan[c.chan].sample);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);
chan[c.chan].sample=12*sampleBank+c.value%12;
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
rWriteDelay(0,(8<<c.chan),180); // turn off
setPhrase(c.chan);
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
@ -245,12 +224,6 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
rateSel=c.value;
rWrite(12,!rateSel);
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_LEGATO: {
break;
}
@ -335,7 +308,6 @@ void DivPlatformMSM6295::reset() {
chan[i].outVol=8;
}
sampleBank=0;
rateSel=rateSelInit;
rWrite(12,!rateSelInit);
if (isBanked) {

View file

@ -27,11 +27,9 @@
class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf {
protected:
struct Channel: public SharedChannel<int> {
bool furnacePCM;
int sample;
Channel():
SharedChannel<int>(8),
furnacePCM(false),
sample(-1) {}
};
Channel chan[4];
@ -53,7 +51,6 @@ class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf {
unsigned char* adpcmMem;
size_t adpcmMemLen;
bool* sampleLoaded;
unsigned char sampleBank;
int delay, updateOsc;

View file

@ -419,79 +419,78 @@ void DivPlatformNES::tick(bool sysTick) {
// PCM
if (chan[4].freqChanged || chan[4].keyOn) {
chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,chan[4].fixedArp?chan[4].baseNoteOverride:chan[4].arpOff,chan[4].fixedArp,false);
if (chan[4].furnaceDac) {
double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(dacSample);
off=(double)s->centerRate/parent->getCenterRate();
}
dacRate=MIN(chan[4].freq*off,32000);
if (chan[4].keyOn) {
if (dpcmMode && !skipRegisterWrites && dacSample>=0 && dacSample<parent->song.sampleLen) {
unsigned int dpcmAddr=sampleOffDPCM[dacSample]+(dacPos>>3);
int dpcmLen=(parent->getSample(dacSample)->lengthDPCM-(dacPos>>3))>>4;
if (dpcmLen<0) dpcmLen=0;
if (dpcmLen>255) dpcmLen=255;
goingToLoop=parent->getSample(dacSample)->isLoopable();
// write DPCM
rWrite(0x4015,15);
if (nextDPCMFreq>=0) {
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
nextDPCMFreq=-1;
} else {
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
}
if (nextDPCMDelta>=0) {
rWrite(0x4011,nextDPCMDelta);
nextDPCMDelta=-1;
}
rWrite(0x4012,(dpcmAddr>>6)&0xff);
rWrite(0x4013,dpcmLen&0xff);
rWrite(0x4015,31);
if (dpcmBank!=(dpcmAddr>>14)) {
dpcmBank=dpcmAddr>>14;
logV("switching bank to %d",dpcmBank);
if (dumpWrites) addWrite(0xffff0004,dpcmBank);
}
// sample custom loop point...
DivSample* lsamp=parent->getSample(dacSample);
// how it works:
// when the initial sample info is written (see above) and playback is launched,
// the parameters (start point in memory and length) are locked until sample end
// is reached.
// thus, if we write new data after just several APU clock cycles, it will be used only when
// sample finishes one full loop.
// thus we can write sample's loop point as "start address" and sample's looped part length
// as "full sample length".
// APU will play full sample once and then repeatedly cycle through the looped part.
// sources:
// https://www.nesdev.org/wiki/APU_DMC
// https://www.youtube.com/watch?v=vB4P8x2Am6Y
if (lsamp->loopEnd>lsamp->loopStart && goingToLoop) {
int loopStartAddr=sampleOffDPCM[dacSample]+(lsamp->loopStart>>3);
int loopLen=(lsamp->loopEnd-lsamp->loopStart)>>3;
rWrite(0x4012,(loopStartAddr>>6)&0xff);
rWrite(0x4013,(loopLen>>4)&0xff);
}
}
} else {
double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(dacSample);
off=(double)s->centerRate/parent->getCenterRate();
}
dacRate=MIN(chan[4].freq*off,48000);
if (chan[4].keyOn) {
if (dpcmMode && !skipRegisterWrites && dacSample>=0 && dacSample<parent->song.sampleLen) {
unsigned int dpcmAddr=sampleOffDPCM[dacSample]+(dacPos>>3);
int dpcmLen=(parent->getSample(dacSample)->lengthDPCM-(dacPos>>3))>>4;
if (dpcmLen<0) dpcmLen=0;
if (dpcmLen>255) dpcmLen=255;
goingToLoop=parent->getSample(dacSample)->isLoopable();
// write DPCM
rWrite(0x4015,15);
if (nextDPCMFreq>=0) {
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
nextDPCMFreq=-1;
} else {
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
}
if (nextDPCMDelta>=0) {
rWrite(0x4011,nextDPCMDelta);
nextDPCMDelta=-1;
}
rWrite(0x4012,(dpcmAddr>>6)&0xff);
rWrite(0x4013,dpcmLen&0xff);
rWrite(0x4015,31);
if (dpcmBank!=(dpcmAddr>>14)) {
dpcmBank=dpcmAddr>>14;
logV("switching bank to %d",dpcmBank);
if (dumpWrites) addWrite(0xffff0004,dpcmBank);
}
// sample custom loop point...
DivSample* lsamp=parent->getSample(dacSample);
// how it works:
// when the initial sample info is written (see above) and playback is launched,
// the parameters (start point in memory and length) are locked until sample end
// is reached.
// thus, if we write new data after just several APU clock cycles, it will be used only when
// sample finishes one full loop.
// thus we can write sample's loop point as "start address" and sample's looped part length
// as "full sample length".
// APU will play full sample once and then repeatedly cycle through the looped part.
// sources:
// https://www.nesdev.org/wiki/APU_DMC
// https://www.youtube.com/watch?v=vB4P8x2Am6Y
if (lsamp->loopEnd>lsamp->loopStart && goingToLoop) {
int loopStartAddr=sampleOffDPCM[dacSample]+(lsamp->loopStart>>3);
int loopLen=(lsamp->loopEnd-lsamp->loopStart)>>3;
rWrite(0x4012,(loopStartAddr>>6)&0xff);
rWrite(0x4013,(loopLen>>4)&0xff);
}
}
} else {
if (nextDPCMFreq>=0) {
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
nextDPCMFreq=-1;
} else {
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
}
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
}
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
if (chan[4].keyOn) chan[4].keyOn=false;
chan[4].freqChanged=false;
}
@ -504,111 +503,64 @@ int DivPlatformNES::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON:
if (c.chan==4) { // PCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_NES);
if (ins->type==DIV_INS_AMIGA || (ins->type==DIV_INS_NES && !parent->song.oldDPCM)) {
if (ins->type==DIV_INS_NES) {
if (!dpcmMode) {
dpcmMode=true;
if (dumpWrites) addWrite(0xffff0002,0);
dacSample=-1;
rWrite(0x4015,15);
rWrite(0x4010,0);
rWrite(0x4012,0);
rWrite(0x4013,0);
rWrite(0x4015,31);
}
if (ins->amiga.useNoteMap) {
nextDPCMFreq=ins->amiga.getDPCMFreq(c.value);
if (nextDPCMFreq<0 || nextDPCMFreq>15) nextDPCMFreq=lastDPCMFreq;
lastDPCMFreq=nextDPCMFreq;
nextDPCMDelta=ins->amiga.getDPCMDelta(c.value);
} else {
if (c.value==DIV_NOTE_NULL) {
nextDPCMFreq=lastDPCMFreq;
} else {
nextDPCMFreq=c.value&15;
}
}
}
if (c.value!=DIV_NOTE_NULL) {
dacSample=(int)ins->amiga.getSample(c.value);
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
dacSample=(int)ins->amiga.getSample(chan[c.chan].sampleNote);
if (ins->type==DIV_INS_AMIGA) {
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
if (ins->type==DIV_INS_NES) {
if (!dpcmMode) {
dpcmMode=true;
if (dumpWrites) addWrite(0xffff0002,0);
dacSample=-1;
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
break;
} else {
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
dacPos=0;
}
dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].furnaceDac=true;
} else {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
dacSample=12*sampleBank+chan[c.chan].note%12;
if (dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
break;
} else {
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
dacPos=0;
}
dacPeriod=0;
dacRate=parent->getSample(dacSample)->rate;
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
chan[c.chan].furnaceDac=false;
if (dpcmMode && !skipRegisterWrites) {
unsigned int dpcmAddr=sampleOffDPCM[dacSample]+(dacPos>>3);
int dpcmLen=(parent->getSample(dacSample)->lengthDPCM-(dacPos>>3))>>4;
if (dpcmLen<0) dpcmLen=0;
if (dpcmLen>255) dpcmLen=255;
goingToLoop=parent->getSample(dacSample)->isLoopable();
// write DPCM
rWrite(0x4015,15);
if (nextDPCMFreq>=0) {
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
nextDPCMFreq=-1;
} else {
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
}
rWrite(0x4012,(dpcmAddr>>6)&0xff);
rWrite(0x4013,dpcmLen&0xff);
rWrite(0x4010,0);
rWrite(0x4012,0);
rWrite(0x4013,0);
rWrite(0x4015,31);
if (dpcmBank!=(dpcmAddr>>14)) {
dpcmBank=dpcmAddr>>14;
logV("switching bank to %d",dpcmBank);
if (dumpWrites) addWrite(0xffff0004,dpcmBank);
}
if (ins->amiga.useNoteMap) {
nextDPCMFreq=ins->amiga.getDPCMFreq(c.value);
if (nextDPCMFreq<0 || nextDPCMFreq>15) nextDPCMFreq=lastDPCMFreq;
lastDPCMFreq=nextDPCMFreq;
nextDPCMDelta=ins->amiga.getDPCMDelta(c.value);
} else {
if (c.value==DIV_NOTE_NULL) {
nextDPCMFreq=lastDPCMFreq;
} else {
nextDPCMFreq=c.value&15;
}
}
}
if (c.value!=DIV_NOTE_NULL) {
dacSample=(int)ins->amiga.getSample(c.value);
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
dacSample=(int)ins->amiga.getSample(chan[c.chan].sampleNote);
if (ins->type==DIV_INS_AMIGA) {
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
break;
} else {
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
dacPos=0;
}
dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
break;
} else if (c.chan==3) { // noise
if (c.value!=DIV_NOTE_NULL) {
@ -779,12 +731,6 @@ int DivPlatformNES::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_POS:
if (c.chan!=4) break;
dacPos=c.value;
@ -900,7 +846,6 @@ void DivPlatformNES::reset() {
dpcmPos=0;
dacRate=0;
dacSample=-1;
sampleBank=0;
dpcmBank=0;
dpcmMode=dpcmModeDefault;
goingToLoop=false;

View file

@ -30,7 +30,7 @@ class DivPlatformNES: public DivDispatch {
struct Channel: public SharedChannel<signed char> {
int prevFreq;
unsigned char duty, sweep, envMode, len;
bool sweepChanged, furnaceDac, setPos;
bool sweepChanged, setPos;
Channel():
SharedChannel<signed char>(15),
prevFreq(65535),
@ -39,7 +39,6 @@ class DivPlatformNES: public DivDispatch {
envMode(3),
len(0x1f),
sweepChanged(false),
furnaceDac(false),
setPos(false) {}
};
Channel chan[5];
@ -59,7 +58,6 @@ class DivPlatformNES: public DivDispatch {
size_t dpcmMemLen;
bool* sampleLoaded;
unsigned char dpcmBank;
unsigned char sampleBank;
unsigned char writeOscBuf;
unsigned char apuType;
unsigned char linearCount;

View file

@ -1387,26 +1387,24 @@ void DivPlatformOPL::tick(bool sysTick) {
// ADPCM
if (adpcmChan>=0) {
if (chan[adpcmChan].furnacePCM) {
chan[adpcmChan].std.next();
chan[adpcmChan].std.next();
if (chan[adpcmChan].std.vol.had) {
chan[adpcmChan].outVol=(chan[adpcmChan].vol*MIN(chan[adpcmChan].macroVolMul,chan[adpcmChan].std.vol.val))/chan[adpcmChan].macroVolMul;
immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol));
}
if (chan[adpcmChan].std.vol.had) {
chan[adpcmChan].outVol=(chan[adpcmChan].vol*MIN(chan[adpcmChan].macroVolMul,chan[adpcmChan].std.vol.val))/chan[adpcmChan].macroVolMul;
immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol));
}
if (NEW_ARP_STRAT) {
chan[adpcmChan].handleArp();
} else if (chan[adpcmChan].std.arp.had) {
if (!chan[adpcmChan].inPorta) {
chan[adpcmChan].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmChan].note,chan[adpcmChan].std.arp.val));
}
chan[adpcmChan].freqChanged=true;
if (NEW_ARP_STRAT) {
chan[adpcmChan].handleArp();
} else if (chan[adpcmChan].std.arp.had) {
if (!chan[adpcmChan].inPorta) {
chan[adpcmChan].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmChan].note,chan[adpcmChan].std.arp.val));
}
if (chan[adpcmChan].std.phaseReset.had) {
if ((chan[adpcmChan].std.phaseReset.val==1) && chan[adpcmChan].active) {
chan[adpcmChan].keyOn=true;
}
chan[adpcmChan].freqChanged=true;
}
if (chan[adpcmChan].std.phaseReset.had) {
if ((chan[adpcmChan].std.phaseReset.val==1) && chan[adpcmChan].active) {
chan[adpcmChan].keyOn=true;
}
}
if (chan[adpcmChan].freqChanged || chan[adpcmChan].keyOn || chan[adpcmChan].keyOff) {
@ -1838,75 +1836,39 @@ int DivPlatformOPL::dispatch(DivCommand c) {
} else if (c.chan==adpcmChan) { // ADPCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255;
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMB) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
chan[c.chan].fixedFreq=0;
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol));
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(8,0);
immWrite(9,(sampleOffB[chan[c.chan].sample]>>2)&0xff);
immWrite(10,(sampleOffB[chan[c.chan].sample]>>10)&0xff);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(7,0x01); // reset
immWrite(9,0);
immWrite(10,0);
immWrite(11,0);
immWrite(12,0);
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].macroInit(ins);
chan[c.chan].fixedFreq=0;
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
}
chan[c.chan].sample=12*sampleBank+c.value%12;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
immWrite(8,0);
immWrite(9,(sampleOffB[chan[c.chan].sample]>>2)&0xff);
immWrite(10,(sampleOffB[chan[c.chan].sample]>>10)&0xff);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff);
int freq=(65536.0*(double)s->rate)/(double)chipRateBase;
chan[c.chan].fixedFreq=freq;
immWrite(16,freq&0xff);
immWrite(17,(freq>>8)&0xff);
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(7,0x01); // reset
immWrite(9,0);
immWrite(10,0);
immWrite(11,0);
immWrite(12,0);
immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol));
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(8,0);
immWrite(9,(sampleOffB[chan[c.chan].sample]>>2)&0xff);
immWrite(10,(sampleOffB[chan[c.chan].sample]>>10)&0xff);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(7,0x01); // reset
immWrite(9,0);
immWrite(10,0);
immWrite(11,0);
immWrite(12,0);
break;
}
break;
}
@ -2087,9 +2049,6 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==adpcmChan) {
if (!chan[c.chan].furnacePCM) break;
}
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
@ -2155,14 +2114,6 @@ int DivPlatformOPL::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_SAMPLE_BANK:
if (adpcmChan<0) break;
sampleBank=c.value;
if (sampleBank>(int)(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
// TODO: OPL4 PCM
if (chan[c.chan].insChanged) {
@ -2935,7 +2886,6 @@ void DivPlatformOPL::reset() {
lastBusy=60;
lfoValue=8;
drumState=0;
sampleBank=0;
drumVol[0]=0;
drumVol[1]=0;
@ -3498,7 +3448,6 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfi
adpcmBMem=new unsigned char[262144];
adpcmBMemLen=0;
iface.adpcmBMem=adpcmBMem;
iface.sampleBank=0;
adpcmB=new ymfm::adpcm_b_engine(iface,2);
}
@ -3506,7 +3455,6 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfi
pcmMem=new unsigned char[4194304];
pcmMemLen=0;
iface.pcmMem=pcmMem;
iface.sampleBank=0;
pcmMemory.memory=pcmMem;
}

View file

@ -36,10 +36,9 @@ class DivOPLAInterface: public ymfm::ymfm_interface {
public:
unsigned char* adpcmBMem;
unsigned char* pcmMem;
int sampleBank;
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
DivOPLAInterface(): adpcmBMem(NULL), pcmMem(NULL), sampleBank(0) {}
DivOPLAInterface(): adpcmBMem(NULL), pcmMem(NULL) {}
};
class DivYMF278MemoryInterface: public MemoryInterface {
@ -60,7 +59,7 @@ class DivPlatformOPL: public DivDispatch {
DivInstrumentFM state;
unsigned int freqH, freqL;
int sample, fixedFreq;
bool furnacePCM, fourOp, hardReset, writeCtrl;
bool fourOp, hardReset, writeCtrl;
bool levelDirect, damp, pseudoReverb, lfoReset, ch;
int lfo, vib, am, ar, d1r, d2r, dl, rc, rr;
int pan;
@ -71,7 +70,6 @@ class DivPlatformOPL: public DivDispatch {
freqL(0),
sample(-1),
fixedFreq(0),
furnacePCM(false),
fourOp(false),
hardReset(false),
writeCtrl(false),
@ -134,7 +132,7 @@ class DivPlatformOPL: public DivDispatch {
const unsigned short* chanMap;
const unsigned char* outChanMap;
int chipFreqBase, chipRateBase;
int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan=-1, pcmChanOffs=-1, sampleBank, totalOutputs, ramSize;
int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan=-1, pcmChanOffs=-1, totalOutputs, ramSize;
int fmMixL=7, fmMixR=7, pcmMixL=7, pcmMixR=7;
unsigned char lastBusy;
unsigned char drumState;

View file

@ -34,14 +34,12 @@ class DivPlatformOPLL: public DivDispatch {
DivInstrumentFM state;
unsigned char freqH, freqL;
int fixedFreq;
bool furnaceDac;
unsigned char pan;
Channel():
SharedChannel<int>(0),
freqH(0),
freqL(0),
fixedFreq(0),
furnaceDac(false),
pan(3) {}
};
Channel chan[11];

View file

@ -225,7 +225,7 @@ void DivPlatformPCE::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
if (chan[i].furnaceDac && chan[i].pcm) {
if (chan[i].pcm) {
if (chan[i].active && chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
if (chan[i].setPos) {
chan[i].setPos=false;
@ -249,7 +249,7 @@ void DivPlatformPCE::tick(bool sysTick) {
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].furnaceDac && chan[i].pcm) {
if (chan[i].pcm) {
double off=1.0;
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].dacSample);
@ -301,79 +301,48 @@ int DivPlatformPCE::dispatch(DivCommand c) {
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:31;
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
chan[c.chan].pcm=true;
} else if (chan[c.chan].furnaceDac) {
} else {
chan[c.chan].pcm=false;
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
}
if (chan[c.chan].pcm) {
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
chan[c.chan].furnaceDac=true;
if (skipRegisterWrites) break;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
chan[c.chan].dacSample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) {
chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol));
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample);
}
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
chan[c.chan].dacPos=0;
}
chan[c.chan].dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
//chan[c.chan].keyOn=true;
if (skipRegisterWrites) break;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
chan[c.chan].dacSample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
chan[c.chan].furnaceDac=false;
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
if (skipRegisterWrites) break;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
chan[c.chan].dacSample=12*sampleBank+chan[c.chan].note%12;
if (chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample);
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
chan[c.chan].dacPos=0;
}
chan[c.chan].dacPeriod=0;
chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate;
if (dumpWrites) {
chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol));
addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate);
}
if (dumpWrites) {
chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol));
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample);
}
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
chan[c.chan].dacPos=0;
}
chan[c.chan].dacPeriod=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
break;
}
@ -491,12 +460,6 @@ int DivPlatformPCE::dispatch(DivCommand c) {
case DIV_CMD_SAMPLE_MODE:
chan[c.chan].pcm=c.value;
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].dacPos=c.value;
chan[c.chan].setPos=true;
@ -630,7 +593,6 @@ void DivPlatformPCE::reset() {
pce->Power(0);
lastPan=0xff;
curChan=-1;
sampleBank=0;
lfoMode=0;
lfoSpeed=255;
// set global volume

View file

@ -32,7 +32,7 @@ class DivPlatformPCE: public DivDispatch {
unsigned int dacPos;
int dacSample;
unsigned char pan;
bool noise, pcm, furnaceDac, deferredWaveUpdate, setPos;
bool noise, pcm, deferredWaveUpdate, setPos;
signed short wave;
int macroVolMul, noiseSeek;
DivWaveSynth ws;
@ -48,7 +48,6 @@ class DivPlatformPCE: public DivDispatch {
pan(255),
noise(false),
pcm(false),
furnaceDac(false),
deferredWaveUpdate(false),
setPos(false),
wave(-1),
@ -70,7 +69,7 @@ class DivPlatformPCE: public DivDispatch {
unsigned char lastPan;
int curChan;
unsigned char sampleBank, lfoMode, lfoSpeed;
unsigned char lfoMode, lfoSpeed;
PCE_PSG* pce;
unsigned char regPool[128];
void updateWave(int ch);

View file

@ -389,12 +389,6 @@ void DivPlatformSAA1099::reset() {
}
lastBusy=60;
dacMode=0;
dacPeriod=0;
dacPos=0;
dacRate=0;
dacSample=-1;
sampleBank=0;
saaEnv[0]=0;
saaEnv[1]=0;
saaNoise[0]=0;

View file

@ -52,13 +52,6 @@ class DivPlatformSAA1099: public DivDispatch {
CSAASound* saa_saaSound;
unsigned char regPool[32];
unsigned char lastBusy;
bool dacMode;
int dacPeriod;
int dacRate;
int dacPos;
int dacSample;
unsigned char sampleBank;
int delay;

View file

@ -78,11 +78,15 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
if (NEW_ARP_STRAT) {
chan[i].handleArp();
if (chan[i].std.arp.had) {
if (chan[i].freqChanged) chan[i].pcm.freq=-1;
}
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<7);
}
chan[i].freqChanged=true;
chan[i].pcm.freq=-1;
}
if (parent->song.newSegaPCM) if (chan[i].std.panL.had) {
@ -105,6 +109,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
chan[i].pcm.freq=-1;
}
if (chan[i].std.phaseReset.had) {
@ -123,12 +128,13 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
}
}
if (oldSlides) chan[i].freq&=~1;
if (chan[i].furnacePCM) {
double off=1.0;
if (chan[i].pcm.sample>=0 && chan[i].pcm.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].pcm.sample);
off=(double)s->centerRate/parent->getCenterRate();
}
double off=1.0;
if (chan[i].pcm.sample>=0 && chan[i].pcm.sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].pcm.sample);
off=(double)s->centerRate/parent->getCenterRate();
}
if (chan[i].pcm.freq==-1) {
chan[i].pcm.freq=MIN(255,((rate*0.5)+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+512)/(128.0*12.0)))*255)/rate)+(oldSlides?chan[i].pitch2:0);
rWrite(7+(i<<3),chan[i].pcm.freq);
}
@ -141,44 +147,23 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
} else {
chan[i].pcm.pos=0;
}
if (chan[i].furnacePCM) {
DivSample* s=parent->getSample(chan[i].pcm.sample);
int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT));
if (actualLength>0xfeff) actualLength=0xfeff;
int actualPos=sampleOffSegaPCM[chan[i].pcm.sample]+chan[i].pcm.pos;
rWrite(0x86+(i<<3),3+((actualPos>>16)<<3));
rWrite(0x84+(i<<3),(actualPos)&0xff);
rWrite(0x85+(i<<3),(actualPos>>8)&0xff);
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
if (!s->isLoopable()) {
rWrite(0x86+(i<<3),2+((actualPos>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
logV("sampleOff: %x loopPos: %x",actualPos,loopPos);
rWrite(4+(i<<3),loopPos&0xff);
rWrite(5+(i<<3),(loopPos>>8)&0xff);
rWrite(0x86+(i<<3),((actualPos>>16)<<3));
}
DivSample* s=parent->getSample(chan[i].pcm.sample);
int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT));
if (actualLength>0xfeff) actualLength=0xfeff;
int actualPos=sampleOffSegaPCM[chan[i].pcm.sample]+chan[i].pcm.pos;
rWrite(0x86+(i<<3),3+((actualPos>>16)<<3));
rWrite(0x84+(i<<3),(actualPos)&0xff);
rWrite(0x85+(i<<3),(actualPos>>8)&0xff);
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
if (!s->isLoopable()) {
rWrite(0x86+(i<<3),2+((actualPos>>16)<<3));
} else {
DivSample* s=parent->getSample(chan[i].pcm.sample);
int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT));
if (actualLength>0xfeff) actualLength=0xfeff;
int actualPos=sampleOffSegaPCM[chan[i].pcm.sample]+chan[i].pcm.pos;
rWrite(0x86+(i<<3),3+((actualPos>>16)<<3));
rWrite(0x84+(i<<3),(actualPos)&0xff);
rWrite(0x85+(i<<3),(actualPos>>8)&0xff);
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
if (!s->isLoopable()) {
rWrite(0x86+(i<<3),2+((actualPos>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
rWrite(4+(i<<3),loopPos&0xff);
rWrite(5+(i<<3),(loopPos>>8)&0xff);
rWrite(0x86+(i<<3),((actualPos>>16)<<3));
}
rWrite(7+(i<<3),chan[i].pcm.freq);
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
logV("sampleOff: %x loopPos: %x",actualPos,loopPos);
rWrite(4+(i<<3),loopPos&0xff);
rWrite(5+(i<<3),(loopPos>>8)&0xff);
rWrite(0x86+(i<<3),((actualPos>>16)<<3));
}
}
chan[i].keyOn=false;
@ -198,56 +183,39 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SEGAPCM) {
chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:127;
chan[c.chan].isNewSegaPCM=(ins->type==DIV_INS_SEGAPCM);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].pcm.sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1;
rWrite(0x86+(c.chan<<3),3);
chan[c.chan].macroInit(NULL);
break;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=(c.value<<7);
chan[c.chan].freqChanged=true;
}
chan[c.chan].furnacePCM=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if (parent->song.newSegaPCM) {
chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127;
chan[c.chan].chVolR=(chan[c.chan].outVol*chan[c.chan].chPanR)/127;
rWrite(2+(c.chan<<3),chan[c.chan].chVolL);
rWrite(3+(c.chan<<3),chan[c.chan].chVolR);
}
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
chan[c.chan].macroInit(NULL);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
chan[c.chan].pcm.sample=12*sampleBank+chan[c.chan].note%12;
if (chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1;
rWrite(0x86+(c.chan<<3),3);
break;
}
chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/rate);
chan[c.chan].furnacePCM=false;
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:127;
chan[c.chan].isNewSegaPCM=(ins->type==DIV_INS_SEGAPCM);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].pcm.sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1;
rWrite(0x86+(c.chan<<3),3);
chan[c.chan].macroInit(NULL);
break;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=(c.value<<7);
chan[c.chan].freqChanged=true;
chan[c.chan].pcm.freq=-1;
}
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if (parent->song.newSegaPCM) {
chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127;
chan[c.chan].chVolR=(chan[c.chan].outVol*chan[c.chan].chPanR)/127;
rWrite(2+(c.chan<<3),chan[c.chan].chVolL);
rWrite(3+(c.chan<<3),chan[c.chan].chVolR);
}
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
break;
}
case DIV_CMD_NOTE_OFF:
@ -310,6 +278,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
case DIV_CMD_PITCH: {
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
chan[c.chan].pcm.freq=-1;
break;
}
case DIV_CMD_NOTE_PORTA: {
@ -332,6 +301,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
}
chan[c.chan].baseFreq=newFreq;
chan[c.chan].freqChanged=true;
chan[c.chan].pcm.freq=-1;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
@ -341,14 +311,9 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=((c.value+chan[c.chan].sampleNoteDelta)<<7);
chan[c.chan].freqChanged=true;
chan[c.chan].pcm.freq=-1;
break;
}
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].pcm.pos=c.value;
chan[c.chan].setPos=true;
@ -488,7 +453,6 @@ void DivPlatformSegaPCM::reset() {
pcmCycles=0;
pcmL=0;
pcmR=0;
sampleBank=0;
delay=0;
pcm.device_start();

View file

@ -28,7 +28,7 @@
class DivPlatformSegaPCM: public DivDispatch {
protected:
struct Channel: public SharedChannel<int> {
bool furnacePCM, isNewSegaPCM, setPos;
bool isNewSegaPCM, setPos;
unsigned char chVolL, chVolR;
unsigned char chPanL, chPanR;
int macroVolMul;
@ -37,12 +37,11 @@ class DivPlatformSegaPCM: public DivDispatch {
int sample;
unsigned int pos; // <<8
unsigned short len;
unsigned char freq;
PCMChannel(): sample(-1), pos(0), len(0), freq(0) {}
short freq;
PCMChannel(): sample(-1), pos(0), len(0), freq(-1) {}
} pcm;
Channel():
SharedChannel<int>(127),
furnacePCM(false),
isNewSegaPCM(false),
setPos(false),
chVolL(127),
@ -68,7 +67,6 @@ class DivPlatformSegaPCM: public DivDispatch {
int delay;
int pcmL, pcmR, pcmCycles;
bool oldSlides;
unsigned char sampleBank;
unsigned char lastBusy;
unsigned char regPool[256];

View file

@ -142,6 +142,7 @@ void DivPlatformSMS::acquire_mame(blip_buffer_t** bb, size_t len) {
// wahahaha heuristic...
int advance=len-h;
for (int i=0; i<4; i++) {
if (sn->m_volume[i]==0) continue;
if (sn->m_count[i]<advance) advance=sn->m_count[i];
}
if (advance<1) advance=1;

View file

@ -612,7 +612,6 @@ void DivPlatformSoundUnit::reset() {
lastPan=0xff;
cycles=0;
curChan=-1;
sampleBank=0;
lfoMode=0;
lfoSpeed=255;
delay=500;

View file

@ -100,7 +100,7 @@ class DivPlatformSoundUnit: public DivDispatch {
int cycles, curChan, delay, sysIDCache;
short tempL;
short tempR;
unsigned char sampleBank, lfoMode, lfoSpeed;
unsigned char lfoMode, lfoSpeed;
SoundUnit* su;
unsigned char* sampleMem;
size_t sampleMemLen;

View file

@ -278,7 +278,7 @@ void DivPlatformSwan::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (i==1 && pcm && furnaceDac) {
if (i==1 && pcm) {
double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(dacSample);
@ -355,7 +355,7 @@ int DivPlatformSwan::dispatch(DivCommand c) {
if (c.chan==1) {
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
pcm=true;
} else if (furnaceDac) {
} else {
pcm=false;
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
@ -368,54 +368,32 @@ int DivPlatformSwan::dispatch(DivCommand c) {
dacPos=0;
}
dacPeriod=0;
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
if (c.value!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) postWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) {
postWrite(0xffff0000,dacSample);
}
}
if (c.value!=DIV_NOTE_NULL) {
chan[1].baseFreq=NOTE_PERIODIC(c.value);
chan[1].freqChanged=true;
chan[1].note=c.value;
}
chan[1].active=true;
chan[1].keyOn=true;
chan[1].macroInit(ins);
furnaceDac=true;
} else {
if (c.value!=DIV_NOTE_NULL) {
chan[1].note=c.value;
}
dacSample=12*sampleBank+chan[1].note%12;
if (dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) postWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) postWrite(0xffff0000,dacSample);
}
dacRate=parent->getSample(dacSample)->rate;
if (dumpWrites) {
postWrite(0xffff0001,dacRate);
}
chan[1].active=true;
chan[1].keyOn=true;
furnaceDac=false;
if (c.value!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) postWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) {
postWrite(0xffff0000,dacSample);
}
}
if (c.value!=DIV_NOTE_NULL) {
chan[1].baseFreq=NOTE_PERIODIC(c.value);
chan[1].freqChanged=true;
chan[1].note=c.value;
}
chan[1].active=true;
chan[1].keyOn=true;
chan[1].macroInit(ins);
break;
}
}
@ -544,12 +522,6 @@ int DivPlatformSwan::dispatch(DivCommand c) {
}
}
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_POS:
dacPos=c.value;
setPos=true;
@ -675,14 +647,12 @@ void DivPlatformSwan::reset() {
swan_sound_init(&ws, true);
pcm=false;
sweep=false;
furnaceDac=false;
setPos=false;
noise=0;
dacPeriod=0;
dacRate=0;
dacPos=0;
dacSample=-1;
sampleBank=0;
rWrite(0x0f,0x00); // wave table at 0x0000
rWrite(0x11,0x0f); // enable speakers, minimum headphone volume
}

View file

@ -41,8 +41,8 @@ class DivPlatformSwan: public DivDispatch {
bool isMuted[4];
bool stereo;
bool useMdfn;
bool pcm, sweep, furnaceDac, setPos;
unsigned char sampleBank, noise;
bool pcm, sweep, setPos;
unsigned char noise;
int dacPeriod, dacRate;
unsigned int dacPos;
int dacSample;

View file

@ -1,670 +0,0 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "swan_before.h"
#include "../engine.h"
#include "furIcons.h"
#include "IconsFontAwesome4.h"
#include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);}}
#define postWrite(a,v) postDACWrites.push(DivRegWrite(a,v));
#define CHIP_DIVIDER 32
const char* regCheatSheetWS[]={
"CH1_Pitch", "00",
"CH2_Pitch", "02",
"CH3_Pitch", "04",
"CH4_Pitch", "06",
"CH1_Vol", "08",
"CH2_Vol", "09",
"CH3_Vol", "0A",
"CH4_Vol", "0B",
"Sweep_Value", "0C",
"Sweep_Time", "0D",
"Noise", "0E",
"Wave_Base", "0F",
"Ctrl", "10",
"Output", "11",
"Random", "12",
"Voice_Ctrl", "14",
"Wave_Mem", "40",
NULL
};
const char** DivPlatformSwan::getRegisterSheet() {
return regCheatSheetWS;
}
void DivPlatformSwan::acquireDirect(blip_buffer_t** bb, size_t len) {
for (int i=0; i<4; i++) {
oscBuf[i]->begin(len);
ws_mdfn->oscBuf[i]=oscBuf[i];
}
ws_mdfn->sbuf[0]=bb[0];
ws_mdfn->sbuf[1]=bb[1];
for (size_t h=0; h<len; h++) {
ws_mdfn->v30mz_timestamp=h;
// heuristic
int pcmAdvance=1;
if (writes.empty()) {
if (!pcm || dacSample==-1) {
break;
} else {
pcmAdvance=len-h;
if (dacRate>0) {
int remainTime=(rate-dacPeriod+dacRate-1)/dacRate;
if (remainTime<pcmAdvance) pcmAdvance=remainTime;
if (remainTime<1) pcmAdvance=1;
}
}
}
// PCM part
if (pcm && dacSample!=-1) {
dacPeriod+=dacRate*pcmAdvance;
while (dacPeriod>=rate) {
DivSample* s=parent->getSample(dacSample);
if (s->samples<=0 || dacPos>=s->samples) {
dacSample=-1;
dacPeriod=0;
break;
}
rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80);
if (s->isLoopable() && dacPos>=(unsigned int)s->loopEnd) {
dacPos=s->loopStart;
} else if (dacPos>=s->samples) {
dacSample=-1;
}
dacPeriod-=rate;
}
}
h+=pcmAdvance-1;
// the rest
while (!writes.empty()) {
QueuedWrite w=writes.front();
regPool[w.addr]=w.val;
if (w.addr<0x40) {
ws_mdfn->SoundWrite(w.addr|0x80,w.val);
} else {
ws_mdfn->SoundCheckRAMWrite(w.addr&0x3f);
ws_mdfn->RAMWrite(w.addr&0x3f,w.val);
}
writes.pop();
}
}
ws_mdfn->v30mz_timestamp=len;
ws_mdfn->SoundUpdate();
ws_mdfn->SoundFlush(NULL,0);
for (int i=0; i<4; i++) {
oscBuf[i]->end(len);
}
}
void DivPlatformSwan::updateWave(int ch) {
unsigned char addr=0x40+ch*16;
for (int i=0; i<16; i++) {
int nibble1=chan[ch].ws.output[i<<1];
int nibble2=chan[ch].ws.output[1+(i<<1)];
rWrite(addr+i,nibble1|(nibble2<<4));
}
}
void DivPlatformSwan::calcAndWriteOutVol(int ch, int env) {
int vl=chan[ch].vol*((chan[ch].pan>>4)&0x0f)*env/225;
int vr=chan[ch].vol*(chan[ch].pan&0x0f)*env/225;
if (ch==1&&pcm) {
vl=(vl>0)?((vl>7)?3:2):0;
vr=(vr>0)?((vr>7)?3:2):0;
chan[1].outVol=vr|(vl<<2);
} else {
chan[ch].outVol=vr|(vl<<4);
}
writeOutVol(ch);
}
void DivPlatformSwan::writeOutVol(int ch) {
unsigned char val=isMuted[ch]?0:chan[ch].outVol;
if (ch==1&&pcm) {
rWrite(0x14,val)
} else {
rWrite(0x08+ch,val);
}
}
void DivPlatformSwan::tick(bool sysTick) {
unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0);
for (int i=0; i<4; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
int env=chan[i].std.vol.val;
if(parent->getIns(chan[i].ins,DIV_INS_SWAN)->type==DIV_INS_AMIGA) {
env=MIN(env/4,15);
}
calcAndWriteOutVol(i,env);
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
}
chan[i].freqChanged=true;
}
if (chan[i].std.wave.had && !(i==1 && pcm)) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
chan[i].ws.changeWave1(chan[i].wave);
}
}
if (chan[i].std.panL.had) {
chan[i].pan&=0x0f;
chan[i].pan|=(chan[i].std.panL.val&15)<<4;
}
if (chan[i].std.panR.had) {
chan[i].pan&=0xf0;
chan[i].pan|=chan[i].std.panR.val&15;
}
if (chan[i].std.panL.had || chan[i].std.panR.had) {
calcAndWriteOutVol(i,chan[i].std.vol.will?chan[i].std.vol.val:15);
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-32768,32767);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].active) {
sndCtrl|=(1<<i);
if (chan[i].ws.tick()) {
updateWave(i);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (i==1 && pcm && furnaceDac) {
double off=1.0;
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(dacSample);
if (s->centerRate<1) {
off=1.0;
} else {
off=parent->getCenterRate()/(double)s->centerRate;
}
}
dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq);
if (dumpWrites) postWrite(0xffff0001,dacRate);
}
if (chan[i].freq>2048) chan[i].freq=2048;
if (chan[i].freq<1) chan[i].freq=1;
int rVal=2048-chan[i].freq;
rWrite(i*2,rVal&0xff);
rWrite(i*2+1,rVal>>8);
if (chan[i].keyOn) {
if (!chan[i].std.vol.will) {
calcAndWriteOutVol(i,15);
}
chan[i].keyOn=false;
}
if (chan[i].keyOff) {
chan[i].keyOff=false;
}
chan[i].freqChanged=false;
}
}
if (chan[3].std.duty.had) {
if (noise!=chan[3].std.duty.val) {
noise=chan[3].std.duty.val;
if (noise>0) {
rWrite(0x0e,((noise-1)&0x07)|0x18);
sndCtrl|=0x80;
} else {
sndCtrl&=~0x80;
}
}
}
if (chan[3].std.phaseReset.had) {
if (noise>0) {
rWrite(0x0e,((noise-1)&0x07)|0x18);
sndCtrl|=0x80;
} else {
sndCtrl&=~0x80;
}
}
unsigned char origSndCtrl=sndCtrl;
bool phaseResetHappens=false;
for (int i=0; i<4; i++) {
if (chan[i].std.phaseReset.had) {
phaseResetHappens=true;
sndCtrl&=~(1<<i);
}
}
if (phaseResetHappens) {
rWrite(0x10,sndCtrl);
sndCtrl=origSndCtrl;
}
rWrite(0x10,sndCtrl);
while (!postDACWrites.empty()) {
const DivRegWrite& w=postDACWrites.back();
if (dumpWrites) addWrite(w.addr,w.val);
postDACWrites.pop();
}
}
int DivPlatformSwan::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SWAN);
if (c.chan==1) {
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
pcm=true;
} else if (furnaceDac) {
pcm=false;
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
}
if (pcm) {
if (skipRegisterWrites) break;
if (setPos) {
setPos=false;
} else {
dacPos=0;
}
dacPeriod=0;
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
if (c.value!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(chan[c.chan].sampleNote);
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) postWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) {
postWrite(0xffff0000,dacSample);
}
}
if (c.value!=DIV_NOTE_NULL) {
chan[1].baseFreq=NOTE_PERIODIC(c.value);
chan[1].freqChanged=true;
chan[1].note=c.value;
}
chan[1].active=true;
chan[1].keyOn=true;
chan[1].macroInit(ins);
furnaceDac=true;
} else {
if (c.value!=DIV_NOTE_NULL) {
chan[1].note=c.value;
}
dacSample=12*sampleBank+chan[1].note%12;
if (dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) postWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) postWrite(0xffff0000,dacSample);
}
dacRate=parent->getSample(dacSample)->rate;
if (dumpWrites) {
postWrite(0xffff0001,dacRate);
}
chan[1].active=true;
chan[1].keyOn=true;
furnaceDac=false;
}
break;
}
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.init(ins,32,15,chan[c.chan].insChanged);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
if (c.chan==1&&pcm) {
dacSample=-1;
if (dumpWrites) postWrite(0xffff0002,0);
pcm=false;
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
}
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
calcAndWriteOutVol(c.chan,15);
}
}
break;
case DIV_CMD_GET_VOLUME:
return chan[c.chan].vol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_WS_SWEEP_TIME:
if (c.chan==2) {
if (c.value==0) {
sweep=false;
} else {
sweep=true;
rWrite(0x0d,(c.value-1)&0xff);
}
}
break;
case DIV_CMD_WS_SWEEP_AMOUNT:
if (c.chan==2) {
rWrite(0x0c,c.value&0xff);
}
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2+chan[c.chan].sampleNoteDelta);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_STD_NOISE_MODE:
if (c.chan==3) {
noise=c.value&0xff;
if (noise>0) rWrite(0x0e,((noise-1)&0x07)|0x18);
}
break;
case DIV_CMD_SAMPLE_MODE:
if (c.chan==1) {
pcm=c.value;
if (!pcm) {
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
}
}
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_POS:
dacPos=c.value;
setPos=true;
break;
case DIV_CMD_PANNING: {
chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4);
calcAndWriteOutVol(c.chan,chan[c.chan].std.vol.will?chan[c.chan].std.vol.val:15);
break;
}
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SWAN));
}
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 15;
break;
case DIV_CMD_MACRO_OFF:
chan[c.chan].std.mask(c.value,true);
break;
case DIV_CMD_MACRO_ON:
chan[c.chan].std.mask(c.value,false);
break;
case DIV_CMD_MACRO_RESTART:
chan[c.chan].std.restart(c.value);
break;
default:
break;
}
return 1;
}
void DivPlatformSwan::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
writeOutVol(ch);
}
void DivPlatformSwan::forceIns() {
noise=0;
for (int i=0; i<4; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
updateWave(i);
writeOutVol(i);
}
}
void* DivPlatformSwan::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformSwan::getChanMacroInt(int ch) {
return &chan[ch].std;
}
unsigned short DivPlatformSwan::getPan(int ch) {
return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15);
}
DivChannelModeHints DivPlatformSwan::getModeHints(int ch) {
DivChannelModeHints ret;
switch (ch) {
case 1: // PCM
ret.count=1;
ret.hint[0]=ICON_FA_VOLUME_UP;
ret.type[0]=pcm?4:0;
break;
case 2: // sweep
ret.count=1;
ret.hint[0]=ICON_FUR_SAW;
ret.type[0]=sweep?2:0;
break;
case 3: // noise
ret.count=1;
ret.hint[0]=ICON_FUR_NOISE;
ret.type[0]=noise?4:0;
break;
}
return ret;
}
DivDispatchOscBuffer* DivPlatformSwan::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformSwan::getRegisterPool() {
// get Random from emulator
regPool[0x12]=ws_mdfn->SoundRead(0x92);
regPool[0x13]=ws_mdfn->SoundRead(0x93);
return regPool;
}
int DivPlatformSwan::getRegisterPoolSize() {
return 128;
}
void DivPlatformSwan::reset() {
while (!writes.empty()) writes.pop();
while (!postDACWrites.empty()) postDACWrites.pop();
memset(regPool,0,128);
for (int i=0; i<4; i++) {
chan[i]=Channel();
chan[i].vol=15;
chan[i].pan=0xff;
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,15,false);
rWrite(0x08+i,0xff);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
ws_mdfn->SoundReset();
pcm=false;
sweep=false;
furnaceDac=false;
setPos=false;
noise=0;
dacPeriod=0;
dacRate=0;
dacPos=0;
dacSample=-1;
sampleBank=0;
rWrite(0x0f,0x00); // wave table at 0x0000
rWrite(0x11,0x09); // enable speakers
}
int DivPlatformSwan::getOutputCount() {
return 2;
}
bool DivPlatformSwan::hasAcquireDirect() {
return true;
}
void DivPlatformSwan::notifyWaveChange(int wave) {
for (int i=0; i<4; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
updateWave(i);
}
}
}
void DivPlatformSwan::notifyInsDeletion(void* ins) {
for (int i=0; i<4; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformSwan::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformSwan::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
void DivPlatformSwan::setFlags(const DivConfig& flags) {
chipClock=3072000;
CHECK_CUSTOM_CLOCK;
rate=chipClock;
for (int i=0; i<4; i++) {
oscBuf[i]->setRate(rate);
}
}
int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<4; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
ws_mdfn=new WSwan();
setFlags(flags);
reset();
return 4;
}
void DivPlatformSwan::quit() {
for (int i=0; i<4; i++) {
delete oscBuf[i];
}
delete ws_mdfn;
}
DivPlatformSwan::~DivPlatformSwan() {
}

View file

@ -1,90 +0,0 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _SWAN_H
#define _SWAN_H
#include "../dispatch.h"
#include "../waveSynth.h"
#include "sound/swan_mdfn.h"
#include "../../fixedQueue.h"
class DivPlatformSwan: public DivDispatch {
struct Channel: public SharedChannel<int> {
unsigned char pan;
int wave;
DivWaveSynth ws;
Channel():
SharedChannel<int>(15),
pan(255),
wave(-1) {}
};
Channel chan[4];
DivDispatchOscBuffer* oscBuf[4];
bool isMuted[4];
bool pcm, sweep, furnaceDac, setPos;
unsigned char sampleBank, noise;
int dacPeriod, dacRate;
unsigned int dacPos;
int dacSample;
unsigned char regPool[0x80];
struct QueuedWrite {
unsigned char addr;
unsigned char val;
QueuedWrite(): addr(0), val(0) {}
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
};
FixedQueue<QueuedWrite,256> writes;
FixedQueue<DivRegWrite,2048> postDACWrites;
WSwan* ws_mdfn;
void updateWave(int ch);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
void acquireDirect(blip_buffer_t** bb, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
unsigned short getPan(int chan);
DivChannelModeHints getModeHints(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
void setFlags(const DivConfig& flags);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
int getOutputCount();
bool hasAcquireDirect();
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformSwan();
private:
void calcAndWriteOutVol(int ch, int env);
void writeOutVol(int ch);
};
#endif

View file

@ -303,32 +303,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) {
//chan[c.chan].keyOn=true;
chan[c.chan].furnaceDac=true;
} else {
chan[c.chan].sampleNote=DIV_NOTE_NULL;
chan[c.chan].sampleNoteDelta=0;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
}
chan[c.chan].dacSample=12*sampleBank+chan[c.chan].note%12;
if (chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample);
}
if (chan[c.chan].setPos) {
chan[c.chan].setPos=false;
} else {
chan[c.chan].dacPos=0;
}
chan[c.chan].dacPeriod=0;
chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate;
if (dumpWrites) {
chWrite(c.chan,2,0x80);
chWrite(c.chan,0,isMuted[c.chan]?0:0x80);
addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate);
}
chan[c.chan].furnaceDac=false;
assert(false && "LEGACY SAMPLE MODE!!!");
}
break;
}
@ -432,12 +407,6 @@ int DivPlatformVRC6::dispatch(DivCommand c) {
}
}
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].dacPos=c.value;
chan[c.chan].setPos=true;
@ -535,7 +504,6 @@ void DivPlatformVRC6::reset() {
addWrite(0xffffffff,0);
}
sampleBank=0;
prevSample=0;
vrc6.reset();

View file

@ -54,7 +54,6 @@ class DivPlatformVRC6: public DivDispatch, public vrcvi_intf {
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
};
FixedQueue<QueuedWrite,64> writes;
unsigned char sampleBank;
vrcvi_core vrc6;
int prevSample;
unsigned char regPool[13];

View file

@ -592,43 +592,15 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
} else {
chan[c.chan].macroInit(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
// huh?
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
chWrite(c.chan,0,0); // reset
chWrite(c.chan,1,0);
chWrite(c.chan,2,0);
chWrite(c.chan,4,0);
chWrite(c.chan,5,0);
break;
}
}
} else {
chan[c.chan].macroInit(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
chan[c.chan].sample=12*sampleBank+c.value%12;
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
// TODO: there was a check for legacy sample bank here. why?
chWrite(c.chan,0,0); // reset
chWrite(c.chan,1,0);
chWrite(c.chan,2,0);
chWrite(c.chan,4,0);
chWrite(c.chan,5,0);
break;
}
DivSample* s=parent->getSample(chan[c.chan].sample);
if (isBanked) {
bankSlot[chan[c.chan].bankSlot]=sampleOffX1[chan[c.chan].sample]>>17;
unsigned int bankedOffs=(chan[c.chan].bankSlot<<17)|(sampleOffX1[chan[c.chan].sample]&0x1ffff);
chWrite(c.chan,4,(bankedOffs>>12)&0xff);
int end=(bankedOffs+MIN(s->length8,0x1ffff)+0xfff)&~0xfff; // padded
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
} else {
chWrite(c.chan,4,(sampleOffX1[chan[c.chan].sample]>>12)&0xff);
int end=(sampleOffX1[chan[c.chan].sample]+s->length8+0xfff)&~0xfff; // padded
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
}
// ????
chan[c.chan].fixedFreq=(((unsigned int)s->rate)<<4)/(chipClock/512);
chan[c.chan].freqChanged=true;
} else {
assert(false && "LEGACY SAMPLE MODE!!!");
}
} else if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
@ -739,12 +711,6 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
chan[c.chan].envChanged=true;
}
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_PANNING: {
if (!stereo) break;
unsigned char newPan=(c.value&0xf0)|(c.value2>>4);
@ -919,7 +885,6 @@ void DivPlatformX1_010::reset() {
chan[i].ws.init(NULL,128,255,false);
}
x1_010.reset();
sampleBank=0;
// set per-channel initial panning
for (int i=0; i<16; i++) {
chWrite(i,0,0);

View file

@ -112,7 +112,6 @@ class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf {
bool stereo=false;
unsigned char* sampleMem;
size_t sampleMemLen;
unsigned char sampleBank;
x1_010_core x1_010;
bool isBanked=false;

View file

@ -1328,7 +1328,6 @@ void DivPlatformYM2203::reset() {
}
lastBusy=60;
sampleBank=0;
delay=0;

View file

@ -55,7 +55,6 @@ class DivPlatformYM2203: public DivPlatformOPN {
bool lastS;
DivPlatformAY8910* ay;
unsigned char sampleBank;
bool extMode, noExtMacros;
unsigned char prescale, nukedMult;

View file

@ -947,31 +947,29 @@ void DivPlatformYM2608::tick(bool sysTick) {
// RSS
for (int i=(9+isCSM); i<(15+isCSM); i++) {
if (chan[i].furnacePCM) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
}
if (chan[i].std.duty.had) {
if (globalRSSVolume!=(chan[i].std.duty.val&0x3f)) {
globalRSSVolume=chan[i].std.duty.val&0x3f;
immWrite(0x11,globalRSSVolume);
hardResetElapsed++;
}
}
if (chan[i].std.panL.had) {
chan[i].pan=chan[i].std.panL.val&3;
}
if (chan[i].std.phaseReset.had) {
if ((chan[i].std.phaseReset.val==1) && chan[i].active) {
chan[i].keyOn=true;
}
}
if (!isMuted[i] && (chan[i].std.vol.had || chan[i].std.panL.had)) {
immWrite(0x18+(i-(9+isCSM)),isMuted[i]?0:((chan[i].pan<<6)|chan[i].outVol));
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
}
if (chan[i].std.duty.had) {
if (globalRSSVolume!=(chan[i].std.duty.val&0x3f)) {
globalRSSVolume=chan[i].std.duty.val&0x3f;
immWrite(0x11,globalRSSVolume);
hardResetElapsed++;
}
}
if (chan[i].std.panL.had) {
chan[i].pan=chan[i].std.panL.val&3;
}
if (chan[i].std.phaseReset.had) {
if ((chan[i].std.phaseReset.val==1) && chan[i].active) {
chan[i].keyOn=true;
}
}
if (!isMuted[i] && (chan[i].std.vol.had || chan[i].std.panL.had)) {
immWrite(0x18+(i-(9+isCSM)),isMuted[i]?0:((chan[i].pan<<6)|chan[i].outVol));
hardResetElapsed++;
}
if (chan[i].keyOff) {
writeRSSOff|=(1<<(i-(9+isCSM)));
chan[i].keyOff=false;
@ -982,57 +980,54 @@ void DivPlatformYM2608::tick(bool sysTick) {
}
}
// ADPCM-B
if (chan[(15+isCSM)].furnacePCM) {
chan[(15+isCSM)].std.next();
chan[(15+isCSM)].std.next();
if (chan[(15+isCSM)].std.vol.had) {
chan[(15+isCSM)].outVol=(chan[(15+isCSM)].vol*MIN(chan[(15+isCSM)].macroVolMul,chan[(15+isCSM)].std.vol.val))/chan[(15+isCSM)].macroVolMul;
immWrite(0x10b,chan[(15+isCSM)].outVol);
hardResetElapsed++;
}
if (chan[(15+isCSM)].std.vol.had) {
chan[(15+isCSM)].outVol=(chan[(15+isCSM)].vol*MIN(chan[(15+isCSM)].macroVolMul,chan[(15+isCSM)].std.vol.val))/chan[(15+isCSM)].macroVolMul;
immWrite(0x10b,chan[(15+isCSM)].outVol);
hardResetElapsed++;
}
if (NEW_ARP_STRAT) {
chan[(15+isCSM)].handleArp();
} else if (chan[(15+isCSM)].std.arp.had) {
if (!chan[(15+isCSM)].inPorta) {
chan[(15+isCSM)].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[(15+isCSM)].note,chan[(15+isCSM)].std.arp.val));
}
chan[(15+isCSM)].freqChanged=true;
if (NEW_ARP_STRAT) {
chan[(15+isCSM)].handleArp();
} else if (chan[(15+isCSM)].std.arp.had) {
if (!chan[(15+isCSM)].inPorta) {
chan[(15+isCSM)].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[(15+isCSM)].note,chan[(15+isCSM)].std.arp.val));
}
chan[(15+isCSM)].freqChanged=true;
}
if (chan[(15+isCSM)].std.pitch.had) {
if (chan[(15+isCSM)].std.pitch.mode) {
chan[(15+isCSM)].pitch2+=chan[(15+isCSM)].std.pitch.val;
CLAMP_VAR(chan[(15+isCSM)].pitch2,-65535,65535);
} else {
chan[(15+isCSM)].pitch2=chan[(15+isCSM)].std.pitch.val;
}
chan[(15+isCSM)].freqChanged=true;
if (chan[(15+isCSM)].std.pitch.had) {
if (chan[(15+isCSM)].std.pitch.mode) {
chan[(15+isCSM)].pitch2+=chan[(15+isCSM)].std.pitch.val;
CLAMP_VAR(chan[(15+isCSM)].pitch2,-65535,65535);
} else {
chan[(15+isCSM)].pitch2=chan[(15+isCSM)].std.pitch.val;
}
chan[(15+isCSM)].freqChanged=true;
}
if (chan[(15+isCSM)].std.panL.had) {
if (chan[(15+isCSM)].pan!=(chan[(15+isCSM)].std.panL.val&3)) {
chan[(15+isCSM)].pan=chan[(15+isCSM)].std.panL.val&3;
if (!isMuted[(15 + isCSM)]) {
immWrite(0x101,(isMuted[(15 + isCSM)]?0:(chan[(15+isCSM)].pan<<6))|memConfig);
hardResetElapsed++;
}
}
}
if (chan[(15+isCSM)].std.phaseReset.had) {
if ((chan[(15+isCSM)].std.phaseReset.val==1) && chan[(15+isCSM)].active) {
chan[(15+isCSM)].keyOn=true;
if (chan[(15+isCSM)].std.panL.had) {
if (chan[(15+isCSM)].pan!=(chan[(15+isCSM)].std.panL.val&3)) {
chan[(15+isCSM)].pan=chan[(15+isCSM)].std.panL.val&3;
if (!isMuted[(15 + isCSM)]) {
immWrite(0x101,(isMuted[(15 + isCSM)]?0:(chan[(15+isCSM)].pan<<6))|memConfig);
hardResetElapsed++;
}
}
}
if (chan[(15+isCSM)].std.phaseReset.had) {
if ((chan[(15+isCSM)].std.phaseReset.val==1) && chan[(15+isCSM)].active) {
chan[(15+isCSM)].keyOn=true;
}
}
if (chan[(15+isCSM)].freqChanged || chan[(15+isCSM)].keyOn || chan[(15+isCSM)].keyOff) {
if (chan[(15+isCSM)].furnacePCM) {
if (chan[(15+isCSM)].sample>=0 && chan[(15+isCSM)].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[(15+isCSM)].sample)->centerRate)/parent->getCenterRate();
chan[(15+isCSM)].freq=parent->calcFreq(chan[(15+isCSM)].baseFreq,chan[(15+isCSM)].pitch,chan[(15+isCSM)].fixedArp?chan[(15+isCSM)].baseNoteOverride:chan[(15+isCSM)].arpOff,chan[(15+isCSM)].fixedArp,false,4,chan[(15+isCSM)].pitch2,(double)chipClock/144,off);
} else {
chan[(15+isCSM)].freq=0;
}
if (chan[(15+isCSM)].sample>=0 && chan[(15+isCSM)].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[(15+isCSM)].sample)->centerRate)/parent->getCenterRate();
chan[(15+isCSM)].freq=parent->calcFreq(chan[(15+isCSM)].baseFreq,chan[(15+isCSM)].pitch,chan[(15+isCSM)].fixedArp?chan[(15+isCSM)].baseNoteOverride:chan[(15+isCSM)].arpOff,chan[(15+isCSM)].fixedArp,false,4,chan[(15+isCSM)].pitch2,(double)chipClock/144,off);
} else {
chan[(15+isCSM)].freq=0;
}
if (chan[adpcmBChanOffs].freq<0) chan[adpcmBChanOffs].freq=0;
if (chan[adpcmBChanOffs].freq>65535) chan[adpcmBChanOffs].freq=65535;
@ -1153,99 +1148,50 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
if (c.chan>(14+isCSM)) { // ADPCM-B
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:255;
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMB) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x10b,chan[c.chan].outVol);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x100,0x01); // reset
immWrite(0x102,(sampleOffB[chan[c.chan].sample]>>5)&0xff);
immWrite(0x103,(sampleOffB[chan[c.chan].sample]>>13)&0xff);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(0x104,(end>>5)&0xff);
immWrite(0x105,(end>>13)&0xff);
immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|memConfig);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x100,0x01); // reset
immWrite(0x102,0);
immWrite(0x103,0);
immWrite(0x104,0);
immWrite(0x105,0);
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
}
chan[c.chan].sample=12*sampleBank+c.value%12;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x100,0x01); // reset
immWrite(0x102,(sampleOffB[chan[c.chan].sample]>>5)&0xff);
immWrite(0x103,(sampleOffB[chan[c.chan].sample]>>13)&0xff);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(0x104,(end>>5)&0xff);
immWrite(0x105,(end>>13)&0xff);
immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|memConfig);
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
immWrite(0x109,freq&0xff);
immWrite(0x10a,(freq>>8)&0xff);
immWrite(0x10b,chan[c.chan].outVol);
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x100,0x01); // reset
immWrite(0x102,0);
immWrite(0x103,0);
immWrite(0x104,0);
immWrite(0x105,0);
break;
immWrite(0x10b,chan[c.chan].outVol);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x100,0x01); // reset
immWrite(0x102,(sampleOffB[chan[c.chan].sample]>>5)&0xff);
immWrite(0x103,(sampleOffB[chan[c.chan].sample]>>13)&0xff);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(0x104,(end>>5)&0xff);
immWrite(0x105,(end>>13)&0xff);
immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|memConfig);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x100,0x01); // reset
immWrite(0x102,0);
immWrite(0x103,0);
immWrite(0x104,0);
immWrite(0x105,0);
break;
}
break;
}
if (c.chan>(8+isCSM)) { // RSS
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:31;
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMA) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x18+(c.chan-(9+isCSM)),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol));
}
} else {
chan[c.chan].macroInit(NULL);
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x18+(c.chan-(9+isCSM)),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol));
}
@ -1366,7 +1312,6 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==(15+isCSM) && !chan[c.chan].furnacePCM) break;
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
@ -1421,18 +1366,10 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
PLEASE_HELP_ME(chan[c.chan],chan[c.chan].state.block);
break;
}
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (c.chan==csmChan) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
}
if (c.chan==(15+isCSM) && !chan[c.chan].furnacePCM) break;
if (c.chan<=psgChanOffs) {
if (chan[c.chan].insChanged) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
@ -1913,7 +1850,6 @@ void DivPlatformYM2608::reset() {
lastBusy=60;
lfoValue=8;
sampleBank=0;
writeRSSOff=0;
writeRSSOn=0;
globalRSSVolume=0x3f;
@ -2116,7 +2052,6 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, const DivCo
adpcmBMem=new unsigned char[getSampleMemCapacity(0)];
adpcmBMemLen=0;
iface.adpcmBMem=adpcmBMem;
iface.sampleBank=0;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<17; i++) {

View file

@ -31,10 +31,9 @@ extern "C" {
class DivYM2608Interface: public DivOPNInterface {
public:
unsigned char* adpcmBMem;
int sampleBank;
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
DivYM2608Interface(): adpcmBMem(NULL), sampleBank(0) {}
DivYM2608Interface(): adpcmBMem(NULL) {}
};
class DivPlatformYM2608: public DivPlatformOPN {
@ -71,7 +70,6 @@ class DivPlatformYM2608: public DivPlatformOPN {
bool* sampleLoaded;
DivPlatformAY8910* ay;
unsigned char sampleBank;
unsigned char writeRSSOff, writeRSSOn;
int globalRSSVolume;

View file

@ -867,31 +867,30 @@ void DivPlatformYM2610::tick(bool sysTick) {
// ADPCM-A
for (int i=adpcmAChanOffs; i<adpcmBChanOffs; i++) {
if (chan[i].furnacePCM) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
}
if (chan[i].std.duty.had) {
if (globalADPCMAVolume!=(chan[i].std.duty.val&0x3f)) {
globalADPCMAVolume=chan[i].std.duty.val&0x3f;
immWrite(0x101,globalADPCMAVolume);
hardResetElapsed++;
}
}
if (chan[i].std.panL.had) {
chan[i].pan=chan[i].std.panL.val&3;
}
if (chan[i].std.phaseReset.had) {
if ((chan[i].std.phaseReset.val==1) && chan[i].active) {
chan[i].keyOn=true;
}
}
if (!isMuted[i] && (chan[i].std.vol.had || chan[i].std.panL.had)) {
immWrite(0x108+(i-adpcmAChanOffs),isMuted[i]?0:((chan[i].pan<<6)|chan[i].outVol));
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
}
if (chan[i].std.duty.had) {
if (globalADPCMAVolume!=(chan[i].std.duty.val&0x3f)) {
globalADPCMAVolume=chan[i].std.duty.val&0x3f;
immWrite(0x101,globalADPCMAVolume);
hardResetElapsed++;
}
}
if (chan[i].std.panL.had) {
chan[i].pan=chan[i].std.panL.val&3;
}
if (chan[i].std.phaseReset.had) {
if ((chan[i].std.phaseReset.val==1) && chan[i].active) {
chan[i].keyOn=true;
}
}
if (!isMuted[i] && (chan[i].std.vol.had || chan[i].std.panL.had)) {
immWrite(0x108+(i-adpcmAChanOffs),isMuted[i]?0:((chan[i].pan<<6)|chan[i].outVol));
hardResetElapsed++;
}
if (chan[i].keyOff) {
writeADPCMAOff|=(1<<(i-adpcmAChanOffs));
chan[i].keyOff=false;
@ -904,63 +903,61 @@ void DivPlatformYM2610::tick(bool sysTick) {
}
}
// ADPCM-B
if (chan[adpcmBChanOffs].furnacePCM) {
chan[adpcmBChanOffs].std.next();
chan[adpcmBChanOffs].std.next();
if (chan[adpcmBChanOffs].std.vol.had) {
chan[adpcmBChanOffs].outVol=(chan[adpcmBChanOffs].vol*MIN(chan[adpcmBChanOffs].macroVolMul,chan[adpcmBChanOffs].std.vol.val))/chan[adpcmBChanOffs].macroVolMul;
immWrite(0x1b,chan[adpcmBChanOffs].outVol);
hardResetElapsed++;
}
if (chan[adpcmBChanOffs].std.vol.had) {
chan[adpcmBChanOffs].outVol=(chan[adpcmBChanOffs].vol*MIN(chan[adpcmBChanOffs].macroVolMul,chan[adpcmBChanOffs].std.vol.val))/chan[adpcmBChanOffs].macroVolMul;
immWrite(0x1b,chan[adpcmBChanOffs].outVol);
hardResetElapsed++;
}
if (NEW_ARP_STRAT) {
chan[adpcmBChanOffs].handleArp();
} else if (chan[adpcmBChanOffs].std.arp.had) {
if (!chan[adpcmBChanOffs].inPorta) {
chan[adpcmBChanOffs].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmBChanOffs].note,chan[adpcmBChanOffs].std.arp.val));
}
chan[adpcmBChanOffs].freqChanged=true;
if (NEW_ARP_STRAT) {
chan[adpcmBChanOffs].handleArp();
} else if (chan[adpcmBChanOffs].std.arp.had) {
if (!chan[adpcmBChanOffs].inPorta) {
chan[adpcmBChanOffs].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmBChanOffs].note,chan[adpcmBChanOffs].std.arp.val));
}
chan[adpcmBChanOffs].freqChanged=true;
}
if (chan[adpcmBChanOffs].std.pitch.had) {
if (chan[adpcmBChanOffs].std.pitch.mode) {
chan[adpcmBChanOffs].pitch2+=chan[adpcmBChanOffs].std.pitch.val;
CLAMP_VAR(chan[adpcmBChanOffs].pitch2,-65535,65535);
} else {
chan[adpcmBChanOffs].pitch2=chan[adpcmBChanOffs].std.pitch.val;
}
chan[adpcmBChanOffs].freqChanged=true;
if (chan[adpcmBChanOffs].std.pitch.had) {
if (chan[adpcmBChanOffs].std.pitch.mode) {
chan[adpcmBChanOffs].pitch2+=chan[adpcmBChanOffs].std.pitch.val;
CLAMP_VAR(chan[adpcmBChanOffs].pitch2,-65535,65535);
} else {
chan[adpcmBChanOffs].pitch2=chan[adpcmBChanOffs].std.pitch.val;
}
chan[adpcmBChanOffs].freqChanged=true;
}
if (chan[adpcmBChanOffs].std.panL.had) {
if (chan[adpcmBChanOffs].pan!=(chan[adpcmBChanOffs].std.panL.val&3)) {
chan[adpcmBChanOffs].pan=chan[adpcmBChanOffs].std.panL.val&3;
if (!isMuted[adpcmBChanOffs]) {
immWrite(0x11,(isMuted[adpcmBChanOffs]?0:(chan[adpcmBChanOffs].pan<<6)));
hardResetElapsed++;
}
}
}
if (chan[adpcmBChanOffs].std.phaseReset.had) {
if ((chan[adpcmBChanOffs].std.phaseReset.val==1) && chan[adpcmBChanOffs].active) {
chan[adpcmBChanOffs].keyOn=true;
if (chan[adpcmBChanOffs].std.panL.had) {
if (chan[adpcmBChanOffs].pan!=(chan[adpcmBChanOffs].std.panL.val&3)) {
chan[adpcmBChanOffs].pan=chan[adpcmBChanOffs].std.panL.val&3;
if (!isMuted[adpcmBChanOffs]) {
immWrite(0x11,(isMuted[adpcmBChanOffs]?0:(chan[adpcmBChanOffs].pan<<6)));
hardResetElapsed++;
}
}
}
if (chan[adpcmBChanOffs].freqChanged || chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) {
if (chan[adpcmBChanOffs].furnacePCM) {
if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[adpcmBChanOffs].sample)->centerRate)/parent->getCenterRate();
chan[adpcmBChanOffs].freq=parent->calcFreq(chan[adpcmBChanOffs].baseFreq,chan[adpcmBChanOffs].pitch,chan[adpcmBChanOffs].fixedArp?chan[adpcmBChanOffs].baseNoteOverride:chan[adpcmBChanOffs].arpOff,chan[adpcmBChanOffs].fixedArp,false,4,chan[adpcmBChanOffs].pitch2,(double)chipClock/144,off);
} else {
chan[adpcmBChanOffs].freq=0;
}
if (chan[adpcmBChanOffs].freq<0) chan[adpcmBChanOffs].freq=0;
if (chan[adpcmBChanOffs].freq>65535) chan[adpcmBChanOffs].freq=65535;
immWrite(0x19,chan[adpcmBChanOffs].freq&0xff);
immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff);
hardResetElapsed+=2;
if (chan[adpcmBChanOffs].std.phaseReset.had) {
if ((chan[adpcmBChanOffs].std.phaseReset.val==1) && chan[adpcmBChanOffs].active) {
chan[adpcmBChanOffs].keyOn=true;
}
}
if (chan[adpcmBChanOffs].freqChanged || chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) {
if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[adpcmBChanOffs].sample)->centerRate)/parent->getCenterRate();
chan[adpcmBChanOffs].freq=parent->calcFreq(chan[adpcmBChanOffs].baseFreq,chan[adpcmBChanOffs].pitch,chan[adpcmBChanOffs].fixedArp?chan[adpcmBChanOffs].baseNoteOverride:chan[adpcmBChanOffs].arpOff,chan[adpcmBChanOffs].fixedArp,false,4,chan[adpcmBChanOffs].pitch2,(double)chipClock/144,off);
} else {
chan[adpcmBChanOffs].freq=0;
}
if (chan[adpcmBChanOffs].freq<0) chan[adpcmBChanOffs].freq=0;
if (chan[adpcmBChanOffs].freq>65535) chan[adpcmBChanOffs].freq=65535;
immWrite(0x19,chan[adpcmBChanOffs].freq&0xff);
immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff);
hardResetElapsed+=2;
if (chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) {
immWrite(0x10,0x01); // reset
hardResetElapsed++;
@ -1071,144 +1068,74 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
if (c.chan>=adpcmBChanOffs) { // ADPCM-B
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:255;
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMB) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
immWrite(0x13,sampleOffB[chan[c.chan].sample]>>16);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x10,0x01); // reset
immWrite(0x12,0);
immWrite(0x13,0);
immWrite(0x14,0);
immWrite(0x15,0);
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
immWrite(0x1b,chan[c.chan].outVol);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
immWrite(0x13,sampleOffB[chan[c.chan].sample]>>16);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].sample=12*sampleBank+c.value%12;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
immWrite(0x13,sampleOffB[chan[c.chan].sample]>>16);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
immWrite(0x19,freq&0xff);
immWrite(0x1a,(freq>>8)&0xff);
immWrite(0x1b,chan[c.chan].outVol);
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x10,0x01); // reset
immWrite(0x12,0);
immWrite(0x13,0);
immWrite(0x14,0);
immWrite(0x15,0);
break;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x10,0x01); // reset
immWrite(0x12,0);
immWrite(0x13,0);
immWrite(0x14,0);
immWrite(0x15,0);
break;
}
break;
}
if (c.chan>=adpcmAChanOffs) { // ADPCM-A
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:31;
if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMA)) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x110+c.chan-adpcmAChanOffs,(sampleOffA[chan[c.chan].sample]>>8)&0xff);
immWrite(0x118+c.chan-adpcmAChanOffs,sampleOffA[chan[c.chan].sample]>>16);
int end=sampleOffA[chan[c.chan].sample]+s->lengthA-1;
immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff);
immWrite(0x128+c.chan-adpcmAChanOffs,end>>16);
immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol));
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs));
immWrite(0x110+c.chan-adpcmAChanOffs,0);
immWrite(0x118+c.chan-adpcmAChanOffs,0);
immWrite(0x120+c.chan-adpcmAChanOffs,0);
immWrite(0x128+c.chan-adpcmAChanOffs,0);
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
}
chan[c.chan].sample=12*sampleBank+c.value%12;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
immWrite(0x110+c.chan-adpcmAChanOffs,(sampleOffA[chan[c.chan].sample]>>8)&0xff);
immWrite(0x118+c.chan-adpcmAChanOffs,sampleOffA[chan[c.chan].sample]>>16);
int end=sampleOffA[chan[c.chan].sample]+s->lengthA-1;
immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff);
immWrite(0x128+c.chan-adpcmAChanOffs,end>>16);
immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol));
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs));
immWrite(0x110+c.chan-adpcmAChanOffs,0);
immWrite(0x118+c.chan-adpcmAChanOffs,0);
immWrite(0x120+c.chan-adpcmAChanOffs,0);
immWrite(0x128+c.chan-adpcmAChanOffs,0);
break;
}
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x110+c.chan-adpcmAChanOffs,(sampleOffA[chan[c.chan].sample]>>8)&0xff);
immWrite(0x118+c.chan-adpcmAChanOffs,sampleOffA[chan[c.chan].sample]>>16);
int end=sampleOffA[chan[c.chan].sample]+s->lengthA-1;
immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff);
immWrite(0x128+c.chan-adpcmAChanOffs,end>>16);
immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol));
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs));
immWrite(0x110+c.chan-adpcmAChanOffs,0);
immWrite(0x118+c.chan-adpcmAChanOffs,0);
immWrite(0x120+c.chan-adpcmAChanOffs,0);
immWrite(0x128+c.chan-adpcmAChanOffs,0);
break;
}
break;
}
@ -1325,7 +1252,6 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==adpcmBChanOffs && !chan[c.chan].furnacePCM) break;
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
@ -1380,15 +1306,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
PLEASE_HELP_ME(chan[c.chan],chan[c.chan].state.block);
break;
}
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (c.chan==adpcmBChanOffs && !chan[c.chan].furnacePCM) break;
if (c.chan==csmChan) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
}
@ -1820,7 +1738,6 @@ void DivPlatformYM2610::reset() {
lastBusy=60;
lfoValue=8;
sampleBank=0;
DivPlatformYM2610Base::reset();
delay=0;

View file

@ -936,31 +936,30 @@ void DivPlatformYM2610B::tick(bool sysTick) {
// ADPCM-A
for (int i=adpcmAChanOffs; i<adpcmBChanOffs; i++) {
if (chan[i].furnacePCM) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
}
if (chan[i].std.duty.had) {
if (globalADPCMAVolume!=(chan[i].std.duty.val&0x3f)) {
globalADPCMAVolume=chan[i].std.duty.val&0x3f;
immWrite(0x101,globalADPCMAVolume);
hardResetElapsed++;
}
}
if (chan[i].std.panL.had) {
chan[i].pan=chan[i].std.panL.val&3;
}
if (chan[i].std.phaseReset.had) {
if ((chan[i].std.phaseReset.val==1) && chan[i].active) {
chan[i].keyOn=true;
}
}
if (!isMuted[i] && (chan[i].std.vol.had || chan[i].std.panL.had)) {
immWrite(0x108+(i-adpcmAChanOffs),isMuted[i]?0:((chan[i].pan<<6)|chan[i].outVol));
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
}
if (chan[i].std.duty.had) {
if (globalADPCMAVolume!=(chan[i].std.duty.val&0x3f)) {
globalADPCMAVolume=chan[i].std.duty.val&0x3f;
immWrite(0x101,globalADPCMAVolume);
hardResetElapsed++;
}
}
if (chan[i].std.panL.had) {
chan[i].pan=chan[i].std.panL.val&3;
}
if (chan[i].std.phaseReset.had) {
if ((chan[i].std.phaseReset.val==1) && chan[i].active) {
chan[i].keyOn=true;
}
}
if (!isMuted[i] && (chan[i].std.vol.had || chan[i].std.panL.had)) {
immWrite(0x108+(i-adpcmAChanOffs),isMuted[i]?0:((chan[i].pan<<6)|chan[i].outVol));
hardResetElapsed++;
}
if (chan[i].keyOff) {
writeADPCMAOff|=(1<<(i-adpcmAChanOffs));
chan[i].keyOff=false;
@ -973,63 +972,61 @@ void DivPlatformYM2610B::tick(bool sysTick) {
}
}
// ADPCM-B
if (chan[adpcmBChanOffs].furnacePCM) {
chan[adpcmBChanOffs].std.next();
chan[adpcmBChanOffs].std.next();
if (chan[adpcmBChanOffs].std.vol.had) {
chan[adpcmBChanOffs].outVol=(chan[adpcmBChanOffs].vol*MIN(chan[adpcmBChanOffs].macroVolMul,chan[adpcmBChanOffs].std.vol.val))/chan[adpcmBChanOffs].macroVolMul;
immWrite(0x1b,chan[adpcmBChanOffs].outVol);
hardResetElapsed++;
}
if (chan[adpcmBChanOffs].std.vol.had) {
chan[adpcmBChanOffs].outVol=(chan[adpcmBChanOffs].vol*MIN(chan[adpcmBChanOffs].macroVolMul,chan[adpcmBChanOffs].std.vol.val))/chan[adpcmBChanOffs].macroVolMul;
immWrite(0x1b,chan[adpcmBChanOffs].outVol);
hardResetElapsed++;
}
if (NEW_ARP_STRAT) {
chan[adpcmBChanOffs].handleArp();
} else if (chan[adpcmBChanOffs].std.arp.had) {
if (!chan[adpcmBChanOffs].inPorta) {
chan[adpcmBChanOffs].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmBChanOffs].note,chan[adpcmBChanOffs].std.arp.val));
}
chan[adpcmBChanOffs].freqChanged=true;
if (NEW_ARP_STRAT) {
chan[adpcmBChanOffs].handleArp();
} else if (chan[adpcmBChanOffs].std.arp.had) {
if (!chan[adpcmBChanOffs].inPorta) {
chan[adpcmBChanOffs].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmBChanOffs].note,chan[adpcmBChanOffs].std.arp.val));
}
chan[adpcmBChanOffs].freqChanged=true;
}
if (chan[adpcmBChanOffs].std.pitch.had) {
if (chan[adpcmBChanOffs].std.pitch.mode) {
chan[adpcmBChanOffs].pitch2+=chan[adpcmBChanOffs].std.pitch.val;
CLAMP_VAR(chan[adpcmBChanOffs].pitch2,-65535,65535);
} else {
chan[adpcmBChanOffs].pitch2=chan[adpcmBChanOffs].std.pitch.val;
}
chan[adpcmBChanOffs].freqChanged=true;
if (chan[adpcmBChanOffs].std.pitch.had) {
if (chan[adpcmBChanOffs].std.pitch.mode) {
chan[adpcmBChanOffs].pitch2+=chan[adpcmBChanOffs].std.pitch.val;
CLAMP_VAR(chan[adpcmBChanOffs].pitch2,-65535,65535);
} else {
chan[adpcmBChanOffs].pitch2=chan[adpcmBChanOffs].std.pitch.val;
}
chan[adpcmBChanOffs].freqChanged=true;
}
if (chan[adpcmBChanOffs].std.panL.had) {
if (chan[adpcmBChanOffs].pan!=(chan[adpcmBChanOffs].std.panL.val&3)) {
chan[adpcmBChanOffs].pan=chan[adpcmBChanOffs].std.panL.val&3;
if (!isMuted[adpcmBChanOffs]) {
immWrite(0x11,(isMuted[adpcmBChanOffs]?0:(chan[adpcmBChanOffs].pan<<6)));
hardResetElapsed++;
}
}
}
if (chan[adpcmBChanOffs].std.phaseReset.had) {
if ((chan[adpcmBChanOffs].std.phaseReset.val==1) && chan[adpcmBChanOffs].active) {
chan[adpcmBChanOffs].keyOn=true;
if (chan[adpcmBChanOffs].std.panL.had) {
if (chan[adpcmBChanOffs].pan!=(chan[adpcmBChanOffs].std.panL.val&3)) {
chan[adpcmBChanOffs].pan=chan[adpcmBChanOffs].std.panL.val&3;
if (!isMuted[adpcmBChanOffs]) {
immWrite(0x11,(isMuted[adpcmBChanOffs]?0:(chan[adpcmBChanOffs].pan<<6)));
hardResetElapsed++;
}
}
}
if (chan[adpcmBChanOffs].freqChanged || chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) {
if (chan[adpcmBChanOffs].furnacePCM) {
if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[adpcmBChanOffs].sample)->centerRate)/parent->getCenterRate();
chan[adpcmBChanOffs].freq=parent->calcFreq(chan[adpcmBChanOffs].baseFreq,chan[adpcmBChanOffs].pitch,chan[adpcmBChanOffs].fixedArp?chan[adpcmBChanOffs].baseNoteOverride:chan[adpcmBChanOffs].arpOff,chan[adpcmBChanOffs].fixedArp,false,4,chan[adpcmBChanOffs].pitch2,(double)chipClock/144,off);
} else {
chan[adpcmBChanOffs].freq=0;
}
if (chan[adpcmBChanOffs].freq<0) chan[adpcmBChanOffs].freq=0;
if (chan[adpcmBChanOffs].freq>65535) chan[adpcmBChanOffs].freq=65535;
immWrite(0x19,chan[adpcmBChanOffs].freq&0xff);
immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff);
hardResetElapsed+=2;
if (chan[adpcmBChanOffs].std.phaseReset.had) {
if ((chan[adpcmBChanOffs].std.phaseReset.val==1) && chan[adpcmBChanOffs].active) {
chan[adpcmBChanOffs].keyOn=true;
}
}
if (chan[adpcmBChanOffs].freqChanged || chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) {
if (chan[adpcmBChanOffs].sample>=0 && chan[adpcmBChanOffs].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[adpcmBChanOffs].sample)->centerRate)/parent->getCenterRate();
chan[adpcmBChanOffs].freq=parent->calcFreq(chan[adpcmBChanOffs].baseFreq,chan[adpcmBChanOffs].pitch,chan[adpcmBChanOffs].fixedArp?chan[adpcmBChanOffs].baseNoteOverride:chan[adpcmBChanOffs].arpOff,chan[adpcmBChanOffs].fixedArp,false,4,chan[adpcmBChanOffs].pitch2,(double)chipClock/144,off);
} else {
chan[adpcmBChanOffs].freq=0;
}
if (chan[adpcmBChanOffs].freq<0) chan[adpcmBChanOffs].freq=0;
if (chan[adpcmBChanOffs].freq>65535) chan[adpcmBChanOffs].freq=65535;
immWrite(0x19,chan[adpcmBChanOffs].freq&0xff);
immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff);
hardResetElapsed+=2;
if (chan[adpcmBChanOffs].keyOn || chan[adpcmBChanOffs].keyOff) {
immWrite(0x10,0x01); // reset
hardResetElapsed++;
@ -1140,144 +1137,74 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
if (c.chan>=adpcmBChanOffs) { // ADPCM-B
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:255;
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMB) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
immWrite(0x13,sampleOffB[chan[c.chan].sample]>>16);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x10,0x01); // reset
immWrite(0x12,0);
immWrite(0x13,0);
immWrite(0x14,0);
immWrite(0x15,0);
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
immWrite(0x1b,chan[c.chan].outVol);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
immWrite(0x13,sampleOffB[chan[c.chan].sample]>>16);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].sample=12*sampleBank+c.value%12;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
immWrite(0x13,sampleOffB[chan[c.chan].sample]>>16);
int end=sampleOffB[chan[c.chan].sample]+s->lengthB-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
immWrite(0x19,freq&0xff);
immWrite(0x1a,(freq>>8)&0xff);
immWrite(0x1b,chan[c.chan].outVol);
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x10,0x01); // reset
immWrite(0x12,0);
immWrite(0x13,0);
immWrite(0x14,0);
immWrite(0x15,0);
break;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(0x10,0x01); // reset
immWrite(0x12,0);
immWrite(0x13,0);
immWrite(0x14,0);
immWrite(0x15,0);
break;
}
break;
}
if (c.chan>=adpcmAChanOffs) { // ADPCM-A
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:31;
if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMA)) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x110+c.chan-adpcmAChanOffs,(sampleOffA[chan[c.chan].sample]>>8)&0xff);
immWrite(0x118+c.chan-adpcmAChanOffs,sampleOffA[chan[c.chan].sample]>>16);
int end=sampleOffA[chan[c.chan].sample]+s->lengthA-1;
immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff);
immWrite(0x128+c.chan-adpcmAChanOffs,end>>16);
immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol));
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs));
immWrite(0x110+c.chan-adpcmAChanOffs,0);
immWrite(0x118+c.chan-adpcmAChanOffs,0);
immWrite(0x120+c.chan-adpcmAChanOffs,0);
immWrite(0x128+c.chan-adpcmAChanOffs,0);
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
break;
}
chan[c.chan].sample=12*sampleBank+c.value%12;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
immWrite(0x110+c.chan-adpcmAChanOffs,(sampleOffA[chan[c.chan].sample]>>8)&0xff);
immWrite(0x118+c.chan-adpcmAChanOffs,sampleOffA[chan[c.chan].sample]>>16);
int end=sampleOffA[chan[c.chan].sample]+s->lengthA-1;
immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff);
immWrite(0x128+c.chan-adpcmAChanOffs,end>>16);
immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol));
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs));
immWrite(0x110+c.chan-adpcmAChanOffs,0);
immWrite(0x118+c.chan-adpcmAChanOffs,0);
immWrite(0x120+c.chan-adpcmAChanOffs,0);
immWrite(0x128+c.chan-adpcmAChanOffs,0);
break;
}
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x110+c.chan-adpcmAChanOffs,(sampleOffA[chan[c.chan].sample]>>8)&0xff);
immWrite(0x118+c.chan-adpcmAChanOffs,sampleOffA[chan[c.chan].sample]>>16);
int end=sampleOffA[chan[c.chan].sample]+s->lengthA-1;
immWrite(0x120+c.chan-adpcmAChanOffs,(end>>8)&0xff);
immWrite(0x128+c.chan-adpcmAChanOffs,end>>16);
immWrite(0x108+c.chan-adpcmAChanOffs,isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].outVol));
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
writeADPCMAOff|=(1<<(c.chan-adpcmAChanOffs));
immWrite(0x110+c.chan-adpcmAChanOffs,0);
immWrite(0x118+c.chan-adpcmAChanOffs,0);
immWrite(0x120+c.chan-adpcmAChanOffs,0);
immWrite(0x128+c.chan-adpcmAChanOffs,0);
break;
}
break;
}
@ -1394,7 +1321,6 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==adpcmBChanOffs && !chan[c.chan].furnacePCM) break;
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
@ -1449,15 +1375,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
PLEASE_HELP_ME(chan[c.chan],chan[c.chan].state.block);
break;
}
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (c.chan==adpcmBChanOffs && !chan[c.chan].furnacePCM) break;
if (c.chan==csmChan) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
}
@ -1889,7 +1807,6 @@ void DivPlatformYM2610B::reset() {
lastBusy=60;
lfoValue=8;
sampleBank=0;
DivPlatformYM2610Base::reset();
delay=0;

View file

@ -38,13 +38,11 @@ class DivYM2610Interface: public DivOPNInterface {
public:
unsigned char* adpcmAMem;
unsigned char* adpcmBMem;
int sampleBank;
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
DivYM2610Interface():
adpcmAMem(NULL),
adpcmBMem(NULL),
sampleBank(0) {}
adpcmBMem(NULL) {}
};
class DivPlatformYM2610Base: public DivPlatformOPN {
@ -78,8 +76,6 @@ class DivPlatformYM2610Base: public DivPlatformOPN {
unsigned int* sampleOffA;
unsigned int* sampleOffB;
unsigned char sampleBank;
bool extMode, noExtMacros;
bool* sampleLoaded[2];
@ -343,7 +339,6 @@ class DivPlatformYM2610Base: public DivPlatformOPN {
adpcmBMemLen=0;
iface.adpcmAMem=adpcmAMem;
iface.adpcmBMem=adpcmBMem;
iface.sampleBank=0;
fm=new ymfm::ym2610b(iface);
fm->set_fidelity(ymfm::OPN_FIDELITY_MED);
setFlags(flags);

View file

@ -51,7 +51,7 @@ void DivSample::putSampleData(SafeWriter* w) {
w->writeString(name,false);
w->writeI(samples);
w->writeI(rate);
w->writeI(centerRate);
w->writeI(centerRate);
w->writeC(depth);
w->writeC(loopMode);
@ -116,7 +116,9 @@ DivDataErrors DivSample::readSampleData(SafeReader& reader, short version) {
if (!isNewSample) {
loopEnd=samples;
}
rate=reader.readI();
// just in case it's not new sample, it's a very old version and we gotta read a rate.
centerRate=reader.readI();
legacyRate=centerRate;
if (isNewSample) {
centerRate=reader.readI();
@ -916,7 +918,6 @@ void DivSample::convert(DivSampleDepth newDepth, unsigned int formatMask) {
if (loopStart>=0) loopStart=(double)loopStart*(tRate/sRate); \
if (loopEnd>=0) loopEnd=(double)loopEnd*(tRate/sRate); \
centerRate=(int)((double)centerRate*(tRate/sRate)); \
rate=(int)((double)rate*(tRate/sRate)); \
samples=finalCount; \
if (depth==DIV_SAMPLE_DEPTH_16BIT) { \
delete[] oldData16; \
@ -1665,9 +1666,9 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
duplicate=new unsigned char[getCurBufLen()];
memcpy(duplicate,getCurBuf(),getCurBufLen());
}
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,brrNoFilter,dither,loopMode);
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,centerRate,loopStart,loopEnd,loop,brrEmphasis,brrNoFilter,dither,loopMode);
} else {
h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,brrNoFilter,dither,loopMode);
h=new DivSampleHistory(depth,centerRate,loopStart,loopEnd,loop,brrEmphasis,brrNoFilter,dither,loopMode);
}
if (!doNotPush) {
while (!redoHist.empty()) {
@ -1695,7 +1696,6 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
memcpy(buf,h->data,h->length); \
} \
} \
rate=h->rate; \
centerRate=h->centerRate; \
loopStart=h->loopStart; \
loopEnd=h->loopEnd; \

View file

@ -66,16 +66,15 @@ struct DivSampleHistory {
unsigned char* data;
unsigned int length, samples;
DivSampleDepth depth;
int rate, centerRate, loopStart, loopEnd;
int centerRate, loopStart, loopEnd;
bool loop, brrEmphasis, brrNoFilter, dither;
DivSampleLoopMode loopMode;
bool hasSample;
DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, bool bf, bool di, DivSampleLoopMode lm):
DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int cr, int ls, int le, bool lp, bool be, bool bf, bool di, DivSampleLoopMode lm):
data((unsigned char*)d),
length(l),
samples(s),
depth(de),
rate(r),
centerRate(cr),
loopStart(ls),
loopEnd(le),
@ -85,12 +84,11 @@ struct DivSampleHistory {
dither(di),
loopMode(lm),
hasSample(true) {}
DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, bool bf, bool di, DivSampleLoopMode lm):
DivSampleHistory(DivSampleDepth de, int cr, int ls, int le, bool lp, bool be, bool bf, bool di, DivSampleLoopMode lm):
data(NULL),
length(0),
samples(0),
depth(de),
rate(r),
centerRate(cr),
loopStart(ls),
loopEnd(le),
@ -105,7 +103,8 @@ struct DivSampleHistory {
struct DivSample {
String name;
int rate, centerRate, loopStart, loopEnd;
int centerRate, loopStart, loopEnd;
int legacyRate;
// valid values are:
// - 0: ZX Spectrum overlay drum (1-bit)
// - 1: 1-bit NES DPCM (1-bit)
@ -337,10 +336,10 @@ struct DivSample {
int redo();
DivSample():
name(""),
rate(32000),
centerRate(8363),
loopStart(-1),
loopEnd(-1),
legacyRate(32000),
depth(DIV_SAMPLE_DEPTH_16BIT),
loop(false),
brrEmphasis(true),

View file

@ -19,6 +19,7 @@
#include "song.h"
#include "../ta-log.h"
#include <inttypes.h>
#include <chrono>
TimeMicros DivSongTimestamps::getTimes(int order, int row) {
@ -347,22 +348,40 @@ void DivSubSong::calcTimestamps(int chans, std::vector<DivGroovePattern>& groove
// MAKE IT WORK
while (!endOfSong) {
// heuristic
int advance=(curVirtualTempoD*ticks)/curVirtualTempoN;
for (int i=0; i<chans; i++) {
if (rowDelay[i]>0) {
if (rowDelay[i]<advance) advance=rowDelay[i];
}
}
if (advance<1) advance=1;
//logV("tick %" PRIu64 " advance: %d",ts.totalTicks,advance);
// cycle channels to find a tick rate/tempo change effect after delay
// (unfortunately Cxxx and F0xx are not pre-effects and obey EDxx)
for (int i=0; i<chans; i++) {
if (rowDelay[i]>0) {
if (--rowDelay[i]==0) {
rowDelay[i]-=advance;
if (rowDelay[i]==0) {
tinyProcessRow(i,true);
}
}
}
// run virtual tempo
tempoAccum+=curVirtualTempoN;
tempoAccum+=curVirtualTempoN*advance;
while (tempoAccum>=curVirtualTempoD) {
tempoAccum-=curVirtualTempoD;
int ticksToRun=tempoAccum/curVirtualTempoD;
tempoAccum%=curVirtualTempoD;
// tick counter
if (--ticks<=0) {
ticks-=ticksToRun;
if (ticks<0) {
// if ticks is negative, we must call ticks back
tempoAccum+=-ticks*curVirtualTempoD;
}
if (ticks<=0) {
if (shallStopSched) {
shallStop=true;
break;
@ -400,8 +419,8 @@ void DivSubSong::calcTimestamps(int chans, std::vector<DivGroovePattern>& groove
if (!endOfSong) {
// update playback time
double dt=divider;//*((double)virtualTempoN/(double)MAX(1,virtualTempoD));
ts.totalTicks++;
double dt=divider/(double)advance;//*((double)virtualTempoN/(double)MAX(1,virtualTempoD));
ts.totalTicks+=advance;
ts.totalTime.micros+=1000000/dt;
totalMicrosOff+=fmod(1000000.0,dt);
@ -410,9 +429,10 @@ void DivSubSong::calcTimestamps(int chans, std::vector<DivGroovePattern>& groove
ts.totalTime.micros++;
}
if (ts.totalTime.micros>=1000000) {
ts.totalTime.micros-=1000000;
// who's gonna play a song for 68 years?
if (ts.totalTime.seconds<0x7fffffff) ts.totalTime.seconds++;
ts.totalTime.seconds+=ts.totalTime.micros/1000000;
if (ts.totalTime.seconds<0) ts.totalTime.seconds=INT_MAX;
ts.totalTime.micros%=1000000;
}
}
if (ts.maxRow[curOrder]<curRow) ts.maxRow[curOrder]=curRow;
@ -425,6 +445,7 @@ void DivSubSong::calcTimestamps(int chans, std::vector<DivGroovePattern>& groove
std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now();
logV("calcTimestamps() took %dµs",std::chrono::duration_cast<std::chrono::microseconds>(timeEnd-timeStart).count());
logV("song length: %s; %" PRIu64 " ticks",ts.totalTime.toString(6,TA_TIME_FORMAT_AUTO),ts.totalTicks);
}
void DivSubSong::clearData() {
@ -728,4 +749,4 @@ void DivSong::unload() {
delete i;
}
subsong.clear();
}
}

View file

@ -171,7 +171,7 @@ struct DivGroovePattern {
struct DivSongTimestamps {
// song duration (in seconds and microseconds)
TimeMicros totalTime;
int totalTicks;
uint64_t totalTicks;
int totalRows;
// loop region (order/row positions)

View file

@ -465,7 +465,6 @@ void DivEngine::registerSystems() {
EffectHandlerMap fmOPN2EffectHandlerMap(fmEffectHandlerMap);
fmOPN2EffectHandlerMap.insert({
{0x17, {DIV_CMD_SAMPLE_MODE, _("17xx: Toggle PCM mode (LEGACY)")}},
{0xdf, {DIV_CMD_SAMPLE_DIR, _("DFxx: Set sample playback direction (0: normal; 1: reverse)")}},
});
@ -884,7 +883,6 @@ void DivEngine::registerSystems() {
{0x11, {DIV_CMD_STD_NOISE_MODE, _("11xx: Toggle noise mode")}},
{0x12, {DIV_CMD_PCE_LFO_MODE, _("12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)")}},
{0x13, {DIV_CMD_PCE_LFO_SPEED, _("13xx: Set LFO speed")}},
{0x17, {DIV_CMD_SAMPLE_MODE, _("17xx: Toggle PCM mode (LEGACY)")}}
}
);
@ -1149,7 +1147,6 @@ void DivEngine::registerSystems() {
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_NULL},
{
{0x12, {DIV_CMD_STD_NOISE_MODE, _("12xx: Set duty cycle (pulse: 0 to 7)")}},
{0x17, {DIV_CMD_SAMPLE_MODE, _("17xx: Toggle PCM mode (LEGACY)")}},
}
);
@ -1403,7 +1400,6 @@ void DivEngine::registerSystems() {
{0x11, {DIV_CMD_STD_NOISE_MODE, _("11xx: Setup noise mode (0: disabled; 1-8: enabled/tap)")}},
{0x12, {DIV_CMD_WS_SWEEP_TIME, _("12xx: Setup sweep period (0: disabled; 1-20: enabled/period)")}},
{0x13, {DIV_CMD_WS_SWEEP_AMOUNT, _("13xx: Set sweep amount")}},
{0x17, {DIV_CMD_SAMPLE_MODE, _("17xx: Toggle PCM mode (LEGACY)")}},
},
{
{0x20, {DIV_CMD_WS_GLOBAL_SPEAKER_VOLUME, _("20xx: Set internal speaker loudness (0-1: 100%, 2-3: 200%, 4-7: 400%, 8: 800%)")}},
@ -1728,7 +1724,6 @@ void DivEngine::registerSystems() {
{0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}},
{0x11, {DIV_CMD_X1_010_ENVELOPE_SHAPE, _("11xx: Set envelope shape")}},
{0x12, {DIV_CMD_X1_010_SAMPLE_BANK_SLOT, _("12xx: Set sample bank slot (0 to 7)")}},
{0x17, {DIV_CMD_SAMPLE_MODE, _("17xx: Toggle PCM mode (LEGACY)")}},
},
{
{0x20, {DIV_CMD_SAMPLE_FREQ, _("20xx: Set PCM frequency (1 to FF)")}},

View file

@ -103,7 +103,6 @@
#define OPNB_CHIP_DEBUG \
FM_OPN_CHIP_DEBUG; \
ImGui::Text("- sampleBank: %d",ch->sampleBank); \
ImGui::Text("- writeADPCMAOff: %d",ch->writeADPCMAOff); \
ImGui::Text("- writeADPCMAOn: %d",ch->writeADPCMAOn); \
ImGui::Text("- globalADPCMAVolume: %d",ch->globalADPCMAVolume); \
@ -164,9 +163,7 @@
ImGui::Text(" - output: %d",ch->dacOutput); \
ImGui::Text("- pan: %x",ch->pan); \
ImGui::Text("- opMask: %x",ch->opMask); \
ImGui::Text("- sampleBank: %d",ch->sampleBank); \
COMMON_CHAN_DEBUG_BOOL; \
ImGui::TextColored(ch->furnaceDac?colorOn:colorOff,">> FurnaceDAC"); \
ImGui::TextColored(ch->hardReset?colorOn:colorOff,">> hardReset"); \
ImGui::TextColored(ch->opMaskChanged?colorOn:colorOff,">> opMaskChanged"); \
ImGui::TextColored(ch->dacMode?colorOn:colorOff,">> DACMode"); \
@ -203,7 +200,6 @@
COMMON_CHAN_DEBUG_BOOL; \
ImGui::TextColored(ch->hardReset?colorOn:colorOff,">> hardReset"); \
ImGui::TextColored(ch->opMaskChanged?colorOn:colorOff,">> opMaskChanged"); \
ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM");
#define OPN_OPCHAN_DEBUG \
DivPlatformOPN::OPNOpChannel* ch=(DivPlatformOPN::OPNOpChannel*)data; \
@ -229,7 +225,6 @@
COMMON_CHAN_DEBUG_BOOL; \
ImGui::TextColored(ch->hardReset?colorOn:colorOff,">> hardReset"); \
ImGui::TextColored(ch->opMaskChanged?colorOn:colorOff,">> opMaskChanged"); \
ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM");
#define OPNB_OPCHAN_DEBUG \
ImGui::Text("- freqHL: %.2x%.2x",ch->freqH,ch->freqL); \
@ -265,7 +260,6 @@ void putDispatchChip(void* data, int type) {
DivPlatformYM2203* ch=(DivPlatformYM2203*)data;
ImGui::Text("> YM2203");
FM_OPN_CHIP_DEBUG;
ImGui::Text("- sampleBank: %d",ch->sampleBank);
ImGui::Text("- prescale: %d",ch->prescale);
FM_OPN_CHIP_DEBUG_BOOL;
ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode");
@ -276,7 +270,6 @@ void putDispatchChip(void* data, int type) {
DivPlatformYM2608* ch=(DivPlatformYM2608*)data;
ImGui::Text("> YM2608");
FM_OPN_CHIP_DEBUG;
ImGui::Text("- sampleBank: %d",ch->sampleBank);
ImGui::Text("- writeRSSOff: %d",ch->writeRSSOff);
ImGui::Text("- writeRSSOn: %d",ch->writeRSSOn);
ImGui::Text("- globalRSSVolume: %d",ch->globalRSSVolume);
@ -318,7 +311,6 @@ void putDispatchChip(void* data, int type) {
COMMON_CHIP_DEBUG;
ImGui::Text("- lastPan: %d",ch->lastPan);
ImGui::Text("- curChan: %d",ch->curChan);
ImGui::Text("- sampleBank: %d",ch->sampleBank);
ImGui::Text("- lfoMode: %d",ch->lfoMode);
ImGui::Text("- lfoSpeed: %d",ch->lfoSpeed);
COMMON_CHIP_DEBUG_BOOL;
@ -336,7 +328,6 @@ void putDispatchChip(void* data, int type) {
ImGui::Text(" - AntiClick: %d",ch->dacAntiClick);
ImGui::Text(" - Sample: %d",ch->dacSample);
ImGui::Text("- dpcmBank: %d",ch->dpcmBank);
ImGui::Text("- sampleBank: %d",ch->sampleBank);
ImGui::Text("- writeOscBuf: %d",ch->writeOscBuf);
ImGui::Text("- apuType: %d",ch->apuType);
COMMON_CHIP_DEBUG_BOOL;
@ -380,7 +371,6 @@ void putDispatchChip(void* data, int type) {
ImGui::Text("- pcmL: %d",ch->pcmL);
ImGui::Text("- pcmR: %d",ch->pcmR);
ImGui::Text("- pcmCycles: %d",ch->pcmCycles);
ImGui::Text("- sampleBank: %d",ch->sampleBank);
COMMON_CHIP_DEBUG_BOOL;
break;
}
@ -388,7 +378,6 @@ void putDispatchChip(void* data, int type) {
DivPlatformAY8910* ch=(DivPlatformAY8910*)data;
ImGui::Text("> AY-3-8910");
COMMON_CHIP_DEBUG;
ImGui::Text("- sampleBank: %d",ch->sampleBank);
ImGui::Text("- stereoSep: %d",ch->stereoSep);
ImGui::Text("- delay: %d",ch->delay);
ImGui::Text("- extClock: %d",ch->extClock);
@ -417,7 +406,6 @@ void putDispatchChip(void* data, int type) {
ImGui::Text("* noise:");
ImGui::Text(" - and: %d",ch->ayNoiseAnd);
ImGui::Text(" - or: %d",ch->ayNoiseOr);
ImGui::Text("- sampleBank: %d",ch->sampleBank);
ImGui::Text("- stereoSep: %d",ch->stereoSep);
ImGui::Text("- delay: %d",ch->delay);
ImGui::Text("- portAVal: %d",ch->portAVal);
@ -445,7 +433,6 @@ void putDispatchChip(void* data, int type) {
DivPlatformX1_010* ch=(DivPlatformX1_010*)data;
ImGui::Text("> X1-010");
COMMON_CHIP_DEBUG;
ImGui::Text("- sampleBank: %d",ch->sampleBank);
ImGui::Text("- bankSlot: [%d,%d,%d,%d,%d,%d,%d,%d]",ch->bankSlot[0],ch->bankSlot[1],ch->bankSlot[2],ch->bankSlot[3],ch->bankSlot[4],ch->bankSlot[5],ch->bankSlot[6],ch->bankSlot[7]);
COMMON_CHIP_DEBUG_BOOL;
ImGui::TextColored(ch->stereo?colorOn:colorOff,">> Stereo");
@ -468,7 +455,6 @@ void putDispatchChip(void* data, int type) {
DivPlatformVRC6* ch=(DivPlatformVRC6*)data;
ImGui::Text("> VRC6");
COMMON_CHIP_DEBUG;
ImGui::Text("- sampleBank: %.2x",ch->sampleBank);
COMMON_CHIP_DEBUG_BOOL;
break;
}
@ -708,7 +694,6 @@ void putDispatchChan(void* data, int chanNum, int type) {
COMMON_CHAN_DEBUG_BOOL;
ImGui::TextColored(ch->noise?colorOn:colorOff,">> Noise");
ImGui::TextColored(ch->pcm?colorOn:colorOff,">> DAC");
ImGui::TextColored(ch->furnaceDac?colorOn:colorOff,">> FurnaceDAC");
break;
}
case DIV_SYSTEM_NES: {
@ -720,7 +705,6 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::Text("- sweep: %.2x",ch->sweep);
COMMON_CHAN_DEBUG_BOOL;
ImGui::TextColored(ch->sweepChanged?colorOn:colorOff,">> SweepChanged");
ImGui::TextColored(ch->furnaceDac?colorOn:colorOff,">> FurnaceDAC");
break;
}
case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: {
@ -771,7 +755,6 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::Text("- chPanR: %.2x",ch->chPanR);
ImGui::Text("- macroVolMul: %.2x",ch->macroVolMul);
COMMON_CHAN_DEBUG_BOOL;
ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM");
ImGui::TextColored(ch->isNewSegaPCM?colorOn:colorOff,">> IsNewSegaPCM");
break;
}
@ -788,7 +771,6 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::Text("- autoEnvNum: %.2x",ch->autoEnvNum);
ImGui::Text("- autoEnvDen: %.2x",ch->autoEnvDen);
COMMON_CHAN_DEBUG_BOOL;
ImGui::TextColored(ch->dac.furnaceDAC?colorOn:colorOff,">> furnaceDAC");
break;
}
case DIV_SYSTEM_AY8930: {
@ -805,7 +787,6 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::Text("- autoEnvNum: %.2x",ch->autoEnvNum);
ImGui::Text("- autoEnvDen: %.2x",ch->autoEnvDen);
COMMON_CHAN_DEBUG_BOOL;
ImGui::TextColored(ch->dac.furnaceDAC?colorOn:colorOff,">> furnaceDAC");
break;
}
case DIV_SYSTEM_QSOUND: {
@ -842,7 +823,6 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::Text("- Rvol: %.2x",ch->rvol);
COMMON_CHAN_DEBUG_BOOL;
ImGui::TextColored(ch->envChanged?colorOn:colorOff,">> EnvChanged");
ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM");
ImGui::TextColored(ch->pcm?colorOn:colorOff,">> PCM");
ImGui::TextColored(ch->env.flag.envEnable?colorOn:colorOff,">> EnvEnable");
ImGui::TextColored(ch->env.flag.envOneshot?colorOn:colorOff,">> EnvOneshot");
@ -881,7 +861,6 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::Text("- duty: %d",ch->duty);
COMMON_CHAN_DEBUG_BOOL;
ImGui::TextColored(ch->pcm?colorOn:colorOff,">> DAC");
ImGui::TextColored(ch->furnaceDac?colorOn:colorOff,">> FurnaceDAC");
break;
}
case DIV_SYSTEM_ES5506: {

View file

@ -21,7 +21,7 @@
#include "guiConst.h"
#include "debug.h"
#include "IconsFontAwesome4.h"
#include <SDL_timer.h>
#include <inttypes.h>
#include <fmt/printf.h>
#include "imgui.h"
#include "imgui_internal.h"
@ -208,7 +208,7 @@ void FurnaceGUI::drawDebug() {
DivSongTimestamps& ts=e->curSubSong->ts;
String timeFormatted=ts.totalTime.toString(-1,TA_TIME_FORMAT_AUTO);
ImGui::Text("song duration: %s (%d ticks; %d rows)",timeFormatted.c_str(),ts.totalTicks,ts.totalRows);
ImGui::Text("song duration: %s (%" PRIu64 " ticks; %d rows)",timeFormatted.c_str(),ts.totalTicks,ts.totalRows);
if (ts.isLoopDefined) {
ImGui::Text("loop region is defined");
} else {
@ -243,7 +243,6 @@ void FurnaceGUI::drawDebug() {
continue;
}
if (ImGui::TreeNode(fmt::sprintf("%d: %s",i,sample->name).c_str())) {
ImGui::Text("rate: %d",sample->rate);
ImGui::Text("centerRate: %d",sample->centerRate);
ImGui::Text("loopStart: %d",sample->loopStart);
ImGui::Text("loopEnd: %d", sample->loopEnd);

View file

@ -956,7 +956,6 @@ void FurnaceGUI::doAction(int what) {
if (sample!=NULL) {
DivWavetable* wave=e->song.wave[curWave];
unsigned int waveLen=wave->len;
sample->rate=(int)round(261.625565301*waveLen); // c3
sample->centerRate=(int)round(261.625565301*waveLen); // c3
sample->loopStart=0;
sample->loopEnd=waveLen;
@ -1045,7 +1044,6 @@ void FurnaceGUI::doAction(int what) {
e->lockEngine([this,prevSample]() {
DivSample* sample=e->getSample(curSample);
if (sample!=NULL) {
sample->rate=prevSample->rate;
sample->centerRate=prevSample->centerRate;
sample->name=prevSample->name;
sample->loopStart=prevSample->loopStart;

View file

@ -1582,8 +1582,11 @@ void FurnaceGUI::doRandomize(int bottom, int top, bool mode, bool eff, int effVa
value=MIN(absoluteTop,bottom);
value2=MIN(absoluteTop,bottom);
} else {
value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1)));
value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1)));
// HACK: MIN will call rand() twice....
int randVal=rand();
value=MIN(absoluteTop,bottom+(randVal%(top-bottom+1)));
randVal=rand();
value2=MIN(absoluteTop,bottom+(randVal%(top-bottom+1)));
}
if (mode) {
value&=15;

View file

@ -5001,7 +5001,7 @@ bool FurnaceGUI::loop() {
break;
default: // effect
if (cursor.xFine<DIV_MAX_COLS) {
if (p->newData[cursor.y][cursor.xFine]>-1) {
if (p->newData[cursor.y][(cursor.xFine-1)|1]>-1) {
info=e->getEffectDesc(p->newData[cursor.y][(cursor.xFine-1)|1],cursor.xCoarse,true);
hasInfo=true;
}
@ -8341,6 +8341,8 @@ void FurnaceGUI::syncState() {
orderLock=e->getConfBool("orderLock",false);
followOrders=e->getConfBool("followOrders",true);
followPattern=e->getConfBool("followPattern",true);
editStep=e->getConfInt("editStep",1);
editStepCoarse=e->getConfInt("editStepCoarse",16);
noteInputMode=e->getConfInt("noteInputMode",GUI_NOTE_INPUT_POLY);
if (noteInputMode!=GUI_NOTE_INPUT_MONO && noteInputMode!=GUI_NOTE_INPUT_POLY && noteInputMode!=GUI_NOTE_INPUT_CHORD) {
noteInputMode=GUI_NOTE_INPUT_POLY;
@ -8513,6 +8515,8 @@ void FurnaceGUI::commitState(DivConfig& conf) {
conf.set("orderLock",orderLock);
conf.set("followOrders",followOrders);
conf.set("followPattern",followPattern);
conf.set("editStep",editStep);
conf.set("editStepCoarse",editStepCoarse);
conf.set("orderEditMode",orderEditMode);
conf.set("noteInputMode",(int)noteInputMode);
conf.set("filePlayerSync",filePlayerSync);
@ -9134,7 +9138,6 @@ FurnaceGUI::FurnaceGUI():
sampleSelStart(-1),
sampleSelEnd(-1),
sampleInfo(true),
sampleCompatRate(false),
sampleDragActive(false),
sampleDragMode(false),
sampleDrag16(false),

View file

@ -2657,7 +2657,7 @@ class FurnaceGUI {
int resampleStrat;
float amplifyVol, amplifyOff;
int sampleSelStart, sampleSelEnd;
bool sampleInfo, sampleCompatRate;
bool sampleInfo;
bool sampleDragActive, sampleDragMode, sampleDrag16, sampleZoomAuto;
bool sampleCheckLoopStart, sampleCheckLoopEnd;
// 0: start

View file

@ -1603,7 +1603,7 @@ bool FurnaceFilePicker::draw(ImGuiWindowFlags winFlags) {
}
// OK/Cancel buttons
ImGui::BeginDisabled(entryName.empty() && chosenEntries.empty());
ImGui::BeginDisabled(entryName.empty() && chosenEntries.empty() && !dirSelect);
if (ImGui::Button(_("OK"))) {
// accept entry
acknowledged=true;
@ -1757,6 +1757,20 @@ bool FurnaceFilePicker::draw(ImGuiWindowFlags winFlags) {
}
}
}
} else {
if (dirSelect) {
finalSelection.push_back(path);
curStatus=FP_STATUS_ACCEPTED;
if (noClose) {
for (FileEntry* j: chosenEntries) {
j->isSelected=false;
}
chosenEntries.clear();
updateEntryName();
} else {
isOpen=false;
}
}
}
}
}

View file

@ -39,7 +39,14 @@ void FurnaceGUI::drawRegView() {
} else {
ImGui::PushFont(patFont);
if (ImGui::BeginTable("Memory",17)) {
ImGui::TableSetupColumn("addr",ImGuiTableColumnFlags_WidthFixed);
float widthOne=ImGui::CalcTextSize("0").x;
if (size>0xfff) { // no im got gonna put some clamped log formula instead
ImGui::TableSetupColumn("addr",ImGuiTableColumnFlags_WidthFixed, widthOne*4.0f);
} else if (size>0xff) {
ImGui::TableSetupColumn("addr",ImGuiTableColumnFlags_WidthFixed, widthOne*3.0f);
} else {
ImGui::TableSetupColumn("addr",ImGuiTableColumnFlags_WidthFixed, widthOne*2.0f);
}
ImGui::TableNextRow();
ImGui::TableNextColumn();

View file

@ -611,7 +611,7 @@ void FurnaceGUI::drawSampleEdit() {
if (isChipVisible[i]) selColumns++;
}
int targetRate=sampleCompatRate?sample->rate:sample->centerRate;
int targetRate=sample->centerRate;
if (ImGui::BeginTable("SampleProps",(selColumns>1)?4:3,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersV|ImGuiTableFlags_BordersOuterH)) {
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
@ -622,20 +622,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::SameLine();
ImGui::Text(_("Info"));
ImGui::TableNextColumn();
pushToggleColors(!sampleCompatRate);
if (ImGui::Button(_("Rate"))) {
sampleCompatRate=false;
}
popToggleColors();
ImGui::SameLine();
pushToggleColors(sampleCompatRate);
if (ImGui::Button(_("Compat Rate"))) {
sampleCompatRate=true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("used in DefleMask-compatible sample mode (17xx), in where samples are mapped to an octave."));
}
popToggleColors();
ImGui::Text(_("Rate"));
ImGui::TableNextColumn();
bool doLoop=(sample->loop);
pushWarningColor(!warnLoop.empty());
@ -774,11 +761,7 @@ void FurnaceGUI::drawSampleEdit() {
if (targetRate<100) targetRate=100;
if (targetRate>384000) targetRate=384000;
if (sampleCompatRate) {
sample->rate=targetRate;
} else {
sample->centerRate=targetRate;
}
sample->centerRate=targetRate;
}
ImGui::AlignTextToFramePadding();
@ -818,11 +801,7 @@ void FurnaceGUI::drawSampleEdit() {
if (targetRate<100) targetRate=100;
if (targetRate>384000) targetRate=384000;
if (sampleCompatRate) {
sample->rate=targetRate;
} else {
sample->centerRate=targetRate;
}
sample->centerRate=targetRate;
}
ImGui::AlignTextToFramePadding();
@ -852,11 +831,7 @@ void FurnaceGUI::drawSampleEdit() {
if (targetRate<100) targetRate=100;
if (targetRate>384000) targetRate=384000;
if (sampleCompatRate) {
sample->rate=targetRate;
} else {
sample->centerRate=targetRate;
}
sample->centerRate=targetRate;
}
ImGui::TableNextColumn();
@ -1662,7 +1637,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::ItemSize(size,style.FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("SETime"))) {
int targetRate=sampleCompatRate?sample->rate:sample->centerRate;
int targetRate=sample->centerRate;
int curDivisorSel=0;
int curMultiplierSel=0;
double divisor=1000.0;

View file

@ -2268,6 +2268,9 @@ void FurnaceGUI::drawSettings() {
drawKeybindSettingsTableRow(GUI_ACTION_WINDOW_EFFECT_LIST);
drawKeybindSettingsTableRow(GUI_ACTION_WINDOW_DEBUG);
drawKeybindSettingsTableRow(GUI_ACTION_WINDOW_CS_PLAYER);
drawKeybindSettingsTableRow(GUI_ACTION_WINDOW_REF_PLAYER);
drawKeybindSettingsTableRow(GUI_ACTION_WINDOW_TUNER);
drawKeybindSettingsTableRow(GUI_ACTION_WINDOW_SPECTRUM);
drawKeybindSettingsTableRow(GUI_ACTION_WINDOW_ABOUT);
drawKeybindSettingsTableRow(GUI_ACTION_COLLAPSE_WINDOW);
drawKeybindSettingsTableRow(GUI_ACTION_CLOSE_WINDOW);
@ -4703,9 +4706,9 @@ void FurnaceGUI::drawSettings() {
// "Debug" - toggles mobile UI
// "Nice Amiga cover of the song!" - enables hidden systems (YMU759/Dummy)
// "42 63" - enables all instrument types
// "4-bit FDS" - enables partial pitch linearity option
// "Power of the Chip" - enables options for multi-threaded audio
// "btcdbcb" - use modern UI padding
// "6-7" - OH PLEASE NO
// "????" - enables stuff
CONFIG_SECTION(_("Cheat Codes")) {
// SUBSECTION ENTER CODE:
@ -4748,6 +4751,22 @@ void FurnaceGUI::drawSettings() {
sty.ItemInnerSpacing=ImVec2(10.0f*dpiScale,10.0f*dpiScale);
settingsOpen=false;
}
if (checker==0x2222225c && checker1==0x2d2) {
mmlString[30]=_("Oh my god... Kill me now so I don't have to go through that again!");
for (int i=0; i<e->getTotalChannelCount(); i++) {
for (int j=0; j<DIV_MAX_PATTERNS; j++) {
if (e->curSubSong->pat[i].data[j]!=NULL) {
DivPattern* p=e->curSubSong->pat[i].data[j];
for (int k=0; k<DIV_MAX_ROWS; k++) {
if (p->newData[k][DIV_PAT_NOTE]>=0 && p->newData[k][DIV_PAT_NOTE]<180) {
int newNote=p->newData[k][DIV_PAT_NOTE]+(rand()%40)-18;
p->newData[k][DIV_PAT_NOTE]=CLAMP(newNote,60,179);
}
}
}
}
}
}
mmlString[31]="";
}

View file

@ -53,6 +53,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
ImGui::PushID(i);
if (ImGui::SmallButton(ICON_FA_ARROW_UP "##SubUp")) {
e->moveSubSongUp(i);
MARK_MODIFIED;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("Move up"));
@ -60,6 +61,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
ImGui::SameLine();
if (ImGui::SmallButton(ICON_FA_ARROW_DOWN "##SubDown")) {
e->moveSubSongDown(i);
MARK_MODIFIED;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("Move down"));

View file

@ -180,6 +180,9 @@ struct FurnaceCV {
// this offset is applied to sprites.
int viewX, viewY;
// other
char hiScoreText[512];
// input
unsigned char joyInputPrev;
unsigned char joyPressed;
@ -666,7 +669,11 @@ static const char* cvText[]={
_N("GAME OVER"),
_N("High Score!"),
_N(" CONGREGURATION\n\n\n"
"YOU ARE GOOD PLAY AT GAME\n"
"PRESS ESCAPE TO RESET GAME\n\n"
"AND PLAY AGAIN AT ELEVATED DIFFICULTY\n\n\n"
"%d SCORES THIS GAME"),
_N("Welcome to Combat Vehicle!\n\n"
"Controls:\n"
@ -1695,7 +1702,8 @@ void FurnaceCV::render(unsigned char joyIn) {
}
memset(tile0,0,80*56*sizeof(short));
memset(tile1,0,80*56*sizeof(short));
startTyping(_(cvText[3]),2,3);
snprintf(hiScoreText,511,_(cvText[3]),hiScore);
startTyping(hiScoreText,2,3);
e->setConf("cvHiScore",hiScore);
e->saveConf();
curText=4;

View file

@ -484,14 +484,53 @@ TAParamResult pOutFormat(String val) {
}
TAParamResult pBitRate(String val) {
try {
int br=std::stoi(val);
if (br<0) {
logE("bit rate shall be positive.");
return TA_PARAM_ERROR;
}
if (br<1000) {
// let's assume the user meant kilobits.
br*=1000;
}
exportOptions.bitRate=br;
} catch (std::exception& e) {
logE("bit rate shall be a number.");
return TA_PARAM_ERROR;
}
return TA_PARAM_SUCCESS;
}
TAParamResult pRateMode(String val) {
if (val=="constant") {
exportOptions.bitRateMode=DIV_EXPORT_BITRATE_CONSTANT;
} else if (val=="variable") {
exportOptions.bitRateMode=DIV_EXPORT_BITRATE_VARIABLE;
} else if (val=="average") {
exportOptions.bitRateMode=DIV_EXPORT_BITRATE_AVERAGE;
} else {
logE("invalid value for ratemode! valid values are: constant, variable and average.");
return TA_PARAM_ERROR;
}
return TA_PARAM_SUCCESS;
}
TAParamResult pCompression(String val) {
try {
float qual=std::stof(val);
if (qual<0) {
logE("quality/compression level shall be positive.");
return TA_PARAM_ERROR;
}
if (qual>10) {
qual=10;
}
exportOptions.vbrQuality=qual;
} catch (std::exception& e) {
logE("quality/compression level shall be a number.");
return TA_PARAM_ERROR;
}
return TA_PARAM_SUCCESS;
}
@ -500,8 +539,10 @@ TAParamResult pBenchmark(String val) {
benchMode=1;
} else if (val=="seek") {
benchMode=2;
} else if (val=="walk") {
benchMode=3;
} else {
logE("invalid value for benchmark! valid values are: render and seek.");
logE("invalid value for benchmark! valid values are: render, seek and walk.");
return TA_PARAM_ERROR;
}
e.setAudio(DIV_AUDIO_DUMMY);
@ -591,7 +632,7 @@ void initParams() {
params.push_back(TAParam("f","outformat",true,pOutFormat,"u8|s16|f32|opus|flac|vorbis|mp3","set audio output format"));
params.push_back(TAParam("b","bitrate",true,pBitRate,"<rate>","set output file bit rate (lossy compression only)"));
params.push_back(TAParam("M","ratemode",true,pRateMode,"constant|variable|average","set output bit rate mode (MP3 only)"));
params.push_back(TAParam("Q","compression",true,pCompression,"<level>","set output quality/compression level (Vorbis, FLAC and MP3 VBR only)"));
params.push_back(TAParam("Q","compression",true,pCompression,"<level>","set output quality/compression level from 0-10 (Vorbis, FLAC and MP3 VBR only)"));
params.push_back(TAParam("O","vgmout",true,pVGMOut,"<filename>","output .vgm data"));
@ -614,7 +655,7 @@ void initParams() {
params.push_back(TAParam("S","safemode",false,pSafeMode,"","enable safe mode (software rendering and no audio)"));
params.push_back(TAParam("A","safeaudio",false,pSafeModeAudio,"","enable safe mode (with audio"));
params.push_back(TAParam("B","benchmark",true,pBenchmark,"render|seek","run performance test"));
params.push_back(TAParam("B","benchmark",true,pBenchmark,"render|seek|walk","run performance test"));
params.push_back(TAParam("V","version",false,pVersion,"","view information about Furnace."));
params.push_back(TAParam("W","warranty",false,pWarranty,"","view warranty disclaimer."));
@ -992,7 +1033,9 @@ int main(int argc, char** argv) {
if (benchMode) {
logI("starting benchmark!");
if (benchMode==2) {
if (benchMode==3) {
e.benchmarkWalk();
} else if (benchMode==2) {
e.benchmarkSeek();
} else {
e.benchmarkPlayback();

View file

@ -41,7 +41,7 @@ else
echo "OK"
else
echo "FAIL FAIL FAIL"
ffmpeg -loglevel quiet -i "test/delta/$testDir/$i" -lavfi showspectrumpic "test/delta/$testDir/$i.png"
ffmpeg -loglevel quiet -i "test/delta/$testDir/$i" -lavfi showspectrumpic -y "test/delta/$testDir/$i.png"
fi
done
fi

11
test/last-stage.sh Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
testDir=20251109215858
if ./assert_delta "delta/$testDir/$1"; then
true
#echo "OK"
else
echo -n "$1... "
echo "FAIL FAIL FAIL"
mpv "delta/$testDir/$1"
#ffmpeg -loglevel quiet -i "test/delta/$testDir/$1" -lavfi showspectrumpic -y "test/delta/$testDir/$1.png"
fi