Merge branch 'master' of https://github.com/tildearrow/furnace into x1_010_bank
This commit is contained in:
commit
2a881c9f66
84 changed files with 3061 additions and 381 deletions
301
src/engine/cmdStream.cpp
Normal file
301
src/engine/cmdStream.cpp
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 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 "cmdStream.h"
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
|
||||
void DivCSPlayer::cleanup() {
|
||||
delete b;
|
||||
}
|
||||
|
||||
bool DivCSPlayer::tick() {
|
||||
bool ticked=false;
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
bool sendVolume=false;
|
||||
if (chan[i].readPos==0) continue;
|
||||
|
||||
ticked=true;
|
||||
|
||||
chan[i].waitTicks--;
|
||||
while (chan[i].waitTicks<=0) {
|
||||
stream.seek(chan[i].readPos,SEEK_SET);
|
||||
unsigned char next=stream.readC();
|
||||
unsigned char command=0;
|
||||
|
||||
if (next<0xb3) { // note
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,next-60));
|
||||
} else if (next>=0xd0 && next<=0xdf) {
|
||||
command=fastCmds[next&15];
|
||||
} else if (next>=0xe0 && next<=0xef) { // preset delay
|
||||
chan[i].waitTicks=fastDelays[next&15];
|
||||
} else switch (next) {
|
||||
case 0xb4: // note on null
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL));
|
||||
break;
|
||||
case 0xb5: // note off
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i));
|
||||
break;
|
||||
case 0xb6: // note off env
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF_ENV,i));
|
||||
break;
|
||||
case 0xb7: // env release
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_ENV_RELEASE,i));
|
||||
break;
|
||||
case 0xb8: case 0xbe: case 0xc0: case 0xc2:
|
||||
case 0xc3: case 0xc4: case 0xc5: case 0xc6:
|
||||
case 0xc7: case 0xc8: case 0xc9: case 0xca:
|
||||
command=next-0xb4;
|
||||
break;
|
||||
case 0xf7:
|
||||
command=stream.readC();
|
||||
break;
|
||||
case 0xf8:
|
||||
logE("TODO: CALL");
|
||||
break;
|
||||
case 0xf9:
|
||||
logE("TODO: RET");
|
||||
break;
|
||||
case 0xfa:
|
||||
logE("TODO: JMP");
|
||||
break;
|
||||
case 0xfb:
|
||||
logE("TODO: RATE");
|
||||
break;
|
||||
case 0xfc:
|
||||
chan[i].waitTicks=(unsigned short)stream.readS();
|
||||
break;
|
||||
case 0xfd:
|
||||
chan[i].waitTicks=(unsigned char)stream.readC();
|
||||
break;
|
||||
case 0xfe:
|
||||
chan[i].waitTicks=1;
|
||||
break;
|
||||
case 0xff:
|
||||
chan[i].readPos=0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chan[i].readPos==0) break;
|
||||
|
||||
if (command) {
|
||||
int arg0=0;
|
||||
int arg1=0;
|
||||
switch (command) {
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
case DIV_CMD_HINT_VIBRATO_RANGE:
|
||||
case DIV_CMD_HINT_VIBRATO_SHAPE:
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
case DIV_CMD_HINT_VIBRATO:
|
||||
case DIV_CMD_HINT_ARPEGGIO:
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=(arg0&0x40)?1:0;
|
||||
arg0=(arg0&0x80)?1:0;
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
arg0=(short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
case DIV_CMD_SAMPLE_DIR:
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
case DIV_CMD_FM_LFO:
|
||||
case DIV_CMD_FM_LFO_WAVE:
|
||||
case DIV_CMD_FM_FB:
|
||||
case DIV_CMD_FM_EXTCH:
|
||||
case DIV_CMD_FM_AM_DEPTH:
|
||||
case DIV_CMD_FM_PM_DEPTH:
|
||||
case DIV_CMD_STD_NOISE_FREQ:
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
case DIV_CMD_WAVE:
|
||||
case DIV_CMD_GB_SWEEP_TIME:
|
||||
case DIV_CMD_GB_SWEEP_DIR:
|
||||
case DIV_CMD_PCE_LFO_MODE:
|
||||
case DIV_CMD_PCE_LFO_SPEED:
|
||||
case DIV_CMD_NES_DMC:
|
||||
case DIV_CMD_C64_CUTOFF:
|
||||
case DIV_CMD_C64_RESONANCE:
|
||||
case DIV_CMD_C64_FILTER_MODE:
|
||||
case DIV_CMD_C64_RESET_TIME:
|
||||
case DIV_CMD_C64_RESET_MASK:
|
||||
case DIV_CMD_C64_FILTER_RESET:
|
||||
case DIV_CMD_C64_DUTY_RESET:
|
||||
case DIV_CMD_C64_EXTENDED:
|
||||
case DIV_CMD_AY_ENVELOPE_SET:
|
||||
case DIV_CMD_AY_ENVELOPE_LOW:
|
||||
case DIV_CMD_AY_ENVELOPE_HIGH:
|
||||
case DIV_CMD_AY_ENVELOPE_SLIDE:
|
||||
case DIV_CMD_AY_NOISE_MASK_AND:
|
||||
case DIV_CMD_AY_NOISE_MASK_OR:
|
||||
case DIV_CMD_AY_AUTO_ENVELOPE:
|
||||
case DIV_CMD_FDS_MOD_DEPTH:
|
||||
case DIV_CMD_FDS_MOD_HIGH:
|
||||
case DIV_CMD_FDS_MOD_LOW:
|
||||
case DIV_CMD_FDS_MOD_POS:
|
||||
case DIV_CMD_FDS_MOD_WAVE:
|
||||
case DIV_CMD_SAA_ENVELOPE:
|
||||
case DIV_CMD_AMIGA_FILTER:
|
||||
case DIV_CMD_AMIGA_AM:
|
||||
case DIV_CMD_AMIGA_PM:
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
case DIV_CMD_MACRO_ON:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_FM_TL:
|
||||
case DIV_CMD_FM_AM:
|
||||
case DIV_CMD_FM_AR:
|
||||
case DIV_CMD_FM_DR:
|
||||
case DIV_CMD_FM_SL:
|
||||
case DIV_CMD_FM_D2R:
|
||||
case DIV_CMD_FM_RR:
|
||||
case DIV_CMD_FM_DT:
|
||||
case DIV_CMD_FM_DT2:
|
||||
case DIV_CMD_FM_RS:
|
||||
case DIV_CMD_FM_KSR:
|
||||
case DIV_CMD_FM_VIB:
|
||||
case DIV_CMD_FM_SUS:
|
||||
case DIV_CMD_FM_WS:
|
||||
case DIV_CMD_FM_SSG:
|
||||
case DIV_CMD_FM_REV:
|
||||
case DIV_CMD_FM_EG_SHIFT:
|
||||
case DIV_CMD_FM_MULT:
|
||||
case DIV_CMD_FM_FINE:
|
||||
case DIV_CMD_AY_IO_WRITE:
|
||||
case DIV_CMD_AY_AUTO_PWM:
|
||||
case DIV_CMD_SURROUND_PANNING:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=(unsigned char)stream.readC();
|
||||
break;
|
||||
case DIV_CMD_C64_FINE_DUTY:
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
case DIV_CMD_LYNX_LFSR_LOAD:
|
||||
arg0=(unsigned short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_FM_FIXFREQ:
|
||||
arg0=(unsigned short)stream.readS();
|
||||
arg1=arg0&0x7ff;
|
||||
arg0>>=12;
|
||||
break;
|
||||
case DIV_CMD_NES_SWEEP:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
arg1=arg0&0x77;
|
||||
arg0=(arg0&8)?1:0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
chan[i].volume=arg0<<8;
|
||||
sendVolume=true;
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
chan[i].volSpeed=arg0;
|
||||
break;
|
||||
default: // dispatch it
|
||||
e->dispatchCmd(DivCommand((DivDispatchCmds)command,i,arg0,arg1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
chan[i].readPos=stream.tell();
|
||||
}
|
||||
|
||||
if (sendVolume || chan[i].volSpeed!=0) {
|
||||
chan[i].volume+=chan[i].volSpeed;
|
||||
if (chan[i].volume<0) {
|
||||
chan[i].volume=0;
|
||||
}
|
||||
if (chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
}
|
||||
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
}
|
||||
|
||||
return ticked;
|
||||
}
|
||||
|
||||
bool DivCSPlayer::init() {
|
||||
unsigned char magic[4];
|
||||
stream.seek(0,SEEK_SET);
|
||||
stream.read(magic,4);
|
||||
|
||||
if (memcmp(magic,"FCS",4)!=0) return false;
|
||||
|
||||
unsigned int chans=stream.readI();
|
||||
|
||||
for (unsigned int i=0; i<chans; i++) {
|
||||
if (i>=DIV_MAX_CHANS) {
|
||||
stream.readI();
|
||||
continue;
|
||||
}
|
||||
if ((int)i>=e->getTotalChannelCount()) {
|
||||
stream.readI();
|
||||
continue;
|
||||
}
|
||||
chan[i].readPos=stream.readI();
|
||||
}
|
||||
|
||||
stream.read(fastDelays,16);
|
||||
stream.read(fastCmds,16);
|
||||
|
||||
// initialize state
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
chan[i].volMax=(e->getDispatch(e->dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->dispatchChanOfChan[i]))<<8)|0xff;
|
||||
chan[i].volume=chan[i].volMax;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// DivEngine
|
||||
|
||||
bool DivEngine::playStream(unsigned char* f, size_t length) {
|
||||
BUSY_BEGIN;
|
||||
cmdStreamInt=new DivCSPlayer(this,f,length);
|
||||
if (!cmdStreamInt->init()) {
|
||||
logE("not a command stream!");
|
||||
lastError="not a command stream";
|
||||
delete[] f;
|
||||
delete cmdStreamInt;
|
||||
cmdStreamInt=NULL;
|
||||
BUSY_END;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!playing) {
|
||||
reset();
|
||||
freelance=true;
|
||||
playing=true;
|
||||
}
|
||||
BUSY_END;
|
||||
return true;
|
||||
}
|
||||
59
src/engine/cmdStream.h
Normal file
59
src/engine/cmdStream.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 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 _CMD_STREAM_H
|
||||
#define _CMD_STREAM_H
|
||||
|
||||
#include "defines.h"
|
||||
#include "safeReader.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
struct DivCSChannelState {
|
||||
unsigned int readPos;
|
||||
int waitTicks;
|
||||
|
||||
int volume, volMax, volSpeed;
|
||||
|
||||
DivCSChannelState():
|
||||
readPos(0),
|
||||
waitTicks(0),
|
||||
volume(0x7f00),
|
||||
volMax(0),
|
||||
volSpeed(0) {}
|
||||
};
|
||||
|
||||
class DivCSPlayer {
|
||||
DivEngine* e;
|
||||
unsigned char* b;
|
||||
SafeReader stream;
|
||||
DivCSChannelState chan[DIV_MAX_CHANS];
|
||||
unsigned char fastDelays[16];
|
||||
unsigned char fastCmds[16];
|
||||
public:
|
||||
void cleanup();
|
||||
bool tick();
|
||||
bool init();
|
||||
DivCSPlayer(DivEngine* en, unsigned char* buf, size_t len):
|
||||
e(en),
|
||||
b(buf),
|
||||
stream(buf,len) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -280,19 +280,31 @@ struct DivRegWrite {
|
|||
* - 0xffffffff: reset
|
||||
*/
|
||||
unsigned int addr;
|
||||
unsigned short val;
|
||||
DivRegWrite(unsigned int a, unsigned short v):
|
||||
unsigned int val;
|
||||
DivRegWrite(unsigned int a, unsigned int v):
|
||||
addr(a), val(v) {}
|
||||
};
|
||||
|
||||
struct DivDelayedWrite {
|
||||
int time;
|
||||
DivRegWrite write;
|
||||
DivDelayedWrite(int t, unsigned int a, unsigned short v):
|
||||
DivDelayedWrite(int t, unsigned int a, unsigned int v):
|
||||
time(t),
|
||||
write(a,v) {}
|
||||
};
|
||||
|
||||
struct DivSamplePos {
|
||||
int sample, pos, freq;
|
||||
DivSamplePos(int s, int p, int f):
|
||||
sample(s),
|
||||
pos(p),
|
||||
freq(f) {}
|
||||
DivSamplePos():
|
||||
sample(-1),
|
||||
pos(0),
|
||||
freq(0) {}
|
||||
};
|
||||
|
||||
struct DivDispatchOscBuffer {
|
||||
bool follow;
|
||||
unsigned int rate;
|
||||
|
|
@ -371,18 +383,29 @@ class DivDispatch {
|
|||
|
||||
/**
|
||||
* get the state of a channel.
|
||||
* @param chan the channel.
|
||||
* @return a pointer, or NULL.
|
||||
*/
|
||||
virtual void* getChanState(int chan);
|
||||
|
||||
/**
|
||||
* get the DivMacroInt of a chanmel.
|
||||
* get the DivMacroInt of a channel.
|
||||
* @param chan the channel.
|
||||
* @return a pointer, or NULL.
|
||||
*/
|
||||
virtual DivMacroInt* getChanMacroInt(int chan);
|
||||
|
||||
/**
|
||||
* get currently playing sample (and its position).
|
||||
* @param chan the channel.
|
||||
* @return a DivSamplePos. if sample is -1 then nothing is playing or the
|
||||
* channel doesn't play samples.
|
||||
*/
|
||||
virtual DivSamplePos getSamplePos(int chan);
|
||||
|
||||
/**
|
||||
* get an oscilloscope buffer for a channel.
|
||||
* @param chan the channel.
|
||||
* @return a pointer to a DivDispatchOscBuffer, or NULL if not supported.
|
||||
*/
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
|
|
|
|||
|
|
@ -1588,7 +1588,7 @@ void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
|
|||
// get unsorted directory
|
||||
DivAssetDir* unsortedDir=NULL;
|
||||
for (DivAssetDir& i: dir) {
|
||||
if (i.name=="Unsorted") {
|
||||
if (i.name.empty()) {
|
||||
unsortedDir=&i;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1596,7 +1596,7 @@ void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
|
|||
|
||||
// create unsorted directory if it doesn't exist
|
||||
if (unsortedDir==NULL) {
|
||||
dir.push_back(DivAssetDir("Unsorted"));
|
||||
dir.push_back(DivAssetDir(""));
|
||||
unsortedDir=&(*dir.rbegin());
|
||||
}
|
||||
|
||||
|
|
@ -2119,6 +2119,11 @@ DivMacroInt* DivEngine::getMacroInt(int chan) {
|
|||
return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]);
|
||||
}
|
||||
|
||||
DivSamplePos DivEngine::getSamplePos(int chan) {
|
||||
if (chan<0 || chan>=chans) return DivSamplePos();
|
||||
return disCont[dispatchOfChan[chan]].dispatch->getSamplePos(dispatchChanOfChan[chan]);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) {
|
||||
if (chan<0 || chan>=chans) return NULL;
|
||||
return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]);
|
||||
|
|
@ -2498,6 +2503,10 @@ void DivEngine::recalcChans() {
|
|||
if (isInsTypePossible[i]) possibleInsTypes.push_back((DivInstrumentType)i);
|
||||
}
|
||||
|
||||
checkAssetDir(song.insDir,song.ins.size());
|
||||
checkAssetDir(song.waveDir,song.wave.size());
|
||||
checkAssetDir(song.sampleDir,song.sample.size());
|
||||
|
||||
hasLoadedSomething=true;
|
||||
}
|
||||
|
||||
|
|
@ -2576,6 +2585,10 @@ int DivEngine::divToFileRate(int drate) {
|
|||
return 4;
|
||||
}
|
||||
|
||||
void DivEngine::testFunction() {
|
||||
logI("it works!");
|
||||
}
|
||||
|
||||
int DivEngine::getEffectiveSampleRate(int rate) {
|
||||
if (rate<1) return 0;
|
||||
switch (song.system[0]) {
|
||||
|
|
@ -4358,6 +4371,7 @@ bool DivEngine::initAudioBackend() {
|
|||
lowLatency=getConfInt("lowLatency",0);
|
||||
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
|
||||
midiOutClock=getConfInt("midiOutClock",0);
|
||||
midiOutProgramChange = getConfInt("midiOutProgramChange",0);
|
||||
midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE);
|
||||
if (metroVol<0.0f) metroVol=0.0f;
|
||||
if (metroVol>2.0f) metroVol=2.0f;
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@
|
|||
#include "instrument.h"
|
||||
#include "song.h"
|
||||
#include "dispatch.h"
|
||||
#include "export.h"
|
||||
#include "dataErrors.h"
|
||||
#include "safeWriter.h"
|
||||
#include "cmdStream.h"
|
||||
#include "../audio/taAudio.h"
|
||||
#include "blip_buf.h"
|
||||
#include <atomic>
|
||||
|
|
@ -47,11 +49,17 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define EXTERN_BUSY_BEGIN e->softLocked=false; e->isBusy.lock();
|
||||
#define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock();
|
||||
#define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev145"
|
||||
#define DIV_ENGINE_VERSION 145
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
#define DIV_VERSION_FC 0xff02
|
||||
#define DIV_VERSION_S3M 0xff03
|
||||
#define DIV_VERSION_FTM 0xff04
|
||||
|
||||
// "Namco C163"
|
||||
#define DIV_C163_DEFAULT_NAME "Namco 163"
|
||||
|
|
@ -359,6 +367,7 @@ class DivEngine {
|
|||
bool systemsRegistered;
|
||||
bool hasLoadedSomething;
|
||||
bool midiOutClock;
|
||||
bool midiOutProgramChange;
|
||||
int midiOutMode;
|
||||
int softLockCount;
|
||||
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
|
||||
|
|
@ -397,6 +406,8 @@ class DivEngine {
|
|||
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
|
||||
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
|
||||
|
||||
DivCSPlayer* cmdStreamInt;
|
||||
|
||||
struct SamplePreview {
|
||||
double rate;
|
||||
int sample;
|
||||
|
|
@ -441,7 +452,6 @@ class DivEngine {
|
|||
// MIDI stuff
|
||||
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;};
|
||||
|
||||
int dispatchCmd(DivCommand c);
|
||||
void processRow(int i, bool afterDelay);
|
||||
void nextOrder();
|
||||
void nextRow();
|
||||
|
|
@ -454,9 +464,12 @@ class DivEngine {
|
|||
void reset();
|
||||
void playSub(bool preserveDrift, int goalRow=0);
|
||||
|
||||
void testFunction();
|
||||
|
||||
bool loadDMF(unsigned char* file, size_t len);
|
||||
bool loadFur(unsigned char* file, size_t len);
|
||||
bool loadMod(unsigned char* file, size_t len);
|
||||
bool loadS3M(unsigned char* file, size_t len);
|
||||
bool loadFTM(unsigned char* file, size_t len);
|
||||
bool loadFC(unsigned char* file, size_t len);
|
||||
|
||||
|
|
@ -493,6 +506,10 @@ class DivEngine {
|
|||
// check whether an asset directory is complete
|
||||
void checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries);
|
||||
|
||||
// add every export method here
|
||||
friend class DivROMExport;
|
||||
friend class DivExportAmigaValidation;
|
||||
|
||||
public:
|
||||
DivSong song;
|
||||
DivOrders* curOrders;
|
||||
|
|
@ -522,6 +539,8 @@ class DivEngine {
|
|||
void createNewFromDefaults();
|
||||
// load a file.
|
||||
bool load(unsigned char* f, size_t length);
|
||||
// play a binary command stream.
|
||||
bool playStream(unsigned char* f, size_t length);
|
||||
// save as .dmf.
|
||||
SafeWriter* saveDMF(unsigned char version);
|
||||
// save as .fur.
|
||||
|
|
@ -529,7 +548,7 @@ class DivEngine {
|
|||
SafeWriter* saveFur(bool notPrimary=false);
|
||||
// build a ROM file (TODO).
|
||||
// specify system to build ROM for.
|
||||
SafeWriter* buildROM(int sys);
|
||||
std::vector<DivROMExportOutput> buildROM(DivROMExportOptions sys);
|
||||
// dump to VGM.
|
||||
// set trailingTicks to:
|
||||
// - 0 to add one tick of trailing
|
||||
|
|
@ -552,6 +571,9 @@ class DivEngine {
|
|||
// notify wavetable change
|
||||
void notifyWaveChange(int wave);
|
||||
|
||||
// dispatch a command
|
||||
int dispatchCmd(DivCommand c);
|
||||
|
||||
// get system IDs
|
||||
static DivSystem systemFromFileFur(unsigned char val);
|
||||
static unsigned char systemToFileFur(DivSystem val);
|
||||
|
|
@ -901,6 +923,9 @@ class DivEngine {
|
|||
// get macro interpreter
|
||||
DivMacroInt* getMacroInt(int chan);
|
||||
|
||||
// get sample position
|
||||
DivSamplePos getSamplePos(int chan);
|
||||
|
||||
// get osc buffer
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
|
||||
|
|
@ -1094,6 +1119,7 @@ class DivEngine {
|
|||
systemsRegistered(false),
|
||||
hasLoadedSomething(false),
|
||||
midiOutClock(false),
|
||||
midiOutProgramChange(false),
|
||||
midiOutMode(DIV_MIDI_MODE_NOTE),
|
||||
softLockCount(0),
|
||||
subticks(0),
|
||||
|
|
@ -1133,6 +1159,7 @@ class DivEngine {
|
|||
audioEngine(DIV_AUDIO_NULL),
|
||||
exportMode(DIV_EXPORT_MODE_ONE),
|
||||
exportFadeOut(0.0),
|
||||
cmdStreamInt(NULL),
|
||||
midiBaseChan(0),
|
||||
midiPoly(true),
|
||||
midiAgeCounter(0),
|
||||
|
|
|
|||
37
src/engine/export.cpp
Normal file
37
src/engine/export.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 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 "engine.h"
|
||||
|
||||
#include "export/amigaValidation.h"
|
||||
|
||||
std::vector<DivROMExportOutput> DivEngine::buildROM(DivROMExportOptions sys) {
|
||||
DivROMExport* exporter=NULL;
|
||||
switch (sys) {
|
||||
case DIV_ROM_AMIGA_VALIDATION:
|
||||
exporter=new DivExportAmigaValidation;
|
||||
break;
|
||||
default:
|
||||
exporter=new DivROMExport;
|
||||
break;
|
||||
}
|
||||
std::vector<DivROMExportOutput> ret=exporter->go(this);
|
||||
delete exporter;
|
||||
return ret;
|
||||
}
|
||||
75
src/engine/export.h
Normal file
75
src/engine/export.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 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 _EXPORT_H
|
||||
#define _EXPORT_H
|
||||
|
||||
#include "song.h"
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
class DivEngine;
|
||||
|
||||
enum DivROMExportOptions {
|
||||
DIV_ROM_ABSTRACT=0,
|
||||
DIV_ROM_AMIGA_VALIDATION,
|
||||
|
||||
DIV_ROM_MAX
|
||||
};
|
||||
|
||||
struct DivROMExportOutput {
|
||||
String name;
|
||||
SafeWriter* data;
|
||||
|
||||
DivROMExportOutput(String n, SafeWriter* d):
|
||||
name(n),
|
||||
data(d) {}
|
||||
DivROMExportOutput():
|
||||
name(""),
|
||||
data(NULL) {}
|
||||
};
|
||||
|
||||
class DivROMExport {
|
||||
public:
|
||||
virtual std::vector<DivROMExportOutput> go(DivEngine* e);
|
||||
virtual ~DivROMExport() {}
|
||||
};
|
||||
|
||||
struct DivROMExportDef {
|
||||
const char* name;
|
||||
const char* author;
|
||||
const char* description;
|
||||
DivSystem requisites[32];
|
||||
int requisitesLen;
|
||||
bool multiOutput;
|
||||
|
||||
DivROMExportDef(const char* n, const char* a, const char* d, std::initializer_list<DivSystem> req, bool multiOut):
|
||||
name(n),
|
||||
author(a),
|
||||
description(d),
|
||||
multiOutput(multiOut) {
|
||||
requisitesLen=0;
|
||||
memset(requisites,0,32*sizeof(DivSystem));
|
||||
for (DivSystem i: req) {
|
||||
requisites[requisitesLen++]=i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
26
src/engine/export/abstract.cpp
Normal file
26
src/engine/export/abstract.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 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 "../export.h"
|
||||
#include "../../ta-log.h"
|
||||
|
||||
std::vector<DivROMExportOutput> DivROMExport::go(DivEngine* e) {
|
||||
logW("what's this? the null ROM export?");
|
||||
return std::vector<DivROMExportOutput>();
|
||||
}
|
||||
276
src/engine/export/amigaValidation.cpp
Normal file
276
src/engine/export/amigaValidation.cpp
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 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 "amigaValidation.h"
|
||||
#include "../engine.h"
|
||||
#include "../platform/amiga.h"
|
||||
|
||||
struct WaveEntry {
|
||||
unsigned int pos;
|
||||
short width;
|
||||
signed char data[256];
|
||||
WaveEntry():
|
||||
pos(0),
|
||||
width(32) {
|
||||
memset(data,0,256);
|
||||
}
|
||||
};
|
||||
|
||||
struct SampleBookEntry {
|
||||
unsigned int loc;
|
||||
unsigned short len;
|
||||
SampleBookEntry():
|
||||
loc(0),
|
||||
len(0) {}
|
||||
};
|
||||
|
||||
std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
|
||||
std::vector<DivROMExportOutput> ret;
|
||||
std::vector<WaveEntry> waves;
|
||||
std::vector<SampleBookEntry> sampleBook;
|
||||
unsigned int wavesDataPtr=0;
|
||||
WaveEntry curWaveState[4];
|
||||
unsigned int sampleBookLoc=0;
|
||||
|
||||
DivPlatformAmiga* amiga=(DivPlatformAmiga*)e->getDispatch(0);
|
||||
|
||||
e->stop();
|
||||
e->repeatPattern=false;
|
||||
e->setOrder(0);
|
||||
EXTERN_BUSY_BEGIN_SOFT;
|
||||
|
||||
// determine loop point
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
|
||||
e->curOrder=0;
|
||||
e->freelance=false;
|
||||
e->playing=false;
|
||||
e->extValuePresent=false;
|
||||
e->remainingLoops=-1;
|
||||
|
||||
// play the song ourselves
|
||||
bool done=false;
|
||||
|
||||
// sample.bin
|
||||
SafeWriter* sample=new SafeWriter;
|
||||
sample->init();
|
||||
for (int i=0; i<256; i++) {
|
||||
sample->writeI(0);
|
||||
}
|
||||
sample->write(&((const unsigned char*)amiga->getSampleMem(0))[0x400],amiga->getSampleMemUsage(0)-0x400);
|
||||
if (sample->tell()&1) sample->writeC(0);
|
||||
|
||||
// seq.bin
|
||||
SafeWriter* seq=new SafeWriter;
|
||||
seq->init();
|
||||
|
||||
amiga->toggleRegisterDump(true);
|
||||
|
||||
// write song data
|
||||
e->playSub(false);
|
||||
size_t songTick=0;
|
||||
size_t lastTick=0;
|
||||
//bool writeLoop=false;
|
||||
int loopPos=-1;
|
||||
for (int i=0; i<e->chans; i++) {
|
||||
e->chan[i].wentThroughNote=false;
|
||||
e->chan[i].goneThroughNote=false;
|
||||
}
|
||||
while (!done) {
|
||||
if (loopPos==-1) {
|
||||
if (loopOrder==e->curOrder && loopRow==e->curRow && e->ticks==1) {
|
||||
//writeLoop=true;
|
||||
}
|
||||
}
|
||||
if (e->nextTick(false,true)) {
|
||||
done=true;
|
||||
amiga->getRegisterWrites().clear();
|
||||
if (lastTick!=songTick) {
|
||||
int delta=songTick-lastTick;
|
||||
if (delta==1) {
|
||||
seq->writeC(0xf1);
|
||||
} else if (delta<256) {
|
||||
seq->writeC(0xf2);
|
||||
seq->writeC(delta-1);
|
||||
} else if (delta<32768) {
|
||||
seq->writeC(0xf3);
|
||||
seq->writeS_BE(delta-1);
|
||||
}
|
||||
lastTick=songTick;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// check wavetable changes
|
||||
for (int i=0; i<4; i++) {
|
||||
if (amiga->chan[i].useWave) {
|
||||
if ((amiga->chan[i].audLen*2)!=curWaveState[i].width || memcmp(curWaveState[i].data,&(((signed char*)amiga->getSampleMem())[i<<8]),amiga->chan[i].audLen*2)!=0) {
|
||||
curWaveState[i].width=amiga->chan[i].audLen*2;
|
||||
memcpy(curWaveState[i].data,&(((signed char*)amiga->getSampleMem())[i<<8]),amiga->chan[i].audLen*2);
|
||||
|
||||
int waveNum=-1;
|
||||
for (size_t j=0; j<waves.size(); j++) {
|
||||
if (waves[j].width!=curWaveState[i].width) continue;
|
||||
if (memcmp(waves[j].data,curWaveState[i].data,curWaveState[i].width)==0) {
|
||||
waveNum=j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (waveNum==-1) {
|
||||
// write new wavetable
|
||||
waveNum=(int)waves.size();
|
||||
curWaveState[i].pos=wavesDataPtr;
|
||||
waves.push_back(curWaveState[i]);
|
||||
wavesDataPtr+=curWaveState[i].width;
|
||||
}
|
||||
|
||||
if (waveNum<256) {
|
||||
seq->writeC((i<<4)|3);
|
||||
seq->writeC(waveNum);
|
||||
} else if (waveNum<65536) {
|
||||
seq->writeC((i<<4)|4);
|
||||
seq->writeS_BE(waveNum);
|
||||
} else{
|
||||
seq->writeC((i<<4)|1);
|
||||
seq->writeC(waves[waveNum].pos>>16);
|
||||
seq->writeC(waves[waveNum].pos>>8);
|
||||
seq->writeC(waves[waveNum].pos);
|
||||
seq->writeS_BE(waves[waveNum].width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get register writes
|
||||
std::vector<DivRegWrite>& writes=amiga->getRegisterWrites();
|
||||
for (DivRegWrite& j: writes) {
|
||||
if (lastTick!=songTick) {
|
||||
int delta=songTick-lastTick;
|
||||
if (delta==1) {
|
||||
seq->writeC(0xf1);
|
||||
} else if (delta<256) {
|
||||
seq->writeC(0xf2);
|
||||
seq->writeC(delta-1);
|
||||
} else if (delta<32768) {
|
||||
seq->writeC(0xf3);
|
||||
seq->writeS_BE(delta-1);
|
||||
}
|
||||
lastTick=songTick;
|
||||
}
|
||||
if (j.addr>=0x200) { // direct loc/len change
|
||||
if (j.addr&4) { // len
|
||||
int sampleBookIndex=-1;
|
||||
for (size_t i=0; i<sampleBook.size(); i++) {
|
||||
if (sampleBook[i].loc==sampleBookLoc && sampleBook[i].len==(j.val&0xffff)) {
|
||||
sampleBookIndex=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sampleBookIndex==-1) {
|
||||
if (sampleBook.size()<256) {
|
||||
SampleBookEntry e;
|
||||
e.loc=sampleBookLoc;
|
||||
e.len=j.val&0xffff;
|
||||
sampleBookIndex=sampleBook.size();
|
||||
sampleBook.push_back(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleBookIndex==-1) {
|
||||
seq->writeC((j.addr&3)<<4);
|
||||
seq->writeC(sampleBookLoc>>16);
|
||||
seq->writeC(sampleBookLoc>>8);
|
||||
seq->writeC(sampleBookLoc);
|
||||
seq->writeS_BE(j.val);
|
||||
} else {
|
||||
seq->writeC(((j.addr&3)<<4)|2);
|
||||
seq->writeC(sampleBookIndex);
|
||||
}
|
||||
} else { // loc
|
||||
sampleBookLoc=j.val;
|
||||
}
|
||||
} else if (j.addr<0xa0) {
|
||||
// don't write INTENA
|
||||
if ((j.addr&15)!=10) {
|
||||
seq->writeC(0xf0|(j.addr&15));
|
||||
seq->writeS_BE(j.val);
|
||||
}
|
||||
} else if ((j.addr&15)!=0 && (j.addr&15)!=2 && (j.addr&15)!=4) {
|
||||
seq->writeC(j.addr-0xa0);
|
||||
if ((j.addr&15)==8) {
|
||||
seq->writeC(j.val);
|
||||
} else {
|
||||
seq->writeS_BE(j.val);
|
||||
}
|
||||
}
|
||||
}
|
||||
writes.clear();
|
||||
|
||||
songTick++;
|
||||
}
|
||||
// end of song
|
||||
seq->writeC(0xff);
|
||||
|
||||
amiga->toggleRegisterDump(false);
|
||||
|
||||
e->remainingLoops=-1;
|
||||
e->playing=false;
|
||||
e->freelance=false;
|
||||
e->extValuePresent=false;
|
||||
|
||||
EXTERN_BUSY_END;
|
||||
|
||||
// wave.bin
|
||||
SafeWriter* wave=new SafeWriter;
|
||||
wave->init();
|
||||
for (WaveEntry& i: waves) {
|
||||
wave->write(i.data,i.width);
|
||||
}
|
||||
|
||||
// sbook.bin
|
||||
SafeWriter* sbook=new SafeWriter;
|
||||
sbook->init();
|
||||
for (SampleBookEntry& i: sampleBook) {
|
||||
// 8 bytes per entry
|
||||
sbook->writeI_BE(i.loc);
|
||||
sbook->writeI_BE(i.len);
|
||||
}
|
||||
|
||||
// wbook.bin
|
||||
SafeWriter* wbook=new SafeWriter;
|
||||
wbook->init();
|
||||
for (WaveEntry& i: waves) {
|
||||
wbook->writeC(i.width);
|
||||
wbook->writeC(i.pos>>16);
|
||||
wbook->writeC(i.pos>>8);
|
||||
wbook->writeC(i.pos);
|
||||
}
|
||||
|
||||
// finish
|
||||
ret.push_back(DivROMExportOutput("sbook.bin",sbook));
|
||||
ret.push_back(DivROMExportOutput("wbook.bin",wbook));
|
||||
ret.push_back(DivROMExportOutput("sample.bin",sample));
|
||||
ret.push_back(DivROMExportOutput("wave.bin",wave));
|
||||
ret.push_back(DivROMExportOutput("seq.bin",seq));
|
||||
|
||||
return ret;
|
||||
}
|
||||
26
src/engine/export/amigaValidation.h
Normal file
26
src/engine/export/amigaValidation.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 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 "../export.h"
|
||||
|
||||
class DivExportAmigaValidation: public DivROMExport {
|
||||
public:
|
||||
std::vector<DivROMExportOutput> go(DivEngine* e);
|
||||
~DivExportAmigaValidation() {}
|
||||
};
|
||||
|
|
@ -30,6 +30,7 @@
|
|||
#define DIV_FTM_MAGIC "FamiTracker Module"
|
||||
#define DIV_FC13_MAGIC "SMOD"
|
||||
#define DIV_FC14_MAGIC "FC14"
|
||||
#define DIV_S3M_MAGIC "SCRM"
|
||||
|
||||
struct InflateBlock {
|
||||
unsigned char* buf;
|
||||
|
|
@ -3226,6 +3227,246 @@ void generateFCPresetWave(int index, DivWavetable* wave) {
|
|||
}
|
||||
}
|
||||
|
||||
bool DivEngine::loadS3M(unsigned char* file, size_t len) {
|
||||
struct InvalidHeaderException {};
|
||||
bool success=false;
|
||||
char magic[4]={0,0,0,0};
|
||||
SafeReader reader=SafeReader(file,len);
|
||||
warnings="";
|
||||
|
||||
unsigned char chanSettings[32];
|
||||
unsigned char ord[256];
|
||||
unsigned short insPtr[256];
|
||||
unsigned short patPtr[256];
|
||||
unsigned char chanPan[16];
|
||||
unsigned char defVol[256];
|
||||
|
||||
try {
|
||||
DivSong ds;
|
||||
ds.version=DIV_VERSION_S3M;
|
||||
ds.linearPitch=0;
|
||||
ds.pitchMacroIsLinear=false;
|
||||
ds.noSlidesOnFirstTick=true;
|
||||
ds.rowResetsArpPos=true;
|
||||
ds.ignoreJumpAtEnd=false;
|
||||
|
||||
// load here
|
||||
if (!reader.seek(0x2c,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
reader.read(magic,4);
|
||||
|
||||
if (memcmp(magic,DIV_S3M_MAGIC,4)!=0) {
|
||||
logW("the magic isn't complete");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
|
||||
if (!reader.seek(0,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
ds.name=reader.readString(28);
|
||||
|
||||
reader.readC(); // 0x1a
|
||||
if (reader.readC()!=16) {
|
||||
logW("type is wrong!");
|
||||
}
|
||||
reader.readS(); // x
|
||||
|
||||
unsigned short ordersLen=reader.readS();
|
||||
ds.insLen=reader.readS();
|
||||
|
||||
if (ds.insLen<0 || ds.insLen>256) {
|
||||
logE("invalid instrument count!");
|
||||
lastError="invalid instrument count!";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned short patCount=reader.readS();
|
||||
|
||||
unsigned short flags=reader.readS();
|
||||
unsigned short version=reader.readS();
|
||||
bool signedSamples=(reader.readS()==1);
|
||||
|
||||
if ((flags&64) || version==0x1300) {
|
||||
ds.noSlidesOnFirstTick=false;
|
||||
}
|
||||
|
||||
reader.readI(); // "SCRM"
|
||||
|
||||
unsigned char globalVol=reader.readC();
|
||||
|
||||
ds.subsong[0]->speeds.val[0]=(unsigned char)reader.readC();
|
||||
ds.subsong[0]->hz=((double)reader.readC())/2.5;
|
||||
ds.subsong[0]->customTempo=true;
|
||||
|
||||
unsigned char masterVol=reader.readC();
|
||||
|
||||
logV("masterVol: %d",masterVol);
|
||||
logV("signedSamples: %d",signedSamples);
|
||||
logV("globalVol: %d",globalVol);
|
||||
|
||||
reader.readC(); // UC
|
||||
bool defaultPan=(((unsigned char)reader.readC())==252);
|
||||
|
||||
reader.readS(); // reserved
|
||||
reader.readI();
|
||||
reader.readI(); // the last 2 bytes is Special. we don't read that.
|
||||
|
||||
reader.read(chanSettings,32);
|
||||
|
||||
logD("reading orders...");
|
||||
for (int i=0; i<ordersLen; i++) {
|
||||
ord[i]=reader.readC();
|
||||
logV("- %.2x",ord[i]);
|
||||
}
|
||||
// should be even
|
||||
if (ordersLen&1) reader.readC();
|
||||
|
||||
logD("reading ins pointers...");
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
insPtr[i]=reader.readS();
|
||||
logV("- %.2x",insPtr[i]);
|
||||
}
|
||||
|
||||
logD("reading pat pointers...");
|
||||
for (int i=0; i<patCount; i++) {
|
||||
patPtr[i]=reader.readS();
|
||||
logV("- %.2x",patPtr[i]);
|
||||
}
|
||||
|
||||
if (defaultPan) {
|
||||
reader.read(chanPan,16);
|
||||
} else {
|
||||
memset(chanPan,0,16);
|
||||
}
|
||||
|
||||
// determine chips to use
|
||||
ds.systemLen=0;
|
||||
|
||||
bool hasPCM=false;
|
||||
bool hasFM=false;
|
||||
|
||||
for (int i=0; i<32; i++) {
|
||||
if (!(chanSettings[i]&128)) continue;
|
||||
if ((chanSettings[i]&127)>=32) continue;
|
||||
if ((chanSettings[i]&127)>=16) {
|
||||
hasFM=true;
|
||||
} else {
|
||||
hasPCM=true;
|
||||
}
|
||||
|
||||
if (hasFM && hasPCM) break;
|
||||
}
|
||||
|
||||
ds.systemName="PC";
|
||||
if (hasPCM) {
|
||||
ds.system[ds.systemLen]=DIV_SYSTEM_ES5506;
|
||||
ds.systemVol[ds.systemLen]=1.0f;
|
||||
ds.systemPan[ds.systemLen]=0;
|
||||
ds.systemLen++;
|
||||
}
|
||||
if (hasFM) {
|
||||
ds.system[ds.systemLen]=DIV_SYSTEM_OPL2;
|
||||
ds.systemVol[ds.systemLen]=1.0f;
|
||||
ds.systemPan[ds.systemLen]=0;
|
||||
ds.systemLen++;
|
||||
}
|
||||
|
||||
// load instruments/samples
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
if (!reader.seek(0x4c+insPtr[i]*16,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete ins;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.read(magic,4);
|
||||
|
||||
if (memcmp(magic,"SCRS",4)==0) {
|
||||
ins->type=DIV_INS_ES5506;
|
||||
} else if (memcmp(magic,"SCRI",4)==0) {
|
||||
ins->type=DIV_INS_OPL;
|
||||
} else {
|
||||
ins->type=DIV_INS_ES5506;
|
||||
ds.ins.push_back(ins);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!reader.seek(insPtr[i]*16,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
delete ins;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
String dosName=reader.readString(13);
|
||||
|
||||
if (ins->type==DIV_INS_ES5506) {
|
||||
unsigned int memSeg=0;
|
||||
memSeg=(unsigned char)reader.readC();
|
||||
memSeg|=((unsigned short)reader.readS())<<8;
|
||||
|
||||
logV("memSeg: %d",memSeg);
|
||||
|
||||
unsigned int length=reader.readI();
|
||||
|
||||
DivSample* s=new DivSample;
|
||||
s->depth=DIV_SAMPLE_DEPTH_8BIT;
|
||||
s->init(length);
|
||||
|
||||
s->loopStart=reader.readI();
|
||||
s->loopEnd=reader.readI();
|
||||
defVol[i]=reader.readC();
|
||||
|
||||
logV("defVol: %d",defVol[i]);
|
||||
|
||||
reader.readC(); // x
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
ds.ins.push_back(ins);
|
||||
}
|
||||
|
||||
if (active) quitDispatch();
|
||||
BUSY_BEGIN_SOFT;
|
||||
saveLock.lock();
|
||||
song.unload();
|
||||
song=ds;
|
||||
changeSong(0);
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
if (active) {
|
||||
initDispatch();
|
||||
BUSY_BEGIN;
|
||||
renderSamples();
|
||||
reset();
|
||||
BUSY_END;
|
||||
}
|
||||
success=true;
|
||||
} catch (EndOfFileException& e) {
|
||||
//logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
} catch (InvalidHeaderException& e) {
|
||||
//logE("invalid header!");
|
||||
lastError="invalid header!";
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
||||
struct InvalidHeaderException {};
|
||||
bool success=false;
|
||||
|
|
@ -3820,12 +4061,58 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
|
||||
#define CHECK_BLOCK_VERSION(x) \
|
||||
if (blockVersion>x) { \
|
||||
logE("incompatible block version %d for %s!",blockVersion,blockName); \
|
||||
lastError="incompatible block version"; \
|
||||
delete[] file; \
|
||||
return false; \
|
||||
logW("incompatible block version %d for %s!",blockVersion,blockName); \
|
||||
}
|
||||
|
||||
const int ftEffectMap[]={
|
||||
-1, // none
|
||||
0x0f,
|
||||
0x0b,
|
||||
0x0d,
|
||||
0xff,
|
||||
-1, // volume? not supported in Furnace yet
|
||||
0x03,
|
||||
0x03, // unused?
|
||||
0x13,
|
||||
0x14,
|
||||
0x00,
|
||||
0x04,
|
||||
0x07,
|
||||
0xe5,
|
||||
0xed,
|
||||
0x11,
|
||||
0x01, // porta up
|
||||
0x02, // porta down
|
||||
0x12,
|
||||
0x90, // sample offset - not supported yet
|
||||
0xe1,
|
||||
0xe2,
|
||||
0x0a,
|
||||
0xec,
|
||||
0x0c,
|
||||
-1, // delayed volume - not supported yet
|
||||
0x11, // FDS
|
||||
0x12,
|
||||
0x13,
|
||||
0x20, // DPCM pitch
|
||||
0x22, // 5B
|
||||
0x24,
|
||||
0x23,
|
||||
0x21,
|
||||
-1, // VRC7 "custom patch port" - not supported?
|
||||
-1, // VRC7 "custom patch write"
|
||||
-1, // release - not supported yet
|
||||
0x09, // select groove
|
||||
-1, // transpose - not supported
|
||||
0x10, // Namco 163
|
||||
-1, // FDS vol env - not supported
|
||||
-1, // FDS auto FM - not supported yet
|
||||
-1, // phase reset - not supported
|
||||
-1, // harmonic - not supported
|
||||
};
|
||||
|
||||
constexpr int ftEffectMapSize=sizeof(ftEffectMap)/sizeof(int);
|
||||
|
||||
bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||
SafeReader reader=SafeReader(file,len);
|
||||
warnings="";
|
||||
|
|
@ -3837,6 +4124,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
unsigned int n163Chans=0;
|
||||
bool hasSequence[256][8];
|
||||
unsigned char sequenceIndex[256][8];
|
||||
unsigned int hilightA=4;
|
||||
unsigned int hilightB=16;
|
||||
double customHz=60;
|
||||
|
||||
memset(hasSequence,0,256*8*sizeof(bool));
|
||||
memset(sequenceIndex,0,256*8);
|
||||
|
|
@ -3857,6 +4147,14 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
return false;
|
||||
}
|
||||
|
||||
for (DivSubSong* i: ds.subsong) {
|
||||
i->clearData();
|
||||
delete i;
|
||||
}
|
||||
ds.subsong.clear();
|
||||
|
||||
ds.linearPitch=0;
|
||||
|
||||
while (true) {
|
||||
blockName=reader.readString(3);
|
||||
if (blockName=="END") {
|
||||
|
|
@ -3874,7 +4172,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
|
||||
logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize);
|
||||
if (blockName=="PARAMS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
// versions 7-9 don't change anything?
|
||||
CHECK_BLOCK_VERSION(9);
|
||||
unsigned int oldSpeedTempo=0;
|
||||
if (blockVersion<=1) {
|
||||
oldSpeedTempo=reader.readI();
|
||||
|
|
@ -3884,15 +4183,32 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
}
|
||||
tchans=reader.readI();
|
||||
unsigned int pal=reader.readI();
|
||||
unsigned int customHz=reader.readI();
|
||||
if (blockVersion>=7) {
|
||||
// advanced Hz control
|
||||
int controlType=reader.readI();
|
||||
switch (controlType) {
|
||||
case 1:
|
||||
customHz=1000000.0/(double)reader.readI();
|
||||
break;
|
||||
default:
|
||||
reader.readI();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
customHz=reader.readI();
|
||||
}
|
||||
unsigned int newVibrato=0;
|
||||
bool sweepReset=false;
|
||||
unsigned int speedSplitPoint=0;
|
||||
if (blockVersion>=3) {
|
||||
newVibrato=reader.readI();
|
||||
}
|
||||
if (blockVersion>=4) {
|
||||
ds.subsong[0]->hilightA=reader.readI();
|
||||
ds.subsong[0]->hilightB=reader.readI();
|
||||
if (blockVersion>=9) {
|
||||
sweepReset=reader.readI();
|
||||
}
|
||||
if (blockVersion>=4 && blockVersion<7) {
|
||||
hilightA=reader.readI();
|
||||
hilightB=reader.readI();
|
||||
}
|
||||
if (expansions&8) if (blockVersion>=5) { // N163 channels
|
||||
n163Chans=reader.readI();
|
||||
|
|
@ -3901,20 +4217,24 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
speedSplitPoint=reader.readI();
|
||||
}
|
||||
|
||||
if (blockVersion>=8) {
|
||||
int fineTuneCents=reader.readC()*100;
|
||||
fineTuneCents+=reader.readC();
|
||||
|
||||
ds.tuning=440.0*pow(2.0,(double)fineTuneCents/1200.0);
|
||||
}
|
||||
|
||||
logV("old speed/tempo: %d",oldSpeedTempo);
|
||||
logV("expansions: %x",expansions);
|
||||
logV("channels: %d",tchans);
|
||||
logV("PAL: %d",pal);
|
||||
logV("custom Hz: %d",customHz);
|
||||
logV("custom Hz: %f",customHz);
|
||||
logV("new vibrato: %d",newVibrato);
|
||||
logV("N163 channels: %d",n163Chans);
|
||||
logV("highlight 1: %d",ds.subsong[0]->hilightA);
|
||||
logV("highlight 2: %d",ds.subsong[0]->hilightB);
|
||||
logV("highlight 1: %d",hilightA);
|
||||
logV("highlight 2: %d",hilightB);
|
||||
logV("split point: %d",speedSplitPoint);
|
||||
|
||||
if (customHz!=0) {
|
||||
ds.subsong[0]->hz=customHz;
|
||||
}
|
||||
logV("sweep reset: %d",sweepReset);
|
||||
|
||||
// initialize channels
|
||||
int systemID=0;
|
||||
|
|
@ -3959,28 +4279,46 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
CHECK_BLOCK_VERSION(1);
|
||||
ds.name=reader.readString(32);
|
||||
ds.author=reader.readString(32);
|
||||
ds.copyright=reader.readString(32);
|
||||
ds.category=reader.readString(32);
|
||||
ds.systemName="NES";
|
||||
} else if (blockName=="HEADER") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
CHECK_BLOCK_VERSION(4);
|
||||
unsigned char totalSongs=reader.readC();
|
||||
logV("%d songs:",totalSongs+1);
|
||||
for (int i=0; i<=totalSongs; i++) {
|
||||
String subSongName=reader.readString();
|
||||
ds.subsong.push_back(new DivSubSong);
|
||||
ds.subsong[i]->name=subSongName;
|
||||
ds.subsong[i]->hilightA=hilightA;
|
||||
ds.subsong[i]->hilightB=hilightB;
|
||||
if (customHz!=0) {
|
||||
ds.subsong[i]->hz=customHz;
|
||||
}
|
||||
logV("- %s",subSongName);
|
||||
}
|
||||
for (unsigned int i=0; i<tchans; i++) {
|
||||
// TODO: obey channel ID
|
||||
unsigned char chID=reader.readC();
|
||||
logV("for channel ID %d",chID);
|
||||
for (int j=0; j<=totalSongs; j++) {
|
||||
unsigned char effectCols=reader.readC();
|
||||
if (j==0) {
|
||||
ds.subsong[0]->pat[i].effectCols=effectCols+1;
|
||||
}
|
||||
ds.subsong[j]->pat[i].effectCols=effectCols+1;
|
||||
logV("- song %d has %d effect columns",j,effectCols);
|
||||
}
|
||||
}
|
||||
|
||||
if (blockVersion>=4) {
|
||||
for (int i=0; i<=totalSongs; i++) {
|
||||
ds.subsong[i]->hilightA=(unsigned char)reader.readC();
|
||||
ds.subsong[i]->hilightB=(unsigned char)reader.readC();
|
||||
}
|
||||
}
|
||||
} else if (blockName=="INSTRUMENTS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
|
||||
/*
|
||||
ds.insLen=reader.readI();
|
||||
if (ds.insLen<0 || ds.insLen>256) {
|
||||
logE("too many instruments/out of range!");
|
||||
|
|
@ -4140,21 +4478,131 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
ins->name=reader.readString((unsigned int)reader.readI());
|
||||
logV("- %d: %s",insIndex,ins->name);
|
||||
}
|
||||
*/
|
||||
} else if (blockName=="SEQUENCES") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="FRAMES") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
|
||||
for (size_t i=0; i<ds.subsong.size(); i++) {
|
||||
DivSubSong* s=ds.subsong[i];
|
||||
|
||||
s->ordersLen=reader.readI();
|
||||
if (blockVersion>=3) {
|
||||
s->speeds.val[0]=reader.readI();
|
||||
}
|
||||
if (blockVersion>=2) {
|
||||
s->virtualTempoN=reader.readI();
|
||||
s->patLen=reader.readI();
|
||||
}
|
||||
int why=tchans;
|
||||
if (blockVersion==1) {
|
||||
why=reader.readI();
|
||||
}
|
||||
logV("reading %d and %d orders",tchans,s->ordersLen);
|
||||
|
||||
for (int j=0; j<s->ordersLen; j++) {
|
||||
for (int k=0; k<why; k++) {
|
||||
unsigned char o=reader.readC();
|
||||
logV("%.2x",o);
|
||||
s->orders.ord[k][j]=o;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (blockName=="PATTERNS") {
|
||||
CHECK_BLOCK_VERSION(5);
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
|
||||
size_t blockEnd=reader.tell()+blockSize;
|
||||
|
||||
if (blockVersion==1) {
|
||||
int patLenOld=reader.readI();
|
||||
for (DivSubSong* i: ds.subsong) {
|
||||
i->patLen=patLenOld;
|
||||
}
|
||||
}
|
||||
|
||||
// so it appears .ftm doesn't keep track of how many patterns are stored in the file....
|
||||
while (reader.tell()<blockEnd) {
|
||||
int subs=0;
|
||||
if (blockVersion>=2) subs=reader.readI();
|
||||
int ch=reader.readI();
|
||||
int patNum=reader.readI();
|
||||
int numRows=reader.readI();
|
||||
|
||||
DivPattern* pat=ds.subsong[subs]->pat[ch].getPattern(patNum,true);
|
||||
for (int i=0; i<numRows; i++) {
|
||||
unsigned int row=0;
|
||||
if (blockVersion>=2 && blockVersion<6) { // row index
|
||||
row=reader.readI();
|
||||
} else {
|
||||
row=reader.readC();
|
||||
}
|
||||
|
||||
unsigned char nextNote=reader.readC();
|
||||
unsigned char nextOctave=reader.readC();
|
||||
if (nextNote==0x0d) {
|
||||
pat->data[row][0]=100;
|
||||
} else if (nextNote==0x0e) {
|
||||
pat->data[row][0]=101;
|
||||
} else if (nextNote==0x01) {
|
||||
pat->data[row][0]=12;
|
||||
pat->data[row][1]=nextOctave-1;
|
||||
} else if (nextNote==0) {
|
||||
pat->data[row][0]=0;
|
||||
} else if (nextNote<0x0d) {
|
||||
pat->data[row][0]=nextNote-1;
|
||||
pat->data[row][1]=nextOctave;
|
||||
}
|
||||
|
||||
unsigned char nextIns=reader.readC();
|
||||
if (nextIns<0x40) {
|
||||
pat->data[row][2]=nextIns;
|
||||
} else {
|
||||
pat->data[row][2]=-1;
|
||||
}
|
||||
|
||||
unsigned char nextVol=reader.readC();
|
||||
if (nextVol<0x10) {
|
||||
pat->data[row][3]=nextVol;
|
||||
} else {
|
||||
pat->data[row][3]=-1;
|
||||
}
|
||||
|
||||
int effectCols=ds.subsong[subs]->pat[ch].effectCols;
|
||||
if (blockVersion>=6) effectCols=4;
|
||||
|
||||
for (int j=0; j<effectCols; j++) {
|
||||
unsigned char nextEffect=reader.readC();
|
||||
unsigned char nextEffectVal=0;
|
||||
if (nextEffect!=0 || blockVersion<6) nextEffectVal=reader.readC();
|
||||
if (nextEffect==0 && nextEffectVal==0) {
|
||||
pat->data[row][4+(j*2)]=-1;
|
||||
pat->data[row][5+(j*2)]=-1;
|
||||
} else {
|
||||
if (nextEffect<ftEffectMapSize) {
|
||||
pat->data[row][4+(j*2)]=ftEffectMap[nextEffect];
|
||||
} else {
|
||||
pat->data[row][4+(j*2)]=-1;
|
||||
}
|
||||
pat->data[row][5+(j*2)]=nextEffectVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (blockName=="DPCM SAMPLES") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="SEQUENCES_VRC6") {
|
||||
// where are the 5B and FDS sequences?
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="SEQUENCES_N163") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="COMMENTS") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else {
|
||||
logE("block %s is unknown!",blockName);
|
||||
lastError="unknown block "+blockName;
|
||||
|
|
@ -4169,6 +4617,27 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
addWarning("FamiTracker import is experimental!");
|
||||
|
||||
ds.version=DIV_VERSION_FTM;
|
||||
|
||||
if (active) quitDispatch();
|
||||
BUSY_BEGIN_SOFT;
|
||||
saveLock.lock();
|
||||
song.unload();
|
||||
song=ds;
|
||||
changeSong(0);
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
if (active) {
|
||||
initDispatch();
|
||||
BUSY_BEGIN;
|
||||
renderSamples();
|
||||
reset();
|
||||
BUSY_END;
|
||||
}
|
||||
} catch (EndOfFileException& e) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include "macroInt.h"
|
||||
#include "instrument.h"
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
|
||||
#define ADSR_LOW source.val[0]
|
||||
#define ADSR_HIGH source.val[1]
|
||||
|
|
@ -52,6 +53,7 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
|
|||
}
|
||||
if (masked) {
|
||||
had=false;
|
||||
has=false;
|
||||
return;
|
||||
}
|
||||
if (delay>0) {
|
||||
|
|
@ -246,8 +248,10 @@ void DivMacroInt::setEngine(DivEngine* eng) {
|
|||
}
|
||||
|
||||
#define ADD_MACRO(m,s) \
|
||||
macroList[macroListLen]=&m; \
|
||||
macroSource[macroListLen++]=&s;
|
||||
if (!m.masked) { \
|
||||
macroList[macroListLen]=&m; \
|
||||
macroSource[macroListLen++]=&s; \
|
||||
}
|
||||
|
||||
void DivMacroInt::init(DivInstrument* which) {
|
||||
ins=which;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ DivMacroInt* DivDispatch::getChanMacroInt(int chan) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
DivSamplePos DivDispatch::getSamplePos(int chan) {
|
||||
return DivSamplePos();
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivDispatch::getOscBuffer(int chan) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,6 +213,10 @@ void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) {
|
|||
//logV("%.3x = %.4x",addr,val);
|
||||
regPool[addr>>1]=val;
|
||||
|
||||
if (!skipRegisterWrites && dumpWrites) {
|
||||
addWrite(addr,val);
|
||||
}
|
||||
|
||||
switch (addr&0x1fe) {
|
||||
case 0x96: { // DMACON
|
||||
if (val&32768) {
|
||||
|
|
@ -400,6 +404,29 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned short dmaOff=0;
|
||||
unsigned short dmaOn=0;
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].keyOn || chan[i].keyOff) {
|
||||
chWrite(i,6,1);
|
||||
dmaOff|=1<<i;
|
||||
}
|
||||
}
|
||||
|
||||
if (dmaOff) rWrite(0x96,dmaOff);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
double off=1.0;
|
||||
if (!chan[i].useWave && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=8363.0/(double)s->centerRate;
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
||||
chan[i].freq=off*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);
|
||||
|
|
@ -409,13 +436,16 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
chWrite(i,6,chan[i].freq);
|
||||
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(0x96,1<<i);
|
||||
if (chan[i].useWave) {
|
||||
rWrite(0x9a,(128<<i));
|
||||
chWrite(i,0,0);
|
||||
chWrite(i,2,i<<8);
|
||||
chWrite(i,4,chan[i].audLen);
|
||||
rWrite(0x96,0x8000|(1<<i));
|
||||
if (dumpWrites) {
|
||||
addWrite(0x200+i,i<<8);
|
||||
addWrite(0x204+i,chan[i].audLen);
|
||||
}
|
||||
dmaOn|=1<<i;
|
||||
} else {
|
||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
|
|
@ -432,13 +462,21 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
chWrite(i,0,0);
|
||||
chWrite(i,2,0x400);
|
||||
chWrite(i,4,1);
|
||||
if (dumpWrites) {
|
||||
addWrite(0x200+i,0x400);
|
||||
addWrite(0x204+i,1);
|
||||
}
|
||||
} else {
|
||||
chWrite(i,0,start>>16);
|
||||
chWrite(i,2,start);
|
||||
chWrite(i,4,len);
|
||||
if (dumpWrites) {
|
||||
addWrite(0x200+i,start);
|
||||
addWrite(0x204+i,len);
|
||||
}
|
||||
}
|
||||
|
||||
rWrite(0x96,0x8000|(1<<i));
|
||||
dmaOn|=1<<i;
|
||||
if (s->isLoopable()) {
|
||||
int loopPos=(sampleOff[chan[i].sample]+s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT))&(~1);
|
||||
int loopEnd=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT))>>1;
|
||||
|
|
@ -455,19 +493,28 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
chWrite(i,0,0);
|
||||
chWrite(i,2,0x400);
|
||||
chWrite(i,4,1);
|
||||
if (dumpWrites) {
|
||||
addWrite(0x200+i,0x400);
|
||||
addWrite(0x204+i,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].keyOff) {
|
||||
rWrite(0x96,1<<i);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
||||
if (dmaOn) rWrite(0x96,0x8000|dmaOn);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
if ((dmaOn&(1<<i)) && !chan[i].useWave && dumpWrites) {
|
||||
addWrite(0x200+i,(chan[i].irLocH<<16)|chan[i].irLocL);
|
||||
addWrite(0x204+i,chan[i].irLen);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].writeVol) {
|
||||
chan[i].writeVol=false;
|
||||
|
|
@ -718,6 +765,18 @@ DivMacroInt* DivPlatformAmiga::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformAmiga::getSamplePos(int ch) {
|
||||
if (ch>=4) return DivSamplePos();
|
||||
if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos();
|
||||
int audPer=amiga.audPer[ch];
|
||||
if (audPer<1) audPer=1;
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
amiga.dmaLoc[ch]-sampleOff[chan[ch].sample],
|
||||
chipClock/audPer
|
||||
);
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::notifyInsChange(int ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ class DivPlatformAmiga: public DivDispatch {
|
|||
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
friend class DivExportAmigaValidation;
|
||||
|
||||
void irq(int ch);
|
||||
void rWrite(unsigned short addr, unsigned short val);
|
||||
|
|
@ -136,6 +137,7 @@ class DivPlatformAmiga: public DivDispatch {
|
|||
int getOutputCount();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
void setFlags(const DivConfig& flags);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
|
|
|
|||
|
|
@ -700,6 +700,15 @@ DivMacroInt* DivPlatformAY8910::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformAY8910::getSamplePos(int ch) {
|
||||
if (ch>=3) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dac.sample,
|
||||
chan[ch].dac.pos,
|
||||
chan[ch].dac.rate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformAY8910::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
int getOutputCount();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
bool getDCOffRequired();
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
|
|
|
|||
|
|
@ -696,6 +696,15 @@ DivMacroInt* DivPlatformAY8930::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformAY8930::getSamplePos(int ch) {
|
||||
if (ch>=3) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dac.sample,
|
||||
chan[ch].dac.pos,
|
||||
chan[ch].dac.rate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformAY8930::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
int getOutputCount();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
|
|
|
|||
|
|
@ -336,6 +336,18 @@ DivMacroInt* DivPlatformGA20::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformGA20::getSamplePos(int ch) {
|
||||
if (ch>=4) return DivSamplePos();
|
||||
if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos();
|
||||
if (!ga20.is_playing(ch)) return DivSamplePos();
|
||||
unsigned char f=chan[ch].freq;
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
ga20.get_position(ch)-sampleOffGA20[chan[ch].sample],
|
||||
chipClock/(4*(0x100-(int)f))
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformGA20::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ class DivPlatformGA20: public DivDispatch, public iremga20_intf {
|
|||
virtual int dispatch(DivCommand c) override;
|
||||
virtual void* getChanState(int chan) override;
|
||||
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
||||
virtual DivSamplePos getSamplePos(int ch) override;
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
|
||||
virtual unsigned char* getRegisterPool() override;
|
||||
virtual int getRegisterPoolSize() override;
|
||||
|
|
|
|||
|
|
@ -172,7 +172,14 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
|
|||
flushFirst=false;
|
||||
}
|
||||
|
||||
OPN2_Clock(&fm,o); os[0]+=o[0]; os[1]+=o[1];
|
||||
OPN2_Clock(&fm,o);
|
||||
if (chipType==2) {
|
||||
os[0]+=CLAMP(o[0],-8192,8191);
|
||||
os[1]+=CLAMP(o[1],-8192,8191);
|
||||
} else {
|
||||
os[0]+=o[0];
|
||||
os[1]+=o[1];
|
||||
}
|
||||
//OPN2_Write(&fm,0,0);
|
||||
if (i==5) {
|
||||
if (fm.dacen) {
|
||||
|
|
@ -1202,6 +1209,17 @@ DivMacroInt* DivPlatformGenesis::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformGenesis::getSamplePos(int ch) {
|
||||
if (!chan[5].dacMode) return DivSamplePos();
|
||||
if (ch<5) return DivSamplePos();
|
||||
if (ch>5 && !softPCM) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dacSample,
|
||||
chan[ch].dacPos,
|
||||
chan[ch].dacRate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformGenesis::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -415,6 +415,16 @@ DivMacroInt* DivPlatformLynx::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformLynx::getSamplePos(int ch) {
|
||||
if (ch>=4) return DivSamplePos();
|
||||
if (!chan[ch].pcm) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
chan[ch].samplePos,
|
||||
chan[ch].sampleFreq
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformLynx::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ class DivPlatformLynx: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "n163.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rRead(a,v) n163.addr_w(a); n163.data_r(v);
|
||||
|
|
@ -166,6 +167,7 @@ void DivPlatformN163::updateWave(int ch, int wave, int pos, int len) {
|
|||
|
||||
void DivPlatformN163::updateWaveCh(int ch) {
|
||||
if (ch<=chanMax) {
|
||||
logV("updateWave with pos %d and len %d",chan[ch].wavePos,chan[ch].waveLen);
|
||||
updateWave(ch,-1,chan[ch].wavePos,chan[ch].waveLen);
|
||||
if (chan[ch].active && !isMuted[ch]) {
|
||||
chan[ch].volumeChanged=true;
|
||||
|
|
@ -337,15 +339,15 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163);
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].wave=ins->n163.wave;
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
chan[c.chan].wavePos=ins->n163.wavePos;
|
||||
chan[c.chan].waveLen=ins->n163.waveLen;
|
||||
chan[c.chan].waveMode=ins->n163.waveMode;
|
||||
chan[c.chan].ws.init(NULL,chan[c.chan].waveLen,15,false);
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
chan[c.chan].waveChanged=true;
|
||||
if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) {
|
||||
chan[c.chan].waveUpdated=true;
|
||||
}
|
||||
chan[c.chan].insChanged=false;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
|
|
@ -360,6 +362,7 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].macroInit(ins);
|
||||
chan[c.chan].ws.init(ins,chan[c.chan].waveLen,15,chan[c.chan].insChanged);
|
||||
chan[c.chan].insChanged=false;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ void DivPlatformNamcoWSG::acquire(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
void DivPlatformNamcoWSG::updateWave(int ch) {
|
||||
if (romMode) return;
|
||||
if (devType==30) {
|
||||
for (int i=0; i<32; i++) {
|
||||
((namco_cus30_device*)namco)->namcos1_cus30_w(i+ch*32,chan[ch].ws.output[i]);
|
||||
|
|
@ -291,9 +292,9 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
rWrite(0x1d,(chan[2].freq>>12)&15);
|
||||
rWrite(0x1e,(chan[2].freq>>16)&15);
|
||||
|
||||
rWrite(0x05,0);
|
||||
rWrite(0x0a,1);
|
||||
rWrite(0x0f,2);
|
||||
rWrite(0x05,romMode?(chan[0].wave&7):0);
|
||||
rWrite(0x0a,romMode?(chan[1].wave&7):1);
|
||||
rWrite(0x0f,romMode?(chan[2].wave&7):2);
|
||||
break;
|
||||
case 15:
|
||||
for (int i=0; i<8; i++) {
|
||||
|
|
@ -304,7 +305,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
}
|
||||
rWrite((i<<3)+0x04,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x05,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|(i<<4));
|
||||
rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|((romMode?(chan[i].wave&7):i)<<4));
|
||||
}
|
||||
break;
|
||||
case 30:
|
||||
|
|
@ -496,10 +497,11 @@ void DivPlatformNamcoWSG::reset() {
|
|||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
// TODO: wave memory
|
||||
namco->set_voices(chans);
|
||||
namco->set_stereo((devType==2 || devType==30));
|
||||
namco->device_start(NULL);
|
||||
|
||||
updateROMWaves();
|
||||
}
|
||||
|
||||
int DivPlatformNamcoWSG::getOutputCount() {
|
||||
|
|
@ -510,6 +512,27 @@ bool DivPlatformNamcoWSG::keyOffAffectsArp(int ch) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::updateROMWaves() {
|
||||
if (romMode) {
|
||||
// copy wavetables
|
||||
for (int i=0; i<8; i++) {
|
||||
int data=0;
|
||||
DivWavetable* w=parent->getWave(i);
|
||||
|
||||
for (int j=0; j<32; j++) {
|
||||
if (w->max<1 || w->len<1) {
|
||||
data=0;
|
||||
} else {
|
||||
data=w->data[j*w->len/32]*15/w->max;
|
||||
if (data<0) data=0;
|
||||
if (data>15) data=15;
|
||||
}
|
||||
namco->update_namco_waveform(i*32+j,data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (chan[i].wave==wave) {
|
||||
|
|
@ -517,6 +540,7 @@ void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
|
|||
updateWave(i);
|
||||
}
|
||||
}
|
||||
updateROMWaves();
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::notifyInsDeletion(void* ins) {
|
||||
|
|
@ -552,6 +576,8 @@ void DivPlatformNamcoWSG::setFlags(const DivConfig& flags) {
|
|||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
newNoise=flags.getBool("newNoise",true);
|
||||
romMode=flags.getBool("romMode",true);
|
||||
if (devType==30) romMode=false;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::poke(unsigned int addr, unsigned short val) {
|
||||
|
|
|
|||
|
|
@ -50,8 +50,10 @@ class DivPlatformNamcoWSG: public DivDispatch {
|
|||
namco_audio_device* namco;
|
||||
int devType, chans;
|
||||
bool newNoise;
|
||||
bool romMode;
|
||||
unsigned char regPool[512];
|
||||
void updateWave(int ch);
|
||||
void updateROMWaves();
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -505,6 +505,16 @@ DivMacroInt* DivPlatformPCE::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformPCE::getSamplePos(int ch) {
|
||||
if (ch>=6) return DivSamplePos();
|
||||
if (!chan[ch].pcm) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dacSample,
|
||||
chan[ch].dacPos,
|
||||
chan[ch].dacRate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformPCE::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
|
|||
const int depthScale=(15-outDepth);
|
||||
int output=0;
|
||||
for (size_t h=0; h<len; h++) {
|
||||
if (!chan[0].active || isMuted) {
|
||||
if (!chan[0].active) {
|
||||
buf[0][h]=0;
|
||||
buf[1][h]=0;
|
||||
oscBuf->data[oscBuf->needle++]=0;
|
||||
|
|
@ -171,7 +171,11 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
|
|||
}
|
||||
}
|
||||
}
|
||||
output=output*chan[0].vol*chan[0].envVol/16384;
|
||||
if (isMuted) {
|
||||
output=0;
|
||||
} else {
|
||||
output=output*chan[0].vol*chan[0].envVol/16384;
|
||||
}
|
||||
oscBuf->data[oscBuf->needle++]=output;
|
||||
if (outStereo) {
|
||||
buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<<depthScale;
|
||||
|
|
@ -437,6 +441,15 @@ DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) {
|
|||
return &chan[0].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformPCMDAC::getSamplePos(int ch) {
|
||||
if (ch>=1) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
chan[ch].audPos,
|
||||
chan[ch].freq
|
||||
);
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::notifyInsChange(int ins) {
|
||||
if (chan[0].ins==ins) {
|
||||
chan[0].insChanged=true;
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ class DivPlatformPCMDAC: public DivDispatch {
|
|||
void muteChannel(int ch, bool mute);
|
||||
int getOutputCount();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
void setFlags(const DivConfig& flags);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ void DivPlatformPV1000::acquire(short** buf, size_t len) {
|
|||
for (size_t h=0; h<len; h++) {
|
||||
short samp;
|
||||
samp=d65010g031_sound_tick(&d65010g031,1);
|
||||
buf[0][h]=samp<<12;
|
||||
buf[0][h]=samp;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.square[i].out<<12);
|
||||
}
|
||||
|
|
@ -263,6 +263,10 @@ void DivPlatformPV1000::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
bool DivPlatformPV1000::getDCOffRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
int DivPlatformPV1000::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
#define _PV1000_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include "sound/d65010g031.h"
|
||||
#include "sound/d65modified.h"
|
||||
#include <queue>
|
||||
|
||||
class DivPlatformPV1000: public DivDispatch {
|
||||
|
|
@ -55,6 +55,7 @@ class DivPlatformPV1000: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
bool getDCOffRequired();
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
~DivPlatformPV1000();
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@ void DivPlatformRF5C68::forceIns() {
|
|||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].sample=-1;
|
||||
chWrite(i,1,isMuted[i]?0:chan[i].panning);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -382,6 +382,17 @@ DivMacroInt* DivPlatformSegaPCM::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformSegaPCM::getSamplePos(int ch) {
|
||||
if (ch>=16) return DivSamplePos();
|
||||
if (chan[ch].pcm.sample<0 || chan[ch].pcm.sample>=parent->song.sampleLen) return DivSamplePos();
|
||||
if (!pcm.is_playing(ch)) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].pcm.sample,
|
||||
pcm.get_addr(ch)-sampleOffSegaPCM[chan[ch].pcm.sample],
|
||||
122*(chan[ch].pcm.freq+1)
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -681,6 +681,20 @@ DivMacroInt* DivPlatformSNES::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformSNES::getSamplePos(int ch) {
|
||||
if (ch>=8) return DivSamplePos();
|
||||
if (!chan[ch].active) return DivSamplePos();
|
||||
if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos();
|
||||
const SPC_DSP::voice_t* v=dsp.get_voice(ch);
|
||||
// TODO: fix?
|
||||
if (sampleMem[v->brr_addr&0xffff]==0) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
((v->brr_addr-sampleOff[chan[ch].sample])*16/9)+v->brr_offset,
|
||||
(chan[ch].freq*125)/16
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformSNES::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ class DivPlatformSNES: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -34,9 +34,31 @@ freely, subject to the following restrictions:
|
|||
TODO:
|
||||
- needs hardware test
|
||||
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
|
||||
|
||||
THIS IS **NOT** NOT NOT NOT!!!! THE ORIGINAL SOFTWARE
|
||||
IT ISN'T
|
||||
THE MODIFICATIONS THAT WERE MADE ARE:
|
||||
|
||||
1. FIX VOLUMES - APPARENTLY THE SQUARES HAVE DIFFERENT VOLUMES (thanks forple)
|
||||
|
||||
*/
|
||||
|
||||
#include "d65010g031.h"
|
||||
#include "d65modified.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static int d65010g031_max(int a, int b) { return (a > b) ? a : b; }
|
||||
|
|
@ -57,12 +79,20 @@ int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// this is the bit I altered
|
||||
// THIS IS **NOT** THE ORIGINAL SOFTWARE! I am plainly marking it as such!
|
||||
const int d65Volumes[3]={
|
||||
3840,
|
||||
5120,
|
||||
8192
|
||||
};
|
||||
|
||||
int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle)
|
||||
{
|
||||
int out = 0;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
out += d65010g031_square_tick(&d65010g031->square[i], cycle);
|
||||
out += d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
@ -39,12 +39,19 @@ public:
|
|||
u8 read(u32 offset);
|
||||
|
||||
inline void set_mute(const int ch, const bool mute) { m_channel[ch & 3].mute = mute; }
|
||||
inline unsigned int get_position(const int ch) {
|
||||
return m_channel[ch&3].pos;
|
||||
}
|
||||
inline bool is_playing(const int ch) {
|
||||
return m_channel[ch&3].play;
|
||||
}
|
||||
|
||||
// device-level overrides
|
||||
void device_reset();
|
||||
|
||||
// sound stream update overrides
|
||||
void sound_stream_update(short** outputs, int len);
|
||||
|
||||
|
||||
private:
|
||||
struct channel_def
|
||||
|
|
|
|||
|
|
@ -134,6 +134,18 @@ uint8_t* segapcm_device::get_ram() {
|
|||
return m_ram;
|
||||
}
|
||||
|
||||
unsigned int segapcm_device::get_addr(int ch) {
|
||||
uint8_t *regs = &m_ram[8*ch];
|
||||
int offset = (regs[0x86] & m_bankmask) << m_bankshift;
|
||||
uint32_t addr = (regs[0x85] << 8) | (regs[0x84]) | offset;
|
||||
return addr;
|
||||
}
|
||||
|
||||
bool segapcm_device::is_playing(int ch) {
|
||||
uint8_t *regs = &m_ram[8*ch];
|
||||
return !(regs[0x86]&1);
|
||||
}
|
||||
|
||||
void segapcm_device::mute(int ch, bool doMute) {
|
||||
m_muted[ch&15]=doMute;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ public:
|
|||
void write(unsigned int offset, uint8_t data);
|
||||
uint8_t read(unsigned int offset);
|
||||
uint8_t* get_ram();
|
||||
unsigned int get_addr(int ch);
|
||||
bool is_playing(int ch);
|
||||
void mute(int ch, bool doMute);
|
||||
|
||||
// device-level overrides
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@ public:
|
|||
uint8_t t_envx_out;
|
||||
sample_t out[2]; // Furnace addition, for per-channel oscilloscope
|
||||
};
|
||||
|
||||
// Furnace addition, gets a voice
|
||||
const voice_t* get_voice(int n);
|
||||
private:
|
||||
enum { brr_block_size = 9 };
|
||||
|
||||
|
|
@ -298,6 +301,10 @@ inline void SPC_DSP::get_voice_outputs( sample_t* outs )
|
|||
}
|
||||
}
|
||||
|
||||
inline const SPC_DSP::voice_t* SPC_DSP::get_voice(int n) {
|
||||
return &m.voices[n];
|
||||
}
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
|
||||
class SPC_State_Copier {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "vic20.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);}
|
||||
|
|
@ -79,9 +80,7 @@ void DivPlatformVIC20::calcAndWriteOutVol(int ch, int env) {
|
|||
}
|
||||
|
||||
void DivPlatformVIC20::writeOutVol(int ch) {
|
||||
if (!isMuted[ch] && chan[ch].active) {
|
||||
rWrite(14,chan[ch].outVol);
|
||||
}
|
||||
rWrite(14,chan[ch].outVol);
|
||||
}
|
||||
|
||||
void DivPlatformVIC20::tick(bool sysTick) {
|
||||
|
|
@ -99,6 +98,20 @@ void DivPlatformVIC20::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
if (chan[i].onOff!=(bool)chan[i].std.duty.val) {
|
||||
chan[i].onOff=(bool)chan[i].std.duty.val;
|
||||
if (chan[i].active) {
|
||||
if (chan[i].onOff) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].keyOff=false;
|
||||
} else {
|
||||
chan[i].keyOn=false;
|
||||
chan[i].keyOff=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val) {
|
||||
chan[i].wave=chan[i].std.wave.val&0x0f;
|
||||
|
|
@ -156,6 +169,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].onOff=true;
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
|
|
|
|||
|
|
@ -27,10 +27,12 @@
|
|||
class DivPlatformVIC20: public DivDispatch {
|
||||
struct Channel: public SharedChannel<int> {
|
||||
int wave, waveWriteCycle;
|
||||
bool onOff;
|
||||
Channel():
|
||||
SharedChannel<int>(15),
|
||||
wave(0),
|
||||
waveWriteCycle(-1) {}
|
||||
waveWriteCycle(-1),
|
||||
onOff(true) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
|
|
|
|||
|
|
@ -448,6 +448,16 @@ DivMacroInt* DivPlatformVRC6::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformVRC6::getSamplePos(int ch) {
|
||||
if (ch>=2) return DivSamplePos();
|
||||
if (!chan[ch].pcm) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dacSample,
|
||||
chan[ch].dacPos,
|
||||
chan[ch].dacRate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformVRC6::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class DivPlatformVRC6: public DivDispatch, public vrcvi_intf {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ int DivEngine::dispatchCmd(DivCommand c) {
|
|||
cmdStream.push_back(c);
|
||||
}
|
||||
|
||||
if (output) if (!skipping && output->midiOut!=NULL) {
|
||||
if (output) if (!skipping && output->midiOut!=NULL && !isChannelMuted(c.chan)) {
|
||||
if (output->midiOut->isDeviceOpen()) {
|
||||
if (midiOutMode==DIV_MIDI_MODE_NOTE) {
|
||||
int scaledVol=(chan[c.chan].volume*127)/MAX(1,chan[c.chan].volMax);
|
||||
|
|
@ -305,7 +305,7 @@ int DivEngine::dispatchCmd(DivCommand c) {
|
|||
chan[c.chan].curMidiNote=-1;
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].lastIns!=c.value) {
|
||||
if (chan[c.chan].lastIns!=c.value && midiOutProgramChange) {
|
||||
output->midiOut->send(TAMidiMessage(0xc0|(c.chan&15),c.value,0));
|
||||
}
|
||||
break;
|
||||
|
|
@ -1385,6 +1385,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
}
|
||||
}
|
||||
|
||||
if (subticks==tickMult && cmdStreamInt) {
|
||||
if (!cmdStreamInt->tick()) {
|
||||
cmdStreamInt->cleanup();
|
||||
delete cmdStreamInt;
|
||||
cmdStreamInt=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
firstTick=false;
|
||||
|
||||
if (shallStop) {
|
||||
|
|
|
|||
|
|
@ -236,10 +236,14 @@ String SafeReader::readString(size_t stlen) {
|
|||
#endif
|
||||
size_t curPos=0;
|
||||
if (isEOF()) throw EndOfFileException(this, len);
|
||||
bool zero=false;
|
||||
|
||||
while (!isEOF() && curPos<stlen) {
|
||||
unsigned char c=readC();
|
||||
if (c!=0) ret.push_back(c);
|
||||
if (c==0) {
|
||||
zero=true;
|
||||
}
|
||||
if (!zero) ret.push_back(c);
|
||||
curPos++;
|
||||
}
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -144,6 +144,16 @@ int SafeWriter::writeI(int val) {
|
|||
return write(&val,4);
|
||||
}
|
||||
|
||||
int SafeWriter::writeI_BE(int val) {
|
||||
unsigned char bytes[4] {
|
||||
(unsigned char)((val>>24)&0xff),
|
||||
(unsigned char)((val>>16)&0xff),
|
||||
(unsigned char)((val>>8)&0xff),
|
||||
(unsigned char)(val&0xff)
|
||||
};
|
||||
return write(bytes,4);
|
||||
}
|
||||
|
||||
int SafeWriter::writeL(int64_t val) {
|
||||
return write(&val,8);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -789,7 +789,7 @@ void DivEngine::registerSystems() {
|
|||
{"S1", "S2", "S3"},
|
||||
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
|
||||
{DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
|
||||
{},
|
||||
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
|
||||
{},
|
||||
ayPostEffectHandlerMap
|
||||
);
|
||||
|
|
@ -870,7 +870,7 @@ void DivEngine::registerSystems() {
|
|||
{"S1", "S2", "S3"},
|
||||
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
|
||||
{DIV_INS_AY8930, DIV_INS_AY8930, DIV_INS_AY8930},
|
||||
{},
|
||||
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
|
||||
{},
|
||||
ay8930PostEffectHandlerMap
|
||||
);
|
||||
|
|
|
|||
|
|
@ -583,8 +583,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID);
|
||||
switch (write.addr&0xff) {
|
||||
case 0: // play sample
|
||||
if (write.val<song.sampleLen) {
|
||||
if (playingSample[streamID]!=write.val) {
|
||||
if (write.val<(unsigned int)song.sampleLen) {
|
||||
if (playingSample[streamID]!=(int)write.val) {
|
||||
pendingFreq[streamID]=write.val;
|
||||
} else {
|
||||
DivSample* sample=song.sample[write.val];
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include "waveSynth.h"
|
||||
#include "engine.h"
|
||||
#include "instrument.h"
|
||||
#include "../ta-log.h"
|
||||
|
||||
bool DivWaveSynth::activeChanged() {
|
||||
if (activeChangedB) {
|
||||
|
|
@ -211,6 +212,7 @@ void DivWaveSynth::setWidth(int val) {
|
|||
|
||||
void DivWaveSynth::changeWave1(int num) {
|
||||
DivWavetable* w1=e->getWave(num);
|
||||
logV("changeWave1 (%d)",width);
|
||||
if (width<1) return;
|
||||
for (int i=0; i<width; i++) {
|
||||
if (w1->max<1 || w1->len<1) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue