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
env:
BUILD_TYPE: Debug
BUILD_TYPE: RelWithDebInfo
jobs:
build:

View file

@ -793,7 +793,7 @@ endif()
if(ANDROID AND NOT TERMUX)
add_library(furnace SHARED ${USED_SOURCES})
elseif(WIN32)
add_executable(furnace ${USED_SOURCES})
add_executable(furnace WIN32 ${USED_SOURCES})
else()
add_executable(furnace ${USED_SOURCES})
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 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);
if (!ItemAdd(frame_bb, id, NULL, ImGuiItemFlags_NoInertialScroll))
if (!ItemAdd(frame_bb, id, NULL, (temp_input_allowed ? ImGuiItemFlags_Inputable : 0) | ImGuiItemFlags_NoInertialScroll))
return false;
// 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.)
format = PatchFormatStringFloatToInt(format);
// Tabbing or CTRL-clicking on Slider turns it into an input box
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);
SetFocusID(id, window);
FocusWindow(window);
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0;
const bool clicked = (hovered && g.IO.MouseClicked[0]);
if (input_requested_by_tabbing || clicked || g.NavActivateId == id || g.NavActivateInputId == id)
{
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
@ -3154,6 +3171,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
if (label_size.x > 0.0f)
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;
}

View file

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

View file

@ -1,8 +1,14 @@
# 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

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

View file

