Merge branch 'tildearrow:master' into master

This commit is contained in:
BlastBrothers 2022-02-22 20:09:41 -05:00 committed by GitHub
commit 9237a0f6fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 3580 additions and 148 deletions

29
.gitignore vendored
View File

@ -1,14 +1,15 @@
.vscode/
build/
release/
t/
winbuild/
win32build/
macbuild/
linuxbuild/
*.swp
.cache/
.DS_Store
test/songs/
test/delta/
test/result/
.vscode/
build/
release/
t/
winbuild/
win32build/
macbuild/
linuxbuild/
*.swp
.cache/
.DS_Store
test/songs/
test/delta/
test/result/
.vs/

View File

@ -18,9 +18,11 @@ set(CMAKE_PROJECT_VERSION_PATCH 7)
if (ANDROID)
set(BUILD_GUI_DEFAULT OFF)
set(USE_RTMIDI_DEFAULT OFF)
set(SYSTEM_SDL2_DEFAULT ON)
else()
set(BUILD_GUI_DEFAULT ON)
set(USE_RTMIDI_DEFAULT ON)
set(SYSTEM_SDL2_DEFAULT OFF)
endif()
@ -33,6 +35,7 @@ else()
endif()
option(BUILD_GUI "Build the tracker (disable to build only a headless player)" ${BUILD_GUI_DEFAULT})
option(USE_RTMIDI "Build with MIDI support using RtMidi. Currently unfinished." ${USE_RTMIDI_DEFAULT})
option(WITH_JACK "Whether to build with JACK support. Auto-detects if JACK is available" ${WITH_JACK_DEFAULT})
option(SYSTEM_FMT "Use a system-installed version of fmt instead of the vendored one" OFF)
option(SYSTEM_LIBSNDFILE "Use a system-installed version of libsndfile instead of the vendored one" OFF)
@ -105,20 +108,22 @@ else()
message(STATUS "Using vendored libsndfile")
endif()
if (SYSTEM_RTMIDI)
find_package(PkgConfig REQUIRED)
pkg_check_modules(RTMIDI REQUIRED rtmidi)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${RTMIDI_INCLUDE_DIRS})
list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${RTMIDI_CFLAGS_OTHER})
list(APPEND DEPENDENCIES_LIBRARIES ${RTMIDI_LIBRARIES})
list(APPEND DEPENDENCIES_LIBRARY_DIRS ${RTMIDI_LIBRARY_DIRS})
list(APPEND DEPENDENCIES_LINK_OPTIONS ${RTMIDI_LDFLAGS_OTHER})
list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${RTMIDI_LDFLAGS})
message(STATUS "Using system-installed RtMidi")
else()
add_subdirectory(extern/rtmidi EXCLUDE_FROM_ALL)
list(APPEND DEPENDENCIES_LIBRARIES rtmidi)
message(STATUS "Using vendored RtMidi")
if (USE_RTMIDI)
if (SYSTEM_RTMIDI)
find_package(PkgConfig REQUIRED)
pkg_check_modules(RTMIDI REQUIRED rtmidi)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${RTMIDI_INCLUDE_DIRS})
list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${RTMIDI_CFLAGS_OTHER})
list(APPEND DEPENDENCIES_LIBRARIES ${RTMIDI_LIBRARIES})
list(APPEND DEPENDENCIES_LIBRARY_DIRS ${RTMIDI_LIBRARY_DIRS})
list(APPEND DEPENDENCIES_LINK_OPTIONS ${RTMIDI_LDFLAGS_OTHER})
list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${RTMIDI_LDFLAGS})
message(STATUS "Using system-installed RtMidi")
else()
add_subdirectory(extern/rtmidi EXCLUDE_FROM_ALL)
list(APPEND DEPENDENCIES_LIBRARIES rtmidi)
message(STATUS "Using vendored RtMidi")
endif()
endif()
if (SYSTEM_ZLIB)
@ -210,6 +215,13 @@ else()
message(STATUS "Building without JACK support")
endif()
if (USE_RTMIDI)
list(APPEND AUDIO_SOURCES src/audio/rtmidi.cpp)
message(STATUS "Building with RtMidi")
else()
message(STATUS "Building without RtMidi")
endif()
set(ENGINE_SOURCES
src/log.cpp
src/fileutils.cpp
@ -260,6 +272,10 @@ src/engine/platform/sound/ymfm/ymfm_opn.cpp
src/engine/platform/sound/ymfm/ymfm_opz.cpp
src/engine/platform/sound/ymfm/ymfm_ssg.cpp
src/engine/platform/sound/lynx/Mikey.cpp
src/engine/platform/sound/qsound.c
src/engine/platform/ym2610Interface.cpp
src/engine/blip_buf.c
@ -294,7 +310,9 @@ src/engine/platform/ay8930.cpp
src/engine/platform/tia.cpp
src/engine/platform/saa.cpp
src/engine/platform/amiga.cpp
src/engine/platform/qsound.cpp
src/engine/platform/dummy.cpp
src/engine/platform/lynx.cpp
)
if (WIN32)

View File

@ -23,7 +23,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (.
- Amiga
- TIA (Atari 2600/7800)
- multiple sound chips in a single song!
- clean-room design (zero reverse-engineered code and zero decompilation; using official DMF specs, guesswork and ABX tests only)
- clean-room design (guesswork and ABX tests only, no decompilation involved)
- bug/quirk implementation for increased playback accuracy
- VGM and audio file export
- accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID, Stella, SAASound and ymfm)

