Merge branch 'master' into spectrum
This commit is contained in:
commit
8626937f89
2907 changed files with 1544042 additions and 23152 deletions
|
|
@ -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
617
src/audio/asio.cpp
Normal 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
61
src/audio/asio.h
Normal 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) {}
|
||||
};
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -87,3 +87,7 @@ std::string taDecodeBase64(const char* buf) {
|
|||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string taDecodeBase64(const std::string& str) {
|
||||
return taDecodeBase64(str.c_str());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
3
src/dummy.c
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
int main(int argc, char** argv) {
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
757
src/engine/platform/multipcm.cpp
Normal file
757
src/engine/platform/multipcm.cpp
Normal 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;
|
||||
}
|
||||
142
src/engine/platform/multipcm.h
Normal file
142
src/engine/platform/multipcm.h
Normal 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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
634
src/engine/platform/sound/nds_unopt.cpp
Normal file
634
src/engine/platform/sound/nds_unopt.cpp
Normal 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
|
||||
415
src/engine/platform/sound/nds_unopt.hpp
Normal file
415
src/engine/platform/sound/nds_unopt.hpp
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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!")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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': {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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"))) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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")) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(¬e[1],"%2d",&val);
|
||||
} else {
|
||||
sscanf(¬e[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) {
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue