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

This commit is contained in:
cam900 2023-05-02 13:46:56 +09:00
commit 523e08ed5c
35 changed files with 677 additions and 193 deletions

View File

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

View File

@ -793,7 +793,7 @@ endif()
if(ANDROID AND NOT TERMUX) if(ANDROID AND NOT TERMUX)
add_library(furnace SHARED ${USED_SOURCES}) add_library(furnace SHARED ${USED_SOURCES})
elseif(WIN32) elseif(WIN32)
add_executable(furnace ${USED_SOURCES}) add_executable(furnace WIN32 ${USED_SOURCES})
else() else()
add_executable(furnace ${USED_SOURCES}) add_executable(furnace ${USED_SOURCES})
endif() endif()

BIN
demos/ay8930/joyful_.fur Normal file

Binary file not shown.

BIN
demos/c64/yeah!.fur Normal file

Binary file not shown.

Binary file not shown.

View File

@ -3112,8 +3112,9 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
ItemSize(bb, style.FramePadding.y); ItemSize(bb, style.FramePadding.y);
if (!ItemAdd(frame_bb, id, NULL, ImGuiItemFlags_NoInertialScroll)) if (!ItemAdd(frame_bb, id, NULL, (temp_input_allowed ? ImGuiItemFlags_Inputable : 0) | ImGuiItemFlags_NoInertialScroll))
return false; return false;
// Default format string when passing NULL // Default format string when passing NULL
@ -3122,13 +3123,29 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.) else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
format = PatchFormatStringFloatToInt(format); format = PatchFormatStringFloatToInt(format);
// Tabbing or CTRL-clicking on Slider turns it into an input box
const bool hovered = ItemHoverable(frame_bb, id); const bool hovered = ItemHoverable(frame_bb, id);
if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavActivateInputId == id) bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
if (!temp_input_is_active)
{ {
SetActiveID(id, window); const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
SetFocusID(id, window); const bool clicked = (hovered && g.IO.MouseClicked[0]);
FocusWindow(window); if (input_requested_by_tabbing || clicked || g.NavActivateId == id || g.NavActivateInputId == id)
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); {
SetActiveID(id, window);
SetFocusID(id, window);
FocusWindow(window);
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
if (temp_input_allowed && (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || g.NavActivateInputId == id))
temp_input_is_active = true;
}
}
if (temp_input_is_active)
{
// Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0;
return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
} }
// Draw frame // Draw frame
@ -3154,6 +3171,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
if (label_size.x > 0.0f) if (label_size.x > 0.0f)
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
return value_changed; return value_changed;
} }

View File

@ -121,8 +121,8 @@ ID | macro
33 | KSR 33 | KSR
---|----------------------------- ---|-----------------------------
40 | operator 2 macros 40 | operator 2 macros
60 | operator 2 macros 60 | operator 3 macros
80 | operator 2 macros 80 | operator 4 macros
the interpretation of duty, wave and extra macros depends on chip/instrument type: the interpretation of duty, wave and extra macros depends on chip/instrument type:

View File

@ -1,8 +1,14 @@
# wavetable editor # wavetable editor
Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. Wavetable synthesizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.6pre4, wavetable editor affects PC Engine, WonderSwan, Namco WSGs, Virtual Boy, Game.com, SCC, FDS, Seta X1-010, Konami Bubble System WSG, SNES, Amiga and channel 3 of Game Boy.
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips. Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan, Namco WSG, N163, Game.com, Virtual Boy and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System, SNES, Namco WSG and N163, 32-level height for PCE and 64-level height for Virtual Boy. If a larger wave is defined for these chips, it will be squashed to fit within the constraints of the chips.
Furnace's wavetable editor features multiple ways of creating desired waveform shape:
- Shape tab allows you to select a few predefined basic shapes and indirectly edit it via "Duty", "Exponent" and "XOR Point" sliders TODO: what the last two are doing? What is amplitude/phase for?)
- FM is for creating the waveform with frequency modulation synthesis principles: One can set carrier/modulation levels, frquency multiplier, connection between operators and FM waveforms of these operators.
- WaveTools allows user to fine-tune the waveform: scale said waveform in both X and Y axes, smoothen, amplify, normalize, convert to signed/unisgned, invert or even randomize the wavetable.
## wavetable synthesizer ## wavetable synthesizer

View File

@ -8,7 +8,7 @@ because the chip lacks sample interpolation, it is recommended that you try to p
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 chip" option in the file menu or the chip manager). 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 chip" option in the file menu or the chip manager).
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. there are also 3 ADPCM channels. ADPCM samples are fixed to 8012 Hz.
# effects # effects

View File

@ -178,7 +178,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc
logD("config does not exist"); logD("config does not exist");
if (createOnFail) { if (createOnFail) {
logI("creating default config."); logI("creating default config.");
reportError(fmt::sprintf("Creating default config: %s",strerror(errno))); //reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
return save(path,redundancy); return save(path,redundancy);
} else { } else {
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno))); reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));
@ -191,7 +191,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc
logD("config does not exist"); logD("config does not exist");
if (createOnFail) { if (createOnFail) {
logI("creating default config."); logI("creating default config.");
reportError(fmt::sprintf("Creating default config: %s",strerror(errno))); //reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
return save(path); return save(path);
} else { } else {
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno))); reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));

View File