BIN
demos/Ice_Wind_OPMSPCM.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -22,6 +22,7 @@ depending on the instrument type, there are currently 10 different types of an i
- [TIA](tia.md) - for use with Atari 2600 system.
- [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610.
- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode.
- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console.
# macros

View File

@ -0,0 +1,25 @@
# Atari Lynx instrument editor
Atari Lynx instrument editor consists of only three macros:
- [Volume] - volume sequence
- [Arpeggio] - pitch sequencer
- [Duty/Int] - bit pattern for LFSR taps and integration.
## Audio generation description
Atari Lynx to generate sound uses 12-bit linear feedback shift register with configurable tap. Nine separate bits can be enable to be the source of feedback.
Namely bits 0, 1, 2, 3, 4, 5, 7, 10 and 11. To generate ANY sound at least one bit MUST be enable.
### Square wave
The LFSR is shifted at the rate define by sound pitch and generates square wave by setting channel output value to +volume or -volume, depending on the bit shifted in.
### Triangle wave
Alternatively when "int" bit is set sound wave is generated by adding or subtracting volume from output effectively producing triangle wave.
#### How triangle wave works?
Hint: To obtain triangle set bits "int" and "11" in "Duty/Int" sequence and set volume to about 22.
By enabling 11th tap bit the value shifted in is negated after 11 bit is shifted in hence the volume is added for 11 cycles and then subtracted for 11 cycles.

View File

@ -26,3 +26,6 @@ In there, you can modify certain data pertaining to your sample, such as the:
- and the sample rate of the sample (from 1KHz (1,000Hz) to 32KHz (32,000Hz)).
To apply the changes you made to a sample, just click the `apply` button at the bottom, near the preview button.
# tips
If you have a sample you wanna use that is about 44100 or anything over 32000Hz downsample the sample to 32000Hz so that the pitch of the sample in Furnace stays like the original wav. You can do this in Audacity by going to the Bottom Left of audacity (If you see "Project Rate (Hz)" you are there) and changing the project rate to 32000Hz and save the file to wav in Audacity using "File -> Export -> Export as WAV".

View File

@ -0,0 +1,21 @@
# Yamaha YM2413/OPLL
The YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip manufactured by Yamaha Corporation and based on the Yamaha YM3812 sound chip (OPL2).
As of Furnace version 0.5.7pre4, the OPLL sound chip is not yet emulated. It is, however, emulated in Deflemask as of version 1.1.0. Support for loading .DMFs which contain the YM2413 was added in Furnace version 0.5.7pre4.
## Technical specifications
The YM2413 is equipped with the following features:
- 9 channels of 2 operator FM synthesis
- A drum/percussion mode, replacing the last 3 voices with 3 rhythm channels
- 1 user-definable patch (this patch can be changed throughout the course of the song)
- 15 pre-defined patches which can all be used at the same time
- Support for ADSR on both the modulator and the carrier
- Sine and half-sine based FM synthesis
- 9 octave note control
- 4096 different frequencies for channels
- 16 unique volume levels (NOTE: Volume 0 is NOT silent.)
- Modulator and carrier key scaling
- Built-in hardware vibrato support
## Effect commands
TODO: Add effect commands here when YM2413 emulation is added.

View File

@ -0,0 +1,18 @@
# Capcom QSound (DL-1425)
This chip was used in Capcom's CP System Dash, CP System II and ZN arcade PCBs.
It supports 16 PCM channels and uses the patented (now expired) QSound stereo expansion algorithm, as the name implies.
Because the chip lacks sample interpolation, it's recommended that you try to play samples at around 24038 Hz to avoid aliasing. This is especially important for e.g. cymbals.
The QSound chip also has a small echo buffer, somewhat similar to the SNES, although with a very basic (and non-adjustable) filter. It is however possible to adjust the feedback and length of the echo buffer. The initial values can be set in the "Configure system" dialog.
There are also 3 ADPCM channels, however they cannot be used in Furnace yet. They have been reserved in case this feature is added later. ADPCM samples are limited to 8012 Hz.
# effects
- `08xx`: Set panning. Valid range is 00-20. 00 for full left, 10 for center and 20 for full right. It is also possible to bypass the QSound algorithm by using the range 30-50.
- `10xx`: Set echo feedback level. This effect will apply to all channels.
- `11xx`: Set echo level.
- `3xxx`: Set the length of the echo delay buffer.

View File

@ -17,6 +17,8 @@ writers:
- tildearrow
- freq-mod
- nicco1690
- DeMOSic
- cam900
other:

View File

@ -25,6 +25,10 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are:
- 57: Furnace dev57
- 53: Furnace 0.5.7
- 52: Furnace 0.5.7pre4
- 51: Furnace 0.5.7pre3
- 50: Furnace 0.5.7pre2
- 49: Furnace 0.5.7pre1
@ -151,7 +155,7 @@ size | description
| - 0xa6: OPL3 4-op + drums (YMF262) - 14 channels
| - 0xa7: OPLL drums (YM2413) - 11 channels
| - 0xa8: Atari Lynx - 4 channels
| - 0xe0: QSound - 16 channels
| - 0xe0: QSound - 19 channels
32 | sound chip volumes
| - signed char, 64=1.0, 127=~2.0
32 | sound chip panning

1
src/audio/rtmidi.cpp Normal file
View File

@ -0,0 +1 @@
#include "rtmidi.h"

View File

@ -97,6 +97,12 @@ enum DivDispatchCmds {
DIV_CMD_SAA_ENVELOPE,
DIV_CMD_LYNX_LFSR_LOAD,
DIV_CMD_QSOUND_ECHO_FEEDBACK,
DIV_CMD_QSOUND_ECHO_DELAY,
DIV_CMD_QSOUND_ECHO_LEVEL,
DIV_ALWAYS_SET_VOLUME,
DIV_CMD_MAX
@ -204,15 +210,34 @@ class DivDispatch {
* @return a pointer, or NULL.
*/
virtual void* getChanState(int chan);
/**
* get the register pool of this dispatch.
* @return a pointer, or NULL.
*/
virtual unsigned char* getRegisterPool();
/**
* get this dispatch's state.
* get the size of the register pool of this dispatch.
* @return the size.
*/
virtual int getRegisterPoolSize();
/**
* get the bit depth of the register pool of this dispatch.
* If the result is 16, it should be casted to unsigned short
* @return the depth. Default value is 8
*/
virtual int getRegisterPoolDepth();
/**
* get this dispatch's state. DO NOT IMPLEMENT YET.
* @return a pointer to the dispatch's state. must be deallocated manually!
*/
virtual void* getState();
/**
* set this dispatch's state.
* set this dispatch's state. DO NOT IMPLEMENT YET.
* @param state a pointer to a state pertaining to this dispatch,
* or NULL if this dispatch does not support state saves.
*/

View File

@ -34,7 +34,9 @@
#include "platform/tia.h"
#include "platform/saa.h"
#include "platform/amiga.h"
#include "platform/qsound.h"
#include "platform/dummy.h"
#include "platform/lynx.h"
#include "../ta-log.h"
#include "song.h"
@ -199,6 +201,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore);
break;
}
case DIV_SYSTEM_LYNX:
dispatch=new DivPlatformLynx;
break;
case DIV_SYSTEM_QSOUND:
dispatch=new DivPlatformQSound;
break;
default:
logW("this system is not supported yet! using dummy platform.\n");
dispatch=new DivPlatformDummy;

View File

@ -400,6 +400,7 @@ bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode)
exportMode=mode;
exporting=true;
stop();
repeatPattern=false;
setOrder(0);
remainingLoops=loops;
exportThread=new std::thread(_runExportThread,this);
@ -548,9 +549,9 @@ void DivEngine::renderSamples() {
if (diff>=tempstep) encoded|=1;
acc+=jediTable[decstep+encoded];
if (acc>0x7ff || acc<-0x800) {
/*if (acc>0x7ff || acc<-0x800) {
logW("clipping! %d\n",acc);
}
}*/
acc&=0xfff;
if (acc&0x800) acc|=~0xfff;
decstep+=adStepSeek[encoded&7]*16;
@ -596,6 +597,41 @@ void DivEngine::renderSamples() {
memPos+=s->adpcmRendLength;
}
adpcmMemLen=memPos+256;
// step 4: allocate qsound pcm samples
if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216];
memset(qsoundMem, 0, 16777216);
memPos=0;
for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
int length = s->rendLength;
if(length > 65536-16)
length = 65536-16;
if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) {
memPos=(memPos+0xffff)&0xff0000;
}
if (memPos>=16777216) {
logW("out of QSound PCM memory for sample %d!\n",i);
break;
}
if (memPos+length>=16777216) {
for(unsigned int i=0; i<16777216-(memPos+length); i++)
{
qsoundMem[(memPos + i) ^ 0x8000] = s->rendData[i] >> ((s->depth == 16) ? 8 : 0);
}
logW("out of QSound PCM memory for sample %d!\n",i);
} else {
for(int i=0; i<length; i++)
{
qsoundMem[(memPos + i) ^ 0x8000] = s->rendData[i] >> ((s->depth == 16) ? 8 : 0);
}
}
s->rendOffQsound=memPos ^ 0x8000;
memPos+=length+16;
}
qsoundMemLen=memPos+256;
}
void DivEngine::createNew() {
@ -728,6 +764,14 @@ void* DivEngine::getDispatchChanState(int ch) {
return disCont[dispatchOfChan[ch]].dispatch->getChanState(dispatchChanOfChan[ch]);
}
unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) {
if (sys<0 || sys>=song.systemLen) return NULL;
if (disCont[sys].dispatch==NULL) return NULL;
size=disCont[sys].dispatch->getRegisterPoolSize();
depth=disCont[sys].dispatch->getRegisterPoolDepth();
return disCont[sys].dispatch->getRegisterPool();
}
void DivEngine::enableCommandStream(bool enable) {
cmdStreamEnabled=enable;
}
@ -1879,7 +1923,7 @@ bool DivEngine::addSampleFromFile(const char* path) {
delete[] buf;
sample->rate=si.samplerate;
if (sample->rate<4000) sample->rate=4000;
if (sample->rate>32000) sample->rate=32000;
if (sample->rate>96000) sample->rate=96000;
song.sample.push_back(sample);
song.sampleLen=sampleCount+1;

View File

@ -37,8 +37,8 @@
warnings+=(String("\n")+x); \
}
#define DIV_VERSION "0.5.7pre4"
#define DIV_ENGINE_VERSION 52
#define DIV_VERSION "dev57"
#define DIV_ENGINE_VERSION 57
enum DivStatusView {
DIV_STATUS_NOTHING=0,
@ -529,6 +529,9 @@ class DivEngine {
// get dispatch channel state
void* getDispatchChanState(int chan);
// get register pool
unsigned char* getRegisterPool(int sys, int& size, int& depth);
// enable command stream dumping
void enableCommandStream(bool enable);
@ -621,6 +624,8 @@ class DivEngine {
size_t adpcmMemLen;
unsigned char* adpcmBMem;
size_t adpcmBMemLen;
unsigned char* qsoundMem;
size_t qsoundMemLen;
DivEngine():
output(NULL),
@ -678,6 +683,8 @@ class DivEngine {
adpcmMem(NULL),
adpcmMemLen(0),
adpcmBMem(NULL),
adpcmBMemLen(0) {}
adpcmBMemLen(0),
qsoundMem(NULL),
qsoundMemLen(0) {}
};
#endif

View File

@ -135,6 +135,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.brokenShortcutSlides=false;
ds.ignoreDuplicateSlides=true;
// 1.1 compat flags
if (ds.version>24) {
ds.waveDutyIsVol=true;
ds.legacyVolumeSlides=false;
}
// Neo Geo detune
if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT) {
ds.tuning=443.23;
@ -950,7 +956,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
// while version 32 stored this value, it was unused.
if (ds.version>=38) {
sample->centerRate=reader.readS();
sample->centerRate=(unsigned short) reader.readS();
} else {
reader.readS();
}

View File

@ -47,6 +47,7 @@ enum DivInstrumentType {
DIV_INS_POKEY=20,
DIV_INS_BEEPER=21,
DIV_INS_SWAN=22,
DIV_INS_MIKEY=23,
};
struct DivInstrumentFM {

View File

@ -29,6 +29,18 @@ void* DivDispatch::getChanState(int chan) {
return NULL;
}
unsigned char* DivDispatch::getRegisterPool() {
return NULL;
}
int DivDispatch::getRegisterPoolSize() {
return 0;
}
int DivDispatch::getRegisterPoolDepth() {
return 8;
}
void* DivDispatch::getState() {
return NULL;
}

View File

@ -145,6 +145,7 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
QueuedWrite& w=writes.front();
if (w.addrOrVal) {
OPM_Write(&fm,1,w.val);
regPool[w.addr&0xff]=w.val;
//printf("write: %x = %.2x\n",w.addr,w.val);
writes.pop();
} else {
@ -216,6 +217,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
QueuedWrite& w=writes.front();
fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0xff]=w.val;
writes.pop();
delay=1;
}
@ -877,6 +879,14 @@ void* DivPlatformArcade::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformArcade::getRegisterPool() {
return regPool;
}
int DivPlatformArcade::getRegisterPoolSize() {
return 256;
}
void DivPlatformArcade::poke(unsigned int addr, unsigned short val) {
immWrite(addr,val);
}
@ -887,6 +897,7 @@ void DivPlatformArcade::poke(std::vector<DivRegWrite>& wlist) {
void DivPlatformArcade::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,256);
if (useYMFM) {
fm_ymfm->reset();
} else {

View File

@ -71,6 +71,8 @@ class DivPlatformArcade: public DivDispatch {
ymfm::ym2151::output_data out_ymfm;
DivArcadeInterface iface;
unsigned char regPool[256];
bool extMode, useYMFM;
bool isMuted[13];
@ -90,6 +92,8 @@ class DivPlatformArcade: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -94,6 +94,7 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l
QueuedWrite w=writes.front();
ay->address_w(w.addr);
ay->data_w(w.val);
regPool[w.addr&0x0f]=w.val;
writes.pop();
}
ay->sound_stream_update(ayBuf,len);
@ -400,9 +401,18 @@ void* DivPlatformAY8910::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformAY8910::getRegisterPool() {
return regPool;
}
int DivPlatformAY8910::getRegisterPoolSize() {
return 16;
}
void DivPlatformAY8910::reset() {
while (!writes.empty()) writes.pop();
ay->device_reset();
memset(regPool,0,16);
for (int i=0; i<3; i++) {
chan[i]=DivPlatformAY8910::Channel();
chan[i].vol=0x0f;

View File

@ -47,6 +47,7 @@ class DivPlatformAY8910: public DivDispatch {
};
std::queue<QueuedWrite> writes;
ay8910_device* ay;
unsigned char regPool[16];
unsigned char lastBusy;
bool dacMode;
@ -76,6 +77,8 @@ class DivPlatformAY8910: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -124,6 +124,7 @@ void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t l
} else {
ay->data_w(w.val);
}
regPool[w.addr&0x1f]=w.val;
writes.pop();
}
ay->sound_stream_update(ayBuf,len);
@ -462,9 +463,18 @@ void* DivPlatformAY8930::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformAY8930::getRegisterPool() {
return regPool;
}
int DivPlatformAY8930::getRegisterPoolSize() {
return 32;
}
void DivPlatformAY8930::reset() {
while (!writes.empty()) writes.pop();
ay->device_reset();
memset(regPool,0,32);
for (int i=0; i<3; i++) {
chan[i]=DivPlatformAY8930::Channel();
chan[i].vol=31;

View File

@ -47,6 +47,7 @@ class DivPlatformAY8930: public DivDispatch {
};
std::queue<QueuedWrite> writes;
ay8930_device* ay;
unsigned char regPool[32];
unsigned char ayNoiseAnd, ayNoiseOr;
bool bank;
@ -69,6 +70,8 @@ class DivPlatformAY8930: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -21,7 +21,7 @@
#include "../engine.h"
#include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {sid.write(a,v); if (dumpWrites) {addWrite(a,v);} }
#define rWrite(a,v) if (!skipRegisterWrites) {sid.write(a,v); regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} }
#define CHIP_FREQBASE 524288
@ -467,12 +467,21 @@ void* DivPlatformC64::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformC64::getRegisterPool() {
return regPool;
}
int DivPlatformC64::getRegisterPoolSize() {
return 32;
}
void DivPlatformC64::reset() {
for (int i=0; i<3; i++) {
chan[i]=DivPlatformC64::Channel();
}
sid.reset();
memset(regPool,0,32);
rWrite(0x18,0x0f);

View File

@ -70,6 +70,7 @@ class DivPlatformC64: public DivDispatch {
int filtCut, resetTime;
SID sid;
unsigned char regPool[32];
friend void putDispatchChan(void*,int,int);
@ -78,6 +79,8 @@ class DivPlatformC64: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -21,8 +21,8 @@
#include "../engine.h"
#include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {GB_apu_write(gb,a,v); if (dumpWrites) {addWrite(a,v);} }
#define immWrite(a,v) {GB_apu_write(gb,a,v); if (dumpWrites) {addWrite(a,v);} }
#define rWrite(a,v) if (!skipRegisterWrites) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
#define immWrite(a,v) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 16
@ -395,6 +395,14 @@ void* DivPlatformGB::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformGB::getRegisterPool() {
return regPool;
}
int DivPlatformGB::getRegisterPoolSize() {
return 64;
}
void DivPlatformGB::reset() {
for (int i=0; i<4; i++) {
chan[i]=DivPlatformGB::Channel();
@ -403,6 +411,7 @@ void DivPlatformGB::reset() {
addWrite(0xffffffff,0);
}
memset(gb,0,sizeof(GB_gameboy_t));
memset(regPool,0,128);
gb->model=GB_MODEL_DMG_B;
GB_apu_init(gb);
GB_set_sample_rate(gb,rate);

View File

@ -55,6 +55,8 @@ class DivPlatformGB: public DivDispatch {
unsigned char lastPan;
GB_gameboy_t* gb;
unsigned char regPool[128];
unsigned char procMute();
void updateWave();
friend void putDispatchChan(void*,int,int);
@ -62,6 +64,8 @@ class DivPlatformGB: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -121,6 +121,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val);
//printf("write: %x = %.2x\n",w.addr,w.val);
lastBusy=0;
regPool[w.addr&0x1ff]=w.val;
writes.pop();
} else {
lastBusy++;
@ -190,6 +191,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
QueuedWrite& w=writes.front();
fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0x1ff]=w.val;
writes.pop();
lastBusy=1;
}
@ -805,8 +807,17 @@ void* DivPlatformGenesis::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformGenesis::getRegisterPool() {
return regPool;
}
int DivPlatformGenesis::getRegisterPoolSize() {
return 512;
}
void DivPlatformGenesis::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,512);
if (useYMFM) {
fm_ymfm->reset();
}

View File

@ -79,6 +79,7 @@ class DivPlatformGenesis: public DivDispatch {
ymfm::ym2612* fm_ymfm;
ymfm::ym2612::output_data out_ymfm;
DivYM2612Interface iface;
unsigned char regPool[512];
bool dacMode;
int dacPeriod;
@ -106,6 +107,8 @@ class DivPlatformGenesis: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -0,0 +1,384 @@
/**
* 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 "lynx.h"
#include "../engine.h"
#include <math.h>
#define rWrite(a,v) {if (!skipRegisterWrites) {mikey->write(a,v); if (dumpWrites) {addWrite(a,v);}}}
#define WRITE_VOLUME(ch,v) rWrite(0x20+(ch<<3),(v))
#define WRITE_FEEDBACK(ch,v) rWrite(0x21+(ch<<3),(v))
#define WRITE_LFSR(ch,v) rWrite(0x23+(ch<<3),(v))
#define WRITE_BACKUP(ch,v) rWrite(0x24+(ch<<3),(v))
#define WRITE_CONTROL(ch,v) rWrite(0x25+(ch<<3),(v))
#define WRITE_OTHER(ch,v) rWrite(0x27+(ch<<3),(v))
#define WRITE_ATTEN(ch,v) rWrite((0x40+ch),(v))
#define CHIP_DIVIDER 64
#if defined( _MSC_VER )
#include <intrin.h>
static int bsr(uint16_t v) {
unsigned long idx;
if (_BitScanReverse(&idx,(unsigned long)v)) {
return idx;
}
else {
return -1;
}
}
#elif defined( __GNUC__ )
static int bsr(uint16_t v)
{
if (v) {
return 16 - __builtin_clz(v);
}
else{
return -1;
}
}
#else
static int bsr(uint16_t v)
{
uint16_t mask = 0x8000;
for (int i = 31; i >= 0; --i) {
if (v&mask)
return (int)i;
mask>>=1;
}
return -1;
}
#endif
static int32_t clamp(int32_t v, int32_t lo, int32_t hi)
{
return v<lo?lo:(v>hi?hi:v);
}
const char* regCheatSheetLynx[]={
"DATA", "0",
NULL
};
const char** DivPlatformLynx::getRegisterSheet() {
return regCheatSheetLynx;
}
const char* DivPlatformLynx::getEffectName(unsigned char effect) {
switch (effect)
{
case 0x30: case 0x31: case 0x32: case 0x33:
case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3a: case 0x3b:
case 0x3c: case 0x3d: case 0x3e: case 0x3f:
return "3xxx: Load LFSR (0 to FFF)";
break;
}
return NULL;
}
void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len) {
mikey->sampleAudio( bufL + start, bufR + start, len );
}
void DivPlatformLynx::tick() {
for (int i=0; i<4; i++) {
chan[i].std.next();
if (chan[i].std.hadVol) {
chan[i].outVol=((chan[i].vol&127)*MIN(127,chan[i].std.vol))>>7;
WRITE_VOLUME(i,(isMuted[i]?0:(chan[i].outVol&127)));
}
if (chan[i].std.hadArp) {
if (!chan[i].inPorta) {
if (chan[i].std.arpMode) {
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp);
chan[i].actualNote=chan[i].std.arp;
} else {
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp);
chan[i].actualNote=chan[i].note+chan[i].std.arp;
}
chan[i].freqChanged=true;
}
} else {
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
chan[i].actualNote=chan[i].note;
chan[i].freqChanged=true;
}
}
if (chan[i].freqChanged) {
if (chan[i].lfsr >= 0) {
WRITE_LFSR(i, (chan[i].lfsr&0xff));
WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4));
chan[i].lfsr=-1;
}
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true);
if (chan[i].std.hadDuty) {
chan[i].duty=chan[i].std.duty;
WRITE_FEEDBACK(i, chan[i].duty.feedback);
}
WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7));
WRITE_BACKUP( i, chan[i].fd.backup );
}
else if (chan[i].std.hadDuty) {
chan[i].duty = chan[i].std.duty;
WRITE_FEEDBACK(i, chan[i].duty.feedback);
WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7));
}
}
}
int DivPlatformLynx::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
chan[c.chan].actualNote=c.value;
if (chan[c.chan].lfsr<0)
chan[c.chan].lfsr=0;
}
chan[c.chan].active=true;
WRITE_VOLUME(c.chan,(isMuted[c.chan]?0:(chan[c.chan].vol&127)));
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
break;
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
WRITE_VOLUME(c.chan, 0);
chan[c.chan].std.init(NULL);
break;
case DIV_CMD_LYNX_LFSR_LOAD:
chan[c.chan].freqChanged=true;
chan[c.chan].lfsr=c.value;
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
chan[c.chan].ins=c.value;
//chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.hasVol) {
chan[c.chan].outVol=c.value;
}
if (chan[c.chan].active) WRITE_VOLUME(c.chan,(isMuted[c.chan]?0:(chan[c.chan].vol&127)));
}
break;
case DIV_CMD_PANNING:
WRITE_ATTEN(c.chan, c.value);
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.hasVol) {
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_PERIODIC(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_LEGATO:
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
chan[c.chan].actualNote=c.value;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 127;
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;
default:
break;
}
return 1;
}
void DivPlatformLynx::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (chan[ch].active) WRITE_VOLUME(ch,(isMuted[ch]?0:(chan[ch].outVol&127)));
}
void DivPlatformLynx::forceIns() {
for (int i=0; i<4; i++) {
if (chan[i].active) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
}
}
}
void* DivPlatformLynx::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformLynx::getRegisterPool()
{
return const_cast<unsigned char*>( mikey->getRegisterPool() );
}
int DivPlatformLynx::getRegisterPoolSize()
{
return 4*8+4;
}
void DivPlatformLynx::reset() {
mikey = std::make_unique<Lynx::Mikey>( rate );
for (int i=0; i<4; i++) {
chan[i]= DivPlatformLynx::Channel();
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
}
bool DivPlatformLynx::keyOffAffectsArp(int ch) {
return true;
}
bool DivPlatformLynx::keyOffAffectsPorta(int ch) {
return true;
}
//int DivPlatformLynx::getPortaFloor(int ch) {
// return 12;
//}
void DivPlatformLynx::notifyInsDeletion(void* ins) {
for (int i=0; i<4; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformLynx::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformLynx::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr, i.val);
}
int DivPlatformLynx::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<4; i++) {
isMuted[i]=false;
}
chipClock = 16000000;
rate = chipClock/128;
reset();
return 4;
}
void DivPlatformLynx::quit() {
mikey.reset();
}
DivPlatformLynx::~DivPlatformLynx() {
}
DivPlatformLynx::MikeyFreqDiv::MikeyFreqDiv(int frequency) {
int clamped=clamp(frequency, 36, 16383);
auto top=bsr(clamped);
if (top>7)
{
clockDivider=top-7;
backup=frequency>>(top-7);
}
else
{
clockDivider=0;
backup=frequency;
}
}
DivPlatformLynx::MikeyDuty::MikeyDuty(int duty) {
//duty:
//9: int
//8: f11
//7: f10
//6: f7
//5: f5
//4: f4
//3: f3
//2: f2
//1: f1
//0: f0
//f7 moved to bit 7 and int moved to bit 5
int_feedback7=((duty&0x40)<<1)|((duty&0x200)>>4);
//f11 and f10 moved to bits 7 & 6
feedback=(duty&0x3f)|((duty&0x180)>>1);
}

View File

@ -0,0 +1,78 @@
#ifndef _LYNX_H
#define _LYNX_H
#include "../dispatch.h"
#include "../macroInt.h"
#include "sound/lynx/Mikey.hpp"
class DivPlatformLynx: public DivDispatch {
struct MikeyFreqDiv {
uint8_t clockDivider;
uint8_t backup;
MikeyFreqDiv(int frequency);
};
struct MikeyDuty {
uint8_t int_feedback7;
uint8_t feedback;
MikeyDuty(int duty);
};
struct Channel {
DivMacroInt std;
MikeyFreqDiv fd;
MikeyDuty duty;
int baseFreq, pitch, note, actualNote, lfsr;
unsigned char ins;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
signed char vol, outVol;
Channel():
std(),
fd(0),
duty(0),
baseFreq(0),
pitch(0),
note(0),
actualNote(0),
lfsr(-1),
ins(-1),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
vol(127),
outVol(127) {}
};
Channel chan[4];
bool isMuted[4];
std::unique_ptr<Lynx::Mikey> mikey;
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);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch);
//int getPortaFloor(int ch);
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();
~DivPlatformLynx();
};
#endif

View File

@ -25,7 +25,7 @@
#define CHIP_DIVIDER 16
#define rWrite(a,v) if (!skipRegisterWrites) {apu_wr_reg(nes,a,v); if (dumpWrites) {addWrite(a,v);} }
#define rWrite(a,v) if (!skipRegisterWrites) {apu_wr_reg(nes,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
const char* regCheatSheetNES[]={
"S0Volume", "4000",
@ -444,6 +444,14 @@ void* DivPlatformNES::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformNES::getRegisterPool() {
return regPool;
}
int DivPlatformNES::getRegisterPoolSize() {
return 32;
}
void DivPlatformNES::reset() {
for (int i=0; i<5; i++) {
chan[i]=DivPlatformNES::Channel();
@ -459,6 +467,7 @@ void DivPlatformNES::reset() {
sampleBank=0;
apu_turn_on(nes,apuType);
memset(regPool,0,128);
nes->apu.cpu_cycles=0;
nes->apu.cpu_opcode_cycle=0;

View File

@ -59,6 +59,7 @@ class DivPlatformNES: public DivDispatch {
unsigned char sampleBank;
unsigned char apuType;
struct NESAPU* nes;
unsigned char regPool[128];
friend void putDispatchChan(void*,int,int);
@ -66,6 +67,8 @@ class DivPlatformNES: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -29,6 +29,7 @@
curChan=c; \
rWrite(0,curChan); \
} \
regPool[16+((c)<<4)+((a)&0x0f)]=v; \
rWrite(a,v); \
}
@ -88,11 +89,12 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
chWrite(i,0x07,0);
if (s->depth==8) {
chWrite(i,0x04,0xdf);
chWrite(i,0x06,(((unsigned char)s->rendData[chan[i].dacPos++]+0x80)>>3));
chWrite(i,0x06,(((unsigned char)s->rendData[chan[i].dacPos]+0x80)>>3));
} else {
chWrite(i,0x04,0xdf);
chWrite(i,0x06,(((unsigned short)s->rendData[chan[i].dacPos++]+0x8000)>>11));
chWrite(i,0x06,(((unsigned short)s->rendData[chan[i].dacPos]+0x8000)>>11));
}
chan[i].dacPos++;
if (chan[i].dacPos>=s->rendLength) {
if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) {
chan[i].dacPos=s->loopStart;
@ -110,6 +112,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
while (!writes.empty() && cycles<24) {
QueuedWrite w=writes.front();
pce->Write(cycles,w.addr,w.val);
regPool[w.addr&0x0f]=w.val;
//cycles+=2;
writes.pop();
}
@ -442,8 +445,17 @@ void* DivPlatformPCE::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformPCE::getRegisterPool() {
return regPool;
}
int DivPlatformPCE::getRegisterPoolSize() {
return 112;
}
void DivPlatformPCE::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,128);
for (int i=0; i<6; i++) {
chan[i]=DivPlatformPCE::Channel();
}

View File

@ -74,12 +74,15 @@ class DivPlatformPCE: public DivDispatch {
int tempR[32];
unsigned char sampleBank, lfoMode, lfoSpeed;
PCE_PSG* pce;
unsigned char regPool[128];
void updateWave(int ch);
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);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -0,0 +1,625 @@
/**
* 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 "qsound.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#include <map>
#define CHIP_DIVIDER (1248*2)
#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,0x1000,(x)-3,false)
#define rWrite(a,v) {if(!skipRegisterWrites) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v); }}
#define immWrite(a,v) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v);}
const char* regCheatSheetQSound[]={
"Ch15_Bank", "00",
"Ch00_Start", "01",
"Ch00_Freq", "02",
"Ch00_Phase", "03",
"Ch00_Loop", "04",
"Ch00_End", "05",
"Ch00_Volume", "06",
"Ch00_Bank", "08",
"Ch01_Start", "09",
"Ch01_Freq", "0A",
"Ch01_Phase", "0B",
"Ch01_Loop", "0C",
"Ch01_End", "0D",
"Ch01_Volume", "0E",
"Ch01_Bank", "10",
"Ch02_Start", "11",
"Ch02_Freq", "12",
"Ch02_Phase", "13",
"Ch02_Loop", "14",
"Ch02_End", "15",
"Ch02_Volume", "16",
"Ch02_Bank", "18",
"Ch03_Start", "19",
"Ch03_Freq", "1A",
"Ch03_Phase", "1B",
"Ch03_Loop", "1C",
"Ch03_End", "1D",
"Ch03_Volume", "1E",
"Ch03_Bank", "20",
"Ch04_Start", "21",
"Ch04_Freq", "22",
"Ch04_Phase", "23",
"Ch04_Loop", "24",
"Ch04_End", "25",
"Ch04_Volume", "26",
"Ch04_Bank", "28",
"Ch05_Start", "29",
"Ch05_Freq", "2A",
"Ch05_Phase", "2B",
"Ch05_Loop", "2C",
"Ch05_End", "2D",
"Ch05_Volume", "2E",
"Ch05_Bank", "30",
"Ch06_Start", "31",
"Ch06_Freq", "32",
"Ch06_Phase", "33",
"Ch06_Loop", "34",
"Ch06_End", "35",
"Ch06_Volume", "36",
"Ch06_Bank", "38",
"Ch07_Start", "39",
"Ch07_Freq", "3A",
"Ch07_Phase", "3B",
"Ch07_Loop", "3C",
"Ch07_End", "3D",
"Ch07_Volume", "3E",
"Ch07_Bank", "40",
"Ch08_Start", "41",
"Ch08_Freq", "42",
"Ch08_Phase", "43",
"Ch08_Loop", "44",
"Ch08_End", "45",
"Ch08_Volume", "46",
"Ch08_Bank", "48",
"Ch09_Start", "49",
"Ch09_Freq", "4A",
"Ch09_Phase", "4B",
"Ch09_Loop", "4C",
"Ch09_End", "4D",
"Ch09_Volume", "4E",
"Ch09_Bank", "50",
"Ch10_Start", "51",
"Ch10_Freq", "52",
"Ch10_Phase", "53",
"Ch10_Loop", "54",
"Ch10_End", "55",
"Ch10_Volume", "56",
"Ch10_Bank", "58",
"Ch11_Start", "59",
"Ch11_Freq", "5A",
"Ch11_Phase", "5B",
"Ch11_Loop", "5C",
"Ch11_End", "5D",
"Ch11_Volume", "5E",
"Ch11_Bank", "60",
"Ch12_Start", "61",
"Ch12_Freq", "62",
"Ch12_Phase", "63",
"Ch12_Loop", "64",
"Ch12_End", "65",
"Ch12_Volume", "66",
"Ch12_Bank", "68",
"Ch13_Start", "69",
"Ch13_Freq", "6A",
"Ch13_Phase", "6B",
"Ch13_Loop", "6C",
"Ch13_End", "6D",
"Ch13_Volume", "6E",
"Ch13_Bank", "70",
"Ch14_Start", "71",
"Ch14_Freq", "72",
"Ch14_Phase", "73",
"Ch14_Loop", "74",
"Ch14_End", "75",
"Ch14_Volume", "76",
"Ch14_Bank", "78",
"Ch15_Start", "79",
"Ch15_Freq", "7A",
"Ch15_Phase", "7B",
"Ch15_Loop", "7C",
"Ch15_End", "7D",
"Ch15_Volume", "7E",
"Ch00_Panning", "80",
"Ch01_Panning", "81",
"Ch02_Panning", "82",
"Ch03_Panning", "83",
"Ch04_Panning", "84",
"Ch05_Panning", "85",
"Ch06_Panning", "86",
"Ch07_Panning", "87",
"Ch08_Panning", "88",
"Ch09_Panning", "89",
"Ch10_Panning", "8A",
"Ch11_Panning", "8B",
"Ch12_Panning", "8C",
"Ch13_Panning", "8D",
"Ch14_Panning", "8E",
"Ch15_Panning", "8F",
"Adpcm0_Panning","90",
"Adpcm1_Panning","91",
"Adpcm2_Panning","92",
"Echo_Feedback","93",
"Ch00_Echo", "BA",
"Ch01_Echo", "BB",
"Ch02_Echo", "BC",
"Ch03_Echo", "BD",
"Ch04_Echo", "BE",
"Ch05_Echo", "BF",
"Ch06_Echo", "C0",
"Ch07_Echo", "C1",
"Ch08_Echo", "C2",
"Ch09_Echo", "C3",
"Ch10_Echo", "C4",
"Ch11_Echo", "C5",
"Ch12_Echo", "C6",
"Ch13_Echo", "C7",
"Ch14_Echo", "C8",
"Ch15_Echo", "C9",
"Adpcm0_Start", "CA",
"Adpcm0_End", "CB",
"Adpcm0_Bank", "CC",
"Adpcm0_Volume","CD",
"Adpcm1_Start", "CE",
"Adpcm1_End", "CF",
"Adpcm1_Bank", "D0",
"Adpcm1_Volume","D1",
"Adpcm2_Start", "D2",
"Adpcm2_End", "D3",
"Adpcm2_Bank", "D4",
"Adpcm2_Volume","D5",
"Adpcm0_KeyOn", "D6",
"Adpcm1_KeyOn", "D7",
"Adpcm2_KeyOn", "D8",
"Echo_Delay", "D9",
"L_Wet_Filter", "DA",
"L_Dry_Filter", "DB",
"R_Wet_Filter", "DC",
"R_Dry_Filter", "DD",
"L_Wet_Delay", "DE",
"L_Dry_Delay", "DF",
"R_Wet_Delay", "E0",
"R_Dry_Delay", "E1",
"Delay_Flag", "E2",
"Mode_Select", "E3", //valid: 0000,0288,0039,061A,004F
"L_Wet_Volume", "E4",
"L_Dry_Volume", "E5",
"R_Wet_Volume", "E6",
"R_Dry_Volume", "E7",
NULL
};
enum q1_register_name {
Q1V_BANK = 0,
Q1V_START = 1,
Q1V_FREQ = 2,
Q1V_PHASE = 3,
Q1V_LOOP = 4,
Q1V_END = 5,
Q1V_VOL = 6,
Q1V_REG_COUNT = 7,
Q1_PAN = 0x80,
Q1_ECHO = 0xba,
Q1A_PAN = 0x90,
Q1A_START = 0xca,
Q1A_END = 0xcb,
Q1A_BANK = 0xcc,
Q1A_VOL = 0xcd,
Q1A_KEYON = 0xd6,
Q1_ECHO_FEEDBACK = 0x93,
Q1_ECHO_LENGTH = 0xd9,
};
const unsigned char q1_reg_map[Q1V_REG_COUNT][16] = {
{0x78,0x00,0x08,0x10,0x18,0x20,0x28,0x30,0x38,0x40,0x48,0x50,0x58,0x60,0x68,0x70},
{0x01,0x09,0x11,0x19,0x21,0x29,0x31,0x39,0x41,0x49,0x51,0x59,0x61,0x69,0x71,0x79},
{0x02,0x0a,0x12,0x1a,0x22,0x2a,0x32,0x3a,0x42,0x4a,0x52,0x5a,0x62,0x6a,0x72,0x7a},
{0x03,0x0b,0x13,0x1b,0x23,0x2b,0x33,0x3b,0x43,0x4b,0x53,0x5b,0x63,0x6b,0x73,0x7b},
{0x04,0x0c,0x14,0x1c,0x24,0x2c,0x34,0x3c,0x44,0x4c,0x54,0x5c,0x64,0x6c,0x74,0x7c},
{0x05,0x0d,0x15,0x1d,0x25,0x2d,0x35,0x3d,0x45,0x4d,0x55,0x5d,0x65,0x6d,0x75,0x7d},
{0x06,0x0e,0x16,0x1e,0x26,0x2e,0x36,0x3e,0x46,0x4e,0x56,0x5e,0x66,0x6e,0x76,0x7e},
};
const char** DivPlatformQSound::getRegisterSheet() {
return regCheatSheetQSound;
}
const char* DivPlatformQSound::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Set echo feedback level (00 to FF)";
break;
case 0x11:
return "11xx: Set channel echo level (00 to FF)";
break;
default:
if((effect & 0xf0) == 0x30)
return "3xxx: Set echo delay buffer length (000 to AA5)";
}
return NULL;
}
void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) {
chip.rom_data = parent->qsoundMem;
chip.rom_mask = 0xffffff;
for (size_t h=start; h<start+len; h++) {
qsound_update(&chip);
bufL[h]=chip.out[0];
bufR[h]=chip.out[1];
}
}
void DivPlatformQSound::tick() {
for (int i=0; i<16; i++) {
chan[i].std.next();
if (chan[i].std.hadVol) {
chan[i].outVol=((chan[i].vol%256)*MIN(255,chan[i].std.vol << 2))>>8;
// Check if enabled and write volume
if(chan[i].active)
{
rWrite(q1_reg_map[Q1V_VOL][i], chan[i].outVol << 5);
//logW("ch %d vol=%04x (hadVol)!\n",i,chan[i].outVol << 5);
}
}
uint16_t qsound_bank = 0;
uint16_t qsound_addr = 0;
uint16_t qsound_loop = 0;
uint16_t qsound_end = 0;
double off=1.0;
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
DivSample* s=parent->song.sample[chan[i].sample];
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/24038.0/16.0;
}
qsound_bank = 0x8000 | (s->rendOffQsound >> 16);
qsound_addr = s->rendOffQsound & 0xffff;
int length = s->length;
if(length > 65536 - 16)
length = 65536 - 16;
if(s->loopStart == -1 || s->loopStart >= length)
{
qsound_end = s->rendOffQsound + length + 15;
qsound_loop = 15;
}
else
{
qsound_end = s->rendOffQsound + length;
qsound_loop = length - s->loopStart;
}
}
if (chan[i].std.hadArp) {
if (!chan[i].inPorta) {
if (chan[i].std.arpMode) {
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].std.arp);
} else {
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false);
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
//if (chan[i].note>0x5d) chan[i].freq=0x01; //????
if (chan[i].keyOn) {
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
rWrite(q1_reg_map[Q1V_END][i], qsound_end);
rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop);
rWrite(q1_reg_map[Q1V_START][i], qsound_addr);
rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000);
//logW("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!\n",i,qsound_bank,qsound_addr,qsound_end,qsound_loop);
// Write sample address. Enable volume
if (!chan[i].std.hadVol) {
rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 5);
//logW("ch %d vol=%04x (!hadVol)!\n",i,chan[i].vol << 5);
}
}
if (chan[i].keyOff) {
rWrite(q1_reg_map[Q1V_VOL][i], 0);
rWrite(q1_reg_map[Q1V_FREQ][i], 0);
// Disable volume
}
else if (chan[i].active) {
//logW("ch %d frequency set to %04x, off=%f, note=%d, %04x!\n",i,chan[i].freq,off,chan[i].note,QS_NOTE_FREQUENCY(chan[i].note));
rWrite(q1_reg_map[Q1V_FREQ][i], chan[i].freq);
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
}
}
int DivPlatformQSound::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
chan[c.chan].sample=ins->amiga.initSample;
double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->song.sample[chan[c.chan].sample];
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/24038.0/16.0;
}
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value);
}
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].std.init(ins);
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].sample=-1;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].std.init(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;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.hasVol) {
// Check if enabled and write volume
chan[c.chan].outVol=c.value;
if(chan[c.chan].active && c.chan < 16)
{
rWrite(q1_reg_map[Q1V_VOL][c.chan], chan[c.chan].outVol << 5);
//logW("ch %d vol=%04x (cmd vol)!\n",c.chan,chan[c.chan].outVol << 5);
}
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.hasVol) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PANNING:
immWrite(Q1_PAN+c.chan, c.value + 0x110);
break;
case DIV_CMD_QSOUND_ECHO_LEVEL:
immWrite(Q1_ECHO+c.chan, c.value << 7);
break;
case DIV_CMD_QSOUND_ECHO_FEEDBACK:
immWrite(Q1_ECHO_FEEDBACK, c.value << 6);
break;
case DIV_CMD_QSOUND_ECHO_DELAY:
immWrite(Q1_ECHO_LENGTH, (c.value > 2725 ? 0xfff : 0xfff - (2725 - c.value)));
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->song.sample[chan[c.chan].sample];
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/24038.0/16.0;
}
}
int destFreq=off*QS_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_LEGATO: {
double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->song.sample[chan[c.chan].sample];
if (s->centerRate<1) {
off=1.0;
} else {
off=(double)s->centerRate/24038.0/16.0;
}
}
chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp-12):(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].std.init(parent->getIns(chan[c.chan].ins));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 255;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformQSound::muteChannel(int ch, bool mute) {
if(mute)
chip.mute_mask |= (1 << ch);
else
chip.mute_mask &= ~(1 << ch);
}
void DivPlatformQSound::forceIns() {
for (int i=0; i<4; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].sample=-1;
}
}
void* DivPlatformQSound::getChanState(int ch) {
return &chan[ch];
}
void DivPlatformQSound::reset() {
for (int i=0; i<16; i++) {
chan[i]=DivPlatformQSound::Channel();
}
qsound_reset(&chip);
while(!chip.ready_flag)
qsound_update(&chip);
immWrite(Q1_ECHO_LENGTH, 0xfff - (2725 - echoDelay));
immWrite(Q1_ECHO_FEEDBACK, echoFeedback << 6);
}
bool DivPlatformQSound::isStereo() {
return true;
}
bool DivPlatformQSound::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformQSound::notifyInsChange(int ins) {
for (int i=0; i<4; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformQSound::notifyWaveChange(int wave) {
// TODO when wavetables are added
}
void DivPlatformQSound::notifyInsDeletion(void* ins) {
for (int i=0; i<4; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformQSound::setFlags(unsigned int flags) {
echoDelay = 2725 - (flags & 0xfff);
echoFeedback = (flags >> 12) & 255;
if(echoDelay < 0)
echoDelay = 0;
if(echoDelay > 2725)
echoDelay = 2725;
//rate=chipClock/CHIP_DIVIDER;
}
void DivPlatformQSound::poke(unsigned int addr, unsigned short val) {
immWrite(addr, val);
immWrite(addr, val);
}
void DivPlatformQSound::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
}
unsigned char* DivPlatformQSound::getRegisterPool() {
unsigned short* regPoolPtr = regPool;
for(int i=0; i<256; i++)
{
uint16_t data = qsound_read_data(&chip, i);
*regPoolPtr++ = data;
}
return (unsigned char*)regPool;
}
int DivPlatformQSound::getRegisterPoolSize() {
return 256;
}
int DivPlatformQSound::getRegisterPoolDepth() {
return 16;
}
int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
// for (int i=0; i<16; i++) {
// isMuted[i]=false;
// }
setFlags(flags);
chipClock=60000000;
rate = qsound_start(&chip, chipClock);
chip.rom_data = (unsigned char*)&chip.rom_mask;
chip.rom_mask = 0;
reset();
return 19;
}
void DivPlatformQSound::quit() {
}

View File

@ -0,0 +1,93 @@
/**
* 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 _QSOUND_H
#define _QSOUND_H
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
#include "sound/qsound.h"
class DivPlatformQSound: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch;
unsigned short audLen;
unsigned int audPos;
int sample, wave;
unsigned char ins;
int note;
int panning;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave;
int vol, outVol;
DivMacroInt std;
Channel():
freq(0),
baseFreq(0),
pitch(0),
audLen(0),
audPos(0),
sample(-1),
ins(-1),
note(0),
panning(0x10),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
vol(255),
outVol(255) {}
};
Channel chan[19];
int echoDelay;
int echoFeedback;
struct qsound_chip chip;
unsigned short regPool[512];
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);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
int getRegisterPoolDepth();
void reset();
void forceIns();
void tick();
void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch);
void setFlags(unsigned int flags);
void notifyInsChange(int ins);
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();
};
#endif

View File

@ -83,6 +83,7 @@ void DivPlatformSAA1099::acquire_mame(short* bufL, short* bufR, size_t start, si
QueuedWrite w=writes.front();
saa.control_w(w.addr);
saa.data_w(w.val);
regPool[w.addr&0x1f]=w.val;
writes.pop();
}
saa.sound_stream_update(saaBuf,len);
@ -103,6 +104,7 @@ void DivPlatformSAA1099::acquire_saaSound(short* bufL, short* bufR, size_t start
while (!writes.empty()) {
QueuedWrite w=writes.front();
saa_saaSound->WriteAddressData(w.addr,w.val);
regPool[w.addr&0x1f]=w.val;
writes.pop();
}
saa_saaSound->GenerateMany((unsigned char*)saaBuf[0],len);
@ -367,8 +369,17 @@ void* DivPlatformSAA1099::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformSAA1099::getRegisterPool() {
return regPool;
}
int DivPlatformSAA1099::getRegisterPoolSize() {
return 32;
}
void DivPlatformSAA1099::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,32);
switch (core) {
case DIV_SAA_CORE_MAME:
saa=saa1099_device();

View File

@ -56,6 +56,7 @@ class DivPlatformSAA1099: public DivDispatch {
DivSAACores core;
saa1099_device saa;
CSAASound* saa_saaSound;
unsigned char regPool[32];
unsigned char lastBusy;
bool dacMode;
@ -85,6 +86,8 @@ class DivPlatformSAA1099: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -0,0 +1,566 @@
/**
* 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 "Mikey.hpp"
#include <array>
#include <cstdint>
#include <vector>
#include <functional>
#include <cassert>
#include <algorithm>
#include <limits>
namespace Lynx
{
namespace
{
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~15;
#if defined ( __cpp_lib_bitops )
#define popcnt(X) std::popcount(X)
#elif defined( _MSC_VER )
# include <intrin.h>
uint32_t popcnt( uint32_t x )
{
return __popcnt( x );
}
#elif defined( __GNUC__ )
uint32_t popcnt( uint32_t x )
{
return __builtin_popcount( x );
}
#else
uint32_t popcnt( uint32_t x )
{
int v = 0;
while ( x != 0 )
{
x &= x - 1;
v++;
}
return v;
}
#endif
int32_t clamp( int32_t v, int32_t lo, int32_t hi )
{
return v < lo ? lo : ( v > hi ? hi : v );
}
class Timer
{
public:
Timer() : mValueUpdateTick{}, mAudShift {}, mEnableReload{}, mEnableCount{}, mTimerDone{}, mBackup{ 0 }, mValue{ 0 }
{
}
int64_t setBackup( int64_t tick, uint8_t backup )
{
mBackup = backup;
return computeAction( tick );
}
int64_t setControlA( int64_t tick, uint8_t controlA )
{
mTimerDone ^= ( controlA & CONTROLA::RESET_DONE ) != 0;
mEnableReload = ( controlA & CONTROLA::ENABLE_RELOAD ) != 0;
mEnableCount = ( controlA & CONTROLA::ENABLE_COUNT ) != 0;
mAudShift = controlA & CONTROLA::AUD_CLOCK_MASK;
return computeAction( tick );
}
int64_t setCount( int64_t tick, uint8_t value )
{
return computeTriggerTime( tick );
}
void setControlB( uint8_t controlB )
{
mTimerDone = ( controlB & CONTROLB::TIMER_DONE ) != 0;
}
int64_t fireAction( int64_t tick )
{
mTimerDone = true;
return computeAction( tick );
}
uint8_t getBackup() const
{
return mBackup;
}
uint8_t getCount( int64_t tick )
{
updateValue( tick );
return mValue;
}
private:
int64_t scaleDiff( int64_t older, int64_t newer ) const
{
int64_t const mask = (int64_t)( ~0ull << ( mAudShift + 4 ) );
return ( ( newer & mask ) - ( older & mask ) ) >> ( mAudShift + 4 );
}
void updateValue( int64_t tick )
{
if ( mEnableCount )
mValue = (uint8_t)std::max( (int64_t)0, mValue - scaleDiff( mValueUpdateTick, tick ) );
mValueUpdateTick = tick;
}
int64_t computeTriggerTime( int64_t tick )
{
if ( mEnableCount && mValue != 0 )
{
//tick value is increased by multipy of 16 (1 MHz resolution) lower bits are unchanged
return tick + ( 1ull + mValue ) * ( 1ull << ( mAudShift + 4 ) );
}
else
{
return CNT_MAX; //infinite
}
}
int64_t computeAction( int64_t tick )
{
updateValue( tick );
if ( mValue == 0 && mEnableReload )
{
mValue = mBackup;
}
return computeTriggerTime( tick );
}
private:
struct CONTROLA
{
static constexpr uint8_t RESET_DONE = 0b01000000;
static constexpr uint8_t ENABLE_RELOAD = 0b00010000;
static constexpr uint8_t ENABLE_COUNT = 0b00001000;
static constexpr uint8_t AUD_CLOCK_MASK = 0b00000111;
};
struct CONTROLB
{
static constexpr uint8_t TIMER_DONE = 0b00001000;
};
private:
int64_t mValueUpdateTick;
int mAudShift;
bool mEnableReload;
bool mEnableCount;
bool mTimerDone;
uint8_t mBackup;
uint8_t mValue;
};
class AudioChannel
{
public:
AudioChannel( uint32_t number ) : mTimer{}, mNumber{ number }, mShiftRegister{}, mTapSelector{}, mEnableIntegrate{}, mVolume{}, mOutput{}, mCtrlA{}
{
}
int64_t fireAction( int64_t tick )
{
trigger();
return adjust( mTimer.fireAction( tick ) );
}
void setVolume( int8_t value )
{
mVolume = value;
}
void setFeedback( uint8_t value )
{
mTapSelector = ( mTapSelector & 0b0011'1100'0000 ) | ( value & 0b0011'1111 ) | ( ( (int)value & 0b1100'0000 ) << 4 );
}
void setOutput( uint8_t value )
{
mOutput = value;
}
void setShift( uint8_t value )
{
mShiftRegister = ( mShiftRegister & 0xff00 ) | value;
}
int64_t setBackup( int64_t tick, uint8_t value )
{
return adjust( mTimer.setBackup( tick, value ) );
}
int64_t setControl( int64_t tick, uint8_t value )
{
if ( mCtrlA == value )
return 0;
mCtrlA = value;
mTapSelector = ( mTapSelector & 0b1111'0111'1111 ) | ( value & FEEDBACK_7 );
mEnableIntegrate = ( value & ENABLE_INTEGRATE ) != 0;
return adjust( mTimer.setControlA( tick, value & ~( FEEDBACK_7 | ENABLE_INTEGRATE ) ) );
}
int64_t setCounter( int64_t tick, uint8_t value )
{
return adjust( mTimer.setCount( tick, value ) );
}
void setOther( uint8_t value )
{
mShiftRegister = ( mShiftRegister & 0b0000'1111'1111 ) | ( ( (int)value & 0b1111'0000 ) << 4 );
mTimer.setControlB( value & 0b0000'1111 );
}
int8_t getOutput() const
{
return mOutput;
}
void fillRegisterPool( int64_t tick, uint8_t* regs )
{
regs[0] = mVolume;
regs[1] = mTapSelector & 0xff;
regs[2] = mOutput;
regs[3] = mShiftRegister & 0xff;
regs[4] = mTimer.getBackup();
regs[5] = mCtrlA;
regs[6] = mTimer.getCount( tick );
regs[7] = ( ( mShiftRegister >> 4 ) & 0xf0 );
}
private:
int64_t adjust( int64_t tick ) const
{
//ticks are advancing in 1 MHz resolution, so lower 4 bits are unused.
//timer number is encoded on lowest 2 bits.
return tick | mNumber;
}
void trigger()
{
uint32_t xorGate = mTapSelector & mShiftRegister;
uint32_t parity = popcnt( xorGate ) & 1;
uint32_t newShift = ( mShiftRegister << 1 ) | ( parity ^ 1 );
mShiftRegister = newShift;
if ( mEnableIntegrate )
{
int32_t temp = mOutput + ( ( newShift & 1 ) ? mVolume : -mVolume );
mOutput = (int8_t)clamp( temp, (int32_t)std::numeric_limits<int8_t>::min(), (int32_t)std::numeric_limits<int8_t>::max() );
}
else
{
mOutput = ( newShift & 1 ) ? mVolume : -mVolume;
}
}
private:
static constexpr uint8_t FEEDBACK_7 = 0b10000000;
static constexpr uint8_t ENABLE_INTEGRATE = 0b00100000;
private:
Timer mTimer;
uint32_t mNumber;
uint32_t mShiftRegister;
uint32_t mTapSelector;
bool mEnableIntegrate;
int8_t mVolume;
int8_t mOutput;
uint8_t mCtrlA;
};
}
/*
"Queue" holding event timepoints.
- 4 channel timer fire points
- 1 sample point
Time is in 16 MHz units but only with 1 MHz resolution.
Four LSBs are used to encode event kind 0-3 are channels, 4 is sampling.
*/
class ActionQueue
{
public:
ActionQueue() : mTab{ CNT_MAX | 0, CNT_MAX | 1, CNT_MAX | 2, CNT_MAX | 3, CNT_MAX | 4 }
{
}
void push( int64_t value )
{
size_t idx = value & 15;
if ( idx < mTab.size() )
{
if ( value & ~15 )
{
//writing only non-zero values
mTab[idx] = value;
}
}
}
int64_t pop()
{
int64_t min1 = std::min( mTab[0], mTab[1] );
int64_t min2 = std::min( mTab[2], mTab[3] );
int64_t min3 = std::min( min1, mTab[4] );
int64_t min4 = std::min( min2, min3 );
assert( ( min4 & 15 ) < (int64_t)mTab.size() );
mTab[min4 & 15] = CNT_MAX | ( min4 & 15 );
return min4;
}
private:
std::array<int64_t, 5> mTab;
};
class MikeyPimpl
{
public:
struct AudioSample
{
int16_t left;
int16_t right;
};
static constexpr uint16_t VOLCNTRL = 0x0;
static constexpr uint16_t FEEDBACK = 0x1;
static constexpr uint16_t OUTPUT = 0x2;
static constexpr uint16_t SHIFT = 0x3;
static constexpr uint16_t BACKUP = 0x4;
static constexpr uint16_t CONTROL = 0x5;
static constexpr uint16_t COUNTER = 0x6;
static constexpr uint16_t OTHER = 0x7;
static constexpr uint16_t ATTENREG0 = 0x40;
static constexpr uint16_t ATTENREG1 = 0x41;
static constexpr uint16_t ATTENREG2 = 0x42;
static constexpr uint16_t ATTENREG3 = 0x43;
static constexpr uint16_t MPAN = 0x44;
static constexpr uint16_t MSTEREO = 0x50;
MikeyPimpl() : mAudioChannels{ AudioChannel{0}, AudioChannel{1}, AudioChannel{2}, AudioChannel{3} },
mAttenuationLeft{ 0x3c, 0x3c, 0x3c, 0x3c },
mAttenuationRight{ 0x3c, 0x3c, 0x3c, 0x3c },
mRegisterPool{}, mPan{ 0xff }, mStereo{}
{
std::fill_n( mRegisterPool.data(), mRegisterPool.size(), (uint8_t)0xff );
}
~MikeyPimpl() {}
int64_t write( int64_t tick, uint8_t address, uint8_t value )
{
assert( address >= 0x20 );
if ( address < 0x40 )
{
size_t idx = ( address >> 3 ) & 3;
switch ( address & 0x7 )
{
case VOLCNTRL:
mAudioChannels[idx].setVolume( (int8_t)value );
break;
case FEEDBACK:
mAudioChannels[idx].setFeedback( value );
break;
case OUTPUT:
mAudioChannels[idx].setOutput( value );
break;
case SHIFT:
mAudioChannels[idx].setShift( value );
break;
case BACKUP:
return mAudioChannels[idx].setBackup( tick, value );
case CONTROL:
return mAudioChannels[idx].setControl( tick, value );
case COUNTER:
return mAudioChannels[idx].setCounter( tick, value );
case OTHER:
mAudioChannels[idx].setOther( value );
break;
}
}
else
{
int idx = address & 3;
switch ( address )
{
case ATTENREG0:
case ATTENREG1:
case ATTENREG2:
case ATTENREG3:
mRegisterPool[8*4+idx] = value;
mAttenuationLeft[idx] = ( value & 0x0f ) << 2;
mAttenuationRight[idx] = ( value & 0xf0 ) >> 2;
break;
case MPAN:
mPan = value;
break;
case MSTEREO:
mStereo = value;
break;
default:
break;
}
}
return 0;
}
int64_t fireTimer( int64_t tick )
{
size_t timer = tick & 0x0f;
assert( timer < 4 );
return mAudioChannels[timer].fireAction( tick );
}
AudioSample sampleAudio() const
{
int left{};
int right{};
for ( size_t i = 0; i < 4; ++i )
{
if ( ( mStereo & ( (uint8_t)0x01 << i ) ) == 0 )
{
const int attenuation = ( mPan & ( (uint8_t)0x01 << i ) ) != 0 ? mAttenuationLeft[i] : 0x3c;
left += mAudioChannels[i].getOutput() * attenuation;
}
if ( ( mStereo & ( (uint8_t)0x10 << i ) ) == 0 )
{
const int attenuation = ( mPan & ( (uint8_t)0x01 << i ) ) != 0 ? mAttenuationRight[i] : 0x3c;
right += mAudioChannels[i].getOutput() * attenuation;
}
}
return { (int16_t)left, (int16_t)right };
}
uint8_t const* getRegisterPool( int64_t tick )
{
for ( size_t i = 0; i < mAudioChannels.size(); ++i )
{
mAudioChannels[i].fillRegisterPool( tick, mRegisterPool.data() + 8 * i );
}
return mRegisterPool.data();
}
private:
std::array<AudioChannel, 4> mAudioChannels;
std::array<int, 4> mAttenuationLeft;
std::array<int, 4> mAttenuationRight;
std::array<uint8_t, 4 * 8 + 4> mRegisterPool;
uint8_t mPan;
uint8_t mStereo;
};
Mikey::Mikey( uint32_t sampleRate ) : mMikey{ std::make_unique<MikeyPimpl>() }, mQueue{ std::make_unique<ActionQueue>() }, mTick{}, mNextTick{}, mSampleRate{ sampleRate }, mSamplesRemainder{}, mTicksPerSample{ 16000000 / mSampleRate, 16000000 % mSampleRate }
{
enqueueSampling();
}
Mikey::~Mikey()
{
}
void Mikey::write( uint8_t address, uint8_t value )
{
if ( auto action = mMikey->write( mTick, address, value ) )
{
mQueue->push( action );
}
}
void Mikey::enqueueSampling()
{
mTick = mNextTick & ~15;
mNextTick = mNextTick + mTicksPerSample.first;
mSamplesRemainder += mTicksPerSample.second;
if ( mSamplesRemainder > mSampleRate )
{
mSamplesRemainder %= mSampleRate;
mNextTick += 1;
}
mQueue->push( ( mNextTick & ~15 ) | 4 );
}
void Mikey::sampleAudio( int16_t* bufL, int16_t* bufR, size_t size )
{
size_t i = 0;
while ( i < size )
{
int64_t value = mQueue->pop();
if ( ( value & 4 ) == 0 )
{
if ( auto newAction = mMikey->fireTimer( value ) )
{
mQueue->push( newAction );
}
}
else
{
auto sample = mMikey->sampleAudio();
bufL[i] = sample.left;
bufR[i] = sample.right;
i += 1;
enqueueSampling();
}
}
}
uint8_t const* Mikey::getRegisterPool()
{
return mMikey->getRegisterPool( mTick );
}
}

View File

@ -0,0 +1,39 @@
#pragma once
#include <cstdint>
#include <memory>
namespace Lynx
{
class MikeyPimpl;
class ActionQueue;
class Mikey
{
public:
Mikey( uint32_t sampleRate );
~Mikey();
void write( uint8_t address, uint8_t value );
void sampleAudio( int16_t* bufL, int16_t* bufR, size_t size );
uint8_t const* getRegisterPool();
private:
void enqueueSampling();
private:
std::unique_ptr<MikeyPimpl> mMikey;
std::unique_ptr<ActionQueue> mQueue;
uint64_t mTick;
uint64_t mNextTick;
uint32_t mSampleRate;
uint32_t mSamplesRemainder;
std::pair<uint32_t, uint32_t> mTicksPerSample;
};
}

View File

@ -0,0 +1,700 @@
/*
Capcom DL-1425 QSound emulator
==============================
by superctr (Ian Karlsson)
with thanks to Valley Bell
2018-05-12 - 2018-05-15
*/
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include "qsound.h"
#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
// ============================================================================
static const int16_t qsound_dry_mix_table[33] = {
-16384,-16384,-16384,-16384,-16384,-16384,-16384,-16384,
-16384,-16384,-16384,-16384,-16384,-16384,-16384,-16384,
-16384,-14746,-13107,-11633,-10486,-9175,-8520,-7209,
-6226,-5226,-4588,-3768,-3277,-2703,-2130,-1802,
0
};
static const int16_t qsound_wet_mix_table[33] = {
0,-1638,-1966,-2458,-2949,-3441,-4096,-4669,
-4915,-5120,-5489,-6144,-7537,-8831,-9339,-9830,
-10240,-10322,-10486,-10568,-10650,-11796,-12288,-12288,
-12534,-12648,-12780,-12829,-12943,-13107,-13418,-14090,
-16384
};
static const int16_t qsound_linear_mix_table[33] = {
-16379,-16338,-16257,-16135,-15973,-15772,-15531,-15251,
-14934,-14580,-14189,-13763,-13303,-12810,-12284,-11729,
-11729,-11144,-10531,-9893,-9229,-8543,-7836,-7109,
-6364,-5604,-4829,-4043,-3246,-2442,-1631,-817,
0
};
static const int16_t qsound_filter_data[5][95] = {
{ // d53 - 0
0,0,0,6,44,-24,-53,-10,59,-40,-27,1,39,-27,56,127,174,36,-13,49,
212,142,143,-73,-20,66,-108,-117,-399,-265,-392,-569,-473,-71,95,-319,-218,-230,331,638,
449,477,-180,532,1107,750,9899,3828,-2418,1071,-176,191,-431,64,117,-150,-274,-97,-238,165,
166,250,-19,4,37,204,186,-6,140,-77,-1,1,18,-10,-151,-149,-103,-9,55,23,
-102,-97,-11,13,-48,-27,5,18,-61,-30,64,72,0,0,0,
},
{ // db2 - 1 - default left filter
0,0,0,85,24,-76,-123,-86,-29,-14,-20,-7,6,-28,-87,-89,-5,100,154,160,
150,118,41,-48,-78,-23,59,83,-2,-176,-333,-344,-203,-66,-39,2,224,495,495,280,
432,1340,2483,5377,1905,658,0,97,347,285,35,-95,-78,-82,-151,-192,-171,-149,-147,-113,
-22,71,118,129,127,110,71,31,20,36,46,23,-27,-63,-53,-21,-19,-60,-92,-69,
-12,25,29,30,40,41,29,30,46,39,-15,-74,0,0,0,
},
{ // e11 - 2 - default right filter
0,0,0,23,42,47,29,10,2,-14,-54,-92,-93,-70,-64,-77,-57,18,94,113,
87,69,67,50,25,29,58,62,24,-39,-131,-256,-325,-234,-45,58,78,223,485,496,
127,6,857,2283,2683,4928,1328,132,79,314,189,-80,-90,35,-21,-186,-195,-99,-136,-258,
-189,82,257,185,53,41,84,68,38,63,77,14,-60,-71,-71,-120,-151,-84,14,29,
-8,7,66,69,12,-3,54,92,52,-6,-15,-2,0,0,0,
},
{ // e70 - 3
0,0,0,2,-28,-37,-17,0,-9,-22,-3,35,52,39,20,7,-6,2,55,121,
129,67,8,1,9,-6,-16,16,66,96,118,130,75,-47,-92,43,223,239,151,219,
440,475,226,206,940,2100,2663,4980,865,49,-33,186,231,103,42,114,191,184,116,29,
-47,-72,-21,60,96,68,31,32,63,87,76,39,7,14,55,85,67,18,-12,-3,
21,34,29,6,-27,-49,-37,-2,16,0,-21,-16,0,0,0,
},
{ // ecf - 4
0,0,0,48,7,-22,-29,-10,24,54,59,29,-36,-117,-185,-213,-185,-99,13,90,
83,24,-5,23,53,47,38,56,67,57,75,107,16,-242,-440,-355,-120,-33,-47,152,
501,472,-57,-292,544,1937,2277,6145,1240,153,47,200,152,36,64,134,74,-82,-208,-266,
-268,-188,-42,65,74,56,89,133,114,44,-3,-1,17,29,29,-2,-76,-156,-187,-151,
-85,-31,-5,7,20,32,24,-5,-20,6,48,62,0,0,0,
}
};
static const int16_t qsound_filter_data2[209] = {
// f2e - following 95 values used for "disable output" filter
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
// f73 - following 45 values used for "mode 2" filter (overlaps with f2e)
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,
-371,-196,-268,-512,-303,-315,-184,-76,276,-256,298,196,990,236,1114,-126,4377,6549,791,
// fa0 - filtering disabled (for 95-taps) (use fa3 or fa4 for mode2 filters)
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,-16384,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
static const int16_t adpcm_step_table[16] = {
154, 154, 128, 102, 77, 58, 58, 58,
58, 58, 58, 58, 77, 102, 128, 154
};
// DSP states
enum {
STATE_INIT1 = 0x288,
STATE_INIT2 = 0x61a,
STATE_REFRESH1 = 0x039,
STATE_REFRESH2 = 0x04f,
STATE_NORMAL1 = 0x314,
STATE_NORMAL2 = 0x6b2,
};
enum {
PANTBL_LEFT = 0,
PANTBL_RIGHT = 1,
PANTBL_DRY = 0,
PANTBL_WET = 1,
};
static void init_pan_tables(struct qsound_chip *chip);
static void init_register_map(struct qsound_chip *chip);
static void state_init(struct qsound_chip *chip);
static void state_refresh_filter_1(struct qsound_chip *chip);
static void state_refresh_filter_2(struct qsound_chip *chip);
static void state_normal_update(struct qsound_chip *chip);
static inline int16_t get_sample(struct qsound_chip *chip, uint16_t bank,uint16_t address);
static inline int16_t* get_filter_table(struct qsound_chip *chip, uint16_t offset);
static inline int16_t pcm_update(struct qsound_chip *chip, int voice_no, int32_t *echo_out);
static inline void adpcm_update(struct qsound_chip *chip, int voice_no, int nibble);
static inline int16_t echo(struct qsound_echo *r,int32_t input);
static inline int32_t fir(struct qsound_fir *f, int16_t input);
static inline int32_t delay(struct qsound_delay *d, int32_t input);
static inline void delay_update(struct qsound_delay *d);
// ============================================================================
long qsound_start(struct qsound_chip *chip, int clock)
{
memset(chip,0,sizeof(*chip));
init_pan_tables(chip);
init_register_map(chip);
return clock / 2 / 1248; // DSP program uses 1248 machine cycles per iteration
}
void qsound_reset(struct qsound_chip *chip)
{
chip->ready_flag = 0;
chip->out[0] = chip->out[1] = 0;
chip->state = 0;
chip->state_counter = 0;
}
uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples)
{
// Clear the buffers
memset(outputs[0], 0, samples * sizeof(*outputs[0]));
memset(outputs[1], 0, samples * sizeof(*outputs[1]));
for (int i = 0; i < samples; i ++)
{
qsound_update(chip);
outputs[0][i] = chip->out[0];
outputs[1][i] = chip->out[1];
}
return 0;
}
uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data)
{
switch (offset)
{
case 0:
chip->data_latch = (chip->data_latch & 0x00ff) | (data << 8);
break;
case 1:
chip->data_latch = (chip->data_latch & 0xff00) | data;
break;
case 2:
qsound_write_data(chip, data, chip->data_latch);
break;
default:
break;
}
return 0;
}
uint8_t qsound_r(struct qsound_chip *chip)
{
// ready bit (0x00 = busy, 0x80 == ready)
return chip->ready_flag;
}
void qsound_write_data(struct qsound_chip *chip, uint8_t address, uint16_t data)
{
uint16_t *destination = chip->register_map[address];
if(destination)
*destination = data;
chip->ready_flag = 0;
}
uint16_t qsound_read_data(struct qsound_chip *chip, uint8_t address)
{
uint16_t data = 0;
uint16_t *source = chip->register_map[address];
if(source)
data = *source;
return data;
}
// ============================================================================
static void init_pan_tables(struct qsound_chip *chip)
{
int i;
for(i=0;i<33;i++)
{
// dry mixing levels
chip->pan_tables[PANTBL_LEFT][PANTBL_DRY][i] = qsound_dry_mix_table[i];
chip->pan_tables[PANTBL_RIGHT][PANTBL_DRY][i] = qsound_dry_mix_table[32-i];
// wet mixing levels
chip->pan_tables[PANTBL_LEFT][PANTBL_WET][i] = qsound_wet_mix_table[i];
chip->pan_tables[PANTBL_RIGHT][PANTBL_WET][i] = qsound_wet_mix_table[32-i];
// linear panning, only for dry component. wet component is muted.
chip->pan_tables[PANTBL_LEFT][PANTBL_DRY][i+0x30] = qsound_linear_mix_table[i];
chip->pan_tables[PANTBL_RIGHT][PANTBL_DRY][i+0x30] = qsound_linear_mix_table[32-i];
}
}
static void init_register_map(struct qsound_chip *chip)
{
int i;
// unused registers
for(i=0;i<256;i++)
chip->register_map[i] = NULL;
// PCM registers
for(i=0;i<16;i++) // PCM voices
{
chip->register_map[(i<<3)+0] = (uint16_t*)&chip->voice[(i+1)%16].bank; // Bank applies to the next channel
chip->register_map[(i<<3)+1] = (uint16_t*)&chip->voice[i].addr; // Current sample position and start position.
chip->register_map[(i<<3)+2] = (uint16_t*)&chip->voice[i].rate; // 4.12 fixed point decimal.
chip->register_map[(i<<3)+3] = (uint16_t*)&chip->voice[i].phase;
chip->register_map[(i<<3)+4] = (uint16_t*)&chip->voice[i].loop_len;
chip->register_map[(i<<3)+5] = (uint16_t*)&chip->voice[i].end_addr;
chip->register_map[(i<<3)+6] = (uint16_t*)&chip->voice[i].volume;
chip->register_map[(i<<3)+7] = NULL; // unused
chip->register_map[i+0x80] = (uint16_t*)&chip->voice_pan[i];
chip->register_map[i+0xba] = (uint16_t*)&chip->voice[i].echo;
}
// ADPCM registers
for(i=0;i<3;i++) // ADPCM voices
{
// ADPCM sample rate is fixed to 8khz. (one channel is updated every third sample)
chip->register_map[(i<<2)+0xca] = (uint16_t*)&chip->adpcm[i].start_addr;
chip->register_map[(i<<2)+0xcb] = (uint16_t*)&chip->adpcm[i].end_addr;
chip->register_map[(i<<2)+0xcc] = (uint16_t*)&chip->adpcm[i].bank;
chip->register_map[(i<<2)+0xcd] = (uint16_t*)&chip->adpcm[i].volume;
chip->register_map[i+0xd6] = (uint16_t*)&chip->adpcm[i].flag; // non-zero to start ADPCM playback
chip->register_map[i+0x90] = (uint16_t*)&chip->voice_pan[16+i];
}
// QSound registers
chip->register_map[0x93] = (uint16_t*)&chip->echo.feedback;
chip->register_map[0xd9] = (uint16_t*)&chip->echo.end_pos;
chip->register_map[0xe2] = (uint16_t*)&chip->delay_update; // non-zero to update delays
chip->register_map[0xe3] = (uint16_t*)&chip->next_state;
for(i=0;i<2;i++) // left, right
{
// Wet
chip->register_map[(i<<1)+0xda] = (uint16_t*)&chip->filter[i].table_pos;
chip->register_map[(i<<1)+0xde] = (uint16_t*)&chip->wet[i].delay;
chip->register_map[(i<<1)+0xe4] = (uint16_t*)&chip->wet[i].volume;
// Dry
chip->register_map[(i<<1)+0xdb] = (uint16_t*)&chip->alt_filter[i].table_pos;
chip->register_map[(i<<1)+0xdf] = (uint16_t*)&chip->dry[i].delay;
chip->register_map[(i<<1)+0xe5] = (uint16_t*)&chip->dry[i].volume;
}
}
static inline int16_t get_sample(struct qsound_chip *chip, uint16_t bank,uint16_t address)
{
uint32_t rom_addr;
uint8_t sample_data;
if (! chip->rom_mask)
return 0; // no ROM loaded
if (! (bank & 0x8000))
return 0; // ignore attempts to read from DSP program ROM
bank &= 0x7FFF;
rom_addr = (bank << 16) | (address << 0);
sample_data = chip->rom_data[rom_addr];
return (int16_t)((sample_data << 8) | (sample_data << 0)); // MAME currently expands the 8 bit ROM data to 16 bits this way.
}
static inline int16_t* get_filter_table(struct qsound_chip *chip, uint16_t offset)
{
int index;
if (offset >= 0xf2e && offset < 0xfff)
return (int16_t*)&qsound_filter_data2[offset-0xf2e]; // overlapping filter data
index = (offset-0xd53)/95;
if(index >= 0 && index < 5)
return (int16_t*)&qsound_filter_data[index]; // normal tables
return NULL; // no filter found.
}
/********************************************************************/
// updates one DSP sample
void qsound_update(struct qsound_chip *chip)
{
switch(chip->state)
{
default:
case STATE_INIT1:
case STATE_INIT2:
state_init(chip); return;
case STATE_REFRESH1:
state_refresh_filter_1(chip); return;
case STATE_REFRESH2:
state_refresh_filter_2(chip); return;
case STATE_NORMAL1:
case STATE_NORMAL2:
state_normal_update(chip); return;
}
}
// Initialization routine
static void state_init(struct qsound_chip *chip)
{
int mode = (chip->state == STATE_INIT2) ? 1 : 0;
int i;
// we're busy for 4 samples, including the filter refresh.
if(chip->state_counter >= 2)
{
chip->state_counter = 0;
chip->state = chip->next_state;
return;
}
else if(chip->state_counter == 1)
{
chip->state_counter++;
return;
}
memset(chip->voice, 0, sizeof(chip->voice));
memset(chip->adpcm, 0, sizeof(chip->adpcm));
memset(chip->filter, 0, sizeof(chip->filter));
memset(chip->alt_filter, 0, sizeof(chip->alt_filter));
memset(chip->wet, 0, sizeof(chip->wet));
memset(chip->dry, 0, sizeof(chip->dry));
memset(&chip->echo, 0, sizeof(chip->echo));
for(i=0;i<19;i++)
{
chip->voice_pan[i] = 0x120;
chip->voice_output[i] = 0;
}
for(i=0;i<16;i++)
chip->voice[i].bank = 0x8000;
for(i=0;i<3;i++)
chip->adpcm[i].bank = 0x8000;
if(mode == 0)
{
// mode 1
chip->wet[0].delay = 0;
chip->dry[0].delay = 46;
chip->wet[1].delay = 0;
chip->dry[1].delay = 48;
chip->filter[0].table_pos = 0xdb2;
chip->filter[1].table_pos = 0xe11;
chip->echo.end_pos = 0x554 + 6;
chip->next_state = STATE_REFRESH1;
}
else
{
// mode 2
chip->wet[0].delay = 1;
chip->dry[0].delay = 0;
chip->wet[1].delay = 0;
chip->dry[1].delay = 0;
chip->filter[0].table_pos = 0xf73;
chip->filter[1].table_pos = 0xfa4;
chip->alt_filter[0].table_pos = 0xf73;
chip->alt_filter[1].table_pos = 0xfa4;
chip->echo.end_pos = 0x53c + 6;
chip->next_state = STATE_REFRESH2;
}
chip->wet[0].volume = 0x3fff;
chip->dry[0].volume = 0x3fff;
chip->wet[1].volume = 0x3fff;
chip->dry[1].volume = 0x3fff;
chip->delay_update = 1;
chip->ready_flag = 0;
chip->state_counter = 1;
}
// Updates filter parameters for mode 1
static void state_refresh_filter_1(struct qsound_chip *chip)
{
const int16_t *table;
for(int ch=0; ch<2; ch++)
{
chip->filter[ch].delay_pos = 0;
chip->filter[ch].tap_count = 95;
table = get_filter_table(chip,chip->filter[ch].table_pos);
if (table != NULL)
memcpy(chip->filter[ch].taps, table, 95 * sizeof(int16_t));
}
chip->state = chip->next_state = STATE_NORMAL1;
}
// Updates filter parameters for mode 2
static void state_refresh_filter_2(struct qsound_chip *chip)
{
const int16_t *table;
for(int ch=0; ch<2; ch++)
{
chip->filter[ch].delay_pos = 0;
chip->filter[ch].tap_count = 45;
table = get_filter_table(chip,chip->filter[ch].table_pos);
if (table != NULL)
memcpy(chip->filter[ch].taps, table, 45 * sizeof(int16_t));
chip->alt_filter[ch].delay_pos = 0;
chip->alt_filter[ch].tap_count = 44;
table = get_filter_table(chip,chip->alt_filter[ch].table_pos);
if (table != NULL)
memcpy(chip->alt_filter[ch].taps, table, 44 * sizeof(int16_t));
}
chip->state = chip->next_state = STATE_NORMAL2;
}
// Updates a PCM voice. There are 16 voices, each are updated every sample
// with full rate and volume control.
static inline int16_t pcm_update(struct qsound_chip *chip, int voice_no, int32_t *echo_out)
{
struct qsound_voice* v = &chip->voice[voice_no];
int32_t new_phase;
int16_t output = 0;
if(!(chip->mute_mask & (1<<voice_no)))
{
// Read sample from rom and apply volume
output = (v->volume * get_sample(chip, v->bank, v->addr))>>14;
*echo_out += (output * v->echo)<<2;
}
// Add delta to the phase and loop back if required
new_phase = v->rate + ((v->addr<<12) | (v->phase>>4));
if((new_phase>>12) >= v->end_addr)
new_phase -= (v->loop_len<<12);
new_phase = CLAMP(new_phase, -0x8000000, 0x7FFFFFF);
v->addr = new_phase>>12;
v->phase = (new_phase<<4)&0xffff;
return output;
}
// Updates an ADPCM voice. There are 3 voices, one is updated every sample
// (effectively making the ADPCM rate 1/3 of the master sample rate), and
// volume is set when starting samples only.
// The ADPCM algorithm is supposedly similar to Yamaha ADPCM. It also seems
// like Capcom never used it, so this was not emulated in the earlier QSound
// emulators.
static inline void adpcm_update(struct qsound_chip *chip, int voice_no, int nibble)
{
struct qsound_adpcm *v = &chip->adpcm[voice_no];
int32_t delta;
int8_t step;
if(!nibble)
{
// Mute voice when it reaches the end address.
if(v->cur_addr == v->end_addr)
v->cur_vol = 0;
// Playback start flag
if(v->flag)
{
chip->voice_output[16+voice_no] = 0;
v->flag = 0;
v->step_size = 10;
v->cur_vol = v->volume;
v->cur_addr = v->start_addr;
}
// get top nibble
step = get_sample(chip, v->bank, v->cur_addr) >> 8;
}
else
{
// get bottom nibble
step = get_sample(chip, v->bank, v->cur_addr++) >> 4;
}
// shift with sign extend
step >>= 4;
// delta = (0.5 + abs(v->step)) * v->step_size
delta = ((1+abs(step<<1)) * v->step_size)>>1;
if(step <= 0)
delta = -delta;
delta += chip->voice_output[16+voice_no];
delta = CLAMP(delta,-32768,32767);
if(chip->mute_mask & (1<<(16+voice_no)))
chip->voice_output[16+voice_no] = 0;
else
chip->voice_output[16+voice_no] = (delta * v->cur_vol)>>16;
v->step_size = (adpcm_step_table[8+step] * v->step_size) >> 6;
v->step_size = CLAMP(v->step_size, 1, 2000);
}
// The echo effect is pretty simple. A moving average filter is used on
// the output from the delay line to smooth samples over time.
static inline int16_t echo(struct qsound_echo *r,int32_t input)
{
// get average of last 2 samples from the delay line
int32_t new_sample;
int32_t old_sample = r->delay_line[r->delay_pos];
int32_t last_sample = r->last_sample;
r->last_sample = old_sample;
old_sample = (old_sample+last_sample) >> 1;
// add current sample to the delay line
new_sample = input + ((old_sample * r->feedback)<<2);
r->delay_line[r->delay_pos++] = new_sample>>16;
if(r->delay_pos >= r->length)
r->delay_pos = 0;
return old_sample;
}
// Process a sample update
static void state_normal_update(struct qsound_chip *chip)
{
int v, ch;
int32_t echo_input = 0;
int16_t echo_output;
chip->ready_flag = 0x80;
// recalculate echo length
if(chip->state == STATE_NORMAL2)
chip->echo.length = chip->echo.end_pos - 0x53c;
else
chip->echo.length = chip->echo.end_pos - 0x554;
chip->echo.length = CLAMP(chip->echo.length, 0, 1024);
// update PCM voices
for(v=0; v<16; v++)
{
chip->voice_output[v] = pcm_update(chip, v, &echo_input);
}
// update ADPCM voices (one every third sample)
adpcm_update(chip, chip->state_counter % 3, chip->state_counter / 3);
echo_output = echo(&chip->echo,echo_input);
// now, we do the magic stuff
for(ch=0; ch<2; ch++)
{
// Echo is output on the unfiltered component of the left channel and
// the filtered component of the right channel.
int32_t wet = (ch == 1) ? echo_output<<16 : 0;
int32_t dry = (ch == 0) ? echo_output<<16 : 0;
int32_t output = 0;
for(int v=0; v<19; v++)
{
uint16_t pan_index = chip->voice_pan[v]-0x110;
if(pan_index > 97)
pan_index = 97;
// Apply different volume tables on the dry and wet inputs.
dry -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_DRY][pan_index])<<2;
wet -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_WET][pan_index])<<2;
}
// Apply FIR filter on 'wet' input
wet = fir(&chip->filter[ch], wet >> 16);
// in mode 2, we do this on the 'dry' input too
if(chip->state == STATE_NORMAL2)
dry = fir(&chip->alt_filter[ch], dry >> 16);
// output goes through a delay line and attenuation
output = (delay(&chip->wet[ch], wet) + delay(&chip->dry[ch], dry));
// DSP round function
output = (output + 0x2000) >> 14;
chip->out[ch] = CLAMP(output, -0x7fff, 0x7fff);
if(chip->delay_update)
{
delay_update(&chip->wet[ch]);
delay_update(&chip->dry[ch]);
}
}
chip->delay_update = 0;
// after 6 samples, the next state is executed.
chip->state_counter++;
if(chip->state_counter > 5)
{
chip->state_counter = 0;
chip->state = chip->next_state;
}
}
// Apply the FIR filter used as the Q1 transfer function
static inline int32_t fir(struct qsound_fir *f, int16_t input)
{
int32_t output = 0, tap = 0;
for(; tap < (f->tap_count-1); tap++)
{
output -= (f->taps[tap] * f->delay_line[f->delay_pos++])<<2;
if(f->delay_pos >= f->tap_count-1)
f->delay_pos = 0;
}
output -= (f->taps[tap] * input)<<2;
f->delay_line[f->delay_pos++] = input;
if(f->delay_pos >= f->tap_count-1)
f->delay_pos = 0;
return output;
}
// Apply delay line and component volume
static inline int32_t delay(struct qsound_delay *d, int32_t input)
{
int32_t output;
d->delay_line[d->write_pos++] = input>>16;
if(d->write_pos >= 51)
d->write_pos = 0;
output = d->delay_line[d->read_pos++]*d->volume;
if(d->read_pos >= 51)
d->read_pos = 0;
return output;
}
// Update the delay read position to match new delay length
static inline void delay_update(struct qsound_delay *d)
{
int16_t new_read_pos = (d->write_pos - d->delay) % 51;
if(new_read_pos < 0)
new_read_pos += 51;
d->read_pos = new_read_pos;
}

View File

@ -0,0 +1,121 @@
#ifndef QSOUND_H
#define QSOUND_H
#ifdef __cplusplus
extern "C" {
#endif
/*
Capcom DL-1425 QSound emulator
==============================
by superctr (Ian Karlsson)
with thanks to Valley Bell
2018-05-12 - 2018-05-15
*/
#include <stdint.h>
struct qsound_voice {
uint16_t bank;
int16_t addr; // top word is the sample address
uint16_t phase;
uint16_t rate;
int16_t loop_len;
int16_t end_addr;
int16_t volume;
int16_t echo;
};
struct qsound_adpcm {
uint16_t start_addr;
uint16_t end_addr;
uint16_t bank;
int16_t volume;
uint16_t flag;
int16_t cur_vol;
int16_t step_size;
uint16_t cur_addr;
};
// Q1 Filter
struct qsound_fir {
int tap_count; // usually 95
int delay_pos;
int16_t table_pos;
int16_t taps[95];
int16_t delay_line[95];
};
// Delay line
struct qsound_delay {
int16_t delay;
int16_t volume;
int16_t write_pos;
int16_t read_pos;
int16_t delay_line[51];
};
struct qsound_echo {
uint16_t end_pos;
int16_t feedback;
int16_t length;
int16_t last_sample;
int16_t delay_line[1024];
int16_t delay_pos;
};
struct qsound_chip {
unsigned long rom_mask;
uint8_t *rom_data;
uint32_t mute_mask;
uint16_t data_latch;
int16_t out[2];
int16_t pan_tables[2][2][98];
struct qsound_voice voice[16];
struct qsound_adpcm adpcm[3];
uint16_t voice_pan[16+3];
int16_t voice_output[16+3];
struct qsound_echo echo;
struct qsound_fir filter[2];
struct qsound_fir alt_filter[2];
struct qsound_delay wet[2];
struct qsound_delay dry[2];
uint16_t state;
uint16_t next_state;
uint16_t delay_update;
int state_counter;
int ready_flag;
uint16_t *register_map[256];
};
long qsound_start(struct qsound_chip *chip, int clock);
void qsound_reset(struct qsound_chip *chip);
void qsound_update(struct qsound_chip *chip);
uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples);
uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data);
uint8_t qsound_r(struct qsound_chip *chip);
void qsound_write_data(struct qsound_chip *chip, uint8_t address, uint16_t data);
uint16_t qsound_read_data(struct qsound_chip *chip, uint8_t address);
#ifdef __cplusplus
};
#endif
#endif

