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

* 'master' of https://github.com/tildearrow/furnace: (70 commits)
  whoops
  GUI: AY8930 credits
  GUI: fix inability to close subsongs
  BANK
  OPN: wire up ExtCh system
  fix build failure
  dev95 - multiple songs in a single file (READ)
  DO NOT USE - THIS FAILS - WORK IN PROGRESS
  enforce asset limits
  old .dmf loading improvements
  add AICA and YMZ ADPCM formats
  allocate ID for YMZ280B
  harden .fur file saver
  Fix AY VGM output, Fix presets
  preparations for UI improvements
  GUI: add more presets
  prepare for ExtCh OPN/OPNA
  GUI: clarify that lock layout doesn't work yet
  GUI: remember last state of order edit mode
  GUI: store edit/followOrders/followPattern state
  ...

# Conflicts:
#	src/engine/fileOps.cpp
#	src/engine/platform/ym2610.cpp
#	src/engine/platform/ym2610b.cpp
#	src/engine/sample.cpp
#	src/engine/sample.h
#	src/engine/sysDef.cpp
#	src/gui/doAction.cpp
#	src/gui/sysConf.cpp
This commit is contained in:
cam900 2022-05-18 03:09:55 +09:00
commit 028adf2c8e
84 changed files with 7825 additions and 1146 deletions

1
.gitignore vendored
View File

@ -17,3 +17,4 @@ android/app/build/
android/app/.cxx/
.vs/
CMakeSettings.json
CMakePresets.json

View File

@ -45,7 +45,13 @@ option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendor
option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF)
set(DEPENDENCIES_INCLUDE_DIRS "")
if (ANDROID)
set(DEPENDENCIES_DEFINES "IS_MOBILE")
else()
set(DEPENDENCIES_DEFINES "")
endif()
set(DEPENDENCIES_COMPILE_OPTIONS "")
set(DEPENDENCIES_LIBRARIES "")
set(DEPENDENCIES_LIBRARY_DIRS "")
@ -319,6 +325,8 @@ src/engine/platform/sound/es550x/es5506.cpp
src/engine/platform/sound/scc/scc.cpp
src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp
src/engine/platform/ym2610Interface.cpp
src/engine/blip_buf.c
@ -352,6 +360,9 @@ src/engine/platform/nes.cpp
src/engine/platform/c64.cpp
src/engine/platform/arcade.cpp
src/engine/platform/tx81z.cpp
src/engine/platform/ym2203.cpp
src/engine/platform/ym2203ext.cpp
src/engine/platform/ym2608.cpp
src/engine/platform/ym2610.cpp
src/engine/platform/ym2610ext.cpp
src/engine/platform/ym2610b.cpp
@ -371,6 +382,7 @@ src/engine/platform/lynx.cpp
src/engine/platform/su.cpp
src/engine/platform/swan.cpp
src/engine/platform/vera.cpp
src/engine/platform/zxbeeper.cpp
src/engine/platform/bubsyswsg.cpp
src/engine/platform/n163.cpp
src/engine/platform/pet.cpp
@ -441,6 +453,7 @@ src/gui/settings.cpp
src/gui/songInfo.cpp
src/gui/songNotes.cpp
src/gui/stats.cpp
src/gui/subSongs.cpp
src/gui/sysConf.cpp
src/gui/sysEx.cpp
src/gui/util.cpp

10
TODO.md
View File

@ -11,11 +11,7 @@
- input pad
- settings
- RF5C68 system
- OPN system
- OPNA system
- ZX beeper system
- Y8950 system
- maybe YMU759 ADPCM channel
- ZX beeper system overlay percussion
- ADPCM chips
- Game Boy envelope macro/sequence
- rewrite the system name detection function anyway
@ -26,11 +22,9 @@
- try to find out why does VSlider not accept keyboard input
- finish lock layout
- if macros have release, note off should release them
- add ability to select entire row when clicking on row number
- store edit/followOrders/followPattern state in config
- add ability to select a column by double clicking
- add ability to move selection by dragging
- Apply button in settings
- find and replace
- finish wave synth
- add mono/poly note preview button
- add mono/poly note preview button

View File

@ -17,6 +17,7 @@ this is a list of systems that Furnace supports, including each system's effects
- [Yamaha YM2612 standalone](ym2612.md)
- [Yamaha YM2151 standalone](ym2151.md)
- [SegaPCM](segapcm.md)
- [Capcom QSound](qsound.md)
- [Atari 2600](tia.md)
- [Philips SAA1099](saa1099.md)
- [Microchip AY8930](ay8930.md)
@ -28,6 +29,7 @@ this is a list of systems that Furnace supports, including each system's effects
- [Yamaha OPL](opl.md)
- [PC Speaker](pcspkr.md)
- [Commodore PET](pet.md)
- [Konami SCC](scc.md)
- [Commodore VIC-20](vic20.md)
- [Konami VRC6](vrc6.md)
- [Famicom Disk System](fds.md)

View File

@ -1,14 +1,12 @@
# Bubble System WSG
a Konami's 2 channel wavetable sound generator logic used at their arcade hardware Bubble System.
a Konami-made 2 channel wavetable sound generator logic used on the Bubble System arcade board, configured with K005289, a 4-bit PROM and DAC.
It's configured with K005289, 4 bit PROM and DAC.
however, the K005289 is just part of the logic used for pitch and wavetable ROM address.
waveform select and volume control are tied with single AY-3-8910 IO for both channels.
another AY-3-8910 IO is used for reading sound hardware status.
Also known as K005289, but that's just part of the logic used for pitch and wavetable ROM address.
Waveform select and Volume control are tied with single AY-3-8910 IO for both channels.
Another AY-3-8910 IO is used for reading sound hardware status.
Furnace emulates this configurations as single system, waveform format is 15 level and 32 width.
Furnace emulates this configuration as single system with 32x16 wavetables.
# effects

View File

@ -0,0 +1,11 @@
# Konami SCC/SCC+
the Sound Creative Chip (SCC) adds 5 channels of wavetable to your MSX!
it was used in (of course) several Konami games, which had better audio quality due to the extra channels provided by this chip (poor AY since nobody used the envelope for bass).
the only problem? the waveform of the fourth channel is shared with the fifth one due to not enough memory in the chip!
the SCC+ fixes this issue though (while being compatible with SCC games).
# effects
- `10xx`: change wave.

View File

@ -4,7 +4,7 @@
yep, that's right! 16 channels of PCM!
a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 24 channels, and later they joined the software mixing gang.
a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 28 channels, and later they joined the software mixing gang.
# effects
- `20xx`: set PCM frequency.

View File

@ -0,0 +1,101 @@
# Yamaha YM2203 (OPN)
a cost-reduced version of the YM2151 (OPM).
it only has 3 FM channels instead of 8 and removes stereo, the LFO and DT2 (coarse detune).
however it does contain an AY/SSG part which provides 3 channels of square wave with noise and envelope.
this chip was used in the NEC PC-88/PC-98 series of computers, the Fujitsu FM-7AV and in some arcade boards.
several variants of this chip were released as well, with more features.
# effects
- `11xx`: set feedback of channel.
- `12xx`: set operator 1 level.
- `13xx`: set operator 2 level.
- `14xx`: set operator 3 level.
- `15xx`: set operator 4 level.
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4).
- `y` is the mutliplier.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 3 system.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4.
- `20xx`: set SSG channel mode. `xx` may be one of the following:
- `00`: square
- `01`: noise
- `02`: square and noise
- `03`: nothing (apparently)
- `04`: envelope and square
- `05`: envelope and noise
- `06`: envelope and square and noise
- `07`: nothing
- `21xx`: set noise frequency. `xx` is a value between 00 and 1F.
- `22xy`: set envelope mode.
- `x` sets the envelope shape, which may be one of the following:
- `0: \___` decay
- `4: /___` attack once
- `8: \\\\` saw
- `9: \___` decay
- `A: \/\/` inverse obelisco
- `B: \¯¯¯` decay once
- `C: ////` inverse saw
- `D: /¯¯¯` attack
- `E: /\/\` obelisco
- `F: /___` attack once
- if `y` is 1 then the envelope will affect this channel.
- `23xx`: set envelope period low byte.
- `24xx`: set envelope period high byte.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.
- `29xy`: enable SSG auto-envelope mode.
- in this mode the envelope period is set to the channel's notes, multiplied by a fraction.
- `x` is the numerator.
- `y` is the denominator.
- if `x` or `y` are 0 this will disable auto-envelope mode.
- `30xx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `50xy`: set AM of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `53xy` set DT of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: -0
- 5: -3
- 6: -2
- 7: -1
- `54xy` set RS of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set SSG-EG of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value (0-8).
- values between 0 and 7 set SSG-EG.
- value 8 disables it.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `59xx`: set DR of operator 3.
- `5Axx`: set DR of operator 4.
- `5Bxx`: set D2R/SR of all operators.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.

View File

@ -0,0 +1,101 @@
# Yamaha YM2608 (OPNA)
like YM2203, but with twice the FM channels, stereo, an ADPCM channel and built-in drums ("rhythm")!
it was one of the available sound chips for the NEC PC-88VA and PC-98 series of computers.
the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-in drums have been replaced with 6 sample channels.
# effects
- `10xy`: set LFO parameters.
- `x` toggles the LFO.
- `y` sets its speed.
- `11xx`: set feedback of channel.
- `12xx`: set operator 1 level.
- `13xx`: set operator 2 level.
- `14xx`: set operator 3 level.
- `15xx`: set operator 4 level.
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4).
- `y` is the mutliplier.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 3 system.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
- `1Cxx`: set attack of operator 3.
- `1Dxx`: set attack of operator 4.
- `20xx`: set SSG channel mode. `xx` may be one of the following:
- `00`: square
- `01`: noise
- `02`: square and noise
- `03`: nothing (apparently)
- `04`: envelope and square
- `05`: envelope and noise
- `06`: envelope and square and noise
- `07`: nothing
- `21xx`: set noise frequency. `xx` is a value between 00 and 1F.
- `22xy`: set envelope mode.
- `x` sets the envelope shape, which may be one of the following:
- `0: \___` decay
- `4: /___` attack once
- `8: \\\\` saw
- `9: \___` decay
- `A: \/\/` inverse obelisco
- `B: \¯¯¯` decay once
- `C: ////` inverse saw
- `D: /¯¯¯` attack
- `E: /\/\` obelisco
- `F: /___` attack once
- if `y` is 1 then the envelope will affect this channel.
- `23xx`: set envelope period low byte.
- `24xx`: set envelope period high byte.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.
- `29xy`: enable SSG auto-envelope mode.
- in this mode the envelope period is set to the channel's notes, multiplied by a fraction.
- `x` is the numerator.
- `y` is the denominator.
- if `x` or `y` are 0 this will disable auto-envelope mode.
- `30xx`: enable envelope hard reset.
- this works by inserting a quick release and tiny delay before a new note.
- `50xy`: set AM of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` determines whether AM is on.
- `51xy` set SL of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `52xy` set RR of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `53xy` set DT of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value:
- 0: +0
- 1: +1
- 2: +2
- 3: +3
- 4: -0
- 5: -3
- 6: -2
- 7: -1
- `54xy` set RS of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value.
- `55xy` set SSG-EG of operator.
- `x` is the operator (1-4). a value of 0 means "all operators".
- `y` is the value (0-8).
- values between 0 and 7 set SSG-EG.
- value 8 disables it.
- `56xx`: set DR of all operators.
- `57xx`: set DR of operator 1.
- `58xx`: set DR of operator 2.
- `59xx`: set DR of operator 3.
- `5Axx`: set DR of operator 4.
- `5Bxx`: set D2R/SR of all operators.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.

View File

@ -16,9 +16,9 @@ it is backward compatible with the original chip.
- `16xy`: set multiplier of operator.
- `x` is the operator (1-4).
- `y` is the mutliplier.
- `18xx`: toggle extended channel 2 mode.
- `18xx`: toggle extended channel 3 mode.
- 0 disables it and 1 enables it.
- only in extended channel 2 system.
- only in extended channel 3 system.
- `19xx`: set attack of all operators.
- `1Axx`: set attack of operator 1.
- `1Bxx`: set attack of operator 2.
@ -95,4 +95,4 @@ it is backward compatible with the original chip.
- `5Cxx`: set D2R/SR of operator 1.
- `5Dxx`: set D2R/SR of operator 2.
- `5Exx`: set D2R/SR of operator 3.
- `5Fxx`: set D2R/SR of operator 4.
- `5Fxx`: set D2R/SR of operator 4.

View File

@ -29,6 +29,11 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are:
- 95: Furnace dev95
- 94: Furnace dev94
- 93: Furnace dev93
- 92: Furnace dev92
- 91: Furnace dev91
- 90: Furnace dev90
- 89: Furnace dev89
- 88: Furnace dev88
@ -122,26 +127,26 @@ size | description
-----|------------------------------------
4 | "INFO" block ID
4 | reserved
1 | time base
1 | speed 1
1 | speed 2
1 | initial arpeggio time
4f | ticks per second
1 | time base (of first song)
1 | speed 1 (of first song)
1 | speed 2 (of first song)
1 | initial arpeggio time (of first song)
4f | ticks per second (of first song)
| - 60 is NTSC
| - 50 is PAL
2 | pattern length
2 | pattern length (of first song)
| - the limit is 256.
2 | orders length
2 | orders length (of first song)
| - the limit is 256 (>=80) or 127 (<80).
1 | highlight A
1 | highlight B
1 | highlight A (of first song)
1 | highlight B (of first song)
2 | instrument count
| - the limit is 256.
2 | wavetable count
| - the limit is 256.
2 | sample count
| - the limit is 256.
4 | pattern count
4 | pattern count (global)
32 | list of sound chips
| - possible soundchips:
| - 0x00: end of list
@ -215,6 +220,7 @@ size | description
| - 0xb5: tildearrow Sound Unit - 8 channels
| - 0xb6: OPN extended - 9 channels
| - 0xb7: PC-98 extended - 19 channels
| - 0xb8: YMZ280B - 8 channels
| - 0xde: YM2610B extended - 19 channels
| - 0xe0: QSound - 19 channels
| - 0xfd: Dummy System - 8 channels
@ -257,20 +263,20 @@ size | description
4?? | pointers to wavetables
4?? | pointers to samples
4?? | pointers to patterns
??? | orders
??? | orders (of first song)
| - a table of bytes
| - size=channels*ordLen
| - read orders then channels
| - the maximum value of a cell is FF (>=80) or 7F (<80).
??? | effect columns
??? | effect columns (of first song)
| - size=channels
1?? | channel hide status
1?? | channel hide status (of first song)
| - size=channels
1?? | channel collapse status
1?? | channel collapse status (of first song)
| - size=channels
S?? | channel names
S?? | channel names (of first song)
| - a list of channelCount C strings
S?? | channel short names
S?? | channel short names (of first song)
| - same as above
STR | song comment
4f | master volume, 1.0f=100% (>=59)
@ -291,6 +297,55 @@ size | description
1 | pitch macro is linear (>=90) or reserved
1 | pitch slide speed in full linear pitch mode (>=94) or reserved
18 | reserved
--- | **additional subsongs** (>=95)
STR | first subsong name
STR | first subsong comment
1 | number of additional subsongs
3 | reserved
4?? | pointers to subsong data
```
# subsong
from version 95 onwards, Furnace supports storing multiple songs on a single file.
the way it's currently done is really weird, but it provides for some backwards compatibility (previous versions will only load the first subsong which is already defined in the `INFO` block).
```
size | description
-----|------------------------------------
4 | "SONG" block ID
4 | reserved
1 | time base
1 | speed 1
1 | speed 2
1 | initial arpeggio time
4f | ticks per second
| - 60 is NTSC
| - 50 is PAL
2 | pattern length
| - the limit is 256.
2 | orders length
| - the limit is 256.
1 | highlight A
1 | highlight B
4 | reserved
STR | subsong name
STR | subsong comment
??? | orders
| - a table of bytes
| - size=channels*ordLen
| - read orders then channels
| - the maximum value of a cell is FF.
??? | effect columns
| - size=channels
1?? | channel hide status
| - size=channels
1?? | channel collapse status
| - size=channels
S?? | channel names
| - a list of channelCount C strings
S?? | channel short names
| - same as above
```
# instrument
@ -743,6 +798,8 @@ size | description
1 | depth
| - 0: ZX Spectrum overlay drum (1-bit)
| - 1: 1-bit NES DPCM (1-bit)
| - 2: AICA ADPCM
| - 3: YMZ ADPCM
| - 4: QSound ADPCM
| - 5: ADPCM-A
| - 6: ADPCM-B
@ -769,7 +826,8 @@ size | description
4 | reserved
2 | channel
2 | pattern index
4 | reserved
2 | subsong (>=95) or reserved
2 | reserved
??? | pattern data
| - size: rows*(4+effectColumns*2)*2
| - read shorts in this order:

View File

@ -179,6 +179,7 @@ enum DivDispatchCmds {
DIV_CMD_ES5506_ENVELOPE_RVRAMP, // (ramp)
DIV_CMD_ES5506_ENVELOPE_K1RAMP, // (ramp, slowdown)
DIV_CMD_ES5506_ENVELOPE_K2RAMP, // (ramp, slowdown)
DIV_CMD_ES5506_PAUSE, // (value)
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol

View File

@ -29,6 +29,9 @@
#include "platform/c64.h"
#include "platform/arcade.h"
#include "platform/tx81z.h"
#include "platform/ym2203.h"
#include "platform/ym2203ext.h"
#include "platform/ym2608.h"
#include "platform/ym2610.h"
#include "platform/ym2610ext.h"
#include "platform/ym2610b.h"
@ -58,6 +61,7 @@
#include "platform/scc.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "platform/zxbeeper.h"
#include "song.h"
void DivDispatchContainer::setRates(double gotRate) {
@ -235,6 +239,15 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_TIA:
dispatch=new DivPlatformTIA;
break;
case DIV_SYSTEM_OPN:
dispatch=new DivPlatformYM2203;
break;
case DIV_SYSTEM_OPN_EXT:
dispatch=new DivPlatformYM2203Ext;
break;
case DIV_SYSTEM_PC98:
dispatch=new DivPlatformYM2608;
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
@ -266,6 +279,14 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(3,true);
break;
case DIV_SYSTEM_Y8950:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(8950,false);
break;
case DIV_SYSTEM_Y8950_DRUMS:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(8950,true);
break;
case DIV_SYSTEM_OPZ:
dispatch=new DivPlatformTX81Z;
break;
@ -279,6 +300,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_PCSPKR:
dispatch=new DivPlatformPCSpeaker;
break;
case DIV_SYSTEM_SFX_BEEPER:
dispatch=new DivPlatformZXBeeper;
break;
case DIV_SYSTEM_LYNX:
dispatch=new DivPlatformLynx;
break;

View File

@ -139,18 +139,18 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
int nextRow=0;
int effectVal=0;
DivPattern* pat[DIV_MAX_CHANS];
for (int i=0; i<song.ordersLen; i++) {
for (int i=0; i<curSubSong->ordersLen; i++) {
for (int j=0; j<chans; j++) {
pat[j]=song.pat[j].getPattern(song.orders.ord[j][i],false);
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
}
for (int j=nextRow; j<song.patLen; j++) {
for (int j=nextRow; j<curSubSong->patLen; j++) {
nextRow=0;
for (int k=0; k<chans; k++) {
for (int l=0; l<song.pat[k].effectCols; l++) {
for (int l=0; l<curPat[k].effectCols; l++) {
effectVal=pat[k]->data[j][5+(l<<1)];
if (effectVal<0) effectVal=0;
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
if (nextOrder==-1 && (i<song.ordersLen-1 || !song.ignoreJumpAtEnd)) {
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
nextOrder=i+1;
nextRow=effectVal;
}
@ -491,6 +491,86 @@ void DivEngine::notifyWaveChange(int wave) {
BUSY_END;
}
int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret) {
ret = NULL;
if (path.empty()) {
return 0;
}
logI("loading ROM %s...",path);
FILE* f=ps_fopen(path.c_str(),"rb");
if (f==NULL) {
logE("error: %s",strerror(errno));
lastError=strerror(errno);
return -1;
}
if (fseek(f,0,SEEK_END)<0) {
logE("size error: %s",strerror(errno));
lastError=fmt::sprintf("on seek: %s",strerror(errno));
fclose(f);
return -1;
}
ssize_t len=ftell(f);
if (len==(SIZE_MAX>>1)) {
logE("could not get file length: %s",strerror(errno));
lastError=fmt::sprintf("on pre tell: %s",strerror(errno));
fclose(f);
return -1;
}
if (len<1) {
if (len==0) {
logE("that file is empty!");
lastError="file is empty";
} else {
logE("tell error: %s",strerror(errno));
lastError=fmt::sprintf("on tell: %s",strerror(errno));
}
fclose(f);
return -1;
}
if (len!=expectedSize) {
logE("ROM size mismatch, expected: %d bytes, was: %d bytes", expectedSize, len);
lastError=fmt::sprintf("ROM size mismatch, expected: %d bytes, was: %d", expectedSize, len);
return -1;
}
if (fseek(f,0,SEEK_SET)<0) {
logE("size error: %s",strerror(errno));
lastError=fmt::sprintf("on get size: %s",strerror(errno));
fclose(f);
return -1;
}
unsigned char* file=new unsigned char[len];
if (fread(file,1,(size_t)len,f)!=(size_t)len) {
logE("read error: %s",strerror(errno));
lastError=fmt::sprintf("on read: %s",strerror(errno));
fclose(f);
delete[] file;
return -1;
}
fclose(f);
ret = file;
return 0;
}
int DivEngine::loadSampleROMs() {
if (yrw801ROM!=NULL) {
delete[] yrw801ROM;
yrw801ROM=NULL;
}
if (tg100ROM!=NULL) {
delete[] tg100ROM;
tg100ROM=NULL;
}
if (mu5ROM!=NULL) {
delete[] mu5ROM;
mu5ROM=NULL;
}
int error = 0;
error += loadSampleROM(getConfString("yrw801Path",""), 0x200000, yrw801ROM);
error += loadSampleROM(getConfString("tg100Path",""), 0x200000, tg100ROM);
error += loadSampleROM(getConfString("mu5Path",""), 0x200000, mu5ROM);
return error;
}
void DivEngine::renderSamplesP() {
BUSY_BEGIN;
renderSamples();
@ -616,6 +696,7 @@ void DivEngine::createNew(const int* description) {
saveLock.lock();
song.unload();
song=DivSong();
changeSong(0);
if (description!=NULL) {
initSongWithDesc(description);
}
@ -637,45 +718,55 @@ void DivEngine::swapChannels(int src, int dest) {
}
for (int i=0; i<256; i++) {
song.orders.ord[dest][i]^=song.orders.ord[src][i];
song.orders.ord[src][i]^=song.orders.ord[dest][i];
song.orders.ord[dest][i]^=song.orders.ord[src][i];
curOrders->ord[dest][i]^=curOrders->ord[src][i];
curOrders->ord[src][i]^=curOrders->ord[dest][i];
curOrders->ord[dest][i]^=curOrders->ord[src][i];
DivPattern* prev=song.pat[src].data[i];
song.pat[src].data[i]=song.pat[dest].data[i];
song.pat[dest].data[i]=prev;
DivPattern* prev=curPat[src].data[i];
curPat[src].data[i]=curPat[dest].data[i];
curPat[dest].data[i]=prev;
}
song.pat[src].effectCols^=song.pat[dest].effectCols;
song.pat[dest].effectCols^=song.pat[src].effectCols;
song.pat[src].effectCols^=song.pat[dest].effectCols;
curPat[src].effectCols^=curPat[dest].effectCols;
curPat[dest].effectCols^=curPat[src].effectCols;
curPat[src].effectCols^=curPat[dest].effectCols;
String prevChanName=song.chanName[src];
String prevChanShortName=song.chanShortName[src];
bool prevChanShow=song.chanShow[src];
bool prevChanCollapse=song.chanCollapse[src];
String prevChanName=curSubSong->chanName[src];
String prevChanShortName=curSubSong->chanShortName[src];
bool prevChanShow=curSubSong->chanShow[src];
bool prevChanCollapse=curSubSong->chanCollapse[src];
song.chanName[src]=song.chanName[dest];
song.chanShortName[src]=song.chanShortName[dest];
song.chanShow[src]=song.chanShow[dest];
song.chanCollapse[src]=song.chanCollapse[dest];
song.chanName[dest]=prevChanName;
song.chanShortName[dest]=prevChanShortName;
song.chanShow[dest]=prevChanShow;
song.chanCollapse[dest]=prevChanCollapse;
curSubSong->chanName[src]=curSubSong->chanName[dest];
curSubSong->chanShortName[src]=curSubSong->chanShortName[dest];
curSubSong->chanShow[src]=curSubSong->chanShow[dest];
curSubSong->chanCollapse[src]=curSubSong->chanCollapse[dest];
curSubSong->chanName[dest]=prevChanName;
curSubSong->chanShortName[dest]=prevChanShortName;
curSubSong->chanShow[dest]=prevChanShow;
curSubSong->chanCollapse[dest]=prevChanCollapse;
}
void DivEngine::stompChannel(int ch) {
logV("stomping channel %d",ch);
for (int i=0; i<256; i++) {
song.orders.ord[ch][i]=0;
curOrders->ord[ch][i]=0;
}
song.pat[ch].wipePatterns();
song.pat[ch].effectCols=1;
song.chanName[ch]="";
song.chanShortName[ch]="";
song.chanShow[ch]=true;
song.chanCollapse[ch]=false;
curPat[ch].wipePatterns();
curPat[ch].effectCols=1;
curSubSong->chanName[ch]="";
curSubSong->chanShortName[ch]="";
curSubSong->chanShow[ch]=true;
curSubSong->chanCollapse[ch]=false;
}
void DivEngine::changeSong(size_t songIndex) {
if (songIndex>=song.subsong.size()) return;
curSubSong=song.subsong[songIndex];
curPat=song.subsong[songIndex]->pat;
curOrders=&song.subsong[songIndex]->orders;
curSubSongIndex=songIndex;
curOrder=0;
curRow=0;
}
void DivEngine::swapChannelsP(int src, int dest) {
@ -688,6 +779,41 @@ void DivEngine::swapChannelsP(int src, int dest) {
BUSY_END;
}
void DivEngine::changeSongP(size_t index) {
if (index>=song.subsong.size()) return;
if (index==curSubSongIndex) return;
stop();
BUSY_BEGIN;
saveLock.lock();
changeSong(index);
saveLock.unlock();
BUSY_END;
}
int DivEngine::addSubSong() {
if (song.subsong.size()>=127) return -1;
BUSY_BEGIN;
saveLock.lock();
song.subsong.push_back(new DivSubSong);
saveLock.unlock();
BUSY_END;
return song.subsong.size()-1;
}
bool DivEngine::removeSubSong(int index) {
if (song.subsong.size()<=1) return false;
stop();
BUSY_BEGIN;
saveLock.lock();
song.subsong[index]->clearData();
delete song.subsong[index];
song.subsong.erase(song.subsong.begin()+index);
changeSong(0);
saveLock.unlock();
BUSY_END;
return true;
}
void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
int chanCount=chans;
quitDispatch();
@ -760,6 +886,7 @@ bool DivEngine::addSystem(DivSystem which) {
return true;
}
// TODO: maybe issue with subsongs?
bool DivEngine::removeSystem(int index, bool preserveOrder) {
if (song.systemLen<=1) {
lastError="cannot remove the last one";
@ -1245,15 +1372,15 @@ void DivEngine::reset() {
}
extValue=0;
extValuePresent=0;
speed1=song.speed1;
speed2=song.speed2;
speed1=curSubSong->speed1;
speed2=curSubSong->speed2;
firstTick=false;
nextSpeed=speed1;
divider=60;
if (song.customTempo) {
divider=song.hz;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
} else {
if (song.pal) {
if (curSubSong->pal) {
divider=60;
} else {
divider=50;
@ -1408,6 +1535,10 @@ int DivEngine::getRow() {
return curRow;
}
size_t DivEngine::getCurrentSubSong() {
return curSubSongIndex;
}
unsigned char DivEngine::getSpeed1() {
return speed1;
}
@ -1417,9 +1548,9 @@ unsigned char DivEngine::getSpeed2() {
}
float DivEngine::getHz() {
if (song.customTempo) {
return song.hz;
} else if (song.pal) {
if (curSubSong->customTempo) {
return curSubSong->hz;
} else if (curSubSong->pal) {
return 60.0;
} else {
return 50.0;
@ -1526,6 +1657,7 @@ void DivEngine::unmuteAll() {
}
int DivEngine::addInstrument(int refChan) {
if (song.ins.size()>=256) return -1;
BUSY_BEGIN;
DivInstrument* ins=new DivInstrument;
int insCount=(int)song.ins.size();
@ -1544,7 +1676,9 @@ int DivEngine::addInstrument(int refChan) {
*ins=song.nullInsQSound;
}
ins->name=fmt::sprintf("Instrument %d",insCount);
ins->type=prefType;
if (prefType!=DIV_INS_NULL) {
ins->type=prefType;
}
saveLock.lock();
song.ins.push_back(ins);
song.insLen=insCount+1;
@ -1554,6 +1688,10 @@ int DivEngine::addInstrument(int refChan) {
}
int DivEngine::addInstrumentPtr(DivInstrument* which) {
if (song.ins.size()>=256) {
delete which;
return -1;
}
BUSY_BEGIN;
saveLock.lock();
song.ins.push_back(which);
@ -1584,10 +1722,10 @@ void DivEngine::delInstrument(int index) {
song.insLen=song.ins.size();
for (int i=0; i<chans; i++) {
for (int j=0; j<256; j++) {
if (song.pat[i].data[j]==NULL) continue;
for (int k=0; k<song.patLen; k++) {
if (song.pat[i].data[j]->data[k][2]>index) {
song.pat[i].data[j]->data[k][2]--;
if (curPat[i].data[j]==NULL) continue;
for (int k=0; k<curSubSong->patLen; k++) {
if (curPat[i].data[j]->data[k][2]>index) {
curPat[i].data[j]->data[k][2]--;
}
}
}
@ -1598,6 +1736,7 @@ void DivEngine::delInstrument(int index) {
}
int DivEngine::addWave() {
if (song.wave.size()>=256) return -1;
BUSY_BEGIN;
saveLock.lock();
DivWavetable* wave=new DivWavetable;
@ -1610,37 +1749,48 @@ int DivEngine::addWave() {
}
bool DivEngine::addWaveFromFile(const char* path) {
if (song.wave.size()>=256) {
lastError="too many wavetables!";
return false;
}
FILE* f=ps_fopen(path,"rb");
if (f==NULL) {
lastError=fmt::sprintf("%s",strerror(errno));
return false;
}
unsigned char* buf;
ssize_t len;
if (fseek(f,0,SEEK_END)!=0) {
fclose(f);
lastError=fmt::sprintf("could not seek to end: %s",strerror(errno));
return false;
}
len=ftell(f);
if (len<0) {
fclose(f);
lastError=fmt::sprintf("could not determine file size: %s",strerror(errno));
return false;
}
if (len==(SIZE_MAX>>1)) {
fclose(f);
lastError="file size is invalid!";
return false;
}
if (len==0) {
fclose(f);
lastError="file is empty";
return false;
}
if (fseek(f,0,SEEK_SET)!=0) {
fclose(f);
lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno));
return false;
}
buf=new unsigned char[len];
if (fread(buf,1,len,f)!=(size_t)len) {
logW("did not read entire wavetable file buffer!");
delete[] buf;
lastError=fmt::sprintf("could not read entire file: %s",strerror(errno));
return false;
}
fclose(f);
@ -1720,6 +1870,7 @@ bool DivEngine::addWaveFromFile(const char* path) {
} catch (EndOfFileException& e) {
delete wave;
delete[] buf;
lastError="premature end of file";
return false;
}
@ -1746,6 +1897,7 @@ void DivEngine::delWave(int index) {
}
int DivEngine::addSample() {
if (song.sample.size()>=256) return -1;
BUSY_BEGIN;
saveLock.lock();
DivSample* sample=new DivSample;
@ -1760,6 +1912,10 @@ int DivEngine::addSample() {
}
int DivEngine::addSampleFromFile(const char* path) {
if (song.sample.size()>=256) {
lastError="too many samples!";
return -1;
}
BUSY_BEGIN;
warnings="";
@ -1957,18 +2113,18 @@ void DivEngine::delSample(int index) {
void DivEngine::addOrder(bool duplicate, bool where) {
unsigned char order[DIV_MAX_CHANS];
if (song.ordersLen>=0xff) return;
if (curSubSong->ordersLen>=0xff) return;
BUSY_BEGIN_SOFT;
if (duplicate) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
order[i]=song.orders.ord[i][curOrder];
order[i]=curOrders->ord[i][curOrder];
}
} else {
bool used[256];
for (int i=0; i<chans; i++) {
memset(used,0,sizeof(bool)*256);
for (int j=0; j<song.ordersLen; j++) {
used[song.orders.ord[i][j]]=true;
for (int j=0; j<curSubSong->ordersLen; j++) {
used[curOrders->ord[i][j]]=true;
}
order[i]=0xff;
for (int j=0; j<256; j++) {
@ -1982,19 +2138,19 @@ void DivEngine::addOrder(bool duplicate, bool where) {
if (where) { // at the end
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
song.orders.ord[i][song.ordersLen]=order[i];
curOrders->ord[i][curSubSong->ordersLen]=order[i];
}
song.ordersLen++;
curSubSong->ordersLen++;
saveLock.unlock();
} else { // after current order
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=song.ordersLen; j>curOrder; j--) {
song.orders.ord[i][j]=song.orders.ord[i][j-1];
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
song.orders.ord[i][curOrder+1]=order[i];
curOrders->ord[i][curOrder+1]=order[i];
}
song.ordersLen++;
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (playing && !freelance) {
@ -2006,21 +2162,21 @@ void DivEngine::addOrder(bool duplicate, bool where) {
void DivEngine::deepCloneOrder(bool where) {
unsigned char order[DIV_MAX_CHANS];
if (song.ordersLen>=0xff) return;
if (curSubSong->ordersLen>=0xff) return;
warnings="";
BUSY_BEGIN_SOFT;
for (int i=0; i<chans; i++) {
bool didNotFind=true;
logD("channel %d",i);
order[i]=song.orders.ord[i][curOrder];
order[i]=curOrders->ord[i][curOrder];
// find free slot
for (int j=0; j<256; j++) {
logD("finding free slot in %d...",j);
if (song.pat[i].data[j]==NULL) {
if (curPat[i].data[j]==NULL) {
int origOrd=order[i];
order[i]=j;
DivPattern* oldPat=song.pat[i].getPattern(origOrd,false);
DivPattern* pat=song.pat[i].getPattern(j,true);
DivPattern* oldPat=curPat[i].getPattern(origOrd,false);
DivPattern* pat=curPat[i].getPattern(j,true);
memcpy(pat->data,oldPat->data,256*32*sizeof(short));
logD("found at %d",j);
didNotFind=false;
@ -2034,19 +2190,19 @@ void DivEngine::deepCloneOrder(bool where) {
if (where) { // at the end
saveLock.lock();
for (int i=0; i<chans; i++) {
song.orders.ord[i][song.ordersLen]=order[i];
curOrders->ord[i][curSubSong->ordersLen]=order[i];
}
song.ordersLen++;
curSubSong->ordersLen++;
saveLock.unlock();
} else { // after current order
saveLock.lock();
for (int i=0; i<chans; i++) {
for (int j=song.ordersLen; j>curOrder; j--) {
song.orders.ord[i][j]=song.orders.ord[i][j-1];
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
song.orders.ord[i][curOrder+1]=order[i];
curOrders->ord[i][curOrder+1]=order[i];
}
song.ordersLen++;
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (playing && !freelance) {
@ -2057,17 +2213,17 @@ void DivEngine::deepCloneOrder(bool where) {
}
void DivEngine::deleteOrder() {
if (song.ordersLen<=1) return;
if (curSubSong->ordersLen<=1) return;
BUSY_BEGIN_SOFT;
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=curOrder; j<song.ordersLen; j++) {
song.orders.ord[i][j]=song.orders.ord[i][j+1];
for (int j=curOrder; j<curSubSong->ordersLen; j++) {
curOrders->ord[i][j]=curOrders->ord[i][j+1];
}
}
song.ordersLen--;
curSubSong->ordersLen--;
saveLock.unlock();
if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1;
if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1;
if (playing && !freelance) {
playSub(false);
}
@ -2082,9 +2238,9 @@ void DivEngine::moveOrderUp() {
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1];
song.orders.ord[i][curOrder-1]^=song.orders.ord[i][curOrder];
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
}
saveLock.unlock();
curOrder--;
@ -2096,15 +2252,15 @@ void DivEngine::moveOrderUp() {
void DivEngine::moveOrderDown() {
BUSY_BEGIN_SOFT;
if (curOrder>=song.ordersLen-1) {
if (curOrder>=curSubSong->ordersLen-1) {
BUSY_END;
return;
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1];
song.orders.ord[i][curOrder+1]^=song.orders.ord[i][curOrder];
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
}
saveLock.unlock();
curOrder++;
@ -2117,12 +2273,12 @@ void DivEngine::moveOrderDown() {
void DivEngine::exchangeIns(int one, int two) {
for (int i=0; i<chans; i++) {
for (int j=0; j<256; j++) {
if (song.pat[i].data[j]==NULL) continue;
for (int k=0; k<song.patLen; k++) {
if (song.pat[i].data[j]->data[k][2]==one) {
song.pat[i].data[j]->data[k][2]=two;
} else if (song.pat[i].data[j]->data[k][2]==two) {
song.pat[i].data[j]->data[k][2]=one;
if (curPat[i].data[j]==NULL) continue;
for (int k=0; k<curSubSong->patLen; k++) {
if (curPat[i].data[j]->data[k][2]==one) {
curPat[i].data[j]->data[k][2]=two;
} else if (curPat[i].data[j]->data[k][2]==two) {
curPat[i].data[j]->data[k][2]=one;
}
}
}
@ -2244,9 +2400,9 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
// 1. check which channels are viable for this instrument
DivInstrument* insInst=getIns(ins);
if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type) notInViableChannel=true;
if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type && getPreferInsType(finalChan)!=DIV_INS_NULL) notInViableChannel=true;
for (int i=0; i<chans; i++) {
if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || getPreferInsSecondType(i)==insInst->type) {
if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || (getPreferInsType(i)==DIV_INS_NULL && finalChanType==DIV_CH_NOISE) || getPreferInsSecondType(i)==insInst->type) {
if (insInst->type==DIV_INS_OPL) {
if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) {
isViable[i]=true;
@ -2326,7 +2482,7 @@ void DivEngine::autoNoteOffAll() {
void DivEngine::setOrder(unsigned char order) {
BUSY_BEGIN_SOFT;
curOrder=order;
if (order>=song.ordersLen) curOrder=0;
if (order>=curSubSong->ordersLen) curOrder=0;
if (playing && !freelance) {
playSub(false);
}
@ -2349,15 +2505,15 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) {
void DivEngine::setSongRate(float hz, bool pal) {
BUSY_BEGIN;
saveLock.lock();
song.pal=!pal;
song.hz=hz;
curSubSong->pal=!pal;
curSubSong->hz=hz;
// what?
song.customTempo=true;
curSubSong->customTempo=true;
divider=60;
if (song.customTempo) {
divider=song.hz;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
} else {
if (song.pal) {
if (curSubSong->pal) {
divider=60;
} else {
divider=50;
@ -2722,6 +2878,8 @@ bool DivEngine::init() {
loadConf();
loadSampleROMs();
// set default system preset
if (!hasLoadedSomething) {
logD("setting default preset");
@ -2795,5 +2953,9 @@ bool DivEngine::quit() {
active=false;
delete[] oscBuf[0];
delete[] oscBuf[1];
if (yrw801ROM!=NULL) delete[] yrw801ROM;
if (tg100ROM!=NULL) delete[] tg100ROM;
if (mu5ROM!=NULL) delete[] mu5ROM;
song.unload();
return true;
}

View File

@ -45,8 +45,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev94"
#define DIV_ENGINE_VERSION 94
#define DIV_VERSION "dev95"
#define DIV_ENGINE_VERSION 95
// for imports
#define DIV_VERSION_MOD 0xff01
@ -188,6 +188,7 @@ typedef std::function<bool(int,unsigned char,unsigned char)> EffectProcess;
struct DivSysDef {
const char* name;
const char* nameJ;
const char* description;
unsigned char id;
unsigned char id_DMF;
int channels;
@ -203,7 +204,7 @@ struct DivSysDef {
EffectProcess postEffectFunc;
DivSysDef(
const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans,
bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound,
bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, const char* desc,
std::initializer_list<const char*> chNames,
std::initializer_list<const char*> chShortNames,
std::initializer_list<int> chTypes,
@ -213,6 +214,7 @@ struct DivSysDef {
EffectProcess postFxHandler=[](int,unsigned char,unsigned char) -> bool {return false;}):
name(sysName),
nameJ(sysNameJ),
description(desc),
id(fileID),
id_DMF(fileID_DMF),
channels(chans),
@ -302,6 +304,7 @@ class DivEngine {
bool hasLoadedSomething;
int softLockCount;
int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed;
size_t curSubSongIndex;
double divider;
int cycles;
double clockDrift;
@ -399,6 +402,8 @@ class DivEngine {
void loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
bool initAudioBackend();
bool deinitAudioBackend();
@ -409,8 +414,14 @@ class DivEngine {
void swapChannels(int src, int dest);
void stompChannel(int ch);
// change song (UNSAFE)
void changeSong(size_t songIndex);
public:
DivSong song;
DivOrders* curOrders;
DivChannelData* curPat;
DivSubSong* curSubSong;
DivInstrument* tempIns;
DivSystem sysOfChan[DIV_MAX_CHANS];
int dispatchOfChan[DIV_MAX_CHANS];
@ -601,6 +612,9 @@ class DivEngine {
// get current row
int getRow();
// get current subsong
size_t getCurrentSubSong();
// get speed 1
unsigned char getSpeed1();
@ -785,6 +799,9 @@ class DivEngine {
// get register cheatsheet
const char** getRegisterSheet(int sys);
// load sample ROMs
int loadSampleROMs();
// UNSAFE render samples - only execute when locked
void renderSamples();
@ -794,6 +811,15 @@ class DivEngine {
// public swap channels
void swapChannelsP(int src, int dest);
// public change song
void changeSongP(size_t index);
// add subsong
int addSubSong();
// remove subsong
bool removeSubSong(int index);
// change system
void changeSystem(int index, DivSystem which, bool preserveOrder=true);
@ -858,6 +884,10 @@ class DivEngine {
// terminate the engine.
bool quit();
unsigned char* yrw801ROM;
unsigned char* tg100ROM;
unsigned char* mu5ROM;
DivEngine():
output(NULL),
exportThread(NULL),
@ -891,6 +921,7 @@ class DivEngine {
curOrder(0),
remainingLoops(-1),
nextSpeed(3),
curSubSongIndex(0),
divider(60),
cycles(0),
clockDrift(0),
@ -926,13 +957,18 @@ class DivEngine {
metroAmp(0.0f),
metroVol(1.0f),
totalProcessed(0),
curOrders(NULL),
curPat(NULL),
tempIns(NULL),
oscBuf{NULL,NULL},
oscSize(1),
oscReadPos(0),
oscWritePos(0),
tickMult(1),
processTime(0) {
processTime(0),
yrw801ROM(NULL),
tg100ROM(NULL),
mu5ROM(NULL) {
memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool));
memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool));
memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int));
@ -947,6 +983,8 @@ class DivEngine {
sysFileMapFur[i]=DIV_SYSTEM_NULL;
sysFileMapDMF[i]=DIV_SYSTEM_NULL;
}
changeSong(0);
}
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -385,6 +385,45 @@ struct DivInstrumentAmiga {
TransWave transWave;
std::vector<TransWaveMap> transWaveMap;
/**
* get the sample at specified note.
* @return the sample.
*/
inline short getSample(int note) {
if (useNoteMap) {
if (note<0) note=0;
if (note>119) note=119;
return noteMap[note].ind;
}
return initSample;
}
/**
* get the sample frequency at specified note.
* @return the frequency, or -1 if not using note map.
*/
inline int getFreq(int note) {
if (useNoteMap) {
if (note<0) note=0;
if (note>119) note=119;
return noteMap[note].freq;
}
return -1;
}
/**
* get the sample reversed flag at specified note.
* @return the reversed flag.
*/
inline bool getReversed(int note) {
if (useNoteMap) {
if (note<0) note=0;
if (note>119) note=119;
return noteMap[note].reversed;
}
return reversed;
}
DivInstrumentAmiga():
initSample(0),
reversed(false),

View File

@ -49,7 +49,7 @@ void DivChannelData::wipePatterns() {
}
}
void DivPattern::copyOn(DivPattern *dest) {
void DivPattern::copyOn(DivPattern* dest) {
dest->name=name;
memcpy(dest->data,data,sizeof(data));
}

View File

@ -220,7 +220,7 @@ void DivPlatformAmiga::tick(bool sysTick) {
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq>4095) chan[i].freq=4095;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].keyOn) {
@ -238,7 +238,6 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
double off=1.0;
if (ins->amiga.useWave) {
chan[c.chan].useWave=true;
chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1;
@ -250,19 +249,11 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
}
}
} else {
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].useWave=false;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
}
}
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value));
chan[c.chan].baseFreq=round(NOTE_PERIODIC_NOROUND(c.value));
}
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
@ -328,17 +319,8 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
break;
case DIV_CMD_NOTE_PORTA: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].sample=ins->amiga.initSample;
double off=1.0;
if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
}
}
int destFreq=round(off*NOTE_PERIODIC_NOROUND(c.value2));
chan[c.chan].sample=ins->amiga.getSample(c.value2);
int destFreq=round(NOTE_PERIODIC_NOROUND(c.value2));
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
@ -361,16 +343,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
break;
}
case DIV_CMD_LEGATO: {
double off=1.0;
if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=8363.0/(double)s->centerRate;
}
}
chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))));
chan[c.chan].baseFreq=round(NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;

View File

@ -27,7 +27,7 @@
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} }
#define CHIP_DIVIDER 8
#define CHIP_DIVIDER ((sunsoft||clockSel)?16:8)
const char* regCheatSheetAY[]={
"FreqL_A", "0",
@ -589,6 +589,7 @@ void DivPlatformAY8910::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformAY8910::setFlags(unsigned int flags) {
clockSel=(flags>>7)&1;
switch (flags&15) {
case 1:
chipClock=COLOR_PAL*2.0/5.0;
@ -620,6 +621,12 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
case 10:
chipClock=2097152;
break;
case 11:
chipClock=COLOR_NTSC;
break;
case 12:
chipClock=3600000;
break;
default:
chipClock=COLOR_NTSC/2.0;
break;
@ -632,7 +639,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
if (ay!=NULL) delete ay;
switch ((flags>>4)&3) {
case 1:
ay=new ym2149_device(rate);
ay=new ym2149_device(rate,clockSel);
sunsoft=false;
intellivision=false;
break;
@ -654,7 +661,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
}
ay->device_start();
stereo=flags>>6;
stereo=(flags>>6)&1;
}
int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {

View File

@ -70,7 +70,7 @@ class DivPlatformAY8910: public DivDispatch {
int delay;
bool extMode;
bool stereo, sunsoft, intellivision;
bool stereo, sunsoft, intellivision, clockSel;
bool ioPortA, ioPortB;
unsigned char portAVal, portBVal;

View File

@ -27,7 +27,7 @@
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite2(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 4
#define CHIP_DIVIDER (clockSel?8:4)
const char* regCheatSheetAY8930[]={
"FreqL_A", "00",
@ -64,7 +64,7 @@ const char* regCheatSheetAY8930[]={
void DivPlatformAY8930::immWrite(unsigned char a, unsigned char v) {
if ((int)bank!=(a>>4)) {
bank=a>>4;
immWrite2(0x0d, 0xa0|(bank<<4)|ayEnvMode[0]);
immWrite2(0x0d, 0xa0|(bank<<4)|chan[0].envelope.mode);
}
if (a==0x0d) {
immWrite2(0x0d,0xa0|(bank<<4)|(v&15));
@ -258,8 +258,8 @@ void DivPlatformAY8930::tick(bool sysTick) {
rWrite(0x16+i,chan[i].std.ex1.val);
}
if (chan[i].std.ex2.had) {
ayEnvMode[i]=chan[i].std.ex2.val;
rWrite(regMode[i],ayEnvMode[i]);
chan[i].envelope.mode=chan[i].std.ex2.val;
rWrite(regMode[i],chan[i].envelope.mode);
}
if (chan[i].std.ex3.had) {
chan[i].autoEnvNum=chan[i].std.ex3.val;
@ -296,29 +296,29 @@ void DivPlatformAY8930::tick(bool sysTick) {
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) {
ayEnvPeriod[i]=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4;
immWrite(regPeriodL[i],ayEnvPeriod[i]);
immWrite(regPeriodH[i],ayEnvPeriod[i]>>8);
chan[i].envelope.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4;
immWrite(regPeriodL[i],chan[i].envelope.period);
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
}
chan[i].freqChanged=false;
}
if (ayEnvSlide[i]!=0) {
ayEnvSlideLow[i]+=ayEnvSlide[i];
while (ayEnvSlideLow[i]>7) {
ayEnvSlideLow[i]-=8;
if (ayEnvPeriod[i]<0xffff) {
ayEnvPeriod[i]++;
immWrite(regPeriodL[i],ayEnvPeriod[i]);
immWrite(regPeriodH[i],ayEnvPeriod[i]>>8);
if (chan[i].envelope.slide!=0) {
chan[i].envelope.slideLow+=chan[i].envelope.slide;
while (chan[i].envelope.slideLow>7) {
chan[i].envelope.slideLow-=8;
if (chan[i].envelope.period<0xffff) {
chan[i].envelope.period++;
immWrite(regPeriodL[i],chan[i].envelope.period);
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
}
}
while (ayEnvSlideLow[i]<-7) {
ayEnvSlideLow[i]+=8;
if (ayEnvPeriod[i]>0) {
ayEnvPeriod[i]--;
immWrite(regPeriodL[i],ayEnvPeriod[i]);
immWrite(regPeriodH[i],ayEnvPeriod[i]>>8);
while (chan[i].envelope.slideLow<-7) {
chan[i].envelope.slideLow+=8;
if (chan[i].envelope.period>0) {
chan[i].envelope.period--;
immWrite(regPeriodL[i],chan[i].envelope.period);
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
}
}
}
@ -435,8 +435,8 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
rWrite(0x06,c.value);
break;
case DIV_CMD_AY_ENVELOPE_SET:
ayEnvMode[c.chan]=c.value>>4;
rWrite(regMode[c.chan],ayEnvMode[c.chan]);
chan[c.chan].envelope.mode=c.value>>4;
rWrite(regMode[c.chan],chan[c.chan].envelope.mode);
if (c.value&15) {
chan[c.chan].psgMode|=4;
} else {
@ -449,19 +449,19 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
}
break;
case DIV_CMD_AY_ENVELOPE_LOW:
ayEnvPeriod[c.chan]&=0xff00;
ayEnvPeriod[c.chan]|=c.value;
immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]);
immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8);
chan[c.chan].envelope.period&=0xff00;
chan[c.chan].envelope.period|=c.value;
immWrite(regPeriodL[c.chan],chan[c.chan].envelope.period);
immWrite(regPeriodH[c.chan],chan[c.chan].envelope.period>>8);
break;
case DIV_CMD_AY_ENVELOPE_HIGH:
ayEnvPeriod[c.chan]&=0xff;
ayEnvPeriod[c.chan]|=c.value<<8;
immWrite(regPeriodL[c.chan],ayEnvPeriod[c.chan]);
immWrite(regPeriodH[c.chan],ayEnvPeriod[c.chan]>>8);
chan[c.chan].envelope.period&=0xff;
chan[c.chan].envelope.period|=c.value<<8;
immWrite(regPeriodL[c.chan],chan[c.chan].envelope.period);
immWrite(regPeriodH[c.chan],chan[c.chan].envelope.period>>8);
break;
case DIV_CMD_AY_ENVELOPE_SLIDE:
ayEnvSlide[c.chan]=c.value;
chan[c.chan].envelope.slide=c.value;
break;
case DIV_CMD_AY_NOISE_MASK_AND:
ayNoiseAnd=c.value;
@ -526,9 +526,9 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) {
void DivPlatformAY8930::forceIns() {
for (int i=0; i<3; i++) {
chan[i].insChanged=true;
immWrite(regPeriodL[i],ayEnvPeriod[i]);
immWrite(regPeriodH[i],ayEnvPeriod[i]>>8);
immWrite(regMode[i],ayEnvMode[i]);
immWrite(regPeriodL[i],chan[i].envelope.period);
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
immWrite(regMode[i],chan[i].envelope.mode);
}
}
@ -556,10 +556,10 @@ void DivPlatformAY8930::reset() {
chan[i]=DivPlatformAY8930::Channel();
chan[i].std.setEngine(parent);
chan[i].vol=31;
ayEnvPeriod[i]=0;
ayEnvMode[i]=0;
ayEnvSlide[i]=0;
ayEnvSlideLow[i]=0;
chan[i].envelope.period=0;
chan[i].envelope.mode=0;
chan[i].envelope.slide=0;
chan[i].envelope.slideLow=0;
}
if (dumpWrites) {
addWrite(0xffffffff,0);
@ -610,6 +610,7 @@ void DivPlatformAY8930::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformAY8930::setFlags(unsigned int flags) {
clockSel=(flags>>7)&1;
switch (flags&15) {
case 1:
chipClock=COLOR_PAL*2.0/5.0;
@ -641,6 +642,12 @@ void DivPlatformAY8930::setFlags(unsigned int flags) {
case 10:
chipClock=2097152;
break;
case 11:
chipClock=COLOR_NTSC;
break;
case 12:
chipClock=3600000;
break;
default:
chipClock=COLOR_NTSC/2.0;
break;
@ -650,7 +657,7 @@ void DivPlatformAY8930::setFlags(unsigned int flags) {
oscBuf[i]->rate=rate;
}
stereo=flags>>6;
stereo=(flags>>6)&1;
}
int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
@ -662,7 +669,7 @@ int DivPlatformAY8930::init(DivEngine* p, int channels, int sugRate, unsigned in
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
ay=new ay8930_device(rate);
ay=new ay8930_device(rate,clockSel);
ay->device_start();
ayBufLen=65536;
for (int i=0; i<3; i++) ayBuf[i]=new short[ayBufLen];

View File

@ -27,6 +27,17 @@
class DivPlatformAY8930: public DivDispatch {
protected:
struct Channel {
struct Envelope {
unsigned char mode;
unsigned short period;
short slideLow;
short slide;
Envelope():
mode(0),
period(0),
slideLow(0),
slide(0) {}
} envelope;
unsigned char freqH, freqL;
int freq, baseFreq, note, pitch, pitch2;
int ins;
@ -59,16 +70,12 @@ class DivPlatformAY8930: public DivDispatch {
int delay;
bool extMode, stereo;
bool extMode, stereo, clockSel;
bool ioPortA, ioPortB;
unsigned char portAVal, portBVal;
short oldWrites[32];
short pendingWrites[32];
unsigned char ayEnvMode[3];
unsigned short ayEnvPeriod[3];
short ayEnvSlideLow[3];
short ayEnvSlide[3];
short* ayBuf[3];
size_t ayBufLen;

View File

@ -111,6 +111,9 @@ const char* DivPlatformES5506::getEffectName(unsigned char effect) {
case 0x11:
return "11xx: Set filter mode (00 to 03)";
break;
case 0x12:
return "120x: Set pause (bit 0)";
break;
case 0x14:
return "14xx: Set filter coefficient K1 low byte";
break;
@ -456,6 +459,15 @@ void DivPlatformES5506::tick(bool sysTick) {
}
}
}
// control macros
if (!chan[i].keyOn) {
if (chan[i].active && chan[i].std.alg.had) {
if (chan[i].pcm.pause!=(bool)(chan[i].std.alg.val&1)) {
chan[i].pcm.pause=chan[i].std.alg.val&1;
pageWriteMask(0x00|i,0x5f,0x00,chan[i].pcm.pause?0x0002:0x0000,0x0002);
}
}
}
// update registers
if (chan[i].volChanged.changed) {
if (!isMuted[i]) { // calculate volume (16 bit)
@ -631,6 +643,9 @@ void DivPlatformES5506::tick(bool sysTick) {
loopFlag|=0x0018;
break;
}
if (chan[i].pcm.pause) {
loopFlag|=0x0002;
}
// Run sample
pageWrite(0x00|i,0x06,chan[i].envelope.ecount); // Clear ECOUNT
pageWriteMask(0x00|i,0x5f,0x00,loopFlag,0x3cff);
@ -661,78 +676,86 @@ void DivPlatformES5506::tick(bool sysTick) {
int DivPlatformES5506::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
DivInstrumentAmiga::NoteMap& noteMapind=ins->amiga.noteMap[c.value];
DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[ins->amiga.transWave.ind];
int sample=ins->amiga.initSample;
if (ins->amiga.transWave.enable) {
sample=transWaveInd.ind;
} else if (ins->amiga.useNoteMap) {
ins->amiga.noteMap[c.value].ind;
}
if (sample>=0 && sample<parent->song.sampleLen) {
chan[c.chan].pcm.index=sample;
chan[c.chan].transWave.enable=ins->amiga.transWave.enable;
chan[c.chan].transWave.sliceEnable=ins->amiga.transWave.sliceEnable;
chan[c.chan].transWave.ind=ins->amiga.transWave.ind;
DivSample* s=parent->getSample(sample);
// get frequency offset
double off=1.0;
double center=s->centerRate;
if (center<1) {
off=1.0;
} else {
off=(double)center/8363.0;
}
if (ins->amiga.useNoteMap) {
off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)c.value-48.0)/12.0));
chan[c.chan].pcm.note=c.value;
}
// get loop mode, transwave loop
double loopStart=(double)s->loopStart;
double loopEnd=(double)s->loopEnd;
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
bool sampleVaild=false;
if (((ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (c.value>=0 && c.value<120)) ||
((!ins->amiga.useNoteMap && ins->amiga.transWave.enable) && (ins->amiga.transWave.ind>=0 && ins->amiga.transWave.ind<ins->amiga.transWaveMap.size())) ||
((!ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (ins->amiga.initSample>=0 && ins->amiga.initSample<parent->song.sampleLen))) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
DivInstrumentAmiga::NoteMap& noteMapind=ins->amiga.noteMap[c.value];
DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[ins->amiga.transWave.ind];
int sample=ins->amiga.initSample;
if (ins->amiga.transWave.enable) {
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
loopMode=transWaveInd.loopMode;
} else if (!s->isLoopable()) { // default
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
}
// get loop position
loopStart=(double)transWaveInd.loopStart;
loopEnd=(double)transWaveInd.loopEnd;
if (ins->amiga.transWave.sliceEnable) { // sliced loop position?
chan[c.chan].transWave.updateSize(s->samples,loopStart,loopEnd);
chan[c.chan].transWave.slice=transWaveInd.slice;
chan[c.chan].transWave.slicePos(transWaveInd.slice);
loopStart=transWaveInd.sliceStart;
loopEnd=transWaveInd.sliceEnd;
}
sample=transWaveInd.ind;
} else if (ins->amiga.useNoteMap) {
sample=noteMapind.ind;
}
// get reversed
bool reversed=ins->amiga.reversed;
if (ins->amiga.transWave.enable&&transWaveInd.reversed!=2) {
reversed=transWaveInd.reversed;
} else if (ins->amiga.useNoteMap&&noteMapind.reversed!=2) {
reversed=noteMapind.reversed;
if (sample>=0 && sample<parent->song.sampleLen) {
sampleVaild=true;
chan[c.chan].pcm.index=sample;
chan[c.chan].pcm.pause=(chan[c.chan].std.alg.will)?(chan[c.chan].std.alg.val&1):false;
chan[c.chan].transWave.enable=ins->amiga.transWave.enable;
chan[c.chan].transWave.sliceEnable=ins->amiga.transWave.sliceEnable;
chan[c.chan].transWave.ind=ins->amiga.transWave.ind;
DivSample* s=parent->getSample(sample);
// get frequency offset
double off=1.0;
double center=s->centerRate;
if (center<1) {
off=1.0;
} else {
off=(double)center/8363.0;
}
if (ins->amiga.useNoteMap) {
off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)c.value-48.0)/12.0));
chan[c.chan].pcm.note=c.value;
}
// get loop mode, transwave loop
double loopStart=(double)s->loopStart;
double loopEnd=(double)s->loopEnd;
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
if (ins->amiga.transWave.enable) {
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
loopMode=transWaveInd.loopMode;
} else if (!s->isLoopable()) { // default
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
}
// get loop position
loopStart=(double)transWaveInd.loopStart;
loopEnd=(double)transWaveInd.loopEnd;
if (ins->amiga.transWave.sliceEnable) { // sliced loop position?
chan[c.chan].transWave.updateSize(s->samples,loopStart,loopEnd);
chan[c.chan].transWave.slice=transWaveInd.slice;
chan[c.chan].transWave.slicePos(transWaveInd.slice);
loopStart=transWaveInd.sliceStart;
loopEnd=transWaveInd.sliceEnd;
}
}
// get reversed
bool reversed=ins->amiga.reversed;
if (ins->amiga.transWave.enable&&transWaveInd.reversed!=2) {
reversed=transWaveInd.reversed;
} else if (ins->amiga.useNoteMap&&noteMapind.reversed!=2) {
reversed=noteMapind.reversed;
}
const unsigned int start=s->offES5506<<10;
const unsigned int length=s->samples-1;
const unsigned int end=start+(length<<11);
chan[c.chan].pcm.loopMode=loopMode;
chan[c.chan].pcm.freqOffs=PITCH_OFFSET*off;
chan[c.chan].pcm.reversed=reversed;
chan[c.chan].pcm.bank=(s->offES5506>>22)&3;
chan[c.chan].pcm.start=start;
chan[c.chan].pcm.end=end;
chan[c.chan].pcm.length=length;
chan[c.chan].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800;
chan[c.chan].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80;
chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xffff;
chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xffff;
chan[c.chan].filter=ins->es5506.filter;
chan[c.chan].envelope=ins->es5506.envelope;
}
const unsigned int start=s->offES5506<<10;
const unsigned int length=s->samples-1;
const unsigned int end=start+(length<<11);
chan[c.chan].pcm.loopMode=loopMode;
chan[c.chan].pcm.freqOffs=PITCH_OFFSET*off;
chan[c.chan].pcm.reversed=reversed;
chan[c.chan].pcm.bank=(s->offES5506>>22)&3;
chan[c.chan].pcm.start=start;
chan[c.chan].pcm.end=end;
chan[c.chan].pcm.length=length;
chan[c.chan].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800;
chan[c.chan].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80;
chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xffff;
chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xffff;
chan[c.chan].filter=ins->es5506.filter;
chan[c.chan].envelope=ins->es5506.envelope;
} else {
}
if (!sampleVaild) {
chan[c.chan].pcm.index=-1;
chan[c.chan].filter=DivInstrumentES5506::Filter();
chan[c.chan].envelope=DivInstrumentES5506::Envelope();
@ -864,6 +887,15 @@ int DivPlatformES5506::dispatch(DivCommand c) {
chan[c.chan].envelope.k2Slow=c.value2&1;
chan[c.chan].envChanged.k2Ramp=1;
break;
// controls
case DIV_CMD_ES5506_PAUSE:
if (chan[c.chan].active) {
if (chan[c.chan].pcm.pause!=(bool)(c.value&1)) {
chan[c.chan].pcm.pause=c.value&1;
pageWriteMask(0x00|i,0x5f,0x00,chan[c.chan].pcm.pause?0x0002:0x0000,0x0002);
}
}
break;
case DIV_CMD_NOTE_PORTA: {
int nextFreq=chan[c.chan].baseFreq;
const int destFreq=NOTE_ES5506(c.chan,c.value2);

View File

@ -36,7 +36,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
int note;
double freqOffs;
double nextFreqOffs;
bool reversed;
bool reversed, pause;
unsigned int bank;
unsigned int start;
unsigned int end;
@ -51,6 +51,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
freqOffs(1.0),
nextFreqOffs(1.0),
reversed(false),
pause(false),
bank(0),
start(0),
end(0),

View File

@ -549,7 +549,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (c.chan==5 && dacMode) {
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) { // Furnace mode
dacSample=ins->amiga.initSample;
dacSample=ins->amiga.getSample(c.value);
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);

View File

@ -193,7 +193,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
if (c.chan==2) { // PCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
if (ins->type==DIV_INS_AMIGA) {
dacSample=ins->amiga.initSample;
dacSample=ins->amiga.getSample(c.value);
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);

View File

@ -382,7 +382,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
if (c.chan==4) { // PCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
if (ins->type==DIV_INS_AMIGA) {
dacSample=ins->amiga.initSample;
dacSample=ins->amiga.getSample(c.value);
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);

View File

@ -19,6 +19,7 @@
#include "opl.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <string.h>
#include <math.h>
@ -246,19 +247,53 @@ const char* DivPlatformOPL::getEffectName(unsigned char effect) {
void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
static short o[2];
static int os[2];
static ymfm::ymfm_output<2> aOut;
for (size_t h=start; h<start+len; h++) {
os[0]=0; os[1]=0;
if (!writes.empty() && --delay<0) {
delay=1;
QueuedWrite& w=writes.front();
OPL3_WriteReg(&fm,w.addr,w.val);
switch (w.addr) {
case 8:
if (adpcmChan>=0) {
adpcmB->write(w.addr-7,(w.val&15)|0x80);
OPL3_WriteReg(&fm,w.addr,w.val&0xc0);
} else {
OPL3_WriteReg(&fm,w.addr,w.val);
}
break;
case 7: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 21: case 22: case 23:
if (adpcmChan>=0) {
adpcmB->write(w.addr-7,w.val);
} else {
OPL3_WriteReg(&fm,w.addr,w.val);
}
break;
default:
OPL3_WriteReg(&fm,w.addr,w.val);
break;
}
regPool[w.addr&511]=w.val;
writes.pop();
}
OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1];
if (adpcmChan>=0) {
adpcmB->clock();
aOut.clear();
adpcmB->output<2>(aOut,0);
if (!isMuted[adpcmChan]) {
os[0]+=aOut.data[0];
os[1]+=aOut.data[0];
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0];
} else {
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
}
}
for (int i=0; i<chans; i++) {
unsigned char ch=outChanMap[i];
if (ch==255) continue;
@ -292,6 +327,15 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len)
//}
}
double DivPlatformOPL::NOTE_ADPCMB(int note) {
if (adpcmChan<0) return 0;
if (chan[adpcmChan].sample>=0 && chan[adpcmChan].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0;
return parent->calcBaseFreq((double)chipClock/144,off,note,false);
}
return 0;
}
void DivPlatformOPL::tick(bool sysTick) {
for (int i=0; i<totalChans; i++) {
int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2;
@ -501,6 +545,45 @@ void DivPlatformOPL::tick(bool sysTick) {
}
}
// ADPCM
if (adpcmChan>=0) {
if (chan[adpcmChan].furnacePCM) {
chan[adpcmChan].std.next();
if (chan[adpcmChan].std.vol.had) {
chan[adpcmChan].outVol=(chan[adpcmChan].vol*MIN(64,chan[adpcmChan].std.vol.val))/64;
immWrite(18,chan[adpcmChan].outVol);
}
if (chan[adpcmChan].std.arp.had) {
if (!chan[adpcmChan].inPorta) {
if (chan[adpcmChan].std.arp.mode) {
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].std.arp.val);
} else {
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note+(signed char)chan[adpcmChan].std.arp.val);
}
}
chan[adpcmChan].freqChanged=true;
} else {
if (chan[adpcmChan].std.arp.mode && chan[adpcmChan].std.arp.finished) {
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note);
chan[adpcmChan].freqChanged=true;
}
}
}
if (chan[adpcmChan].freqChanged) {
if (chan[adpcmChan].sample>=0 && chan[adpcmChan].sample<parent->song.sampleLen) {
double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0;
chan[adpcmChan].freq=parent->calcFreq(chan[adpcmChan].baseFreq,chan[adpcmChan].pitch,false,4,chan[adpcmChan].pitch2,(double)chipClock/144,off);
} else {
chan[adpcmChan].freq=0;
}
immWrite(16,chan[adpcmChan].freq&0xff);
immWrite(17,(chan[adpcmChan].freq>>8)&0xff);
chan[adpcmChan].freqChanged=false;
}
}
for (int i=0; i<512; i++) {
if (pendingWrites[i]!=oldWrites[i]) {
immWrite(i,pendingWrites[i]&0xff);
@ -588,6 +671,7 @@ int DivPlatformOPL::toFreq(int freq) {
void DivPlatformOPL::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (ch==adpcmChan) return;
if (oplType<3 && ch<melodicChans) {
fm.channel[outChanMap[ch]].muted=mute;
}
@ -625,13 +709,78 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) {
}
int DivPlatformOPL::dispatch(DivCommand c) {
if (c.chan>=totalChans) return 0;
if (c.chan>=totalChans && c.chan!=adpcmChan) return 0;
// ineffective in 4-op mode
if (oplType==3 && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) {
if (oplType==3 && c.chan!=adpcmChan && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) {
if (chan[c.chan-1].fourOp) return 0;
}
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
if (c.chan==adpcmChan) { // ADPCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].furnacePCM=true;
} else {
chan[c.chan].furnacePCM=false;
}
if (skipRegisterWrites) break;
if (chan[c.chan].furnacePCM) {
chan[c.chan].macroInit(ins);
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(18,chan[c.chan].outVol);
}
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(9,(s->offB>>2)&0xff);
immWrite(10,(s->offB>>10)&0xff);
int end=s->offB+s->lengthB-1;
immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff);
immWrite(8,2);
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
chan[c.chan].freqChanged=true;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
} else {
immWrite(7,0x01); // reset
immWrite(9,0);
immWrite(10,0);
immWrite(11,0);
immWrite(12,0);
break;
}
} else {
chan[c.chan].sample=-1;
chan[c.chan].macroInit(NULL);
chan[c.chan].outVol=chan[c.chan].vol;
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
immWrite(7,0x01); // reset
immWrite(9,0);
immWrite(10,0);
immWrite(11,0);
immWrite(12,0);
break;
}
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
immWrite(9,(s->offB>>2)&0xff);
immWrite(10,(s->offB>>10)&0xff);
int end=s->offB+s->lengthB-1;
immWrite(11,(end>>2)&0xff);
immWrite(12,(end>>10)&0xff);
immWrite(8,2);
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
int freq=(65536.0*(double)s->rate)/(double)rate;
immWrite(16,freq&0xff);
immWrite(17,(freq>>8)&0xff);
}
break;
}
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPL);
if (chan[c.chan].insChanged) {
@ -714,11 +863,19 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_OFF:
if (c.chan==adpcmChan) {
immWrite(7,0x01); // reset
break;
}
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
break;
case DIV_CMD_NOTE_OFF_ENV:
if (c.chan==adpcmChan) {
immWrite(7,0x01); // reset
break;
}
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
chan[c.chan].active=false;
@ -728,7 +885,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
chan[c.chan].std.release();
break;
case DIV_CMD_VOLUME: {
if (pretendYMU) {
if (pretendYMU && c.chan!=adpcmChan) {
c.value=pow(((double)c.value/127.0),0.5)*63.0;
if (c.value<0) c.value=0;
if (c.value>63) c.value=63;
@ -737,6 +894,10 @@ int DivPlatformOPL::dispatch(DivCommand c) {
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
}
if (c.chan==adpcmChan) { // ADPCM-B
immWrite(18,chan[c.chan].outVol);
break;
}
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
for (int i=0; i<ops; i++) {
unsigned char slot=slots[i][c.chan];
@ -757,7 +918,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_GET_VOLUME: {
if (pretendYMU) {
if (pretendYMU && c.chan!=adpcmChan) {
return pow(((double)chan[c.chan].vol/63.0),2.0)*127.0;
}
return chan[c.chan].vol;
@ -771,6 +932,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
case DIV_CMD_PANNING: {
if (oplType!=3) break;
if (c.chan==adpcmChan) break;
if (c.value==0 && c.value2==0) {
chan[c.chan].pan=3;
} else {
@ -791,12 +953,15 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==adpcmChan) {
if (!chan[c.chan].furnacePCM) break;
}
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
}
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
int destFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(c.value2)):(NOTE_FREQUENCY(c.value2));
int newFreq;
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
@ -827,13 +992,22 @@ int DivPlatformOPL::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_SAMPLE_BANK:
if (adpcmChan<0) break;
sampleBank=c.value;
if (sampleBank>(int)(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(c.value)):(NOTE_FREQUENCY(c.value));
chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true;
break;
}
case DIV_CMD_FM_LFO: {
if (c.chan==adpcmChan) break;
if (c.value&2) {
dvb=c.value&1;
} else {
@ -843,6 +1017,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_FB: {
if (c.chan==adpcmChan) break;
chan[c.chan].state.fb=c.value&7;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (isMuted[c.chan]) {
@ -859,6 +1034,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_MULT: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value>=ops) break;
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
@ -870,6 +1046,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_TL: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value>=ops) break;
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value];
@ -889,6 +1066,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_AR: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -911,6 +1089,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_DR: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -933,6 +1112,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_SL: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -955,6 +1135,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_RR: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -977,6 +1158,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_AM: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -999,6 +1181,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_VIB: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -1021,6 +1204,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_SUS: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -1043,6 +1227,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_KSR: {
if (c.chan==adpcmChan) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
for (int i=0; i<ops; i++) {
@ -1065,6 +1250,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_WS: {
if (c.chan==adpcmChan) break;
if (oplType<2) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
@ -1088,6 +1274,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_RS: {
if (c.chan==adpcmChan) break;
if (oplType<2) break;
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (c.value<0) {
@ -1143,12 +1330,14 @@ int DivPlatformOPL::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_HARD_RESET:
if (c.chan==adpcmChan) break;
chan[c.chan].hardReset=c.value;
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;
case DIV_CMD_GET_VOLMAX:
if (c.chan==adpcmChan) return 255;
if (pretendYMU) return 127;
return 63;
break;
@ -1237,7 +1426,7 @@ unsigned char* DivPlatformOPL::getRegisterPool() {
}
int DivPlatformOPL::getRegisterPoolSize() {
return 512;
return (oplType<3)?256:512;
}
void DivPlatformOPL::reset() {
@ -1272,6 +1461,21 @@ void DivPlatformOPL::reset() {
chan[i].vol=0x3f;
chan[i].outVol=0x3f;
}
if (adpcmChan>=0) {
chan[adpcmChan]=DivPlatformOPL::Channel();
chan[adpcmChan].std.setEngine(parent);
chan[adpcmChan].vol=0xff;
chan[adpcmChan].outVol=0xff;
adpcmB->reset();
// volume
immWrite(18,0xff);
// ADPCM limit
immWrite(20,0xff);
immWrite(19,0xff);
}
if (oplType<3) for (int i=0; i<melodicChans; i++) {
fm.channel[outChanMap[i]].muted=isMuted[i];
@ -1285,6 +1489,7 @@ void DivPlatformOPL::reset() {
lastBusy=60;
lfoValue=8;
drumState=0;
sampleBank=0;
drumVol[0]=0;
drumVol[1]=0;
@ -1349,8 +1554,9 @@ void DivPlatformOPL::setYMFM(bool use) {
void DivPlatformOPL::setOPLType(int type, bool drums) {
pretendYMU=false;
adpcmChan=-1;
switch (type) {
case 1: case 2:
case 1: case 2: case 8950:
slotsNonDrums=slotsOPL2;
slotsDrums=slotsOPL2Drums;
slots=drums?slotsDrums:slotsNonDrums;
@ -1360,6 +1566,9 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
chans=9;
melodicChans=drums?6:9;
totalChans=drums?11:9;
if (type==8950) {
adpcmChan=drums?11:9;
}
break;
case 3: case 759:
slotsNonDrums=slotsOPL3;
@ -1371,11 +1580,16 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
chans=18;
melodicChans=drums?15:18;
totalChans=drums?20:18;
if (type==759) pretendYMU=true;
if (type==759) {
pretendYMU=true;
adpcmChan=16;
}
break;
}
if (type==759) {
oplType=3;
} else if (type==8950) {
oplType=1;
} else {
oplType=type;
}
@ -1425,6 +1639,45 @@ void DivPlatformOPL::setFlags(unsigned int flags) {
}
}
const void* DivPlatformOPL::getSampleMem(int index) {
return (index==0 && adpcmChan>=0) ? adpcmBMem : NULL;
}
size_t DivPlatformOPL::getSampleMemCapacity(int index) {
return (index==0 && adpcmChan>=0) ? 262144 : 0;
}
size_t DivPlatformOPL::getSampleMemUsage(int index) {
return (index==0 && adpcmChan>=0) ? adpcmBMemLen : 0;
}
void DivPlatformOPL::renderSamples() {
if (adpcmChan<0) return;
memset(adpcmBMem,0,getSampleMemCapacity(0));
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int paddedLen=(s->lengthB+255)&(~0xff);
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
memPos=(memPos+0xfffff)&0xf00000;
}
if (memPos>=getSampleMemCapacity(0)) {
logW("out of ADPCM memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=getSampleMemCapacity(0)) {
memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(0)-memPos);
logW("out of ADPCM memory for sample %d!",i);
} else {
memcpy(adpcmBMem+memPos,s->dataB,paddedLen);
}
s->offB=memPos;
memPos+=paddedLen;
}
adpcmBMemLen=memPos+256;
}
int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
@ -1437,6 +1690,14 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f
}
setFlags(flags);
if (adpcmChan>=0) {
adpcmBMem=new unsigned char[getSampleMemCapacity(0)];
adpcmBMemLen=0;
iface.adpcmBMem=adpcmBMem;
iface.sampleBank=0;
adpcmB=new ymfm::adpcm_b_engine(iface,2);
}
reset();
return totalChans;
}
@ -1445,6 +1706,10 @@ void DivPlatformOPL::quit() {
for (int i=0; i<18; i++) {
delete oscBuf[i];
}
if (adpcmChan>=0) {
delete adpcmB;
delete[] adpcmBMem;
}
}
DivPlatformOPL::~DivPlatformOPL() {

View File

@ -23,6 +23,16 @@
#include "../macroInt.h"
#include <queue>
#include "../../../extern/opl/opl3.h"
#include "sound/ymfm/ymfm_adpcm.h"
class DivOPLAInterface: public ymfm::ymfm_interface {
public:
unsigned char* adpcmBMem;
int sampleBank;
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
DivOPLAInterface(): adpcmBMem(NULL), sampleBank(0) {}
};
class DivPlatformOPL: public DivDispatch {
protected:
@ -30,8 +40,8 @@ class DivPlatformOPL: public DivDispatch {
DivInstrumentFM state;
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, note, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, fourOp, hardReset;
int freq, baseFreq, pitch, pitch2, note, ins, sample;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnacePCM, inPorta, fourOp, hardReset;
int vol, outVol;
unsigned char pan;
void macroInit(DivInstrument* which) {
@ -47,13 +57,14 @@ class DivPlatformOPL: public DivDispatch {
pitch2(0),
note(0),
ins(-1),
sample(-1),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
furnaceDac(false),
furnacePCM(false),
inPorta(false),
fourOp(false),
hardReset(false),
@ -73,13 +84,18 @@ class DivPlatformOPL: public DivDispatch {
};
std::queue<QueuedWrite> writes;
opl3_chip fm;
unsigned char* adpcmBMem;
size_t adpcmBMemLen;
DivOPLAInterface iface;
ymfm::adpcm_b_engine* adpcmB;
const unsigned char** slotsNonDrums;
const unsigned char** slotsDrums;
const unsigned char** slots;
const unsigned short* chanMap;
const unsigned char* outChanMap;
double chipFreqBase;
int delay, oplType, chans, melodicChans, totalChans;
int delay, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank;
unsigned char lastBusy;
unsigned char drumState;
unsigned char drumVol[5];
@ -97,6 +113,7 @@ class DivPlatformOPL: public DivDispatch {
int octave(int freq);
int toFreq(int freq);
double NOTE_ADPCMB(int note);
friend void putDispatchChan(void*,int,int);
@ -127,6 +144,10 @@ class DivPlatformOPL: public DivDispatch {
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char* getEffectName(unsigned char effect);
const void* getSampleMem(int index);
size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformOPL();

View File

@ -0,0 +1,38 @@
/**
* 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 "sound/ymfm/ymfm.h"
#include "opl.h"
#include "../engine.h"
uint8_t DivOPLAInterface::ymfm_external_read(ymfm::access_class type, uint32_t address) {
switch (type) {
case ymfm::ACCESS_ADPCM_B:
if (adpcmBMem==NULL) {
return 0;
}
return adpcmBMem[address&0xffffff];
default:
return 0;
}
return 0;
}
void DivOPLAInterface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) {
}

View File

@ -270,7 +270,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
if (chan[c.chan].pcm) {
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].dacSample=ins->amiga.initSample;
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);

View File

@ -390,7 +390,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value);
}

View File

@ -21,9 +21,9 @@
#include "../engine.h"
#include <math.h>
#define CHIP_DIVIDER 32
#define CHIP_DIVIDER 16
#define rWrite(a,v) {if(!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }}
#define rWrite(a,v) {if (!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if (dumpWrites) addWrite(a,v); }}
const char* regCheatSheetSCC[]={
"Ch1_Wave", "00",
@ -105,6 +105,11 @@ void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len)
void DivPlatformSCC::updateWave(int ch) {
int dstCh=(!isPlus && ch>=4)?3:ch;
if (ch==3) {
lastUpdated34=3;
} else if (ch==4) {
lastUpdated34=4;
}
for (int i=0; i<32; i++) {
rWrite(dstCh*32+i,(unsigned char)chan[ch].ws.output[i]-128);
}
@ -153,11 +158,11 @@ void DivPlatformSCC::tick(bool sysTick) {
}
}
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2;
if (chan[i].freq<1) chan[i].freq=1;
if (chan[i].freq>4096) chan[i].freq=4096;
rWrite(regBase+0+i*2,(chan[i].freq-1)&0xff);
rWrite(regBase+1+i*2,(chan[i].freq-1)>>8);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>4095) chan[i].freq=4095;
rWrite(regBase+0+i*2,chan[i].freq&0xff);
rWrite(regBase+1+i*2,chan[i].freq>>8);
chan[i].freqChanged=false;
}
}
@ -281,7 +286,14 @@ void DivPlatformSCC::forceIns() {
for (int i=0; i<5; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
updateWave(i);
if (isPlus || i<3) {
updateWave(i);
}
}
if (!isPlus) {
if (lastUpdated34>=3) {
updateWave(lastUpdated34);
}
}
}
@ -316,6 +328,7 @@ void DivPlatformSCC::reset() {
if (dumpWrites) {
addWrite(0xffffffff,0);
}
lastUpdated34=0;
}
bool DivPlatformSCC::isStereo() {
@ -326,7 +339,9 @@ void DivPlatformSCC::notifyWaveChange(int wave) {
for (int i=0; i<5; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(chan[i].wave);
updateWave(i);
if (chan[i].active) {
updateWave(i);
}
}
}
}
@ -358,8 +373,8 @@ int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int f
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
chipClock=COLOR_NTSC;
rate=chipClock/16;
chipClock=COLOR_NTSC/2.0;
rate=chipClock/8;
for (int i=0; i<5; i++) {
oscBuf[i]->rate=rate;
}

View File

@ -57,6 +57,7 @@ class DivPlatformSCC: public DivDispatch {
DivDispatchOscBuffer* oscBuf[5];
bool isMuted[5];
unsigned char writeOscBuf;
int lastUpdated34;
scc_core* scc;
bool isPlus;

View File

@ -162,7 +162,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].pcm.sample=ins->amiga.initSample;
chan[c.chan].pcm.sample=ins->amiga.getSample(c.value);
if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1;
if (dumpWrites) {

View File

@ -1021,10 +1021,8 @@ void ay8910_device::ay8910_write_reg(int r, int v)
m_tone[2].set_duty(m_regs[AY_CDUTY]);
break;
case AY_NOISEAND:
m_noise_and=m_regs[AY_NOISEAND];
break;
case AY_NOISEOR:
m_noise_or=m_regs[AY_NOISEOR];
// No action required
break;
default:
m_regs[r] = 0; // reserved, set as 0
@ -1047,7 +1045,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
if (!m_ready)
{
for (int chan = 0; chan < m_streams; chan++)
memset(outputs[chan],0,outLen*sizeof(short));
memset(outputs[chan],0,outLen*sizeof(short));
}
/* The 8910 has three outputs, each output is the mix of one of the three */
@ -1063,8 +1061,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
for (int chan = 0; chan < NUM_CHANNELS; chan++)
{
tone = &m_tone[chan];
const int period = std::max<int>(1,tone->period);
tone->count += is_expanded_mode() ? 16 : (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1;
const int period = tone->period * (m_step_mul << 1);
tone->count += is_expanded_mode() ? 32 : ((m_feature & PSG_HAS_EXPANDED_MODE) ? 1 : 2);
while (tone->count >= period)
{
tone->duty_cycle = (tone->duty_cycle - 1) & 0x1f;
@ -1073,8 +1071,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
}
}
m_count_noise++;
if (m_count_noise >= noise_period())
const int period_noise = (int)(noise_period()) * m_step_mul;
if ((++m_count_noise) >= period_noise)
{
/* toggle the prescaler output. Noise is no different to
* channels.
@ -1082,39 +1080,27 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
m_count_noise = 0;
m_prescale_noise = (m_prescale_noise + 1) & ((m_feature & PSG_HAS_EXPANDED_MODE) ? 3 : 1);
if (!m_prescale_noise || is_expanded_mode()) // AY8930 noise generator rate is twice compares as compatibility mode
if (is_expanded_mode()) // AY8930 noise generator rate is twice? compares as compatibility mode
{
if (is_expanded_mode()) {
// This is called "Noise value" on the docs, but is a counter whose period is determined by the LFSR.
// Using AND/OR gates, specific periods can be "filtered" out.
// A square wave can be generated through this behavior, which can be used for crude AM pulse width modulation.
// The period of the noise is determined by this value.
// The least significant byte of the LFSR is bitwise ANDed with the AND mask, and then bitwise ORed with the OR mask.
unsigned int noiseValuePeriod = ((m_rng & 0xFF & m_noise_and) | m_noise_or);
// Clock the noise value.
if (m_noise_value >= noiseValuePeriod) {
m_noise_value = 0;
// When everything is finally said and done, a 1bit latch is flipped.
// This is the final output of the noise, to be multiplied by the tone and envelope generators of the channel.
m_noise_latch ^= 1;
// The 17-bit LFSR is updated, using an XOR across bits 0 and 2.
unsigned int feedback = (m_rng & 1) ^ ((m_rng >> 2) & 1);
m_rng >>= 1;
m_rng |= (feedback << 16);
}
m_noise_value++;
} else {
/* The Random Number Generator of the 8910 is a 17-bit shift */
/* register. The input to the shift register is bit0 XOR bit3 */
/* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */
m_rng ^= (((m_rng & 1) ^ ((m_rng >> 3) & 1)) << 17);
m_rng >>= 1;
}
// This is called "Noise value" on the docs, but is a counter whose period is determined by the LFSR.
// Using AND/OR gates, specific periods can be "filtered" out.
// A square wave can be generated through this behavior, which can be used for crude AM pulse width modulation.
// The period of the noise is determined by this value.
// The least significant byte of the LFSR is bitwise ANDed with the AND mask, and then bitwise ORed with the OR mask.
if ((++m_noise_value) >= (((unsigned char)(m_rng) & noise_and()) | noise_or())) // Clock the noise value.
{
m_noise_value = 0;
// When everything is finally said and done, a 1bit latch is flipped.
// This is the final output of the noise, to be multiplied by the tone and envelope generators of the channel.
m_noise_out ^= 1;
noise_rng_tick();
}
}
else if (!m_prescale_noise)
noise_rng_tick();
}
for (int chan = 0; chan < NUM_CHANNELS; chan++)
@ -1129,9 +1115,8 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
envelope = &m_envelope[chan];
if (envelope->holding == 0)
{
const int period = envelope->period * m_step;
envelope->count++;
if (envelope->count >= period)
const int period = envelope->period * m_env_step_mul;
if ((++envelope->count) >= period)
{
envelope->count = 0;
envelope->step--;
@ -1263,20 +1248,14 @@ void ay8910_device::device_start()
}
void ay8910_device::ay8910_reset_ym(bool ay8930)
void ay8910_device::ay8910_reset_ym()
{
m_active = false;
m_register_latch = 0;
if (ay8930) {
m_rng = 0x1ffff;
} else {
m_rng = 1;
}
m_rng = (m_feature & PSG_HAS_EXPANDED_MODE) ? 0x1ffff : 1;
m_mode = 0; // ay-3-8910 compatible mode
m_noise_and = 0xff;
m_noise_or = 0;
m_noise_value = 0;
m_noise_latch = 0;
m_noise_value = 0;
m_noise_out = 0;
for (int chan = 0; chan < NUM_CHANNELS; chan++)
{
m_tone[chan].reset();
@ -1335,7 +1314,7 @@ void ay8910_device::ay8910_write_ym(int addr, unsigned char data)
unsigned char ay8910_device::ay8910_read_ym()
{
int r = m_register_latch + get_register_bank();
unsigned char r = m_register_latch + get_register_bank();
if (!m_active) return 0xff; // high impedance
@ -1380,7 +1359,7 @@ unsigned char ay8910_device::ay8910_read_ym()
void ay8910_device::device_reset()
{
ay8910_reset_ym(chip_type == AY8930);
ay8910_reset_ym();
}
/*************************************
@ -1452,28 +1431,27 @@ ay8910_device::ay8910_device(unsigned int clock)
}
ay8910_device::ay8910_device(device_type type, unsigned int clock,
psg_type_t psg_type, int streams, int ioports, int feature)
psg_type_t psg_type, int streams, int ioports, int feature, bool clk_sel)
: chip_type(type),
m_type(psg_type),
m_type(psg_type),
m_streams(streams),
m_ready(0),
m_active(false),
m_register_latch(0),
m_last_enable(0),
m_prescale_noise(0),
m_noise_value(0),
m_count_noise(0),
m_rng(0),
m_noise_and(0),
m_noise_or(0),
m_noise_value(0),
m_noise_latch(0),
m_noise_out(0),
m_mode(0),
m_env_step_mask((!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 0x0f : 0x1f),
m_step( (feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY) ? 2 : 1),
m_step_mul( ((feature & PSG_HAS_INTERNAL_DIVIDER) || ((feature & PSG_PIN26_IS_CLKSEL) && clk_sel)) ? 2 : 1),
m_env_step_mul( ((feature & PSG_HAS_EXPANDED_MODE) || (psg_type == PSG_TYPE_AY)) ? (m_step_mul << 1) : m_step_mul),
m_zero_is_off( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? 1 : 0),
m_par( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param),
m_par_env( (!(feature & PSG_HAS_EXPANDED_MODE)) && (psg_type == PSG_TYPE_AY) ? &ay8910_param : &ym2149_param_env),
m_flags(AY8910_LEGACY_OUTPUT),
m_flags(AY8910_LEGACY_OUTPUT | (((feature & PSG_PIN26_IS_CLKSEL) && clk_sel) ? YM2149_PIN26_LOW : 0)),
m_feature(feature)
{
memset(&m_regs,0,sizeof(m_regs));
@ -1485,16 +1463,16 @@ ay8910_device::ay8910_device(device_type type, unsigned int clock,
m_res_load[0] = m_res_load[1] = m_res_load[2] = 1000; //Default values for resistor loads
// TODO : measure ay8930 volume parameters (PSG_TYPE_YM for temporary 5 bit handling)
set_type((m_feature & PSG_HAS_EXPANDED_MODE) ? PSG_TYPE_YM : psg_type);
set_type((m_feature & PSG_HAS_EXPANDED_MODE) ? PSG_TYPE_YM : psg_type, clk_sel);
}
void ay8910_device::set_type(psg_type_t psg_type)
void ay8910_device::set_type(psg_type_t psg_type, bool clk_sel)
{
m_type = psg_type;
if (psg_type == PSG_TYPE_AY)
{
m_env_step_mask = 0x0f;
m_step = 2;
m_env_step_mul = is_clock_divided() ? 4 : 2;
m_zero_is_off = 1;
m_par = &ay8910_param;
m_par_env = &ay8910_param;
@ -1502,11 +1480,15 @@ void ay8910_device::set_type(psg_type_t psg_type)
else
{
m_env_step_mask = 0x1f;
m_step = (m_feature & PSG_HAS_EXPANDED_MODE) ? 2 : 1;
m_env_step_mul = is_clock_divided() ? 2 : 1;
m_zero_is_off = 0;
m_par = &ym2149_param;
m_par_env = &ym2149_param_env;
}
if (m_feature & PSG_HAS_EXPANDED_MODE)
m_env_step_mul <<= 1;
set_clock_sel(clk_sel);
}
@ -1535,24 +1517,24 @@ ay8914_device::ay8914_device(unsigned int clock)
ay8930_device::ay8930_device(unsigned int clock)
: ay8910_device(AY8930, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL | PSG_HAS_EXPANDED_MODE)
ay8930_device::ay8930_device(unsigned int clock, bool clk_sel)
: ay8910_device(AY8930, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL | PSG_HAS_EXPANDED_MODE, clk_sel)
{
}
ym2149_device::ym2149_device(unsigned int clock)
: ay8910_device(YM2149, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL)
ym2149_device::ym2149_device(unsigned int clock, bool clk_sel)
: ay8910_device(YM2149, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL, clk_sel)
{
}
ym3439_device::ym3439_device(unsigned int clock)
: ay8910_device(YM3439, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL)
ym3439_device::ym3439_device(unsigned int clock, bool clk_sel)
: ay8910_device(YM3439, clock, PSG_TYPE_YM, 3, 2, PSG_PIN26_IS_CLKSEL, clk_sel)
{
}

View File

@ -3,6 +3,10 @@
#ifndef MAME_SOUND_AY8910_H
#define MAME_SOUND_AY8910_H
#pragma once
#include <algorithm>
#define ALL_8910_CHANNELS -1
/* Internal resistance at Volume level 7. */
@ -89,7 +93,8 @@ public:
// configuration helpers
void set_flags(int flags) { m_flags = flags; }
void set_psg_type(psg_type_t psg_type) { set_type(psg_type); }
void set_psg_type(psg_type_t psg_type) { set_type(psg_type, m_flags & YM2149_PIN26_LOW); }
void set_psg_type(psg_type_t psg_type, bool clk_sel) { set_type(psg_type, clk_sel); }
void set_resistors_load(int res_load0, int res_load1, int res_load2) { m_res_load[0] = res_load0; m_res_load[1] = res_load1; m_res_load[2] = res_load2; }
unsigned char data_r() { return ay8910_read_ym(); }
@ -97,7 +102,24 @@ public:
void data_w(unsigned char data);
// /RES
void reset_w(unsigned char data = 0) { ay8910_reset_ym(chip_type == AY8930); }
void reset_w(unsigned char data = 0) { ay8910_reset_ym(); }
// Clock select pin
void set_clock_sel(bool clk_sel)
{
if (m_feature & PSG_PIN26_IS_CLKSEL)
{
if (clk_sel)
m_flags |= YM2149_PIN26_LOW;
else
m_flags &= ~YM2149_PIN26_LOW;
m_step_mul = is_clock_divided() ? 2 : 1;
m_env_step_mul = (!(m_feature & PSG_HAS_EXPANDED_MODE)) && (m_type == PSG_TYPE_AY) ? (m_step_mul << 1) : m_step_mul;
if (m_feature & PSG_HAS_EXPANDED_MODE)
m_env_step_mul <<= 1;
}
}
// use this when BC1 == A0; here, BC1=0 selects 'data' and BC1=1 selects 'latch address'
void data_address_w(int offset, unsigned char data) { ay8910_write_ym(~offset & 1, data); } // note that directly connecting BC1 to A0 puts data on 0 and address on 1
@ -127,7 +149,7 @@ public:
// internal interface for PSG component of YM device
// FIXME: these should be private, but vector06 accesses them directly
ay8910_device(device_type type, unsigned int clock, psg_type_t psg_type, int streams, int ioports, int feature = PSG_DEFAULT);
ay8910_device(device_type type, unsigned int clock, psg_type_t psg_type, int streams, int ioports, int feature = PSG_DEFAULT, bool clk_sel = false);
// device-level overrides
void device_start();
@ -138,7 +160,7 @@ public:
void ay8910_write_ym(int addr, unsigned char data);
unsigned char ay8910_read_ym();
void ay8910_reset_ym(bool ay8930);
void ay8910_reset_ym();
private:
static constexpr int NUM_CHANNELS = 3;
@ -189,7 +211,7 @@ private:
void reset()
{
period = 0;
period = 1;
volume = 0;
duty = 0;
count = 0;
@ -199,7 +221,7 @@ private:
void set_period(unsigned char fine, unsigned char coarse)
{
period = fine | (coarse << 8);
period = std::max<unsigned int>(1, fine | (coarse << 8));
}
void set_volume(unsigned char val)
@ -223,7 +245,7 @@ private:
void reset()
{
period = 0;
period = 1;
count = 0;
step = 0;
volume = 0;
@ -235,7 +257,7 @@ private:
void set_period(unsigned char fine, unsigned char coarse)
{
period = fine | (coarse << 8);
period = std::max<unsigned int>(1, fine | (coarse << 8));
}
void set_shape(unsigned char shape, unsigned char mask)
@ -258,6 +280,18 @@ private:
}
};
inline void noise_rng_tick()
{
// The Random Number Generator of the 8910 is a 17-bit shift
// register. The input to the shift register is bit0 XOR bit3
// (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips.
if (m_feature & PSG_HAS_EXPANDED_MODE) // AY8930 LFSR algorithm is slightly different, verified from manual
m_rng = (m_rng >> 1) | ((BIT(m_rng, 0) ^ BIT(m_rng, 2)) << 16);
else
m_rng = (m_rng >> 1) | ((BIT(m_rng, 0) ^ BIT(m_rng, 3)) << 16);
}
// inlines
inline bool tone_enable(int chan) { return BIT(m_regs[AY_ENABLE], chan); }
inline unsigned char tone_volume(tone_t *tone) { return tone->volume & (is_expanded_mode() ? 0x1f : 0x0f); }
@ -266,14 +300,19 @@ private:
inline unsigned char get_envelope_chan(int chan) { return is_expanded_mode() ? chan : 0; }
inline bool noise_enable(int chan) { return BIT(m_regs[AY_ENABLE], 3 + chan); }
inline unsigned char noise_period() { return is_expanded_mode() ? m_regs[AY_NOISEPER] & 0xff : m_regs[AY_NOISEPER] & 0x1f; }
inline unsigned char noise_output() { return is_expanded_mode() ? m_noise_latch & 1 : m_rng & 1; }
inline unsigned char noise_period() { return std::max<unsigned char>(1, is_expanded_mode() ? (m_regs[AY_NOISEPER] & 0xff) : (m_regs[AY_NOISEPER] & 0x1f)); }
inline unsigned char noise_output() { return is_expanded_mode() ? m_noise_out & 1 : m_rng & 1; }
inline bool is_expanded_mode() { return ((m_feature & PSG_HAS_EXPANDED_MODE) && ((m_mode & 0xe) == 0xa)); }
inline unsigned char get_register_bank() { return is_expanded_mode() ? (m_mode & 0x1) << 4 : 0; }
inline unsigned char noise_and() { return m_regs[AY_NOISEAND] & 0xff; }
inline unsigned char noise_or() { return m_regs[AY_NOISEOR] & 0xff; }
inline bool is_clock_divided() { return ((m_feature & PSG_HAS_INTERNAL_DIVIDER) || ((m_feature & PSG_PIN26_IS_CLKSEL) && (m_flags & YM2149_PIN26_LOW))); }
// internal helpers
void set_type(psg_type_t psg_type);
void set_type(psg_type_t psg_type, bool clk_sel);
inline float mix_3D();
void ay8910_write_reg(int r, int v);
void build_mixer_table();
@ -284,22 +323,21 @@ private:
int m_ready;
//sound_stream *m_channel;
bool m_active;
int m_register_latch;
unsigned char m_register_latch;
unsigned char m_regs[16 * 2];
int m_last_enable;
tone_t m_tone[NUM_CHANNELS];
envelope_t m_envelope[NUM_CHANNELS];
unsigned char m_prescale_noise;
int m_count_noise;
int m_rng;
unsigned int m_noise_and;
unsigned int m_noise_or;
unsigned int m_noise_value;
unsigned int m_noise_latch;
signed short m_noise_value;
signed short m_count_noise;
unsigned int m_rng;
unsigned char m_noise_out;
unsigned char m_mode;
unsigned char m_env_step_mask;
/* init parameters ... */
int m_step;
int m_step_mul;
int m_env_step_mul;
int m_zero_is_off;
unsigned char m_vol_enabled[NUM_CHANNELS];
const ay_ym_param *m_par;
@ -337,19 +375,19 @@ public:
class ay8930_device : public ay8910_device
{
public:
ay8930_device(unsigned int clock);
ay8930_device(unsigned int clock, bool clk_sel = false);
};
class ym2149_device : public ay8910_device
{
public:
ym2149_device(unsigned int clock);
ym2149_device(unsigned int clock, bool clk_sel = false);
};
class ym3439_device : public ay8910_device
{
public:
ym3439_device(unsigned int clock);
ym3439_device(unsigned int clock, bool clk_sel = false);
};
class ymz284_device : public ay8910_device

View File

@ -0,0 +1,543 @@
/*
This data is derived from the chip's output - internal ROM can't be read.
It was verified, using real YM2608, that this ADPCM stream produces 100% correct output signal.
*/
const unsigned char YM2608_ADPCM_ROM[0x2000] = {
/* Source: 01BD.ROM */
/* Length: 448 / 0x000001C0 */
0x88,0x08,0x08,0x08,0x00,0x88,0x16,0x76,0x99,0xB8,0x22,0x3A,0x84,0x3C,0xB1,0x54,
0x10,0xA9,0x98,0x32,0x80,0x33,0x9A,0xA7,0x4A,0xB4,0x58,0xBC,0x15,0x29,0x8A,0x97,
0x9B,0x44,0xAC,0x80,0x12,0xDE,0x13,0x1B,0xC0,0x58,0xC8,0x11,0x0A,0xA2,0x1A,0xA0,
0x00,0x98,0x0B,0x93,0x9E,0x92,0x0A,0x88,0xBE,0x14,0x1B,0x98,0x08,0xA1,0x4A,0xC1,
0x30,0xD9,0x33,0x98,0x10,0x89,0x17,0x1A,0x82,0x29,0x37,0x0C,0x83,0x50,0x9A,0x24,
0x1A,0x83,0x10,0x23,0x19,0xB3,0x72,0x8A,0x16,0x10,0x0A,0x93,0x70,0x99,0x23,0x99,
0x02,0x20,0x91,0x18,0x02,0x41,0xAB,0x24,0x18,0x81,0x99,0x4A,0xE8,0x28,0x9A,0x99,
0xA1,0x2F,0xA8,0x9D,0x90,0x08,0xCC,0xA3,0x1D,0xCA,0x82,0x0B,0xD8,0x08,0xB9,0x09,
0xBC,0xB8,0x00,0xBE,0x90,0x1B,0xCA,0x00,0x9B,0x8A,0xA8,0x91,0x0F,0xB3,0x3D,0xB8,
0x31,0x0B,0xA5,0x0A,0x11,0xA1,0x48,0x92,0x10,0x50,0x91,0x30,0x23,0x09,0x37,0x39,
0xA2,0x72,0x89,0x92,0x30,0x83,0x1C,0x96,0x28,0xB9,0x24,0x8C,0xA1,0x31,0xAD,0xA9,
0x13,0x9C,0xBA,0xA8,0x0B,0xBF,0xB8,0x9B,0xCA,0x88,0xDB,0xB8,0x19,0xFC,0x92,0x0A,
0xBA,0x89,0xAB,0xB8,0xAB,0xD8,0x08,0xAD,0xBA,0x33,0x9D,0xAA,0x83,0x3A,0xC0,0x40,
0xB9,0x15,0x39,0xA2,0x52,0x89,0x02,0x63,0x88,0x13,0x23,0x03,0x52,0x02,0x54,0x00,
0x11,0x23,0x23,0x35,0x20,0x01,0x44,0x41,0x80,0x24,0x40,0xA9,0x45,0x19,0x81,0x12,
0x81,0x02,0x11,0x21,0x19,0x02,0x61,0x8A,0x13,0x3A,0x10,0x12,0x23,0x8B,0x37,0x18,
0x91,0x24,0x10,0x81,0x34,0x20,0x05,0x32,0x82,0x53,0x20,0x14,0x33,0x31,0x34,0x52,
0x00,0x43,0x32,0x13,0x52,0x22,0x13,0x52,0x11,0x43,0x11,0x32,0x32,0x32,0x22,0x02,
0x13,0x12,0x89,0x22,0x19,0x81,0x81,0x08,0xA8,0x08,0x8B,0x90,0x1B,0xBA,0x8A,0x9B,
0xB9,0x89,0xCA,0xB9,0xAB,0xCA,0x9B,0xCA,0xB9,0xAB,0xDA,0x99,0xAC,0xBB,0x9B,0xAC,
0xAA,0xBA,0xAC,0xAB,0x9A,0xAA,0xAA,0xBA,0xB8,0xA9,0xBA,0x99,0xA9,0x9A,0xA0,0x8A,
0xA9,0x08,0x8A,0xA9,0x00,0x99,0x89,0x88,0x98,0x08,0x99,0x00,0x89,0x80,0x08,0x98,
0x00,0x88,0x88,0x80,0x90,0x80,0x90,0x80,0x81,0x99,0x08,0x88,0x99,0x09,0x00,0x1A,
0xA8,0x10,0x9A,0x88,0x08,0x0A,0x8A,0x89,0x99,0xA8,0x98,0xA9,0x99,0x99,0xA9,0x99,
0xAA,0x8A,0xAA,0x9B,0x8A,0x9A,0xA9,0x9A,0xBA,0x99,0x9A,0xAA,0x99,0x89,0xA9,0x99,
0x98,0x9A,0x98,0x88,0x09,0x89,0x09,0x08,0x08,0x09,0x18,0x18,0x00,0x12,0x00,0x11,
0x11,0x11,0x12,0x12,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,0x32,0x31,0x32,0x31,
0x32,0x32,0x21,0x31,0x21,0x32,0x21,0x12,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
/* Source: 02SD.ROM */
/* Length: 640 / 0x00000280 */
0x0A,0xDC,0x14,0x0B,0xBA,0xBC,0x01,0x0F,0xF5,0x2F,0x87,0x19,0xC9,0x24,0x1B,0xA1,
0x31,0x99,0x90,0x32,0x32,0xFE,0x83,0x48,0xA8,0xA9,0x23,0x19,0xBC,0x91,0x02,0x41,
0xDE,0x81,0x28,0xA8,0x0A,0xB1,0x72,0xDA,0x23,0xBC,0x04,0x19,0xB8,0x21,0x8A,0x03,
0x29,0xBA,0x14,0x21,0x0B,0xC0,0x43,0x08,0x91,0x50,0x93,0x0F,0x86,0x1A,0x91,0x18,
0x21,0xCB,0x27,0x0A,0xA1,0x42,0x8C,0xA9,0x21,0x10,0x08,0xAB,0x94,0x2A,0xDA,0x02,
0x8B,0x91,0x09,0x98,0xAE,0x80,0xA9,0x02,0x0A,0xE9,0x21,0xBB,0x15,0x20,0xBE,0x92,
0x42,0x09,0xA9,0x11,0x34,0x08,0x12,0x0A,0x27,0x29,0xA1,0x52,0x12,0x8E,0x92,0x28,
0x92,0x2B,0xD1,0x23,0xBF,0x81,0x10,0x99,0xA8,0x0A,0xC4,0x3B,0xB9,0xB0,0x00,0x62,
0xCF,0x92,0x29,0x92,0x2B,0xB1,0x1C,0xB2,0x72,0xAA,0x88,0x11,0x18,0x80,0x13,0x9E,
0x03,0x18,0xB0,0x60,0xA1,0x28,0x88,0x08,0x04,0x10,0x8F,0x96,0x19,0x90,0x01,0x09,
0xC8,0x50,0x91,0x8A,0x01,0xAB,0x03,0x50,0xBA,0x9D,0x93,0x68,0xBA,0x80,0x22,0xCB,
0x41,0xBC,0x92,0x60,0xB9,0x1A,0x95,0x4A,0xC8,0x20,0x88,0x33,0xAC,0x92,0x38,0x83,
0x09,0x80,0x16,0x09,0x29,0xD0,0x54,0x8C,0xA2,0x28,0x91,0x89,0x93,0x60,0xCD,0x85,
0x1B,0xA1,0x49,0x90,0x8A,0x80,0x34,0x0C,0xC9,0x14,0x19,0x98,0xA0,0x40,0xA9,0x21,
0xD9,0x34,0x0A,0xA9,0x10,0x23,0xCB,0x25,0xAA,0x25,0x9B,0x13,0xCD,0x16,0x09,0xA0,
0x80,0x01,0x19,0x90,0x88,0x21,0xAC,0x33,0x8B,0xD8,0x27,0x3B,0xB8,0x81,0x31,0x80,
0xAF,0x97,0x0A,0x82,0x0A,0xA0,0x21,0x89,0x8A,0xA2,0x32,0x8D,0xBB,0x87,0x19,0x21,
0xC9,0xBC,0x45,0x09,0x90,0x09,0xA1,0x24,0x1A,0xD0,0x10,0x08,0x11,0xA9,0x21,0xE8,
0x60,0xA9,0x14,0x0C,0xD1,0x32,0xAB,0x04,0x0C,0x81,0x90,0x29,0x83,0x9B,0x01,0x8F,
0x97,0x0B,0x82,0x18,0x88,0xBA,0x06,0x39,0xC8,0x23,0xBC,0x04,0x09,0x92,0x08,0x1A,
0xBB,0x74,0x8C,0x81,0x18,0x81,0x9D,0x83,0x41,0xCD,0x81,0x40,0x9A,0x90,0x10,0x12,
0x9C,0xA1,0x68,0xD8,0x33,0x9C,0x91,0x01,0x12,0xBE,0x02,0x09,0x12,0x99,0x9A,0x36,
0x0A,0xB0,0x30,0x88,0xA3,0x2D,0x12,0xBC,0x03,0x3A,0x11,0xBD,0x08,0xC8,0x62,0x80,
0x8B,0xD8,0x23,0x38,0xF9,0x12,0x08,0x99,0x91,0x21,0x99,0x85,0x2F,0xB2,0x30,0x90,
0x88,0xD9,0x53,0xAC,0x82,0x19,0x91,0x20,0xCC,0x96,0x29,0xC9,0x24,0x89,0x80,0x99,
0x12,0x08,0x18,0x88,0x99,0x23,0xAB,0x73,0xCB,0x33,0x9F,0x04,0x2B,0xB1,0x08,0x03,
0x1B,0xC9,0x21,0x32,0xFA,0x33,0xDB,0x02,0x33,0xAE,0xB9,0x54,0x8B,0xA1,0x20,0x89,
0x90,0x11,0x88,0x09,0x98,0x23,0xBE,0x37,0x8D,0x81,0x20,0xAA,0x34,0xBB,0x13,0x18,
0xB9,0x40,0xB1,0x18,0x83,0x8E,0xB2,0x72,0xBC,0x82,0x30,0xA9,0x9A,0x24,0x8B,0x27,
0x0E,0x91,0x20,0x90,0x08,0xB0,0x32,0xB9,0x21,0xB0,0xAC,0x45,0x9A,0xA1,0x50,0xA9,
0x80,0x0A,0x26,0x9B,0x11,0xBB,0x23,0x71,0xCB,0x12,0x10,0xB8,0x40,0xA9,0xA5,0x39,
0xC0,0x30,0xB2,0x20,0xAA,0xBA,0x76,0x1C,0xC1,0x48,0x98,0x80,0x18,0x81,0xAA,0x23,
0x9C,0xA2,0x32,0xAC,0x9A,0x43,0x9C,0x12,0xAD,0x82,0x72,0xBC,0x00,0x82,0x39,0xD1,
0x3A,0xB8,0x35,0x9B,0x10,0x40,0xF9,0x22,0x0A,0xC0,0x51,0xB9,0x82,0x18,0x98,0xA3,
0x79,0xD0,0x20,0x88,0x09,0x01,0x99,0x82,0x11,0x38,0xFC,0x33,0x09,0xC8,0x40,0xA9,
0x11,0x29,0xAA,0x94,0x3A,0xC2,0x4A,0xC0,0x89,0x52,0xBC,0x11,0x08,0x09,0xB8,0x71,
0xA9,0x08,0xA8,0x62,0x8D,0x92,0x10,0x00,0x9E,0x94,0x38,0xBA,0x13,0x88,0x90,0x4A,
0xE2,0x30,0xBA,0x02,0x00,0x19,0xD9,0x62,0xBB,0x04,0x0B,0xA3,0x68,0xB9,0x21,0x88,
0x9D,0x04,0x10,0x8C,0xC8,0x62,0x99,0xAA,0x24,0x1A,0x80,0x9A,0x14,0x9B,0x26,0x8C,
0x92,0x30,0xB9,0x09,0xA3,0x71,0xBB,0x10,0x19,0x82,0x39,0xDB,0x02,0x44,0x9F,0x10,
/* Source: 04TOP.ROM */
/* Length: 5952 / 0x00001740 */
0x07,0xFF,0x7C,0x3C,0x31,0xC6,0xC4,0xBB,0x7F,0x7F,0x7B,0x82,0x8A,0x4D,0x5F,0x7C,
0x3E,0x44,0xD2,0xB3,0xA0,0x19,0x1B,0x6C,0x81,0x28,0xC4,0xA1,0x1C,0x4B,0x18,0x00,
0x2A,0xA2,0x0A,0x7C,0x2A,0x00,0x01,0x89,0x98,0x48,0x8A,0x3C,0x28,0x2A,0x5B,0x3E,
0x3A,0x1A,0x3B,0x3D,0x4B,0x3B,0x4A,0x08,0x2A,0x1A,0x2C,0x4A,0x3B,0x82,0x99,0x3C,
0x5D,0x29,0x2B,0x39,0x0B,0x23,0xAB,0x1A,0x4C,0x79,0xA3,0x01,0xC1,0x2A,0x0A,0x38,
0xA7,0xB9,0x12,0x1F,0x29,0x08,0x82,0xA1,0x08,0xA9,0x42,0xAA,0x95,0xB3,0x90,0x81,
0x09,0xD4,0x1A,0x80,0x1B,0x07,0xB8,0x12,0x8E,0x49,0x81,0x92,0xD3,0x90,0xA1,0x2A,
0x02,0xE1,0xA3,0x99,0x02,0xB3,0x94,0xB3,0xB0,0xF4,0x98,0x93,0x90,0x13,0xE1,0x81,
0x99,0x38,0x91,0xA6,0xD3,0x99,0x94,0xC1,0x83,0xB1,0x92,0x98,0x49,0xC4,0xB2,0xA4,
0xA3,0xD0,0x1A,0x30,0xBA,0x59,0x02,0xD4,0xA0,0xA4,0xA2,0x8A,0x01,0x00,0xB7,0xA8,
0x18,0x2A,0x2B,0x1E,0x23,0xC8,0x1A,0x00,0x39,0xA0,0x18,0x92,0x4F,0x2D,0x5A,0x10,
0x89,0x81,0x2A,0x8B,0x6A,0x02,0x09,0xB3,0x8D,0x48,0x1B,0x80,0x19,0x34,0xF8,0x29,
0x0A,0x7B,0x2A,0x28,0x81,0x0C,0x02,0x1E,0x29,0x09,0x12,0xC2,0x94,0xE1,0x18,0x98,
0x02,0xC4,0x89,0x91,0x1A,0x20,0xA9,0x02,0x1B,0x48,0x8E,0x20,0x88,0x2D,0x08,0x59,
0x1B,0x02,0xA3,0xB1,0x8A,0x1E,0x58,0x80,0xC2,0xB6,0x88,0x91,0x88,0x11,0xA1,0xA3,
0xE2,0x01,0xB0,0x19,0x11,0x09,0xF4,0x88,0x09,0x88,0x19,0x89,0x12,0xF1,0x2A,0x28,
0x8C,0x25,0x99,0xA4,0x98,0x39,0xA1,0x00,0xD0,0x58,0xAA,0x59,0x01,0x0C,0x00,0x2B,
0x00,0x08,0x89,0x6B,0x69,0x90,0x01,0x90,0x98,0x12,0xB3,0xF3,0xA0,0x89,0x02,0x3B,
0x0C,0x50,0xA9,0x4E,0x6B,0x19,0x28,0x09,0xA2,0x08,0x2F,0x20,0x88,0x92,0x8A,0x11,
0xC4,0x93,0xF1,0x18,0x88,0x11,0xF2,0x80,0x92,0xA8,0x02,0xA8,0xB7,0xB3,0xA3,0xA0,
0x88,0x1A,0x40,0xE2,0x91,0x19,0x88,0x18,0x91,0x83,0xC1,0xB5,0x92,0xA9,0xC6,0x90,
0x01,0xC2,0x81,0x98,0x03,0xF0,0x00,0x2C,0x2A,0x92,0x2C,0x83,0x1F,0x3A,0x29,0x00,
0xB8,0x70,0xAB,0x69,0x18,0x89,0x10,0x0D,0x12,0x0B,0x88,0x4A,0x3A,0x9B,0x70,0xA8,
0x28,0x2F,0x2A,0x3A,0x1B,0x85,0x88,0x8B,0x6A,0x29,0x00,0x91,0x91,0x1B,0x7C,0x29,
0x01,0x88,0x90,0x19,0x2B,0x2B,0x00,0x39,0xA8,0x5E,0x21,0x89,0x91,0x09,0x3A,0x6F,
0x2A,0x18,0x18,0x8B,0x50,0x89,0x2B,0x19,0x49,0x88,0x29,0xF5,0x89,0x08,0x09,0x12,
0xAA,0x15,0xB0,0x82,0xAC,0x38,0x00,0x3F,0x81,0x10,0xB0,0x49,0xA2,0x81,0x3A,0xC8,
0x87,0x90,0xC4,0xA3,0x99,0x19,0x83,0xE1,0x84,0xE2,0xA2,0x90,0x80,0x93,0xB5,0xC4,
0xB3,0xA1,0x0A,0x18,0x92,0xC4,0xA0,0x93,0x0C,0x3A,0x18,0x01,0x1E,0x20,0xB1,0x82,
0x8C,0x03,0xB5,0x2E,0x82,0x19,0xB2,0x1B,0x1B,0x6B,0x4C,0x19,0x12,0x8B,0x5A,0x11,
0x0C,0x3A,0x2C,0x18,0x3D,0x08,0x2A,0x5C,0x18,0x00,0x88,0x3D,0x29,0x80,0x2A,0x09,
0x00,0x7A,0x0A,0x10,0x0B,0x69,0x98,0x10,0x81,0x3F,0x00,0x18,0x19,0x91,0xB7,0x9A,
0x28,0x8A,0x48,0x92,0xF3,0xA2,0x88,0x98,0x87,0xA1,0x88,0x80,0x81,0x95,0xD1,0xA3,
0x1B,0x1C,0x39,0x10,0xA1,0x2A,0x0B,0x7A,0x4B,0x80,0x13,0xC1,0xD1,0x2B,0x2A,0x85,
0xB2,0xA2,0x93,0xB2,0xD3,0x80,0xD1,0x18,0x08,0x08,0xB7,0x98,0x81,0x3F,0x01,0x88,
0x01,0xE2,0x00,0x9A,0x59,0x08,0x10,0xC3,0x99,0x84,0xA9,0xA5,0x91,0x91,0x91,0x80,
0xB5,0x94,0xC0,0x01,0x98,0x09,0x84,0xB0,0x80,0x7A,0x08,0x18,0x90,0xA8,0x6A,0x1C,
0x39,0x2A,0xB7,0x98,0x19,0x10,0x2A,0xA1,0x10,0xBD,0x39,0x18,0x2D,0x39,0x3F,0x10,
0x3F,0x01,0x09,0x19,0x0A,0x38,0x8C,0x40,0xB3,0xB4,0x93,0xAD,0x20,0x2B,0xD4,0x81,
0xC3,0xB0,0x39,0xA0,0x23,0xD8,0x04,0xB1,0x9B,0xA7,0x1A,0x92,0x08,0xA5,0x88,0x81,
0xE2,0x01,0xB8,0x01,0x81,0xC1,0xC7,0x90,0x92,0x80,0xA1,0x97,0xA0,0xA2,0x82,0xB8,
0x18,0x00,0x9C,0x78,0x98,0x83,0x0B,0x0B,0x32,0x7D,0x19,0x10,0xA1,0x19,0x09,0x0A,
0x78,0xA8,0x10,0x1B,0x29,0x29,0x1A,0x14,0x2F,0x88,0x4A,0x1B,0x10,0x10,0xAB,0x79,
0x0D,0x49,0x18,0xA0,0x02,0x1F,0x19,0x3A,0x2B,0x11,0x8A,0x88,0x79,0x8A,0x20,0x49,
0x9B,0x58,0x0B,0x28,0x18,0xA9,0x3A,0x7D,0x00,0x29,0x88,0x82,0x3D,0x1A,0x38,0xBA,
0x15,0x09,0xAA,0x51,0x8B,0x83,0x3C,0x8A,0x58,0x1B,0xB5,0x01,0xBB,0x50,0x19,0x99,
0x24,0xCA,0x21,0x1B,0xA2,0x87,0xA8,0xB1,0x68,0xA1,0xA6,0xA2,0xA8,0x29,0x8B,0x24,
0xB4,0xE2,0x92,0x8A,0x00,0x19,0x93,0xB5,0xB4,0xB1,0x81,0xB1,0x03,0x9A,0x82,0xA7,
0x90,0xD6,0xA0,0x80,0x1B,0x29,0x01,0xA4,0xE1,0x18,0x0A,0x2A,0x29,0x92,0xC7,0xA8,
0x81,0x19,0x89,0x30,0x10,0xE0,0x30,0xB8,0x10,0x0C,0x1A,0x79,0x1B,0xA7,0x80,0xA0,
0x00,0x0B,0x28,0x18,0xB1,0x85,0x1E,0x00,0x20,0xA9,0x18,0x18,0x1C,0x13,0xBC,0x15,
0x99,0x2E,0x12,0x00,0xE1,0x00,0x0B,0x3B,0x21,0x90,0x06,0xC9,0x2A,0x49,0x0A,0x18,
0x20,0xD1,0x3C,0x08,0x00,0x83,0xC9,0x41,0x8E,0x18,0x08,0x02,0xA0,0x09,0xA4,0x7B,
0x90,0x19,0x2A,0x10,0x2A,0xA8,0x71,0xBA,0x10,0x4A,0x0E,0x22,0xB2,0xB2,0x1B,0x8C,
0x78,0x1A,0xB5,0x93,0xA9,0x1B,0x49,0x19,0x29,0xA3,0xC6,0x88,0xAA,0x32,0x0D,0x1B,
0x22,0x08,0xC2,0x18,0xB9,0x79,0x3F,0x01,0x10,0xA9,0x84,0x1C,0x09,0x21,0xB0,0xA7,
0x0A,0x99,0x50,0x0C,0x81,0x28,0x8B,0x48,0x2E,0x00,0x08,0x99,0x38,0x5B,0x88,0x14,
0xA9,0x08,0x11,0xAA,0x72,0xC1,0xB3,0x09,0x8A,0x05,0x91,0xF2,0x81,0xA1,0x09,0x02,
0xF2,0x92,0x99,0x1A,0x49,0x80,0xC5,0x90,0x90,0x18,0x09,0x12,0xA1,0xF2,0x81,0x98,
0xC6,0x91,0xA0,0x11,0xA0,0x94,0xB4,0xF2,0x81,0x8B,0x03,0x80,0xD2,0x93,0xA8,0x88,
0x69,0xA0,0x03,0xB8,0x88,0x32,0xBC,0x97,0x80,0xB1,0x3B,0x1A,0xA6,0x00,0xD1,0x01,
0x0B,0x3B,0x30,0x9B,0x31,0x3E,0x92,0x19,0x8A,0xD3,0x5C,0x1B,0x41,0xA0,0x93,0xA2,
0xAF,0x39,0x4C,0x01,0x92,0xA8,0x81,0x3C,0x0D,0x78,0x98,0x00,0x19,0x0A,0x20,0x2D,
0x29,0x3C,0x1B,0x48,0x88,0x99,0x7A,0x2D,0x29,0x2A,0x82,0x80,0xA8,0x49,0x3E,0x19,
0x11,0x98,0x82,0x9A,0x3B,0x28,0x2F,0x20,0x4C,0x90,0x29,0x19,0x9A,0x7A,0x29,0x28,
0x98,0x88,0x33,0xCD,0x11,0x3A,0xC1,0xA4,0xA0,0xC4,0x82,0xC8,0x50,0x98,0xB2,0x21,
0xC0,0xB6,0x98,0x82,0x80,0x9C,0x23,0x00,0xF8,0x30,0xA8,0x1A,0x68,0xA8,0x86,0x9A,
0x01,0x2A,0x0A,0x97,0x91,0xC1,0x18,0x89,0x02,0x83,0xE0,0x01,0x8B,0x29,0x30,0xE2,
0x91,0x0B,0x18,0x3B,0x1C,0x11,0x28,0xAC,0x78,0x80,0x93,0x91,0xA9,0x49,0x8B,0x87,
0x90,0x99,0x3D,0x5A,0x81,0x08,0xA1,0x11,0x2F,0x1A,0x21,0x9B,0x15,0xA2,0xB0,0x11,
0xC0,0x91,0x5B,0x98,0x24,0xA2,0xF2,0x92,0x8B,0x6A,0x18,0x81,0xB5,0xB1,0x88,0x4C,
0x00,0x00,0xA4,0xC1,0x2B,0x1A,0x59,0x0A,0x02,0x80,0x1E,0x02,0x08,0xB3,0x80,0x9A,
0x23,0xB8,0xF2,0x84,0xAB,0x01,0x48,0x90,0xA7,0x90,0x0A,0x29,0x09,0x95,0x99,0xA0,
0x59,0x2B,0x00,0x97,0xB0,0x29,0x89,0x2A,0x03,0xD0,0xB7,0x1B,0x81,0x00,0xA6,0xB1,
0x90,0x09,0x48,0xC0,0x11,0x00,0x8A,0x00,0x5B,0x83,0x9A,0x18,0x2F,0x3C,0x18,0x11,
0xA9,0x04,0x1A,0x4F,0x01,0x98,0x81,0x09,0x09,0x4A,0x18,0xB4,0xA2,0x0B,0x59,0x90,
0x3B,0x49,0xBC,0x40,0x6A,0x88,0x3A,0x08,0x3E,0x3A,0x80,0x93,0xB0,0xE1,0x5A,0x00,
0xA4,0xB3,0xE3,0x90,0x0D,0x38,0x09,0x82,0xC4,0xA1,0xB1,0x4C,0x18,0x10,0x91,0xB2,
0x13,0xEA,0x34,0x99,0x88,0xA6,0x89,0x92,0x91,0xC1,0x20,0xB2,0xC2,0x86,0xD2,0xB3,
0x80,0xB2,0x08,0x09,0x87,0x91,0xC0,0x11,0x89,0x90,0x28,0xB9,0x79,0x19,0xA4,0x82,
0xD0,0x03,0x0C,0xA3,0xA5,0xB2,0xB2,0x1B,0x29,0x13,0xF1,0xB4,0x81,0x9D,0x38,0x00,
0xC4,0xA1,0x89,0x59,0x1A,0x81,0xA4,0xA9,0x1C,0x6A,0x19,0x02,0xB1,0x1A,0x4A,0x0B,
0x78,0x89,0x81,0x1C,0x2A,0x29,0x4A,0xA3,0x3E,0x1C,0x49,0x1A,0x08,0x21,0xAE,0x28,
0x4B,0x19,0x20,0x8C,0x10,0x3A,0xAB,0x26,0x8B,0x18,0x59,0x99,0x13,0xA2,0xAB,0x79,
0x2F,0x18,0x10,0xB2,0x80,0x1B,0x4D,0x5A,0x80,0x82,0x98,0x81,0x80,0x09,0xA5,0x90,
0x91,0x03,0xC2,0xE2,0x81,0xA8,0x82,0x09,0xC6,0xA3,0xB1,0x08,0x5B,0x08,0x05,0xD1,
0xA2,0x89,0x2A,0x28,0x91,0xA6,0x88,0xB0,0x49,0x80,0x09,0x08,0x88,0x07,0xB8,0x05,
0x99,0x81,0x88,0x18,0xE2,0x00,0xC3,0x18,0x0D,0x10,0x30,0xD0,0x93,0x8A,0x09,0x10,
0x2F,0x11,0x90,0xA1,0x20,0x9B,0xB1,0x73,0xC8,0x94,0x98,0x3B,0x01,0x0C,0x30,0x19,
0xF8,0x12,0x90,0xBA,0x78,0x0A,0x11,0x98,0xA0,0x79,0x8A,0x30,0x2B,0xC2,0x11,0x0D,
0x09,0x7A,0x00,0x82,0xB9,0x01,0x7A,0x89,0x21,0x09,0xA1,0x0A,0x7C,0x10,0x88,0xB5,
0x88,0x0A,0x2B,0x69,0x1A,0x10,0xA0,0x5B,0x19,0x1A,0x10,0x19,0x1A,0x6C,0x20,0x90,
0xA5,0x98,0x1B,0x0A,0x69,0x82,0xD1,0x18,0x09,0x19,0x2A,0x93,0xD4,0x9A,0x01,0x49,
0xA2,0xA2,0x82,0xD8,0x22,0xAA,0x97,0xA9,0x2D,0x38,0x2A,0xB6,0x80,0x90,0x0A,0x3C,
0x82,0x94,0xB8,0x21,0x0E,0x2A,0x22,0xB8,0x00,0x4F,0x2B,0x3A,0x81,0xA1,0x29,0x2C,
0x6A,0x13,0xD1,0xA2,0x98,0x28,0x0C,0x01,0xD5,0x08,0xA9,0x31,0xB3,0xB0,0xA7,0xB0,
0x29,0x1B,0x87,0xA2,0xA1,0xB2,0x4A,0x89,0x11,0xC3,0xF3,0x98,0x08,0x03,0xA0,0xA3,
0xC5,0x90,0xB3,0xB5,0xB4,0xB8,0x02,0x91,0x91,0xD3,0xA4,0xC1,0x1B,0x82,0x28,0xA4,
0xD1,0x94,0x8A,0x28,0x08,0x03,0xE0,0x80,0xD4,0x90,0x91,0xA1,0x3B,0x3D,0x02,0xE4,
0xA1,0x92,0x89,0x1A,0x4B,0x95,0xB3,0x90,0x99,0x6A,0x0A,0x30,0xA1,0x93,0xA6,0xA9,
0x85,0x8B,0x82,0x10,0xB1,0xA3,0x94,0xF8,0x38,0x9A,0x30,0x1A,0x8B,0xA7,0x89,0x01,
0x5B,0x19,0x18,0x11,0xF0,0x18,0x1C,0x39,0x19,0x0C,0x12,0x1C,0x2A,0x7B,0x3A,0x88,
0x2B,0x18,0x2B,0x5C,0x20,0x92,0x8D,0x38,0x8A,0x3A,0x5B,0x2E,0x3A,0x2B,0x10,0x12,
0xBB,0x6A,0x4D,0x18,0x10,0xB1,0x81,0x2A,0x8B,0x79,0x80,0x01,0x0A,0x09,0x5B,0x2D,
0x84,0x8A,0x08,0x02,0xA2,0x91,0x82,0xE8,0x50,0x9B,0x85,0xA3,0xB0,0xA3,0x1B,0x02,
0x18,0xF3,0xA2,0x88,0xAB,0x53,0xD1,0xB4,0xA3,0x09,0x09,0x18,0xD4,0x08,0xB0,0x09,
0x58,0xD1,0x82,0x89,0x81,0x1A,0x18,0x05,0xB9,0xC3,0x30,0xC0,0x95,0x80,0xC3,0x89,
0x89,0x13,0x88,0xF2,0x93,0x0E,0x18,0x01,0x92,0xA5,0xB8,0x2A,0x39,0xAA,0x33,0x9A,
0xB1,0x11,0xF5,0xA1,0xA1,0x0A,0x50,0xB8,0x03,0xC4,0xA0,0x4E,0x29,0x10,0x88,0xC2,
0x1A,0x39,0x1D,0x28,0x98,0x94,0x0E,0x10,0x2A,0x3C,0x02,0x2D,0x1B,0x4B,0x3B,0x49,
0x19,0xA9,0x48,0x2F,0x29,0x10,0x89,0x02,0x0C,0x10,0x09,0xB9,0x70,0x1B,0x8A,0x50,
0xA8,0x2B,0x49,0x89,0x69,0x88,0x95,0x89,0x90,0x92,0x4C,0x19,0x82,0xC1,0x01,0x80,
0xA0,0x2B,0x7A,0x81,0x10,0xC2,0xB7,0x98,0x88,0x19,0x2C,0x03,0xB1,0xA4,0xA1,0x0C,
0x3B,0x78,0x88,0x85,0xB1,0xA0,0x1B,0x3A,0x4A,0x08,0x94,0x81,0xF1,0x80,0x00,0x0C,
0x59,0x09,0x18,0x90,0xA6,0x92,0x8C,0x1A,0x79,0x92,0xA8,0x00,0x81,0x2E,0x2A,0x13,
0xA2,0xB0,0xA5,0x88,0x88,0x89,0x11,0x19,0xA0,0xF3,0x82,0xB0,0x83,0x5F,0x2A,0x01,
0xA1,0x94,0xB0,0x09,0x78,0x98,0xA3,0xA6,0xA0,0x91,0x80,0x93,0x98,0xC1,0x12,0x18,
0xC9,0x17,0xA0,0xA0,0x1A,0x21,0x80,0x99,0xD4,0x30,0x9D,0x00,0x10,0x2F,0x08,0x1C,
0x21,0x08,0xB4,0xC3,0x2B,0xA9,0x52,0xD2,0xA3,0xD1,0x09,0x10,0x8B,0x24,0x92,0xD1,
0x80,0x19,0xA0,0x2C,0x12,0x49,0xAA,0xB6,0x95,0xB8,0x08,0x3A,0x2B,0x01,0xF3,0xB3,
0x0B,0x09,0x79,0x18,0xA2,0xA4,0xA0,0x18,0x0C,0x20,0x08,0xA9,0x16,0x0C,0x00,0x1B,
0x08,0x2B,0x7B,0x01,0x01,0xB9,0x59,0x19,0x8B,0x45,0xA8,0x80,0x0C,0x1A,0x41,0x1E,
0x00,0x28,0xA8,0x5A,0x00,0xC1,0x49,0x99,0x21,0x1D,0x08,0x85,0x99,0x95,0x89,0x90,
0x11,0x90,0xD1,0x28,0xB2,0xA7,0x99,0x81,0x02,0xAC,0x13,0x81,0xB2,0xA6,0xA9,0x28,
0x1C,0xB1,0x33,0xD1,0xC1,0x58,0xA8,0x14,0xB0,0xB7,0x91,0xA0,0x82,0x89,0xC2,0x28,
0xA1,0xB2,0x49,0xD2,0x94,0xC8,0x12,0x80,0x99,0x85,0x08,0xD3,0x09,0xA2,0xB3,0x1E,
0x08,0x21,0xB9,0x23,0xB4,0xAB,0x41,0xAC,0x87,0x09,0xA2,0xC5,0x0B,0x2A,0x5A,0x91,
0x20,0x9A,0x89,0x78,0x9B,0x31,0x89,0x80,0x29,0x0A,0xB7,0x3C,0x98,0x48,0x1D,0x00,
0x01,0xB0,0x20,0x2F,0x29,0x4A,0x89,0x94,0x1C,0x88,0x28,0x2B,0x10,0x88,0x9A,0x71,
0x9A,0x08,0x4A,0x2F,0x18,0x2B,0x18,0x02,0xA8,0x4B,0x7A,0x99,0x48,0x80,0xA8,0x20,
0x1D,0x40,0xA8,0x10,0x08,0xA8,0xC5,0x88,0xC2,0x18,0x88,0x2A,0x12,0xF3,0x82,0xD8,
0x20,0x0A,0x09,0xA6,0x98,0x04,0xB9,0x11,0x18,0xC3,0xE1,0x29,0xA1,0x11,0xC1,0x03,
0xE2,0x9A,0x33,0xA9,0xB5,0x98,0x92,0xA1,0x02,0xF8,0x21,0xA8,0x10,0x02,0xC1,0xB7,
0x1B,0x90,0x5B,0x3C,0x83,0x93,0xE0,0x19,0x1A,0x11,0x11,0xF1,0x92,0x89,0x19,0x2C,
0x2C,0x41,0x99,0x92,0x90,0x3F,0x18,0x4B,0x00,0x08,0xD2,0x01,0xB2,0xAA,0x78,0x09,
0x01,0x91,0xA2,0x98,0x2F,0x3A,0x2C,0x01,0x00,0x93,0xE0,0x28,0x2C,0x2B,0x01,0x12,
0xE1,0x80,0xB3,0x3D,0x3A,0x0A,0x50,0x98,0xC2,0xA0,0x11,0xAA,0x30,0x87,0x90,0xC2,
0x29,0x88,0x38,0xC8,0xB5,0x90,0xBA,0x70,0x1A,0x02,0x94,0xD0,0x80,0x1A,0x82,0xA6,
0xB0,0x91,0x18,0xB3,0x00,0x13,0xF1,0xA2,0xC1,0x82,0xB0,0x00,0x15,0x0B,0xD3,0x02,
0xA8,0x91,0x2B,0x1F,0x49,0x88,0xA6,0x80,0x88,0x08,0x1B,0xA5,0x80,0xB9,0x06,0x0B,
0x90,0x21,0x9D,0x48,0x18,0xA0,0x15,0xC9,0x82,0x2B,0x1A,0x42,0x9A,0xC4,0x39,0xBC,
0x69,0x00,0xA0,0x29,0x8C,0x39,0x59,0x08,0x09,0x49,0xA9,0x6B,0x81,0x00,0x98,0xB0,
0x68,0x3D,0x81,0x88,0x18,0x19,0x1D,0x12,0x80,0xB2,0x3A,0x3F,0x85,0x92,0xD0,0x00,
0x0A,0x19,0x12,0xF1,0x02,0x9B,0x19,0x40,0xB9,0x11,0x02,0xF2,0x1A,0x08,0x94,0x0A,
0xC2,0x83,0x0B,0xB4,0xA4,0xC0,0x32,0xD8,0x86,0x98,0x90,0x95,0x89,0xA3,0x83,0xC2,
0x92,0xE1,0x92,0x82,0xD9,0x03,0x08,0xA9,0x85,0x92,0xA2,0x80,0xE0,0x30,0x8B,0xB3,
0x87,0x89,0x90,0x83,0xA0,0x08,0x92,0x93,0x3E,0xAB,0x43,0x89,0xE3,0x80,0x83,0x2F,
0x00,0xA3,0x80,0xC9,0x22,0x3F,0x08,0x81,0x0B,0x33,0x9A,0xA3,0x7B,0x0C,0x29,0x4A,
0x1B,0x21,0xAA,0x70,0x1B,0x0D,0x48,0x1A,0x81,0x88,0xB1,0x39,0x3F,0x08,0x58,0xA0,
0x81,0x1A,0x1A,0x2B,0x6D,0x11,0x0A,0x91,0x01,0x1A,0x98,0x5A,0x0C,0x03,0xB1,0x84,
0xA3,0xAD,0x58,0x2A,0xA1,0x84,0xB1,0xA0,0x5C,0x2B,0x13,0xA8,0x95,0x83,0xE8,0x10,
0x81,0xB0,0x00,0xC2,0x96,0xA0,0x91,0x00,0x2C,0x90,0x30,0xF2,0x80,0xA8,0x39,0x21,
0xC1,0x03,0xAC,0x39,0x7C,0x29,0x91,0x1A,0x00,0x19,0x2C,0x3A,0x93,0xB0,0x29,0x8F,
0x28,0x02,0x93,0xF3,0xA9,0x01,0x03,0xE0,0x08,0x09,0x1D,0x58,0xA1,0x83,0xA9,0x6B,
0x2A,0x3C,0x21,0x89,0xC2,0x2C,0x4B,0x8A,0x50,0x81,0x98,0xA8,0x32,0x0C,0x8E,0x24,
0x0B,0x1A,0x81,0x92,0xA1,0x4F,0x18,0x3A,0x0A,0xB4,0x18,0x2E,0x39,0x82,0x19,0xD3,
0xD0,0x28,0x1B,0x11,0x98,0x07,0xAA,0x28,0x00,0x88,0xB4,0x89,0x1B,0x1F,0x22,0x00,
0xB3,0xC9,0x33,0xAB,0x2B,0xB5,0x48,0x98,0x98,0xA7,0x10,0xD2,0xC1,0x23,0xCA,0x93,
0xC6,0x80,0xA1,0x88,0x02,0x89,0xE2,0x09,0x38,0xBA,0x40,0x89,0x21,0xD8,0x49,0x10,
0x8D,0x02,0x90,0xC3,0x9A,0x24,0x89,0x08,0x84,0xA5,0x9C,0x10,0x11,0x9C,0x88,0x30,
0x3C,0xA1,0x94,0x58,0x8C,0x0B,0x69,0x29,0x9A,0x81,0x12,0x2B,0x8B,0x79,0x94,0xB0,
0xC1,0x84,0xC2,0x99,0x25,0x99,0x11,0xA2,0x93,0xE4,0x99,0x80,0x0A,0x00,0x10,0xB7,
0xB0,0x31,0xBA,0x3C,0x21,0xB3,0xF1,0x18,0xA0,0x2A,0x20,0xA3,0x06,0xE8,0x28,0xA1,
0xB4,0x08,0x0B,0x11,0x4B,0xB7,0x90,0xA5,0x98,0x3D,0x19,0x02,0xA1,0xC4,0xB2,0x19,
0x28,0xC0,0xA5,0x92,0xB1,0xA3,0x0A,0x0A,0x08,0x2B,0x70,0xC4,0xB3,0x00,0xBC,0x4B,
0x39,0x12,0xE3,0xA0,0x00,0x3F,0x18,0x29,0x94,0xD1,0x19,0x09,0x00,0xA1,0x83,0x99,
0x9B,0x35,0x80,0xC4,0xB1,0x6A,0x1A,0x1C,0x29,0x38,0x0E,0x19,0x5A,0x1A,0x82,0x8A,
0x59,0x2A,0x2E,0x20,0x88,0xA8,0x3A,0x38,0x3D,0x00,0xB3,0x29,0xAD,0x49,0x10,0x0C,
0x01,0x01,0xA3,0x8F,0x85,0x09,0x1B,0x88,0x10,0xA3,0xD2,0x90,0x3C,0x5C,0x39,0x03,
0xD1,0xA0,0x00,0x2A,0x0B,0x04,0xA7,0x90,0xA0,0x11,0x90,0x99,0x83,0xB4,0xB1,0xF1,
0x84,0x88,0x90,0x18,0x18,0xD3,0xD2,0xB3,0xA0,0x1A,0x21,0xA7,0xB2,0xB3,0x92,0x9A,
0x22,0xB9,0x28,0x38,0xBD,0x87,0x2A,0xB1,0x13,0x0D,0x0A,0x38,0xC9,0x24,0xC0,0x19,
0x23,0x0F,0x01,0x88,0xC0,0x2A,0x82,0x18,0x28,0xF0,0x18,0x2A,0x29,0x4B,0x35,0xB8,
0xA3,0x9D,0x18,0x1B,0x40,0x00,0x9A,0x5C,0x3A,0x09,0x2F,0x38,0x8A,0x3B,0x3B,0x11,
0x5C,0x19,0x2B,0x4A,0x08,0x0A,0x3D,0x20,0x4F,0x3A,0x19,0x2A,0x18,0x4D,0x1B,0x3A,
0x11,0x0D,0x3A,0x3C,0x4B,0x93,0x81,0xAA,0x6B,0x4A,0x18,0x00,0xC3,0xC3,0x9A,0x59,
0x2A,0x1B,0xA7,0xA1,0x81,0x88,0x88,0x58,0xB2,0xB1,0x2B,0x83,0xD4,0x81,0x08,0x0F,
0x00,0x20,0xC2,0xE2,0x80,0x08,0x1C,0x29,0x04,0xB1,0xA2,0x01,0x1C,0x91,0x00,0x0C,
0x49,0xB0,0x43,0xF2,0x99,0x39,0x3F,0x00,0x81,0x94,0xC1,0x09,0x1A,0x69,0x90,0x80,
0x94,0xAA,0x20,0x2A,0x91,0xB1,0x39,0x7A,0x38,0xD1,0x10,0x8A,0x8C,0x5A,0x01,0xB5,
0x98,0x80,0x2A,0x0B,0x32,0x92,0xF1,0x81,0x9A,0x23,0x8A,0xA3,0xB7,0x09,0x03,0x08,
0xD0,0x94,0x9A,0x09,0x01,0x93,0xB7,0xC2,0x8C,0x3A,0x83,0x99,0x05,0xA0,0x0B,0x29,
0x93,0xE5,0x80,0x89,0x38,0x90,0x8A,0xD7,0xA1,0x19,0x1B,0x48,0x98,0x92,0xC3,0xA1,
0x09,0x3F,0x02,0x0C,0x22,0xC3,0xB2,0xA1,0x01,0x9F,0x4A,0x01,0xA3,0xD3,0xB0,0x28,
0x3F,0x29,0x20,0xA2,0xC2,0xB1,0x08,0x5A,0x98,0x13,0xD2,0xC1,0x01,0xB2,0x80,0x3D,
0x03,0xC1,0x89,0x96,0x90,0x90,0x3A,0x1A,0x9A,0x32,0xB6,0xA2,0x8E,0x4A,0x28,0x8A,
0x84,0xA2,0x8A,0x2D,0x49,0x09,0x88,0x18,0x30,0x9D,0x2C,0x23,0xB1,0x0C,0x92,0x2D,
0x39,0x82,0xC4,0x2E,0x10,0x1A,0x10,0xB9,0x48,0x19,0x39,0xBA,0x34,0xDA,0x2D,0x48,
0x1A,0xA6,0x98,0x83,0x9A,0x1D,0x38,0x04,0xD0,0x18,0x90,0x2C,0x11,0x93,0xD3,0x9A,
0x11,0x08,0x82,0xF1,0x01,0xA0,0x2A,0x93,0xD3,0xB4,0xB8,0x82,0x2F,0x11,0xA3,0xB3,
0xA8,0x3B,0x09,0x23,0x96,0xC8,0x3B,0x3F,0x93,0x82,0xA1,0x90,0x3F,0x28,0x81,0xD1,
0x93,0x08,0x2D,0x18,0x91,0xB3,0xB5,0x98,0x2A,0x2B,0x84,0xB1,0x5B,0x8A,0x31,0x18,
0x80,0x8B,0x7E,0x39,0x2B,0x02,0xC1,0x8B,0x6C,0x49,0x09,0x10,0xA1,0x08,0x01,0x0C,
0x20,0xA1,0x09,0x4F,0x18,0x00,0x01,0xA0,0x5C,0x1B,0x5B,0x10,0x92,0x90,0x2B,0x5A,
0x3D,0x18,0x91,0x19,0x98,0x2D,0x39,0x89,0x2D,0x3A,0x48,0x2C,0x11,0xB5,0x9A,0x19,
0x5B,0x28,0x90,0x95,0x98,0x89,0x2B,0x40,0x08,0x90,0xF3,0x0A,0x08,0xA6,0x80,0x91,
0xB2,0xA0,0x02,0xF2,0xA1,0xB7,0x89,0x81,0x82,0x91,0xB1,0x21,0xAB,0x32,0xE9,0x04,
0xA2,0x8D,0x12,0x91,0xA3,0xA3,0xD2,0x8B,0x39,0xD1,0x84,0xE2,0x90,0x00,0x2B,0x29,
0xA3,0xD4,0xA1,0x91,0x1D,0x5A,0x08,0x19,0x11,0x99,0x08,0x18,0x49,0x0F,0x18,0x10,
0x82,0xF1,0x00,0x89,0x2F,0x3A,0x01,0xB3,0xC2,0x81,0x3F,0x29,0x08,0x10,0xA1,0xA1,
0x3B,0x5D,0x19,0x28,0x0B,0x38,0x82,0x91,0x19,0xBD,0x3B,0x7A,0x80,0x12,0xB3,0xE0,
0x0B,0x6A,0x01,0x88,0xA4,0x08,0x0B,0x08,0x59,0x80,0x80,0x1D,0x49,0x89,0x00,0x84,
0x99,0x1A,0x2B,0x32,0xE3,0xB4,0xA9,0x3A,0x99,0x31,0xE3,0xAA,0x58,0x3B,0x88,0x95,
0xC0,0x18,0x4A,0x09,0x30,0xF2,0xA3,0x1C,0x1B,0x49,0x00,0xD3,0xB2,0xA0,0x18,0x11,
0x92,0xD3,0xB2,0x91,0x80,0xE7,0xA1,0x91,0x98,0x19,0x22,0xC2,0xD2,0x18,0x8D,0x3B,
0x10,0xA5,0x91,0x98,0x02,0x3E,0x80,0x01,0x90,0xAA,0x13,0xF1,0x02,0xD1,0x08,0x19,
0x49,0xB4,0x91,0xB4,0x99,0x2A,0x0C,0x32,0xC0,0x05,0x88,0x0B,0x80,0x2C,0x81,0x10,
0x0B,0x51,0xA9,0x19,0x05,0xBF,0x28,0x20,0xE1,0x90,0x80,0x28,0x19,0x08,0x26,0xB1,
0xA1,0x18,0x88,0x2A,0xF0,0x12,0x8A,0xB3,0x14,0x1B,0xD4,0xD8,0x10,0x08,0x8A,0x17,
0xA0,0x98,0x2B,0x3A,0x29,0x48,0xA4,0x99,0x0E,0x4A,0x12,0x8B,0x31,0x8B,0x4E,0x1A,
0x11,0xB5,0x89,0x91,0x29,0x89,0xC2,0x97,0x90,0x0A,0x19,0x11,0x91,0xC1,0xD5,0x08,
0x89,0x20,0x91,0xB1,0x1A,0x2D,0x18,0x29,0xD2,0x3B,0x3E,0x3A,0x2A,0x90,0x82,0x1C,
0x49,0x3B,0x93,0xB6,0xC8,0x4C,0x02,0x91,0x93,0xF2,0x88,0x2D,0x28,0x81,0x82,0xC1,
0x89,0x2D,0x6B,0x19,0x82,0x80,0x18,0x8B,0x39,0x39,0xC8,0x3A,0x6A,0x0A,0x22,0xD2,
0x09,0x2C,0x1A,0x68,0x92,0xE2,0x89,0x2A,0x2A,0x30,0xC2,0xA3,0xB4,0x1D,0x2A,0x09,
0x93,0x18,0xF2,0x89,0x28,0xB3,0x01,0x8F,0x18,0x11,0xA1,0x93,0x90,0xD1,0x7A,0x20,
0xC3,0xA2,0xA8,0x88,0x1D,0x28,0xA5,0xA2,0xA2,0x0B,0x29,0x2B,0x87,0xC1,0x80,0x0A,
0x19,0x01,0x12,0xF1,0x10,0x80,0x0A,0x18,0x08,0x2F,0x4A,0x02,0x89,0x1B,0x29,0x5D,
0x4C,0x08,0x82,0xA1,0x0A,0x3A,0x4B,0x29,0xC6,0xC3,0x09,0x09,0x88,0x39,0x98,0x82,
0xA5,0x1A,0x30,0x11,0xBD,0x3F,0x12,0x8B,0x28,0xC3,0x88,0x3F,0x2B,0x3B,0x48,0xA1,
0x80,0x8A,0x4D,0x39,0x01,0x93,0xA2,0xF1,0x19,0x19,0x0A,0x02,0xB2,0x8B,0x24,0xD2,
0x4B,0x12,0xC8,0x2E,0x10,0xB5,0x89,0x01,0x09,0x1C,0x2A,0x03,0xD4,0x91,0x98,0x99,
0x11,0x2B,0xE4,0x00,0x00,0x01,0xE0,0xA5,0x89,0x99,0x31,0x18,0xD0,0xB7,0x98,0x18,
0x0A,0x10,0x94,0xC2,0x90,0x18,0x00,0x99,0x87,0xA0,0x90,0x2A,0x3C,0x02,0xB8,0xC1,
0x79,0x1A,0x20,0x08,0xA1,0xD2,0x1C,0x29,0x03,0xD1,0x29,0x99,0x2C,0x50,0xB3,0xD1,
0x08,0x09,0x3C,0x10,0x04,0xB2,0x0D,0x2B,0x59,0x80,0x90,0x01,0x0F,0x3A,0x18,0x01,
0xA2,0x9B,0x5B,0x3D,0x81,0x03,0xD2,0x98,0x59,0x90,0x81,0x92,0xB4,0x8B,0x1B,0x40,
0xB2,0xB5,0x08,0x4B,0x01,0x09,0xD1,0x91,0x8B,0x7A,0x10,0xB3,0xC3,0x99,0x49,0x1A,
0x29,0xB5,0xA2,0xAB,0x40,0x81,0x19,0xB7,0xB0,0x20,0x2B,0xD4,0x88,0xA1,0x91,0x3C,
0x82,0x37,0xD3,0xB1,0x8A,0x1B,0x30,0xB3,0xF4,0xA1,0x91,0x09,0x10,0x03,0xD0,0x83,
0xA9,0x8F,0x10,0x01,0x90,0x18,0x80,0x20,0x2B,0xF1,0x28,0x99,0x2A,0x41,0xF0,0x12,
0xAA,0x83,0x82,0xD1,0xC1,0x08,0x89,0x59,0x09,0x83,0x87,0xB0,0x2A,0x4D,0x18,0x09,
0x19,0xB3,0x4B,0x3F,0x39,0x19,0x09,0x01,0x89,0x03,0x1F,0x00,0x1A,0x0B,0x10,0x68,
0xA0,0x18,0x8C,0x6A,0x09,0x08,0x97,0xA1,0x81,0x1B,0x2B,0x4C,0x03,0xB4,0xA8,0x92,
0x4B,0x3C,0xA1,0x81,0x95,0xA8,0x81,0x12,0xBB,0x92,0x45,0xB9,0x93,0xF4,0x88,0x0A,
0x2D,0x28,0x00,0xA3,0xA3,0x8A,0x3F,0x48,0xB1,0x92,0xB4,0xA8,0x30,0x80,0xD3,0x80,
0xD1,0x19,0x3B,0xC4,0x81,0xC1,0x29,0x0D,0x20,0x13,0xC8,0xB4,0x4C,0x09,0x00,0x82,
0xC2,0x3B,0x0D,0x30,0x0B,0x12,0xF0,0x1B,0x20,0x0A,0xA6,0x80,0x0A,0x4A,0x4A,0x80,
0x94,0xB1,0x2E,0x3B,0x1A,0x10,0x93,0x10,0x4C,0x3D,0x08,0x82,0xC9,0x19,0x6A,0x2B,
0x38,0xD1,0x08,0x19,0x2A,0x5A,0x82,0xB1,0x8D,0x29,0x78,0x09,0x82,0x0A,0x2C,0x1B,
0x19,0x41,0xB8,0x8C,0x79,0x2B,0x11,0x88,0x82,0x91,0xDC,0x28,0x11,0xB0,0x11,0x18,
0xC9,0x62,0xA1,0x91,0x98,0x3B,0x3A,0xB0,0xF4,0x01,0xC0,0x29,0x39,0xF8,0x95,0x91,
0x88,0x88,0x91,0x03,0xA1,0xE2,0x18,0x82,0xD1,0xA2,0xD1,0x80,0x19,0x20,0x83,0xB1,
0xE3,0x80,0x91,0x4D,0x1A,0x03,0xB2,0x09,0x18,0xD1,0x19,0x09,0x92,0xA6,0xA0,0xB6,
0xB2,0x8B,0x38,0x10,0x42,0xD3,0xD0,0xA8,0x20,0x2C,0x10,0x01,0xB1,0xB4,0xAB,0x5B,
0x79,0x80,0x10,0x1A,0xA8,0x3D,0x18,0x20,0xB3,0x8F,0x18,0x01,0x00,0x09,0xF3,0x89,
0x69,0x88,0x81,0x91,0x08,0xE1,0x1A,0x08,0x11,0x81,0x1E,0x29,0xA0,0x01,0x00,0x90,
0x3E,0x7B,0x18,0x82,0xC3,0xA1,0x2A,0x2C,0x5B,0x81,0xA5,0x90,0x81,0x00,0x0B,0x1A,
0x1C,0x2C,0x32,0xC0,0xF3,0x80,0x2D,0x2A,0x10,0x02,0xE4,0xC1,0x89,0x4A,0x09,0x01,
0x03,0xD2,0x98,0x2A,0x39,0x8A,0x89,0x26,0xB1,0xB2,0x12,0xC0,0x0A,0x5A,0x18,0x98,
0xF3,0x92,0x99,0x99,0x79,0x01,0xB5,0xA1,0x80,0x80,0x90,0x83,0xA0,0xE2,0x81,0x29,
0x93,0x8A,0x0A,0x6A,0x1F,0x18,0x02,0xC8,0x01,0x19,0x3B,0x4A,0x98,0x17,0xA8,0x0D,
0x38,0xA1,0x91,0x10,0xA2,0x2B,0x4C,0xA6,0x81,0xBA,0x21,0x4C,0x80,0x21,0xD1,0x92,
0x2C,0x08,0x30,0x9F,0x93,0x2A,0x89,0x03,0x8B,0x87,0x0A,0x0D,0x12,0x98,0xA4,0x93,
0xBB,0x59,0x18,0xA1,0x32,0xE9,0x84,0x08,0x8A,0x02,0xA1,0x91,0x4B,0xB4,0x20,0x88,
0xF0,0x3A,0x1A,0x88,0x87,0xB1,0x92,0x0A,0x08,0x6B,0x83,0xC3,0x91,0xC0,0x2B,0x79,
0x08,0x8A,0x84,0xA0,0x89,0x40,0x1B,0xA1,0x39,0x98,0x17,0xC2,0xA2,0x12,0xCD,0x20,
0x89,0x92,0x25,0xB0,0x2D,0x3A,0x8B,0x58,0x2A,0xA0,0x4C,0x08,0x30,0xAE,0x82,0x59,
0x89,0x1A,0x10,0xC2,0x18,0x2C,0x40,0x1E,0x01,0xA3,0x8A,0x81,0x2C,0x29,0x29,0xA9,
0x13,0x51,0xAD,0x12,0x89,0x8F,0x18,0x2C,0x39,0x00,0xC1,0x10,0x3C,0x2A,0x41,0xC8,
0xA2,0x91,0x0A,0x6C,0x10,0x12,0x88,0xE8,0x30,0x91,0x81,0xD8,0x01,0x1B,0x0D,0x07,
0x00,0xA8,0x92,0x0A,0x28,0xD2,0xC3,0x02,0xAA,0x94,0x81,0xB4,0xB3,0x1A,0x0B,0x13,
0xF9,0x16,0xA1,0x8A,0x59,0x19,0x02,0xC1,0x91,0x8B,0x3D,0x18,0x3B,0xA4,0x94,0x80,
0x99,0x88,0x1C,0x79,0x0A,0x02,0x03,0xF8,0x90,0x39,0x5B,0x19,0x02,0xC3,0x90,0xBB,
0x58,0x6A,0x09,0x02,0x89,0x91,0x88,0x1A,0x69,0x8A,0x19,0x15,0xA0,0xA2,0x00,0x9A,
0x6B,0x49,0x88,0xA3,0x92,0xBB,0x6B,0x3D,0x38,0x01,0x98,0x91,0x3F,0x09,0x18,0x20,
0x90,0x80,0xAC,0x70,0x91,0x9B,0x51,0x09,0x88,0x99,0x14,0x8B,0x98,0x83,0x79,0xA0,
0x99,0x13,0x01,0x19,0xE0,0x83,0x0B,0xB0,0x0C,0x31,0x95,0xB5,0xC2,0x8A,0x39,0x20,
0x80,0x39,0xF3,0xB1,0x10,0x88,0x5E,0x18,0x94,0xA1,0x88,0xA1,0x98,0x15,0xAA,0x39,
0xD4,0x84,0xC0,0xA2,0xA2,0x0C,0x81,0x86,0xB5,0xA1,0xB1,0x14,0x1B,0xB1,0x02,0x92,
0xC3,0xE0,0x88,0x11,0xAA,0x69,0x18,0x81,0xA3,0xB0,0x01,0xBF,0x2A,0x31,0x93,0xF1,
0x00,0x89,0x18,0x19,0x11,0xD3,0xE0,0x10,0x18,0xB1,0x18,0x24,0x9A,0x2B,0xA4,0xC0,
0xB0,0x31,0x6C,0x19,0xB4,0x12,0xA8,0xEA,0x58,0x10,0x8B,0x93,0x82,0x88,0x9A,0x41,
0x10,0xC3,0xEA,0x41,0xA9,0x9C,0x34,0xA1,0x2A,0x79,0xA2,0x01,0xA8,0xB3,0x28,0xCC,
0x41,0x9A,0xB3,0x4B,0xB3,0x27,0x8B,0x83,0x2B,0x2F,0x08,0x28,0xB2,0x80,0x2C,0x30,
0x5E,0x09,0x12,0x9B,0x09,0x22,0x5B,0x19,0x8A,0x11,0x59,0x99,0xA4,0x32,0xCD,0x18,
0x08,0x10,0x85,0xB3,0xB4,0x1E,0x88,0x28,0x8A,0x11,0x09,0xC0,0x79,0x80,0x91,0x3B,
0x80,0x10,0x0F,0x01,0x80,0x91,0x19,0x3D,0x92,0x28,0xA8,0x37,0x9A,0x0A,0x3A,0x8A,
0x45,0xA9,0xA4,0x00,0xAA,0x09,0x3D,0x59,0x20,0xE1,0x08,0x98,0x90,0x59,0x10,0x09,
0xA3,0xC3,0x93,0x99,0x2B,0x69,0x11,0xD1,0xB1,0xA4,0x91,0x3C,0x89,0x83,0xF0,0x10,
0x91,0xA1,0x89,0x59,0x05,0x99,0x93,0x94,0xC8,0x08,0x0A,0x09,0x17,0xB1,0x83,0xC1,
0x91,0x40,0xA2,0xC2,0x98,0xC3,0xBA,0x28,0x23,0x0F,0x80,0x50,0xB8,0x19,0x10,0x96,
0x98,0x8C,0x05,0x98,0x19,0x29,0x2B,0x3B,0x0A,0xE2,0x01,0x0F,0x3C,0x38,0x08,0x09,
0x81,0x4A,0x6C,0x08,0x00,0x88,0x98,0x38,0x2C,0x5A,0x1B,0x20,0x1A,0x39,0xB0,0x09,
0xCB,0x5B,0x49,0x09,0x71,0x00,0xC1,0x0E,0x08,0x38,0x0C,0x02,0x10,0x0E,0x10,0x8A,
0x48,0x19,0x90,0x92,0x0D,0xA3,0x98,0x3B,0x79,0x19,0x01,0x10,0xE1,0x80,0x19,0x2B,
0x10,0xF2,0x02,0xAB,0x84,0x9A,0x29,0xB4,0x80,0x92,0x03,0x88,0x95,0xD0,0x03,0x90,
0xA0,0xC7,0xA1,0xB0,0xA2,0x02,0x18,0xB5,0xD4,0x01,0xC0,0x08,0xA2,0x93,0xA8,0xA0,
0xC3,0x20,0xF3,0x90,0x00,0xD5,0x08,0x89,0xA5,0x80,0xA0,0x81,0x82,0xC2,0x09,0xD1,
0x13,0xCB,0x03,0x84,0x91,0xE1,0x1B,0x12,0x08,0xAB,0x87,0x18,0xAB,0x58,0x89,0x28,
0x81,0xC9,0x33,0xA9,0x80,0x2E,0x20,0x83,0xB9,0x20,0x3B,0x9E,0x7A,0x08,0x81,0x18,
0x0B,0x88,0x79,0x80,0x8B,0x00,0x12,0x0E,0x89,0x51,0x1B,0x81,0xA0,0x3A,0x01,0xAF,
0x11,0x28,0xBA,0x35,0x98,0x88,0x52,0xC0,0x83,0x2F,0xA9,0x11,0x0A,0x19,0x25,0xD0,
0x30,0x9C,0x08,0x21,0x98,0x81,0x2A,0xF3,0x2A,0x80,0xB6,0x2B,0x08,0x93,0xE9,0x02,
0x81,0x8C,0x21,0x00,0xA6,0xA9,0x94,0x01,0x8F,0x80,0x94,0x98,0x93,0xB4,0x00,0x08,
0xC0,0x14,0x98,0xB3,0xB4,0xC1,0x09,0x18,0xA7,0x00,0xA3,0xC8,0x0A,0x3C,0x19,0x96,
0x83,0xC1,0x99,0x19,0x4A,0x85,0x80,0xC1,0x91,0x99,0x90,0x2A,0x17,0x95,0x99,0x88,
0x12,0xAE,0x39,0x08,0x92,0x84,0xB0,0xA8,0x79,0x09,0x19,0x01,0xB2,0xA3,0x8F,0x28,
0x2B,0xA2,0x40,0x82,0xA0,0x4C,0xA9,0x39,0x8D,0x81,0x70,0x88,0xA0,0x1A,0x49,0x2D,
0x1A,0x26,0xA8,0x98,0x08,0x29,0x0B,0x12,0x96,0xB1,0xB2,0x3A,0x13,0x9B,0x60,0xA0,
0x88,0xB2,0x34,0xEA,0x1A,0x2A,0x79,0x98,0x10,0x04,0x8C,0x1C,0x81,0x04,0x8C,0x83,
0x19,0x2F,0x81,0x93,0x98,0x10,0x08,0x30,0x2A,0xFA,0x05,0x08,0x2A,0x89,0x91,0xA3,
0xFA,0x11,0x11,0x00,0x8C,0x04,0x8A,0x2A,0xB5,0x10,0xA9,0xC2,0x3D,0x1B,0x32,0x04,
0x0A,0x1A,0x09,0x40,0x1F,0x92,0x1D,0x2A,0x91,0x10,0x30,0x2F,0x0B,0x68,0x99,0xA2,
0x92,0x88,0x78,0xA9,0x20,0x28,0xE2,0x92,0x1A,0x99,0x4B,0x19,0x22,0xA1,0xE2,0x21,
0x2F,0x98,0x29,0x18,0x91,0x08,0xB0,0x79,0x1A,0x82,0x3B,0xB1,0xA7,0x8A,0xB3,0x98,
0x5B,0x23,0xCA,0x42,0x83,0xF0,0x90,0x18,0x98,0x08,0xB4,0x20,0xA3,0xC0,0x43,0xD8,
0x80,0x81,0xA3,0x99,0xD9,0xA7,0x19,0x90,0x10,0x05,0xB1,0x8B,0x02,0xA4,0xBD,0x23,
0x93,0x8A,0x99,0x4B,0x03,0xC1,0xF8,0x38,0x09,0x2B,0x14,0xD0,0x03,0x8A,0x2A,0x39,
0xB9,0x97,0x90,0xAA,0x50,0x01,0x99,0x51,0xD1,0x09,0x1A,0xB5,0x00,0x8B,0x93,0x08,
0x98,0x11,0xF9,0x85,0x2B,0x08,0x96,0x89,0x90,0x2A,0x12,0x4A,0xD8,0x85,0x2B,0x0E,
0x10,0x00,0x01,0xB1,0x9B,0x69,0x1A,0x90,0x40,0xB8,0x01,0x08,0x0A,0x2C,0x09,0x14,
0x4B,0xE2,0x82,0x88,0xB1,0x78,0x0A,0x01,0xC2,0x93,0x19,0xCE,0x20,0x3C,0x82,0xB4,
0x1B,0x20,0x8C,0x3B,0x29,0xAB,0x86,0x23,0xD8,0x81,0x9A,0x5A,0x49,0xB0,0x16,0xA0,
0xB0,0x28,0x1B,0x13,0x93,0xE4,0xA2,0xA9,0x08,0x5A,0xB3,0x12,0xC1,0xE1,0x10,0x88,
0x01,0x0C,0x92,0x08,0x89,0xB7,0x88,0x81,0x10,0x9A,0x17,0xA0,0xB0,0x13,0x99,0xE0,
0x39,0x31,0xD2,0xB2,0x80,0x0B,0x2D,0x49,0x80,0x01,0xB0,0x06,0x09,0x0C,0x3A,0x69,
0xA0,0x08,0xB2,0xA1,0x69,0x2B,0x5A,0x81,0x92,0xBA,0x21,0xB1,0x7D,0x10,0x80,0x08,
0x88,0x82,0x32,0x0D,0xB0,0x1A,0x1C,0x21,0x94,0xA9,0x58,0xB9,0x5A,0x4A,0xA0,0x13,
0xA9,0x80,0x7C,0x00,0x20,0x8A,0x04,0x0C,0x00,0x82,0x2A,0xB2,0xAC,0x4B,0x69,0xA0,
0xA6,0x81,0x9B,0x19,0x38,0x8B,0x17,0xB2,0x81,0x2A,0xBB,0x94,0x29,0xA2,0x15,0xBA,
0x97,0xA3,0xB9,0x79,0x01,0xB2,0x02,0xF1,0x90,0x0A,0x29,0x11,0x88,0xE5,0xA0,0x81,
0x19,0x91,0x90,0x28,0xB3,0x14,0xD0,0xB5,0x91,0x9A,0x29,0x0B,0x07,0xA2,0xB3,0x01,
0x9D,0x28,0x41,0xD0,0x91,0x90,0x82,0x1A,0xA8,0x44,0x9A,0xA9,0x21,0xE3,0xA9,0x4B,
0x19,0x78,0x89,0x83,0xA3,0xB9,0x5A,0x3D,0x80,0x82,0xA2,0xA0,0x6C,0x10,0x20,0x8B,
0x93,0x8B,0x0E,0x33,0xA9,0xB1,0x68,0x8A,0x31,0xAC,0x94,0xB4,0x8B,0x32,0x0B,0xB4,
0x81,0x91,0x1D,0x33,0xD9,0x31,0xE1,0x8B,0x3B,0x30,0x12,0x49,0xD2,0x8E,0x29,0x18,
0x8A,0x92,0x02,0xAA,0x59,0x1C,0x32,0x88,0x01,0x23,0xFB,0x83,0x29,0xDA,0x59,0x01,
0x81,0x92,0xE1,0x18,0x8A,0x1D,0x30,0x93,0xF1,0x00,0x01,0x0B,0x39,0x92,0x89,0xA0,
0x11,0x5B,0xE0,0x82,0x09,0x13,0xAA,0xB4,0x16,0xD8,0x91,0x2A,0x29,0x84,0x1B,0xC5,
0x98,0x98,0x31,0x98,0x99,0x17,0xA9,0x20,0x92,0xC3,0x18,0x9D,0x20,0x3D,0x89,0x94,
0xA2,0x1C,0x5C,0x29,0x39,0xA0,0xB3,0x00,0x0C,0x4C,0x48,0x92,0x0A,0x91,0x85,0x9A,
0x01,0x82,0x1F,0x10,0x99,0x15,0xC1,0xA0,0x39,0x1A,0x1D,0x85,0xB4,0x90,0x1A,0x2A,
0x4B,0x01,0xB2,0x93,0xBE,0x12,0x83,0xC9,0x18,0x09,0x20,0x78,0xF1,0x08,0x19,0x88,
0x3A,0x83,0xB3,0xA9,0x93,0x7A,0x0A,0x96,0x98,0x00,0xA8,0x3A,0x30,0x92,0xF2,0x9B,
0x3D,0x38,0x92,0x92,0xC3,0xB8,0x6B,0x29,0x01,0x01,0xB2,0x2F,0x09,0x19,0x18,0x01,
0x3B,0x7B,0x10,0xA1,0x90,0x39,0x0F,0x38,0x0A,0xB5,0xA4,0x89,0x8B,0x6A,0x2B,0x12,
0xC8,0x90,0x40,0x2A,0x9E,0x22,0x88,0x18,0x09,0x3A,0xC3,0xE8,0x09,0x59,0x08,0x12,
0x94,0xD0,0x1A,0x2C,0x38,0x00,0xA1,0x83,0xE8,0x08,0x3A,0x08,0x10,0x9E,0x83,0x1D,
0x92,0x19,0x2C,0x39,0x3B,0x59,0x04,0xE1,0x80,0x08,0x8D,0x21,0x81,0xB2,0xB2,0x02,
0x99,0x91,0xA4,0xD6,0x98,0x99,0x03,0x80,0x98,0xA7,0x91,0x09,0xA1,0xB2,0xB3,0xE1,
0x12,0x92,0xB1,0x81,0x06,0x99,0x0A,0x23,0xC4,0xB1,0xF2,0x89,0x19,0x3A,0x94,0x82,
0xE0,0x89,0x38,0x0B,0xA4,0xA5,0x80,0x80,0x8C,0x34,0xB9,0xA9,0x23,0x13,0xB9,0xC1,
0xC7,0x1B,0x89,0x10,0x20,0x11,0xE3,0xA8,0x4B,0x0B,0x40,0x91,0x90,0x1B,0x5F,0x2A,
0x18,0x82,0x91,0x0B,0x4A,0x28,0xCA,0x40,0x80,0x5B,0x2C,0x13,0xB0,0x8A,0xA9,0x5A,
0x58,0x89,0x82,0x88,0x2E,0x3B,0x31,0xA1,0x9B,0x01,0x7A,0x2C,0x01,0x91,0x93,0x3F,
0x88,0x39,0x10,0xF1,0x91,0x8B,0x48,0x0A,0x12,0xE3,0xA8,0x18,0x28,0x92,0x97,0x98,
0x99,0x19,0xA1,0x11,0xB6,0x88,0x3B,0x10,0xD3,0xC3,0xA1,0x2A,0x8A,0x49,0x04,0xF1,
0x91,0x02,0x8A,0x89,0x04,0xF1,0x98,0x80,0x18,0x12,0xE3,0x81,0x98,0x80,0x01,0xB3,
0xF2,0x99,0x12,0x2A,0xB5,0xB3,0x92,0xAA,0x19,0x50,0xB2,0xC3,0x92,0xD0,0x2B,0x68,
0x93,0x99,0xC0,0x2C,0x3E,0x80,0x20,0x08,0x93,0x0D,0x2A,0x31,0x8D,0x02,0x2B,0x91,
0x08,0x0A,0x03,0x2C,0x3C,0x52,0xB9,0xA0,0x12,0xBF,0x3A,0x29,0x01,0x88,0xC0,0x6A,
0x3C,0x0A,0x49,0x18,0x0B,0x39,0x2B,0x69,0x0A,0x84,0x2A,0x2A,0x1C,0x2A,0xC3,0x8C,
0x19,0x50,0x09,0x91,0xA7,0x8D,0x18,0x1A,0x28,0x00,0xA0,0x94,0x10,0x1F,0x20,0x90,
0x8A,0x12,0xD0,0x1A,0x5A,0x81,0x04,0xBC,0x23,0x10,0xE0,0x90,0x90,0x18,0x1A,0xA6,
0x12,0xB1,0xD0,0x4A,0x08,0x82,0x92,0xB6,0x9A,0x0A,0x12,0x88,0xC3,0xC5,0x8A,0x89,
0x20,0xB5,0x93,0x0B,0x18,0x00,0x09,0xF2,0x88,0x2A,0x4A,0x08,0x05,0xB2,0xA9,0x3B,
0x5D,0x28,0xA4,0xB1,0x00,0x19,0x19,0x7A,0xA3,0xB3,0x0A,0x90,0xA1,0xC4,0x80,0xBA,
0x50,0x13,0xC1,0xC2,0x9A,0x2A,0x7B,0x28,0x84,0xC1,0x09,0x3B,0x4E,0x20,0x91,0xA1,
0x18,0xAB,0x79,0x10,0xB4,0x08,0x9A,0x11,0x2B,0xF0,0x93,0xAA,0x01,0x6A,0x01,0x93,
0x80,0xB8,0x2A,0x5B,0x10,0x80,0x89,0x4A,0x5B,0x92,0x15,0xB2,0xA0,0x2F,0x19,0x93,
0xB8,0x95,0x80,0x1C,0x21,0xA9,0x02,0x0B,0xA0,0x5A,0x18,0x98,0x39,0x1B,0x68,0x00,
0x91,0x91,0x9C,0x39,0x3E,0x18,0x84,0xB3,0x9B,0x7A,0x08,0x18,0x0A,0xB5,0x91,0x0B,
0x28,0x39,0x19,0x90,0x0A,0x50,0xAC,0x11,0x01,0xAB,0x88,0x52,0x1B,0x83,0xC4,0xA2,
0x9A,0xAB,0x03,0x90,0x19,0x93,0x81,0x08,0x92,0x9A,0x68,0x98,0x19,0x39,0xC1,0x92,
0x8A,0x38,0x4E,0x02,0xB1,0x90,0xC3,0x18,0x2B,0x04,0xC3,0xD2,0x91,0x90,0x81,0x89,
0x13,0xF1,0x88,0x93,0xA2,0x00,0x91,0xC0,0x5B,0x21,0x99,0x93,0x06,0x9A,0x1B,0x48,
0x99,0xB7,0x90,0x89,0x18,0x1B,0x11,0xA4,0xB2,0x81,0x9A,0x08,0x97,0x98,0x91,0x10,
0xB8,0x06,0xA2,0xA0,0x29,0x2B,0x21,0xC2,0xD1,0x10,0x1A,0x4A,0x29,0xF1,0x98,0x29,
0x1B,0x31,0x10,0xA0,0xA1,0x1D,0x5A,0x29,0xB2,0x82,0xA8,0x0F,0x28,0x21,0x09,0x91,
0x82,0x4D,0x10,0xA3,0xB0,0x89,0x4C,0x39,0xA0,0xA4,0xA1,0x89,0x1E,0x28,0x29,0xA3,
0xC3,0x2D,0x19,0x01,0x49,0x01,0x9B,0x0C,0x21,0xC2,0xA2,0x93,0x7C,0x2A,0x10,0x90,
/* Source: 08HH.ROM */
/* Length: 384 / 0x00000180 */
0x75,0xF2,0xAB,0x7D,0x7E,0x5C,0x3B,0x4B,0x3C,0x4D,0x4A,0x02,0xB3,0xC5,0xE7,0xE3,
0x92,0xB3,0xC4,0xB3,0xC3,0x8A,0x3B,0x5D,0x5C,0x3A,0x84,0xC2,0x91,0xA4,0xE7,0xF7,
0xF7,0xF4,0xA1,0x1B,0x49,0xA5,0xB1,0x1E,0x7F,0x5A,0x00,0x89,0x39,0xB7,0xA8,0x3D,
0x4A,0x84,0xE7,0xF7,0xE2,0x2D,0x4C,0x3A,0x4E,0x7D,0x04,0xB0,0x2D,0x4B,0x10,0x80,
0xA3,0x99,0x10,0x0E,0x59,0x93,0xC4,0xB1,0x81,0xC4,0xA2,0xB2,0x88,0x08,0x3F,0x3B,
0x28,0xA6,0xC3,0xA2,0xA2,0xC5,0xC1,0x3F,0x7E,0x39,0x81,0x93,0xC2,0xA3,0xE5,0xD2,
0x80,0x93,0xB8,0x6D,0x49,0x82,0xD4,0xA1,0x90,0x01,0xA0,0x09,0x04,0xE3,0xB2,0x91,
0xB7,0xB3,0xA8,0x2A,0x03,0xF3,0xA1,0x92,0xC5,0xC3,0xB2,0x0B,0x30,0xB3,0x8E,0x6D,
0x4A,0x01,0xB4,0xB4,0xC4,0xC3,0x99,0x3B,0x12,0xE3,0xA1,0x88,0x82,0xB4,0x9A,0x5C,
0x3A,0x18,0x93,0xC3,0xB3,0xB4,0xA8,0x19,0x04,0xF3,0xA8,0x3B,0x10,0xA2,0x88,0xA5,
0xB2,0x0B,0x6D,0x4B,0x10,0x91,0x89,0x3C,0x18,0x18,0xA6,0xC4,0xC3,0x98,0x19,0x2B,
0x20,0x91,0xA0,0x4E,0x28,0x93,0xB3,0xC2,0x92,0xA9,0x5A,0x96,0xC4,0xC2,0x09,0x01,
0xC4,0xA1,0x92,0xC4,0xA1,0x89,0x10,0xA3,0xA1,0x90,0x1C,0x5A,0x01,0xC5,0xA1,0x92,
0xD4,0xB3,0xC4,0xC4,0xC3,0xA1,0x88,0x1A,0x28,0x89,0x3C,0x3A,0x3D,0x29,0x00,0x93,
0xB0,0x3D,0x28,0x80,0x91,0x82,0xE3,0x99,0x2A,0x11,0xD6,0xC3,0x99,0x29,0x82,0xC4,
0xC3,0xA1,0x0A,0x3B,0x3D,0x3A,0x02,0xC3,0xA2,0x99,0x3B,0x2C,0x7C,0x28,0x81,0xA3,
0xB2,0xA3,0xB1,0x08,0x1A,0x3C,0x18,0x2E,0x4C,0x39,0xA5,0xB3,0xB4,0xC2,0x88,0x08,
0x19,0x0A,0x49,0xB7,0xB3,0xA2,0xA1,0x92,0xA1,0x93,0xB1,0x0C,0x7D,0x39,0x93,0xB3,
0xB1,0x1A,0x19,0x5D,0x28,0xA6,0xC4,0xB2,0x90,0x09,0x2A,0x18,0x1B,0x5B,0x28,0x88,
0x2C,0x29,0x82,0xA0,0x18,0x91,0x2D,0x29,0x2B,0x5C,0x4C,0x3B,0x4C,0x28,0x80,0x92,
0x90,0x09,0x2B,0x28,0x1D,0x6B,0x11,0xC5,0xB2,0x0B,0x39,0x09,0x4D,0x28,0x88,0x00,
0x1B,0x28,0x94,0xE3,0xA0,0x1A,0x28,0xB5,0xB4,0xB3,0xB2,0x93,0xE2,0x91,0x92,0xD4,
0xA0,0x1B,0x4A,0x01,0xA1,0x88,0x2D,0x5C,0x3B,0x28,0x08,0x93,0xD4,0xB2,0x91,0xB4,
0xA0,0x3E,0x3B,0x4B,0x3B,0x29,0x08,0x93,0x9B,0x7B,0x3A,0x19,0x00,0x80,0x80,0xA0,
/* Source: 10TOM.ROM */
/* Length: 640 / 0x00000280 */
0x77,0x27,0x87,0x01,0x2D,0x4F,0xC3,0xC1,0x92,0x91,0x89,0x59,0x83,0x1A,0x32,0xC2,
0x95,0xB1,0x81,0x88,0x81,0x4A,0x3D,0x11,0x9E,0x0B,0x88,0x0C,0x18,0x3B,0x11,0x11,
0x91,0x00,0xA0,0xE2,0x0A,0x48,0x13,0x24,0x81,0x48,0x1B,0x39,0x1C,0x83,0x84,0xA1,
0xD1,0x8E,0x8A,0x0B,0xC0,0x98,0x92,0xB8,0x39,0x90,0x10,0x92,0xF0,0xB5,0x88,0x32,
0x49,0x51,0x21,0x03,0x82,0x10,0x8A,0x7A,0x09,0x00,0xA2,0xCA,0x1B,0xCC,0x1C,0xB9,
0x8E,0x89,0x89,0xA1,0x89,0x92,0x29,0x11,0x60,0x40,0x14,0x22,0x32,0x78,0x40,0x01,
0x02,0x90,0x81,0xAB,0x0B,0x00,0xAF,0x99,0xCC,0xAB,0xDA,0xA9,0x99,0x1B,0x30,0x14,
0x92,0x22,0x19,0x68,0x32,0x14,0x26,0x13,0x23,0x23,0x20,0x12,0x9A,0xA8,0xB9,0xFA,
0xAA,0xCA,0xCC,0x0C,0xA8,0xAE,0x88,0xB9,0x88,0xA0,0x02,0x21,0x50,0x43,0x03,0x81,
0x2A,0x11,0x34,0x63,0x24,0x33,0x22,0x38,0x8B,0xEA,0xAE,0x99,0xA0,0x90,0x82,0x00,
0x89,0xBF,0x8A,0xE8,0xA9,0x90,0x01,0x12,0x13,0x12,0x08,0xA9,0xAA,0xC9,0x22,0x63,
0x63,0x12,0x44,0x00,0x10,0x88,0x9C,0x98,0xA1,0x85,0x03,0x32,0x36,0x80,0x89,0xDB,
0xDB,0xBB,0xB9,0xBA,0x01,0x81,0x28,0x19,0xCB,0xFA,0xBC,0x09,0x13,0x37,0x34,0x34,
0x23,0x31,0x20,0x10,0x00,0x00,0x28,0x38,0x10,0x88,0xEC,0x8D,0xCB,0xBC,0xCC,0xBB,
0xBB,0xC9,0x99,0x00,0x00,0x33,0x11,0x22,0x81,0x07,0x41,0x54,0x34,0x34,0x22,0x31,
0x00,0x88,0x9A,0x9B,0x98,0xAB,0x8E,0x9B,0xBD,0x9C,0xBC,0xBB,0xDA,0xAA,0xA9,0x99,
0x18,0x38,0x60,0x20,0x31,0x13,0x13,0x51,0x14,0x31,0x53,0x33,0x35,0x22,0x01,0x8A,
0x9C,0xA9,0xCA,0xC9,0xA8,0x00,0x10,0x81,0x9C,0x9E,0xAB,0xCC,0xAB,0xBA,0x98,0x30,
0x52,0x03,0x81,0x08,0x9C,0xAC,0xAC,0x18,0x11,0x03,0x51,0x61,0x41,0x31,0x31,0x02,
0x01,0x20,0x24,0x43,0x44,0x40,0x30,0x10,0xBC,0xBE,0xCB,0xDB,0xAB,0xBA,0x99,0x98,
0x99,0xAA,0xBD,0xAA,0xC8,0x90,0x11,0x53,0x37,0x23,0x43,0x34,0x33,0x33,0x33,0x11,
0x28,0x00,0x19,0xA9,0x9A,0xCB,0xCE,0xBB,0xEB,0xBC,0xBB,0xCA,0xBA,0xA8,0x88,0x11,
0x12,0x21,0x20,0x22,0x26,0x26,0x23,0x23,0x43,0x24,0x22,0x32,0x20,0x31,0x81,0x9A,
0xBC,0xBC,0xCB,0xBD,0x9A,0xA9,0x90,0x98,0xBA,0xCC,0xCB,0xBC,0x8B,0x88,0x22,0x35,
0x23,0x12,0x99,0x8B,0xAA,0xAA,0x89,0x82,0x93,0x31,0x42,0x23,0x23,0x21,0x32,0x11,
0x20,0x13,0x13,0x24,0x24,0x24,0x22,0x11,0x8A,0x9E,0xAC,0xAC,0xAA,0xBA,0xAA,0xAB,
0xBD,0xBC,0xCB,0xCB,0xA9,0xA8,0x91,0x12,0x44,0x43,0x44,0x34,0x34,0x42,0x33,0x42,
0x21,0x11,0x11,0x88,0x80,0xAA,0x0B,0xAC,0xCB,0xEC,0xAC,0xBA,0xCA,0xAB,0x9A,0x99,
0x80,0x91,0x09,0x08,0x10,0x22,0x44,0x43,0x44,0x33,0x43,0x22,0x13,0x21,0x22,0x20,
0x09,0x88,0xB9,0xC8,0xBB,0xAB,0xAB,0xA9,0xA9,0x9B,0x9B,0x99,0x90,0x90,0x00,0x81,
0x00,0x08,0x09,0x8A,0x9A,0xAA,0xA9,0xA9,0x99,0x90,0x80,0x01,0x80,0x00,0x09,0x31,
0x32,0x44,0x33,0x43,0x34,0x33,0x24,0x22,0x23,0x12,0x10,0x09,0x9B,0xAB,0xCA,0xCC,
0xBB,0xCB,0xDA,0xCA,0xAB,0xCA,0xAB,0xA9,0xA8,0x92,0x12,0x43,0x53,0x35,0x23,0x33,
0x43,0x43,0x52,0x22,0x22,0x21,0x01,0x09,0x89,0xA9,0xBB,0xBD,0xBC,0xCB,0xDA,0xAB,
0xAB,0xAB,0xAA,0xA9,0x99,0xA8,0x09,0x01,0x11,0x34,0x25,0x23,0x33,0x51,0x22,0x31,
0x12,0x20,0x21,0x12,0x10,0x80,0x99,0x9A,0x99,0x99,0x88,0x08,0x00,0x88,0xA9,0x99,
0x99,0x80,0x80,0x10,0x01,0x00,0x9A,0xAA,0xBB,0xBA,0xBA,0xA9,0x99,0x99,0x89,0x99,
0x99,0x00,0x01,0x33,0x35,0x24,0x23,0x34,0x23,0x33,0x34,0x33,0x43,0x32,0x21,0x88,
0xAB,0xBD,0xBB,0xDB,0xAB,0xBA,0xBB,0xDA,0xBB,0xCB,0xBB,0xBC,0xA8,0x90,0x01,0x12,
0x23,0x43,0x53,0x34,0x34,0x39,0x80,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,
/* Source: 20RIM.ROM */
/* Length: 128 / 0x00000080 */
0x0F,0xFF,0x73,0x8E,0x71,0xCD,0x00,0x49,0x10,0x90,0x21,0x49,0xA0,0xDB,0x02,0x3A,
0xE3,0x0A,0x50,0x98,0xC0,0x59,0xA2,0x99,0x09,0x22,0xA2,0x80,0x10,0xA8,0x5B,0xD2,
0x88,0x21,0x09,0x96,0xA8,0x10,0x0A,0xE0,0x08,0x48,0x19,0xAB,0x52,0xA8,0x92,0x0C,
0x03,0x19,0xE2,0x0A,0x12,0xC2,0x81,0x1E,0x01,0xD0,0x48,0x88,0x98,0x01,0x49,0x91,
0xAA,0x2C,0x25,0x89,0x88,0xB5,0x81,0xA2,0x9A,0x12,0x9E,0x38,0x3B,0x81,0x9B,0x59,
0x01,0x93,0xCA,0x4A,0x21,0xA0,0x3D,0x0A,0x39,0x3D,0x12,0xA8,0x3F,0x18,0x01,0x92,
0x1C,0x00,0xB2,0x48,0xB9,0x94,0xA3,0x19,0x4F,0x19,0xB2,0x32,0x90,0xBA,0x01,0xE6,
0x91,0x80,0xC1,0xA4,0x2A,0x08,0xA1,0xB1,0x25,0xD2,0x88,0x99,0x21,0x80,0x88,0x80,
};

View File

@ -465,6 +465,8 @@ public:
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
fm_engine* debug_fm_engine() { return &m_fm; }
protected:
// internal helpers
void update_prescale(uint8_t prescale);
@ -546,6 +548,12 @@ public:
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
// get the engine
fm_engine* debug_fm_engine() { return &m_fm; }
ssg_engine* debug_ssg_engine() { return &m_ssg; }
adpcm_a_engine* debug_adpcm_a_engine() { return &m_adpcm_a; }
adpcm_b_engine* debug_adpcm_b_engine() { return &m_adpcm_b; }
protected:
// internal helpers
void update_prescale(uint8_t prescale);

View File

@ -186,7 +186,8 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
DivSample* sample=parent->getSample(ins->amiga.initSample);
// TODO: sample map?
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
if (sample!=NULL) {
double off=0.25;
if (sample->centerRate<1) {
@ -204,7 +205,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
if (chan[i].keyOn) {
if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
DivSample* sample=parent->getSample(ins->amiga.initSample);
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
if (sample!=NULL) {
unsigned int sampleEnd=sample->offSU+sample->samples;
if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1;

View File

@ -263,7 +263,7 @@ int DivPlatformSwan::dispatch(DivCommand c) {
dacPos=0;
dacPeriod=0;
if (ins->type==DIV_INS_AMIGA) {
dacSample=ins->amiga.initSample;
dacSample=ins->amiga.getSample(c.value);
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
@ -448,6 +448,7 @@ void DivPlatformSwan::muteChannel(int ch, bool mute) {
}
void DivPlatformSwan::forceIns() {
noise=0;
for (int i=0; i<4; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;

View File

@ -260,7 +260,7 @@ int DivPlatformVERA::dispatch(DivCommand c) {
if (c.chan<16) {
rWriteLo(c.chan,2,chan[c.chan].vol);
} else {
chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.initSample;
chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.getSample(c.value);
if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) {
chan[16].pcm.sample=-1;
}
@ -414,7 +414,7 @@ void DivPlatformVERA::muteChannel(int ch, bool mute) {
}
float DivPlatformVERA::getPostAmp() {
return 8.0f;
return 4.0f;
}
bool DivPlatformVERA::isStereo() {

View File

@ -243,7 +243,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) {
if (chan[c.chan].pcm) {
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].dacSample=ins->amiga.initSample;
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);

View File

@ -559,7 +559,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
if (chan[c.chan].furnacePCM) {
chan[c.chan].pcm=true;
chan[c.chan].macroInit(ins);
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
/**
* 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 _YM2203_H
#define _YM2203_H
#include "../dispatch.h"
#include "../macroInt.h"
#include <queue>
#include "sound/ymfm/ymfm_opn.h"
#include "ay.h"
class DivYM2203Interface: public ymfm::ymfm_interface {
};
class DivPlatformYM2203: public DivDispatch {
protected:
const unsigned short chanOffs[3]={
0x00, 0x01, 0x02
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
int vol, outVol;
int sample;
DivMacroInt std;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freqH(0),
freqL(0),
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
portaPauseFreq(0),
note(0),
ins(-1),
psgMode(1),
autoEnvNum(0),
autoEnvDen(0),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
inPorta(false),
furnacePCM(false),
hardReset(false),
vol(0),
outVol(15),
sample(-1) {}
};
Channel chan[6];
DivDispatchOscBuffer* oscBuf[6];
bool isMuted[6];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
ymfm::ym2203* fm;
ymfm::ym2203::output_data fmout;
DivYM2203Interface iface;
unsigned char regPool[512];
unsigned char lastBusy;
DivPlatformAY8910* ay;
unsigned char sampleBank;
int delay;
bool extMode;
short oldWrites[256];
short pendingWrites[256];
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
void setSkipRegisterWrites(bool val);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
void setFlags(unsigned int flags);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2203();
};
#endif

View File

@ -0,0 +1,564 @@
/**
* 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 "ym2203ext.h"
#include "../engine.h"
#include <math.h>
#include "ym2610shared.h"
#include "fmshared_OPN.h"
int DivPlatformYM2203Ext::dispatch(DivCommand c) {
if (c.chan<2) {
return DivPlatformYM2203::dispatch(c);
}
if (c.chan>5) {
c.chan-=3;
return DivPlatformYM2203::dispatch(c);
}
int ch=c.chan-2;
int ordch=orderedOps[ch];
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
// TODO: how does this work?!
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
}
}
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+0x70,op.d2r&31);
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+0x90,op.ssgEnv&15);
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
}
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
}
opChan[ch].keyOn=true;
opChan[ch].active=true;
break;
}
case DIV_CMD_NOTE_OFF:
opChan[ch].keyOff=true;
opChan[ch].keyOn=false;
opChan[ch].active=false;
break;
case DIV_CMD_VOLUME: {
opChan[ch].vol=c.value;
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
}
break;
}
case DIV_CMD_GET_VOLUME: {
return opChan[ch].vol;
break;
}
case DIV_CMD_INSTRUMENT:
if (opChan[ch].ins!=c.value || c.value2==1) {
opChan[ch].insChanged=true;
}
opChan[ch].ins=c.value;
break;
case DIV_CMD_PANNING: {
if (c.value==0 && c.value2==0) {
opChan[ch].pan=3;
} else {
opChan[ch].pan=(c.value2>0)|((c.value>0)<<1);
}
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (parent->song.sharedExtStat) {
for (int i=0; i<4; i++) {
if (ch==i) continue;
opChan[i].pan=opChan[ch].pan;
}
}
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
break;
}
case DIV_CMD_PITCH: {
opChan[ch].pitch=c.value;
opChan[ch].freqChanged=true;
break;
}
case DIV_CMD_NOTE_PORTA: {
if (parent->song.linearPitch==2) {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>opChan[ch].baseFreq) {
opChan[ch].baseFreq+=c.value;
if (opChan[ch].baseFreq>=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
} else {
opChan[ch].baseFreq-=c.value;
if (opChan[ch].baseFreq<=destFreq) {
opChan[ch].baseFreq=destFreq;
return2=true;
}
}
opChan[ch].freqChanged=true;
if (return2) {
//opChan[ch].inPorta=false;
return 2;
}
break;
}
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
int newFreq;
bool return2=false;
if (opChan[ch].portaPause) {
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
}
if (destFreq>opChan[ch].baseFreq) {
newFreq=opChan[ch].baseFreq+c.value;
if (newFreq>=destFreq) {
newFreq=destFreq;
return2=true;
}
} else {
newFreq=opChan[ch].baseFreq-c.value;
if (newFreq<=destFreq) {
newFreq=destFreq;
return2=true;
}
}
// what the heck!
if (!opChan[ch].portaPause) {
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
}
}
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
if (parent->song.fbPortaPause) {
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
opChan[ch].portaPause=true;
break;
} else {
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
}
}
}
opChan[ch].portaPause=false;
opChan[ch].freqChanged=true;
opChan[ch].baseFreq=newFreq;
if (return2) return 2;
break;
}
case DIV_CMD_LEGATO: {
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
opChan[ch].freqChanged=true;
break;
}
case DIV_CMD_FM_LFO: {
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
break;
}
case DIV_CMD_FM_MULT: { // TODO
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
break;
}
case DIV_CMD_FM_TL: { // TODO
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (isOutput[ins->fm.alg][c.value]) {
rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
} else {
rWrite(baseAddr+0x40,c.value2);
}
break;
}
case DIV_CMD_FM_AR: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.ar=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
}
} else {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.ar=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
}
break;
}
case DIV_CMD_FM_RS: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.rs=c.value2&3;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.rs=c.value2&3;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
break;
}
case DIV_CMD_FM_AM: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.am=c.value2&1;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.am=c.value2&1;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
break;
}
case DIV_CMD_FM_DR: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.dr=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.dr=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
break;
}
case DIV_CMD_FM_SL: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.sl=c.value2&15;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.sl=c.value2&15;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
break;
}
case DIV_CMD_FM_RR: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.rr=c.value2&15;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.rr=c.value2&15;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
break;
}
case DIV_CMD_FM_D2R: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.d2r=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.d2r=c.value2&31;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
}
break;
}
case DIV_CMD_FM_DT: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.dt=c.value&7;
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.dt=c.value2&7;
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
break;
}
case DIV_CMD_FM_SSG: {
if (c.value<0) {
for (int i=0; i<4; i++) {
DivInstrumentFM::Operator& op=chan[2].state.op[i];
op.ssgEnv=8^(c.value2&15);
unsigned short baseAddr=chanOffs[2]|opOffs[i];
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
} else if (c.value<4) {
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.ssgEnv=8^(c.value2&15);
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
break;
}
case DIV_CMD_GET_VOLMAX:
return 127;
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;
case DIV_CMD_PRE_PORTA:
break;
default:
//printf("WARNING: unimplemented command %d\n",c.cmd);
break;
}
return 1;
}
static int opChanOffsL[4]={
0xa9, 0xaa, 0xa8, 0xa2
};
static int opChanOffsH[4]={
0xad, 0xae, 0xac, 0xa6
};
void DivPlatformYM2203Ext::tick(bool sysTick) {
if (extMode) {
bool writeSomething=false;
unsigned char writeMask=2;
for (int i=0; i<4; i++) {
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn || opChan[i].keyOff) {
writeSomething=true;
writeMask&=~(1<<(4+i));
opChan[i].keyOff=false;
}
}
if (writeSomething) {
immWrite(0x28,writeMask);
}
}
DivPlatformYM2203::tick(sysTick);
bool writeNoteOn=false;
unsigned char writeMask=2;
if (extMode) for (int i=0; i<4; i++) {
if (opChan[i].freqChanged) {
if (parent->song.linearPitch==2) {
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
} else {
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
int block=(opChan[i].baseFreq&0xf800)>>11;
if (fNum<0) fNum=0;
if (fNum>2047) {
while (block<7) {
fNum>>=1;
block++;
}
if (fNum>2047) fNum=2047;
}
opChan[i].freq=(block<<11)|fNum;
}
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
}
writeMask|=opChan[i].active<<(4+i);
if (opChan[i].keyOn) {
writeNoteOn=true;
writeMask|=1<<(4+i);
opChan[i].keyOn=false;
}
}
if (writeNoteOn) {
immWrite(0x28,writeMask);
}
}
void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) {
if (ch<2) {
DivPlatformYM2203::muteChannel(ch,mute);
return;
}
if (ch>5) {
DivPlatformYM2203::muteChannel(ch-3,mute);
return;
}
isOpMuted[ch-2]=mute;
int ordch=orderedOps[ch-2];
DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
if (isOpMuted[ch-2]) {
rWrite(baseAddr+0x40,127);
} else if (isOutput[ins->fm.alg][ordch]) {
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch-2].vol&0x7f))/127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
}
void DivPlatformYM2203Ext::forceIns() {
for (int i=0; i<3; i++) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (isMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;
}
}
for (int i=3; i<6; i++) {
chan[i].insChanged=true;
}
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
for (int i=0; i<4; i++) {
opChan[i].insChanged=true;
if (opChan[i].active) {
opChan[i].keyOn=true;
opChan[i].freqChanged=true;
}
}
}
void* DivPlatformYM2203Ext::getChanState(int ch) {
if (ch>=6) return &chan[ch-3];
if (ch>=2) return &opChan[ch-2];
return &chan[ch];
}
DivDispatchOscBuffer* DivPlatformYM2203Ext::getOscBuffer(int ch) {
if (ch>=6) return oscBuf[ch-3];
if (ch<3) return oscBuf[ch];
return NULL;
}
void DivPlatformYM2203Ext::reset() {
DivPlatformYM2203::reset();
for (int i=0; i<4; i++) {
opChan[i]=DivPlatformYM2203Ext::OpChannel();
opChan[i].vol=127;
}
// channel 2 mode
immWrite(0x27,0x40);
extMode=true;
}
bool DivPlatformYM2203Ext::keyOffAffectsArp(int ch) {
return (ch>8);
}
void DivPlatformYM2203Ext::notifyInsChange(int ins) {
DivPlatformYM2203::notifyInsChange(ins);
for (int i=0; i<4; i++) {
if (opChan[i].ins==ins) {
opChan[i].insChanged=true;
}
}
}
int DivPlatformYM2203Ext::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) {
DivPlatformYM2203::init(parent,channels,sugRate,flags);
for (int i=0; i<4; i++) {
isOpMuted[i]=false;
}
reset();
return 19;
}
void DivPlatformYM2203Ext::quit() {
DivPlatformYM2203::quit();
}
DivPlatformYM2203Ext::~DivPlatformYM2203Ext() {
}

View File

@ -0,0 +1,51 @@
/**
* 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 "../dispatch.h"
#include "ym2203.h"
class DivPlatformYM2203Ext: public DivPlatformYM2203 {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
int vol;
unsigned char pan;
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
};
OpChannel opChan[4];
bool isOpMuted[4];
friend void putDispatchChan(void*,int,int);
public:
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
void notifyInsChange(int ins);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2203Ext();
};

View File

@ -0,0 +1,45 @@
/**
* 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.
*/
static unsigned short opOffs[4]={
0x00, 0x04, 0x08, 0x0c
};
static bool isOutput[8][4]={
// 1 3 2 4
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,false,true},
{false,false,true ,true},
{false,true ,true ,true},
{false,true ,true ,true},
{true ,true ,true ,true},
};
static unsigned char dtTable[8]={
7,6,5,0,1,2,3,4
};
static int orderedOps[4]={
0,2,1,3
};
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_FREQBASE 4720270

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,148 @@
/**
* 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 _YM2608_H
#define _YM2608_H
#include "../dispatch.h"
#include "../macroInt.h"
#include <queue>
#include "sound/ymfm/ymfm_opn.h"
#include "ay.h"
class DivYM2608Interface: public ymfm::ymfm_interface {
public:
unsigned char* adpcmBMem;
int sampleBank;
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
DivYM2608Interface(): adpcmBMem(NULL), sampleBank(0) {}
};
class DivPlatformYM2608: public DivDispatch {
protected:
const unsigned short chanOffs[6]={
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
};
struct Channel {
DivInstrumentFM state;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
unsigned char psgMode, autoEnvNum, autoEnvDen;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
int vol, outVol;
int sample;
unsigned char pan;
DivMacroInt std;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freqH(0),
freqL(0),
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
portaPauseFreq(0),
note(0),
ins(-1),
psgMode(1),
autoEnvNum(0),
autoEnvDen(0),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
portaPause(false),
inPorta(false),
furnacePCM(false),
hardReset(false),
vol(0),
outVol(15),
sample(-1),
pan(3) {}
};
Channel chan[16];
DivDispatchOscBuffer* oscBuf[16];
bool isMuted[16];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
bool addrOrVal;
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
std::queue<QueuedWrite> writes;
ymfm::ym2608* fm;
ymfm::ym2608::output_data fmout;
unsigned char regPool[512];
unsigned char lastBusy;
unsigned char* adpcmBMem;
size_t adpcmBMemLen;
DivYM2608Interface iface;
DivPlatformAY8910* ay;
unsigned char sampleBank;
unsigned char writeRSSOff, writeRSSOn;
int delay;
bool extMode;
short oldWrites[512];
short pendingWrites[512];
double NOTE_OPNB(int ch, int note);
double NOTE_ADPCMB(int note);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch);
void notifyInsChange(int ins);
void notifyInsDeletion(void* ins);
void setSkipRegisterWrites(bool val);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
const void* getSampleMem(int index);
size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformYM2608();
};
#endif

View File

@ -0,0 +1,42 @@
/**
* 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 "sound/ymfm/ymfm.h"
#include "ym2608.h"
#include "../engine.h"
#include "sound/rss.h"
uint8_t DivYM2608Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) {
switch (type) {
case ymfm::ACCESS_ADPCM_A:
return YM2608_ADPCM_ROM[address&0x1fff];
case ymfm::ACCESS_ADPCM_B:
if (adpcmBMem==NULL) {
return 0;
}
return adpcmBMem[address&0x3ffff];
default:
return 0;
}
return 0;
}
void DivYM2608Interface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) {
}

View File

@ -470,7 +470,7 @@ double DivPlatformYM2610::NOTE_ADPCMB(int note) {
void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) {
static int os[2];
ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine();
ymfm::ym2610::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine();
ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine();
@ -795,7 +795,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol);
}
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(s->offB>>8)&0xff);
@ -839,9 +839,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144);
chan[c.chan].freqChanged=true;
immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
immWrite(0x19,freq&0xff);
immWrite(0x1a,(freq>>8)&0xff);
}
break;
}
@ -997,6 +998,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==13 && !chan[c.chan].furnacePCM) break;
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
@ -1077,6 +1079,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (c.chan==13 && !chan[c.chan].furnacePCM) break;
chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value);
chan[c.chan].freqChanged=true;
break;

View File

@ -450,7 +450,7 @@ double DivPlatformYM2610B::NOTE_ADPCMB(int note) {
void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) {
static int os[2];
ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine();
ymfm::ym2610b::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine();
ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine();
@ -773,7 +773,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol);
}
chan[c.chan].sample=ins->amiga.initSample;
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(s->offB>>8)&0xff);
@ -817,9 +817,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144);
chan[c.chan].freqChanged=true;
immWrite(0x10,(s->isLoopable())?0x90:0x80); // start/repeat
int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0);
immWrite(0x19,freq&0xff);
immWrite(0x1a,(freq>>8)&0xff);
}
break;
}
@ -975,6 +976,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
break;
}
case DIV_CMD_PITCH: {
if (c.chan==15 && !chan[c.chan].furnacePCM) break;
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
@ -1055,6 +1057,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
iface.sampleBank=sampleBank;
break;
case DIV_CMD_LEGATO: {
if (c.chan==15 && !chan[c.chan].furnacePCM) break;
chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value);
chan[c.chan].freqChanged=true;
break;

View File

@ -0,0 +1,334 @@
/**
* 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 "zxbeeper.h"
#include "../engine.h"
#include <math.h>
#define CHIP_FREQBASE 8192*6
const char** DivPlatformZXBeeper::getRegisterSheet() {
return NULL;
}
const char* DivPlatformZXBeeper::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Change waveform";
break;
case 0x11:
return "11xx: Toggle noise mode";
break;
case 0x12:
return "12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)";
break;
case 0x13:
return "13xx: Set LFO speed";
break;
case 0x17:
return "17xx: Toggle PCM mode";
break;
}
return NULL;
}
void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t h=start; h<start+len; h++) {
// clock here
bool o=false;
unsigned short oldPos=chan[curChan].sPosition;
if (sOffTimer) {
sOffTimer--;
o=true;
}
chan[curChan].sPosition+=chan[curChan].freq;
if (oldPos>chan[curChan].sPosition) {
if (!isMuted[curChan] && chan[curChan].outVol) sOffTimer+=chan[curChan].duty;
}
if (++curChan>=6) curChan=0;
bufL[h]=o?16384:0;
}
}
void DivPlatformZXBeeper::tick(bool sysTick) {
for (int i=0; i<6; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&1)*MIN(1,chan[i].std.vol.val));
}
if (chan[i].std.duty.had) {
chan[i].duty=chan[i].std.duty.val;
chan[i].freqChanged=true;
}
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].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.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-2048,2048);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (chan[i].active) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].freq>65535) chan[i].freq=65535;
}
if (chan[i].keyOn) {
//rWrite(16+i*5,0x80);
//chWrite(i,0x04,0x80|chan[i].vol);
}
if (chan[i].keyOff) {
chan[i].freq=0;
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
}
}
int DivPlatformZXBeeper::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=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].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
chan[c.chan].pcm=false;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_STD_NOISE_MODE:
chan[c.chan].duty=c.value;
break;
case DIV_CMD_SAMPLE_MODE:
chan[c.chan].pcm=c.value;
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
sampleBank=parent->song.sample.size()/12;
}
break;
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 1;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformZXBeeper::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
}
void DivPlatformZXBeeper::forceIns() {
for (int i=0; i<6; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
}
}
void* DivPlatformZXBeeper::getChanState(int ch) {
return &chan[ch];
}
DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformZXBeeper::getRegisterPool() {
ulaOut=sOffTimer?0x10:0x08;
return &ulaOut;
}
int DivPlatformZXBeeper::getRegisterPoolSize() {
return 1;
}
void DivPlatformZXBeeper::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,128);
for (int i=0; i<6; i++) {
chan[i]=DivPlatformZXBeeper::Channel();
chan[i].std.setEngine(parent);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
lastPan=0xff;
memset(tempL,0,32*sizeof(int));
memset(tempR,0,32*sizeof(int));
cycles=0;
curChan=0;
sOffTimer=0;
sampleBank=0;
ulaOut=0;
}
bool DivPlatformZXBeeper::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformZXBeeper::notifyWaveChange(int wave) {
}
void DivPlatformZXBeeper::notifyInsDeletion(void* ins) {
for (int i=0; i<6; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformZXBeeper::setFlags(unsigned int flags) {
if (flags&1) { // technically there is no PAL PC Engine but oh well...
chipClock=COLOR_PAL*4.0/5.0;
} else {
chipClock=COLOR_NTSC;
}
rate=chipClock/4;
for (int i=0; i<6; i++) {
oscBuf[i]->rate=rate;
}
}
void DivPlatformZXBeeper::poke(unsigned int addr, unsigned short val) {
}
void DivPlatformZXBeeper::poke(std::vector<DivRegWrite>& wlist) {
}
int DivPlatformZXBeeper::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<6; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
reset();
return 6;
}
void DivPlatformZXBeeper::quit() {
for (int i=0; i<6; i++) {
delete oscBuf[i];
}
}
DivPlatformZXBeeper::~DivPlatformZXBeeper() {
}

View File

@ -0,0 +1,108 @@
/**
* 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 _ZXBEEPER_H
#define _ZXBEEPER_H
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
class DivPlatformZXBeeper: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, note;
int dacPeriod, dacRate;
unsigned int dacPos;
int dacSample, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
signed char vol, outVol;
unsigned short sPosition;
unsigned char duty;
DivMacroInt std;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
note(0),
dacPeriod(0),
dacRate(0),
dacPos(0),
dacSample(-1),
ins(-1),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
noise(false),
pcm(false),
furnaceDac(false),
vol(1),
outVol(1),
sPosition(0),
duty(64) {}
};
Channel chan[6];
DivDispatchOscBuffer* oscBuf[6];
bool isMuted[6];
struct QueuedWrite {
unsigned char addr;
unsigned char val;
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
};
std::queue<QueuedWrite> writes;
unsigned char lastPan, ulaOut;
int cycles, curChan, sOffTimer, delay;
int tempL[32];
int tempR[32];
unsigned char sampleBank, lfoMode, lfoSpeed;
unsigned char regPool[128];
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
void setFlags(unsigned int flags);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
~DivPlatformZXBeeper();
};
#endif

View File

@ -30,7 +30,7 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
void DivEngine::nextOrder() {
curRow=0;
if (repeatPattern) return;
if (++curOrder>=song.ordersLen) {
if (++curOrder>=curSubSong->ordersLen) {
endOfSong=true;
curOrder=0;
}
@ -181,6 +181,7 @@ const char* cmdName[]={
"ES5506_ENVELOPE_RVRAMP",
"ES5506_ENVELOPE_K1RAMP",
"ES5506_ENVELOPE_K2RAMP",
"ES5506_PAUSE",
"ALWAYS_SET_VOLUME"
};
@ -277,28 +278,55 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char
void DivEngine::processRow(int i, bool afterDelay) {
int whatOrder=afterDelay?chan[i].delayOrder:curOrder;
int whatRow=afterDelay?chan[i].delayRow:curRow;
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false);
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][whatOrder],false);
// pre effects
if (!afterDelay) for (int j=0; j<song.pat[i].effectCols; j++) {
short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)];
if (!afterDelay) {
bool returnAfterPre=false;
for (int j=0; j<curPat[i].effectCols; j++) {
short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)];
if (effectVal==-1) effectVal=0;
if (effect==0xed && effectVal!=0) {
if (effectVal<=nextSpeed) {
chan[i].rowDelay=effectVal+1;
chan[i].delayOrder=whatOrder;
chan[i].delayRow=whatRow;
if (effectVal==nextSpeed) {
//if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true;
} else {
chan[i].delayLocked=false;
}
return;
} else {
chan[i].delayLocked=false;
if (effectVal==-1) effectVal=0;
switch (effect) {
case 0x09: // speed 1
if (effectVal>0) speed1=effectVal;
break;
case 0x0f: // speed 2
if (effectVal>0) speed2=effectVal;
break;
case 0x0b: // change order
if (changeOrd==-1) {
changeOrd=effectVal;
changePos=0;
}
break;
case 0x0d: // next order
if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
changeOrd=-2;
changePos=effectVal;
}
break;
case 0xed: // delay
if (effectVal!=0) {
if (effectVal<=nextSpeed) {
chan[i].rowDelay=effectVal+1;
chan[i].delayOrder=whatOrder;
chan[i].delayRow=whatRow;
if (effectVal==nextSpeed) {
//if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true;
} else {
chan[i].delayLocked=false;
}
returnAfterPre=true;
} else {
chan[i].delayLocked=false;
}
}
break;
}
}
if (returnAfterPre) return;
}
if (chan[i].delayLocked) return;
@ -389,7 +417,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
bool panChanged=false;
// effects
for (int j=0; j<song.pat[i].effectCols; j++) {
for (int j=0; j<curPat[i].effectCols; j++) {
short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)];
@ -397,24 +425,6 @@ void DivEngine::processRow(int i, bool afterDelay) {
// per-system effect
if (!perSystemEffect(i,effect,effectVal)) switch (effect) {
case 0x09: // speed 1
if (effectVal>0) speed1=effectVal;
break;
case 0x0f: // speed 2
if (effectVal>0) speed2=effectVal;
break;
case 0x0b: // change order
if (changeOrd==-1) {
changeOrd=effectVal;
changePos=0;
}
break;
case 0x0d: // next order
if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) {
changeOrd=-2;
changePos=effectVal;
}
break;
case 0x08: // panning (split 4-bit)
chan[i].panL=(effectVal>>4)|(effectVal&0xf0);
chan[i].panR=(effectVal&15)|((effectVal&15)<<4);
@ -550,7 +560,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
break;
case 0xe0: // arp speed
if (effectVal>0) {
song.arpLen=effectVal;
curSubSong->arpLen=effectVal;
}
break;
case 0xe1: // portamento up
@ -730,7 +740,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
chan[i].noteOnInhibit=false;
// post effects
for (int j=0; j<song.pat[i].effectCols; j++) {
for (int j=0; j<curPat[i].effectCols; j++) {
short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)];
@ -748,10 +758,10 @@ void DivEngine::nextRow() {
strcpy(pb1,"");
strcpy(pb3,"");
for (int i=0; i<chans; i++) {
snprintf(pb,4095," %.2x",song.orders.ord[i][curOrder]);
snprintf(pb,4095," %.2x",curOrders->ord[i][curOrder]);
strcat(pb1,pb);
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false);
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][curOrder],false);
snprintf(pb2,4095,"\x1b[37m %s",
formatNote(pat->data[curRow][0],pat->data[curRow][1]));
strcat(pb3,pb2);
@ -767,7 +777,7 @@ void DivEngine::nextRow() {
snprintf(pb2,4095,"\x1b[0;36m%.2x",pat->data[curRow][2]);
strcat(pb3,pb2);
}
for (int j=0; j<song.pat[i].effectCols; j++) {
for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==-1) {
strcat(pb3,"\x1b[m--");
} else {
@ -799,32 +809,32 @@ void DivEngine::nextRow() {
if (changeOrd==-2) changeOrd=curOrder+1;
if (changeOrd<=curOrder) endOfSong=true;
curOrder=changeOrd;
if (curOrder>=song.ordersLen) {
if (curOrder>=curSubSong->ordersLen) {
curOrder=0;
endOfSong=true;
}
changeOrd=-1;
}
if (haltOn==DIV_HALT_PATTERN) halted=true;
} else if (playing) if (++curRow>=song.patLen) {
} else if (playing) if (++curRow>=curSubSong->patLen) {
nextOrder();
if (haltOn==DIV_HALT_PATTERN) halted=true;
}
if (song.brokenSpeedSel) {
if ((song.patLen&1) && curOrder&1) {
ticks=((curRow&1)?speed2:speed1)*(song.timeBase+1);
if ((curSubSong->patLen&1) && curOrder&1) {
ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1);
nextSpeed=(curRow&1)?speed1:speed2;
} else {
ticks=((curRow&1)?speed1:speed2)*(song.timeBase+1);
ticks=((curRow&1)?speed1:speed2)*(curSubSong->timeBase+1);
nextSpeed=(curRow&1)?speed2:speed1;
}
} else {
if (speedAB) {
ticks=speed2*(song.timeBase+1);
ticks=speed2*(curSubSong->timeBase+1);
nextSpeed=speed1;
} else {
ticks=speed1*(song.timeBase+1);
ticks=speed1*(curSubSong->timeBase+1);
nextSpeed=speed2;
}
speedAB=!speedAB;
@ -832,7 +842,7 @@ void DivEngine::nextRow() {
// post row details
for (int i=0; i<chans; i++) {
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false);
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][curOrder],false);
if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) {
if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) {
if (!chan[i].legato) {
@ -841,7 +851,7 @@ void DivEngine::nextRow() {
if (song.oneTickCut) {
bool doPrepareCut=true;
for (int j=0; j<song.pat[i].effectCols; j++) {
for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==0x03) {
doPrepareCut=false;
break;
@ -1010,7 +1020,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
}
if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) {
if (--chan[i].arpTicks<1) {
chan[i].arpTicks=song.arpLen;
chan[i].arpTicks=curSubSong->arpLen;
chan[i].arpStage++;
if (chan[i].arpStage>2) chan[i].arpStage=0;
switch (chan[i].arpStage) {
@ -1051,7 +1061,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
}
}
if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,song.ordersLen,curRow,song.patLen,cmdsPerSecond);
if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond);
}
if (haltOn==DIV_HALT_TICK) halted=true;
@ -1328,11 +1338,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
if (!freelance && stepPlay!=-1 && subticks==1) {
unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC);
if (realPos>=size) realPos=size-1;
if (song.hilightA>0) {
if ((curRow%song.hilightA)==0 && ticks==1) metroTick[realPos]=1;
if (curSubSong->hilightA>0) {
if ((curRow%curSubSong->hilightA)==0 && ticks==1) metroTick[realPos]=1;
}
if (song.hilightB>0) {
if ((curRow%song.hilightB)==0 && ticks==1) metroTick[realPos]=2;
if (curSubSong->hilightB>0) {
if ((curRow%curSubSong->hilightB)==0 && ticks==1) metroTick[realPos]=2;
}
}
if (nextTick()) {

View File

@ -97,6 +97,18 @@ bool DivSample::initInternal(DivSampleDepth d, int count) {
dataDPCM=new unsigned char[lengthDPCM];
memset(dataDPCM,0,lengthDPCM);
break;
case DIV_SAMPLE_DEPTH_AICA_ADPCM: // AICA ADPCM
if (dataAICA!=NULL) delete[] dataAICA;
lengthAICA=(count+1)/2;
dataAICA=new unsigned char[(lengthAICA+255)&(~0xff)];
memset(dataAICA,0,(lengthAICA+255)&(~0xff));
break;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM
if (dataZ!=NULL) delete[] dataZ;
lengthZ=(count+1)/2;
dataZ=new unsigned char[(lengthZ+255)&(~0xff)];
memset(dataZ,0,(lengthZ+255)&(~0xff));
break;
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM
if (dataQSoundA!=NULL) delete[] dataQSoundA;
lengthQSoundA=(count+1)/2;
@ -666,6 +678,12 @@ void DivSample::render() {
}
break;
}
case DIV_SAMPLE_DEPTH_AICA_ADPCM: // AICA ADPCM
aica_decode(dataAICA,data16,samples);
break;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM
ymz_decode(dataZ,data16,samples);
break;
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM
bs_decode(dataQSoundA,data16,samples);
break;
@ -718,6 +736,14 @@ void DivSample::render() {
if (accum>127) accum=127;
}
}
if (depth!=DIV_SAMPLE_DEPTH_AICA_ADPCM) { // AICA ADPCM
if (!initInternal(DIV_SAMPLE_DEPTH_AICA_ADPCM,samples)) return;
aica_encode(data16,dataAICA,(samples+511)&(~0x1ff));
}
if (depth!=DIV_SAMPLE_DEPTH_YMZ_ADPCM) { // YMZ ADPCM
if (!initInternal(DIV_SAMPLE_DEPTH_YMZ_ADPCM,samples)) return;
ymz_encode(data16,dataZ,(samples+511)&(~0x1ff));
}
if (depth!=DIV_SAMPLE_DEPTH_QSOUND_ADPCM) { // QSound ADPCM
if (!initInternal(DIV_SAMPLE_DEPTH_QSOUND_ADPCM,samples)) return;
bs_encode(data16,dataQSoundA,samples);
@ -754,6 +780,10 @@ void* DivSample::getCurBuf() {
return data1;
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
return dataDPCM;
case DIV_SAMPLE_DEPTH_AICA_ADPCM:
return dataAICA;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
return dataZ;
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
return dataQSoundA;
case DIV_SAMPLE_DEPTH_ADPCM_A:
@ -782,6 +812,10 @@ unsigned int DivSample::getCurBufLen() {
return length1;
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
return lengthDPCM;
case DIV_SAMPLE_DEPTH_AICA_ADPCM:
return lengthAICA;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
return lengthZ;
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
return lengthQSoundA;
case DIV_SAMPLE_DEPTH_ADPCM_A:
@ -896,6 +930,8 @@ DivSample::~DivSample() {
if (data16) delete[] data16;
if (data1) delete[] data1;
if (dataDPCM) delete[] dataDPCM;
if (dataAICA) delete[] dataAICA;
if (dataZ) delete[] dataZ;
if (dataQSoundA) delete[] dataQSoundA;
if (dataA) delete[] dataA;
if (dataB) delete[] dataB;

View File

@ -38,6 +38,8 @@ enum DivResampleFilters {
enum DivSampleDepth: unsigned char {
DIV_SAMPLE_DEPTH_1BIT=0,
DIV_SAMPLE_DEPTH_1BIT_DPCM=1,
DIV_SAMPLE_DEPTH_AICA_ADPCM=2,
DIV_SAMPLE_DEPTH_YMZ_ADPCM=3,
DIV_SAMPLE_DEPTH_QSOUND_ADPCM=4,
DIV_SAMPLE_DEPTH_ADPCM_A=5,
DIV_SAMPLE_DEPTH_ADPCM_B=6,
@ -103,6 +105,8 @@ struct DivSample {
// valid values are:
// - 0: ZX Spectrum overlay drum (1-bit)
// - 1: 1-bit NES DPCM (1-bit)
// - 2: AICA ADPCM
// - 3: YMZ ADPCM
// - 4: QSound ADPCM
// - 5: ADPCM-A
// - 6: ADPCM-B
@ -118,6 +122,8 @@ struct DivSample {
short* data16; // 16
unsigned char* data1; // 0
unsigned char* dataDPCM; // 1
unsigned char* dataAICA; // 2
unsigned char* dataZ; // 3
unsigned char* dataQSoundA; // 4
unsigned char* dataA; // 5
unsigned char* dataB; // 6
@ -125,8 +131,8 @@ struct DivSample {
unsigned char* dataBRR; // 9
unsigned char* dataVOX; // 10
unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX;
unsigned int length8, length16, length1, lengthDPCM, lengthAICA, lengthZ, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offAICA, offZ, offQSoundA, offA, offB, offX68, offBRR, offVOX;
unsigned int offSegaPCM, offQSound, offX1_010, offES5506, offSU;
unsigned int samples;
@ -267,6 +273,8 @@ struct DivSample {
data16(NULL),
data1(NULL),
dataDPCM(NULL),
dataAICA(NULL),
dataZ(NULL),
dataQSoundA(NULL),
dataA(NULL),
dataB(NULL),
@ -277,6 +285,8 @@ struct DivSample {
length16(0),
length1(0),
lengthDPCM(0),
lengthAICA(0),
lengthZ(0),
lengthQSoundA(0),
lengthA(0),
lengthB(0),
@ -287,6 +297,8 @@ struct DivSample {
off16(0),
off1(0),
offDPCM(0),
offAICA(0),
offZ(0),
offQSoundA(0),
offA(0),
offB(0),

View File

@ -19,7 +19,7 @@
#include "song.h"
void DivSong::clearSongData() {
void DivSubSong::clearData() {
for (int i=0; i<DIV_MAX_CHANS; i++) {
pat[i].wipePatterns();
}
@ -28,6 +28,15 @@ void DivSong::clearSongData() {
ordersLen=1;
}
void DivSong::clearSongData() {
for (DivSubSong* i: subsong) {
i->clearData();
delete i;
}
subsong.clear();
subsong.push_back(new DivSubSong);
}
void DivSong::clearInstruments() {
for (DivInstrument* i: ins) {
delete i;
@ -71,7 +80,9 @@ void DivSong::unload() {
sample.clear();
sampleLen=0;
for (int i=0; i<DIV_MAX_CHANS; i++) {
pat[i].wipePatterns();
for (DivSubSong* i: subsong) {
i->clearData();
delete i;
}
subsong.clear();
}

View File

@ -65,7 +65,9 @@ enum DivSystem {
DIV_SYSTEM_MMC5,
DIV_SYSTEM_N163,
DIV_SYSTEM_OPN,
DIV_SYSTEM_OPN_EXT,
DIV_SYSTEM_PC98,
DIV_SYSTEM_PC98_EXT,
DIV_SYSTEM_OPL,
DIV_SYSTEM_OPL2,
DIV_SYSTEM_OPL3,
@ -109,6 +111,44 @@ enum DivSystem {
DIV_SYSTEM_MAX // boundary for max system number
};
struct DivSubSong {
String name, notes;
unsigned char hilightA, hilightB;
unsigned char timeBase, speed1, speed2, arpLen;
bool pal;
bool customTempo;
float hz;
int patLen, ordersLen;
DivOrders orders;
DivChannelData pat[DIV_MAX_CHANS];
bool chanShow[DIV_MAX_CHANS];
unsigned char chanCollapse[DIV_MAX_CHANS];
String chanName[DIV_MAX_CHANS];
String chanShortName[DIV_MAX_CHANS];
void clearData();
DivSubSong():
hilightA(4),
hilightB(16),
timeBase(0),
speed1(6),
speed2(6),
arpLen(1),
pal(true),
customTempo(false),
hz(60.0),
patLen(64),
ordersLen(1) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
chanShow[i]=true;
chanCollapse[i]=0;
}
}
};
struct DivSong {
// version number used for saving the song.
// Furnace will save using the latest possible version,
@ -155,14 +195,21 @@ struct DivSong {
// - introduces Genesis system
// - introduces system number
// - patterns now stored in current known format
// - 8: ???
// - only used in the Medivo YMU cover
// - 7: ???
// - only present in a later version of First.dmf
// - pattern format changes: empty field is 0xFF instead of 0x80
// - instrument now stored in pattern
// - 5: BETA 3
// - adds arpeggio tick
// - 4: BETA 2
// - possibly adds instrument number (stored in channel)?
// - cannot confirm as I don't have any version 4 modules
// - 3: BETA 1
// - possibly the first version that could save
// - basic format, no system number, 16 instruments, one speed, YMU759-only
// - patterns were stored in a different format (chars instead of shorts)
// - patterns were stored in a different format (chars instead of shorts) and no instrument
// - if somebody manages to find a version 2 or even 1 module, please tell me as it will be worth more than a luxury vehicle
unsigned short version;
bool isDMF;
@ -216,13 +263,19 @@ struct DivSong {
// - 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)
// - bit 4-5: chip type (ignored on AY8930)
// - 0: AY-3-8910 or similar
// - 1: YM2149
// - 2: Sunsoft 5B
// - bit 6: stereo
// - 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é, Game Blaster)
@ -284,19 +337,10 @@ struct DivSong {
String nameJ, authorJ, categoryJ;
// other things
String chanName[DIV_MAX_CHANS];
String chanShortName[DIV_MAX_CHANS];
String notes;
// highlight
unsigned char hilightA, hilightB;
// module details
unsigned char timeBase, speed1, speed2, arpLen;
bool pal;
bool customTempo;
float hz;
int patLen, ordersLen, insLen, waveLen, sampleLen;
int insLen, waveLen, sampleLen;
float masterVol;
float tuning;
@ -344,14 +388,11 @@ struct DivSong {
bool snDutyReset;
bool pitchMacroIsLinear;
DivOrders orders;
std::vector<DivInstrument*> ins;
DivChannelData pat[DIV_MAX_CHANS];
std::vector<DivWavetable*> wave;
std::vector<DivSample*> sample;
bool chanShow[DIV_MAX_CHANS];
unsigned char chanCollapse[DIV_MAX_CHANS];
std::vector<DivSubSong*> subsong;
DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound;
DivWavetable nullWave;
@ -377,7 +418,6 @@ struct DivSong {
*/
void clearSamples();
/**
* unloads the song, freeing all memory associated with it.
* use before destroying the object.
@ -401,17 +441,6 @@ struct DivSong {
manInfo(""),
createdDate(""),
revisionDate(""),
hilightA(4),
hilightB(16),
timeBase(0),
speed1(6),
speed2(6),
arpLen(1),
pal(true),
customTempo(false),
hz(60.0),
patLen(64),
ordersLen(1),
insLen(0),
waveLen(0),
sampleLen(0),
@ -457,10 +486,7 @@ struct DivSong {
systemPan[i]=0;
systemFlags[i]=0;
}
for (int i=0; i<DIV_MAX_CHANS; i++) {
chanShow[i]=true;
chanCollapse[i]=0;
}
subsong.push_back(new DivSubSong);
system[0]=DIV_SYSTEM_YM2612;
system[1]=DIV_SYSTEM_SMS;

View File

@ -283,7 +283,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) {
const char* DivEngine::getChannelName(int chan) {
if (chan<0 || chan>chans) return "??";
if (!song.chanName[chan].empty()) return song.chanName[chan].c_str();
if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str();
if (sysDefs[sysOfChan[chan]]==NULL) return "??";
const char* ret=sysDefs[sysOfChan[chan]]->chanNames[dispatchChanOfChan[chan]];
@ -293,7 +293,7 @@ const char* DivEngine::getChannelName(int chan) {
const char* DivEngine::getChannelShortName(int chan) {
if (chan<0 || chan>chans) return "??";
if (!song.chanShortName[chan].empty()) return song.chanShortName[chan].c_str();
if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str();
if (sysDefs[sysOfChan[chan]]==NULL) return "??";
const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]];
@ -734,6 +734,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YMU759]=new DivSysDef(
"Yamaha YMU759 (MA-2)", NULL, 0x01, 0x01, 17, true, false, 0, false,
"a chip which found its way inside mobile phones in the 2000's.\nas proprietary as it is, it passed away after losing to MP3 in the mobile hardware battle.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM" }, // name
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM" }, // short
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM }, // type
@ -742,16 +743,19 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_GENESIS]=new DivSysDef(
"Sega Genesis/Mega Drive", "セガメガドライブ", 0x02, 0x02, 10, true, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_GENESIS_EXT]=new DivSysDef(
"Sega Genesis Extended Channel 3", NULL, 0x42, 0x42, 13, true, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_SMS]=new DivSysDef(
"TI SN76489", NULL, 0x03, 0x03, 4, false, true, 0x150, false,
"a square/noise sound chip found on the Sega Master System, ColecoVision, Tandy, TI's own 99/4A and a few other places.",
{"Square 1", "Square 2", "Square 3", "Noise"},
{"S1", "S2", "S3", "NO"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE},
@ -771,11 +775,13 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SMS_OPLL]=new DivSysDef(
"Sega Master System + FM Expansion", NULL, 0x43, 0x43, 13, true, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_GB]=new DivSysDef(
"Game Boy", NULL, 0x04, 0x04, 4, false, true, 0x161, false,
"the most popular portable game console of the era.",
{"Pulse 1", "Pulse 2", "Wavetable", "Noise"},
{"S1", "S2", "WA", "NO"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE},
@ -804,6 +810,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_PCE]=new DivSysDef(
"PC Engine/TurboGrafx-16", NULL, 0x05, 0x05, 6, false, true, 0x161, false,
"an '80's game console with a wavetable sound chip, popular in Japan.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -835,6 +842,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_NES]=new DivSysDef(
"NES (Ricoh 2A03)", NULL, 0x06, 0x06, 5, false, true, 0x161, false,
"also known as Famicom in Japan, it's the most well-known game console of the '80's.",
{"Pulse 1", "Pulse 2", "Triangle", "Noise", "PCM"},
{"S1", "S2", "TR", "NO", "PCM"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE, DIV_CH_NOISE, DIV_CH_PCM},
@ -866,16 +874,19 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_NES_VRC7]=new DivSysDef(
"NES + Konami VRC7", NULL, 0x46, 0x46, 11, true, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_NES_FDS]=new DivSysDef(
"Famicom Disk System", NULL, 0, 0x86, 6, false, true, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_C64_6581]=new DivSysDef(
"Commodore 64 (6581)", NULL, 0x47, 0x47, 3, false, true, 0, false,
"this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.",
{"Channel 1", "Channel 2", "Channel 3"},
{"CH1", "CH2", "CH3"},
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -887,6 +898,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_C64_8580]=new DivSysDef(
"Commodore 64 (8580)", NULL, 0x07, 0x07, 3, false, true, 0, false,
"this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.\nthis is the newer revision of the chip.",
{"Channel 1", "Channel 2", "Channel 3"},
{"CH1", "CH2", "CH3"},
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -898,6 +910,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_ARCADE]=new DivSysDef(
"DefleCade", NULL, 0x08, 0x08, 13, true, false, 0, true,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
@ -914,6 +927,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610]=new DivSysDef(
"Neo Geo CD", NULL, 0x09, 0x09, 13, true, true, 0x151, false,
"like Neo Geo, but lacking the ADPCM-B channel since they couldn't connect the pins.",
{"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"},
{"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -925,6 +939,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610_EXT]=new DivSysDef(
"Neo Geo CD Extended Channel 2", NULL, 0x49, 0x49, 16, true, true, 0x151, false,
"like Neo Geo, but lacking the ADPCM-B channel since they couldn't connect the pins.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"},
{"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"},
{DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -936,6 +951,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_AY8910]=new DivSysDef(
"AY-3-8910", NULL, 0x80, 0, 3, false, true, 0x151, false,
"this chip is everywhere! ZX Spectrum, MSX, Amstrad CPC, Intellivision, Vectrex...\nthe discovery of envelope bass helped it beat the SN76489 with ease.",
{"PSG 1", "PSG 2", "PSG 3"},
{"S1", "S2", "S3"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
@ -947,6 +963,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_AMIGA]=new DivSysDef(
"Amiga", NULL, 0x81, 0, 4, false, true, 0, false,
"a computer from the '80's with full sampling capabilities, giving it a sound ahead of its time.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -973,6 +990,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2151]=new DivSysDef(
"Yamaha YM2151 (OPM)", NULL, 0x82, 0, 8, true, false, 0x150, false,
"this was Yamaha's first integrated FM chip.\nit was used in several synthesizers, computers and arcade boards.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -998,6 +1016,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2612]=new DivSysDef(
"Yamaha YM2612 (OPN2)", NULL, 0x83, 0, 6, true, false, 0x150, false,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "F3", "F4", "F5", "F6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1009,6 +1028,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_TIA]=new DivSysDef(
"Atari 2600", NULL, 0x84, 0, 2, false, true, 0, false,
"it's a challenge to make music on this chip which barely has musical capabilities...",
{"Channel 1", "Channel 2"},
{"CH1", "CH2"},
{DIV_CH_WAVE, DIV_CH_WAVE},
@ -1029,6 +1049,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SAA1099]=new DivSysDef(
"Philips SAA1099", NULL, 0x97, 0, 6, false, true, 0x171, false,
"supposedly an upgrade from the AY-3-8910, this was present on the Creative Music System (Game Blaster) and SAM Coupé.",
{"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6"},
{"S1", "S2", "S3", "S4", "S5", "S6"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
@ -1055,6 +1076,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_AY8930]=new DivSysDef(
"Microchip AY8930", NULL, 0x9a, 0, 3, false, true, 0x151, false,
"an improved version of the AY-3-8910 with a bigger frequency range, duty cycles, configurable noise and per-channel envelopes!",
{"PSG 1", "PSG 2", "PSG 3"},
{"S1", "S2", "S3"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
@ -1077,6 +1099,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VIC20]=new DivSysDef(
"Commodore VIC-20", NULL, 0x85, 0, 4, false, true, 0, false,
"Commodore's successor to the PET.\nits square wave channels are more than just square...",
{"Low", "Mid", "High", "Noise"},
{"LO", "MID", "HI", "NO"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE},
@ -1087,6 +1110,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_PET]=new DivSysDef(
"Commodore PET", NULL, 0x86, 0, 1, false, true, 0, false,
"one channel of 1-bit wavetable which is better (and worse) than the PC Speaker.",
{"Wave"},
{"PET"},
{DIV_CH_PULSE},
@ -1097,6 +1121,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SNES]=new DivSysDef(
"SNES", NULL, 0x87, 0, 8, false, true, 0, false,
"FM? nah... samples! Nintendo's answer to Sega.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1105,6 +1130,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VRC6]=new DivSysDef(
"Konami VRC6", NULL, 0x88, 0, 3, false, true, 0, false,
"an expansion chip for the Famicom, featuring a quirky sawtooth channel.",
{"VRC6 1", "VRC6 2", "VRC6 Saw"},
{"V1", "V2", "VS"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_WAVE},
@ -1138,6 +1164,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPLL]=new DivSysDef(
"Yamaha YM2413 (OPLL)", NULL, 0x89, 0, 9, true, false, 0x150, false,
"cost-reduced version of the OPL with 16 patches and only one of them is user-configurable.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1149,6 +1176,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_FDS]=new DivSysDef(
"Famicom Disk System (chip)", NULL, 0x8a, 0, 1, false, true, 0x161, false,
"a disk drive for the Famicom which also contains one wavetable channel.",
{"FDS"},
{"FDS"},
{DIV_CH_WAVE},
@ -1183,6 +1211,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_MMC5]=new DivSysDef(
"MMC5", NULL, 0x8b, 0, 3, false, true, 0, false,
"an expansion chip for the Famicom, featuring a little-known PCM channel.",
{"Pulse 1", "Pulse 2", "PCM"},
{"S1", "S2", "PCM"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM},
@ -1205,6 +1234,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_N163]=new DivSysDef(
"Namco 163", NULL, 0x8c, 0, 8, false, true, 0, false,
"an expansion chip for the Famicom, with full wavetable.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1259,23 +1289,56 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPN]=new DivSysDef(
"Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0, false,
"Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, true, 0x151, false,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)",
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"},
{"F1", "F2", "F3", "S1", "S2", "S3"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
{},
fmHardResetEffectHandler,
fmPostEffectHandler
);
sysDefs[DIV_SYSTEM_OPN_EXT]=new DivSysDef(
"Yamaha YM2203 (OPN) Extended Channel 3", NULL, 0xb6, 0, 9, true, true, 0x151, false,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "PSG 1", "PSG 2", "PSG 3"},
{"F1", "F2", "O1", "O2", "O3", "O4", "S1", "S2", "S3"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY},
{},
fmHardResetEffectHandler,
fmPostEffectHandler
);
sysDefs[DIV_SYSTEM_PC98]=new DivSysDef(
"Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, false, 0, false,
"Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, true, 0, false,
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA},
{},
fmHardResetEffectHandler,
fmPostEffectHandler
);
sysDefs[DIV_SYSTEM_PC98_EXT]=new DivSysDef(
"Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0, false,
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA},
{},
fmHardResetEffectHandler,
fmPostEffectHandler
);
sysDefs[DIV_SYSTEM_OPL]=new DivSysDef(
"Yamaha YM3526 (OPL)", NULL, 0x8f, 0, 9, true, false, 0x151, false,
"OPN, but what if you only had two operators, no stereo, no detune and a lower ADSR parameter range?",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1287,6 +1350,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL2]=new DivSysDef(
"Yamaha YM3812 (OPL2)", NULL, 0x90, 0, 9, true, false, 0x151, false,
"OPL, but what if you had more waveforms to choose than the normal sine?",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1298,6 +1362,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL3]=new DivSysDef(
"Yamaha YMF262 (OPL3)", NULL, 0x91, 0, 18, true, false, 0x151, false,
"OPL2, but what if you had twice the channels, 4-op mode, stereo and even more waveforms?",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"},
{DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1309,6 +1374,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef(
"MultiPCM", NULL, 0x92, 0, 28, false, true, 0, false,
"how many channels of PCM do you want?\nMultiPCM: yes",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1317,14 +1383,16 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_PCSPKR]=new DivSysDef(
"PC Speaker", NULL, 0x93, 0, 1, false, true, 0, false,
"good luck!",
{"Square"},
{"SQ"},
{DIV_CH_PULSE},
{DIV_INS_STD}
{DIV_INS_BEEPER}
);
sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef(
"POKEY", NULL, 0x94, 0, 4, false, true, 0, false,
"TIA, but better and more flexible.\nused in the Atari 8-bit family of computers (400/800/XL/XE).",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1333,6 +1401,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef(
"Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0, false,
"this is like SNES' sound chip but without interpolation and the rest of nice bits.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1341,6 +1410,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SWAN]=new DivSysDef(
"WonderSwan", NULL, 0x96, 0, 4, false, true, 0x171, false,
"developed by the same team under the Game Boy and the Virtual Boy...",
{"Wave", "Wave/PCM", "Wave", "Wave/Noise"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_WAVE, DIV_CH_PCM, DIV_CH_WAVE, DIV_CH_NOISE},
@ -1372,6 +1442,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPZ]=new DivSysDef(
"Yamaha YM2414 (OPZ)", NULL, 0x98, 0, 8, true, false, 0, false,
"like OPM, but with more waveforms, fixed frequency mode and totally... undocumented.\nused in the Yamaha TX81Z and some other synthesizers.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1392,6 +1463,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_POKEMINI]=new DivSysDef(
"Pokémon Mini", NULL, 0x99, 0, 1, false, true, 0, false,
"this one is like PC Speaker but has duty cycles.",
{"Square"},
{"SQ"},
{DIV_CH_PULSE},
@ -1400,6 +1472,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SEGAPCM]=new DivSysDef(
"SegaPCM", NULL, 0x9b, 0, 16, false, true, 0x151, false,
"used in some Sega arcade boards (like OutRun), and usually paired with a YM2151.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1411,6 +1484,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VBOY]=new DivSysDef(
"Virtual Boy", NULL, 0x9c, 0, 6, false, true, 0, false,
"a console which failed to sell well due to its headache-inducing features.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "NO"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_NOISE},
@ -1419,6 +1493,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef(
"Konami VRC7", NULL, 0x9d, 0, 6, true, false, 0x151, false,
"like OPLL, but even more cost reductions applied. three less FM channels, and no drums mode...",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "F3", "F4", "F5", "F6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1430,6 +1505,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610B]=new DivSysDef(
"Yamaha YM2610B (OPNB-B)", NULL, 0x9e, 0, 16, true, false, 0x151, false,
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1441,6 +1517,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SFX_BEEPER]=new DivSysDef(
"ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false,
"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"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1449,6 +1526,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2612_EXT]=new DivSysDef(
"Yamaha YM2612 (OPN2) Extended Channel 3", NULL, 0xa0, 0, 9, true, false, 0x150, false,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},
@ -1459,11 +1537,14 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SCC]=new DivSysDef(
"Konami SCC", NULL, 0xa1, 0, 5, false, true, 0, false,
"Konami SCC", NULL, 0xa1, 0, 5, false, true, 0x161, false,
"a wavetable chip made by Konami for use with the MSX.\nthe last channel shares its wavetable with the previous one though.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"CH1", "CH2", "CH3", "CH4", "CH5"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC},
{},
waveOnlyEffectHandler
);
auto oplDrumsEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool {
@ -1482,6 +1563,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL_DRUMS]=new DivSysDef(
"Yamaha YM3526 (OPL) with drums", NULL, 0xa2, 0, 11, true, false, 0x151, false,
"the OPL chip but with drums mode enabled.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1493,6 +1575,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL2_DRUMS]=new DivSysDef(
"Yamaha YM3812 (OPL2) with drums", NULL, 0xa3, 0, 11, true, false, 0x151, false,
"the OPL2 chip but with drums mode enabled.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1504,6 +1587,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL3_DRUMS]=new DivSysDef(
"Yamaha YMF262 (OPL3) with drums", NULL, 0xa4, 0, 20, true, false, 0x151, false,
"the OPL3 chip but with drums mode enabled.",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"},
{DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1515,6 +1599,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610_FULL]=new DivSysDef(
"Yamaha YM2610 (OPNB)", NULL, 0xa5, 0, 14, true, false, 0x151, false,
"this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.",
{"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1526,6 +1611,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610_FULL_EXT]=new DivSysDef(
"Yamaha YM2610 (OPNB) Extended Channel 2", NULL, 0xa6, 0, 17, true, false, 0x151, false,
"this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1537,6 +1623,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPLL_DRUMS]=new DivSysDef(
"Yamaha YM2413 (OPLL) with drums", NULL, 0xa7, 0, 11, true, false, 0x150, false,
"the OPLL chips but with drums mode turned on.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1548,6 +1635,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_LYNX]=new DivSysDef(
"Atari Lynx", NULL, 0xa8, 0, 4, false, true, 0, false,
"a portable console made by Atari. it has all of Atari's trademark waveforms.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1566,6 +1654,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_QSOUND]=new DivSysDef(
"Capcom QSound", NULL, 0xe0, 0, 19, false, true, 0x161, false,
"used in some of Capcom's arcade boards. surround-like sampled sound with echo.",
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1596,6 +1685,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VERA]=new DivSysDef(
"VERA", NULL, 0xac, 0, 17, false, true, 0, false,
"the chip used in a computer design created by The 8-Bit Guy.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM},
@ -1618,6 +1708,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_YM2610B_EXT]=new DivSysDef(
"Yamaha YM2610B (OPNB-B) Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false,
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1629,6 +1720,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SEGAPCM_COMPAT]=new DivSysDef(
"SegaPCM (compatible 5-channel mode)", NULL, 0xa9, 0, 5, false, true, 0x151, false,
"this is the same thing as SegaPCM, but only exposes 5 of the channels for compatibility with DefleMask.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"P1", "P2", "P3", "P4", "P5"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1640,6 +1732,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_X1_010]=new DivSysDef(
"Seta/Allumer X1-010", NULL, 0xb0, 0, 16, false, true, 0x171, false,
"a sound chip used in several Seta/Allumer-manufactured arcade boards with too many channels of wavetable sound, which also are capable of sampled sound.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
@ -1690,6 +1783,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_BUBSYS_WSG]=new DivSysDef(
"Konami Bubble System WSG", NULL, 0xad, 0, 2, false, true, 0, false,
"this is the wavetable part of the Bubble System, which also had two AY-3-8910s.",
{"Channel 1", "Channel 2"},
{"CH1", "CH2"},
{DIV_CH_WAVE, DIV_CH_WAVE},
@ -1701,6 +1795,7 @@ void DivEngine::registerSystems() {
// to Grauw: feel free to change this to 24 during development of OPL4's PCM part.
sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef(
"Yamaha OPL4", NULL, 0xae, 0, 42, true, true, 0, false,
"like OPL3, but this time it also has a 24-channel version of MultiPCM.",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"},
{DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1709,6 +1804,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_OPL4_DRUMS]=new DivSysDef(
"Yamaha OPL4 with drums", NULL, 0xaf, 0, 44, true, true, 0, false,
"the OPL4 but with drums mode turned on.",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"},
{DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1717,6 +1813,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef(
"Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0/*0x171*/, false,
"a sample chip used in the Ensoniq's unique transwave synthesizers, \nand SoundScape series PC ISA soundcards...\nthat's just yet another (partially)SB compatible one with emulated OPL3 and MIDI ROMpler.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28", "Channel 29", "Channel 30", "Channel 31", "Channel 32"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1742,14 +1839,6 @@ void DivEngine::registerSystems() {
case 0x17: // filter coefficient K2, 8 bit MSB
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2,ch,(effectVal&0xff)<<8,0xff00));
break;
case 0x18: // filter coefficient K1 slide up
case 0x19: // filter coefficient K1 slide down
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1_SLIDE,ch,effectVal,effect&0x01));
break;
case 0x1a: // filter coefficient K2 slide up
case 0x1b: // filter coefficient K2 slide down
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2_SLIDE,ch,effectVal,effect&0x01));
break;
case 0x20:
case 0x21: // envelope ECOUNT
dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_COUNT,ch,((effect&0x01)<<8)|effectVal));
@ -1779,35 +1868,66 @@ void DivEngine::registerSystems() {
break;
}
return true;
},
[this](int ch, unsigned char effect, unsigned char effectVal) -> bool {
switch (effect) {
case 0x12: // pause
dispatchCmd(DivCommand(DIV_CMD_ES5506_PAUSE,ch,effectVal&1));
break;
case 0x18: // filter coefficient K1 slide up
case 0x19: // filter coefficient K1 slide down
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1_SLIDE,ch,effectVal,effect&0x01));
break;
case 0x1a: // filter coefficient K2 slide up
case 0x1b: // filter coefficient K2 slide down
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2_SLIDE,ch,effectVal,effect&0x01));
break;
default:
return false;
break;
}
return true;
}
);
sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef(
"Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0, false,
"like OPL but with an ADPCM channel.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM},
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA}
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA},
{},
oplEffectHandler,
fmOPLPostEffectHandler
);
sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef(
"Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0, false,
"the Y8950 chip, in drums mode.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA}
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA},
{},
oplEffectHandler,
fmOPLPostEffectHandler
);
sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef(
"Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0, false,
"Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0x161, false,
"this is a variant of Konami's SCC chip with the last channel's wavetable being independent.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"CH1", "CH2", "CH3", "CH4", "CH5"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}
{DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC},
{},
waveOnlyEffectHandler
);
sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef(
"tildearrow Sound Unit", NULL, 0xb5, 0, 8, false, true, 0, false,
"tildearrow's fantasy sound chip. put SID, AY and VERA in a blender, and you get this!",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
@ -1817,6 +1937,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_MSM6295]=new DivSysDef(
"OKI MSM6295", NULL, 0xaa, 0, 4, false, true, 0, false,
"an ADPCM sound chip manufactured by OKI and used in many arcade boards.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
@ -1825,6 +1946,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_MSM6258]=new DivSysDef(
"OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0, false,
"an ADPCM sound chip manufactured by OKI and used in the Sharp X68000.",
{"Sample"},
{"PCM"},
{DIV_CH_PCM},
@ -1833,6 +1955,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false,
"this is a system designed for testing purposes..",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},

View File

@ -564,6 +564,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break;
}
break;
case DIV_SYSTEM_OPN:
w->writeC(5|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
@ -637,6 +642,71 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break;
}
break;
case DIV_SYSTEM_SCC:
if (write.addr<0x80) {
w->writeC(0xd2);
w->writeC(baseAddr2|0);
w->writeC(write.addr&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0x8a) {
w->writeC(0xd2);
w->writeC(baseAddr2|1);
w->writeC((write.addr-0x80)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0x8f) {
w->writeC(0xd2);
w->writeC(baseAddr2|2);
w->writeC((write.addr-0x8a)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0x90) {
w->writeC(0xd2);
w->writeC(baseAddr2|3);
w->writeC((write.addr-0x8f)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr>=0xe0) {
w->writeC(0xd2);
w->writeC(baseAddr2|5);
w->writeC((write.addr-0xe0)&0x7f);
w->writeC(write.val&0xff);
} else {
logW("SCC: writing to unmapped address %.2x!",write.addr);
}
break;
case DIV_SYSTEM_SCC_PLUS:
if (write.addr<0x80) {
w->writeC(0xd2);
w->writeC(baseAddr2|0);
w->writeC(write.addr&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0xa0) {
w->writeC(0xd2);
w->writeC(baseAddr2|4);
w->writeC(write.addr);
w->writeC(write.val&0xff);
} else if (write.addr<0xaa) {
w->writeC(0xd2);
w->writeC(baseAddr2|1);
w->writeC((write.addr-0xa0)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0xaf) {
w->writeC(0xd2);
w->writeC(baseAddr2|2);
w->writeC((write.addr-0xaa)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr<0xb0) {
w->writeC(0xd2);
w->writeC(baseAddr2|3);
w->writeC((write.addr-0xaf)&0x7f);
w->writeC(write.val&0xff);
} else if (write.addr>=0xe0) {
w->writeC(0xd2);
w->writeC(baseAddr2|5);
w->writeC((write.addr-0xe0)&0x7f);
w->writeC(write.val&0xff);
} else {
logW("SCC+: writing to unmapped address %.2x!",write.addr);
}
break;
default:
logW("write not handled!");
break;
@ -876,11 +946,41 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
}
break;
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
case DIV_SYSTEM_AY8930: {
if (!hasAY) {
bool hasClockDivider=false; // Configurable clock divider
bool hasStereo=true; // Stereo
hasAY=disCont[i].dispatch->chipClock;
ayConfig=(song.system[i]==DIV_SYSTEM_AY8930)?3:0;
ayFlags=1;
if (song.system[i]==DIV_SYSTEM_AY8930) { // AY8930
ayConfig=0x03;
hasClockDivider=true;
} else {
switch ((song.systemFlags[i]>>4)&3) {
default:
case 0: // AY8910
ayConfig=0x00;
break;
case 1: // YM2149
ayConfig=0x10;
hasClockDivider=true;
break;
case 2: // Sunsoft 5B
ayConfig=0x10;
ayFlags|=0x12; // Clock internally divided, Single sound output
hasStereo=false; // due to above, can't be per-channel stereo configurable
break;
case 3: // AY8914
ayConfig=0x04;
break;
}
}
if (hasClockDivider && ((song.systemFlags[i]>>7)&1)) {
ayFlags|=0x10;
}
if (hasStereo && ((song.systemFlags[i]>>6)&1)) {
ayFlags|=0x80;
}
willExport[i]=true;
} else if (!(hasAY&0x40000000)) {
isSecond[i]=true;
@ -889,6 +989,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++;
}
break;
}
case DIV_SYSTEM_SAA1099:
if (!hasSAA) {
hasSAA=disCont[i].dispatch->chipClock;
@ -924,6 +1025,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++;
}
break;
case DIV_SYSTEM_OPN:
if (!hasOPN) {
hasOPN=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeDACSamples=true;
} else if (!(hasOPN&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasOPN|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
@ -1042,6 +1155,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++;
}
break;
case DIV_SYSTEM_SCC:
case DIV_SYSTEM_SCC_PLUS:
if (!hasK051649) {
hasK051649=disCont[i].dispatch->chipClock;
if (song.system[i]==DIV_SYSTEM_SCC_PLUS) {
hasK051649|=0x80000000;
}
willExport[i]=true;
} else if (!(hasK051649&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasK051649|=0x40000000;
if (song.system[i]==DIV_SYSTEM_SCC_PLUS) {
hasK051649|=0x80000000;
}
howManyChips++;
}
break;
default:
break;
}

View File

@ -91,7 +91,7 @@ const char* aboutLine[]={
"ymfm by Aaron Giles",
"MAME SN76496 by Nicola Salmoria",
"MAME AY-3-8910 by Couriersud",
"with AY8930 fixes by Eulous",
"with AY8930 fixes by Eulous, cam900 and Grauw",
"MAME SAA1099 by Juergen Buchmueller and Manuel Abadia",
"SAASound by Dave Hooper and Simon Owen",
"SameBoy by Lior Halphon",

View File

@ -20,7 +20,7 @@
#define DETERMINE_FIRST \
int firstChannel=0; \
for (int i=0; i<e->getTotalChannelCount(); i++) { \
if (e->song.chanShow[i]) { \
if (e->curSubSong->chanShow[i]) { \
firstChannel=i; \
break; \
} \
@ -29,7 +29,7 @@
#define DETERMINE_LAST \
int lastChannel=0; \
for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \
if (e->song.chanShow[i]) { \
if (e->curSubSong->chanShow[i]) { \
lastChannel=i+1; \
break; \
} \

View File

@ -38,7 +38,7 @@ void FurnaceGUI::drawChannels() {
ImGui::PushID(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Checkbox("##Visible",&e->song.chanShow[i]);
ImGui::Checkbox("##Visible",&e->curSubSong->chanShow[i]);
ImGui::SameLine();
ImGui::BeginDisabled(i==0);
if (ImGui::Button(ICON_FA_CHEVRON_UP)) {
@ -53,10 +53,10 @@ void FurnaceGUI::drawChannels() {
ImGui::EndDisabled();
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]);
ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->curSubSong->chanName[i]);
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->song.chanShortName[i]);
ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->curSubSong->chanShortName[i]);
ImGui::PopID();
}
ImGui::EndTable();

View File

@ -21,28 +21,48 @@
#include "actionUtil.h"
void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) {
void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) {
DETERMINE_FIRST_LAST;
if (xCoarse!=selStart.xCoarse || xFine!=selStart.xFine || y!=selStart.y) {
curNibble=false;
}
cursor.xCoarse=xCoarse;
cursor.xFine=xFine;
cursor.y=y;
selStart.xCoarse=xCoarse;
selStart.xFine=xFine;
selStart.y=y;
selEnd.xCoarse=xCoarse;
selEnd.xFine=xFine;
selEnd.y=y;
if (fullRow) {
selStart.xCoarse=firstChannel;
selStart.xFine=0;
selEnd.xCoarse=lastChannel-1;
selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2;
selStart.y=y;
selEnd.y=y;
} else {
cursor.xCoarse=xCoarse;
cursor.xFine=xFine;
cursor.y=y;
selStart.xCoarse=xCoarse;
selStart.xFine=xFine;
selStart.y=y;
selEnd.xCoarse=xCoarse;
selEnd.xFine=xFine;
selEnd.y=y;
}
selecting=true;
selectingFull=fullRow;
e->setMidiBaseChan(cursor.xCoarse);
}
void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y) {
void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y, bool fullRow) {
if (!selecting) return;
selEnd.xCoarse=xCoarse;
selEnd.xFine=xFine;
selEnd.y=y;
if (selectingFull) {
DETERMINE_LAST;
selEnd.xCoarse=lastChannel-1;
selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2;
selEnd.y=y;
} else {
selEnd.xCoarse=xCoarse;
selEnd.xFine=xFine;
selEnd.y=y;
}
}
void FurnaceGUI::finishSelection() {
@ -66,6 +86,7 @@ void FurnaceGUI::finishSelection() {
selEnd.xFine^=selStart.xFine;
}
selecting=false;
selectingFull=false;
// boundary check
int chanCount=e->getTotalChannelCount();
@ -73,21 +94,21 @@ void FurnaceGUI::finishSelection() {
if (selStart.xCoarse<0) selStart.xCoarse=0;
if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1;
if (selStart.y<0) selStart.y=0;
if (selStart.y>=e->song.patLen) selStart.y=e->song.patLen-1;
if (selStart.y>=e->curSubSong->patLen) selStart.y=e->curSubSong->patLen-1;
if (selEnd.xCoarse<0) selEnd.xCoarse=0;
if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1;
if (selEnd.y<0) selEnd.y=0;
if (selEnd.y>=e->song.patLen) selEnd.y=e->song.patLen-1;
if (selEnd.y>=e->curSubSong->patLen) selEnd.y=e->curSubSong->patLen-1;
if (cursor.xCoarse<0) cursor.xCoarse=0;
if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1;
if (cursor.y<0) cursor.y=0;
if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1;
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
if (e->song.chanCollapse[selStart.xCoarse]==3) {
if (e->curSubSong->chanCollapse[selStart.xCoarse]==3) {
selStart.xFine=0;
}
if (e->song.chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->song.chanCollapse[selEnd.xCoarse])) {
selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2;
if (e->curSubSong->chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->curSubSong->chanCollapse[selEnd.xCoarse])) {
selEnd.xFine=2+e->curPat[cursor.xCoarse].effectCols*2;
}
e->setMidiBaseChan(cursor.xCoarse);
@ -105,7 +126,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
demandScrollX=true;
if (x>0) {
for (int i=0; i<x; i++) {
if (++cursor.xFine>=(e->song.chanCollapse[cursor.xCoarse]?(4-e->song.chanCollapse[cursor.xCoarse]):(3+e->song.pat[cursor.xCoarse].effectCols*2))) {
if (++cursor.xFine>=(e->curSubSong->chanCollapse[cursor.xCoarse]?(4-e->curSubSong->chanCollapse[cursor.xCoarse]):(3+e->curPat[cursor.xCoarse].effectCols*2))) {
cursor.xFine=0;
if (++cursor.xCoarse>=lastChannel) {
if (settings.wrapHorizontal!=0 && !select) {
@ -113,10 +134,10 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
if (settings.wrapHorizontal==2) y++;
} else {
cursor.xCoarse=lastChannel-1;
cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?(3-e->song.chanCollapse[cursor.xCoarse]):(2+e->song.pat[cursor.xCoarse].effectCols*2);
cursor.xFine=e->curSubSong->chanCollapse[cursor.xCoarse]?(3-e->curSubSong->chanCollapse[cursor.xCoarse]):(2+e->curPat[cursor.xCoarse].effectCols*2);
}
} else {
while (!e->song.chanShow[cursor.xCoarse]) {
while (!e->curSubSong->chanShow[cursor.xCoarse]) {
cursor.xCoarse++;
if (cursor.xCoarse>=e->getTotalChannelCount()) break;
}
@ -129,21 +150,21 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
if (--cursor.xCoarse<firstChannel) {
if (settings.wrapHorizontal!=0 && !select) {
cursor.xCoarse=lastChannel-1;
cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2;
cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2;
if (settings.wrapHorizontal==2) y--;
} else {
cursor.xCoarse=firstChannel;
cursor.xFine=0;
}
} else {
while (!e->song.chanShow[cursor.xCoarse]) {
while (!e->curSubSong->chanShow[cursor.xCoarse]) {
cursor.xCoarse--;
if (cursor.xCoarse<0) break;
}
if (e->song.chanCollapse[cursor.xCoarse]) {
cursor.xFine=3-e->song.chanCollapse[cursor.xCoarse];
if (e->curSubSong->chanCollapse[cursor.xCoarse]) {
cursor.xFine=3-e->curSubSong->chanCollapse[cursor.xCoarse];
} else {
cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2;
cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2;
}
}
}
@ -154,18 +175,18 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
if (y>0) {
for (int i=0; i<y; i++) {
cursor.y++;
if (cursor.y>=e->song.patLen) {
if (cursor.y>=e->curSubSong->patLen) {
if (settings.wrapVertical!=0 && !select) {
cursor.y=0;
if (settings.wrapVertical==2) {
if ((!e->isPlaying() || !followPattern) && curOrder<(e->song.ordersLen-1)) {
if ((!e->isPlaying() || !followPattern) && curOrder<(e->curSubSong->ordersLen-1)) {
setOrder(curOrder+1);
} else {
cursor.y=e->song.patLen-1;
cursor.y=e->curSubSong->patLen-1;
}
}
} else {
cursor.y=e->song.patLen-1;
cursor.y=e->curSubSong->patLen-1;
}
}
}
@ -174,7 +195,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
cursor.y--;
if (cursor.y<0) {
if (settings.wrapVertical!=0 && !select) {
cursor.y=e->song.patLen-1;
cursor.y=e->curSubSong->patLen-1;
if (settings.wrapVertical==2) {
if ((!e->isPlaying() || !followPattern) && curOrder>0) {
setOrder(curOrder-1);
@ -208,7 +229,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
do {
cursor.xCoarse--;
if (cursor.xCoarse<0) break;
} while (!e->song.chanShow[cursor.xCoarse]);
} while (!e->curSubSong->chanShow[cursor.xCoarse]);
if (cursor.xCoarse<firstChannel) {
if (overflow) {
cursor.xCoarse=lastChannel-1;
@ -232,7 +253,7 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) {
do {
cursor.xCoarse++;
if (cursor.xCoarse>=e->getTotalChannelCount()) break;
} while (!e->song.chanShow[cursor.xCoarse]);
} while (!e->curSubSong->chanShow[cursor.xCoarse]);
if (cursor.xCoarse>=lastChannel) {
if (overflow) {
cursor.xCoarse=firstChannel;
@ -269,14 +290,14 @@ void FurnaceGUI::moveCursorTop(bool select) {
void FurnaceGUI::moveCursorBottom(bool select) {
finishSelection();
curNibble=false;
if (cursor.y==e->song.patLen-1) {
if (cursor.y==e->curSubSong->patLen-1) {
DETERMINE_LAST;
cursor.xCoarse=lastChannel-1;
if (cursor.xCoarse<0) cursor.xCoarse=0;
cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2;
cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2;
demandScrollX=true;
} else {
cursor.y=e->song.patLen-1;
cursor.y=e->curSubSong->patLen-1;
}
if (!select) {
selStart=cursor;
@ -289,7 +310,7 @@ void FurnaceGUI::moveCursorBottom(bool select) {
void FurnaceGUI::editAdvance() {
finishSelection();
cursor.y+=editStep;
if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1;
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
selStart=cursor;
selEnd=cursor;
updateScroll(cursor.y);

View File

@ -153,6 +153,8 @@ void FurnaceGUI::drawDebug() {
ImGui::Text("rate: %d",sample->rate);
ImGui::Text("centerRate: %d",sample->centerRate);
ImGui::Text("loopStart: %d",sample->loopStart);
ImGui::Text("loopEnd: %d",sample->loopEnd);
ImGui::Text("loopMode: %d",(int)(sample->loopMode));
ImGui::Text("loopOffP: %d",sample->loopOffP);
ImGui::Text("depth: %d",sample->depth);
ImGui::Text("length8: %d",sample->length8);

View File

@ -470,7 +470,7 @@ void FurnaceGUI::doAction(int what) {
e->unmuteAll();
break;
case GUI_ACTION_PAT_NEXT_ORDER:
if (curOrder<e->song.ordersLen-1) {
if (curOrder<e->curSubSong->ordersLen-1) {
setOrder(curOrder+1);
}
break;
@ -481,21 +481,21 @@ void FurnaceGUI::doAction(int what) {
break;
case GUI_ACTION_PAT_COLLAPSE:
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
if (e->song.chanCollapse[cursor.xCoarse]==0) {
e->song.chanCollapse[cursor.xCoarse]=3;
} else if (e->song.chanCollapse[cursor.xCoarse]>0) {
e->song.chanCollapse[cursor.xCoarse]--;
if (e->curSubSong->chanCollapse[cursor.xCoarse]==0) {
e->curSubSong->chanCollapse[cursor.xCoarse]=3;
} else if (e->curSubSong->chanCollapse[cursor.xCoarse]>0) {
e->curSubSong->chanCollapse[cursor.xCoarse]--;
}
break;
case GUI_ACTION_PAT_INCREASE_COLUMNS:
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
e->song.pat[cursor.xCoarse].effectCols++;
if (e->song.pat[cursor.xCoarse].effectCols>8) e->song.pat[cursor.xCoarse].effectCols=8;
e->curPat[cursor.xCoarse].effectCols++;
if (e->curPat[cursor.xCoarse].effectCols>8) e->curPat[cursor.xCoarse].effectCols=8;
break;
case GUI_ACTION_PAT_DECREASE_COLUMNS:
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
e->song.pat[cursor.xCoarse].effectCols--;
if (e->song.pat[cursor.xCoarse].effectCols<1) e->song.pat[cursor.xCoarse].effectCols=1;
e->curPat[cursor.xCoarse].effectCols--;
if (e->curPat[cursor.xCoarse].effectCols<1) e->curPat[cursor.xCoarse].effectCols=1;
break;
case GUI_ACTION_PAT_INTERPOLATE:
doInterpolate();
@ -525,14 +525,22 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_INS_LIST_ADD:
curIns=e->addInstrument(cursor.xCoarse);
MARK_MODIFIED;
if (curIns==-1) {
showError("too many instruments!");
} else {
MARK_MODIFIED;
}
break;
case GUI_ACTION_INS_LIST_DUPLICATE:
if (curIns>=0 && curIns<(int)e->song.ins.size()) {
int prevIns=curIns;
curIns=e->addInstrument(cursor.xCoarse);
(*e->song.ins[curIns])=(*e->song.ins[prevIns]);
MARK_MODIFIED;
if (curIns==-1) {
showError("too many instruments!");
} else {
(*e->song.ins[curIns])=(*e->song.ins[prevIns]);
MARK_MODIFIED;
}
}
break;
case GUI_ACTION_INS_LIST_OPEN:
@ -571,14 +579,22 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_WAVE_LIST_ADD:
curWave=e->addWave();
MARK_MODIFIED;
if (curWave==-1) {
showError("too many wavetables!");
} else {
MARK_MODIFIED;
}
break;
case GUI_ACTION_WAVE_LIST_DUPLICATE:
if (curWave>=0 && curWave<(int)e->song.wave.size()) {
int prevWave=curWave;
curWave=e->addWave();
(*e->song.wave[curWave])=(*e->song.wave[prevWave]);
MARK_MODIFIED;
if (curWave==-1) {
showError("too many wavetables!");
} else {
(*e->song.wave[curWave])=(*e->song.wave[prevWave]);
MARK_MODIFIED;
}
}
break;
case GUI_ACTION_WAVE_LIST_OPEN:
@ -614,33 +630,41 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_LIST_ADD:
curSample=e->addSample();
if (curSample==-1) {
showError("too many samples!");
} else {
MARK_MODIFIED;
}
updateSampleTex=true;
MARK_MODIFIED;
break;
case GUI_ACTION_SAMPLE_LIST_DUPLICATE:
if (curSample>=0 && curSample<(int)e->song.sample.size()) {
DivSample* prevSample=e->getSample(curSample);
curSample=e->addSample();
updateSampleTex=true;
e->lockEngine([this,prevSample]() {
DivSample* sample=e->getSample(curSample);
if (sample!=NULL) {
sample->rate=prevSample->rate;
sample->centerRate=prevSample->centerRate;
sample->name=prevSample->name;
sample->loopStart=prevSample->loopStart;
sample->loopEnd=prevSample->loopEnd;
sample->loopMode=prevSample->loopMode;
sample->depth=prevSample->depth;
if (sample->init(prevSample->samples)) {
if (prevSample->getCurBuf()!=NULL) {
memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen());
if (curSample==-1) {
showError("too many samples!");
} else {
e->lockEngine([this,prevSample]() {
DivSample* sample=e->getSample(curSample);
if (sample!=NULL) {
sample->rate=prevSample->rate;
sample->centerRate=prevSample->centerRate;
sample->name=prevSample->name;
sample->loopStart=prevSample->loopStart;
sample->loopEnd=prevSample->loopEnd;
sample->loopMode=prevSample->loopMode;
sample->depth=prevSample->depth;
if (sample->init(prevSample->samples)) {
if (prevSample->getCurBuf()!=NULL) {
memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen());
}
}
}
}
e->renderSamples();
});
MARK_MODIFIED;
e->renderSamples();
});
MARK_MODIFIED;
}
updateSampleTex=true;
}
break;
case GUI_ACTION_SAMPLE_LIST_OPEN:
@ -1156,11 +1180,15 @@ void FurnaceGUI::doAction(int what) {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample];
curIns=e->addInstrument(cursor.xCoarse);
e->song.ins[curIns]->type=DIV_INS_AMIGA;
e->song.ins[curIns]->name=sample->name;
e->song.ins[curIns]->amiga.initSample=curSample;
nextWindow=GUI_WINDOW_INS_EDIT;
MARK_MODIFIED;
if (curIns==-1) {
showError("too many instruments!");
} else {
e->song.ins[curIns]->type=DIV_INS_AMIGA;
e->song.ins[curIns]->name=sample->name;
e->song.ins[curIns]->amiga.initSample=curSample;
nextWindow=GUI_WINDOW_INS_EDIT;
MARK_MODIFIED;
}
break;
}
@ -1170,7 +1198,7 @@ void FurnaceGUI::doAction(int what) {
}
break;
case GUI_ACTION_ORDERS_DOWN:
if (curOrder<e->song.ordersLen-1) {
if (curOrder<e->curSubSong->ordersLen-1) {
setOrder(curOrder+1);
}
break;
@ -1183,7 +1211,7 @@ void FurnaceGUI::doAction(int what) {
orderCursor=firstChannel;
break;
}
} while (!e->song.chanShow[orderCursor]);
} while (!e->curSubSong->chanShow[orderCursor]);
break;
}
case GUI_ACTION_ORDERS_RIGHT: {
@ -1195,20 +1223,20 @@ void FurnaceGUI::doAction(int what) {
orderCursor=lastChannel-1;
break;
}
} while (!e->song.chanShow[orderCursor]);
} while (!e->curSubSong->chanShow[orderCursor]);
break;
}
case GUI_ACTION_ORDERS_INCREASE: {
if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break;
if (e->song.orders.ord[orderCursor][curOrder]<0x7f) {
e->song.orders.ord[orderCursor][curOrder]++;
if (e->curOrders->ord[orderCursor][curOrder]<0x7f) {
e->curOrders->ord[orderCursor][curOrder]++;
}
break;
}
case GUI_ACTION_ORDERS_DECREASE: {
if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break;
if (e->song.orders.ord[orderCursor][curOrder]>0) {
e->song.orders.ord[orderCursor][curOrder]--;
if (e->curOrders->ord[orderCursor][curOrder]>0) {
e->curOrders->ord[orderCursor][curOrder]--;
}
break;
}

View File

@ -45,7 +45,7 @@ void FurnaceGUI::drawEditControls() {
ImGui::Text("Edit Step");
ImGui::SameLine();
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1;
if (editStep<0) editStep=0;
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
@ -148,7 +148,7 @@ void FurnaceGUI::drawEditControls() {
ImGui::SameLine();
ImGui::SetNextItemWidth(96.0f*dpiScale);
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1;
if (editStep<0) editStep=0;
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
@ -217,7 +217,7 @@ void FurnaceGUI::drawEditControls() {
ImGui::Text("Step");
ImGui::SetNextItemWidth(avail);
if (ImGui::InputInt("##EditStep",&editStep,0,0)) {
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1;
if (editStep<0) editStep=0;
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
@ -317,7 +317,7 @@ void FurnaceGUI::drawEditControls() {
ImGui::SetCursorPosX(cursor);
ImGui::SetNextItemWidth(avail);
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1;
if (editStep<0) editStep=0;
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {

View File

@ -44,8 +44,8 @@ const char* noteNameNormal(short note, short octave) {
void FurnaceGUI::prepareUndo(ActionType action) {
switch (action) {
case GUI_UNDO_CHANGE_ORDER:
oldOrders=e->song.orders;
oldOrdersLen=e->song.ordersLen;
memcpy(&oldOrders,e->curOrders,sizeof(DivOrders));
oldOrdersLen=e->curSubSong->ordersLen;
break;
case GUI_UNDO_PATTERN_EDIT:
case GUI_UNDO_PATTERN_DELETE:
@ -63,7 +63,7 @@ void FurnaceGUI::prepareUndo(ActionType action) {
case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND:
for (int i=0; i<e->getTotalChannelCount(); i++) {
e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false)->copyOn(oldPat[i]);
e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]);
}
break;
}
@ -78,18 +78,19 @@ void FurnaceGUI::makeUndo(ActionType action) {
s.selEnd=selEnd;
s.order=curOrder;
s.nibble=curNibble;
size_t subSong=e->getCurrentSubSong();
switch (action) {
case GUI_UNDO_CHANGE_ORDER:
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=0; j<128; j++) {
if (oldOrders.ord[i][j]!=e->song.orders.ord[i][j]) {
s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j]));
if (oldOrders.ord[i][j]!=e->curOrders->ord[i][j]) {
s.ord.push_back(UndoOrderData(subSong,i,j,oldOrders.ord[i][j],e->curOrders->ord[i][j]));
}
}
}
s.oldOrdersLen=oldOrdersLen;
s.newOrdersLen=e->song.ordersLen;
if (oldOrdersLen!=e->song.ordersLen) {
s.newOrdersLen=e->curSubSong->ordersLen;
if (oldOrdersLen!=e->curSubSong->ordersLen) {
doPush=true;
}
if (!s.ord.empty()) {
@ -112,11 +113,11 @@ void FurnaceGUI::makeUndo(ActionType action) {
case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND:
for (int i=0; i<e->getTotalChannelCount(); i++) {
DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false);
for (int j=0; j<e->song.patLen; j++) {
DivPattern* p=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false);
for (int j=0; j<e->curSubSong->patLen; j++) {
for (int k=0; k<32; k++) {
if (p->data[j][k]!=oldPat[i]->data[j][k]) {
s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k]));
s.pat.push_back(UndoPatternData(subSong,i,e->curOrders->ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k]));
}
}
}
@ -137,15 +138,15 @@ void FurnaceGUI::makeUndo(ActionType action) {
void FurnaceGUI::doSelectAll() {
finishSelection();
curNibble=false;
if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectCols*2) {
if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern
if (selStart.xFine==0 && selEnd.xFine==2+e->curPat[selEnd.xCoarse].effectCols*2) {
if (selStart.y==0 && selEnd.y==e->curSubSong->patLen-1) { // select entire pattern
selStart.xCoarse=0;
selStart.xFine=0;
selEnd.xCoarse=e->getTotalChannelCount()-1;
selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2;
selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2;
} else { // select entire column
selStart.y=0;
selEnd.y=e->song.patLen-1;
selEnd.y=e->curSubSong->patLen-1;
}
} else {
int selStartX=0;
@ -153,26 +154,26 @@ void FurnaceGUI::doSelectAll() {
// find row position
for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) {
i.xFine++;
if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) {
if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) {
i.xFine=0;
i.xCoarse++;
}
}
for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) {
i.xFine++;
if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) {
if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) {
i.xFine=0;
i.xCoarse++;
}
}
float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1);
if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down
if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->curSubSong->patLen-1)) { // up-down
selStart.y=0;
selEnd.y=e->song.patLen-1;
selEnd.y=e->curSubSong->patLen-1;
} else { // left-right
selStart.xFine=0;
selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2;
selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2;
}
}
}
@ -198,9 +199,9 @@ void FurnaceGUI::doDelete() {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskDelete,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) {
@ -235,12 +236,12 @@ void FurnaceGUI::doPullDelete() {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskPullDelete,iFine);
for (int j=selStart.y; j<e->song.patLen; j++) {
if (j<e->song.patLen-1) {
for (int j=selStart.y; j<e->curSubSong->patLen; j++) {
if (j<e->curSubSong->patLen-1) {
if (iFine==0) {
pat->data[j][iFine]=pat->data[j+1][iFine];
}
@ -267,11 +268,11 @@ void FurnaceGUI::doInsert() {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskInsert,iFine);
for (int j=e->song.patLen-1; j>=selStart.y; j--) {
for (int j=e->curSubSong->patLen-1; j>=selStart.y; j--) {
if (j==selStart.y) {
if (iFine==0) {
pat->data[j][iFine]=0;
@ -299,9 +300,9 @@ void FurnaceGUI::doTranspose(int amount, OperationMask& mask) {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(mask,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) {
@ -367,9 +368,9 @@ void FurnaceGUI::doCopy(bool cut) {
}
clipboard+='\n';
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (iFine==0) {
clipboard+=noteNameNormal(pat->data[j][0],pat->data[j][1]);
if (cut) {
@ -432,7 +433,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
int j=cursor.y;
char note[4];
for (size_t i=2; i<data.size() && j<e->song.patLen; i++) {
for (size_t i=2; i<data.size() && j<e->curSubSong->patLen; i++) {
size_t charPos=0;
int iCoarse=cursor.xCoarse;
int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff;
@ -440,10 +441,10 @@ void FurnaceGUI::doPaste(PasteMode mode) {
String& line=data[i];
while (charPos<line.size() && iCoarse<lastChannel) {
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
if (line[charPos]=='|') {
iCoarse++;
if (iCoarse<lastChannel) while (!e->song.chanShow[iCoarse]) {
if (iCoarse<lastChannel) while (!e->curSubSong->chanShow[iCoarse]) {
iCoarse++;
if (iCoarse>=lastChannel) break;
}
@ -530,7 +531,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
break;
}
if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) {
if (iFine<(3+e->song.pat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val;
if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val;
}
}
}
@ -543,7 +544,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
break;
}
j++;
if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && curOrder<e->song.ordersLen-1) {
if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->curSubSong->patLen && curOrder<e->curSubSong->ordersLen-1) {
j=0;
curOrder++;
}
@ -554,7 +555,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
}
if (settings.cursorPastePos) {
cursor.y=j;
if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1;
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
updateScroll(cursor.y);
}
@ -567,8 +568,8 @@ void FurnaceGUI::doChangeIns(int ins) {
int iCoarse=selStart.xCoarse;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (int j=selStart.y; j<=selEnd.y; j++) {
if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) {
pat->data[j][2]=ins;
@ -587,9 +588,9 @@ void FurnaceGUI::doInterpolate() {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskInterpolate,iFine);
points.clear();
if (iFine!=0) {
@ -646,9 +647,9 @@ void FurnaceGUI::doFade(int p0, int p1, bool mode) {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskFade,iFine);
if (iFine!=0) {
int absoluteTop=255;
@ -684,9 +685,9 @@ void FurnaceGUI::doInvertValues() {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskInvertVal,iFine);
if (iFine!=0) {
int top=255;
@ -715,9 +716,9 @@ void FurnaceGUI::doScale(float top) {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskScale,iFine);
if (iFine!=0) {
int absoluteTop=255;
@ -746,9 +747,9 @@ void FurnaceGUI::doRandomize(int bottom, int top, bool mode) {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskRandomize,iFine);
if (iFine!=0) {
int absoluteTop=255;
@ -792,9 +793,9 @@ void FurnaceGUI::doFlip() {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskFlip,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) {
@ -823,9 +824,9 @@ void FurnaceGUI::doCollapse(int divider) {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskCollapseExpand,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) {
@ -880,9 +881,9 @@ void FurnaceGUI::doExpand(int multiplier) {
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskCollapseExpand,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) {
@ -891,7 +892,7 @@ void FurnaceGUI::doExpand(int multiplier) {
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
}
for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) {
if ((j+selStart.y)>=e->song.patLen) break;
if ((j+selStart.y)>=e->curSubSong->patLen) break;
if ((j%multiplier)!=0) {
if (iFine==0) {
pat->data[j+selStart.y][0]=0;
@ -921,9 +922,10 @@ void FurnaceGUI::doUndo() {
switch (us.type) {
case GUI_UNDO_CHANGE_ORDER:
e->song.ordersLen=us.oldOrdersLen;
e->curSubSong->ordersLen=us.oldOrdersLen;
for (UndoOrderData& i: us.ord) {
e->song.orders.ord[i.chan][i.ord]=i.oldVal;
e->changeSongP(i.subSong);
e->curOrders->ord[i.chan][i.ord]=i.oldVal;
}
break;
case GUI_UNDO_PATTERN_EDIT:
@ -942,7 +944,8 @@ void FurnaceGUI::doUndo() {
case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND:
for (UndoPatternData& i: us.pat) {
DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true);
e->changeSongP(i.subSong);
DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true);
p->data[i.row][i.col]=i.oldVal;
}
if (!e->isPlaying() || !followPattern) {
@ -967,9 +970,10 @@ void FurnaceGUI::doRedo() {
switch (us.type) {
case GUI_UNDO_CHANGE_ORDER:
e->song.ordersLen=us.newOrdersLen;
e->curSubSong->ordersLen=us.newOrdersLen;
for (UndoOrderData& i: us.ord) {
e->song.orders.ord[i.chan][i.ord]=i.newVal;
e->changeSongP(i.subSong);
e->curOrders->ord[i.chan][i.ord]=i.newVal;
}
break;
case GUI_UNDO_PATTERN_EDIT:
@ -988,7 +992,8 @@ void FurnaceGUI::doRedo() {
case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND:
for (UndoPatternData& i: us.pat) {
DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true);
e->changeSongP(i.subSong);
DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true);
p->data[i.row][i.col]=i.newVal;
}
if (!e->isPlaying()) {

View File

@ -59,7 +59,7 @@ extern "C" {
#define BACKUP_FUR "/backup.fur"
#endif
#ifdef ANDROID
#ifdef IS_MOBILE
#define MOBILE_UI_DEFAULT true
#else
#define MOBILE_UI_DEFAULT false
@ -194,21 +194,6 @@ void FurnaceGUI::decodeKeyMap(std::map<int,int>& map, String source) {
}
}
void FurnaceGUI::encodeMMLStr(String& target, unsigned char* macro, unsigned char macroLen, signed char macroLoop, signed char macroRel) {
target="";
char buf[32];
for (int i=0; i<macroLen; i++) {
if (i==macroLoop) target+="| ";
if (i==macroRel) target+="/ ";
if (i==macroLen-1) {
snprintf(buf,31,"%d",macro[i]);
} else {
snprintf(buf,31,"%d ",macro[i]);
}
target+=buf;
}
}
void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex) {
target="";
char buf[32];
@ -290,53 +275,6 @@ void FurnaceGUI::decodeMMLStrW(String& source, int* macro, int& macroLen, int ma
}
}
void FurnaceGUI::decodeMMLStr(String& source, unsigned char* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel) {
int buf=0;
bool hasVal=false;
macroLen=0;
macroLoop=-1;
macroRel=-1;
for (char& i: source) {
switch (i) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
hasVal=true;
buf*=10;
buf+=i-'0';
break;
case ' ':
if (hasVal) {
hasVal=false;
macro[macroLen]=buf;
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
macroLen++;
buf=0;
}
break;
case '|':
if (macroLoop==-1) {
macroLoop=macroLen;
}
break;
case '/':
if (macroRel==-1) {
macroRel=macroLen;
}
break;
}
if (macroLen>=128) break;
}
if (hasVal && macroLen<128) {
hasVal=false;
macro[macroLen]=buf;
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
macroLen++;
buf=0;
}
}
void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel) {
int buf=0;
bool negaBuf=false;
@ -370,11 +308,29 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe
}
break;
case '|':
if (hasVal) {
hasVal=false;
macro[macroLen]=negaBuf?-buf:buf;
negaBuf=false;
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
macroLen++;
buf=0;
}
if (macroLoop==-1) {
macroLoop=macroLen;
}
break;
case '/':
if (hasVal) {
hasVal=false;
macro[macroLen]=negaBuf?-buf:buf;
negaBuf=false;
if (macro[macroLen]<macroMin) macro[macroLen]=macroMin;
if (macro[macroLen]>macroMax) macro[macroLen]=macroMax;
macroLen++;
buf=0;
}
if (macroRel==-1) {
macroRel=macroLen;
}
@ -837,9 +793,9 @@ void FurnaceGUI::prepareLayout() {
}
float FurnaceGUI::calcBPM(int s1, int s2, float hz) {
float hl=e->song.hilightA;
float hl=e->curSubSong->hilightA;
if (hl<=0.0f) hl=4.0f;
float timeBase=e->song.timeBase+1;
float timeBase=e->curSubSong->timeBase+1;
float speedSum=s1+s2;
if (timeBase<1.0f) timeBase=1.0f;
if (speedSum<1.0f) speedSum=1.0f;
@ -901,7 +857,7 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) {
}
void FurnaceGUI::noteInput(int num, int key, int vol) {
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true);
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true);
prepareUndo(GUI_UNDO_PATTERN_EDIT);
@ -945,7 +901,7 @@ void FurnaceGUI::noteInput(int num, int key, int vol) {
}
void FurnaceGUI::valueInput(int num, bool direct, int target) {
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true);
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true);
prepareUndo(GUI_UNDO_PATTERN_EDIT);
if (target==-1) target=cursor.xFine+1;
if (direct) {
@ -1009,7 +965,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) {
editAdvance();
} else {
if (settings.effectCursorDir==2) {
if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectCols*2))) {
if (++cursor.xFine>=(3+(e->curPat[cursor.xCoarse].effectCols*2))) {
cursor.xFine=3;
}
} else {
@ -1167,7 +1123,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
int num=valueKeys.at(ev.key.keysym.sym);
if (orderCursor>=0 && orderCursor<e->getTotalChannelCount()) {
e->lockSave([this,num]() {
e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num);
e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num);
});
if (orderEditMode==2 || orderEditMode==3) {
curNibble=!curNibble;
@ -1176,7 +1132,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
orderCursor++;
if (orderCursor>=e->getTotalChannelCount()) orderCursor=0;
} else if (orderEditMode==3) {
if (curOrder<e->song.ordersLen-1) {
if (curOrder<e->curSubSong->ordersLen-1) {
setOrder(curOrder+1);
}
}
@ -1507,6 +1463,19 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
dpiScale
);
break;
case GUI_FILE_YRW801_ROM_OPEN:
case GUI_FILE_TG100_ROM_OPEN:
case GUI_FILE_MU5_ROM_OPEN:
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
hasOpened=fileDialog->openLoad(
"Load ROM",
{"compatible files", "*.rom *.bin",
"all files", ".*"},
"compatible files{.rom,.bin},.*",
workingDirROM,
dpiScale
);
break;
}
if (hasOpened) curFileDialog=type;
//ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard;
@ -2062,7 +2031,7 @@ void FurnaceGUI::editOptions(bool topMenu) {
ImGui::PopFont();
ImGui::SameLine();
if (ImGui::Button("Set")) {
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true);
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true);
latchIns=pat->data[cursor.y][2];
latchVol=pat->data[cursor.y][3];
latchEffect=pat->data[cursor.y][4];
@ -2381,7 +2350,7 @@ bool FurnaceGUI::loop() {
}
sampleDragActive=false;
if (selecting) {
cursor=selEnd;
if (!selectingFull) cursor=selEnd;
finishSelection();
demandScrollX=true;
if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y &&
@ -2453,6 +2422,15 @@ bool FurnaceGUI::loop() {
wantCaptureKeyboard=ImGui::GetIO().WantTextInput;
if (wantCaptureKeyboard!=oldWantCaptureKeyboard) {
oldWantCaptureKeyboard=wantCaptureKeyboard;
if (wantCaptureKeyboard) {
SDL_StartTextInput();
} else {
SDL_StopTextInput();
}
}
if (wantCaptureKeyboard) {
WAKE_UP;
}
@ -2775,10 +2753,12 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("settings")) {
#ifndef IS_MOBILE
if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) {
doAction(GUI_ACTION_FULLSCREEN);
}
if (ImGui::MenuItem("lock layout",NULL,lockLayout)) {
#endif
if (ImGui::MenuItem("lock layout (not working!)",NULL,lockLayout)) {
lockLayout=!lockLayout;
}
if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) {
@ -2798,6 +2778,7 @@ bool FurnaceGUI::loop() {
}
if (ImGui::BeginMenu("window")) {
if (ImGui::MenuItem("song information",BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO),songInfoOpen)) songInfoOpen=!songInfoOpen;
if (ImGui::MenuItem("subsongs",BIND_FOR(GUI_ACTION_WINDOW_SUBSONGS),subSongsOpen)) subSongsOpen=!subSongsOpen;
if (ImGui::MenuItem("instruments",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen;
if (ImGui::MenuItem("wavetables",BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST),waveListOpen)) waveListOpen=!waveListOpen;
if (ImGui::MenuItem("samples",BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST),sampleListOpen)) sampleListOpen=!sampleListOpen;
@ -2837,12 +2818,12 @@ bool FurnaceGUI::loop() {
if (e->isPlaying()) {
int totalTicks=e->getTotalTicks();
int totalSeconds=e->getTotalSeconds();
ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000);
ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000);
} else {
bool hasInfo=false;
String info;
if (cursor.xCoarse>=0 && cursor.xCoarse<e->getTotalChannelCount()) {
DivPattern* p=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],false);
DivPattern* p=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false);
if (cursor.xFine>=0) switch (cursor.xFine) {
case 0: // note
if (p->data[cursor.y][0]>0) {
@ -2903,6 +2884,7 @@ bool FurnaceGUI::loop() {
ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0);
drawSubSongs();
drawPattern();
drawEditControls();
drawSongInfo();
@ -3013,6 +2995,11 @@ bool FurnaceGUI::loop() {
case GUI_FILE_EXPORT_LAYOUT:
workingDirLayout=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_YRW801_ROM_OPEN:
case GUI_FILE_TG100_ROM_OPEN:
case GUI_FILE_MU5_ROM_OPEN:
workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
}
if (fileDialog->accepted()) {
fileName=fileDialog->getFileName();
@ -3176,8 +3163,11 @@ bool FurnaceGUI::loop() {
break;
}
case GUI_FILE_WAVE_OPEN:
e->addWaveFromFile(copyOfName.c_str());
MARK_MODIFIED;
if (!e->addWaveFromFile(copyOfName.c_str())) {
showError("cannot load wavetable! ("+e->getLastError()+")");
} else {
MARK_MODIFIED;
}
break;
case GUI_FILE_EXPORT_VGM: {
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion);
@ -3226,6 +3216,15 @@ bool FurnaceGUI::loop() {
case GUI_FILE_EXPORT_LAYOUT:
exportLayout(copyOfName);
break;
case GUI_FILE_YRW801_ROM_OPEN:
settings.yrw801Path=copyOfName;
break;
case GUI_FILE_TG100_ROM_OPEN:
settings.tg100Path=copyOfName;
break;
case GUI_FILE_MU5_ROM_OPEN:
settings.mu5Path=copyOfName;
break;
}
curFileDialog=GUI_FILE_OPEN;
}
@ -3278,7 +3277,7 @@ bool FurnaceGUI::loop() {
}
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove)) {
if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) {
ImGui::SetWindowPos(ImVec2(((scrW*dpiScale)-ImGui::GetWindowSize().x)*0.5,((scrH*dpiScale)-ImGui::GetWindowSize().y)*0.5));
drawNewSong();
ImGui::EndPopup();
@ -3494,7 +3493,7 @@ bool FurnaceGUI::loop() {
stop();
e->lockEngine([this]() {
for (int i=0; i<e->getTotalChannelCount(); i++) {
DivPattern* pat=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],true);
DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],true);
memset(pat->data,-1,256*32*sizeof(short));
for (int j=0; j<256; j++) {
pat->data[j][0]=0;
@ -3653,12 +3652,23 @@ bool FurnaceGUI::init() {
regViewOpen=e->getConfBool("regViewOpen",false);
logOpen=e->getConfBool("logOpen",false);
effectListOpen=e->getConfBool("effectListOpen",false);
subSongsOpen=e->getConfBool("subSongsOpen",true);
tempoView=e->getConfBool("tempoView",true);
waveHex=e->getConfBool("waveHex",false);
lockLayout=e->getConfBool("lockLayout",false);
#ifdef IS_MOBILE
fullScreen=true;
#else
fullScreen=e->getConfBool("fullScreen",false);
#endif
mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT);
edit=e->getConfBool("edit",false);
followOrders=e->getConfBool("followOrders",true);
followPattern=e->getConfBool("followPattern",true);
orderEditMode=e->getConfInt("orderEditMode",0);
if (orderEditMode<0) orderEditMode=0;
if (orderEditMode>3) orderEditMode=3;
syncSettings();
@ -3666,6 +3676,8 @@ bool FurnaceGUI::init() {
dpiScale=settings.dpiScale;
}
initSystemPresets();
#if !(defined(__APPLE__) || defined(_WIN32))
unsigned char* furIcon=getFurnaceIcon();
SDL_Surface* icon=SDL_CreateRGBSurfaceFrom(furIcon,256,256,32,256*4,0xff,0xff00,0xff0000,0xff000000);
@ -3679,6 +3691,7 @@ bool FurnaceGUI::init() {
#endif
SDL_SetHint("SDL_HINT_VIDEO_ALLOW_SCREENSAVER","1");
SDL_SetHint("SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH","1");
SDL_Init(SDL_INIT_VIDEO);
@ -3831,6 +3844,7 @@ bool FurnaceGUI::finish() {
e->setConf("regViewOpen",regViewOpen);
e->setConf("logOpen",logOpen);
e->setConf("effectListOpen",effectListOpen);
e->setConf("subSongsOpen",subSongsOpen);
// commit last window size
e->setConf("lastWindowWidth",scrW);
@ -3841,6 +3855,10 @@ bool FurnaceGUI::finish() {
e->setConf("lockLayout",lockLayout);
e->setConf("fullScreen",fullScreen);
e->setConf("mobileUI",mobileUI);
e->setConf("edit",edit);
e->setConf("followOrders",followOrders);
e->setConf("followPattern",followPattern);
e->setConf("orderEditMode",orderEditMode);
for (int i=0; i<DIV_MAX_CHANS; i++) {
delete oldPat[i];
@ -3870,6 +3888,7 @@ FurnaceGUI::FurnaceGUI():
displayExporting(false),
vgmExportLoop(true),
wantCaptureKeyboard(false),
oldWantCaptureKeyboard(false),
displayMacroMenu(false),
displayNew(false),
fullScreen(false),
@ -3946,6 +3965,7 @@ FurnaceGUI::FurnaceGUI():
logOpen(false),
effectListOpen(false),
chanOscOpen(false),
subSongsOpen(true),
/*
editControlsDocked(false),
ordersDocked(false),
@ -3973,8 +3993,10 @@ FurnaceGUI::FurnaceGUI():
logDocked(false),
effectListDocked(false),
chanOscDocked(false),
subSongsDocked(false),
*/
selecting(false),
selectingFull(false),
curNibble(false),
orderNibble(false),
followOrders(true),
@ -4134,8 +4156,6 @@ FurnaceGUI::FurnaceGUI():
valueKeys[SDLK_KP_8]=8;
valueKeys[SDLK_KP_9]=9;
initSystemPresets();
memset(willExport,1,32*sizeof(bool));
peak[0]=0;

View File

@ -231,7 +231,8 @@ enum FurnaceGUIWindows {
GUI_WINDOW_REGISTER_VIEW,
GUI_WINDOW_LOG,
GUI_WINDOW_EFFECT_LIST,
GUI_WINDOW_CHAN_OSC
GUI_WINDOW_CHAN_OSC,
GUI_WINDOW_SUBSONGS
};
enum FurnaceGUIFileDialogs {
@ -257,7 +258,10 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_IMPORT_LAYOUT,
GUI_FILE_EXPORT_COLORS,
GUI_FILE_EXPORT_KEYBINDS,
GUI_FILE_EXPORT_LAYOUT
GUI_FILE_EXPORT_LAYOUT,
GUI_FILE_YRW801_ROM_OPEN,
GUI_FILE_TG100_ROM_OPEN,
GUI_FILE_MU5_ROM_OPEN
};
enum FurnaceGUIWarnings {
@ -334,6 +338,7 @@ enum FurnaceGUIActions {
GUI_ACTION_WINDOW_LOG,
GUI_ACTION_WINDOW_EFFECT_LIST,
GUI_ACTION_WINDOW_CHAN_OSC,
GUI_ACTION_WINDOW_SUBSONGS,
GUI_ACTION_COLLAPSE_WINDOW,
GUI_ACTION_CLOSE_WINDOW,
@ -547,9 +552,10 @@ enum ActionType {
};
struct UndoPatternData {
int chan, pat, row, col;
int subSong, chan, pat, row, col;
short oldVal, newVal;
UndoPatternData(int c, int p, int r, int co, short v1, short v2):
UndoPatternData(int s, int c, int p, int r, int co, short v1, short v2):
subSong(s),
chan(c),
pat(p),
row(r),
@ -559,9 +565,10 @@ struct UndoPatternData {
};
struct UndoOrderData {
int chan, ord;
int subSong, chan, ord;
unsigned char oldVal, newVal;
UndoOrderData(int c, int o, unsigned char v1, unsigned char v2):
UndoOrderData(int s, int c, int o, unsigned char v1, unsigned char v2):
subSong(s),
chan(c),
ord(o),
oldVal(v1),
@ -718,11 +725,14 @@ struct FurnaceGUISysDef {
struct FurnaceGUISysCategory {
const char* name;
const char* description;
std::vector<FurnaceGUISysDef> systems;
FurnaceGUISysCategory(const char* n):
name(n) {}
FurnaceGUISysCategory(const char* n, const char* d):
name(n),
description(d) {}
FurnaceGUISysCategory():
name(NULL) {}
name(NULL),
description(NULL) {}
};
static const char* modeDummy[1]={NULL};
@ -767,11 +777,11 @@ class FurnaceGUI {
bool updateSampleTex;
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM;
String mmlString[17];
String mmlStringW;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, displayMacroMenu;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
bool displayNew, fullScreen, preserveChanPos;
bool willExport[32];
int vgmExportVersion;
@ -825,6 +835,9 @@ class FurnaceGUI {
int saaCore;
int nesCore;
int fdsCore;
String yrw801Path;
String tg100Path;
String mu5Path;
int mainFont;
int patFont;
int audioRate;
@ -863,6 +876,7 @@ class FurnaceGUI {
int roundedButtons;
int roundedMenus;
int loadJapanese;
int loadChinese;
int fmLayout;
int sampleLayout;
int waveLayout;
@ -892,6 +906,7 @@ class FurnaceGUI {
int hiddenSystems;
int horizontalDataView;
int noMultiSystem;
int oldMacroVSlider;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -911,6 +926,9 @@ class FurnaceGUI {
saaCore(1),
nesCore(0),
fdsCore(0),
yrw801Path(""),
tg100Path(""),
mu5Path(""),
mainFont(0),
patFont(0),
audioRate(44100),
@ -947,6 +965,7 @@ class FurnaceGUI {
roundedButtons(1),
roundedMenus(0),
loadJapanese(0),
loadChinese(0),
fmLayout(0),
sampleLayout(0),
waveLayout(0),
@ -976,6 +995,7 @@ class FurnaceGUI {
hiddenSystems(0),
horizontalDataView(0),
noMultiSystem(0),
oldMacroVSlider(0),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -996,16 +1016,18 @@ class FurnaceGUI {
bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen;
bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen;
bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen;
bool subSongsOpen;
/* there ought to be a better way...
bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked;
bool waveListDocked, waveEditDocked, sampleListDocked, sampleEditDocked, aboutDocked, settingsDocked;
bool mixerDocked, debugDocked, inspectorDocked, oscDocked, volMeterDocked, statsDocked, compatFlagsDocked;
bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked, chanOscDocked;
bool subSongsDocked;
*/
SelectionPoint selStart, selEnd, cursor;
bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
bool selecting, selectingFull, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
float peak[2];
@ -1110,7 +1132,7 @@ class FurnaceGUI {
ImVec2 patWindowPos, patWindowSize;
// pattern view specific
ImVec2 threeChars, twoChars;
ImVec2 fourChars, threeChars, twoChars;
SelectionPoint sel1, sel2;
int dummyRows, demandX;
int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax;
@ -1232,6 +1254,7 @@ class FurnaceGUI {
void drawNewSong();
void drawLog();
void drawEffectList();
void drawSubSongs();
void parseKeybinds();
void promptKey(int which);
@ -1251,8 +1274,8 @@ class FurnaceGUI {
void commitSettings();
void processDrags(int dragX, int dragY);
void startSelection(int xCoarse, int xFine, int y);
void updateSelection(int xCoarse, int xFine, int y);
void startSelection(int xCoarse, int xFine, int y, bool fullRow=false);
void updateSelection(int xCoarse, int xFine, int y, bool fullRow=false);
void finishSelection();
void moveCursor(int x, int y, bool select);
@ -1309,8 +1332,6 @@ class FurnaceGUI {
void initSystemPresets();
void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false);
void encodeMMLStr(String& target, unsigned char* macro, unsigned char macroLen, signed char macroLoop, signed char macroRel);
void decodeMMLStr(String& source, unsigned char* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel);
void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel);
void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex=false);

View File

@ -101,7 +101,7 @@ const char* insTypes[DIV_INS_MAX]={
"Konami SCC/Bubble System WSG",
"FM (OPZ)",
"POKEY",
"PC Beeper",
"Beeper",
"WonderSwan",
"Atari Lynx",
"VERA",
@ -116,8 +116,8 @@ const char* insTypes[DIV_INS_MAX]={
const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={
"1-bit PCM",
"1-bit DPCM",
NULL,
NULL,
"Yamaha AICA",
"YMZ/YMU ADPCM",
"QSound ADPCM",
"ADPCM-A",
"ADPCM-B",
@ -492,6 +492,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("WINDOW_CHANNELS", "Channels", 0),
D("WINDOW_REGISTER_VIEW", "Register View", 0),
D("WINDOW_LOG", "Log Viewer", 0),
D("WINDOW_SUBSONGS", "Subsongs", 0),
D("EFFECT_LIST", "Effect List", 0),
D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0),
@ -838,14 +839,21 @@ const int availableSystems[]={
DIV_SYSTEM_AY8910,
DIV_SYSTEM_AMIGA,
DIV_SYSTEM_PCSPKR,
DIV_SYSTEM_SFX_BEEPER,
DIV_SYSTEM_YMU759,
DIV_SYSTEM_DUMMY,
DIV_SYSTEM_SOUND_UNIT,
DIV_SYSTEM_OPN,
DIV_SYSTEM_OPN_EXT,
DIV_SYSTEM_PC98,
DIV_SYSTEM_PC98_EXT,
DIV_SYSTEM_OPLL,
DIV_SYSTEM_OPLL_DRUMS,
DIV_SYSTEM_VRC7,
DIV_SYSTEM_OPL,
DIV_SYSTEM_OPL_DRUMS,
DIV_SYSTEM_Y8950,
DIV_SYSTEM_Y8950_DRUMS,
DIV_SYSTEM_OPL2,
DIV_SYSTEM_OPL2_DRUMS,
DIV_SYSTEM_OPL3,

View File

@ -191,8 +191,12 @@ const char* es5506EnvelopeModes[3]={
"k1 slowdown", "k2 slowdown", NULL
};
const char* es5506ControlModes[3]={
"pause", "transwave slice", NULL
const char* es5506ControlModes[2]={
"pause", NULL
};
const char* transwaveControlModes[2]={
"slice", NULL
};
const char* oneBit[2]={
@ -1135,7 +1139,7 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
float loopIndicator[256];
int index=0;
float reservedSpace=ImGui::GetStyle().ScrollbarSize;
float reservedSpace=(settings.oldMacroVSlider)?(20.0f*dpiScale+ImGui::GetStyle().ItemSpacing.x):ImGui::GetStyle().ScrollbarSize;
if (ImGui::BeginTable("MacroSpace",2)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0);
@ -1211,8 +1215,8 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
i.macro->vZoom=24;
i.macro->vScroll=120-12;
} else if (i.macro->name=="pitch") {
i.macro->vZoom=64;
i.macro->vScroll=1024-32;
i.macro->vZoom=128;
i.macro->vScroll=2048-64;
} else {
i.macro->vZoom=i.max-i.min;
i.macro->vScroll=0;
@ -1251,37 +1255,60 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
}
if (i.macro->open) {
if (ImGui::IsItemHovered() && ctrlWheeling) {
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
i.macro->vZoom+=wheelY;
if (i.macro->vZoom<1) i.macro->vZoom=1;
if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min;
if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) {
i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
if (ImGui::IsItemHovered()) {
if (ctrlWheeling) {
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vZoom<1) i.macro->vZoom=1;
if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min;
if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) {
i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
} else {
macroPointSize+=wheelY;
if (macroPointSize<1) macroPointSize=1;
if (macroPointSize>256) macroPointSize=256;
}
} else {
macroPointSize+=wheelY;
if (macroPointSize<1) macroPointSize=1;
if (macroPointSize>256) macroPointSize=256;
} else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
}
// slider
if (!i.isBitfield) {
ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll;
ImS64 availV=i.macro->vZoom;
ImS64 contentsV=(i.max-i.min);
if (settings.oldMacroVSlider) {
ImGui::SameLine(0.0f);
if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) {
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
if (ImGui::IsItemHovered() && ctrlWheeling) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
} else {
ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll;
ImS64 availV=i.macro->vZoom;
ImS64 contentsV=(i.max-i.min);
ImGui::SameLine(0.0f);
ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos());
scrollbarPos.Min.x-=ImGui::GetStyle().ItemSpacing.x;
scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize;
scrollbarPos.Max.x-=ImGui::GetStyle().ItemSpacing.x;
scrollbarPos.Max.y+=i.height*dpiScale;
ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale));
ImGui::SameLine(0.0f);
ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x);
ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos());
scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize;
scrollbarPos.Max.y+=i.height*dpiScale;
ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale));
if (ImGui::IsItemHovered() && ctrlWheeling) {
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
}
if (ImGui::ScrollbarEx(scrollbarPos,ImGui::GetID("IMacroVScroll"),ImGuiAxis_Y,&scrollV,availV,contentsV,0)) {
i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV;
if (ImGui::ScrollbarEx(scrollbarPos,ImGui::GetID("IMacroVScroll"),ImGuiAxis_Y,&scrollV,availV,contentsV,0)) {
i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV;
}
}
}
@ -3110,7 +3137,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_GB) {
volMax=0;
}
if (ins->type==DIV_INS_PET) {
if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) {
volMax=1;
}
if (ins->type==DIV_INS_FDS) {
@ -3145,6 +3172,10 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="Duty/Int";
dutyMax=10;
}
if (ins->type==DIV_INS_BEEPER) {
dutyLabel="Pulse Width";
dutyMax=255;
}
if (ins->type==DIV_INS_AY8930) {
dutyMax=255;
}
@ -3406,7 +3437,9 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc("Envelope K1 ramp",&ins->std.ex6Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER]));
macroList.push_back(FurnaceGUIMacroDesc("Envelope K2 ramp",&ins->std.ex7Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER]));
macroList.push_back(FurnaceGUIMacroDesc("Envelope mode",&ins->std.ex8Macro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506EnvelopeModes));
macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506ControlModes));
macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506ControlModes));
macroList.push_back(FurnaceGUIMacroDesc("Transwave control",&ins->std.fbMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,transwaveControlModes));
macroList.push_back(FurnaceGUIMacroDesc("Transwave slice",&ins->std.fmsMacro,0,4095,160,uiColors[GUI_COLOR_MACRO_OTHER]));
}
if (ins->type==DIV_INS_SU) {
macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex3Macro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,suControlBits));

View File

@ -18,6 +18,7 @@
*/
#include "gui.h"
#include <imgui.h>
void FurnaceGUI::drawNewSong() {
bool accepted=false;
@ -27,44 +28,53 @@ void FurnaceGUI::drawNewSong() {
ImGui::Text("Choose a System!");
ImGui::PopFont();
if (ImGui::BeginTable("sysPicker",2)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f);
ImVec2 avail=ImGui::GetContentRegionAvail();
avail.y-=ImGui::GetFrameHeightWithSpacing();
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableNextColumn();
ImGui::Text("Categories");
ImGui::TableNextColumn();
ImGui::Text("Systems");
if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) {
if (ImGui::BeginTable("sysPicker",2,ImGuiTableFlags_BordersInnerV)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f);
ImGui::TableNextRow();
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableNextColumn();
ImGui::Text("Categories");
ImGui::TableNextColumn();
ImGui::Text("Systems");
// CATEGORIES
ImGui::TableNextColumn();
int index=0;
for (FurnaceGUISysCategory& i: sysCategories) {
if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \
newSongCategory=index;
}
index++;
}
ImGui::TableNextRow();
// SYSTEMS
ImGui::TableNextColumn();
if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) {
for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) {
nextDesc=i.definition.data();
accepted=true;
// CATEGORIES
ImGui::TableNextColumn();
int index=0;
for (FurnaceGUISysCategory& i: sysCategories) {
if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \
newSongCategory=index;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s",i.description);
}
index++;
}
// SYSTEMS
ImGui::TableNextColumn();
if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) {
for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) {
nextDesc=i.definition.data();
accepted=true;
}
}
ImGui::EndTable();
}
ImGui::EndTable();
}
ImGui::EndTable();
}
ImGui::EndChild();
if (ImGui::Button("I'm feeling lucky")) {
if (sysCategories.size()==0) {

View File

@ -37,7 +37,7 @@ void FurnaceGUI::drawOrders() {
ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale);
int displayChans=0;
for (int i=0; i<e->getTotalChannelCount(); i++) {
if (e->song.chanShow[i]) displayChans++;
if (e->curSubSong->chanShow[i]) displayChans++;
}
ImGui::PushFont(patFont);
bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x)));
@ -56,12 +56,12 @@ void FurnaceGUI::drawOrders() {
ImGui::TableNextColumn();
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
for (int i=0; i<e->getTotalChannelCount(); i++) {
if (!e->song.chanShow[i]) continue;
if (!e->curSubSong->chanShow[i]) continue;
ImGui::TableNextColumn();
ImGui::Text("%s",e->getChannelShortName(i));
}
ImGui::PopStyleColor();
for (int i=0; i<e->song.ordersLen; i++) {
for (int i=0; i<e->curSubSong->ordersLen; i++) {
ImGui::TableNextRow(0,lineHeight);
if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE]));
ImGui::TableNextColumn();
@ -84,16 +84,16 @@ void FurnaceGUI::drawOrders() {
}
ImGui::PopStyleColor();
for (int j=0; j<e->getTotalChannelCount(); j++) {
if (!e->song.chanShow[j]) continue;
if (!e->curSubSong->chanShow[j]) continue;
ImGui::TableNextColumn();
DivPattern* pat=e->song.pat[j].getPattern(e->song.orders.ord[j][i],false);
DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false);
/*if (!pat->name.empty()) {
snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i);
} else {*/
snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->song.orders.ord[j][i],j,i);
snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i);
//}
ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->song.orders.ord[j][i]==e->song.orders.ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]);
ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]);
if (ImGui::Selectable(selID,(orderEditMode!=0 && curOrder==i && orderCursor==j))) {
if (curOrder==i) {
if (orderEditMode==0) {
@ -101,10 +101,10 @@ void FurnaceGUI::drawOrders() {
e->lockSave([this,i,j]() {
if (changeAllOrders) {
for (int k=0; k<e->getTotalChannelCount(); k++) {
if (e->song.orders.ord[k][i]<0xff) e->song.orders.ord[k][i]++;
if (e->curOrders->ord[k][i]<0xff) e->curOrders->ord[k][i]++;
}
} else {
if (e->song.orders.ord[j][i]<0xff) e->song.orders.ord[j][i]++;
if (e->curOrders->ord[j][i]<0xff) e->curOrders->ord[j][i]++;
}
});
e->walkSong(loopOrder,loopRow,loopEnd);
@ -137,10 +137,10 @@ void FurnaceGUI::drawOrders() {
e->lockSave([this,i,j]() {
if (changeAllOrders) {
for (int k=0; k<e->getTotalChannelCount(); k++) {
if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--;
if (e->curOrders->ord[k][i]>0) e->curOrders->ord[k][i]--;
}
} else {
if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--;
if (e->curOrders->ord[j][i]>0) e->curOrders->ord[j][i]--;
}
});
e->walkSong(loopOrder,loopRow,loopEnd);

View File

@ -43,21 +43,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
return;
}
// check if we are in range
if (ord<0 || ord>=e->song.ordersLen) {
if (ord<0 || ord>=e->curSubSong->ordersLen) {
return;
}
if (i<0 || i>=e->song.patLen) {
if (i<0 || i>=e->curSubSong->patLen) {
return;
}
bool isPushing=false;
ImVec4 activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE];
ImVec4 inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE];
ImVec4 rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX];
if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) {
activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI2];
inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI2];
rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI2];
} else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
} else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) {
activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI1];
inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI1];
rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI1];
@ -68,9 +68,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
} else if (isPlaying && oldRow==i && ord==e->getOrder()) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD]));
} else if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
} else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2]));
} else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
} else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
}
} else {
@ -79,24 +79,34 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
} else if (isPlaying && oldRow==i && ord==e->getOrder()) {
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD]));
} else if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
} else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) {
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2]));
} else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
} else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) {
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
} else {
isPushing=false;
}
}
// row number
ImGui::PushStyleColor(ImGuiCol_Text,rowIndexColor);
if (settings.patRowsBase==1) {
ImGui::TextColored(rowIndexColor," %.2X ",i);
snprintf(id,31," %.2X ##PR_%d",i,i);
} else {
ImGui::TextColored(rowIndexColor,"%3d ",i);
snprintf(id,31,"%3d ##PR_%d",i,i);
}
ImGui::Selectable(id,false,ImGuiSelectableFlags_NoPadWithHalfSpacing,fourChars);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) {
updateSelection(0,0,i,true);
}
if (ImGui::IsItemClicked()) {
startSelection(0,0,i,true);
}
ImGui::PopStyleColor();
// for each column
for (int j=0; j<chans; j++) {
// check if channel is not hidden
if (!e->song.chanShow[j]) {
if (!e->curSubSong->chanShow[j]) {
patChanX[j]=ImGui::GetCursorPosX();
continue;
}
@ -145,7 +155,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PopStyleColor();
// the following is only visible when the channel is not collapsed
if (e->song.chanCollapse[j]<3) {
if (e->curSubSong->chanCollapse[j]<3) {
// instrument
if (pat->data[i][2]==-1) {
ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor);
@ -185,7 +195,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PopStyleColor();
}
if (e->song.chanCollapse[j]<2) {
if (e->curSubSong->chanCollapse[j]<2) {
// volume
if (pat->data[i][3]==-1) {
sprintf(id,"..##PV_%d_%d",i,j);
@ -219,9 +229,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PopStyleColor();
}
if (e->song.chanCollapse[j]<1) {
if (e->curSubSong->chanCollapse[j]<1) {
// effects
for (int k=0; k<e->song.pat[j].effectCols; k++) {
for (int k=0; k<e->curPat[j].effectCols; k++) {
int index=4+(k<<1);
bool selectedEffect=selectedRow && (j32+index-1>=sel1XSum && j32+index-1<=sel2XSum);
bool selectedEffectVal=selectedRow && (j32+index>=sel1XSum && j32+index<=sel2XSum);
@ -348,7 +358,7 @@ void FurnaceGUI::drawPattern() {
int displayChans=0;
const DivPattern* patCache[DIV_MAX_CHANS];
for (int i=0; i<chans; i++) {
if (e->song.chanShow[i]) displayChans++;
if (e->curSubSong->chanShow[i]) displayChans++;
}
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f));
ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
@ -373,7 +383,7 @@ void FurnaceGUI::drawPattern() {
}
ImGui::TableSetupScrollFreeze(1,1);
for (int i=0; i<chans; i++) {
if (!e->song.chanShow[i]) continue;
if (!e->curSubSong->chanShow[i]) continue;
ImGui::TableSetupColumn(fmt::sprintf("c%d",i).c_str(),ImGuiTableColumnFlags_WidthFixed);
}
ImGui::TableNextRow();
@ -398,10 +408,10 @@ void FurnaceGUI::drawPattern() {
cmdStream.clear();
}
for (int i=0; i<chans; i++) {
if (!e->song.chanShow[i]) continue;
if (!e->curSubSong->chanShow[i]) continue;
ImGui::TableNextColumn();
bool displayTooltip=false;
if (e->song.chanCollapse[i]) {
if (e->curSubSong->chanCollapse[i]) {
const char* chName=e->getChannelShortName(i);
if (strlen(chName)>3) {
snprintf(chanID,2048,"...##_CH%d",i);
@ -411,7 +421,7 @@ void FurnaceGUI::drawPattern() {
displayTooltip=true;
} else {
const char* chName=e->getChannelName(i);
size_t chNameLimit=6+4*e->song.pat[i].effectCols;
size_t chNameLimit=6+4*e->curPat[i].effectCols;
if (strlen(chName)>chNameLimit) {
String shortChName=chName;
shortChName.resize(chNameLimit-3);
@ -473,7 +483,7 @@ void FurnaceGUI::drawPattern() {
e->toggleSolo(i);
}
if (extraChannelButtons==2) {
DivPattern* pat=e->song.pat[i].getPattern(e->song.orders.ord[i][ord],true);
DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][ord],true);
ImGui::PushFont(mainFont);
if (patNameTarget==i) {
snprintf(chanID,2048,"##PatNameI%d_%d",i,ord);
@ -500,30 +510,30 @@ void FurnaceGUI::drawPattern() {
}
ImGui::PopFont();
} else if (extraChannelButtons==1) {
snprintf(chanID,2048,"%c##_HCH%d",e->song.chanCollapse[i]?'+':'-',i);
snprintf(chanID,2048,"%c##_HCH%d",e->curSubSong->chanCollapse[i]?'+':'-',i);
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+4.0f*dpiScale);
if (ImGui::SmallButton(chanID)) {
if (e->song.chanCollapse[i]==0) {
e->song.chanCollapse[i]=3;
} else if (e->song.chanCollapse[i]>0) {
e->song.chanCollapse[i]--;
if (e->curSubSong->chanCollapse[i]==0) {
e->curSubSong->chanCollapse[i]=3;
} else if (e->curSubSong->chanCollapse[i]>0) {
e->curSubSong->chanCollapse[i]--;
}
}
if (!e->song.chanCollapse[i]) {
if (!e->curSubSong->chanCollapse[i]) {
ImGui::SameLine();
snprintf(chanID,2048,"<##_LCH%d",i);
ImGui::BeginDisabled(e->song.pat[i].effectCols<=1);
ImGui::BeginDisabled(e->curPat[i].effectCols<=1);
if (ImGui::SmallButton(chanID)) {
e->song.pat[i].effectCols--;
if (e->song.pat[i].effectCols<1) e->song.pat[i].effectCols=1;
e->curPat[i].effectCols--;
if (e->curPat[i].effectCols<1) e->curPat[i].effectCols=1;
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(e->song.pat[i].effectCols>=8);
ImGui::BeginDisabled(e->curPat[i].effectCols>=8);
snprintf(chanID,2048,">##_RCH%d",i);
if (ImGui::SmallButton(chanID)) {
e->song.pat[i].effectCols++;
if (e->song.pat[i].effectCols>8) e->song.pat[i].effectCols=8;
e->curPat[i].effectCols++;
if (e->curPat[i].effectCols>8) e->curPat[i].effectCols=8;
}
ImGui::EndDisabled();
}
@ -535,6 +545,7 @@ void FurnaceGUI::drawPattern() {
ImGui::TextColored(uiColors[GUI_COLOR_EE_VALUE]," %.2X",e->getExtValue());
}
float oneCharSize=ImGui::CalcTextSize("A").x;
fourChars=ImVec2(oneCharSize*4.0f,lineHeight);
threeChars=ImVec2(oneCharSize*3.0f,lineHeight);
twoChars=ImVec2(oneCharSize*2.0f,lineHeight);
//ImVec2 oneChar=ImVec2(oneCharSize,lineHeight);
@ -544,10 +555,10 @@ void FurnaceGUI::drawPattern() {
ImGui::BeginDisabled();
if (settings.viewPrevPattern) {
if ((ord-1)>=0) for (int i=0; i<chans; i++) {
patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord-1],true);
patCache[i]=e->curPat[i].getPattern(e->curOrders->ord[i][ord-1],true);
}
for (int i=0; i<dummyRows-1; i++) {
patternRow(e->song.patLen+i-dummyRows+1,e->isPlaying(),lineHeight,chans,ord-1,patCache,true);
patternRow(e->curSubSong->patLen+i-dummyRows+1,e->isPlaying(),lineHeight,chans,ord-1,patCache,true);
}
} else {
for (int i=0; i<dummyRows-1; i++) {
@ -558,16 +569,16 @@ void FurnaceGUI::drawPattern() {
ImGui::EndDisabled();
// active area
for (int i=0; i<chans; i++) {
patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord],true);
patCache[i]=e->curPat[i].getPattern(e->curOrders->ord[i][ord],true);
}
for (int i=0; i<e->song.patLen; i++) {
for (int i=0; i<e->curSubSong->patLen; i++) {
patternRow(i,e->isPlaying(),lineHeight,chans,ord,patCache,false);
}
// next pattern
ImGui::BeginDisabled();
if (settings.viewPrevPattern) {
if ((ord+1)<e->song.ordersLen) for (int i=0; i<chans; i++) {
patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord+1],true);
if ((ord+1)<e->curSubSong->ordersLen) for (int i=0; i<chans; i++) {
patCache[i]=e->curPat[i].getPattern(e->curOrders->ord[i][ord+1],true);
}
for (int i=0; i<=dummyRows; i++) {
patternRow(i,e->isPlaying(),lineHeight,chans,ord+1,patCache,true);
@ -601,7 +612,7 @@ void FurnaceGUI::drawPattern() {
if (curOrder>0) {
setOrder(curOrder-1);
ImGui::SetScrollY(ImGui::GetScrollMaxY());
updateScroll(e->song.patLen);
updateScroll(e->curSubSong->patLen);
}
haveHitBounds=false;
} else {
@ -613,7 +624,7 @@ void FurnaceGUI::drawPattern() {
} else {
if (ImGui::GetScrollY()>=ImGui::GetScrollMaxY()) {
if (haveHitBounds) {
if (curOrder<(e->song.ordersLen-1)) {
if (curOrder<(e->curSubSong->ordersLen-1)) {
setOrder(curOrder+1);
ImGui::SetScrollY(0);
updateScroll(0);
@ -765,7 +776,7 @@ void FurnaceGUI::drawPattern() {
// note slides
ImVec2 arrowPoints[7];
if (e->isPlaying()) for (int i=0; i<chans; i++) {
if (!e->song.chanShow[i]) continue;
if (!e->curSubSong->chanShow[i]) continue;
DivChannelState* ch=e->getChanState(i);
if (ch->portaSpeed>0) {
ImVec4 col=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH];

View File

@ -31,27 +31,41 @@
// ));
void FurnaceGUI::initSystemPresets() {
sysCategories.clear();
FurnaceGUISysCategory cat;
cat=FurnaceGUISysCategory("FM");
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612", {
DIV_SYSTEM_YM2612, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612 (extended channel 3)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, 0,
0
}
));
cat=FurnaceGUISysCategory("FM","chips which use frequency modulation (FM) to generate sound.\nsome of these also pack more (like square and sample channels).");
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2151", {
DIV_SYSTEM_YM2151, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2203", {
DIV_SYSTEM_OPN, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2203 (extended channel 3)", {
DIV_SYSTEM_OPN_EXT, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2608", {
DIV_SYSTEM_PC98, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2608 (extended channel 3)", {
DIV_SYSTEM_PC98_EXT, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2610", {
DIV_SYSTEM_YM2610_FULL, 64, 0, 0,
@ -76,6 +90,18 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612", {
DIV_SYSTEM_YM2612, 64, 0, (int)0x80000000,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2612 (extended channel 3)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, (int)0x80000000,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2413", {
DIV_SYSTEM_OPLL, 64, 0, 0,
@ -94,6 +120,18 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3438", {
DIV_SYSTEM_YM2612, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3438 (extended channel 3)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3526", {
DIV_SYSTEM_OPL, 64, 0, 0,
@ -106,6 +144,18 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha Y8950", {
DIV_SYSTEM_Y8950, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha Y8950 (drums mode)", {
DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM3812", {
DIV_SYSTEM_OPL2, 64, 0, 0,
@ -130,11 +180,25 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
if (settings.hiddenSystems) {
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YMU759", {
DIV_SYSTEM_YMU759, 64, 0, 0,
0
}
));
}
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Square");
cat=FurnaceGUISysCategory("Square","these chips generate square/pulse tones only (but may include noise).");
cat.systems.push_back(FurnaceGUISysDef(
"TI SN76489", {
DIV_SYSTEM_SMS, 64, 0, 4,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega PSG (SN76489-like)", {
DIV_SYSTEM_SMS, 64, 0, 0,
0
}
@ -145,15 +209,33 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YM2149(F)", {
DIV_SYSTEM_AY8910, 64, 0, 16,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Philips SAA1099", {
DIV_SYSTEM_SAA1099, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"PC Speaker", {
DIV_SYSTEM_PCSPKR, 32, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Commodore VIC", {
DIV_SYSTEM_VIC20, 64, 0, 1,
0
}
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Sample");
cat=FurnaceGUISysCategory("Sample","chips/systems which use PCM or ADPCM samples for sound synthesis.");
cat.systems.push_back(FurnaceGUISysDef(
"Amiga", {
DIV_SYSTEM_AMIGA, 64, 0, 0,
@ -186,7 +268,129 @@ void FurnaceGUI::initSystemPresets() {
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Game consoles");
cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound.");
cat.systems.push_back(FurnaceGUISysDef(
"PC Engine", {
DIV_SYSTEM_PCE, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Commodore PET (pseudo-wavetable)", {
DIV_SYSTEM_PET, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Konami Bubble System WSG", {
DIV_SYSTEM_BUBSYS_WSG, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Konami SCC", {
DIV_SYSTEM_SCC, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Konami SCC+", {
DIV_SYSTEM_SCC_PLUS, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Namco 163", {
DIV_SYSTEM_N163, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Famicom Disk System (chip)", {
DIV_SYSTEM_FDS, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"WonderSwan", {
DIV_SYSTEM_SWAN, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Seta/Allumer X1-010", {
DIV_SYSTEM_X1_010, 64, 0, 0,
0
}
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Specialized","chips/systems with unique sound synthesis methods.");
cat.systems.push_back(FurnaceGUISysDef(
"MOS Technology SID (6581)", {
DIV_SYSTEM_C64_6581, 64, 0, 1,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MOS Technology SID (8580)", {
DIV_SYSTEM_C64_8580, 64, 0, 1,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Microchip AY8930", {
DIV_SYSTEM_AY8930, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Game Boy", {
DIV_SYSTEM_GB, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Atari Lynx", {
DIV_SYSTEM_LYNX, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Atari TIA", {
DIV_SYSTEM_TIA, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Commander X16 (VERA only)", {
DIV_SYSTEM_VERA, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"ZX Spectrum (beeper only)", {
DIV_SYSTEM_SFX_BEEPER, 64, 0, 0,
0
}
));
if (settings.hiddenSystems) {
cat.systems.push_back(FurnaceGUISysDef(
"Dummy System", {
DIV_SYSTEM_DUMMY, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"tildearrow Sound Unit", {
DIV_SYSTEM_SOUND_UNIT, 64, 0, 0,
0
}
));
}
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Game consoles","let's play some chiptune making games!");
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis", {
DIV_SYSTEM_YM2612, 64, 0, 0,
@ -263,7 +467,7 @@ void FurnaceGUI::initSystemPresets() {
cat.systems.push_back(FurnaceGUISysDef(
"NES with Sunsoft 5B", {
DIV_SYSTEM_NES, 64, 0, 0,
DIV_SYSTEM_AY8910, 64, 0, 38,
DIV_SYSTEM_AY8910, 64, 0, 32,
0
}
));
@ -345,7 +549,7 @@ void FurnaceGUI::initSystemPresets() {
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Computers");
cat=FurnaceGUISysCategory("Computers","let's get to work on chiptune today.");
cat.systems.push_back(FurnaceGUISysDef(
"Commodore PET", {
DIV_SYSTEM_PET, 64, 0, 0,
@ -417,6 +621,20 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + MSX-AUDIO", {
DIV_SYSTEM_AY8910, 64, 0, 16,
DIV_SYSTEM_Y8950, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + MSX-AUDIO (drums mode)", {
DIV_SYSTEM_AY8910, 64, 0, 16,
DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + MSX-MUSIC", {
DIV_SYSTEM_AY8910, 64, 0, 16,
@ -431,6 +649,15 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + Darky", {
DIV_SYSTEM_AY8910, 64, 0, 16,
DIV_SYSTEM_AY8930, 64, 0, 139, // 3.58MHz
DIV_SYSTEM_AY8930, 64, 0, 139, // 3.58MHz or 3.6MHz selectable via register
// per-channel mixer (soft panning, post processing) isn't emulated at all
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"MSX + SCC", {
DIV_SYSTEM_AY8910, 64, 0, 16,
@ -445,6 +672,30 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"NEC PC-98 (with PC-9801-26K)", {
DIV_SYSTEM_OPN, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"NEC PC-98 (with PC-9801-26K; extended channel 3)", {
DIV_SYSTEM_OPN_EXT, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"NEC PC-98 (with PC-9801-86)", {
DIV_SYSTEM_PC98, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"NEC PC-98 (with PC-9801-86; extended channel 3)", {
DIV_SYSTEM_PC98_EXT, 64, 0, 3,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"ZX Spectrum (48K)", {
DIV_SYSTEM_AY8910, 64, 0, 2,
@ -605,13 +856,13 @@ void FurnaceGUI::initSystemPresets() {
cat.systems.push_back(FurnaceGUISysDef(
"Commander X16", {
DIV_SYSTEM_VERA, 64, 0, 0,
DIV_SYSTEM_YM2151, 64, 0, 0,
DIV_SYSTEM_YM2151, 32, 0, 0,
0
}
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Arcade systems");
cat=FurnaceGUISysCategory("Arcade systems","INSERT COIN");
cat.systems.push_back(FurnaceGUISysDef(
"Bally Midway MCR", {
DIV_SYSTEM_AY8910, 64, 0, 0,
@ -693,7 +944,7 @@ void FurnaceGUI::initSystemPresets() {
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("DefleMask-compatible");
cat=FurnaceGUISysCategory("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program.");
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis", {
DIV_SYSTEM_YM2612, 64, 0, 0,

View File

@ -883,6 +883,33 @@ void FurnaceGUI::drawSettings() {
ImGui::Text("FDS core");
ImGui::SameLine();
ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2);
ImGui::Separator();
ImGui::Text("Sample ROMs:");
ImGui::Text("OPL4 YRW801 path");
ImGui::SameLine();
ImGui::InputText("##YRW801Path",&settings.yrw801Path);
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FOLDER "##YRW801Load")) {
openFileDialog(GUI_FILE_YRW801_ROM_OPEN);
}
ImGui::Text("MultiPCM TG100 path");
ImGui::SameLine();
ImGui::InputText("##TG100Path",&settings.tg100Path);
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FOLDER "##TG100Load")) {
openFileDialog(GUI_FILE_TG100_ROM_OPEN);
}
ImGui::Text("MultiPCM MU5 path");
ImGui::SameLine();
ImGui::InputText("##MU5Path",&settings.mu5Path);
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FOLDER "##MU5Load")) {
openFileDialog(GUI_FILE_MU5_ROM_OPEN);
}
}
ImGui::EndChild();
ImGui::EndTabItem();
@ -951,6 +978,19 @@ void FurnaceGUI::drawSettings() {
);
}
bool loadChineseB=settings.loadChinese;
if (ImGui::Checkbox("Display Chinese (Simplified) characters",&loadChineseB)) {
settings.loadChinese=loadChineseB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Only toggle this option if you have enough graphics memory.\n"
"This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n"
"请在确保你有足够的显存后再启动此设定\n"
"这是一个在ImGui实现动态字体加载之前的临时解决方案"
);
}
ImGui::Separator();
ImGui::Text("Orders row number format:");
@ -1147,6 +1187,11 @@ void FurnaceGUI::drawSettings() {
settings.sampleLayout=sampleLayoutB;
}
bool oldMacroVSliderB=settings.oldMacroVSlider;
if (ImGui::Checkbox("Use classic macro editor vertical slider",&oldMacroVSliderB)) {
settings.oldMacroVSlider=oldMacroVSliderB;
}
bool roundedWindowsB=settings.roundedWindows;
if (ImGui::Checkbox("Rounded window corners",&roundedWindowsB)) {
settings.roundedWindows=roundedWindowsB;
@ -1450,6 +1495,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_LIST);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_EDIT);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SONG_INFO);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SUBSONGS);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PATTERN);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_LIST);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_EDIT);
@ -1784,6 +1830,9 @@ void FurnaceGUI::syncSettings() {
settings.saaCore=e->getConfInt("saaCore",1);
settings.nesCore=e->getConfInt("nesCore",0);
settings.fdsCore=e->getConfInt("fdsCore",0);
settings.yrw801Path=e->getConfString("yrw801Path","");
settings.tg100Path=e->getConfString("tg100Path","");
settings.mu5Path=e->getConfString("mu5Path","");
settings.mainFont=e->getConfInt("mainFont",0);
settings.patFont=e->getConfInt("patFont",0);
settings.mainFontPath=e->getConfString("mainFontPath","");
@ -1820,6 +1869,7 @@ void FurnaceGUI::syncSettings() {
settings.roundedButtons=e->getConfInt("roundedButtons",1);
settings.roundedMenus=e->getConfInt("roundedMenus",0);
settings.loadJapanese=e->getConfInt("loadJapanese",0);
settings.loadChinese=e->getConfInt("loadChinese",0);
settings.fmLayout=e->getConfInt("fmLayout",0);
settings.sampleLayout=e->getConfInt("sampleLayout",0);
settings.waveLayout=e->getConfInt("waveLayout",0);
@ -1849,6 +1899,7 @@ void FurnaceGUI::syncSettings() {
settings.hiddenSystems=e->getConfInt("hiddenSystems",0);
settings.horizontalDataView=e->getConfInt("horizontalDataView",0);
settings.noMultiSystem=e->getConfInt("noMultiSystem",0);
settings.oldMacroVSlider=e->getConfInt("oldMacroVSlider",0);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -1895,6 +1946,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.roundedButtons,0,1);
clampSetting(settings.roundedMenus,0,1);
clampSetting(settings.loadJapanese,0,1);
clampSetting(settings.loadChinese,0,1);
clampSetting(settings.fmLayout,0,3);
clampSetting(settings.susPosition,0,1);
clampSetting(settings.effectCursorDir,0,2);
@ -1920,7 +1972,8 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.moveWindowTitle,0,1);
clampSetting(settings.hiddenSystems,0,1);
clampSetting(settings.horizontalDataView,0,1);
clampSetting(settings.noMultiSystem,0,1)
clampSetting(settings.noMultiSystem,0,1);
clampSetting(settings.oldMacroVSlider,0,1);
settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys",""));
if (settings.initialSys.size()<4) {
@ -1953,6 +2006,10 @@ void FurnaceGUI::syncSettings() {
}
void FurnaceGUI::commitSettings() {
bool sampleROMsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") ||
settings.tg100Path!=e->getConfString("tg100Path","") ||
settings.mu5Path!=e->getConfString("mu5Path","");
e->setConf("mainFontSize",settings.mainFontSize);
e->setConf("patFontSize",settings.patFontSize);
e->setConf("iconSize",settings.iconSize);
@ -1968,6 +2025,9 @@ void FurnaceGUI::commitSettings() {
e->setConf("saaCore",settings.saaCore);
e->setConf("nesCore",settings.nesCore);
e->setConf("fdsCore",settings.fdsCore);
e->setConf("yrw801Path",settings.yrw801Path);
e->setConf("tg100Path",settings.tg100Path);
e->setConf("mu5Path",settings.mu5Path);
e->setConf("mainFont",settings.mainFont);
e->setConf("patFont",settings.patFont);
e->setConf("mainFontPath",settings.mainFontPath);
@ -2004,6 +2064,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("roundedButtons",settings.roundedButtons);
e->setConf("roundedMenus",settings.roundedMenus);
e->setConf("loadJapanese",settings.loadJapanese);
e->setConf("loadChinese",settings.loadChinese);
e->setConf("fmLayout",settings.fmLayout);
e->setConf("sampleLayout",settings.sampleLayout);
e->setConf("waveLayout",settings.waveLayout);
@ -2034,6 +2095,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("initialSys",e->encodeSysDesc(settings.initialSys));
e->setConf("horizontalDataView",settings.horizontalDataView);
e->setConf("noMultiSystem",settings.noMultiSystem);
e->setConf("oldMacroVSlider",settings.oldMacroVSlider);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {
@ -2055,6 +2117,12 @@ void FurnaceGUI::commitSettings() {
e->saveConf();
if (sampleROMsChanged) {
if (e->loadSampleROMs()) {
showError(e->getLastError());
}
}
if (!e->switchMaster()) {
showError("could not initialize audio!");
}
@ -2587,6 +2655,9 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
if (settings.loadJapanese) {
range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesJapanese());
}
if (settings.loadChinese) {
range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon());
}
// I'm terribly sorry
range.UsedChars[0x80>>5]=0;

View File

@ -18,6 +18,7 @@
*/
#include "gui.h"
#include "imgui.h"
#include "misc/cpp/imgui_stdlib.h"
#include "intConst.h"
@ -74,28 +75,28 @@ void FurnaceGUI::drawSongInfo() {
ImGui::TableNextColumn();
float avail=ImGui::GetContentRegionAvail().x;
ImGui::SetNextItemWidth(avail);
unsigned char realTB=e->song.timeBase+1;
unsigned char realTB=e->curSubSong->timeBase+1;
if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED
if (realTB<1) realTB=1;
if (realTB>16) realTB=16;
e->song.timeBase=realTB-1;
e->curSubSong->timeBase=realTB-1;
}
ImGui::TableNextColumn();
ImGui::Text("%.2f BPM",calcBPM(e->song.speed1,e->song.speed2,e->song.hz));
ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speed1,e->curSubSong->speed2,e->curSubSong->hz));
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Speed");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->song.speed1,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->song.speed1<1) e->song.speed1=1;
if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->curSubSong->speed1,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->speed1<1) e->curSubSong->speed1=1;
if (e->isPlaying()) play();
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->song.speed2,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->song.speed2<1) e->song.speed2=1;
if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->curSubSong->speed2,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->speed2<1) e->curSubSong->speed2=1;
if (e->isPlaying()) play();
}
@ -104,12 +105,12 @@ void FurnaceGUI::drawSongInfo() {
ImGui::Text("Highlight");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->song.hilightA,&_ONE,&_THREE)) {
if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->curSubSong->hilightA,&_ONE,&_THREE)) {
MARK_MODIFIED;
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->song.hilightB,&_ONE,&_THREE)) {
if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->curSubSong->hilightB,&_ONE,&_THREE)) {
MARK_MODIFIED;
}
@ -118,11 +119,11 @@ void FurnaceGUI::drawSongInfo() {
ImGui::Text("Pattern Length");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
int patLen=e->song.patLen;
int patLen=e->curSubSong->patLen;
if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED
if (patLen<1) patLen=1;
if (patLen>256) patLen=256;
e->song.patLen=patLen;
e->curSubSong->patLen=patLen;
}
ImGui::TableNextRow();
@ -130,11 +131,11 @@ void FurnaceGUI::drawSongInfo() {
ImGui::Text("Song Length");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
int ordLen=e->song.ordersLen;
int ordLen=e->curSubSong->ordersLen;
if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED
if (ordLen<1) ordLen=1;
if (ordLen>256) ordLen=256;
e->song.ordersLen=ordLen;
e->curSubSong->ordersLen=ordLen;
if (curOrder>=ordLen) {
setOrder(ordLen-1);
}
@ -147,7 +148,7 @@ void FurnaceGUI::drawSongInfo() {
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail);
float setHz=tempoView?e->song.hz*2.5:e->song.hz;
float setHz=tempoView?e->curSubSong->hz*2.5:e->curSubSong->hz;
if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED
if (tempoView) setHz/=2.5;
if (setHz<10) setHz=10;
@ -156,13 +157,13 @@ void FurnaceGUI::drawSongInfo() {
}
if (tempoView) {
ImGui::TableNextColumn();
ImGui::Text("= %gHz",e->song.hz);
ImGui::Text("= %gHz",e->curSubSong->hz);
} else {
if (e->song.hz>=49.98 && e->song.hz<=50.02) {
if (e->curSubSong->hz>=49.98 && e->curSubSong->hz<=50.02) {
ImGui::TableNextColumn();
ImGui::Text("PAL");
}
if (e->song.hz>=59.9 && e->song.hz<=60.11) {
if (e->curSubSong->hz>=59.9 && e->curSubSong->hz<=60.11) {
ImGui::TableNextColumn();
ImGui::Text("NTSC");
}

94
src/gui/subSongs.cpp Normal file
View File

@ -0,0 +1,94 @@
#include "gui.h"
#include "imgui.h"
#include "IconsFontAwesome4.h"
#include "misc/cpp/imgui_stdlib.h"
void FurnaceGUI::drawSubSongs() {
if (nextWindow==GUI_WINDOW_SUBSONGS) {
subSongsOpen=true;
ImGui::SetNextWindowFocus();
nextWindow=GUI_WINDOW_NOTHING;
}
if (!subSongsOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
if (ImGui::Begin("Subsongs",&subSongsOpen,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) {
char id[1024];
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x);
if (e->curSubSong->name.empty()) {
snprintf(id,1023,"%d. <no name>",(int)e->getCurrentSubSong()+1);
} else {
snprintf(id,1023,"%d. %s",(int)e->getCurrentSubSong()+1,e->curSubSong->name.c_str());
}
if (ImGui::BeginCombo("##SubSong",id)) {
for (size_t i=0; i<e->song.subsong.size(); i++) {
if (e->song.subsong[i]->name.empty()) {
snprintf(id,1023,"%d. <no name>",(int)i+1);
} else {
snprintf(id,1023,"%d. %s",(int)i+1,e->song.subsong[i]->name.c_str());
}
if (ImGui::Selectable(id,i==e->getCurrentSubSong())) {
e->changeSongP(i);
updateScroll(0);
oldOrder=0;
oldOrder1=0;
oldRow=0;
cursor.xCoarse=0;
cursor.xFine=0;
cursor.y=0;
selStart=cursor;
selEnd=cursor;
curOrder=0;
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_PLUS "##SubSongAdd")) {
if (!e->addSubSong()) {
showError("too many subsongs!");
} else {
e->changeSongP(e->song.subsong.size()-1);
updateScroll(0);
oldOrder=0;
oldOrder1=0;
oldRow=0;
cursor.xCoarse=0;
cursor.xFine=0;
cursor.y=0;
selStart=cursor;
selEnd=cursor;
curOrder=0;
MARK_MODIFIED;
}
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_MINUS "##SubSongDel")) {
if (!e->removeSubSong(e->getCurrentSubSong())) {
showError("this is the only subsong!");
} else {
undoHist.clear();
redoHist.clear();
updateScroll(0);
oldOrder=0;
oldOrder1=0;
oldRow=0;
cursor.xCoarse=0;
cursor.xFine=0;
cursor.y=0;
selStart=cursor;
selEnd=cursor;
curOrder=0;
MARK_MODIFIED;
}
}
ImGui::Text("Name");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputText("##SubSongName",&e->curSubSong->name)) {
MARK_MODIFIED;
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
ImGui::End();
}

View File

@ -168,13 +168,13 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
if (ImGui::RadioButton("1MHz (Amstrad CPC)",(flags&15)==5)) {
copyOfFlags=(flags&(~15))|5;
}
if (ImGui::RadioButton("0.89MHz (Sunsoft 5B)",(flags&15)==6)) {
if (ImGui::RadioButton("0.89MHz (Pre-divided Sunsoft 5B)",(flags&15)==6)) {
copyOfFlags=(flags&(~15))|6;
}
if (ImGui::RadioButton("1.67MHz (?)",(flags&15)==7)) {
copyOfFlags=(flags&(~15))|7;
}
if (ImGui::RadioButton("0.83MHz (Sunsoft 5B on PAL)",(flags&15)==8)) {
if (ImGui::RadioButton("0.83MHz (Pre-divided Sunsoft 5B on PAL)",(flags&15)==8)) {
copyOfFlags=(flags&(~15))|8;
}
if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) {
@ -183,6 +183,14 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) {
copyOfFlags=(flags&(~15))|10;
}
if (ImGui::RadioButton("3.58MHz (Darky)",(flags&15)==11)) {
copyOfFlags=(flags&(~15))|11;
}
if (ImGui::RadioButton("3.6MHz (Darky)",(flags&15)==12)) {
copyOfFlags=(flags&(~15))|12;
}
if (type==DIV_SYSTEM_AY8910) {
ImGui::Text("Chip type:");
if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) {
@ -199,11 +207,18 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
}
}
bool stereo=flags&0x40;
ImGui::BeginDisabled((flags&0x30)==32);
ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)==32));
if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) {
copyOfFlags=(flags&(~0x40))|(stereo?0x40:0);
}
ImGui::EndDisabled();
bool clockSel=flags&0x80;
ImGui::BeginDisabled((type==DIV_SYSTEM_AY8910) && ((flags&0x30)!=16));
if (ImGui::Checkbox("Half Clock divider##_AY_CLKSEL",&clockSel)) {
copyOfFlags=(flags&(~0x80))|(clockSel?0x80:0);
}
ImGui::EndDisabled();
break;
}
case DIV_SYSTEM_SAA1099:
@ -322,6 +337,21 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
} rightClickable
break;
}
case DIV_SYSTEM_OPN: {
if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) {
copyOfFlags=(flags&0x80000000)|0;
}
if (ImGui::RadioButton("PAL (3.54MHz)",(flags&3)==1)) {
copyOfFlags=(flags&0x80000000)|1;
}
if (ImGui::RadioButton("Arcade (4MHz)",(flags&3)==2)) {
copyOfFlags=(flags&0x80000000)|2;
}
if (ImGui::RadioButton("PC-9801-26K? TODO: CONFIRM (3MHz)",(flags&3)==3)) {
copyOfFlags=(flags&0x80000000)|3;
}
break;
}
case DIV_SYSTEM_GB:
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_VERA: