diff --git a/CMakeLists.txt b/CMakeLists.txt index 43c14562..5f137e13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -448,6 +448,7 @@ src/engine/brrUtils.c src/engine/safeReader.cpp src/engine/safeWriter.cpp src/engine/config.cpp +src/engine/configEngine.cpp src/engine/dispatchContainer.cpp src/engine/engine.cpp src/engine/fileOps.cpp diff --git a/papers/format.md b/papers/format.md index 1d4e7fc7..a58193be 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,9 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 119: Furnace dev119 (still not released) +- 118: Furnace dev118 +- 117: Furnace dev117 - 116: Furnace 0.6pre1.5 - 115: Furnace dev115 - 114: Furnace dev114 @@ -248,7 +251,7 @@ size | description | - 0xb9: Namco WSG - 3 channels | - 0xba: Namco 15xx - 8 channels | - 0xbb: Namco CUS30 - 8 channels - | - 0xbc: reserved - 8 channels + | - 0xbc: MSM5232 - 8 channels | - 0xbd: YM2612 extra features extended - 11 channels | - 0xbe: YM2612 extra features - 7 channels | - 0xbf: T6W28 - 4 channels @@ -258,7 +261,6 @@ size | description | - 0xc3: OPN CSM - 10 channels | - 0xc4: PC-98 CSM - 20 channels | - 0xc5: YM2610B CSM - 20 channels - | - 0xc6: MSM5232 - 8 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels @@ -270,7 +272,9 @@ size | description | - signed char, 64=1.0, 127=~2.0 32 | sound chip panning | - signed char, -128=left, 127=right - 128 | sound chip parameters + 128 | sound chip flag pointers (>=119) or sound chip flags + | - before 118, these were 32-bit flags. + | - for conversion details, see the "converting from old flags" section. STR | song name STR | song author 4f | A-4 tuning @@ -409,6 +413,23 @@ size | description | - same as above ``` +# chip flags + +``` +size | description +-----|------------------------------------ + 4 | "FLAG" block ID + 4 | size of this block + STR | data +``` + +flags are stored in text (`key=value`) format. for example: + +``` +clock=4000000 +stereo=true +``` + # instrument notes: @@ -906,6 +927,7 @@ size | description 1 | attack 1 | decay 1 | sustain + | - bit 3: sustain mode (>=118) 1 | release --- | **macro speeds/delays** (>=111) 1 | volume macro speed @@ -1154,3 +1176,384 @@ size | description ``` wavetable data follows. + +# converting from old flags + +prior to format version 119, chip flags were stored as a 32-bit integer. +this section will help you understand the old flag format. + +chips which aren't on this list don't have any flags. + +## 0x02: Genesis (COMPOUND) and 0x42: Genesis extended (COMPOUND) + +- bit 31: ladderEffect (bool) +- bit 0-30: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 8MHz + - 3: Firecore (`COLOR_NTSC*12/7`) + - 4: System 32 (`COLOR_NTSC*9/4`) + - only 0 and 1 apply to the SN part as well. + +## 0x03: SMS (SN76489) + +- flags AND 0xff03: clockSel (int) + - 0x0000: NTSC (becomes 0) + - 0x0001: PAL (becomes 1) + - 0x0002: 4MHz (becomes 2) + - 0x0003: half NTSC (becomes 3) + - 0x0100: 3MHz (becomes 4) + - 0x0101: 2MHz (becomes 5) + - 0x0102: eighth NTSC (becomes 6) +- flags AND 0xcc: chipType (int) + - 0x00: Sega PSG (becomes 0) + - 0x04: TI SN76489 (becomes 1) + - 0x08: SN with Atari-like short noise (becomes 2) + - 0x0c: Game Gear (becomes 3) + - 0x40: TI SN76489A (becomes 4) + - 0x44: TI SN76496 (becomes 5) + - 0x48: NCR 8496 (becomes 6) + - 0x4c: Tandy PSSJ 3-voice sound (becomes 7) + - 0x80: TI SN94624 (becomes 8) + - 0x84: TI SN76494 (becomes 9) +- bit 4: noPhaseReset (bool) + +## 0x04: Game Boy + +- bits 0-1: chipType (int) + - 0: DMG (rev B) + - 1: CGB (rev C) + - 2: CGB (rev E) + - 3: AGB +- bit 3: noAntiClick (bool) + +## 0x05: PC Engine + +- bit 1: clockSel (int) + - 0: NTSC + - 1: pseudo-PAL +- bit 2: chipType (int) + - 0: HuC6280 + - 1: HuC6280A +- bit 3: noAntiClick (bool) + +## 0x06: NES, 0x88: VRC6, 0x8a: FDS and 0x8b: MMC5 + +- flags: clockSel (int) + - 0: NTSC (2A03) + - 1: PAL (2A07) + - 2: Dendy + +## 0x07: C64 (8580) and 0x47: C64 (6581) + +- bit 0-3: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: SSI 2001 + +## 0x08: Arcade (YM2151+SegaPCM; COMPOUND) + +- bit 0-7: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 4MHz + - this clock only applies to the YM2151. + +## 0x09: Neo Geo CD (YM2610), 0xa5: Neo Geo (YM2610), 0xa6: Neo Geo extended (YM2610), 0x49: Neo Geo CD extended, 0x9e: YM2610B and 0xde: YM2610B extended + +- bit 0-7: clockSel (int) + - 0: 8MHz + - 1: 8.06MHz (Neo Geo AES) + +## 0x80: AY-3-8910 + +- bit 0-3: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: ZX Spectrum 48K (1.75MHz) + - 3: 2MHz + - 4: 1.5MHz + - 5: 1MHz + - 6: Sunsoft 5B + - 7: PAL NES + - 8: Sunsoft 5B on PAL NES + - 9: 1.10MHz + - 10: 2^21MHz + - 11: double NTSC + - 12: 3.6MHz + - 13: 1.25MHz + - 14: 1.536MHz +- bit 4-5: chipType (int) + - 0: AY-3-8910 + - 1: YM2149(F) + - 2: Sunsoft 5B + - 3: AY-3-8914 +- bit 6: stereo (bool) +- bit 8-15: stereoSep (int) + +## 0x81: Amiga + +- bit 0: clockSel (int) + - 0: NTSC + - 1: PAL +- bit 1: chipType (int) + - 0: Amiga 500 + - 1: Amiga 1200 +- bit 2: bypassLimits (bool) +- bit 8-14: stereoSep (int) + +## 0x82: YM2151 alone + +- bit 0-7: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 4MHz + +## 0x83: YM2612 alone, 0xa0: YM2612 extended, 0xbd: YM2612 extra features extended and 0xbe: YM2612 extra features + +- bit 31: ladderEffect (bool) +- bit 0-30: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 8MHz + - 3: Firecore (`COLOR_NTSC*12/7`) + - 4: System 32 (`COLOR_NTSC*9/4`) + +## 0x84: TIA + +- bit 0: clockSel (int) + - 0: NTSC + - 1: PAL +- bit 1-2: mixingType (int) + - 0: mono + - 1: mono (no distortion) + - 2: stereo + +## 0x85: VIC-20 + +- bit 0: clockSel (int) + - 0: NTSC + - 1: PAL + +## 0x87: SNES + +- bit 0-6: volScaleL (int) +- bit 8-14: volScaleR (int) + +## 0x89: OPLL (YM2413) and 0xa7: OPLL drums (YM2413) + +- bit 0-3: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 4MHz + - 3: half NTSC +- bit 4-31: patchSet (int) + - 0: YM2413 + - 1: YMF281 + - 2: YM2423 + - 3: VRC7 + +## 0x8c: Namco 163 + +- bit 0-3: clockSel (int) + - 0: NTSC (2A03) + - 1: PAL (2A07) + - 2: Dendy +- bit 4-6: channels (int) +- bit 7: multiplex (bool) + +## 0x8d: OPN (YM2203) and 0xb6: OPN extended + +- bit 0-4: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 4MHz + - 3: 3MHz + - 4: 3.99MHz + - 5: 1.5MHz +- bit 5-6: prescale (int) + - 0: /6 + - 1: /3 + - 2: /2 + +## 0x8e: PC-98 (YM2608) and 0xb7: PC-98 extended + +- bit 0-4: clockSel (int) + - 0: 8MHz + - 1: 7.98MHz +- bit 5-6: prescale (int) + - 0: /6 + - 1: /3 + - 2: /2 + +## 0x8f: OPL (YM3526), 0xa2: OPL drums (YM3526), 0x90: OPL2 (YM3812), 0xa3: OPL2 drums (YM3812), 0xb2: Yamaha Y8950 and 0xb3: Yamaha Y8950 drums + +- bit 0-7: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 4MHz + - 3: 3MHz + - 4: 3.99MHz + - 5: 3.5MHz + +## 0x91: OPL3 (YMF262) and 0xa4: OPL3 drums (YMF262) + +- bit 0-7: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 14MHz + - 3: 16MHz + - 4: 15MHz + +## 0x93: Intel 8253 (beeper) + +- bit 0-1: speakerType (int) + - 0: unfiltered + - 1: cone + - 2: piezo + - 3: system + +## 0x95: RF5C68 + +- bit 0-3: clockSel (int) + - 0: 8MHz + - 1: 10MHz + - 2: 12.5MHz +- bit 4-31: chipType (int) + - 0: RF5C68 + - 1: RF5C164 + +## 0x97: Philips SAA1099 + +- flags: clockSel (int) + - 0: 8MHz + - 1: NTSC + - 2: PAL + +## 0x98: OPZ (YM2414) + +- flags: clockSel (int) + - 0: NTSC + - 1: pseudo-PAL + - 2: 4MHz + +## 0x9a: AY8930 + +- bit 0-3: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: ZX Spectrum 48K (1.75MHz) + - 3: 2MHz + - 4: 1.5MHz + - 5: 1MHz + - 6: Sunsoft 5B + - 7: PAL NES + - 8: Sunsoft 5B on PAL NES + - 9: 1.10MHz + - 10: 2^21MHz + - 11: double NTSC + - 12: 3.6MHz +- bit 6: stereo (bool) +- bit 8-15: stereoSep (int) + +## 0x9d: VRC7 + +- bit 0-3: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 4MHz + - 3: half NTSC + +## 0x9f: ZX Spectrum (beeper) + +- bit 0-1: clockSel (int) + - 0: NTSC + - 1: PAL + +## 0xa1: Konami SCC and 0xb4: Konami SCC+ + +- bit 0-6: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 1.5MHz + - 3: 2MHz + +## 0xaa: MSM6295 + +- bit 0-6: clockSel (int) + - 0: 1MHz + - 1: 1.056MHz + - 2: 4MHz + - 3: 4.224MHz + - 4: NTSC + - 5: half NTSC + - 6: 2/7 NTSC + - 7: quarter NTSC + - 8: 2MHz + - 9: 2.112MHz + - 10: 875KHz + - 11: 937.5KHz + - 12: 1.5MHz + - 13: 3MHz + - 14: 1/3 NTSC +- bit 7: rateSel (bool) + +## 0xab: MSM6258 + +- flags: clockSel (int) + - 0: 4MHz + - 1: 4.096MHz + - 2: 8MHz + - 3: 8.192MHz + +## 0xae: OPL4 (YMF278B) and 0xaf: OPL4 drums (YMF278B) + +- bit 0-7: clockSel (int) + - 0: NTSC + - 1: PAL + - 2: 33.8688MHz + +## 0xb0: Seta/Allumer X1-010 + +- bit 0-3: clockSel (int) + - 0: 16MHz + - 1: 16.67MHz +- bit 4: stereo (bool) + +## 0xb5: tildearrow Sound Unit + +- bit 0: clockSel (int) + - 0: NTSC + - 1: PAL +- bit 2: echo (bool) +- bit 3: swapEcho (bool) +- bit 4: sampleMemSize (int) + - 0: 8K + - 1: 64K +- bit 5: pdm (bool) +- bit 8-13: echoDelay (int) +- bit 16-19: echoFeedback (int) +- bit 20-23: echoResolution (int) +- bit 24-31: echoVol (int) + +## 0xb8: YMZ280B + +- bit 0-7: clockSel (int) + - 0: 16.9344MHz + - 1: NTSC + - 2: PAL + - 3: 16MHz + - 4: 16.67MHz + - 5: 14MHz + +## 0xc0: PCM DAC + +- bit 0-15: rate (int) + - add +1 to this value +- bit 16-19: outDepth (int) +- bit 20: stereo (bool) + +## 0xe0: QSound + +- bit 0-11: echoDelay (int) +- bit 12-19: echoFeedback (int) \ No newline at end of file diff --git a/src/engine/brrUtils.c b/src/engine/brrUtils.c index e4463571..f5fce33b 100644 --- a/src/engine/brrUtils.c +++ b/src/engine/brrUtils.c @@ -22,78 +22,417 @@ #include "brrUtils.h" +#define NEXT_SAMPLE buf[j]-(buf[j]>>3) + +#define DO_ONE_DEC(r) \ + if (nextDec&8) nextDec|=0xfffffff0; \ +\ + if (r>=13) { /* invalid shift */ \ + nextDec=(nextDec<0)?0xfffff800:0; \ + } else { \ + nextDec<<=r; /* 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; \ + } \ +\ + if (nextDec>32767) nextDec=32767; \ + if (nextDec<-32768) nextDec=-32768; \ + nextDec&=0x7fff; \ + if (nextDec&0x4000) nextDec|=0xffff8000; \ +\ + last2=last1; \ + last1=nextDec; \ + long brrEncode(short* buf, unsigned char* out, long len, long loopStart) { 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 + // - 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 + // - 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 // 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 + // - if yes, mark end and finish // 8. go to 1 long total=0; - unsigned char next[9]; + unsigned char next0[8]; + unsigned char next1[8]; + unsigned char next2[8]; + unsigned char next3[8]; unsigned char filter=0; - unsigned char range=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; + + int last1=0; + int last2=0; + int nextDec=0; + int maxError[4]; + int avgError[4]; len&=~15; loopStart&=~15; for (long i=0; i((8<>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<((8<((8<>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; + } 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] %d\n",i>>4,avgError[0],avgError[1],avgError[2],avgError[3],filter); } else { + // don't filter on the first or loop block filter=0; } - range=0; - for (int j=0; j<16; j++) { - short s=buf[j]-(buf[j]>>13); - if (s<0) s=-s; - while (range<12 && s>((8<=len)?((loopStart>=0)?3:1):0); switch (filter) { case 0: - for (int j=0; j<16; j++) { - short s=buf[j]-(buf[j]>>13); - o0=s>>range; - if (o0>7) o0=7; - if (o0<-8) o0=-8; - if (range>=12) if (o0<-7) o0=-7; - o=o0&15; - if (j&1) { - next[1+(j>>1)]|=o; - } else { - next[1+(j>>1)]=o<<4; - } + 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; } - - out[0]=next[0]; - out[1]=next[1]; - out[2]=next[2]; - out[3]=next[3]; - out[4]=next[4]; - out[5]=next[5]; - out[6]=next[6]; - out[7]=next[7]; - out[8]=next[8]; buf+=16; out+=9; total+=9; @@ -135,9 +474,6 @@ long brrEncode(short* buf, unsigned char* out, long len, long loopStart) { *out=next<<1; \ out++; -// TODO: -// - what happens during overflow? -// - what happens when range values 12 to 15 are used? long brrDecode(unsigned char* buf, short* out, long len) { if (len==0) return 0; diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 38cb2b04..6270bdfa 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -17,101 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "engine.h" +#include "config.h" #include "../ta-log.h" #include "../fileutils.h" #include -#ifdef _WIN32 -#include "winStuff.h" -#define CONFIG_FILE "\\furnace.cfg" -#else -#ifdef __HAIKU__ -#include -#include -#endif -#include -#include -#include -#define CONFIG_FILE "/furnace.cfg" -#endif - -#ifdef IS_MOBILE -#ifdef HAVE_SDL2 -#include -#else -#error "Furnace mobile requires SDL2!" -#endif -#endif - -void DivEngine::initConfDir() { -#ifdef _WIN32 - // maybe move this function in here instead? - configPath=getWinConfigPath(); -#elif defined(IS_MOBILE) - configPath=SDL_GetPrefPath("tildearrow","furnace"); -#else -#ifdef __HAIKU__ - char userSettingsDir[PATH_MAX]; - status_t findUserDir = find_directory(B_USER_SETTINGS_DIRECTORY,0,true,userSettingsDir,PATH_MAX); - if (findUserDir==B_OK) { - configPath=userSettingsDir; - } else { - logW("unable to find/create user settings directory (%s)!",strerror(findUserDir)); - configPath="."; - return; - } -#else - // TODO this should check XDG_CONFIG_HOME first - char* home=getenv("HOME"); - if (home==NULL) { - int uid=getuid(); - struct passwd* entry=getpwuid(uid); - if (entry==NULL) { - logW("unable to determine home directory (%s)!",strerror(errno)); - configPath="."; - return; - } else { - configPath=entry->pw_dir; - } - } else { - configPath=home; - } -#ifdef __APPLE__ - configPath+="/Library/Application Support"; -#else - // FIXME this doesn't honour XDG_CONFIG_HOME *at all* - configPath+="/.config"; -#endif // __APPLE__ -#endif // __HAIKU__ -#ifdef __APPLE__ - configPath+="/Furnace"; -#else - configPath+="/furnace"; -#endif // __APPLE__ - struct stat st; - std::string pathSep="/"; - configPath+=pathSep; - size_t sepPos=configPath.find(pathSep,1); - while (sepPos!=std::string::npos) { - std::string subpath=configPath.substr(0,sepPos++); - if (stat(subpath.c_str(),&st)!=0) { - logI("creating config path element %s ...",subpath.c_str()); - if (mkdir(subpath.c_str(),0755)!=0) { - logW("could not create config path element %s! (%s)",subpath.c_str(),strerror(errno)); - configPath="."; - return; - } - } - sepPos=configPath.find(pathSep,sepPos); - } - configPath.resize(configPath.length()-pathSep.length()); -#endif // _WIN32 -} - -bool DivEngine::saveConf() { - configFile=configPath+String(CONFIG_FILE); - FILE* f=ps_fopen(configFile.c_str(),"wb"); +bool DivConfig::save(const char* path) { + FILE* f=ps_fopen(path,"wb"); if (f==NULL) { logW("could not write config file! %s",strerror(errno)); return false; @@ -128,43 +40,72 @@ bool DivEngine::saveConf() { return true; } -bool DivEngine::loadConf() { +String DivConfig::toString() { + String ret; + for (auto& i: conf) { + ret+=fmt::sprintf("%s=%s\n",i.first,i.second); + } + return ret; +} + +void DivConfig::parseLine(const char* line) { + String key=""; + String value=""; + bool keyOrValue=false; + for (const char* i=line; *i; i++) { + if (*i=='\n') continue; + if (keyOrValue) { + value+=*i; + } else { + if (*i=='=') { + keyOrValue=true; + } else { + key+=*i; + } + } + } + if (keyOrValue) { + conf[key]=value; + } +} + +bool DivConfig::loadFromFile(const char* path, bool createOnFail) { char line[4096]; - configFile=configPath+String(CONFIG_FILE); - FILE* f=ps_fopen(configFile.c_str(),"rb"); + FILE* f=ps_fopen(path,"rb"); if (f==NULL) { - logI("creating default config."); - return saveConf(); + if (createOnFail) { + logI("creating default config."); + return save(path); + } else { + return false; + } } logI("loading config."); while (!feof(f)) { - String key=""; - String value=""; - bool keyOrValue=false; if (fgets(line,4095,f)==NULL) { break; } - for (char* i=line; *i; i++) { - if (*i=='\n') continue; - if (keyOrValue) { - value+=*i; - } else { - if (*i=='=') { - keyOrValue=true; - } else { - key+=*i; - } - } - } - if (keyOrValue) { - conf[key]=value; - } + parseLine(line); } fclose(f); return true; } -bool DivEngine::getConfBool(String key, bool fallback) { +bool DivConfig::loadFromMemory(const char* buf) { + String line; + const char* readPos=buf; + while (*readPos) { + line+=*readPos; + readPos++; + if ((*readPos)=='\n' || (*readPos)==0) { + parseLine(line.c_str()); + line=""; + } + } + return true; +} + +bool DivConfig::getConfBool(String key, bool fallback) { try { String val=conf.at(key); if (val=="true") { @@ -177,7 +118,7 @@ bool DivEngine::getConfBool(String key, bool fallback) { return fallback; } -int DivEngine::getConfInt(String key, int fallback) { +int DivConfig::getConfInt(String key, int fallback) { try { String val=conf.at(key); int ret=std::stoi(val); @@ -188,7 +129,7 @@ int DivEngine::getConfInt(String key, int fallback) { return fallback; } -float DivEngine::getConfFloat(String key, float fallback) { +float DivConfig::getConfFloat(String key, float fallback) { try { String val=conf.at(key); float ret=std::stof(val); @@ -199,7 +140,7 @@ float DivEngine::getConfFloat(String key, float fallback) { return fallback; } -double DivEngine::getConfDouble(String key, double fallback) { +double DivConfig::getConfDouble(String key, double fallback) { try { String val=conf.at(key); double ret=std::stod(val); @@ -210,7 +151,7 @@ double DivEngine::getConfDouble(String key, double fallback) { return fallback; } -String DivEngine::getConfString(String key, String fallback) { +String DivConfig::getConfString(String key, String fallback) { try { String val=conf.at(key); return val; @@ -219,7 +160,7 @@ String DivEngine::getConfString(String key, String fallback) { return fallback; } -void DivEngine::setConf(String key, bool value) { +void DivConfig::setConf(String key, bool value) { if (value) { conf[key]="true"; } else { @@ -227,22 +168,22 @@ void DivEngine::setConf(String key, bool value) { } } -void DivEngine::setConf(String key, int value) { +void DivConfig::setConf(String key, int value) { conf[key]=fmt::sprintf("%d",value); } -void DivEngine::setConf(String key, float value) { +void DivConfig::setConf(String key, float value) { conf[key]=fmt::sprintf("%f",value); } -void DivEngine::setConf(String key, double value) { +void DivConfig::setConf(String key, double value) { conf[key]=fmt::sprintf("%f",value); } -void DivEngine::setConf(String key, const char* value) { +void DivConfig::setConf(String key, const char* value) { conf[key]=String(value); } -void DivEngine::setConf(String key, String value) { +void DivConfig::setConf(String key, String value) { conf[key]=value; } diff --git a/src/engine/config.h b/src/engine/config.h new file mode 100644 index 00000000..0d3fdc41 --- /dev/null +++ b/src/engine/config.h @@ -0,0 +1,52 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 _DIVCONFIG_H +#define _DIVCONFIG_H + +#include "../ta-utils.h" +#include + +class DivConfig { + std::map conf; + void parseLine(const char* line); + public: + // config loading/saving + bool loadFromMemory(const char* buf); + bool loadFromFile(const char* path, bool createOnFail=true); + String toString(); + bool save(const char* path); + + // get a config value + bool getConfBool(String key, bool fallback); + int getConfInt(String key, int fallback); + float getConfFloat(String key, float fallback); + double getConfDouble(String key, double fallback); + String getConfString(String key, String fallback); + + // set a config value + void setConf(String key, bool value); + void setConf(String key, int value); + void setConf(String key, float value); + void setConf(String key, double value); + void setConf(String key, const char* value); + void setConf(String key, String value); +}; + +#endif \ No newline at end of file diff --git a/src/engine/configEngine.cpp b/src/engine/configEngine.cpp new file mode 100644 index 00000000..df93cc95 --- /dev/null +++ b/src/engine/configEngine.cpp @@ -0,0 +1,162 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "engine.h" +#include "../ta-log.h" + +#ifdef _WIN32 +#include "winStuff.h" +#define CONFIG_FILE "\\furnace.cfg" +#else +#ifdef __HAIKU__ +#include +#include +#endif +#include +#include +#include +#define CONFIG_FILE "/furnace.cfg" +#endif + +#ifdef IS_MOBILE +#ifdef HAVE_SDL2 +#include +#else +#error "Furnace mobile requires SDL2!" +#endif +#endif + +void DivEngine::initConfDir() { +#ifdef _WIN32 + // maybe move this function in here instead? + configPath=getWinConfigPath(); +#elif defined(IS_MOBILE) + configPath=SDL_GetPrefPath("tildearrow","furnace"); +#else +#ifdef __HAIKU__ + char userSettingsDir[PATH_MAX]; + status_t findUserDir = find_directory(B_USER_SETTINGS_DIRECTORY,0,true,userSettingsDir,PATH_MAX); + if (findUserDir==B_OK) { + configPath=userSettingsDir; + } else { + logW("unable to find/create user settings directory (%s)!",strerror(findUserDir)); + configPath="."; + return; + } +#else + // TODO this should check XDG_CONFIG_HOME first + char* home=getenv("HOME"); + if (home==NULL) { + int uid=getuid(); + struct passwd* entry=getpwuid(uid); + if (entry==NULL) { + logW("unable to determine home directory (%s)!",strerror(errno)); + configPath="."; + return; + } else { + configPath=entry->pw_dir; + } + } else { + configPath=home; + } +#ifdef __APPLE__ + configPath+="/Library/Application Support"; +#else + // FIXME this doesn't honour XDG_CONFIG_HOME *at all* + configPath+="/.config"; +#endif // __APPLE__ +#endif // __HAIKU__ +#ifdef __APPLE__ + configPath+="/Furnace"; +#else + configPath+="/furnace"; +#endif // __APPLE__ + struct stat st; + std::string pathSep="/"; + configPath+=pathSep; + size_t sepPos=configPath.find(pathSep,1); + while (sepPos!=std::string::npos) { + std::string subpath=configPath.substr(0,sepPos++); + if (stat(subpath.c_str(),&st)!=0) { + logI("creating config path element %s ...",subpath.c_str()); + if (mkdir(subpath.c_str(),0755)!=0) { + logW("could not create config path element %s! (%s)",subpath.c_str(),strerror(errno)); + configPath="."; + return; + } + } + sepPos=configPath.find(pathSep,sepPos); + } + configPath.resize(configPath.length()-pathSep.length()); +#endif // _WIN32 +} + +bool DivEngine::saveConf() { + configFile=configPath+String(CONFIG_FILE); + return conf.save(configFile.c_str()); +} + +bool DivEngine::loadConf() { + configFile=configPath+String(CONFIG_FILE); + return conf.loadFromFile(configFile.c_str()); +} + +bool DivEngine::getConfBool(String key, bool fallback) { + return conf.getConfBool(key,fallback); +} + +int DivEngine::getConfInt(String key, int fallback) { + return conf.getConfInt(key,fallback); +} + +float DivEngine::getConfFloat(String key, float fallback) { + return conf.getConfFloat(key,fallback); +} + +double DivEngine::getConfDouble(String key, double fallback) { + return conf.getConfDouble(key,fallback); +} + +String DivEngine::getConfString(String key, String fallback) { + return conf.getConfString(key,fallback); +} + +void DivEngine::setConf(String key, bool value) { + conf.setConf(key,value); +} + +void DivEngine::setConf(String key, int value) { + conf.setConf(key,value); +} + +void DivEngine::setConf(String key, float value) { + conf.setConf(key,value); +} + +void DivEngine::setConf(String key, double value) { + conf.setConf(key,value); +} + +void DivEngine::setConf(String key, const char* value) { + conf.setConf(key,value); +} + +void DivEngine::setConf(String key, String value) { + conf.setConf(key,value); +} \ No newline at end of file diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a4f67c6e..8adc054a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -401,6 +401,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { SafeWriter* DivEngine::saveCommand(bool binary) { stop(); repeatPattern=false; + shallStop=false; setOrder(0); BUSY_BEGIN_SOFT; // determine loop point @@ -1121,7 +1122,7 @@ void DivEngine::initSongWithDesc(const int* description) { song.system[index]=(DivSystem)description[i]; song.systemVol[index]=description[i+1]; song.systemPan[index]=description[i+2]; - song.systemFlags[index]=description[i+3]; + song.systemFlagsOld[index]=description[i+3]; index++; chanCount+=getChannelCount(song.system[index]); if (chanCount>=DIV_MAX_CHANS) break; @@ -1344,7 +1345,7 @@ void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { } song.system[index]=which; - song.systemFlags[index]=0; + song.systemFlagsOld[index]=0; recalcChans(); saveLock.unlock(); BUSY_END; @@ -1370,7 +1371,7 @@ bool DivEngine::addSystem(DivSystem which) { song.system[song.systemLen]=which; song.systemVol[song.systemLen]=64; song.systemPan[song.systemLen]=0; - song.systemFlags[song.systemLen++]=0; + song.systemFlagsOld[song.systemLen++]=0; recalcChans(); saveLock.unlock(); BUSY_END; @@ -1414,7 +1415,7 @@ bool DivEngine::removeSystem(int index, bool preserveOrder) { song.system[i]=song.system[i+1]; song.systemVol[i]=song.systemVol[i+1]; song.systemPan[i]=song.systemPan[i+1]; - song.systemFlags[i]=song.systemFlags[i+1]; + song.systemFlagsOld[i]=song.systemFlagsOld[i+1]; } recalcChans(); saveLock.unlock(); @@ -1540,9 +1541,9 @@ bool DivEngine::swapSystem(int src, int dest, bool preserveOrder) { song.systemPan[dest]^=song.systemPan[src]; song.systemPan[src]^=song.systemPan[dest]; - song.systemFlags[src]^=song.systemFlags[dest]; - song.systemFlags[dest]^=song.systemFlags[src]; - song.systemFlags[src]^=song.systemFlags[dest]; + song.systemFlagsOld[src]^=song.systemFlagsOld[dest]; + song.systemFlagsOld[dest]^=song.systemFlagsOld[src]; + song.systemFlagsOld[src]^=song.systemFlagsOld[dest]; recalcChans(); saveLock.unlock(); @@ -1873,6 +1874,7 @@ void DivEngine::play() { sPreview.wave=-1; sPreview.pos=0; sPreview.dir=false; + shallStop=false; if (stepPlay==0) { freelance=false; playSub(false); @@ -2031,6 +2033,7 @@ void DivEngine::reset() { speed1=curSubSong->speed1; speed2=curSubSong->speed2; firstTick=false; + shallStop=false; nextSpeed=speed1; divider=60; if (curSubSong->customTempo) { @@ -3469,9 +3472,9 @@ void DivEngine::setOrder(unsigned char order) { void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { BUSY_BEGIN_SOFT; saveLock.lock(); - song.systemFlags[system]=flags; + song.systemFlagsOld[system]=flags; saveLock.unlock(); - disCont[system].dispatch->setFlags(song.systemFlags[system]); + disCont[system].dispatch->setFlags(song.systemFlagsOld[system]); disCont[system].setRates(got.rate); if (restart && isPlaying()) { playSub(false); @@ -3629,7 +3632,7 @@ void DivEngine::rescanAudioDevices() { void DivEngine::initDispatch() { BUSY_BEGIN; for (int i=0; i conf; + DivConfig conf; std::deque pendingNotes; // bitfield unsigned char walked[8192]; @@ -998,6 +1000,7 @@ class DivEngine { lowQuality(false), playing(false), freelance(false), + shallStop(false), speedAB(false), endOfSong(false), consoleMode(false), diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 0d8817eb..e6e97fff 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1235,7 +1235,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // system props for (int i=0; i<32; i++) { - ds.systemFlags[i]=reader.readI(); + ds.systemFlagsOld[i]=reader.readI(); } // handle compound systems @@ -2373,7 +2373,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.systemLen=(chCount+3)/4; for(int i=0; i1 || bypassLimits)?2:0); // PAL + ds.systemFlagsOld[i]=1|(80<<8)|(bypassLimits?4:0)|((ds.systemLen>1 || bypassLimits)?2:0); // PAL } for(int i=0; ichanShow[i]=true; @@ -2579,7 +2579,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ds.system[0]=DIV_SYSTEM_AMIGA; ds.systemVol[0]=64; ds.systemPan[0]=0; - ds.systemFlags[0]=1|(80<<8); // PAL + ds.systemFlagsOld[0]=1|(80<<8); // PAL ds.systemName="Amiga"; seqLen=reader.readI_BE(); @@ -3219,11 +3219,11 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { } if (expansions&16) { ds.system[systemID]=DIV_SYSTEM_N163; - ds.systemFlags[systemID++]=n163Chans; + ds.systemFlagsOld[systemID++]=n163Chans; } if (expansions&32) { ds.system[systemID]=DIV_SYSTEM_AY8910; - ds.systemFlags[systemID++]=38; // Sunsoft 5B + ds.systemFlagsOld[systemID++]=38; // Sunsoft 5B } ds.systemLen=systemID; @@ -3734,7 +3734,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } for (int i=0; i<32; i++) { - w->writeI(song.systemFlags[i]); + w->writeI(song.systemFlagsOld[i]); } // song name diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index e944fdbf..020edb96 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -559,13 +559,12 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(es5506.envelope.k2Slow); // SNES - // @tildearrow please update this w->writeC(snes.useEnv); - w->writeC(0); - w->writeC(0); + w->writeC(snes.gainMode); + w->writeC(snes.gain); w->writeC(snes.a); w->writeC(snes.d); - w->writeC(snes.s); + w->writeC((snes.s&7)|(snes.sus?8:0)); w->writeC(snes.r); // macro speed/delay @@ -1259,11 +1258,19 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { // SNES if (version>=109) { snes.useEnv=reader.readC(); - reader.readC(); - reader.readC(); + if (version<118) { + // why why why + reader.readC(); + reader.readC(); + } else { + snes.gainMode=(DivInstrumentSNES::GainMode)reader.readC(); + snes.gain=reader.readC(); + } snes.a=reader.readC(); snes.d=reader.readC(); snes.s=reader.readC(); + snes.sus=snes.s&8; + snes.s&=7; snes.r=reader.readC(); } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 1336bbd3..bc089839 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -610,12 +610,13 @@ struct DivInstrumentSNES { GAIN_MODE_INC_LINEAR=6, GAIN_MODE_INC_INVLOG=7 }; - bool useEnv; + bool useEnv, sus; GainMode gainMode; unsigned char gain; unsigned char a, d, s, r; DivInstrumentSNES(): useEnv(true), + sus(false), gainMode(GAIN_MODE_DIRECT), gain(127), a(15), diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 47743099..aaf64e07 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -218,6 +218,10 @@ void DivPlatformArcade::tick(bool sysTick) { immWrite(0x18,chan[i].std.ex3.val); } + if (chan[i].std.ex1.had || chan[i].std.ex2.had || chan[i].std.ex3.had) { + immWrite(0x01,0x00); // LFO On + } + if (chan[i].std.alg.had) { chan[i].state.alg=chan[i].std.alg.val; if (isMuted[i]) { diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index f7db0ef4..ae3362ec 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -47,7 +47,7 @@ void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t l chan.audPos%=chan.audLen; chan.audDir=false; } - output=(chan.ws.output[chan.audPos]^0x80)<<8; + output=(chan.ws.output[chan.audPos]-0x80)<<8; } else { DivSample* s=parent->getSample(chan.sample); if (s->samples>0) { @@ -195,7 +195,7 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA); if (ins->amiga.useWave) { chan.useWave=true; - chan.audLen=ins->amiga.waveLen; + chan.audLen=ins->amiga.waveLen+1; if (chan.insChanged) { if (chan.wave<0) { chan.wave=0; diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index d8dcd374..e99db88a 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -83,7 +83,7 @@ void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len bufL[h]=out[0]; bufR[h]=out[1]; for (int i=0; i<8; i++) { - int next=chOut[i*2]+chOut[i*2+1]; + int next=(3*(chOut[i*2]+chOut[i*2+1]))>>2; if (next<-32768) next=-32768; if (next>32767) next=32767; next=(next*254)/MAX(1,globalVolL+globalVolR); @@ -106,18 +106,9 @@ void DivPlatformSNES::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); - } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); - } + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); } chan[i].freqChanged=true; - } else { - if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); - chan[i].freqChanged=true; - } } if (chan[i].std.duty.had) { noiseFreq=chan[i].std.duty.val; @@ -217,7 +208,9 @@ void DivPlatformSNES::tick(bool sysTick) { chan[i].keyOn=false; } if (chan[i].keyOff) { - koff|=(1<snes; } - if (chan[c.chan].state.useEnv) { - chWrite(c.chan,5,chan[c.chan].state.a|(chan[c.chan].state.d<<4)|0x80); - chWrite(c.chan,6,chan[c.chan].state.r|(chan[c.chan].state.s<<5)); - } else { - chWrite(c.chan,5,0); - switch (chan[c.chan].state.gainMode) { - case DivInstrumentSNES::GAIN_MODE_DIRECT: - chWrite(c.chan,7,chan[c.chan].state.gain&127); - break; - case DivInstrumentSNES::GAIN_MODE_DEC_LINEAR: - chWrite(c.chan,7,0x80|(chan[c.chan].state.gain&31)); - break; - case DivInstrumentSNES::GAIN_MODE_INC_LINEAR: - chWrite(c.chan,7,0xc0|(chan[c.chan].state.gain&31)); - break; - case DivInstrumentSNES::GAIN_MODE_DEC_LOG: - chWrite(c.chan,7,0xa0|(chan[c.chan].state.gain&31)); - break; - case DivInstrumentSNES::GAIN_MODE_INC_INVLOG: - chWrite(c.chan,7,0xe0|(chan[c.chan].state.gain&31)); - break; - } - } + chan[c.chan].active=true; + if (chan[c.chan].insChanged || chan[c.chan].state.sus) { + writeEnv(c.chan); + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value)); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } - chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(ins); chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: - chan[c.chan].sample=-1; chan[c.chan].active=false; chan[c.chan].keyOff=true; - chan[c.chan].macroInit(NULL); + chan[c.chan].keyOn=false; + if (chan[c.chan].state.sus) { + writeEnv(c.chan); + } else { + chan[c.chan].macroInit(NULL); + } break; case DIV_CMD_NOTE_OFF_ENV: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + if (chan[c.chan].state.sus) { + writeEnv(c.chan); + } + chan[c.chan].std.release(); + break; case DIV_CMD_ENV_RELEASE: chan[c.chan].std.release(); break; @@ -359,7 +344,10 @@ int DivPlatformSNES::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - writeOutVol(c.chan); + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + writeOutVol(c.chan); + } } break; case DIV_CMD_GET_VOLUME: @@ -374,6 +362,11 @@ int DivPlatformSNES::dispatch(DivCommand c) { chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; + case DIV_CMD_WAVE: + if (!chan[c.chan].useWave) break; + chan[c.chan].wave=c.value; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + break; case DIV_CMD_NOTE_PORTA: { int destFreq=round(NOTE_FREQUENCY(c.value2)); bool return2=false; @@ -410,10 +403,117 @@ int DivPlatformSNES::dispatch(DivCommand c) { chan[c.chan].inPorta=c.value; break; case DIV_CMD_SAMPLE_POS: + // may have to remove this chan[c.chan].audPos=c.value; chan[c.chan].setPos=true; break; - // TODO SNES-specific commands + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].noise=c.value; + writeNoise=true; + break; + case DIV_CMD_SNES_PITCH_MOD: + chan[c.chan].pitchMod=c.value; + writePitchMod=true; + break; + case DIV_CMD_SNES_INVERT: + chan[c.chan].invertL=(c.value>>4); + chan[c.chan].invertR=c.chan&15; + writeOutVol(c.chan); + break; + case DIV_CMD_SNES_GAIN_MODE: + if (c.value) { + chan[c.chan].state.useEnv=false; + switch (c.value) { + case 1: + chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_DIRECT; + break; + case 2: + chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LINEAR; + break; + case 3: + chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LOG; + break; + case 4: + chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_INC_LINEAR; + break; + case 5: + chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_INC_INVLOG; + break; + } + } else { + chan[c.chan].state.useEnv=true; + } + writeEnv(c.chan); + break; + case DIV_CMD_SNES_GAIN: + if (chan[c.chan].state.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT) { + chan[c.chan].state.gain=c.value&0x7f; + } else { + chan[c.chan].state.gain=c.value&0x1f; + } + if (!chan[c.chan].state.useEnv) writeEnv(c.chan); + break; + case DIV_CMD_STD_NOISE_FREQ: + noiseFreq=c.value&0x1f; + writeControl=true; + break; + case DIV_CMD_FM_AR: + chan[c.chan].state.a=c.value&15; + if (chan[c.chan].state.useEnv) writeEnv(c.chan); + break; + case DIV_CMD_FM_DR: + chan[c.chan].state.d=c.value&7; + if (chan[c.chan].state.useEnv) writeEnv(c.chan); + break; + case DIV_CMD_FM_SL: + chan[c.chan].state.s=c.value&7; + if (chan[c.chan].state.useEnv) writeEnv(c.chan); + break; + case DIV_CMD_FM_RR: + chan[c.chan].state.r=c.value&0x1f; + if (chan[c.chan].state.useEnv) writeEnv(c.chan); + break; + case DIV_CMD_SNES_ECHO: + chan[c.chan].echo=c.value; + writeEcho=true; + break; + case DIV_CMD_SNES_ECHO_DELAY: { + echoDelay=c.value&15; + unsigned char esa=0xf8-(echoDelay<<3); + if (echoOn) { + rWrite(0x6d,esa); + rWrite(0x7d,echoDelay); + } + break; + } + case DIV_CMD_SNES_ECHO_ENABLE: + echoOn=c.value; + initEcho(); + break; + case DIV_CMD_SNES_ECHO_FEEDBACK: + echoFeedback=c.value; + if (echoOn) { + rWrite(0x0d,echoFeedback); + } + break; + case DIV_CMD_SNES_ECHO_FIR: + echoFIR[c.value&7]=c.value2; + if (echoOn) { + rWrite(0x0f+((c.value&7)<<4),echoFIR[c.value&7]); + } + break; + case DIV_CMD_SNES_ECHO_VOL_LEFT: + echoVolL=c.value; + if (echoOn) { + rWrite(0x2c,echoVolL); + } + break; + case DIV_CMD_SNES_ECHO_VOL_RIGHT: + echoVolR=c.value; + if (echoOn) { + rWrite(0x3c,echoVolR); + } + break; case DIV_CMD_GET_VOLMAX: return 127; break; @@ -450,6 +550,36 @@ void DivPlatformSNES::writeOutVol(int ch) { chWrite(ch,1,outR); } +void DivPlatformSNES::writeEnv(int ch) { + if (chan[ch].state.useEnv) { + chWrite(ch,5,chan[ch].state.a|(chan[ch].state.d<<4)|0x80); + if (chan[ch].state.sus && chan[ch].active) { + chWrite(ch,6,chan[ch].state.s<<5); + } else { + chWrite(ch,6,chan[ch].state.r|(chan[ch].state.s<<5)); + } + } else { + chWrite(ch,5,0); + switch (chan[ch].state.gainMode) { + case DivInstrumentSNES::GAIN_MODE_DIRECT: + chWrite(ch,7,chan[ch].state.gain&127); + break; + case DivInstrumentSNES::GAIN_MODE_DEC_LINEAR: + chWrite(ch,7,0x80|(chan[ch].state.gain&31)); + break; + case DivInstrumentSNES::GAIN_MODE_INC_LINEAR: + chWrite(ch,7,0xc0|(chan[ch].state.gain&31)); + break; + case DivInstrumentSNES::GAIN_MODE_DEC_LOG: + chWrite(ch,7,0xa0|(chan[ch].state.gain&31)); + break; + case DivInstrumentSNES::GAIN_MODE_INC_INVLOG: + chWrite(ch,7,0xe0|(chan[ch].state.gain&31)); + break; + } + } +} + void DivPlatformSNES::muteChannel(int ch, bool mute) { isMuted[ch]=mute; writeOutVol(ch); @@ -469,6 +599,7 @@ void DivPlatformSNES::forceIns() { writeNoise=true; writePitchMod=true; writeEcho=true; + initEcho(); } void* DivPlatformSNES::getChanState(int ch) { @@ -497,7 +628,28 @@ int DivPlatformSNES::getRegisterPoolSize() { return 128; } +void DivPlatformSNES::initEcho() { + unsigned char esa=0xf8-(echoDelay<<3); + if (echoOn) { + rWrite(0x6d,esa); + rWrite(0x7d,echoDelay); + rWrite(0x0d,echoFeedback); + rWrite(0x2c,echoVolL); + rWrite(0x3c,echoVolR); + for (int i=0; i<8; i++) { + rWrite(0x0f+(i<<4),echoFIR[i]); + } + } else { + rWrite(0x6d,0); + rWrite(0x7d,0); + rWrite(0x2c,0); + rWrite(0x3c,0); + } + writeControl=true; +} + void DivPlatformSNES::reset() { + memcpy(sampleMem,copyOfSampleMem,65536); dsp.init(sampleMem); dsp.set_output(NULL,0); memset(regPool,0,128); @@ -521,6 +673,22 @@ void DivPlatformSNES::reset() { writeNoise=false; writePitchMod=false; writeEcho=false; + + echoDelay=0; + echoFeedback=0; + echoFIR[0]=127; + echoFIR[1]=0; + echoFIR[2]=0; + echoFIR[3]=0; + echoFIR[4]=0; + echoFIR[5]=0; + echoFIR[6]=0; + echoFIR[7]=0; + echoVolL=127; + echoVolR=127; + echoOn=false; + + initEcho(); } bool DivPlatformSNES::isStereo() { @@ -574,7 +742,7 @@ size_t DivPlatformSNES::getSampleMemUsage(int index) { } void DivPlatformSNES::renderSamples() { - memset(sampleMem,0,getSampleMemCapacity()); + memset(copyOfSampleMem,0,getSampleMemCapacity()); memset(sampleOff,0,256*sizeof(unsigned int)); // skip past sample table and wavetable buffer @@ -585,17 +753,18 @@ void DivPlatformSNES::renderSamples() { int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length); if (actualLength>0) { sampleOff[i]=memPos; - memcpy(&sampleMem[memPos],s->dataBRR,actualLength); + memcpy(©OfSampleMem[memPos],s->dataBRR,actualLength); memPos+=actualLength; } if (actualLengthrate=rate; + isMuted[i]=false; + } setFlags(flags); reset(); return 8; diff --git a/src/engine/platform/snes.h b/src/engine/platform/snes.h index 4d05df83..61ddb486 100644 --- a/src/engine/platform/snes.h +++ b/src/engine/platform/snes.h @@ -77,11 +77,15 @@ class DivPlatformSNES: public DivDispatch { bool isMuted[8]; int globalVolL, globalVolR; unsigned char noiseFreq; + signed char echoVolL, echoVolR, echoFeedback; + signed char echoFIR[8]; + unsigned char echoDelay; size_t sampleTableBase; bool writeControl; bool writeNoise; bool writePitchMod; bool writeEcho; + bool echoOn; struct QueuedWrite { unsigned char addr; @@ -91,6 +95,7 @@ class DivPlatformSNES: public DivDispatch { std::queue writes; signed char sampleMem[65536]; + signed char copyOfSampleMem[65536]; size_t sampleMemLen; unsigned int sampleOff[256]; unsigned char regPool[0x80]; @@ -126,6 +131,8 @@ class DivPlatformSNES: public DivDispatch { private: void updateWave(int ch); void writeOutVol(int ch); + void writeEnv(int ch); + void initEcho(); }; #endif diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 13fc014d..6a086437 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -203,6 +203,7 @@ void DivPlatformYM2203::tick(bool sysTick) { ay->tick(sysTick); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); @@ -807,6 +808,7 @@ void DivPlatformYM2203::forceIns() { ay->forceIns(); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index f3b279c9..fc629487 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -448,6 +448,7 @@ void DivPlatformYM2203Ext::forceIns() { ay->forceIns(); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index b247be44..87fc7914 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -654,6 +654,7 @@ void DivPlatformYM2608::tick(bool sysTick) { ay->tick(sysTick); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); @@ -1199,6 +1200,7 @@ void DivPlatformYM2608::forceIns() { ay->forceIns(); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 66a4d252..0b73b42c 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -483,6 +483,7 @@ void DivPlatformYM2608Ext::forceIns() { ay->forceIns(); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 462955b0..63d6e034 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -593,13 +593,14 @@ void DivPlatformYM2610::tick(bool sysTick) { ay->tick(sysTick); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); } int DivPlatformYM2610::dispatch(DivCommand c) { - if (c.chan>=psgChanOffs && c.chan<7) { + if (c.chan>=psgChanOffs && c.chandispatch(c); } @@ -1169,6 +1170,7 @@ void DivPlatformYM2610::forceIns() { ay->forceIns(); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 1786f29f..e3757162 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -656,6 +656,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { ay->tick(sysTick); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); @@ -1232,6 +1233,7 @@ void DivPlatformYM2610B::forceIns() { ay->forceIns(); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 5c8c2c63..201bb598 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -474,6 +474,7 @@ void DivPlatformYM2610BExt::forceIns() { ay->forceIns(); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 22c9ebf9..ac82bb81 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -474,6 +474,7 @@ void DivPlatformYM2610Ext::forceIns() { ay->forceIns(); ay->flushWrites(); for (DivRegWrite& i: ay->getRegisterWrites()) { + if (i.addr>15) continue; immWrite(i.addr&15,i.val); } ay->getRegisterWrites().clear(); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 52cc34d3..dc7670b5 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -858,15 +858,7 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xff: // stop song - freelance=false; - playing=false; - extValuePresent=false; - stepPlay=0; - remainingLoops=-1; - sPreview.sample=-1; - sPreview.wave=-1; - sPreview.pos=0; - sPreview.dir=false; + shallStop=true; break; } } @@ -1299,6 +1291,21 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { firstTick=false; + if (shallStop) { + freelance=false; + playing=false; + extValuePresent=false; + stepPlay=0; + remainingLoops=-1; + sPreview.sample=-1; + sPreview.wave=-1; + sPreview.pos=0; + sPreview.dir=false; + ret=true; + shallStop=false; + return ret; + } + // system tick for (int i=0; itick(subticks==tickMult); diff --git a/src/engine/song.h b/src/engine/song.h index e4c3770f..03f51836 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -25,6 +25,7 @@ #define DIV_MAX_CHANS 128 #include "../ta-utils.h" +#include "config.h" #include "orders.h" #include "instrument.h" #include "pattern.h" @@ -233,202 +234,10 @@ struct DivSong { unsigned char systemLen; signed char systemVol[32]; signed char systemPan[32]; - // interpretation of these flags varies depending on system. - // - most systems: - // - bit 0: PAL - // - NES: - // - bit 0-1: system type - // - 0: NTSC - // - 1: PAL - // - 2: Dendy - // - SMS/SN76489: - // - bit 0-1, 8-15: clock rate - // - 0000: 3.58MHz (NTSC) - // - 0001: 3.55MHz (PAL) - // - 0002: 4MHz (Other) - // - 0003: 1.79MHz (half NTSC) - // - 0100: 3MHz - // - 0101: 2MHz - // - 0102: 447KHz (NTSC / 8) - // - bit 2-3, 6-7: chip type - // - 00: Sega VDP (16-bit noise) - // - 04: real SN76489 (15-bit noise) - // - 08: real SN76489 with Atari-like short noise buzz (15-bit noise) - // - 0c: Game Gear (16-bit noise, stereo) - // - 40: real SN76489A (17-bit noise) - // - 44: real SN76496 (17-bit noise) - // - 48: NCR 8496 (16-bit noise) - // - 4c: Tandy PSSJ-3 (16-bit noise) - // - 80: real SN94624 (15-bit noise) - // - 84: real SN76494 (17-bit noise) - // - bit 4: disable noise phase reset - // - YM2612/YM3438: - // - bit 0-30: clock rate - // - 0: Genesis NTSC (7.67MHz) - // - 1: Genesis PAL (7.61MHz) - // - 2: FM Towns (8MHz) - // - 3: AtGames Genesis (6.13MHz) - // - 4: Sega System 32 (8.06MHz) - // - bit 31: DAC distortion - // - 0: disable - // - 1: enable - // - YM2151: - // - bit 0-7: clock rate - // - 0: 3.58MHz (NTSC) - // - 1: 3.55MHz (PAL) - // - 2: 4MHz - // - YM2610(B): - // - bit 0-7: clock rate - // - 0: 8MHz (Neo Geo MVS) - // - 1: 8.06MHz (Neo Geo AES) - // - AY-3-8910/AY8930: - // - bit 0-3: clock rate - // - 0: 1.79MHz (MSX NTSC) - // - 1: 1.77MHz (ZX Spectrum, MSX PAL, etc.) - // - 2: 1.75MHz (ZX Spectrum) - // - 3: 2MHz (Atari ST) - // - 4: 1.5MHz (Vectrex) - // - 5: 1MHz (Amstrad CPC) - // - 6: 0.89MHz (Sunsoft 5B) - // - 7: 1.67MHz - // - 8: 0.83MHz (Sunsoft 5B on PAL) - // - 9: 1.10MHz (Gamate/VIC-20 PAL) - // - 10: 2.097152MHz (Game Boy) - // - 11: 3.58MHz (Darky) - // - 12: 3.6MHz (Darky) - // - 13: 1.25MHz - // - 14: 1.536MHz - // - bit 4-5: chip type (ignored on AY8930) - // - 0: AY-3-8910 or similar - // - 1: YM2149 - // - 2: Sunsoft 5B - // - 3: AY-3-8914 - // - bit 6: stereo (ignored on Sunsoft 5B) - // - 0: mono - // - 1: stereo ABC - // - bit 7: clock divider pin (YM2149, AY8930) - // - 0: high (disable divider) - // - 1: low (internally divided to half) - // - SAA1099: - // - bit 0-1: clock rate - // - 0: 8MHz (SAM CoupĂ©) - // - 1: 7.15MHz (Game Blaster, NTSC) - // - 2: 7.09MHz (PAL) - // - Amiga: - // - bit 0: clock rate - // - 0: 7.15MHz (NTSC) - // - 1: 7.09MHz (PAL) - // - bit 1: model - // - 0: Amiga 500 - // - 1: Amiga 1200 - // - bit 8-14: stereo separation - // - 0 is 0% while 127 is 100% - // - PC Speaker: - // - bit 0-1: speaker type - // - 0: unfiltered - // - 1: cone - // - 2: piezo - // - 3: real (TODO) - // - QSound: - // - bit 12-20: echo feedback - // - Valid values are 0-255 - // - bit 0-11: echo delay length - // - Valid values are 0-2725 - // - 0 is max length, 2725 is min length - // - OPLL: - // - bit 0-3: clock rate - // - 0: NTSC (3.58MHz) - // - 1: PAL (3.55MHz) - // - 2: Other (4MHz) - // - 3: half NTSC (1.79MHz) - // - bit 4-7: patch set - // - 0: YM2413 - // - 1: YMF281 - // - 2: YM2423 - // - 3: VRC7 - // - 4: custom (TODO) - // - X1-010: - // - bit 0-3: clock rate - // - 0: 16MHz (Seta 1) - // - 1: 16.67MHz (Seta 2) - // - bit 4: stereo - // - 0: mono - // - 1: stereo - // - YM2203: - // - bit 0-4: clock rate - // - 0: 3.58MHz (NTSC) - // - 1: 3.55MHz (PAL) - // - 2: 4MHz - // - 3: 3MHz - // - 4: 3.9936MHz (PC-88, PC-98) - // - 5: 1.5MHz - // - bit 5-6: output rate - // - 0: FM: clock / 72, SSG: clock / 16 - // - 1: FM: clock / 36, SSG: clock / 8 - // - 2: FM: clock / 24, SSG: clock / 4 - // - YM2608: - // - bit 0-4: clock rate - // - 0: 8MHz - // - 1: 7.987MHz (PC-88, PC-98) - // - bit 5-6: output rate - // - 0: FM: clock / 144, SSG: clock / 32 - // - 1: FM: clock / 72, SSG: clock / 16 - // - 2: FM: clock / 48, SSG: clock / 8 - // - YM3526, YM3812, Y8950: - // - bit 0-7: clock rate - // - 0: 3.58MHz (NTSC) - // - 1: 3.55MHz (PAL) - // - 2: 4MHz - // - 3: 3MHz - // - 4: 3.9936MHz (PC-88, PC-98) - // - 5: 3.5MHz - // - YMF262: - // - bit 0-7: clock rate - // - 0: 14.32MHz (NTSC) - // - 1: 14.19MHz (PAL) - // - 2: 14MHz - // - 3: 16MHz - // - 4: 15MHz - // - YMF289B: (TODO) - // - bit 0-7: clock rate - // - 0: 33.8688MHz - // - 1: 28.64MHz (NTSC) - // - 2: 28.38MHz (PAL) - // - MSM6295: - // - bit 0-6: clock rate - // - 0: 1MHz - // - 1: 1.056MHz - // - 2: 4MHz - // - 3: 4.224MHz - // - 4: 3.58MHz (NTSC) - // - 5: 1.79MHz (Half NTSC) - // - 6: 1.023MHz - // - 7: 0.895MHz (Quarter NTSC) - // - 8: 2MHz - // - 9: 2.112MHz - // - 10: 0.875MHz - // - 11: 0.9375MHz - // - 12: 1.5MHz - // - 13: 3MHz - // - 14: 1.193MHz - // - bit 7: Output rate - // - 0: clock / 132 - // - 1: clock / 165 - // - SCC/+: - // - bit 0-6: clock rate - // - 0: 1.79MHz (MSX NTSC) - // - 1: 1.77MHz (PAL) - // - 2: 1.5MHz - // - 3: 2MHz - // - YMZ280B: - // - bit 0-7: clock rate - // - 0: 16.9344MHz - // - 1: 14.32MHz (NTSC) - // - 2: 14.19MHz (PAL) - // - 3: 16MHz - // - 4: 16.67MHz - // - 5: 14MHz - unsigned int systemFlags[32]; + // this one will be removed soon... + unsigned int systemFlagsOld[32]; + // ...and replaced with... this! + DivConfig systemFlags[32]; // song information String name, author, systemName; @@ -624,7 +433,7 @@ struct DivSong { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; systemPan[i]=0; - systemFlags[i]=0; + systemFlagsOld[i]=0; } subsong.push_back(new DivSubSong); system[0]=DIV_SYSTEM_YM2612; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 7cc9d65b..e8f4c333 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -61,7 +61,7 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta return "help! what's going on!"; case 1: if (ds.system[0]==DIV_SYSTEM_AY8910) { - switch (ds.systemFlags[0]&0x3f) { + switch (ds.systemFlagsOld[0]&0x3f) { case 0: // AY-3-8910, 1.79MHz case 1: // AY-3-8910, 1.77MHz case 2: // AY-3-8910, 1.75MHz @@ -88,35 +88,35 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta return "Intellivision (PAL)"; default: - if ((ds.systemFlags[0]&0x30)==0x00) { + if ((ds.systemFlagsOld[0]&0x30)==0x00) { return "AY-3-8910"; - } else if ((ds.systemFlags[0]&0x30)==0x10) { + } else if ((ds.systemFlagsOld[0]&0x30)==0x10) { return "Yamaha YM2149"; - } else if ((ds.systemFlags[0]&0x30)==0x20) { + } else if ((ds.systemFlagsOld[0]&0x30)==0x20) { return "Overclocked Sunsoft 5B"; - } else if ((ds.systemFlags[0]&0x30)==0x30) { + } else if ((ds.systemFlagsOld[0]&0x30)==0x30) { return "Intellivision"; } } } else if (ds.system[0]==DIV_SYSTEM_SMS) { - switch (ds.systemFlags[0]&0x0f) { + switch (ds.systemFlagsOld[0]&0x0f) { case 0: case 1: return "Sega Master System"; case 6: return "BBC Micro"; } } else if (ds.system[0]==DIV_SYSTEM_YM2612) { - switch (ds.systemFlags[0]&3) { + switch (ds.systemFlagsOld[0]&3) { case 2: return "FM Towns"; } } else if (ds.system[0]==DIV_SYSTEM_YM2151) { - switch (ds.systemFlags[0]&3) { + switch (ds.systemFlagsOld[0]&3) { case 2: return "Sharp X68000"; } } else if (ds.system[0]==DIV_SYSTEM_SAA1099) { - switch (ds.systemFlags[0]&3) { + switch (ds.systemFlagsOld[0]&3) { case 0: return "SAM CoupĂ©"; } @@ -875,23 +875,11 @@ void DivEngine::registerSystems() { {DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES}, {}, { - {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, - {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Toggle noise mode"}}, - {0x12, {DIV_CMD_SNES_ECHO, "12xx: Toggle echo on this channel"}}, - {0x13, {DIV_CMD_SNES_PITCH_MOD, "13xx: Toggle pitch modulation"}}, - {0x14, {DIV_CMD_SNES_INVERT, "14xy: Toggle invert (x: left; y: right)"}}, - {0x15, {DIV_CMD_SNES_GAIN_MODE, "15xx: Set gain mode"}}, - {0x16, {DIV_CMD_SNES_GAIN, "16xx: Set gain"}}, {0x18, {DIV_CMD_SNES_ECHO_ENABLE, "18xx: Enable echo buffer"}}, - {0x19, {DIV_CMD_SNES_ECHO_DELAY, "19xx: Set echo delay"}}, + {0x19, {DIV_CMD_SNES_ECHO_DELAY, "19xx: Set echo delay (0 to F)"}}, {0x1a, {DIV_CMD_SNES_ECHO_VOL_LEFT, "1Axx: Set left echo volume"}}, {0x1b, {DIV_CMD_SNES_ECHO_VOL_RIGHT, "1Bxx: Set right echo volume"}}, {0x1c, {DIV_CMD_SNES_ECHO_FEEDBACK, "1Cxx: Set echo feedback"}}, - {0x1d, {DIV_CMD_STD_NOISE_FREQ, "1Dxx: Set noise frequency"}}, - {0x20, {DIV_CMD_FM_AR, "20xx: Set attack"}}, - {0x21, {DIV_CMD_FM_DR, "21xx: Set decay"}}, - {0x22, {DIV_CMD_FM_SL, "22xx: Set sustain"}}, - {0x23, {DIV_CMD_FM_RR, "23xx: Set release"}}, {0x30, {DIV_CMD_SNES_ECHO_FIR, "30xx: Set echo filter coefficient 0",constVal<0>,effectVal}}, {0x31, {DIV_CMD_SNES_ECHO_FIR, "31xx: Set echo filter coefficient 1",constVal<1>,effectVal}}, {0x32, {DIV_CMD_SNES_ECHO_FIR, "32xx: Set echo filter coefficient 2",constVal<2>,effectVal}}, @@ -900,6 +888,20 @@ void DivEngine::registerSystems() { {0x35, {DIV_CMD_SNES_ECHO_FIR, "35xx: Set echo filter coefficient 5",constVal<5>,effectVal}}, {0x36, {DIV_CMD_SNES_ECHO_FIR, "36xx: Set echo filter coefficient 6",constVal<6>,effectVal}}, {0x37, {DIV_CMD_SNES_ECHO_FIR, "37xx: Set echo filter coefficient 7",constVal<7>,effectVal}}, + }, + { + {0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}}, + {0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Toggle noise mode"}}, + {0x12, {DIV_CMD_SNES_ECHO, "12xx: Toggle echo on this channel"}}, + {0x13, {DIV_CMD_SNES_PITCH_MOD, "13xx: Toggle pitch modulation"}}, + {0x14, {DIV_CMD_SNES_INVERT, "14xy: Toggle invert (x: left; y: right)"}}, + {0x15, {DIV_CMD_SNES_GAIN_MODE, "15xx: Set envelope mode (0: ADSR, 1: gain/direct, 2: dec, 3: exp, 4: inc, 5: bent)"}}, + {0x16, {DIV_CMD_SNES_GAIN, "16xx: Set gain (00 to 7F if direct; 00 to 1F otherwise)"}}, + {0x1d, {DIV_CMD_STD_NOISE_FREQ, "1Dxx: Set noise frequency (00 to 1F)"}}, + {0x20, {DIV_CMD_FM_AR, "20xx: Set attack (0 to F)"}}, + {0x21, {DIV_CMD_FM_DR, "21xx: Set decay (0 to 7)"}}, + {0x22, {DIV_CMD_FM_SL, "22xx: Set sustain (0 to 7)"}}, + {0x23, {DIV_CMD_FM_RR, "23xx: Set release (00 to 1F)"}}, } ); @@ -1193,7 +1195,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_SFX_BEEPER]=new DivSysDef( - "ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false, 1U<chipClock; willExport[i]=true; - switch ((song.systemFlags[i]>>2)&3) { + switch ((song.systemFlagsOld[i]>>2)&3) { case 1: // real SN snNoiseConfig=3; snNoiseSize=15; @@ -1094,7 +1094,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p ayConfig=0x03; hasClockDivider=true; } else { - switch ((song.systemFlags[i]>>4)&3) { + switch ((song.systemFlagsOld[i]>>4)&3) { default: case 0: // AY8910 ayConfig=0x00; @@ -1113,10 +1113,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p break; } } - if (hasClockDivider && ((song.systemFlags[i]>>7)&1)) { + if (hasClockDivider && ((song.systemFlagsOld[i]>>7)&1)) { ayFlags|=0x10; } - if (hasStereo && ((song.systemFlags[i]>>6)&1)) { + if (hasStereo && ((song.systemFlagsOld[i]>>6)&1)) { ayFlags|=0x80; } willExport[i]=true; @@ -1358,7 +1358,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p // chips even though the only difference is the output resolution // these system types are currently handled by reusing isSecond flag // also this system is not dual-able - if ((song.systemFlags[i]>>4)==1) { + if ((song.systemFlagsOld[i]>>4)==1) { if (!hasRFC1) { hasRFC1=disCont[i].dispatch->chipClock; isSecond[i]=true; diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 6c9207ea..2ad6c4c4 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -383,9 +383,9 @@ void FurnaceGUI::drawDebug() { } if (ImGui::TreeNode("ADSR Test Area")) { static int tl, ar, dr, d2r, sl, rr, sus, egt, algOrGlobalSus, instType; - static float maxArDr, maxTl; + static float maxArDr, maxTl, maxRr; ImGui::Text("This window was done out of frustration"); - drawFMEnv(tl,ar,dr,d2r,rr,sl,sus,egt,algOrGlobalSus,maxTl,maxArDr,ImVec2(200.0f*dpiScale,100.0f*dpiScale),instType); + drawFMEnv(tl,ar,dr,d2r,rr,sl,sus,egt,algOrGlobalSus,maxTl,maxArDr,maxRr,ImVec2(200.0f*dpiScale,100.0f*dpiScale),instType); ImGui::InputInt("tl",&tl); ImGui::InputInt("ar",&ar); @@ -399,6 +399,7 @@ void FurnaceGUI::drawDebug() { ImGui::InputInt("instType",&instType); ImGui::InputFloat("maxArDr",&maxArDr); ImGui::InputFloat("maxTl",&maxTl); + ImGui::InputFloat("maxRr",&maxRr); ImGui::TreePop(); } if (ImGui::TreeNode("User Interface")) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index edc89ac1..9ce08ab5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1396,9 +1396,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openLoad( "Open File", - {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod", + {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod *.fc", "all files", ".*"}, - "compatible files{.fur,.dmf,.mod,.fc13,.fc14,.smod},.*", + "compatible files{.fur,.dmf,.mod,.fc13,.fc14,.smod,.fc},.*", workingDirSong, dpiScale ); @@ -3438,7 +3438,7 @@ bool FurnaceGUI::loop() { if (ImGui::BeginMenu("configure chip...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true); + drawSysConf(i,e->song.system[i],e->song.systemFlagsOld[i],true); ImGui::TreePop(); } } diff --git a/src/gui/gui.h b/src/gui/gui.h index 76a25efd..c4b1005d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1617,7 +1617,7 @@ class FurnaceGUI { void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); - void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, const ImVec2& size, unsigned short instType); + void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, float maxRr, const ImVec2& size, unsigned short instType); void drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size); void drawSysConf(int chan, DivSystem type, unsigned int& flags, bool modifyOnChange); void kvsConfig(DivInstrument* ins); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6229c955..f6b8b9a8 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1074,7 +1074,7 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons } } -void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, const ImVec2& size, unsigned short instType) { +void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, float maxRr, const ImVec2& size, unsigned short instType) { ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); @@ -1100,7 +1100,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, float arPos=float(maxArDr-ar)/maxArDr; //peak of AR, start of DR float drPos=arPos+((sl/15.0)*(float(maxArDr-dr)/maxArDr)); //end of DR, start of D2R float d2rPos=drPos+(((15.0-sl)/15.0)*(float(31.0-d2r)/31.0)); //End of D2R - float rrPos=(float(15-rr)/15.0); //end of RR + float rrPos=(float(maxRr-rr)/float(maxRr)); //end of RR //shrink all the x positions horizontally arPos/=2.0; @@ -1129,7 +1129,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, //addAALine(dl,pos3,posSLineVEnd,colorS); //draw vert. line through sustain level addAALine(dl,pos1,pos2,color); //A addAALine(dl,pos2,posDecayRate0Pt,color); //Line from A to end of graph - } else if (d2r==0.0 || (instType==DIV_INS_OPL && sus==1.0) || (instType==DIV_INS_OPLL && egt!=0.0)) { //envelope stays at the sustain level forever + } else if (d2r==0.0 || ((instType==DIV_INS_OPL || instType==DIV_INS_SNES) && sus==1.0) || (instType==DIV_INS_OPLL && egt!=0.0)) { //envelope stays at the sustain level forever dl->AddTriangleFilled(posRStart,posREnd,pos1,colorS); //draw release as shaded triangle behind everything addAALine(dl,pos3,posSLineHEnd,colorR); //draw horiz line through sustain level addAALine(dl,pos3,posSLineVEnd,colorR); //draw vert. line through sustain level @@ -1908,7 +1908,7 @@ void FurnaceGUI::drawInsEdit() { if (e->song.system[i]==DIV_SYSTEM_VRC7) { isPresent[3]=true; } else if (e->song.system[i]==DIV_SYSTEM_OPLL || e->song.system[i]==DIV_SYSTEM_OPLL_DRUMS) { - isPresent[(e->song.systemFlags[i]>>4)&3]=true; + isPresent[(e->song.systemFlagsOld[i]>>4)&3]=true; } } if (!isPresent[0] && !isPresent[1] && !isPresent[2] && !isPresent[3]) { @@ -2397,7 +2397,7 @@ void FurnaceGUI::drawInsEdit() { } ImGui::TableNextColumn(); - drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,op.sus,op.ssgEnv&8,ins->fm.alg,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight),ins->type); + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,op.sus,op.ssgEnv&8,ins->fm.alg,maxTl,maxArDr,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight),ins->type); if (settings.separateFMColors) { popAccentColors(); @@ -2814,7 +2814,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_OPZ) { envHeight-=ImGui::GetFrameHeightWithSpacing()*2.0f; } - drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,op.sus,op.ssgEnv&8,ins->fm.alg,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,envHeight),ins->type); + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,op.sus,op.ssgEnv&8,ins->fm.alg,maxTl,maxArDr,15,ImVec2(ImGui::GetContentRegionAvail().x,envHeight),ins->type); if (ins->type==DIV_INS_OPZ) { ImGui::Separator(); @@ -3007,7 +3007,7 @@ void FurnaceGUI::drawInsEdit() { } //52.0 controls vert scaling; default 96 - drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,op.sus,op.ssgEnv&8,ins->fm.alg,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale),ins->type); + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,op.sus,op.ssgEnv&8,ins->fm.alg,maxTl,maxArDr,15,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale),ins->type); //P(CWSliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable if (ImGui::BeginTable("opParams",2,ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); \ @@ -3654,7 +3654,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); ImGui::TableNextColumn(); - drawFMEnv(0,16-ins->c64.a,16-ins->c64.d,15-ins->c64.r,15-ins->c64.r,15-ins->c64.s,0,0,0,15,16,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); + drawFMEnv(0,16-ins->c64.a,16-ins->c64.d,15-ins->c64.r,15-ins->c64.r,15-ins->c64.s,0,0,0,15,16,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); ImGui::EndTable(); } @@ -3773,11 +3773,16 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Use wavetable (Amiga/SNES only)",&ins->amiga.useWave)); if (ins->amiga.useWave) { int len=ins->amiga.waveLen+1; + int origLen=len; if (ImGui::InputInt("Width",&len,2,16)) { if (ins->type==DIV_INS_SNES) { if (len<16) len=16; if (len>256) len=256; - ins->amiga.waveLen=(len&(~15))-1; + if (len>origLen) { + ins->amiga.waveLen=((len+15)&(~15))-1; + } else { + ins->amiga.waveLen=(len&(~15))-1; + } } else { if (len<2) len=2; if (len>256) len=256; @@ -4211,7 +4216,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN)); ImGui::TableNextColumn(); - drawFMEnv(0,ins->multipcm.ar,ins->multipcm.d1r,ins->multipcm.d2r,ins->multipcm.rr,ins->multipcm.dl,0,0,0,127,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); + drawFMEnv(0,ins->multipcm.ar,ins->multipcm.d1r,ins->multipcm.d2r,ins->multipcm.rr,ins->multipcm.dl,0,0,0,127,15,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); ImGui::EndTable(); } if (ImGui::BeginTable("MultiPCMLFOParams",3,ImGuiTableFlags_SizingStretchSame)) { @@ -4267,10 +4272,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); ImGui::TableNextColumn(); - drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.r,ins->snes.r,(14-ins->snes.s*2),(ins->snes.r==0),0,0,7,16,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); + drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.r,ins->snes.r,(14-ins->snes.s*2),(ins->snes.r==0 || ins->snes.sus),0,0,7,16,31,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); ImGui::EndTable(); } + ImGui::Checkbox("Make sustain effective",&ins->snes.sus); } else { if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 805072f1..b55a064c 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -273,7 +273,7 @@ void FurnaceGUI::drawSettings() { settings.initialSys.push_back(e->song.system[i]); settings.initialSys.push_back(e->song.systemVol[i]); settings.initialSys.push_back(e->song.systemPan[i]); - settings.initialSys.push_back(e->song.systemFlags[i]); + settings.initialSys.push_back(e->song.systemFlagsOld[i]); } settings.initialSysName=e->song.systemName; } diff --git a/src/gui/sysManager.cpp b/src/gui/sysManager.cpp index 386d2658..5d7a59c7 100644 --- a/src/gui/sysManager.cpp +++ b/src/gui/sysManager.cpp @@ -70,7 +70,7 @@ void FurnaceGUI::drawSysManager() { } ImGui::TableNextColumn(); if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSM%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true); + drawSysConf(i,e->song.system[i],e->song.systemFlagsOld[i],true); ImGui::TreePop(); } ImGui::TableNextColumn();