Merge branch 'master' into ymf289b

This commit is contained in:
tildearrow 2023-05-11 16:59:38 -05:00
commit 25eb720631
230 changed files with 69242 additions and 87659 deletions

428
src/engine/cmdStream.cpp Normal file
View file

@ -0,0 +1,428 @@
/**
* 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.
*/
#define _USE_MATH_DEFINES
#include "cmdStream.h"
#include "dispatch.h"
#include "engine.h"
#include "../ta-log.h"
bool DivCSChannelState::doCall(unsigned int addr) {
if (callStackPos>=8) {
readPos=0;
return false;
}
callStack[callStackPos++]=readPos;
readPos=addr;
return true;
}
void DivCSPlayer::cleanup() {
delete b;
}
bool DivCSPlayer::tick() {
bool ticked=false;
for (int i=0; i<e->getTotalChannelCount(); i++) {
bool sendVolume=false;
bool sendPitch=false;
if (chan[i].readPos==0) continue;
ticked=true;
chan[i].waitTicks--;
while (chan[i].waitTicks<=0) {
if (!stream.seek(chan[i].readPos,SEEK_SET)) {
logE("%d: access violation! $%x",i,chan[i].readPos);
chan[i].readPos=0;
break;
}
unsigned char next=stream.readC();
unsigned char command=0;
if (next<0xb3) { // note
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,(int)next-60));
chan[i].note=(int)next-60;
chan[i].vibratoPos=0;
} 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));
chan[i].vibratoPos=0;
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: {
unsigned int callAddr=chan[i].readPos+2+stream.readS();
if (!chan[i].doCall(callAddr)) {
logE("%d: (callb16) stack error!",i);
}
break;
}
case 0xf6: {
unsigned int callAddr=chan[i].readPos+4+stream.readI();
if (!chan[i].doCall(callAddr)) {
logE("%d: (callb32) stack error!",i);
}
break;
}
case 0xf5: {
unsigned int callAddr=stream.readI();
if (!chan[i].doCall(callAddr)) {
logE("%d: (call) stack error!",i);
}
break;
}
case 0xf4: {
logE("%d: (callsym) not supported here!",i);
chan[i].readPos=0;
break;
}
case 0xf9:
if (!chan[i].callStackPos) {
logE("%d: (ret) stack error!",i);
chan[i].readPos=0;
break;
}
chan[i].readPos=chan[i].callStack[--chan[i].callStackPos];
break;
case 0xfa:
chan[i].readPos=stream.readI();
break;
case 0xfb:
logE("TODO: RATE");
stream.readI();
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;
logI("%d: stop",i);
break;
default:
logE("%d: illegal instruction $%.2x! $%.x",i,next,chan[i].readPos);
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_VOLUME:
case DIV_CMD_HINT_ARP_TIME:
arg0=(unsigned char)stream.readC();
break;
case DIV_CMD_HINT_PITCH:
arg0=(signed char)stream.readC();
break;
case DIV_CMD_PANNING:
case DIV_CMD_HINT_VIBRATO:
case DIV_CMD_HINT_ARPEGGIO:
case DIV_CMD_HINT_PORTA:
arg0=(signed 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_HINT_LEGATO:
arg0=(unsigned char)stream.readC();
if (arg0==0xff) {
arg0=DIV_NOTE_NULL;
} else {
arg0-=60;
}
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;
case DIV_CMD_HINT_PITCH:
chan[i].pitch=arg0;
sendPitch=true;
break;
case DIV_CMD_HINT_VIBRATO:
chan[i].vibratoDepth=arg0;
chan[i].vibratoRate=arg1;
sendPitch=true;
break;
case DIV_CMD_HINT_PORTA:
chan[i].portaTarget=arg0;
chan[i].portaSpeed=arg1;
break;
case DIV_CMD_HINT_LEGATO:
chan[i].note=arg0;
e->dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
break;
case DIV_CMD_HINT_ARPEGGIO:
chan[i].arp=(((unsigned char)arg0)<<4)|(arg1&15);
break;
case DIV_CMD_HINT_ARP_TIME:
arpSpeed=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));
}
if (sendPitch || chan[i].vibratoDepth!=0) {
if (chan[i].vibratoDepth>0) {
chan[i].vibratoPos+=chan[i].vibratoRate;
if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64;
}
e->dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(vibTable[chan[i].vibratoPos&63]*chan[i].vibratoDepth)/15));
}
if (chan[i].portaSpeed) {
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(e->song.linearPitch==2?e->song.pitchSlideSpeed:1),chan[i].portaTarget));
}
if (chan[i].arp && !chan[i].portaSpeed) {
if (chan[i].arpTicks==0) {
switch (chan[i].arpStage) {
case 0:
e->dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note));
break;
case 1:
e->dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp>>4)));
break;
case 2:
e->dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note+(chan[i].arp&15)));
break;
}
chan[i].arpStage++;
if (chan[i].arpStage>=3) chan[i].arpStage=0;
chan[i].arpTicks=arpSpeed;
}
chan[i].arpTicks--;
}
}
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;
}
for (int i=0; i<64; i++) {
vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI));
}
arpSpeed=1;
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;
}

89
src/engine/cmdStream.h Normal file
View file

