Merge branch 'master' of https://github.com/tildearrow/furnace into k007232

This commit is contained in:
cam900 2022-12-08 14:22:20 +09:00
commit 217872bdd5
19 changed files with 408 additions and 417 deletions

View file

@ -11,7 +11,7 @@ defaults:
shell: bash
env:
BUILD_TYPE: Debug
BUILD_TYPE: Release
jobs:
build:

View file

@ -126,7 +126,7 @@ void k007232_core::voice_t::write(u8 address, u8 data)
m_start = (m_start & ~0x0ff00) | (u32(data) << 8);
break;
case 4: // start address bit 16
m_start = (m_start & ~0x10000) | (u32(bitfield(data, 16)) << 16);
m_start = (m_start & ~0x10000) | (u32(bitfield(data, 0)) << 16);
break;
case 5: // keyon trigger
keyon();

View file

@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are:
- 129: Furnace dev129
- 128: Furnace dev128
- 127: Furnace dev127
- 126: Furnace dev126
- 125: Furnace dev125
@ -1120,9 +1122,11 @@ size | description
| - 16: 16-bit PCM
1 | loop direction (>=123) or reserved
| - 0: forward
| - 0: backward
| - 0: ping-pong
2 | reserved
| - 1: backward
| - 2: ping-pong
1 | flags (>=129) or reserved
| - 0: BRR emphasis
1 | reserved
4 | loop start
| - -1 means no loop
4 | loop end

View file

@ -106,6 +106,13 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) {
audioSysStarted=true;
}
const char* audioDriver=SDL_GetCurrentAudioDriver();
if (audioDriver==NULL) {
logD("SDL audio driver: NULL!");
} else {
logD("SDL audio driver: %s",audioDriver);
}
desc=request;
desc.outFormat=TA_AUDIO_FORMAT_F32;

View file

@ -21,6 +21,8 @@
*/
#include "brrUtils.h"
#include <stdio.h>
#include <string.h>
#define NEXT_SAMPLE buf[j]-(buf[j]>>3)
@ -56,384 +58,257 @@
last2=last1; \
last1=nextDec; \
long brrEncode(short* buf, unsigned char* out, long len, long loopStart) {
void brrEncodeBlock(const short* buf, unsigned char* out, unsigned char range, unsigned char filter, short* last1, short* last2, int* errorSum) {
// encode one block using BRR
unsigned char nibble=0;
int preOut=0;
int pred=0;
int nextDec=0;
int nextError=0;
*errorSum=0;
for (int j=0; j<16; j++) {
short s=NEXT_SAMPLE;
switch (filter) {
case 0: // no filter
pred=s;
break;
case 1: // simple
pred=s-(((int)(*last1*2)*15)>>4);
break;
case 2: // complex
pred=s+(((int)(*last2*2)*15)>>4)-(((int)(*last1*2)*61)>>5);
break;
case 3:
pred=s+(((int)(*last2*2)*13)>>4)-(((int)(*last1*2)*115)>>6);
break;
}
if (pred<-32768) pred=-32768;
if (pred>32767) pred=32767;
preOut=pred>>range;
if (range) {
if (pred&(1<<(range>>1))) preOut++;
if (filter==0 && range>=12) if (preOut<-7) preOut=-7;
}
if (preOut>7) preOut=7;
if (preOut<-8) preOut=-8;
nibble=preOut&15;
if (j&1) {
out[j>>1]|=nibble;
} else {
out[j>>1]=nibble<<4;
}
// roll last1/last2
nextDec=nibble;
if (nextDec&8) nextDec|=0xfffffff0;
if (range>=13) { /* invalid shift */
nextDec=(nextDec<0)?0xfffff800:0;
} else {
nextDec<<=range; /* range */
nextDec>>=1;
}
switch (filter) { /* filter */
case 0:
break;
case 1:
nextDec+=(*last1)+((-(*last1))>>4);
break;
case 2:
nextDec+=(*last1)*2+((-(*last1)*3)>>5)-(*last2)+((*last2)>>4);
break;
case 3:
nextDec+=(*last1)*2+((-(*last1)*13)>>6)-(*last2)+(((*last2)*3)>>4);
break;
}
nextDec&=0x7fff;
if (nextDec&0x4000) nextDec|=0xffff8000;
nextError=s-(nextDec<<1);
if (nextError<0) nextError=-nextError;
*errorSum+=nextError;
*last2=*last1;
*last1=nextDec;
}
}
long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigned char emphasis) {
if (len==0) return 0;
// encoding process:
// 1. read next group of 16 samples
// 2. is this the first block?
// - if yes, don't filter. output and then go to 1
// 3. is this the loop block?
// - if yes, don't filter. output and then go to 1
// 4. try encoding using 4 filters
// - perform linear prediction
// - calculate range
// - decode and apply correction to achieve low error
// 4. try encoding using 3 filters and 12 ranges (besides no filter)
// 5. which one of these yields the least amount of error?
// 6. output the one which does
// 7. is this the last block?
// - if yes, mark end and finish
// 8. go to 1
// 7. do we still have more to encode?
// - if so, go to 1
// 8. is loop point set?
// - if not, end process here
// 9. is transition between last block and loop block smooth?
// - if not, encode the loop block again and output it
long total=0;
unsigned char next0[8];
unsigned char next1[8];
unsigned char next2[8];
unsigned char next3[8];
unsigned char filter=0;
unsigned char range0=0;
unsigned char range1=0;
unsigned char range2=0;
unsigned char range3=0;
unsigned char o=0;
int pred1[16];
int pred2[16];
int pred3[16];
short o1=0;
short o2=0;
short o0=0;
short o1f1=0;
short o1f2=0;
short o1f3=0;
//short o2f1=0;
short o2f2=0;
short o2f3=0;
unsigned char range=0;
int last1=0;
int last2=0;
int nextDec=0;
int maxError[4];
int avgError[4];
short x0=0;
short x1=0;
short x2=0;
int emphOut=0;
short in[17];
short last1[4][13];
short last2[4][13];
int avgError[4][13];
unsigned char possibleOut[4][13][8];
memset(in,0,16*sizeof(short));
memset(last1,0,4*13*sizeof(short));
memset(last2,0,4*13*sizeof(short));
memset(avgError,0,4*13*sizeof(int));
memset(possibleOut,0,4*13*8);
len&=~15;
loopStart&=~15;
for (long i=0; i<len; i+=16) {
range0=0;
// encode with no filter
for (int j=0; j<16; j++) {
short s=NEXT_SAMPLE;
if (s<0) s=-s;
while (range0<12 && s>((8<<range0)-1)) range0++;
}
for (int j=0; j<16; j++) {
short s=NEXT_SAMPLE;
o0=s>>range0;
if (range0) if (s&(1<<(range1>>1))) o0++;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
if (range0>=12) if (o0<-7) o0=-7;
o=o0&15;
if (j&1) {
next0[j>>1]|=o;
} else {
next0[j>>1]=o<<4;
}
}
// encode with filter
if (i /*&& i!=loopStart*/) {
// 1: x = o0 - o1 * 15/16
// 2: x = o0 + o2 * 15/16 - o1 * 61/32
// 3: x = o0 + o2 * 13/16 - o1 * 115/64
range1=0;
range2=0;
range3=0;
//o2f1=o2;
o2f2=o2;
o2f3=o2;
o1f1=o1;
o1f2=o1;
o1f3=o1;
// first pass
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
pred1[j]=s-(((int)o1*15)>>4);
if (pred1[j]<-32768) pred1[j]=-32768;
if (pred1[j]>32767) pred1[j]=32767;
pred2[j]=s+(((int)o2*15)>>4)-(((int)o1*61)>>5);
if (pred2[j]<-32768) pred2[j]=-32768;
if (pred2[j]>32767) pred2[j]=32767;
pred3[j]=s+(((int)o2*13)>>4)-(((int)o1*115)>>6);
if (pred3[j]<-32768) pred3[j]=-32768;
if (pred3[j]>32767) pred3[j]=32767;
o2=o1;
o1=s;
}
// calculate range of values
for (int j=0; j<16; j++) {
short s=pred1[j];
if (s<0) s=-s;
while (range1<12 && s>((8<<range1)-1)) range1++;
s=pred2[j];
if (s<0) s=-s;
while (range2<12 && s>((8<<range2)-1)) range2++;
s=pred3[j];
if (s<0) s=-s;
while (range3<12 && s>((8<<range3)-1)) range3++;
}
// second pass
int prevLast1=last1;
int prevLast2=last2;
filter=1;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
pred1[j]=s-(((int)o1f1*15)>>4);
if (pred1[j]<-32768) pred1[j]=-32768;
if (pred1[j]>32767) pred1[j]=32767;
o0=pred1[j]>>range1;
if (range1) if (pred1[j]&(1<<(range1>>1))) o0++;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
o=o0&15;
if (j&1) {
next1[j>>1]|=o;
if (i+17>len) {
long p=i;
for (int j=0; j<17; j++) {
if (p>=len) {
if (loopStart<0 || loopStart>=len) {
in[j]=0;
} else {
p=loopStart;
in[j]=buf[p++];
}
} else {
next1[j>>1]=o<<4;
}
nextDec=o;
DO_ONE_DEC(range1);
//o2f1=last2<<1;
o1f1=last1<<1;
}
last1=prevLast1;
last2=prevLast2;
filter=2;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
pred2[j]=s+(((int)o2f2*15)>>4)-(((int)o1f2*61)>>5);
if (pred2[j]<-32768) pred2[j]=-32768;
if (pred2[j]>32767) pred2[j]=32767;
o0=pred2[j]>>range2;
if (range2) if (pred2[j]&(1<<(range2>>1))) o0++;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
o=o0&15;
if (j&1) {
next2[j>>1]|=o;
} else {
next2[j>>1]=o<<4;
}
nextDec=o;
DO_ONE_DEC(range2);
o2f2=last2<<1;
o1f2=last1<<1;
}
last1=prevLast1;
last2=prevLast2;
filter=3;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
pred3[j]=s+(((int)o2f3*13)>>4)-(((int)o1f3*115)>>6);
if (pred3[j]<-32768) pred3[j]=-32768;
if (pred3[j]>32767) pred3[j]=32767;
o0=pred3[j]>>range3;
if (range3) if (pred3[j]&(1<<(range3>>1))) o0++;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
o=o0&15;
if (j&1) {
next3[j>>1]|=o;
} else {
next3[j>>1]=o<<4;
}
nextDec=o;
DO_ONE_DEC(range3);
o2f3=last2<<1;
o1f3=last1<<1;
}
last1=prevLast1;
last2=prevLast2;
// find best filter
int error=0;
maxError[0]=0;
maxError[1]=0;
maxError[2]=0;
maxError[3]=0;
avgError[0]=0;
avgError[1]=0;
avgError[2]=0;
avgError[3]=0;
// test filter 0
filter=0;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
if (j&1) {
nextDec=next0[j>>1]&15;
} else {
nextDec=next0[j>>1]>>4;
}
DO_ONE_DEC(range0);
error=s-(nextDec<<1);
if (error<0) error=-error;
avgError[0]+=error;
if (error>maxError[0]) maxError[0]=error;
}
last1=prevLast1;
last2=prevLast2;
// test filter 1
filter=1;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
if (j&1) {
nextDec=next1[j>>1]&15;
} else {
nextDec=next1[j>>1]>>4;
}
DO_ONE_DEC(range1);
error=s-(nextDec<<1);
if (error<0) error=-error;
avgError[1]+=error;
if (error>maxError[1]) maxError[1]=error;
}
last1=prevLast1;
last2=prevLast2;
// test filter 2
filter=2;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
if (j&1) {
nextDec=next2[j>>1]&15;
} else {
nextDec=next2[j>>1]>>4;
}
DO_ONE_DEC(range2);
error=s-(nextDec<<1);
if (error<0) error=-error;
avgError[2]+=error;
if (error>maxError[2]) maxError[2]=error;
}
last1=prevLast1;
last2=prevLast2;
// test filter 3
filter=3;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
if (j&1) {
nextDec=next3[j>>1]&15;
} else {
nextDec=next3[j>>1]>>4;
}
DO_ONE_DEC(range3);
error=s-(nextDec<<1);
if (error<0) error=-error;
avgError[3]+=error;
if (error>maxError[3]) maxError[3]=error;
}
last1=prevLast1;
last2=prevLast2;
// pick best filter
int candError=0x7fffffff;
for (int j=0; j<4; j++) {
if (avgError[j]<candError) {
candError=avgError[j];
filter=j;
in[j]=buf[p++];
}
}
//printf("block %ld: %8d %8d %8d %8d -> %d\n",i>>4,avgError[0],avgError[1],avgError[2],avgError[3],filter);
} else {
// don't filter on the first or loop block
memcpy(in,&buf[i],17*sizeof(short));
}
// emphasis
if (emphasis) {
for (int j=0; j<17; j++) {
x0=x1;
x1=x2;
x2=in[j];
if (j==0) continue;
emphOut=((x1<<11)-x0*370-in[j]*374)/1305;
if (emphOut<-32768) emphOut=-32768;
if (emphOut>32767) emphOut=32767;
in[j-1]=emphOut;
}
}
// encode
for (int j=0; j<4; j++) {
for (int k=0; k<13; k++) {
brrEncodeBlock(in,possibleOut[j][k],k,j,&last1[j][k],&last2[j][k],&avgError[j][k]);
}
}
// find best filter/range
int candError=0x7fffffff;
if (i==0) {
filter=0;
for (int k=0; k<13; k++) {
if (avgError[0][k]<candError) {
candError=avgError[0][k];
range=k;
}
}
} else {
for (int j=0; j<4; j++) {
for (int k=0; k<13; k++) {
if (avgError[j][k]<candError) {
candError=avgError[j][k];
filter=j;
range=k;
}
}
}
}
switch (filter) {
case 0:
for (int j=0; j<8; j++) {
nextDec=next0[j]>>4;
DO_ONE_DEC(range0);
nextDec=next0[j]&15;
DO_ONE_DEC(range0);
}
o2=last2<<1;
o1=last1<<1;
out[0]=(range0<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
out[1]=next0[0];
out[2]=next0[1];
out[3]=next0[2];
out[4]=next0[3];
out[5]=next0[4];
out[6]=next0[5];
out[7]=next0[6];
out[8]=next0[7];
break;
case 1:
for (int j=0; j<8; j++) {
nextDec=next1[j]>>4;
DO_ONE_DEC(range1);
nextDec=next1[j]&15;
DO_ONE_DEC(range1);
}
o2=last2<<1;
o1=last1<<1;
out[0]=(range1<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
out[1]=next1[0];
out[2]=next1[1];
out[3]=next1[2];
out[4]=next1[3];
out[5]=next1[4];
out[6]=next1[5];
out[7]=next1[6];
out[8]=next1[7];
break;
case 2:
for (int j=0; j<8; j++) {
nextDec=next2[j]>>4;
DO_ONE_DEC(range2);
nextDec=next2[j]&15;
DO_ONE_DEC(range2);
}
o2=last2<<1;
o1=last1<<1;
out[0]=(range2<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
out[1]=next2[0];
out[2]=next2[1];
out[3]=next2[2];
out[4]=next2[3];
out[5]=next2[4];
out[6]=next2[5];
out[7]=next2[6];
out[8]=next2[7];
break;
case 3:
for (int j=0; j<8; j++) {
nextDec=next3[j]>>4;
DO_ONE_DEC(range3);
nextDec=next3[j]&15;
DO_ONE_DEC(range3);
}
o2=last2<<1;
o1=last1<<1;
out[0]=(range3<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
out[1]=next3[0];
out[2]=next3[1];
out[3]=next3[2];
out[4]=next3[3];
out[5]=next3[4];
out[6]=next3[5];
out[7]=next3[6];
out[8]=next3[7];
break;
// write
out[0]=(range<<4)|(filter<<2)|((i+16>=len && loopStart<0)?1:0);
for (int j=0; j<8; j++) {
out[j+1]=possibleOut[filter][range][j];
}
for (int j=0; j<4; j++) {
for (int k=0; k<13; k++) {
last1[j][k]=last1[filter][range];
last2[j][k]=last2[filter][range];
}
}
out+=9;
total+=9;
}
// encode loop block
if (loopStart>=0) {
long p=loopStart;
for (int i=0; i<17; i++) {
if (p>=len) {
p=loopStart;
}
in[i]=buf[p++];
}
if (emphasis) {
for (int j=0; j<17; j++) {
x0=x1;
x1=x2;
x2=in[j];
if (j==0) continue;
emphOut=((x1<<11)-x0*370-in[j]*374)/1305;
if (emphOut<-32768) emphOut=-32768;
if (emphOut>32767) emphOut=32767;
in[j-1]=emphOut;
}
}
// encode (filter 0/1 only)
for (int j=0; j<2; j++) {
for (int k=0; k<13; k++) {
brrEncodeBlock(in,possibleOut[j][k],k,j,&last1[j][k],&last2[j][k],&avgError[j][k]);
}
}
// find best filter/range
int candError=0x7fffffff;
for (int j=0; j<2; j++) {
for (int k=0; k<13; k++) {
if (avgError[j][k]<candError) {
candError=avgError[j][k];
filter=j;
range=k;
}
}
}
// write
out[0]=(range<<4)|(filter<<2)|3;
for (int j=0; j<8; j++) {
out[j+1]=possibleOut[filter][range][j];
}
for (int j=0; j<4; j++) {
for (int k=0; k<13; k++) {
last1[j][k]=last1[filter][range];
last2[j][k]=last2[filter][range];
}
}
buf+=16;
out+=9;
total+=9;
}
@ -474,9 +349,11 @@ long brrEncode(short* buf, unsigned char* out, long len, long loopStart) {
*out=next<<1; \
out++;
long brrDecode(unsigned char* buf, short* out, long len) {
long brrDecode(unsigned char* buf, short* out, long len, unsigned char emphasis) {
if (len==0) return 0;
short* outOrig=out;
long total=0;
int last1=0;
@ -503,5 +380,20 @@ long brrDecode(unsigned char* buf, short* out, long len) {
buf+=9;
}
if (emphasis) {
short x0=0;
short x1=0;
short x2=0;
for (long i=0; i<=total; i++) {
x0=x1;
x1=x2;
x2=(i>=total)?0:outOrig[i];
if (i==0) continue;
outOrig[i-1]=(x0*370+x1*1305+x2*374)>>11;
}
}
return total;
}

View file

@ -30,21 +30,23 @@ extern "C" {
/**
* read len samples from buf, encode in BRR and output to out.
* @param buf input data.
* @param out output buffer. shall be at least 9*(len/16) shorts in size.
* @param out output buffer. shall be at least 9*((15+len)/16) shorts in size (9 more if loopStart is not -1!)
* @param len input length (should be a multiple of 16. if it isn't, the output will be padded).
* @param loopStart beginning of loop area (may be -1 for no loop). this is used to ensure the respective block has no filter in order to loop properly.
* @param emphasis apply filter to compensate for Gaussian interpolation high frequency loss.
* @return number of written samples.
*/
long brrEncode(short* buf, unsigned char* out, long len, long loopStart);
long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigned char emphasis);
/**
* read len bytes from buf, decode BRR and output to out.
* @param buf input data.
* @param out output buffer. shall be at least 16*(len/9) shorts in size.
* @param len input length (shall be a multiple of 9).
* @param emphasis apply filter to simulate Gaussian interpolation high frequency loss.
* @return number of written bytes.
*/
long brrDecode(unsigned char* buf, short* out, long len);
long brrDecode(unsigned char* buf, short* out, long len, unsigned char emphasis);
#ifdef __cplusplus
}

View file

@ -47,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev128"
#define DIV_ENGINE_VERSION 128
#define DIV_VERSION "dev129"
#define DIV_ENGINE_VERSION 129
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02

View file

@ -783,7 +783,7 @@ void DivPlatformQSound::renderSamples(int sysID) {
for (int i=0; i<length; i++) {
sampleMem[(memPos+i)]=s->dataQSoundA[i];
}
sampleLoaded[i]=true;
sampleLoadedBS[i]=true;
}
offBS[i]=memPos;
memPos+=length+16;

View file

@ -125,6 +125,9 @@ double DivPlatformSMS::NOTE_SN(int ch, int note) {
if (parent->song.linearPitch==2 || !easyNoise) {
return NOTE_PERIODIC(note);
}
int easyStartingPeriod=16;
//int easyThreshold=round(CHIP_CLOCK/(parent->song.tuning*0.0625*pow(2.0,(float)(nbase+384)/(128.0*12.0)))/CHIP_DIVIDER);
int easyThreshold=round(log((chipClock/(easyStartingPeriod*CHIP_DIVIDER))/parent->song.tuning)/log(2.0));
if (note>easyThreshold) {
return MAX(0,easyStartingPeriod-(note-easyThreshold));
}
@ -132,12 +135,16 @@ double DivPlatformSMS::NOTE_SN(int ch, int note) {
}
int DivPlatformSMS::snCalcFreq(int ch) {
if (parent->song.linearPitch==2 && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(easyThreshold<<7)) {
int ret=(((easyStartingPeriod<<7)+0x40)-(chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2-(easyThreshold<<7)))>>7;
double CHIP_DIVIDER=toneDivider;
if (ch==3) CHIP_DIVIDER=noiseDivider;
int easyStartingPeriod=16;
int easyThreshold=round(128.0*log((chipClock/(easyStartingPeriod*CHIP_DIVIDER))/parent->song.tuning)/log(2.0));
if (parent->song.linearPitch==2 && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(easyThreshold)) {
int ret=(((easyStartingPeriod<<7))-(chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2-(easyThreshold)))>>7;
if (ret<0) ret=0;
return ret;
}
return parent->calcFreq(chan[ch].baseFreq,chan[ch].pitch,true,0,chan[ch].pitch2,chipClock,ch==3?noiseDivider:toneDivider);
return parent->calcFreq(chan[ch].baseFreq,chan[ch].pitch,true,0,chan[ch].pitch2,chipClock,CHIP_DIVIDER);
}
void DivPlatformSMS::tick(bool sysTick) {
@ -464,40 +471,27 @@ void DivPlatformSMS::setFlags(const DivConfig& flags) {
switch (flags.getInt("clockSel",0)) {
case 1:
chipClock=COLOR_PAL*4.0/5.0;
easyThreshold=84;
easyStartingPeriod=13;
break;
case 2:
chipClock=4000000;
easyThreshold=86;
easyStartingPeriod=13;
break;
case 3:
chipClock=COLOR_NTSC/2.0;
easyThreshold=72;
easyStartingPeriod=13;
break;
case 4:
chipClock=3000000;
easyThreshold=81;
easyStartingPeriod=13;
break;
case 5:
chipClock=2000000;
easyThreshold=74;
easyStartingPeriod=13;
break;
case 6:
chipClock=COLOR_NTSC/8.0;
easyThreshold=48;
easyStartingPeriod=13;
break;
default:
chipClock=COLOR_NTSC;
easyThreshold=84;
easyStartingPeriod=13;
break;
}
CHECK_CUSTOM_CLOCK;
resetPhase=!flags.getBool("noPhaseReset",false);
easyNoise=!flags.getBool("noEasyNoise",false);
divider=16;
@ -568,7 +562,7 @@ void DivPlatformSMS::setFlags(const DivConfig& flags) {
stereo=false;
break;
}
CHECK_CUSTOM_CLOCK;
rate=chipClock/divider;
for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate;

View file

@ -65,8 +65,6 @@ class DivPlatformSMS: public DivDispatch {
int divider=16;
double toneDivider=64.0;
double noiseDivider=64.0;
int easyThreshold;
int easyStartingPeriod;
bool updateSNMode;
bool resetPhase;
bool isRealSN;

View file

@ -214,13 +214,13 @@ void DivPlatformSNES::tick(bool sysTick) {
loop=start;
} else if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
start=sampleOff[chan[i].sample];
end=MIN(start+MAX(s->lengthBRR,1),getSampleMemCapacity());
end=MIN(start+MAX(s->lengthBRR+((s->loop && s->depth!=DIV_SAMPLE_DEPTH_BRR)?9:0),1),getSampleMemCapacity());
loop=MAX(start,end-1);
if (chan[i].audPos>0) {
start=start+MIN(chan[i].audPos,s->lengthBRR-1)/16*9;
}
if (s->loopStart>=0) {
loop=start+s->loopStart/16*9;
loop=((s->depth!=DIV_SAMPLE_DEPTH_BRR)?9:0)+start+((s->loopStart/16)*9);
}
} else {
start=0;
@ -817,7 +817,7 @@ void DivPlatformSNES::renderSamples(int sysID) {
continue;
}
int length=s->lengthBRR;
int length=s->lengthBRR+((s->loop && s->depth!=DIV_SAMPLE_DEPTH_BRR)?9:0);
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length);
if (actualLength>0) {
sampleOff[i]=memPos;

View file

@ -80,7 +80,7 @@ void DivPlatformVIC20::calcAndWriteOutVol(int ch, int env) {
}
void DivPlatformVIC20::writeOutVol(int ch) {
if (!isMuted[ch]) {
if (!isMuted[ch] && chan[ch].active) {
rWrite(14,chan[ch].outVol);
}
}

View file

@ -52,8 +52,8 @@ void DivSample::putSampleData(SafeWriter* w) {
w->writeI(centerRate);
w->writeC(depth);
w->writeC(loopMode);
w->writeC(brrEmphasis);
w->writeC(0); // reserved
w->writeC(0);
w->writeI(loop?loopStart:-1);
w->writeI(loop?loopEnd:-1);
@ -125,9 +125,13 @@ DivDataErrors DivSample::readSampleData(SafeReader& reader, short version) {
reader.readC();
}
if (version>=129) {
brrEmphasis=reader.readC();
} else {
reader.readC();
}
// reserved
reader.readC();
reader.readC();
loopStart=reader.readI();
loopEnd=reader.readI();
@ -490,8 +494,8 @@ bool DivSample::initInternal(DivSampleDepth d, int count) {
case DIV_SAMPLE_DEPTH_BRR: // BRR
if (dataBRR!=NULL) delete[] dataBRR;
lengthBRR=9*((count+15)/16);
dataBRR=new unsigned char[lengthBRR];
memset(dataBRR,0,lengthBRR);
dataBRR=new unsigned char[lengthBRR+9];
memset(dataBRR,0,lengthBRR+9);
break;
case DIV_SAMPLE_DEPTH_VOX: // VOX
if (dataVOX!=NULL) delete[] dataVOX;
@ -1041,7 +1045,7 @@ void DivSample::render(unsigned int formatMask) {
}
break;
case DIV_SAMPLE_DEPTH_BRR: // BRR
brrDecode(dataBRR,data16,lengthBRR);
brrDecode(dataBRR,data16,lengthBRR,brrEmphasis);
break;
case DIV_SAMPLE_DEPTH_VOX: // VOX
oki_decode(dataVOX,data16,samples);
@ -1100,7 +1104,7 @@ void DivSample::render(unsigned int formatMask) {
}
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_BRR)) { // BRR
if (!initInternal(DIV_SAMPLE_DEPTH_BRR,samples)) return;
brrEncode(data16,dataBRR,(samples+15)&(~15),loop?loopStart:-1);
brrEncode(data16,dataBRR,samples,loop?loopStart:-1,brrEmphasis);
}
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_VOX)) { // VOX
if (!initInternal(DIV_SAMPLE_DEPTH_VOX,samples)) return;
@ -1174,9 +1178,9 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
duplicate=new unsigned char[getCurBufLen()];
memcpy(duplicate,getCurBuf(),getCurBufLen());
}
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loop,loopMode);
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,loopMode);
} else {
h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loop,loopMode);
h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loop,brrEmphasis,loopMode);
}
if (!doNotPush) {
while (!redoHist.empty()) {

View file

@ -60,10 +60,10 @@ struct DivSampleHistory {
unsigned int length, samples;
DivSampleDepth depth;
int rate, centerRate, loopStart, loopEnd;
bool loop;
bool loop, brrEmphasis;
DivSampleLoopMode loopMode;
bool hasSample;
DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, DivSampleLoopMode lm):
DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, DivSampleLoopMode lm):
data((unsigned char*)d),
length(l),
samples(s),
@ -73,9 +73,10 @@ struct DivSampleHistory {
loopStart(ls),
loopEnd(le),
loop(lp),
brrEmphasis(be),
loopMode(lm),
hasSample(true) {}
DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, DivSampleLoopMode lm):
DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, DivSampleLoopMode lm):
data(NULL),
length(0),
samples(0),
@ -85,6 +86,7 @@ struct DivSampleHistory {
loopStart(ls),
loopEnd(le),
loop(lp),
brrEmphasis(be),
loopMode(lm),
hasSample(false) {}
~DivSampleHistory();
@ -106,7 +108,7 @@ struct DivSample {
// - 10: VOX ADPCM
// - 16: 16-bit PCM
DivSampleDepth depth;
bool loop;
bool loop, brrEmphasis;
// valid values are:
// - 0: Forward loop
// - 1: Backward loop
@ -306,6 +308,7 @@ struct DivSample {
loopOffP(0),
depth(DIV_SAMPLE_DEPTH_16BIT),
loop(false),
brrEmphasis(true),
loopMode(DIV_SAMPLE_LOOP_FORWARD),
data8(NULL),
data16(NULL),

View file

@ -688,20 +688,48 @@ void FurnaceGUI::actualWaveList() {
void FurnaceGUI::actualSampleList() {
for (int i=0; i<(int)e->song.sample.size(); i++) {
bool memWarning=false;
DivSample* sample=e->song.sample[i];
ImGui::TableNextRow();
ImGui::TableNextColumn();
for (int j=0; j<e->song.systemLen; j++) {
DivDispatch* dispatch=e->getDispatch(j);
if (dispatch==NULL) continue;
for (int k=0; k<4; k++) {
if (dispatch->getSampleMemCapacity(k)==0) continue;
if (!dispatch->isSampleLoaded(k,i) && sample->renderOn[k][j]) {
memWarning=true;
break;
}
}
if (memWarning) break;
}
if (memWarning) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_SAMPLE_CHIP_WARNING]);
if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) {
curSample=i;
samplePos=0;
updateSampleTex=true;
}
if (wantScrollList && curSample==i) ImGui::SetScrollHereY();
if (ImGui::IsItemHovered() && !mobileUI) {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]);
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
sampleEditOpen=true;
}
ImGui::PopStyleColor();
}
if (memWarning) {
ImGui::SameLine();
ImGui::Text(ICON_FA_EXCLAMATION_TRIANGLE);
if (ImGui::IsItemHovered() && !mobileUI) {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]);
ImGui::SetTooltip("out of memory for this sample!");
ImGui::PopStyleColor();
}
ImGui::PopStyleColor();
}
if (wantScrollList && curSample==i) ImGui::SetScrollHereY();
}
}

