mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-23 13:05:11 +00:00
dev126 - new ins format
This commit is contained in:
parent
1b79f9d7f3
commit
d6ae735ffe
11 changed files with 408 additions and 196 deletions
|
@ -2348,142 +2348,24 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
|
||||
// read samples
|
||||
for (int i=0; i<ds.sampleLen; i++) {
|
||||
int vol=0;
|
||||
int pitch=0;
|
||||
DivSample* sample=new DivSample;
|
||||
|
||||
if (!reader.seek(samplePtr[i],SEEK_SET)) {
|
||||
logE("couldn't seek to sample %d!",i);
|
||||
lastError=fmt::sprintf("couldn't seek to sample %d!",i);
|
||||
ds.unload();
|
||||
delete sample;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.read(magic,4);
|
||||
if (strcmp(magic,"SMPL")!=0 && strcmp(magic,"SMP2")!=0) {
|
||||
logE("%d: invalid sample header!",i);
|
||||
lastError="invalid sample header!";
|
||||
if (sample->readSampleData(reader,ds.version)!=DIV_DATA_SUCCESS) {
|
||||
lastError="invalid sample header/data!";
|
||||
ds.unload();
|
||||
delete sample;
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
bool isNewSample=(strcmp(magic,"SMP2")==0);
|
||||
reader.readI();
|
||||
DivSample* sample=new DivSample;
|
||||
logD("reading sample %d at %x...",i,samplePtr[i]);
|
||||
if (!isNewSample) logV("(old sample)");
|
||||
|
||||
sample->name=reader.readString();
|
||||
sample->samples=reader.readI();
|
||||
if (!isNewSample) {
|
||||
sample->loopEnd=sample->samples;
|
||||
}
|
||||
sample->rate=reader.readI();
|
||||
|
||||
if (isNewSample) {
|
||||
sample->centerRate=reader.readI();
|
||||
sample->depth=(DivSampleDepth)reader.readC();
|
||||
if (ds.version>=123) {
|
||||
sample->loopMode=(DivSampleLoopMode)reader.readC();
|
||||
} else {
|
||||
sample->loopMode=DIV_SAMPLE_LOOP_FORWARD;
|
||||
reader.readC();
|
||||
}
|
||||
|
||||
// reserved
|
||||
reader.readC();
|
||||
reader.readC();
|
||||
|
||||
sample->loopStart=reader.readI();
|
||||
sample->loopEnd=reader.readI();
|
||||
sample->loop=(sample->loopStart>=0)&&(sample->loopEnd>=0);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
reader.readI();
|
||||
}
|
||||
} else {
|
||||
if (ds.version<58) {
|
||||
vol=reader.readS();
|
||||
pitch=reader.readS();
|
||||
} else {
|
||||
reader.readI();
|
||||
}
|
||||
sample->depth=(DivSampleDepth)reader.readC();
|
||||
|
||||
// reserved
|
||||
reader.readC();
|
||||
|
||||
// while version 32 stored this value, it was unused.
|
||||
if (ds.version>=38) {
|
||||
sample->centerRate=(unsigned short)reader.readS();
|
||||
} else {
|
||||
reader.readS();
|
||||
}
|
||||
|
||||
if (ds.version>=19) {
|
||||
sample->loopStart=reader.readI();
|
||||
sample->loop=(sample->loopStart>=0)&&(sample->loopEnd>=0);
|
||||
} else {
|
||||
reader.readI();
|
||||
}
|
||||
}
|
||||
|
||||
if (ds.version>=58) { // modern sample
|
||||
sample->init(sample->samples);
|
||||
reader.read(sample->getCurBuf(),sample->getCurBufLen());
|
||||
#ifdef TA_BIG_ENDIAN
|
||||
// convert 16-bit samples to big-endian
|
||||
if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
unsigned char* sampleBuf=(unsigned char*)sample->getCurBuf();
|
||||
size_t sampleBufLen=sample->getCurBufLen();
|
||||
for (size_t pos=0; pos<sampleBufLen; pos+=2) {
|
||||
sampleBuf[pos]^=sampleBuf[pos+1];
|
||||
sampleBuf[pos+1]^=sampleBuf[pos];
|
||||
sampleBuf[pos]^=sampleBuf[pos+1];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else { // legacy sample
|
||||
int length=sample->samples;
|
||||
short* data=new short[length];
|
||||
reader.read(data,2*length);
|
||||
|
||||
#ifdef TA_BIG_ENDIAN
|
||||
// convert 16-bit samples to big-endian
|
||||
for (int pos=0; pos<length; pos++) {
|
||||
data[pos]=((unsigned short)data[pos]>>8)|((unsigned short)data[pos]<<8);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pitch!=5) {
|
||||
logD("%d: scaling from %d...",i,pitch);
|
||||
}
|
||||
|
||||
// render data
|
||||
if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) {
|
||||
logW("%d: sample depth is wrong! (%d)",i,sample->depth);
|
||||
sample->depth=DIV_SAMPLE_DEPTH_16BIT;
|
||||
}
|
||||
sample->samples=(double)sample->samples/samplePitches[pitch];
|
||||
sample->init(sample->samples);
|
||||
|
||||
unsigned int k=0;
|
||||
float mult=(float)(vol)/50.0f;
|
||||
for (double j=0; j<length; j+=samplePitches[pitch]) {
|
||||
if (k>=sample->samples) {
|
||||
break;
|
||||
}
|
||||
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
float next=(float)(data[(unsigned int)j]-0x80)*mult;
|
||||
sample->data8[k++]=fmin(fmax(next,-128),127);
|
||||
} else {
|
||||
float next=(float)data[(unsigned int)j]*mult;
|
||||
sample->data16[k++]=fmin(fmax(next,-32768),32767);
|
||||
}
|
||||
}
|
||||
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
ds.sample.push_back(sample);
|
||||
}
|
||||
|
@ -4635,45 +4517,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* sample=song.sample[i];
|
||||
samplePtr.push_back(w->tell());
|
||||
w->write("SMP2",4);
|
||||
blockStartSeek=w->tell();
|
||||
w->writeI(0);
|
||||
|
||||
w->writeString(sample->name,false);
|
||||
w->writeI(sample->samples);
|
||||
w->writeI(sample->rate);
|
||||
w->writeI(sample->centerRate);
|
||||
w->writeC(sample->depth);
|
||||
w->writeC(sample->loopMode);
|
||||
w->writeC(0); // reserved
|
||||
w->writeC(0);
|
||||
w->writeI(sample->loop?sample->loopStart:-1);
|
||||
w->writeI(sample->loop?sample->loopEnd:-1);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
w->writeI(0xffffffff);
|
||||
}
|
||||
|
||||
#ifdef TA_BIG_ENDIAN
|
||||
// store 16-bit samples as little-endian
|
||||
if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
unsigned char* sampleBuf=(unsigned char*)sample->getCurBuf();
|
||||
size_t bufLen=sample->getCurBufLen();
|
||||
for (size_t i=0; i<bufLen; i+=2) {
|
||||
w->writeC(sampleBuf[i+1]);
|
||||
w->writeC(sampleBuf[i]);
|
||||
}
|
||||
} else {
|
||||
w->write(sample->getCurBuf(),sample->getCurBufLen());
|
||||
}
|
||||
#else
|
||||
w->write(sample->getCurBuf(),sample->getCurBufLen());
|
||||
#endif
|
||||
|
||||
blockEndSeek=w->tell();
|
||||
w->seek(blockStartSeek,SEEK_SET);
|
||||
w->writeI(blockEndSeek-blockStartSeek-4);
|
||||
w->seek(0,SEEK_END);
|
||||
sample->putSampleData(w);
|
||||
}
|
||||
|
||||
/// PATTERN
|
||||
|
|
|
@ -549,8 +549,6 @@ void DivInstrument::writeFeatureWS(SafeWriter* w) {
|
|||
}
|
||||
|
||||
size_t DivInstrument::writeFeatureSL(SafeWriter* w, std::vector<int>& list, const DivSong* song) {
|
||||
FEATURE_BEGIN("SL");
|
||||
|
||||
bool sampleUsed[256];
|
||||
memset(sampleUsed,0,256*sizeof(bool));
|
||||
|
||||
|
@ -572,6 +570,10 @@ size_t DivInstrument::writeFeatureSL(SafeWriter* w, std::vector<int>& list, cons
|
|||
}
|
||||
}
|
||||
|
||||
if (list.empty()) return 0;
|
||||
|
||||
FEATURE_BEGIN("SL");
|
||||
|
||||
w->writeC(list.size());
|
||||
|
||||
for (int i: list) {
|
||||
|
@ -591,8 +593,6 @@ size_t DivInstrument::writeFeatureSL(SafeWriter* w, std::vector<int>& list, cons
|
|||
}
|
||||
|
||||
size_t DivInstrument::writeFeatureWL(SafeWriter* w, std::vector<int>& list, const DivSong* song) {
|
||||
FEATURE_BEGIN("WL");
|
||||
|
||||
bool waveUsed[256];
|
||||
memset(waveUsed,0,256*sizeof(bool));
|
||||
|
||||
|
@ -617,6 +617,10 @@ size_t DivInstrument::writeFeatureWL(SafeWriter* w, std::vector<int>& list, cons
|
|||
}
|
||||
}
|
||||
|
||||
if (list.empty()) return 0;
|
||||
|
||||
FEATURE_BEGIN("WL");
|
||||
|
||||
w->writeC(list.size());
|
||||
|
||||
for (int i: list) {
|
||||
|
@ -687,9 +691,14 @@ void DivInstrument::writeFeatureX1(SafeWriter* w) {
|
|||
void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
|
||||
size_t blockStartSeek=0;
|
||||
size_t blockEndSeek=0;
|
||||
size_t slSeek=0;
|
||||
size_t wlSeek=0;
|
||||
std::vector<int> waveList;
|
||||
std::vector<int> sampleList;
|
||||
|
||||
std::vector<unsigned int> wavePtr;
|
||||
std::vector<unsigned int> samplePtr;
|
||||
|
||||
if (fui) {
|
||||
w->write("FINS",4);
|
||||
} else {
|
||||
|
@ -747,17 +756,21 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
|
|||
break;
|
||||
case DIV_INS_AMIGA:
|
||||
featureSM=true;
|
||||
if (!amiga.useWave) featureSL=true;
|
||||
break;
|
||||
case DIV_INS_PCE:
|
||||
checkForWL=true;
|
||||
featureSM=true;
|
||||
if (amiga.useSample) featureSL=true;
|
||||
if (ws.enabled) featureWS=true;
|
||||
break;
|
||||
case DIV_INS_AY:
|
||||
featureSM=true;
|
||||
if (amiga.useSample) featureSL=true;
|
||||
break;
|
||||
case DIV_INS_AY8930:
|
||||
featureSM=true;
|
||||
if (amiga.useSample) featureSL=true;
|
||||
break;
|
||||
case DIV_INS_TIA:
|
||||
break;
|
||||
|
@ -769,6 +782,7 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
|
|||
break;
|
||||
case DIV_INS_VRC6:
|
||||
featureSM=true;
|
||||
if (amiga.useSample) featureSL=true;
|
||||
break;
|
||||
case DIV_INS_OPLL:
|
||||
featureFM=true;
|
||||
|
@ -807,36 +821,44 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
|
|||
case DIV_INS_SWAN:
|
||||
checkForWL=true;
|
||||
featureSM=true;
|
||||
if (amiga.useSample) featureSL=true;
|
||||
if (ws.enabled) featureWS=true;
|
||||
break;
|
||||
case DIV_INS_MIKEY:
|
||||
featureSM=true;
|
||||
if (amiga.useSample) featureSL=true;
|
||||
break;
|
||||
case DIV_INS_VERA:
|
||||
break;
|
||||
case DIV_INS_X1_010:
|
||||
checkForWL=true;
|
||||
featureX1=true;
|
||||
featureSM=true;
|
||||
if (amiga.useSample) featureSL=true;
|
||||
if (ws.enabled) featureWS=true;
|
||||
break;
|
||||
case DIV_INS_VRC6_SAW:
|
||||
break;
|
||||
case DIV_INS_ES5506:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
featureES=true;
|
||||
break;
|
||||
case DIV_INS_MULTIPCM:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
featureMP=true;
|
||||
break;
|
||||
case DIV_INS_SNES:
|
||||
featureSM=true;
|
||||
if (!amiga.useWave) featureSL=true;
|
||||
featureSN=true;
|
||||
checkForWL=true;
|
||||
if (ws.enabled) featureWS=true;
|
||||
break;
|
||||
case DIV_INS_SU:
|
||||
featureSM=true;
|
||||
if (amiga.useSample) featureSL=true;
|
||||
featureSU=true;
|
||||
break;
|
||||
case DIV_INS_NAMCO:
|
||||
|
@ -854,27 +876,35 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
|
|||
break;
|
||||
case DIV_INS_MSM6258:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_MSM6295:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_ADPCMA:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_ADPCMB:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_SEGAPCM:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_QSOUND:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_YMZ280B:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_RF5C68:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_MSM5232:
|
||||
break;
|
||||
|
@ -1049,10 +1079,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
|
|||
writeFeatureWS(w);
|
||||
}
|
||||
if (featureSL) {
|
||||
writeFeatureSL(w,sampleList,song);
|
||||
slSeek=writeFeatureSL(w,sampleList,song);
|
||||
}
|
||||
if (featureWL) {
|
||||
writeFeatureWL(w,waveList,song);
|
||||
wlSeek=writeFeatureWL(w,waveList,song);
|
||||
}
|
||||
if (featureMP) {
|
||||
writeFeatureMP(w);
|
||||
|
@ -1069,7 +1099,44 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
|
|||
|
||||
if (fui && (featureSL || featureWL)) {
|
||||
w->write("EN",2);
|
||||
// TODO: write wave/sample data here
|
||||
|
||||
if (wlSeek!=0 && !waveList.empty()) {
|
||||
for (int i: waveList) {
|
||||
if (i<0 || i>=(int)song->wave.size()) {
|
||||
wavePtr.push_back(0);
|
||||
continue;
|
||||
}
|
||||
DivWavetable* wave=song->wave[i];
|
||||
|
||||
wavePtr.push_back(w->tell());
|
||||
wave->putWaveData(w);
|
||||
}
|
||||
|
||||
w->seek(wlSeek,SEEK_SET);
|
||||
for (unsigned int i: wavePtr) {
|
||||
w->writeI(i);
|
||||
}
|
||||
w->seek(0,SEEK_END);
|
||||
}
|
||||
|
||||
if (slSeek!=0 && !sampleList.empty()) {
|
||||
for (int i: sampleList) {
|
||||
if (i<0 || i>=(int)song->sample.size()) {
|
||||
samplePtr.push_back(0);
|
||||
continue;
|
||||
}
|
||||
DivSample* sample=song->sample[i];
|
||||
|
||||
samplePtr.push_back(w->tell());
|
||||
sample->putSampleData(w);
|
||||
}
|
||||
|
||||
w->seek(slSeek,SEEK_SET);
|
||||
for (unsigned int i: samplePtr) {
|
||||
w->writeI(i);
|
||||
}
|
||||
w->seek(0,SEEK_END);
|
||||
}
|
||||
}
|
||||
|
||||
blockEndSeek=w->tell();
|
||||
|
@ -2204,13 +2271,15 @@ void DivInstrument::readFeatureWS(SafeReader& reader) {
|
|||
READ_FEAT_END;
|
||||
}
|
||||
|
||||
void DivInstrument::readFeatureSL(SafeReader& reader, const DivSong* song) {
|
||||
void DivInstrument::readFeatureSL(SafeReader& reader, DivSong* song, short version) {
|
||||
READ_FEAT_BEGIN;
|
||||
|
||||
unsigned int samplePtr[256];
|
||||
unsigned char sampleIndex[256];
|
||||
unsigned char sampleRemap[256];
|
||||
memset(samplePtr,0,256*sizeof(unsigned int));
|
||||
memset(sampleIndex,0,256);
|
||||
memset(sampleRemap,0,256);
|
||||
|
||||
unsigned char sampleCount=reader.readC();
|
||||
|
||||
|
@ -2223,20 +2292,53 @@ void DivInstrument::readFeatureSL(SafeReader& reader, const DivSong* song) {
|
|||
|
||||
size_t lastSeek=reader.tell();
|
||||
|
||||
// TODO: load samples here
|
||||
// load samples
|
||||
for (int i=0; i<sampleCount; i++) {
|
||||
reader.seek(samplePtr[i],SEEK_SET);
|
||||
if (song->sample.size()>=256) {
|
||||
break;
|
||||
}
|
||||
DivSample* sample=new DivSample;
|
||||
int sampleCount=(int)song->sample.size();
|
||||
|
||||
DivDataErrors result=sample->readSampleData(reader,version);
|
||||
if (result==DIV_DATA_SUCCESS) {
|
||||
song->sample.push_back(sample);
|
||||
song->sampleLen=sampleCount+1;
|
||||
sampleRemap[sampleIndex[i]]=sampleCount;
|
||||
} else {
|
||||
delete sample;
|
||||
sampleRemap[sampleIndex[i]]=0;
|
||||
}
|
||||
}
|
||||
|
||||
reader.seek(lastSeek,SEEK_CUR);
|
||||
|
||||
// re-map samples
|
||||
if (amiga.initSample>=0 && amiga.initSample<256) {
|
||||
amiga.initSample=sampleRemap[amiga.initSample];
|
||||
}
|
||||
|
||||
if (amiga.useNoteMap) {
|
||||
for (int i=0; i<120; i++) {
|
||||
if (amiga.noteMap[i].map>=0 && amiga.noteMap[i].map<256) {
|
||||
amiga.noteMap[i].map=sampleRemap[amiga.noteMap[i].map];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
READ_FEAT_END;
|
||||
}
|
||||
|
||||
void DivInstrument::readFeatureWL(SafeReader& reader, const DivSong* song) {
|
||||
void DivInstrument::readFeatureWL(SafeReader& reader, DivSong* song, short version) {
|
||||
READ_FEAT_BEGIN;
|
||||
|
||||
unsigned int wavePtr[256];
|
||||
unsigned char waveIndex[256];
|
||||
unsigned char waveRemap[256];
|
||||
memset(wavePtr,0,256*sizeof(unsigned int));
|
||||
memset(waveIndex,0,256);
|
||||
memset(waveRemap,0,256);
|
||||
|
||||
unsigned char waveCount=reader.readC();
|
||||
|
||||
|
@ -2249,10 +2351,40 @@ void DivInstrument::readFeatureWL(SafeReader& reader, const DivSong* song) {
|
|||
|
||||
size_t lastSeek=reader.tell();
|
||||
|
||||
// TODO: load samples here
|
||||
// load wavetables
|
||||
for (int i=0; i<waveCount; i++) {
|
||||
reader.seek(wavePtr[i],SEEK_SET);
|
||||
if (song->wave.size()>=256) {
|
||||
break;
|
||||
}
|
||||
DivWavetable* wave=new DivWavetable;
|
||||
int waveCount=(int)song->wave.size();
|
||||
|
||||
DivDataErrors result=wave->readWaveData(reader,version);
|
||||
if (result==DIV_DATA_SUCCESS) {
|
||||
song->wave.push_back(wave);
|
||||
song->waveLen=waveCount+1;
|
||||
waveRemap[waveIndex[i]]=waveCount;
|
||||
} else {
|
||||
delete wave;
|
||||
waveRemap[waveIndex[i]]=0;
|
||||
}
|
||||
}
|
||||
|
||||
reader.seek(lastSeek,SEEK_CUR);
|
||||
|
||||
// re-map wavetables
|
||||
if (ws.enabled) {
|
||||
if (ws.wave1>=0 && ws.wave1<256) ws.wave1=waveRemap[ws.wave1];
|
||||
if (ws.effect&0x80) {
|
||||
if (ws.wave2>=0 && ws.wave2<256) ws.wave2=waveRemap[ws.wave2];
|
||||
}
|
||||
}
|
||||
if (n163.wave>=0 && n163.wave<256) n163.wave=waveRemap[n163.wave];
|
||||
for (int i=0; i<std.waveMacro.len; i++) {
|
||||
if (std.waveMacro.val[i]>=0 && std.waveMacro.val[i]<256) std.waveMacro.val[i]=waveRemap[std.waveMacro.val[i]];
|
||||
}
|
||||
|
||||
READ_FEAT_END;
|
||||
}
|
||||
|
||||
|
@ -2358,9 +2490,9 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b
|
|||
} else if (memcmp(featCode,"WS",2)==0) { // WaveSynth
|
||||
readFeatureWS(reader);
|
||||
} else if (memcmp(featCode,"SL",2)==0 && fui && song!=NULL) { // sample list
|
||||
readFeatureSL(reader,NULL);
|
||||
readFeatureSL(reader,song,version);
|
||||
} else if (memcmp(featCode,"WL",2)==0 && fui && song!=NULL) { // wave list
|
||||
readFeatureWL(reader,NULL);
|
||||
readFeatureWL(reader,song,version);
|
||||
} else if (memcmp(featCode,"MP",2)==0) { // MultiPCM
|
||||
readFeatureMP(reader);
|
||||
} else if (memcmp(featCode,"SU",2)==0) { // Sound Unit
|
||||
|
@ -3118,30 +3250,32 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version, DivS
|
|||
return readInsDataOld(reader,version);
|
||||
}
|
||||
|
||||
bool DivInstrument::save(const char* path) {
|
||||
bool DivInstrument::save(const char* path, bool oldFormat, DivSong* song) {
|
||||
SafeWriter* w=new SafeWriter();
|
||||
w->init();
|
||||
|
||||
//putInsData2(w,true);
|
||||
if (oldFormat) {
|
||||
// write magic
|
||||
w->write("-Furnace instr.-",16);
|
||||
|
||||
// write magic
|
||||
w->write("-Furnace instr.-",16);
|
||||
// write version
|
||||
w->writeS(DIV_ENGINE_VERSION);
|
||||
|
||||
// write version
|
||||
w->writeS(DIV_ENGINE_VERSION);
|
||||
// reserved
|
||||
w->writeS(0);
|
||||
|
||||
// reserved
|
||||
w->writeS(0);
|
||||
// pointer to data
|
||||
w->writeI(32);
|
||||
|
||||
// pointer to data
|
||||
w->writeI(32);
|
||||
// currently reserved (TODO; wavetable and sample here)
|
||||
w->writeS(0);
|
||||
w->writeS(0);
|
||||
w->writeI(0);
|
||||
|
||||
// currently reserved (TODO; wavetable and sample here)
|
||||
w->writeS(0);
|
||||
w->writeS(0);
|
||||
w->writeI(0);
|
||||
|
||||
putInsData(w);
|
||||
putInsData(w);
|
||||
} else {
|
||||
putInsData2(w,true,song);
|
||||
}
|
||||
|
||||
FILE* outFile=ps_fopen(path,"wb");
|
||||
if (outFile==NULL) {
|
||||
|
|
|
@ -674,8 +674,8 @@ struct DivInstrument {
|
|||
void readFeatureN1(SafeReader& reader);
|
||||
void readFeatureFD(SafeReader& reader);
|
||||
void readFeatureWS(SafeReader& reader);
|
||||
void readFeatureSL(SafeReader& reader, const DivSong* song);
|
||||
void readFeatureWL(SafeReader& reader, const DivSong* song);
|
||||
void readFeatureSL(SafeReader& reader, DivSong* song, short version);
|
||||
void readFeatureWL(SafeReader& reader, DivSong* song, short version);
|
||||
void readFeatureMP(SafeReader& reader);
|
||||
void readFeatureSU(SafeReader& reader);
|
||||
void readFeatureES(SafeReader& reader);
|
||||
|
@ -707,9 +707,11 @@ struct DivInstrument {
|
|||
/**
|
||||
* save this instrument to a file.
|
||||
* @param path file path.
|
||||
* @param oldFormat whether to save in legacy Furnace ins format.
|
||||
* @param song if new format, a DivSong to read wavetables and samples.
|
||||
* @return whether it was successful.
|
||||
*/
|
||||
bool save(const char* path);
|
||||
bool save(const char* path, bool oldFormat=false, DivSong* song=NULL);
|
||||
|
||||
/**
|
||||
* save this instrument to a file in .dmp format.
|
||||
|
|
|
@ -39,6 +39,185 @@ DivSampleHistory::~DivSampleHistory() {
|
|||
if (data!=NULL) delete[] data;
|
||||
}
|
||||
|
||||
void DivSample::putSampleData(SafeWriter* w) {
|
||||
size_t blockStartSeek, blockEndSeek;
|
||||
|
||||
w->write("SMP2",4);
|
||||
blockStartSeek=w->tell();
|
||||
w->writeI(0);
|
||||
|
||||
w->writeString(name,false);
|
||||
w->writeI(samples);
|
||||
w->writeI(rate);
|
||||
w->writeI(centerRate);
|
||||
w->writeC(depth);
|
||||
w->writeC(loopMode);
|
||||
w->writeC(0); // reserved
|
||||
w->writeC(0);
|
||||
w->writeI(loop?loopStart:-1);
|
||||
w->writeI(loop?loopEnd:-1);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
w->writeI(0xffffffff);
|
||||
}
|
||||
|
||||
#ifdef TA_BIG_ENDIAN
|
||||
// store 16-bit samples as little-endian
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
unsigned char* sampleBuf=(unsigned char*)getCurBuf();
|
||||
size_t bufLen=getCurBufLen();
|
||||
for (size_t i=0; i<bufLen; i+=2) {
|
||||
w->writeC(sampleBuf[i+1]);
|
||||
w->writeC(sampleBuf[i]);
|
||||
}
|
||||
} else {
|
||||
w->write(getCurBuf(),getCurBufLen());
|
||||
}
|
||||
#else
|
||||
w->write(getCurBuf(),getCurBufLen());
|
||||
#endif
|
||||
|
||||
blockEndSeek=w->tell();
|
||||
w->seek(blockStartSeek,SEEK_SET);
|
||||
w->writeI(blockEndSeek-blockStartSeek-4);
|
||||
w->seek(0,SEEK_END);
|
||||
}
|
||||
|
||||
// Delek why
|
||||
static double samplePitchesSD[11]={
|
||||
0.1666666666, 0.2, 0.25, 0.333333333, 0.5,
|
||||
1,
|
||||
2, 3, 4, 5, 6
|
||||
};
|
||||
|
||||
DivDataErrors DivSample::readSampleData(SafeReader& reader, short version) {
|
||||
int vol=0;
|
||||
int pitch=0;
|
||||
char magic[4];
|
||||
|
||||
reader.read(magic,4);
|
||||
if (strcmp(magic,"SMPL")!=0 && strcmp(magic,"SMP2")!=0) {
|
||||
return DIV_DATA_INVALID_HEADER;
|
||||
}
|
||||
bool isNewSample=(strcmp(magic,"SMP2")==0);
|
||||
reader.readI();
|
||||
if (!isNewSample) logV("(old sample)");
|
||||
|
||||
name=reader.readString();
|
||||
samples=reader.readI();
|
||||
if (!isNewSample) {
|
||||
loopEnd=samples;
|
||||
}
|
||||
rate=reader.readI();
|
||||
|
||||
if (isNewSample) {
|
||||
centerRate=reader.readI();
|
||||
depth=(DivSampleDepth)reader.readC();
|
||||
if (version>=123) {
|
||||
loopMode=(DivSampleLoopMode)reader.readC();
|
||||
} else {
|
||||
loopMode=DIV_SAMPLE_LOOP_FORWARD;
|
||||
reader.readC();
|
||||
}
|
||||
|
||||
// reserved
|
||||
reader.readC();
|
||||
reader.readC();
|
||||
|
||||
loopStart=reader.readI();
|
||||
loopEnd=reader.readI();
|
||||
loop=(loopStart>=0)&&(loopEnd>=0);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
reader.readI();
|
||||
}
|
||||
} else {
|
||||
if (version<58) {
|
||||
vol=reader.readS();
|
||||
pitch=reader.readS();
|
||||
} else {
|
||||
reader.readI();
|
||||
}
|
||||
depth=(DivSampleDepth)reader.readC();
|
||||
|
||||
// reserved
|
||||
reader.readC();
|
||||
|
||||
// while version 32 stored this value, it was unused.
|
||||
if (version>=38) {
|
||||
centerRate=(unsigned short)reader.readS();
|
||||
} else {
|
||||
reader.readS();
|
||||
}
|
||||
|
||||
if (version>=19) {
|
||||
loopStart=reader.readI();
|
||||
loop=(loopStart>=0)&&(loopEnd>=0);
|
||||
} else {
|
||||
reader.readI();
|
||||
}
|
||||
}
|
||||
|
||||
if (version>=58) { // modern sample
|
||||
init(samples);
|
||||
reader.read(getCurBuf(),getCurBufLen());
|
||||
#ifdef TA_BIG_ENDIAN
|
||||
// convert 16-bit samples to big-endian
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
unsigned char* sampleBuf=(unsigned char*)getCurBuf();
|
||||
size_t sampleBufLen=getCurBufLen();
|
||||
for (size_t pos=0; pos<sampleBufLen; pos+=2) {
|
||||
sampleBuf[pos]^=sampleBuf[pos+1];
|
||||
sampleBuf[pos+1]^=sampleBuf[pos];
|
||||
sampleBuf[pos]^=sampleBuf[pos+1];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else { // legacy sample
|
||||
int length=samples;
|
||||
short* data=new short[length];
|
||||
reader.read(data,2*length);
|
||||
|
||||
#ifdef TA_BIG_ENDIAN
|
||||
// convert 16-bit samples to big-endian
|
||||
for (int pos=0; pos<length; pos++) {
|
||||
data[pos]=((unsigned short)data[pos]>>8)|((unsigned short)data[pos]<<8);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pitch!=5) {
|
||||
logD("scaling from %d...",pitch);
|
||||
}
|
||||
|
||||
// render data
|
||||
if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) {
|
||||
logW("sample depth is wrong! (%d)",depth);
|
||||
depth=DIV_SAMPLE_DEPTH_16BIT;
|
||||
}
|
||||
samples=(double)samples/samplePitchesSD[pitch];
|
||||
init(samples);
|
||||
|
||||
unsigned int k=0;
|
||||
float mult=(float)(vol)/50.0f;
|
||||
for (double j=0; j<length; j+=samplePitchesSD[pitch]) {
|
||||
if (k>=samples) {
|
||||
break;
|
||||
}
|
||||
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
float next=(float)(data[(unsigned int)j]-0x80)*mult;
|
||||
data8[k++]=fmin(fmax(next,-128),127);
|
||||
} else {
|
||||
float next=(float)data[(unsigned int)j]*mult;
|
||||
data16[k++]=fmin(fmax(next,-32768),32767);
|
||||
}
|
||||
}
|
||||
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
return DIV_DATA_SUCCESS;
|
||||
}
|
||||
|
||||
bool DivSample::isLoopable() {
|
||||
return loop && ((loopStart>=0 && loopStart<loopEnd) && (loopEnd>loopStart && loopEnd<=(int)samples));
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#define _SAMPLE_H
|
||||
|
||||
#include "../ta-utils.h"
|
||||
#include "safeWriter.h"
|
||||
#include "dataErrors.h"
|
||||
#include <deque>
|
||||
|
||||
enum DivSampleLoopMode: unsigned char {
|
||||
|
@ -130,6 +132,20 @@ struct DivSample {
|
|||
std::deque<DivSampleHistory*> undoHist;
|
||||
std::deque<DivSampleHistory*> redoHist;
|
||||
|
||||
/**
|
||||
* put sample data.
|
||||
* @param w a SafeWriter.
|
||||
*/
|
||||
void putSampleData(SafeWriter* w);
|
||||
|
||||
/**
|
||||
* read sample data.
|
||||
* @param reader the reader.
|
||||
* @param version the format version.
|
||||
* @return a DivDataErrors.
|
||||
*/
|
||||
DivDataErrors readSampleData(SafeReader& reader, short version);
|
||||
|
||||
/**
|
||||
* check if sample is loopable.
|
||||
* @return whether it is loopable.
|
||||
|
|
|
@ -132,6 +132,9 @@ void FurnaceGUI::drawInsList(bool asChild) {
|
|||
if (ImGui::MenuItem("instrument")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE);
|
||||
}
|
||||
if (ImGui::MenuItem("instrument (legacy .fui)")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE_OLD);
|
||||
}
|
||||
if (ImGui::MenuItem("instrument (.dmp)")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE_DMP);
|
||||
}
|
||||
|
@ -151,6 +154,9 @@ void FurnaceGUI::drawInsList(bool asChild) {
|
|||
}
|
||||
} else {
|
||||
if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) {
|
||||
if (ImGui::MenuItem("save in legacy format...")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE_OLD);
|
||||
}
|
||||
if (ImGui::MenuItem("save as .dmp...")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE_DMP);
|
||||
}
|
||||
|
@ -434,6 +440,9 @@ void FurnaceGUI::drawInsList(bool asChild) {
|
|||
if (ImGui::MenuItem("save")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE);
|
||||
}
|
||||
if (ImGui::MenuItem("save (legacy .fui)")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE_OLD);
|
||||
}
|
||||
if (ImGui::MenuItem("save (.dmp)")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE_DMP);
|
||||
}
|
||||
|
|
|
@ -599,6 +599,9 @@ void FurnaceGUI::doAction(int what) {
|
|||
case GUI_ACTION_INS_LIST_SAVE:
|
||||
if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE);
|
||||
break;
|
||||
case GUI_ACTION_INS_LIST_SAVE_OLD:
|
||||
if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE_OLD);
|
||||
break;
|
||||
case GUI_ACTION_INS_LIST_SAVE_DMP:
|
||||
if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE_DMP);
|
||||
break;
|
||||
|
|
|
@ -1506,6 +1506,16 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
|||
dpiScale
|
||||
);
|
||||
break;
|
||||
case GUI_FILE_INS_SAVE_OLD:
|
||||
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
|
||||
hasOpened=fileDialog->openSave(
|
||||
"Save Instrument",
|
||||
{"Furnace instrument", "*.fui"},
|
||||
"Furnace instrument{.fui}",
|
||||
workingDirIns,
|
||||
dpiScale
|
||||
);
|
||||
break;
|
||||
case GUI_FILE_INS_SAVE_DMP:
|
||||
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
|
||||
hasOpened=fileDialog->openSave(
|
||||
|
@ -3855,6 +3865,7 @@ bool FurnaceGUI::loop() {
|
|||
case GUI_FILE_INS_OPEN:
|
||||
case GUI_FILE_INS_OPEN_REPLACE:
|
||||
case GUI_FILE_INS_SAVE:
|
||||
case GUI_FILE_INS_SAVE_OLD:
|
||||
case GUI_FILE_INS_SAVE_DMP:
|
||||
workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||
break;
|
||||
|
@ -3950,6 +3961,9 @@ bool FurnaceGUI::loop() {
|
|||
if (curFileDialog==GUI_FILE_INS_SAVE) {
|
||||
checkExtension(".fui");
|
||||
}
|
||||
if (curFileDialog==GUI_FILE_INS_SAVE_OLD) {
|
||||
checkExtension(".fui");
|
||||
}
|
||||
if (curFileDialog==GUI_FILE_INS_SAVE_DMP) {
|
||||
checkExtension(".dmp");
|
||||
}
|
||||
|
@ -4041,7 +4055,12 @@ bool FurnaceGUI::loop() {
|
|||
break;
|
||||
case GUI_FILE_INS_SAVE:
|
||||
if (curIns>=0 && curIns<(int)e->song.ins.size()) {
|
||||
e->song.ins[curIns]->save(copyOfName.c_str());
|
||||
e->song.ins[curIns]->save(copyOfName.c_str(),false,&e->song);
|
||||
}
|
||||
break;
|
||||
case GUI_FILE_INS_SAVE_OLD:
|
||||
if (curIns>=0 && curIns<(int)e->song.ins.size()) {
|
||||
e->song.ins[curIns]->save(copyOfName.c_str(),true);
|
||||
}
|
||||
break;
|
||||
case GUI_FILE_INS_SAVE_DMP:
|
||||
|
|
|
@ -313,6 +313,7 @@ enum FurnaceGUIFileDialogs {
|
|||
GUI_FILE_INS_OPEN,
|
||||
GUI_FILE_INS_OPEN_REPLACE,
|
||||
GUI_FILE_INS_SAVE,
|
||||
GUI_FILE_INS_SAVE_OLD,
|
||||
GUI_FILE_INS_SAVE_DMP,
|
||||
GUI_FILE_WAVE_OPEN,
|
||||
GUI_FILE_WAVE_OPEN_REPLACE,
|
||||
|
@ -507,6 +508,7 @@ enum FurnaceGUIActions {
|
|||
GUI_ACTION_INS_LIST_OPEN,
|
||||
GUI_ACTION_INS_LIST_OPEN_REPLACE,
|
||||
GUI_ACTION_INS_LIST_SAVE,
|
||||
GUI_ACTION_INS_LIST_SAVE_OLD,
|
||||
GUI_ACTION_INS_LIST_SAVE_DMP,
|
||||
GUI_ACTION_INS_LIST_MOVE_UP,
|
||||
GUI_ACTION_INS_LIST_MOVE_DOWN,
|
||||
|
|
|
@ -590,6 +590,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
|
|||
D("INS_LIST_OPEN", "Open", 0),
|
||||
D("INS_LIST_OPEN_REPLACE", "Open (replace current)", 0),
|
||||
D("INS_LIST_SAVE", "Save", 0),
|
||||
D("INS_LIST_SAVE_OLD", "Save (legacy .fui)", 0),
|
||||
D("INS_LIST_SAVE_DMP", "Save (.dmp)", 0),
|
||||
D("INS_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP),
|
||||
D("INS_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN),
|
||||
|
@ -1084,4 +1085,4 @@ const char* chipCategoryNames[]={
|
|||
"Special",
|
||||
"Sample",
|
||||
NULL
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2167,6 +2167,9 @@ void FurnaceGUI::drawInsEdit() {
|
|||
doAction(GUI_ACTION_INS_LIST_SAVE);
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) {
|
||||
if (ImGui::MenuItem("save in legacy format...")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE_OLD);
|
||||
}
|
||||
if (ImGui::MenuItem("save as .dmp...")) {
|
||||
doAction(GUI_ACTION_INS_LIST_SAVE_DMP);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue