Merge branch 'master' into spectrum

This commit is contained in:
Eknous-P 2025-10-29 12:35:13 +04:00
commit 8626937f89
2907 changed files with 1544042 additions and 23152 deletions

View file

@ -37,6 +37,18 @@ void* TAAudio::getContext() {
return NULL;
}
TAAudioDeviceStatus TAAudio::getDeviceStatus() {
return deviceStatus;
}
void TAAudio::acceptDeviceStatus() {
deviceStatus=TA_AUDIO_DEVICE_OK;
}
int TAAudio::specialCommand(TAAudioCommand which) {
return -1;
}
bool TAAudio::quit() {
return true;
}
@ -121,4 +133,4 @@ TAMidiIn::~TAMidiIn() {
}
TAMidiOut::~TAMidiOut() {
}
}

617
src/audio/asio.cpp Normal file
View file

@ -0,0 +1,617 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <string.h>
#include "asio.h"
#include "../ta-log.h"
static TAAudioASIO* callbackInstance=NULL;
extern AsioDrivers* asioDrivers;
bool loadAsioDriver(char *name);
static void _onBufferSwitch(long index, ASIOBool isDirect) {
if (callbackInstance==NULL) return;
callbackInstance->onProcess(index);
}
static void _onSampleRate(ASIOSampleRate rate) {
if (callbackInstance==NULL) return;
callbackInstance->onSampleRate(*(double*)(&rate));
}
static long _onMessage(long type, long value, void* msg, double* opt) {
if (callbackInstance==NULL) return 0;
switch (type) {
case kAsioSelectorSupported:
switch (value) {
case kAsioSelectorSupported:
case kAsioEngineVersion:
case kAsioResetRequest:
case kAsioBufferSizeChange:
case kAsioResyncRequest:
return 1;
default:
return 0;
}
break;
case kAsioEngineVersion:
return 2;
break;
case kAsioResetRequest:
callbackInstance->requestDeviceChange();
return 1;
break;
case kAsioBufferSizeChange:
callbackInstance->onBufferSize(value);
return 1;
break;
case kAsioResyncRequest:
// ignore
return 1;
break;
}
return 0;
}
void TAAudioASIO::onSampleRate(double rate) {
sampleRateChanged(SampleRateChangeEvent(rate));
}
void TAAudioASIO::onBufferSize(int bufsize) {
bufferSizeChanged(BufferSizeChangeEvent(bufsize));
desc.bufsize=bufsize;
}
void TAAudioASIO::onProcess(int index) {
if (audioProcCallback!=NULL) {
if (midiIn!=NULL) midiIn->gather();
audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize);
}
// upload here...
for (int i=0; i<totalChans; i++) {
if (chanInfo[i].isInput==ASIOTrue) continue;
int ch=chanInfo[i].channel;
if (ch>=desc.outChans) continue;
float* srcBuf=outBufs[ch];
switch (chanInfo[i].type) {
// little-endian
case ASIOSTInt16LSB: {
short* buf=(short*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
buf[j]=CLAMP(srcBuf[j],-1.0,1.0)*32767.0f;
}
break;
}
case ASIOSTInt24LSB: {
unsigned char* buf=(unsigned char*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
int val=CLAMP(srcBuf[j],-1.0,1.0)*8388608.0f;
if (val<-8388608) val=-8388608;
if (val>8388607) val=-8388607;
*(buf++)=(val)&0xff;
*(buf++)=(val>>8)&0xff;
*(buf++)=(val>>16)&0xff;
}
break;
}
case ASIOSTInt32LSB:
case ASIOSTInt32LSB16:
case ASIOSTInt32LSB18:
case ASIOSTInt32LSB20:
case ASIOSTInt32LSB24: {
int* buf=(int*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
int val=CLAMP(srcBuf[j],-1.0,1.0)*8388608.0f;
if (val<-8388608) val=-8388608;
if (val>8388607) val=-8388607;
val<<=8;
buf[j]=val;
}
break;
}
case ASIOSTFloat32LSB: {
float* buf=(float*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
buf[j]=srcBuf[j];
}
break;
}
case ASIOSTFloat64LSB: {
double* buf=(double*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
buf[j]=srcBuf[j];
}
break;
}
// big-endian
case ASIOSTInt16MSB: {
unsigned short* buf=(unsigned short*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
unsigned short val=(unsigned short)((short)(CLAMP(srcBuf[j],-1.0,1.0)*32767.0f));
buf[j]=(val>>8)|(val<<8);
}
break;
}
case ASIOSTInt24MSB: {
unsigned char* buf=(unsigned char*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
int val=CLAMP(srcBuf[j],-1.0,1.0)*8388608.0f;
if (val<-8388608) val=-8388608;
if (val>8388607) val=-8388607;
*(buf++)=(val>>16)&0xff;
*(buf++)=(val>>8)&0xff;
*(buf++)=(val)&0xff;
}
break;
}
case ASIOSTInt32MSB:
case ASIOSTInt32MSB16:
case ASIOSTInt32MSB18:
case ASIOSTInt32MSB20:
case ASIOSTInt32MSB24: {
unsigned int* buf=(unsigned int*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
int val=CLAMP(srcBuf[j],-1.0,1.0)*8388608.0f;
if (val<-8388608) val=-8388608;
if (val>8388607) val=-8388607;
val<<=8;
unsigned char* uVal=(unsigned char*)&val;
buf[j]=(uVal[0]<<24)|(uVal[1]<<16)|(uVal[2]<<8)|(uVal[3]);
}
break;
}
case ASIOSTFloat32MSB: {
unsigned int* buf=(unsigned int*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
float val=srcBuf[j];
unsigned char* uVal=(unsigned char*)&val;
buf[j]=(uVal[0]<<24)|(uVal[1]<<16)|(uVal[2]<<8)|(uVal[3]);
}
break;
}
case ASIOSTFloat64MSB: {
unsigned char* buf=(unsigned char*)bufInfo[i].buffers[index];
for (unsigned int j=0; j<desc.bufsize; j++) {
double val=srcBuf[j];
unsigned char* uVal=(unsigned char*)&val;
*(buf++)=uVal[7];
*(buf++)=uVal[6];
*(buf++)=uVal[5];
*(buf++)=uVal[4];
*(buf++)=uVal[3];
*(buf++)=uVal[2];
*(buf++)=uVal[1];
*(buf++)=uVal[0];
}
break;
}
default: // unsupported
break;
}
}
}
String TAAudioASIO::getErrorStr(ASIOError which) {
switch (which) {
case ASE_OK:
return "OK";
break;
case ASE_SUCCESS:
return "Success";
break;
case ASE_NotPresent:
return "Not present";
break;
case ASE_HWMalfunction:
return "Hardware error";
break;
case ASE_InvalidParameter:
return "Invalid parameter";
break;
case ASE_InvalidMode:
return "Invalid mode";
break;
case ASE_SPNotAdvancing:
return "Sample position not advancing";
break;
case ASE_NoClock:
return "Clock not initialized";
break;
case ASE_NoMemory:
return "Out of memory";
break;
default:
break;
}
return "Unknown error";
}
String TAAudioASIO::getFormatName(ASIOSampleType which) {
switch (which) {
case ASIOSTInt16LSB:
return "16-bit LSB";
break;
case ASIOSTInt24LSB:
return "24-bit packed LSB";
break;
case ASIOSTInt32LSB:
return "32-bit LSB";
break;
case ASIOSTFloat32LSB:
return "32-bit float LSB";
break;
case ASIOSTFloat64LSB:
return "64-bit float LSB";
break;
case ASIOSTInt16MSB:
return "16-bit MSB";
break;
case ASIOSTInt24MSB:
return "24-bit packed MSB";
break;
case ASIOSTInt32MSB:
return "32-bit MSB";
break;
case ASIOSTFloat32MSB:
return "32-bit float MSB";
break;
case ASIOSTFloat64MSB:
return "64-bit float MSB";
break;
case ASIOSTInt32LSB16:
return "16-bit padded LSB";
break;
case ASIOSTInt32LSB18:
return "18-bit padded LSB";
break;
case ASIOSTInt32LSB20:
return "20-bit padded LSB";
break;
case ASIOSTInt32LSB24:
return "24-bit padded LSB";
break;
case ASIOSTInt32MSB16:
return "16-bit padded MSB";
break;
case ASIOSTInt32MSB18:
return "18-bit padded MSB";
break;
case ASIOSTInt32MSB20:
return "20-bit padded MSB";
break;
case ASIOSTInt32MSB24:
return "24-bit padded MSB";
break;
case ASIOSTDSDInt8LSB1:
return "1-bit LSB";
break;
case ASIOSTDSDInt8MSB1:
return "1-bit MSB";
break;
case ASIOSTDSDInt8NER8:
return "1-bit padded";
break;
}
return "Unknown";
}
void* TAAudioASIO::getContext() {
return (void*)&driverInfo;
}
int TAAudioASIO::specialCommand(TAAudioCommand which) {
switch (which) {
case TA_AUDIO_CMD_SETUP:
if (ASIOControlPanel()==ASE_NotPresent) return 0;
return 1;
break;
}
return -1;
}
bool TAAudioASIO::quit() {
if (!initialized) return false;
ASIOError result;
if (running) {
logV("CRASH: STOPPING NOW (QUIT)......");
result=ASIOStop();
if (result!=ASE_OK) {
logE("could not stop on quit! (%s)",getErrorStr(result));
}
running=false;
}
logV("CRASH: ASIODisposeBuffers()");
result=ASIODisposeBuffers();
if (result!=ASE_OK) {
logE("could not destroy buffers! (%s)",getErrorStr(result));
}
logV("CRASH: erase inBufs");
for (int i=0; i<desc.inChans; i++) {
delete[] inBufs[i];
inBufs[i]=NULL;
}
logV("CRASH: erase outBufs");
for (int i=0; i<desc.outChans; i++) {
delete[] outBufs[i];
outBufs[i]=NULL;
}
logV("CRASH: erase arrays");
if (inBufs!=NULL) {
delete[] inBufs;
inBufs=NULL;
}
if (outBufs!=NULL) {
delete[] outBufs;
outBufs=NULL;
}
logV("CRASH: ASIOExit()");
result=ASIOExit();
if (result!=ASE_OK) {
logE("could not exit!",getErrorStr(result));
}
logV("CRASH: removeCurrentDriver()");
asioDrivers->removeCurrentDriver();
logV("CRASH: reset callback instance");
callbackInstance=NULL;
initialized=false;
return true;
}
bool TAAudioASIO::setRun(bool run) {
if (!initialized) return false;
if (running==run) {
return running;
}
ASIOError result;
if (run) {
result=ASIOStart();
if (result!=ASE_OK) {
logE("could not start running! (%s)",getErrorStr(result));
return false;
}
running=true;
} else {
// does it matter whether stop was successful?
logV("CRASH: STOPPING NOW......");
result=ASIOStop();
if (result!=ASE_OK) {
logE("could not stop running! (%s)",getErrorStr(result));
}
running=false;
}
return running;
}
void TAAudioASIO::requestDeviceChange() {
deviceStatus=TA_AUDIO_DEVICE_RESET;
}
bool TAAudioASIO::init(TAAudioDesc& request, TAAudioDesc& response) {
if (initialized) return false;
if (callbackInstance) {
logE("can't initialize more than one output!");
return false;
}
desc=request;
desc.outFormat=TA_AUDIO_FORMAT_F32;
if (desc.deviceName.empty()) {
// load first driver if not specified
logV("getting driver names...");
if (!driverNamesInit) {
for (int i=0; i<ASIO_DRIVER_MAX; i++) {
// 64 just in case
driverNames[i]=new char[64];
}
driverNamesInit=true;
}
driverCount=asioDrivers->getDriverNames(driverNames,ASIO_DRIVER_MAX);
// quit if we couldn't find any drivers
if (driverCount<1) {
logE("no ASIO drivers available");
return false;
}
desc.deviceName=driverNames[0];
}
// load driver
logV("loading ASIO driver... (%s)",desc.deviceName);
strncpy(deviceNameCopy,desc.deviceName.c_str(),63);
if (!loadAsioDriver(deviceNameCopy)) {
logE("failed to load ASIO driver!");
return false;
}
// init
memset(&driverInfo,0,sizeof(driverInfo));
memset(bufInfo,0,sizeof(ASIOBufferInfo)*ASIO_CHANNEL_MAX*2);
memset(chanInfo,0,sizeof(ASIOChannelInfo)*ASIO_CHANNEL_MAX*2);
driverInfo.asioVersion=2;
driverInfo.sysRef=NULL;
ASIOError result=ASIOInit(&driverInfo);
if (result!=ASE_OK) {
logE("could not init device! (%s)",getErrorStr(result));
asioDrivers->removeCurrentDriver();
return false;
}
// setup callbacks
// unfortunately, only one TAAudio instance may exist at a time when using ASIO...
callbackInstance=this;
memset(&callbacks,0,sizeof(ASIOCallbacks));
callbacks.bufferSwitch=_onBufferSwitch;
callbacks.sampleRateDidChange=_onSampleRate;
callbacks.asioMessage=_onMessage;
// get driver information
long maxInChans=0;
long maxOutChans=0;
result=ASIOGetChannels(&maxInChans,&maxOutChans);
if (result!=ASE_OK) {
logE("could not get channel count! (%s)",getErrorStr(result));
ASIOExit();
asioDrivers->removeCurrentDriver();
return false;
}
if (maxInChans>ASIO_CHANNEL_MAX) maxInChans=ASIO_CHANNEL_MAX;
if (maxOutChans>ASIO_CHANNEL_MAX) maxOutChans=ASIO_CHANNEL_MAX;
if (desc.inChans>maxInChans) desc.inChans=maxInChans;
if (desc.outChans>maxOutChans) desc.outChans=maxOutChans;
long minBufSize=0;
long maxBufSize=0;
long actualBufSize=0;
long bufSizeGranularity=0;
result=ASIOGetBufferSize(&minBufSize,&maxBufSize,&actualBufSize,&bufSizeGranularity);
if (result!=ASE_OK) {
logE("could not get buffer size! (%s)",getErrorStr(result));
ASIOExit();
asioDrivers->removeCurrentDriver();
return false;
}
ASIOSampleRate outRate;
result=ASIOGetSampleRate(&outRate);
if (result!=ASE_OK) {
logE("could not get sample rate! (%s)",getErrorStr(result));
ASIOExit();
asioDrivers->removeCurrentDriver();
return false;
}
desc.rate=*(double*)(&outRate);
totalChans=0;
if (desc.inChans>0) {
inBufs=new float*[desc.inChans];
for (int i=0; i<desc.inChans; i++) {
chanInfo[totalChans].channel=i;
chanInfo[totalChans].isInput=ASIOTrue;
result=ASIOGetChannelInfo(&chanInfo[totalChans]);
if (result!=ASE_OK) {
logW("failed to get channel info for input channel %d! (%s)",i,getErrorStr(result));
}
bufInfo[totalChans].channelNum=i;
bufInfo[totalChans++].isInput=ASIOTrue;
inBufs[i]=new float[actualBufSize];
}
}
if (desc.outChans>0) {
outBufs=new float*[desc.outChans];
for (int i=0; i<desc.outChans; i++) {
chanInfo[totalChans].channel=i;
chanInfo[totalChans].isInput=ASIOFalse;
result=ASIOGetChannelInfo(&chanInfo[totalChans]);
if (result!=ASE_OK) {
logW("failed to get channel info for output channel %d! (%s)",i,getErrorStr(result));
}
bufInfo[totalChans].channelNum=i;
bufInfo[totalChans++].isInput=ASIOFalse;
outBufs[i]=new float[actualBufSize];
}
}
for (int i=0; i<totalChans; i++) {
logV("channel %d info: (index %d)",chanInfo[i].channel,i);
logV("- name: %s",chanInfo[i].name);
logV("- sample type: %s",getFormatName(chanInfo[i].type));
logV("- group: %d",chanInfo[i].channelGroup);
logV("- is input: %s",(chanInfo[i].isInput==ASIOTrue)?"yes":"no");
logV("- is active: %s",(chanInfo[i].isActive==ASIOTrue)?"yes":"no");
}
result=ASIOCreateBuffers(bufInfo,totalChans,actualBufSize,&callbacks);
if (result!=ASE_OK) {
logE("could not create buffers! (%s)",getErrorStr(result));
if (inBufs!=NULL) {
for (int i=0; i<desc.inChans; i++) {
if (inBufs[i]!=NULL) {
delete[] inBufs[i];
inBufs[i]=NULL;
}
}
delete[] inBufs;
inBufs=NULL;
}
if (outBufs!=NULL) {
for (int i=0; i<desc.outChans; i++) {
if (outBufs[i]!=NULL) {
delete[] outBufs[i];
outBufs[i]=NULL;
}
}
delete[] outBufs;
outBufs=NULL;
}
result=ASIOExit();
if (result!=ASE_OK) {
logE("could not exit either! (%s)",getErrorStr(result));
}
asioDrivers->removeCurrentDriver();
return false;
}
desc.bufsize=actualBufSize;
desc.fragments=2;
response=desc;
initialized=true;
return true;
}
std::vector<String> TAAudioASIO::listAudioDevices() {
std::vector<String> ret;
if (!asioDrivers) asioDrivers=new AsioDrivers;
if (!driverNamesInit) {
for (int i=0; i<ASIO_DRIVER_MAX; i++) {
// 64 just in case
driverNames[i]=new char[64];
}
driverNamesInit=true;
}
driverCount=asioDrivers->getDriverNames(driverNames,ASIO_DRIVER_MAX);
for (int i=0; i<driverCount; i++) {
ret.push_back(driverNames[i]);
}
return ret;
}

61
src/audio/asio.h Normal file
View file

@ -0,0 +1,61 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "taAudio.h"
#include <asiodrivers.h>
#include <asio.h>
#define ASIO_DRIVER_MAX 64
#define ASIO_CHANNEL_MAX 16
class TAAudioASIO: public TAAudio {
ASIODriverInfo driverInfo;
ASIOBufferInfo bufInfo[ASIO_CHANNEL_MAX*2];
ASIOChannelInfo chanInfo[ASIO_CHANNEL_MAX*2];
ASIOCallbacks callbacks;
int totalChans;
char* driverNames[ASIO_DRIVER_MAX];
int driverCount;
bool driverNamesInit;
char deviceNameCopy[64];
public:
void onSampleRate(double rate);
void onBufferSize(int bufsize);
void onProcess(int nframes);
void requestDeviceChange();
String getErrorStr(ASIOError which);
String getFormatName(ASIOSampleType which);
void* getContext();
int specialCommand(TAAudioCommand which);
bool quit();
bool setRun(bool run);
bool init(TAAudioDesc& request, TAAudioDesc& response);
std::vector<String> listAudioDevices();
TAAudioASIO():
totalChans(0),
driverCount(0),
driverNamesInit(false) {}
};

View file

@ -36,6 +36,20 @@ struct BufferSizeChangeEvent {
bufsize(bs) {}
};
enum TAAudioDeviceStatus {
// device is working
TA_AUDIO_DEVICE_OK=0,
// device has reset - reload audio engine
TA_AUDIO_DEVICE_RESET,
// device has been removed - reload audio engine with another device
TA_AUDIO_DEVICE_REMOVED
};
enum TAAudioCommand {
// open control panel for audio device
TA_AUDIO_CMD_SETUP=0
};
enum TAAudioFormat {
TA_AUDIO_FORMAT_F32=0,
TA_AUDIO_FORMAT_F64,
@ -157,6 +171,8 @@ class TAAudio {
protected:
TAAudioDesc desc;
TAAudioFormat outFormat;
TAAudioDeviceStatus deviceStatus;
bool running, initialized;
float** inBufs;
float** outBufs;
@ -173,15 +189,19 @@ class TAAudio {
void setCallback(void (*callback)(void*,float**,float**,int,int,unsigned int), void* user);
virtual void* getContext();
virtual int specialCommand(TAAudioCommand which);
virtual bool quit();
virtual bool setRun(bool run);
virtual std::vector<String> listAudioDevices();
TAAudioDeviceStatus getDeviceStatus();
void acceptDeviceStatus();
bool initMidi(bool jack);
void quitMidi();
virtual bool init(TAAudioDesc& request, TAAudioDesc& response);
TAAudio():
outFormat(TA_AUDIO_FORMAT_F32),
deviceStatus(TA_AUDIO_DEVICE_OK),
running(false),
initialized(false),
inBufs(NULL),

View file

@ -87,3 +87,7 @@ std::string taDecodeBase64(const char* buf) {
return data;
}
std::string taDecodeBase64(const std::string& str) {
return taDecodeBase64(str.c_str());
}

View file

@ -24,5 +24,6 @@
std::string taEncodeBase64(const std::string& data);
std::string taDecodeBase64(const char* str);
std::string taDecodeBase64(const std::string& str);
#endif

3
src/dummy.c Normal file
View file

@ -0,0 +1,3 @@
int main(int argc, char** argv) {
}

View file

@ -586,7 +586,7 @@ bool DivCSPlayer::tick() {
}
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));
e->dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(e->song.linearPitch?e->song.pitchSlideSpeed:1),chan[i].portaTarget));
}
if (chan[i].arp && !chan[i].portaSpeed) {
if (chan[i].arpTicks==0) {

View file

@ -58,6 +58,9 @@ bool DivConfig::save(const char* path, bool redundancy) {
reportError(fmt::sprintf("could not write config file! %s",strerror(errno)));
return false;
}
if (redundancy) {
fputs("!DIV_CONFIG_START!\n",f);
}
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()) {
@ -69,6 +72,9 @@ bool DivConfig::save(const char* path, bool redundancy) {
return false;
}
}
if (redundancy) {
fputs("~DIV_CONFIG_END~\n",f);
}
fclose(f);
logD("config file written successfully.");
return true;
@ -124,8 +130,12 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc
if (redundancy) {
unsigned char* readBuf=new unsigned char[CHECK_BUF_SIZE];
size_t readBufLen=0;
bool weRescued=false;
for (int i=0; i<REDUNDANCY_NUM_ATTEMPTS; i++) {
bool viable=false;
bool startCheck=true;
bool hasStartMarker=false;
unsigned char endMarker[18];
if (i>0) {
snprintf(line,4095,"%s.%d",path,i);
} else {
@ -143,15 +153,27 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc
// check whether there's something
while (!feof(f)) {
bool willBreak=false;
readBufLen=fread(readBuf,1,CHECK_BUF_SIZE,f);
if (ferror(f)) {
logV("fread(): %s",strerror(errno));
break;
}
if (startCheck) {
if (readBufLen>=19) {
if (memcmp(readBuf,"!DIV_CONFIG_START!\n",19)==0) {
hasStartMarker=true;
logV("start marker found");
}
}
startCheck=false;
}
for (size_t j=0; j<readBufLen; j++) {
if (readBuf[j]==0) {
viable=false;
willBreak=true;
logW("a zero?");
break;
}
@ -160,7 +182,30 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc
}
}
if (viable) break;
if (readBufLen>=18) {
memcpy(endMarker,&readBuf[readBufLen-18],18);
} else if (readBufLen>0) {
// shift buffer left
for (size_t j=0, k=readBufLen; j<readBufLen && k<18; j++, k++) {
endMarker[j]=endMarker[k];
}
// copy to end
memcpy(&endMarker[18-readBufLen],readBuf,readBufLen);
}
if (willBreak) break;
}
// check for end marker if start marker is present
if (hasStartMarker) {
if (memcmp(endMarker,"\n~DIV_CONFIG_END~\n",18)!=0) {
// file is incomplete
viable=false;
logV("end marker NOT found!");
reportError("saved from an incomplete config.\nyeah! for a second I thought you were going to lose it.");
weRescued=true;
}
}
// there's something
@ -184,6 +229,9 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc
logD("config does not exist");
if (createOnFail) {
logI("creating default config.");
if (weRescued) {
reportError("what the FUCK is that supposed to mean?!");
}
//reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
return save(path,redundancy);
} else {
@ -340,6 +388,34 @@ std::vector<int> DivConfig::getIntList(String key, std::initializer_list<int> fa
return fallback;
}
std::vector<String> DivConfig::getStringList(String key, std::initializer_list<String> fallback) const {
String next;
std::vector<String> ret;
auto val=conf.find(key);
if (val!=conf.cend()) {
try {
for (char i: val->second) {
if (i==',') {
String result=taDecodeBase64(next);
ret.push_back(result);
next="";
} else {
next+=i;
}
}
if (!next.empty()) {
String result=taDecodeBase64(next);
ret.push_back(result);
}
return ret;
} catch (std::out_of_range& e) {
} catch (std::invalid_argument& e) {
}
}
return fallback;
}
bool DivConfig::has(String key) const {
auto val=conf.find(key);
return (val!=conf.cend());
@ -384,6 +460,17 @@ void DivConfig::set(String key, const std::vector<int>& value) {
conf[key]=val;
}
void DivConfig::set(String key, const std::vector<String>& value) {
String val;
bool comma=false;
for (const String& i: value) {
if (comma) val+=',';
val+=taEncodeBase64(i);
comma=true;
}
conf[key]=val;
}
bool DivConfig::remove(String key) {
return conf.erase(key);
}

View file

@ -45,6 +45,7 @@ class DivConfig {
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;
std::vector<String> getStringList(String key, std::initializer_list<String> fallback) const;
// check for existence
bool has(String key) const;
@ -57,6 +58,7 @@ class DivConfig {
void set(String key, const char* value);
void set(String key, String value);
void set(String key, const std::vector<int>& value);
void set(String key, const std::vector<String>& value);
// remove a config value
bool remove(String key);

View file

@ -31,6 +31,22 @@
#define DIV_MAX_COLS 32
#define DIV_MAX_EFFECTS 8
// pattern fields
#define DIV_PAT_NOTE 0
#define DIV_PAT_INS 1
#define DIV_PAT_VOL 2
#define DIV_PAT_FX(_x) (3+((_x)<<1))
#define DIV_PAT_FXVAL(_x) (4+((_x)<<1))
// column type checks
#define DIV_PAT_IS_EFFECT(_x) ((_x)>DIV_PAT_VOL && (!((_x)&1)))
#define DIV_PAT_IS_EFFECT_VAL(_x) ((_x)>DIV_PAT_VOL && ((_x)&1))
#define DIV_NOTE_NULL_PAT 252
#define DIV_NOTE_OFF 253
#define DIV_NOTE_REL 254
#define DIV_MACRO_REL 255
// sample related
#define DIV_MAX_SAMPLE_TYPE 4

View file

@ -917,6 +917,16 @@ class DivDispatch {
*/
virtual void notifyWaveChange(int wave);
/**
* notify sample change.
*/
virtual void notifySampleChange(int sample);
/**
* notify addition of an instrument.
*/
virtual void notifyInsAddition(int sysID);
/**
* notify deletion of an instrument.
*/
@ -966,14 +976,14 @@ class DivDispatch {
* @param index the memory index.
* @return a pointer to sample memory, or NULL.
*/
virtual const void* getSampleMem(int index = 0);
virtual const void* getSampleMem(int index=0);
/**
* Get sample memory capacity.
* @param index the memory index.
* @return memory capacity in bytes, or 0 if memory doesn't exist.
*/
virtual size_t getSampleMemCapacity(int index = 0);
virtual size_t getSampleMemCapacity(int index=0);
/**
* get sample memory name.
@ -982,12 +992,26 @@ class DivDispatch {
*/
virtual const char* getSampleMemName(int index=0);
/**
* Get sample memory start offset.
* @param index the memory index.
* @return memory start offset in bytes.
*/
virtual size_t getSampleMemOffset(int index = 0);
/**
* Get sample memory usage.
* @param index the memory index.
* @return memory usage in bytes.
*/
virtual size_t getSampleMemUsage(int index = 0);
virtual size_t getSampleMemUsage(int index=0);
/**
* check whether chip has sample pointer header in sample memory.
* @param index the memory index.
* @return whether it did.
*/
virtual bool hasSamplePtrHeader(int index=0);
/**
* check whether sample has been loaded in memory.
@ -1074,7 +1098,7 @@ class DivDispatch {
if ((x)<(xMin)) (x)=(xMin); \
if ((x)>(xMax)) (x)=(xMax);
#define NEW_ARP_STRAT (parent->song.linearPitch==2 && !parent->song.oldArpStrategy)
#define NEW_ARP_STRAT (parent->song.linearPitch && !parent->song.oldArpStrategy)
#define HACKY_LEGATO_MESS chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode && !NEW_ARP_STRAT
#endif

View file

@ -93,6 +93,7 @@
#include "platform/bifurcator.h"
#include "platform/sid2.h"
#include "platform/sid3.h"
#include "platform/multipcm.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "song.h"
@ -787,6 +788,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl4Core",0));
}
break;
case DIV_SYSTEM_MULTIPCM:
dispatch=new DivPlatformMultiPCM;
break;
case DIV_SYSTEM_DUMMY:
dispatch=new DivPlatformDummy;
break;

View file

@ -36,6 +36,9 @@
#ifdef HAVE_PA
#include "../audio/pa.h"
#endif
#ifdef HAVE_ASIO
#include "../audio/asio.h"
#endif
#include "../audio/pipe.h"
#include <math.h>
#include <float.h>
@ -283,6 +286,14 @@ void DivEngine::notifyWaveChange(int wave) {
BUSY_END;
}
void DivEngine::notifySampleChange(int sample) {
BUSY_BEGIN;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifySampleChange(sample);
}
BUSY_END;
}
int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret) {
ret=NULL;
if (path.empty()) {
@ -595,10 +606,41 @@ void DivEngine::createNewFromDefaults() {
BUSY_END;
}
void DivEngine::copyChannel(int src, int dest) {
logV("copying channel %d to %d",src,dest);
if (src==dest) {
logV("not copying because it's the same channel!");
return;
}
for (int i=0; i<DIV_MAX_PATTERNS; i++) {
curOrders->ord[dest][i]=curOrders->ord[src][i];
DivPattern* srcPat=curPat[src].data[i];
DivPattern* destPat=curPat[dest].data[i];
if (srcPat==NULL) {
if (destPat!=NULL) {
delete destPat;
curPat[dest].data[i]=NULL;
}
} else {
curPat[src].data[i]->copyOn(curPat[dest].getPattern(i, true));
}
}
curPat[dest].effectCols=curPat[src].effectCols;
curSubSong->chanName[dest]=curSubSong->chanName[src];
curSubSong->chanShortName[dest]=curSubSong->chanShortName[src];
curSubSong->chanShow[dest]=curSubSong->chanShow[src];
curSubSong->chanShowChanOsc[dest]=curSubSong->chanShowChanOsc[src];
curSubSong->chanCollapse[dest]=curSubSong->chanCollapse[src];
}
void DivEngine::swapChannels(int src, int dest) {
logV("swapping channel %d with %d",src,dest);
if (src==dest) {
logV("not swapping channels because it's the same channel!",src,dest);
logV("not swapping channels because it's the same channel!");
return;
}
@ -738,6 +780,16 @@ void DivEngine::checkAssetDir(std::vector<DivAssetDir>& dir, size_t entries) {
delete[] inAssetDir;
}
void DivEngine::copyChannelP(int src, int dest) {
if (src<0 || src>=chans) return;
if (dest<0 || dest>=chans) return;
BUSY_BEGIN;
saveLock.lock();
copyChannel(src,dest);
saveLock.unlock();
BUSY_END;
}
void DivEngine::swapChannelsP(int src, int dest) {
if (src<0 || src>=chans) return;
if (dest<0 || dest>=chans) return;
@ -898,8 +950,8 @@ void DivEngine::delUnusedIns() {
for (int k=0; k<DIV_MAX_PATTERNS; k++) {
if (song.subsong[j]->pat[i].data[k]==NULL) continue;
for (int l=0; l<song.subsong[j]->patLen; l++) {
if (song.subsong[j]->pat[i].data[k]->data[l][2]>=0 && song.subsong[j]->pat[i].data[k]->data[l][2]<256) {
isUsed[song.subsong[j]->pat[i].data[k]->data[l][2]]=true;
if (song.subsong[j]->pat[i].data[k]->newData[l][DIV_PAT_INS]>=0 && song.subsong[j]->pat[i].data[k]->newData[l][DIV_PAT_INS]<256) {
isUsed[song.subsong[j]->pat[i].data[k]->newData[l][DIV_PAT_INS]]=true;
}
}
}
@ -984,38 +1036,6 @@ void DivEngine::delUnusedSamples() {
}
}
// scan in pattern (legacy sample mode)
// disabled because it is unreliable
/*
for (DivSubSong* i: song.subsong) {
for (int j=0; j<getTotalChannelCount(); j++) {
bool is17On=false;
int bank=0;
for (int k=0; k<i->ordersLen; k++) {
DivPattern* p=i->pat[j].getPattern(i->orders.ord[j][k],false);
for (int l=0; l<i->patLen; l++) {
for (int m=0; m<i->pat[j].effectCols; m++) {
if (p->data[l][4+(m<<1)]==0x17) {
is17On=(p->data[l][5+(m<<1)]>0);
}
if (p->data[l][4+(m<<1)]==0xeb) {
bank=p->data[l][5+(m<<1)];
if (bank==-1) bank=0;
}
}
if (is17On) {
if (p->data[l][1]!=0 || p->data[l][0]!=0) {
if (p->data[l][0]<=12) {
int note=(12*bank)+(p->data[l][0]%12);
if (note<256) isUsed[note]=true;
}
}
}
}
}
}
}*/
// delete
for (int i=0; i<song.sampleLen; i++) {
if (!isUsed[i]) {
@ -1503,14 +1523,13 @@ String DivEngine::getPlaybackDebugInfo() {
"totalCmds: %d\n"
"lastCmds: %d\n"
"cmdsPerSecond: %d\n"
"globalPitch: %d\n"
"extValue: %d\n"
"tempoAccum: %d\n"
"totalProcessed: %d\n"
"bufferPos: %d\n",
curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift,
midiClockCycles,midiClockDrift,midiTimeCycles,midiTimeDrift,changeOrd,changePos,totalSeconds,totalTicks,
totalTicksR,curMidiClock,curMidiTime,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
totalTicksR,curMidiClock,curMidiTime,totalCmds,lastCmds,cmdsPerSecond,
(int)extValue,(int)tempoAccum,(int)totalProcessed,(int)bufferPos
);
}
@ -1763,7 +1782,7 @@ int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period)
}*/
double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) {
if (song.linearPitch==2) { // full linear
if (song.linearPitch) { // linear
return (note<<7);
}
double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0);
@ -1813,7 +1832,7 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri
return bf|((block)<<(bits));
int DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits, int fixedBlock) {
if (song.linearPitch==2) { // full linear
if (song.linearPitch) { // linear
return (note<<7);
}
int bf=calcBaseFreq(clock,divider,note,false);
@ -1825,7 +1844,8 @@ int DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int
}
int DivEngine::calcFreq(int base, int pitch, int arp, bool arpFixed, bool period, int octave, int pitch2, double clock, double divider, int blockBits, int fixedBlock) {
if (song.linearPitch==2) {
// linear pitch
if (song.linearPitch) {
// do frequency calculation here
int nbase=base+pitch+pitch2;
if (!song.oldArpStrategy) {
@ -1849,24 +1869,7 @@ int DivEngine::calcFreq(int base, int pitch, int arp, bool arpFixed, bool period
return bf;
}
}
if (song.linearPitch==1) {
// global pitch multiplier
int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0));
if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me
if (song.pitchMacroIsLinear) {
pitch+=pitch2;
}
pitch+=2048;
if (pitch<0) pitch=0;
if (pitch>4095) pitch=4095;
int ret=period?
((base*(reversePitchTable[pitch]))/whatTheFuck):
(((base*(pitchTable[pitch]))>>10)*whatTheFuck)/1024;
if (!song.pitchMacroIsLinear) {
ret+=period?(-pitch2):pitch2;
}
return ret;
}
// non-linear pitch
return period?
base-pitch-pitch2:
base+((pitch*octave)>>1)+pitch2;
@ -2158,7 +2161,7 @@ void DivEngine::reset() {
chan[i]=DivChannelState();
if (i<chans) chan[i].volMax=(disCont[dispatchOfChan[i]].dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff;
chan[i].volume=chan[i].volMax;
if (song.linearPitch==0) chan[i].vibratoFine=4;
if (!song.linearPitch) chan[i].vibratoFine=4;
}
extValue=0;
extValuePresent=0;
@ -2173,7 +2176,6 @@ void DivEngine::reset() {
elapsedBeats=0;
nextSpeed=speeds.val[0];
divider=curSubSong->hz;
globalPitch=0;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->reset();
disCont[i].clear();
@ -2244,6 +2246,64 @@ int DivEngine::getEffectiveSampleRate(int rate) {
return rate;
}
short DivEngine::splitNoteToNote(short note, short octave) {
if (note==100) {
return DIV_NOTE_OFF;
} else if (note==101) {
return DIV_NOTE_REL;
} else if (note==102) {
return DIV_MACRO_REL;
} else if (note==0 && octave!=0) {
// "BUG" note!
return DIV_NOTE_NULL_PAT;
} else if (note==0 && octave==0) {
return -1;
} else {
int seek=(note+(signed char)octave*12)+60;
if (seek<0 || seek>=180) {
return DIV_NOTE_NULL_PAT;
} else {
return seek;
}
}
return -1;
}
void DivEngine::noteToSplitNote(short note, short& outNote, short& outOctave) {
switch (note) {
case DIV_NOTE_OFF:
outNote=100;
outOctave=0;
break;
case DIV_NOTE_REL:
outNote=101;
outOctave=0;
break;
case DIV_MACRO_REL:
outNote=102;
outOctave=0;
break;
case DIV_NOTE_NULL_PAT:
// "BUG" note!
outNote=0;
outOctave=1;
break;
case -1:
outNote=0;
outOctave=0;
break;
default:
outNote=note%12;
outOctave=(unsigned char)(note-60)/12;
if (outNote==0) {
outNote=12;
outOctave--;
}
break;
}
}
void DivEngine::previewSample(int sample, int note, int pStart, int pEnd) {
BUSY_BEGIN;
previewSampleNoLock(sample,note,pStart,pEnd);
@ -2647,6 +2707,9 @@ int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) {
song.ins.push_back(ins);
song.insLen=insCount+1;
checkAssetDir(song.insDir,song.ins.size());
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyInsAddition(i);
}
saveLock.unlock();
BUSY_END;
return insCount;
@ -2664,6 +2727,9 @@ int DivEngine::addInstrumentPtr(DivInstrument* which) {
checkAssetDir(song.insDir,song.ins.size());
checkAssetDir(song.waveDir,song.wave.size());
checkAssetDir(song.sampleDir,song.sample.size());
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyInsAddition(i);
}
saveLock.unlock();
BUSY_END;
return song.insLen;
@ -2675,6 +2741,9 @@ void DivEngine::loadTempIns(DivInstrument* which) {
tempIns=new DivInstrument;
}
*tempIns=*which;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyInsAddition(i);
}
BUSY_END;
}
@ -2691,8 +2760,8 @@ void DivEngine::delInstrumentUnsafe(int index) {
for (int k=0; k<DIV_MAX_PATTERNS; k++) {
if (song.subsong[j]->pat[i].data[k]==NULL) continue;
for (int l=0; l<song.subsong[j]->patLen; l++) {
if (song.subsong[j]->pat[i].data[k]->data[l][2]>index) {
song.subsong[j]->pat[i].data[k]->data[l][2]--;
if (song.subsong[j]->pat[i].data[k]->newData[l][DIV_PAT_INS]>index) {
song.subsong[j]->pat[i].data[k]->newData[l][DIV_PAT_INS]--;
}
}
}
@ -3053,7 +3122,7 @@ void DivEngine::deepCloneOrder(int pos, bool where) {
order[i]=j;
DivPattern* oldPat=curPat[i].getPattern(origOrd,false);
DivPattern* pat=curPat[i].getPattern(j,true);
memcpy(pat->data,oldPat->data,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short));
memcpy(pat->newData,oldPat->newData,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short));
logD("found at %d",j);
didNotFind=false;
break;
@ -3159,10 +3228,10 @@ void DivEngine::exchangeIns(int one, int two) {
for (int k=0; k<DIV_MAX_PATTERNS; k++) {
if (song.subsong[j]->pat[i].data[k]==NULL) continue;
for (int l=0; l<song.subsong[j]->patLen; l++) {
if (song.subsong[j]->pat[i].data[k]->data[l][2]==one) {
song.subsong[j]->pat[i].data[k]->data[l][2]=two;
} else if (song.subsong[j]->pat[i].data[k]->data[l][2]==two) {
song.subsong[j]->pat[i].data[k]->data[l][2]=one;
if (song.subsong[j]->pat[i].data[k]->newData[l][DIV_PAT_INS]==one) {
song.subsong[j]->pat[i].data[k]->newData[l][DIV_PAT_INS]=two;
} else if (song.subsong[j]->pat[i].data[k]->newData[l][DIV_PAT_INS]==two) {
song.subsong[j]->pat[i].data[k]->newData[l][DIV_PAT_INS]=one;
}
}
}
@ -3729,6 +3798,21 @@ TAAudioDesc& DivEngine::getAudioDescGot() {
return got;
}
TAAudioDeviceStatus DivEngine::getAudioDeviceStatus() {
if (output==NULL) return TA_AUDIO_DEVICE_OK;
return output->getDeviceStatus();
}
void DivEngine::acceptAudioDeviceStatus() {
if (output==NULL) return;
output->acceptDeviceStatus();
}
int DivEngine::audioBackendCommand(TAAudioCommand which) {
if (output==NULL) return -1;
return output->specialCommand(which);
}
std::vector<String>& DivEngine::getAudioDevices() {
return audioDevs;
}
@ -3843,6 +3927,8 @@ bool DivEngine::initAudioBackend() {
audioEngine=DIV_AUDIO_JACK;
} else if (getConfString("audioEngine","SDL")=="PortAudio") {
audioEngine=DIV_AUDIO_PORTAUDIO;
} else if (getConfString("audioEngine","SDL")=="ASIO") {
audioEngine=DIV_AUDIO_ASIO;
} else {
audioEngine=DIV_AUDIO_SDL;
}
@ -3904,6 +3990,21 @@ bool DivEngine::initAudioBackend() {
#endif
#else
output=new TAAudioPA;
#endif
break;
case DIV_AUDIO_ASIO:
#ifndef HAVE_ASIO
logE("Furnace was not compiled with ASIO support!");
setConf("audioEngine","SDL");
saveConf();
#ifdef HAVE_SDL2
output=new TAAudioSDL;
#else
logE("Furnace was not compiled with SDL support either!");
output=new TAAudio;
#endif
#else
output=new TAAudioASIO;
#endif
break;
case DIV_AUDIO_SDL:
@ -4159,10 +4260,6 @@ bool DivEngine::init() {
for (int i=0; i<128; i++) {
tremTable[i]=255*0.5*(1.0-cos(((double)i/128.0)*(2*M_PI)));
}
for (int i=0; i<4096; i++) {
reversePitchTable[i]=round(1024.0*pow(2.0,(2048.0-(double)i)/(12.0*128.0)));
pitchTable[i]=round(1024.0*pow(2.0,((double)i-2048.0)/(12.0*128.0)));
}
for (int i=0; i<DIV_MAX_CHANS; i++) {
isMuted[i]=0;

View file

@ -54,8 +54,8 @@ class DivWorkPool;
#define DIV_UNSTABLE
#define DIV_VERSION "dev233"
#define DIV_ENGINE_VERSION 233
#define DIV_VERSION "dev237"
#define DIV_ENGINE_VERSION 237
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -76,6 +76,7 @@ enum DivAudioEngines {
DIV_AUDIO_SDL=1,
DIV_AUDIO_PORTAUDIO=2,
DIV_AUDIO_PIPE=3,
DIV_AUDIO_ASIO=4,
DIV_AUDIO_NULL=126,
DIV_AUDIO_DUMMY=127
@ -102,28 +103,51 @@ enum DivMIDIModes {
};
enum DivAudioExportFormats {
DIV_EXPORT_FORMAT_S16=0,
DIV_EXPORT_FORMAT_F32
DIV_EXPORT_FORMAT_WAV=0,
DIV_EXPORT_FORMAT_OPUS,
DIV_EXPORT_FORMAT_FLAC,
DIV_EXPORT_FORMAT_VORBIS,
DIV_EXPORT_FORMAT_MPEG_L3
};
enum DivAudioExportBitrateModes {
DIV_EXPORT_BITRATE_CONSTANT=0,
DIV_EXPORT_BITRATE_VARIABLE,
DIV_EXPORT_BITRATE_AVERAGE,
};
enum DivAudioExportWavFormats {
DIV_EXPORT_WAV_U8=0,
DIV_EXPORT_WAV_S16,
DIV_EXPORT_WAV_F32
};
struct DivAudioExportOptions {
DivAudioExportModes mode;
DivAudioExportFormats format;
DivAudioExportBitrateModes bitRateMode;
DivAudioExportWavFormats wavFormat;
int sampleRate;
int chans;
int loops;
double fadeOut;
int orderBegin, orderEnd;
bool channelMask[DIV_MAX_CHANS];
int bitRate;
float vbrQuality;
DivAudioExportOptions():
mode(DIV_EXPORT_MODE_ONE),
format(DIV_EXPORT_FORMAT_S16),
format(DIV_EXPORT_FORMAT_WAV),
bitRateMode(DIV_EXPORT_BITRATE_CONSTANT),
wavFormat(DIV_EXPORT_WAV_S16),
sampleRate(44100),
chans(2),
loops(0),
fadeOut(0.0),
orderBegin(-1),
orderEnd(-1) {
orderEnd(-1),
bitRate(128000),
vbrQuality(6.0f) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
channelMask[i]=true;
}
@ -140,7 +164,7 @@ struct DivChannelState {
int panDepth, panRate, panPos, panSpeed;
int sampleOff;
unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta, cutType;
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff, releasing;
bool doNote, legato, portaStop, keyOn, keyOff, stopOnOff, releasing;
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp, sampleOffSet;
bool wentThroughNote, goneThroughNote;
@ -197,7 +221,6 @@ struct DivChannelState {
portaStop(false),
keyOn(false),
keyOff(false),
nowYouCanStop(true),
stopOnOff(false),
releasing(false),
arpYield(false),
@ -486,7 +509,7 @@ class DivEngine {
int midiTimeCycles;
double midiTimeDrift;
int stepPlay;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, curMidiClock, curMidiTime, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, curMidiClock, curMidiTime, totalCmds, lastCmds, cmdsPerSecond;
double totalTicksOff;
int curMidiTimePiece, curMidiTimeCode;
unsigned char extValue, pendingMetroTick;
@ -499,9 +522,13 @@ class DivEngine {
DivAudioEngines audioEngine;
DivAudioExportModes exportMode;
DivAudioExportFormats exportFormat;
DivAudioExportWavFormats wavFormat;
DivAudioExportBitrateModes exportBitRateMode;
double exportFadeOut;
bool isFadingOut;
int exportOutputs;
int exportBitRate;
float exportVBRQuality;
bool exportChannelMask[DIV_MAX_CHANS];
DivConfig conf;
FixedQueue<DivNoteEvent,8192> pendingNotes;
@ -549,8 +576,6 @@ class DivEngine {
short vibTable[64];
short tremTable[128];
int reversePitchTable[4096];
int pitchTable[4096];
short effectSlotMap[4096];
int midiBaseChan;
bool midiPoly;
@ -612,6 +637,7 @@ class DivEngine {
void loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadVGI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadEIF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadSBI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadOPLI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
@ -648,6 +674,7 @@ class DivEngine {
void exchangeWave(int one, int two);
void exchangeSample(int one, int two);
void copyChannel(int src, int dest);
void swapChannels(int src, int dest);
void stompChannel(int ch);
@ -696,6 +723,8 @@ class DivEngine {
int lastNBIns, lastNBOuts, lastNBSize;
std::atomic<size_t> processTime;
float chipPeak[DIV_MAX_CHIPS][DIV_MAX_OUTPUTS];
void runExportThread();
void nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size);
DivInstrument* getIns(int index, DivInstrumentType fallbackType=DIV_INS_FM);
@ -719,7 +748,7 @@ class DivEngine {
SafeWriter* saveDMF(unsigned char version);
// save as .fur.
// if notPrimary is true then the song will not be altered
SafeWriter* saveFur(bool notPrimary=false, bool newPatternFormat=true);
SafeWriter* saveFur(bool notPrimary=false);
// return a ROM exporter.
DivROMExport* buildROM(DivROMExportOptions sys);
// dump to VGM.
@ -747,6 +776,8 @@ class DivEngine {
void notifyInsChange(int ins);
// notify wavetable change
void notifyWaveChange(int wave);
// notify sample change
void notifySampleChange(int sample);
// dispatch a command
int dispatchCmd(DivCommand c);
@ -923,6 +954,10 @@ class DivEngine {
// get effective sample rate
int getEffectiveSampleRate(int rate);
// convert between old and new note/octave format
short splitNoteToNote(short note, short octave);
void noteToSplitNote(short note, short& outNote, short& outOctave);
// is FM system
bool isFMSystem(DivSystem sys);
@ -1259,6 +1294,9 @@ class DivEngine {
// >=0: render specific sample
void renderSamplesP(int whichSample=-1);
// public copy channel
void copyChannelP(int src, int dest);
// public swap channels
void swapChannelsP(int src, int dest);
@ -1365,6 +1403,15 @@ class DivEngine {
// get audio desc
TAAudioDesc& getAudioDescGot();
// get audio device status
TAAudioDeviceStatus getAudioDeviceStatus();
// acknowledge an audio device status change
void acceptAudioDeviceStatus();
// send command to audio backend
int audioBackendCommand(TAAudioCommand which);
// init dispatch
void initDispatch(bool isRender=false);
@ -1465,7 +1512,6 @@ class DivEngine {
totalCmds(0),
lastCmds(0),
cmdsPerSecond(0),
globalPitch(0),
totalTicksOff(0.0),
curMidiTimePiece(0),
curMidiTimeCode(0),
@ -1478,10 +1524,14 @@ class DivEngine {
haltOn(DIV_HALT_NONE),
audioEngine(DIV_AUDIO_NULL),
exportMode(DIV_EXPORT_MODE_ONE),
exportFormat(DIV_EXPORT_FORMAT_S16),
exportFormat(DIV_EXPORT_FORMAT_WAV),
wavFormat(DIV_EXPORT_WAV_S16),
exportBitRateMode(DIV_EXPORT_BITRATE_CONSTANT),
exportFadeOut(0.0),
isFadingOut(false),
exportOutputs(2),
exportBitRate(128000),
exportVBRQuality(6.0f),
cmdStreamInt(NULL),
midiBaseChan(0),
midiPoly(true),
@ -1527,14 +1577,13 @@ class DivEngine {
memset(sysOfChan,0,DIV_MAX_CHANS*sizeof(int));
memset(vibTable,0,64*sizeof(short));
memset(tremTable,0,128*sizeof(short));
memset(reversePitchTable,0,4096*sizeof(int));
memset(pitchTable,0,4096*sizeof(int));
memset(effectSlotMap,-1,4096*sizeof(short));
memset(sysDefs,0,DIV_MAX_CHIP_DEFS*sizeof(void*));
memset(romExportDefs,0,DIV_ROM_MAX*sizeof(void*));
memset(walked,0,8192);
memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*)));
memset(exportChannelMask,1,DIV_MAX_CHANS*sizeof(bool));
memset(chipPeak,0,DIV_MAX_CHIPS*DIV_MAX_OUTPUTS*sizeof(float));
for (int i=0; i<DIV_MAX_CHIP_DEFS; i++) {
sysFileMapFur[i]=DIV_SYSTEM_NULL;

View file

@ -783,109 +783,120 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true);
if (ds.version>0x08) { // current pattern format
for (int k=0; k<ds.subsong[0]->patLen; k++) {
// note
pat->data[k][0]=reader.readS();
// octave
pat->data[k][1]=reader.readS();
if (ds.system[0]==DIV_SYSTEM_SMS && ds.version<0x0e && pat->data[k][1]>0) {
short note=reader.readS();
short octave=reader.readS();
if (ds.system[0]==DIV_SYSTEM_SMS && ds.version<0x0e && octave>0) {
// apparently it was up one octave before
pat->data[k][1]--;
} else if (ds.system[0]==DIV_SYSTEM_GENESIS && ds.version<0x0e && pat->data[k][1]>0 && i>5) {
octave--;
} else if (ds.system[0]==DIV_SYSTEM_GENESIS && ds.version<0x0e && octave>0 && i>5) {
// ditto
pat->data[k][1]--;
} else if (ds.system[0]==DIV_SYSTEM_MSX2 && pat->data[k][1]>0 && i<3) {
octave--;
} else if (ds.system[0]==DIV_SYSTEM_MSX2 && octave>0 && i<3) {
// why the hell?
pat->data[k][1]++;
octave++;
}
if (ds.version<0x12) {
if (ds.system[0]==DIV_SYSTEM_GB && i==3 && pat->data[k][1]>0) {
if (ds.system[0]==DIV_SYSTEM_GB && i==3 && octave>0) {
// back then noise was 2 octaves lower
pat->data[k][1]-=2;
octave-=2;
}
}
if (ds.system[0]==DIV_SYSTEM_YMU759 && pat->data[k][0]!=0) {
if (ds.system[0]==DIV_SYSTEM_YMU759 && note!=0) {
// apparently YMU759 is stored 2 octaves lower
pat->data[k][1]+=2;
octave+=2;
}
if (pat->data[k][0]==0 && pat->data[k][1]!=0) {
logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]);
pat->data[k][0]=12;
pat->data[k][1]--;
if (note==0 && octave!=0) {
logD("what? %d:%d:%d note %d octave %d",i,j,k,note,octave);
note=12;
octave--;
}
pat->newData[k][DIV_PAT_NOTE]=splitNoteToNote(note,octave);
// volume
pat->data[k][3]=reader.readS();
pat->newData[k][DIV_PAT_VOL]=reader.readS();
if (ds.version<0x0a) {
// back then volume was stored as 00-ff instead of 00-7f/0-f
if (i>5) {
pat->data[k][3]>>=4;
pat->newData[k][DIV_PAT_VOL]>>=4;
} else {
pat->data[k][3]>>=1;
pat->newData[k][DIV_PAT_VOL]>>=1;
}
}
if (ds.version<0x12) {
if (ds.system[0]==DIV_SYSTEM_GB && i==2 && pat->data[k][3]>0) {
if (ds.system[0]==DIV_SYSTEM_GB && i==2 && pat->newData[k][DIV_PAT_VOL]>0) {
// volume range of GB wave channel was 0-3 rather than 0-F
pat->data[k][3]=(pat->data[k][3]&3)*5;
pat->newData[k][DIV_PAT_VOL]=(pat->newData[k][DIV_PAT_VOL]&3)*5;
}
}
for (int l=0; l<chan.effectCols; l++) {
// effect
pat->data[k][4+(l<<1)]=reader.readS();
pat->data[k][5+(l<<1)]=reader.readS();
pat->newData[k][DIV_PAT_FX(l)]=reader.readS();
pat->newData[k][DIV_PAT_FXVAL(l)]=reader.readS();
if (ds.version<0x14) {
if (pat->data[k][4+(l<<1)]==0xe5 && pat->data[k][5+(l<<1)]!=-1) {
pat->data[k][5+(l<<1)]=128+((pat->data[k][5+(l<<1)]-128)/4);
// the range of E5xx was different back then
if (pat->newData[k][DIV_PAT_FX(l)]==0xe5 && pat->newData[k][DIV_PAT_FXVAL(l)]!=-1) {
pat->newData[k][DIV_PAT_FXVAL(l)]=128+((pat->newData[k][DIV_PAT_FXVAL(l)]-128)/4);
}
}
// YM2151: pitch effect range is different
if (ds.system[0]==DIV_SYSTEM_ARCADE && pat->newData[k][DIV_PAT_FX(l)]==0xe5 && pat->newData[k][DIV_PAT_FXVAL(l)]!=-1) {
int newVal=(2*((pat->newData[k][DIV_PAT_FXVAL(l)]&0xff)-0x80))+0x80;
if (newVal<0) newVal=0;
if (newVal>0xff) newVal=0xff;
pat->newData[k][DIV_PAT_FXVAL(l)]=newVal;
}
}
// instrument
pat->data[k][2]=reader.readS();
pat->newData[k][DIV_PAT_INS]=reader.readS();
// this is sad
if (ds.system[0]==DIV_SYSTEM_NES_FDS) {
if (i==5 && pat->data[k][2]!=-1) {
if (pat->data[k][2]>=0 && pat->data[k][2]<ds.insLen) {
ds.ins[pat->data[k][2]]->type=DIV_INS_FDS;
if (i==5 && pat->newData[k][DIV_PAT_INS]!=-1) {
if (pat->newData[k][DIV_PAT_INS]>=0 && pat->newData[k][DIV_PAT_INS]<ds.insLen) {
ds.ins[pat->newData[k][DIV_PAT_INS]]->type=DIV_INS_FDS;
}
}
}
if (ds.system[0]==DIV_SYSTEM_MSX2) {
if (i>=3 && pat->data[k][2]!=-1) {
if (pat->data[k][2]>=0 && pat->data[k][2]<ds.insLen) {
ds.ins[pat->data[k][2]]->type=DIV_INS_SCC;
if (i>=3 && pat->newData[k][DIV_PAT_INS]!=-1) {
if (pat->newData[k][DIV_PAT_INS]>=0 && pat->newData[k][DIV_PAT_INS]<ds.insLen) {
ds.ins[pat->newData[k][DIV_PAT_INS]]->type=DIV_INS_SCC;
}
}
}
}
} else { // historic pattern format
if (i<16) pat->data[0][2]=historicColIns[i];
if (i<16) pat->newData[0][DIV_PAT_INS]=historicColIns[i];
for (int k=0; k<ds.subsong[0]->patLen; k++) {
// note
pat->data[k][0]=reader.readC();
// octave
pat->data[k][1]=reader.readC();
if (pat->data[k][0]!=0) {
short note=reader.readC();
short octave=reader.readC();
if (note!=0) {
// YMU759 is stored 2 octaves lower
pat->data[k][1]+=2;
octave+=2;
}
if (pat->data[k][0]==0 && pat->data[k][1]!=0) {
logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]);
pat->data[k][0]=12;
pat->data[k][1]--;
if (note==0 && octave!=0) {
logD("what? %d:%d:%d note %d octave %d",i,j,k,note,octave);
note=12;
octave--;
}
pat->newData[k][DIV_PAT_NOTE]=splitNoteToNote(note,octave);
// volume and effect
unsigned char vol=reader.readC();
unsigned char fx=reader.readC();
unsigned char fxVal=reader.readC();
pat->data[k][3]=(vol==0x80 || vol==0xff)?-1:vol;
pat->newData[k][DIV_PAT_VOL]=(vol==0x80 || vol==0xff)?-1:vol;
// effect
pat->data[k][4]=(fx==0x80 || fx==0xff)?-1:fx;
pat->data[k][5]=(fxVal==0x80 || fx==0xff)?-1:fxVal;
pat->newData[k][DIV_PAT_FX(0)]=(fx==0x80 || fx==0xff)?-1:fx;
pat->newData[k][DIV_PAT_FXVAL(0)]=(fxVal==0x80 || fx==0xff)?-1:fxVal;
// instrument
if (ds.version>0x05) {
pat->data[k][2]=reader.readC();
if (pat->data[k][2]==0x80 || pat->data[k][2]==0xff) pat->data[k][2]=-1;
pat->newData[k][DIV_PAT_INS]=reader.readC();
if (pat->newData[k][DIV_PAT_INS]==0x80 || pat->newData[k][DIV_PAT_INS]==0xff) pat->newData[k][DIV_PAT_INS]=-1;
}
}
}
@ -1279,8 +1290,8 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
for (int i=0; i<chans; i++) {
for (int j=0; j<curSubSong->ordersLen; j++) {
if (curOrders->ord[i][j]>0x7f) {
logE("order %d, %d is out of range (0-127)!",curOrders->ord[i][j]);
lastError=fmt::sprintf("order %d, %d is out of range (0-127)",curOrders->ord[i][j]);
logE("order %d, %d is out of range (0-127)!",i,j);
lastError=fmt::sprintf("order %d, %d is out of range (0-127)",i,j);
return NULL;
}
}
@ -1630,12 +1641,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
bool relWarning=false;
for (int i=0; i<getChannelCount(sys); i++) {
short note, octave;
w->writeC(curPat[i].effectCols);
for (int j=0; j<curSubSong->ordersLen; j++) {
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false);
for (int k=0; k<curSubSong->patLen; k++) {
if ((pat->data[k][0]==101 || pat->data[k][0]==102) && pat->data[k][1]==0) {
if (pat->newData[k][DIV_PAT_NOTE]==DIV_NOTE_REL || pat->newData[k][DIV_PAT_NOTE]==DIV_MACRO_REL) {
w->writeS(100);
w->writeS(0);
if (!relWarning) {
@ -1643,18 +1655,19 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
addWarning("note/macro release will be converted to note off!");
}
} else {
w->writeS(pat->data[k][0]); // note
w->writeS(pat->data[k][1]); // octave
noteToSplitNote(pat->newData[k][DIV_PAT_NOTE],note,octave);
w->writeS(note); // note
w->writeS(octave); // octave
}
w->writeS(pat->data[k][3]); // volume
w->writeS(pat->newData[k][DIV_PAT_VOL]); // volume
#ifdef TA_BIG_ENDIAN
for (int l=0; l<curPat[i].effectCols*2; l++) {
w->writeS(pat->data[k][4+l]);
w->writeS(pat->newData[k][DIV_PAT_FX(0)+l]);
}
#else
w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects
w->write(&pat->newData[k][DIV_PAT_FX(0)],2*curPat[i].effectCols*2); // effects
#endif
w->writeS(pat->data[k][2]); // instrument
w->writeS(pat->newData[k][DIV_PAT_INS]); // instrument
}
}
}

View file

@ -418,8 +418,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
ds.subsong[0]->orders.ord[j][i]=i;
DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true);
if (j==3 && seq[i].speed) {
p->data[0][6]=0x0f;
p->data[0][7]=seq[i].speed;
p->newData[0][DIV_PAT_FX(1)]=0x0f;
p->newData[0][DIV_PAT_FXVAL(1)]=seq[i].speed;
}
bool ignoreNext=false;
@ -428,80 +428,65 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
FCPattern& fp=pat[seq[i].pat[j]];
if (fp.note[k]>0 && fp.note[k]<0x49) {
lastNote[j]=fp.note[k];
short note=(fp.note[k]+seq[i].transpose[j])%12;
short octave=2+((fp.note[k]+seq[i].transpose[j])/12);
if (fp.note[k]>=0x3d) octave-=6;
if (note==0) {
note=12;
octave--;
}
octave&=0xff;
p->data[k][0]=note;
p->data[k][1]=octave;
p->newData[k][DIV_PAT_NOTE]=fp.note[k]+seq[i].transpose[j]+84;
// wrap-around if the note is too high
if (fp.note[k]>=0x3d) p->newData[k][DIV_PAT_NOTE]-=6*12;
if (isSliding[j]) {
isSliding[j]=false;
p->data[k][4]=2;
p->data[k][5]=0;
p->newData[k][DIV_PAT_FX(0)]=2;
p->newData[k][DIV_PAT_FXVAL(0)]=0;
}
} else if (fp.note[k]==0x49) {
if (k>0) {
p->data[k-1][4]=0x0d;
p->data[k-1][5]=0;
p->newData[k-1][DIV_PAT_FX(0)]=0x0d;
p->newData[k-1][DIV_PAT_FXVAL(0)]=0;
}
} else if (k==0 && lastTranspose[j]!=seq[i].transpose[j]) {
p->data[0][2]=lastIns[j];
p->data[0][4]=0x03;
p->data[0][5]=0xff;
p->newData[0][DIV_PAT_INS]=lastIns[j];
p->newData[0][DIV_PAT_FX(0)]=0x03;
p->newData[0][DIV_PAT_FXVAL(0)]=0xff;
lastTranspose[j]=seq[i].transpose[j];
short note=(lastNote[j]+seq[i].transpose[j])%12;
short octave=2+((lastNote[j]+seq[i].transpose[j])/12);
if (lastNote[j]>=0x3d) octave-=6;
if (note==0) {
note=12;
octave--;
}
octave&=0xff;
p->data[k][0]=note;
p->data[k][1]=octave;
p->newData[k][DIV_PAT_NOTE]=lastNote[j]+seq[i].transpose[j]+84;
// wrap-around if the note is too high
if (lastNote[j]>=0x3d) p->newData[k][DIV_PAT_NOTE]-=6*12;
}
if (fp.val[k]) {
if (ignoreNext) {
ignoreNext=false;
} else {
if (fp.val[k]==0xf0) {
p->data[k][0]=100;
p->data[k][1]=0;
p->data[k][2]=-1;
p->newData[k][DIV_PAT_NOTE]=DIV_NOTE_OFF;
p->newData[k][DIV_PAT_INS]=-1;
} else if (fp.val[k]&0xe0) {
if (fp.val[k]&0x40) {
p->data[k][4]=2;
p->data[k][5]=0;
p->newData[k][DIV_PAT_FX(0)]=2;
p->newData[k][DIV_PAT_FXVAL(0)]=0;
isSliding[j]=false;
} else if (fp.val[k]&0x80) {
isSliding[j]=true;
if (k<31) {
if (fp.val[k+1]&0x20) {
p->data[k][4]=2;
p->data[k][5]=fp.val[k+1]&0x1f;
p->newData[k][DIV_PAT_FX(0)]=2;
p->newData[k][DIV_PAT_FXVAL(0)]=fp.val[k+1]&0x1f;
} else {
p->data[k][4]=1;
p->data[k][5]=fp.val[k+1]&0x1f;
p->newData[k][DIV_PAT_FX(0)]=1;
p->newData[k][DIV_PAT_FXVAL(0)]=fp.val[k+1]&0x1f;
}
ignoreNext=true;
} else {
p->data[k][4]=2;
p->data[k][5]=0;
p->newData[k][DIV_PAT_FX(0)]=2;
p->newData[k][DIV_PAT_FXVAL(0)]=0;
}
}
} else {
p->data[k][2]=(fp.val[k]+seq[i].offsetIns[j])&0x3f;
lastIns[j]=p->data[k][2];
p->newData[k][DIV_PAT_INS]=(fp.val[k]+seq[i].offsetIns[j])&0x3f;
lastIns[j]=p->newData[k][DIV_PAT_INS];
}
}
} else if (fp.note[k]>0 && fp.note[k]<0x49) {
p->data[k][2]=seq[i].offsetIns[j];
lastIns[j]=p->data[k][2];
p->newData[k][DIV_PAT_INS]=seq[i].offsetIns[j];
lastIns[j]=p->newData[k][DIV_PAT_INS];
}
}
}

View file

@ -270,7 +270,7 @@ int convert_vrc6_duties[4] = {1, 3, 7, 3};
int findEmptyFx(short* data) {
for (int i=0; i<7; i++) {
if (data[4+i*2]==-1) return i;
if (data[DIV_PAT_FX(i)]==-1) return i;
}
return -1;
}
@ -1752,17 +1752,13 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (map_channels[ch] != 0xff) {
if (nextNote == 0x0d) {
pat->data[row][0] = 101;
pat->newData[row][DIV_PAT_NOTE] = DIV_NOTE_REL;
} else if (nextNote == 0x0e) {
pat->data[row][0] = 100;
} else if (nextNote == 0x01) {
pat->data[row][0] = 12;
pat->data[row][1] = nextOctave - 1;
pat->newData[row][DIV_PAT_NOTE] = DIV_NOTE_OFF;
} else if (nextNote == 0) {
pat->data[row][0] = 0;
pat->newData[row][DIV_PAT_NOTE] = -1;
} else if (nextNote < 0x0d) {
pat->data[row][0] = nextNote - 1;
pat->data[row][1] = nextOctave;
pat->newData[row][DIV_PAT_NOTE] = nextOctave*12 + (nextNote - 1) + 60;
}
}
@ -1770,27 +1766,27 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
// TODO: you sure about 0xff?
if (map_channels[ch] != 0xff) {
if (nextIns < 0x40 && nextNote != 0x0d && nextNote != 0x0e) {
pat->data[row][2] = nextIns;
pat->newData[row][DIV_PAT_INS] = nextIns;
} else {
pat->data[row][2] = -1;
pat->newData[row][DIV_PAT_INS] = -1;
}
}
unsigned char nextVol = reader.readC();
if (map_channels[ch] != 0xff) {
if (nextVol < 0x10) {
pat->data[row][3] = nextVol;
pat->newData[row][DIV_PAT_VOL] = nextVol;
if (map_channels[ch] == vrc6_saw_chan) // scale volume
{
// TODO: shouldn't it be 32?
pat->data[row][3] = (pat->data[row][3] * 42) / 15;
pat->newData[row][DIV_PAT_VOL] = (pat->newData[row][DIV_PAT_VOL] * 42) / 15;
}
if (map_channels[ch] == fds_chan) {
pat->data[row][3] = (pat->data[row][3] * 31) / 15;
pat->newData[row][DIV_PAT_VOL] = (pat->newData[row][DIV_PAT_VOL] * 31) / 15;
}
} else {
pat->data[row][3] = -1;
pat->newData[row][DIV_PAT_VOL] = -1;
}
}
@ -1829,15 +1825,15 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (nextEffect == FT_EF_SPEED && nextEffectVal < 20)
nextEffectVal++;
if (pat->data[row][3] == 0)
pat->data[row][3] = 0xf;
if (pat->newData[row][DIV_PAT_VOL] == 0)
pat->newData[row][DIV_PAT_VOL] = 0xf;
else {
pat->data[row][3]--;
pat->data[row][3] &= 0x0F;
pat->newData[row][DIV_PAT_VOL]--;
pat->newData[row][DIV_PAT_VOL] &= 0x0F;
}
if (pat->data[row][0] == 0)
pat->data[row][2] = -1;
if (pat->newData[row][DIV_PAT_NOTE] == -1)
pat->newData[row][DIV_PAT_INS] = -1;
}
if (blockVersion == 3) {
@ -1902,43 +1898,43 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (map_channels[ch] != 0xff) {
if (nextEffect == 0 && nextEffectVal == 0) {
pat->data[row][4 + (j * 2)] = -1;
pat->data[row][5 + (j * 2)] = -1;
pat->newData[row][DIV_PAT_FX(j)] = -1;
pat->newData[row][DIV_PAT_FXVAL(j)] = -1;
} else {
if ((eft && nextEffect<eftEffectMapSize) || (!eft && nextEffect<ftEffectMapSize)) {
if (eft) {
pat->data[row][4 + (j * 2)] = eftEffectMap[nextEffect];
pat->data[row][5 + (j * 2)] = eftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal;
pat->newData[row][DIV_PAT_FX(j)] = eftEffectMap[nextEffect];
pat->newData[row][DIV_PAT_FXVAL(j)] = eftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal;
if (pat->data[row][4 + (j * 2)] == 0x100) {
pat->data[row][3] += pat->data[row][5 + (j * 2)] ? 0x10 : 0; // extra volume bit for AY8930
pat->data[row][4 + (j * 2)] = -1;
pat->data[row][5 + (j * 2)] = -1;
if (pat->newData[row][DIV_PAT_FX(j)] == 0x100) {
pat->newData[row][DIV_PAT_VOL] += pat->newData[row][DIV_PAT_FXVAL(j)] ? 0x10 : 0; // extra volume bit for AY8930
pat->newData[row][DIV_PAT_FX(j)] = -1;
pat->newData[row][DIV_PAT_FXVAL(j)] = -1;
}
if (eftEffectMap[nextEffect] == 0x0f && nextEffectVal > 0x1f) {
pat->data[row][4 + (j * 2)] = 0xfd; // BPM speed change!
pat->newData[row][DIV_PAT_FX(j)] = 0xfd; // BPM speed change!
}
if ((eftEffectMap[nextEffect] == 0xe1 || eftEffectMap[nextEffect] == 0xe2) && (nextEffectVal & 0xf0) == 0) {
pat->data[row][5 + (j * 2)] |= 0x10; // in FamiTracker if e1/e2 commands speed is 0 the portamento still has some speed!
pat->newData[row][DIV_PAT_FXVAL(j)] |= 0x10; // in FamiTracker if e1/e2 commands speed is 0 the portamento still has some speed!
}
} else {
pat->data[row][4 + (j * 2)] = ftEffectMap[nextEffect];
pat->data[row][5 + (j * 2)] = ftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal;
pat->newData[row][DIV_PAT_FX(j)] = ftEffectMap[nextEffect];
pat->newData[row][DIV_PAT_FXVAL(j)] = ftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal;
if (ftEffectMap[nextEffect] == 0x0f && nextEffectVal > 0x1f) {
pat->data[row][4 + (j * 2)] = 0xfd; // BPM speed change!
pat->newData[row][DIV_PAT_FX(j)] = 0xfd; // BPM speed change!
}
if ((ftEffectMap[nextEffect] == 0xe1 || ftEffectMap[nextEffect] == 0xe2) && (nextEffectVal & 0xf0) == 0) {
pat->data[row][5 + (j * 2)] |= 0x10; // in FamiTracker if e1/e2 commands speed is 0 the portamento still has some speed!
pat->newData[row][DIV_PAT_FXVAL(j)] |= 0x10; // in FamiTracker if e1/e2 commands speed is 0 the portamento still has some speed!
}
}
for (int v = 0; v < 8; v++) {
if (map_channels[ch] == n163_chans[v]) {
if (pat->data[row][4 + (j * 2)] == 0x12) {
pat->data[row][4 + (j * 2)] = 0x110; // N163 wave change (we'll map this later)
if (pat->newData[row][DIV_PAT_FX(j)] == 0x12) {
pat->newData[row][DIV_PAT_FX(j)] = 0x110; // N163 wave change (we'll map this later)
}
}
}
@ -1947,23 +1943,24 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
{
if (map_channels[ch] == vrc7_chans[vrr])
{
if (pat->data[row][4 + (j * 2)] == 0x12)
if (pat->newData[row][DIV_PAT_FX(j)] == 0x12)
{
pat->data[row][4 + (j * 2)] = 0x10; // set VRC7 patch
pat->newData[row][DIV_PAT_FX(j)] = 0x10; // set VRC7 patch
}
}
}
for (int v = 0; v < 3; v++) {
if (map_channels[ch] == s5b_chans[v] || map_channels[ch] == ay8930_chans[v]) {
if (pat->data[row][4 + (j * 2)] == 0x22 && (pat->data[row][5 + (j * 2)] & 0xf0) != 0) {
pat->data[row][4 + (7 * 2)] = -666; //marker
if (pat->newData[row][DIV_PAT_FX(j)] == 0x22 && (pat->newData[row][DIV_PAT_FXVAL(j)] & 0xf0) != 0) {
// TODO: in the second stage of pattern refactor this will have to change.
pat->newData[row][DIV_PAT_FX(7)] = -666; //marker
}
}
}
} else {
pat->data[row][4 + (j * 2)] = -1;
pat->data[row][5 + (j * 2)] = -1;
pat->newData[row][DIV_PAT_FX(j)] = -1;
pat->newData[row][DIV_PAT_FXVAL(j)] = -1;
}
}
}
@ -2374,7 +2371,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
CHECK_BLOCK_VERSION(3);
unsigned int linear_pitch = reader.readI();
ds.linearPitch = linear_pitch == 0 ? 0 : 2;
ds.linearPitch = linear_pitch == 0 ? 0 : 1;
if (blockVersion >= 2) {
int fineTuneCents = reader.readC() * 100;
@ -2438,8 +2435,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (ds.subsong[j]->pat[ii].data[k] == NULL)
continue;
for (int l = 0; l < ds.subsong[j]->patLen; l++) {
if (ds.subsong[j]->pat[ii].data[k]->data[l][2] > index) {
ds.subsong[j]->pat[ii].data[k]->data[l][2]--;
if (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] > index) {
ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS]--;
}
}
}
@ -2456,12 +2453,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (ds.subsong[j]->pat[ii].data[k] == NULL)
continue;
for (int l = 0; l < ds.subsong[j]->patLen; l++) {
if (ds.subsong[j]->pat[ii].data[k]->data[l][4 + 7*2] == -666) {
if (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_FX(7)] == -666) {
bool converted = false;
// for()? if()? THESE ARE NOT FUNCTIONS!
for (int hh = 0; hh < 7; hh++) { // oh and now you 1TBS up. oh man...
if (ds.subsong[j]->pat[ii].data[k]->data[l][4 + hh*2] == 0x22 && !converted) {
int slot = findEmptyFx(ds.subsong[j]->pat[ii].data[k]->data[l]);
if (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_FX(hh)] == 0x22 && !converted) {
int slot = findEmptyFx(ds.subsong[j]->pat[ii].data[k]->newData[l]);
if (slot != -1) {
// space your comments damn it!
// Hxy - Envelope automatic pitch
@ -2469,7 +2466,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
// Sets envelope period to the note period shifted by x and envelope type y.
// Approximate envelope frequency is note frequency * (2^|x - 8|) / 32.
int ftAutoEnv = (ds.subsong[j]->pat[ii].data[k]->data[l][5 + hh*2] >> 4) & 15;
int ftAutoEnv = (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_FXVAL(hh)] >> 4) & 15;
int autoEnvDen = 16; // ???? with 32 it's an octave lower...
int autoEnvNum = (1 << (abs(ftAutoEnv - 8)));
@ -2479,18 +2476,18 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
if (autoEnvDen < 16 && autoEnvNum < 16) {
ds.subsong[j]->pat[ii].data[k]->data[l][4 + slot*2] = 0x29;
ds.subsong[j]->pat[ii].data[k]->data[l][5 + slot*2] = (autoEnvNum << 4) | autoEnvDen;
ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_FX(slot)] = 0x29;
ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_FXVAL(slot)] = (autoEnvNum << 4) | autoEnvDen;
}
ds.subsong[j]->pat[ii].data[k]->data[l][5 + hh*2] = (ds.subsong[j]->pat[ii].data[k]->data[l][5 + hh*2] & 0xf) << 4;
ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_FXVAL(hh)] = (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_FXVAL(hh)] & 0xf) << 4;
converted = true;
}
}
}
ds.subsong[j]->pat[ii].data[k]->data[l][4 + (7 * 2)] = -1; //delete marker
ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_FX(7)] = -1; //delete marker
}
}
}
@ -2506,10 +2503,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
for (int p = 0; p < s->ordersLen; p++) {
for (int r = 0; r < s->patLen; r++) {
DivPattern* pat = s->pat[c].getPattern(s->orders.ord[c][p], true);
short* s_row_data = pat->data[r];
short* s_row_data = pat->newData[r];
for (int eff = 0; eff < DIV_MAX_EFFECTS - 1; eff++) {
if (s_row_data[4 + 2 * eff] != -1 && eff + 1 > num_fx) {
if (s_row_data[DIV_PAT_FX(eff)] != -1 && eff + 1 > num_fx) {
num_fx = eff + 1;
}
}
@ -2556,7 +2553,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
continue;
for (int l = 0; l < ds.subsong[j]->patLen; l++) {
// 1TBS > GNU
if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == i) { // instrument
if (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] == i) { // instrument
DivInstrument* ins = ds.ins[i];
bool go_to_end = false;
@ -2606,7 +2603,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (ds.subsong[j]->pat[ii].data[k] == NULL)
continue;
for (int l = 0; l < ds.subsong[j]->patLen; l++) {
if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == i) // instrument
if (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] == i) // instrument
{
DivInstrument* ins = ds.ins[i];
bool go_to_end = false;
@ -2658,7 +2655,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (ds.subsong[j]->pat[ii].data[k] == NULL)
continue;
for (int l = 0; l < ds.subsong[j]->patLen; l++) {
if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == i) // instrument
if (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] == i) // instrument
{
DivInstrument* ins = ds.ins[i];
bool go_to_end = false;
@ -2729,17 +2726,17 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (ds.subsong[j]->pat[ii].data[k] == NULL)
continue;
for (int l = 0; l < ds.subsong[j]->patLen; l++) {
if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == ins_vrc6_conv[i][0] && (ii == vrc6_chans[0] || ii == vrc6_chans[1])) // change ins index
if (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] == ins_vrc6_conv[i][0] && (ii == vrc6_chans[0] || ii == vrc6_chans[1])) // change ins index
{
ds.subsong[j]->pat[ii].data[k]->data[l][2] = ins_vrc6_conv[i][1];
ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] = ins_vrc6_conv[i][1];
}
if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == ins_vrc6_saw_conv[i][0] && ii == vrc6_saw_chan) {
ds.subsong[j]->pat[ii].data[k]->data[l][2] = ins_vrc6_saw_conv[i][1];
if (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] == ins_vrc6_saw_conv[i][0] && ii == vrc6_saw_chan) {
ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] = ins_vrc6_saw_conv[i][1];
}
if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == ins_nes_conv[i][0] && (ii == mmc5_chans[0] || ii == mmc5_chans[1] || ii < 5)) {
ds.subsong[j]->pat[ii].data[k]->data[l][2] = ins_nes_conv[i][1];
if (ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] == ins_nes_conv[i][0] && (ii == mmc5_chans[0] || ii == mmc5_chans[1] || ii < 5)) {
ds.subsong[j]->pat[ii].data[k]->newData[l][DIV_PAT_INS] = ins_nes_conv[i][1];
}
}
}
@ -2785,19 +2782,19 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
DivPattern* p=i->pat[j].getPattern(i->orders.ord[j][k],true);
for (int l=0; l<i->patLen; l++) {
// check for instrument change
if (p->data[l][2]!=-1) {
curWaveOff=n163WaveOff[p->data[l][2]&127];
if (p->newData[l][DIV_PAT_INS]!=-1) {
curWaveOff=n163WaveOff[p->newData[l][DIV_PAT_INS]&127];
}
// check effect columns for 0x110 (dummy wave change)
for (int m=0; m<i->pat[j].effectCols; m++) {
if (p->data[l][4+(m<<1)]==0x110) {
if (p->newData[l][DIV_PAT_FX(m)]==0x110) {
// map wave
p->data[l][4+(m<<1)]=0x10;
if (p->data[l][5+(m<<1)]==-1) {
p->data[l][5+(m<<1)]=curWaveOff&0xff;
p->newData[l][DIV_PAT_FX(m)]=0x10;
if (p->newData[l][DIV_PAT_FXVAL(m)]==-1) {
p->newData[l][DIV_PAT_FXVAL(m)]=curWaveOff&0xff;
} else {
p->data[l][5+(m<<1)]=(p->data[l][5+(m<<1)]+curWaveOff)&0xff;
p->newData[l][DIV_PAT_FXVAL(m)]=(p->newData[l][DIV_PAT_FXVAL(m)]+curWaveOff)&0xff;
}
}
}

View file

@ -1778,32 +1778,28 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
if (mask&1) { // note
unsigned char note=reader.readC();
// TODO: PAT2 format with new off/===/rel values!
if (note==180) {
pat->data[j][0]=100;
pat->data[j][1]=0;
pat->newData[j][0]=DIV_NOTE_OFF;
} else if (note==181) {
pat->data[j][0]=101;
pat->data[j][1]=0;
pat->newData[j][0]=DIV_NOTE_REL;
} else if (note==182) {
pat->data[j][0]=102;
pat->data[j][1]=0;
pat->newData[j][0]=DIV_MACRO_REL;
} else if (note<180) {
pat->data[j][0]=newFormatNotes[note];
pat->data[j][1]=newFormatOctaves[note];
pat->newData[j][DIV_PAT_NOTE]=note;
} else {
pat->data[j][0]=0;
pat->data[j][1]=0;
pat->newData[j][0]=-1;
}
}
if (mask&2) { // instrument
pat->data[j][2]=(unsigned char)reader.readC();
pat->newData[j][DIV_PAT_INS]=(unsigned char)reader.readC();
}
if (mask&4) { // volume
pat->data[j][3]=(unsigned char)reader.readC();
pat->newData[j][DIV_PAT_VOL]=(unsigned char)reader.readC();
}
for (unsigned char k=0; k<16; k++) {
if (effectMask&(1<<k)) {
pat->data[j][4+k]=(unsigned char)reader.readC();
pat->newData[j][DIV_PAT_FX(0)+k]=(unsigned char)reader.readC();
}
}
}
@ -1844,18 +1840,20 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true);
for (int j=0; j<ds.subsong[subs]->patLen; j++) {
pat->data[j][0]=reader.readS();
pat->data[j][1]=reader.readS();
pat->data[j][2]=reader.readS();
pat->data[j][3]=reader.readS();
for (int k=0; k<ds.subsong[subs]->pat[chan].effectCols; k++) {
pat->data[j][4+(k<<1)]=reader.readS();
pat->data[j][5+(k<<1)]=reader.readS();
short note=reader.readS();
short octave=reader.readS();
if (note==0 && octave!=0) {
logD("what? %d:%d:%d note %d octave %d",chan,i,j,note,octave);
note=12;
octave--;
}
if (pat->data[j][0]==0 && pat->data[j][1]!=0) {
logD("what? %d:%d:%d note %d octave %d",chan,i,j,pat->data[j][0],pat->data[j][1]);
pat->data[j][0]=12;
pat->data[j][1]--;
pat->newData[j][DIV_PAT_NOTE]=splitNoteToNote(note,octave);
pat->newData[j][DIV_PAT_INS]=reader.readS();
pat->newData[j][DIV_PAT_VOL]=reader.readS();
for (int k=0; k<ds.subsong[subs]->pat[chan].effectCols; k++) {
pat->newData[j][DIV_PAT_FX(k)]=reader.readS();
pat->newData[j][DIV_PAT_FXVAL(k)]=reader.readS();
}
}
@ -2145,6 +2143,44 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
}
}
// YM2151 E5xx range was different
if (ds.version<236) {
int ch=0;
// so much nesting
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_YM2151) {
// find all E5xx effects and adapt to normal range
for (int j=ch; j<ch+8; j++) {
for (size_t k=0; k<ds.subsong.size(); k++) {
for (int l=0; l<DIV_MAX_PATTERNS; l++) {
DivPattern* p=ds.subsong[k]->pat[j].data[l];
if (p==NULL) continue;
for (int m=0; m<DIV_MAX_ROWS; m++) {
for (int n=0; n<ds.subsong[k]->pat[j].effectCols; n++) {
if (p->newData[m][DIV_PAT_FX(n)]==0xe5 && p->newData[m][DIV_PAT_FXVAL(n)]!=-1) {
int newVal=(2*((p->newData[m][DIV_PAT_FXVAL(n)]&0xff)-0x80))+0x80;
if (newVal<0) newVal=0;
if (newVal>0xff) newVal=0xff;
p->newData[m][DIV_PAT_FXVAL(n)]=newVal;
}
}
}
}
}
}
}
ch+=getChannelCount(ds.system[i]);
}
}
// warn on partial pitch linearity
if (ds.linearPitch>1) {
ds.linearPitch=1;
} else if (ds.version<237 && ds.linearPitch!=0) {
addWarning("this song used partial pitch linearity, which has been removed from Furnace. you may have to adjust your song.");
}
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();
@ -2171,7 +2207,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
return true;
}
SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
SafeWriter* DivEngine::saveFur(bool notPrimary) {
saveLock.lock();
std::vector<int> subSongPtr;
std::vector<int> sysFlagsPtr;
@ -2622,133 +2658,100 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false);
patPtr.push_back(w->tell());
if (newPatternFormat) {
w->write("PATN",4);
blockStartSeek=w->tell();
w->writeI(0);
w->write("PATN",4);
blockStartSeek=w->tell();
w->writeI(0);
w->writeC(i.subsong);
w->writeC(i.chan);
w->writeS(i.pat);
w->writeString(pat->name,false);
w->writeC(i.subsong);
w->writeC(i.chan);
w->writeS(i.pat);
w->writeString(pat->name,false);
unsigned char emptyRows=0;
unsigned char emptyRows=0;
for (int j=0; j<song.subsong[i.subsong]->patLen; j++) {
unsigned char mask=0;
unsigned char finalNote=255;
unsigned short effectMask=0;
for (int j=0; j<song.subsong[i.subsong]->patLen; j++) {
unsigned char mask=0;
unsigned char finalNote=255;
unsigned short effectMask=0;
if (pat->data[j][0]==100) {
finalNote=180;
} else if (pat->data[j][0]==101) { // note release
finalNote=181;
} else if (pat->data[j][0]==102) { // macro release
finalNote=182;
} else if (pat->data[j][1]==0 && pat->data[j][0]==0) {
finalNote=255;
} else {
int seek=(pat->data[j][0]+(signed char)pat->data[j][1]*12)+60;
if (seek<0 || seek>=180) {
finalNote=255;
} else {
finalNote=seek;
}
}
if (finalNote!=255) mask|=1; // note
if (pat->data[j][2]!=-1) mask|=2; // instrument
if (pat->data[j][3]!=-1) mask|=4; // volume
for (int k=0; k<song.subsong[i.subsong]->pat[i.chan].effectCols*2; k+=2) {
if (k==0) {
if (pat->data[j][4+k]!=-1) mask|=8;
if (pat->data[j][5+k]!=-1) mask|=16;
} else if (k<8) {
if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=32;
} else {
if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=64;
}
if (pat->data[j][4+k]!=-1) effectMask|=(1<<k);
if (pat->data[j][5+k]!=-1) effectMask|=(2<<k);
}
if (mask==0) {
emptyRows++;
if (emptyRows>127) {
w->writeC(128|(emptyRows-2));
emptyRows=0;
}
} else {
if (emptyRows>1) {
w->writeC(128|(emptyRows-2));
emptyRows=0;
} else if (emptyRows) {
w->writeC(0);
emptyRows=0;
}
w->writeC(mask);
if (mask&32) w->writeC(effectMask&0xff);
if (mask&64) w->writeC((effectMask>>8)&0xff);
if (mask&1) w->writeC(finalNote);
if (mask&2) w->writeC(pat->data[j][2]);
if (mask&4) w->writeC(pat->data[j][3]);
if (mask&8) w->writeC(pat->data[j][4]);
if (mask&16) w->writeC(pat->data[j][5]);
if (mask&32) {
if (effectMask&4) w->writeC(pat->data[j][6]);
if (effectMask&8) w->writeC(pat->data[j][7]);
if (effectMask&16) w->writeC(pat->data[j][8]);
if (effectMask&32) w->writeC(pat->data[j][9]);
if (effectMask&64) w->writeC(pat->data[j][10]);
if (effectMask&128) w->writeC(pat->data[j][11]);
}
if (mask&64) {
if (effectMask&256) w->writeC(pat->data[j][12]);
if (effectMask&512) w->writeC(pat->data[j][13]);
if (effectMask&1024) w->writeC(pat->data[j][14]);
if (effectMask&2048) w->writeC(pat->data[j][15]);
if (effectMask&4096) w->writeC(pat->data[j][16]);
if (effectMask&8192) w->writeC(pat->data[j][17]);
if (effectMask&16384) w->writeC(pat->data[j][18]);
if (effectMask&32768) w->writeC(pat->data[j][19]);
}
}
if (pat->newData[j][DIV_PAT_NOTE]==DIV_NOTE_OFF) { // note off
finalNote=180;
} else if (pat->newData[j][DIV_PAT_NOTE]==DIV_NOTE_REL) { // note release
finalNote=181;
} else if (pat->newData[j][DIV_PAT_NOTE]==DIV_MACRO_REL) { // macro release
finalNote=182;
} else if (pat->newData[j][DIV_PAT_NOTE]==-1) { // empty
finalNote=255;
} else {
finalNote=pat->newData[j][DIV_PAT_NOTE];
}
// stop
w->writeC(0xff);
} else {
w->write("PATR",4);
blockStartSeek=w->tell();
w->writeI(0);
w->writeS(i.chan);
w->writeS(i.pat);
w->writeS(i.subsong);
w->writeS(0); // reserved
for (int j=0; j<song.subsong[i.subsong]->patLen; j++) {
w->writeS(pat->data[j][0]); // note
w->writeS(pat->data[j][1]); // octave
w->writeS(pat->data[j][2]); // instrument
w->writeS(pat->data[j][3]); // volume
#ifdef TA_BIG_ENDIAN
for (int k=0; k<song.subsong[i.subsong]->pat[i.chan].effectCols*2; k++) {
w->writeS(pat->data[j][4+k]);
if (finalNote!=255) mask|=1; // note
if (pat->newData[j][DIV_PAT_INS]!=-1) mask|=2; // instrument
if (pat->newData[j][DIV_PAT_VOL]!=-1) mask|=4; // volume
for (int k=0; k<song.subsong[i.subsong]->pat[i.chan].effectCols*2; k+=2) {
if (k==0) {
if (pat->newData[j][DIV_PAT_FX(0)+k]!=-1) mask|=8;
if (pat->newData[j][DIV_PAT_FXVAL(0)+k]!=-1) mask|=16;
} else if (k<8) {
if (pat->newData[j][DIV_PAT_FX(0)+k]!=-1 || pat->newData[j][DIV_PAT_FXVAL(0)+k]!=-1) mask|=32;
} else {
if (pat->newData[j][DIV_PAT_FX(0)+k]!=-1 || pat->newData[j][DIV_PAT_FXVAL(0)+k]!=-1) mask|=64;
}
#else
w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects
#endif
if (pat->newData[j][DIV_PAT_FX(0)+k]!=-1) effectMask|=(1<<k);
if (pat->newData[j][DIV_PAT_FXVAL(0)+k]!=-1) effectMask|=(2<<k);
}
w->writeString(pat->name,false);
if (mask==0) {
emptyRows++;
if (emptyRows>127) {
w->writeC(128|(emptyRows-2));
emptyRows=0;
}
} else {
if (emptyRows>1) {
w->writeC(128|(emptyRows-2));
emptyRows=0;
} else if (emptyRows) {
w->writeC(0);
emptyRows=0;
}
w->writeC(mask);
if (mask&32) w->writeC(effectMask&0xff);
if (mask&64) w->writeC((effectMask>>8)&0xff);
if (mask&1) w->writeC(finalNote);
if (mask&2) w->writeC(pat->newData[j][DIV_PAT_INS]);
if (mask&4) w->writeC(pat->newData[j][DIV_PAT_VOL]);
if (mask&8) w->writeC(pat->newData[j][DIV_PAT_FX(0)]);
if (mask&16) w->writeC(pat->newData[j][DIV_PAT_FXVAL(0)]);
if (mask&32) {
if (effectMask&4) w->writeC(pat->newData[j][DIV_PAT_FX(1)]);
if (effectMask&8) w->writeC(pat->newData[j][DIV_PAT_FXVAL(1)]);
if (effectMask&16) w->writeC(pat->newData[j][DIV_PAT_FX(2)]);
if (effectMask&32) w->writeC(pat->newData[j][DIV_PAT_FXVAL(2)]);
if (effectMask&64) w->writeC(pat->newData[j][DIV_PAT_FX(3)]);
if (effectMask&128) w->writeC(pat->newData[j][DIV_PAT_FXVAL(3)]);
}
if (mask&64) {
if (effectMask&256) w->writeC(pat->newData[j][DIV_PAT_FX(4)]);
if (effectMask&512) w->writeC(pat->newData[j][DIV_PAT_FXVAL(4)]);
if (effectMask&1024) w->writeC(pat->newData[j][DIV_PAT_FX(5)]);
if (effectMask&2048) w->writeC(pat->newData[j][DIV_PAT_FXVAL(5)]);
if (effectMask&4096) w->writeC(pat->newData[j][DIV_PAT_FX(6)]);
if (effectMask&8192) w->writeC(pat->newData[j][DIV_PAT_FXVAL(6)]);
if (effectMask&16384) w->writeC(pat->newData[j][DIV_PAT_FX(7)]);
if (effectMask&32768) w->writeC(pat->newData[j][DIV_PAT_FXVAL(7)]);
}
}
}
// stop
w->writeC(0xff);
blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4);

View file

@ -277,7 +277,7 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
}
if (flags&8) {
ds.linearPitch=2;
ds.linearPitch=1;
} else {
ds.linearPitch=0;
}
@ -363,6 +363,103 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
patPtr[i]=reader.readI();
}
// skip edit history if present
if (special&2) {
logD("skipping edit history...");
unsigned short editHistSize=reader.readS();
if (editHistSize>0) {
if (!reader.seek(editHistSize*8,SEEK_CUR)) {
logV("what? I wasn't expecting that from you.");
}
}
}
// read extension blocks, if any
logD("looking for extensions...");
bool hasExtensions=true;
while (hasExtensions) {
char extType[4];
unsigned int extSize=0;
memset(extType,0,4);
reader.read(extType,4);
extSize=reader.readI();
if (memcmp(extType,"PNAM",4)==0) {
logV("found MPT extension: pattern names");
// check whether this block is valid
if (extSize>patCount*32) {
logV("block may not be valid");
break;
}
// read pattern names
logV("reading pattern names...");
for (unsigned int i=0; i<(extSize>>5); i++) {
DivPattern* p=ds.subsong[0]->pat[0].getPattern(i,true);
p->name=reader.readStringLatin1(32);
}
} else if (memcmp(extType,"CNAM",4)==0) {
logV("found MPT extension: channel names");
// check whether this block is valid
if (extSize>DIV_MAX_CHANS*20) {
logV("block may not be valid");
break;
}
// read channel names
logV("reading channel names...");
for (unsigned int i=0; i<(extSize>>5); i++) {
String chanName=reader.readStringLatin1(20);
for (DivSubSong* j: ds.subsong) {
j->chanName[i]=chanName;
}
}
} else if (memcmp(extType,"CHFX",4)==0) {
logV("found MPT extension: channel effects");
// skip (stop if we cannot seek)
if (!reader.seek(extSize,SEEK_CUR)) {
break;
}
} else if (
extType[0]=='F' &&
(extType[1]=='X' || (extType[1]>='0' && extType[1]<='9')) &&
(extType[2]>='0' && extType[2]<='9') &&
(extType[3]>='0' && extType[3]<='9')
) { // effect slot
logV("found MPT extension: effect slot");
// skip (stop if we cannot seek)
if (!reader.seek(extSize,SEEK_CUR)) {
break;
}
} else {
logV("no further extensions found... %.2x%.2x%.2x%.2x",extType[0],extType[1],extType[2],extType[3]);
hasExtensions=false;
}
}
// read song comment
logD("reading song comment...");
if (reader.seek(commentPtr,SEEK_SET)) {
try {
String comment=reader.readStringLatin1Special(commentLen);
ds.notes="";
ds.notes.reserve(comment.size());
for (char& i: comment) {
if (i=='\r') {
ds.notes+='\n';
} else {
ds.notes+=i;
}
}
} catch (EndOfFileException& e) {
logW("couldn't read song comment due to premature end of file.");
}
} else {
logW("couldn't seek to comment!");
}
// read instruments
for (int i=0; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
@ -965,7 +1062,7 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
int readRow=0;
bool mustCommitInitial=true;
memset(effectCol,4,64);
memset(effectCol,0,64);
memset(vibStatus,0,64);
memset(vibStatusChanged,0,64*sizeof(bool));
memset(vibing,0,64*sizeof(bool));
@ -1042,86 +1139,86 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
for (int j=0; j<64; j++) {
DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true);
if (vibing[j]!=vibingOld[j] || vibStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x04;
p->data[readRow][effectCol[j]++]=vibing[j]?vibStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x04;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=vibing[j]?vibStatus[j]:0;
doesVibrato[j]=true;
} else if (doesVibrato[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x04;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x04;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (volSliding[j]!=volSlidingOld[j] || volSlideStatusChanged[j]) {
if (volSlideStatus[j]>=0xf1 && volSliding[j]) {
p->data[readRow][effectCol[j]++]=0xf9;
p->data[readRow][effectCol[j]++]=volSlideStatus[j]&15;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0xf9;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=volSlideStatus[j]&15;
volSliding[j]=false;
} else if ((volSlideStatus[j]&15)==15 && volSlideStatus[j]>=0x10 && volSliding[j]) {
p->data[readRow][effectCol[j]++]=0xf8;
p->data[readRow][effectCol[j]++]=volSlideStatus[j]>>4;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0xf8;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=volSlideStatus[j]>>4;
volSliding[j]=false;
} else {
p->data[readRow][effectCol[j]++]=0xfa;
p->data[readRow][effectCol[j]++]=volSliding[j]?volSlideStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0xfa;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=volSliding[j]?volSlideStatus[j]:0;
}
doesVolSlide[j]=true;
} else if (doesVolSlide[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0xfa;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0xfa;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (porting[j]!=portingOld[j] || portaStatusChanged[j]) {
if (portaStatus[j]>=0xe0 && portaType[j]!=3 && porting[j]) {
p->data[readRow][effectCol[j]++]=portaType[j]|0xf0;
p->data[readRow][effectCol[j]++]=(portaStatus[j]&15)*((portaStatus[j]>=0xf0)?1:1);
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=portaType[j]|0xf0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=(portaStatus[j]&15)*((portaStatus[j]>=0xf0)?1:1);
porting[j]=false;
} else {
p->data[readRow][effectCol[j]++]=portaType[j];
p->data[readRow][effectCol[j]++]=porting[j]?portaStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=portaType[j];
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=porting[j]?portaStatus[j]:0;
}
doesPitchSlide[j]=true;
} else if (doesPitchSlide[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x01;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x01;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (arping[j]!=arpingOld[j] || arpStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x00;
p->data[readRow][effectCol[j]++]=arping[j]?arpStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x00;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=arping[j]?arpStatus[j]:0;
doesArp[j]=true;
} else if (doesArp[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x00;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x00;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (treming[j]!=tremingOld[j] || tremStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x07;
p->data[readRow][effectCol[j]++]=treming[j]?tremStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x07;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=treming[j]?tremStatus[j]:0;
doesTremolo[j]=true;
} else if (doesTremolo[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x07;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x07;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (panning[j]!=panningOld[j] || panStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x84;
p->data[readRow][effectCol[j]++]=panning[j]?panStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x84;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=panning[j]?panStatus[j]:0;
doesPanbrello[j]=true;
} else if (doesPanbrello[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x84;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x84;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (panSliding[j]!=panSlidingOld[j] || panSlideStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x83;
p->data[readRow][effectCol[j]++]=panSliding[j]?panSlideStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x83;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=panSliding[j]?panSlideStatus[j]:0;
doesPanSlide[j]=true;
} else if (doesPanSlide[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x83;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x83;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if ((effectCol[j]>>1)-2>ds.subsong[0]->pat[j].effectCols) {
ds.subsong[0]->pat[j].effectCols=(effectCol[j]>>1)-1;
if ((effectCol[j]>>1)>=ds.subsong[0]->pat[j].effectCols) {
ds.subsong[0]->pat[j].effectCols=(effectCol[j]>>1)+1;
}
}
@ -1153,16 +1250,16 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
if (readRow>0) {
// place end of pattern marker
DivPattern* p=ds.subsong[0]->pat[0].getPattern(i,true);
p->data[readRow-1][effectCol[0]++]=0x0d;
p->data[readRow-1][effectCol[0]++]=0;
p->newData[readRow-1][DIV_PAT_FX(0)+effectCol[0]++]=0x0d;
p->newData[readRow-1][DIV_PAT_FX(0)+effectCol[0]++]=0;
if ((effectCol[0]>>1)-2>ds.subsong[0]->pat[0].effectCols) {
ds.subsong[0]->pat[0].effectCols=(effectCol[0]>>1)-1;
if ((effectCol[0]>>1)>=ds.subsong[0]->pat[0].effectCols) {
ds.subsong[0]->pat[0].effectCols=(effectCol[0]>>1)+1;
}
}
break;
}
memset(effectCol,4,64);
memset(effectCol,0,64);
continue;
}
@ -1205,25 +1302,17 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
if (hasNote) {
if (note[chan]==255) { // note release
p->data[readRow][0]=101;
p->data[readRow][1]=0;
p->newData[readRow][DIV_PAT_NOTE]=DIV_NOTE_REL;
} else if (note[chan]==254) { // note off
p->data[readRow][0]=100;
p->data[readRow][1]=0;
p->newData[readRow][DIV_PAT_NOTE]=DIV_NOTE_OFF;
} else if (note[chan]<120) {
p->data[readRow][0]=note[chan]%12;
p->data[readRow][1]=note[chan]/12;
if (p->data[readRow][0]==0) {
p->data[readRow][0]=12;
p->data[readRow][1]--;
}
p->newData[readRow][DIV_PAT_NOTE]=note[chan]+60;
} else { // note fade, but Furnace does not support that
p->data[readRow][0]=102;
p->data[readRow][1]=0;
p->newData[readRow][DIV_PAT_NOTE]=DIV_MACRO_REL;
}
}
if (hasIns) {
p->data[readRow][2]=ins[chan]-1;
p->newData[readRow][DIV_PAT_INS]=ins[chan]-1;
if ((note[chan]<120 || ds.insLen==0) && ins[chan]>0) {
unsigned char targetPan=0;
if (ds.insLen==0) {
@ -1235,26 +1324,26 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
}
}
if (targetPan&128) {
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=CLAMP((targetPan&127)<<2,0,255);
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x80;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=CLAMP((targetPan&127)<<2,0,255);
}
}
if (hasNote && (note[chan]<120 || ds.insLen==0) && ins[chan]>0) {
if (ds.insLen==0) {
p->data[readRow][3]=defVol[(ins[chan]-1)&255];
p->newData[readRow][DIV_PAT_VOL]=defVol[(ins[chan]-1)&255];
} else {
p->data[readRow][3]=defVol[noteMap[(ins[chan]-1)&255][note[chan]]];
p->newData[readRow][DIV_PAT_VOL]=defVol[noteMap[(ins[chan]-1)&255][note[chan]]];
}
}
}
if (hasVol) {
if (vol[chan]<=64) {
p->data[readRow][3]=vol[chan];
p->newData[readRow][DIV_PAT_VOL]=vol[chan];
} else { // effects in volume column
if (vol[chan]>=128 && vol[chan]<=192) { // panning
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=CLAMP((vol[chan]-128)<<2,0,255);
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x80;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=CLAMP((vol[chan]-128)<<2,0,255);
} else if (vol[chan]>=65 && vol[chan]<=74) { // fine vol up
} else if (vol[chan]>=75 && vol[chan]<=84) { // fine vol down
} else if (vol[chan]>=85 && vol[chan]<=94) { // vol slide up
@ -1301,16 +1390,16 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
if (hasEffect) {
switch (effect[chan]+'A'-1) {
case 'A': // speed
p->data[readRow][effectCol[chan]++]=0x0f;
p->data[readRow][effectCol[chan]++]=effectVal[chan];
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0f;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal[chan];
break;
case 'B': // go to order
p->data[readRow][effectCol[chan]++]=0x0b;
p->data[readRow][effectCol[chan]++]=orders[effectVal[chan]];
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0b;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=orders[effectVal[chan]];
break;
case 'C': // next order
p->data[readRow][effectCol[chan]++]=0x0d;
p->data[readRow][effectCol[chan]++]=effectVal[chan];
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0d;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal[chan];
break;
case 'D': // vol slide
if (effectVal[chan]!=0) {
@ -1393,8 +1482,8 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
case 'N': // channel vol slide
break;
case 'O': // offset
p->data[readRow][effectCol[chan]++]=0x91;
p->data[readRow][effectCol[chan]++]=effectVal[chan];
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x91;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal[chan];
break;
case 'P': // pan slide
if (effectVal[chan]!=0) {
@ -1407,8 +1496,8 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
if (effectVal[chan]!=0) {
lastRetrig[chan]=effectVal[chan];
}
p->data[readRow][effectCol[chan]++]=0x0c;
p->data[readRow][effectCol[chan]++]=lastRetrig[chan]&15;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0c;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=lastRetrig[chan]&15;
break;
case 'R': // tremolo
if (effectVal[chan]!=0) {
@ -1419,24 +1508,80 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
break;
case 'S': // special...
switch (effectVal[chan]>>4) {
case 0x8:
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=(effectVal[chan]&15)<<4;
case 0x3: // vibrato waveform
switch (effectVal[chan]&3) {
case 0x0: // sine
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xe3;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x00;
break;
case 0x1: // ramp down
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xe3;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x05;
break;
case 0x2: // square
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xe3;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x06;
break;
case 0x3: // random
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xe3;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x07;
break;
}
break;
case 0xc:
p->data[readRow][effectCol[chan]++]=0xec;
p->data[readRow][effectCol[chan]++]=effectVal[chan]&15;
case 0x7:
switch (effectVal[chan]&15) {
case 0x7: // volume envelope off
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf5;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x00;
break;
case 0x8: // volume envelope on
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf6;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x00;
break;
case 0x9: // panning envelope off
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf5;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0c;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf5;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0d;
break;
case 0xa: // panning envelope on
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf6;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0c;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf6;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0d;
break;
case 0xb: // pitch envelope off
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf5;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x04;
break;
case 0xc: //pitch envelope on
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf6;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x04;
break;
}
break;
case 0xd:
p->data[readRow][effectCol[chan]++]=0xed;
p->data[readRow][effectCol[chan]++]=effectVal[chan]&15;
case 0x8: // panning
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x80;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=(effectVal[chan]&15)<<4;
break;
case 0xa: // offset (high nibble)
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x92;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal[chan]&15;
break;
case 0xc: // note cut
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xec;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal[chan]&15;
break;
case 0xd: // note delay
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xed;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal[chan]&15;
break;
}
break;
case 'T': // tempo
if (effectVal[chan]>=0x20) {
p->data[readRow][effectCol[chan]++]=0xf0;
p->data[readRow][effectCol[chan]++]=effectVal[chan];
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal[chan];
}
break;
case 'U': // fine vibrato
@ -1451,8 +1596,8 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
case 'W': // global volume slide (!)
break;
case 'X': // panning
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=effectVal[chan];
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x80;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal[chan];
break;
case 'Y': // panbrello
if (effectVal[chan]!=0) {
@ -1522,7 +1667,7 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
for (int i=0; i<(maxChan+32)>>5; i++) {
ds.system[i]=DIV_SYSTEM_ES5506;
ds.systemFlags[i].set("amigaVol",true);
if (ds.linearPitch!=2) {
if (!ds.linearPitch) {
ds.systemFlags[i].set("amigaPitch",true);
}
}
@ -1537,18 +1682,18 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
for (int j=0; j<maxChan; j++) {
DivPattern* p=ds.subsong[i]->pat[j].getPattern(ds.subsong[i]->orders.ord[j][0],true);
for (int k=0; k<DIV_MAX_EFFECTS; k++) {
if (p->data[0][4+(k<<1)]==0x80) {
if (p->newData[0][DIV_PAT_FX(k)]==0x80) {
// give up if there's a panning effect already
break;
}
if (p->data[0][4+(k<<1)]==-1) {
if (p->newData[0][DIV_PAT_FX(k)]==-1) {
if ((chanPan[j]&127)==100) {
// should be surround...
p->data[0][4+(k<<1)]=0x80;
p->data[0][5+(k<<1)]=0x80;
p->newData[0][DIV_PAT_FX(k)]=0x80;
p->newData[0][DIV_PAT_FXVAL(k)]=0x80;
} else {
p->data[0][4+(k<<1)]=0x80;
p->data[0][5+(k<<1)]=CLAMP((chanPan[j]&127)<<2,0,255);
p->newData[0][DIV_PAT_FX(k)]=0x80;
p->newData[0][DIV_PAT_FXVAL(k)]=CLAMP((chanPan[j]&127)<<2,0,255);
}
if (ds.subsong[i]->pat[j].effectCols<=k) ds.subsong[i]->pat[j].effectCols=k+1;
break;

View file

@ -185,21 +185,20 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
}
for (int row=0; row<64; row++) {
for (int ch=0; ch<chCount; ch++) {
short* dstrow=chpats[ch]->data[row];
short* dstrowN=chpats[ch]->newData[row];
unsigned char data[4];
reader.read(&data,4);
// instrument
short ins=(data[0]&0xf0)|(data[2]>>4);
if (ins>0) {
dstrow[2]=ins-1;
dstrow[3]=defaultVols[ins-1];
dstrowN[DIV_PAT_INS]=ins-1;
dstrowN[DIV_PAT_VOL]=defaultVols[ins-1];
}
// note
int period=data[1]+((data[0]&0x0f)<<8);
if (period>0 && period<0x0fff) {
short note=(short)round(log2(3424.0/period)*12);
dstrow[0]=((note+11)%12)+1;
dstrow[1]=(note-1)/12+1;
dstrowN[DIV_PAT_NOTE]=note+72;
if (period<114) {
bypassLimits=true;
}
@ -207,8 +206,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
// effects are done later
short fxtyp=data[2]&0x0f;
short fxval=data[3];
dstrow[4]=fxtyp;
dstrow[5]=fxval;
dstrowN[DIV_PAT_FX(0)]=fxtyp;
dstrowN[DIV_PAT_FXVAL(0)]=fxval;
switch (fxtyp) {
case 0:
if (fxval!=0) fxUsage[ch][0]=true;
@ -256,7 +255,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
for (int ch=0; ch<=chCount; ch++) {
unsigned char fxCols=1;
for (int pat=0; pat<=patMax; pat++) {
auto* data=ds.subsong[0]->pat[ch].getPattern(pat,true)->data;
auto* newData=ds.subsong[0]->pat[ch].getPattern(pat,true)->newData;
short lastPitchEffect=-1;
short lastEffectState[5]={-1,-1,-1,-1,-1};
short setEffectState[5]={-1,-1,-1,-1,-1};
@ -264,11 +263,11 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
const short fxUsageTyp[5]={0x00,0x01,0x04,0x07,0xFA};
short effectState[5]={0,0,0,0,0};
unsigned char curFxCol=0;
short fxTyp=data[row][4];
short fxVal=data[row][5];
auto writeFxCol=[data,row,&curFxCol](short typ, short val) {
data[row][4+curFxCol*2]=typ;
data[row][5+curFxCol*2]=val;
short fxTyp=newData[row][DIV_PAT_FX(0)];
short fxVal=newData[row][DIV_PAT_FXVAL(0)];
auto writeFxCol=[newData,row,&curFxCol](short typ, short val) {
newData[row][DIV_PAT_FX(curFxCol)]=typ;
newData[row][DIV_PAT_FXVAL(curFxCol)]=val;
curFxCol++;
};
writeFxCol(-1,-1);
@ -293,7 +292,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
effectState[1]=fxVal;
if ((effectState[1]!=lastEffectState[1]) ||
(fxTyp!=lastPitchEffect) ||
(effectState[1]!=0 && data[row][0]>0)) {
(effectState[1]!=0 && newData[row][DIV_PAT_NOTE]>-1)) {
writeFxCol(fxTyp,fxVal);
}
lastPitchEffect=fxTyp;
@ -331,7 +330,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
writeFxCol(fxTyp,fxVal);
break;
case 12: // set vol
data[row][3]=MIN(0x40,fxVal);
newData[row][DIV_PAT_VOL]=MIN(0x40,fxVal);
break;
case 13: // break to row (BCD)
writeFxCol(fxTyp,((fxVal>>4)*10)+(fxVal&15));
@ -356,6 +355,20 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
case 2: // single note slide down
writeFxCol(fxTyp-1+0xf1,fxVal);
break;
case 4: // vibrato waveform
switch (fxVal&3) {
case 0: // sine
writeFxCol(0xe3,0x00);
break;
case 1: // ramp down
writeFxCol(0xe3,0x05);
break;
case 2: // square
case 3:
writeFxCol(0xe3,0x06);
break;
}
break;
case 9: // retrigger
writeFxCol(0x0c,fxVal);
break;
@ -373,7 +386,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
for (int i=0; i<5; i++) {
// pitch slide and volume slide needs to be kept active on new note
// even after target/max is reached
if (fxUsage[ch][i] && (effectState[i]!=lastEffectState[i] || (effectState[i]!=0 && i==4 && data[row][3]>=0))) {
if (fxUsage[ch][i] && (effectState[i]!=lastEffectState[i] || (effectState[i]!=0 && i==4 && newData[row][DIV_PAT_VOL]>=0))) {
writeFxCol(fxUsageTyp[i],effectState[i]);
}
}

View file

@ -771,7 +771,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
bool mustCommitInitial=true;
memset(effectCol,4,32);
memset(effectCol,0,32);
memset(vibStatus,0,32);
memset(vibStatusChanged,0,32*sizeof(bool));
memset(vibing,0,32*sizeof(bool));
@ -811,95 +811,95 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
for (int j=0; j<32; j++) {
DivPattern* p=ds.subsong[0]->pat[chanMap[j]].getPattern(i,true);
if (vibing[j]!=vibingOld[j] || vibStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x04;
p->data[readRow][effectCol[j]++]=vibing[j]?vibStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x04;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=vibing[j]?vibStatus[j]:0;
doesVibrato[j]=true;
} else if (doesVibrato[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x04;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x04;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (volSliding[j]!=volSlidingOld[j] || volSlideStatusChanged[j]) {
if (volSlideStatus[j]>=0xf1 && volSliding[j]) {
p->data[readRow][effectCol[j]++]=0xf9;
p->data[readRow][effectCol[j]++]=volSlideStatus[j]&15;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0xf9;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=volSlideStatus[j]&15;
volSliding[j]=false;
} else if ((volSlideStatus[j]&15)==15 && volSlideStatus[j]>=0x10 && volSliding[j]) {
p->data[readRow][effectCol[j]++]=0xf8;
p->data[readRow][effectCol[j]++]=volSlideStatus[j]>>4;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0xf8;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=volSlideStatus[j]>>4;
volSliding[j]=false;
} else {
p->data[readRow][effectCol[j]++]=0xfa;
p->data[readRow][effectCol[j]++]=volSliding[j]?volSlideStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0xfa;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=volSliding[j]?volSlideStatus[j]:0;
}
doesVolSlide[j]=true;
} else if (doesVolSlide[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0xfa;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0xfa;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (porting[j]!=portingOld[j] || portaStatusChanged[j]) {
if (portaStatus[j]>=0xe0 && portaType[j]!=3 && porting[j]) {
p->data[readRow][effectCol[j]++]=portaType[j]|0xf0;
p->data[readRow][effectCol[j]++]=(portaStatus[j]&15)*((portaStatus[j]>=0xf0)?1:1);
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=portaType[j]|0xf0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=(portaStatus[j]&15)*((portaStatus[j]>=0xf0)?1:1);
porting[j]=false;
} else {
p->data[readRow][effectCol[j]++]=portaType[j];
p->data[readRow][effectCol[j]++]=porting[j]?portaStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=portaType[j];
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=porting[j]?portaStatus[j]:0;
}
doesPitchSlide[j]=true;
} else if (doesPitchSlide[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x01;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x01;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (arping[j]!=arpingOld[j] || arpStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x00;
p->data[readRow][effectCol[j]++]=arping[j]?arpStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x00;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=arping[j]?arpStatus[j]:0;
doesArp[j]=true;
} else if (doesArp[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x00;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x00;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (treming[j]!=tremingOld[j] || tremStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x07;
p->data[readRow][effectCol[j]++]=treming[j]?tremStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x07;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=treming[j]?tremStatus[j]:0;
doesTremolo[j]=true;
} else if (doesTremolo[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x07;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x07;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (panning[j]!=panningOld[j] || panStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x84;
p->data[readRow][effectCol[j]++]=panning[j]?panStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x84;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=panning[j]?panStatus[j]:0;
doesPanbrello[j]=true;
} else if (doesPanbrello[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x84;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x84;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (panSliding[j]!=panSlidingOld[j] || panSlideStatusChanged[j]) {
p->data[readRow][effectCol[j]++]=0x83;
p->data[readRow][effectCol[j]++]=panSliding[j]?panSlideStatus[j]:0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x83;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=panSliding[j]?panSlideStatus[j]:0;
doesPanSlide[j]=true;
} else if (doesPanSlide[j] && mustCommitInitial) {
p->data[readRow][effectCol[j]++]=0x83;
p->data[readRow][effectCol[j]++]=0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0x83;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[j]++]=0;
}
if (effectCol[j]>=4+8*2) {
if (effectCol[j]>=8*2) {
logE("oh crap!");
}
if ((effectCol[j]>>1)-2>ds.subsong[0]->pat[j].effectCols) {
ds.subsong[0]->pat[chanMap[j]].effectCols=(effectCol[j]>>1)-1;
if ((effectCol[j]>>1)>=ds.subsong[0]->pat[j].effectCols) {
ds.subsong[0]->pat[chanMap[j]].effectCols=(effectCol[j]>>1)+1;
}
}
readRow++;
memset(effectCol,4,32);
memset(effectCol,0,32);
memcpy(vibingOld,vibing,32*sizeof(bool));
memcpy(volSlidingOld,volSliding,32*sizeof(bool));
memcpy(portingOld,porting,32*sizeof(bool));
@ -935,22 +935,16 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
unsigned char ins=reader.readC();
if (note==254) { // note off
p->data[readRow][0]=100;
p->data[readRow][1]=0;
p->newData[readRow][DIV_PAT_NOTE]=DIV_NOTE_OFF;
} else if (note!=255) {
p->data[readRow][0]=note&15;
p->data[readRow][1]=note>>4;
if ((note&15)==0) {
p->data[readRow][0]=12;
p->data[readRow][1]--;
}
p->newData[readRow][DIV_PAT_NOTE]=(note&15)+(note>>4)*12+60;
}
p->data[readRow][2]=(short)ins-1;
p->newData[readRow][DIV_PAT_INS]=(short)ins-1;
}
if (hasVol) {
unsigned char vol=reader.readC();
if (vol==255) {
p->data[readRow][3]=-1;
p->newData[readRow][DIV_PAT_VOL]=-1;
} else {
// check for OPL channel
if ((chanSettings[chan]&31)>=16) {
@ -958,17 +952,17 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
} else {
if (vol>64) vol=64;
}
p->data[readRow][3]=vol;
p->newData[readRow][DIV_PAT_VOL]=vol;
}
} else if (p->data[readRow][2]!=-1) {
} else if (p->newData[readRow][DIV_PAT_INS]!=-1) {
// populate with instrument volume
unsigned char vol=defVol[p->data[readRow][2]&255];
unsigned char vol=defVol[p->newData[readRow][DIV_PAT_INS]&255];
if ((chanSettings[chan]&31)>=16) {
if (vol>63) vol=63;
} else {
if (vol>64) vol=64;
}
p->data[readRow][3]=vol;
p->newData[readRow][DIV_PAT_VOL]=vol;
}
if (hasEffect) {
unsigned char effect=reader.readC();
@ -976,17 +970,17 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
switch (effect+'A'-1) {
case 'A': // speed
p->data[readRow][effectCol[chan]++]=0x0f;
p->data[readRow][effectCol[chan]++]=effectVal;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0f;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal;
break;
case 'B': // go to order
p->data[readRow][effectCol[chan]++]=0x0b;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0b;
logD("0B: %x %x",effectVal,orders[effectVal]);
p->data[readRow][effectCol[chan]++]=orders[effectVal];
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=orders[effectVal];
break;
case 'C': // next order
p->data[readRow][effectCol[chan]++]=0x0d;
p->data[readRow][effectCol[chan]++]=(effectVal>>4)*10+(effectVal&15);
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0d;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=(effectVal>>4)*10+(effectVal&15);
break;
case 'D': // vol slide
if (effectVal!=0) {
@ -1078,8 +1072,8 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
case 'N': // channel vol slide (extension)
break;
case 'O': // offset
p->data[readRow][effectCol[chan]++]=0x91;
p->data[readRow][effectCol[chan]++]=effectVal;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x91;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal;
break;
case 'P': // pan slide (extension)
if (effectVal!=0) {
@ -1089,8 +1083,8 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
panSliding[chan]=true;
break;
case 'Q': // retrigger
p->data[readRow][effectCol[chan]++]=0x0c;
p->data[readRow][effectCol[chan]++]=effectVal&15;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x0c;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal&15;
break;
case 'R': // tremolo
if (effectVal!=0) {
@ -1101,23 +1095,43 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
break;
case 'S': // special...
switch (effectVal>>4) {
case 0x8:
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=(effectVal&15)<<4;
case 0x3: // vibrato waveform
switch (effectVal&3) {
case 0x0: // sine
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xe3;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x00;
break;
case 0x1: // ramp down
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xe3;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x05;
break;
case 0x2: // square
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xe3;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x06;
break;
case 0x3: // random
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xe3;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x07;
break;
}
break;
case 0xc:
p->data[readRow][effectCol[chan]++]=0xec;
p->data[readRow][effectCol[chan]++]=effectVal&15;
case 0x8: // panning
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x80;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=(effectVal&15)<<4;
break;
case 0xd:
p->data[readRow][effectCol[chan]++]=0xed;
p->data[readRow][effectCol[chan]++]=effectVal&15;
case 0xc: // note cut
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xec;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal&15;
break;
case 0xd: // note delay
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xed;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal&15;
break;
}
break;
case 'T': // tempo
p->data[readRow][effectCol[chan]++]=0xf0;
p->data[readRow][effectCol[chan]++]=effectVal;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0xf0;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=effectVal;
break;
case 'U': // fine vibrato
if (effectVal!=0) {
@ -1132,8 +1146,8 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
break;
case 'X': // panning (extension)
if (effectVal<=0x80) {
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=(effectVal&0x80)?0xff:(effectVal<<1);
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=0x80;
p->newData[readRow][DIV_PAT_FX(0)+effectCol[chan]++]=(effectVal&0x80)?0xff:(effectVal<<1);
}
break;
case 'Y': // panbrello (extension)
@ -1173,16 +1187,16 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
for (int j=0; j<16; j++) {
DivPattern* p=ds.subsong[i]->pat[chanMap[j]].getPattern(ds.subsong[i]->orders.ord[j][0],true);
for (int k=0; k<DIV_MAX_EFFECTS; k++) {
if (p->data[0][4+(k<<1)]==0x80) {
if (p->newData[0][DIV_PAT_FX(k)]==0x80) {
// give up if there's a panning effect already
break;
}
if (p->data[0][4+(k<<1)]==-1) {
p->data[0][4+(k<<1)]=0x80;
if (p->newData[0][DIV_PAT_FX(k)]==-1) {
p->newData[0][DIV_PAT_FX(k)]=0x80;
if (chanPan[j]&16) {
p->data[0][5+(k<<1)]=(j&1)?0xcc:0x33;
p->newData[0][DIV_PAT_FXVAL(k)]=(j&1)?0xcc:0x33;
} else {
p->data[0][5+(k<<1)]=(chanPan[j]&15)|((chanPan[j]&15)<<4);
p->newData[0][DIV_PAT_FXVAL(k)]=(chanPan[j]&15)|((chanPan[j]&15)<<4);
}
if (ds.subsong[i]->pat[chanMap[j]].effectCols<=k) ds.subsong[i]->pat[chanMap[j]].effectCols=k+1;
break;

View file

@ -321,9 +321,8 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) {
for (int l=0; l<chans; l++) {
DivPattern* p=s->pat[l].getPattern(s->orders.ord[l][j],false);
int note=p->data[k][0];
int octave=p->data[k][1];
short note, octave;
noteToSplitNote(p->newData[k][DIV_PAT_NOTE],note,octave);
if (note==0 && octave==0) {
w->writeText("|... ");
@ -344,28 +343,28 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) {
w->writeText(fmt::sprintf("|%s%d ",(octave<0)?notesNegative[note]:notes[note],(octave<0)?(-octave):octave));
}
if (p->data[k][2]==-1) {
if (p->newData[k][DIV_PAT_INS]==-1) {
w->writeText(".. ");
} else {
w->writeText(fmt::sprintf("%.2X ",p->data[k][2]&0xff));
w->writeText(fmt::sprintf("%.2X ",p->newData[k][DIV_PAT_INS]&0xff));
}
if (p->data[k][3]==-1) {
if (p->newData[k][DIV_PAT_VOL]==-1) {
w->writeText("..");
} else {
w->writeText(fmt::sprintf("%.2X",p->data[k][3]&0xff));
w->writeText(fmt::sprintf("%.2X",p->newData[k][DIV_PAT_VOL]&0xff));
}
for (int m=0; m<s->pat[l].effectCols; m++) {
if (p->data[k][4+(m<<1)]==-1) {
if (p->newData[k][DIV_PAT_FX(m)]==-1) {
w->writeText(" ..");
} else {
w->writeText(fmt::sprintf(" %.2X",p->data[k][4+(m<<1)]&0xff));
w->writeText(fmt::sprintf(" %.2X",p->newData[k][DIV_PAT_FX(m)]&0xff));
}
if (p->data[k][5+(m<<1)]==-1) {
if (p->newData[k][DIV_PAT_FXVAL(m)]==-1) {
w->writeText("..");
} else {
w->writeText(fmt::sprintf("%.2X",p->data[k][5+(m<<1)]&0xff));
w->writeText(fmt::sprintf("%.2X",p->newData[k][DIV_PAT_FXVAL(m)]&0xff));
}
}
}

View file

@ -254,16 +254,10 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
if (patDataBuf[k]==0) continue;
else if (patDataBuf[k]==1) {
// note off
pat->data[k][0]=100;
pat->newData[k][DIV_PAT_NOTE]=DIV_NOTE_OFF;
} else {
unsigned char invertedNote=~patDataBuf[k];
pat->data[k][0]=invertedNote%12;
pat->data[k][1]=(invertedNote/12)-1;
if (pat->data[k][0]==0) {
pat->data[k][0]=12;
pat->data[k][1]--;
}
pat->newData[k][DIV_PAT_NOTE]=invertedNote+60;
}
}
@ -273,7 +267,7 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
logD("parsing volumes of pattern %d channel %d",i,j);
for (int k=0; k<256; k++) {
if (patDataBuf[k]==0) continue;
else pat->data[k][3]=0x60+patDataBuf[k];
else pat->newData[k][DIV_PAT_VOL]=0x60+patDataBuf[k];
}
// instrument
@ -282,7 +276,7 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
logD("parsing instruments of pattern %d channel %d",i,j);
for (int k=0; k<256; k++) {
if (patDataBuf[k]==0) continue;
pat->data[k][2]=info.insNumMaps[patDataBuf[k]-1];
pat->newData[k][DIV_PAT_INS]=info.insNumMaps[patDataBuf[k]-1];
}
// effects
@ -300,76 +294,76 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
case 0:
// arpeggio or no effect (if effect val is 0)
if (effectVal[k]==0) break;
pat->data[k][4+(l*2)]=effectNum[k];
pat->data[k][5+(l*2)]=effectVal[k];
pat->newData[k][DIV_PAT_FX(l)]=effectNum[k];
pat->newData[k][DIV_PAT_FXVAL(l)]=effectVal[k];
break;
case 1:
// pitch slide up
case 2:
// pitch slide down
pat->data[k][4+(l*2)]=effectNum[k];
pat->newData[k][DIV_PAT_FX(l)]=effectNum[k];
if (effectVal[k]) {
lastSlide=effectVal[k];
pat->data[k][5+(l*2)]=effectVal[k];
pat->newData[k][DIV_PAT_FXVAL(l)]=effectVal[k];
} else {
pat->data[k][5+(l*2)]=lastSlide;
pat->newData[k][DIV_PAT_FXVAL(l)]=lastSlide;
}
break;
case 3:
// portamento
case 4:
// vibrato
pat->data[k][5+(l*2)]=0;
pat->newData[k][DIV_PAT_FXVAL(l)]=0;
if (effectVal[k]&0xF0) {
pat->data[k][5+(l*2)]|=effectVal[k]&0xF0;
pat->newData[k][DIV_PAT_FXVAL(l)]|=effectVal[k]&0xF0;
} else {
pat->data[k][5+(l*2)]|=lastVibrato&0xF0;
pat->newData[k][DIV_PAT_FXVAL(l)]|=lastVibrato&0xF0;
}
if (effectVal[k]&0x0F) {
pat->data[k][5+(l*2)]|=effectVal[k]&0x0F;
pat->newData[k][DIV_PAT_FXVAL(l)]|=effectVal[k]&0x0F;
} else {
pat->data[k][5+(l*2)]|=lastVibrato&0x0F;
pat->newData[k][DIV_PAT_FXVAL(l)]|=lastVibrato&0x0F;
}
pat->data[k][4+(l*2)]=effectNum[k];
lastVibrato=pat->data[k][5+(l*2)];
pat->newData[k][DIV_PAT_FX(l)]=effectNum[k];
lastVibrato=pat->newData[k][DIV_PAT_FXVAL(l)];
break;
case 5:
// poramento + volume slide
pat->data[k][4+(l*2)]=0x06;
pat->data[k][5+(l*2)]=effectVal[k];
pat->newData[k][DIV_PAT_FX(l)]=0x06;
pat->newData[k][DIV_PAT_FXVAL(l)]=effectVal[k];
break;
case 6:
// vibrato + volume slide
pat->data[k][4+(l*2)]=0x05;
pat->data[k][5+(l*2)]=effectVal[k];
pat->newData[k][DIV_PAT_FX(l)]=0x05;
pat->newData[k][DIV_PAT_FXVAL(l)]=effectVal[k];
break;
case 8:
// modify TL of operator 1
pat->data[k][4+(l*2)]=0x12;
pat->data[k][5+(l*2)]=effectVal[k];
pat->newData[k][DIV_PAT_FX(l)]=0x12;
pat->newData[k][DIV_PAT_FXVAL(l)]=effectVal[k];
break;
case 9:
// modify TL of operator 2
pat->data[k][4+(l*2)]=0x13;
pat->data[k][5+(l*2)]=effectVal[k];
pat->newData[k][DIV_PAT_FX(l)]=0x13;
pat->newData[k][DIV_PAT_FXVAL(l)]=effectVal[k];
break;
case 10:
// volume slide
pat->data[k][4+(l*2)]=0xA;
pat->data[k][5+(l*2)]=effectVal[k];
pat->newData[k][DIV_PAT_FX(l)]=0xA;
pat->newData[k][DIV_PAT_FXVAL(l)]=effectVal[k];
break;
case 11:
// multi-frequency mode of CH3 control
// TODO
case 12:
// modify TL of operator 3
pat->data[k][4+(l*2)]=0x14;
pat->data[k][5+(l*2)]=effectVal[k];
pat->newData[k][DIV_PAT_FX(l)]=0x14;
pat->newData[k][DIV_PAT_FXVAL(l)]=effectVal[k];
break;
case 13:
// modify TL of operator 4
pat->data[k][4+(l*2)]=0x15;
pat->data[k][5+(l*2)]=effectVal[k];
pat->newData[k][DIV_PAT_FX(l)]=0x15;
pat->newData[k][DIV_PAT_FXVAL(l)]=effectVal[k];
break;
case 14:
switch (effectVal[k]>>4) {
@ -378,18 +372,18 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
case 2:
case 3:
// modify multiplier of operators
pat->data[k][4+(l*2)]=0x16;
pat->data[k][5+(l*2)]=((effectVal[k]&0xF0)+0x100)|(effectVal[k]&0xF);
pat->newData[k][DIV_PAT_FX(l)]=0x16;
pat->newData[k][DIV_PAT_FXVAL(l)]=((effectVal[k]&0xF0)+0x100)|(effectVal[k]&0xF);
break;
case 8:
// pan
pat->data[k][4+(l*2)]=0x80;
pat->newData[k][DIV_PAT_FX(l)]=0x80;
if ((effectVal[k]&0xF)==1) {
pat->data[k][5+(l*2)]=0;
pat->newData[k][DIV_PAT_FXVAL(l)]=0;
} else if ((effectVal[k]&0xF)==2) {
pat->data[k][5+(l*2)]=0xFF;
pat->newData[k][DIV_PAT_FXVAL(l)]=0xFF;
} else {
pat->data[k][5+(l*2)]=0x80;
pat->newData[k][DIV_PAT_FXVAL(l)]=0x80;
}
break;
}
@ -407,9 +401,9 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
speed.interleaveFactor=effectVal[k]&0xF;
} else if ((effectVal[k]>>4)==(effectVal[k]&0xF)) {
// if both speeds are equal
pat->data[k][4+(l*2)]=0x0F;
pat->newData[k][DIV_PAT_FX(l)]=0x0F;
unsigned char speedSet=effectVal[k]>>4;
pat->data[k][5+(l*2)]=speedSet;
pat->newData[k][DIV_PAT_FXVAL(l)]=speedSet;
break;
} else {
speed.speedEven=effectVal[k]>>4;
@ -418,8 +412,8 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
auto speedIndex = speeds.find(speed);
if (speedIndex != speeds.end()) {
pat->data[k][4+(l*2)]=0x09;
pat->data[k][5+(l*2)]=speedIndex->second;
pat->newData[k][DIV_PAT_FX(l)]=0x09;
pat->newData[k][DIV_PAT_FXVAL(l)]=speedIndex->second;
break;
}
if (speed.interleaveFactor>8) {
@ -437,8 +431,8 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
info.ds->grooves.push_back(groove);
speeds[speed]=speedGrooveIndex;
pat->data[k][4+(l*2)]=0x09;
pat->data[k][5+(l*2)]=speedGrooveIndex;
pat->newData[k][DIV_PAT_FX(l)]=0x09;
pat->newData[k][DIV_PAT_FXVAL(l)]=speedGrooveIndex;
speedGrooveIndex++;
break;
}
@ -447,8 +441,8 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
// put a "jump to next pattern" effect if the pattern is smaller than the maximum pattern length
if (info.patLens[i]!=0 && info.patLens[i]<info.ds->subsong[0]->patLen) {
pat->data[info.patLens[i]-1][4+(usedEffectsCol*4)]=0x0D;
pat->data[info.patLens[i]-1][5+(usedEffectsCol*4)]=0x00;
pat->newData[info.patLens[i]-1][DIV_PAT_FX(0)+(usedEffectsCol*4)]=0x0D;
pat->newData[info.patLens[i]-1][DIV_PAT_FXVAL(0)+(usedEffectsCol*4)]=0x00;
}
}
}
@ -473,28 +467,30 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
unsigned char truePatLen=(info.patLens[info.orderList[i]]<info.ds->subsong[0]->patLen) ? info.patLens[info.orderList[i]] : info.ds->subsong[0]->patLen;
// default instrument
if (i==0 && pat->data[0][2]==-1) pat->data[0][2]=0;
if (i==0 && pat->newData[0][DIV_PAT_INS]==-1) pat->newData[0][DIV_PAT_INS]=0;
for (int k=0; k<truePatLen; k++) {
if (chArpeggio[j] && pat->data[k][4+(l*2)]!=0x00 && pat->data[k][0]!=-1) {
pat->data[k][4+usedEffectsCol*2+(l*2)]=0x00;
pat->data[k][5+usedEffectsCol*2+(l*2)]=0;
// TODO: -1 check? does it still work after refactor?
if (chArpeggio[j] && pat->newData[k][DIV_PAT_FX(l)]!=0x00 && pat->newData[k][DIV_PAT_NOTE]!=-1) {
pat->newData[k][DIV_PAT_FX(usedEffectsCol)+(l*2)]=0x00;
pat->newData[k][DIV_PAT_FXVAL(usedEffectsCol)+(l*2)]=0;
chArpeggio[j]=false;
} else if (chPorta[j] && pat->data[k][4+(l*2)]!=0x03 && pat->data[k][4+(l*2)]!=0x01 && pat->data[k][4+(l*2)]!=0x02) {
pat->data[k][4+usedEffectsCol*2+(l*2)]=0x03;
pat->data[k][5+usedEffectsCol*2+(l*2)]=0;
} else if (chPorta[j] && pat->newData[k][DIV_PAT_FX(l)]!=0x03 && pat->newData[k][DIV_PAT_FX(l)]!=0x01 && pat->newData[k][DIV_PAT_FX(l)]!=0x02) {
pat->newData[k][DIV_PAT_FX(usedEffectsCol)+(l*2)]=0x03;
pat->newData[k][DIV_PAT_FXVAL(usedEffectsCol)+(l*2)]=0;
chPorta[j]=false;
} else if (chVibrato[j] && pat->data[k][4+(l*2)]!=0x04 && pat->data[k][0]!=-1) {
pat->data[k][4+usedEffectsCol*2+(l*2)]=0x04;
pat->data[k][5+usedEffectsCol*2+(l*2)]=0;
} else if (chVibrato[j] && pat->newData[k][DIV_PAT_FX(l)]!=0x04 && pat->newData[k][DIV_PAT_NOTE]!=-1) {
pat->newData[k][DIV_PAT_FX(usedEffectsCol)+(l*2)]=0x04;
pat->newData[k][DIV_PAT_FXVAL(usedEffectsCol)+(l*2)]=0;
chVibrato[j]=false;
} else if (chVolumeSlide[j] && pat->data[k][4+(l*2)]!=0x0A) {
pat->data[k][4+usedEffectsCol*2+(l*2)]=0x0A;
pat->data[k][5+usedEffectsCol*2+(l*2)]=0;
} else if (chVolumeSlide[j] && pat->newData[k][DIV_PAT_FX(l)]!=0x0A) {
pat->newData[k][DIV_PAT_FX(usedEffectsCol)+(l*2)]=0x0A;
pat->newData[k][DIV_PAT_FXVAL(usedEffectsCol)+(l*2)]=0;
chVolumeSlide[j]=false;
}
switch (pat->data[k][4+l]) {
// TODO: looks like we have a bug here! it should be DIV_PAT_FX(l), right?
switch (pat->newData[k][DIV_PAT_FX(0)+l]) {
case 0:
chArpeggio[j]=true;
break;
@ -527,16 +523,16 @@ void TFMParsePattern(struct TFMParsePatternInfo info) {
lastPat->copyOn(newPat);
info.ds->subsong[0]->orders.ord[i][info.ds->subsong[0]->ordersLen - 1] = info.maxPat;
newPat->data[info.patLens[lastPatNum]-1][4+(usedEffectsCol*4)] = 0x0B;
newPat->data[info.patLens[lastPatNum]-1][5+(usedEffectsCol*4)] = info.loopPos;
newPat->newData[info.patLens[lastPatNum]-1][DIV_PAT_FX(usedEffectsCol*2)] = 0x0B;
newPat->newData[info.patLens[lastPatNum]-1][DIV_PAT_FXVAL(usedEffectsCol*2)] = info.loopPos;
info.ds->subsong[0]->pat[i].data[info.maxPat] = newPat;
}
} else {
for (int i=0;i<6;i++) {
int lastPatNum=info.ds->subsong[0]->orders.ord[i][info.ds->subsong[0]->ordersLen - 1];
DivPattern* lastPat=info.ds->subsong[0]->pat[i].getPattern(lastPatNum, false);
lastPat->data[info.patLens[lastPatNum]-1][4+(usedEffectsCol*4)] = 0x0B;
lastPat->data[info.patLens[lastPatNum]-1][5+(usedEffectsCol*4)] = info.loopPos;
lastPat->newData[info.patLens[lastPatNum]-1][DIV_PAT_FX(usedEffectsCol*2)] = 0x0B;
lastPat->newData[info.patLens[lastPatNum]-1][DIV_PAT_FXVAL(usedEffectsCol*2)] = info.loopPos;
}
}
}

View file

@ -251,7 +251,7 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
unsigned short totalChans=reader.readS();
unsigned short patCount=reader.readS();
ds.insLen=(unsigned short)reader.readS();
ds.linearPitch=(reader.readS()&1)?2:0;
ds.linearPitch=(reader.readS()&1)?1:0;
ds.subsong[0]->speeds.val[0]=reader.readS();
ds.subsong[0]->speeds.len=1;
double bpm=(unsigned short)reader.readS();
@ -325,7 +325,9 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
for (unsigned short i=0; i<patCount; i++) {
logV("pattern %d",i);
headerSeek=reader.tell();
headerSeek+=reader.readI();
unsigned int headerSeekAdd=reader.readI();
logV("seek is %d",headerSeekAdd);
headerSeek+=headerSeekAdd;
unsigned char packType=reader.readC();
if (packType!=0) {
@ -350,7 +352,10 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
return false;
}
unsigned int packedSeek=headerSeek+(unsigned short)reader.readS();
unsigned short packedSize=reader.readS();
logV("packed size: %d",packedSize);
unsigned int packedSeek=headerSeek+packedSize;
logV("seeking to %x...",headerSeek);
if (!reader.seek(headerSeek,SEEK_SET)) {
@ -363,6 +368,10 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
// read data
for (int j=0; j<totalRows; j++) {
if (reader.tell()>=packedSeek) {
logV("end of data - stopping here...");
break;
}
for (int k=0; k<totalChans; k++) {
unsigned char note=reader.readC();
unsigned char vol=0;
@ -777,7 +786,7 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
bool mustCommitInitial=true;
memset(effectCol,4,128);
memset(effectCol,0,128);
memset(vibStatus,0,128);
memset(vibStatusChanged,0,128*sizeof(bool));
memset(vibing,0,128*sizeof(bool));
@ -836,7 +845,10 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
return false;
}
unsigned int packedSeek=headerSeek+(unsigned short)reader.readS();
unsigned short packedSize=reader.readS();
logV("packed size: %d",packedSize);
unsigned int packedSeek=headerSeek+packedSize;
logV("seeking to %x...",headerSeek);
if (!reader.seek(headerSeek,SEEK_SET)) {
@ -849,6 +861,10 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
// read data
for (int j=0; j<totalRows; j++) {
if (reader.tell()>=packedSeek) {
logV("end of data - stopping here...");
break;
}
for (int k=0; k<totalChans; k++) {
DivPattern* p=ds.subsong[0]->pat[k].getPattern(i,true);
@ -885,32 +901,26 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
if (note!=0) {
lastNote[k]=note;
if (note>96) {
p->data[j][0]=101;
p->data[j][1]=0;
p->newData[j][DIV_PAT_NOTE]=DIV_NOTE_REL;
} else {
note--;
p->data[j][0]=note%12;
p->data[j][1]=note/12;
if (p->data[j][0]==0) {
p->data[j][0]=12;
p->data[j][1]=(unsigned char)(p->data[j][1]-1);
}
p->newData[j][DIV_PAT_NOTE]=note+60;
}
}
}
if (hasIns) {
ins=reader.readC();
p->data[j][2]=((int)ins)-1;
p->newData[j][DIV_PAT_INS]=((int)ins)-1;
// default volume
if (lastNote[k]<96 && ins>0) {
p->data[j][3]=sampleVol[(((ins-1)&255)<<8)|(noteMap[(((ins-1)&255)<<7)|(lastNote[k]&127)])];
p->newData[j][DIV_PAT_VOL]=sampleVol[(((ins-1)&255)<<8)|(noteMap[(((ins-1)&255)<<7)|(lastNote[k]&127)])];
}
writePanning=true;
}
if (hasVol) {
vol=reader.readC();
if (vol>=0x10 && vol<=0x50) {
p->data[j][3]=vol-0x10;
p->newData[j][DIV_PAT_VOL]=vol-0x10;
} else { // effects in volume column
switch (vol>>4) {
case 0x6: // vol slide down
@ -970,11 +980,11 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
vibing[k]=true;
break;
case 0xc: // panning
p->data[j][effectCol[k]++]=0x80;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x80;
if ((vol&15)==8) {
p->data[j][effectCol[k]++]=0x80;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x80;
} else {
p->data[j][effectCol[k]++]=(vol&15)|((vol&15)<<4);
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=(vol&15)|((vol&15)<<4);
}
writePanning=false;
break;
@ -1097,14 +1107,14 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
treming[k]=true;
break;
case 8: // panning
p->data[j][effectCol[k]++]=0x80;
p->data[j][effectCol[k]++]=effectVal;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x80;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=effectVal;
writePanning=false;
break;
case 9: // offset
if (hasNote) {
p->data[j][effectCol[k]++]=0x91;
p->data[j][effectCol[k]++]=effectVal;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x91;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=effectVal;
}
break;
case 0xa: // vol slide
@ -1118,26 +1128,43 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
volSliding[k]=true;
break;
case 0xb: // go to order
p->data[j][effectCol[k]++]=0x0b;
p->data[j][effectCol[k]++]=effectVal;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x0b;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=effectVal;
break;
case 0xc: // set volume
p->data[j][3]=effectVal;
p->newData[j][DIV_PAT_VOL]=effectVal;
break;
case 0xd: // next order
p->data[j][effectCol[k]++]=0x0d;
p->data[j][effectCol[k]++]=effectVal;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x0d;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=effectVal;
break;
case 0xe: // special...
// TODO: implement the rest
switch (effectVal>>4) {
case 0x5:
p->data[j][effectCol[k]++]=0xe5;
p->data[j][effectCol[k]++]=(effectVal&15)<<4;
case 0x4: // vibrato waveform
switch (effectVal&3) {
case 0x0: // sine
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xe3;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x00;
break;
case 0x1: // ramp down
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xe3;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x05;
break;
case 0x2: // square
case 0x3:
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xe3;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x06;
break;
}
break;
case 0x5: // fine tune
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xe5;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=(effectVal&15)<<4;
break;
case 0x9:
p->data[j][effectCol[k]++]=0x0c;
p->data[j][effectCol[k]++]=(effectVal&15);
case 0x9: // retrigger
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x0c;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=(effectVal&15);
break;
case 0xa: // vol slide up (fine)
volSlideStatus[k]=((effectVal&15)<<4)|0xf;
@ -1155,33 +1182,33 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
}
volSliding[k]=true;
break;
case 0xc:
p->data[j][effectCol[k]++]=0xdc;
p->data[j][effectCol[k]++]=MAX(1,effectVal&15);
case 0xc: // note cut
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xdc;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=MAX(1,effectVal&15);
break;
case 0xd:
p->data[j][effectCol[k]++]=0xed;
p->data[j][effectCol[k]++]=MAX(1,effectVal&15);
case 0xd: // note delay
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xed;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=MAX(1,effectVal&15);
break;
}
break;
case 0xf: // speed/tempo
if (effectVal>=0x20) {
p->data[j][effectCol[k]++]=0xf0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xf0;
} else if (effectVal==0) {
p->data[j][effectCol[k]++]=0xff;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xff;
} else {
p->data[j][effectCol[k]++]=0x0f;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x0f;
}
p->data[j][effectCol[k]++]=effectVal;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=effectVal;
break;
case 0x10: // G: global volume (!)
break;
case 0x11: // H: global volume slide (!)
break;
case 0x14: // K: key off
p->data[j][effectCol[k]++]=0xe7;
p->data[j][effectCol[k]++]=effectVal;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xe7;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=effectVal;
break;
case 0x15: // L: set envelope position (!)
break;
@ -1193,8 +1220,8 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
panSliding[k]=true;
break;
case 0x1b: // R: retrigger
p->data[j][effectCol[k]++]=0x0c;
p->data[j][effectCol[k]++]=effectVal;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x0c;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=effectVal;
break;
case 0x1d: // T: tremor (!)
break;
@ -1211,8 +1238,8 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
}
if (writePanning && hasNote && note<96 && ins>0) {
p->data[j][effectCol[k]++]=0x80;
p->data[j][effectCol[k]++]=samplePan[(((ins-1)&255)<<8)|(noteMap[(((ins-1)&255)<<7)|(note&127)])];
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x80;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=samplePan[(((ins-1)&255)<<8)|(noteMap[(((ins-1)&255)<<7)|(note&127)])];
}
}
@ -1220,80 +1247,80 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
for (int k=0; k<totalChans; k++) {
DivPattern* p=ds.subsong[0]->pat[k].getPattern(i,true);
if (vibing[k]!=vibingOld[k] || vibStatusChanged[k]) {
p->data[j][effectCol[k]++]=0x04;
p->data[j][effectCol[k]++]=vibing[k]?vibStatus[k]:0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x04;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=vibing[k]?vibStatus[k]:0;
doesVibrato[k]=true;
} else if (doesVibrato[k] && mustCommitInitial) {
p->data[j][effectCol[k]++]=0x04;
p->data[j][effectCol[k]++]=0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x04;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0;
}
if (volSliding[k]!=volSlidingOld[k] || volSlideStatusChanged[k]) {
if (volSlideStatus[k]>=0xf1 && volSliding[k]) {
p->data[j][effectCol[k]++]=0xf9;
p->data[j][effectCol[k]++]=volSlideStatus[k]&15;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xf9;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=volSlideStatus[k]&15;
volSliding[k]=false;
} else if ((volSlideStatus[k]&15)==15 && volSlideStatus[k]>=0x10 && volSliding[k]) {
p->data[j][effectCol[k]++]=0xf8;
p->data[j][effectCol[k]++]=volSlideStatus[k]>>4;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xf8;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=volSlideStatus[k]>>4;
volSliding[k]=false;
} else {
p->data[j][effectCol[k]++]=0xfa;
p->data[j][effectCol[k]++]=volSliding[k]?volSlideStatus[k]:0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xfa;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=volSliding[k]?volSlideStatus[k]:0;
}
doesVolSlide[k]=true;
} else if (doesVolSlide[k] && mustCommitInitial) {
p->data[j][effectCol[k]++]=0xfa;
p->data[j][effectCol[k]++]=0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0xfa;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0;
}
if (porting[k]!=portingOld[k] || portaStatusChanged[k]) {
p->data[j][effectCol[k]++]=portaType[k];
p->data[j][effectCol[k]++]=porting[k]?portaStatus[k]:0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=portaType[k];
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=porting[k]?portaStatus[k]:0;
doesPitchSlide[k]=true;
} else if (doesPitchSlide[k] && mustCommitInitial) {
p->data[j][effectCol[k]++]=0x01;
p->data[j][effectCol[k]++]=0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x01;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0;
}
if (arping[k]!=arpingOld[k] || arpStatusChanged[k]) {
p->data[j][effectCol[k]++]=0x00;
p->data[j][effectCol[k]++]=arping[k]?arpStatus[k]:0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x00;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=arping[k]?arpStatus[k]:0;
doesArp[k]=true;
} else if (doesArp[k] && mustCommitInitial) {
p->data[j][effectCol[k]++]=0x00;
p->data[j][effectCol[k]++]=0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x00;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0;
}
if (treming[k]!=tremingOld[k] || tremStatusChanged[k]) {
p->data[j][effectCol[k]++]=0x07;
p->data[j][effectCol[k]++]=treming[k]?tremStatus[k]:0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x07;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=treming[k]?tremStatus[k]:0;
doesTremolo[k]=true;
} else if (doesTremolo[k] && mustCommitInitial) {
p->data[j][effectCol[k]++]=0x07;
p->data[j][effectCol[k]++]=0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x07;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0;
}
if (panning[k]!=panningOld[k] || panStatusChanged[k]) {
p->data[j][effectCol[k]++]=0x84;
p->data[j][effectCol[k]++]=panning[k]?panStatus[k]:0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x84;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=panning[k]?panStatus[k]:0;
doesPanbrello[k]=true;
} else if (doesPanbrello[k] && mustCommitInitial) {
p->data[j][effectCol[k]++]=0x84;
p->data[j][effectCol[k]++]=0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x84;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0;
}
if (panSliding[k]!=panSlidingOld[k] || panSlideStatusChanged[k]) {
p->data[j][effectCol[k]++]=0x83;
p->data[j][effectCol[k]++]=panSliding[k]?panSlideStatus[k]:0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x83;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=panSliding[k]?panSlideStatus[k]:0;
doesPanSlide[k]=true;
} else if (doesPanSlide[k] && mustCommitInitial) {
p->data[j][effectCol[k]++]=0x83;
p->data[j][effectCol[k]++]=0;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0x83;
p->newData[j][DIV_PAT_FX(0)+effectCol[k]++]=0;
}
if ((effectCol[k]>>1)-2>ds.subsong[0]->pat[k].effectCols) {
ds.subsong[0]->pat[k].effectCols=(effectCol[k]>>1)-1;
if ((effectCol[k]>>1)>=ds.subsong[0]->pat[k].effectCols) {
ds.subsong[0]->pat[k].effectCols=(effectCol[k]>>1)+1;
}
}
@ -1313,14 +1340,14 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
if (j==totalRows-1) {
// place end of pattern marker
DivPattern* p=ds.subsong[0]->pat[0].getPattern(i,true);
p->data[j][effectCol[0]++]=0x0d;
p->data[j][effectCol[0]++]=0;
p->newData[j][DIV_PAT_FX(0)+effectCol[0]++]=0x0d;
p->newData[j][DIV_PAT_FX(0)+effectCol[0]++]=0;
if ((effectCol[0]>>1)-2>ds.subsong[0]->pat[0].effectCols) {
ds.subsong[0]->pat[0].effectCols=(effectCol[0]>>1)-1;
if ((effectCol[0]>>1)>=ds.subsong[0]->pat[0].effectCols) {
ds.subsong[0]->pat[0].effectCols=(effectCol[0]>>1)+1;
}
}
memset(effectCol,4,64);
memset(effectCol,0,64);
}
logV("seeking to %x...",packedSeek);

View file

@ -27,6 +27,7 @@ enum DivInsFormats {
DIV_INSFORMAT_DMP,
DIV_INSFORMAT_TFI,
DIV_INSFORMAT_VGI,
DIV_INSFORMAT_EIF,
DIV_INSFORMAT_FTI,
DIV_INSFORMAT_BTI,
DIV_INSFORMAT_S3I,
@ -494,6 +495,48 @@ void DivEngine::loadVGI(SafeReader& reader, std::vector<DivInstrument*>& ret, St
ret.push_back(ins);
}
void DivEngine::loadEIF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins=new DivInstrument;
try {
unsigned char bytes[29];
reader.seek(0,SEEK_SET);
ins->type=DIV_INS_FM;
ins->name=stripPath;
for (int i=0; i<29; i++) {
bytes[i] = reader.readC();
}
ins->fm.alg=bytes[0]&0x07;
ins->fm.fb=(bytes[0]>>3)&0x07;
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=ins->fm.op[i];
op.mult=bytes[1+i]&0x0F;
op.dt=(bytes[1+i]>>4)&0x07;
op.tl=bytes[5+i]&0x7F;
op.rs=(bytes[9+i]>>6)&0x03;
op.ar=bytes[9+i]&0x1F;
op.dr=bytes[13+i]&0x1F;
op.am=(bytes[13+i]&0x80)?1:0;
op.d2r=bytes[17+i]&0x1F;
op.rr=bytes[21+i]&0x0F;
op.sl=(bytes[21+i]>>4)&0x0F;
op.ssgEnv=bytes[25+i]&0x0F;
}
} catch (EndOfFileException& e) {
lastError="premature end of file";
logE("premature end of file");
delete ins;
return;
}
ret.push_back(ins);
}
void DivEngine::loadS3I(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath) {
DivInstrument* ins=new DivInstrument;
try {
@ -1971,6 +2014,8 @@ std::vector<DivInstrument*> DivEngine::instrumentFromFile(const char* path, bool
format=DIV_INSFORMAT_TFI;
} else if (extS==".vgi") {
format=DIV_INSFORMAT_VGI;
} else if (extS==".eif") {
format=DIV_INSFORMAT_EIF;
} else if (extS==".fti") {
format=DIV_INSFORMAT_FTI;
} else if (extS==".bti") {
@ -2015,6 +2060,9 @@ std::vector<DivInstrument*> DivEngine::instrumentFromFile(const char* path, bool
case DIV_INSFORMAT_VGI:
loadVGI(reader,ret,stripPath);
break;
case DIV_INSFORMAT_EIF:
loadEIF(reader,ret,stripPath);
break;
case DIV_INSFORMAT_FTI: // TODO
break;
case DIV_INSFORMAT_BTI: // TODO

View file

@ -309,18 +309,22 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
logD("sample is 8-bit unsigned");
buf=new unsigned char[si.channels*si.frames];
sampleLen=sizeof(unsigned char);
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
logD("sample is 32-bit float");
buf=new float[si.channels*si.frames];
sampleLen=sizeof(float);
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_DOUBLE) {
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_DOUBLE) {
logD("sample is 64-bit float");
buf=new float[si.channels*si.frames];
buf=new double[si.channels*si.frames];
sampleLen=sizeof(double);
} else {
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_16) {
logD("sample is 16-bit signed");
buf=new short[si.channels*si.frames];
sampleLen=sizeof(short);
} else {
logD("sample is in a different format - reading as floats");
buf=new float[si.channels*si.frames];
sampleLen=sizeof(float);
}
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8 ||
(si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT ||
@ -328,10 +332,14 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) {
logW("sample read size mismatch!");
}
} else {
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_16) {
if (sf_read_short(f,(short*)buf,si.frames*si.channels)!=(si.frames*si.channels)) {
logW("sample read size mismatch!");
}
} else {
if (sf_read_float(f,(float*)buf,si.frames*si.channels)!=(si.frames*si.channels)) {
logW("sample read size mismatch!");
}
}
DivSample* sample=new DivSample;
int sampleCount=(int)song.sample.size();
@ -354,20 +362,7 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
sample->data8[index++]=averaged;
}
delete[] (unsigned char*)buf;
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
float averaged=0.0f;
for (int j=0; j<si.channels; j++) {
averaged+=((float*)buf)[i+j];
}
averaged/=si.channels;
averaged*=32767.0;
if (averaged<-32768.0) averaged=-32768.0;
if (averaged>32767.0) averaged=32767.0;
sample->data16[index++]=averaged;
}
delete[] (float*)buf;
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_DOUBLE) {
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_DOUBLE) {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
double averaged=0.0f;
for (int j=0; j<si.channels; j++) {
@ -380,7 +375,7 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
sample->data16[index++]=averaged;
}
delete[] (double*)buf;
} else {
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_16) {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0;
for (int j=0; j<si.channels; j++) {
@ -390,6 +385,19 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
sample->data16[index++]=averaged;
}
delete[] (short*)buf;
} else {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
float averaged=0.0f;
for (int j=0; j<si.channels; j++) {
averaged+=((float*)buf)[i+j];
}
averaged/=si.channels;
averaged*=32767.0;
if (averaged<-32768.0) averaged=-32768.0;
if (averaged>32767.0) averaged=32767.0;
sample->data16[index++]=averaged;
}
delete[] (float*)buf;
}
sample->rate=si.samplerate;
@ -402,13 +410,12 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
{
// There's no documentation on libsndfile detune range, but the code
// implies -50..50. Yet when loading a file you can get a >50 value.
// disabled for now
/*
if(inst.detune > 50)
inst.detune = inst.detune - 100;
short pitch = ((0x3c-inst.basenote)*100) + inst.detune;
sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0));
*/
if (getConfInt("sampleImportInstDetune", 0)) {
if(inst.detune > 50)
inst.detune = inst.detune - 100;
short pitch = ((0x3c-inst.basenote)*100) + inst.detune;
sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0));
}
if(inst.loop_count && inst.loops[0].mode >= SF_LOOP_FORWARD)
{
sample->loop=true;

View file

@ -1893,7 +1893,7 @@ void DivInstrument::readFeatureMA(SafeReader& reader, short version) {
target=&std.ex10Macro;
break;
default:
logW("invalid macro code %d!");
logW("invalid macro code %d!", macroCode);
break;
}

View file

@ -45,7 +45,7 @@ std::vector<std::pair<int,int>> DivChannelData::optimize() {
for (int j=0; j<DIV_MAX_PATTERNS; j++) {
if (j==i) continue;
if (data[j]==NULL) continue;
if (memcmp(data[i]->data,data[j]->data,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short))==0) {
if (memcmp(data[i]->newData,data[j]->newData,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short))==0) {
delete data[j];
data[j]=NULL;
logV("%d == %d",i,j);
@ -86,15 +86,11 @@ void DivChannelData::wipePatterns() {
void DivPattern::copyOn(DivPattern* dest) {
dest->name=name;
memcpy(dest->data,data,sizeof(data));
memcpy(dest->newData,newData,sizeof(newData));
}
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;
}
memset(newData,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short));
}
DivChannelData::DivChannelData():

View file

@ -22,7 +22,22 @@
struct DivPattern {
String name;
short data[DIV_MAX_ROWS][DIV_MAX_COLS];
/**
* pattern data is stored in this order:
* - 0 note: 0 (min) is C-(-5), 60 is C-0, and 179 (max) is B-9.
252 is null/bug, 253 is note off, 254 is note release and
255 is macro release.
* - 1 instrument
* - 2 volume
* - 3 effect
* - 4 effect value
* - 5... (the rest of effects/effect values)
*
* use the DIV_PAT_* macros in defines.h for convenience.
*
* if a cell is -1, it means "empty".
*/
short newData[DIV_MAX_ROWS][DIV_MAX_COLS];
/**
* clear the pattern.
@ -39,14 +54,10 @@ struct DivPattern {
struct DivChannelData {
unsigned char effectCols;
// data goes as follows: data[ROW][TYPE]
// TYPE is:
// 0: note
// 1: octave
// 2: instrument
// 3: volume
// 4-5+: effect/effect value
// do NOT access directly unless you know what you're doing!
/**
* do NOT access directly unless you know what you're doing!
* use getPattern() instead.
*/
DivPattern* data[DIV_MAX_PATTERNS];
/**

View file

@ -160,7 +160,15 @@ void DivDispatch::notifyInsChange(int ins) {
}
void DivDispatch::notifyWaveChange(int ins) {
void DivDispatch::notifyWaveChange(int wave) {
}
void DivDispatch::notifySampleChange(int sample) {
}
void DivDispatch::notifyInsAddition(int sysID) {
}
@ -213,6 +221,14 @@ size_t DivDispatch::getSampleMemUsage(int index) {
return 0;
}
bool DivDispatch::hasSamplePtrHeader(int index) {
return false;
}
size_t DivDispatch::getSampleMemOffset(int index) {
return 0;
}
const DivMemoryComposition* DivDispatch::getMemCompo(int index) {
return NULL;
}

View file

@ -166,17 +166,8 @@ void DivPlatformAY8910::runTFX(int runRate, int advance) {
if (runRate!=0) counterRatio=(double)rate/(double)runRate;
int timerPeriod, output;
for (int i=0; i<3; i++) {
if (chan[i].active && (chan[i].curPSGMode.val&16) && !(chan[i].curPSGMode.val&8) && chan[i].tfx.mode!=-1) {
if (chan[i].active && (chan[i].curPSGMode.val&16) && !(chan[i].curPSGMode.val&8)) {
if (chan[i].tfx.mode == -1 && !isMuted[i]) {
/*
bug: if in the timer FX macro the user enables
and then disables PWM while there is no volume macro
there is now a random chance that the resulting output
is silent or has volume set incorrectly
i've tried to implement a fix, but it seems to be
ineffective, so...
TODO: actually implement a proper fix
*/
if (intellivision && chan[i].curPSGMode.getEnvelope()) {
immWrite(0x08+i,(chan[i].outVol&0xc)<<2);
continue;
@ -186,12 +177,40 @@ void DivPlatformAY8910::runTFX(int runRate, int advance) {
}
}
chan[i].tfx.counter += counterRatio;
if (chan[i].tfx.counter >= chan[i].tfx.period && chan[i].tfx.mode == 0) {
if (chan[i].tfx.counter >= chan[i].tfx.period) {
chan[i].tfx.counter -= chan[i].tfx.period;
chan[i].tfx.out ^= 1;
switch (chan[i].tfx.mode) {
case 0:
// pwm
// we will handle the modulator gen after this switch... if we don't, crackling happens
chan[i].tfx.out ^= 1;
break;
case 1:
// syncbuzzer
if (!isMuted[i]) {
if (intellivision && chan[i].curPSGMode.getEnvelope()) {
immWrite(0x08 + i, (chan[i].outVol & 0xc) << 2);
}
else {
immWrite(0x08 + i, (chan[i].outVol & 15) | ((chan[i].curPSGMode.getEnvelope()) << 2));
}
}
if (intellivision && selCore) {
immWrite(0xa, ayEnvMode);
}
else {
immWrite(0xd, ayEnvMode);
}
break;
case 2:
default:
// unimplemented, or invalid effects here
break;
}
}
if (chan[i].tfx.mode == 0) {
// pwm
output = ((chan[i].tfx.out) ? chan[i].outVol : (chan[i].tfx.lowBound-(15-chan[i].outVol)));
// TODO: fix this stupid crackling noise that happens
// everytime the volume changes
output = (output <= 0) ? 0 : output; // underflow
output = (output >= 15) ? 15 : output; // overflow
output &= 15; // i don't know if i need this but i'm too scared to remove it
@ -204,20 +223,6 @@ void DivPlatformAY8910::runTFX(int runRate, int advance) {
}
}
}
if (chan[i].tfx.counter >= chan[i].tfx.period && chan[i].tfx.mode == 1) {
chan[i].tfx.counter -= chan[i].tfx.period;
if (!isMuted[i]) {
// TODO: ???????
if (intellivision && selCore) {
immWrite(0xa, ayEnvMode);
} else {
immWrite(0xd, ayEnvMode);
}
}
}
if (chan[i].tfx.counter >= chan[i].tfx.period && chan[i].tfx.mode == 2) {
chan[i].tfx.counter -= chan[i].tfx.period;
}
}
if (chan[i].tfx.num > 0) {
timerPeriod = chan[i].freq*chan[i].tfx.den/chan[i].tfx.num;

View file

@ -23,7 +23,7 @@
#include <math.h>
#define PITCH_OFFSET ((double)(16*2048*(chanMax+1)))
#define NOTE_ES5506(c,note) ((amigaPitch && parent->song.linearPitch!=2)?parent->calcBaseFreq(COLOR_NTSC*16,chan[c].pcm.freqOffs,note,true):parent->calcBaseFreq(chipClock,chan[c].pcm.freqOffs,note,false))
#define NOTE_ES5506(c,note) ((amigaPitch && !parent->song.linearPitch)?parent->calcBaseFreq(COLOR_NTSC*16,chan[c].pcm.freqOffs,note,true):parent->calcBaseFreq(chipClock,chan[c].pcm.freqOffs,note,false))
#define rWrite(a,...) {if(!skipRegisterWrites) {hostIntf32.push_back(QueuedHostIntf(4,(a),__VA_ARGS__)); }}
#define immWrite(a,...) {hostIntf32.push_back(QueuedHostIntf(4,(a),__VA_ARGS__));}
@ -603,7 +603,7 @@ void DivPlatformES5506::tick(bool sysTick) {
const unsigned int length=s->samples-1;
const unsigned int end=start+(length<<11);
const unsigned int nextBank=(offES5506>>22)&3;
const double nextFreqOffs=((amigaPitch && parent->song.linearPitch!=2)?16:PITCH_OFFSET)*off;
const double nextFreqOffs=((amigaPitch && !parent->song.linearPitch)?16:PITCH_OFFSET)*off;
chan[i].pcm.loopMode=loopMode;
chan[i].pcm.bank=nextBank;
chan[i].pcm.start=start;
@ -746,7 +746,7 @@ void DivPlatformES5506::tick(bool sysTick) {
chan[i].pcm.nextPos=0;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (amigaPitch && parent->song.linearPitch!=2) {
if (amigaPitch && !parent->song.linearPitch) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch*16,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,2,chan[i].pitch2*16,16*COLOR_NTSC,chan[i].pcm.freqOffs);
chan[i].freq=PITCH_OFFSET*(COLOR_NTSC/chan[i].freq)/(chipClock/16.0);
chan[i].freq=CLAMP(chan[i].freq,0,0x1ffff);
@ -767,7 +767,7 @@ void DivPlatformES5506::tick(bool sysTick) {
}
chan[i].pcm.loopStart=(chan[i].pcm.start+(s->loopStart<<11))&0xfffff800;
chan[i].pcm.loopEnd=(chan[i].pcm.start+((s->loopEnd)<<11))&0xffffff80;
chan[i].pcm.freqOffs=((amigaPitch && parent->song.linearPitch!=2)?16:PITCH_OFFSET)*off;
chan[i].pcm.freqOffs=((amigaPitch && !parent->song.linearPitch)?16:PITCH_OFFSET)*off;
unsigned int startPos=chan[i].pcm.direction?chan[i].pcm.end:chan[i].pcm.start;
if (chan[i].pcm.nextPos) {
const unsigned int start=chan[i].pcm.start;
@ -1212,7 +1212,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
int nextFreq=chan[c.chan].baseFreq;
int destFreq=NOTE_ES5506(c.chan,c.value2+chan[c.chan].sampleNoteDelta);
bool return2=false;
if (amigaPitch && parent->song.linearPitch!=2) {
if (amigaPitch && !parent->song.linearPitch) {
c.value*=16;
}
if (destFreq>nextFreq) {
@ -1433,6 +1433,10 @@ size_t DivPlatformES5506::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
size_t DivPlatformES5506::getSampleMemOffset(int index) {
return index == 0 ? 128 : 0;
}
bool DivPlatformES5506::isSampleLoaded(int index, int sample) {
if (index!=0) return false;
if (sample<0 || sample>32767) return false;
@ -1452,7 +1456,7 @@ void DivPlatformES5506::renderSamples(int sysID) {
memCompo=DivMemoryComposition();
memCompo.name="Sample Memory";
size_t memPos=128; // add silent at begin and end of each bank for reverse playback and add 1 for loop
size_t memPos=getSampleMemOffset(); // add silent at begin and end of each bank for reverse playback and add 1 for loop
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
if (!s->renderOn[0][sysID]) {
@ -1462,18 +1466,18 @@ void DivPlatformES5506::renderSamples(int sysID) {
unsigned int length=s->length16;
// fit sample size to single bank size
if (length>(4194304-128)) {
length=4194304-128;
if (length>(4194304-getSampleMemOffset())) {
length=4194304-getSampleMemOffset();
}
if ((memPos&0xc00000)!=((memPos+length+128)&0xc00000)) {
memPos=((memPos+0x3fffff)&0xffc00000)+128;
if ((memPos&0xc00000)!=((memPos+length+getSampleMemOffset())&0xc00000)) {
memPos=((memPos+0x3fffff)&0xffc00000)+getSampleMemOffset();
}
if (memPos>=(getSampleMemCapacity()-128)) {
if (memPos>=(getSampleMemCapacity()-getSampleMemOffset())) {
logW("out of ES5506 memory for sample %d!",i);
break;
}
if (memPos+length>=(getSampleMemCapacity()-128)) {
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,(getSampleMemCapacity()-128)-memPos);
if (memPos+length>=(getSampleMemCapacity()-getSampleMemOffset())) {
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,(getSampleMemCapacity()-getSampleMemOffset())-memPos);
logW("out of ES5506 memory for sample %d!",i);
} else {
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,length);

View file

@ -317,6 +317,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
virtual const void* getSampleMem(int index = 0) override;
virtual size_t getSampleMemCapacity(int index = 0) override;
virtual size_t getSampleMemUsage(int index = 0) override;
virtual size_t getSampleMemOffset(int index = 0) override;
virtual bool isSampleLoaded(int index, int sample) override;
virtual const DivMemoryComposition* getMemCompo(int index) override;
virtual void renderSamples(int sysID) override;

View file

@ -337,7 +337,7 @@ void DivPlatformESFM::tick(bool sysTick) {
if (chan[i].freqChanged) {
int mul=2;
int fixedBlock=chan[i].state.fm.block;
if (parent->song.linearPitch!=2) {
if (!parent->song.linearPitch) {
mul=octave(chan[i].baseFreq,fixedBlock)*2;
}
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,mul,chan[i].pitch2,chipClock,CHIP_FREQBASE);
@ -569,7 +569,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
bool return2=false;
int mul=1;
int fixedBlock=0;
if (parent->song.linearPitch!=2) {
if (!parent->song.linearPitch) {
fixedBlock=chan[c.chan].state.fm.block;
mul=octave(chan[c.chan].baseFreq,fixedBlock);
}
@ -586,7 +586,7 @@ int DivPlatformESFM::dispatch(DivCommand c) {
return2=true;
}
}
if (!chan[c.chan].portaPause && parent->song.linearPitch!=2) {
if (!chan[c.chan].portaPause && !parent->song.linearPitch) {
if (mul!=octave(newFreq,fixedBlock)) {
chan[c.chan].portaPause=true;
break;

View file

@ -214,6 +214,13 @@ class DivPlatformOPN: public DivPlatformFMBase {
return DivPlatformFMBase::mapVelocity(ch,vel);
}
virtual float getGain(int ch, int vol) {
if (extSys) {
if (ch>=extChanOffs+4) {
ch-=4;
} else if (ch>=extChanOffs) {
ch=extChanOffs;
}
}
if (vol==0) return 0;
if (ch==csmChan) return 1;
if (ch==adpcmBChanOffs) return (float)vol/255.0;

View file

@ -894,7 +894,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
for (int i=0; i<csmChan; i++) {
if (i==2 && extMode) continue;
if (chan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
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,11,chan[i].state.block);
} else {
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
@ -1223,7 +1223,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
int destFreq=NOTE_FREQUENCY(c.value2+chan[c.chan].sampleNoteDelta);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {

View file

@ -169,7 +169,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
@ -656,7 +656,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
unsigned char hardResetMask=0;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,2,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,2,opChan[i].pitch2);

View file

@ -468,6 +468,10 @@ size_t DivPlatformK053260::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
size_t DivPlatformK053260::getSampleMemOffset(int index) {
return index == 0 ? 1 : 0;
}
bool DivPlatformK053260::isSampleLoaded(int index, int sample) {
if (index!=0) return false;
if (sample<0 || sample>32767) return false;
@ -487,7 +491,7 @@ void DivPlatformK053260::renderSamples(int sysID) {
memCompo=DivMemoryComposition();
memCompo.name="Sample ROM";
size_t memPos=1; // for avoid silence
size_t memPos=getSampleMemOffset(); // for avoid silence
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
if (!s->renderOn[0][sysID]) {
@ -499,10 +503,10 @@ void DivPlatformK053260::renderSamples(int sysID) {
if (s->depth==DIV_SAMPLE_DEPTH_ADPCM_K) {
length=MIN(65535,s->getEndPosition(DIV_SAMPLE_DEPTH_ADPCM_K));
actualLength=MIN((int)(getSampleMemCapacity()-memPos-1),length);
actualLength=MIN((int)(getSampleMemCapacity()-memPos-getSampleMemOffset()),length);
if (actualLength>0) {
sampleOff[i]=memPos-1;
memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength+1));
sampleOff[i]=memPos-getSampleMemOffset();
memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength+getSampleMemOffset()));
for (int j=0; j<actualLength; j++) {
sampleMem[memPos++]=s->dataK[j];
}
@ -510,10 +514,10 @@ void DivPlatformK053260::renderSamples(int sysID) {
}
} else {
length=MIN(65535,s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT));
actualLength=MIN((int)(getSampleMemCapacity()-memPos-1),length);
actualLength=MIN((int)(getSampleMemCapacity()-memPos-getSampleMemOffset()),length);
if (actualLength>0) {
sampleOff[i]=memPos-1;
memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength+1));
sampleOff[i]=memPos-getSampleMemOffset();
memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+actualLength+getSampleMemOffset()));
for (int j=0; j<actualLength; j++) {
sampleMem[memPos++]=s->data8[j];
}

View file

@ -84,6 +84,7 @@ class DivPlatformK053260: public DivDispatch, public k053260_intf {
virtual const void* getSampleMem(int index = 0) override;
virtual size_t getSampleMemCapacity(int index = 0) override;
virtual size_t getSampleMemUsage(int index = 0) override;
virtual size_t getSampleMemOffset(int index = 0) override;
virtual bool isSampleLoaded(int index, int sample) override;
virtual const DivMemoryComposition* getMemCompo(int index) override;
virtual void renderSamples(int chipID) override;

View file

@ -429,7 +429,7 @@ int DivPlatformLynx::dispatch(DivCommand c) {
}
}
chan[c.chan].freqChanged=true;
if (chan[c.chan].pcm && parent->song.linearPitch==2) {
if (chan[c.chan].pcm && parent->song.linearPitch) {
chan[c.chan].sampleBaseFreq=chan[c.chan].baseFreq;
}
if (return2) {
@ -492,6 +492,9 @@ void DivPlatformLynx::forceIns() {
if (chan[i].active) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
if (!chan[i].pcm) {
WRITE_FEEDBACK(i,chan[i].duty.feedback);
}
}
WRITE_ATTEN(i,chan[i].pan);
}

View file

@ -36,7 +36,7 @@ static const int msmRates[4]={
int DivPlatformMSM6258::calcVGMRate() {
int ret=chipClock/((clockSel+1)*512*msmRates[rateSel&3]);
logD("MSM rate: %d",ret);
//logD("MSM rate: %d",ret);
return ret;
}

View file

@ -0,0 +1,757 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "multipcm.h"
#include "../engine.h"
#include "../bsr.h"
#include "../../ta-log.h"
#include <string.h>
#include <math.h>
const unsigned char slotsMPCM[28]={
0x00,0x01,0x02,0x03,0x04,0x05,0x06,
0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,
0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,
};
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite(a,v) \
if (!skipRegisterWrites) { \
writes.push(QueuedWrite(a,v)); \
if (dumpWrites) { \
addWrite(1,slotsMPCM[(a>>3)&0x1f]); \
addWrite(2,a&0x7); \
addWrite(0,v); \
} \
}
#define chWrite(c,a,v) \
if (!skipRegisterWrites) { \
rWrite((c<<3)|(a&0x7),v); \
}
#define chImmWrite(c,a,v) \
if (!skipRegisterWrites) { \
writes.push(QueuedWrite((c<<3)|(a&0x7),v)); \
if (dumpWrites) { \
addWrite(1,slotsMPCM[c&0x1f]); \
addWrite(2,a&0x7); \
addWrite(0,v); \
} \
}
#define CHIP_FREQBASE (117440512)
const char* regCheatSheetMultiPCM[]={
"Pan", "0",
"SampleL", "1",
"SampleH_FreqL", "2",
"FreqH", "3",
"KeyOn", "4",
"TL_LD", "5",
"LFO_VIB", "6",
"AM", "7",
NULL
};
const char** DivPlatformMultiPCM::getRegisterSheet() {
return regCheatSheetMultiPCM;
}
#define PCM_ADDR_PAN 0 // Panpot
#define PCM_ADDR_WAVE_L 1 // Wavetable number LSB
#define PCM_ADDR_WAVE_H_FN_L 2 // Wavetable number MSB, F-number LSB
#define PCM_ADDR_FN_H_OCT 3 // F-number MSB, Pseudo-reverb, Octave
#define PCM_ADDR_KEY 4 // Key
#define PCM_ADDR_TL 5 // Total level, Level direct
#define PCM_ADDR_LFO_VIB 6
#define PCM_ADDR_AM 7
void DivPlatformMultiPCM::acquire(short** buf, size_t len) {
thread_local short o[4];
thread_local int os[2];
thread_local short pcmBuf[28];
for (int i=0; i<28; i++) {
oscBuf[i]->begin(len);
}
for (size_t h=0; h<len; h++) {
os[0]=0; os[1]=0;
if (!writes.empty() && --delay<0) {
QueuedWrite& w=writes.front();
if (w.addr==0xfffffffe) {
delay=w.val;
} else {
delay=1;
pcm.writeReg(slotsMPCM[(w.addr>>3)&0x1f],w.addr&0x7,w.val);
regPool[w.addr]=w.val;
}
writes.pop();
}
pcm.generate(o[0],o[1],o[2],o[3],pcmBuf);
// stereo output only
os[0]+=o[0];
os[1]+=o[1];
os[0]+=o[2];
os[1]+=o[3];
for (int i=0; i<28; i++) {
oscBuf[i]->putSample(h,CLAMP(pcmBuf[i],-32768,32767));
}
if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767;
if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767;
buf[0][h]=os[0];
buf[1][h]=os[1];
}
for (int i=0; i<28; i++) {
oscBuf[i]->end(len);
}
}
void DivPlatformMultiPCM::tick(bool sysTick) {
for (int i=0; i<28; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LOG((chan[i].vol&0x7f),(0x7f*chan[i].std.vol.val)/chan[i].macroVolMul,0x7f);
chImmWrite(i,PCM_ADDR_TL,((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0));
}
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,-131071,131071);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1 && chan[i].active) {
chan[i].keyOn=true;
chan[i].writeCtrl=true;
}
}
if (chan[i].std.panL.had) { // panning
chan[i].pan=chan[i].std.panL.val&0xf;
chImmWrite(i,PCM_ADDR_PAN,(isMuted[i]?8:chan[i].pan)<<4);
}
if (chan[i].std.ex1.had) {
chan[i].lfo=chan[i].std.ex1.val&0x7;
chWrite(i,PCM_ADDR_LFO_VIB,(chan[i].lfo<<3)|(chan[i].vib));
}
if (chan[i].std.fms.had) {
chan[i].vib=chan[i].std.fms.val&0x7;
chWrite(i,PCM_ADDR_LFO_VIB,(chan[i].lfo<<3)|(chan[i].vib));
}
if (chan[i].std.ams.had) {
chan[i].am=chan[i].std.ams.val&0x7;
chWrite(i,PCM_ADDR_AM,chan[i].am);
}
}
for (int i=0; i<224; i++) {
if (pendingWrites[i]!=oldWrites[i]) {
immWrite(i,pendingWrites[i]&0xff);
oldWrites[i]=pendingWrites[i];
}
}
for (int i=0; i<28; i++) {
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
DivSample* s=parent->getSample(parent->getIns(chan[i].ins)->amiga.initSample);
unsigned char ctrl=0;
double off=(s->centerRate>=1)?((double)s->centerRate/parent->getCenterRate()):1.0;
chan[i].freq=(int)(off*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<0x400) chan[i].freq=0x400;
chan[i].freqH=0;
if (chan[i].freq>0x3ffffff) {
chan[i].freq=0x3ffffff;
chan[i].freqH=15;
} else if (chan[i].freq>=0x800) {
chan[i].freqH=bsr32(chan[i].freq)-11;
}
chan[i].freqL=(chan[i].freq>>chan[i].freqH)&0x3ff;
chan[i].freqH=8^chan[i].freqH;
ctrl|=chan[i].active?0x80:0;
int waveNum=chan[i].sample;
if (waveNum>=0) {
if (chan[i].keyOn) {
chImmWrite(i,PCM_ADDR_KEY,ctrl&~0x80); // force keyoff first
chImmWrite(i,PCM_ADDR_WAVE_H_FN_L,((chan[i].freqL&0x3f)<<2)|((waveNum>>8)&1));
chImmWrite(i,PCM_ADDR_WAVE_L,waveNum&0xff);
if (!chan[i].std.vol.had) {
chan[i].outVol=chan[i].vol;
chImmWrite(i,PCM_ADDR_TL,((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0));
}
chan[i].writeCtrl=true;
chan[i].keyOn=false;
}
if (chan[i].keyOff) {
chan[i].writeCtrl=true;
chan[i].keyOff=false;
}
if (chan[i].freqChanged) {
chImmWrite(i,PCM_ADDR_WAVE_H_FN_L,((chan[i].freqL&0x3f)<<2)|((waveNum>>8)&1));
chImmWrite(i,PCM_ADDR_FN_H_OCT,((chan[i].freqH&0xf)<<4)|((chan[i].freqL>>6)&0xf));
chan[i].freqChanged=false;
}
if (chan[i].writeCtrl) {
chImmWrite(i,PCM_ADDR_KEY,ctrl);
chan[i].writeCtrl=false;
}
} else {
// cut if we don't have a sample
chImmWrite(i,PCM_ADDR_KEY,ctrl&~0x80);
}
}
}
}
void DivPlatformMultiPCM::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chImmWrite(ch,PCM_ADDR_PAN,(isMuted[ch]?8:chan[ch].pan)<<4);
}
int DivPlatformMultiPCM::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_MULTIPCM);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127;
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=chan[c.chan].ins;
chan[c.chan].sampleNote=c.value;
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
}
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.insLen) {
chan[c.chan].sample=-1;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
if (chan[c.chan].insChanged) {
if (ins->type==DIV_INS_MULTIPCM) {
chan[c.chan].lfo=ins->multipcm.lfo;
chan[c.chan].vib=ins->multipcm.vib;
chan[c.chan].am=ins->multipcm.am;
} else {
chan[c.chan].lfo=0;
chan[c.chan].vib=0;
chan[c.chan].am=0;
}
chan[c.chan].insChanged=false;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
chan[c.chan].std.release();
break;
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_VOLUME: {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
}
chImmWrite(c.chan,PCM_ADDR_TL,((0x7f-chan[c.chan].outVol)<<1)|(chan[c.chan].levelDirect?1:0));
break;
}
case DIV_CMD_GET_VOLUME:
return chan[c.chan].vol;
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].insChanged=true;
}
chan[c.chan].ins=c.value;
break;
case DIV_CMD_PANNING: {
chan[c.chan].pan=8^MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,15)+1,15);
chImmWrite(c.chan,PCM_ADDR_PAN,(isMuted[c.chan]?8:chan[c.chan].pan)<<4);
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+chan[c.chan].sampleNoteDelta);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val-12):(0)));
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
break;
}
case DIV_CMD_MULTIPCM_LFO:
chan[c.chan].lfo=c.value&7;
chWrite(c.chan,PCM_ADDR_LFO_VIB,(chan[c.chan].lfo<<3)|(chan[c.chan].vib));
break;
case DIV_CMD_MULTIPCM_VIB:
chan[c.chan].vib=c.value&7;
chWrite(c.chan,PCM_ADDR_LFO_VIB,(chan[c.chan].lfo<<3)|(chan[c.chan].vib));
break;
case DIV_CMD_MULTIPCM_AM:
chan[c.chan].am=c.value&7;
chWrite(c.chan,PCM_ADDR_AM,chan[c.chan].am);
break;
case DIV_CMD_MACRO_OFF:
chan[c.chan].std.mask(c.value,true);
break;
case DIV_CMD_MACRO_ON:
chan[c.chan].std.mask(c.value,false);
break;
case DIV_CMD_MACRO_RESTART:
chan[c.chan].std.restart(c.value);
break;
case DIV_CMD_GET_VOLMAX:
return 127;
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_MULTIPCM));
}
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_PRE_NOTE:
break;
default:
//printf("WARNING: unimplemented command %d\n",c.cmd);
break;
}
return 1;
}
void DivPlatformMultiPCM::forceIns() {
for (int i=0; i<28; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
}
for (int i=0; i<224; i++) {
oldWrites[i]=-1;
}
}
void DivPlatformMultiPCM::toggleRegisterDump(bool enable) {
DivDispatch::toggleRegisterDump(enable);
}
void* DivPlatformMultiPCM::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformMultiPCM::getChanMacroInt(int ch) {
return &chan[ch].std;
}
unsigned short DivPlatformMultiPCM::getPan(int ch) {
return parent->convertPanLinearToSplit(8^chan[ch].pan,8,15);
}
DivDispatchOscBuffer* DivPlatformMultiPCM::getOscBuffer(int ch) {
return oscBuf[ch];
}
int DivPlatformMultiPCM::mapVelocity(int ch, float vel) {
// -0.375dB per step
// -6: 64: 16
// -12: 32: 32
// -18: 16: 48
// -24: 8: 64
// -30: 4: 80
// -36: 2: 96
// -42: 1: 112
if (vel==0) return 0;
if (vel>=1.0) return 127;
return CLAMP(round(128.0-(112.0-log2(vel*127.0)*16.0)),0,127);
}
float DivPlatformMultiPCM::getGain(int ch, int vol) {
if (vol==0) return 0;
return 1.0/pow(10.0,(float)(127-vol)*0.375/20.0);
}
unsigned char* DivPlatformMultiPCM::getRegisterPool() {
return regPool;
}
int DivPlatformMultiPCM::getRegisterPoolSize() {
return 224;
}
void DivPlatformMultiPCM::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,224);
pcm.reset();
renderInstruments();
for (int i=0; i<28; i++) {
chan[i]=DivPlatformMultiPCM::Channel();
chan[i].std.setEngine(parent);
chImmWrite(i,PCM_ADDR_PAN,(isMuted[i]?8:chan[i].pan)<<4);
}
for (int i=0; i<224; i++) {
oldWrites[i]=-1;
pendingWrites[i]=-1;
}
curChan=-1;
curAddr=-1;
if (dumpWrites) {
addWrite(0xffffffff,0);
}
delay=0;
}
int DivPlatformMultiPCM::getOutputCount() {
return 2;
}
bool DivPlatformMultiPCM::keyOffAffectsArp(int ch) {
return false;
}
bool DivPlatformMultiPCM::keyOffAffectsPorta(int ch) {
return false;
}
bool DivPlatformMultiPCM::getLegacyAlwaysSetVolume() {
return false;
}
void DivPlatformMultiPCM::notifyInsChange(int ins) {
renderInstruments();
for (int i=0; i<28; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformMultiPCM::notifySampleChange(int sample) {
renderInstruments();
}
void DivPlatformMultiPCM::notifyInsAddition(int sysID) {
renderInstruments();
}
void DivPlatformMultiPCM::notifyInsDeletion(void* ins) {
renderInstruments();
for (int i=0; i<28; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformMultiPCM::poke(unsigned int addr, unsigned short val) {
immWrite(addr,val);
}
void DivPlatformMultiPCM::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
}
int DivPlatformMultiPCM::getPortaFloor(int ch) {
return 0;
}
void DivPlatformMultiPCM::setFlags(const DivConfig& flags) {
chipClock=10000000.0;
CHECK_CUSTOM_CLOCK;
pcm.setClockFrequency(chipClock);
rate=chipClock/224;
for (int i=0; i<28; i++) {
oscBuf[i]->setRate(rate);
}
}
const void* DivPlatformMultiPCM::getSampleMem(int index) {
return (index==0)?pcmMem:NULL;
}
size_t DivPlatformMultiPCM::getSampleMemCapacity(int index) {
return (index==0)?2097152:0;
}
size_t DivPlatformMultiPCM::getSampleMemUsage(int index) {
return (index==0)?pcmMemLen:0;
}
bool DivPlatformMultiPCM::hasSamplePtrHeader(int index) {
return (index==0);
}
bool DivPlatformMultiPCM::isSampleLoaded(int index, int sample) {
if (index!=0) return false;
if (sample<0 || sample>32767) return false;
return sampleLoaded[sample];
}
const DivMemoryComposition* DivPlatformMultiPCM::getMemCompo(int index) {
if (index!=0) return NULL;
return &memCompo;
}
// this is called on instrument change, reset and/or renderSamples().
// I am not making this part of DivDispatch as this is the only chip with
// instruments in ROM.
void DivPlatformMultiPCM::renderInstruments() {
// instrument table
int insCount=parent->song.insLen;
for (int i=0; i<insCount; i++) {
DivInstrument* ins=parent->song.ins[i];
unsigned int insAddr=(i*12);
short sample=ins->amiga.initSample;
if (sample>=0 && sample<parent->song.sampleLen) {
DivSample* s=parent->song.sample[sample];
unsigned char bitDepth;
int startPos=sampleOff[sample];
int endPos=CLAMP(s->isLoopable()?s->loopEnd:(s->samples+1),1,0x10000);
int loop=s->isLoopable()?CLAMP(s->loopStart,0,endPos-2):(endPos-2);
switch (s->depth) {
case DIV_SAMPLE_DEPTH_8BIT:
bitDepth=0;
break;
case DIV_SAMPLE_DEPTH_12BIT:
bitDepth=3;
if (!s->isLoopable()) {
endPos++;
loop++;
}
break;
default:
bitDepth=0;
break;
}
pcmMem[insAddr]=(bitDepth<<6)|((startPos>>16)&0x1f);
pcmMem[1+insAddr]=(startPos>>8)&0xff;
pcmMem[2+insAddr]=(startPos)&0xff;
pcmMem[3+insAddr]=(loop>>8)&0xff;
pcmMem[4+insAddr]=(loop)&0xff;
pcmMem[5+insAddr]=((~(endPos-1))>>8)&0xff;
pcmMem[6+insAddr]=(~(endPos-1))&0xff;
if (ins->type==DIV_INS_MULTIPCM) {
pcmMem[7+insAddr]=(ins->multipcm.lfo<<3)|ins->multipcm.vib; // LFO, VIB
pcmMem[8+insAddr]=(ins->multipcm.ar<<4)|ins->multipcm.d1r; // AR, D1R
pcmMem[9+insAddr]=(ins->multipcm.dl<<4)|ins->multipcm.d2r; // DL, D2R
pcmMem[10+insAddr]=(ins->multipcm.rc<<4)|ins->multipcm.rr; // RC, RR
pcmMem[11+insAddr]=ins->multipcm.am; // AM
} else {
pcmMem[7+insAddr]=0; // LFO, VIB
pcmMem[8+insAddr]=(0xf<<4)|(0xf<<0); // AR, D1R
pcmMem[9+insAddr]=0; // DL, D2R
pcmMem[10+insAddr]=(0xf<<4)|(0xf<<0); // RC, RR
pcmMem[11+insAddr]=0; // AM
}
} else {
// fill to dummy instrument
pcmMem[insAddr]=0;
pcmMem[1+insAddr]=0;
pcmMem[2+insAddr]=0;
pcmMem[3+insAddr]=0;
pcmMem[4+insAddr]=0;
pcmMem[5+insAddr]=0xff;
pcmMem[6+insAddr]=0xff;
pcmMem[7+insAddr]=0; // LFO, VIB
pcmMem[8+insAddr]=(0xf<<4)|(0xf<<0); // AR, D1R
pcmMem[9+insAddr]=(0xf<<4)|(0xf<<0); // DL, D2R
pcmMem[10+insAddr]=(0xf<<4)|(0xf<<0); // RC, RR
pcmMem[11+insAddr]=0; // AM
}
}
}
void DivPlatformMultiPCM::renderSamples(int sysID) {
memset(pcmMem,0,2097152);
memset(sampleOff,0,32768*sizeof(unsigned int));
memset(sampleLoaded,0,32768*sizeof(bool));
memCompo=DivMemoryComposition();
memCompo.name="Sample Memory";
size_t memPos=0x1800;
int sampleCount=parent->song.sampleLen;
if (sampleCount>512) {
// mark the rest as unavailable
for (int i=512; i<sampleCount; i++) {
sampleLoaded[i]=false;
}
sampleCount=512;
}
for (int i=0; i<sampleCount; i++) {
DivSample* s=parent->song.sample[i];
if (!s->renderOn[0][sysID]) {
sampleOff[i]=0;
continue;
}
int length;
int sampleLength;
unsigned char* src=(unsigned char*)s->getCurBuf();
switch (s->depth) {
case DIV_SAMPLE_DEPTH_8BIT:
sampleLength=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
length=MIN(65535,sampleLength+1);
break;
case DIV_SAMPLE_DEPTH_12BIT:
sampleLength=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_12BIT);
length=MIN(98303,sampleLength+3);
break;
default:
sampleLength=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
length=MIN(65535,sampleLength+1);
src=(unsigned char*)s->data8;
break;
}
if (sampleLength<1) length=0;
int actualLength=MIN((int)(getSampleMemCapacity(0)-memPos),length);
if (actualLength>0) {
for (int i=0, j=0; i<actualLength; i++, j++) {
if (j>=sampleLength && s->depth!=DIV_SAMPLE_DEPTH_12BIT) j=sampleLength-1;
pcmMem[memPos+i]=src[j];
}
sampleOff[i]=memPos;
memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+length));
memPos+=length;
}
if (actualLength<length) {
logW("out of MultiPCM memory for sample %d!",i);
break;
}
sampleLoaded[i]=true;
pcmMemLen=memPos+256;
memCompo.used=pcmMemLen;
}
renderInstruments();
memCompo.capacity=getSampleMemCapacity(0);
}
int DivPlatformMultiPCM::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<28; i++) {
isMuted[i]=false;
}
for (int i=0; i<28; i++) {
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
pcmMem=new unsigned char[2097152];
pcmMemLen=0;
pcmMemory.memory=pcmMem;
reset();
return 28;
}
void DivPlatformMultiPCM::quit() {
for (int i=0; i<28; i++) {
delete oscBuf[i];
}
delete[] pcmMem;
}
// initialization of important arrays
DivPlatformMultiPCM::DivPlatformMultiPCM():
pcmMemory(0x200000),
pcm(pcmMemory) {
sampleOff=new unsigned int[32768];
sampleLoaded=new bool[32768];
}
DivPlatformMultiPCM::~DivPlatformMultiPCM() {
delete[] sampleOff;
delete[] sampleLoaded;
}

View file

@ -0,0 +1,142 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _MULTIPCM_H
#define _MULTIPCM_H
#include "../dispatch.h"
#include "../../fixedQueue.h"
#include "sound/ymf278b/ymf278.h"
class DivYMW258MemoryInterface: public MemoryInterface {
public:
unsigned char* memory;
DivYMW258MemoryInterface(unsigned size_) : memory(NULL), size(size_) {};
byte operator[](unsigned address) const override {
if (memory && address<size) {
return memory[address];
}
return 0;
};
unsigned getSize() const override { return size; };
void write(unsigned address, byte value) override {};
void clear(byte value) override {};
private:
unsigned size;
};
class DivPlatformMultiPCM: public DivDispatch {
protected:
struct Channel: public SharedChannel<int> {
unsigned int freqH, freqL;
int sample;
bool writeCtrl, levelDirect;
int lfo, vib, am;
int pan;
int macroVolMul;
Channel():
SharedChannel<int>(0x7f),
freqH(0),
freqL(0),
sample(-1),
writeCtrl(false),
levelDirect(true),
lfo(0),
vib(0),
am(0),
pan(0),
macroVolMul(64) {}
};
Channel chan[28];
DivDispatchOscBuffer* oscBuf[28];
bool isMuted[28];
struct QueuedWrite {
unsigned int addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(): addr(0), val(0), addrOrVal(false) {}
QueuedWrite(unsigned int a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
FixedQueue<QueuedWrite,4096> writes;
unsigned char* pcmMem;
size_t pcmMemLen;
DivYMW258MemoryInterface pcmMemory;
unsigned int* sampleOff;
bool* sampleLoaded;
int delay, curChan, curAddr;
unsigned char regPool[224];
short oldWrites[224];
short pendingWrites[224];
// chips
YMW258 pcm;
DivMemoryComposition memCompo;
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
void renderInstruments();
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
unsigned short getPan(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
int mapVelocity(int ch, float vel);
float getGain(int ch, int vol);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
int getOutputCount();
bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch);
bool getLegacyAlwaysSetVolume();
void toggleRegisterDump(bool enable);
void setFlags(const DivConfig& flags);
void notifyInsChange(int ins);
void notifySampleChange(int sample);
void notifyInsAddition(int sysID);
void notifyInsDeletion(void* ins);
int getPortaFloor(int ch);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const void* getSampleMem(int index);
size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index);
bool hasSamplePtrHeader(int index=0);
bool isSampleLoaded(int index, int sample);
const DivMemoryComposition* getMemCompo(int index);
void renderSamples(int chipID);
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
DivPlatformMultiPCM();
~DivPlatformMultiPCM();
};
#endif

View file

@ -386,13 +386,13 @@ int DivPlatformN163::dispatch(DivCommand c) {
int destFreq=destFreqD;
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:16);
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:16);
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:16);
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:16);
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;

View file

@ -406,13 +406,13 @@ int DivPlatformNamcoWSG::dispatch(DivCommand c) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:8);
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:8);
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:8);
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:8);
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;

View file

@ -23,6 +23,7 @@
#include <math.h>
#define CHIP_DIVIDER 32
#define NDS_CORE_QUALITY 64
#define rRead8(a) (nds.read8(a))
#define rWrite8(a,v) {if(!skipRegisterWrites){writes.push_back(QueuedWrite((a),1,(v)));regPool[(a)]=(v);if(dumpWrites)addWrite((a),(v));}}
@ -68,6 +69,45 @@ const char** DivPlatformNDS::getRegisterSheet() {
return regCheatSheetNDS;
}
#ifdef ORIG_NDS_CORE
void DivPlatformNDS::acquire(short** buf, size_t len) {
for (int i=0; i<16; i++) {
oscBuf[i]->begin(len);
}
while (!writes.empty()) {
QueuedWrite w=writes.front();
if (w.size==4) {
nds.write32(w.addr>>2,w.val);
} else if (w.size==2) {
nds.write16(w.addr>>1,w.val);
} else {
nds.write8(w.addr,w.val);
}
writes.pop();
}
for (size_t h=0; h<len; h++) {
nds.tick(NDS_CORE_QUALITY);
int lout=((nds.loutput()-0x200)<<5); // scale to 16 bit
int rout=((nds.routput()-0x200)<<5); // scale to 16 bit
if (lout>32767) lout=32767;
if (lout<-32768) lout=-32768;
if (rout>32767) rout=32767;
if (rout<-32768) rout=-32768;
buf[0][h]=lout;
buf[1][h]=rout;
for (int i=0; i<16; i++) {
oscBuf[i]->putSample(h,(nds.chan_lout(i)+nds.chan_rout(i))>>1);
}
}
for (int i=0; i<16; i++) {
oscBuf[i]->end(len);
}
}
#else
void DivPlatformNDS::acquireDirect(blip_buffer_t** bb, size_t len) {
for (int i=0; i<16; i++) {
oscBuf[i]->begin(len);
@ -95,13 +135,7 @@ void DivPlatformNDS::acquireDirect(blip_buffer_t** bb, size_t len) {
oscBuf[i]->end(len);
}
}
void DivPlatformNDS::postProcess(short* buf, int outIndex, size_t len, int sampleRate) {
// this is where we handle global volume. it is faster than doing it on each blip...
for (size_t i=0; i<len; i++) {
buf[i]=((buf[i]*globalVolume)>>7);
}
}
#endif
u8 DivPlatformNDS::read_byte(u32 addr) {
if (addr<getSampleMemCapacity()) {
@ -475,7 +509,11 @@ int DivPlatformNDS::getOutputCount() {
}
bool DivPlatformNDS::hasAcquireDirect() {
#ifdef ORIG_NDS_CORE
return false;
#else
return true;
#endif
}
void DivPlatformNDS::notifyInsChange(int ins) {
@ -598,7 +636,11 @@ void DivPlatformNDS::setFlags(const DivConfig& flags) {
isDSi=flags.getBool("chipType",0);
chipClock=33513982;
CHECK_CUSTOM_CLOCK;
#ifdef ORIG_NDS_CORE
rate=chipClock/(2*NDS_CORE_QUALITY);
#else
rate=chipClock/2;
#endif
for (int i=0; i<16; i++) {
oscBuf[i]->setRate(rate);
}

View file

@ -21,7 +21,11 @@
#define _NDS_H
#include "../dispatch.h"
#ifdef ORIG_NDS_CORE
#include "sound/nds_unopt.hpp"
#else
#include "sound/nds.hpp"
#endif
using namespace nds_sound_emu;
@ -73,8 +77,11 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf {
virtual u8 read_byte(u32 addr) override;
virtual void write_byte(u32 addr, u8 data) override;
#ifdef ORIG_NDS_CORE
virtual void acquire(short** buf, size_t len) override;
#else
virtual void acquireDirect(blip_buffer_t** bb, size_t len) override;
virtual void postProcess(short* buf, int outIndex, size_t len, int sampleRate) override;
#endif
virtual int dispatch(DivCommand c) override;
virtual void* getChanState(int chan) override;
virtual DivMacroInt* getChanMacroInt(int ch) override;

View file

@ -1510,7 +1510,7 @@ void DivPlatformOPL::tick(bool sysTick) {
if (chan[i].freqChanged) {
int mul=2;
int fixedBlock=chan[i].state.block;
if (parent->song.linearPitch!=2) {
if (!parent->song.linearPitch) {
mul=octave(chan[i].baseFreq,fixedBlock)*2;
}
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,mul,chan[i].pitch2,chipClock,CHIP_FREQBASE);
@ -2116,7 +2116,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
bool return2=false;
int mul=1;
int fixedBlock=0;
if (parent->song.linearPitch!=2) {
if (!parent->song.linearPitch) {
fixedBlock=chan[c.chan].state.block;
mul=octave(chan[c.chan].baseFreq,fixedBlock);
}
@ -2133,7 +2133,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
return2=true;
}
}
if (!chan[c.chan].portaPause && parent->song.linearPitch!=2) {
if (!chan[c.chan].portaPause && !parent->song.linearPitch) {
if (mul!=octave(newFreq,fixedBlock)) {
chan[c.chan].portaPause=true;
break;
@ -2993,6 +2993,12 @@ void DivPlatformOPL::notifyInsChange(int ins) {
}
}
void DivPlatformOPL::notifySampleChange(int sample) {
if (pcmChanOffs>=0) {
renderInstruments();
}
}
void DivPlatformOPL::notifyInsDeletion(void* ins) {
for (int i=0; i<totalChans; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
@ -3244,6 +3250,14 @@ size_t DivPlatformOPL::getSampleMemUsage(int index) {
(index==0 && adpcmChan>=0)?adpcmBMemLen:0;
}
bool DivPlatformOPL::hasSamplePtrHeader(int index) {
return (index==0 && pcmChanOffs>=0);
}
size_t DivPlatformOPL::getSampleMemOffset(int index) {
return (index==0 && pcmChanOffs>=0 && ramSize<=0x200000)?0x200000:0;
}
bool DivPlatformOPL::isSampleLoaded(int index, int sample) {
if (index!=0) return false;
if (sample<0 || sample>32767) return false;
@ -3256,6 +3270,58 @@ const DivMemoryComposition* DivPlatformOPL::getMemCompo(int index) {
return &memCompo;
}
// this is called on instrument change, reset and/or renderSamples().
// I am not making this part of DivDispatch as this is the only chip with
// instruments in ROM.
void DivPlatformOPL::renderInstruments() {
if (pcmChanOffs>=0) {
const int maxSample=PCM_IN_RAM?128:512;
int sampleCount=parent->song.sampleLen;
if (sampleCount>maxSample) {
sampleCount=maxSample;
}
// instrument table
for (int i=0; i<sampleCount; i++) {
DivSample* s=parent->song.sample[i];
unsigned int insAddr=(i*12)+(PCM_IN_RAM?0x200000:0);
unsigned char bitDepth;
int endPos=CLAMP(s->isLoopable()?s->loopEnd:(s->samples+1),1,0x10000);
int loop=s->isLoopable()?CLAMP(s->loopStart,0,endPos-2):(endPos-2);
switch (s->depth) {
case DIV_SAMPLE_DEPTH_8BIT:
bitDepth=0;
break;
case DIV_SAMPLE_DEPTH_12BIT:
bitDepth=1;
if (!s->isLoopable()) {
endPos++;
loop++;
}
break;
case DIV_SAMPLE_DEPTH_16BIT:
bitDepth=2;
break;
default:
bitDepth=0;
break;
}
pcmMem[insAddr]=(bitDepth<<6)|((sampleOffPCM[i]>>16)&0x3f);
pcmMem[1+insAddr]=(sampleOffPCM[i]>>8)&0xff;
pcmMem[2+insAddr]=(sampleOffPCM[i])&0xff;
pcmMem[3+insAddr]=(loop>>8)&0xff;
pcmMem[4+insAddr]=(loop)&0xff;
pcmMem[5+insAddr]=((~(endPos-1))>>8)&0xff;
pcmMem[6+insAddr]=(~(endPos-1))&0xff;
// on MultiPCM this consists of instrument params, but on OPL4 this is not used
pcmMem[7+insAddr]=0; // LFO, VIB
pcmMem[8+insAddr]=(0xf<<4)|(0xf<<0); // AR, D1R
pcmMem[9+insAddr]=0; // DL, D2R
pcmMem[10+insAddr]=(0xf<<4)|(0xf<<0); // RC, RR
pcmMem[11+insAddr]=0; // AM
}
}
}
void DivPlatformOPL::renderSamples(int sysID) {
if (adpcmChan<0 && pcmChanOffs<0) return;
if (adpcmChan>=0 && adpcmBMem!=NULL) {
@ -3341,45 +3407,7 @@ void DivPlatformOPL::renderSamples(int sysID) {
}
pcmMemLen=memPos+256;
// instrument table
for (int i=0; i<sampleCount; i++) {
DivSample* s=parent->song.sample[i];
unsigned int insAddr=(i*12)+(PCM_IN_RAM?0x200000:0);
unsigned char bitDepth;
int endPos=CLAMP(s->isLoopable()?s->loopEnd:(s->samples+1),1,0x10000);
int loop=s->isLoopable()?CLAMP(s->loopStart,0,endPos-2):(endPos-2);
switch (s->depth) {
case DIV_SAMPLE_DEPTH_8BIT:
bitDepth=0;
break;
case DIV_SAMPLE_DEPTH_12BIT:
bitDepth=1;
if (!s->isLoopable()) {
endPos++;
loop++;
}
break;
case DIV_SAMPLE_DEPTH_16BIT:
bitDepth=2;
break;
default:
bitDepth=0;
break;
}
pcmMem[insAddr]=(bitDepth<<6)|((sampleOffPCM[i]>>16)&0x3f);
pcmMem[1+insAddr]=(sampleOffPCM[i]>>8)&0xff;
pcmMem[2+insAddr]=(sampleOffPCM[i])&0xff;
pcmMem[3+insAddr]=(loop>>8)&0xff;
pcmMem[4+insAddr]=(loop)&0xff;
pcmMem[5+insAddr]=((~(endPos-1))>>8)&0xff;
pcmMem[6+insAddr]=(~(endPos-1))&0xff;
// on MultiPCM this consists of instrument params, but on OPL4 this is not used
pcmMem[7+insAddr]=0; // LFO, VIB
pcmMem[8+insAddr]=(0xf<<4)|(0xf<<0); // AR, D1R
pcmMem[9+insAddr]=0; // DL, D2R
pcmMem[10+insAddr]=(0xf<<4)|(0xf<<0); // RC, RR
pcmMem[11+insAddr]=0; // AM
}
renderInstruments();
if (PCM_IN_RAM) {
memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_RESERVED,"ROM data",0,0,0x200000));
}

View file

@ -186,6 +186,8 @@ class DivPlatformOPL: public DivDispatch {
void acquire_ymfm2(short** buf, size_t len);
void acquire_ymfm1(short** buf, size_t len);
void renderInstruments();
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
@ -211,6 +213,7 @@ class DivPlatformOPL: public DivDispatch {
void toggleRegisterDump(bool enable);
void setFlags(const DivConfig& flags);
void notifyInsChange(int ins);
void notifySampleChange(int sample);
void notifyInsDeletion(void* ins);
int getPortaFloor(int ch);
void poke(unsigned int addr, unsigned short val);
@ -218,6 +221,8 @@ class DivPlatformOPL: public DivDispatch {
const void* getSampleMem(int index);
size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index);
bool hasSamplePtrHeader(int index=0);
size_t getSampleMemOffset(int index);
bool isSampleLoaded(int index, int sample);
const DivMemoryComposition* getMemCompo(int index);
void renderSamples(int chipID);

View file

@ -341,7 +341,7 @@ void DivPlatformOPLL::tick(bool sysTick) {
if (chan[i].freqChanged) {
int mul=2;
int fixedBlock=chan[i].state.block;
if (parent->song.linearPitch!=2) {
if (!parent->song.linearPitch) {
mul=octave(chan[i].baseFreq,fixedBlock)*2;
}
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,mul,chan[i].pitch2,chipClock,CHIP_FREQBASE);
@ -684,7 +684,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
bool return2=false;
int mul=1;
int fixedBlock=0;
if (parent->song.linearPitch!=2) {
if (!parent->song.linearPitch) {
fixedBlock=chan[c.chan].state.block;
mul=octave(chan[c.chan].baseFreq,fixedBlock);
}

View file

@ -223,7 +223,7 @@ void DivPlatformPOKEY::tick(bool sysTick) {
}
// non-linear pitch
if (parent->song.linearPitch==0) {
if (!parent->song.linearPitch) {
chan[i].freq-=chan[i].pitch;
}

View file

@ -322,7 +322,7 @@ void DivPlatformQSound::tick(bool sysTick) {
}
int loopStart=s->loopStart;
int length=s->loopEnd;
int length=s->isLoopable()?s->loopEnd:s->samples;
if (i<16) {
if (length>65536-16) {
length=65536-16;

View file

@ -261,13 +261,13 @@ int DivPlatformSAA1099::dispatch(DivCommand c) {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH));
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:(8-chan[c.chan].freqH));
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH));
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:(8-chan[c.chan].freqH));
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
@ -485,7 +485,7 @@ void DivPlatformSAA1099::setCoreQuality(unsigned char q) {
coreQuality=8;
break;
case 5:
coreQuality=1;
coreQuality=4;
break;
default:
coreQuality=32;

View file

@ -315,7 +315,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
case DIV_CMD_NOTE_PORTA: {
int destFreq=((c.value2+chan[c.chan].sampleNoteDelta)<<7);
int newFreq;
int mul=(oldSlides || parent->song.linearPitch!=2)?8:1;
int mul=(oldSlides || !parent->song.linearPitch)?8:1;
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
newFreq=chan[c.chan].baseFreq+c.value*mul;

View file

@ -267,13 +267,13 @@ int DivPlatformSM8521::dispatch(DivCommand c) {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:8);
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:8);
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:8);
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:8);
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;

View file

@ -189,7 +189,7 @@ void DivPlatformSMS::acquireDirect(blip_buffer_t** bb, size_t len) {
double DivPlatformSMS::NOTE_SN(int ch, int note) {
double CHIP_DIVIDER=toneDivider;
if (ch==3) CHIP_DIVIDER=noiseDivider;
if (parent->song.linearPitch==2 || !easyNoise) {
if (parent->song.linearPitch || !easyNoise) {
return NOTE_PERIODIC(note);
}
int easyStartingPeriod=16;
@ -209,7 +209,7 @@ int DivPlatformSMS::snCalcFreq(int ch) {
if (chan[ch].fixedArp) {
curFreq=chan[ch].baseNoteOverride<<7;
}
if (parent->song.linearPitch==2 && easyNoise && curFreq>easyThreshold) {
if (parent->song.linearPitch && easyNoise && curFreq>easyThreshold) {
int ret=(((easyStartingPeriod<<7))-(curFreq-(easyThreshold)))>>7;
if (ret<0) ret=0;
return ret;

View file

@ -964,6 +964,10 @@ size_t DivPlatformSNES::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
bool DivPlatformSNES::hasSamplePtrHeader(int index) {
return true;
}
bool DivPlatformSNES::isSampleLoaded(int index, int sample) {
if (index!=0) return false;
if (sample<0 || sample>32767) return false;

View file

@ -124,9 +124,10 @@ class DivPlatformSNES: public DivDispatch {
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);
const void* getSampleMem(int index=0);
size_t getSampleMemCapacity(int index=0);
size_t getSampleMemUsage(int index=0);
bool hasSamplePtrHeader(int index=0);
bool isSampleLoaded(int index, int sample);
const DivMemoryComposition* getMemCompo(int index);
void renderSamples(int chipID);

View file

@ -225,6 +225,9 @@ namespace nds_sound_emu
{
case 0x00:
m_control = (m_control & ~mask) | (data & mask);
for (u8 i = 0; i < 16; i++) {
m_channel[i].setMasterVol(mvol());
}
break;
case 0x04:
mask &= 0x3ff;
@ -302,6 +305,7 @@ namespace nds_sound_emu
m_ctl_repeat = bitfield(m_control, 27, 2);
m_ctl_format = bitfield(m_control, 29, 2);
m_ctl_busy = bitfield(m_control, 31);
computeVol();
if (bitfield(old ^ m_control, 31))
{
@ -393,14 +397,14 @@ namespace nds_sound_emu
advance();
m_counter += 0x10000 - m_freq;
}
m_output = (m_sample * m_ctl_volume) >> (7 + m_ctl_voldiv);
const s32 loutput = (m_output * lvol()) >> 7;
const s32 routput = (m_output * rvol()) >> 7;
m_output = (m_sample * m_final_volume) >> (14 + m_ctl_voldiv);
const s32 loutput = (m_output * lvol()) >> 8;
const s32 routput = (m_output * rvol()) >> 8;
i+=cycle-1;
if (m_loutput!=loutput || m_routput!=routput) {
m_oscBuf->putSample(i,(loutput+routput)>>1);
m_oscBuf->putSample(i,(loutput+routput));
}
if (m_loutput!=loutput) {
blip_add_delta(m_bb[0],i,loutput-m_loutput);
@ -559,6 +563,17 @@ namespace nds_sound_emu
}
}
void nds_sound_t::channel_t::computeVol()
{
m_final_volume = m_ctl_volume * m_master_volume;
}
void nds_sound_t::channel_t::setMasterVol(s32 masterVol)
{
m_master_volume = masterVol;
computeVol();
}
// capture
void nds_sound_t::capture_t::reset()
{

View file

@ -232,6 +232,7 @@ namespace nds_sound_emu
void write(u32 offset, u32 data, u32 mask = ~0);
void update(s32 cycle);
void setMasterVol(s32 masterVol);
void set_bb(blip_buffer_t* bbLeft, blip_buffer_t* bbRight) { m_bb[0] = bbLeft; m_bb[1] = bbRight; }
void set_oscbuf(DivDispatchOscBuffer* oscBuf) { m_oscBuf = oscBuf; }
void resetTS(u32 what) { m_lastts = what; }
@ -272,6 +273,7 @@ namespace nds_sound_emu
void keyoff();
void fetch();
void advance();
void computeVol();
// interfaces
nds_sound_t &m_host; // host device
@ -303,6 +305,8 @@ namespace nds_sound_emu
// internal states
bool m_playing = false; // playing flag
s32 m_final_volume = 0; // calculated volume
s32 m_master_volume = 0; // master volume cache
s32 m_adpcm_out = 0; // current ADPCM sample value
s32 m_adpcm_index = 0; // current ADPCM step
s32 m_prev_adpcm_out = 0; // previous ADPCM sample value

View file

@ -0,0 +1,634 @@
/*
============================================================================
NDS sound emulator
by cam900
This file is licensed under zlib license.
============================================================================
zlib License
(C) 2024-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 to further verifications from real hardware
Tech info: https://problemkaputt.de/gbatek.htm
*/
#include "nds_unopt.hpp"
namespace nds_sound_emu
{
void nds_sound_t::reset()
{
for (channel_t &elem : m_channel)
elem.reset();
for (capture_t &elem : m_capture)
elem.reset();
m_control = 0;
m_bias = 0;
m_loutput = 0;
m_routput = 0;
}
void nds_sound_t::tick(s32 cycle)
{
m_loutput = m_routput = (m_bias & 0x3ff);
if (!enable())
return;
// mix outputs
s32 lmix = 0, rmix = 0;
for (u8 i = 0; i < 16; i++)
{
channel_t &channel = m_channel[i];
channel.update(cycle);
// bypass mixer
if (((i == 1) && (mix_ch1())) || ((i == 3) && (mix_ch3())))
continue;
lmix += channel.loutput();
rmix += channel.routput();
}
// send mixer output to capture
m_capture[0].update(lmix, cycle);
m_capture[1].update(rmix, cycle);
// select left/right output source
switch (lout_from())
{
case 0: // left mixer
break;
case 1: // channel 1
lmix = m_channel[1].loutput();
break;
case 2: // channel 3
lmix = m_channel[3].loutput();
break;
case 3: // channel 1 + 3
lmix = m_channel[1].loutput() + m_channel[3].loutput();
break;
}
switch (rout_from())
{
case 0: // right mixer
break;
case 1: // channel 1
rmix = m_channel[1].routput();
break;
case 2: // channel 3
rmix = m_channel[3].routput();
break;
case 3: // channel 1 + 3
rmix = m_channel[1].routput() + m_channel[3].routput();
break;
}
// adjust master volume
lmix = (lmix * mvol()) >> 13;
rmix = (rmix * mvol()) >> 13;
// add bias and clip output
m_loutput = clamp<s32>((lmix + (m_bias & 0x3ff)), 0, 0x3ff);
m_routput = clamp<s32>((rmix + (m_bias & 0x3ff)), 0, 0x3ff);
}
u8 nds_sound_t::read8(u32 addr)
{
return bitfield(read32(addr >> 2), bitfield(addr, 0, 2) << 3, 8);
}
u16 nds_sound_t::read16(u32 addr)
{
return bitfield(read32(addr >> 1), bitfield(addr, 0) << 4, 16);
}
u32 nds_sound_t::read32(u32 addr)
{
addr <<= 2; // word address
switch (addr & 0x100)
{
case 0x000:
if ((addr & 0xc) == 0)
return m_channel[bitfield(addr, 4, 4)].control();
break;
case 0x100:
switch (addr & 0xff)
{
case 0x00:
return m_control;
case 0x04:
return m_bias;
case 0x08:
return m_capture[0].control() | (m_capture[1].control() << 8);
case 0x10:
case 0x18:
return m_capture[bitfield(addr, 3)].dstaddr();
default:
break;
}
break;
}
return 0;
}
void nds_sound_t::write8(u32 addr, u8 data)
{
const u8 bit = bitfield(addr, 0, 2);
const u32 in = u32(data) << (bit << 3);
const u32 in_mask = 0xff << (bit << 3);
write32(addr >> 2, in, in_mask);
}
void nds_sound_t::write16(u32 addr, u16 data, u16 mask)
{
const u8 bit = bitfield(addr, 0);
const u32 in = u32(data) << (bit << 4);
const u32 in_mask = u32(mask) << (bit << 4);
write32(addr >> 1, in, in_mask);
}
void nds_sound_t::write32(u32 addr, u32 data, u32 mask)
{
addr <<= 2; // word address
switch (addr & 0x100)
{
case 0x000:
m_channel[bitfield(addr, 4, 4)].write(bitfield(addr, 2, 2), data, mask);
break;
case 0x100:
switch (addr & 0xff)
{
case 0x00:
m_control = (m_control & ~mask) | (data & mask);
break;
case 0x04:
mask &= 0x3ff;
m_bias = (m_bias & ~mask) | (data & mask);
break;
case 0x08:
if (bitfield(mask, 0, 8))
m_capture[0].control_w(data & 0xff);
if (bitfield(mask, 8, 8))
m_capture[1].control_w((data >> 8) & 0xff);
break;
case 0x10:
case 0x14:
case 0x18:
case 0x1c:
m_capture[bitfield(addr, 3)].addrlen_w(bitfield(addr, 2), data, mask);
break;
default:
break;
}
break;
}
}
// channels
void nds_sound_t::channel_t::reset()
{
m_control = 0;
m_sourceaddr = 0;
m_freq = 0;
m_loopstart = 0;
m_length = 0;
m_playing = false;
m_adpcm_out = 0;
m_adpcm_index = 0;
m_prev_adpcm_out = 0;
m_prev_adpcm_index = 0;
m_cur_addr = 0;
m_cur_state = 0;
m_cur_bitaddr = 0;
m_delay = 0;
m_sample = 0;
m_lfsr = 0x7fff;
m_lfsr_out = 0x7fff;
m_counter = 0x10000;
m_output = 0;
m_loutput = 0;
m_routput = 0;
}
void nds_sound_t::channel_t::write(u32 offset, u32 data, u32 mask)
{
const u32 old = m_control;
switch (offset & 3)
{
case 0: // Control/Status
m_control = (m_control & ~mask) | (data & mask);
if (bitfield(old ^ m_control, 31))
{
if (busy())
keyon();
else if (!busy())
keyoff();
}
// reset hold flag
if (!m_playing && !hold())
{
m_sample = m_lfsr_out = 0;
m_output = m_loutput = m_routput = 0;
}
break;
case 1: // Source address
mask &= 0x7ffffff;
m_sourceaddr = (m_sourceaddr & ~mask) | (data & mask);
break;
case 2: // Frequency, Loopstart
if (bitfield(mask, 0, 16))
m_freq = (m_freq & bitfield(~mask, 0, 16)) | (bitfield(data & mask, 0, 16));
if (bitfield(mask, 16, 16))
m_loopstart = (m_loopstart & bitfield(~mask, 16, 16)) | (bitfield(data & mask, 16, 16));
break;
case 3: // Length
mask &= 0x3fffff;
m_length = (m_length & ~mask) | (data & mask);
break;
}
}
void nds_sound_t::channel_t::keyon()
{
if (!m_playing)
{
m_playing = true;
m_delay = format() == 2 ? 11 : 3; // 3 (11 for ADPCM) delay for playing sample
m_cur_bitaddr = m_cur_addr = 0;
m_cur_state = (format() == 2) ? STATE_ADPCM_LOAD : ((m_loopstart == 0) ? STATE_POST_LOOP : STATE_PRE_LOOP);
m_counter = 0x10000;
m_sample = 0;
m_lfsr_out = 0x7fff;
m_lfsr = 0x7fff;
}
}
void nds_sound_t::channel_t::keyoff()
{
if (m_playing)
{
if (busy())
m_control &= ~(1 << 31);
if (!hold())
{
m_sample = m_lfsr_out = 0;
m_output = m_loutput = m_routput = 0;
}
m_playing = false;
}
}
void nds_sound_t::channel_t::update(s32 cycle)
{
if (m_playing)
{
// get output
fetch();
m_counter -= cycle;
while (m_counter <= m_freq)
{
// advance
advance();
m_counter += 0x10000 - m_freq;
}
m_output = (m_sample * volume()) >> (7 + voldiv());
m_loutput = (m_output * lvol()) >> 7;
m_routput = (m_output * rvol()) >> 7;
}
}
void nds_sound_t::channel_t::fetch()
{
if (m_playing)
{
// fetch samples
switch (format())
{
case 0: // PCM8
m_sample = s16(m_host.m_intf.read_byte(addr()) << 8);
break;
case 1: // PCM16
m_sample = m_host.m_intf.read_word(addr());
break;
case 2: // ADPCM
m_sample = m_cur_state == STATE_ADPCM_LOAD ? 0 : m_adpcm_out;
break;
case 3: // PSG or Noise
m_sample = 0;
if (m_psg) // psg
m_sample = (duty() == 7) ? -0x7fff : ((m_cur_bitaddr < s32(u32(7) - duty())) ? -0x7fff : 0x7fff);
else if (m_noise) // noise
m_sample = m_lfsr_out;
break;
}
}
// apply delay
if (format() != 3 && m_delay > 0)
m_sample = 0;
}
void nds_sound_t::channel_t::advance()
{
if (m_playing)
{
// advance bit address
switch (format())
{
case 0: // PCM8
m_cur_bitaddr += 8;
break;
case 1: // PCM16
m_cur_bitaddr += 16;
break;
case 2: // ADPCM
if (m_cur_state == STATE_ADPCM_LOAD) // load ADPCM data
{
if (m_cur_bitaddr == 0)
m_prev_adpcm_out = m_adpcm_out = m_host.m_intf.read_word(addr());
if (m_cur_bitaddr == 16)
m_prev_adpcm_index = m_adpcm_index = clamp<s32>(m_host.m_intf.read_byte(addr()) & 0x7f, 0, 88);
}
else // decode ADPCM
{
const u8 input = bitfield(m_host.m_intf.read_byte(addr()), m_cur_bitaddr & 4, 4);
s32 diff = ((bitfield(input, 0, 3) * 2 + 1) * m_host.adpcm_diff_table[m_adpcm_index] / 8);
if (bitfield(input, 3)) diff = -diff;
m_adpcm_out = clamp<s32>(m_adpcm_out + diff, -0x8000, 0x7fff);
m_adpcm_index = clamp<s32>(m_adpcm_index + m_host.adpcm_index_table[bitfield(input, 0, 3)], 0, 88);
}
m_cur_bitaddr += 4;
break;
case 3: // PSG or Noise
if (m_psg) // psg
m_cur_bitaddr = (m_cur_bitaddr + 1) & 7;
else if (m_noise) // noise
{
if (bitfield(m_lfsr, 0))
{
m_lfsr = (m_lfsr >> 1) ^ 0x6000;
m_lfsr_out = -0x7fff;
}
else
{
m_lfsr >>= 1;
m_lfsr_out = 0x7fff;
}
}
break;
}
// address update
if (format() != 3)
{
// adjust delay
m_delay--;
// update address, loop
while (m_cur_bitaddr >= 32)
{
// already loaded?
if (format() == 2 && m_cur_state == STATE_ADPCM_LOAD)
{
m_cur_state = m_loopstart == 0 ? STATE_POST_LOOP : STATE_PRE_LOOP;
}
m_cur_addr++;
if (m_cur_state == STATE_PRE_LOOP && m_cur_addr >= m_loopstart)
{
m_cur_state = STATE_POST_LOOP;
m_cur_addr = 0;
if (format() == 2)
{
m_prev_adpcm_out = m_adpcm_out;
m_prev_adpcm_index = m_adpcm_index;
}
}
else if (m_cur_state == STATE_POST_LOOP && m_cur_addr >= m_length)
{
switch (repeat())
{
case 0: // manual; not correct?
case 2: // one-shot
case 3: // prohibited
keyoff();
break;
case 1: // loop infinitely
if (format() == 2)
{
if (m_loopstart == 0) // reload ADPCM
{
m_cur_state = STATE_ADPCM_LOAD;
}
else // restore
{
m_adpcm_out = m_prev_adpcm_out;
m_adpcm_index = m_prev_adpcm_index;
}
}
m_cur_addr = 0;
break;
}
}
m_cur_bitaddr -= 32;
}
}
}
}
// capture
void nds_sound_t::capture_t::reset()
{
m_control = 0;
m_dstaddr = 0;
m_length = 0;
m_counter = 0x10000;
m_cur_addr = 0;
m_cur_waddr = 0;
m_cur_bitaddr = 0;
m_enable = false;
}
void nds_sound_t::capture_t::control_w(u8 data)
{
const u8 old = m_control;
m_control = data;
if (bitfield(old ^ m_control, 7))
{
if (busy())
capture_on();
else if (!busy())
capture_off();
}
}
void nds_sound_t::capture_t::addrlen_w(u32 offset, u32 data, u32 mask)
{
switch (offset & 1)
{
case 0: // Destination Address
mask &= 0x7ffffff;
m_dstaddr = (m_dstaddr & ~mask) | (data & mask);
break;
case 1: // Buffer Length
mask &= 0xffff;
m_length = (m_length & ~mask) | (data & mask);
break;
}
}
void nds_sound_t::capture_t::update(s32 mix, s32 cycle)
{
if (m_enable)
{
s32 inval = 0;
// get inputs
// TODO: hardware bugs aren't emulated, add mode behavior not verified
if (addmode())
inval = get_source() ? m_input.output() + m_output.output() : mix;
else
inval = get_source() ? m_input.output() : mix;
// clip output
inval = clamp<s32>(inval, -0x8000, 0x7fff);
// update counter
m_counter -= cycle;
while (m_counter <= m_output.freq())
{
// write to memory; TODO: verify write behavior
if (format()) // 8 bit output
{
m_fifo[m_fifo_head & 7].write_byte(m_cur_bitaddr & 0x18, (inval >> 8) & 0xff);
m_cur_bitaddr += 8;
}
else
{
m_fifo[m_fifo_head & 7].write_word(m_cur_bitaddr & 0x10, inval & 0xffff);
m_cur_bitaddr += 16;
}
// update address
while (m_cur_bitaddr >= 32)
{
// clear FIFO empty flag
m_fifo_empty = false;
// advance FIFO head position
m_fifo_head = (m_fifo_head + 1) & 7;
if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask()))
m_fifo_full = true;
// update loop
if (++m_cur_addr >= m_length)
{
if (repeat())
m_cur_addr = 0;
else
capture_off();
}
if (m_fifo_full)
{
// execute FIFO
fifo_write();
// check repeat
if (m_cur_waddr >= m_length && repeat())
m_cur_waddr = 0;
}
m_cur_bitaddr -= 32;
}
m_counter += 0x10000 - m_output.freq();
}
}
}
bool nds_sound_t::capture_t::fifo_write()
{
if (m_fifo_empty)
return true;
// clear FIFO full flag
m_fifo_full = false;
// write FIFO data to memory
m_host.m_intf.write_dword(waddr(), m_fifo[m_fifo_tail].data());
m_cur_waddr++;
// advance FIFO tail position
m_fifo_tail = (m_fifo_tail + 1) & 7;
if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask()))
m_fifo_empty = true;
return m_fifo_empty;
}
void nds_sound_t::capture_t::capture_on()
{
if (!m_enable)
{
m_enable = true;
// reset address
m_cur_bitaddr = 0;
m_cur_addr = m_cur_waddr = 0;
m_counter = 0x10000;
// reset FIFO
m_fifo_head = m_fifo_tail = 0;
m_fifo_empty = true;
m_fifo_full = false;
}
}
void nds_sound_t::capture_t::capture_off()
{
if (m_enable)
{
// flush FIFO
while (m_cur_waddr < m_length)
{
if (fifo_write())
break;
}
m_enable = false;
if (busy())
m_control &= ~(1 << 7);
}
}
}; // namespace nds_sound_emu

View file

@ -0,0 +1,415 @@
/*
============================================================================
NDS sound emulator
by cam900
This file is licensed under zlib license.
============================================================================
zlib License
(C) 2024-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 to further verifications from real hardware
Tech info: https://problemkaputt.de/gbatek.htm
*/
#ifndef NDS_SOUND_EMU_H
#define NDS_SOUND_EMU_H
namespace nds_sound_emu
{
using u8 = unsigned char;
using u16 = unsigned short;
using u32 = unsigned int;
using u64 = unsigned long long;
using s8 = signed char;
using s16 = signed short;
using s32 = signed int;
using s64 = signed long long;
template<typename T>
static const inline T bitfield(const T in, const u8 pos)
{
return (in >> pos) & 1;
} // bitfield
template<typename T>
static const inline T bitfield(const T in, const u8 pos, const u8 len)
{
return (in >> pos) & ((1 << len) - 1);
} // bitfield
template<typename T>
static const inline T clamp(const T in, const T min, const T max)
{
return (in < min) ? min : ((in > max) ? max : in);
} // clamp
class nds_sound_intf
{
public:
nds_sound_intf()
{
}
virtual u8 read_byte(u32 addr) { return 0; }
inline u16 read_word(u32 addr) { return read_byte(addr) | (u16(read_byte(addr + 1)) << 8); }
inline u32 read_dword(u32 addr) { return read_word(addr) | (u16(read_word(addr + 2)) << 16); }
virtual void write_byte(u32 addr, u8 data) {}
inline void write_word(u32 addr, u16 data)
{
write_byte(addr, data & 0xff);
write_byte(addr + 1, data >> 8);
}
inline void write_dword(u32 addr, u32 data)
{
write_word(addr, data & 0xffff);
write_word(addr + 2, data >> 16);
}
};
class nds_sound_t
{
public:
nds_sound_t(nds_sound_intf &intf)
: m_intf(intf)
, m_channel{
channel_t(*this, false, false), channel_t(*this, false, false),
channel_t(*this, false, false), channel_t(*this, false, false),
channel_t(*this, false, false), channel_t(*this, false, false),
channel_t(*this, false, false), channel_t(*this, false, false),
channel_t(*this, true, false), channel_t(*this, true, false),
channel_t(*this, true, false), channel_t(*this, true, false),
channel_t(*this, true, false), channel_t(*this, true, false),
channel_t(*this, false, true), channel_t(*this, false, true)
}
, m_capture{
capture_t(*this, m_channel[0], m_channel[1]),
capture_t(*this, m_channel[2], m_channel[3])
}
, m_control(0)
, m_bias(0)
, m_loutput(0)
, m_routput(0)
{
}
void reset();
void tick(s32 cycle);
// host accesses
u32 read32(u32 addr);
void write32(u32 addr, u32 data, u32 mask = ~0);
u16 read16(u32 addr);
void write16(u32 addr, u16 data, u16 mask = ~0);
u8 read8(u32 addr);
void write8(u32 addr, u8 data);
s32 loutput() { return m_loutput; }
s32 routput() { return m_routput; }
// for debug
s32 chan_lout(u8 ch) { return m_channel[ch].loutput(); }
s32 chan_rout(u8 ch) { return m_channel[ch].routput(); }
private:
// ADPCM tables
s8 adpcm_index_table[8] =
{
-1, -1, -1, -1, 2, 4, 6, 8
};
u16 adpcm_diff_table[89] =
{
0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x0010,
0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001c, 0x001f, 0x0022, 0x0025,
0x0029, 0x002d, 0x0032, 0x0037, 0x003c, 0x0042, 0x0049, 0x0050, 0x0058,
0x0061, 0x006b, 0x0076, 0x0082, 0x008f, 0x009d, 0x00ad, 0x00be, 0x00d1,
0x00e6, 0x00fd, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01c1, 0x01ee,
0x0220, 0x0256, 0x0292, 0x02d4, 0x031c, 0x036c, 0x03c3, 0x0424, 0x048e,
0x0502, 0x0583, 0x0610, 0x06ab, 0x0756, 0x0812, 0x08e0, 0x09c3, 0x0abd,
0x0bd0, 0x0cff, 0x0e4c, 0x0fba, 0x114c, 0x1307, 0x14ee, 0x1706, 0x1954,
0x1bdc, 0x1ea5, 0x21b6, 0x2515, 0x28ca, 0x2cdf, 0x315b, 0x364b, 0x3bb9,
0x41b2, 0x4844, 0x4f7e, 0x5771, 0x602f, 0x69ce, 0x7462, 0x7fff
};
// structs
enum
{
STATE_ADPCM_LOAD = 0,
STATE_PRE_LOOP,
STATE_POST_LOOP
};
class channel_t
{
public:
channel_t(nds_sound_t &host, bool psg, bool noise)
: m_host(host)
, m_psg(psg)
, m_noise(noise)
, m_control(0)
, m_sourceaddr(0)
, m_freq(0)
, m_loopstart(0)
, m_length(0)
, m_playing(false)
, m_adpcm_out(0)
, m_adpcm_index(0)
, m_prev_adpcm_out(0)
, m_prev_adpcm_index(0)
, m_cur_addr(0)
, m_cur_state(0)
, m_cur_bitaddr(0)
, m_delay(0)
, m_sample(0)
, m_lfsr(0x7fff)
, m_lfsr_out(0x7fff)
, m_counter(0x10000)
, m_output(0)
, m_loutput(0)
, m_routput(0)
{
}
void reset();
void write(u32 offset, u32 data, u32 mask = ~0);
void update(s32 cycle);
// getters
// control word
u32 control() const { return m_control; }
u32 freq() const { return m_freq; }
// outputs
s32 output() const { return m_output; }
s32 loutput() const { return m_loutput; }
s32 routput() const { return m_routput; }
private:
// inline constants
const u8 m_voldiv_shift[4] = {0, 1, 2, 4};
// control bits
s32 volume() const { return bitfield(m_control, 0, 7); } // global volume
u32 voldiv() const { return m_voldiv_shift[bitfield(m_control, 8, 2)]; } // volume shift
bool hold() const { return bitfield(m_control, 15); } // hold bit
u32 pan() const { return bitfield(m_control, 16, 7); } // panning (0...127, 0 = left, 127 = right, 64 = half)
u32 duty() const { return bitfield(m_control, 24, 3); } // PSG duty
u32 repeat() const { return bitfield(m_control, 27, 2); } // Repeat mode (Manual, Loop infinitely, One-shot)
u32 format() const { return bitfield(m_control, 29, 2); } // Sound Format (PCM8, PCM16, ADPCM, PSG/Noise when exists)
bool busy() const { return bitfield(m_control, 31); } // Busy flag
// calculated values
s32 lvol() const { return (pan() == 0x7f) ? 0 : 128 - pan(); } // calculated left volume
s32 rvol() const { return (pan() == 0x7f) ? 128 : pan(); } // calculated right volume
// calculated address
u32 addr() const { return (m_sourceaddr & ~3) + (m_cur_bitaddr >> 3) + (m_cur_state == STATE_POST_LOOP ? ((m_loopstart + m_cur_addr) << 2) : (m_cur_addr << 2)); }
void keyon();
void keyoff();
void fetch();
void advance();
// interfaces
nds_sound_t &m_host; // host device
// configuration
bool m_psg = false; // PSG Enable
bool m_noise = false; // Noise Enable
// registers
u32 m_control = 0; // Control
u32 m_sourceaddr = 0; // Source Address
u16 m_freq = 0; // Frequency
u16 m_loopstart = 0; // Loop Start
u32 m_length = 0; // Length
// internal states
bool m_playing = false; // playing flag
s32 m_adpcm_out = 0; // current ADPCM sample value
s32 m_adpcm_index = 0; // current ADPCM step
s32 m_prev_adpcm_out = 0; // previous ADPCM sample value
s32 m_prev_adpcm_index = 0; // previous ADPCM step
u32 m_cur_addr = 0; // current address
s32 m_cur_state = 0; // current state
s32 m_cur_bitaddr = 0; // bit address
s32 m_delay = 0; // delay
s16 m_sample = 0; // current sample
u32 m_lfsr = 0x7fff; // noise LFSR
s16 m_lfsr_out = 0x7fff; // LFSR output
s32 m_counter = 0x10000; // clock counter
s32 m_output = 0; // current output
s32 m_loutput = 0; // current left output
s32 m_routput = 0; // current right output
};
class capture_t
{
public:
capture_t(nds_sound_t &host, channel_t &input, channel_t &output)
: m_host(host)
, m_input(input)
, m_output(output)
, m_control(0)
, m_dstaddr(0)
, m_length(0)
, m_counter(0x10000)
, m_cur_addr(0)
, m_cur_waddr(0)
, m_cur_bitaddr(0)
, m_enable(0)
, m_fifo{
fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t(),
fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t()
}
, m_fifo_head(0)
, m_fifo_tail(0)
, m_fifo_empty(true)
, m_fifo_full(false)
{
}
void reset();
void update(s32 mix, s32 cycle);
void control_w(u8 data);
void addrlen_w(u32 offset, u32 data, u32 mask = ~0);
// getters
u32 control() const { return m_control; }
u32 dstaddr() const { return m_dstaddr; }
private:
// inline constants
// control bits
bool addmode() const { return bitfield(m_control, 0); } // Add mode (add channel 1/3 output with channel 0/2)
bool get_source() const { return bitfield(m_control, 1); } // Select source (left or right mixer, channel 0/2)
bool repeat() const { return bitfield(m_control, 2); } // repeat flag
bool format() const { return bitfield(m_control, 3); } // store format (PCM16, PCM8)
bool busy() const { return bitfield(m_control, 7); } // busy flag
// FIFO offset mask
u32 fifo_mask() const { return format() ? 7 : 3; }
// calculated address
u32 waddr() const { return (m_dstaddr & ~3) + (m_cur_waddr << 2); }
void capture_on();
void capture_off();
bool fifo_write();
// interfaces
nds_sound_t &m_host; // host device
channel_t &m_input; // Input channel
channel_t &m_output; // Output channel
// registers
u8 m_control = 0; // Control
u32 m_dstaddr = 0; // Destination Address
u32 m_length = 0; // Buffer Length
// internal states
u32 m_counter = 0x10000; // clock counter
u32 m_cur_addr = 0; // current address
u32 m_cur_waddr = 0; // current write address
s32 m_cur_bitaddr = 0; // bit address
bool m_enable = false; // capture enable
// FIFO
class fifo_data_t
{
public:
fifo_data_t()
: m_data(0)
{
}
void reset()
{
m_data = 0;
}
// accessors
void write_byte(const u8 bit, const u8 data)
{
u32 input = u32(data) << bit;
u32 mask = (0xff << bit);
m_data = (m_data & ~mask) | (input & mask);
}
void write_word(const u8 bit, const u16 data)
{
u32 input = u32(data) << bit;
u32 mask = (0xffff << bit);
m_data = (m_data & ~mask) | (input & mask);
}
// getters
u32 data() const { return m_data; }
private:
u32 m_data = 0;
};
fifo_data_t m_fifo[8]; // FIFO (8 word, for 16 sample delay)
u32 m_fifo_head = 0; // FIFO head
u32 m_fifo_tail = 0; // FIFO tail
bool m_fifo_empty = true; // FIFO empty flag
bool m_fifo_full = false; // FIFO full flag
};
nds_sound_intf &m_intf; // memory interface
channel_t m_channel[16]; // 16 channels
capture_t m_capture[2]; // 2 capture channels
inline u8 mvol() const { return bitfield(m_control, 0, 7); } // master volume
inline u8 lout_from() const { return bitfield(m_control, 8, 2); } // left output source (mixer, channel 1, channel 3, channel 1+3)
inline u8 rout_from() const { return bitfield(m_control, 10, 2); } // right output source (mixer, channel 1, channel 3, channel 1+3)
inline bool mix_ch1() const { return bitfield(m_control, 12); } // mix/bypass channel 1
inline bool mix_ch3() const { return bitfield(m_control, 13); } // mix/bypass channel 3
inline bool enable() const { return bitfield(m_control, 15); } // global enable
u32 m_control = 0; // global control
u32 m_bias = 0; // output bias
s32 m_loutput = 0; // left output
s32 m_routput = 0; // right output
};
}; // namespace nds_sound_emu
#endif // NDS_SOUND_EMU_H

View file

@ -68,13 +68,13 @@ public:
uint16_t wave; // wavetable number
uint16_t FN; // f-number TODO store 'FN | 1024'?
int8_t OCT; // octave [-8..+7]
bool PRVB; // pseudo-reverb
bool PRVB = false; // pseudo-reverb
uint8_t TLdest; // destination total level
uint8_t TL; // total level (goes towards TLdest)
uint8_t pan; // panpot 0..15
bool ch; // channel select
bool ch = false; // channel select
bool keyon; // slot keyed on
bool DAMP;
bool DAMP = false;
uint8_t lfo; // LFO speed 0..7
uint8_t vib; // vibrato 0..7
uint8_t AM; // AM level 0..7

View file

@ -484,13 +484,13 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
int destFreq=NOTE_SU(c.chan,c.value2+chan[c.chan].sampleNoteDelta);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9)));
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch)?1:(1+(chan[c.chan].baseFreq>>9)));
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9)));
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch)?1:(1+(chan[c.chan].baseFreq>>9)));
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;

View file

@ -95,7 +95,7 @@ void DivPlatformT6W28::writeOutVol(int ch) {
double DivPlatformT6W28::NOTE_SN(int ch, int note) {
double CHIP_DIVIDER=16;
if (ch==3) CHIP_DIVIDER=15;
if (parent->song.linearPitch==2 || !easyNoise) {
if (parent->song.linearPitch || !easyNoise) {
return NOTE_PERIODIC(note);
}
if (note>107) {
@ -105,7 +105,7 @@ double DivPlatformT6W28::NOTE_SN(int ch, int note) {
}
int DivPlatformT6W28::snCalcFreq(int ch) {
if (parent->song.linearPitch==2 && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(107<<7)) {
if (parent->song.linearPitch && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(107<<7)) {
int ret=(((13<<7)+0x40)-(chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2-(107<<7)))>>7;
if (ret<0) ret=0;
return ret;
@ -129,7 +129,7 @@ void DivPlatformT6W28::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (i==3 && chan[i].std.duty.had) {
if (chan[i].duty!=chan[i].std.duty.val) {
if (chan[i].duty!=(((chan[i].std.duty.val==1)?4:0)|3)) {
chan[i].duty=((chan[i].std.duty.val==1)?4:0)|3;
rWrite(1,0xe0+chan[i].duty);
}
@ -153,7 +153,9 @@ void DivPlatformT6W28::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
rWrite(1,0xe0+chan[i].duty);
if (chan[i].std.phaseReset.val==1) {
rWrite(1,0xe0+chan[i].duty);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=snCalcFreq(i);

View file

@ -321,7 +321,7 @@ void DivPlatformTX81Z::tick(bool sysTick) {
}
// fixed pitch
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
bool freqChangeOp=false;
if (op.egt) {

View file

@ -661,7 +661,7 @@ void DivPlatformYM2203::tick(bool sysTick) {
for (int i=0; i<3; i++) {
if (i==2 && extMode) continue;
if (chan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[i].state.block);
} else {
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2);
@ -867,7 +867,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
}
break;
}
if (c.chan>(psgChanOffs-1) || parent->song.linearPitch==2) { // PSG
if (c.chan>(psgChanOffs-1) || parent->song.linearPitch) { // PSG
int destFreq=NOTE_FNUM_BLOCK(c.value2,11,chan[c.chan].state.block);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {

View file

@ -146,7 +146,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
@ -551,7 +551,7 @@ void DivPlatformYM2203Ext::tick(bool sysTick) {
unsigned char hardResetMask=0;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2);

View file

@ -916,7 +916,7 @@ void DivPlatformYM2608::tick(bool sysTick) {
for (int i=0; i<6; i++) {
if (i==2 && extMode) continue;
if (chan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[i].state.block);
} else {
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2);
@ -1395,7 +1395,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
}
break;
}
if (c.chan>(5+isCSM) || parent->song.linearPitch==2) { // PSG, ADPCM-B
if (c.chan>(5+isCSM) || parent->song.linearPitch) { // PSG, ADPCM-B
int destFreq=NOTE_OPNB(c.chan,c.value2+chan[c.chan].sampleNoteDelta);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {

View file

@ -166,7 +166,7 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
@ -612,7 +612,7 @@ void DivPlatformYM2608Ext::tick(bool sysTick) {
unsigned char hardResetMask=0;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2);

View file

@ -836,7 +836,7 @@ void DivPlatformYM2610::tick(bool sysTick) {
for (int i=0; i<(psgChanOffs-isCSM); i++) {
if (i==1 && extMode) continue;
if (chan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[i].state.block);
} else {
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
@ -1354,7 +1354,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
}
break;
}
if (c.chan>=psgChanOffs || parent->song.linearPitch==2) { // PSG, ADPCM-B
if (c.chan>=psgChanOffs || parent->song.linearPitch) { // PSG, ADPCM-B
int destFreq=NOTE_OPNB(c.chan,c.value2+chan[c.chan].sampleNoteDelta);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {

View file

@ -905,7 +905,7 @@ void DivPlatformYM2610B::tick(bool sysTick) {
for (int i=0; i<(psgChanOffs-isCSM); i++) {
if (i==2 && extMode) continue;
if (chan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[i].state.block);
} else {
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,4,chan[i].pitch2);
@ -1423,7 +1423,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
}
break;
}
if (c.chan>=psgChanOffs || parent->song.linearPitch==2) { // PSG, ADPCM-B
if (c.chan>=psgChanOffs || parent->song.linearPitch) { // PSG, ADPCM-B
int destFreq=NOTE_OPNB(c.chan,c.value2+chan[c.chan].sampleNoteDelta);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {

View file

@ -162,7 +162,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
@ -604,7 +604,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
unsigned char hardResetMask=0;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2);

View file

@ -162,7 +162,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
@ -604,7 +604,7 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
unsigned char hardResetMask=0;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
if (parent->song.linearPitch==2) {
if (parent->song.linearPitch) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11,chan[extChanOffs].state.block);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,opChan[i].fixedArp?opChan[i].baseNoteOverride:opChan[i].arpOff,opChan[i].fixedArp,false,4,opChan[i].pitch2);

View file

@ -287,7 +287,7 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) {
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2+chan[c.chan].sampleNoteDelta);
bool return2=false;
int multiplier=(parent->song.linearPitch==2)?1:256;
int multiplier=(parent->song.linearPitch)?1:256;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*multiplier;
if (chan[c.chan].baseFreq>=destFreq) {

File diff suppressed because it is too large Load diff

View file

@ -271,6 +271,15 @@ String SafeReader::readStringWithEncoding(DivStringEncoding encoding, size_t stl
ret.push_back(c);
}
}
} else if (encoding==DIV_ENCODING_LATIN1_SPECIAL) {
if (c&0x80) {
if (c>=0xa0) {
ret.push_back(0xc0|(c>>6));
ret.push_back(0x80|(c&63));
}
} else {
ret.push_back(c);
}
} else {
ret.push_back(c);
}
@ -297,6 +306,15 @@ String SafeReader::readStringWithEncoding(DivStringEncoding encoding) {
ret.push_back(c);
}
}
} else if (encoding==DIV_ENCODING_LATIN1_SPECIAL) {
if (c&0x80) {
if (c>=0xa0) {
ret.push_back(0xc0|(c>>6));
ret.push_back(0x80|(c&63));
}
} else {
ret.push_back(c);
}
} else {
ret.push_back(c);
}
@ -320,6 +338,14 @@ String SafeReader::readStringLatin1(size_t stlen) {
return readStringWithEncoding(DIV_ENCODING_LATIN1,stlen);
}
String SafeReader::readStringLatin1Special() {
return readStringWithEncoding(DIV_ENCODING_LATIN1_SPECIAL);
}
String SafeReader::readStringLatin1Special(size_t stlen) {
return readStringWithEncoding(DIV_ENCODING_LATIN1_SPECIAL,stlen);
}
String SafeReader::readStringLine() {
String ret;
unsigned char c;

View file

@ -33,6 +33,7 @@ enum DivStringEncoding {
DIV_ENCODING_NONE=0,
DIV_ENCODING_UTF8,
DIV_ENCODING_LATIN1,
DIV_ENCODING_LATIN1_SPECIAL,
DIV_ENCODING_SHIFT_JIS
};
@ -77,6 +78,8 @@ class SafeReader {
String readString(size_t len);
String readStringLatin1();
String readStringLatin1(size_t len);
String readStringLatin1Special();
String readStringLatin1Special(size_t len);
String readStringLine();
String readStringToken(unsigned char delim, bool stripContiguous);
String readStringToken();

View file

@ -544,6 +544,10 @@ bool DivSample::saveRaw(const char* path) {
// 16-bit memory is padded to 512, to make things easier for ADPCM-A/B.
bool DivSample::initInternal(DivSampleDepth d, int count) {
if (count<0) {
logE("initInternal(%d,%d) - NEGATIVE!",(int)d,count);
return false;
}
logV("initInternal(%d,%d)",(int)d,count);
switch (d) {
case DIV_SAMPLE_DEPTH_1BIT: // 1-bit
@ -650,7 +654,11 @@ bool DivSample::initInternal(DivSampleDepth d, int count) {
return true;
}
bool DivSample::init(unsigned int count) {
bool DivSample::init(int count) {
if (count<0 || count>16777215) {
logE("tried to init sample with length %d!",count);
return false;
}
if (!initInternal(depth,count)) return false;
setSampleCount(count);
return true;

View file

@ -243,7 +243,7 @@ struct DivSample {
* @param count number of samples.
* @return whether it was successful.
*/
bool init(unsigned int count);
bool init(int count);
/**
* resize sample data. make sure the sample has been initialized before doing so.

View file

@ -54,7 +54,12 @@ sf_count_t SFWrapper::ioGetSize() {
}
sf_count_t SFWrapper::ioSeek(sf_count_t offset, int whence) {
return fseek(f,offset,whence);
fseek(f,offset,whence);
long ret=ftell(f);
if (ret<0) {
return -1;
}
return ret;
}
sf_count_t SFWrapper::ioRead(void* ptr, sf_count_t count) {

View file

@ -54,9 +54,9 @@ bool DivSubSong::walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int
}
for (int k=0; k<chans; k++) {
for (int l=0; l<pat[k].effectCols; l++) {
effectVal=subPat[k]->data[j][5+(l<<1)];
effectVal=subPat[k]->newData[j][DIV_PAT_FXVAL(l)];
if (effectVal<0) effectVal=0;
if (subPat[k]->data[j][4+(l<<1)]==0x0d) {
if (subPat[k]->newData[j][DIV_PAT_FX(l)]==0x0d) {
if (jumpTreatment==2) {
if ((i<ordersLen-1 || !ignoreJumpAtEnd)) {
nextOrder=i+1;
@ -78,7 +78,7 @@ bool DivSubSong::walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int
nextRow=effectVal;
}
}
} else if (subPat[k]->data[j][4+(l<<1)]==0x0b) {
} else if (subPat[k]->newData[j][DIV_PAT_FX(l)]==0x0b) {
if (nextOrder==-1 || jumpTreatment==0) {
nextOrder=effectVal;
if (jumpTreatment==1 || jumpTreatment==2 || !jumpingOrder) {
@ -160,10 +160,10 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int&
}
for (int k=0; k<chans; k++) {
for (int l=0; l<pat[k].effectCols; l++) {
effectVal=subPat[k]->data[j][5+(l<<1)];
effectVal=subPat[k]->newData[j][DIV_PAT_FXVAL(l)];
if (effectVal<0) effectVal=0;
if (subPat[k]->data[j][4+(l<<1)]==0xff) {
if (subPat[k]->newData[j][DIV_PAT_FX(l)]==0xff) {
hasFFxx=true;
// FFxx makes YOU SHALL NOT PASS!!! move
@ -173,7 +173,7 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int&
return;
}
switch (subPat[k]->data[j][4+(l<<1)]) {
switch (subPat[k]->newData[j][DIV_PAT_FX(l)]) {
case 0x09: { // select groove pattern/speed 1
if (grooves.empty()) {
if (effectVal>0) curSpeeds.val[0]=effectVal;
@ -208,7 +208,7 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int&
}
}
if (subPat[k]->data[j][4+(l<<1)]==0x0d) {
if (subPat[k]->newData[j][DIV_PAT_FX(l)]==0x0d) {
if (jumpTreatment==2) {
if ((i<ordersLen-1 || !ignoreJumpAtEnd)) {
nextOrder=i+1;
@ -230,7 +230,7 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int&
nextRow=effectVal;
}
}
} else if (subPat[k]->data[j][4+(l<<1)]==0x0b) {
} else if (subPat[k]->newData[j][DIV_PAT_FX(l)]==0x0b) {
if (nextOrder==-1 || jumpTreatment==0) {
nextOrder=effectVal;
if (jumpTreatment==1 || jumpTreatment==2 || !jumpingOrder) {
@ -278,6 +278,24 @@ void DivSubSong::clearData() {
ordersLen=1;
}
void DivSubSong::removeUnusedPatterns() {
for (int i=0; i<DIV_MAX_CHANS; i++) {
bool used[DIV_MAX_PATTERNS];
memset(used,0,DIV_MAX_PATTERNS*sizeof(bool));
for (int j=0; j<ordersLen; j++) {
used[orders.ord[i][j]]=true;
}
for (int j=0; j<DIV_MAX_PATTERNS; j++) {
if (!used[j] && pat[i].data[j]!=NULL) {
delete pat[i].data[j];
pat[i].data[j]=NULL;
}
}
}
}
void DivSubSong::optimizePatterns() {
for (int i=0; i<DIV_MAX_CHANS; i++) {
logD("optimizing channel %d...",i);

View file

@ -196,6 +196,7 @@ struct DivSubSong {
void findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, std::vector<DivGroovePattern>& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0);
void clearData();
void removeUnusedPatterns();
void optimizePatterns();
void rearrangePatterns();
void sortOrders();
@ -280,9 +281,6 @@ struct DivSong {
// compatibility flags
bool limitSlides;
// linear pitch
// 0: not linear
// 1: only pitch changes (04xy/E5xx) linear
// 2: full linear
unsigned char linearPitch;
unsigned char pitchSlideSpeed;
// loop behavior
@ -427,7 +425,7 @@ struct DivSong {
masterVol(1.0f),
tuning(440.0f),
limitSlides(false),
linearPitch(2),
linearPitch(1),
pitchSlideSpeed(4),
loopModality(2),
delayBehavior(2),

View file

@ -623,6 +623,12 @@ void DivEngine::registerSystems() {
{0x2f, {DIV_CMD_MULTIPCM_LEVEL_DIRECT, _("2Fxx: PCM Level Direct"), effectValAnd<1>}},
});
EffectHandlerMap multiPCMPostEffectHandlerMap={
{0x20, {DIV_CMD_MULTIPCM_LFO, _("20xx: PCM LFO Rate (0 to 7)"), effectValAnd<7>}},
{0x21, {DIV_CMD_MULTIPCM_VIB, _("21xx: PCM LFO PM Depth (0 to 7)"), effectValAnd<7>}},
{0x22, {DIV_CMD_MULTIPCM_AM, _("22xx: PCM LFO AM Depth (0 to 7)"), effectValAnd<7>}},
};
EffectHandlerMap c64PostEffectHandlerMap={
{0x10, {DIV_CMD_WAVE, _("10xx: Set waveform (bit 0: triangle; bit 1: saw; bit 2: pulse; bit 3: noise)")}},
{0x11, {DIV_CMD_C64_CUTOFF, _("11xx: Set coarse cutoff (not recommended; use 4xxx instead)")}},
@ -1169,6 +1175,8 @@ void DivEngine::registerSystems() {
{},
{
{0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}},
},
{
{0x11, {DIV_CMD_FDS_MOD_DEPTH, _("11xx: Set modulation depth")}},
{0x12, {DIV_CMD_FDS_MOD_HIGH, _("12xy: Set modulation speed high byte (x: enable; y: value)")}},
{0x13, {DIV_CMD_FDS_MOD_LOW, _("13xx: Set modulation speed low byte")}},
@ -1327,14 +1335,16 @@ void DivEngine::registerSystems() {
fmOPLPostEffectHandlerMap
);
// TODO: add 12-bit and 16-bit big endian formats
sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef(
_("MultiPCM"), NULL, 0x92, 0, 28, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 0, 0,
_("MultiPCM"), NULL, 0x92, 0, 28, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_12BIT), 0, 0,
_("how many channels of PCM do you want?\nMultiPCM: yes"),
{_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5"), _("Channel 6"), _("Channel 7"), _("Channel 8"), _("Channel 9"), _("Channel 10"), _("Channel 11"), _("Channel 12"), _("Channel 13"), _("Channel 14"), _("Channel 15"), _("Channel 16"), _("Channel 17"), _("Channel 18"), _("Channel 19"), _("Channel 20"), _("Channel 21"), _("Channel 22"), _("Channel 23"), _("Channel 24"), _("Channel 25"), _("Channel 26"), _("Channel 27"), _("Channel 28")},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM}
{DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM},
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
{},
multiPCMPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_PCSPKR]=new DivSysDef(

View file

@ -708,6 +708,21 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0x04);
w->writeC(0x00);
break;
case DIV_SYSTEM_MULTIPCM:
for (int i=0; i<28; i++) {
w->writeC(0xb5); // set channel
w->writeC(baseAddr2|1);
w->writeC(i);
for (int j=0; j<8; j++) {
w->writeC(0xb5);
w->writeC(baseAddr2|2);
w->writeC(j);
w->writeC(0xb5); // keyoff
w->writeC(baseAddr2|0);
w->writeC(0);
}
}
break;
default:
break;
}
@ -1216,6 +1231,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case DIV_SYSTEM_MULTIPCM:
w->writeC(0xb5);
w->writeC(baseAddr2|(write.addr&0x7f));
w->writeC(write.val);
break;
default:
logW("write not handled!");
break;
@ -1398,6 +1418,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
DivDispatch* writeC219[2]={NULL,NULL};
DivDispatch* writeNES[2]={NULL,NULL};
DivDispatch* writePCM_OPL4[2]={NULL,NULL};
DivDispatch* writeMultiPCM[2]={NULL,NULL};
int writeNESIndex[2]={0,0};
@ -2018,6 +2039,21 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
howManyChips++;
}
break;
case DIV_SYSTEM_MULTIPCM:
if (!hasMultiPCM) {
hasMultiPCM=disCont[i].dispatch->rate*180; // for fix pitch in VGM players
CHIP_VOL(13,1.0);
willExport[i]=true;
writeMultiPCM[0]=disCont[i].dispatch;
} else if (!(hasMultiPCM&0x40000000)) {
isSecond[i]=true;
CHIP_VOL_SECOND(13,1.0);
willExport[i]=true;
writeMultiPCM[1]=disCont[i].dispatch;
hasMultiPCM|=0x40000000;
howManyChips++;
}
break;
default:
break;
}
@ -2369,13 +2405,26 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
}
// PCM (OPL4)
if (writePCM_OPL4[i]!=NULL && writePCM_OPL4[i]->getSampleMemUsage(0)>0) {
size_t usage=writePCM_OPL4[i]->getSampleMemUsage(0)-writePCM_OPL4[i]->getSampleMemOffset(0);
unsigned char* mem=((unsigned char*)writePCM_OPL4[i]->getSampleMem(0))+writePCM_OPL4[i]->getSampleMemOffset(0);
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x84);
w->writeI((writePCM_OPL4[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
w->writeI((usage+8)|(i*0x80000000));
w->writeI(writePCM_OPL4[i]->getSampleMemCapacity(0));
w->writeI(writePCM_OPL4[i]->getSampleMemOffset(0));
for (size_t i=0; i<usage; i++) {
w->writeC(mem[i]);
}
}
if (writeMultiPCM[i]!=NULL && writeMultiPCM[i]->getSampleMemUsage()>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x89);
w->writeI((writeMultiPCM[i]->getSampleMemUsage()+8)|(i*0x80000000));
w->writeI(writeMultiPCM[i]->getSampleMemCapacity());
w->writeI(0);
w->write(writePCM_OPL4[i]->getSampleMem(0),writePCM_OPL4[i]->getSampleMemUsage(0));
w->write(writeMultiPCM[i]->getSampleMem(),writeMultiPCM[i]->getSampleMemUsage());
}
}
@ -2407,13 +2456,17 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
w->write(writeGA20[i]->getSampleMem(),writeGA20[i]->getSampleMemUsage());
}
if (writeK053260[i]!=NULL && writeK053260[i]->getSampleMemUsage()>0) {
size_t usage=writeK053260[i]->getSampleMemUsage()-writeK053260[i]->getSampleMemOffset();
unsigned char* mem=((unsigned char*)writeK053260[i]->getSampleMem())+writeK053260[i]->getSampleMemOffset();
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x8e);
w->writeI((writeK053260[i]->getSampleMemUsage()+8)|(i*0x80000000));
w->writeI((usage+8)|(i*0x80000000));
w->writeI(writeK053260[i]->getSampleMemCapacity());
w->writeI(0);
w->write(writeK053260[i]->getSampleMem(),writeK053260[i]->getSampleMemUsage());
w->writeI(writeK053260[i]->getSampleMemOffset());
for (size_t i=0; i<usage; i++) {
w->writeC(mem[i]);
}
}
if (writeNES[i]!=NULL && writeNES[i]->getSampleMemUsage()>0) {
if (dpcm07) {
@ -2458,17 +2511,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
for (int i=0; i<2; i++) {
if (writeES5506[i]!=NULL && writeES5506[i]->getSampleMemUsage()>0) {
// split sample data into 4 areas
unsigned short* mem=(unsigned short*)writeES5506[i]->getSampleMem();
int memOffs=(int)writeES5506[i]->getSampleMemOffset();
unsigned short* mem=((unsigned short*)writeES5506[i]->getSampleMem())+(memOffs>>1);
for (int b=0; b<4; b++) {
int offs=b<<22;
int memLen=CLAMP((int)writeES5506[i]->getSampleMemUsage()-offs,0,0x400000);
int memLen=CLAMP((int)writeES5506[i]->getSampleMemUsage()-memOffs-offs,0,0x400000-memOffs);
if (memLen>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x90);
w->writeI((memLen+8)|(i*0x80000000));
w->writeI(MIN(writeES5506[i]->getSampleMemCapacity(),0x400000));
w->writeI(b<<28);
w->writeI(memOffs+(b<<28));
for (int i=0; i<(memLen>>1); i++) {
w->writeS(mem[(offs>>1)+i]);
}

View file

@ -111,6 +111,60 @@ bool DivEngine::getIsFadingOut() {
}
#ifdef HAVE_SNDFILE
#define MAP_BITRATE \
if (exportFormat!=DIV_EXPORT_FORMAT_WAV) { \
double mappedLevel=0.0; \
\
switch (exportFormat) { \
case DIV_EXPORT_FORMAT_OPUS: \
mappedLevel=1.0-((double)((exportBitRate/(double)MAX(1,exportOutputs))-6000.0)/250000.0); \
break; \
case DIV_EXPORT_FORMAT_FLAC: \
mappedLevel=exportVBRQuality*0.125; \
break; \
case DIV_EXPORT_FORMAT_VORBIS: \
mappedLevel=1.0-(exportVBRQuality*0.1); \
break; \
case DIV_EXPORT_FORMAT_MPEG_L3: { \
int mappedBitRateMode=SF_BITRATE_MODE_CONSTANT; \
switch (exportBitRateMode) { \
case DIV_EXPORT_BITRATE_CONSTANT: \
mappedBitRateMode=SF_BITRATE_MODE_CONSTANT; \
break; \
case DIV_EXPORT_BITRATE_VARIABLE: \
mappedBitRateMode=SF_BITRATE_MODE_VARIABLE; \
break; \
case DIV_EXPORT_BITRATE_AVERAGE: \
mappedBitRateMode=SF_BITRATE_MODE_AVERAGE; \
break; \
} \
if (exportBitRateMode==DIV_EXPORT_BITRATE_VARIABLE) { \
mappedLevel=1.0-(exportVBRQuality*0.1); \
} else { \
if (got.rate>=32000) { \
mappedLevel=(320000.0-(double)exportBitRate)/288000.0; \
} else if (got.rate>=16000) { \
mappedLevel=(160000.0-(double)exportBitRate)/152000.0; \
} else { \
mappedLevel=(64000.0-(double)exportBitRate)/56000.0; \
} \
} \
\
if (sf_command(sf,SFC_SET_BITRATE_MODE,&mappedBitRateMode,sizeof(mappedBitRateMode))==SF_FALSE) { \
logE("could not set bit rate mode! (%s)",sf_strerror(sf)); \
} \
break; \
} \
default: \
break; \
} \
\
if (sf_command(sf,SFC_SET_COMPRESSION_LEVEL,&mappedLevel,sizeof(mappedLevel))!=SF_TRUE) { \
logE("could not set compression level! (%s)",sf_strerror(sf)); \
} \
}
void DivEngine::runExportThread() {
size_t fadeOutSamples=got.rate*exportFadeOut;
size_t curFadeOutSample=0;
@ -121,12 +175,39 @@ void DivEngine::runExportThread() {
SNDFILE* sf;
SF_INFO si;
SFWrapper sfWrap;
memset(&si,0,sizeof(SF_INFO));
si.samplerate=got.rate;
si.channels=exportOutputs;
if (exportFormat==DIV_EXPORT_FORMAT_S16) {
si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
} else {
si.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
switch (exportFormat) {
case DIV_EXPORT_FORMAT_WAV:
si.format=SF_FORMAT_WAV;
switch (wavFormat) {
case DIV_EXPORT_WAV_U8:
si.format|=SF_FORMAT_PCM_U8;
break;
case DIV_EXPORT_WAV_S16:
si.format|=SF_FORMAT_PCM_16;
break;
case DIV_EXPORT_WAV_F32:
si.format|=SF_FORMAT_FLOAT;
break;
default:
si.format|=SF_FORMAT_PCM_U8;
break;
}
break;
case DIV_EXPORT_FORMAT_OPUS:
si.format=SF_FORMAT_OGG|SF_FORMAT_OPUS;
break;
case DIV_EXPORT_FORMAT_FLAC:
si.format=SF_FORMAT_FLAC|SF_FORMAT_PCM_16;
break;
case DIV_EXPORT_FORMAT_VORBIS:
si.format=SF_FORMAT_OGG|SF_FORMAT_VORBIS;
break;
case DIV_EXPORT_FORMAT_MPEG_L3:
si.format=SF_FORMAT_MPEG|SF_FORMAT_MPEG_LAYER_III;
break;
}
sf=sfWrap.doOpen(exportPath.c_str(),SFM_WRITE,&si);
@ -136,6 +217,8 @@ void DivEngine::runExportThread() {
return;
}
MAP_BITRATE;
float* outBuf[DIV_MAX_OUTPUTS];
float* outBufFinal;
for (int i=0; i<exportOutputs; i++) {
@ -163,6 +246,7 @@ void DivEngine::runExportThread() {
total++;
if (isFadingOut) {
double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples));
if (fadeOutSamples<1.0) mul=0.0;
for (int j=0; j<exportOutputs; j++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[j][i]))*mul;
}
@ -216,6 +300,7 @@ void DivEngine::runExportThread() {
String fname[DIV_MAX_CHIPS];
SFWrapper sfWrap[DIV_MAX_CHIPS];
for (int i=0; i<song.systemLen; i++) {
memset(&si[0],0,sizeof(SF_INFO));
sf[i]=NULL;
si[i].samplerate=got.rate;
si[i].channels=disCont[i].dispatch->getOutputCount();
@ -263,6 +348,7 @@ void DivEngine::runExportThread() {
total++;
if (isFadingOut) {
double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples));
if (fadeOutSamples<1.0) mul=0.0;
for (int i=0; i<song.systemLen; i++) {
for (int k=0; k<si[i].channels; k++) {
if (disCont[i].bbOut[k]==NULL) {
@ -345,14 +431,41 @@ void DivEngine::runExportThread() {
SNDFILE* sf;
SF_INFO si;
SFWrapper sfWrap;
memset(&si,0,sizeof(SF_INFO));
String fname=fmt::sprintf("%s_c%02d.wav",exportPath,i+1);
logI("- %s",fname.c_str());
si.samplerate=got.rate;
si.channels=exportOutputs;
if (exportFormat==DIV_EXPORT_FORMAT_S16) {
si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
} else {
si.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
switch (exportFormat) {
case DIV_EXPORT_FORMAT_WAV:
si.format=SF_FORMAT_WAV;
switch (wavFormat) {
case DIV_EXPORT_WAV_U8:
si.format|=SF_FORMAT_PCM_U8;
break;
case DIV_EXPORT_WAV_S16:
si.format|=SF_FORMAT_PCM_16;
break;
case DIV_EXPORT_WAV_F32:
si.format|=SF_FORMAT_FLOAT;
break;
default:
si.format|=SF_FORMAT_PCM_U8;
break;
}
break;
case DIV_EXPORT_FORMAT_OPUS:
si.format=SF_FORMAT_OGG|SF_FORMAT_OPUS;
break;
case DIV_EXPORT_FORMAT_FLAC:
si.format=SF_FORMAT_FLAC|SF_FORMAT_PCM_16;
break;
case DIV_EXPORT_FORMAT_VORBIS:
si.format=SF_FORMAT_OGG|SF_FORMAT_VORBIS;
break;
case DIV_EXPORT_FORMAT_MPEG_L3:
si.format=SF_FORMAT_MPEG|SF_FORMAT_MPEG_LAYER_III;
break;
}
sf=sfWrap.doOpen(fname.c_str(),SFM_WRITE,&si);
@ -361,6 +474,8 @@ void DivEngine::runExportThread() {
break;
}
MAP_BITRATE;
for (int j=0; j<chans; j++) {
bool mute=(j!=i);
isMuted[j]=mute;
@ -400,6 +515,7 @@ void DivEngine::runExportThread() {
total++;
if (isFadingOut) {
double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples));
if (fadeOutSamples<1.0) mul=0.0;
for (int k=0; k<exportOutputs; k++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[k][j]))*mul;
}
@ -490,6 +606,10 @@ bool DivEngine::saveAudio(const char* path, DivAudioExportOptions options) {
exportPath=path;
exportMode=options.mode;
exportFormat=options.format;
wavFormat=options.wavFormat;
exportBitRate=options.bitRate;
exportBitRateMode=options.bitRateMode;
exportVBRQuality=options.vbrQuality;
exportFadeOut=options.fadeOut;
memcpy(exportChannelMask,options.channelMask,DIV_MAX_CHANS*sizeof(bool));
if (exportMode!=DIV_EXPORT_MODE_ONE) {
@ -509,7 +629,12 @@ bool DivEngine::saveAudio(const char* path, DivAudioExportOptions options) {
repeatPattern=false;
setOrder(0);
remainingLoops=-1;
got.rate=options.sampleRate;
if (options.format==DIV_EXPORT_FORMAT_OPUS) {
// Opus only supports 48KHz and a couple divisors of that number...
got.rate=48000;
} else {
got.rate=options.sampleRate;
}
if (shallSwitchCores()) {
bool isMutedBefore[DIV_MAX_CHANS];

View file

@ -50,6 +50,27 @@ bool moveFiles(const char* src, const char* dest) {
#endif
}
bool copyFiles(const char* src, const char* dest) {
FILE* f=ps_fopen(src,"rb");
if (f==NULL) return false;
FILE* of=ps_fopen(dest,"wb");
if (of==NULL) {
fclose(f);
return false;
}
char block[2048];
while (!feof(f)) {
size_t readTotal=fread(block,1,2048,f);
if (readTotal) fwrite(block,1,readTotal,of);
}
fclose(of);
fclose(f);
return true;
}
bool deleteFile(const char* path) {
#ifdef _WIN32
return DeleteFileW(utf8To16(path).c_str());

View file

@ -23,6 +23,7 @@
FILE* ps_fopen(const char* path, const char* mode);
bool moveFiles(const char* src, const char* dest);
bool copyFiles(const char* src, const char* dest);
bool deleteFile(const char* path);
// returns 1 if file exists, 0 if it doesn't and -1 on error.
int fileExists(const char* path);

View file

@ -118,9 +118,22 @@ const char* aboutLine[]={
_N("zlib by Jean-loup Gailly"),
_N("and Mark Adler"),
_N("libsndfile by Erik de Castro Lopo"),
#ifdef HAVE_OGG
_N("libogg by Xiph.Org Foundation"),
_N("libvorbis by Xiph.Org Foundation"),
_N("FLAC library by Xiph.Org Foundation"),
_N("libopus by Xiph.Org and contributors"),
#endif
#ifdef HAVE_MP3_EXPORT
_N("libmpg123 by Michael Hipp, Thomas Orgis, Taihei Momma and contributors"),
_N("LAME by Mike Cheng, Mark Taylor and The LAME Project"),
#endif
_N("Portable File Dialogs by Sam Hocevar"),
_N("Native File Dialog by Frogtoss Games"),
"PortAudio",
#ifdef HAVE_ASIO
_N("ASIO® by Steinberg Media Technologies"),
#endif
_N("Weak-JACK by x42"),
_N("RtMidi by Gary P. Scavone"),
_N("FFTW by Matteo Frigo and Steven G. Johnson"),
@ -186,8 +199,13 @@ const char* aboutLine[]={
"",
_N("copyright © 2021-2025 tildearrow"),
_N("(and contributors)."),
#ifdef FURNACE_GPL3
_N("licensed under GPLv3! see"),
_N("LICENSE for more information."),
#else
_N("licensed under GPLv2+! see"),
_N("LICENSE for more information."),
#endif
"",
_N("help Furnace grow:"),
"https://github.com/tildearrow/furnace",
@ -203,6 +221,10 @@ const char* aboutLine[]={
_N("the original program."),
"",
_N("it also comes with ABSOLUTELY NO WARRANTY."),
#ifdef HAVE_ASIO
"",
_N("ASIO is a registered trademark of Steinberg Media Technologies GmbH."),
#endif
"",
_N("thanks to all contributors/bug reporters!")
};

View file

@ -902,10 +902,8 @@ void FurnaceGUI::drawChanOsc() {
case 'n': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL || !(chanState->keyOn)) break;
short tempNote=chanState->note; //all of this conversion is necessary because notes 100-102 are special chars
short noteMod=tempNote%12+12; //also note 0 is a BUG, hence +12 on the note and -1 on the octave
short oct=tempNote/12-1;
text+=fmt::sprintf("%s",noteName(noteMod,oct));
// no more conversion necessary after the note/octave unification :>
text+=fmt::sprintf("%s",noteName(chanState->note+60));
break;
}
case 'l': {

View file

@ -50,7 +50,6 @@ void FurnaceGUI::drawChannels() {
ImGui::TableNextColumn();
ImGui::Text(_("Osc"));
ImGui::TableNextColumn();
ImGui::Text(_("Swap"));
ImGui::TableNextColumn();
ImGui::Text(_("Name"));
for (int i=0; i<e->getTotalChannelCount(); i++) {
@ -79,14 +78,17 @@ void FurnaceGUI::drawChannels() {
ImGui::Button(ICON_FA_ARROWS "##ChanDrag");
ImGui::EndDragDropSource();
} else if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("%s #%d\n(drag to swap channels)"),e->getSystemName(e->sysOfChan[i]),e->dispatchChanOfChan[i]);
ImGui::SetTooltip(_("%s #%d\n(drag to swap channels)\n(Shift-drag to copy channel contents)"),e->getSystemName(e->sysOfChan[i]),e->dispatchChanOfChan[i]);
}
if (ImGui::BeginDragDropTarget()) {
const ImGuiPayload* dragItem=ImGui::AcceptDragDropPayload("FUR_CHAN");
if (dragItem!=NULL) {
if (dragItem->IsDataType("FUR_CHAN")) {
if (chanToMove!=i && chanToMove>=0) {
e->swapChannelsP(chanToMove,i);
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift))
e->copyChannelP(chanToMove,i);
else
e->swapChannelsP(chanToMove,i);
MARK_MODIFIED;
}
chanToMove=-1;

View file

@ -302,26 +302,15 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::BeginTabItem(_("Pitch/Playback"))) {
ImGui::Text(_("Pitch linearity:"));
ImGui::Indent();
if (ImGui::RadioButton(_("None"),e->song.linearPitch==0)) {
if (ImGui::RadioButton(_("None"),!e->song.linearPitch)) {
e->song.linearPitch=0;
MARK_MODIFIED;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("like ProTracker/FamiTracker"));
}
if (e->song.linearPitch==1) {
pushWarningColor(true);
if (ImGui::RadioButton(_("Partial (only 04xy/E5xx)"),e->song.linearPitch==1)) {
e->song.linearPitch=1;
MARK_MODIFIED;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("like DefleMask\n\nthis pitch linearity mode is deprecated due to:\n- excessive complexity\n- lack of possible optimization\n\nit is recommended to change it now because I will remove this option in the future!"));
}
popWarningColor();
}
if (ImGui::RadioButton(_("Full"),e->song.linearPitch==2)) {
e->song.linearPitch=2;
if (ImGui::RadioButton(_("Full"),e->song.linearPitch)) {
e->song.linearPitch=1;
MARK_MODIFIED;
}
if (ImGui::IsItemHovered()) {

View file

@ -279,6 +279,7 @@ void FurnaceGUI::sampleListItem(int i, int dir, int asset) {
curSample=i;
samplePos=0;
updateSampleTex=true;
notifySampleChange=true;
lastAssetType=2;
}
if (ImGui::IsItemHovered() && !mobileUI) {
@ -309,6 +310,7 @@ void FurnaceGUI::sampleListItem(int i, int dir, int asset) {
curSample=i;
samplePos=0;
updateSampleTex=true;
notifySampleChange=true;
lastAssetType=2;
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
if (ImGui::MenuItem(_("edit"))) {

View file

@ -57,6 +57,7 @@
#include "../engine/platform/k053260.h"
#include "../engine/platform/c140.h"
#include "../engine/platform/msm6295.h"
#include "../engine/platform/multipcm.h"
#include "../engine/platform/dummy.h"
#define COMMON_CHIP_DEBUG \
@ -550,6 +551,16 @@ void putDispatchChip(void* data, int type) {
COMMON_CHIP_DEBUG_BOOL;
break;
}
case DIV_SYSTEM_MULTIPCM: {
DivPlatformMultiPCM* ch=(DivPlatformMultiPCM*)data;
ImGui::Text("> MultiPCM");
COMMON_CHIP_DEBUG;
ImGui::Text("- delay: %d",ch->delay);
ImGui::Text("- curChan: %.2x",ch->curChan);
ImGui::Text("- curAddr: %.2x",ch->curAddr);
COMMON_CHIP_DEBUG_BOOL;
break;
}
default: {
DivDispatch* ch=(DivDispatch*)data;
COMMON_CHIP_DEBUG;
@ -1097,6 +1108,22 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::TextColored(ch->setPos?colorOn:colorOff,">> SetPos");
break;
}
case DIV_SYSTEM_MULTIPCM: {
DivPlatformMultiPCM::Channel* ch=(DivPlatformMultiPCM::Channel*)data;
ImGui::Text("> MultiPCM");
COMMON_CHAN_DEBUG;
ImGui::Text("- Sample: %d",ch->sample);
ImGui::Text("- freqHL: %.2x%.2x",ch->freqH,ch->freqL);
ImGui::Text("- lfo: %.2x",ch->lfo);
ImGui::Text("- vib: %.2x",ch->vib);
ImGui::Text("- am: %.2x",ch->am);
ImGui::Text("- pan: %.2x",ch->pan);
ImGui::Text("- macroVolMul: %.2x",ch->macroVolMul);
COMMON_CHAN_DEBUG_BOOL;
ImGui::TextColored(ch->writeCtrl?colorOn:colorOff,">> WriteCtrl");
ImGui::TextColored(ch->levelDirect?colorOn:colorOff,">> LevelDirect");
break;
}
default:
ImGui::Text("Unimplemented chip! Help!");
break;

View file

@ -179,7 +179,6 @@ void FurnaceGUI::drawDebug() {
ImGui::TextColored(ch->portaStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> PortaStop");
ImGui::TextColored(ch->keyOn?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key On");
ImGui::TextColored(ch->keyOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key Off");
ImGui::TextColored(ch->nowYouCanStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> NowYouCanStop");
ImGui::TextColored(ch->stopOnOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Stop on Off");
ImGui::TextColored(ch->arpYield?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Arp Yield");
ImGui::TextColored(ch->delayLocked?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> DelayLocked");
@ -335,6 +334,35 @@ void FurnaceGUI::drawDebug() {
ImGui::Unindent();
ImGui::TreePop();
}
if (ImGui::TreeNode("New File Picker Test")) {
static bool check0, check1, check2, check3, check4, check5;
ImGui::Checkbox("Modal",&check0);
ImGui::Checkbox("No Close",&check1);
ImGui::Checkbox("Save",&check2);
ImGui::Checkbox("Multi Select",&check3);
ImGui::Checkbox("Dir Select",&check4);
ImGui::Checkbox("Embeddable",&check5);
int fpFlags=(
(check0?FP_FLAGS_MODAL:0)|
(check1?FP_FLAGS_NO_CLOSE:0)|
(check2?FP_FLAGS_SAVE:0)|
(check3?FP_FLAGS_MULTI_SELECT:0)|
(check4?FP_FLAGS_DIR_SELECT:0)|
(check5?FP_FLAGS_EMBEDDABLE:0)
);
if (ImGui::Button("Open")) {
newFilePicker->open("New File Picker","/home","",fpFlags,
{_("songs"), "*.fur *.dmf *.mod *.s3m *.xm *.it *.fc13 *.fc14 *.smod *.fc *.ftm *.0cc *.dnm *.eft *.fub *.tfe",
_("instruments"), "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.gyb *.opm *.wopl *.wopn",
_("audio"), "*.wav",
_("all files"), "*"}
);
}
ImGui::TreePop();
}
if (ImGui::TreeNode("File Selection Test")) {
if (ImGui::Button("Test Open")) {
openFileDialog(GUI_FILE_TEST_OPEN);
@ -365,12 +393,17 @@ void FurnaceGUI::drawDebug() {
ImGui::TreePop();
}
if (ImGui::TreeNode("Scroll Text Test")) {
/*
ImGui::ScrollText(ImGui::GetID("scrolltest1"),"Lorem ipsum, quia dolor sit, amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?");
ImGui::ScrollText(ImGui::GetID("scrolltest2"),"quis autem vel eum iure reprehenderit");
ImGui::ScrollText(ImGui::GetID("scrolltest3"),"qui in ea voluptate velit esse",ImVec2(100.0f*dpiScale,0),true);
ImGui::ScrollText(ImGui::GetID("scrolltest4"),"quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur?",ImVec2(0,0),true);
*/
ImGui::ScrollText(ImGui::GetID("scrolltest1"),"Lorem ipsum, quia dolor sit, amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?",ImGui::GetCursorPos());
ImGui::ScrollText(ImGui::GetID("scrolltest2"),"quis autem vel eum iure reprehenderit",ImGui::GetCursorPos());
ImGui::ScrollText(ImGui::GetID("scrolltest3"),"qui in ea voluptate velit esse",ImGui::GetCursorPos(),ImVec2(100.0f*dpiScale,0),true);
ImGui::ScrollText(ImGui::GetID("scrolltest4"),"quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur?",ImGui::GetCursorPos(),ImVec2(0,0),true);
ImGui::TreePop();
}
if (ImGui::TreeNode("Vertical Text Test")) {
VerticalText("Test");
VerticalText("Test 2");
ImGui::SameLine();
VerticalText("Test 3");
ImGui::TreePop();
}
if (ImGui::TreeNode("Pitch Table Calculator")) {

View file

@ -222,6 +222,9 @@ void FurnaceGUI::doAction(int what) {
}
break;
}
case GUI_ACTION_OPEN_EDIT_MENU:
openEditMenu=true;
break;
case GUI_ACTION_PANIC:
e->syncReset();
break;
@ -700,10 +703,10 @@ void FurnaceGUI::doAction(int what) {
break;
case GUI_ACTION_PAT_LATCH: {
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][cursor.order],true);
latchIns=pat->data[cursor.y][2];
latchVol=pat->data[cursor.y][3];
latchEffect=pat->data[cursor.y][4];
latchEffectVal=pat->data[cursor.y][5];
latchIns=pat->newData[cursor.y][DIV_PAT_INS];
latchVol=pat->newData[cursor.y][DIV_PAT_VOL];
latchEffect=pat->newData[cursor.y][DIV_PAT_FX(0)];
latchEffectVal=pat->newData[cursor.y][DIV_PAT_FXVAL(0)];
latchTarget=0;
latchNibble=false;
break;
@ -936,10 +939,10 @@ void FurnaceGUI::doAction(int what) {
sample->loopEnd=waveLen;
sample->loop=true;
sample->loopMode=DIV_SAMPLE_LOOP_FORWARD;
sample->depth=DIV_SAMPLE_DEPTH_8BIT;
sample->depth=DIV_SAMPLE_DEPTH_16BIT;
if (sample->init(waveLen)) {
for (unsigned short i=0; i<waveLen; i++) {
sample->data8[i]=((wave->data[i]*256)/(wave->max+1))-128;
sample->data16[i]=((wave->data[i]*65535.0f)/(wave->max))-32768;
}
}
}
@ -949,6 +952,7 @@ void FurnaceGUI::doAction(int what) {
MARK_MODIFIED;
}
updateSampleTex=true;
notifySampleChange=true;
}
break;
case GUI_ACTION_WAVE_LIST_MOVE_UP:
@ -1006,6 +1010,7 @@ void FurnaceGUI::doAction(int what) {
MARK_MODIFIED;
}
updateSampleTex=true;
notifySampleChange=true;
break;
case GUI_ACTION_SAMPLE_LIST_DUPLICATE:
if (curSample>=0 && curSample<(int)e->song.sample.size()) {
@ -1040,6 +1045,7 @@ void FurnaceGUI::doAction(int what) {
MARK_MODIFIED;
}
updateSampleTex=true;
notifySampleChange=true;
}
break;
case GUI_ACTION_SAMPLE_LIST_OPEN:
@ -1065,6 +1071,7 @@ void FurnaceGUI::doAction(int what) {
curSample--;
wantScrollListSample=true;
updateSampleTex=true;
notifySampleChange=true;
MARK_MODIFIED;
}
break;
@ -1073,6 +1080,7 @@ void FurnaceGUI::doAction(int what) {
curSample++;
wantScrollListSample=true;
updateSampleTex=true;
notifySampleChange=true;
MARK_MODIFIED;
}
break;
@ -1084,6 +1092,7 @@ void FurnaceGUI::doAction(int what) {
curSample--;
}
updateSampleTex=true;
notifySampleChange=true;
break;
case GUI_ACTION_SAMPLE_LIST_EDIT:
sampleEditOpen=true;
@ -1092,11 +1101,13 @@ void FurnaceGUI::doAction(int what) {
if (--curSample<0) curSample=0;
wantScrollListSample=true;
updateSampleTex=true;
notifySampleChange=true;
break;
case GUI_ACTION_SAMPLE_LIST_DOWN:
if (++curSample>=(int)e->song.sample.size()) curSample=((int)e->song.sample.size())-1;
wantScrollListSample=true;
updateSampleTex=true;
notifySampleChange=true;
break;
case GUI_ACTION_SAMPLE_LIST_PREVIEW:
e->previewSample(curSample);
@ -1186,6 +1197,7 @@ void FurnaceGUI::doAction(int what) {
e->lockEngine([this,sample,start,end]() {
sample->strip(start,end);
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1238,6 +1250,7 @@ void FurnaceGUI::doAction(int what) {
sampleSelStart=pos;
sampleSelEnd=pos+sampleClipboardLen;
updateSampleTex=true;
notifySampleChange=true;
MARK_MODIFIED;
break;
}
@ -1269,6 +1282,7 @@ void FurnaceGUI::doAction(int what) {
sampleSelEnd=pos+sampleClipboardLen;
if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples;
updateSampleTex=true;
notifySampleChange=true;
MARK_MODIFIED;
break;
}
@ -1306,6 +1320,7 @@ void FurnaceGUI::doAction(int what) {
sampleSelEnd=pos+sampleClipboardLen;
if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples;
updateSampleTex=true;
notifySampleChange=true;
MARK_MODIFIED;
break;
}
@ -1371,6 +1386,7 @@ void FurnaceGUI::doAction(int what) {
}
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1402,6 +1418,7 @@ void FurnaceGUI::doAction(int what) {
}
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1433,6 +1450,7 @@ void FurnaceGUI::doAction(int what) {
}
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1462,6 +1480,7 @@ void FurnaceGUI::doAction(int what) {
}
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1478,6 +1497,7 @@ void FurnaceGUI::doAction(int what) {
sample->strip(start,end);
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1496,6 +1516,7 @@ void FurnaceGUI::doAction(int what) {
sample->trim(start,end);
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1531,6 +1552,7 @@ void FurnaceGUI::doAction(int what) {
}
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1558,6 +1580,7 @@ void FurnaceGUI::doAction(int what) {
}
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1583,6 +1606,7 @@ void FurnaceGUI::doAction(int what) {
}
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});
@ -1716,6 +1740,7 @@ void FurnaceGUI::doAction(int what) {
sample->loopEnd=end;
sample->loop=true;
updateSampleTex=true;
notifySampleChange=true;
e->renderSamples(curSample);
});

View file

@ -76,7 +76,7 @@ const char* mobileButtonLabels[32]={
// page 4
_N("fade"),
_N("randomize"),
_N("menu"),
_N("opmask"),
_N("scroll\nmode"),
_N("input\nlatch"),
@ -118,7 +118,7 @@ const int mobileButtonActions[32]={
// page 4
GUI_ACTION_PAT_FADE,
0,
GUI_ACTION_OPEN_EDIT_MENU,
0,
GUI_ACTION_PAT_SCROLL_MODE,
0,

View file

@ -35,21 +35,22 @@ static const char* modPlugFormatHeaders[]={
NULL,
};
const char* FurnaceGUI::noteNameNormal(short note, short octave) {
if (note==100) { // note cut
const char* FurnaceGUI::noteNameNormal(short note) {
if (note==DIV_NOTE_OFF) { // note cut
return "OFF";
} else if (note==101) { // note off and envelope release
} else if (note==DIV_NOTE_REL) { // note off and envelope release
return "===";
} else if (note==102) { // envelope release only
} else if (note==DIV_MACRO_REL) { // envelope release only
return "REL";
} else if (octave==0 && note==0) {
} else if (note==-1) {
return "...";
} else if (note==DIV_NOTE_NULL_PAT) {
return "BUG";
}
int seek=(note+(signed char)octave*12)+60;
if (seek<0 || seek>=180) {
if (note<0 || note>=180) {
return "???";
}
return noteNames[seek];
return noteNames[note];
}
void FurnaceGUI::prepareUndo(ActionType action, UndoRegion region) {
@ -222,16 +223,16 @@ void FurnaceGUI::makeUndo(ActionType action, UndoRegion region) {
for (int j=jBegin; j<=jEnd; j++) {
for (int k=0; k<DIV_MAX_COLS; k++) {
if (p->data[j][k]!=op->data[j][k]) {
s.pat.push_back(UndoPatternData(subSong,i,e->curOrders->ord[i][h],j,k,op->data[j][k],p->data[j][k]));
if (p->newData[j][k]!=op->newData[j][k]) {
s.pat.push_back(UndoPatternData(subSong,i,e->curOrders->ord[i][h],j,k,op->newData[j][k],p->newData[j][k]));
if (k>=4) {
if (op->data[j][k&(~1)]==0x0b ||
p->data[j][k&(~1)]==0x0b ||
op->data[j][k&(~1)]==0x0d ||
p->data[j][k&(~1)]==0x0d ||
op->data[j][k&(~1)]==0xff ||
p->data[j][k&(~1)]==0xff) {
if (k>=DIV_PAT_FX(0)) {
if (op->newData[j][k&(~1)]==0x0b ||
p->newData[j][k&(~1)]==0x0b ||
op->newData[j][k&(~1)]==0x0d ||
p->newData[j][k&(~1)]==0x0d ||
op->newData[j][k&(~1)]==0xff ||
p->newData[j][k&(~1)]==0xff) {
shallWalk=true;
}
}
@ -366,13 +367,12 @@ void FurnaceGUI::doDelete() {
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
touch(jOrder,j);
if (iFine==0) {
pat->data[j][iFine]=0;
if (selStart.y==selEnd.y && selStart.order==selEnd.order) pat->data[j][2]=-1;
if (selStart.y==selEnd.y && selStart.order==selEnd.order) pat->newData[j][DIV_PAT_VOL]=-1;
}
pat->data[j][iFine+1]=(iFine<1)?0:-1;
pat->newData[j][iFine]=-1;
if (selStart.y==selEnd.y && selStart.order==selEnd.order && iFine>2 && iFine&1 && settings.effectDeletionAltersValue) {
pat->data[j][iFine+2]=-1;
if (selStart.y==selEnd.y && selStart.order==selEnd.order && DIV_PAT_IS_EFFECT(iFine) && settings.effectDeletionAltersValue) {
pat->newData[j][iFine+1]=-1;
}
}
j=0;
@ -439,16 +439,12 @@ void FurnaceGUI::doPullDelete() {
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
maskOut(opMaskPullDelete,iFine);
for (int j=sStart.y; j<e->curSubSong->patLen; j++) {
// TODO: we've got a problem here. this should pull from the next row if the selection spans
// more than one order.
if (j<e->curSubSong->patLen-1) {
if (iFine==0) {
pat->data[j][iFine]=pat->data[j+1][iFine];
}
pat->data[j][iFine+1]=pat->data[j+1][iFine+1];
pat->newData[j][iFine]=pat->newData[j+1][iFine];
} else {
if (iFine==0) {
pat->data[j][iFine]=0;
}
pat->data[j][iFine+1]=(iFine<1)?0:-1;
pat->newData[j][iFine]=-1;
}
}
}
@ -486,15 +482,9 @@ void FurnaceGUI::doInsert() {
maskOut(opMaskInsert,iFine);
for (int j=e->curSubSong->patLen-1; j>=sStart.y; j--) {
if (j==sStart.y) {
if (iFine==0) {
pat->data[j][iFine]=0;
}
pat->data[j][iFine+1]=(iFine<1)?0:-1;
pat->newData[j][iFine]=-1;
} else {
if (iFine==0) {
pat->data[j][iFine]=pat->data[j-1][iFine];
}
pat->data[j][iFine+1]=pat->data[j-1][iFine+1];
pat->newData[j][iFine]=pat->newData[j-1][iFine];
}
}
}
@ -522,45 +512,22 @@ void FurnaceGUI::doTranspose(int amount, OperationMask& mask) {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
touch(jOrder,j);
if (iFine==0) {
int origNote=pat->data[j][0];
int origOctave=(signed char)pat->data[j][1];
if (origNote!=0 && origNote!=100 && origNote!=101 && origNote!=102) {
origNote+=amount;
while (origNote>12) {
origNote-=12;
origOctave++;
}
while (origNote<1) {
origNote+=12;
origOctave--;
}
if (origOctave==9 && origNote>11) {
origNote=11;
origOctave=9;
}
if (origOctave>9) {
origNote=11;
origOctave=9;
}
if (origOctave<-5) {
origNote=1;
origOctave=-5;
}
pat->data[j][0]=origNote;
pat->data[j][1]=(unsigned char)origOctave;
}
} else {
int top=255;
if (iFine==1) {
if (e->song.ins.empty()) continue;
top=e->song.ins.size()-1;
} else if (iFine==2) { // volume
top=e->getMaxVolumeChan(iCoarse);
}
if (pat->data[j][iFine+1]==-1) continue;
pat->data[j][iFine+1]=MIN(top,MAX(0,pat->data[j][iFine+1]+amount));
int top=255;
if (iFine==DIV_PAT_NOTE) {
top=179;
// don't transpose special notes
if (pat->newData[j][iFine]==DIV_NOTE_OFF) continue;
if (pat->newData[j][iFine]==DIV_NOTE_REL) continue;
if (pat->newData[j][iFine]==DIV_MACRO_REL) continue;
if (pat->newData[j][iFine]==DIV_NOTE_NULL_PAT) continue;
} else if (iFine==DIV_PAT_INS) {
if (e->song.ins.empty()) continue;
top=e->song.ins.size()-1;
} else if (iFine==DIV_PAT_VOL) { // volume
top=e->getMaxVolumeChan(iCoarse);
}
if (pat->newData[j][iFine]==-1) continue;
pat->newData[j][iFine]=MIN(top,MAX(0,pat->newData[j][iFine]+amount));
}
j=0;
}
@ -596,19 +563,18 @@ String FurnaceGUI::doCopy(bool cut, bool writeClipboard, const SelectionPoint& s
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
if (iFine==0) {
clipb+=noteNameNormal(pat->data[j][0],pat->data[j][1]);
clipb+=noteNameNormal(pat->newData[j][DIV_PAT_NOTE]);
if (cut) {
pat->data[j][0]=0;
pat->data[j][1]=0;
pat->newData[j][DIV_PAT_NOTE]=-1;
}
} else {
if (pat->data[j][iFine+1]==-1) {
if (pat->newData[j][iFine]==-1) {
clipb+="..";
} else {
clipb+=fmt::sprintf("%.2X",pat->data[j][iFine+1]);
clipb+=fmt::sprintf("%.2X",pat->newData[j][iFine]);
}
if (cut) {
pat->data[j][iFine+1]=-1;
pat->newData[j][iFine]=-1;
}
}
}
@ -685,12 +651,12 @@ void FurnaceGUI::doPasteFurnace(PasteMode mode, int arg, bool readClipboard, Str
mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) && strcmp(note,"...")==0) {
// do nothing.
} else {
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || (pat->data[j][0]==0 && pat->data[j][1]==0)) {
if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) {
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || (pat->newData[j][DIV_PAT_NOTE]==-1)) {
if (!decodeNote(note,pat->newData[j][DIV_PAT_NOTE])) {
invalidData=true;
break;
}
if (mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) pat->data[j][2]=arg;
if (mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) pat->newData[j][DIV_PAT_INS]=arg;
}
}
} else {
@ -706,12 +672,12 @@ void FurnaceGUI::doPasteFurnace(PasteMode mode, int arg, bool readClipboard, Str
note[1]=line[charPos++];
note[2]=0;
if (iFine==1) {
if (iFine==DIV_PAT_INS) {
if (!opMaskPaste.ins || mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) {
iFine++;
continue;
}
} else if (iFine==2) {
} else if (iFine==DIV_PAT_VOL) {
if (!opMaskPaste.vol) {
iFine++;
continue;
@ -731,7 +697,7 @@ void FurnaceGUI::doPasteFurnace(PasteMode mode, int arg, bool readClipboard, Str
if (strcmp(note,"..")==0) {
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG ||
mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG)) {
pat->data[j][iFine+1]=-1;
pat->newData[j][iFine]=-1;
}
} else {
unsigned int val=0;
@ -739,8 +705,8 @@ void FurnaceGUI::doPasteFurnace(PasteMode mode, int arg, bool readClipboard, Str
invalidData=true;
break;
}
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || pat->data[j][iFine+1]==-1) {
if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val;
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || pat->newData[j][iFine]==-1) {
if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->newData[j][iFine]=val;
}
}
}
@ -1024,7 +990,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
charPos++;
continue;
}
if (iFine==0) { // note
if (iFine==DIV_PAT_NOTE) { // note
if (charPos>=line.size()) {
invalidData=true;
break;
@ -1050,25 +1016,28 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
if (strcmp(note,"...")==0 || strcmp(note," ")==0) {
// do nothing.
} else {
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || (pat->data[j][0]==0 && pat->data[j][1]==0)) {
if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) {
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || (pat->newData[j][DIV_PAT_NOTE]==-1)) {
if (!decodeNote(note,pat->newData[j][DIV_PAT_NOTE])) {
if (strcmp(note, "^^^")==0) {
pat->data[j][0]=100;
pat->data[j][1]=0;
pat->newData[j][0]=DIV_NOTE_OFF;
} else if (strcmp(note, "~~~")==0 || strcmp(note,"===")==0) {
pat->data[j][0]=101;
pat->data[j][1]=0;
pat->newData[j][0]=DIV_NOTE_REL;
} else {
invalidData=true;
}
break;
} else {
pat->data[j][1]--; // MPT is one octave higher...
// MPT is one octave higher...
if (pat->newData[j][DIV_PAT_NOTE]<12) {
pat->newData[j][DIV_PAT_NOTE]=0;
} else {
pat->newData[j][DIV_PAT_NOTE]-=12;
}
}
if (mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) pat->data[j][2]=arg;
if (mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) pat->newData[j][DIV_PAT_INS]=arg;
}
}
} else if (iFine==1) { // instrument
} else if (iFine==DIV_PAT_INS) { // instrument
if (charPos>=line.size()) {
invalidData=true;
break;
@ -1081,7 +1050,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
note[1]=line[charPos++];
note[2]=0;
if (iFine==1) {
if (iFine==DIV_PAT_INS) {
if (!opMaskPaste.ins || mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG) {
iFine++;
continue;
@ -1091,7 +1060,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
if (strcmp(note,"..")==0 || strcmp(note," ")==0) {
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG ||
mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG)) {
pat->data[j][iFine+1]=-1;
pat->newData[j][iFine]=-1;
}
} else {
unsigned int val=0;
@ -1100,8 +1069,8 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
break;
}
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || pat->data[j][iFine+1]==-1) {
pat->data[j][iFine+1]=val-1;
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || pat->newData[j][iFine]==-1) {
pat->newData[j][iFine]=val-1;
}
}
} else { // volume and effects
@ -1122,14 +1091,12 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
note[2]=line[charPos++];
note[3]=0;
if (iFine==2) {
if (iFine==DIV_PAT_VOL) {
if (!opMaskPaste.vol) {
iFine++;
continue;
}
}
else if ((iFine&1)==0) {
} else if ((iFine&1)==0) { // FUCKING HELL WITH THE INDENTATION?!?!
if (!opMaskPaste.effectVal) {
iFine++;
continue;
@ -1143,9 +1110,8 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
if (strcmp(note,"...")==0 || strcmp(note," ")==0) {
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG ||
mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG))
{
pat->data[j][iFine+1]=-1;
mode==GUI_PASTE_MODE_INS_BG || mode==GUI_PASTE_MODE_INS_FG)) {
pat->newData[j][iFine]=-1;
}
} else {
unsigned int val=0;
@ -1153,19 +1119,19 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
symbol=note[0];
if (iFine==2) {
if (iFine==DIV_PAT_VOL) {
sscanf(&note[1],"%2d",&val);
} else {
sscanf(&note[1],"%2X",&val);
}
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || pat->data[j][iFine+1]==-1) {
// if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val;
if (iFine==2) { // volume
if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_INS_BG) || pat->newData[j][iFine]==-1) {
// if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->newData[j][iFine]=val;
if (iFine==DIV_PAT_VOL) { // volume
switch(symbol) {
case 'v':
{
pat->data[j][iFine+1]=val;
pat->newData[j][iFine]=val;
break;
}
default:
@ -1176,7 +1142,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
if (mptFormat==0) {
eff=convertEffectMPT_MOD(symbol, val); // up to 4 effects stored in one variable
if (((eff&0x0f00)>>8)==0x0C) { // set volume
pat->data[j][iFine]=eff&0xff;
pat->newData[j][iFine-1]=eff&0xff;
}
}
@ -1189,7 +1155,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
if (((eff&0x0f00)>>8)==0x0C)
{
pat->data[j][iFine]=eff&0xff;
pat->newData[j][iFine-1]=eff&0xff;
}
}
@ -1201,12 +1167,12 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
eff=convertEffectMPT_MPTM(symbol, val);
}
pat->data[j][iFine+1]=((eff&0xff00)>>8);
pat->data[j][iFine+2]=(eff&0xff);
pat->newData[j][iFine]=((eff&0xff00)>>8);
pat->newData[j][iFine+1]=(eff&0xff);
if (eff>0xffff) {
pat->data[j][iFine+3]=((eff&0xff000000)>>24);
pat->data[j][iFine+4]=((eff&0xff0000)>>16);
pat->newData[j][iFine+2]=((eff&0xff000000)>>24);
pat->newData[j][iFine+3]=((eff&0xff0000)>>16);
}
}
}
@ -1346,8 +1312,8 @@ void FurnaceGUI::doChangeIns(int ins) {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
touch(jOrder,j);
if (pat->data[j][2]!=-1 || !((pat->data[j][0]==0 || pat->data[j][0]==100 || pat->data[j][0]==101 || pat->data[j][0]==102) && pat->data[j][1]==0)) {
pat->data[j][2]=ins;
if (pat->newData[j][DIV_PAT_INS]!=-1 || !(pat->newData[j][DIV_PAT_NOTE]==-1 || pat->newData[j][DIV_PAT_NOTE]==DIV_NOTE_OFF || pat->newData[j][DIV_PAT_NOTE]==DIV_NOTE_REL || pat->newData[j][DIV_PAT_NOTE]==DIV_MACRO_REL)) {
pat->newData[j][DIV_PAT_INS]=ins;
}
}
j=0;
@ -1372,15 +1338,15 @@ void FurnaceGUI::doInterpolate() {
maskOut(opMaskInterpolate,iFine);
points.clear();
resetTouches;
if (iFine!=0) {
if (iFine!=DIV_PAT_NOTE) {
int jOrder=selStart.order;
int j=selStart.y;
for (; jOrder<=selEnd.order; jOrder++) {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
touch(jOrder,j);
if (pat->data[j][iFine+1]!=-1) {
points.emplace(points.end(),j|(jOrder<<8),pat->data[j][iFine+1]);
if (pat->newData[j][iFine]!=-1) {
points.emplace(points.end(),j|(jOrder<<8),pat->newData[j][iFine]);
}
}
j=0;
@ -1395,7 +1361,7 @@ void FurnaceGUI::doInterpolate() {
);
for (int k=0, k_p=curPoint.first; k<distance; k++) {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][(k_p>>8)&0xff],true);
pat->data[k_p&0xff][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/(double)distance);
pat->newData[k_p&0xff][iFine]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/(double)distance);
k_p++;
if ((k_p&0xff)>=e->curSubSong->patLen) {
k_p&=~0xff;
@ -1410,9 +1376,9 @@ void FurnaceGUI::doInterpolate() {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
touch(jOrder,j);
if (pat->data[j][0]!=0 || pat->data[j][1]!=0) {
if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) {
points.emplace(points.end(),j|(jOrder<<8),pat->data[j][0]+(signed char)pat->data[j][1]*12);
if (pat->newData[j][DIV_PAT_NOTE]!=-1) {
if (pat->newData[j][DIV_PAT_NOTE]!=DIV_NOTE_OFF && pat->newData[j][DIV_PAT_NOTE]!=DIV_NOTE_REL && pat->newData[j][DIV_PAT_NOTE]!=DIV_MACRO_REL) {
points.emplace(points.end(),j|(jOrder<<8),pat->newData[j][DIV_PAT_NOTE]);
}
}
}
@ -1429,13 +1395,7 @@ void FurnaceGUI::doInterpolate() {
for (int k=0, k_p=curPoint.first; k<distance; k++) {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][(k_p>>8)&0xff],true);
int val=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/(double)distance);
pat->data[k_p&0xff][0]=val%12;
pat->data[k_p&0xff][1]=val/12;
if (pat->data[k_p&0xff][0]==0) {
pat->data[k_p&0xff][0]=12;
pat->data[k_p&0xff][1]--;
}
pat->data[k_p&0xff][1]&=255;
pat->newData[k_p&0xff][DIV_PAT_NOTE]=val;
k_p++;
if ((k_p&0xff)>=e->curSubSong->patLen) {
k_p&=~0xff;
@ -1469,12 +1429,12 @@ void FurnaceGUI::doFade(int p0, int p1, bool mode) {
int j_p=0;
maskOut(opMaskFade,iFine);
resetTouches;
if (iFine!=0) {
if (iFine!=DIV_PAT_NOTE) {
int absoluteTop=255;
if (iFine==1) {
if (iFine==DIV_PAT_INS) {
if (e->song.ins.empty()) continue;
absoluteTop=e->song.ins.size()-1;
} else if (iFine==2) { // volume
} else if (iFine==DIV_PAT_VOL) { // volume
absoluteTop=e->getMaxVolumeChan(iCoarse);
}
if (distance<1) continue;
@ -1485,9 +1445,9 @@ void FurnaceGUI::doFade(int p0, int p1, bool mode) {
int value=p0+double(p1-p0)*fraction;
if (mode) { // nibble
value&=15;
pat->data[j][iFine+1]=MIN(absoluteTop,value|(value<<4));
pat->newData[j][iFine]=MIN(absoluteTop,value|(value<<4));
} else { // byte
pat->data[j][iFine+1]=MIN(absoluteTop,value);
pat->newData[j][iFine]=MIN(absoluteTop,value);
}
j_p++;
}
@ -1514,20 +1474,20 @@ void FurnaceGUI::doInvertValues() {
int j=selStart.y;
maskOut(opMaskInvertVal,iFine);
resetTouches;
if (iFine!=0) {
if (iFine!=DIV_PAT_NOTE) {
int top=255;
if (iFine==1) {
if (iFine==DIV_PAT_INS) {
if (e->song.ins.empty()) continue;
top=e->song.ins.size()-1;
} else if (iFine==2) { // volume
} else if (iFine==DIV_PAT_VOL) { // volume
top=e->getMaxVolumeChan(iCoarse);
}
for (; jOrder<=selEnd.order; jOrder++) {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
touch(jOrder,j);
if (pat->data[j][iFine+1]==-1) continue;
pat->data[j][iFine+1]=top-pat->data[j][iFine+1];
if (pat->newData[j][iFine]==-1) continue;
pat->newData[j][iFine]=top-pat->newData[j][iFine];
}
j=0;
}
@ -1552,20 +1512,20 @@ void FurnaceGUI::doScale(float top) {
int j=selStart.y;
maskOut(opMaskScale,iFine);
resetTouches;
if (iFine!=0) {
if (iFine!=DIV_PAT_NOTE) {
int absoluteTop=255;
if (iFine==1) {
if (iFine==DIV_PAT_INS) {
if (e->song.ins.empty()) continue;
absoluteTop=e->song.ins.size()-1;
} else if (iFine==2) { // volume
} else if (iFine==DIV_PAT_VOL) { // volume
absoluteTop=e->getMaxVolumeChan(iCoarse);
}
for (; jOrder<=selEnd.order; jOrder++) {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
touch(jOrder,j);
if (pat->data[j][iFine+1]==-1) continue;
pat->data[j][iFine+1]=MIN(absoluteTop,(double)pat->data[j][iFine+1]*(top/100.0f));
if (pat->newData[j][iFine]==-1) continue;
pat->newData[j][iFine]=MIN(absoluteTop,(double)pat->newData[j][iFine]*(top/100.0f));
}
j=0;
}
@ -1590,40 +1550,40 @@ void FurnaceGUI::doRandomize(int bottom, int top, bool mode, bool eff, int effVa
int j=selStart.y;
maskOut(opMaskRandomize,iFine);
resetTouches;
if (iFine!=0) {
int absoluteTop=255;
if (iFine==1) {
if (e->song.ins.empty()) continue;
absoluteTop=e->song.ins.size()-1;
} else if (iFine==2) { // volume
absoluteTop=e->getMaxVolumeChan(iCoarse);
}
for (; jOrder<=selEnd.order; jOrder++) {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
int value=0;
int value2=0;
touch(jOrder,j);
if (top-bottom<=0) {
value=MIN(absoluteTop,bottom);
value2=MIN(absoluteTop,bottom);
} else {
value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1)));
value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1)));
}
if (mode) {
value&=15;
value2&=15;
pat->data[j][iFine+1]=value|(value2<<4);
} else {
pat->data[j][iFine+1]=value;
}
if (eff && iFine>2 && (iFine&1)) {
pat->data[j][iFine+1]=effVal;
}
int absoluteTop=255;
if (iFine==DIV_PAT_NOTE) {
absoluteTop=179;
} else if (iFine==DIV_PAT_INS) {
if (e->song.ins.empty()) continue;
absoluteTop=e->song.ins.size()-1;
} else if (iFine==DIV_PAT_VOL) { // volume
absoluteTop=e->getMaxVolumeChan(iCoarse);
}
for (; jOrder<=selEnd.order; jOrder++) {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
int value=0;
int value2=0;
touch(jOrder,j);
if (top-bottom<=0) {
value=MIN(absoluteTop,bottom);
value2=MIN(absoluteTop,bottom);
} else {
value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1)));
value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1)));
}
if (mode) {
value&=15;
value2&=15;
pat->newData[j][iFine]=value|(value2<<4);
} else {
pat->newData[j][iFine]=value;
}
if (eff && iFine>2 && (iFine&1)) {
pat->newData[j][iFine]=effVal;
}
j=0;
}
j=0;
}
}
iFine=0;
@ -1655,7 +1615,7 @@ void FurnaceGUI::doFlip() {
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true);
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
PatBufferEntry put;
memcpy(put.data,pat->data[j],DIV_MAX_COLS*sizeof(short));
memcpy(put.data,pat->newData[j],DIV_MAX_COLS*sizeof(short));
patBuffer.push_back(put);
}
j=0;
@ -1674,10 +1634,7 @@ void FurnaceGUI::doFlip() {
for (; j<e->curSubSong->patLen && (j<=selEnd.y || jOrder<selEnd.order); j++) {
j_i--;
touch(jOrder,j);
if (iFine==0) {
pat->data[j][0]=patBuffer[j_i].data[0];
}
pat->data[j][iFine+1]=patBuffer[j_i].data[iFine+1];
pat->newData[j][iFine]=patBuffer[j_i].data[iFine];
}
j=0;
}
@ -1711,38 +1668,18 @@ void FurnaceGUI::doCollapse(int divider, const SelectionPoint& sStart, const Sel
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
maskOut(opMaskCollapseExpand,iFine);
for (int j=sStart.y; j<=sEnd.y; j++) {
if (iFine==0) {
patBuffer.data[j][0]=pat->data[j][0];
}
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
patBuffer.newData[j][iFine]=pat->newData[j][iFine];
}
for (int j=0; j<=sEnd.y-sStart.y; j++) {
if (j*divider>=sEnd.y-sStart.y) {
if (iFine==0) {
pat->data[j+sStart.y][0]=0;
pat->data[j+sStart.y][1]=0;
} else {
pat->data[j+sStart.y][iFine+1]=-1;
}
pat->newData[j+sStart.y][iFine]=-1;
} else {
if (iFine==0) {
pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y][0];
}
pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y][iFine+1];
pat->newData[j+sStart.y][iFine]=patBuffer.newData[j*divider+sStart.y][iFine];
if (iFine==0) {
for (int k=1; k<divider; k++) {
if ((j*divider+k)>=sEnd.y-sStart.y) break;
if (!(pat->data[j+sStart.y][0]==0 && pat->data[j+sStart.y][1]==0)) break;
pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y+k][0];
pat->data[j+sStart.y][1]=patBuffer.data[j*divider+sStart.y+k][1];
}
} else {
for (int k=1; k<divider; k++) {
if ((j*divider+k)>=sEnd.y-sStart.y) break;
if (pat->data[j+sStart.y][iFine+1]!=-1) break;
pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y+k][iFine+1];
}
for (int k=1; k<divider; k++) {
if ((j*divider+k)>=sEnd.y-sStart.y) break;
if (pat->newData[j+sStart.y][iFine]!=-1) break;
pat->newData[j+sStart.y][iFine]=patBuffer.newData[j*divider+sStart.y+k][iFine];
}
}
}
@ -1772,26 +1709,15 @@ void FurnaceGUI::doExpand(int multiplier, const SelectionPoint& sStart, const Se
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
maskOut(opMaskCollapseExpand,iFine);
for (int j=sStart.y; j<=sEnd.y; j++) {
if (iFine==0) {
patBuffer.data[j][0]=pat->data[j][0];
}
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
patBuffer.newData[j][iFine]=pat->newData[j][iFine];
}
for (int j=0; j<=(sEnd.y-sStart.y)*multiplier; j++) {
if ((j+sStart.y)>=e->curSubSong->patLen) break;
if ((j%multiplier)!=0) {
if (iFine==0) {
pat->data[j+sStart.y][0]=0;
pat->data[j+sStart.y][1]=0;
} else {
pat->data[j+sStart.y][iFine+1]=-1;
}
pat->newData[j+sStart.y][iFine]=-1;
continue;
}
if (iFine==0) {
pat->data[j+sStart.y][0]=patBuffer.data[j/multiplier+sStart.y][0];
}
pat->data[j+sStart.y][iFine+1]=patBuffer.data[j/multiplier+sStart.y][iFine+1];
pat->newData[j+sStart.y][iFine]=patBuffer.newData[j/multiplier+sStart.y][iFine];
}
}
iFine=0;
@ -1823,24 +1749,17 @@ void FurnaceGUI::doCollapseSong(int divider) {
pat->clear();
for (int k=0; k<DIV_MAX_ROWS; k++) {
for (int l=0; l<DIV_MAX_COLS; l++) {
if (l==0) {
if (!(pat->data[k/divider][0]==0 && pat->data[k/divider][1]==0)) continue;
} else {
if (pat->data[k/divider][l+1]!=-1) continue;
}
if (pat->newData[k/divider][l]!=-1) continue;
if (l==0) {
pat->data[k/divider][l]=patCopy.data[k][l];
}
pat->data[k/divider][l+1]=patCopy.data[k][l+1];
pat->newData[k/divider][l]=patCopy.newData[k][l];
if (l>3 && !(l&1)) { // scale effects as needed
switch (pat->data[k/divider][l]) {
if (DIV_PAT_IS_EFFECT(l)) { // scale effects as needed
switch (pat->newData[k/divider][l]) {
case 0x0d:
pat->data[k/divider][l+1]/=divider;
pat->newData[k/divider][l+1]/=divider;
break;
case 0x0f:
pat->data[k/divider][l+1]=CLAMP(pat->data[k/divider][l+1]*divider,1,255);
pat->newData[k/divider][l+1]=CLAMP(pat->newData[k/divider][l+1]*divider,1,255);
break;
}
}
@ -1850,8 +1769,8 @@ void FurnaceGUI::doCollapseSong(int divider) {
// put undo
for (int k=0; k<DIV_MAX_ROWS; k++) {
for (int l=0; l<DIV_MAX_COLS; l++) {
if (pat->data[k][l]!=patCopy.data[k][l]) {
us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l]));
if (pat->newData[k][l]!=patCopy.newData[k][l]) {
us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.newData[k][l],pat->newData[k][l]));
}
}
}
@ -1906,24 +1825,17 @@ void FurnaceGUI::doExpandSong(int multiplier) {
pat->clear();
for (int k=0; k<(256/multiplier); k++) {
for (int l=0; l<DIV_MAX_COLS; l++) {
if (l==0) {
if (!(pat->data[k*multiplier][0]==0 && pat->data[k*multiplier][1]==0)) continue;
} else {
if (pat->data[k*multiplier][l+1]!=-1) continue;
}
if (pat->newData[k*multiplier][l]!=-1) continue;
if (l==0) {
pat->data[k*multiplier][l]=patCopy.data[k][l];
}
pat->data[k*multiplier][l+1]=patCopy.data[k][l+1];
pat->newData[k*multiplier][l]=patCopy.newData[k][l];
if (l>3 && !(l&1)) { // scale effects as needed
switch (pat->data[k*multiplier][l]) {
if (DIV_PAT_IS_EFFECT(l)) { // scale effects as needed
switch (pat->newData[k*multiplier][l]) {
case 0x0d:
pat->data[k*multiplier][l+1]/=multiplier;
pat->newData[k*multiplier][l+1]/=multiplier;
break;
case 0x0f:
pat->data[k*multiplier][l+1]=CLAMP(pat->data[k*multiplier][l+1]/multiplier,1,255);
pat->newData[k*multiplier][l+1]=CLAMP(pat->newData[k*multiplier][l+1]/multiplier,1,255);
break;
}
}
@ -1933,8 +1845,8 @@ void FurnaceGUI::doExpandSong(int multiplier) {
// put undo
for (int k=0; k<DIV_MAX_ROWS; k++) {
for (int l=0; l<DIV_MAX_COLS; l++) {
if (pat->data[k][l]!=patCopy.data[k][l]) {
us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l]));
if (pat->newData[k][l]!=patCopy.newData[k][l]) {
us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.newData[k][l],pat->newData[k][l]));
}
}
}
@ -1983,28 +1895,23 @@ void FurnaceGUI::doAbsorbInstrument() {
for (int i=searchStartRow; i>=0 && !foundAll(); i--) {
// absorb most recent instrument
if (!foundIns && pat->data[i][2] >= 0) {
if (!foundIns && pat->newData[i][DIV_PAT_INS] >= 0) {
foundIns=true;
curIns=pat->data[i][2];
curIns=pat->newData[i][DIV_PAT_INS];
}
// absorb most recent octave (i.e. set curOctave such that the "main row" (QWERTY) of
// notes will result in an octave number equal to the previous note). make sure to
// skip "special note values" like OFF/REL/=== and "none", since there won't be valid
// octave values
unsigned char note=pat->data[i][0];
if (!foundOctave && note!=0 && note!=100 && note!=101 && note!=102) {
short note=pat->newData[i][DIV_PAT_NOTE];
if (!foundOctave && note!=-1 && note!=DIV_NOTE_OFF && note!=DIV_NOTE_REL && note!=DIV_MACRO_REL) {
foundOctave=true;
// decode octave data (was signed cast to unsigned char)
int octave=pat->data[i][1];
if (octave>128) octave-=256;
// decode octave data
int octave=(pat->newData[i][DIV_PAT_NOTE]-60)/12;
// @NOTE the special handling when note==12, which is really an octave above what's
// stored in the octave data. without this handling, if you press Q, then
// "ABSORB_INSTRUMENT", then Q again, you'd get a different octave!
if (pat->data[i][0]==12) octave++;
curOctave=CLAMP(octave-1, GUI_EDIT_OCTAVE_MIN, GUI_EDIT_OCTAVE_MAX);
curOctave=CLAMP(octave-1,GUI_EDIT_OCTAVE_MIN,GUI_EDIT_OCTAVE_MAX);
}
}
}
@ -2198,7 +2105,7 @@ void FurnaceGUI::doUndo() {
for (UndoPatternData& i: us.pat) {
e->changeSongP(i.subSong);
DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true);
p->data[i.row][i.col]=i.oldVal;
p->newData[i.row][i.col]=i.oldVal;
}
if (us.type!=GUI_UNDO_REPLACE) {
if (!e->isPlaying() || !followPattern) {
@ -2276,7 +2183,7 @@ void FurnaceGUI::doRedo() {
for (UndoPatternData& i: us.pat) {
e->changeSongP(i.subSong);
DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true);
p->data[i.row][i.col]=i.newVal;
p->newData[i.row][i.col]=i.newVal;
}
if (us.type!=GUI_UNDO_REPLACE) {
if (!e->isPlaying() || !followPattern) {

View file

@ -23,6 +23,22 @@
#include "misc/cpp/imgui_stdlib.h"
#include <imgui.h>
const char* audioExportFormats[]={
_N("Wave"),
_N("Opus"),
_N("FLAC (Free Lossless Audio Codec)"),
_N("Vorbis"),
_N("MP3"),
NULL
};
const char* audioExportWavFormats[]={
_N("8-bit int (unsigned)"),
_N("16-bit int"),
_N("32-bit float"),
NULL
};
void FurnaceGUI::drawExportAudio(bool onWindow) {
exitDisabledTimer=1;
@ -34,27 +50,46 @@ void FurnaceGUI::drawExportAudio(bool onWindow) {
}
if (ImGui::RadioButton(_("multiple files (one per chip)"),audioExportOptions.mode==DIV_EXPORT_MODE_MANY_SYS)) {
audioExportOptions.mode=DIV_EXPORT_MODE_MANY_SYS;
}
audioExportOptions.format=DIV_EXPORT_FORMAT_WAV;
audioExportOptions.wavFormat=DIV_EXPORT_WAV_S16;
}
if (ImGui::RadioButton(_("multiple files (one per channel)"),audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN)) {
audioExportOptions.mode=DIV_EXPORT_MODE_MANY_CHAN;
}
ImGui::Unindent();
ImGui::Separator();
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_SYS) {
ImGui::Text(_("Bit depth:"));
ImGui::Indent();
if (ImGui::RadioButton(_("16-bit integer"),audioExportOptions.format==DIV_EXPORT_FORMAT_S16)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_S16;
if (ImGui::BeginCombo(_("File Format"),audioExportFormats[audioExportOptions.format])) {
for (size_t i=0; i<(supportsMP3?5:4); i++) {
if (ImGui::Selectable(_(audioExportFormats[i]),audioExportOptions.format==i)) {
audioExportOptions.format=(DivAudioExportFormats)i;
}
}
ImGui::EndCombo();
}
if (ImGui::RadioButton(_("32-bit float"),audioExportOptions.format==DIV_EXPORT_FORMAT_F32)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_F32;
ImGui::Separator();
} else {
audioExportOptions.format=DIV_EXPORT_FORMAT_WAV;
audioExportOptions.wavFormat=DIV_EXPORT_WAV_S16;
}
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_SYS && audioExportOptions.format==DIV_EXPORT_FORMAT_WAV) {
if (ImGui::BeginCombo(_("Format"),audioExportWavFormats[audioExportOptions.wavFormat])) {
for (size_t i=0; audioExportWavFormats[i]; i++) {
if (ImGui::Selectable(_(audioExportWavFormats[i]),audioExportOptions.wavFormat==i)) {
audioExportOptions.wavFormat=(DivAudioExportWavFormats)i;
}
}
ImGui::EndCombo();
}
ImGui::Unindent();
}
if (ImGui::InputInt(_("Sample rate"),&audioExportOptions.sampleRate,100,10000)) {
if (audioExportOptions.sampleRate<8000) audioExportOptions.sampleRate=8000;
if (audioExportOptions.sampleRate>384000) audioExportOptions.sampleRate=384000;
if (audioExportOptions.format!=DIV_EXPORT_FORMAT_OPUS) {
if (ImGui::InputInt(_("Sample rate"),&audioExportOptions.sampleRate,100,10000)) {
if (audioExportOptions.sampleRate<8000) audioExportOptions.sampleRate=8000;
if (audioExportOptions.sampleRate>384000) audioExportOptions.sampleRate=384000;
}
}
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_SYS) {
@ -64,6 +99,57 @@ void FurnaceGUI::drawExportAudio(bool onWindow) {
}
}
if (audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3) {
ImGui::Text(_("Bit rate mode:"));
ImGui::Indent();
if (ImGui::RadioButton(_("Constant"),audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_CONSTANT)) {
audioExportOptions.bitRateMode=DIV_EXPORT_BITRATE_CONSTANT;
}
if (ImGui::RadioButton(_("Variable"),audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_VARIABLE)) {
audioExportOptions.bitRateMode=DIV_EXPORT_BITRATE_VARIABLE;
}
if (ImGui::RadioButton(_("Average"),audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_AVERAGE)) {
audioExportOptions.bitRateMode=DIV_EXPORT_BITRATE_AVERAGE;
}
ImGui::Unindent();
}
int minBitRate=6000;
int maxBitRate=256000;
if (audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3) {
if (audioExportOptions.sampleRate>=32000) {
minBitRate=32000;
maxBitRate=320000;
} else if (audioExportOptions.sampleRate>=16000) {
minBitRate=8000;
maxBitRate=160000;
} else {
minBitRate=8000;
maxBitRate=64000;
}
}
if (audioExportOptions.format!=DIV_EXPORT_FORMAT_WAV) {
if (audioExportOptions.format==DIV_EXPORT_FORMAT_FLAC) {
if (ImGui::SliderFloat(_("Compression level"),&audioExportOptions.vbrQuality,0,8)) {
if (audioExportOptions.vbrQuality<0) audioExportOptions.vbrQuality=0;
if (audioExportOptions.vbrQuality>8) audioExportOptions.vbrQuality=8;
}
} else if (audioExportOptions.format==DIV_EXPORT_FORMAT_VORBIS || (audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3 && audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_VARIABLE)) {
if (ImGui::SliderFloat(_("Quality"),&audioExportOptions.vbrQuality,0,10)) {
if (audioExportOptions.vbrQuality<0) audioExportOptions.vbrQuality=0;
if (audioExportOptions.vbrQuality>10) audioExportOptions.vbrQuality=10;
}
} else {
if (ImGui::InputInt(_("Bit rate"),&audioExportOptions.bitRate,1000,10000)) {
}
if (audioExportOptions.bitRate<minBitRate) audioExportOptions.bitRate=minBitRate;
if (audioExportOptions.bitRate>maxBitRate) audioExportOptions.bitRate=maxBitRate;
}
}
ImGui::Separator();
if (ImGui::InputInt(_("Loops"),&audioExportOptions.loops,1,2)) {
if (audioExportOptions.loops<0) audioExportOptions.loops=0;
}
@ -125,6 +211,30 @@ void FurnaceGUI::drawExportAudio(bool onWindow) {
if (isOneOn) {
if (ImGui::Button(_("Export"),ImVec2(200.0f*dpiScale,0))) {
switch (audioExportOptions.format) {
case DIV_EXPORT_FORMAT_WAV:
audioExportFilterName=_("Wave file");
audioExportFilterExt=".wav";
break;
case DIV_EXPORT_FORMAT_OPUS:
case DIV_EXPORT_FORMAT_VORBIS:
audioExportFilterName=_("Ogg files");
audioExportFilterExt=".ogg";
break;
case DIV_EXPORT_FORMAT_FLAC:
audioExportFilterName=_("FLAC files");
audioExportFilterExt=".flac";
break;
case DIV_EXPORT_FORMAT_MPEG_L3:
audioExportFilterName=_("MPEG Layer 3 files");
audioExportFilterExt=".mp3";
break;
default:
audioExportFilterName=_("all files");
audioExportFilterExt="*";
break;
}
switch (audioExportOptions.mode) {
case DIV_EXPORT_MODE_ONE:
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
@ -540,7 +650,7 @@ void FurnaceGUI::drawExport() {
ImGui::CloseCurrentPopup();
}
if (ImGui::Button(_("Set pitch linearity to Partial"))) {
e->song.linearPitch=1;
showError(_("No!"));
ImGui::CloseCurrentPopup();
}
if (ImGui::Button(_("Set fat to max"))) {

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