@ -55,6 +55,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
return "03xx: Portamento"; return "03xx: Portamento";
case 0x04: case 0x04:
return "04xy: Vibrato (x: speed; y: depth)"; return "04xy: Vibrato (x: speed; y: depth)";
case 0x05:
return "05xy: Volume slide + vibrato (compatibility only!)";
case 0x06:
return "06xy: Volume slide + portamento (compatibility only!)";
case 0x07: case 0x07:
return "07xy: Tremolo (x: speed; y: depth)"; return "07xy: Tremolo (x: speed; y: depth)";
case 0x08: case 0x08:
@ -2419,6 +2423,13 @@ void DivEngine::stepOne(int row) {
void DivEngine::stop() { void DivEngine::stop() {
BUSY_BEGIN; BUSY_BEGIN;
freelance=false; freelance=false;
if (!playing) {
//Send midi panic
if (output) if (output->midiOut!=NULL) {
output->midiOut->send(TAMidiMessage(TA_MIDI_CONTROL,0x7B,0));
logV("Midi panic sent");
}
}
playing=false; playing=false;
extValuePresent=false; extValuePresent=false;
endOfSong=false; // what? endOfSong=false; // what?

View File

@ -105,7 +105,7 @@ struct DivChannelState {
int delayOrder, delayRow, retrigSpeed, retrigTick; int delayOrder, delayRow, retrigSpeed, retrigTick;
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine; int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine;
int tremoloDepth, tremoloRate, tremoloPos; int tremoloDepth, tremoloRate, tremoloPos;
unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR; unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta;
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff;
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp; bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp;
bool wentThroughNote, goneThroughNote; bool wentThroughNote, goneThroughNote;
@ -146,6 +146,8 @@ struct DivChannelState {
panR(255), panR(255),
panRL(0), panRL(0),
panRR(0), panRR(0),
lastVibrato(0),
lastPorta(0),
doNote(false), doNote(false),
legato(false), legato(false),
portaStop(false), portaStop(false),

View File

@ -23,11 +23,7 @@
static DivPattern emptyPat; static DivPattern emptyPat;
DivPattern::DivPattern() { DivPattern::DivPattern() {
memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); clear();
for (int i=0; i<DIV_MAX_ROWS; i++) {
data[i][0]=0;
data[i][1]=0;
}
} }
DivPattern* DivChannelData::getPattern(int index, bool create) { DivPattern* DivChannelData::getPattern(int index, bool create) {
@ -93,6 +89,14 @@ void DivPattern::copyOn(DivPattern* dest) {
memcpy(dest->data,data,sizeof(data)); memcpy(dest->data,data,sizeof(data));
} }
void DivPattern::clear() {
memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short));
for (int i=0; i<DIV_MAX_ROWS; i++) {
data[i][0]=0;
data[i][1]=0;
}
}
DivChannelData::DivChannelData(): DivChannelData::DivChannelData():
effectCols(1) { effectCols(1) {
memset(data,0,DIV_MAX_PATTERNS*sizeof(void*)); memset(data,0,DIV_MAX_PATTERNS*sizeof(void*));

View File

@ -24,6 +24,11 @@ struct DivPattern {
String name; String name;
short data[DIV_MAX_ROWS][DIV_MAX_COLS]; short data[DIV_MAX_ROWS][DIV_MAX_COLS];
/**
* clear the pattern.
*/
void clear();
/** /**
* copy this pattern to another. * copy this pattern to another.
* @param dest the destination pattern. * @param dest the destination pattern.

View File

@ -687,6 +687,7 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) {
void DivPlatformAY8910::forceIns() { void DivPlatformAY8910::forceIns() {
for (int i=0; i<3; i++) { for (int i=0; i<3; i++) {
chan[i].insChanged=true; chan[i].insChanged=true;
chan[i].freqChanged=true;
} }
immWrite(0x0b,ayEnvPeriod); immWrite(0x0b,ayEnvPeriod);
immWrite(0x0c,ayEnvPeriod>>8); immWrite(0x0c,ayEnvPeriod>>8);

View File

@ -1558,7 +1558,7 @@ DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) {
} }
DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) {
if (oplType==759) { if (oplType==759 || chipType==8950) {
if (ch>=totalChans+1) return NULL; if (ch>=totalChans+1) return NULL;
} else { } else {
if (ch>=totalChans) return NULL; if (ch>=totalChans) return NULL;

View File

@ -29,6 +29,7 @@ const char* regCheatSheetPV1000[]={
"CH1_Pitch", "00", "CH1_Pitch", "00",
"CH2_Pitch", "01", "CH2_Pitch", "01",
"CH3_Pitch", "02", "CH3_Pitch", "02",
"Control", "03",
NULL NULL
}; };
@ -38,11 +39,10 @@ const char** DivPlatformPV1000::getRegisterSheet() {
void DivPlatformPV1000::acquire(short** buf, size_t len) { void DivPlatformPV1000::acquire(short** buf, size_t len) {
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
short samp; short samp=d65010g031_sound_tick(&d65010g031,1);
samp=d65010g031_sound_tick(&d65010g031,1);
buf[0][h]=samp; buf[0][h]=samp;
for (int i=0; i<3; i++) { for (int i=0; i<3; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.square[i].out<<12); oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.out[i]);
} }
} }
} }
@ -73,17 +73,17 @@ void DivPlatformPV1000::tick(bool sysTick) {
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=0x3f-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); chan[i].freq=0x3f-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq<1) chan[i].freq=1; if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>62) chan[i].freq=62; if (chan[i].freq>62) chan[i].freq=62;
if (isMuted[i]) chan[i].keyOn=false; if (isMuted[i]) chan[i].keyOn=false;
if (chan[i].keyOn) { if (chan[i].keyOn) {
rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0 : chan[i].freq); rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq);
chan[i].keyOn=false; chan[i].keyOn=false;
} else if (chan[i].freqChanged && chan[i].active && !isMuted[i]) { } else if (chan[i].freqChanged && chan[i].active && !isMuted[i]) {
rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0 : chan[i].freq); rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq);
} }
if (chan[i].keyOff) { if (chan[i].keyOff) {
rWrite(i,0); rWrite(i,0x3f);
chan[i].keyOff=false; chan[i].keyOff=false;
} }
chan[i].freqChanged=false; chan[i].freqChanged=false;
@ -137,6 +137,13 @@ int DivPlatformPV1000::dispatch(DivCommand c) {
chan[c.chan].pitch=c.value; chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
break; break;
case DIV_CMD_STD_NOISE_MODE: // ring modulation
if (c.value&1) {
rWrite(3,3);
} else {
rWrite(3,2);
}
break;
case DIV_CMD_NOTE_PORTA: { case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2); int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false; bool return2=false;
@ -224,16 +231,21 @@ unsigned char* DivPlatformPV1000::getRegisterPool() {
} }
int DivPlatformPV1000::getRegisterPoolSize() { int DivPlatformPV1000::getRegisterPoolSize() {
return 3; return 4;
} }
void DivPlatformPV1000::reset() { void DivPlatformPV1000::reset() {
memset(regPool,0,3); memset(regPool,0,4);
for (int i=0; i<3; i++) { for (int i=0; i<3; i++) {
chan[i]=Channel(); chan[i]=Channel();
chan[i].std.setEngine(parent); chan[i].std.setEngine(parent);
} }
d65010g031_reset(&d65010g031); d65010g031_reset(&d65010g031);
// mute
rWrite(0,0x3f);
rWrite(1,0x3f);
rWrite(2,0x3f);
rWrite(3,2);
} }
int DivPlatformPV1000::getOutputCount() { int DivPlatformPV1000::getOutputCount() {

View File

@ -33,7 +33,7 @@ class DivPlatformPV1000: public DivDispatch {
DivDispatchOscBuffer* oscBuf[3]; DivDispatchOscBuffer* oscBuf[3];
bool isMuted[3]; bool isMuted[3];
unsigned char regPool[3]; unsigned char regPool[4];
d65010g031_t d65010g031; d65010g031_t d65010g031;
friend void putDispatchChip(void*,int); friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int); friend void putDispatchChan(void*,int,int);

View File

@ -151,8 +151,8 @@ void DivPlatformRF5C68::tick(bool sysTick) {
if (s->isLoopable()) { if (s->isLoopable()) {
loop=start+s->loopStart; loop=start+s->loopStart;
} }
start=MIN(start,getSampleMemCapacity()-31); start=MIN(start,getSampleMemCapacity()-32);
loop=MIN(loop,getSampleMemCapacity()-31); loop=MIN(loop,getSampleMemCapacity()-32);
rWrite(8,keyoff); // force keyoff first rWrite(8,keyoff); // force keyoff first
chWrite(i,6,start>>8); chWrite(i,6,start>>8);
chWrite(i,4,loop&0xff); chWrite(i,4,loop&0xff);
@ -425,7 +425,7 @@ void DivPlatformRF5C68::renderSamples(int sysID) {
} }
int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT); int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length); int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-32,length);
if (actualLength>0) { if (actualLength>0) {
sampleOffRFC[i]=memPos; sampleOffRFC[i]=memPos;
for (int j=0; j<actualLength; j++) { for (int j=0; j<actualLength; j++) {
@ -435,8 +435,8 @@ void DivPlatformRF5C68::renderSamples(int sysID) {
sampleMem[memPos++]=(val>0)?(val|0x80):(0-val); sampleMem[memPos++]=(val>0)?(val|0x80):(0-val);
} }
// write end of sample marker // write end of sample marker
memset(&sampleMem[memPos],0xff,31); memset(&sampleMem[memPos],0xff,32);
memPos+=31; memPos+=32;
} }
if (actualLength<length) { if (actualLength<length) {
logW("out of RF5C68 PCM memory for sample %d!",i); logW("out of RF5C68 PCM memory for sample %d!",i);

View File

@ -67,7 +67,7 @@ int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle)
{ {
if (square->period > 0) if (square->period > 0)
{ {
int period = d65010g031_max(1, (0x3f - square->period)); const int period = square->period;
square->counter += cycle; square->counter += cycle;
while (square->counter >= period) while (square->counter >= period)
{ {
@ -82,9 +82,9 @@ int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle)
// this is the bit I altered // this is the bit I altered
// THIS IS **NOT** THE ORIGINAL SOFTWARE! I am plainly marking it as such! // THIS IS **NOT** THE ORIGINAL SOFTWARE! I am plainly marking it as such!
const int d65Volumes[3]={ const int d65Volumes[3]={
3840, 3840, // -6dB
5120, 5120, // -3dB
8192 8192 // 0dB
}; };
int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle) int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle)
@ -92,7 +92,29 @@ int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle)
int out = 0; int out = 0;
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
{ {
out += d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i]; d65010g031->out[i] = 0;
}
if (d65010g031->ctrl & 2)
{
if (d65010g031->ctrl & 1) // ring modulation
{
int sout[3] = {
d65010g031_square_tick(&d65010g031->square[0], cycle),
d65010g031_square_tick(&d65010g031->square[1], cycle),
d65010g031_square_tick(&d65010g031->square[2], cycle),
};
d65010g031->out[0] = (sout[0] ^ sout[1]) ? d65Volumes[0] : -d65Volumes[0];
d65010g031->out[1] = (sout[1] ^ sout[2]) ? d65Volumes[1] : -d65Volumes[1];
d65010g031->out[2] = (sout[2] ? d65Volumes[2] : -d65Volumes[2]);
}
else
{
for (int i = 0; i < 3; i++)
{
d65010g031->out[i] = d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i];
}
}
out = d65010g031->out[0] + d65010g031->out[1] + d65010g031->out[2];
} }
return out; return out;
} }
@ -105,12 +127,25 @@ void d65010g031_reset(struct d65010g031_t *d65010g031)
d65010g031->square[i].counter = 0; d65010g031->square[i].counter = 0;
d65010g031->square[i].out = 0; d65010g031->square[i].out = 0;
} }
d65010g031->ctrl = 0;
} }
void d65010g031_write(struct d65010g031_t *d65010g031, const unsigned char a, const unsigned char d) void d65010g031_write(struct d65010g031_t *d65010g031, const unsigned char a, const unsigned char d)
{ {
if (a < 3) switch (a)
{ {
d65010g031->square[a].period = d & 0x3f; case 3:
d65010g031->ctrl = d;
break;
default:
{
const unsigned char per = (unsigned char)(~d) & 0x3f;
if ((per == 0) && (d65010g031->square[a].period != 0))
{
d65010g031->square[a].out ^= 1;
}
d65010g031->square[a].period = per;
break;
}
} }
} }

View File

@ -54,6 +54,8 @@ struct d65010g031_square_t
struct d65010g031_t struct d65010g031_t
{ {
struct d65010g031_square_t square[3]; struct d65010g031_square_t square[3];
signed short out[3];
unsigned char ctrl;
}; };
int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle); int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle);

View File

@ -222,7 +222,8 @@ void DivPlatformX1_010::acquire(short** buf, size_t len) {
if (stereo) buf[1][h]=tempR; if (stereo) buf[1][h]=tempR;
for (int i=0; i<16; i++) { for (int i=0; i<16; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))>>1; int vo=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))<<3;
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(vo,-32768,32767);
} }
} }
} }

View File

@ -680,6 +680,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
chan[i].inPorta=false; chan[i].inPorta=false;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else { } else {
chan[i].lastPorta=effectVal;
calledPorta=true; calledPorta=true;
if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) { if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) {
chan[i].portaNote=chan[i].note; chan[i].portaNote=chan[i].note;
@ -700,11 +701,78 @@ void DivEngine::processRow(int i, bool afterDelay) {
} }
break; break;
case 0x04: // vibrato case 0x04: // vibrato
if (effectVal) chan[i].lastVibrato=effectVal;
chan[i].vibratoDepth=effectVal&15; chan[i].vibratoDepth=effectVal&15;
chan[i].vibratoRate=effectVal>>4; chan[i].vibratoRate=effectVal>>4;
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate)); dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate));
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
break; break;
case 0x05: // vol slide + vibrato
if (effectVal==0) {
chan[i].vibratoDepth=0;
chan[i].vibratoRate=0;
} else {
chan[i].vibratoDepth=chan[i].lastVibrato&15;
chan[i].vibratoRate=chan[i].lastVibrato>>4;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_VIBRATO,i,chan[i].vibratoDepth,chan[i].vibratoRate));
dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15)));
// TODO: non-0x-or-x0 value should be treated as 00
if (effectVal!=0) {
if ((effectVal&15)!=0) {
chan[i].volSpeed=-(effectVal&15)*64;
} else {
chan[i].volSpeed=(effectVal>>4)*64;
}
// tremolo and vol slides are incompatible
chan[i].tremoloDepth=0;
chan[i].tremoloRate=0;
} else {
chan[i].volSpeed=0;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
break;
case 0x06: // vol slide + porta
if (effectVal==0 || chan[i].lastPorta==0) {
chan[i].portaNote=-1;
chan[i].portaSpeed=-1;
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].inPorta=false;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else {
calledPorta=true;
if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) {
chan[i].portaNote=chan[i].note;
chan[i].portaSpeed=-1;
} else {
chan[i].portaNote=chan[i].note;
chan[i].portaSpeed=chan[i].lastPorta;
chan[i].inPorta=true;
chan[i].wasShorthandPorta=false;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_PORTA,i,CLAMP(chan[i].portaNote,-128,127),MAX(chan[i].portaSpeed,0)));
chan[i].portaStop=true;
if (chan[i].keyOn) chan[i].doNote=false;
chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?!
chan[i].scheduledSlideReset=false;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,1));
lastSlide=0x1337; // i hate this so much
}
// TODO: non-0x-or-x0 value should be treated as 00
if (effectVal!=0) {
if ((effectVal&15)!=0) {
chan[i].volSpeed=-(effectVal&15)*64;
} else {
chan[i].volSpeed=(effectVal>>4)*64;
}
// tremolo and vol slides are incompatible
chan[i].tremoloDepth=0;
chan[i].tremoloRate=0;
} else {
chan[i].volSpeed=0;
}
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
break;
case 0x07: // tremolo case 0x07: // tremolo
// TODO // TODO
// this effect is really weird. i thought it would alter the tremolo depth but turns out it's completely different // this effect is really weird. i thought it would alter the tremolo depth but turns out it's completely different
@ -1125,25 +1193,47 @@ void DivEngine::nextRow() {
bool wantPreNote=false; bool wantPreNote=false;
if (disCont[dispatchOfChan[i]].dispatch!=NULL) { if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
wantPreNote=disCont[dispatchOfChan[i]].dispatch->getWantPreNote(); wantPreNote=disCont[dispatchOfChan[i]].dispatch->getWantPreNote();
if (wantPreNote) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); if (wantPreNote) {
int addition=0;
for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==0xed) {
if (pat->data[curRow][5+(j<<1)]>0) {
addition=pat->data[curRow][5+(j<<1)];
break;
}
}
}
dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks+addition));
}
} }
if (song.oneTickCut) { if (song.oneTickCut) {
bool doPrepareCut=true; bool doPrepareCut=true;
int addition=0;
for (int j=0; j<curPat[i].effectCols; j++) { for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==0x03) { if (pat->data[curRow][4+(j<<1)]==0x03) {
doPrepareCut=false; doPrepareCut=false;
break; break;
} }
if (pat->data[curRow][4+(j<<1)]==0x06) {
doPrepareCut=false;
break;
}
if (pat->data[curRow][4+(j<<1)]==0xea) { if (pat->data[curRow][4+(j<<1)]==0xea) {
if (pat->data[curRow][5+(j<<1)]>0) { if (pat->data[curRow][5+(j<<1)]>0) {
doPrepareCut=false; doPrepareCut=false;
break; break;
} }
} }
if (pat->data[curRow][4+(j<<1)]==0xed) {
if (pat->data[curRow][5+(j<<1)]>0) {
addition=pat->data[curRow][5+(j<<1)];
break;
}
}
} }
if (doPrepareCut && !wantPreNote && chan[i].cut<=0) chan[i].cut=ticks; if (doPrepareCut && !wantPreNote && chan[i].cut<=0) chan[i].cut=ticks+addition;
} }
} }
} }

View File

@ -892,10 +892,10 @@ bool DivSample::resampleBlep(double r) {
} }
} }
for (int i=0; i<finalCount; i++) { for (int i=0; i<finalCount; i++) {
float result=floatData[i]+data16[i]; float result=floatData[i]+data8[i];
if (result<-128) result=-128; if (result<-128) result=-128;
if (result>127) result=127; if (result>127) result=127;
data16[i]=round(result); data8[i]=round(result);
} }
} }
delete[] floatData; delete[] floatData;

View File

@ -1839,7 +1839,11 @@ void DivEngine::registerSystems() {
{"Square 1", "Square 2", "Square 3"}, {"Square 1", "Square 2", "Square 3"},
{"S1", "S2", "S3"}, {"S1", "S2", "S3"},
{DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{DIV_INS_PV1000, DIV_INS_PV1000, DIV_INS_PV1000} {DIV_INS_PV1000, DIV_INS_PV1000, DIV_INS_PV1000},
{},
{
{0x10, {DIV_CMD_STD_NOISE_MODE, "10xx: Set ring modulation (0: disable, 1: enable)"}}
}
); );
sysDefs[DIV_SYSTEM_SFX_BEEPER_QUADTONE]=new DivSysDef( sysDefs[DIV_SYSTEM_SFX_BEEPER_QUADTONE]=new DivSysDef(

View File

@ -1083,6 +1083,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
bool trailing=false; bool trailing=false;
bool beenOneLoopAlready=false; bool beenOneLoopAlready=false;
bool mayWriteRate=(fmod(curSubSong->hz,1.0)<0.00001 || fmod(curSubSong->hz,1.0)>0.99999);
int countDown=MAX(0,trailingTicks)+1; int countDown=MAX(0,trailingTicks)+1;
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
@ -2399,6 +2400,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
w->writeI(0); w->writeI(0);
w->writeI(0); w->writeI(0);
} }
if (mayWriteRate) {
w->writeI(round(curSubSong->hz));
}
w->seek(0x34,SEEK_SET); w->seek(0x34,SEEK_SET);
w->writeI(songOff-0x34); w->writeI(songOff-0x34);
if (version>=0x170) { if (version>=0x170) {

View File

@ -561,18 +561,32 @@ void FurnaceGUI::doAction(int what) {
doFlip(); doFlip();
break; break;
case GUI_ACTION_PAT_COLLAPSE_ROWS: case GUI_ACTION_PAT_COLLAPSE_ROWS:
doCollapse(2); doCollapse(2,selStart,selEnd);
break; break;
case GUI_ACTION_PAT_EXPAND_ROWS: case GUI_ACTION_PAT_EXPAND_ROWS:
doExpand(2); doExpand(2,selStart,selEnd);
break; break;
case GUI_ACTION_PAT_COLLAPSE_PAT: // TODO case GUI_ACTION_PAT_COLLAPSE_PAT: {
SelectionPoint selEndPat;
selEndPat.xCoarse=e->getTotalChannelCount()-1;
selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2;
selEndPat.y=e->curSubSong->patLen-1;
doCollapse(2,SelectionPoint(0,0,0),selEndPat);
break; break;
case GUI_ACTION_PAT_EXPAND_PAT: // TODO }
case GUI_ACTION_PAT_EXPAND_PAT: {
SelectionPoint selEndPat;
selEndPat.xCoarse=e->getTotalChannelCount()-1;
selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2;
selEndPat.y=e->curSubSong->patLen-1;
doExpand(2,SelectionPoint(0,0,0),selEndPat);
break; break;
case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO }
case GUI_ACTION_PAT_COLLAPSE_SONG:
doCollapseSong(2);
break; break;
case GUI_ACTION_PAT_EXPAND_SONG: // TODO case GUI_ACTION_PAT_EXPAND_SONG:
doExpandSong(2);
break; break;
case GUI_ACTION_PAT_LATCH: // TODO case GUI_ACTION_PAT_LATCH: // TODO
break; break;

View File

@ -67,6 +67,9 @@ void FurnaceGUI::prepareUndo(ActionType action) {
e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]); e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]);
} }
break; break;
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO
break;
case GUI_UNDO_REPLACE: // this is handled by doReplace() case GUI_UNDO_REPLACE: // this is handled by doReplace()
break; break;
} }
@ -130,6 +133,9 @@ void FurnaceGUI::makeUndo(ActionType action) {
doPush=true; doPush=true;
} }
break; break;
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO
break;
case GUI_UNDO_REPLACE: // this is handled by doReplace() case GUI_UNDO_REPLACE: // this is handled by doReplace()
break; break;
} }
@ -839,50 +845,56 @@ void FurnaceGUI::doFlip() {
makeUndo(GUI_UNDO_PATTERN_FLIP); makeUndo(GUI_UNDO_PATTERN_FLIP);
} }
void FurnaceGUI::doCollapse(int divider) { void FurnaceGUI::doCollapse(int divider, const SelectionPoint& sStart, const SelectionPoint& sEnd) {
if (divider<2) return;
if (e->curSubSong->patLen<divider) {
showError("can't collapse any further!");
return;
}
finishSelection(); finishSelection();
prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); prepareUndo(GUI_UNDO_PATTERN_COLLAPSE);
DivPattern patBuffer; DivPattern patBuffer;
int iCoarse=selStart.xCoarse; int iCoarse=sStart.xCoarse;
int iFine=selStart.xFine; int iFine=sStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=sEnd.xCoarse; iCoarse++) {
if (!e->curSubSong->chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
maskOut(opMaskCollapseExpand,iFine); maskOut(opMaskCollapseExpand,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) { for (int j=sStart.y; j<=sEnd.y; j++) {
if (iFine==0) { if (iFine==0) {
patBuffer.data[j][0]=pat->data[j][0]; patBuffer.data[j][0]=pat->data[j][0];
} }
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
} }
for (int j=0; j<=selEnd.y-selStart.y; j++) { for (int j=0; j<=sEnd.y-sStart.y; j++) {
if (j*divider>=selEnd.y-selStart.y) { if (j*divider>=sEnd.y-sStart.y) {
if (iFine==0) { if (iFine==0) {
pat->data[j+selStart.y][0]=0; pat->data[j+sStart.y][0]=0;
pat->data[j+selStart.y][1]=0; pat->data[j+sStart.y][1]=0;
} else { } else {
pat->data[j+selStart.y][iFine+1]=-1; pat->data[j+sStart.y][iFine+1]=-1;
} }
} else { } else {
if (iFine==0) { if (iFine==0) {
pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0]; pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y][0];
} }
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1]; pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y][iFine+1];
if (iFine==0) { if (iFine==0) {
for (int k=1; k<divider; k++) { for (int k=1; k<divider; k++) {
if ((j*divider+k)>=selEnd.y-selStart.y) break; if ((j*divider+k)>=sEnd.y-sStart.y) break;
if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; if (!(pat->data[j+sStart.y][0]==0 && pat->data[j+sStart.y][1]==0)) break;
pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y+k][0];
pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; pat->data[j+sStart.y][1]=patBuffer.data[j*divider+sStart.y+k][1];
} }
} else { } else {
for (int k=1; k<divider; k++) { for (int k=1; k<divider; k++) {
if ((j*divider+k)>=selEnd.y-selStart.y) break; if ((j*divider+k)>=sEnd.y-sStart.y) break;
if (pat->data[j+selStart.y][iFine+1]!=-1) break; if (pat->data[j+sStart.y][iFine+1]!=-1) break;
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y+k][iFine+1];
} }
} }
} }
@ -894,41 +906,41 @@ void FurnaceGUI::doCollapse(int divider) {
makeUndo(GUI_UNDO_PATTERN_COLLAPSE); makeUndo(GUI_UNDO_PATTERN_COLLAPSE);
} }
void FurnaceGUI::doExpand(int multiplier) { void FurnaceGUI::doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd) {
if (multiplier<1) return; if (multiplier<2) return;
finishSelection(); finishSelection();
prepareUndo(GUI_UNDO_PATTERN_EXPAND); prepareUndo(GUI_UNDO_PATTERN_EXPAND);
DivPattern patBuffer; DivPattern patBuffer;
int iCoarse=selStart.xCoarse; int iCoarse=sStart.xCoarse;
int iFine=selStart.xFine; int iFine=sStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=sEnd.xCoarse; iCoarse++) {
if (!e->curSubSong->chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
maskOut(opMaskCollapseExpand,iFine); maskOut(opMaskCollapseExpand,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) { for (int j=sStart.y; j<=sEnd.y; j++) {
if (iFine==0) { if (iFine==0) {
patBuffer.data[j][0]=pat->data[j][0]; patBuffer.data[j][0]=pat->data[j][0];
} }
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
} }
for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { for (int j=0; j<=(sEnd.y-sStart.y)*multiplier; j++) {
if ((j+selStart.y)>=e->curSubSong->patLen) break; if ((j+sStart.y)>=e->curSubSong->patLen) break;
if ((j%multiplier)!=0) { if ((j%multiplier)!=0) {
if (iFine==0) { if (iFine==0) {
pat->data[j+selStart.y][0]=0; pat->data[j+sStart.y][0]=0;
pat->data[j+selStart.y][1]=0; pat->data[j+sStart.y][1]=0;
} else { } else {
pat->data[j+selStart.y][iFine+1]=-1; pat->data[j+sStart.y][iFine+1]=-1;
} }
continue; continue;
} }
if (iFine==0) { if (iFine==0) {
pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0]; pat->data[j+sStart.y][0]=patBuffer.data[j/multiplier+sStart.y][0];
} }
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1]; pat->data[j+sStart.y][iFine+1]=patBuffer.data[j/multiplier+sStart.y][iFine+1];
} }
} }
iFine=0; iFine=0;
@ -937,6 +949,162 @@ void FurnaceGUI::doExpand(int multiplier) {
makeUndo(GUI_UNDO_PATTERN_EXPAND); makeUndo(GUI_UNDO_PATTERN_EXPAND);
} }
void FurnaceGUI::doCollapseSong(int divider) {
if (divider<2) return;
finishSelection();
UndoStep us;
us.type=GUI_UNDO_PATTERN_COLLAPSE_SONG;
DivPattern patCopy;
size_t subSong=e->getCurrentSubSong();
for (int i=0; i<e->getTotalChannelCount(); i++) {
for (int j=0; j<DIV_MAX_PATTERNS; j++) {
if (e->curPat[i].data[j]==NULL) continue;
DivPattern* pat=e->curPat[i].getPattern(j,true);
pat->copyOn(&patCopy);
pat->clear();
for (int k=0; k<DIV_MAX_ROWS; k++) {
for (int l=0; l<DIV_MAX_COLS; l++) {
if (l==0) {
if (!(pat->data[k/divider][0]==0 && pat->data[k/divider][1]==0)) continue;
} else {
if (pat->data[k/divider][l+1]!=-1) continue;
}
if (l==0) {
pat->data[k/divider][l]=patCopy.data[k][l];
}
pat->data[k/divider][l+1]=patCopy.data[k][l+1];
if (l>3 && !(l&1)) { // scale effects as needed
switch (pat->data[k/divider][l]) {
case 0x0d:
pat->data[k/divider][l+1]/=divider;
break;
case 0x0f:
pat->data[k/divider][l+1]=CLAMP(pat->data[k/divider][l+1]*divider,1,255);
break;
}
}
}
}
// put undo
for (int k=0; k<DIV_MAX_ROWS; k++) {
for (int l=0; l<DIV_MAX_COLS; l++) {
if (pat->data[k][l]!=patCopy.data[k][l]) {
us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l]));
}
}
}
}
}
// magic
unsigned char* subSongInfoCopy=new unsigned char[1024];
memcpy(subSongInfoCopy,e->curSubSong,1024);
e->curSubSong->patLen/=divider;
for (int i=0; i<e->curSubSong->speeds.len; i++) {
e->curSubSong->speeds.val[i]=CLAMP(e->curSubSong->speeds.val[i]*divider,1,255);
}
unsigned char* newSubSongInfo=(unsigned char*)e->curSubSong;
for (int i=0; i<1024; i++) {
if (subSongInfoCopy[i]!=newSubSongInfo[i]) {
us.other.push_back(UndoOtherData(GUI_UNDO_TARGET_SUBSONG,subSong,i,subSongInfoCopy[i],newSubSongInfo[i]));
}
}
if (!us.pat.empty()) {
undoHist.push_back(us);
redoHist.clear();
if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front();
}
if (e->isPlaying()) e->play();
}
void FurnaceGUI::doExpandSong(int multiplier) {
if (multiplier<2) return;
if (e->curSubSong->patLen>(256/multiplier)) {
showError("can't expand any further!");
return;
}
finishSelection();
UndoStep us;
us.type=GUI_UNDO_PATTERN_EXPAND_SONG;
DivPattern patCopy;
size_t subSong=e->getCurrentSubSong();
for (int i=0; i<e->getTotalChannelCount(); i++) {
for (int j=0; j<DIV_MAX_PATTERNS; j++) {
if (e->curPat[i].data[j]==NULL) continue;
DivPattern* pat=e->curPat[i].getPattern(j,true);
pat->copyOn(&patCopy);
pat->clear();
for (int k=0; k<(256/multiplier); k++) {
for (int l=0; l<DIV_MAX_COLS; l++) {
if (l==0) {
if (!(pat->data[k*multiplier][0]==0 && pat->data[k*multiplier][1]==0)) continue;
} else {
if (pat->data[k*multiplier][l+1]!=-1) continue;
}
if (l==0) {
pat->data[k*multiplier][l]=patCopy.data[k][l];
}
pat->data[k*multiplier][l+1]=patCopy.data[k][l+1];
if (l>3 && !(l&1)) { // scale effects as needed
switch (pat->data[k*multiplier][l]) {
case 0x0d:
pat->data[k*multiplier][l+1]/=multiplier;
break;
case 0x0f:
pat->data[k*multiplier][l+1]=CLAMP(pat->data[k*multiplier][l+1]/multiplier,1,255);
break;
}
}
}
}
// put undo
for (int k=0; k<DIV_MAX_ROWS; k++) {
for (int l=0; l<DIV_MAX_COLS; l++) {
if (pat->data[k][l]!=patCopy.data[k][l]) {
us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l]));
}
}
}
}
}
// magic
unsigned char* subSongInfoCopy=new unsigned char[1024];
memcpy(subSongInfoCopy,e->curSubSong,1024);
e->curSubSong->patLen*=multiplier;
for (int i=0; i<e->curSubSong->speeds.len; i++) {
e->curSubSong->speeds.val[i]=CLAMP(e->curSubSong->speeds.val[i]/multiplier,1,255);
}
unsigned char* newSubSongInfo=(unsigned char*)e->curSubSong;
for (int i=0; i<1024; i++) {
if (subSongInfoCopy[i]!=newSubSongInfo[i]) {
us.other.push_back(UndoOtherData(GUI_UNDO_TARGET_SUBSONG,subSong,i,subSongInfoCopy[i],newSubSongInfo[i]));
}
}
if (!us.pat.empty()) {
undoHist.push_back(us);
redoHist.clear();
if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front();
}
if (e->isPlaying()) e->play();
}
void FurnaceGUI::doDrag() { void FurnaceGUI::doDrag() {
int len=dragEnd.xCoarse-dragStart.xCoarse+1; int len=dragEnd.xCoarse-dragStart.xCoarse+1;
@ -985,6 +1153,8 @@ void FurnaceGUI::doUndo() {
case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_FLIP:
case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND: case GUI_UNDO_PATTERN_EXPAND:
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
case GUI_UNDO_PATTERN_EXPAND_SONG:
case GUI_UNDO_PATTERN_DRAG: case GUI_UNDO_PATTERN_DRAG:
case GUI_UNDO_REPLACE: case GUI_UNDO_REPLACE:
for (UndoPatternData& i: us.pat) { for (UndoPatternData& i: us.pat) {
@ -1005,6 +1175,22 @@ void FurnaceGUI::doUndo() {
break; break;
} }
bool shallReplay=false;
for (UndoOtherData& i: us.other) {
switch (i.target) {
case GUI_UNDO_TARGET_SONG:
((unsigned char*)(&e->song))[i.off]=i.oldVal;
shallReplay=true;
break;
case GUI_UNDO_TARGET_SUBSONG:
if (i.subtarget<0 || i.subtarget>=(int)e->song.subsong.size()) break;
((unsigned char*)(e->song.subsong[i.subtarget]))[i.off]=i.oldVal;
shallReplay=true;
break;
}
}
if (shallReplay && e->isPlaying()) play();
if (curOrder>=e->curSubSong->ordersLen) { if (curOrder>=e->curSubSong->ordersLen) {
curOrder=e->curSubSong->ordersLen-1; curOrder=e->curSubSong->ordersLen-1;
oldOrder=curOrder; oldOrder=curOrder;
@ -1045,6 +1231,8 @@ void FurnaceGUI::doRedo() {
case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND: case GUI_UNDO_PATTERN_EXPAND:
case GUI_UNDO_PATTERN_DRAG: case GUI_UNDO_PATTERN_DRAG:
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
case GUI_UNDO_PATTERN_EXPAND_SONG:
case GUI_UNDO_REPLACE: case GUI_UNDO_REPLACE:
for (UndoPatternData& i: us.pat) { for (UndoPatternData& i: us.pat) {
e->changeSongP(i.subSong); e->changeSongP(i.subSong);
@ -1065,6 +1253,22 @@ void FurnaceGUI::doRedo() {
break; break;
} }
bool shallReplay=false;
for (UndoOtherData& i: us.other) {
switch (i.target) {
case GUI_UNDO_TARGET_SONG:
((unsigned char*)(&e->song))[i.off]=i.newVal;
shallReplay=true;
break;
case GUI_UNDO_TARGET_SUBSONG:
if (i.subtarget<0 || i.subtarget>=(int)e->song.subsong.size()) break;
((unsigned char*)(e->song.subsong[i.subtarget]))[i.off]=i.newVal;
shallReplay=true;
break;
}
}
if (shallReplay && e->isPlaying()) play();
if (curOrder>=e->curSubSong->ordersLen) { if (curOrder>=e->curSubSong->ordersLen) {
curOrder=e->curSubSong->ordersLen-1; curOrder=e->curSubSong->ordersLen-1;
oldOrder=curOrder; oldOrder=curOrder;

View File

@ -2842,9 +2842,23 @@ void FurnaceGUI::editOptions(bool topMenu) {
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip();
if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2,selStart,selEnd);
if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2,selStart,selEnd);
if (topMenu) {
ImGui::Separator();
if (ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT))) doAction(GUI_ACTION_PAT_COLLAPSE_PAT);
if (ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT))) doAction(GUI_ACTION_PAT_EXPAND_PAT);
}
}
if (topMenu) {
ImGui::Separator();
if (ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG))) doAction(GUI_ACTION_PAT_COLLAPSE_SONG);
if (ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG))) doAction(GUI_ACTION_PAT_EXPAND_SONG);
}
if (!basicMode) {
if (topMenu) { if (topMenu) {
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("find/replace",BIND_FOR(GUI_ACTION_WINDOW_FIND),findOpen)) { if (ImGui::MenuItem("find/replace",BIND_FOR(GUI_ACTION_WINDOW_FIND),findOpen)) {
@ -2856,16 +2870,6 @@ void FurnaceGUI::editOptions(bool topMenu) {
} }
} }
} }
/*if (topMenu) {
ImGui::Separator();
ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT));
ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT));
ImGui::Separator();
ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG));
ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG));
}*/
} }
void FurnaceGUI::toggleMobileUI(bool enable, bool force) { void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
@ -3245,18 +3249,18 @@ void FurnaceGUI::pointMotion(int x, int y, int xrel, int yrel) {
// how many pixels should be visible at least at x/y dir // how many pixels should be visible at least at x/y dir
#define OOB_PIXELS_SAFETY 25 #define OOB_PIXELS_SAFETY 25
bool FurnaceGUI::detectOutOfBoundsWindow() { bool FurnaceGUI::detectOutOfBoundsWindow(SDL_Rect& failing) {
int count=SDL_GetNumVideoDisplays(); int count=SDL_GetNumVideoDisplays();
if (count<1) { if (count<1) {
logW("bounds check: error %s",SDL_GetError()); logW("bounds check: error: %s",SDL_GetError());
return false; return false;
} }
SDL_Rect rect; SDL_Rect rect;
for (int i=0; i<count; i++) { for (int i=0; i<count; i++) {
if (SDL_GetDisplayUsableBounds(i,&rect)!=0) { if (SDL_GetDisplayUsableBounds(i,&rect)!=0) {
logW("bounds check: error %s",SDL_GetError()); logW("bounds check: error in display %d: %s",i,SDL_GetError());
return false; continue;
} }
bool xbound=((rect.x+OOB_PIXELS_SAFETY)<=(scrX+scrW)) && ((rect.x+rect.w-OOB_PIXELS_SAFETY)>=scrX); bool xbound=((rect.x+OOB_PIXELS_SAFETY)<=(scrX+scrW)) && ((rect.x+rect.w-OOB_PIXELS_SAFETY)>=scrX);
@ -3268,6 +3272,7 @@ bool FurnaceGUI::detectOutOfBoundsWindow() {
} }
} }
failing=rect;
return false; return false;
} }
@ -3838,11 +3843,11 @@ bool FurnaceGUI::loop() {
} }
ImGui::Checkbox("loop",&vgmExportLoop); ImGui::Checkbox("loop",&vgmExportLoop);
if (vgmExportLoop && e->song.loopModality==2) { if (vgmExportLoop && e->song.loopModality==2) {
ImGui::Text("trailing ticks:"); ImGui::Text("loop trail:");
if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) {
vgmExportTrailingTicks=-1; vgmExportTrailingTicks=-1;
} }
if (ImGui::RadioButton("one loop",vgmExportTrailingTicks==-2)) { if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) {
vgmExportTrailingTicks=-2; vgmExportTrailingTicks=-2;
} }
if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) {
@ -6003,10 +6008,23 @@ bool FurnaceGUI::init() {
#ifndef IS_MOBILE #ifndef IS_MOBILE
// if window would spawn out of bounds, force it to be get default position // if window would spawn out of bounds, force it to be get default position
if (!detectOutOfBoundsWindow()) { SDL_Rect bounds;
if (!detectOutOfBoundsWindow(bounds)) {
scrMax=false; scrMax=false;
scrX=scrConfX=SDL_WINDOWPOS_CENTERED; scrX=scrConfX=SDL_WINDOWPOS_CENTERED;
scrY=scrConfY=SDL_WINDOWPOS_CENTERED; scrY=scrConfY=SDL_WINDOWPOS_CENTERED;
// make sure our window isn't big
/*if (bounds.w<scrW) {
logD("resizing width because it does not fit");
scrW=bounds.w-OOB_PIXELS_SAFETY*2;
if (scrW<200) scrW=200;
}
if (bounds.h<scrH) {
logD("resizing height because it does not fit");
scrH=bounds.h-OOB_PIXELS_SAFETY*2;
if (scrH<100) scrH=100;
}*/
} }
#endif #endif

View File

@ -722,6 +722,8 @@ enum NoteCtrl {
struct SelectionPoint { struct SelectionPoint {
int xCoarse, xFine; int xCoarse, xFine;
int y; int y;
SelectionPoint(int xc, int xf, int yp):
xCoarse(xc), xFine(xf), y(yp) {}
SelectionPoint(): SelectionPoint():
xCoarse(0), xFine(0), y(0) {} xCoarse(0), xFine(0), y(0) {}
}; };
@ -743,10 +745,17 @@ enum ActionType {
GUI_UNDO_PATTERN_FLIP, GUI_UNDO_PATTERN_FLIP,
GUI_UNDO_PATTERN_COLLAPSE, GUI_UNDO_PATTERN_COLLAPSE,
GUI_UNDO_PATTERN_EXPAND, GUI_UNDO_PATTERN_EXPAND,
GUI_UNDO_PATTERN_COLLAPSE_SONG,
GUI_UNDO_PATTERN_EXPAND_SONG,
GUI_UNDO_PATTERN_DRAG, GUI_UNDO_PATTERN_DRAG,
GUI_UNDO_REPLACE GUI_UNDO_REPLACE
}; };
enum UndoOtherTarget {
GUI_UNDO_TARGET_SONG,
GUI_UNDO_TARGET_SUBSONG
};
struct UndoPatternData { struct UndoPatternData {
int subSong, chan, pat, row, col; int subSong, chan, pat, row, col;
short oldVal, newVal; short oldVal, newVal;
@ -771,6 +780,19 @@ struct UndoOrderData {
newVal(v2) {} newVal(v2) {}
}; };
struct UndoOtherData {
UndoOtherTarget target;
int subtarget;
size_t off;
unsigned char oldVal, newVal;
UndoOtherData(UndoOtherTarget t, int st, size_t o, unsigned char v1, unsigned char v2):
target(t),
subtarget(st),
off(o),
oldVal(v1),
newVal(v2) {}
};
struct UndoStep { struct UndoStep {
ActionType type; ActionType type;
SelectionPoint cursor, selStart, selEnd; SelectionPoint cursor, selStart, selEnd;
@ -780,6 +802,7 @@ struct UndoStep {
int oldPatLen, newPatLen; int oldPatLen, newPatLen;
std::vector<UndoOrderData> ord; std::vector<UndoOrderData> ord;
std::vector<UndoPatternData> pat; std::vector<UndoPatternData> pat;
std::vector<UndoOtherData> other;
}; };
// -1 = any // -1 = any
@ -2065,8 +2088,10 @@ class FurnaceGUI {
void doScale(float top); void doScale(float top);
void doRandomize(int bottom, int top, bool mode); void doRandomize(int bottom, int top, bool mode);
void doFlip(); void doFlip();
void doCollapse(int divider); void doCollapse(int divider, const SelectionPoint& sStart, const SelectionPoint& sEnd);
void doExpand(int multiplier); void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd);
void doCollapseSong(int divider);
void doExpandSong(int multiplier);
void doUndo(); void doUndo();
void doRedo(); void doRedo();
void doFind(); void doFind();
@ -2135,7 +2160,7 @@ class FurnaceGUI {
void runBackupThread(); void runBackupThread();
void pushPartBlend(); void pushPartBlend();
void popPartBlend(); void popPartBlend();
bool detectOutOfBoundsWindow(); bool detectOutOfBoundsWindow(SDL_Rect& failing);
int processEvent(SDL_Event* ev); int processEvent(SDL_Event* ev);
bool loop(); bool loop();
bool finish(); bool finish();

View File

@ -1464,7 +1464,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (!i.isBitfield) { if (!i.isBitfield) {
if (settings.oldMacroVSlider) { if (settings.oldMacroVSlider) {
ImGui::SameLine(0.0f); ImGui::SameLine(0.0f);
if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) { if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"",ImGuiSliderFlags_NoInput)) {
if (i.macro->vScroll<0) i.macro->vScroll=0; if (i.macro->vScroll<0) i.macro->vScroll=0;
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
} }
@ -1592,7 +1592,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MAAR",&i.macro->val[2],0,255)) { PARAMETER if (CWSliderInt("##MAAR",&i.macro->val[2],0,255)) { PARAMETER
if (i.macro->val[2]<0) i.macro->val[2]=0; if (i.macro->val[2]<0) i.macro->val[2]=0;
if (i.macro->val[2]>255) i.macro->val[2]=255; if (i.macro->val[2]>255) i.macro->val[2]=255;
} } rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Sustain"); ImGui::Text("Sustain");
@ -1601,7 +1601,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MASL",&i.macro->val[5],0,255)) { PARAMETER if (CWSliderInt("##MASL",&i.macro->val[5],0,255)) { PARAMETER
if (i.macro->val[5]<0) i.macro->val[5]=0; if (i.macro->val[5]<0) i.macro->val[5]=0;
if (i.macro->val[5]>255) i.macro->val[5]=255; if (i.macro->val[5]>255) i.macro->val[5]=255;
} } rightClickable
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -1611,7 +1611,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MAHT",&i.macro->val[3],0,255)) { PARAMETER if (CWSliderInt("##MAHT",&i.macro->val[3],0,255)) { PARAMETER
if (i.macro->val[3]<0) i.macro->val[3]=0; if (i.macro->val[3]<0) i.macro->val[3]=0;
if (i.macro->val[3]>255) i.macro->val[3]=255; if (i.macro->val[3]>255) i.macro->val[3]=255;
} } rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("SusTime"); ImGui::Text("SusTime");
@ -1620,7 +1620,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MAST",&i.macro->val[6],0,255)) { PARAMETER if (CWSliderInt("##MAST",&i.macro->val[6],0,255)) { PARAMETER
if (i.macro->val[6]<0) i.macro->val[6]=0; if (i.macro->val[6]<0) i.macro->val[6]=0;
if (i.macro->val[6]>255) i.macro->val[6]=255; if (i.macro->val[6]>255) i.macro->val[6]=255;
} } rightClickable
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -1630,7 +1630,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MADR",&i.macro->val[4],0,255)) { PARAMETER if (CWSliderInt("##MADR",&i.macro->val[4],0,255)) { PARAMETER
if (i.macro->val[4]<0) i.macro->val[4]=0; if (i.macro->val[4]<0) i.macro->val[4]=0;
if (i.macro->val[4]>255) i.macro->val[4]=255; if (i.macro->val[4]>255) i.macro->val[4]=255;
} } rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("SusDecay"); ImGui::Text("SusDecay");
@ -1639,7 +1639,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MASR",&i.macro->val[7],0,255)) { PARAMETER if (CWSliderInt("##MASR",&i.macro->val[7],0,255)) { PARAMETER
if (i.macro->val[7]<0) i.macro->val[7]=0; if (i.macro->val[7]<0) i.macro->val[7]=0;
if (i.macro->val[7]>255) i.macro->val[7]=255; if (i.macro->val[7]>255) i.macro->val[7]=255;
} } rightClickable
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -1652,7 +1652,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MARR",&i.macro->val[8],0,255)) { PARAMETER if (CWSliderInt("##MARR",&i.macro->val[8],0,255)) { PARAMETER
if (i.macro->val[8]<0) i.macro->val[8]=0; if (i.macro->val[8]<0) i.macro->val[8]=0;
if (i.macro->val[8]>255) i.macro->val[8]=255; if (i.macro->val[8]>255) i.macro->val[8]=255;
} } rightClickable
ImGui::EndTable(); ImGui::EndTable();
} }
@ -1695,7 +1695,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MLSpeed",&i.macro->val[11],0,255)) { PARAMETER if (CWSliderInt("##MLSpeed",&i.macro->val[11],0,255)) { PARAMETER
if (i.macro->val[11]<0) i.macro->val[11]=0; if (i.macro->val[11]<0) i.macro->val[11]=0;
if (i.macro->val[11]>255) i.macro->val[11]=255; if (i.macro->val[11]>255) i.macro->val[11]=255;
} } rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Phase"); ImGui::Text("Phase");
@ -1704,7 +1704,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MLPhase",&i.macro->val[13],0,1023)) { PARAMETER if (CWSliderInt("##MLPhase",&i.macro->val[13],0,1023)) { PARAMETER
if (i.macro->val[13]<0) i.macro->val[13]=0; if (i.macro->val[13]<0) i.macro->val[13]=0;
if (i.macro->val[13]>1023) i.macro->val[13]=1023; if (i.macro->val[13]>1023) i.macro->val[13]=1023;
} } rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Shape"); ImGui::Text("Shape");
@ -1713,7 +1713,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (CWSliderInt("##MLShape",&i.macro->val[12],0,2,macroLFOShapes[i.macro->val[12]&3])) { PARAMETER if (CWSliderInt("##MLShape",&i.macro->val[12],0,2,macroLFOShapes[i.macro->val[12]&3])) { PARAMETER
if (i.macro->val[12]<0) i.macro->val[12]=0; if (i.macro->val[12]<0) i.macro->val[12]=0;
if (i.macro->val[12]>2) i.macro->val[12]=2; if (i.macro->val[12]>2) i.macro->val[12]=2;
} } rightClickable
ImGui::EndTable(); ImGui::EndTable();
} }
@ -2839,37 +2839,37 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
op.ar&=maxArDr; op.ar&=maxArDr;
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
op.dr&=maxArDr; op.dr&=maxArDr;
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable
if (settings.susPosition==0) { if (settings.susPosition==0) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
op.sl&=15; op.sl&=15;
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable
} }
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
op.d2r&=31; op.d2r&=31;
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
op.rr&=15; op.rr&=15;
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable
if (settings.susPosition==1) { if (settings.susPosition==1) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
op.sl&=15; op.sl&=15;
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -2878,38 +2878,38 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
op.tl&=maxTl; op.tl&=maxTl;
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); P(CWVSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
CENTER_VSLIDER; CENTER_VSLIDER;
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) {
P(CWVSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); P(CWVSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable
} else { } else {
int ksl=ins->type==DIV_INS_OPLL?op.ksl:kslMap[op.ksl&3]; int ksl=ins->type==DIV_INS_OPLL?op.ksl:kslMap[op.ksl&3];
if (CWVSliderInt("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),&ksl,0,3)) { if (CWVSliderInt("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),&ksl,0,3)) {
op.ksl=(ins->type==DIV_INS_OPLL?ksl:kslMap[ksl&3]); op.ksl=(ins->type==DIV_INS_OPLL?ksl:kslMap[ksl&3]);
PARAMETER; PARAMETER;
} } rightClickable
} }
if (ins->type==DIV_INS_OPZ) { if (ins->type==DIV_INS_OPZ) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##EGS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); P(CWVSliderScalar("##EGS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##REV",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN)); P(CWVSliderScalar("##REV",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN)); rightClickable
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable
if (ins->type==DIV_INS_OPZ) { if (ins->type==DIV_INS_OPZ) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
CENTER_VSLIDER; CENTER_VSLIDER;
P(CWVSliderScalar("##FINE",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##FINE",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN)); rightClickable
} }
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) {
@ -2920,7 +2920,7 @@ void FurnaceGUI::drawInsEdit() {
if (detune<-3) detune=-3; if (detune<-3) detune=-3;
if (detune>7) detune=7; if (detune>7) detune=7;
op.dt=detuneUnmap[settings.unsignedDetune?1:0][detune+3]; op.dt=detuneUnmap[settings.unsignedDetune?1:0][detune+3];
} } rightClickable
if (ins->type!=DIV_INS_FM) { if (ins->type!=DIV_INS_FM) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -3169,19 +3169,19 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
op.ar&=maxArDr; op.ar&=maxArDr;
P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable
ImGui::SameLine(); ImGui::SameLine();
op.dr&=maxArDr; op.dr&=maxArDr;
float textX_DR=ImGui::GetCursorPosX(); float textX_DR=ImGui::GetCursorPosX();
P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable
float textX_SL=0.0f; float textX_SL=0.0f;
if (settings.susPosition==0) { if (settings.susPosition==0) {
ImGui::SameLine(); ImGui::SameLine();
op.sl&=15; op.sl&=15;
textX_SL=ImGui::GetCursorPosX(); textX_SL=ImGui::GetCursorPosX();
P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable
} }
float textX_D2R=0.0f; float textX_D2R=0.0f;
@ -3189,19 +3189,19 @@ void FurnaceGUI::drawInsEdit() {
ImGui::SameLine(); ImGui::SameLine();
op.d2r&=31; op.d2r&=31;
textX_D2R=ImGui::GetCursorPosX(); textX_D2R=ImGui::GetCursorPosX();
P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable
} }
ImGui::SameLine(); ImGui::SameLine();
op.rr&=15; op.rr&=15;
float textX_RR=ImGui::GetCursorPosX(); float textX_RR=ImGui::GetCursorPosX();
P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable
if (settings.susPosition==1) { if (settings.susPosition==1) {
ImGui::SameLine(); ImGui::SameLine();
op.sl&=15; op.sl&=15;
textX_SL=ImGui::GetCursorPosX(); textX_SL=ImGui::GetCursorPosX();
P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable
} }
ImVec2 prevCurPos=ImGui::GetCursorPos(); ImVec2 prevCurPos=ImGui::GetCursorPos();
@ -3500,7 +3500,7 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
op.tl&=maxTl; op.tl&=maxTl;
P(CWVSliderScalar("##TL",ImVec2(ImGui::GetFrameHeight(),sliderHeight-((ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM)?(ImGui::GetFrameHeightWithSpacing()+ImGui::CalcTextSize(FM_SHORT_NAME(FM_AM)).y+ImGui::GetStyle().ItemSpacing.y):0.0f)),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); P(CWVSliderScalar("##TL",ImVec2(ImGui::GetFrameHeight(),sliderHeight-((ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM)?(ImGui::GetFrameHeightWithSpacing()+ImGui::CalcTextSize(FM_SHORT_NAME(FM_AM)).y+ImGui::GetStyle().ItemSpacing.y):0.0f)),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) {
CENTER_TEXT(FM_SHORT_NAME(FM_AM)); CENTER_TEXT(FM_SHORT_NAME(FM_AM));
@ -4299,13 +4299,13 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
drawFMEnv(0,16-ins->c64.a,16-ins->c64.d,15-ins->c64.r,15-ins->c64.r,15-ins->c64.s,0,0,0,15,16,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); drawFMEnv(0,16-ins->c64.a,16-ins->c64.d,15-ins->c64.r,15-ins->c64.r,15-ins->c64.s,0,0,0,15,16,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type);
@ -4800,7 +4800,7 @@ void FurnaceGUI::drawInsEdit() {
// filter // filter
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWSliderScalar("Filter 4,3 Mode",ImGuiDataType_U8,&ins->es5506.filter.mode,&_ZERO,&_THREE,es5506FilterModes[ins->es5506.filter.mode&3])); rightClickable P(CWSliderScalar("Filter Mode",ImGuiDataType_U8,&ins->es5506.filter.mode,&_ZERO,&_THREE,es5506FilterModes[ins->es5506.filter.mode&3]));
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWSliderScalar("Filter K1",ImGuiDataType_U16,&ins->es5506.filter.k1,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable P(CWSliderScalar("Filter K1",ImGuiDataType_U16,&ins->es5506.filter.k1,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable
@ -4884,17 +4884,17 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Attack Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.ar,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Attack Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.ar,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Decay 1 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d1r,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Decay 1 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d1r,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Decay Level",sliderSize,ImGuiDataType_U8,&ins->multipcm.dl,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Decay Level",sliderSize,ImGuiDataType_U8,&ins->multipcm.dl,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Decay 2 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d2r,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Decay 2 Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.d2r,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Release Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.rr,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Release Rate",sliderSize,ImGuiDataType_U8,&ins->multipcm.rr,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Rate Correction",sliderSize,ImGuiDataType_U8,&ins->multipcm.rc,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
drawFMEnv(0,ins->multipcm.ar,ins->multipcm.d1r,ins->multipcm.d2r,ins->multipcm.rr,ins->multipcm.dl,0,0,0,127,15,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); drawFMEnv(0,ins->multipcm.ar,ins->multipcm.d1r,ins->multipcm.d2r,ins->multipcm.rr,ins->multipcm.dl,0,0,0,127,15,15,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type);
ImGui::EndTable(); ImGui::EndTable();
@ -4952,17 +4952,17 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->snes.a,&_ZERO,&_FIFTEEN)); P(CWVSliderScalar("##Attack",sliderSize,ImGuiDataType_U8,&ins->snes.a,&_ZERO,&_FIFTEEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->snes.d,&_ZERO,&_SEVEN)); P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->snes.d,&_ZERO,&_SEVEN)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->snes.s,&_ZERO,&_SEVEN)); P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->snes.s,&_ZERO,&_SEVEN)); rightClickable
if (ins->snes.sus) { if (ins->snes.sus) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Decay2",sliderSize,ImGuiDataType_U8,&ins->snes.d2,&_ZERO,&_THIRTY_ONE)); P(CWVSliderScalar("##Decay2",sliderSize,ImGuiDataType_U8,&ins->snes.d2,&_ZERO,&_THIRTY_ONE)); rightClickable
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.sus?ins->snes.d2:ins->snes.r,ins->snes.sus?ins->snes.r:31,(14-ins->snes.s*2),(ins->snes.r==0 || (ins->snes.sus && ins->snes.d2==0)),0,0,7,16,31,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.sus?ins->snes.d2:ins->snes.r,ins->snes.sus?ins->snes.r:31,(14-ins->snes.s*2),(ins->snes.r==0 || (ins->snes.sus && ins->snes.d2==0)),0,0,7,16,31,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type);
@ -5024,7 +5024,7 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31; unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31;
if (ins->snes.gain>gainMax) ins->snes.gain=gainMax; if (ins->snes.gain>gainMax) ins->snes.gain=gainMax;
P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Envelope goes here..."); ImGui::Text("Envelope goes here...");