@ -0,0 +1,89 @@
/**
* 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 note, pitch;
int volume, volMax, volSpeed;
int vibratoDepth, vibratoRate, vibratoPos;
int portaTarget, portaSpeed;
unsigned char arp, arpStage, arpTicks;
unsigned int callStack[8];
unsigned char callStackPos;
struct TraceEntry {
unsigned int addr;
unsigned char length;
unsigned char data[11];
} trace[32];
unsigned char tracePos;
bool doCall(unsigned int addr);
DivCSChannelState():
readPos(0),
waitTicks(0),
note(-1),
pitch(0),
volume(0x7f00),
volMax(0),
volSpeed(0),
vibratoDepth(0),
vibratoRate(0),
vibratoPos(0),
portaTarget(0),
portaSpeed(0),
arp(0),
arpStage(0),
arpTicks(0),
callStackPos(0) {}
};
class DivCSPlayer {
DivEngine* e;
unsigned char* b;
SafeReader stream;
DivCSChannelState chan[DIV_MAX_CHANS];
unsigned char fastDelays[16];
unsigned char fastCmds[16];
unsigned char arpSpeed;
short vibTable[64];
public:
void cleanup();
bool tick();
bool init();
DivCSPlayer(DivEngine* en, unsigned char* buf, size_t len):
e(en),
b(buf),
stream(buf,len) {}
};
#endif

View file

@ -23,21 +23,54 @@
#include "../fileutils.h"
#include <fmt/printf.h>
bool DivConfig::save(const char* path) {
#define REDUNDANCY_NUM_ATTEMPTS 5
#define CHECK_BUF_SIZE 8192
bool DivConfig::save(const char* path, bool redundancy) {
if (redundancy) {
char oldPath[4096];
char newPath[4096];
if (fileExists(path)==1) {
logD("rotating config files...");
for (int i=4; i>=0; i--) {
if (i>0) {
snprintf(oldPath,4095,"%s.%d",path,i);
} else {
strncpy(oldPath,path,4095);
}
snprintf(newPath,4095,"%s.%d",path,i+1);
if (i>=4) {
logV("remove %s",oldPath);
deleteFile(oldPath);
} else {
logV("move %s to %s",oldPath,newPath);
moveFiles(oldPath,newPath);
}
}
}
}
logD("opening config for write: %s",path);
FILE* f=ps_fopen(path,"wb");
if (f==NULL) {
logW("could not write config file! %s",strerror(errno));
reportError(fmt::sprintf("could not write config file! %s",strerror(errno)));
return false;
}
for (auto& i: conf) {
String toWrite=fmt::sprintf("%s=%s\n",i.first,i.second);
if (fwrite(toWrite.c_str(),1,toWrite.size(),f)!=toWrite.size()) {
logW("could not write config file! %s",strerror(errno));
reportError(fmt::sprintf("could not write config file! %s",strerror(errno)));
logV("removing config file");
fclose(f);
deleteFile(path);
return false;
}
}
fclose(f);
logD("config file written successfully.");
return true;
}
@ -63,6 +96,7 @@ void DivConfig::parseLine(const char* line) {
String value="";
bool keyOrValue=false;
for (const char* i=line; *i; i++) {
if (*i=='\r') continue;
if (*i=='\n') continue;
if (keyOrValue) {
value+=*i;
@ -79,17 +113,94 @@ void DivConfig::parseLine(const char* line) {
}
}
bool DivConfig::loadFromFile(const char* path, bool createOnFail) {
bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundancy) {
char line[4096];
FILE* f=ps_fopen(path,"rb");
if (f==NULL) {
if (createOnFail) {
logI("creating default config.");
return save(path);
} else {
return false;
logD("opening config for read: %s",path);
FILE* f=NULL;
if (redundancy) {
unsigned char* readBuf=new unsigned char[CHECK_BUF_SIZE];
size_t readBufLen=0;
for (int i=0; i<REDUNDANCY_NUM_ATTEMPTS; i++) {
bool viable=false;
if (i>0) {
snprintf(line,4095,"%s.%d",path,i);
} else {
strncpy(line,path,4095);
}
logV("trying: %s",line);
// try to open config
f=ps_fopen(line,"rb");
// check whether we could open it
if (f==NULL) {
logV("fopen(): %s",strerror(errno));
continue;
}
// check whether there's something
while (!feof(f)) {
readBufLen=fread(readBuf,1,CHECK_BUF_SIZE,f);
if (ferror(f)) {
logV("fread(): %s",strerror(errno));
break;
}
for (size_t j=0; j<readBufLen; j++) {
if (readBuf[j]!='\r' && readBuf[j]!='\n' && readBuf[j]!=' ') {
viable=true;
break;
}
}
if (viable) break;
}
// there's something
if (viable) {
if (fseek(f,0,SEEK_SET)==-1) {
logV("fseek(): %s",strerror(errno));
viable=false;
} else {
break;
}
}
// close it (because there's nothing)
fclose(f);
f=NULL;
}
delete[] readBuf;
// we couldn't read at all
if (f==NULL) {
logD("config does not exist");
if (createOnFail) {
logI("creating default config.");
//reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
return save(path,redundancy);
} else {
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));
return false;
}
}
} else {
f=ps_fopen(path,"rb");
if (f==NULL) {
logD("config does not exist");
if (createOnFail) {
logI("creating default config.");
//reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
return save(path);
} else {
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));
return false;
}
}
}
logI("loading config.");
while (!feof(f)) {
if (fgets(line,4095,f)==NULL) {
@ -97,6 +208,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail) {
}
parseLine(line);
}
logD("end of file (%s)",strerror(errno));
fclose(f);
return true;
}
@ -175,6 +287,33 @@ String DivConfig::getString(String key, String fallback) const {
return fallback;
}
std::vector<int> DivConfig::getIntList(String key, std::initializer_list<int> fallback) const {
String next;
std::vector<int> ret;
try {
String val=conf.at(key);
for (char i: val) {
if (i==',') {
int num=std::stoi(next);
ret.push_back(num);
next="";
} else {
next+=i;
}
}
if (!next.empty()) {
int num=std::stoi(next);
ret.push_back(num);
}
return ret;
} catch (std::out_of_range& e) {
} catch (std::invalid_argument& e) {
}
return fallback;
}
bool DivConfig::has(String key) const {
try {
String test=conf.at(key);
@ -212,6 +351,17 @@ void DivConfig::set(String key, String value) {
conf[key]=value;
}
void DivConfig::set(String key, const std::vector<int>& value) {
String val;
bool comma=false;
for (int i: value) {
if (comma) val+=',';
val+=fmt::sprintf("%d",i);
comma=true;
}
conf[key]=val;
}
bool DivConfig::remove(String key) {
return conf.erase(key);
}

View file

@ -22,6 +22,8 @@
#include "../ta-utils.h"
#include <map>
#include <vector>
#include <initializer_list>
class DivConfig {
std::map<String,String> conf;
@ -30,10 +32,10 @@ class DivConfig {
// config loading/saving
bool loadFromMemory(const char* buf);
bool loadFromBase64(const char* buf);
bool loadFromFile(const char* path, bool createOnFail=true);
bool loadFromFile(const char* path, bool createOnFail=true, bool redundancy=false);
String toString();
String toBase64();
bool save(const char* path);
bool save(const char* path, bool redundancy=false);
// get the map
const std::map<String,String>& configMap();
@ -44,6 +46,7 @@ class DivConfig {
float getFloat(String key, float fallback) const;
double getDouble(String key, double fallback) const;
String getString(String key, String fallback) const;
std::vector<int> getIntList(String key, std::initializer_list<int> fallback) const;
// check for existence
bool has(String key) const;
@ -55,6 +58,7 @@ class DivConfig {
void set(String key, double value);
void set(String key, const char* value);
void set(String key, String value);
void set(String key, const std::vector<int>& value);
// remove a config value
bool remove(String key);

View file

@ -110,12 +110,12 @@ void DivEngine::initConfDir() {
bool DivEngine::saveConf() {
configFile=configPath+String(CONFIG_FILE);
return conf.save(configFile.c_str());
return conf.save(configFile.c_str(),true);
}
bool DivEngine::loadConf() {
configFile=configPath+String(CONFIG_FILE);
return conf.loadFromFile(configFile.c_str());
return conf.loadFromFile(configFile.c_str(),true,true);
}
bool DivEngine::getConfBool(String key, bool fallback) {

View file

@ -229,6 +229,13 @@ enum DivDispatchCmds {
DIV_CMD_ES5506_ENVELOPE_K2RAMP, // (ramp, slowdown)
DIV_CMD_ES5506_PAUSE, // (value)
DIV_CMD_HINT_ARP_TIME, // (value)
DIV_CMD_SNES_GLOBAL_VOL_LEFT,
DIV_CMD_SNES_GLOBAL_VOL_RIGHT,
DIV_CMD_NES_LINEAR_LENGTH,
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_MAX
@ -280,19 +287,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 +390,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);

View file

@ -59,6 +59,7 @@
#include "platform/lynx.h"
#include "platform/pokey.h"
#include "platform/zxbeeper.h"
#include "platform/zxbeeperquadtone.h"
#include "platform/bubsyswsg.h"
#include "platform/n163.h"
#include "platform/pet.h"
@ -76,6 +77,7 @@
#include "platform/k007232.h"
#include "platform/ga20.h"
#include "platform/sm8521.h"
#include "platform/pv1000.h"
#include "platform/pcmdac.h"
#include "platform/dummy.h"
#include "../ta-log.h"
@ -388,6 +390,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_SFX_BEEPER:
dispatch=new DivPlatformZXBeeper;
break;
case DIV_SYSTEM_SFX_BEEPER_QUADTONE:
dispatch=new DivPlatformZXBeeperQuadTone;
break;
case DIV_SYSTEM_LYNX:
dispatch=new DivPlatformLynx;
break;
@ -493,6 +498,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_SM8521:
dispatch=new DivPlatformSM8521;
break;
case DIV_SYSTEM_PV1000:
dispatch=new DivPlatformPV1000;
break;
case DIV_SYSTEM_PCM_DAC:
dispatch=new DivPlatformPCMDAC;
break;

View file

@ -55,6 +55,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
return "03xx: Portamento";
case 0x04:
return "04xy: Vibrato (x: speed; y: depth)";
case 0x05:
return "05xy: Volume slide + vibrato (compatibility only!)";
case 0x06:
return "06xy: Volume slide + portamento (compatibility only!)";
case 0x07:
return "07xy: Tremolo (x: speed; y: depth)";
case 0x08:
@ -418,6 +422,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
case DIV_CMD_AMIGA_PM:
case DIV_CMD_MACRO_OFF:
case DIV_CMD_MACRO_ON:
case DIV_CMD_HINT_ARP_TIME:
w->writeC(1); // length
w->writeC(c.value);
break;
@ -1481,6 +1486,7 @@ void DivEngine::createNewFromDefaults() {
bool oldVol=getConfInt("configVersion",DIV_ENGINE_VERSION)<135;
if (preset.empty()) {
// try loading old preset
logD("trying to load old preset");
preset=decodeSysDesc(getConfString("initialSys",""));
oldVol=false;
}
@ -1566,6 +1572,49 @@ void DivEngine::changeSong(size_t songIndex) {
prevRow=0;
}
void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
bool* inAssetDir=new bool[entries];
memset(inAssetDir,0,entries*sizeof(bool));
for (DivAssetDir& i: dir) {
for (size_t j=0; j<i.entries.size(); j++) {
// erase invalid entry
if (i.entries[j]<0 || i.entries[j]>=(int)entries) {
i.entries.erase(i.entries.begin()+j);
j--;
continue;
}
// mark entry as present
inAssetDir[j]=true;
}
}
// get unsorted directory
DivAssetDir* unsortedDir=NULL;
for (DivAssetDir& i: dir) {
if (i.name.empty()) {
unsortedDir=&i;
break;
}
}
// create unsorted directory if it doesn't exist
if (unsortedDir==NULL) {
dir.push_back(DivAssetDir(""));
unsortedDir=&(*dir.rbegin());
}
// add missing items to unsorted directory
for (size_t i=0; i<entries; i++) {
if (!inAssetDir[i]) {
unsortedDir->entries.push_back(i);
}
}
delete[] inAssetDir;
}
void DivEngine::swapChannelsP(int src, int dest) {
if (src<0 || src>=chans) return;
if (dest<0 || dest>=chans) return;
@ -1985,11 +2034,17 @@ String DivEngine::getPlaybackDebugInfo() {
"divider: %f\n"
"cycles: %d\n"
"clockDrift: %f\n"
"midiClockCycles: %d\n"
"midiClockDrift: %f\n"
"midiTimeCycles: %d\n"
"midiTimeDrift: %f\n"
"changeOrd: %d\n"
"changePos: %d\n"
"totalSeconds: %d\n"
"totalTicks: %d\n"
"totalTicksR: %d\n"
"curMidiClock: %d\n"
"curMidiTime: %d\n"
"totalCmds: %d\n"
"lastCmds: %d\n"
"cmdsPerSecond: %d\n"
@ -1999,7 +2054,8 @@ String DivEngine::getPlaybackDebugInfo() {
"totalProcessed: %d\n"
"bufferPos: %d\n",
curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift,
changeOrd,changePos,totalSeconds,totalTicks,totalTicksR,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
midiClockCycles,midiClockDrift,midiTimeCycles,midiTimeDrift,changeOrd,changePos,totalSeconds,totalTicks,
totalTicksR,curMidiClock,curMidiTime,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
(int)extValue,(int)tempoAccum,(int)totalProcessed,(int)bufferPos
);
}
@ -2075,6 +2131,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]);
@ -2111,16 +2172,26 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
prevOrder=0;
prevRow=0;
stepPlay=0;
int prevDrift;
int prevDrift, prevMidiClockDrift, prevMidiTimeDrift;
prevDrift=clockDrift;
prevMidiClockDrift=midiClockDrift;
prevMidiTimeDrift=midiTimeDrift;
clockDrift=0;
cycles=0;
midiClockCycles=0;
midiClockDrift=0;
midiTimeCycles=0;
midiTimeDrift=0;
if (!preserveDrift) {
ticks=1;
tempoAccum=0;
totalTicks=0;
totalSeconds=0;
totalTicksR=0;
curMidiClock=0;
curMidiTime=0;
curMidiTimeCode=0;
curMidiTimePiece=0;
totalLoops=0;
lastLoopPos=-1;
}
@ -2137,6 +2208,10 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
skipping=false;
return;
}
if (!preserveDrift) {
runMidiClock(cycles);
runMidiTime(cycles);
}
}
int oldOrder=curOrder;
while (playing && (curRow<goalRow || ticks>1)) {
@ -2144,6 +2219,10 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
skipping=false;
return;
}
if (!preserveDrift) {
runMidiClock(cycles);
runMidiTime(cycles);
}
if (oldOrder!=curOrder) break;
if (ticks-((tempoAccum+curSubSong->virtualTempoN)/MAX(1,curSubSong->virtualTempoD))<1 && curRow>=goalRow) break;
}
@ -2157,9 +2236,22 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
repeatPattern=oldRepeatPattern;
if (preserveDrift) {
clockDrift=prevDrift;
midiClockDrift=prevMidiClockDrift;
midiTimeDrift=prevMidiTimeDrift;
} else {
clockDrift=0;
cycles=0;
midiClockCycles=0;
midiClockDrift=0;
midiTimeCycles=0;
midiTimeDrift=0;
if (curMidiTime>0) {
curMidiTime--;
}
if (curMidiClock>0) {
curMidiClock--;
}
curMidiTimePiece=0;
}
if (!preserveDrift) {
ticks=1;
@ -2328,9 +2420,74 @@ void DivEngine::play() {
for (int i=0; i<DIV_MAX_CHANS; i++) {
keyHit[i]=false;
}
curMidiTimePiece=0;
if (output) if (!skipping && output->midiOut!=NULL) {
int pos=totalTicksR/6;
output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(pos>>7)&0x7f,pos&0x7f));
if (midiOutClock) {
output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(curMidiClock>>7)&0x7f,curMidiClock&0x7f));
}
if (midiOutTime) {
TAMidiMessage msg;
msg.type=TA_MIDI_SYSEX;
msg.sysExData.reset(new unsigned char[10],std::default_delete<unsigned char[]>());
msg.sysExLen=10;
unsigned char* msgData=msg.sysExData.get();
int actualTime=curMidiTime;
int timeRate=midiOutTimeRate;
int drop=0;
if (timeRate<1 || timeRate>4) {
if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) {
timeRate=1;
} else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) {
timeRate=2;
} else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) {
timeRate=4;
} else {
timeRate=4;
}
}
switch (timeRate) {
case 1: // 24
msgData[5]=(actualTime/(60*60*24))%24;
msgData[6]=(actualTime/(60*24))%60;
msgData[7]=(actualTime/24)%60;
msgData[8]=actualTime%24;
break;
case 2: // 25
msgData[5]=(actualTime/(60*60*25))%24;
msgData[6]=(actualTime/(60*25))%60;
msgData[7]=(actualTime/25)%60;
msgData[8]=actualTime%25;
break;
case 3: // 29.97 (NTSC drop)
// drop
drop=((actualTime/(30*60))-(actualTime/(30*600)))*2;
actualTime+=drop;
msgData[5]=(actualTime/(60*60*30))%24;
msgData[6]=(actualTime/(60*30))%60;
msgData[7]=(actualTime/30)%60;
msgData[8]=actualTime%30;
break;
case 4: // 30 (NTSC non-drop)
default:
msgData[5]=(actualTime/(60*60*30))%24;
msgData[6]=(actualTime/(60*30))%60;
msgData[7]=(actualTime/30)%60;
msgData[8]=actualTime%30;
break;
}
msgData[5]|=(timeRate-1)<<5;
msgData[0]=0xf0;
msgData[1]=0x7f;
msgData[2]=0x7f;
msgData[3]=0x01;
msgData[4]=0x01;
msgData[9]=0xf7;
output->midiOut->send(msg);
}
output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_PLAY,0,0));
}
BUSY_END;
@ -2369,6 +2526,13 @@ void DivEngine::stepOne(int row) {
void DivEngine::stop() {
BUSY_BEGIN;
freelance=false;
if (!playing) {
//Send midi panic
if (output) if (output->midiOut!=NULL) {
output->midiOut->send(TAMidiMessage(TA_MIDI_CONTROL,0x7B,0));
logV("Midi panic sent");
}
}
playing=false;
extValuePresent=false;
endOfSong=false; // what?
@ -2391,6 +2555,16 @@ void DivEngine::stop() {
}
}
}
// reset all chan oscs
for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=disCont[dispatchOfChan[i]].dispatch->getOscBuffer(dispatchChanOfChan[i]);
if (buf!=NULL) {
memset(buf->data,0,65536*sizeof(short));
buf->needle=0;
buf->readNeedle=0;
}
}
BUSY_END;
}
@ -2454,6 +2628,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;
}
@ -2532,6 +2710,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]) {
@ -2600,10 +2782,17 @@ void DivEngine::previewSampleNoLock(int sample, int note, int pStart, int pEnd)
if (rate<=0) rate=song.sample[sample]->centerRate;
}
if (rate<100) rate=100;
double rateOrig=rate;
sPreview.rateMul=1;
while (sPreview.rateMul<0x40000000 && rate<got.rate) {
sPreview.rateMul<<=1;
rate*=2.0;
}
blip_set_rates(samp_bb,rate,got.rate);
samp_prevSample=0;
sPreview.rate=rate;
sPreview.rate=rateOrig;
sPreview.pos=(sPreview.pBegin>=0)?sPreview.pBegin:0;
sPreview.posSub=0;
sPreview.sample=sample;
sPreview.wave=-1;
sPreview.dir=false;
@ -2628,10 +2817,17 @@ void DivEngine::previewWaveNoLock(int wave, int note) {
blip_clear(samp_bb);
double rate=song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0));
if (rate<100) rate=100;
double rateOrig=rate;
sPreview.rateMul=1;
while (sPreview.rateMul<0x40000000 && rate<got.rate) {
sPreview.rateMul<<=1;
rate*=2.0;
}
blip_set_rates(samp_bb,rate,got.rate);
samp_prevSample=0;
sPreview.rate=rate;
sPreview.rate=rateOrig;
sPreview.pos=0;
sPreview.posSub=0;
sPreview.sample=-1;
sPreview.wave=wave;
sPreview.dir=false;
@ -2997,15 +3193,17 @@ DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) {
// read as .dmw
reader.seek(0,SEEK_SET);
int len=reader.readI();
logD("wave length %d",len);
if (len<=0 || len>256) {
throw EndOfFileException(&reader,reader.size());
}
wave->len=len;
wave->max=(unsigned char)reader.readC();
if (wave->max==255) { // new wavetable format
unsigned char waveVersion=reader.readC();
logI("reading modern .dmw...");
logD("wave version %d",waveVersion);
wave->max=reader.readC();
wave->max=(unsigned char)reader.readC();
for (int i=0; i<len; i++) {
wave->data[i]=reader.readI();
}
@ -3624,7 +3822,8 @@ void DivEngine::addOrder(int pos, bool duplicate, bool where) {
}
curSubSong->ordersLen++;
saveLock.unlock();
if (pos<=curOrder) curOrder++;
curOrder=pos+1;
prevOrder=curOrder;
if (playing && !freelance) {
playSub(false);
}
@ -4271,6 +4470,10 @@ void DivEngine::quitDispatch() {
}
cycles=0;
clockDrift=0;
midiClockCycles=0;
midiClockDrift=0;
midiTimeCycles=0;
midiTimeDrift=0;
chans=0;
playing=false;
curSpeed=0;
@ -4287,6 +4490,10 @@ void DivEngine::quitDispatch() {
totalTicks=0;
totalSeconds=0;
totalTicksR=0;
curMidiClock=0;
curMidiTime=0;
curMidiTimeCode=0;
curMidiTimePiece=0;
totalCmds=0;
lastCmds=0;
cmdsPerSecond=0;
@ -4313,6 +4520,9 @@ bool DivEngine::initAudioBackend() {
lowLatency=getConfInt("lowLatency",0);
metroVol=(float)(getConfInt("metroVol",100))/100.0f;
midiOutClock=getConfInt("midiOutClock",0);
midiOutTime=getConfInt("midiOutTime",0);
midiOutTimeRate=getConfInt("midiOutTimeRate",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;
@ -4468,6 +4678,7 @@ bool DivEngine::init() {
bool oldVol=getConfInt("configVersion",DIV_ENGINE_VERSION)<135;
if (preset.empty()) {
// try loading old preset
logD("trying to load old preset");
preset=decodeSysDesc(getConfString("initialSys",""));
oldVol=false;
}

View file

@ -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 DIV_VERSION "dev142"
#define DIV_ENGINE_VERSION 142
#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 "dev155"
#define DIV_ENGINE_VERSION 155
// 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"
@ -97,7 +105,7 @@ struct DivChannelState {
int delayOrder, delayRow, retrigSpeed, retrigTick;
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine;
int tremoloDepth, tremoloRate, tremoloPos;
unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR;
unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta;
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff;
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp;
bool wentThroughNote, goneThroughNote;
@ -138,6 +146,8 @@ struct DivChannelState {
panR(255),
panRL(0),
panRR(0),
lastVibrato(0),
lastPorta(0),
doNote(false),
legato(false),
portaStop(false),
@ -359,7 +369,10 @@ class DivEngine {
bool systemsRegistered;
bool hasLoadedSomething;
bool midiOutClock;
bool midiOutTime;
bool midiOutProgramChange;
int midiOutMode;
int midiOutTimeRate;
int softLockCount;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
size_t curSubSongIndex;
@ -367,8 +380,13 @@ class DivEngine {
double divider;
int cycles;
double clockDrift;
int midiClockCycles;
double midiClockDrift;
int midiTimeCycles;
double midiTimeDrift;
int stepPlay;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, curMidiClock, curMidiTime, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
int curMidiTimePiece, curMidiTimeCode;
unsigned char extValue, pendingMetroTick;
DivGroovePattern speeds;
short tempoAccum;
@ -397,12 +415,15 @@ class DivEngine {
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
DivCSPlayer* cmdStreamInt;
struct SamplePreview {
double rate;
int sample;
int wave;
int pos;
int pBegin, pEnd;
int rateMul, posSub;
bool dir;
SamplePreview():
rate(0.0),
@ -411,6 +432,8 @@ class DivEngine {
pos(0),
pBegin(-1),
pEnd(-1),
rateMul(1),
posSub(0),
dir(false) {}
} sPreview;
@ -441,7 +464,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();
@ -453,10 +475,15 @@ class DivEngine {
void recalcChans();
void reset();
void playSub(bool preserveDrift, int goalRow=0);
void runMidiClock(int totalCycles=1);
void runMidiTime(int totalCycles=1);
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);
@ -490,6 +517,13 @@ class DivEngine {
// change song (UNSAFE)
void changeSong(size_t songIndex);
// 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;
@ -519,6 +553,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.
@ -526,7 +562,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
@ -549,6 +585,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);
@ -898,6 +937,9 @@ class DivEngine {
// get macro interpreter
DivMacroInt* getMacroInt(int chan);
// get sample position
DivSamplePos getSamplePos(int chan);
// get osc buffer
DivDispatchOscBuffer* getOscBuffer(int chan);
@ -1091,7 +1133,10 @@ class DivEngine {
systemsRegistered(false),
hasLoadedSomething(false),
midiOutClock(false),
midiOutTime(false),
midiOutProgramChange(false),
midiOutMode(DIV_MIDI_MODE_NOTE),
midiOutTimeRate(0),
softLockCount(0),
subticks(0),
ticks(0),
@ -1112,16 +1157,24 @@ class DivEngine {
divider(60),
cycles(0),
clockDrift(0),
midiClockCycles(0),
midiClockDrift(0),
midiTimeCycles(0),
midiTimeDrift(0),
stepPlay(0),
changeOrd(-1),
changePos(0),
totalSeconds(0),
totalTicks(0),
totalTicksR(0),
curMidiClock(0),
curMidiTime(0),
totalCmds(0),
lastCmds(0),
cmdsPerSecond(0),
globalPitch(0),
curMidiTimePiece(0),
curMidiTimeCode(0),
extValue(0),
pendingMetroTick(0),
tempoAccum(0),
@ -1130,6 +1183,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
View 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
View 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

View 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>();
}

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

View 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() {}
};

View file

@ -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;
@ -695,6 +696,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
ds.wave.push_back(wave);
}
// sometimes there's a single length 0 wavetable in the file. I don't know why.
if (ds.waveLen==1) {
if (ds.wave[0]->len==0) {
ds.clearWavetables();
}
}
}
logV("%x",reader.tell());
@ -849,6 +857,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
sample->rate=22050;
if (ds.version>=0x0b) {
sample->rate=fileToDivRate(reader.readC());
sample->centerRate=sample->rate;
pitch=reader.readC();
vol=reader.readC();
}
@ -873,24 +882,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
// what the hell man...
cutStart=reader.readI();
cutEnd=reader.readI();
if (cutStart<0 || cutStart>length) {
logE("cutStart is out of range! (%d)",cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<0 || cutEnd>length) {
logE("cutEnd is out of range! (%d)",cutEnd);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<cutStart) {
logE("cutEnd %d is before cutStart %d. what's going on?",cutEnd,cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
logV("cutStart: %d cutEnd: %d",cutStart,cutEnd);
}
if (length>0) {
if (ds.version>0x08) {
@ -902,19 +894,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
data=new short[length];
reader.read(data,length*2);
}
if (ds.version>0x1b) {
if (cutStart!=0 || cutEnd!=length) {
// cut data
short* newData=new short[cutEnd-cutStart];
memcpy(newData,&data[cutStart],(cutEnd-cutStart)*sizeof(short));
delete[] data;
data=newData;
length=cutEnd-cutStart;
cutStart=0;
cutEnd=length;
}
}
#ifdef TA_BIG_ENDIAN
// convert to big-endian
@ -923,27 +902,76 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
#endif
if (pitch!=5) {
int scaledLen=ceil((double)length/samplePitches[pitch]);
if (scaledLen>0) {
// resample
logD("%d: scaling from %d...",i,pitch);
}
// render data
if (!sample->init((double)length/samplePitches[pitch])) {
logE("%d: error while initializing sample!",i);
}
unsigned int k=0;
float mult=(float)(vol)/50.0f;
for (double j=0; j<length; j+=samplePitches[pitch]) {
if (k>=sample->samples) {
break;
short* newData=new short[scaledLen];
memset(newData,0,scaledLen*sizeof(short));
int k=0;
float mult=(float)(vol)/50.0f;
for (double j=0; j<length; j+=samplePitches[pitch]) {
if (k>=scaledLen) {
break;
}
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
float next=(float)(data[(unsigned int)j]-0x80)*mult;
newData[k++]=fmin(fmax(next,-128),127);
} else {
float next=(float)data[(unsigned int)j]*mult;
newData[k++]=fmin(fmax(next,-32768),32767);
}
}
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
float next=(float)(data[(unsigned int)j]-0x80)*mult;
sample->data8[k++]=fmin(fmax(next,-128),127);
} else {
float next=(float)data[(unsigned int)j]*mult;
sample->data16[k++]=fmin(fmax(next,-32768),32767);
delete[] data;
data=newData;
}
logV("length: %d. scaledLen: %d.",length,scaledLen);
if (ds.version>=0x1b) {
if (cutStart<0 || cutStart>scaledLen) {
logE("cutStart is out of range! (%d, scaledLen: %d)",cutStart,scaledLen);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<0 || cutEnd>scaledLen) {
logE("cutEnd is out of range! (%d, scaledLen: %d)",cutEnd,scaledLen);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<cutStart) {
logE("cutEnd %d is before cutStart %d. what's going on?",cutEnd,cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutStart!=0 || cutEnd!=scaledLen) {
// cut data
short* newData=new short[cutEnd-cutStart];
memcpy(newData,&data[cutStart],(cutEnd-cutStart)*sizeof(short));
delete[] data;
data=newData;
scaledLen=cutEnd-cutStart;
cutStart=0;
cutEnd=scaledLen;
}
}
// copy data
if (!sample->init(scaledLen)) {
logE("%d: error while initializing sample!",i);
} else {
for (int i=0; i<scaledLen; i++) {
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
sample->data8[i]=data[i];
} else {
sample->data16[i]=data[i];
}
}
}
@ -1012,6 +1040,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.systemFlags[0].set("noEasyNoise",true);
}
// NES PCM
if (ds.system[0]==DIV_SYSTEM_NES) {
ds.systemFlags[0].set("dpcmMode",false);
}
ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0));
if (active) quitDispatch();
@ -1479,6 +1512,7 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS
}
break;
case DIV_SYSTEM_SFX_BEEPER:
case DIV_SYSTEM_SFX_BEEPER_QUADTONE:
newFlags.set("clockSel",(int)(oldFlags&1));
break;
case DIV_SYSTEM_SCC:
@ -1758,6 +1792,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<138) {
ds.brokenPortaLegato=true;
}
if (ds.version<155) {
ds.brokenFMOff=true;
}
ds.isDMF=false;
reader.readS(); // reserved
@ -2266,7 +2303,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version>=138) {
ds.brokenPortaLegato=reader.readC();
for (int i=0; i<7; i++) {
if (ds.version>=155) {
ds.brokenFMOff=reader.readC();
} else {
reader.readC();
}
for (int i=0; i<6; i++) {
reader.readC();
}
}
@ -2677,6 +2719,33 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
}
// Namco C30 noise compat
if (ds.version<145) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_NAMCO_CUS30) {
ds.systemFlags[i].set("newNoise",false);
}
}
}
// SrgaPCM slide compat
if (ds.version<153) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_SEGAPCM || ds.system[i]==DIV_SYSTEM_SEGAPCM_COMPAT) {
ds.systemFlags[i].set("oldSlides",true);
}
}
}
// NES PCM compat
if (ds.version<154) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_NES) {
ds.systemFlags[i].set("dpcmMode",false);
}
}
}
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();
@ -2925,6 +2994,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
size_t pos=reader.tell();
logD("reading sample data...");
for (int i=0; i<insCount; i++) {
logV("- %d: %d %d %d",i,pos,ds.sample[i]->samples,sampLens[i]);
if (!reader.seek(pos,SEEK_SET)) {
logD("%d: couldn't seek to %d",i,pos);
throw EndOfFileException(&reader,reader.tell());
@ -2934,6 +3004,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
}
// convert effects
logD("converting module...");
for (int ch=0; ch<=chCount; ch++) {
unsigned char fxCols=1;
for (int pat=0; pat<=patMax; pat++) {
@ -3214,6 +3285,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;
@ -3808,12 +4119,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="";
@ -3825,6 +4182,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);
@ -3845,6 +4205,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") {
@ -3862,7 +4230,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();
@ -3872,15 +4241,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();
@ -3889,20 +4275,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;
@ -3947,28 +4337,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!");
@ -4128,21 +4536,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;
@ -4157,6 +4675,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";
@ -4702,7 +5241,6 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
for (int i=0; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i];
insPtr.push_back(w->tell());
logV("writing instrument %d...",i);
ins->putInsData2(w,false);
}

View file

@ -214,7 +214,6 @@ bool DivInstrumentSNES::operator==(const DivInstrumentSNES& other) {
#undef _C
#define FEATURE_BEGIN(x) \
logV("- %s",x); \
w->write(x,2); \
size_t featStartSeek=w->tell(); \
w->writeS(0);
@ -928,6 +927,8 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
checkForWL=true;
if (ws.enabled) featureWS=true;
break;
case DIV_INS_PV1000:
break;
case DIV_INS_MAX:
break;
@ -2097,6 +2098,12 @@ void DivInstrument::readFeatureSM(SafeReader& reader, short version) {
amiga.noteMap[note].freq=reader.readS();
amiga.noteMap[note].map=reader.readS();
}
if (version<152) {
for (int note=0; note<120; note++) {
amiga.noteMap[note].freq=note;
}
}
}
READ_FEAT_END;
@ -2964,6 +2971,12 @@ DivDataErrors DivInstrument::readInsDataOld(SafeReader &reader, short version) {
for (int note=0; note<120; note++) {
amiga.noteMap[note].map=reader.readS();
}
if (version<152) {
for (int note=0; note<120; note++) {
amiga.noteMap[note].freq=note;
}
}
}
}

View file

@ -79,6 +79,7 @@ enum DivInstrumentType: unsigned short {
DIV_INS_GA20=46,
DIV_INS_POKEMINI=47,
DIV_INS_SM8521=48,
DIV_INS_PV1000=49,
DIV_INS_MAX,
DIV_INS_NULL
};
@ -413,7 +414,7 @@ struct DivInstrumentAmiga {
if (note>119) note=119;
return noteMap[note].freq;
}
return -1;
return note;
}
DivInstrumentAmiga():
@ -422,8 +423,9 @@ struct DivInstrumentAmiga {
useSample(false),
useWave(false),
waveLen(31) {
for (SampleMap& elem: noteMap) {
elem=SampleMap();
for (int i=0; i<120; i++) {
noteMap[i].map=-1;
noteMap[i].freq=i;
}
}
};

View 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;

View file

@ -23,11 +23,7 @@
static DivPattern emptyPat;
DivPattern::DivPattern() {
memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short));
for (int i=0; i<DIV_MAX_ROWS; i++) {
data[i][0]=0;
data[i][1]=0;
}
clear();
}
DivPattern* DivChannelData::getPattern(int index, bool create) {
@ -93,6 +89,14 @@ void DivPattern::copyOn(DivPattern* dest) {
memcpy(dest->data,data,sizeof(data));
}
void DivPattern::clear() {
memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short));
for (int i=0; i<DIV_MAX_ROWS; i++) {
data[i][0]=0;
data[i][1]=0;
}
}
DivChannelData::DivChannelData():
effectCols(1) {
memset(data,0,DIV_MAX_PATTERNS*sizeof(void*));

View file

@ -24,6 +24,11 @@ struct DivPattern {
String name;
short data[DIV_MAX_ROWS][DIV_MAX_COLS];
/**
* clear the pattern.
*/
void clear();
/**
* copy this pattern to another.
* @param dest the destination pattern.

View file

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

View file

@ -20,11 +20,15 @@
#define _USE_MATH_DEFINES
#include "amiga.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#define AMIGA_DIVIDER 8
#define AMIGA_VPMASK 7
#define CHIP_DIVIDER 16
#define chWrite(c,a,v) rWrite(((c)<<4)+0xa0+(a),(v));
const char* regCheatSheetAmiga[]={
"DMACON", "96",
"INTENA", "9A",
@ -78,54 +82,85 @@ const char** DivPlatformAmiga::getRegisterSheet() {
void DivPlatformAmiga::acquire(short** buf, size_t len) {
static int outL, outR, output;
for (size_t h=0; h<len; h++) {
bool hsync=bypassLimits;
outL=0;
outR=0;
for (int i=0; i<4; i++) {
if (!chan[i].active) {
oscBuf[i]->data[oscBuf[i]->needle++]=0;
continue;
// TODO:
// - improve DMA overrun behavior
// - does V/P mod really work like that?
amiga.volPos=(amiga.volPos+1)&AMIGA_VPMASK;
if (!bypassLimits) {
amiga.hPos+=AMIGA_DIVIDER;
if (amiga.hPos>=228) {
amiga.hPos-=228;
hsync=true;
}
if (chan[i].useWave || (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen)) {
chan[i].audSub-=AMIGA_DIVIDER;
if (chan[i].audSub<0) {
if (chan[i].useWave) {
writeAudDat(chan[i].ws.output[(chan[i].audPos++)&255]^0x80);
if (chan[i].audPos>=(unsigned int)(chan[i].audLen<<1)) {
chan[i].audPos=0;
}
} else {
DivSample* s=parent->getSample(chan[i].sample);
if (s->samples>0) {
if (chan[i].audPos<s->samples) {
writeAudDat(s->data8[chan[i].audPos++]);
}
if (s->isLoopable() && chan[i].audPos>=MIN(131071,(unsigned int)s->loopEnd)) {
chan[i].audPos=s->loopStart;
} else if (chan[i].audPos>=MIN(131071,s->samples)) {
chan[i].sample=-1;
}
} else {
chan[i].sample=-1;
}
for (int i=0; i<4; i++) {
// run DMA
if (amiga.dmaEn && amiga.audEn[i] && !amiga.audIr[i]) {
amiga.audTick[i]-=AMIGA_DIVIDER;
if (amiga.audTick[i]<0) {
amiga.audTick[i]+=MAX(AMIGA_DIVIDER,amiga.audPer[i]);
if (amiga.audByte[i]) {
// read next samples
if (!amiga.incLoc[i]) {
amiga.audDat[0][i]=sampleMem[(amiga.dmaLoc[i])&chipMask];
amiga.audDat[1][i]=sampleMem[(amiga.dmaLoc[i]+1)&chipMask];
amiga.incLoc[i]=true;
}
amiga.audWord[i]=!amiga.audWord[i];
}
/*if (chan[i].freq<124) {
if (++chan[i].busClock>=512) {
unsigned int rAmount=(124-chan[i].freq)*2;
if (chan[i].audPos>=rAmount) {
chan[i].audPos-=rAmount;
amiga.audByte[i]=!amiga.audByte[i];
if (!amiga.audByte[i] && (amiga.useV[i] || amiga.useP[i])) {
amiga.nextOut2[i]=((unsigned char)amiga.audDat[0][i])<<8|((unsigned char)amiga.audDat[1][i]);
if (i<3) {
if (amiga.useV[i] && amiga.useP[i]) {
if (amiga.audWord[i]) {
amiga.audPer[i+1]=amiga.nextOut2[i];
} else {
amiga.audVol[i+1]=amiga.nextOut2[i];
}
} else if (amiga.useV[i]) {
amiga.audVol[i+1]=amiga.nextOut2[i];
} else {
amiga.audPer[i+1]=amiga.nextOut2[i];
}
chan[i].busClock=0;
}
}*/
if (bypassLimits) {
chan[i].audSub+=MAX(AMIGA_DIVIDER,chan[i].freq);
} else {
chan[i].audSub+=MAX(114,chan[i].freq);
amiga.nextOut[i]=amiga.audDat[amiga.audByte[i]][i];
}
}
if (hsync) {
if (amiga.incLoc[i]) {
amiga.incLoc[i]=false;
amiga.dmaLoc[i]+=2;
// check for length
if ((--amiga.dmaLen[i])==0) {
if (amiga.audInt[i]) {
amiga.audIr[i]=true;
irq(i);
}
amiga.dmaLoc[i]=amiga.audLoc[i];
amiga.dmaLen[i]=amiga.audLen[i];
}
}
}
}
// output
if (!isMuted[i]) {
output=chan[i].audDat*chan[i].outVol;
if (amiga.audVol[i]>=64) {
output=amiga.nextOut[i]<<6;
} else if (amiga.audVol[i]<=0) {
output=0;
} else {
output=amiga.nextOut[i]*volTable[amiga.audVol[i]][amiga.volPos];
}
if (i==0 || i==3) {
outL+=(output*sep1)>>7;
outR+=(output*sep2)>>7;
@ -133,11 +168,12 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
outL+=(output*sep2)>>7;
outR+=(output*sep1)>>7;
}
oscBuf[i]->data[oscBuf[i]->needle++]=output<<2;
oscBuf[i]->data[oscBuf[i]->needle++]=(amiga.nextOut[i]*MIN(64,amiga.audVol[i]))<<2;
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=0;
}
}
filter[0][0]+=(filtConst*(outL-filter[0][0]))>>12;
filter[0][1]+=(filtConst*(filter[0][0]-filter[0][1]))>>12;
filter[1][0]+=(filtConst*(outR-filter[1][0]))>>12;
@ -147,11 +183,184 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
}
}
void DivPlatformAmiga::irq(int ch) {
// disable interrupt
rWrite(0x9a,128<<ch);
if (chan[ch].irLocL==0x400 && chan[ch].irLocH==0 && chan[ch].irLen==1) {
// turn off DMA
rWrite(0x96,1<<ch);
} else {
// write latched loc/len
chWrite(ch,0,chan[ch].irLocH);
chWrite(ch,2,chan[ch].irLocL);
chWrite(ch,4,chan[ch].irLen);
}
// acknowledge interrupt
rWrite(0x9c,128<<ch);
}
#define UPDATE_DMA(x) \
amiga.dmaLen[x]=amiga.audLen[x]; \
amiga.dmaLoc[x]=amiga.audLoc[x]; \
amiga.audByte[x]=true; \
amiga.audTick[x]=0;
void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) {
if (addr&1) return;
//logV("%.3x = %.4x",addr,val);
regPool[addr>>1]=val;
if (!skipRegisterWrites && dumpWrites) {
addWrite(addr,val);
}
switch (addr&0x1fe) {
case 0x96: { // DMACON
if (val&32768) {
if (val&1) amiga.audEn[0]=true;
if (val&2) amiga.audEn[1]=true;
if (val&4) amiga.audEn[2]=true;
if (val&8) amiga.audEn[3]=true;
if (val&512) amiga.dmaEn=true;
} else {
if (val&1) {
amiga.audEn[0]=false;
UPDATE_DMA(0);
}
if (val&2) {
amiga.audEn[1]=false;
UPDATE_DMA(1);
}
if (val&4) {
amiga.audEn[2]=false;
UPDATE_DMA(2);
}
if (val&8) {
amiga.audEn[3]=false;
UPDATE_DMA(3);
}
if (val&512) {
amiga.dmaEn=false;
}
}
break;
}
case 0x9a: { // INTENA
if (val&32768) {
if (val&128) amiga.audInt[0]=true;
if (val&256) amiga.audInt[1]=true;
if (val&512) amiga.audInt[2]=true;
if (val&1024) amiga.audInt[3]=true;
} else {
if (val&128) amiga.audInt[0]=false;
if (val&256) amiga.audInt[1]=false;
if (val&512) amiga.audInt[2]=false;
if (val&1024) amiga.audInt[3]=false;
}
break;
}
case 0x9c: { // INTREQ
if (val&32768) {
if (val&128) {
amiga.audIr[0]=true;
irq(0);
}
if (val&256) {
amiga.audIr[1]=true;
irq(1);
}
if (val&512) {
amiga.audIr[2]=true;
irq(2);
}
if (val&1024) {
amiga.audIr[3]=true;
irq(3);
}
} else {
if (val&128) amiga.audIr[0]=false;
if (val&256) amiga.audIr[1]=false;
if (val&512) amiga.audIr[2]=false;
if (val&1024) amiga.audIr[3]=false;
}
break;
}
case 0x9e: { // ADKCON
if (val&32768) {
if (val&1) amiga.useV[0]=true;
if (val&2) amiga.useV[1]=true;
if (val&4) amiga.useV[2]=true;
if (val&8) amiga.useV[3]=true;
if (val&16) amiga.useP[0]=true;
if (val&32) amiga.useP[1]=true;
if (val&64) amiga.useP[2]=true;
if (val&128) amiga.useP[3]=true;
} else {
if (val&1) amiga.useV[0]=false;
if (val&2) amiga.useV[1]=false;
if (val&4) amiga.useV[2]=false;
if (val&8) amiga.useV[3]=false;
if (val&16) amiga.useP[0]=false;
if (val&32) amiga.useP[1]=false;
if (val&64) amiga.useP[2]=false;
if (val&128) amiga.useP[3]=false;
}
break;
}
default: { // AUDx
if (addr>=0xa0 && addr<0xe0) {
const unsigned char ch=((addr-0xa0)>>4)&3;
bool updateDMA=false;
switch (addr&15) {
case 0: // LCH
amiga.audLoc[ch]&=0xffff;
amiga.audLoc[ch]|=val<<16;
updateDMA=true;
break;
case 2: // LCL
amiga.audLoc[ch]&=0xffff0000;
amiga.audLoc[ch]|=val&0xfffe;
updateDMA=true;
break;
case 4: // LEN
amiga.audLen[ch]=val;
updateDMA=true;
break;
case 6: // PER
amiga.audPer[ch]=val;
break;
case 8: // VOL
amiga.audVol[ch]=val;
break;
case 10: // DAT
amiga.audDat[0][ch]=val&0xff;
amiga.audDat[1][ch]=val>>8;
break;
}
if (updateDMA && !amiga.audEn[ch]) {
UPDATE_DMA(ch);
}
}
break;
}
}
}
void DivPlatformAmiga::updateWave(int ch) {
for (int i=0; i<MIN(256,(chan[ch].audLen<<1)); i++) {
sampleMem[(ch<<8)|i]=chan[ch].ws.output[i]^0x80;
}
}
void DivPlatformAmiga::tick(bool sysTick) {
for (int i=0; i<4; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol%65)*MIN(64,chan[i].std.vol.val))>>6;
chan[i].writeVol=true;
}
double off=1.0;
if (!chan[i].useWave && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
@ -173,11 +382,13 @@ void DivPlatformAmiga::tick(bool sysTick) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
chan[i].updateWave=true;
}
}
if (chan[i].useWave && chan[i].active) {
chan[i].ws.tick();
if (chan[i].ws.tick()) {
chan[i].updateWave=true;
}
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
@ -189,8 +400,31 @@ void DivPlatformAmiga::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].audPos=0;
if (chan[i].std.phaseReset.val==1 && chan[i].active) {
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) {
@ -198,15 +432,115 @@ void DivPlatformAmiga::tick(bool sysTick) {
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);
if (chan[i].freq>4095) chan[i].freq=4095;
if (chan[i].freq<0) chan[i].freq=0;
chWrite(i,6,chan[i].freq);
if (chan[i].keyOn) {
}
if (chan[i].keyOff) {
if (chan[i].useWave) {
rWrite(0x9a,(128<<i));
chWrite(i,0,0);
chWrite(i,2,i<<8);
chWrite(i,4,chan[i].audLen);
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);
int start=chan[i].audPos&(~1);
if (start>s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) start=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
int len=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-start;
if (len<0) len=0;
if (len>131070) len=131070;
len>>=1;
start+=sampleOff[chan[i].sample];
if (len<1) {
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);
}
}
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;
chan[i].irLocH=loopPos>>16;
chan[i].irLocL=loopPos;
chan[i].irLen=MIN(65535,loopEnd);
} else {
chan[i].irLocH=0;
chan[i].irLocL=0x400;
chan[i].irLen=1;
}
rWrite(0x9a,0x8000|(128<<i));
} else {
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].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;
chWrite(i,8,chan[i].outVol);
}
if (chan[i].updateWave) {
chan[i].updateWave=false;
updateWave(i);
}
}
if (updateADKCon) {
updateADKCon=false;
rWrite(0x9e,0xff);
rWrite(0x9e,(
0x8000|
(chan[0].useV?1:0)|
(chan[1].useV?2:0)|
(chan[2].useV?4:0)|
(chan[3].useV?8:0)|
(chan[0].useP?16:0)|
(chan[1].useP?32:0)|
(chan[2].useP?64:0)|
(chan[3].useP?128:0)
));
}
}
int DivPlatformAmiga::dispatch(DivCommand c) {
@ -214,6 +548,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
if (ins->amiga.useWave) {
if (!chan[c.chan].useWave) chan[c.chan].updateWave=true;
chan[c.chan].useWave=true;
chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1;
if (chan[c.chan].insChanged) {
@ -221,10 +556,14 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
chan[c.chan].wave=0;
chan[c.chan].ws.setWidth(chan[c.chan].audLen<<1);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].updateWave=true;
}
}
} else {
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
chan[c.chan].useWave=false;
}
if (c.value!=DIV_NOTE_NULL) {
@ -248,9 +587,11 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
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].writeVol=true;
}
if (chan[c.chan].useWave) {
chan[c.chan].ws.init(ins,chan[c.chan].audLen<<1,255,chan[c.chan].insChanged);
chan[c.chan].updateWave=true;
}
chan[c.chan].insChanged=false;
break;
@ -276,6 +617,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
chan[c.chan].writeVol=true;
}
}
break;
@ -294,6 +636,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
chan[c.chan].wave=c.value;
chan[c.chan].keyOn=true;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].updateWave=true;
break;
case DIV_CMD_NOTE_PORTA: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
@ -336,6 +679,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
case DIV_CMD_SAMPLE_POS:
if (chan[c.chan].useWave) break;
chan[c.chan].audPos=c.value;
if (chan[c.chan].active) chan[c.chan].keyOn=true;
chan[c.chan].setPos=true;
break;
case DIV_CMD_AMIGA_FILTER:
@ -344,9 +688,11 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
break;
case DIV_CMD_AMIGA_AM:
chan[c.chan].useV=c.value;
updateADKCon=true;
break;
case DIV_CMD_AMIGA_PM:
chan[c.chan].useP=c.value;
updateADKCon=true;
break;
case DIV_CMD_GET_VOLMAX:
return 64;
@ -374,9 +720,12 @@ void DivPlatformAmiga::forceIns() {
for (int i=0; i<4; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].audPos=131072;
chan[i].audDat=0;
chan[i].sample=-1;
/*chan[i].keyOn=false;
chan[i].keyOff=false;
chan[i].sample=-1;*/
if (!chan[i].useWave) {
rWrite(0x96,1<<i);
}
}
}
@ -389,6 +738,7 @@ DivDispatchOscBuffer* DivPlatformAmiga::getOscBuffer(int ch) {
}
void DivPlatformAmiga::reset() {
memset(regPool,0,256*sizeof(unsigned short));
for (int i=0; i<4; i++) {
chan[i]=DivPlatformAmiga::Channel();
chan[i].std.setEngine(parent);
@ -399,6 +749,11 @@ void DivPlatformAmiga::reset() {
}
filterOn=false;
filtConst=filterOn?filtConstOn:filtConstOff;
updateADKCon=true;
amiga=Amiga();
// enable DMA
rWrite(0x96,0x8200);
}
int DivPlatformAmiga::getOutputCount() {
@ -413,6 +768,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) {
@ -425,6 +792,7 @@ void DivPlatformAmiga::notifyWaveChange(int wave) {
for (int i=0; i<4; i++) {
if (chan[i].useWave && chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
chan[i].updateWave=true;
}
}
}
@ -449,6 +817,10 @@ void DivPlatformAmiga::setFlags(const DivConfig& flags) {
sep1=sep+127;
sep2=127-sep;
amigaModel=flags.getInt("chipType",0);
chipMem=flags.getInt("chipMem",21);
if (chipMem<18) chipMem=18;
if (chipMem>21) chipMem=21;
chipMask=(1<<chipMem)-1;
bypassLimits=flags.getBool("bypassLimits",false);
if (amigaModel) {
filtConstOff=4000;
@ -459,6 +831,116 @@ void DivPlatformAmiga::setFlags(const DivConfig& flags) {
}
}
void DivPlatformAmiga::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformAmiga::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
unsigned char* DivPlatformAmiga::getRegisterPool() {
// update DMACONR
regPool[1]=(
(amiga.audEn[0]?1:0)|
(amiga.audEn[1]?2:0)|
(amiga.audEn[2]?4:0)|
(amiga.audEn[3]?8:0)|
(amiga.dmaEn?512:0)
);
// update ADKCONR
regPool[0x10>>1]=(
(amiga.useV[0]?1:0)|
(amiga.useV[1]?2:0)|
(amiga.useV[2]?4:0)|
(amiga.useV[3]?8:0)|
(amiga.useP[0]?16:0)|
(amiga.useP[1]?32:0)|
(amiga.useP[2]?64:0)|
(amiga.useP[3]?128:0)
);
// update INTENAR
regPool[0x1c>>1]=(
(amiga.audInt[0]?128:0)|
(amiga.audInt[1]?256:0)|
(amiga.audInt[2]?512:0)|
(amiga.audInt[3]?1024:0)|
16384 // INTEN
);
// update INTREQR
regPool[0x1e>>1]=(
(amiga.audIr[0]?128:0)|
(amiga.audIr[1]?256:0)|
(amiga.audIr[2]?512:0)|
(amiga.audIr[3]?1024:0)
);
return (unsigned char*)regPool;
}
int DivPlatformAmiga::getRegisterPoolSize() {
return 128;
}
int DivPlatformAmiga::getRegisterPoolDepth() {
return 16;
}
const void* DivPlatformAmiga::getSampleMem(int index) {
return index == 0 ? sampleMem : NULL;
}
size_t DivPlatformAmiga::getSampleMemCapacity(int index) {
return index == 0 ? (1<<chipMem) : 0;
}
size_t DivPlatformAmiga::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
bool DivPlatformAmiga::isSampleLoaded(int index, int sample) {
if (index!=0) return false;
if (sample<0 || sample>255) return false;
return sampleLoaded[sample];
}
void DivPlatformAmiga::renderSamples(int sysID) {
memset(sampleMem,0,2097152);
memset(sampleOff,0,256*sizeof(unsigned int));
memset(sampleLoaded,0,256*sizeof(bool));
// first 1024 bytes reserved for wavetable
// the next 2 bytes are reserved for end of sample
size_t memPos=1026;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
if (!s->renderOn[0][sysID]) {
sampleOff[i]=0;
continue;
}
if (memPos>=getSampleMemCapacity()) {
logW("out of Amiga memory for sample %d!",i);
break;
}
int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length);
if (actualLength>0) {
sampleOff[i]=memPos;
memcpy(&sampleMem[memPos],s->data8,actualLength);
memPos+=actualLength;
}
// align memPos to short
if (memPos&1) memPos++;
sampleLoaded[i]=true;
}
sampleMemLen=memPos;
}
int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
@ -467,12 +949,28 @@ int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, const DivCon
oscBuf[i]=new DivDispatchOscBuffer;
isMuted[i]=false;
}
// Paula volume is implemented using PWM rather than a multiplication.
// sources:
// - https://www.youtube.com/watch?v=xyQlmsD7PAg
// - https://linusakesson.net/music/paulimba/index.php
memset(volTable,0,64*64);
for (int i=0; i<64; i++) {
for (int j=0; j<64; j++) {
volTable[i][j/AMIGA_DIVIDER]+=(j<i)*(64/AMIGA_DIVIDER);
}
}
sampleMem=new unsigned char[2097152];
sampleMemLen=0;
setFlags(flags);
reset();
return 6;
}
void DivPlatformAmiga::quit() {
delete[] sampleMem;
for (int i=0; i<4; i++) {
delete oscBuf[i];
}

View file

@ -26,29 +26,32 @@
class DivPlatformAmiga: public DivDispatch {
struct Channel: public SharedChannel<signed char> {
unsigned int audLoc;
unsigned short audLen;
unsigned short audLen, irLocL, irLocH, irLen;
unsigned int audPos;
int audSub;
signed char audDat;
unsigned char volPos;
int sample, wave;
int busClock;
bool useWave, setPos, useV, useP;
bool useWave, setPos, useV, useP, dmaOn, audDatClock, writeVol, updateWave;
DivWaveSynth ws;
Channel():
SharedChannel<signed char>(64),
audLoc(0),
audLen(0),
irLocL(0),
irLocH(0),
irLen(2),
audPos(0),
audSub(0),
audDat(0),
volPos(0),
sample(-1),
wave(-1),
busClock(0),
useWave(false),
setPos(false),
useV(false),
useP(false) {}
useP(false),
dmaOn(false),
audDatClock(false),
writeVol(true),
updateWave(true) {}
};
Channel chan[4];
DivDispatchOscBuffer* oscBuf[4];
@ -56,21 +59,77 @@ class DivPlatformAmiga: public DivDispatch {
bool bypassLimits;
bool amigaModel;
bool filterOn;
bool updateADKCon;
struct Amiga {
// register state
bool audInt[4]; // interrupt on
bool audIr[4]; // interrupt request
bool audEn[4]; // audio DMA on
bool useP[4]; // period modulation
bool useV[4]; // volume modulation
bool dmaEn;
unsigned int audLoc[4]; // address
unsigned short audLen[4]; // length
unsigned short audPer[4]; // period
unsigned char audVol[4]; // volume
signed char audDat[2][4]; // data
signed char nextOut[4];
unsigned short nextOut2[4];
// internal state
int audTick[4]; // tick of period
unsigned int dmaLoc[4]; // address
unsigned short dmaLen[4]; // position
bool audByte[4]; // which byte of audDat to output
bool audWord[4]; // for P/V
bool incLoc[4]; // whether dmaLoc/dmaLen should be updated
unsigned char volPos; // position of volume PWM
unsigned short hPos; // horizontal position of beam
unsigned char state[4]; // current channel state
Amiga() {
memset(this,0,sizeof(*this));
}
} amiga;
int filter[2][4];
int filtConst;
int filtConstOff, filtConstOn;
int chipMem, chipMask;
unsigned char volTable[64][64];
unsigned int sampleOff[256];
bool sampleLoaded[256];
unsigned short regPool[256];
unsigned char* sampleMem;
size_t sampleMemLen;
int sep1, sep2;
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);
void updateWave(int ch);
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
int getRegisterPoolDepth();
void reset();
void forceIns();
void tick(bool sysTick=true);
@ -78,11 +137,19 @@ 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);
void notifyInsDeletion(void* ins);
void renderSamples(int chipID);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const void* getSampleMem(int index=0);
size_t getSampleMemCapacity(int index=0);
size_t getSampleMemUsage(int index=0);
bool isSampleLoaded(int index, int sample);
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
};

View file

@ -120,7 +120,7 @@ void DivPlatformAY8910::runDAC() {
bool end=false;
bool changed=false;
int prevOut=chan[i].dac.out;
while (chan[i].dac.period>rate && !end) {
while (chan[i].dac.period>dacRate && !end) {
DivSample* s=parent->getSample(chan[i].dac.sample);
if (s->samples<=0) {
chan[i].dac.sample=-1;
@ -143,7 +143,7 @@ void DivPlatformAY8910::runDAC() {
end=true;
break;
}
chan[i].dac.period-=rate;
chan[i].dac.period-=dacRate;
}
if (changed && !end) {
if (!isMuted[i]) {
@ -292,8 +292,6 @@ void DivPlatformAY8910::tick(bool sysTick) {
if (chan[i].std.phaseReset.val==1) {
if (chan[i].nextPSGMode.dac) {
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AY);
chan[i].dac.sample=ins->amiga.getSample(chan[i].note);
if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
if (dumpWrites) {
rWrite(0x08+i,0);
@ -405,7 +403,10 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
if (chan[c.chan].nextPSGMode.dac) {
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);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
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);
@ -686,6 +687,7 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) {
void DivPlatformAY8910::forceIns() {
for (int i=0; i<3; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
}
immWrite(0x0b,ayEnvPeriod);
immWrite(0x0c,ayEnvPeriod>>8);
@ -700,6 +702,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];
}
@ -786,6 +797,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
chipClock=extClock;
rate=chipClock/extDiv;
clockSel=false;
dacRate=chipClock/dacRateDiv;
} else {
clockSel=flags.getBool("halfClock",false);
switch (flags.getInt("clockSel",0)) {
@ -840,6 +852,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
}
CHECK_CUSTOM_CLOCK;
rate=chipClock/8;
dacRate=rate;
}
for (int i=0; i<3; i++) {
oscBuf[i]->rate=rate;

View file

@ -104,7 +104,9 @@ class DivPlatformAY8910: public DivDispatch {
bool extMode;
unsigned int extClock;
int dacRate;
unsigned char extDiv;
unsigned char dacRateDiv;
bool stereo, sunsoft, intellivision, clockSel;
bool ioPortA, ioPortB;
@ -119,7 +121,6 @@ class DivPlatformAY8910: public DivDispatch {
short* ayBuf[3];
size_t ayBufLen;
void runDAC();
void checkWrites();
void updateOutSel(bool immediate=false);
@ -127,6 +128,7 @@ class DivPlatformAY8910: public DivDispatch {
friend void putDispatchChan(void*,int,int);
public:
void runDAC();
void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8);
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
@ -143,6 +145,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);
@ -150,10 +153,11 @@ class DivPlatformAY8910: public DivDispatch {
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8):
DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8, unsigned char ddiv=24):
DivDispatch(),
extMode(useExtMode),
extClock(eclk),
extDiv(ediv) {}
extDiv(ediv),
dacRateDiv(ddiv) {}
};
#endif

View file

@ -280,8 +280,6 @@ void DivPlatformAY8930::tick(bool sysTick) {
if (chan[i].std.phaseReset.val==1) {
if (chan[i].nextPSGMode.dac) {
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AY8930);
chan[i].dac.sample=ins->amiga.getSample(chan[i].note);
if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
if (dumpWrites) {
rWrite(0x08+i,0);
@ -406,7 +404,10 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
if (chan[c.chan].nextPSGMode.dac) {
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);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
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);
@ -696,6 +697,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];
}

View file

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

View file

@ -410,7 +410,7 @@ void DivPlatformES5506::tick(bool sysTick) {
if (chan[i].pcmChanged.changed) {
if (chan[i].pcmChanged.index) {
const int next=chan[i].pcm.next;
bool sampleVaild=false;
bool sampleValid=false;
if (((ins->amiga.useNoteMap) && (next>=0 && next<120)) ||
((!ins->amiga.useNoteMap) && (next>=0 && next<parent->song.sampleLen))) {
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next];
@ -420,7 +420,7 @@ void DivPlatformES5506::tick(bool sysTick) {
}
if (sample>=0 && sample<parent->song.sampleLen) {
const unsigned int offES5506=sampleOffES5506[sample];
sampleVaild=true;
sampleValid=true;
chan[i].pcm.index=sample;
chan[i].pcm.isNoteMap=ins->amiga.useNoteMap;
DivSample* s=parent->getSample(sample);
@ -433,7 +433,6 @@ void DivPlatformES5506::tick(bool sysTick) {
off=(double)center/8363.0;
}
if (ins->amiga.useNoteMap) {
off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)next-48.0)/12.0));
chan[i].pcm.note=next;
}
// get loop mode
@ -459,7 +458,7 @@ void DivPlatformES5506::tick(bool sysTick) {
}
}
}
if (sampleVaild) {
if (sampleValid) {
if (!chan[i].keyOn) {
pageWrite(0x20|i,0x03,(chan[i].pcm.direction)?chan[i].pcm.end:chan[i].pcm.start);
}
@ -618,10 +617,6 @@ void DivPlatformES5506::tick(bool sysTick) {
} else {
off=(double)center/8363.0;
}
if (ins->amiga.useNoteMap) {
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[chan[i].pcm.note];
off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)chan[i].pcm.note-48.0)/12.0));
}
chan[i].pcm.loopStart=(chan[i].pcm.start+(s->loopStart<<11))&0xfffff800;
chan[i].pcm.loopEnd=(chan[i].pcm.start+((s->loopEnd-1)<<11))&0xffffff80;
chan[i].pcm.freqOffs=PITCH_OFFSET*off;
@ -750,24 +745,22 @@ int DivPlatformES5506::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5506);
bool sampleVaild=false;
bool sampleValid=false;
if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) ||
((!ins->amiga.useNoteMap) && (ins->amiga.initSample>=0 && ins->amiga.initSample<parent->song.sampleLen))) {
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[c.value];
int sample=ins->amiga.initSample;
if (ins->amiga.useNoteMap) {
sample=noteMapind.map;
}
int sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
if (sample>=0 && sample<parent->song.sampleLen) {
sampleVaild=true;
sampleValid=true;
chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xfff;
chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xfff;
chan[c.chan].pcm.next=sample;
chan[c.chan].pcm.note=c.value;
chan[c.chan].pcm.next=ins->amiga.useNoteMap?c.value:sample;
chan[c.chan].filter=ins->es5506.filter;
chan[c.chan].envelope=ins->es5506.envelope;
}
}
if (!sampleVaild) {
if (!sampleValid) {
chan[c.chan].pcm.index=chan[c.chan].pcm.next=-1;
chan[c.chan].filter=DivInstrumentES5506::Filter();
chan[c.chan].envelope=DivInstrumentES5506::Envelope();

View file

@ -200,7 +200,10 @@ int DivPlatformGA20::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255;
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
}
@ -336,6 +339,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];
}