View File

@ -22,7 +22,7 @@
#include <string.h>
#include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {tia.set(a,v); if (dumpWrites) {addWrite(a,v);} }
#define rWrite(a,v) if (!skipRegisterWrites) {tia.set(a,v); regPool[((a)-0x15)&0x0f]=v; if (dumpWrites) {addWrite(a,v);} }
const char* regCheatSheetTIA[]={
"AUDC0", "15",
@ -281,8 +281,17 @@ void* DivPlatformTIA::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformTIA::getRegisterPool() {
return regPool;
}
int DivPlatformTIA::getRegisterPoolSize() {
return 6;
}
void DivPlatformTIA::reset() {
tia.reset();
memset(regPool,0,16);
for (int i=0; i<2; i++) {
chan[i]=DivPlatformTIA::Channel();
chan[i].vol=0x0f;

View File

@ -38,6 +38,7 @@ class DivPlatformTIA: public DivDispatch {
Channel chan[2];
bool isMuted[2];
TIASound tia;
unsigned char regPool[16];
friend void putDispatchChan(void*,int,int);
unsigned char dealWithFreq(unsigned char shape, int base, int pitch);
@ -46,6 +47,8 @@ class DivPlatformTIA: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -111,6 +111,7 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l
QueuedWrite& w=writes.front();
fm->write(0x0+((w.addr>>8)<<1),w.addr);
fm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0x1ff]=w.val;
writes.pop();
delay=4;
}
@ -848,6 +849,14 @@ void* DivPlatformYM2610::getChanState(int ch) {
return &chan[ch];
}
unsigned char* DivPlatformYM2610::getRegisterPool() {
return regPool;
}
int DivPlatformYM2610::getRegisterPoolSize() {
return 512;
}
void DivPlatformYM2610::poke(unsigned int addr, unsigned short val) {
immWrite(addr,val);
}
@ -858,6 +867,7 @@ void DivPlatformYM2610::poke(std::vector<DivRegWrite>& wlist) {
void DivPlatformYM2610::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,512);
if (dumpWrites) {
addWrite(0xffffffff,0);
}

View File

@ -59,6 +59,7 @@ class DivPlatformYM2610: public DivDispatch {
ymfm::ym2610* fm;
ymfm::ym2610::output_data fmout;
DivYM2610Interface iface;
unsigned char regPool[512];
unsigned char lastBusy;
bool dacMode;
@ -88,6 +89,8 @@ class DivPlatformYM2610: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick();

View File

@ -108,6 +108,12 @@ const char* cmdName[DIV_CMD_MAX]={
"AY_AUTO_ENVELOPE",
"SAA_ENVELOPE",
"LYNX_LFSR_LOAD",
"QSOUND_ECHO_FEEDBACK",
"QSOUND_ECHO_DELAY",
"QSOUND_ECHO_LEVEL",
"ALWAYS_SET_VOLUME"
};
@ -218,6 +224,31 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
return false;
}
break;
case DIV_SYSTEM_LYNX:
if (effect>=0x30 && effect<0x40) {
int value = ((int)(effect&0x0f)<<8)|effectVal;
dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value));
break;
}
return false;
break;
case DIV_SYSTEM_QSOUND:
switch (effect) {
case 0x10: // echo feedback
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_FEEDBACK,ch,effectVal));
break;
case 0x11: // echo level
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_LEVEL,ch,effectVal));
break;
default:
if ((effect & 0xf0)==0x30) {
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_DELAY,ch,((effect & 0x0f) << 8) | effectVal));
} else {
return false;
}
break;
}
break;
default:
return false;
}