View file

@ -23,19 +23,29 @@
void FurnaceGUI::drawMobileControls() {
float timeScale=1.0f/(60.0f*ImGui::GetIO().DeltaTime);
if (mobileMenuOpen) {
if (mobileMenuPos<0.999f) {
WAKE_UP;
mobileMenuPos+=MIN(0.1,(1.0-mobileMenuPos)*0.65)*timeScale;
if (dragMobileMenu) {
if (portrait) {
mobileMenuPos=(dragMobileMenuOrigin.y-ImGui::GetMousePos().y)/(canvasH*0.65);
} else {
mobileMenuPos=1.0f;
mobileMenuPos=(ImGui::GetMousePos().x-dragMobileMenuOrigin.x)/(canvasW*0.65);
}
if (mobileMenuPos<0.0f) mobileMenuPos=0.0f;
if (mobileMenuPos>1.0f) mobileMenuPos=1.0f;
} else {
if (mobileMenuPos>0.001f) {
WAKE_UP;
mobileMenuPos-=MIN(0.1,mobileMenuPos*0.65)*timeScale;
if (mobileMenuOpen) {
if (mobileMenuPos<0.999f) {
WAKE_UP;
mobileMenuPos+=MIN(0.1,(1.0-mobileMenuPos)*0.65)*timeScale;
} else {
mobileMenuPos=1.0f;
}
} else {
mobileMenuPos=0.0f;
if (mobileMenuPos>0.001f) {
WAKE_UP;
mobileMenuPos-=MIN(0.1,mobileMenuPos*0.65)*timeScale;
} else {
mobileMenuPos=0.0f;
}
}
}
ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*canvasH)-(0.16*canvasW)):ImVec2(0.5*canvasW*mobileMenuPos,0.0f));
@ -54,7 +64,20 @@ void FurnaceGUI::drawMobileControls() {
}
}
if (ImGui::Button(mobButtonName,buttonSize)) {
mobileMenuOpen=!mobileMenuOpen;
if (!dragMobileMenu) {
mobileMenuOpen=!mobileMenuOpen;
}
}
if (ImGui::IsItemActive() && ImGui::GetIO().MouseDragMaxDistanceSqr[ImGuiMouseButton_Left]>ImGui::GetIO().ConfigInertialScrollToleranceSqr*2.0f) {
if (!dragMobileMenu) {
dragMobileMenu=true;
dragMobileMenuOrigin=ImGui::GetMousePos();
if (portrait) {
dragMobileMenuOrigin.y+=mobileMenuPos*canvasH*0.65f;
} else {
dragMobileMenuOrigin.x-=mobileMenuPos*canvasW*0.65f;
}
}
}
if (!portrait) ImGui::Separator();