View file

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

View file

@ -82,8 +82,12 @@ void DivPlatformGB::acquire(short** buf, size_t len) {
void DivPlatformGB::updateWave() {
rWrite(0x1a,0);
for (int i=0; i<16; i++) {
int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31];
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31];
int nibble1=ws.output[((i<<1)+antiClickWavePos)&31];
int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31];
if (invertWave) {
nibble1^=15;
nibble2^=15;
}
rWrite(0x30+i,(nibble1<<4)|nibble2);
}
antiClickWavePos&=31;
@ -658,6 +662,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) {
model=GB_MODEL_AGB;
break;
}
invertWave=flags.getBool("invertWave",true);
enoughAlready=flags.getBool("enoughAlready",false);
}

View file

@ -57,6 +57,7 @@ class DivPlatformGB: public DivDispatch {
DivDispatchOscBuffer* oscBuf[4];
bool isMuted[4];
bool antiClickEnabled;
bool invertWave;
bool enoughAlready;
unsigned char lastPan;
DivWaveSynth ws;

View file

@ -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) {
@ -674,7 +681,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (c.chan>=5 && chan[c.chan].dacMode) {
//if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) { // Furnace mode
if (c.value!=DIV_NOTE_NULL) chan[c.chan].dacSample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
@ -1202,6 +1212,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];
}

View file

@ -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();

View file

@ -433,13 +433,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
if (isOpMuted[ch-2] || !op.enable) {
rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
}
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));

View file

@ -34,7 +34,7 @@ const char* regCheatSheetK007232[]={
"CHX_StartM", "X*6+3",
"CHX_StartH", "X*6+4",
"CHX_Keyon", "X*6+5",
"SLEV", "C", // external IO
"SLEV", "C", // external IO (Volume for Mono speaker)
"Loop", "D",
// off-chip
"CHX_Volume", "X*2+10",
@ -157,8 +157,7 @@ void DivPlatformK007232::tick(bool sysTick) {
rWrite(0x10+i,(chan[i].lvol&0xf)|((chan[i].rvol&0xf)<<4));
chan[i].prevPan=newPan;
}
}
else {
} else {
const unsigned char prevVolume=lastVolume;
lastVolume=(lastVolume&~(0xf<<(i<<2)))|((chan[i].resVol&0xf)<<(i<<2));
if (prevVolume!=lastVolume) {
@ -274,7 +273,10 @@ int DivPlatformK007232::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:15;
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
}
@ -477,6 +479,7 @@ void DivPlatformK007232::setFlags(const DivConfig& flags) {
rate=chipClock/4;
stereo=flags.getBool("stereo",false);
for (int i=0; i<2; i++) {
chan[i].volumeChanged=true;
oscBuf[i]->rate=rate;
}
}

View file

@ -261,13 +261,14 @@ int DivPlatformLynx::dispatch(DivCommand c) {
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127;
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
if (chan[c.chan].pcm) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleBaseFreq=NOTE_FREQUENCY(c.value);
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].sampleAccum=0;
chan[c.chan].samplePos=0;
}
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
chan[c.chan].actualNote=c.value;
@ -415,6 +416,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];
}

View file

@ -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();

View file

@ -176,7 +176,10 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
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);
if (c.value!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);

View file

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

View file