View File

@ -100,7 +100,7 @@ void FurnaceGUI::drawOsc() {
if (ImGui::VSliderFloat("##OscZoom",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscZoom,0.5,2.0)) { if (ImGui::VSliderFloat("##OscZoom",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscZoom,0.5,2.0)) {
if (oscZoom<0.5) oscZoom=0.5; if (oscZoom<0.5) oscZoom=0.5;
if (oscZoom>2.0) oscZoom=2.0; if (oscZoom>2.0) oscZoom=2.0;
} } rightClickable
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("zoom: %.2fx (%.1fdB)",oscZoom,20.0*log10(oscZoom*2.0)); ImGui::SetTooltip("zoom: %.2fx (%.1fdB)",oscZoom,20.0*log10(oscZoom*2.0));
} }
@ -111,7 +111,7 @@ void FurnaceGUI::drawOsc() {
if (ImGui::VSliderFloat("##OscWinSize",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscWindowSize,5.0,100.0)) { if (ImGui::VSliderFloat("##OscWinSize",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscWindowSize,5.0,100.0)) {
if (oscWindowSize<5.0) oscWindowSize=5.0; if (oscWindowSize<5.0) oscWindowSize=5.0;
if (oscWindowSize>100.0) oscWindowSize=100.0; if (oscWindowSize>100.0) oscWindowSize=100.0;
} } rightClickable
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("window size: %.1fms",oscWindowSize); ImGui::SetTooltip("window size: %.1fms",oscWindowSize);
} }