@ -178,7 +178,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundanc
logD("config does not exist");
if (createOnFail) {
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);
} else {
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");
if (createOnFail) {
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);
} else {
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";
case 0x04:
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:
return "07xy: Tremolo (x: speed; y: depth)";
case 0x08:
@ -2419,6 +2423,13 @@ void DivEngine::stepOne(int row) {
void DivEngine::stop() {
BUSY_BEGIN;
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;
extValuePresent=false;
endOfSong=false; // what?

View file

@ -105,7 +105,7 @@ struct DivChannelState {
int delayOrder, delayRow, retrigSpeed, retrigTick;
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine;
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 arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp;
bool wentThroughNote, goneThroughNote;
@ -146,6 +146,8 @@ struct DivChannelState {
panR(255),
panRL(0),
panRR(0),
lastVibrato(0),
lastPorta(0),
doNote(false),
legato(false),
portaStop(false),

View file

@ -23,11 +23,7 @@
static DivPattern emptyPat;
DivPattern::DivPattern() {
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;
}
clear();
}
DivPattern* DivChannelData::getPattern(int index, bool create) {
@ -93,6 +89,14 @@ void DivPattern::copyOn(DivPattern* dest) {
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():
effectCols(1) {
memset(data,0,DIV_MAX_PATTERNS*sizeof(void*));

View file

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

View file

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

View file

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

View file

@ -29,6 +29,7 @@ const char* regCheatSheetPV1000[]={
"CH1_Pitch", "00",
"CH2_Pitch", "01",
"CH3_Pitch", "02",
"Control", "03",
NULL
};
@ -38,11 +39,10 @@ const char** DivPlatformPV1000::getRegisterSheet() {
void DivPlatformPV1000::acquire(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
short samp;
samp=d65010g031_sound_tick(&d65010g031,1);
short samp=d65010g031_sound_tick(&d65010g031,1);
buf[0][h]=samp;
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) {
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 (isMuted[i]) chan[i].keyOn=false;
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;
} 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) {
rWrite(i,0);
rWrite(i,0x3f);
chan[i].keyOff=false;
}
chan[i].freqChanged=false;
@ -137,6 +137,13 @@ int DivPlatformPV1000::dispatch(DivCommand c) {
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
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: {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
@ -224,16 +231,21 @@ unsigned char* DivPlatformPV1000::getRegisterPool() {
}
int DivPlatformPV1000::getRegisterPoolSize() {
return 3;
return 4;
}
void DivPlatformPV1000::reset() {
memset(regPool,0,3);
memset(regPool,0,4);
for (int i=0; i<3; i++) {
chan[i]=Channel();
chan[i].std.setEngine(parent);
}
d65010g031_reset(&d65010g031);
// mute
rWrite(0,0x3f);
rWrite(1,0x3f);
rWrite(2,0x3f);
rWrite(3,2);
}
int DivPlatformPV1000::getOutputCount() {

View file

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

View file

@ -151,8 +151,8 @@ void DivPlatformRF5C68::tick(bool sysTick) {
if (s->isLoopable()) {
loop=start+s->loopStart;
}
start=MIN(start,getSampleMemCapacity()-31);
loop=MIN(loop,getSampleMemCapacity()-31);
start=MIN(start,getSampleMemCapacity()-32);
loop=MIN(loop,getSampleMemCapacity()-32);
rWrite(8,keyoff); // force keyoff first
chWrite(i,6,start>>8);
chWrite(i,4,loop&0xff);
@ -425,7 +425,7 @@ void DivPlatformRF5C68::renderSamples(int sysID) {
}
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) {
sampleOffRFC[i]=memPos;
for (int j=0; j<actualLength; j++) {
@ -435,8 +435,8 @@ void DivPlatformRF5C68::renderSamples(int sysID) {
sampleMem[memPos++]=(val>0)?(val|0x80):(0-val);
}
// write end of sample marker
memset(&sampleMem[memPos],0xff,31);
memPos+=31;
memset(&sampleMem[memPos],0xff,32);
memPos+=32;
}
if (actualLength<length) {
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)
{
int period = d65010g031_max(1, (0x3f - square->period));
const int period = square->period;
square->counter += cycle;
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 **NOT** THE ORIGINAL SOFTWARE! I am plainly marking it as such!
const int d65Volumes[3]={
3840,
5120,
8192
3840, // -6dB
5120, // -3dB
8192 // 0dB
};
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;
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;
}
@ -105,12 +127,25 @@ void d65010g031_reset(struct d65010g031_t *d65010g031)
d65010g031->square[i].counter = 0;
d65010g031->square[i].out = 0;
}
d65010g031->ctrl = 0;
}
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_square_t square[3];
signed short out[3];
unsigned char ctrl;
};
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;
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;
dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0));
} else {
chan[i].lastPorta=effectVal;
calledPorta=true;
if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) {
chan[i].portaNote=chan[i].note;
@ -700,11 +701,78 @@ void DivEngine::processRow(int i, bool afterDelay) {
}
break;
case 0x04: // vibrato
if (effectVal) chan[i].lastVibrato=effectVal;
chan[i].vibratoDepth=effectVal&15;
chan[i].vibratoRate=effectVal>>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)));
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
// TODO
// 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;
if (disCont[dispatchOfChan[i]].dispatch!=NULL) {
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) {
bool doPrepareCut=true;
int addition=0;
for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==0x03) {
doPrepareCut=false;
break;
}
if (pat->data[curRow][4+(j<<1)]==0x06) {
doPrepareCut=false;
break;
}
if (pat->data[curRow][4+(j<<1)]==0xea) {
if (pat->data[curRow][5+(j<<1)]>0) {
doPrepareCut=false;
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++) {
float result=floatData[i]+data16[i];
float result=floatData[i]+data8[i];
if (result<-128) result=-128;
if (result>127) result=127;
data16[i]=round(result);
data8[i]=round(result);
}
}
delete[] floatData;

View file

@ -1839,7 +1839,11 @@ void DivEngine::registerSystems() {
{"Square 1", "Square 2", "Square 3"},
{"S1", "S2", "S3"},
{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(

View file

@ -1083,6 +1083,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
bool trailing=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;
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);
}
if (mayWriteRate) {
w->writeI(round(curSubSong->hz));
}
w->seek(0x34,SEEK_SET);
w->writeI(songOff-0x34);
if (version>=0x170) {

View file

@ -561,18 +561,32 @@ void FurnaceGUI::doAction(int what) {
doFlip();
break;
case GUI_ACTION_PAT_COLLAPSE_ROWS:
doCollapse(2);
doCollapse(2,selStart,selEnd);
break;
case GUI_ACTION_PAT_EXPAND_ROWS:
doExpand(2);
doExpand(2,selStart,selEnd);
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;
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;
case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO
}
case GUI_ACTION_PAT_COLLAPSE_SONG:
doCollapseSong(2);
break;
case GUI_ACTION_PAT_EXPAND_SONG: // TODO
case GUI_ACTION_PAT_EXPAND_SONG:
doExpandSong(2);
break;
case GUI_ACTION_PAT_LATCH: // TODO
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]);
}
break;
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO
break;
case GUI_UNDO_REPLACE: // this is handled by doReplace()
break;
}
@ -130,6 +133,9 @@ void FurnaceGUI::makeUndo(ActionType action) {
doPush=true;
}
break;
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO
break;
case GUI_UNDO_REPLACE: // this is handled by doReplace()
break;
}
@ -839,50 +845,56 @@ void FurnaceGUI::doFlip() {
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();
prepareUndo(GUI_UNDO_PATTERN_COLLAPSE);
DivPattern patBuffer;
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
int iCoarse=sStart.xCoarse;
int iFine=sStart.xFine;
for (; iCoarse<=sEnd.xCoarse; iCoarse++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); 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) {
patBuffer.data[j][0]=pat->data[j][0];
}
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
}
for (int j=0; j<=selEnd.y-selStart.y; j++) {
if (j*divider>=selEnd.y-selStart.y) {
for (int j=0; j<=sEnd.y-sStart.y; j++) {
if (j*divider>=sEnd.y-sStart.y) {
if (iFine==0) {
pat->data[j+selStart.y][0]=0;
pat->data[j+selStart.y][1]=0;
pat->data[j+sStart.y][0]=0;
pat->data[j+sStart.y][1]=0;
} else {
pat->data[j+selStart.y][iFine+1]=-1;
pat->data[j+sStart.y][iFine+1]=-1;
}
} else {
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) {
for (int k=1; k<divider; k++) {
if ((j*divider+k)>=selEnd.y-selStart.y) break;
if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break;
pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0];
pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1];
if ((j*divider+k)>=sEnd.y-sStart.y) break;
if (!(pat->data[j+sStart.y][0]==0 && pat->data[j+sStart.y][1]==0)) break;
pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y+k][0];
pat->data[j+sStart.y][1]=patBuffer.data[j*divider+sStart.y+k][1];
}
} else {
for (int k=1; k<divider; k++) {
if ((j*divider+k)>=selEnd.y-selStart.y) break;
if (pat->data[j+selStart.y][iFine+1]!=-1) break;
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1];
if ((j*divider+k)>=sEnd.y-sStart.y) break;
if (pat->data[j+sStart.y][iFine+1]!=-1) break;
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);
}
void FurnaceGUI::doExpand(int multiplier) {
if (multiplier<1) return;
void FurnaceGUI::doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd) {
if (multiplier<2) return;
finishSelection();
prepareUndo(GUI_UNDO_PATTERN_EXPAND);
DivPattern patBuffer;
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
int iCoarse=sStart.xCoarse;
int iFine=sStart.xFine;
for (; iCoarse<=sEnd.xCoarse; iCoarse++) {
if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); 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) {
patBuffer.data[j][0]=pat->data[j][0];
}
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
}
for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) {
if ((j+selStart.y)>=e->curSubSong->patLen) break;
for (int j=0; j<=(sEnd.y-sStart.y)*multiplier; j++) {
if ((j+sStart.y)>=e->curSubSong->patLen) break;
if ((j%multiplier)!=0) {
if (iFine==0) {
pat->data[j+selStart.y][0]=0;
pat->data[j+selStart.y][1]=0;
pat->data[j+sStart.y][0]=0;
pat->data[j+sStart.y][1]=0;
} else {
pat->data[j+selStart.y][iFine+1]=-1;
pat->data[j+sStart.y][iFine+1]=-1;
}
continue;
}
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;
@ -937,6 +949,162 @@ void FurnaceGUI::doExpand(int multiplier) {
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() {
int len=dragEnd.xCoarse-dragStart.xCoarse+1;
@ -985,6 +1153,8 @@ void FurnaceGUI::doUndo() {
case GUI_UNDO_PATTERN_FLIP:
case GUI_UNDO_PATTERN_COLLAPSE:
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_REPLACE:
for (UndoPatternData& i: us.pat) {
@ -1005,6 +1175,22 @@ void FurnaceGUI::doUndo() {
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) {
curOrder=e->curSubSong->ordersLen-1;
oldOrder=curOrder;
@ -1045,6 +1231,8 @@ void FurnaceGUI::doRedo() {
case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND:
case GUI_UNDO_PATTERN_DRAG:
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
case GUI_UNDO_PATTERN_EXPAND_SONG:
case GUI_UNDO_REPLACE:
for (UndoPatternData& i: us.pat) {
e->changeSongP(i.subSong);
@ -1065,6 +1253,22 @@ void FurnaceGUI::doRedo() {
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) {
curOrder=e->curSubSong->ordersLen-1;
oldOrder=curOrder;

View file

@ -2842,9 +2842,23 @@ void FurnaceGUI::editOptions(bool topMenu) {
ImGui::Separator();
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("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(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,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) {
ImGui::Separator();
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) {
@ -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
#define OOB_PIXELS_SAFETY 25
bool FurnaceGUI::detectOutOfBoundsWindow() {
bool FurnaceGUI::detectOutOfBoundsWindow(SDL_Rect& failing) {
int count=SDL_GetNumVideoDisplays();
if (count<1) {
logW("bounds check: error %s",SDL_GetError());
logW("bounds check: error: %s",SDL_GetError());
return false;
}
SDL_Rect rect;
for (int i=0; i<count; i++) {
if (SDL_GetDisplayUsableBounds(i,&rect)!=0) {
logW("bounds check: error %s",SDL_GetError());
return false;
logW("bounds check: error in display %d: %s",i,SDL_GetError());
continue;
}
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;
}
@ -3838,11 +3843,11 @@ bool FurnaceGUI::loop() {
}
ImGui::Checkbox("loop",&vgmExportLoop);
if (vgmExportLoop && e->song.loopModality==2) {
ImGui::Text("trailing ticks:");
ImGui::Text("loop trail:");
if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) {
vgmExportTrailingTicks=-1;
}
if (ImGui::RadioButton("one loop",vgmExportTrailingTicks==-2)) {
if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) {
vgmExportTrailingTicks=-2;
}
if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) {
@ -6003,10 +6008,23 @@ bool FurnaceGUI::init() {
#ifndef IS_MOBILE
// if window would spawn out of bounds, force it to be get default position
if (!detectOutOfBoundsWindow()) {
SDL_Rect bounds;
if (!detectOutOfBoundsWindow(bounds)) {
scrMax=false;
scrX=scrConfX=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

View file

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

View file

@ -1464,7 +1464,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
if (!i.isBitfield) {
if (settings.oldMacroVSlider) {
ImGui::SameLine(0.0f);
if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) {
if (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>((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 (i.macro->val[2]<0) i.macro->val[2]=0;
if (i.macro->val[2]>255) i.macro->val[2]=255;
}
} rightClickable
ImGui::TableNextColumn();
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 (i.macro->val[5]<0) i.macro->val[5]=0;
if (i.macro->val[5]>255) i.macro->val[5]=255;
}
} rightClickable
ImGui::TableNextRow();
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 (i.macro->val[3]<0) i.macro->val[3]=0;
if (i.macro->val[3]>255) i.macro->val[3]=255;
}
} rightClickable
ImGui::TableNextColumn();
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 (i.macro->val[6]<0) i.macro->val[6]=0;
if (i.macro->val[6]>255) i.macro->val[6]=255;
}
} rightClickable
ImGui::TableNextRow();
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 (i.macro->val[4]<0) i.macro->val[4]=0;
if (i.macro->val[4]>255) i.macro->val[4]=255;
}
} rightClickable
ImGui::TableNextColumn();
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 (i.macro->val[7]<0) i.macro->val[7]=0;
if (i.macro->val[7]>255) i.macro->val[7]=255;
}
} rightClickable
ImGui::TableNextRow();
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 (i.macro->val[8]<0) i.macro->val[8]=0;
if (i.macro->val[8]>255) i.macro->val[8]=255;
}
} rightClickable
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 (i.macro->val[11]<0) i.macro->val[11]=0;
if (i.macro->val[11]>255) i.macro->val[11]=255;
}
} rightClickable
ImGui::TableNextColumn();
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 (i.macro->val[13]<0) i.macro->val[13]=0;
if (i.macro->val[13]>1023) i.macro->val[13]=1023;
}
} rightClickable
ImGui::TableNextColumn();
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 (i.macro->val[12]<0) i.macro->val[12]=0;
if (i.macro->val[12]>2) i.macro->val[12]=2;
}
} rightClickable
ImGui::EndTable();
}
@ -2839,37 +2839,37 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
op.ar&=maxArDr;
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();
op.dr&=maxArDr;
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) {
ImGui::TableNextColumn();
op.sl&=15;
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) {
ImGui::TableNextColumn();
op.d2r&=31;
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();
op.rr&=15;
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) {
ImGui::TableNextColumn();
op.sl&=15;
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();
@ -2878,38 +2878,38 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
op.tl&=maxTl;
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();
CENTER_VSLIDER;
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 {
int ksl=ins->type==DIV_INS_OPLL?op.ksl:kslMap[op.ksl&3];
if (CWVSliderInt("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),&ksl,0,3)) {
op.ksl=(ins->type==DIV_INS_OPLL?ksl:kslMap[ksl&3]);
PARAMETER;
}
} rightClickable
}
if (ins->type==DIV_INS_OPZ) {
ImGui::TableNextColumn();
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();
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();
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) {
ImGui::TableNextColumn();
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) {
@ -2920,7 +2920,7 @@ void FurnaceGUI::drawInsEdit() {
if (detune<-3) detune=-3;
if (detune>7) detune=7;
op.dt=detuneUnmap[settings.unsignedDetune?1:0][detune+3];
}
} rightClickable
if (ins->type!=DIV_INS_FM) {
ImGui::TableNextColumn();
@ -3169,19 +3169,19 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
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();
op.dr&=maxArDr;
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;
if (settings.susPosition==0) {
ImGui::SameLine();
op.sl&=15;
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;
@ -3189,19 +3189,19 @@ void FurnaceGUI::drawInsEdit() {
ImGui::SameLine();
op.d2r&=31;
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();
op.rr&=15;
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) {
ImGui::SameLine();
op.sl&=15;
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();
@ -3500,7 +3500,7 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
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) {
CENTER_TEXT(FM_SHORT_NAME(FM_AM));
@ -4299,13 +4299,13 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextRow();
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();
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();
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();
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();
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
ImGui::TableNextRow();
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::TableNextColumn();
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::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();
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();
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();
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();
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();
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();
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();
@ -4952,17 +4952,17 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextRow();
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();
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();
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) {
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();
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();
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();
unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31;
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::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 (oscZoom<0.5) oscZoom=0.5;
if (oscZoom>2.0) oscZoom=2.0;
}
} rightClickable
if (ImGui::IsItemHovered()) {
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 (oscWindowSize<5.0) oscWindowSize=5.0;
if (oscWindowSize>100.0) oscWindowSize=100.0;
}
} rightClickable
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("window size: %.1fms",oscWindowSize);
}