View file

@ -2931,6 +2931,14 @@ void FurnaceGUI::pointUp(int x, int y, int button) {
}
orderScrollLocked=false;
orderScrollTolerance=false;
if (dragMobileMenu) {
dragMobileMenu=false;
if (mobileMenuOpen) {
mobileMenuOpen=(mobileMenuPos>=0.85f);
} else {
mobileMenuOpen=(mobileMenuPos>=0.15f);
}
}
if (selecting) {
if (!selectingFull) cursor=selEnd;
finishSelection();
@ -5785,6 +5793,7 @@ FurnaceGUI::FurnaceGUI():
keepLoopAlive(false),
orderScrollLocked(false),
orderScrollTolerance(false),
dragMobileMenu(false),
curWindow(GUI_WINDOW_NOTHING),
nextWindow(GUI_WINDOW_NOTHING),
curWindowLast(GUI_WINDOW_NOTHING),
@ -5877,6 +5886,7 @@ FurnaceGUI::FurnaceGUI():
orderScroll(0.0f),
orderScrollSlideOrigin(0.0f),
orderScrollRealOrigin(0.0f,0.0f),
dragMobileMenuOrigin(0.0f,0.0f),
layoutTimeBegin(0),
layoutTimeEnd(0),
layoutTimeDelta(0),

View file

@ -1436,7 +1436,7 @@ class FurnaceGUI {
SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd;
bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
bool keepLoopAlive, orderScrollLocked, orderScrollTolerance;
bool keepLoopAlive, orderScrollLocked, orderScrollTolerance, dragMobileMenu;
FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
std::atomic<FurnaceGUIWindows> curWindowThreadSafe;
float peak[2];
@ -1582,6 +1582,7 @@ class FurnaceGUI {
float nextScroll, nextAddScroll, orderScroll, orderScrollSlideOrigin;
ImVec2 orderScrollRealOrigin;
ImVec2 dragMobileMenuOrigin;
int layoutTimeBegin, layoutTimeEnd, layoutTimeDelta;
int renderTimeBegin, renderTimeEnd, renderTimeDelta;

View file

@ -190,6 +190,31 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::EndCombo();
}
bool isThereSNES=false;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_SNES) {
isThereSNES=true;
break;
}
}
if (sample->depth==DIV_SAMPLE_DEPTH_BRR || isThereSNES) {
bool be=sample->brrEmphasis;
if (ImGui::Checkbox("BRR emphasis",&be)) {
sample->prepareUndo(true);
sample->brrEmphasis=be;
e->renderSamplesP();
updateSampleTex=true;
MARK_MODIFIED;
}
if (ImGui::IsItemHovered()) {
if (sample->depth==DIV_SAMPLE_DEPTH_BRR) {
ImGui::SetTooltip("this is a BRR sample.\nenabling this option will muffle it (only affects non-SNES chips).");
} else {
ImGui::SetTooltip("enable this option to slightly boost high frequencies\nto compensate for the SNES' Gaussian filter's muffle.");
}
}
}
ImGui::TableNextColumn();
ImGui::Text("C-4 (Hz)");
ImGui::SameLine();