View File

@ -1111,23 +1111,26 @@ void FurnaceGUI::drawSampleEdit() {
if (sampleTex!=NULL) { if (sampleTex!=NULL) {
if (updateSampleTex) { if (updateSampleTex) {
unsigned int* data=NULL; unsigned int* dataT=NULL;
int pitch=0; int pitch=0;
logD("updating sample texture."); logD("updating sample texture.");
if (SDL_LockTexture(sampleTex,NULL,(void**)&data,&pitch)!=0) { if (SDL_LockTexture(sampleTex,NULL,(void**)&dataT,&pitch)!=0) {
logE("error while locking sample texture! %s",SDL_GetError()); logE("error while locking sample texture! %s",SDL_GetError());
} else { } else {
unsigned int* data=new unsigned int[sampleTexW*sampleTexH];
ImU32 bgColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_BG]); ImU32 bgColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_BG]);
ImU32 bgColorLoop=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_LOOP]); ImU32 bgColorLoop=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_LOOP]);
ImU32 lineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_FG]); ImU32 lineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_FG]);
ImU32 centerLineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_CENTER]); ImU32 centerLineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_CENTER]);
int ij=0;
for (int i=0; i<availY; i++) { for (int i=0; i<availY; i++) {
for (int j=0; j<availX; j++) { for (int j=0; j<availX; j++) {
int scaledPos=samplePos+(j*sampleZoom); int scaledPos=samplePos+(j*sampleZoom);
if (sample->isLoopable() && (scaledPos>=sample->loopStart && scaledPos<=sample->loopEnd)) { if (sample->isLoopable() && (scaledPos>=sample->loopStart && scaledPos<=sample->loopEnd)) {
data[i*availX+j]=bgColorLoop; data[ij++]=bgColorLoop;
} else { } else {
data[i*availX+j]=bgColor; data[ij++]=bgColor;
} }
} }
} }
@ -1143,11 +1146,15 @@ void FurnaceGUI::drawSampleEdit() {
for (unsigned int i=0; i<(unsigned int)availX; i++) { for (unsigned int i=0; i<(unsigned int)availX; i++) {
if (xCoarse>=sample->samples) break; if (xCoarse>=sample->samples) break;
int y1, y2; int y1, y2;
int candMin=INT_MAX;
int candMax=INT_MIN;
int totalAdvance=0; int totalAdvance=0;
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
y1=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; if (candMin>sample->data8[xCoarse]) candMin=sample->data8[xCoarse];
if (candMax<sample->data8[xCoarse]) candMax=sample->data8[xCoarse];
} else { } else {
y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; if (candMin>sample->data16[xCoarse]) candMin=sample->data16[xCoarse];
if (candMax<sample->data16[xCoarse]) candMax=sample->data16[xCoarse];
} }
xFine+=xAdvanceFine; xFine+=xAdvanceFine;
if (xFine>=16777216) { if (xFine>=16777216) {
@ -1157,27 +1164,44 @@ void FurnaceGUI::drawSampleEdit() {
totalAdvance+=xAdvanceCoarse; totalAdvance+=xAdvanceCoarse;
if (xCoarse>=sample->samples) break; if (xCoarse>=sample->samples) break;
do { do {
if (xCoarse>=sample->samples) break;
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
y2=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; if (candMin>sample->data8[xCoarse]) candMin=sample->data8[xCoarse];
if (candMax<sample->data8[xCoarse]) candMax=sample->data8[xCoarse];
} else { } else {
y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; if (candMin>sample->data16[xCoarse]) candMin=sample->data16[xCoarse];
} if (candMax<sample->data16[xCoarse]) candMax=sample->data16[xCoarse];
if (y1>y2) {
y2^=y1;
y1^=y2;
y2^=y1;
}
if (y1<0) y1=0;
if (y1>=availY) y1=availY-1;
if (y2<0) y2=0;
if (y2>=availY) y2=availY-1;
for (int j=y1; j<=y2; j++) {
data[i+availX*(availY-j-1)]=lineColor;
} }
if (totalAdvance>0) xCoarse++; if (totalAdvance>0) xCoarse++;
} while ((totalAdvance--)>0); } while ((totalAdvance--)>0);
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
y1=(((unsigned char)candMin^0x80)*availY)>>8;
y2=(((unsigned char)candMax^0x80)*availY)>>8;
} else {
y1=(((unsigned short)candMin^0x8000)*availY)>>16;
y2=(((unsigned short)candMax^0x8000)*availY)>>16;
}
if (y1>y2) {
y2^=y1;
y1^=y2;
y2^=y1;
}
if (y1<0) y1=0;
if (y1>=availY) y1=availY-1;
if (y2<0) y2=0;
if (y2>=availY) y2=availY-1;
const int s1=i+availX*(availY-y1-1);
const int s2=i+availX*(availY-y2-1);
for (int j=s2; j<=s1; j+=availX) {
data[j]=lineColor;
}
} }
memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int));
SDL_UnlockTexture(sampleTex); SDL_UnlockTexture(sampleTex);
delete[] data;
} }
updateSampleTex=false; updateSampleTex=false;
} }