View File

@ -33,7 +33,7 @@ struct DivSample {
// - 16: 16-bit PCM
unsigned char depth;
short* data;
unsigned int rendLength, adpcmRendLength, rendOff, rendOffP, rendOffContiguous;
unsigned int rendLength, adpcmRendLength, rendOff, rendOffP, rendOffContiguous, rendOffQsound;
short* rendData;
unsigned char* adpcmRendData;
@ -54,6 +54,7 @@ struct DivSample {
rendOff(0),
rendOffP(0),
rendOffContiguous(0),
rendOffQsound(0),
rendData(NULL),
adpcmRendData(NULL) {}
~DivSample();

View File

@ -87,6 +87,8 @@ enum DivSystem {
DIV_SYSTEM_YM2610_FULL,
DIV_SYSTEM_YM2610_FULL_EXT,
DIV_SYSTEM_OPLL_DRUMS,
DIV_SYSTEM_LYNX,
DIV_SYSTEM_QSOUND
};
struct DivSong {
@ -210,6 +212,12 @@ struct DivSong {
// - 1: Amiga 1200
// - bit 8-14: stereo separation
// - 0 is 0% while 127 is 100%
// - QSound:
// - bit 12-20: echo feedback
// - Valid values are 0-255
// - bit 0-11: echo delay length
// - Valid values are 0-2725
// - 0 is max length, 2725 is min length
unsigned int systemFlags[32];
// song information

View File

@ -129,6 +129,10 @@ DivSystem DivEngine::systemFromFile(unsigned char val) {
return DIV_SYSTEM_YM2610_FULL_EXT;
case 0xa7:
return DIV_SYSTEM_OPLL_DRUMS;
case 0xa8:
return DIV_SYSTEM_LYNX;
case 0xe0:
return DIV_SYSTEM_QSOUND;
}
return DIV_SYSTEM_NULL;
}
@ -242,6 +246,10 @@ unsigned char DivEngine::systemToFile(DivSystem val) {
return 0xa6;
case DIV_SYSTEM_OPLL_DRUMS:
return 0xa7;
case DIV_SYSTEM_LYNX:
return 0xa8;
case DIV_SYSTEM_QSOUND:
return 0xe0;
case DIV_SYSTEM_NULL:
return 0;
@ -354,6 +362,10 @@ int DivEngine::getChannelCount(DivSystem sys) {
return 17;
case DIV_SYSTEM_OPLL_DRUMS:
return 11;
case DIV_SYSTEM_LYNX:
return 4;
case DIV_SYSTEM_QSOUND:
return 19;
}
return 0;
}
@ -474,6 +486,10 @@ const char* DivEngine::getSystemName(DivSystem sys) {
return "Yamaha OPL3 with drums";
case DIV_SYSTEM_OPLL_DRUMS:
return "Yamaha OPLL with drums";
case DIV_SYSTEM_LYNX:
return "Atari Lynx";
case DIV_SYSTEM_QSOUND:
return "Capcom QSound";
}
return "Unknown";
}
@ -589,6 +605,10 @@ const char* DivEngine::getSystemChips(DivSystem sys) {
return "Yamaha YM2610 (extended channel 2)";
case DIV_SYSTEM_OPLL_DRUMS:
return "Yamaha YM2413 with drums";
case DIV_SYSTEM_LYNX:
return "Mikey";
case DIV_SYSTEM_QSOUND:
return "Capcom DL-1425";
}
return "Unknown";
}
@ -668,7 +688,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) {
sys!=DIV_SYSTEM_YM2151);
}
const char* chanNames[36][24]={
const char* chanNames[37][24]={
{"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"}, // YMU759/SegaPCM
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis
{"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", "Noise"}, // Genesis (extended channel 3)
@ -681,7 +701,7 @@ const char* chanNames[36][24]={
{"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"}, // YM2610
{"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"}, // YM2610 (extended channel 2)
{"PSG 1", "PSG 2", "PSG 3"}, // AY-3-8910
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga/POKEY/Swan
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga/POKEY/Swan/Lynx
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, // YM2151/YM2414
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, // YM2612
{"Channel 1", "Channel 2"}, // TIA
@ -705,9 +725,10 @@ const char* chanNames[36][24]={
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 drums
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6"}, // OPL3 4-op
{"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums
{"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"}, // QSound
};
const char* chanShortNames[36][24]={
const char* chanShortNames[37][24]={
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3)
@ -720,7 +741,7 @@ const char* chanShortNames[36][24]={
{"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610
{"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610 (extended channel 2)
{"S1", "S2", "S3"}, // AY-3-8910
{"CH1", "CH2", "CH3", "CH4"}, // Amiga
{"CH1", "CH2", "CH3", "CH4"}, // Amiga/Lynx
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"}, // YM2151
{"F1", "F2", "F3", "F4", "F5", "F6"}, // YM2612
{"CH1", "CH2"}, // TIA
@ -744,9 +765,10 @@ const char* chanShortNames[36][24]={
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"}, // OPL3 drums
{"F1", "F2", "F3", "F4", "F5", "F6", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6"}, // OPL3 4-op
{"F1", "F2", "F3", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "BD", "SD", "TM", "TP", "HH"}, // OPL3 4-op + drums
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, // QSound
};
const int chanTypes[36][24]={
const int chanTypes[37][24]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3)
@ -775,7 +797,7 @@ const int chanTypes[36][24]={
{0, 0, 0, 1, 1, 1}, // OPN
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound
{1}, // PC Speaker/Pokémon Mini
{3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC
{0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B
@ -783,9 +805,10 @@ const int chanTypes[36][24]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 drums
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op
{0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums
{3, 3, 3, 3}, //Lynx
};
const DivInstrumentType chanPrefType[42][24]={
const DivInstrumentType chanPrefType[43][24]={
{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_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis
{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_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3)
@ -814,7 +837,7 @@ const DivInstrumentType chanPrefType[42][24]={
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, // OPN
{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}, // PC-98
{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_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL/OPL2/OPL3
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound
{DIV_INS_STD}, // PC Speaker/Pokémon Mini
{DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}, // Virtual Boy
{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}, // YM2610B
@ -828,6 +851,7 @@ const DivInstrumentType chanPrefType[42][24]={
{DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER}, // ZX beeper
{DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN}, // Swan
{DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z
{DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx
};
const char* DivEngine::getChannelName(int chan) {
@ -881,6 +905,7 @@ const char* DivEngine::getChannelName(int chan) {
case DIV_SYSTEM_AMIGA:
case DIV_SYSTEM_POKEY:
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_LYNX:
return chanNames[12][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_YM2151:
@ -956,6 +981,9 @@ const char* DivEngine::getChannelName(int chan) {
case DIV_SYSTEM_AY8930:
return chanNames[17][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_QSOUND:
return chanNames[36][dispatchChanOfChan[chan]];
break;
}
return "??";
}
@ -1011,6 +1039,7 @@ const char* DivEngine::getChannelShortName(int chan) {
case DIV_SYSTEM_AMIGA:
case DIV_SYSTEM_POKEY:
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_LYNX:
return chanShortNames[12][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_YM2151:
@ -1086,6 +1115,9 @@ const char* DivEngine::getChannelShortName(int chan) {
case DIV_SYSTEM_AY8930:
return chanShortNames[17][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_QSOUND:
return chanShortNames[36][dispatchChanOfChan[chan]];
break;
}
return "??";
}
@ -1187,6 +1219,7 @@ int DivEngine::getChannelType(int chan) {
break;
case DIV_SYSTEM_MULTIPCM:
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_QSOUND:
return chanTypes[28][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_PCSPKR:
@ -1214,6 +1247,9 @@ int DivEngine::getChannelType(int chan) {
case DIV_SYSTEM_AY8930:
return chanTypes[17][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_LYNX:
return chanTypes[36][dispatchChanOfChan[chan]];
break;
}
return 1;
}
@ -1310,6 +1346,7 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
break;
case DIV_SYSTEM_MULTIPCM:
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_QSOUND:
return chanPrefType[28][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_PCSPKR:
@ -1355,6 +1392,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
case DIV_SYSTEM_OPZ:
return chanPrefType[41][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_LYNX:
return chanPrefType[42][dispatchChanOfChan[chan]];
break;
}
return DIV_INS_FM;
}
@ -1375,6 +1415,7 @@ bool DivEngine::isVGMExportable(DivSystem which) {
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
case DIV_SYSTEM_SAA1099:
case DIV_SYSTEM_QSOUND:
return true;
default:
return false;

View File

@ -260,6 +260,41 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0);
}
break;
case DIV_SYSTEM_LYNX:
w->writeC(0x4e);
w->writeC(0x44);
w->writeC(0xff); //stereo attenuation select
w->writeC(0x4e);
w->writeC(0x50);
w->writeC(0x00); //stereo channel disable
for (int i=0; i<4; i++) { //stereo attenuation value
w->writeC(0x4e);
w->writeC(0x40+i);
w->writeC(0xff);
}
break;
case DIV_SYSTEM_QSOUND:
for (int i=0; i<16; i++) {
w->writeC(0xc4);
w->writeC(0);
w->writeC(0);
w->writeC(2+(i*8));
w->writeC(0xc4);
w->writeC(0);
w->writeC(0);
w->writeC(6+(i*8));
}
for (int i=0; i<3; i++) {
w->writeC(0xc4);
w->writeC(0);
w->writeC(0);
w->writeC(0xcd+(i*4));
w->writeC(0xc4);
w->writeC(0x00);
w->writeC(0x01);
w->writeC(0xd6+i);
}
break;
default:
break;
}
@ -377,6 +412,17 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC((isSecond?0x80:0)|(write.addr&0xff));
w->writeC(write.val);
break;
case DIV_SYSTEM_LYNX:
w->writeC(0x4e);
w->writeC(write.addr&0xff);
w->writeC(write.val&0xff);
break;
case DIV_SYSTEM_QSOUND:
w->writeC(0xc4);
w->writeC((write.val>>8)&0xff);
w->writeC(write.val&0xff);
w->writeC(write.addr&0xff);
break;
default:
logW("write not handled!\n");
break;
@ -385,6 +431,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
stop();
repeatPattern=false;
setOrder(0);
isBusy.lock();
double origRate=got.rate;
@ -456,6 +503,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
int hasX1=0;
int hasC352=0;
int hasGA20=0;
int hasLynx=0;
int howManyChips=0;
@ -488,6 +536,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
bool writePCESamples=false;
bool writeADPCM=false;
bool writeSegaPCM=false;
bool writeQSound=false;
for (int i=0; i<song.systemLen; i++) {
willExport[i]=false;
@ -667,6 +716,31 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
howManyChips++;
}
break;
case DIV_SYSTEM_LYNX:
if (!hasLynx) {
hasLynx=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasLynx&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasLynx|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_QSOUND:
if (!hasQSound) {
// could set chipClock to 4000000 here for compatibility
// However I think it it not necessary because old VGM players will still
// not be able to handle the 64kb sample bank trick
hasQSound=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeQSound=true;
} else if (!(hasQSound&0x40000000)) {
isSecond[i]=true;
willExport[i]=false;
addWarning("dual QSound is not supported by the VGM format");
}
break;
default:
break;
}
@ -752,7 +826,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
w->writeI(hasX1);
w->writeI(hasC352);
w->writeI(hasGA20);
for (int i=0; i<7; i++) { // reserved
w->writeI(hasLynx);
for (int i=0; i<6; i++) { // reserved
w->writeI(0);
}
@ -899,6 +974,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
w->write(adpcmMem,adpcmMemLen);
}
if (writeQSound && qsoundMemLen>0) {
// always write a whole bank
unsigned int blockSize=(qsoundMemLen + 0xffff) & ~0xffff;
if(blockSize > 0x1000000)
blockSize = 0x1000000;
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x8F);
w->writeI(blockSize+8);
w->writeI(0x1000000);
w->writeI(0);
w->write(qsoundMem,blockSize);
}
// initialize streams
int streamID=0;
for (int i=0; i<song.systemLen; i++) {

View File

@ -154,7 +154,7 @@ bool FurnaceGUI::decodeNote(const char* what, short& note, short& octave) {
String FurnaceGUI::encodeKeyMap(std::map<int,int>& map) {
String ret;
for (std::map<int,int>::value_type& i: map) {
ret+=fmt::printf("%d:%d;",i.first,i.second);
ret+=fmt::sprintf("%d:%d;",i.first,i.second);
}
return ret;
}
@ -1156,6 +1156,10 @@ void FurnaceGUI::drawInsList() {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]);
name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d\n",i,ins->name,i);
break;
case DIV_INS_MIKEY:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i);
break;
default:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]);
name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d\n",i,ins->name,i);
@ -1267,11 +1271,11 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::Text("Length: %d",sample->length);
if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) {
if (sample->rate<100) sample->rate=100;
if (sample->rate>32000) sample->rate=32000;
if (sample->rate>96000) sample->rate=96000;
}
if (ImGui::InputInt("Pitch of C-4 (Hz)",&sample->centerRate,10,200)) {
if (sample->centerRate<100) sample->centerRate=100;
if (sample->centerRate>32000) sample->centerRate=32000;
if (sample->centerRate>65535) sample->centerRate=65535;
}
ImGui::Text("effective rate: %dHz",e->getEffectiveSampleRate(sample->rate));
bool doLoop=(sample->loopStart>=0);
@ -1379,7 +1383,6 @@ void FurnaceGUI::drawOsc() {
if (!oscOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0,0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0));
if (ImGui::Begin("Oscilloscope",&oscOpen)) {
@ -1393,7 +1396,7 @@ void FurnaceGUI::drawOsc() {
ImGui::PlotLines("##SingleOsc",values,512,0,NULL,-1.0f,1.0f,ImGui::GetContentRegionAvail());
ImGui::EndDisabled();
}
ImGui::PopStyleVar(4);
ImGui::PopStyleVar(3);
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_OSCILLOSCOPE;
ImGui::End();
}
@ -1487,7 +1490,7 @@ void FurnaceGUI::drawVolMeter() {
ImGui::End();
}
const char* aboutLine[57]={
const char* aboutLine[]={
"tildearrow",
"is proud to present",
"",
@ -1497,8 +1500,39 @@ const char* aboutLine[57]={
"compatible with DefleMask modules.",
"",
"zero disassembly.",
"zero reverse-engineering.",
"only time and dedication.",
"just clean-room design,",
"time and dedication.",
"",
"> CREDITS <",
"",
"-- program --",
"tildearrow",
"",
"-- graphics --",
"tildearrow",
"",
"-- documentation --",
"tildearrow",
"freq-mod",
"nicco1690",
"DeMOSic",
"cam900",
"",
"-- demo songs --",
"0x5066",
"breakthetargets",
"kleeder",
"NikonTeen",
"SuperJet Spade",
"TheDuccinator",
"tildearrow",
"Ultraprogramer",
"",
"-- additional feedback/fixes --",
"fd",
"OPNA2608",
"plane",
"TheEssem",
"",
"powered by:",
"Dear ImGui by Omar Cornut",
@ -1518,6 +1552,7 @@ const char* aboutLine[57]={
"puNES by FHorse",
"reSID by Dag Lem",
"Stella by Stella Team",
"QSound emulator by Ian Karlsson and Valley Bell",
"",
"greetings to:",
"Delek",
@ -1525,7 +1560,8 @@ const char* aboutLine[57]={
"ILLUMIDARO",
"all members of Deflers of Noice!",
"",
"copyright © 2021-2022 tildearrow.",
"copyright © 2021-2022 tildearrow",
"(and contributors).",
"licensed under GPLv2+! see",
"LICENSE for more information.",
"",
@ -1544,9 +1580,15 @@ const char* aboutLine[57]={
"",
"it also comes with ABSOLUTELY NO WARRANTY.",
"",
"thanks to all contributors!"
"look out for Furnace 0.6 coming somewhere",
"before the equinox with more systems",
"and plenty of other things...",
"",
"thanks to all contributors/bug reporters!"
};
const size_t aboutCount = sizeof(aboutLine)/sizeof(aboutLine[0]);
void FurnaceGUI::drawAbout() {
// do stuff
if (ImGui::Begin("About Furnace",NULL,ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoDocking|ImGuiWindowFlags_NoTitleBar)) {
@ -1586,7 +1628,7 @@ void FurnaceGUI::drawAbout() {
skip=false;
skip2=false;
for (int i=(-fmod(160-(aboutSin*2),160))*2; i<scrW; i+=160) {
for (int i=(-160+fmod(aboutSin*2,160))*2; i<scrW; i+=160) {
skip2=!skip2;
skip=skip2;
for (int j=(-240-cos(double(aboutSin*M_PI/300.0))*240.0)*2; j<scrH; j+=160) {
@ -1596,7 +1638,7 @@ void FurnaceGUI::drawAbout() {
}
}
for (int i=0; i<56; i++) {
for (size_t i=0; i<aboutCount; i++) {
double posX=(scrW*dpiScale/2.0)+(sin(double(i)*0.5+double(aboutScroll)/90.0)*120*dpiScale)-(ImGui::CalcTextSize(aboutLine[i]).x*0.5);
double posY=(scrH-aboutScroll+42*i)*dpiScale;
if (posY<-80*dpiScale || posY>scrH*dpiScale) continue;
@ -1626,7 +1668,7 @@ void FurnaceGUI::drawAbout() {
while (aboutHue>1) aboutHue--;
while (aboutSin>=2400) aboutSin-=2400;
if (aboutScroll>(42*57+scrH)) aboutScroll=-20;
if (aboutScroll>(42*aboutCount+scrH)) aboutScroll=-20;
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ABOUT;
ImGui::End();
@ -1863,12 +1905,16 @@ void FurnaceGUI::drawStats() {
if (ImGui::Begin("Statistics",&statsOpen)) {
String adpcmUsage=fmt::sprintf("%d/16384KB",e->adpcmMemLen/1024);
String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024);
String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024);
ImGui::Text("ADPCM-A");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->adpcmMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmUsage.c_str());
ImGui::Text("ADPCM-B");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str());
ImGui::Text("QSound");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str());
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS;
ImGui::End();
@ -2039,6 +2085,56 @@ void FurnaceGUI::drawChannels() {
ImGui::End();
}
void FurnaceGUI::drawRegView() {
if (nextWindow==GUI_WINDOW_REGISTER_VIEW) {
channelsOpen=true;
ImGui::SetNextWindowFocus();
nextWindow=GUI_WINDOW_NOTHING;
}
if (!regViewOpen) return;
if (ImGui::Begin("Register View",&regViewOpen)) {
for (int i=0; i<e->song.systemLen; i++) {
ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i]));
int size=0;
int depth=8;
unsigned char* regPool=e->getRegisterPool(i,size,depth);
unsigned short* regPoolW=(unsigned short*) regPool;
if (regPool==NULL) {
ImGui::Text("- no register pool available");
} else {
ImGui::PushFont(patFont);
if (ImGui::BeginTable("Memory",17)) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
for (int i=0; i<16; i++) {
ImGui::TableNextColumn();
ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX]," %X",i);
}
for (int i=0; i<=((size-1)>>4); i++) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX],"%.2X",i*16);
for (int j=0; j<16; j++) {
ImGui::TableNextColumn();
if (i*16+j>=size) continue;
if(depth == 8)
ImGui::Text("%.2x",regPool[i*16+j]);
else if(depth == 16)
ImGui::Text("%.4x",regPoolW[i*16+j]);
else
ImGui::Text("??");
}
}
ImGui::EndTable();
}
ImGui::PopFont();
}
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_REGISTER_VIEW;
ImGui::End();
}
void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) {
if (xCoarse!=selStart.xCoarse || xFine!=selStart.xFine || y!=selStart.y) {
curNibble=false;
@ -2728,7 +2824,7 @@ void FurnaceGUI::doPaste() {
invalidData=true;
break;
}
if (iFine<(3+e->song.pat[cursor.xCoarse].effectRows*2)) pat->data[j][iFine+1]=val;
if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val;
}
}
iFine++;
@ -2822,6 +2918,7 @@ void FurnaceGUI::doRedo() {
void FurnaceGUI::play(int row) {
e->walkSong(loopOrder,loopRow,loopEnd);
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
if (row>0) {
e->playToRow(row);
} else {
@ -3052,6 +3149,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_WINDOW_CHANNELS:
nextWindow=GUI_WINDOW_CHANNELS;
break;
case GUI_ACTION_WINDOW_REGISTER_VIEW:
nextWindow=GUI_WINDOW_REGISTER_VIEW;
break;
case GUI_ACTION_COLLAPSE_WINDOW:
collapseWindow=true;
@ -3121,6 +3221,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_WINDOW_CHANNELS:
channelsOpen=false;
break;
case GUI_WINDOW_REGISTER_VIEW:
regViewOpen=false;
break;
default:
break;
}
@ -4390,6 +4493,8 @@ bool FurnaceGUI::loop() {
sysAddOption(DIV_SYSTEM_TIA);
sysAddOption(DIV_SYSTEM_SAA1099);
sysAddOption(DIV_SYSTEM_AY8930);
sysAddOption(DIV_SYSTEM_LYNX);
sysAddOption(DIV_SYSTEM_QSOUND);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("configure system...")) {
@ -4555,6 +4660,23 @@ bool FurnaceGUI::loop() {
}
break;
}
case DIV_SYSTEM_QSOUND: {
ImGui::Text("Echo delay:");
int echoBufSize=2725 - (flags & 4095);
if (ImGui::SliderInt("##EchoBufSize",&echoBufSize,0,2725)) {
if (echoBufSize<0) echoBufSize=0;
if (echoBufSize>2725) echoBufSize=2725;
e->setSysFlags(i,(flags & ~4095) | ((2725 - echoBufSize) & 4095),restart);
}
ImGui::Text("Echo feedback:");
int echoFeedback=(flags>>12)&255;
if (ImGui::SliderInt("##EchoFeedback",&echoFeedback,0,255)) {
if (echoFeedback<0) echoFeedback=0;
if (echoFeedback>255) echoFeedback=255;
e->setSysFlags(i,(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12),restart);
}
break;
}
case DIV_SYSTEM_GB:
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_EXT:
@ -4597,6 +4719,8 @@ bool FurnaceGUI::loop() {
sysChangeOption(i,DIV_SYSTEM_TIA);
sysChangeOption(i,DIV_SYSTEM_SAA1099);
sysChangeOption(i,DIV_SYSTEM_AY8930);
sysChangeOption(i,DIV_SYSTEM_LYNX);
sysChangeOption(i,DIV_SYSTEM_QSOUND);
ImGui::EndMenu();
}
}
@ -4641,6 +4765,10 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("settings")) {
if (ImGui::MenuItem("reset layout")) {
ImGui::LoadIniSettingsFromMemory(defaultLayout);
ImGui::SaveIniSettingsToDisk(finalLayoutPath);
}
if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) {
syncSettings();
settingsOpen=true;
@ -4667,6 +4795,7 @@ bool FurnaceGUI::loop() {
if (ImGui::MenuItem("piano/input pad",BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen;
if (ImGui::MenuItem("oscilloscope",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen;
if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen;
if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen;
if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen;
ImGui::EndMenu();
@ -4770,6 +4899,7 @@ bool FurnaceGUI::loop() {
drawPiano();
drawNotes();
drawChannels();
drawRegView();
if (ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) {
//ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard;
@ -5132,6 +5262,7 @@ void FurnaceGUI::applyUISettings() {
GET_UI_COLOR(GUI_COLOR_INSTR_POKEY,ImVec4(0.5f,1.0f,0.3f,1.0f));
GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f));
GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f));
GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f));
GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f));
GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f));
GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f));
@ -5380,6 +5511,7 @@ bool FurnaceGUI::init() {
pianoOpen=e->getConfBool("pianoOpen",false);
notesOpen=e->getConfBool("notesOpen",false);
channelsOpen=e->getConfBool("channelsOpen",false);
regViewOpen=e->getConfBool("regViewOpen",false);
syncSettings();
@ -5533,6 +5665,7 @@ bool FurnaceGUI::finish() {
e->setConf("pianoOpen",pianoOpen);
e->setConf("notesOpen",notesOpen);
e->setConf("channelsOpen",channelsOpen);
e->setConf("regViewOpen",regViewOpen);
// commit last window size
e->setConf("lastWindowWidth",scrW);
@ -5602,6 +5735,7 @@ FurnaceGUI::FurnaceGUI():
pianoOpen(false),
notesOpen(false),
channelsOpen(false),
regViewOpen(false),
selecting(false),
curNibble(false),
orderNibble(false),
@ -5658,6 +5792,7 @@ FurnaceGUI::FurnaceGUI():
oldOrdersLen(0) {
// octave 1
/*
noteKeys[SDL_SCANCODE_Z]=0;
noteKeys[SDL_SCANCODE_S]=1;
noteKeys[SDL_SCANCODE_X]=2;
@ -5703,6 +5838,7 @@ FurnaceGUI::FurnaceGUI():
// env release
noteKeys[SDL_SCANCODE_GRAVE]=102;
*/
// value keys
valueKeys[SDLK_0]=0;
@ -5741,4 +5877,5 @@ FurnaceGUI::FurnaceGUI():
memset(patChanX,0,sizeof(float)*(DIV_MAX_CHANS+1));
memset(patChanSlideY,0,sizeof(float)*(DIV_MAX_CHANS+1));
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
}

View File

@ -66,6 +66,7 @@ enum FurnaceGUIColors {
GUI_COLOR_INSTR_POKEY,
GUI_COLOR_INSTR_BEEPER,
GUI_COLOR_INSTR_SWAN,
GUI_COLOR_INSTR_MIKEY,
GUI_COLOR_INSTR_UNKNOWN,
GUI_COLOR_CHANNEL_FM,
GUI_COLOR_CHANNEL_PULSE,
@ -127,6 +128,7 @@ enum FurnaceGUIWindows {
GUI_WINDOW_PIANO,
GUI_WINDOW_NOTES,
GUI_WINDOW_CHANNELS,
GUI_WINDOW_REGISTER_VIEW
};
enum FurnaceGUIFileDialogs {
@ -209,6 +211,7 @@ enum FurnaceGUIActions {
GUI_ACTION_WINDOW_PIANO,
GUI_ACTION_WINDOW_NOTES,
GUI_ACTION_WINDOW_CHANNELS,
GUI_ACTION_WINDOW_REGISTER_VIEW,
GUI_ACTION_COLLAPSE_WINDOW,
GUI_ACTION_CLOSE_WINDOW,
@ -520,7 +523,7 @@ class FurnaceGUI {
bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen;
bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen;
bool mixerOpen, debugOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen;
bool pianoOpen, notesOpen, channelsOpen;
bool pianoOpen, notesOpen, channelsOpen, regViewOpen;
SelectionPoint selStart, selEnd, cursor;
bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders;
bool collapseWindow, demandScrollX, fancyPattern, wantPatName;
@ -567,9 +570,9 @@ class FurnaceGUI {
int samplePreviewNote;
// SDL_Scancode,int
std::map<SDL_Scancode,int> noteKeys;
std::map<int,int> noteKeys;
// SDL_Keycode,int
std::map<SDL_Keycode,int> valueKeys;
std::map<int,int> valueKeys;
int arpMacroScroll;
@ -620,6 +623,7 @@ class FurnaceGUI {
std::deque<UndoStep> redoHist;
float keyHit[DIV_MAX_CHANS];
int lastIns[DIV_MAX_CHANS];
void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size);
void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, const ImVec2& size);
@ -647,6 +651,7 @@ class FurnaceGUI {
void drawPiano();
void drawNotes();
void drawChannels();
void drawRegView();
void drawAbout();
void drawSettings();
void drawDebug();

View File

@ -26,7 +26,7 @@
#include <fmt/printf.h>
#include "plot_nolerp.h"
const char* insTypes[23]={
const char* insTypes[24]={
"Standard",
"FM (4-operator)",
"Game Boy",
@ -49,7 +49,8 @@ const char* insTypes[23]={
"FM (OPZ)",
"POKEY",
"PC Beeper",
"WonderSwan"
"WonderSwan",
"Atari Lynx"
};
const char* ssgEnvTypes[8]={
@ -131,6 +132,10 @@ const char* c64SpecialBits[3]={
"sync", "ring", NULL
};
const char* mikeyFeedbackBits[11] = {
"0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL
};
const int orderedOps[4]={
0, 2, 1, 3
};
@ -506,7 +511,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr,
#define PARAMETER modified=true; e->notifyInsChange(curIns);
#define NORMAL_MACRO(macro,macroLen,macroLoop,macroRel,macroMin,macroHeight,macroName,displayName,displayHeight,displayLoop,bitfield,bfVal,drawSlider,sliderVal,sliderLow,macroDispMin,bitOff,macroMode,macroColor,mmlStr,macroAMin,macroAMax,hoverFunc) \
#define NORMAL_MACRO(macro,macroLen,macroLoop,macroRel,macroMin,macroHeight,macroName,displayName,displayHeight,displayLoop,bitfield,bfVal,drawSlider,sliderVal,sliderLow,macroDispMin,bitOff,macroMode,macroColor,mmlStr,macroAMin,macroAMax,hoverFunc,blockMode) \
ImGui::TableNextRow(); \
ImGui::TableNextColumn(); \
ImGui::Text("%s",displayName); \
@ -543,7 +548,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr,
if (bitfield) { \
PlotBitfield("##IMacro_" macroName,asInt,totalFit,0,bfVal,macroHeight,ImVec2(availableWidth,(displayLoop)?(displayHeight*dpiScale):(32.0f*dpiScale))); \
} else { \
PlotCustom("##IMacro_" macroName,asFloat,totalFit,macroDragScroll,NULL,macroDispMin+macroMin,macroHeight+macroDispMin,ImVec2(availableWidth,(displayLoop)?(displayHeight*dpiScale):(32.0f*dpiScale)),sizeof(float),macroColor,macroLen-macroDragScroll,hoverFunc); \
PlotCustom("##IMacro_" macroName,asFloat,totalFit,macroDragScroll,NULL,macroDispMin+macroMin,macroHeight+macroDispMin,ImVec2(availableWidth,(displayLoop)?(displayHeight*dpiScale):(32.0f*dpiScale)),sizeof(float),macroColor,macroLen-macroDragScroll,hoverFunc,blockMode); \
} \
if (displayLoop && ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \
macroDragStart=ImGui::GetItemRectMin(); \
@ -722,9 +727,9 @@ void FurnaceGUI::drawInsEdit() {
} else {
DivInstrument* ins=e->song.ins[curIns];
ImGui::InputText("Name",&ins->name);
if (ins->type<0 || ins->type>22) ins->type=DIV_INS_FM;
if (ins->type<0 || ins->type>23) ins->type=DIV_INS_FM;
int insType=ins->type;
if (ImGui::Combo("Type",&insType,insTypes,23)) {
if (ImGui::Combo("Type",&insType,insTypes,24)) {
ins->type=(DivInstrumentType)insType;
}
@ -812,15 +817,15 @@ void FurnaceGUI::drawInsEdit() {
}
if (ImGui::BeginTabItem("FM Macros")) {
MACRO_BEGIN(0);
NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,7,"alg",FM_NAME(FM_ALG),96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,7,NULL);
NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL);
NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL);
NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL);
NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,7,"alg",FM_NAME(FM_ALG),96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,7,NULL,false);
NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false);
NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false);
NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL,false);
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,127,"ex1","AM Depth",128,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,127,NULL);
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,127,"ex2","PM Depth",128,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,127,NULL);
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,255,"ex3","LFO Speed",128,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL);
NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,3,"wave","LFO Shape",48,ins->std.waveMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[7],0,3,&macroLFOWaves);
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,127,"ex1","AM Depth",128,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,127,NULL,false);
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,127,"ex2","PM Depth",128,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,127,NULL,false);
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,255,"ex3","LFO Speed",128,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false);
NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,3,"wave","LFO Shape",48,ins->std.waveMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[7],0,3,&macroLFOWaves,false);
MACRO_END;
ImGui::EndTabItem();
}
@ -987,7 +992,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_AMIGA) {
volMax=64;
}
if (ins->type==DIV_INS_FM) {
if (ins->type==DIV_INS_FM || ins->type == DIV_INS_MIKEY) {
volMax=127;
}
if (ins->type==DIV_INS_GB) {
@ -1012,6 +1017,10 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM) {
dutyLabel="Noise Freq";
}
if (ins->type == DIV_INS_MIKEY) {
dutyLabel = "Duty/Int";
dutyMax = 10;
}
if (ins->type==DIV_INS_AY8930) {
dutyMax=255;
}
@ -1030,6 +1039,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_C64) waveMax=4;
if (ins->type==DIV_INS_SAA1099) waveMax=2;
if (ins->type==DIV_INS_FM) waveMax=0;
if (ins->type==DIV_INS_MIKEY) waveMax=0;
const char** waveNames=ayShapeBits;
if (ins->type==DIV_INS_C64) waveNames=c64ShapeBits;
@ -1046,42 +1056,47 @@ void FurnaceGUI::drawInsEdit() {
if (settings.macroView==0) { // modern view
MACRO_BEGIN(28*dpiScale);
if (volMax>0) {
NORMAL_MACRO(ins->std.volMacro,ins->std.volMacroLen,ins->std.volMacroLoop,ins->std.volMacroRel,volMin,volMax,"vol",volumeLabel,160,ins->std.volMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_VOLUME],mmlString[0],volMin,volMax,NULL);
NORMAL_MACRO(ins->std.volMacro,ins->std.volMacroLen,ins->std.volMacroLoop,ins->std.volMacroRel,volMin,volMax,"vol",volumeLabel,160,ins->std.volMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_VOLUME],mmlString[0],volMin,volMax,NULL,false);
}
NORMAL_MACRO(ins->std.arpMacro,ins->std.arpMacroLen,ins->std.arpMacroLoop,ins->std.arpMacroRel,arpMacroScroll,arpMacroScroll+24,"arp","Arpeggio",160,ins->std.arpMacroOpen,false,NULL,true,&arpMacroScroll,(arpMode?0:-80),0,0,&ins->std.arpMacroMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[1],-92,94,(ins->std.arpMacroMode?(&macroHoverNote):NULL));
NORMAL_MACRO(ins->std.arpMacro,ins->std.arpMacroLen,ins->std.arpMacroLoop,ins->std.arpMacroRel,arpMacroScroll,arpMacroScroll+24,"arp","Arpeggio",160,ins->std.arpMacroOpen,false,NULL,true,&arpMacroScroll,(arpMode?0:-80),0,0,&ins->std.arpMacroMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[1],-92,94,(ins->std.arpMacroMode?(&macroHoverNote):NULL),true);
if (dutyMax>0) {
NORMAL_MACRO(ins->std.dutyMacro,ins->std.dutyMacroLen,ins->std.dutyMacroLoop,ins->std.dutyMacroRel,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL);
if (ins->type == DIV_INS_MIKEY) {
NORMAL_MACRO(ins->std.dutyMacro,ins->std.dutyMacroLen,ins->std.dutyMacroLoop,ins->std.dutyMacroRel,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacroOpen,true,mikeyFeedbackBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false);
}
else {
NORMAL_MACRO(ins->std.dutyMacro,ins->std.dutyMacroLen,ins->std.dutyMacroLoop,ins->std.dutyMacroRel,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false);
}
}
if (waveMax>0) {
NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,waveMax,"wave","Waveform",bitMode?64:160,ins->std.waveMacroOpen,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL);
NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,waveMax,"wave","Waveform",bitMode?64:160,ins->std.waveMacroOpen,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false);
}
if (ex1Max>0) {
if (ins->type==DIV_INS_C64) {
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1MacroOpen,true,filtModeBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL);
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1MacroOpen,true,filtModeBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
} else if (ins->type==DIV_INS_SAA1099) {
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL);
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
} else {
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL);
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
}
}
if (ex2Max>0) {
if (ins->type==DIV_INS_C64) {
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL);
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false);
} else {
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",64,ins->std.ex2MacroOpen,true,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL);
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",64,ins->std.ex2MacroOpen,true,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false);
}
}
if (ins->type==DIV_INS_C64) {
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,2,"ex3","Special",32,ins->std.ex3MacroOpen,true,c64SpecialBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL);
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,2,"ex3","Special",32,ins->std.ex3MacroOpen,true,c64SpecialBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false);
}
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) {
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,15,"ex3","AutoEnv Num",96,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL);
NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,15,"alg","AutoEnv Den",96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL);
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,15,"ex3","AutoEnv Num",96,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL,false);
NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,15,"alg","AutoEnv Den",96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL,false);
}
if (ins->type==DIV_INS_AY8930) {
// oh my i am running out of macros
NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,8,"fb","Noise AND Mask",96,ins->std.fbMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,8,NULL);
NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,8,"fms","Noise OR Mask",96,ins->std.fmsMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,8,NULL);
NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,8,"fb","Noise AND Mask",96,ins->std.fbMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,8,NULL,false);
NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,8,"fms","Noise OR Mask",96,ins->std.fmsMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,8,NULL,false);
}
MACRO_END;

View File

@ -86,6 +86,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
if (i<0 || i>=e->song.patLen) {
return;
}
bool isPushing=false;
// check overflow highlight
if (settings.overflowHighlight) {
if (edit && cursor.y==i) {
@ -97,6 +98,19 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
} else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
}
} else {
isPushing=true;
if (edit && cursor.y==i) {
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
} else if (isPlaying && oldRow==i) {
ImGui::PushStyleColor(ImGuiCol_Header,0x40ffffff);
} else if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2]));
} else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
} else {
isPushing=false;
}
}
// row number
if (settings.patRowsBase==1) {
@ -117,19 +131,6 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::TableNextColumn();
patChanX[j]=ImGui::GetCursorPosX();
// check overflow highlight
if (!settings.overflowHighlight) {
if (edit && cursor.y==i) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
} else if (isPlaying && oldRow==i) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,0x40ffffff);
} else if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2]));
} else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
}
}
// selection highlight flags
int sel1XSum=sel1.xCoarse*32+sel1.xFine;
int sel2XSum=sel2.xCoarse*32+sel2.xFine;
@ -141,6 +142,8 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
bool cursorIns=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==1);
bool cursorVol=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==2);
// note
sprintf(id,"%s##PN_%d_%d",noteName(pat->data[i][0],pat->data[i][1]),i,j);
if (pat->data[i][0]==0 && pat->data[i][1]==0) {
@ -156,7 +159,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
ImGui::Selectable(id,selectedNote,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars);
if (selectedNote) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedNote,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars);
if (selectedNote) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
startSelection(j,0,i);
@ -185,7 +190,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
ImGui::Selectable(id,selectedIns,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
if (selectedIns) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedIns,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
if (selectedIns) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
startSelection(j,1,i);
@ -215,7 +222,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
ImGui::Selectable(id,selectedVol,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
if (selectedVol) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedVol,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
if (selectedVol) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
startSelection(j,2,i);
@ -268,7 +277,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
ImGui::Selectable(id,selectedEffect,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
if (selectedEffect) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedEffect,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
if (selectedEffect) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
startSelection(j,index-1,i);
@ -292,7 +303,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
demandX=ImGui::GetCursorPosX();
ImGui::PopStyleColor(3);
} else {
ImGui::Selectable(id,selectedEffectVal,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
if (selectedEffectVal) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
ImGui::Selectable(id,isPushing || selectedEffectVal,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
if (selectedEffectVal) ImGui::PopStyleColor();
}
if (ImGui::IsItemClicked()) {
startSelection(j,index,i);
@ -304,6 +317,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
}
}
}
if (isPushing) {
ImGui::PopStyleColor();
}
ImGui::TableNextColumn();
patChanX[chans]=ImGui::GetCursorPosX();
}
@ -315,6 +331,9 @@ void FurnaceGUI::drawPattern() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!patternOpen) return;
float scrollX=0;
if (e->isPlaying() && followPattern) cursor.y=oldRow;
demandX=0;
sel1=selStart;
@ -381,6 +400,15 @@ void FurnaceGUI::drawPattern() {
if (ImGui::Selectable((extraChannelButtons==2)?" --##ExtraChannelButtons":" ++##ExtraChannelButtons",false,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) {
if (++extraChannelButtons>2) extraChannelButtons=0;
}
if (ImGui::IsItemHovered()) {
if (extraChannelButtons==2) {
ImGui::SetTooltip("Pattern names (click to collapse)\nRight-click for visualizer");
} else if (extraChannelButtons==1) {
ImGui::SetTooltip("Expanded (click for pattern names)\nRight-click for visualizer");
} else {
ImGui::SetTooltip("Compact (click to expand)\nRight-click for visualizer");
}
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
fancyPattern=!fancyPattern;
e->enableCommandStream(fancyPattern);
@ -428,7 +456,6 @@ void FurnaceGUI::drawPattern() {
ImGui::PushStyleColor(ImGuiCol_Header,chanHead);
ImGui::PushStyleColor(ImGuiCol_HeaderActive,chanHeadActive);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered,chanHeadHover);
// help me why is the color leakingggggggg
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(chanHead));
if (muted) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_CHANNEL_MUTED]);
ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale));
@ -554,6 +581,7 @@ void FurnaceGUI::drawPattern() {
}
demandScrollX=false;
}
scrollX=ImGui::GetScrollX();
ImGui::EndTable();
}
@ -569,7 +597,6 @@ void FurnaceGUI::drawPattern() {
//if (i.cmd==DIV_CMD_NOTE_ON) continue;
if (i.cmd==DIV_CMD_PRE_PORTA) continue;
if (i.cmd==DIV_CMD_PRE_NOTE) continue;
if (i.cmd==DIV_CMD_INSTRUMENT) continue;
if (i.cmd==DIV_CMD_SAMPLE_BANK) continue;
if (i.cmd==DIV_CMD_GET_VOLUME) continue;
if (i.cmd==DIV_ALWAYS_SET_VOLUME) continue;
@ -589,8 +616,8 @@ void FurnaceGUI::drawPattern() {
switch (i.cmd) {
case DIV_CMD_NOTE_ON:
partIcon=ICON_FA_ASTERISK;
life=64.0f;
lifeSpeed=2.0f;
life=96.0f;
lifeSpeed=3.0f;
break;
case DIV_CMD_LEGATO:
partIcon=ICON_FA_COG;
@ -618,6 +645,23 @@ void FurnaceGUI::drawPattern() {
color=volGrad;
break;
}
case DIV_CMD_INSTRUMENT: {
if (lastIns[i.chan]==i.value) {
num=0;
break;
}
lastIns[i.chan]=i.value;
speedX=0.0f;
speedY=0.0f;
grav=0.0f;
frict=0.98;
spread=30.0f;
life=128.0f;
lifeSpeed=6.0f;
color=insGrad;
num=7+pow(i.value,0.6);
break;
}
case DIV_CMD_PANNING: {
if (i.value==0) {
num=0;
@ -661,7 +705,7 @@ void FurnaceGUI::drawPattern() {
particles.push_back(Particle(
color,
partIcon,
off.x+patChanX[i.chan]+fmod(rand(),width),
off.x+patChanX[i.chan]+fmod(rand(),width)-scrollX,
off.y+(ImGui::GetWindowHeight()*0.5f)+randRange(0,patFont->FontSize),
(speedX+randRange(-spread,spread))*0.5*dpiScale,
(speedY+randRange(-spread,spread))*0.5*dpiScale,
@ -675,31 +719,30 @@ void FurnaceGUI::drawPattern() {
// note slides
ImVec2 arrowPoints[7];
for (int i=0; i<chans; i++) {
if (e->isPlaying()) for (int i=0; i<chans; i++) {
if (!e->song.chanShow[i]) continue;
DivChannelState* ch=e->getChanState(i);
if (ch->portaSpeed>0) {
ImVec4 col=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH];
col.w*=0.2;
float width=patChanX[i+1]-patChanX[i];
if (e->isPlaying()) {
particles.push_back(Particle(
pitchGrad,
(ch->portaNote<=ch->note)?ICON_FA_CHEVRON_DOWN:ICON_FA_CHEVRON_UP,
off.x+patChanX[i]+fmod(rand(),width),
off.y+fmod(rand(),MAX(1,ImGui::GetWindowHeight())),
0.0f,
(7.0f+(rand()%5)+pow(ch->portaSpeed,0.7f))*((ch->portaNote<=ch->note)?1:-1),
0.0f,
1.0f,
255.0f,
15.0f
));
}
particles.push_back(Particle(
pitchGrad,
(ch->portaNote<=ch->note)?ICON_FA_CHEVRON_DOWN:ICON_FA_CHEVRON_UP,
off.x+patChanX[i]+fmod(rand(),width)-scrollX,
off.y+fmod(rand(),MAX(1,ImGui::GetWindowHeight())),
0.0f,
(7.0f+(rand()%5)+pow(ch->portaSpeed,0.7f))*((ch->portaNote<=ch->note)?1:-1),
0.0f,
1.0f,
255.0f,
15.0f
));
for (float j=-patChanSlideY[i]; j<ImGui::GetWindowHeight(); j+=width*0.7) {
ImVec2 tMin=ImVec2(off.x+patChanX[i],off.y+j);
ImVec2 tMax=ImVec2(off.x+patChanX[i+1],off.y+j+width*0.6);
if (width>0.1) for (float j=-patChanSlideY[i]; j<ImGui::GetWindowHeight(); j+=width*0.7) {
ImVec2 tMin=ImVec2(off.x+patChanX[i]-scrollX,off.y+j);
ImVec2 tMax=ImVec2(off.x+patChanX[i+1]-scrollX,off.y+j+width*0.6);
if (ch->portaNote<=ch->note) {
arrowPoints[0]=ImLerp(tMin,tMax,ImVec2(0.1,1.0-0.8));
arrowPoints[1]=ImLerp(tMin,tMax,ImVec2(0.5,1.0-0.0));

View File

@ -289,7 +289,7 @@ void PlotBitfield(const char* label, const int* values, int values_count, int va
PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size);
}
int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float))
int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = ImGui::GetCurrentWindow();
@ -384,6 +384,10 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett
bgColor);
}
if (blockMode) {
window->DrawList->AddLine(ImLerp(inner_bb.Min,inner_bb.Max,ImVec2(0.0f,histogram_zero_line_t)),ImLerp(inner_bb.Min,inner_bb.Max,ImVec2(1.0f,histogram_zero_line_t)),col_base);
}
for (int n = 0; n < res_w; n++)
{
const float t1 = t0 + t_step;
@ -394,7 +398,7 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett
// NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, blockMode?tp0.y:histogram_zero_line_t));
if (plot_type == ImGuiPlotType_Lines)
{
window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
@ -403,6 +407,10 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett
{
if (pos1.x >= pos0.x + 2.0f)
pos1.x -= 1.0f;
if (blockMode) {
pos0.y-=(inner_bb.Max.y-inner_bb.Min.y)*inv_scale;
//pos1.y+=1.0f;
}
window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
}
@ -423,8 +431,8 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett
return idx_hovered;
}
void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float))
void PlotCustom(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float), bool blockMode)
{
FurnacePlotArrayGetterData data(values, stride);
PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc);
PlotCustomEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size, color, highlight, hoverFunc, blockMode);
}

View File

@ -22,4 +22,4 @@
void PlotNoLerp(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));
void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));
void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float) = NULL);
void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float) = NULL, bool blockMode=false);