View file

@ -1111,23 +1111,26 @@ void FurnaceGUI::drawSampleEdit() {
if (sampleTex!=NULL) {
if (updateSampleTex) {
unsigned int* data=NULL;
unsigned int* dataT=NULL;
int pitch=0;
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());
} else {
unsigned int* data=new unsigned int[sampleTexW*sampleTexH];
ImU32 bgColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_BG]);
ImU32 bgColorLoop=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_LOOP]);
ImU32 lineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_FG]);
ImU32 centerLineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_CENTER]);
int ij=0;
for (int i=0; i<availY; i++) {
for (int j=0; j<availX; j++) {
int scaledPos=samplePos+(j*sampleZoom);
if (sample->isLoopable() && (scaledPos>=sample->loopStart && scaledPos<=sample->loopEnd)) {
data[i*availX+j]=bgColorLoop;
data[ij++]=bgColorLoop;
} 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++) {
if (xCoarse>=sample->samples) break;
int y1, y2;
int candMin=INT_MAX;
int candMax=INT_MIN;
int totalAdvance=0;
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 {
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;
if (xFine>=16777216) {
@ -1157,27 +1164,44 @@ void FurnaceGUI::drawSampleEdit() {
totalAdvance+=xAdvanceCoarse;
if (xCoarse>=sample->samples) break;
do {
if (xCoarse>=sample->samples) break;
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 {
y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536;
}
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 (candMin>sample->data16[xCoarse]) candMin=sample->data16[xCoarse];
if (candMax<sample->data16[xCoarse]) candMax=sample->data16[xCoarse];
}
if (totalAdvance>0) xCoarse++;
} 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);
delete[] data;
}
updateSampleTex=false;
}

View file

@ -276,6 +276,8 @@ void FurnaceGUI::drawSettings() {
ImVec2 setWindowSize=ImVec2(canvasW,canvasH);
ImGui::SetNextWindowPos(setWindowPos);
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 (!settingsOpen) {
@ -2303,10 +2305,12 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_FLIP_SELECTION);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE_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);
// TODO: collapse/expand pattern and song
KEYBIND_CONFIG_END;
ImGui::TreePop();
}

View file

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