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

This commit is contained in:
cam900 2022-09-29 22:05:41 +09:00
commit 62f1ba73eb
33 changed files with 1426 additions and 499 deletions

View File

@ -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

View File

@ -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)

View File

@ -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<len; i+=16) {
// don't filter on the first or loop block
if (i && i!=loopStart) {
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;
} 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;
}
}
//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
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<<range)-1)) range++;
}
next[0]=(range<<4)|(filter<<2)|((i+16>=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;

View File

@ -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 <fmt/printf.h>
#ifdef _WIN32
#include "winStuff.h"
#define CONFIG_FILE "\\furnace.cfg"
#else
#ifdef __HAIKU__
#include <support/SupportDefs.h>
#include <storage/FindDirectory.h>
#endif
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#define CONFIG_FILE "/furnace.cfg"
#endif
#ifdef IS_MOBILE
#ifdef HAVE_SDL2
#include <SDL.h>
#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;
}

52
src/engine/config.h Normal file
View File

@ -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 <map>
class DivConfig {
std::map<String,String> 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

162
src/engine/configEngine.cpp Normal file
View File

@ -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 <support/SupportDefs.h>
#include <storage/FindDirectory.h>
#endif
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#define CONFIG_FILE "/furnace.cfg"
#endif
#ifdef IS_MOBILE
#ifdef HAVE_SDL2
#include <SDL.h>
#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);
}

View File

@ -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<song.systemLen; i++) {
disCont[i].init(song.system[i],this,getChannelCount(song.system[i]),got.rate,song.systemFlags[i]);
disCont[i].init(song.system[i],this,getChannelCount(song.system[i]),got.rate,song.systemFlagsOld[i]);
disCont[i].setRates(got.rate);
disCont[i].setQuality(lowQuality);
}

View File

@ -19,6 +19,7 @@
#ifndef _ENGINE_H
#define _ENGINE_H
#include "config.h"
#include "instrument.h"
#include "song.h"
#include "dispatch.h"
@ -46,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev117"
#define DIV_ENGINE_VERSION 117
#define DIV_VERSION "dev118"
#define DIV_ENGINE_VERSION 118
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -327,6 +328,7 @@ class DivEngine {
bool lowQuality;
bool playing;
bool freelance;
bool shallStop;
bool speedAB;
bool endOfSong;
bool consoleMode;
@ -365,7 +367,7 @@ class DivEngine {
DivAudioEngines audioEngine;
DivAudioExportModes exportMode;
double exportFadeOut;
std::map<String,String> conf;
DivConfig conf;
std::deque<DivNoteEvent> 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),

View File

@ -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; i<ds.systemLen; i++) {
ds.system[i]=DIV_SYSTEM_AMIGA;
ds.systemFlags[i]=1|(80<<8)|(bypassLimits?4:0)|((ds.systemLen>1 || bypassLimits)?2:0); // PAL
ds.systemFlagsOld[i]=1|(80<<8)|(bypassLimits?4:0)|((ds.systemLen>1 || bypassLimits)?2:0); // PAL
}
for(int i=0; i<chCount; i++) {
ds.subsong[0]->chanShow[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

View File

@ -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();
}

View File

@ -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),

View File

@ -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]) {

View File

@ -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;

View File

@ -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<<i);
if (!chan[i].state.sus) {
koff|=(1<<i);
}
chan[i].keyOff=false;
}
if (chan[i].freqChanged) {
@ -228,7 +221,7 @@ void DivPlatformSNES::tick(bool sysTick) {
}
}
if (writeControl) {
unsigned char control=noiseFreq&0x1f;
unsigned char control=(noiseFreq&0x1f)|(echoOn?0:0x20);
rWrite(0x6c,control);
writeControl=false;
}
@ -291,9 +284,9 @@ int DivPlatformSNES::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.setWidth(chan[c.chan].wtLen);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.setWidth(chan[c.chan].wtLen);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.init(ins,chan[c.chan].wtLen,15,chan[c.chan].insChanged);
} else {
@ -306,47 +299,39 @@ int DivPlatformSNES::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->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(&copyOfSampleMem[memPos],s->dataBRR,actualLength);
memPos+=actualLength;
}
if (actualLength<length) {
// terminate the sample
sampleMem[memPos-9]=1;
copyOfSampleMem[memPos-9]=1;
logW("out of BRR memory for sample %d!",i);
break;
}
}
sampleMemLen=memPos;
memcpy(sampleMem,copyOfSampleMem,65536);
}
void DivPlatformSNES::setFlags(unsigned int flags) {
@ -607,13 +776,14 @@ int DivPlatformSNES::init(DivEngine* p, int channels, int sugRate, unsigned int
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<8; i++) {
oscBuf[i]=new DivDispatchOscBuffer;
isMuted[i]=false;
}
sampleMemLen=0;
chipClock=1024000;
rate=chipClock/32;
for (int i=0; i<8; i++) {
oscBuf[i]=new DivDispatchOscBuffer;
oscBuf[i]->rate=rate;
isMuted[i]=false;
}
setFlags(flags);
reset();
return 8;

View File

@ -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<QueuedWrite> 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

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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.chan<adpcmAChanOffs) {
c.chan-=psgChanOffs;
return ay->dispatch(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();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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; i<song.systemLen; i++) disCont[i].dispatch->tick(subticks==tickMult);

View File

@ -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;

View File

@ -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<<DIV_SAMPLE_DEPTH_1BIT,
"ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"the ZX Spectrum only had a basic beeper capable of...\n...a bunch of thin pulses and tons of other interesting stuff!\nFurnace provides a thin pulse system.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"},

View File

@ -979,7 +979,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
if (!hasSN) {
hasSN=disCont[i].dispatch->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;

View File

@ -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")) {

View File

@ -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; i<e->song.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();
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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();