View File

@ -22,7 +22,9 @@
#include "util.h"
#include "IconsFontAwesome4.h"
#include "misc/cpp/imgui_stdlib.h"
#include <SDL_scancode.h>
#include <fmt/printf.h>
#include <imgui.h>
#ifdef __APPLE__
#define FURKMOD_CMD FURKMOD_META
@ -30,6 +32,8 @@
#define FURKMOD_CMD FURKMOD_CTRL
#endif
#define DEFAULT_NOTE_KEYS "5:7;6:4;7:3;8:16;10:6;11:8;12:24;13:10;16:11;17:9;18:26;19:28;20:12;21:17;22:1;23:19;24:23;25:5;26:14;27:2;28:21;29:0;30:100;31:13;32:15;34:18;35:20;36:22;38:25;39:27;43:100;46:101;47:29;48:31;53:102;"
const char* mainFonts[]={
"IBM Plex Sans",
"Liberation Sans",
@ -113,6 +117,15 @@ void FurnaceGUI::promptKey(int which) {
actionKeys[which]=0;
}
struct MappedInput {
int scan;
int val;
MappedInput():
scan(SDL_SCANCODE_UNKNOWN), val(0) {}
MappedInput(int s, int v):
scan(s), val(v) {}
};
void FurnaceGUI::drawSettings() {
if (nextWindow==GUI_WINDOW_SETTINGS) {
settingsOpen=true;
@ -461,6 +474,7 @@ void FurnaceGUI::drawSettings() {
UI_COLOR_CONFIG(GUI_COLOR_INSTR_POKEY,"POKEY");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_BEEPER,"PC Beeper");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx");
UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown");
ImGui::TreePop();
}
@ -563,6 +577,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PIANO,"Piano");
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_NOTES,"Song Comments");
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CHANNELS,"Channels");
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_REGISTER_VIEW,"Register View");
UI_KEYBIND_CONFIG(GUI_ACTION_COLLAPSE_WINDOW,"Collapse/expand current window");
UI_KEYBIND_CONFIG(GUI_ACTION_CLOSE_WINDOW,"Close current window");
@ -570,6 +585,89 @@ void FurnaceGUI::drawSettings() {
KEYBIND_CONFIG_END;
ImGui::TreePop();
}
if (ImGui::TreeNode("Note input")) {
std::vector<MappedInput> sorted;
if (ImGui::BeginTable("keysNoteInput",4)) {
for (std::map<int,int>::value_type& i: noteKeys) {
std::vector<MappedInput>::iterator j;
for (j=sorted.begin(); j!=sorted.end(); j++) {
if (j->val>i.second) {
break;
}
}
sorted.insert(j,MappedInput(i.first,i.second));
}
static char id[4096];
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableNextColumn();
ImGui::Text("Key");
ImGui::TableNextColumn();
ImGui::Text("Type");
ImGui::TableNextColumn();
ImGui::Text("Value");
ImGui::TableNextColumn();
ImGui::Text("Remove");
for (MappedInput& i: sorted) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s",SDL_GetScancodeName((SDL_Scancode)i.scan));
ImGui::TableNextColumn();
if (i.val==102) {
snprintf(id,4095,"Envelope release##SNType_%d",i.scan);
if (ImGui::Button(id)) {
noteKeys[i.scan]=0;
}
} else if (i.val==101) {
snprintf(id,4095,"Note release##SNType_%d",i.scan);
if (ImGui::Button(id)) {
noteKeys[i.scan]=102;
}
} else if (i.val==100) {
snprintf(id,4095,"Note off##SNType_%d",i.scan);
if (ImGui::Button(id)) {
noteKeys[i.scan]=101;
}
} else {
snprintf(id,4095,"Note##SNType_%d",i.scan);
if (ImGui::Button(id)) {
noteKeys[i.scan]=100;
}
}
ImGui::TableNextColumn();
if (i.val<100) {
snprintf(id,4095,"##SNValue_%d",i.scan);
if (ImGui::InputInt(id,&i.val,1,1)) {
if (i.val<0) i.val=0;
if (i.val>96) i.val=96;
noteKeys[i.scan]=i.val;
}
}
ImGui::TableNextColumn();
snprintf(id,4095,ICON_FA_TIMES "##SNRemove_%d",i.scan);
if (ImGui::Button(id)) {
noteKeys.erase(i.scan);
}
}
ImGui::EndTable();
if (ImGui::BeginCombo("##SNAddNew","Add...")) {
for (int i=0; i<SDL_NUM_SCANCODES; i++) {
const char* sName=SDL_GetScancodeName((SDL_Scancode)i);
if (sName==NULL) continue;
if (sName[0]==0) continue;
snprintf(id,4095,"%s##SNNewKey_%d",sName,i);
if (ImGui::Selectable(id)) {
noteKeys[(SDL_Scancode)i]=0;
}
}
ImGui::EndCombo();
}
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Pattern")) {
KEYBIND_CONFIG_BEGIN("keysPattern");
@ -805,6 +903,7 @@ void FurnaceGUI::syncSettings() {
LOAD_KEYBIND(GUI_ACTION_WINDOW_PIANO,0);
LOAD_KEYBIND(GUI_ACTION_WINDOW_NOTES,0);
LOAD_KEYBIND(GUI_ACTION_WINDOW_CHANNELS,0);
LOAD_KEYBIND(GUI_ACTION_WINDOW_REGISTER_VIEW,0);
LOAD_KEYBIND(GUI_ACTION_COLLAPSE_WINDOW,0);
LOAD_KEYBIND(GUI_ACTION_CLOSE_WINDOW,FURKMOD_SHIFT|SDLK_ESCAPE);
@ -906,6 +1005,8 @@ void FurnaceGUI::syncSettings() {
LOAD_KEYBIND(GUI_ACTION_ORDERS_MOVE_DOWN,FURKMOD_SHIFT|SDLK_DOWN);
LOAD_KEYBIND(GUI_ACTION_ORDERS_REPLAY,0);
decodeKeyMap(noteKeys,e->getConfString("noteKeys",DEFAULT_NOTE_KEYS));
parseKeybinds();
}
@ -990,6 +1091,7 @@ void FurnaceGUI::commitSettings() {
PUT_UI_COLOR(GUI_COLOR_INSTR_POKEY);
PUT_UI_COLOR(GUI_COLOR_INSTR_BEEPER);
PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN);
PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY);
PUT_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN);
PUT_UI_COLOR(GUI_COLOR_CHANNEL_FM);
PUT_UI_COLOR(GUI_COLOR_CHANNEL_PULSE);
@ -1071,6 +1173,7 @@ void FurnaceGUI::commitSettings() {
SAVE_KEYBIND(GUI_ACTION_WINDOW_PIANO);
SAVE_KEYBIND(GUI_ACTION_WINDOW_NOTES);
SAVE_KEYBIND(GUI_ACTION_WINDOW_CHANNELS);
SAVE_KEYBIND(GUI_ACTION_WINDOW_REGISTER_VIEW);
SAVE_KEYBIND(GUI_ACTION_COLLAPSE_WINDOW);
SAVE_KEYBIND(GUI_ACTION_CLOSE_WINDOW);
@ -1172,6 +1275,8 @@ void FurnaceGUI::commitSettings() {
SAVE_KEYBIND(GUI_ACTION_ORDERS_MOVE_DOWN);
SAVE_KEYBIND(GUI_ACTION_ORDERS_REPLAY);
e->setConf("noteKeys",encodeKeyMap(noteKeys));
e->saveConf();
if (!e->switchMaster()) {