@ -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:
@ -316,9 +317,16 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
rWrite((i<<3)+0x100,0);
rWrite((i<<3)+0x104,(chan[(i+1)&7].noise?0x80:0));
}
rWrite((i<<3)+0x103,chan[i].freq&0xff);
rWrite((i<<3)+0x102,(chan[i].freq>>8)&0xff);
rWrite((i<<3)+0x101,((chan[i].freq>>16)&15)|(i<<4));
if (chan[i].noise && newNoise) {
int noiseFreq=chan[i].freq>>9;
if (noiseFreq<0) noiseFreq=0;
if (noiseFreq>255) noiseFreq=255;
rWrite((i<<3)+0x103,noiseFreq);
} else {
rWrite((i<<3)+0x103,chan[i].freq&0xff);
rWrite((i<<3)+0x102,(chan[i].freq>>8)&0xff);
rWrite((i<<3)+0x101,((chan[i].freq>>16)&15)|(i<<4));
}
}
break;
}
@ -489,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() {
@ -503,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) {
@ -510,6 +540,7 @@ void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
updateWave(i);
}
}
updateROMWaves();
}
void DivPlatformNamcoWSG::notifyInsDeletion(void* ins) {
@ -544,6 +575,9 @@ void DivPlatformNamcoWSG::setFlags(const DivConfig& flags) {
for (int i=0; i<chans; i++) {
oscBuf[i]->rate=rate;
}
newNoise=flags.getBool("newNoise",true);
romMode=flags.getBool("romMode",false);
if (devType==30) romMode=false;
}
void DivPlatformNamcoWSG::poke(unsigned int addr, unsigned short val) {

View file

@ -49,8 +49,11 @@ 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:

View file

@ -211,7 +211,7 @@ void DivPlatformNES::tick(bool sysTick) {
chan[i].outVol=VOL_SCALE_LINEAR_BROKEN(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
if (chan[i].outVol<0) chan[i].outVol=0;
if (i==2) { // triangle
rWrite(0x4000+i*4,(chan[i].outVol==0)?0:255);
rWrite(0x4000+i*4,(chan[i].outVol==0)?0:linearCount);
chan[i].freqChanged=true;
} else {
rWrite(0x4000+i*4,(chan[i].envMode<<4)|chan[i].outVol|((chan[i].duty&3)<<6));
@ -262,7 +262,7 @@ void DivPlatformNES::tick(bool sysTick) {
//rWrite(16+i*5,chan[i].sweep);
}
}
if (i<2) if (chan[i].std.phaseReset.had) {
if (i<3) if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].freqChanged=true;
chan[i].prevFreq=-1;
@ -337,14 +337,22 @@ void DivPlatformNES::tick(bool sysTick) {
goingToLoop=parent->getSample(dacSample)->isLoopable();
// write DPCM
rWrite(0x4015,15);
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
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(0x4015,31);
dpcmBank=dpcmAddr>>14;
}
} else {
if (dpcmMode) {
if (nextDPCMFreq>=0) {
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
nextDPCMFreq=-1;
} else {
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
}
}
@ -353,6 +361,8 @@ void DivPlatformNES::tick(bool sysTick) {
if (chan[4].keyOn) chan[4].keyOn=false;
chan[4].freqChanged=false;
}
nextDPCMFreq=-1;
}
int DivPlatformNES::dispatch(DivCommand c) {
@ -361,7 +371,10 @@ int DivPlatformNES::dispatch(DivCommand c) {
if (c.chan==4) { // 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);
if (c.value!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
@ -398,12 +411,17 @@ int DivPlatformNES::dispatch(DivCommand c) {
chan[c.chan].furnaceDac=false;
if (dpcmMode && !skipRegisterWrites) {
unsigned int dpcmAddr=sampleOffDPCM[dacSample];
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
unsigned int dpcmLen=parent->getSample(dacSample)->lengthDPCM>>4;
if (dpcmLen>255) dpcmLen=255;
goingToLoop=parent->getSample(dacSample)->isLoopable();
// write DPCM
rWrite(0x4015,15);
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
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(0x4015,31);
@ -431,7 +449,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
rWrite(0x4000+c.chan*4,linearCount);
} else if (!parent->song.brokenOutVol2) {
rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
}
@ -463,7 +481,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
}
if (chan[c.chan].active) {
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
rWrite(0x4000+c.chan*4,linearCount);
} else {
rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
}
@ -539,6 +557,16 @@ int DivPlatformNES::dispatch(DivCommand c) {
countMode=c.value;
rWrite(0x4017,countMode?0x80:0);
break;
case DIV_CMD_NES_LINEAR_LENGTH:
if (c.chan==2) {
linearCount=c.value;
if (chan[c.chan].active) {
rWrite(0x4000+c.chan*4,(chan[c.chan].outVol==0)?0:linearCount);
chan[c.chan].freqChanged=true;
chan[c.chan].prevFreq=-1;
}
}
break;
case DIV_CMD_NES_DMC:
rWrite(0x4011,c.value&0x7f);
break;
@ -552,6 +580,14 @@ int DivPlatformNES::dispatch(DivCommand c) {
rWrite(0x4013,0);
rWrite(0x4015,31);
break;
case DIV_CMD_SAMPLE_FREQ: {
bool goingToLoop=parent->getSample(dacSample)->isLoopable();
if (dpcmMode) {
nextDPCMFreq=c.value&15;
rWrite(0x4010,(c.value&15)|(goingToLoop?0x40:0));
}
break;
}
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
@ -652,9 +688,11 @@ void DivPlatformNES::reset() {
dacSample=-1;
sampleBank=0;
dpcmBank=0;
dpcmMode=false;
dpcmMode=dpcmModeDefault;
goingToLoop=false;
countMode=false;
nextDPCMFreq=-1;
linearCount=255;
if (useNP) {
nes1_NP->Reset();
@ -706,6 +744,8 @@ void DivPlatformNES::setFlags(const DivConfig& flags) {
for (int i=0; i<5; i++) {
oscBuf[i]->rate=rate/32;
}
dpcmModeDefault=flags.getBool("dpcmMode",true);
}
void DivPlatformNES::notifyInsDeletion(void* ins) {

View file

@ -52,7 +52,10 @@ class DivPlatformNES: public DivDispatch {
unsigned char sampleBank;
unsigned char writeOscBuf;
unsigned char apuType;
unsigned char linearCount;
signed char nextDPCMFreq;
bool dpcmMode;
bool dpcmModeDefault;
bool dacAntiClickOn;
bool useNP;
bool goingToLoop;

View file

@ -865,7 +865,10 @@ int DivPlatformOPL::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(18,chan[c.chan].outVol);
}
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
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);
@ -1564,7 +1567,7 @@ DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) {
}
DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) {
if (oplType==759) {
if (oplType==759 || chipType==8950) {
if (ch>=totalChans+1) return NULL;
} else {
if (ch>=totalChans) return NULL;

View file

@ -282,7 +282,10 @@ int DivPlatformPCE::dispatch(DivCommand c) {
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);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
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);
@ -505,6 +508,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];
}

View file

@ -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();

View file

@ -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;
@ -267,7 +271,10 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) {
}
}
} else {
if (c.value!=DIV_NOTE_NULL) chan[0].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[0].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
chan[0].useWave=false;
}
if (c.value!=DIV_NOTE_NULL) {
@ -437,6 +444,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;

View file

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

View file

@ -0,0 +1,302 @@
/**
* 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 "pv1000.h"
#include "../engine.h"
#include <math.h>
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; d65010g031_write(&d65010g031,a,v);}
#define CHIP_DIVIDER 1024
const char* regCheatSheetPV1000[]={
"CH1_Pitch", "00",
"CH2_Pitch", "01",
"CH3_Pitch", "02",
"Control", "03",
NULL
};
const char** DivPlatformPV1000::getRegisterSheet() {
return regCheatSheetPV1000;
}
void DivPlatformPV1000::acquire(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
short samp=d65010g031_sound_tick(&d65010g031,1);
buf[0][h]=samp;
for (int i=0; i<3; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.out[i]);
}
}
}
void DivPlatformPV1000::tick(bool sysTick) {
for (int i=0; i<3; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(chan[i].vol && chan[i].std.vol.val);
chan[i].freqChanged=true;
}
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.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].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=0x3f-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].freq<0) chan[i].freq=0;
if (chan[i].freq>62) chan[i].freq=62;
if (isMuted[i]) chan[i].keyOn=false;
if (chan[i].keyOn) {
rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq);
chan[i].keyOn=false;
} else if (chan[i].freqChanged && chan[i].active && !isMuted[i]) {
rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq);
}
if (chan[i].keyOff) {
rWrite(i,0x3f);
chan[i].keyOff=false;
}
chan[i].freqChanged=false;
}
}
}
int DivPlatformPV1000::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PV1000);
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);
break;
}
case DIV_CMD_NOTE_OFF:
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;
}
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) {
chan[c.chan].outVol=c.value;
}
if (chan[c.chan].active) {
chan[c.chan].freqChanged=true;
}
}
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_STD_NOISE_MODE: // ring modulation
if (c.value&1) {
rWrite(3,3);
} else {
rWrite(3,2);
}
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((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_PV1000));
}
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 1;
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_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformPV1000::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (mute) {
chan[ch].keyOff=true;
} else if (chan[ch].active) {
chan[ch].keyOn=true;
chan[ch].freqChanged=true;
}
}
void DivPlatformPV1000::forceIns() {
for (int i=0; i<3; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
}
}
void* DivPlatformPV1000::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformPV1000::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivDispatchOscBuffer* DivPlatformPV1000::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformPV1000::getRegisterPool() {
return regPool;
}
int DivPlatformPV1000::getRegisterPoolSize() {
return 4;
}
void DivPlatformPV1000::reset() {
memset(regPool,0,4);
for (int i=0; i<3; i++) {
chan[i]=Channel();
chan[i].std.setEngine(parent);
}
d65010g031_reset(&d65010g031);
// mute
rWrite(0,0x3f);
rWrite(1,0x3f);
rWrite(2,0x3f);
rWrite(3,2);
}
int DivPlatformPV1000::getOutputCount() {
return 1;
}
void DivPlatformPV1000::notifyInsDeletion(void* ins) {
for (int i=0; i<3; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformPV1000::setFlags(const DivConfig& flags) {
chipClock=COLOR_NTSC*5.0;
CHECK_CUSTOM_CLOCK;
rate=chipClock/1024;
for (int i=0; i<3; i++) {
oscBuf[i]->rate=rate;
}
}
void DivPlatformPV1000::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
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;
skipRegisterWrites=false;
for (int i=0; i<3; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
reset();
return 4;
}
void DivPlatformPV1000::quit() {
for (int i=0; i<3; i++) {
delete oscBuf[i];
}
}
DivPlatformPV1000::~DivPlatformPV1000() {
}

View file

@ -0,0 +1,64 @@
/**
* 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 _PV1000_H
#define _PV1000_H
#include "../dispatch.h"
#include "sound/d65modified.h"
#include <queue>
class DivPlatformPV1000: public DivDispatch {
struct Channel: public SharedChannel<int> {
Channel():
SharedChannel<int>(1) {}
};
Channel chan[3];
DivDispatchOscBuffer* oscBuf[3];
bool isMuted[3];
unsigned char regPool[4];
d65010g031_t d65010g031;
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
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 notifyInsDeletion(void* ins);
int getOutputCount();
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();
};
#endif

View file

@ -450,7 +450,10 @@ int DivPlatformQSound::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].isNewQSound=(ins->type==DIV_INS_QSOUND);
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value);
}

View file

@ -151,8 +151,8 @@ void DivPlatformRF5C68::tick(bool sysTick) {
if (s->isLoopable()) {
loop=start+s->loopStart;
}
start=MIN(start,getSampleMemCapacity()-31);
loop=MIN(loop,getSampleMemCapacity()-31);
start=MIN(start,getSampleMemCapacity()-32);
loop=MIN(loop,getSampleMemCapacity()-32);
rWrite(8,keyoff); // force keyoff first
chWrite(i,6,start>>8);
chWrite(i,4,loop&0xff);
@ -182,7 +182,10 @@ int DivPlatformRF5C68::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255;
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
}
@ -307,6 +310,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);
}
}
@ -421,7 +425,7 @@ void DivPlatformRF5C68::renderSamples(int sysID) {
}
int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length);
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-32,length);
if (actualLength>0) {
sampleOffRFC[i]=memPos;
for (int j=0; j<actualLength; j++) {
@ -431,8 +435,8 @@ void DivPlatformRF5C68::renderSamples(int sysID) {
sampleMem[memPos++]=(val>0)?(val|0x80):(0-val);
}
// write end of sample marker
memset(&sampleMem[memPos],0xff,31);
memPos+=31;
memset(&sampleMem[memPos],0xff,32);
memPos+=32;
}
if (actualLength<length) {
logW("out of RF5C68 PCM memory for sample %d!",i);

View file

@ -72,7 +72,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<6);
chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<7);
}
chan[i].freqChanged=true;
}
@ -106,21 +106,22 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64;
chan[i].freq=chan[i].baseFreq+(chan[i].pitch)-128+(oldSlides?0:chan[i].pitch2);
if (!parent->song.oldArpStrategy) {
if (chan[i].fixedArp) {
chan[i].freq=(chan[i].baseNoteOverride<<6)+(chan[i].pitch>>1)-64+chan[i].pitch2;
chan[i].freq=(chan[i].baseNoteOverride<<7)+chan[i].pitch-128+(chan[i].pitch2<<(oldSlides?1:0));
} else {
chan[i].freq+=chan[i].arpOff<<6;
chan[i].freq+=chan[i].arpOff<<7;
}
}
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/8363.0;
}
chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250)+chan[i].pitch2;
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);
}
chan[i].freqChanged=false;
@ -136,11 +137,12 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
rWrite(0x86+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
rWrite(6+(i<<3),MIN(255,((sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+actualLength-1)>>8));
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
if (loopStart<0 || loopStart>=actualLength) {
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart+sampleLoopOff[chan[i].pcm.sample];
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
logV("sampleOff: %x loopPos: %x",sampleOffSegaPCM[chan[i].pcm.sample],loopPos);
rWrite(4+(i<<3),loopPos&0xff);
rWrite(5+(i<<3),(loopPos>>8)&0xff);
rWrite(0x86+(i<<3),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
@ -153,11 +155,11 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
rWrite(0x86+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
rWrite(6+(i<<3),MIN(255,((sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+actualLength-1)>>8));
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
if (loopStart<0 || loopStart>=actualLength) {
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
} else {
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart+sampleLoopOff[chan[i].pcm.sample];
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),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
@ -185,7 +187,10 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
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);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].pcm.sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
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);
@ -197,7 +202,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=(c.value<<6);
chan[c.chan].baseFreq=(c.value<<7);
chan[c.chan].freqChanged=true;
}
chan[c.chan].furnacePCM=true;
@ -215,7 +220,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
rWrite(0x86+(c.chan<<3),3);
break;
}
chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/31250);
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;
@ -285,17 +290,18 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
int destFreq=(c.value2<<6);
int destFreq=(c.value2<<7);
int newFreq;
int mul=(oldSlides || parent->song.linearPitch!=2)?8:1;
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
newFreq=chan[c.chan].baseFreq+c.value*4;
newFreq=chan[c.chan].baseFreq+c.value*mul;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=chan[c.chan].baseFreq-c.value*4;
newFreq=chan[c.chan].baseFreq-c.value*mul;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
@ -310,7 +316,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
break;
}
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=(c.value<<6);
chan[c.chan].baseFreq=(c.value<<7);
chan[c.chan].freqChanged=true;
break;
}
@ -333,7 +339,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
return 127;
break;
case DIV_CMD_PRE_PORTA:
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=(chan[c.chan].note<<6);
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=(chan[c.chan].note<<7);
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_PRE_NOTE:
@ -381,6 +387,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];
}
@ -445,39 +462,38 @@ void DivPlatformSegaPCM::reset() {
}
}
void DivPlatformSegaPCM::renderSamples(int sysID) {
void DivPlatformSegaPCM::renderSamples(int sysID) {
size_t memPos=0;
memset(sampleMem,0,16777216);
memset(sampleLoaded,0,256*sizeof(bool));
memset(sampleOffSegaPCM,0,256*sizeof(unsigned int));
memset(sampleEndSegaPCM,0,256);
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* sample=parent->getSample(i);
unsigned int alignedSize=(sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)+0xff)&(~0xff);
if (alignedSize>65536) alignedSize=65536;
if ((memPos&0xff0000)!=((memPos+alignedSize)&0xff0000)) {
memPos=(memPos+0xffff)&0xff0000;
unsigned int alignedSize=sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
if (alignedSize>=65279) alignedSize=65279;
if ((memPos&(~0xffff))!=((memPos+alignedSize)&(~0xffff))) {
memPos=(memPos+0xffff)&(~0xffff);
}
if (alignedSize&0xff) {
memPos=((memPos+255)&(~0xff))+256-(alignedSize&0xff);
}
logV("- sample %d will be at %x with length %x",i,memPos,alignedSize);
sampleLoaded[i]=true;
if (memPos>=16777216) break;
sampleOffSegaPCM[i]=memPos;
unsigned int readPos=0;
for (unsigned int j=0; j<alignedSize; j++) {
if (readPos>=(unsigned int)sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
if (sample->isLoopable()) {
readPos=sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
sampleMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
} else {
sampleMem[memPos++]=0x80;
}
if (j>=sample->samples) {
sampleMem[memPos++]=0;
} else {
sampleMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
sampleMem[memPos++]=((unsigned char)sample->data8[j]+0x80);
}
readPos++;
sampleEndSegaPCM[i]=((memPos+0xff)>>8)-1;
if (memPos>=16777216) break;
}
sampleLoopOff[i]=readPos-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
logV(" and it ends in %d",sampleEndSegaPCM[i]);
if (memPos>=16777216) break;
}
sampleMemLen=memPos;
@ -490,6 +506,8 @@ void DivPlatformSegaPCM::setFlags(const DivConfig& flags) {
for (int i=0; i<16; i++) {
oscBuf[i]->rate=rate;
}
oldSlides=flags.getBool("oldSlides",false);
}
int DivPlatformSegaPCM::getOutputCount() {

View file

@ -65,6 +65,7 @@ class DivPlatformSegaPCM: public DivDispatch {
segapcm_device pcm;
int delay;
int pcmL, pcmR, pcmCycles;
bool oldSlides;
unsigned char sampleBank;
unsigned char lastBusy;
@ -76,7 +77,7 @@ class DivPlatformSegaPCM: public DivDispatch {
short pendingWrites[256];
unsigned int sampleOffSegaPCM[256];
unsigned int sampleLoopOff[256];
unsigned char sampleEndSegaPCM[256];
bool sampleLoaded[256];
friend void putDispatchChip(void*,int);
@ -87,6 +88,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();

View file

@ -84,11 +84,13 @@ void DivPlatformSM8521::tick(bool sysTick) {
unsigned char keyState=0x80;
for (int i=0; i<3; i++) {
// anti-click
/*
if (antiClickEnabled && sysTick && chan[i].freq>0) {
chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f));
chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq;
chan[i].antiClickPeriodCount%=chan[i].freq;
}
*/
chan[i].std.next();
if (chan[i].std.vol.had) {
@ -167,7 +169,7 @@ void DivPlatformSM8521::tick(bool sysTick) {
int DivPlatformSM8521::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE);
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SM8521);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
@ -261,7 +263,7 @@ int DivPlatformSM8521::dispatch(DivCommand c) {
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_PCE));
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SM8521));
}
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;
@ -362,9 +364,9 @@ void DivPlatformSM8521::notifyInsDeletion(void* ins) {
}
void DivPlatformSM8521::setFlags(const DivConfig& flags) {
antiClickEnabled=!flags.getBool("noAntiClick",false);
chipClock=11059200;
CHECK_CUSTOM_CLOCK;
antiClickEnabled=!flags.getBool("noAntiClick",false);
rate=chipClock/4/8; // CKIN -> fCLK(/2) -> Function blocks (/2)
for (int i=0; i<3; i++) {
oscBuf[i]->rate=rate;

View file

@ -43,6 +43,23 @@ float DivPlatformSMS::getPostAmp() {
return 1.5f;
}
void DivPlatformSMS::poolWrite(unsigned short a, unsigned char v) {
if (a) {
regPool[9]=v;
} else {
if (v>=0x80) {
regPool[(v>>4)&7]&=~15;
regPool[(v>>4)&7]|=v&15;
chanLatch=(v>>5)&3;
} else {
regPool[chanLatch<<1]&=15;
regPool[chanLatch<<1]|=((v&15)<<4);
regPool[1+(chanLatch<<1)]&=15;
regPool[1+(chanLatch<<1)]|=v&0xf0;
}
}
}
void DivPlatformSMS::acquire_nuked(short** buf, size_t len) {
int oL=0;
int oR=0;
@ -54,6 +71,9 @@ void DivPlatformSMS::acquire_nuked(short** buf, size_t len) {
} else if (w.addr==1) {
YMPSG_WriteStereo(&sn_nuked,w.val);
}
poolWrite(w.addr,w.val);
writes.pop();
}
YMPSG_Clock(&sn_nuked);
@ -97,6 +117,9 @@ void DivPlatformSMS::acquire_mame(short** buf, size_t len) {
else if (w.addr==0) {
sn->write(w.val);
}
poolWrite(w.addr,w.val);
writes.pop();
}
for (size_t h=0; h<len; h++) {
@ -428,7 +451,17 @@ DivDispatchOscBuffer* DivPlatformSMS::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformSMS::getRegisterPool() {
return regPool;
}
int DivPlatformSMS::getRegisterPoolSize() {
return stereo?9:8;
}
void DivPlatformSMS::reset() {
memset(regPool,0,16);
chanLatch=0;
while (!writes.empty()) writes.pop();
for (int i=0; i<4; i++) {
chan[i]=DivPlatformSMS::Channel();

View file

@ -42,6 +42,8 @@ class DivPlatformSMS: public DivDispatch {
unsigned char lastPan;
unsigned char oldValue;
unsigned char snNoiseMode;
unsigned char regPool[16];
unsigned char chanLatch;
int divider=16;
double toneDivider=64.0;
double noiseDivider=64.0;
@ -65,6 +67,7 @@ class DivPlatformSMS: public DivDispatch {
double NOTE_SN(int ch, int note);
int snCalcFreq(int ch);
void poolWrite(unsigned short a, unsigned char v);
void acquire_nuked(short** buf, size_t len);
void acquire_mame(short** buf, size_t len);
@ -74,6 +77,8 @@ class DivPlatformSMS: public DivDispatch {
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);

View file

@ -202,6 +202,7 @@ void DivPlatformSNES::tick(bool sysTick) {
}
}
for (int i=0; i<8; i++) {
// TODO: if wavetable length is higher than 32, we lose precision!
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
DivSample* s=parent->getSample(chan[i].sample);
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
@ -300,6 +301,11 @@ void DivPlatformSNES::tick(bool sysTick) {
rWrite(0x4d,echoBits);
writeEcho=false;
}
if (writeDryVol) {
rWrite(0x0c,dryVolL);
rWrite(0x1c,dryVolR);
writeDryVol=false;
}
for (int i=0; i<8; i++) {
if (chan[i].shallWriteEnv) {
writeEnv(i);
@ -336,7 +342,10 @@ int DivPlatformSNES::dispatch(DivCommand c) {
}
chan[c.chan].ws.init(ins,chan[c.chan].wtLen,15,chan[c.chan].insChanged);
} else {
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
chan[c.chan].useWave=false;
}
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
@ -560,6 +569,14 @@ int DivPlatformSNES::dispatch(DivCommand c) {
rWrite(0x3c,echoVolR);
}
break;
case DIV_CMD_SNES_GLOBAL_VOL_LEFT:
dryVolL=c.value;
writeDryVol=true;
break;
case DIV_CMD_SNES_GLOBAL_VOL_RIGHT:
dryVolR=c.value;
writeDryVol=true;
break;
case DIV_CMD_GET_VOLMAX:
return 127;
break;
@ -670,6 +687,7 @@ void DivPlatformSNES::forceIns() {
writeNoise=true;
writePitchMod=true;
writeEcho=true;
writeDryVol=true;
initEcho();
}
@ -681,6 +699,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];
}
@ -744,6 +776,10 @@ void DivPlatformSNES::reset() {
writeNoise=false;
writePitchMod=false;
writeEcho=true;
writeDryVol=false;
dryVolL=127;
dryVolR=127;
echoDelay=initEchoDelay;
echoFeedback=initEchoFeedback;

View file

@ -59,6 +59,7 @@ class DivPlatformSNES: public DivDispatch {
unsigned char noiseFreq;
signed char delay;
signed char echoVolL, echoVolR, echoFeedback;
signed char dryVolL, dryVolR;
signed char echoFIR[8];
unsigned char echoDelay;
size_t sampleTableBase;
@ -66,6 +67,7 @@ class DivPlatformSNES: public DivDispatch {
bool writeNoise;
bool writePitchMod;
bool writeEcho;
bool writeDryVol;
bool echoOn;
bool initEchoOn;
@ -97,6 +99,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();

View file

@ -0,0 +1,151 @@
/*
============================================================================
NEC D65010G031 sound emulator
by cam900
This file is licensed under zlib license.
============================================================================
zlib License
(C) 2023-present cam900 and contributors
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
============================================================================
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 "d65modified.h"
#include <stdlib.h>
static int d65010g031_max(int a, int b) { return (a > b) ? a : b; }
int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle)
{
if (square->period > 0)
{
const int period = square->period;
square->counter += cycle;
while (square->counter >= period)
{
square->counter -= period;
square->out ^= 1;
}
return square->out;
}
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, // -6dB
5120, // -3dB
8192 // 0dB
};
int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle)
{
int out = 0;
for (int i = 0; i < 3; i++)
{
d65010g031->out[i] = 0;
}
if (d65010g031->ctrl & 2)
{
if (d65010g031->ctrl & 1) // ring modulation
{
int sout[3] = {
d65010g031_square_tick(&d65010g031->square[0], cycle),
d65010g031_square_tick(&d65010g031->square[1], cycle),
d65010g031_square_tick(&d65010g031->square[2], cycle),
};
d65010g031->out[0] = (sout[0] ^ sout[1]) ? d65Volumes[0] : -d65Volumes[0];
d65010g031->out[1] = (sout[1] ^ sout[2]) ? d65Volumes[1] : -d65Volumes[1];
d65010g031->out[2] = (sout[2] ? d65Volumes[2] : -d65Volumes[2]);
}
else
{
for (int i = 0; i < 3; i++)
{
d65010g031->out[i] = d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i];
}
}
out = d65010g031->out[0] + d65010g031->out[1] + d65010g031->out[2];
}
return out;
}
void d65010g031_reset(struct d65010g031_t *d65010g031)
{
for (int i = 0; i < 3; i++)
{
d65010g031->square[i].period = 0;
d65010g031->square[i].counter = 0;
d65010g031->square[i].out = 0;
}
d65010g031->ctrl = 0;
}
void d65010g031_write(struct d65010g031_t *d65010g031, const unsigned char a, const unsigned char d)
{
switch (a)
{
case 3:
d65010g031->ctrl = d;
break;
default:
{
const unsigned char per = (unsigned char)(~d) & 0x3f;
if ((per == 0) && (d65010g031->square[a].period != 0))
{
d65010g031->square[a].out ^= 1;
}
d65010g031->square[a].period = per;
break;
}
}
}

View file

@ -0,0 +1,73 @@
/*
============================================================================
NEC D65010G031 sound emulator
by cam900
This file is licensed under zlib license.
============================================================================
zlib License
(C) 2023-present cam900 and contributors
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
============================================================================
TODO:
- needs hardware test
*/
#ifndef _D65010G031_EMU_H
#define _D65010G031_EMU_H
#ifdef __cplusplus
extern "C"
{
#endif
struct d65010g031_square_t
{
unsigned char period; // Period (0 = Off)
int counter; // clock counter
signed short out; // output
};
struct d65010g031_t
{
struct d65010g031_square_t square[3];
signed short out[3];
unsigned char ctrl;
};
int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle);
int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle);
void d65010g031_reset(struct d65010g031_t *d65010g031);
void d65010g031_write(struct d65010g031_t *d65010g031, const unsigned char a, const unsigned char d);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _D65010G031_EMU_H

View file

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

View file

@ -178,7 +178,7 @@ static signed interference(GB_gameboy_t *gb)
ret /= 4;
}
ret += rand() % (MAX_CH_AMP / 12);
//ret += rand() % (MAX_CH_AMP / 12);
return ret;
}

View file

@ -0,0 +1,820 @@
// license:BSD-3-Clause
/**********************************************************************************************
*
* SGS-Thomson Microelectronics M114S/M114A/M114AF Digital Sound Generator
* by Steve Ellenoff
* 09/02/2004
*
* Thanks to R.Belmont for Support & Agreeing to read through that nasty data sheet with me..
* Big thanks to Destruk for help in tracking down the data sheet.. Could have never done it
* without it!!
*
* A note about the word "table" as used by the M114S datasheet. A table refers to rom data
* representing 1 full period/cycle of a sound. The chip always reads from 2 different tables
* and intermixes them during playback for smoother sound. Different table lengths simply allow
* the chip to play lower frequencies due to the fact that the table represents 1 full period,ie
* 1 full sine wave, if that's what the rom data happens to contain.
*
* It would seem that the chip comes in two versions - the M114A 4Mhz & M114AF 6Mhz versions. Unlike other sound chips which
* allow for clock variations on the same chip, this chip uses hard coded frequency tables
* in an internal ROM based on which version of the chip is being used.
*
* Some ideas were taken from the source from BSMT2000 & AY8910
*
* Heavily modified/extended by Carsten Waechter later-on
*
* The mixer can optionally mix every (internal) channel into a separate stream for testing (set M114S_OUTPUT_CHANNELS to 16)
*
* TODO: - No full/tested support for the M114AF 6Mhz version of the chip (but seems at least to work 'good enough')
* - Maybe also low pass filter heavily/some channels only?? (some channels sound too high frequency heavy (clicks, pops), for example on Dakar)
**********************************************************************************************/
#include "m114s.h"
#include <stdbool.h>
#if defined(_MSC_VER) && (_MSC_VER <= 1500)
#define llabs _abs64
#endif
#define SAMPLE_RATE 48000
#define USE_FREQTABLE_FROM_MANUAL // undef to use a generated freqtable with 'correct' semitones (i.e. unlike the real chip) for the M114A 4MHz
//!! unsure how the actual vol envelope actually works internally and at what precision -> spec says 'either immediate' (=no envelope) or 'gradual increments of 1/256 of maximum amplitude'
// BUT THEN also mentions: only 8 MSBits from the 10 of v_linear are influenced! -> simple increase/decrease of the 8 MSBits with same frequency as table read (if diff > 128, every 2nd read if diff > 64, every 4th read if diff > 32 or every 8th read if diff <= 32)
#define USE_VOL_ENVELOPE // to test no volume envelope (=no smooth blending in of new volume that is set), set to 0
#define DO_FULL_PRECISION_MIXING // unclear how the mixing of channels and interpolation of channels works internally, so this allows to switch between two modes (use highest precision vs a lower precision/interpretation of the datasheet text)
#define USE_LERP_FOR_REPEATED_SAMPLES // unclear how repeated table reads really work, datasheet can be interpreted either way
#define MR_GAME_VOLUME_HACK // rather do this for specific machines only!? (i.e. is this only due to the filter network of output channels of Mr.Game??!)
#if 0
#include <stdio.h>
#include <stdlib.h>
#define LOG(x) printf x
#else
#define LOG(x) logerror x
#endif
/**********************************************************************************************
CONSTANTS
***********************************************************************************************/
#define FRAC_BITS 16
#define FRAC_ONE (1 << FRAC_BITS)
#define FRAC_MASK (FRAC_ONE - 1)
/**********************************************************************************************
GLOBALS
***********************************************************************************************/
/* Table 1 & Table 2 Repetition Values based on Mode */
static const int mode_to_rep[8][2] = {
{2,2}, //Mode 0
{1,1}, //Mode 1
{4,4}, //Mode 2
{1,1}, //Mode 3
{1,2}, //Mode 4
{1,1}, //Mode 5
{1,4}, //Mode 6
{1,1} //Mode 7
};
/* Table 1 Length Values based on Mode */ // these are always larger as table 2 values! (thus mixing code below makes sense to not handle the other case)
static const int mode_to_len_t1[8][8] = {
{16,32,64,128,256,512,1024,2048 }, //Mode 0
{16,32,64,128,256,512,1024,2048 }, //Mode 1
{16,32,64,128,256,512,1024,1024 }, //Mode 2
{16,32,64,128,256,512,1024,2048 }, //Mode 3
{16,32,64,128,256,512,1024,2048 }, //Mode 4
{16,32,64,128,256,512,1024,2048 }, //Mode 5
{16,32,64,128,256,512,1024,2048 }, //Mode 6
{16,32,64,128,256,512,1024,2048 }, //Mode 7
};
/* Table 2 Length Values based on Mode */
static const int mode_to_len_t2[8][8] = {
{16,32,64,128,256,512,1024,2048 }, //Mode 0 //datasheet has last value = 1048 //!! -> try 1024 instead??
{16,32,64,128,256,512,1024,2048 }, //Mode 1
{16,32,64,128,256,512,1024,1024 }, //Mode 2
{16,32,64,128,256,512,1024,2048 }, //Mode 3
{ 8,16,32, 64,128,256, 512,1024 }, //Mode 4
{16,16,16, 32, 64,128, 256, 512 }, //Mode 5
{ 4, 8,16, 32, 64,128, 256, 512 }, //Mode 6
{16,16,16, 16, 32, 64, 128, 256 }, //Mode 7
};
/* Attenuation Table */
static const int v_linear[32] = {
1023,939,863,791,727,667,611,559,515,471,431,
395,363,335,307,283,259,235,215,199,183,166,
152,140,128,117,107, 98, 90, 83, 76, 69
};
// a_decibel = 20 * log((v_linear+1)/1024)
/*static const double a_decibel[32] = {
0.00, 0.74, 1.48, 2.23, 2.96, 3.71, 4.47, 5.24, 5.95,
6.73, 7.50, 8.25, 8.98, 9.68,10.43,11.14,11.91,12.75,
13.52,14.19,14.91,15.75,16.51,17.22,17.99,18.77,19.54,
20.29,21.03,21.72,22.48,23.30
};*/
#ifdef USE_FREQTABLE_FROM_MANUAL
/* Frequency Table for a 4Mhz Clocked Chip */
static const double freqtable4Mhz[256] = {
1016.78,1021.45,1026.69,1031.46,1036.27,1041.67,1044.39,1045.48,
1046.57,1047.67,1048.77,1051.52,1056.52,1061.57,1066.67,1071.81, //0x00 - 0x0F
1077.01,1082.25,1087.55,1092.90,1098.30,1103.14,1106.81,1107.42,
1108.65,1109.88,1111.11,1114.21,1119.19,1124.86,1130.58,1135.72, //0x10 - 0x1F
1140.90,1146.79,1152.07,1158.08,1163.47,1168.91,1172.33,1173.71,
1174.40,1175.78,1177.16,1180.64,1186.24,1191.90,1197.60,1203.37, //0x20 - 0x2F
1209.19,1215.07,1221.00,1226.99,1232.29,1238.39,1242.24,1243.78,
1244.56,1245.33,1246.88,1250.78,1256.28,1262.63,1269.04,1274.70, //0x30 - 0x3F
1281.23,1287.00,1293.66,1299.55,1305.48,1312.34,1315.79,1317.52,
1318.39,1319.26,1321.00,1324.50,1331.56,1337.79,1344.09,1350.44, //0x40 - 0x4F
1356.85,1363.33,1369.86,1376.46,1383.13,1389.85,1393.73,1395.67,
1396.65,1397.62,1398.60,1403.51,1410.44,1417.43,1424.50,1430.62, //0x50 - 0x5F
1437.81,1445.09,1451.38,1458.79,1466.28,1472.75,1478.20,1479.29,
1480.38,1481.48,1482.58,1486.99,1494.77,1501.50,1508.30,1516.30, //0x60 - 0x6F
1523.23,1530.22,1538.46,1545.60,1552.80,1560.06,1564.95,1566.17,
1567.40,1568.63,1569.86,1576.04,1583.53,1591.09,1598.72,1606.43, //0x70 - 0x7F
1614.21,1622.06,1629.99,1638.00,1644.74,1652.89,1658.37,1659.75,
1661.13,1662.51,1663.89,1669.45,1677.85,1684.92,1693.48,1702.13, //0x80 - 0x8F
1709.40,1718.21,1727.12,1734.61,1743.68,1751.31,1757.47,1759.01, //(2nd entry in manual shows 1781.21 but that's cleary wrong)
1760.56,1762.11,1763.89,1768.35,1777.78,1785.71,1793.72,1803.43, //0x90 - 0x9F
1811.59,1819.84,1829.83,1838.24,1846.72,1855.29,1860.47,1862.20,
1863.93,1865.67,1867.41,1874.41,1883.24,1892.15,1901.14,1910.22, //0xA0 - 0xAF
1919.39,1928.64,1937.98,1947.42,1956.95,1966.57,1972.39,1974.33,
1976.28,1978.24,1980.20,1984.13,1994.02,2004.01,2014.10,2024.29, //0xB0 - 0xBF
2032.52,2042.90,2053.39,2063.98,2072.54,2083.33,2087.68,2089.86,
2092.05,2094.24,2096.44,2103.05,2114.16,2123.14,2134.47,2143.62, //0xC0 - 0xCF
2155.17,2164.50,2176.28,2185.79,2195.39,2207.51,2212.39,2214.84,
2217.29,2219.76,2222.22,2227.17,2239.64,2249.72,2259.89,2272.73, //0xD0 - 0xDF
2283.11,2293.58,2304.15,2314.81,2325.58,2339.18,2344.67,2347.42,
2350.18,2352.94,2355.71,2361.28,2372.48,2383.79,2395.21,2406.74, //0xE0 - 0xEF
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, //0xF0 - 0xFF (Special codes)
};
/* Frequency Table for a 6Mhz Clocked Chip */
static const double freqtable6Mhz[256] = {
1523.78,1523.79,1538.64,1545.79,1552.99,1561.08,1565.16,1566.80,
1568.44,1570.08,1571.73,1575.86,1583.35,1590.91,1598.55,1606.26, //0x00 - 0x0F
1614.04,1621.90,1629.84,1637.86,1645.95,1653.22,1658.71,1659.62,
1661.46,1663.31,1665.16,1669.79,1677.27,1685.76,1694.34,1702.03, //0x10 - 0x1F
1709.80,1718.62,1726.54,1735.54,1743.62,1751.77,1758.91,1758.97,
1760.00,1762.07,1764.14,1769.35,1777.75,1786.22,1794.78,1803.42, //0x20 - 0x2F
1812.14,1820.95,1829.84,1838.82,1846.75,1855.90,1861.66,1863.98,
1865.14,1866.30,1868.63,1874.47,1882.71,1892.22,1901.83,1910.31, //0x30 - 0x3F
1920.10,1928.75,1938.73,1947.55,1956.45,1966.72,1971.89,1974.49,
1975.79,1977.10,1979.71,1984.95,1995.53,2004.87,2014.30,2023.82, //0x40 - 0x4F
2033.43,2043.14,2052.93,2062.82,2072.82,2082.89,2088.70,2091.61,
2093.07,2094.54,2096.00,2103.35,2113.74,2124.22,2134.81,2143.98, //0x50 - 0x5F
2154.77,2165.66,2175.09,2186.20,2197.42,2207.13,2215.28,2216.92,
2218.56,2220.21,2221.85,2228.46,2240.12,2250.21,2260.39,2272.39, //0x60 - 0x6F
2282.77,2293.25,2305.60,2316.29,2327.08,2337.97,2345.29,2347.13,
2348.97,2350.81,2352.65,2361.92,2373.14,2384.47,2395.91,2407.45, //0x70 - 0x7F
2419.11,2430.88,2442.77,2454.77,2464.87,2477.09,2485.31,2487.37,
2489.44,2491.50,2493.58,2501.90,2514.50,2525.09,2537.92,2550.88, //0x80 - 0x8F
2561.78,2574.98,2588.32,2599.55,2613.15,2624.59,2633.81,2636.13,
2638.45,2640.78,2643.10,2650.11,2664.25,2676.14,2688.14,2702.69, //0x90 - 0x9F
2714.93,2727.28,2742.25,2754.85,2767.57,2780.41,2788.17,2790.76,
2793.06,2795.97,2798.58,2809.07,2822.30,2835.65,2849.13,2862.73, //0xA0 - 0xAF
2876.47,2890.34,2904.34,2918.48,2932.76,2947.18,2955.90,2958.82,
2961.74,2964.67,2967.60,2973.49,2988.31,3003.29,3018.41,3033.68, //0xB0 - 0xBF
3046.02,3061.57,3077.29,3093.17,3105.99,3122.17,3128.68,3131.95,
3135.23,3138.51,3141.80,3151.71,3168.37,3181.83,3198.80,3212.52, //0xC0 - 0xCF
3229.83,3243.81,3261.46,3275.72,3290.10,3308.26,3315.58,3319.25,
3322.93,3326.61,3330.31,3337.73,3356.42,3371.52,3386.76,3406.00, //0xD0 - 0xDF
3421.55,3437.25,3453.09,3469.07,3485.21,3505.59,3513.81,3517.93,
3522.07,3526.21,3530.37,3538.70,3555.49,3572.44,3589.56,3606.84, //0xE0 - 0xEF
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, //0xF0 - 0xFF (Special codes)
};
#else
/* Frequency Table for a 4Mhz Clocked Chip
Note: Only the 1st set of frequencies came from the manual, the rest were calculated using 1 semitone increments
using a program called Test Tone Generator 3.91
*/
static const double freqtable4Mhz[256] = {
1016.78,1021.45,1026.69,1031.46,1036.27,1041.67,1044.39,1045.48,
1046.57,1047.67,1048.77,1051.52,1056.52,1061.57,1066.67,1071.81, //0x00 - 0x0F
1077.24,1082.19,1087.74,1092.79,1097.89,1103.61,1106.49,1107.65,
1108.80,1109.97,1111.13,1114.05,1119.34,1124.69,1130.10,1135.54, //0x10 - 0x1F
1141.29,1146.54,1152.42,1157.77,1163.17,1169.24,1172.29,1173.51,
1174.74,1175.97,1177.20,1180.29,1185.90,1191.57,1197.30,1203.07, //0x20 - 0x2F
1209.16,1214.72,1220.95,1226.62,1232.34,1238.76,1242.00,1243.29,
1244.59,1245.90,1247.20,1250.47,1256.42,1262.43,1268.49,1274.60, //0x30 - 0x3F
1281.06,1286.95,1293.55,1299.56,1305.62,1312.42,1315.85,1317.22,
1318.60,1319.98,1321.37,1324.83,1331.13,1337.49,1343.92,1350.40, //0x40 - 0x4F
1357.24,1363.47,1370.46,1376.83,1383.25,1390.46,1394.09,1395.55,
1397.00,1398.47,1399.94,1403.61,1410.29,1417.03,1423.83,1430.70, //0x50 - 0x5F
1437.94,1444.55,1451.96,1457.07,1465.51,1473.14,1477.00,1478.53,
1480.07,1481.63,1483.18,1486.99,1494.14,1501.29,1508.50,1515.77, //0x60 - 0x6F
1523.45,1530.45,1538.30,1545.44,1552.65,1560.74,1564.82,1566.45,
1568.08,1569.73,1571.38,1575.50,1583.00,1590.56,1598.20,1605.90, //0x70 - 0x7F
1614.04,1621.45,1629.77,1637.34,1644.98,1653.55,1657.87,1659.60,
1661.33,1663.07,1664.82,1669.18,1677.12,1685.14,1693.23,1701.39, //0x80 - 0x8F
1710.01,1717.87,1726.69,1734.70,1742.79,1751.87,1756.45,1758.28,
1760.11,1761.96,1763.81,1768.44,1776.85,1785.34,1793.92,1802.56, //0x90 - 0x9F
1811.70,1820.02,1829.35,1837.85,1846.42,1856.05,1860.89,1862.83,
1864.78,1866.74,1868.70,1873.60,1882.50,1891.50,1900.59,1909.75, //0xA0 - 0xAF
1919.43,1928.24,1938.13,1947.14,1956.22,1966.41,1971.55,1973.60,
1975.66,1977.74,1979.81,1985.01,1994.44,2003.98,2013.61,2023.31, //0xB0 - 0xBF
2033.56,2042.90,2053.38,2062.92,2072.54,2083.34,2088.78,2090.96,
2093.14,2095.34,2097.54,2103.04,2113.04,2123.14,2133.34,2143.62, //0xC0 - 0xCF
2154.48,2164.38,2175.48,2185.59,2195.78,2207.22,2212.99,2215.30,
2217.60,2219.94,2222.27,2228.09,2238.69,2249.39,2260.20,2271.09, //0xD0 - 0xDF
2282.59,2293.08,2304.84,2315.55,2326.35,2338.47,2344.58,2347.02,
2349.47,2351.94,2354.41,2360.58,2371.80,2383.14,2394.59,2406.13, //0xE0 - 0xEF
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, //0xF0 - 0xFF (Special codes)
};
#endif
/**********************************************************************************************
read_sample -- returns 1 sample from the channel's output buffer, but upsamples/interpolates the data
as necessary to match the Machine driver's output sample rate.
***********************************************************************************************/
int16_t read_sample(struct M114SChannel * const channel, const uint32_t length)
{
const uint32_t pos = channel->outpos >> FRAC_BITS;
if (pos < length)
{
const int32_t frac = channel->outpos & FRAC_MASK;
// interpolate
const int16_t val1 = channel->output[pos];
const int16_t val2 = channel->output[pos+1 < length ? pos : 0]; // wrap around to 0 (see code below!)?
const int16_t sample = (val1 * ((int32_t)FRAC_ONE - frac) + val2 * frac) >> FRAC_BITS;
channel->outpos += channel->incr;
return sample;
}
else {
//LOG(("End of Table\n"));
//channel->end_of_table++;
channel->outpos = 0; // related to cyclic reading of tables //!! rather wrap over including exact FRAC_BITS instead of just nulling it?
return channel->output[0]; //!! dto. would need interpolation then
}
}
/**********************************************************************************************
read_table -- Reads the two tables of rom data into a temporary buffer,
mixes the samples using the chip's internal interpolation equation,
applies the volume, and writes the single mixed sample to the output buffer for the channel.
It processes the entire table1 length of data.
Note: Eventually this should flag an End of Table, and should process new table data
***********************************************************************************************/
void read_table(struct M114SChip * const chip, struct M114SChannel * const channel) // get rid of this and write directly to output buffer?!
{
int i;
#ifndef USE_LERP_FOR_REPEATED_SAMPLES
int j;
#endif
const int8_t * const rom = &chip->region_base[0];
const int t1start = channel->table1.start_address;
const int t2start = channel->table2.start_address;
const int lent1 = channel->table1.length;
const int lent2 = channel->table2.length;
const int rep1 = channel->table1.reread;
const int rep2 = channel->table2.reread;
const int intp = channel->regs.interp;
int8_t tb1[4096]; // Temp copy buffer for Table 1 // long enough to hold max sizes of 2048*2 or 1024*4
int8_t tb2[4096]; // Temp copy buffer for Table 2 // dto.
memset(&tb1,0,sizeof(tb1));
memset(&tb2,0,sizeof(tb2));
//LOG(("t1s = %d t2s = %d, l1=%d l2=%d, r1=%d, r2=%d, int = %d\n",t1start,t2start,lent1,lent2,rep1,rep2,intp));
// datasheet says: multiple reading permits interpolation between two adjoining samples on the same table, so do we really need to lerp instead of just repeating same values here?
#ifdef USE_LERP_FOR_REPEATED_SAMPLES
//Scan Table 1
if(rep1 == 1)
memcpy(tb1,&rom[t1start],lent1);
else if(rep1 == 2)
for(i=0; i<lent1; i++)
{
tb1[i*2+0] = rom[i+t1start];
tb1[i*2+1] = ((int)rom[i+t1start] + (int)rom[t1start + ((i < lent1-1) ? i+1 : i)])/2;
}
else //if(rep1 == 4)
for(i=0; i<lent1; i++)
{
int val1 = (int)rom[i+t1start];
int val2 = (int)rom[t1start + ((i < lent1-1) ? i+1 : i)];
tb1[i*4+0] = rom[i+t1start];
tb1[i*4+1] = (val1*3 + val2 )/4;
tb1[i*4+2] = (val1 + val2 )/2;
tb1[i*4+3] = (val1 + val2*3)/4;
}
//Scan Table 2
if (rep2 == 1)
memcpy(tb2,&rom[t2start+0x2000],lent2); //A13 is toggled high on Table 2 reading (Implementation specific - ie, Mr. Game)
else if(rep2 == 2)
for(i=0; i<lent2; i++)
{
tb2[i*2+0] = rom[i+t2start+0x2000];
tb2[i*2+1] = ((int)rom[i+t2start+0x2000] + (int)rom[t2start+0x2000 + ((i < lent2-1) ? i+1 : i)])/2;
}
else //if(rep2 == 4)
for(i=0; i<lent2; i++)
{
int val1 = (int)rom[i+t2start+0x2000];
int val2 = (int)rom[t2start+0x2000 + ((i < lent2-1) ? i+1 : i)];
tb2[i*4+0] = rom[i+t2start+0x2000];
tb2[i*4+1] = (val1*3 + val2 )/4;
tb2[i*4+2] = (val1 + val2 )/2;
tb2[i*4+3] = (val1 + val2*3)/4;
}
#else
//Scan Table 1
for(i=0; i<lent1; i++)
for(j=0; j<rep1; j++)
tb1[j+(i*rep1)] = rom[i+t1start];
//Scan Table 2
for(i=0; i<lent2; i++)
for(j=0; j<rep2; j++)
tb2[j+(i*rep2)] = rom[i+t2start+0x2000]; //A13 is toggled high on Table 2 reading (Implementation specific - ie, Mr. Game)
#endif
// Table1 is always larger, so use that as the size
// How to make up difference (table 2 can be shorter than table 1)? -> was memset above already, so zero for now
/*for(i=lent2*rep2; i<lent1*rep1; i++)
tb2[i] = ?;*/
// Now Mix based on Interpolation Bits
for(i=0; i<lent1*rep1; i++) {
int l;
//Apply volume - If envelope - inc/dec volume to calculate sample volume (only 8 most significant bits from the 10 bits, thus +/-4), otherwise, apply directly
#ifdef USE_VOL_ENVELOPE
if(channel->regs.env_enable && ((i & (abs(channel->step_rate_volume_env)-1)) == abs(channel->step_rate_volume_env)/2)) { // check if we match the frequency that the value must be updated
channel->current_volume += channel->step_rate_volume_env > 0 ? 4 : -4; // dependent on sign inc/dec by 4
if(channel->step_rate_volume_env > 0)
{
if(channel->current_volume > channel->target_volume)
channel->current_volume = channel->target_volume;
}
else if(channel->current_volume < channel->target_volume)
channel->current_volume = channel->target_volume;
}
#endif
//write to output buffer
#ifdef DO_FULL_PRECISION_MIXING
l = (int)tb1[i] * (intp + 1) + (int)tb2[i] * (15 - intp);
channel->output[i] = (int16_t)(0x6f * l * (channel->current_volume + 1) / (1024*16)); // Max Volume would be 256 for an int16_t value (so why was 0x6f chosen??)
#else
l = ((int)tb1[i] * (intp + 1) / 16) + ((int)tb2[i] * (15 - intp) / 16); // formula seen in datasheet, but unclear what this means precision wise (i.e. is this only meant as real number pseudo code?)
channel->output[i] = (int16_t)(0x6f * l * (channel->current_volume + 1) / 1024); // Max Volume would be 256 for an int16_t value (so why was 0x6f chosen??) //!! do 0x6f scale AFTER division??
#endif
}
}
/**********************************************************************************************
m114s_update -- update the sound chip so that it is in sync with CPU execution
***********************************************************************************************/
//Seems this sometimes still produces some static, but I don't know why!
void m114s_update(struct M114SChip* chip,
int16_t **buffer,
int samples)
{
while (samples > 0)
{
#if M114S_OUTPUT_CHANNELS == 1
int32_t accum = 0;
#else
int32_t accum[M114S_OUTPUT_CHANNELS];
#endif
int c;
#if M114S_OUTPUT_CHANNELS != 1
/* clear accum */
for(c = 0; c < M114S_OUTPUT_CHANNELS; c++)
accum[c] = 0;
#endif
/* loop over channels */
for (c = 0; c < M114S_CHANNELS; c++)
{
struct M114SChannel * const channel = &chip->channels[c];
/* Grab the next sample from the table data if the channel is active */
if (channel->active)
{
//We use Table 1 to drive everything, as Table 2 is really for mixing into Table 1..
int32_t sample = read_sample(channel, channel->table1.total_length); //!! int16_t if MR_GAME_VOLUME_HACK would be off
#ifdef MR_GAME_VOLUME_HACK
sample = sample*chip->channel_volume[channel->regs.outputs] / 100; // boost percussion on Dakar, penalty some of the other instruments
#endif
//Mix the output of this channel to the appropriate output channel
#if M114S_OUTPUT_CHANNELS == 1
accum
#elif M114S_OUTPUT_CHANNELS == 4
accum[channel->regs.outputs]
#else
accum[c]
#endif
+= sample;
}
}
/* Update the buffer & Ensure we don't clip */
#if M114S_OUTPUT_CHANNELS == 1
accum /= (int32_t)4;
*buffer++ = (accum < -32768) ? -32768 : ((accum > 32767) ? 32767 : accum);
#else
for (c = 0; c < M114S_OUTPUT_CHANNELS; c++)
*buffer[c]++ = (accum[c] < -32768) ? -32768 : ((accum[c] > 32767) ? 32767 : accum[c]);
#endif
samples--;
}
}
/**********************************************************************************************
M114S_sh_start -- start emulation of the M114S
***********************************************************************************************/
INLINE void init_channel(struct M114SChannel * const channel)
{
//set all internal registers to 0!
channel->active = 0;
channel->outpos = 0;
channel->prev_volume = 0;
memset(&channel->output,0,sizeof(channel->output));
memset(&channel->regs, 0,sizeof(channel->regs));
memset(&channel->table1,0,sizeof(channel->table1));
memset(&channel->table2,0,sizeof(channel->table2));
}
INLINE void init_all_channels(struct M114SChip * const chip)
{
int i;
/* init the channels */
for (i = 0; i < M114S_CHANNELS; i++)
init_channel(&chip->channels[i]);
//Chip init stuff
memset(&chip->tempch_regs,0,sizeof(chip->tempch_regs));
chip->channel = 0;
chip->bytes_read = 0;
}
int M114S_sh_start(const struct MachineSound *msound)
{
const struct M114Sinterface *intf = msound->sound_interface;
#if M114S_OUTPUT_CHANNELS == 1
#else
int vol[M114S_OUTPUT_CHANNELS];
#endif
int i,j;
/* initialize the chips */
memset(&m114schip, 0, sizeof(m114schip));
for (i = 0; i < intf->num; i++)
{
/* Chip specific setup based on clock speed */
switch(intf->baseclock[i]) {
// M114A 4 Mhz
case 4000000:
m114schip[i].reset_cycles = 4000000 * 0.000128; // Chip resets in 128us (microseconds)
m114schip[i].is_M114A = 1;
break;
// M114AF 6 Mhz
case 6000000:
case 5994560: // from datasheet: 5.99456 MHz
m114schip[i].reset_cycles = intf->baseclock[i] * 0.000085; // Chip resets in 85us (microseconds)
m114schip[i].is_M114A = 0;
LOG(("M114S Chip #%d - 6Mhz chip clock not fully supported/tested at this time!\n", i));
return 1;
default:
LOG(("M114S Chip #%d - Invalid Base Clock value specified! Only 4Mhz & 6Mhz values allowed!\n",i));
return 1;
}
/* initialize the region & interface info */
m114schip[i].cpu_num = intf->cpunum[i];
m114schip[i].region_base = (int8_t *)memory_region(intf->region[i]);
m114schip[i].intf = (struct M114Sinterface *)intf;
/* init the channels */
init_all_channels(&m114schip[i]);
for(j = 0; j < 4; j++)
m114schip[i].channel_volume[j] = intf->mixing_level[i][j];
}
/* success */
return 0;
}
/**********************************************************************************************
M114S_sh_stop -- stop emulation of the M114S
***********************************************************************************************/
void M114S_sh_stop(void)
{
}
/**********************************************************************************************
M114S_sh_reset -- reset emulation of the M114S
***********************************************************************************************/
void M114S_sh_reset(void)
{
int i;
for (i = 0; i < 1; i++) {
/* reset all channels */
init_all_channels(&m114schip[i]);
}
}
/**********************************************************************************************
process_freq_codes -- There are up to 16 special values for frequency that signify a code
***********************************************************************************************/
void process_freq_codes(struct M114SChip * const chip)
{
//Grab pointer to channel being programmed
struct M114SChannel * const channel = &chip->channels[chip->channel];
switch(channel->regs.frequency)
{
//ROMID - ROM Identification (Are you kidding me?)
case 0xf8:
LOG(("* * Channel: %02d: Frequency Code: %02x - ROMID * * \n",chip->channel,channel->regs.frequency));
break;
//SSG - Set Syncro Global
case 0xf9:
LOG(("* * Channel: %02d: Frequency Code: %02x - SSG * * \n",chip->channel,channel->regs.frequency));
break;
//RSS - Reverse Syncro Status
case 0xfa:
// check if used, as this will force frequency changes to wait until table ends
LOG(("* * Channel: %02d: Frequency Code: %02x - RSS * * \n",chip->channel,channel->regs.frequency));
break;
//RSG - Reset Syncro Global
case 0xfb:
LOG(("* * Channel: %02d: Frequency Code: %02x - RSG * * \n",chip->channel,channel->regs.frequency));
break;
//PSF - Previously Selected Frequency
case 0xfc:
// seems to be unused by Mr.Game
LOG(("* * Channel: %02d: Frequency Code: %02x - PSF * * \n",chip->channel,channel->regs.frequency));
break;
//FFT - Forced Table Termination
case 0xff:
//Stop whatever output from playing by simulating an end of table event!
channel->outpos = 0; // but this will just put the cyclic counter back to the beginning!?!
//LOG(("* * Channel: %02d: Frequency Code: %02x - FFT * * \n",chip->channel,channel->regs.frequency));
break;
default:
LOG(("* * Channel: %02d: Frequency Code: %02x - UNKNOWN * * \n",chip->channel,channel->regs.frequency));
break;
}
}
/**********************************************************************************************
process_channel_data -- complete programming for a channel now exists, process it!
***********************************************************************************************/
void process_channel_data(struct M114SChip * const chip)
{
//Grab pointer to channel being programmed
struct M114SChannel * const channel = &chip->channels[chip->channel];
//Reset # of bytes for next group
chip->bytes_read = 0;
//Copy data to the appropriate channel registers from our temp channel registers
memcpy(&channel->regs,&chip->tempch_regs,sizeof(chip->tempch_regs));
//Look for the 16 special frequency codes
if(channel->regs.frequency >= 0xf0) {
process_freq_codes(chip);
//FFT & PSF are the only codes that should continue to process channel data AFAIK
if(channel->regs.frequency != 0xff && channel->regs.frequency != 0xfc)
return;
}
//If Attenuation set to 0x3F - The channel becomes inactive
if(channel->regs.atten == 0x3f) {
channel->active = 0;
return;
}
else
//Process this channel
{
//Calculate # of repetitions for Table 1 & Table 2
const int rep1 = mode_to_rep[channel->regs.read_meth][0];
const int rep2 = mode_to_rep[channel->regs.read_meth][1];
//Calculate Table Length for Table 1 & Table 2
const int lent1 = mode_to_len_t1[channel->regs.read_meth][channel->regs.table_len];
const int lent2 = mode_to_len_t2[channel->regs.read_meth][channel->regs.table_len];
//Start & Stop Address - Note the special case for table length of 16 - Bit 5 always 1 in this case // but also for special cases 8 and 4 for table 2?!?
//Calculate Table 1 Start & End Address in ROM
const int t1start = ((channel->regs.table1_addr<<5) & (~(lent1-1)&0x1fff)) | (lent1 == 16 ? 0x10 : 0); //T1 Addr is only upper 8 bits, but masked by length
//const int t1end = t1start | (lent1-1);
//Calculate Table 2 Start & End Address in ROM
const int t2start = ((channel->regs.table2_addr<<5) & (~(lent2-1)&0x1fff)) | (lent2 <= 16 ? 0x10 : 0); //T2 Addr is only upper 8 bits, but masked by length
//const int t2end = t2start | (lent2-1);
//Calculate initial frequency of both tables
double freq = chip->is_M114A ? freqtable4Mhz[channel->regs.frequency] : freqtable6Mhz[channel->regs.frequency];
//Calculate new volume
channel->target_volume = channel->regs.atten < 32 ? v_linear[channel->regs.atten] : 0; // channel must be kept active, but is it really 0? Or is the last value used (v_linear[31])? Or something inbetween??
//Adjust frequency if octave divisor set
if (channel->regs.oct_divisor)
freq /= 2.; // maybe new frequency must be clamped to freqtable[0]??
//Channel is now active!
channel->active = 1;
// Setup Sample Rate/Step size increase
channel->incr = (uint32_t)(freq * ((double)(16u << FRAC_BITS) / SAMPLE_RATE));
//Assign start & stop address offsets to ROM
channel->table1.start_address = t1start;
channel->table2.start_address = t2start;
//channel->table1.stop_address = t1end;
//channel->table2.stop_address = t2end;
//Assign # of times to re-read & Length
channel->table1.reread = rep1;
channel->table2.reread = rep2;
channel->table1.length = lent1;
channel->table2.length = lent2;
channel->table1.total_length = lent1*rep1;
channel->table2.total_length = lent2*rep2;
//Calculate Sample Volume
//- If Envelope should be used, Take the difference in new volume & current volume, and break it into the # of samples in the table
//- If No Envelope should be used, Volume is simply based on the Volume Table
#ifdef USE_VOL_ENVELOPE
if(channel->regs.env_enable) {
const int diff = channel->target_volume - channel->prev_volume;
channel->step_rate_volume_env = abs(diff) > 128 ? 1 : abs(diff) > 64 ? 2 : abs(diff) > 32 ? 4 : 8;
if(channel->target_volume < channel->prev_volume)
channel->step_rate_volume_env = -channel->step_rate_volume_env;
channel->current_volume = channel->prev_volume;
}
else
#endif
{
channel->current_volume = channel->target_volume;
}
//Update Last Volume
channel->prev_volume = channel->target_volume;
//Temp hack to ensure we only generate the ouput data 1x - this is WRONG and needs to be addressed eventually!
//if(channel->output[0] == 0)
read_table(chip,channel);
#if 0
//if(chip->channel == 2) {
if(channel->regs.outputs == 2) {
if(channel->regs.frequency == 0x70)
{
LOG(("orig: = %0f, freq = %0f, incr = %0d \n",freqtable4Mhz[channel->regs.frequency],freq,channel->incr));
}
//LOG(("EOT=%d\n",channel->end_of_table));
LOG(("C:%02d V:%02d FQ:%03x TS1:%02x TS2:%02x T1L:%04d T1R:%01d T2L:%04d T2R:%01d OD=%01d I:%02d E:%01d\n",
chip->channel,
channel->regs.atten,
channel->regs.frequency,
t1start,t2start,
lent1,rep1,
lent2,rep2,
channel->regs.oct_divisor,
channel->regs.interp,
channel->regs.env_enable
));
}
#endif
}
}
/**********************************************************************************************
m114s_data_write -- handle a write to the data bus of the M114S
The chip has a data bus width of 6 bits, and must be fed 8 consecutive bytes
- thus 48 bits of programming! All data must be fed in order, so we make a few assumptions.
***********************************************************************************************/
void m114s_data_write(struct M114SChip* chip, data8_t data)
{
/* Check if the chip needs to 'auto-reset' - this occurs if during the programming sequence (ie before all 8 bytes read)
a certain amount of time elapses without receiving another byte of programming...
128us in the 4Mhz chip, 85us in the 6Mhz chip.
*/
static UINT64 last_totcyc = 0;
UINT64 curr_totcyc = cpu_gettotalcycles64(chip->cpu_num);
double diff = (double)llabs((INT64)(curr_totcyc-last_totcyc));
last_totcyc = curr_totcyc;
if(chip->bytes_read && diff > chip->reset_cycles) {
LOG(("M114S: Auto Reset - bytes read=%0d - data=%0x, elapsed cycles = %f\n",chip->bytes_read,data&0x3f,diff));
M114S_sh_reset();
}
data &= 0x3f; //Strip off bits 7-8 (only 6 bits for the data bus to the chip)
chip->bytes_read++;
switch(chip->bytes_read)
{
/* BYTE #1 -
Bits 0-5: Attenuation Value (0-63) - 0 = No Attenuation, 3E = Max, 3F = Silence active channel */
case 1:
chip->tempch_regs.atten = data;
break;
/* BYTE #2 -
Bits 0-1: Table 2 Address (Bits 6-7)
Bits 2-3: Table 1 Address (Bits 6-7)
Bits 4-5: Output Pin Selection (0-3) */
case 2:
chip->tempch_regs.table2_addr = (data & 0x03)<<6;
chip->tempch_regs.table1_addr = (data & 0x0c)<<4;
chip->tempch_regs.outputs = (data & 0x30)>>4;
break;
/* BYTE #3 -
Bits 0-5: Table 2 Address (Bits 0-5) */
case 3:
chip->tempch_regs.table2_addr |= data;
break;
/* BYTE #4 -
Bits 0-5: Table 1 Address (Bits 0-5) */
case 4:
chip->tempch_regs.table1_addr |= data;
break;
/* BYTE #5 -
Bits 0-2: Reading Method
Bits 3-5: Table Length */
case 5:
chip->tempch_regs.read_meth = data & 0x07;
chip->tempch_regs.table_len = (data & 0x38)>>3;
break;
/* BYTE #6 -
Bits 0 : Octave Divisor
Bits 1 : Envelope Enable/Disable
Bits 2-5: Interpolation Value (0-15) */
case 6:
chip->tempch_regs.oct_divisor = (data & 0x01);
chip->tempch_regs.env_enable = (data & 0x02);
chip->tempch_regs.interp = (data & 0x3c)>>2;
break;
/* BYTE #7 -
Bits 0-1: Frequency (Bits 0-1)
Bits 2-5: Channel */
case 7:
chip->tempch_regs.frequency = (data & 0x03);
chip->channel = (data & 0x3c)>>2;
break;
/* BYTE #8 -
Bits 0-5: Frequency (Bits 2-7) */
case 8:
chip->tempch_regs.frequency |= (data<<2);
/* Process the channel data */
process_channel_data(chip);
break;
default:
LOG(("M114S.C - logic error - too many bytes processed: %x\n",chip->bytes_read));
break;
}
}

View file

@ -0,0 +1,106 @@
/**********************************************************************************************
*
* SGS-Thomson Microelectronics M114S/M114A/M114AF Digital Sound Generator
* by Steve Ellenoff
* 09/02/2004
*
* Thanks to R.Belmont for Support & Agreeing to read through that nasty data sheet with me..
* Big thanks to Destruk for help in tracking down the data sheet.. Could have never done it
* without it!!
*
*
* Code based largely on Aaron Gile's BSMT2000 driver.
**********************************************************************************************/
#ifndef M114S_H
#define M114S_H
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include <stdint.h>
// by default output all 4 channels because Furnace supports that
#define M114S_OUTPUT_CHANNELS 4 // HW uses 4, but if set to 16 instead, we simply output each (internal) channel independently (which is not how the real chip works, as that one mixes the 16 down to 4 outputs)
#define M114S_CHANNELS 16 // Chip has 16 internal channels for sound output
/* struct describing a single table */
struct M114STable
{
uint8_t reread; /* # of times to re-read each byte from table */
//uint32_t position; /* current reading position for table */
uint32_t start_address; /* start address (offset into ROM) for table */
//uint32_t stop_address; /* stop address (offset into ROM) for table */
uint16_t length; /* length in bytes of the table */
uint16_t total_length; /* total length in bytes of the table (including repetitions) */
};
/* struct describing the registers for a single channel */
struct M114SChannelRegs
{
uint8_t atten; /* Attenuation Register */
uint8_t outputs; /* Output Pin Register */
uint8_t table1_addr; /* Table 1 MSB Starting Address Register */
uint8_t table2_addr; /* Table 2 MSB Starting Address Register */
uint8_t table_len; /* Table Length Register */
uint8_t read_meth; /* Read Method Register */
uint8_t interp; /* Interpolation Register */
bool env_enable; /* Envelope Enable Register */
bool oct_divisor; /* Octave Divisor Register */
uint8_t frequency; /* Frequency Register */
};
/* struct describing a single playing channel */
struct M114SChannel
{
/* registers */
struct M114SChannelRegs regs; /* register data for the channel */
/* internal state */
bool active; /* is the channel active */
int16_t output[4096]; /* Holds output samples mixed from table 1 & 2 */
uint32_t outpos; /* Index into output samples */
int prev_volume; /* Holds the last volume code set for the channel */
int current_volume; /* Volume to apply to each sample output */
int target_volume; /* Target to match for volume envelope */
int step_rate_volume_env; /* Volume step for volume envelope */
//int end_of_table; /* End of Table Flag */
struct M114STable table1; /* Table 1 Data */
struct M114STable table2; /* Table 2 Data */
uint32_t incr; /* Current Sample Rate/Step size increase to play back table */
};
/* struct describing the entire M114S chip */
struct M114SChip
{
/* vars to be reset when the chip is reset */
int bytes_read; /* # of bytes read */
int channel; /* Which channel is being programmed via the data bus */
struct M114SChannelRegs tempch_regs; /* temporary channel register data for gathering the data programming */
struct M114SChannel channels[M114S_CHANNELS]; /* All the chip's internal channels */
int channel_volume[4]; /* scale for the HW/chip outputs */
int8_t * region_base; /* pointer to the base of the ROM region */
struct M114Sinterface* intf; /* Pointer to the interface */
double reset_cycles; /* # of cycles that must pass between programming bytes to auto reset the chip */
int cpu_num; /* # of the cpu controlling the M114S */
bool is_M114A; /* M114A 4MHz or M114AF 6MHz */
};
struct M114Sinterface
{
int num; /* total number of chips */
int baseclock[MAX_M114S]; /* input clock - Allowed values are 4Mhz & 6Mhz only! */
int region[MAX_M114S]; /* memory region where the sample ROM lives */
int mixing_level[MAX_M114S][4]; /* master volume, one for each output sample */
int cpunum[MAX_M114S]; /* # of the cpu controlling the M114S */
};
int M114S_sh_start(const struct MachineSound *msound);
void M114S_sh_stop(void);
void M114S_sh_reset(void);
void m114s_data_write(struct M114SChip* chip, data8_t data);
void m114s_update(struct M114SChip* chip, int16_t **buffer, int samples);
#endif

View file

@ -493,16 +493,6 @@ namespace xgm
noise = 1;
noise_tap = (1<<1);
if (option[OPT_RANDOMIZE_NOISE])
{
noise |= ::rand();
counter[1] = -(rand() & 511);
}
if (option[OPT_RANDOMIZE_TRI])
{
tphase = ::rand() & 31;
counter[0] = -(rand() & 2047);
}
SetRate(rate);
}

View file

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

View file

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

View file

@ -5,6 +5,9 @@
SM8521 sound emulator
by cam900
MODIFIED BY TILDEARROW
THIS IS NOT THE ORIGINAL VERSION
This file is licensed under zlib license.
============================================================================
@ -69,7 +72,8 @@ void sm8521_noise_tick(struct sm8521_noise_t *noise, const int cycle)
noise->base.counter += cycle;
while (noise->base.counter >= (noise->base.t + 1))
{
noise->lfsr = rand() & 0x1; // unknown algorithm
// unknown algorithm, but don't use rand()
noise->lfsr = ( noise->lfsr>>1|(((noise->lfsr) ^ (noise->lfsr >> 5) ^ (noise->lfsr >> 8) ^ (noise->lfsr >> 13) ) & 1)<<31);
noise->base.counter -= (noise->base.t + 1);
}
noise->base.out = (((noise->lfsr & 0x1) ? 7 : -8) * noise->base.level) >> 1; // scale out to 8bit
@ -125,7 +129,7 @@ void sm8521_reset(struct sm8521_t *sm8521)
sm8521->noise.base.level = 0;
sm8521->noise.base.out = 0;
sm8521->noise.base.counter = 0;
sm8521->noise.lfsr = 0;
sm8521->noise.lfsr = 0x89abcdef;
sm8521->out = 0;
sm8521->sgda = 0;
sm8521->sgc = 0;

View file

@ -5,6 +5,9 @@
SM8521 sound emulator
by cam900
MODIFIED BY TILDEARROW
THIS IS NOT THE ORIGINAL VERSION
This file is licensed under zlib license.
============================================================================

View file

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

View file

@ -138,9 +138,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE);
if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
// TODO: sample map?
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
DivSample* sample=parent->getSample(chan[i].sample);
if (sample!=NULL) {
double off=0.25;
if (sample->centerRate<1) {
@ -209,6 +207,12 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
writeControlUpper(c.chan);
}
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample);
if (chan[c.chan].pcm) {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value);
chan[c.chan].freqChanged=true;

View file

@ -26,7 +26,7 @@
class DivPlatformSoundUnit: public DivDispatch {
struct Channel: public SharedChannel<signed char> {
int cutoff, baseCutoff, res, control, hasOffset;
int cutoff, baseCutoff, res, control, hasOffset, sample;
signed char pan;
unsigned char duty;
bool noise, pcm, phaseReset, filterPhaseReset, switchRoles;
@ -43,6 +43,7 @@ class DivPlatformSoundUnit: public DivDispatch {
res(0),
control(0),
hasOffset(0),
sample(-1),
pan(0),
duty(63),
noise(false),

View file

@ -261,7 +261,10 @@ 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);
if (c.value!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) postWrite(0xffff0002,0);

View file

@ -36,7 +36,6 @@ class DivPlatformT6W28: public DivDispatch {
Channel chan[4];
DivDispatchOscBuffer* oscBuf[4];
bool isMuted[4];
bool antiClickEnabled;
bool easyNoise;
struct QueuedWrite {
unsigned char addr;

View file

@ -121,10 +121,8 @@ void DivPlatformTIA::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
int bf=chan[i].baseFreq;
if (!parent->song.oldArpStrategy) {
if (!chan[i].fixedArp) {
bf+=chan[i].arpOff<<8;
}
if (!chan[i].fixedArp) {
bf+=chan[i].arpOff<<8;
}
if (chan[i].fixedArp) {
chan[i].freq=chan[i].baseNoteOverride&31;

View file

@ -509,12 +509,8 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
chan[c.chan].chVolL=(c.value>0);
chan[c.chan].chVolR=(c.value2>0);
chan[c.chan].freqChanged=true;
/*
if (isMuted[c.chan]) {
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
} else {
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7));
}*/
immWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|(chan[c.chan].active?0x40:0)|(chan[c.chan].chVolR<<7));
break;
}
case DIV_CMD_PITCH: {

View file

@ -235,7 +235,11 @@ int DivPlatformVERA::dispatch(DivCommand c) {
if (c.chan<16) {
rWriteLo(c.chan,2,chan[c.chan].vol);
} else {
if (c.value!=DIV_NOTE_NULL) chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
DivInstrument* ins=parent->getIns(chan[16].ins,DIV_INS_VERA);
chan[16].pcm.sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) {
chan[16].pcm.sample=-1;
}

View file

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

View file

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

View file

@ -179,8 +179,6 @@ void DivPlatformVRC6::tick(bool sysTick) {
if (chan[i].std.phaseReset.val && chan[i].active) {
if ((i!=2) && (!chan[i].pcm)) {
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_VRC6);
chan[i].dacSample=ins->amiga.getSample(chan[i].note);
if (chan[i].dacSample<0 || chan[i].dacSample>=parent->song.sampleLen) {
if (dumpWrites) {
chWrite(i,2,0x80);
@ -242,7 +240,10 @@ int DivPlatformVRC6::dispatch(DivCommand c) {
if (chan[c.chan].pcm) {
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
if (c.value!=DIV_NOTE_NULL) chan[c.chan].dacSample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
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);
@ -448,6 +449,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];
}

View file

@ -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();

View file

@ -222,7 +222,8 @@ void DivPlatformX1_010::acquire(short** buf, size_t len) {
if (stereo) buf[1][h]=tempR;
for (int i=0; i<16; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))>>1;
int vo=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))<<3;
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(vo,-32768,32767);
}
}
}
@ -540,7 +541,10 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
if (chan[c.chan].furnacePCM) {
chan[c.chan].pcm=true;
chan[c.chan].macroInit(ins);
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
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);
if (isBanked) {
@ -924,14 +928,14 @@ void DivPlatformX1_010::notifyInsDeletion(void* ins) {
void DivPlatformX1_010::setFlags(const DivConfig& flags) {
switch (flags.getInt("clockSel",0)) {
case 0: // 16MHz (earlier hardwares)
chipClock=16000000;
break;
case 1: // 16.67MHz (later hardwares)
chipClock=50000000.0/3.0;
break;
case 2: // 14.32MHz (see https://github.com/mamedev/mame/blob/master/src/mame/taito/champbwl.cpp#L620)
chipClock=COLOR_NTSC*4.0;
break;
// Other clock is used
default:
default: // 16MHz (earlier hardwares)
chipClock=16000000;
break;
}

View file

@ -169,6 +169,15 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) {
static short ignored[2];
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os=0;
// Nuked part
for (unsigned int i=0; i<nukedMult; i++) {
@ -242,6 +251,15 @@ void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os=0;
if (!writes.empty()) {
if (--delay<1) {
@ -567,7 +585,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].macroInit(NULL);
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;

View file

@ -542,13 +542,8 @@ void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
if (isOpMuted[ch-2] || !op.enable) {
rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
}
}

View file

@ -321,6 +321,15 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
// Nuked part
for (int i=0; i<nukedMult; i++) {
@ -427,6 +436,15 @@ void DivPlatformYM2608::acquire_ymfm(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
if (!writes.empty()) {
if (--delay<1) {
@ -896,7 +914,10 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
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);
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(0x100,0x01); // reset
@ -1004,7 +1025,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].macroInit(NULL);
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;
@ -1672,7 +1693,7 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, const DivCo
fm=new ymfm::ym2608(iface);
fm->set_fidelity(ymfm::OPN_FIDELITY_MIN);
// YM2149, 2MHz
ay=new DivPlatformAY8910(true,chipClock,ayDiv);
ay=new DivPlatformAY8910(true,chipClock,ayDiv,48);
ay->init(p,3,sugRate,ayFlags);
ay->toggleRegisterDump(true);
setFlags(flags);

View file

@ -564,13 +564,8 @@ void DivPlatformYM2608Ext::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
if (isOpMuted[ch-2] || !op.enable) {
rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
}
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));

View file

@ -256,6 +256,15 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
// Nuked part
for (int i=0; i<24; i++) {
@ -360,6 +369,15 @@ void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
if (!writes.empty()) {
if (--delay<1 && !(fm->read(0)&0x80)) {
@ -827,7 +845,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
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);
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(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
@ -976,7 +997,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].macroInit(NULL);
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;

View file

@ -320,7 +320,16 @@ void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
// Nuked part
for (int i=0; i<24; i++) {
if (!writes.empty()) {
@ -426,6 +435,15 @@ void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) {
}
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
os[0]=0; os[1]=0;
if (!writes.empty()) {
if (--delay<1 && !(fm->read(0)&0x80)) {
@ -894,7 +912,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
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);
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(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
@ -1043,7 +1064,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].macroInit(NULL);
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;

View file

@ -560,13 +560,8 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch];
if (isOpMuted[ch-extChanOffs] || !op.enable) {
rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
}
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));

View file

@ -560,13 +560,8 @@ void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch];
if (isOpMuted[ch-extChanOffs] || !op.enable) {
rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
}
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));

View file

@ -250,7 +250,7 @@ class DivPlatformYM2610Base: public DivPlatformOPN {
fm->set_fidelity(ymfm::OPN_FIDELITY_MED);
setFlags(flags);
// YM2149, 2MHz
ay=new DivPlatformAY8910(true,chipClock,32);
ay=new DivPlatformAY8910(true,chipClock,32,144);
ay->init(p,3,sugRate,ayFlags);
ay->toggleRegisterDump(true);
return 0;

View file

@ -212,7 +212,10 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].isNewYMZ=ins->type==DIV_INS_YMZ280B;
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255;
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
}

View file

@ -0,0 +1,407 @@
/**
* 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 "zxbeeperquadtone.h"
#include "../engine.h"
#include <math.h>
#define rWrite(a,v) regPool[a]=v
#define CHIP_FREQBASE 2048*320
#define CHIP_DIVIDER 16*13
const char** DivPlatformZXBeeperQuadTone::getRegisterSheet() {
return NULL;
}
void DivPlatformZXBeeperQuadTone::acquire(short** buf, size_t len) {
bool o=false;
for (size_t h=0; h<len; h++) {
if (curSample>=0 && curSample<parent->song.sampleLen && !isMuted[4]) {
while (curSamplePeriod>=chan[4].freq) {
DivSample* s=parent->getSample(curSample);
if (s->samples>0) {
if (!isMuted[4]) o=(s->data8[curSamplePos++]>0);
if (curSamplePos>=s->samples) curSample=-1;
// (theoretical) 32KiB limit
if (curSamplePos>=32768*8) curSample=-1;
} else {
curSample=-1;
}
curSamplePeriod-=chan[4].freq;
}
curSamplePeriod+=40;
if ((outputClock&3)==0) {
oscBuf[0]->data[oscBuf[0]->needle++]=0;
oscBuf[1]->data[oscBuf[1]->needle++]=0;
oscBuf[2]->data[oscBuf[2]->needle++]=0;
oscBuf[3]->data[oscBuf[3]->needle++]=0;
oscBuf[4]->data[oscBuf[4]->needle++]=o?32767:0;
}
} else {
int ch=outputClock/2;
int b=ch*4;
if ((outputClock&1)==0) {
chan[ch].sPosition+=(regPool[1+b]<<8)|regPool[0+b];
chan[ch].out=regPool[3+b]+((((chan[ch].sPosition>>8)&0xff)<regPool[2+b])?1:0);
if (isMuted[ch]) chan[ch].out=0;
}
if ((outputClock&3)==0) {
oscBuf[4]->data[oscBuf[4]->needle++]=0;
}
o=chan[ch].out&0x10;
oscBuf[ch]->data[oscBuf[ch]->needle++]=o?32767:0;
chan[ch].out<<=1;
// if muted, ztill run sample
if (curSample>=0 && curSample<parent->song.sampleLen && isMuted[4]) {
while (curSamplePeriod>=chan[4].freq) {
DivSample* s=parent->getSample(curSample);
if (s->samples>0) {
if (curSamplePos>=s->samples) curSample=-1;
// (theoretical) 32KiB limit
if (curSamplePos>=32768*8) curSample=-1;
} else {
curSample=-1;
}
curSamplePeriod-=chan[4].freq;
}
curSamplePeriod+=40;
}
}
outputClock=(outputClock+1)&7;
buf[0][h]=o?32767:0;
}
}
void DivPlatformZXBeeperQuadTone::tick(bool sysTick) {
for (int i=0; i<4; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=(MIN(chan[i].vol,2)*MIN(chan[i].std.vol.val,2)/2);
writeOutVol(i);
}
if (chan[i].std.duty.had) {
chan[i].duty=chan[i].std.duty.val;
rWrite(2+i*4,chan[i].duty^0xff);
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
}
chan[i].freqChanged=true;
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-65535,65535);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (chan[i].active) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>32768) chan[i].freq=32768;
rWrite(0+i*4,chan[i].freq&0xff);
rWrite(1+i*4,chan[i].freq>>8);
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
}
if (NEW_ARP_STRAT) {
chan[4].handleArp();
} else if (chan[4].std.arp.had) {
if (!chan[4].inPorta) {
chan[4].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[4].note,chan[4].std.arp.val));
}
chan[4].freqChanged=true;
}
if (chan[4].std.pitch.had) {
if (chan[4].std.pitch.mode) {
chan[4].pitch2+=chan[4].std.pitch.val;
CLAMP_VAR(chan[4].pitch2,-65535,65535);
} else {
chan[4].pitch2=chan[4].std.pitch.val;
}
chan[4].freqChanged=true;
}
if (chan[4].freqChanged || chan[4].keyOn || chan[4].keyOff) {
if (chan[4].active) {
double off=CHIP_DIVIDER;
if (curSample>=0 && curSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(curSample);
off=(s->centerRate>=1)?(CHIP_DIVIDER*(double)s->centerRate/8363.0):CHIP_DIVIDER;
}
chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,chan[4].fixedArp?chan[4].baseNoteOverride:chan[4].arpOff,chan[4].fixedArp,true,2,chan[4].pitch2,chipClock,off);
if (chan[4].freq>258) chan[4].freq=258;
if (chan[4].freq<3) chan[4].freq=3;
chan[4].freq*=13;
}
if (chan[4].keyOn) chan[4].keyOn=false;
if (chan[4].keyOff) chan[4].keyOff=false;
chan[4].freqChanged=false;
}
}
int DivPlatformZXBeeperQuadTone::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
if (c.chan<4) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(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);
chan[c.chan].insChanged=false;
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
writeOutVol(c.chan);
}
} else {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
if (c.value!=DIV_NOTE_NULL) {
curSample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
// TODO support offset commands
curSamplePos=0;
curSamplePeriod=0;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
chan[c.chan].insChanged=false;
}
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
writeOutVol(c.chan);
if (c.chan>=4) {
curSample=-1;
}
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) {
chan[c.chan].outVol=c.value;
writeOutVol(c.chan);
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_STD_NOISE_MODE:
chan[c.chan].duty=c.value;
if (c.chan<4) rWrite(2+c.chan*4,chan[c.chan].duty^0xff);
break;
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((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_POKEMINI));
}
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 2;
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_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformZXBeeperQuadTone::writeOutVol(int ch) {
if (ch>=4) return;
unsigned char val=(chan[ch].outVol>=1)?((chan[ch].outVol>=2)?31:7):0;
rWrite(3+ch*4,(chan[ch].active)?val:0);
}
void DivPlatformZXBeeperQuadTone::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
writeOutVol(ch);
}
void DivPlatformZXBeeperQuadTone::forceIns() {
for (int i=0; i<5; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
}
}
void* DivPlatformZXBeeperQuadTone::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformZXBeeperQuadTone::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivDispatchOscBuffer* DivPlatformZXBeeperQuadTone::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformZXBeeperQuadTone::getRegisterPool() {
return regPool;
}
int DivPlatformZXBeeperQuadTone::getRegisterPoolSize() {
return 16;
}
void DivPlatformZXBeeperQuadTone::reset() {
memset(regPool,0,16);
for (int i=0; i<5; i++) {
chan[i]=DivPlatformZXBeeperQuadTone::Channel();
chan[i].std.setEngine(parent);
if (i<4) rWrite(2+i*4,128);
}
cycles=0;
curChan=0;
sOffTimer=0;
ulaOut=0;
curSample=-1;
curSamplePos=0;
curSamplePeriod=0;
outputClock=0;
}
bool DivPlatformZXBeeperQuadTone::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformZXBeeperQuadTone::notifyWaveChange(int wave) {
}
void DivPlatformZXBeeperQuadTone::notifyInsDeletion(void* ins) {
for (int i=0; i<5; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformZXBeeperQuadTone::setFlags(const DivConfig& flags) {
if (flags.getInt("clockSel",0)) {
chipClock=COLOR_PAL*4.0/5.0;
} else {
chipClock=COLOR_NTSC;
}
CHECK_CUSTOM_CLOCK;
rate=chipClock/40;
for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate/4;
}
}
void DivPlatformZXBeeperQuadTone::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformZXBeeperQuadTone::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformZXBeeperQuadTone::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
for (int i=0; i<5; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
reset();
return 5;
}
void DivPlatformZXBeeperQuadTone::quit() {
for (int i=0; i<5; i++) {
delete oscBuf[i];
}
}
DivPlatformZXBeeperQuadTone::~DivPlatformZXBeeperQuadTone() {
}

View file

@ -0,0 +1,73 @@
/**
* 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 _ZXBEEPERQUADTONE_H
#define _ZXBEEPERQUADTONE_H
#include "../dispatch.h"
class DivPlatformZXBeeperQuadTone: public DivDispatch {
struct Channel: public SharedChannel<unsigned char> {
unsigned short sPosition;
unsigned char duty;
unsigned char out;
Channel():
SharedChannel<unsigned char>(2),
sPosition(0),
duty(128),
out(0) {}
};
Channel chan[5];
DivDispatchOscBuffer* oscBuf[5];
bool isMuted[5];
unsigned char ulaOut;
int cycles, curChan, sOffTimer, delay, curSample, curSamplePeriod;
unsigned int curSamplePos;
unsigned int outputClock;
unsigned char regPool[16];
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
void setFlags(const DivConfig& flags);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
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();
~DivPlatformZXBeeperQuadTone();
private:
void writeOutVol(int ch);
};
#endif

View file

@ -229,6 +229,13 @@ const char* cmdName[]={
"ES5506_ENVELOPE_K2RAMP",
"ES5506_PAUSE",
"HINT_ARP_TIME",
"SNES_GLOBAL_VOL_LEFT",
"SNES_GLOBAL_VOL_RIGHT",
"NES_LINEAR_LENGTH",
"ALWAYS_SET_VOLUME"
};
@ -278,7 +285,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 +312,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;
@ -410,6 +417,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
effectVal&=255;
switch (effect) {
case 0x09: // select groove pattern/speed 1
@ -510,13 +518,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (chan[i].stopOnOff) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].stopOnOff=false;
}
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
chan[i+1].portaNote=-1;
chan[i+1].portaSpeed=-1;
@ -533,13 +541,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (chan[i].stopOnOff) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].stopOnOff=false;
}
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
chan[i+1].portaNote=-1;
chan[i+1].portaSpeed=-1;
@ -590,6 +598,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
effectVal&=255;
// per-system effect
if (!perSystemEffect(i,effect,effectVal)) switch (effect) {
@ -632,13 +641,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (effectVal==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].inPorta=false;
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else {
chan[i].portaNote=song.limitSlides?0x60:255;
chan[i].portaSpeed=effectVal;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=false;
@ -654,13 +663,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (effectVal==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].inPorta=false;
if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else {
chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->getPortaFloor(dispatchChanOfChan[i]):-60;
chan[i].portaSpeed=effectVal;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=false;
@ -674,10 +683,11 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (effectVal==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].inPorta=false;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else {
chan[i].lastPorta=effectVal;
calledPorta=true;
if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) {
chan[i].portaNote=chan[i].note;
@ -688,7 +698,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
chan[i].inPorta=true;
chan[i].wasShorthandPorta=false;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].portaStop=true;
if (chan[i].keyOn) chan[i].doNote=false;
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
@ -698,11 +708,78 @@ void DivEngine::processRow(int i, bool afterDelay) {
}
break;
case 0x04: // vibrato
if (effectVal) chan[i].lastVibrato=effectVal;
chan[i].vibratoDepth=effectVal&15;
chan[i].vibratoRate=effectVal>>4;
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate));
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
break;
case 0x05: // vol slide + vibrato
if (effectVal==0) {
chan[i].vibratoDepth=0;
chan[i].vibratoRate=0;
} else {
chan[i].vibratoDepth=chan[i].lastVibrato&15;
chan[i].vibratoRate=chan[i].lastVibrato>>4;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate));
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
// TODO: non-0x-or-x0 value should be treated as 00
if (effectVal!=0) {
if ((effectVal&15)!=0) {
chan[i].volSpeed=-(effectVal&15)*64;
} else {
chan[i].volSpeed=(effectVal>>4)*64;
}
// tremolo and vol slides are incompatible
chan[i].tremoloDepth=0;
chan[i].tremoloRate=0;
} else {
chan[i].volSpeed=0;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
break;
case 0x06: // vol slide + porta
if (effectVal==0 || chan[i].lastPorta==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].inPorta=false;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else {
calledPorta=true;
if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) {
chan[i].portaNote=chan[i].note;
chan[i].portaSpeed=-1;
} else {
chan[i].portaNote=chan[i].note;
chan[i].portaSpeed=chan[i].lastPorta;
chan[i].inPorta=true;
chan[i].wasShorthandPorta=false;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].portaStop=true;
if (chan[i].keyOn) chan[i].doNote=false;
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
chan[i].scheduledSlideReset=false;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,1));
lastSlide=0x1337; // i hate this so much
}
// TODO: non-0x-or-x0 value should be treated as 00
if (effectVal!=0) {
if ((effectVal&15)!=0) {
chan[i].volSpeed=-(effectVal&15)*64;
} else {
chan[i].volSpeed=(effectVal>>4)*64;
}
// tremolo and vol slides are incompatible
chan[i].tremoloDepth=0;
chan[i].tremoloRate=0;
} else {
chan[i].volSpeed=0;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
break;
case 0x07: // tremolo
// TODO
// this effect is really weird. i thought it would alter the tremolo depth but turns out it's completely different
@ -765,12 +842,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
case 0xe0: // arp speed
if (effectVal>0) {
curSubSong->arpLen=effectVal;
dispatchCmd(DivCommand(DIV_CMD_HINT_ARP_TIME,i,curSubSong->arpLen));
}
break;
case 0xe1: // portamento up
chan[i].portaNote=chan[i].note+(effectVal&15);
chan[i].portaSpeed=(effectVal>>4)*4;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
@ -789,7 +867,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
case 0xe2: // portamento down
chan[i].portaNote=chan[i].note-(effectVal&15);
chan[i].portaSpeed=(effectVal>>4)*4;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].portaStop=true;
chan[i].nowYouCanStop=false;
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
@ -947,13 +1025,13 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (chan[i].inPorta && chan[i].keyOn && !chan[i].shorthandPorta) {
if (song.e1e2StopOnSameNote && chan[i].wasShorthandPorta) {
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
if (!song.brokenShortcutSlides) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
chan[i].wasShorthandPorta=false;
chan[i].inPorta=false;
} else {
chan[i].portaNote=chan[i].note;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
}
} else if (!chan[i].noteOnInhibit) {
dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8));
@ -966,7 +1044,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (!chan[i].keyOn && chan[i].scheduledSlideReset) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].scheduledSlideReset=false;
chan[i].inPorta=false;
}
@ -988,6 +1066,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
effectVal&=255;
perSystemPostEffect(i,effect,effectVal);
}
}
@ -1122,25 +1201,47 @@ void DivEngine::nextRow() {
bool wantPreNote=false;
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
wantPreNote=disCont[dispatchOfChan[i]].dispatch->getWantPreNote();
if (wantPreNote) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks));
if (wantPreNote) {
int addition=0;
for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==0xed) {
if (pat->data[curRow][5+(j<<1)]>0) {
addition=pat->data[curRow][5+(j<<1)]&255;
break;
}
}
}
dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks+addition));
}
}
if (song.oneTickCut) {
bool doPrepareCut=true;
int addition=0;
for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==0x03) {
doPrepareCut=false;
break;
}
if (pat->data[curRow][4+(j<<1)]==0x06) {
doPrepareCut=false;
break;
}
if (pat->data[curRow][4+(j<<1)]==0xea) {
if (pat->data[curRow][5+(j<<1)]>0) {
doPrepareCut=false;
break;
}
}
if (pat->data[curRow][4+(j<<1)]==0xed) {
if (pat->data[curRow][5+(j<<1)]>0) {
addition=pat->data[curRow][5+(j<<1)]&255;
break;
}
}
}
if (doPrepareCut && !wantPreNote && chan[i].cut<=0) chan[i].cut=ticks;
if (doPrepareCut && !wantPreNote && chan[i].cut<=0) chan[i].cut=ticks+addition;
}
}
}
@ -1216,11 +1317,6 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if (--subticks<=0) {
subticks=tickMult;
// MIDI clock
if (output) if (!skipping && output->midiOut!=NULL && midiOutClock) {
output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
}
if (stepPlay!=1) {
tempoAccum+=curSubSong->virtualTempoN;
while (tempoAccum>=curSubSong->virtualTempoD) {
@ -1290,10 +1386,10 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
}
if (chan[i].vibratoDepth>0) {
chan[i].vibratoPos+=chan[i].vibratoRate;
if (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64;
while (chan[i].vibratoPos>=64) chan[i].vibratoPos-=64;
chan[i].vibratoPosGiant+=chan[i].vibratoRate;
if (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512;
while (chan[i].vibratoPos>=512) chan[i].vibratoPos-=512;
switch (chan[i].vibratoDir) {
case 1: // up
@ -1311,7 +1407,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) {
if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) {
chan[i].portaSpeed=0;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].oldNote=chan[i].note;
chan[i].note=chan[i].portaNote;
chan[i].inPorta=false;
@ -1330,13 +1426,13 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
if (chan[i].stopOnOff) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].stopOnOff=false;
}
if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,chan[i].portaNote,chan[i].portaSpeed));
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
/*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) {
chan[i+1].portaNote=-1;
chan[i+1].portaSpeed=-1;
@ -1385,6 +1481,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) {
@ -1400,6 +1505,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
ret=true;
shallStop=false;
shallStopSched=false;
// reset all chan oscs
for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=disCont[dispatchOfChan[i]].dispatch->getOscBuffer(dispatchChanOfChan[i]);
if (buf!=NULL) {
memset(buf->data,0,65536*sizeof(short));
buf->needle=0;
buf->readNeedle=0;
}
}
return ret;
}
@ -1432,6 +1546,147 @@ int DivEngine::getBufferPos() {
return bufferPos>>MASTER_CLOCK_PREC;
}
void DivEngine::runMidiClock(int totalCycles) {
if (freelance) return;
midiClockCycles-=totalCycles;
while (midiClockCycles<=0) {
curMidiClock++;
if (output) if (!skipping && output->midiOut!=NULL && midiOutClock) {
output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0));
}
double hl=curSubSong->hilightA;
if (hl<=0.0) hl=4.0;
double timeBase=curSubSong->timeBase+1;
double speedSum=0;
double vD=curSubSong->virtualTempoD;
for (int i=0; i<MIN(16,speeds.len); i++) {
speedSum+=speeds.val[i];
}
speedSum/=MAX(1,speeds.len);
if (timeBase<1.0) timeBase=1.0;
if (speedSum<1.0) speedSum=1.0;
if (vD<1) vD=1;
double bpm=((24.0*divider)/(timeBase*hl*speedSum))*(double)curSubSong->virtualTempoN/vD;
midiClockCycles+=got.rate*pow(2,MASTER_CLOCK_PREC)/(bpm);
midiClockDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(bpm));
if (midiClockDrift>=(bpm)) {
midiClockDrift-=(bpm);
midiClockCycles++;
}
}
}
void DivEngine::runMidiTime(int totalCycles) {
if (freelance) return;
midiTimeCycles-=totalCycles;
while (midiTimeCycles<=0) {
if (curMidiTimePiece==0) {
curMidiTimeCode=curMidiTime;
}
if (!(curMidiTimePiece&3)) curMidiTime++;
double frameRate=96.0;
int timeRate=midiOutTimeRate;
if (timeRate<1 || timeRate>4) {
if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) {
timeRate=1;
} else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) {
timeRate=2;
} else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) {
timeRate=4;
} else {
timeRate=4;
}
}
int hour=0;
int minute=0;
int second=0;
int frame=0;
int drop=0;
int actualTime=curMidiTimeCode;
switch (timeRate) {
case 1: // 24
frameRate=96.0;
hour=(actualTime/(60*60*24))%24;
minute=(actualTime/(60*24))%60;
second=(actualTime/24)%60;
frame=actualTime%24;
break;
case 2: // 25
frameRate=100.0;
hour=(actualTime/(60*60*25))%24;
minute=(actualTime/(60*25))%60;
second=(actualTime/25)%60;
frame=actualTime%25;
break;
case 3: // 29.97 (NTSC drop)
frameRate=120.0*(1000.0/1001.0);
// drop
drop=((actualTime/(30*60))-(actualTime/(30*600)))*2;
actualTime+=drop;
hour=(actualTime/(60*60*30))%24;
minute=(actualTime/(60*30))%60;
second=(actualTime/30)%60;
frame=actualTime%30;
break;
case 4: // 30 (NTSC non-drop)
default:
frameRate=120.0;
hour=(actualTime/(60*60*30))%24;
minute=(actualTime/(60*30))%60;
second=(actualTime/30)%60;
frame=actualTime%30;
break;
}
if (output) if (!skipping && output->midiOut!=NULL && midiOutTime) {
unsigned char val=0;
switch (curMidiTimePiece) {
case 0:
val=frame&15;
break;
case 1:
val=frame>>4;
break;
case 2:
val=second&15;
break;
case 3:
val=second>>4;
break;
case 4:
val=minute&15;
break;
case 5:
val=minute>>4;
break;
case 6:
val=hour&15;
break;
case 7:
val=(hour>>4)|((timeRate-1)<<1);
break;
}
val|=curMidiTimePiece<<4;
output->midiOut->send(TAMidiMessage(TA_MIDI_MTC_FRAME,val,0));
}
curMidiTimePiece=(curMidiTimePiece+1)&7;
midiTimeCycles+=got.rate*pow(2,MASTER_CLOCK_PREC)/(frameRate);
midiTimeDrift+=fmod(got.rate*pow(2,MASTER_CLOCK_PREC),(double)(frameRate));
if (midiTimeDrift>=(frameRate)) {
midiTimeDrift-=(frameRate);
midiTimeCycles++;
}
}
}
void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) {
lastLoopPos=-1;
@ -1520,10 +1775,13 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
samp_temp=0;
} else {
samp_temp=s->data16[sPreview.pos];
if (sPreview.dir) {
sPreview.pos--;
} else {
sPreview.pos++;
if (--sPreview.posSub<=0) {
sPreview.posSub=sPreview.rateMul;
if (sPreview.dir) {
sPreview.pos--;
} else {
sPreview.pos++;
}
}
}
blip_add_delta(samp_bb,i,samp_temp-samp_prevSample);
@ -1628,8 +1886,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
} else {
samp_temp=((MIN(wave->data[sPreview.pos],wave->max)<<14)/wave->max)-8192;
}
if (++sPreview.pos>=wave->len) {
sPreview.pos=0;
if (--sPreview.posSub<=0) {
sPreview.posSub=sPreview.rateMul;
if (++sPreview.pos>=wave->len) {
sPreview.pos=0;
}
}
blip_add_delta(samp_bb,i,samp_temp-samp_prevSample);
samp_prevSample=samp_temp;
@ -1710,7 +1971,18 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
pendingMetroTick=0;
}
} else {
// 3. tick the clock and fill buffers as needed
// 3. run MIDI clock
int midiTotal=MIN(cycles,runLeftG);
for (int i=0; i<midiTotal; i++) {
runMidiClock();
}
// 4. run MIDI timecode
for (int i=0; i<midiTotal; i++) {
runMidiTime();
}
// 5. tick the clock and fill buffers as needed
if (cycles<runLeftG) {
for (int i=0; i<song.systemLen; i++) {
int total=(cycles*disCont[i].runtotal)/(size<<MASTER_CLOCK_PREC);

View file

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

View file

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

View file

@ -19,6 +19,7 @@
#include "sample.h"
#include "../ta-log.h"
#include "../fileutils.h"
#include <math.h>
#include <string.h>
#ifdef HAVE_SNDFILE
@ -444,6 +445,35 @@ bool DivSample::save(const char* path) {
#endif
}
bool DivSample::saveRaw(const char* path) {
if (samples<1) {
logE("sample is empty though!");
return false;
}
FILE* f=ps_fopen(path,"wb");
if (f==NULL) {
logE("could not save sample: %s!",strerror(errno));
return false;
}
if (depth==DIV_SAMPLE_DEPTH_BRR) {
if (isLoopable()) {
unsigned short loopPos=getLoopStartPosition(DIV_SAMPLE_DEPTH_BRR);
fputc(loopPos&0xff,f);
fputc(loopPos>>8,f);
} else {
fputc(0,f);
fputc(0,f);
}
}
if (fwrite(getCurBuf(),1,getCurBufLen(),f)!=getCurBufLen()) {
logW("did not write entire instrument!");
}
fclose(f);
return true;
}
// 16-bit memory is padded to 512, to make things easier for ADPCM-A/B.
bool DivSample::initInternal(DivSampleDepth d, int count) {
switch (d) {
@ -455,9 +485,9 @@ bool DivSample::initInternal(DivSampleDepth d, int count) {
break;
case DIV_SAMPLE_DEPTH_1BIT_DPCM: // DPCM
if (dataDPCM!=NULL) delete[] dataDPCM;
lengthDPCM=(count+7)/8;
lengthDPCM=1+((((count+7)/8)+15)&(~15));
dataDPCM=new unsigned char[lengthDPCM];
memset(dataDPCM,0,lengthDPCM);
memset(dataDPCM,0xaa,lengthDPCM);
break;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM
if (dataZ!=NULL) delete[] dataZ;
@ -892,10 +922,10 @@ bool DivSample::resampleBlep(double r) {
}
}
for (int i=0; i<finalCount; i++) {
float result=floatData[i]+data16[i];
float result=floatData[i]+data8[i];
if (result<-128) result=-128;
if (result>127) result=127;
data16[i]=round(result);
data8[i]=round(result);
}
}
delete[] floatData;
@ -1062,12 +1092,14 @@ void DivSample::render(unsigned int formatMask) {
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_1BIT_DPCM)) { // DPCM
if (!initInternal(DIV_SAMPLE_DEPTH_1BIT_DPCM,samples)) return;
int accum=63;
int next=63;
for (unsigned int i=0; i<samples; i++) {
int next=((unsigned short)(data16[i]^0x8000))>>9;
next=((unsigned short)(data16[i]^0x8000))>>9;
if (next>accum) {
dataDPCM[i>>3]|=1<<(i&7);
accum++;
} else {
dataDPCM[i>>3]&=~(1<<(i&7));
accum--;
}
if (accum<0) accum=0;

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