View File

@ -276,6 +276,8 @@ void FurnaceGUI::drawSettings() {
ImVec2 setWindowSize=ImVec2(canvasW,canvasH); ImVec2 setWindowSize=ImVec2(canvasW,canvasH);
ImGui::SetNextWindowPos(setWindowPos); ImGui::SetNextWindowPos(setWindowPos);
ImGui::SetNextWindowSize(setWindowSize); ImGui::SetNextWindowSize(setWindowSize);
} else {
ImGui::SetNextWindowSizeConstraints(ImVec2(200.0f*dpiScale,100.0f*dpiScale),ImVec2(canvasW,canvasH));
} }
if (ImGui::Begin("Settings",&settingsOpen,ImGuiWindowFlags_NoDocking|globalWinFlags)) { if (ImGui::Begin("Settings",&settingsOpen,ImGuiWindowFlags_NoDocking|globalWinFlags)) {
if (!settingsOpen) { if (!settingsOpen) {
@ -2303,10 +2305,12 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_FLIP_SELECTION); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_FLIP_SELECTION);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_ROWS);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_ROWS); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_ROWS);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_PAT);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_PAT);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_SONG);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_SONG);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH);
// TODO: collapse/expand pattern and song
KEYBIND_CONFIG_END; KEYBIND_CONFIG_END;
ImGui::TreePop(); ImGui::TreePop();
} }

View File

@ -598,7 +598,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderFloat("##WGDuty",&waveGenDuty,0.0f,1.0f)) { if (CWSliderFloat("##WGDuty",&waveGenDuty,0.0f,1.0f)) {
doGenerateWave(); doGenerateWave();
} } rightClickable
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -607,7 +607,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderInt("##WGExp",&waveGenPower,1,8)) { if (CWSliderInt("##WGExp",&waveGenPower,1,8)) {
doGenerateWave(); doGenerateWave();
} } rightClickable
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -616,7 +616,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderFloat("##WGXOR",&waveGenInvertPoint,0.0f,1.0f)) { if (CWSliderFloat("##WGXOR",&waveGenInvertPoint,0.0f,1.0f)) {
doGenerateWave(); doGenerateWave();
} } rightClickable
ImGui::EndTable(); ImGui::EndTable();
} }
@ -636,7 +636,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderFloat("##WGAmp",&waveGenAmp[i],-1.0f,1.0f)) { if (CWSliderFloat("##WGAmp",&waveGenAmp[i],-1.0f,1.0f)) {
doGenerateWave(); doGenerateWave();
} } rightClickable
if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) {
waveGenAmp[i]=0.0f; waveGenAmp[i]=0.0f;
doGenerateWave(); doGenerateWave();
@ -647,7 +647,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (CWSliderFloat("##WGPhase",&waveGenPhase[i],0.0f,1.0f)) { if (CWSliderFloat("##WGPhase",&waveGenPhase[i],0.0f,1.0f)) {
doGenerateWave(); doGenerateWave();
} } rightClickable
if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) {
waveGenPhase[i]=0.0f; waveGenPhase[i]=0.0f;
doGenerateWave(); doGenerateWave();
@ -690,7 +690,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::PushID(i); ImGui::PushID(i);
if (CWSliderFloat("##WGTL",&waveGenTL[i],0.0f,1.0f)) { if (CWSliderFloat("##WGTL",&waveGenTL[i],0.0f,1.0f)) {
doGenerateWave(); doGenerateWave();
} } rightClickable
ImGui::PopID(); ImGui::PopID();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -698,7 +698,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::PushID(i); ImGui::PushID(i);
if (CWSliderInt("##WGMULT",&waveGenMult[i],1,16)) { if (CWSliderInt("##WGMULT",&waveGenMult[i],1,16)) {
doGenerateWave(); doGenerateWave();
} } rightClickable
ImGui::PopID(); ImGui::PopID();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -706,7 +706,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::PushID(i); ImGui::PushID(i);
if (CWSliderInt("##WGFB",&waveGenFB[i],0,7)) { if (CWSliderInt("##WGFB",&waveGenFB[i],0,7)) {
doGenerateWave(); doGenerateWave();
} } rightClickable
ImGui::PopID(); ImGui::PopID();
} }