mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-29 07:53:01 +00:00
Merge branch 'opz-per-operator-arp-and-pitch-macros-and-more' of https://github.com/LTVA1/furnace into opz-per-operator-arp-and-pitch-macros-and-more
This commit is contained in:
commit
9a811c87a1
31 changed files with 305 additions and 314 deletions
|
@ -61,7 +61,7 @@ if you need to use more samples, you may change the sample bank using effect `EB
|
|||
due to limitations in some of those sound chips, some restrictions exist:
|
||||
|
||||
- Amiga: maximum frequency is 31469Hz, but anything over 28867 will sound glitchy on hardware. sample lengths and loop will be set to an even number, and your sample can't be longer than 131070.
|
||||
- NES: if on DPCM mode, only a limited selection of frequencies is available, and loop position isn't supported (only entire sample).
|
||||
- NES: if on DPCM mode, only a limited selection of frequencies is available.
|
||||
- SegaPCM: your sample can't be longer than 65535, and the maximum frequency is 31.25KHz.
|
||||
- QSound: your sample can't be longer than 65535, and the loop length shall not be greater than 32767.
|
||||
- ADPCM-A: no looping supported. your samples will play at around 18.518KHz.
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
# Sega Genesis/Mega Drive
|
||||
|
||||
a video game console that showed itself as the first true rival to Nintendo's video game market near-monopoly in the US during the '80s.
|
||||
|
||||
this console is powered by two sound chips: the [Yamaha YM2612](ym2612.md) and [a derivative of the SN76489](sms.md).
|
||||
|
||||
## effects
|
||||
|
||||
- `10xy`: **set LFO parameters.**
|
||||
- `x` toggles the LFO.
|
||||
- `y` sets its speed.
|
||||
- `11xx`: **set feedback of channel.**
|
||||
- `12xx`: **set operator 1 level.**
|
||||
- `13xx`: **set operator 2 level.**
|
||||
- `14xx`: **set operator 3 level.**
|
||||
- `15xx`: **set operator 4 level.**
|
||||
- `16xy`: **set multiplier of operator.**
|
||||
- `x` is the operator (1-4).
|
||||
- `y` is the new MULT value..
|
||||
- `17xx`: **enable PCM channel.**
|
||||
- this only works on channel 6.
|
||||
- _this effect is here for compatibility reasons!_ it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used).
|
||||
- `18xx`: **toggle extended channel 3 mode.**
|
||||
- `0` disables it and `1` enables it.
|
||||
- only in extended channel 3 chip.
|
||||
- `19xx`: **set attack of all operators.**
|
||||
- `1Axx`: **set attack of operator 1.**
|
||||
- `1Bxx`: **set attack of operator 2.**
|
||||
- `1Cxx`: **set attack of operator 3.**
|
||||
- `1Dxx`: **set attack of operator 4.**
|
||||
- `20xy`: **set PSG noise mode.**
|
||||
- `x` controls whether to inherit frequency from PSG channel 3.
|
||||
- `0`: use one of 3 preset frequencies (`C`: A-2; `C#`: A-3; `D`: A-4).
|
||||
- `1`: use frequency of PSG channel 3.
|
||||
- `y` controls whether to select noise or thin pulse.
|
||||
- `0`: thin pulse.
|
||||
- `1`: noise.
|
||||
|
||||
|
||||
|
||||
## system modes
|
||||
|
||||
## extended channel 3
|
||||
|
||||
in ExtCh mode, channel 3 is split into one column for each of its four operators. feedback and LFO levels are shared. the frequency of each operator may be controlled independently with notes and effects. this can be used for more polyphony or more complex sounds.
|
||||
|
||||
all four operators are still combined according to the algorithm in use. for example, algorithm 7 acts as four independent sine waves. algorithm 4 acts as two independent 2-op sounds. even with algorithm 0, placing a note in any operator triggers that operator alone.
|
||||
|
||||
## CSM
|
||||
|
||||
CSM is short for "Composite Sinusoidal Modeling". CSM works by sending key-on and key-off commands to channel 3 at a specific frequency, controlled by the added "CSM Timer" channel. this can be used to create vocal formants (speech synthesis!) or other complex effects.
|
||||
|
||||
CSM is beyond the scope of this documentation. for more information, see this [brief SSG-EG and CSM video tutorial](https://www.youtube.com/watch?v=IKOR0TUlnWU).
|
||||
|
||||
## DualPCM
|
||||
|
||||
[info here.](ym2612.md)
|
||||
|
||||
## Sega CD
|
||||
|
||||
this isn't a mode so much as a chip configuration. it adds the [Ricoh RF5C68](ricoh.md) found in the Sega CD add-on, providing 8 channels of PCM.
|
||||
|
||||
## chip config
|
||||
|
||||
see [YM2612](ym2612.md) and [SN76489](sms.md) for respective configuration.
|
|
@ -68,6 +68,9 @@ in most arcade boards the chip was used in combination with a PCM chip, like [Se
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## info
|
||||
|
||||
|
|
|
@ -99,6 +99,9 @@ several variants of this chip were released as well, with more features.
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## extended channel 3
|
||||
|
||||
|
|
|
@ -99,6 +99,9 @@ the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## extended channel 3
|
||||
|
||||
|
|
|
@ -97,6 +97,9 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and [2 differen
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## extended channel 2
|
||||
|
||||
|
|
|
@ -96,6 +96,9 @@ it is backward compatible with the original chip.
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## extended channel 3
|
||||
|
||||
|
|
|
@ -82,6 +82,9 @@ thanks to the Z80 sound CPU, DualPCM can play two samples at once! this mode spl
|
|||
- `5Dxx`: **set D2R/SR of operator 2.**
|
||||
- `5Exx`: **set D2R/SR of operator 3.**
|
||||
- `5Fxx`: **set D2R/SR of operator 4.**
|
||||
- `60xx`: **set operator mask.**
|
||||
- `xx` goes from `0` to `F`. it is a bitfield.
|
||||
- each bit corresponds to an operator.
|
||||
|
||||
## info
|
||||
|
||||
|
|
|
@ -260,6 +260,7 @@ size | description
|
|||
| - 0xe4: µPD1771C - 1 channel (UNAVAILABLE)
|
||||
| - 0xf0: SID2 - 3 channels
|
||||
| - 0xf1: 5E01 - 5 channels
|
||||
| - 0xf5: SID3 - 7 channels
|
||||
| - 0xfc: Pong - 1 channel
|
||||
| - 0xfd: Dummy System - 8 channels
|
||||
| - 0xfe: reserved for development
|
||||
|
|
|
@ -266,6 +266,8 @@ enum DivDispatchCmds {
|
|||
|
||||
DIV_CMD_FDS_MOD_AUTO,
|
||||
|
||||
DIV_CMD_FM_OPMASK, // (mask)
|
||||
|
||||
DIV_CMD_MAX
|
||||
};
|
||||
|
||||
|
|
|
@ -203,11 +203,9 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivEngine::findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, int& length)
|
||||
{
|
||||
if (curSubSong!=NULL)
|
||||
{
|
||||
curSubSong->findLength(loopOrder, loopRow, fadeoutLen, rowsForFadeout, hasFFxx, orders, song.grooves, length, chans, song.jumpTreatment, song.ignoreJumpAtEnd);
|
||||
void DivEngine::findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, int& length) {
|
||||
if (curSubSong!=NULL) {
|
||||
curSubSong->findLength(loopOrder,loopRow,fadeoutLen,rowsForFadeout,hasFFxx,orders,song.grooves,length,chans,song.jumpTreatment,song.ignoreJumpAtEnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -475,7 +475,7 @@ class DivEngine {
|
|||
int midiOutTimeRate;
|
||||
float midiVolExp;
|
||||
int softLockCount;
|
||||
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, curExportChan /*for per-channel export progress*/, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
|
||||
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, curExportChan, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
|
||||
size_t curSubSongIndex;
|
||||
size_t bufferPos;
|
||||
double divider;
|
||||
|
@ -1015,19 +1015,19 @@ class DivEngine {
|
|||
// get how many loops is left
|
||||
void getLoopsLeft(int& loops);
|
||||
|
||||
//get how many loops in total export needs to do
|
||||
// get how many loops in total export needs to do
|
||||
void getTotalLoops(int& loops);
|
||||
|
||||
// get current position in song
|
||||
void getCurSongPos(int& row, int& order);
|
||||
|
||||
//get how many files export needs to create
|
||||
// get how many files export needs to create
|
||||
void getTotalAudioFiles(int& files);
|
||||
|
||||
//get which file is processed right now (progress for e.g. per-channel export)
|
||||
// get which file is processed right now (progress for e.g. per-channel export)
|
||||
void getCurFileIndex(int& file);
|
||||
|
||||
//get fadeout state
|
||||
// get fadeout state
|
||||
bool getIsFadingOut();
|
||||
|
||||
// add instrument
|
||||
|
|
|
@ -761,6 +761,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
immWrite(0x19,0x80|pmDepth);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -1463,6 +1463,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
if (c.chan>=6) break;
|
||||
chan[c.chan].hardReset=c.value;
|
||||
|
|
|
@ -28,7 +28,7 @@ struct _nla_table nla_table;
|
|||
|
||||
#define CHIP_DIVIDER 16
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite((a),v)); if (dumpWrites) {addWrite((a),v);} }
|
||||
|
||||
const char* regCheatSheetNES[]={
|
||||
"S0Volume", "4000",
|
||||
|
@ -86,10 +86,10 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
|||
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \
|
||||
if (dacAntiClickOn && dacAntiClick<next) { \
|
||||
dacAntiClick+=8; \
|
||||
rWrite(0x4011,dacAntiClick); \
|
||||
doWrite(0x4011,dacAntiClick); \
|
||||
} else { \
|
||||
dacAntiClickOn=false; \
|
||||
rWrite(0x4011,next); \
|
||||
doWrite(0x4011,next); \
|
||||
} \
|
||||
} \
|
||||
dacPos++; \
|
||||
|
@ -108,6 +108,13 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
|||
void DivPlatformNES::acquire_puNES(short** buf, size_t len) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
doPCM;
|
||||
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
doWrite(w.addr,w.val);
|
||||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
apu_tick(nes,NULL);
|
||||
nes->apu.odd_cycle=!nes->apu.odd_cycle;
|
||||
|
@ -134,6 +141,13 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) {
|
|||
int out2[2];
|
||||
for (size_t i=0; i<len; i++) {
|
||||
doPCM;
|
||||
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
doWrite(w.addr,w.val);
|
||||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
nes1_NP->Tick(8);
|
||||
nes2_NP->TickFrameSequence(8);
|
||||
|
@ -297,7 +311,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
if (chan[i].sweepChanged) {
|
||||
chan[i].sweepChanged=false;
|
||||
if (i==0) {
|
||||
//rWrite(16+i*5,chan[i].sweep);
|
||||
// rWrite(16+i*5,chan[i].sweep);
|
||||
}
|
||||
}
|
||||
if (i<3) if (chan[i].std.phaseReset.had) {
|
||||
|
@ -338,7 +352,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
//rWrite(16+i*5+2,8);
|
||||
// rWrite(16+i*5+2,8);
|
||||
if (i==2) { // triangle
|
||||
rWrite(0x4000+i*4,0x00);
|
||||
} else {
|
||||
|
@ -400,6 +414,34 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
logV("switching bank to %d",dpcmBank);
|
||||
if (dumpWrites) addWrite(0xffff0004,dpcmBank);
|
||||
}
|
||||
|
||||
// sample custom loop point...
|
||||
DivSample* lsamp=parent->getSample(dacSample);
|
||||
|
||||
// how it works:
|
||||
// when the initial sample info is written (see above) and playback is launched,
|
||||
// the parameters (start point in memory and length) are locked until sample end
|
||||
// is reached.
|
||||
|
||||
// thus, if we write new data after just several APU clock cycles, it will be used only when
|
||||
// sample finishes one full loop.
|
||||
|
||||
// thus we can write sample's loop point as "start address" and sample's looped part length
|
||||
// as "full sample length".
|
||||
|
||||
// APU will play full sample once and then repeatedly cycle through the looped part.
|
||||
|
||||
// sources:
|
||||
// https://www.nesdev.org/wiki/APU_DMC
|
||||
// https://www.youtube.com/watch?v=vB4P8x2Am6Y
|
||||
|
||||
if (lsamp->loopEnd>lsamp->loopStart && goingToLoop) {
|
||||
int loopStartAddr=(sampleOffDPCM[dacSample]+lsamp->loopStart)>>3;
|
||||
int loopLen=(lsamp->loopEnd-lsamp->loopStart)>>3;
|
||||
|
||||
rWrite(0x4012,(loopStartAddr>>6)&0xff);
|
||||
rWrite(0x4013,(loopLen>>4)&0xff);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (nextDPCMFreq>=0) {
|
||||
|
@ -790,6 +832,7 @@ float DivPlatformNES::getPostAmp() {
|
|||
}
|
||||
|
||||
void DivPlatformNES::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
for (int i=0; i<5; i++) {
|
||||
chan[i]=DivPlatformNES::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include "sound/nes_nsfplay/nes_apu.h"
|
||||
#include "sound/nes_nsfplay/5e01_apu.h"
|
||||
#include "../../fixedQueue.h"
|
||||
|
||||
class DivPlatformNES: public DivDispatch {
|
||||
struct Channel: public SharedChannel<signed char> {
|
||||
|
@ -44,6 +45,13 @@ class DivPlatformNES: public DivDispatch {
|
|||
Channel chan[5];
|
||||
DivDispatchOscBuffer* oscBuf[5];
|
||||
bool isMuted[5];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
QueuedWrite(): addr(0), val(0) {}
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
FixedQueue<QueuedWrite,128> writes;
|
||||
int dacPeriod, dacRate, dpcmPos;
|
||||
unsigned int dacPos, dacAntiClick;
|
||||
int dacSample;
|
||||
|
|
|
@ -135,19 +135,12 @@ static short const gauss [512] =
|
|||
1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,
|
||||
};
|
||||
|
||||
void SPC_DSP::setupInterpolation(bool interpolate)
|
||||
{
|
||||
for(int i = 0; i < voice_count; i++)
|
||||
{
|
||||
m.voices[i].interpolate = interpolate;
|
||||
}
|
||||
}
|
||||
void SPC_DSP::setupInterpolation(bool interpolate){for(int i=0;i<voice_count;i++){m.voices[i].interpolate=interpolate;}}
|
||||
|
||||
inline int SPC_DSP::interpolate( voice_t const* v )
|
||||
{
|
||||
// Make pointers into gaussian based on fractional position between samples
|
||||
if(v->interpolate)
|
||||
{
|
||||
if (v->interpolate) {
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
|
@ -163,9 +156,7 @@ inline int SPC_DSP::interpolate( voice_t const* v )
|
|||
CLAMP16( out );
|
||||
out &= ~1;
|
||||
return out;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return v->buf [(v->interp_pos >> 12) + v->buf_pos]; //Furnace addition -- no interpolation
|
||||
}
|
||||
}
|
||||
|
|
|
@ -916,6 +916,9 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
|||
immWrite(0x17,0x80|pmDepth);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
// TODO: if OPZ supports op mask
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -1004,6 +1004,13 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -1537,6 +1537,13 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -1507,6 +1507,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -1576,6 +1576,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
@ -264,7 +264,9 @@ const char* cmdName[]={
|
|||
"BIFURCATOR_STATE_LOAD",
|
||||
"BIFURCATOR_PARAMETER",
|
||||
|
||||
"FDS_MOD_AUTO"
|
||||
"FDS_MOD_AUTO",
|
||||
|
||||
"FM_OPMASK"
|
||||
};
|
||||
|
||||
static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!");
|
||||
|
|
|
@ -102,9 +102,8 @@ bool DivSubSong::walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int
|
|||
return false;
|
||||
}
|
||||
|
||||
double calcRowLenInSeconds(const DivGroovePattern& speeds, float hz, int vN, int vD, int timeBaseFromSong)
|
||||
{
|
||||
double hl=1; //count for 1 row
|
||||
double calcRowLenInSeconds(const DivGroovePattern& speeds, float hz, int vN, int vD, int timeBaseFromSong) {
|
||||
double hl=1; //count for 1 row
|
||||
if (hl<=0.0) hl=4.0;
|
||||
double timeBase=timeBaseFromSong+1;
|
||||
double speedSum=0;
|
||||
|
@ -115,25 +114,23 @@ double calcRowLenInSeconds(const DivGroovePattern& speeds, float hz, int vN, int
|
|||
if (timeBase<1.0) timeBase=1.0;
|
||||
if (speedSum<1.0) speedSum=1.0;
|
||||
if (vD<1) vD=1;
|
||||
//return (60.0 * hz / (timeBase * hl * speedSum)) * (double)vN / (double)vD;
|
||||
return 1.0 / ((60.0*hz/(timeBase*hl*speedSum))*(double)vN/(double)vD / 60.0);
|
||||
return 1.0/((60.0*hz/(timeBase*hl*speedSum))*(double)vN/(double)vD/60.0);
|
||||
}
|
||||
|
||||
void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders_vec, std::vector<DivGroovePattern>& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat)
|
||||
{
|
||||
length = 0;
|
||||
hasFFxx = false;
|
||||
rowsForFadeout = 0;
|
||||
void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders_vec, std::vector<DivGroovePattern>& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat) {
|
||||
length=0;
|
||||
hasFFxx=false;
|
||||
rowsForFadeout=0;
|
||||
|
||||
float secondsPerThisRow = 0.0f;
|
||||
float secondsPerThisRow=0.0f;
|
||||
|
||||
DivGroovePattern curSpeeds = speeds; //simulate that we are playing the song, track all speed/BPM/tempo/engine rate changes
|
||||
short curVirtualTempoN = virtualTempoN;
|
||||
short curVirtualTempoD = virtualTempoD;
|
||||
float curHz = hz;
|
||||
double curDivider = (double)timeBase;
|
||||
DivGroovePattern curSpeeds=speeds; //simulate that we are playing the song, track all speed/BPM/tempo/engine rate changes
|
||||
short curVirtualTempoN=virtualTempoN;
|
||||
short curVirtualTempoD=virtualTempoD;
|
||||
float curHz=hz;
|
||||
double curDivider=(double)timeBase;
|
||||
|
||||
double curLen = 0.0; //how many seconds passed since the start of song
|
||||
double curLen=0.0; //how many seconds passed since the start of song
|
||||
|
||||
int nextOrder=-1;
|
||||
int nextRow=0;
|
||||
|
@ -145,49 +142,39 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int&
|
|||
if (firstPat>0) {
|
||||
memset(wsWalked,255,32*firstPat);
|
||||
}
|
||||
for (int i=firstPat; i<ordersLen; i++)
|
||||
{
|
||||
bool jumped = false;
|
||||
for (int i=firstPat; i<ordersLen; i++) {
|
||||
bool jumped=false;
|
||||
|
||||
for (int j=0; j<chans; j++)
|
||||
{
|
||||
for (int j=0; j<chans; j++) {
|
||||
subPat[j]=pat[j].getPattern(orders.ord[j][i],false);
|
||||
}
|
||||
if (i>lastSuspectedLoopEnd)
|
||||
{
|
||||
if (i>lastSuspectedLoopEnd) {
|
||||
lastSuspectedLoopEnd=i;
|
||||
}
|
||||
for (int j=nextRow; j<patLen; j++)
|
||||
{
|
||||
for (int j=nextRow; j<patLen; j++) {
|
||||
nextRow=0;
|
||||
bool changingOrder=false;
|
||||
bool jumpingOrder=false;
|
||||
if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7)))
|
||||
{
|
||||
if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) {
|
||||
return;
|
||||
}
|
||||
for (int k=0; k<chans; k++)
|
||||
{
|
||||
for (int l=0; l<pat[k].effectCols; l++)
|
||||
{
|
||||
for (int k=0; k<chans; k++) {
|
||||
for (int l=0; l<pat[k].effectCols; l++) {
|
||||
effectVal=subPat[k]->data[j][5+(l<<1)];
|
||||
if (effectVal<0) effectVal=0;
|
||||
|
||||
if (subPat[k]->data[j][4+(l<<1)]==0xff)
|
||||
{
|
||||
hasFFxx = true;
|
||||
if (subPat[k]->data[j][4+(l<<1)]==0xff) {
|
||||
hasFFxx=true;
|
||||
|
||||
//FFxx makes YOU SHALL NOT PASS!!! move
|
||||
orders_vec.push_back(j + 1); //order len
|
||||
length += j + 1; //add length of order to song length
|
||||
// FFxx makes YOU SHALL NOT PASS!!! move
|
||||
orders_vec.push_back(j+1); // order len
|
||||
length+=j+1; // add length of order to song length
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch(subPat[k]->data[j][4+(l<<1)]) //track speed/BMP/Hz/tempo changes
|
||||
{
|
||||
case 0x09: // select groove pattern/speed 1
|
||||
{
|
||||
switch (subPat[k]->data[j][4+(l<<1)]) {
|
||||
case 0x09: { // select groove pattern/speed 1
|
||||
if (grooves.empty()) {
|
||||
if (effectVal>0) curSpeeds.val[0]=effectVal;
|
||||
} else {
|
||||
|
@ -198,8 +185,7 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int&
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 0x0f: // speed 1/speed 2
|
||||
{
|
||||
case 0x0f: { // speed 1/speed 2
|
||||
if (curSpeeds.len==2 && grooves.empty()) {
|
||||
if (effectVal>0) curSpeeds.val[1]=effectVal;
|
||||
} else {
|
||||
|
@ -207,68 +193,47 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int&
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 0xfd: // virtual tempo num
|
||||
{
|
||||
case 0xfd: { // virtual tempo num
|
||||
if (effectVal>0) curVirtualTempoN=effectVal;
|
||||
break;
|
||||
}
|
||||
case 0xfe: // virtual tempo den
|
||||
{
|
||||
case 0xfe: { // virtual tempo den
|
||||
if (effectVal>0) curVirtualTempoD=effectVal;
|
||||
break;
|
||||
}
|
||||
case 0xf0: // set Hz by tempo (set bpm)
|
||||
{
|
||||
case 0xf0: { // set Hz by tempo (set bpm)
|
||||
curDivider=(double)effectVal*2.0/5.0;
|
||||
if (curDivider<1) curDivider=1;
|
||||
//cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider;
|
||||
//clockDrift=0;
|
||||
//subticks=0;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (subPat[k]->data[j][4+(l<<1)]==0x0d)
|
||||
{
|
||||
if (jumpTreatment==2)
|
||||
{
|
||||
if ((i<ordersLen-1 || !ignoreJumpAtEnd))
|
||||
{
|
||||
if (subPat[k]->data[j][4+(l<<1)]==0x0d) {
|
||||
if (jumpTreatment==2) {
|
||||
if ((i<ordersLen-1 || !ignoreJumpAtEnd)) {
|
||||
nextOrder=i+1;
|
||||
nextRow=effectVal;
|
||||
jumpingOrder=true;
|
||||
}
|
||||
}
|
||||
else if (jumpTreatment==1)
|
||||
{
|
||||
if (nextOrder==-1 && (i<ordersLen-1 || !ignoreJumpAtEnd))
|
||||
{
|
||||
} else if (jumpTreatment==1) {
|
||||
if (nextOrder==-1 && (i<ordersLen-1 || !ignoreJumpAtEnd)) {
|
||||
nextOrder=i+1;
|
||||
nextRow=effectVal;
|
||||
jumpingOrder=true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((i<ordersLen-1 || !ignoreJumpAtEnd))
|
||||
{
|
||||
if (!changingOrder)
|
||||
{
|
||||
} else {
|
||||
if ((i<ordersLen-1 || !ignoreJumpAtEnd)) {
|
||||
if (!changingOrder) {
|
||||
nextOrder=i+1;
|
||||
}
|
||||
jumpingOrder=true;
|
||||
nextRow=effectVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (subPat[k]->data[j][4+(l<<1)]==0x0b)
|
||||
{
|
||||
if (nextOrder==-1 || jumpTreatment==0)
|
||||
{
|
||||
} else if (subPat[k]->data[j][4+(l<<1)]==0x0b) {
|
||||
if (nextOrder==-1 || jumpTreatment==0) {
|
||||
nextOrder=effectVal;
|
||||
if (jumpTreatment==1 || jumpTreatment==2 || !jumpingOrder)
|
||||
{
|
||||
if (jumpTreatment==1 || jumpTreatment==2 || !jumpingOrder) {
|
||||
nextRow=0;
|
||||
}
|
||||
changingOrder=true;
|
||||
|
@ -277,32 +242,29 @@ void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int&
|
|||
}
|
||||
}
|
||||
|
||||
if(i > loopOrder || (i == loopOrder && j > loopRow))
|
||||
{
|
||||
if(curLen <= fadeoutLen && fadeoutLen > 0.0) //we count each row fadeout lasts. When our time is greater than fadeout length we successfully counted the number of fadeout rows
|
||||
{
|
||||
secondsPerThisRow = calcRowLenInSeconds(speeds, curHz, curVirtualTempoN, curVirtualTempoD, curDivider);
|
||||
curLen += secondsPerThisRow;
|
||||
if (i>loopOrder || (i==loopOrder && j>loopRow)) {
|
||||
// we count each row fadeout lasts. When our time is greater than fadeout length we successfully counted the number of fadeout rows
|
||||
if (curLen<=fadeoutLen && fadeoutLen>0.0) {
|
||||
secondsPerThisRow=calcRowLenInSeconds(speeds,curHz,curVirtualTempoN,curVirtualTempoD,curDivider);
|
||||
curLen+=secondsPerThisRow;
|
||||
rowsForFadeout++;
|
||||
}
|
||||
}
|
||||
|
||||
wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7);
|
||||
|
||||
if (nextOrder!=-1)
|
||||
{
|
||||
if (nextOrder!=-1) {
|
||||
i=nextOrder-1;
|
||||
orders_vec.push_back(j + 1); //order len
|
||||
length += j + 1; //add length of order to song length
|
||||
jumped = true;
|
||||
orders_vec.push_back(j+1); // order len
|
||||
length+=j+1; // add length of order to song length
|
||||
jumped=true;
|
||||
nextOrder=-1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!jumped) //if no jump occured we add full pattern length
|
||||
{
|
||||
orders_vec.push_back(patLen); //order len
|
||||
length += patLen; //add length of order to song length
|
||||
if (!jumped) { // if no jump occured we add full pattern length
|
||||
orders_vec.push_back(patLen); // order len
|
||||
length+=patLen; // add length of order to song length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ struct DivSubSong {
|
|||
bool walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0);
|
||||
|
||||
/**
|
||||
* find song length in rows (up to specified loop point). Also find length of every row
|
||||
* find song length in rows (up to specified loop point).
|
||||
*/
|
||||
void findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, std::vector<DivGroovePattern>& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0);
|
||||
|
||||
|
|
|
@ -504,6 +504,7 @@ void DivEngine::registerSystems() {
|
|||
{0x5d, {DIV_CMD_FM_D2R, _("5Dxx: Set decay 2 of operator 2 (0 to 1F)"), constVal<1>, effectValAnd<31>}},
|
||||
{0x5e, {DIV_CMD_FM_D2R, _("5Exx: Set decay 2 of operator 3 (0 to 1F)"), constVal<2>, effectValAnd<31>}},
|
||||
{0x5f, {DIV_CMD_FM_D2R, _("5Fxx: Set decay 2 of operator 4 (0 to 1F)"), constVal<3>, effectValAnd<31>}},
|
||||
{0x60, {DIV_CMD_FM_OPMASK, _("60xx: Set operator mask (bits 0-3)")}},
|
||||
};
|
||||
|
||||
EffectHandlerMap fmOPMPostEffectHandlerMap(fmOPNPostEffectHandlerMap);
|
||||
|
@ -514,6 +515,7 @@ void DivEngine::registerSystems() {
|
|||
{0x1e, {DIV_CMD_FM_AM_DEPTH, _("1Exx: Set AM depth (0 to 7F)"), effectValAnd<127>}},
|
||||
{0x1f, {DIV_CMD_FM_PM_DEPTH, _("1Fxx: Set PM depth (0 to 7F)"), effectValAnd<127>}},
|
||||
{0x55, {DIV_CMD_FM_SSG, _("55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)"), effectOpVal<4>, effectValAnd<3>}},
|
||||
{0x60, {DIV_CMD_FM_OPMASK, _("60xx: Set operator mask (bits 0-3)")}},
|
||||
});
|
||||
|
||||
EffectHandlerMap fmOPZPostEffectHandlerMap(fmOPMPostEffectHandlerMap);
|
||||
|
|
|
@ -33,79 +33,70 @@ bool DivEngine::isExporting() {
|
|||
return exporting;
|
||||
}
|
||||
|
||||
void DivEngine::getLoopsLeft(int& loops) {
|
||||
if(totalLoops < 0 || exportLoopCount == 0)
|
||||
{
|
||||
loops = 0;
|
||||
void DivEngine::getLoopsLeft(int &loops) {
|
||||
if (totalLoops<0 || exportLoopCount==0) {
|
||||
loops=0;
|
||||
return;
|
||||
}
|
||||
loops = exportLoopCount - 1 - totalLoops;
|
||||
loops=exportLoopCount-1-totalLoops;
|
||||
}
|
||||
|
||||
void DivEngine::getTotalLoops(int& loops) {
|
||||
loops = exportLoopCount - 1;
|
||||
void DivEngine::getTotalLoops(int &loops) {
|
||||
loops=exportLoopCount-1;
|
||||
}
|
||||
|
||||
void DivEngine::getCurSongPos(int& row, int& order) {
|
||||
row = curRow;
|
||||
order = curOrder;
|
||||
void DivEngine::getCurSongPos(int &row, int &order) {
|
||||
row=curRow;
|
||||
order=curOrder;
|
||||
}
|
||||
|
||||
void DivEngine::getTotalAudioFiles(int& files)
|
||||
{
|
||||
files = 0;
|
||||
void DivEngine::getTotalAudioFiles(int &files) {
|
||||
files=0;
|
||||
|
||||
switch(exportMode)
|
||||
{
|
||||
case DIV_EXPORT_MODE_ONE:
|
||||
{
|
||||
files = 1;
|
||||
switch (exportMode) {
|
||||
case DIV_EXPORT_MODE_ONE: {
|
||||
files=1;
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_SYS:
|
||||
{
|
||||
files = 1; //there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file
|
||||
case DIV_EXPORT_MODE_MANY_SYS: {
|
||||
files=1; // there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_CHAN:
|
||||
{
|
||||
for(int i = 0; i < chans; i++)
|
||||
{
|
||||
if (exportChannelMask[i]) files++;
|
||||
case DIV_EXPORT_MODE_MANY_CHAN: {
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (exportChannelMask[i]) {
|
||||
files++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DivEngine::getCurFileIndex(int& file)
|
||||
{
|
||||
file = 0;
|
||||
void DivEngine::getCurFileIndex(int &file) {
|
||||
file=0;
|
||||
|
||||
switch(exportMode)
|
||||
{
|
||||
case DIV_EXPORT_MODE_ONE:
|
||||
{
|
||||
file = 0;
|
||||
switch (exportMode) {
|
||||
case DIV_EXPORT_MODE_ONE: {
|
||||
file=0;
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_SYS:
|
||||
{
|
||||
file = 0; //there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file
|
||||
case DIV_EXPORT_MODE_MANY_SYS: {
|
||||
file=0; // there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_CHAN:
|
||||
{
|
||||
file = curExportChan;
|
||||
case DIV_EXPORT_MODE_MANY_CHAN: {
|
||||
file=curExportChan;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool DivEngine::getIsFadingOut()
|
||||
{
|
||||
bool DivEngine::getIsFadingOut() {
|
||||
return isFadingOut;
|
||||
}
|
||||
|
||||
|
@ -327,7 +318,7 @@ void DivEngine::runExportThread() {
|
|||
// take control of audio output
|
||||
deinitAudioBackend();
|
||||
|
||||
curExportChan = 0;
|
||||
curExportChan=0;
|
||||
|
||||
float* outBuf[DIV_MAX_OUTPUTS];
|
||||
float* outBufFinal;
|
||||
|
@ -463,7 +454,7 @@ void DivEngine::runExportThread() {
|
|||
}
|
||||
logI("done!");
|
||||
exporting=false;
|
||||
curExportChan = 0;
|
||||
curExportChan=0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -302,6 +302,7 @@ const char* aboutLine[]={
|
|||
_N("NDS sound emulator by cam900"),
|
||||
"",
|
||||
_N("greetings to:"),
|
||||
"floxy!",
|
||||
"NEOART Costa Rica",
|
||||
"Xenium Demoparty",
|
||||
"@party",
|
||||
|
|
120
src/gui/gui.cpp
120
src/gui/gui.cpp
|
@ -2589,34 +2589,32 @@ void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
|
|||
int loopEnd=0;
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
|
||||
e->findSongLength(loopOrder, loopRow, audioExportOptions.fadeOut, songFadeoutSectionLength, songHasSongEndCommand, songOrdersLengths, songLength); //for progress estimation
|
||||
e->findSongLength(loopOrder,loopRow,audioExportOptions.fadeOut,songFadeoutSectionLength,songHasSongEndCommand,songOrdersLengths,songLength); // for progress estimation
|
||||
|
||||
songLoopedSectionLength = songLength;
|
||||
for(int i = 0; i < loopOrder; i++)
|
||||
{
|
||||
songLoopedSectionLength -= songOrdersLengths[i];
|
||||
songLoopedSectionLength=songLength;
|
||||
for (int i=0; i<loopOrder; i++) {
|
||||
songLoopedSectionLength-=songOrdersLengths[i];
|
||||
}
|
||||
songLoopedSectionLength -= loopRow;
|
||||
songLoopedSectionLength-=loopRow;
|
||||
|
||||
e->saveAudio(path.c_str(),audioExportOptions);
|
||||
|
||||
totalFiles = 0;
|
||||
totalFiles=0;
|
||||
e->getTotalAudioFiles(totalFiles);
|
||||
int totalLoops = 0;
|
||||
int totalLoops=0;
|
||||
|
||||
lengthOfOneFile = songLength;
|
||||
lengthOfOneFile=songLength;
|
||||
|
||||
if(!songHasSongEndCommand)
|
||||
{
|
||||
if (!songHasSongEndCommand) {
|
||||
e->getTotalLoops(totalLoops);
|
||||
|
||||
lengthOfOneFile += songLoopedSectionLength * totalLoops;
|
||||
lengthOfOneFile += songFadeoutSectionLength; //account for fadeout
|
||||
lengthOfOneFile+=songLoopedSectionLength*totalLoops;
|
||||
lengthOfOneFile+=songFadeoutSectionLength; // account for fadeout
|
||||
}
|
||||
|
||||
totalLength = lengthOfOneFile * totalFiles;
|
||||
totalLength=lengthOfOneFile*totalFiles;
|
||||
|
||||
curProgress = 0.0f;
|
||||
curProgress=0.0f;
|
||||
|
||||
displayExporting=true;
|
||||
}
|
||||
|
@ -5861,69 +5859,53 @@ bool FurnaceGUI::loop() {
|
|||
MEASURE_BEGIN(popup);
|
||||
|
||||
centerNextWindow(_("Rendering..."),canvasW,canvasH);
|
||||
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) {
|
||||
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove)) {
|
||||
// WHAT the HELL?!
|
||||
WAKE_UP;
|
||||
if(audioExportOptions.mode != DIV_EXPORT_MODE_MANY_CHAN)
|
||||
{
|
||||
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_CHAN) {
|
||||
ImGui::Text(_("Please wait..."));
|
||||
}
|
||||
float* progressLambda = &curProgress;
|
||||
int curPosInRows = 0;
|
||||
int* curPosInRowsLambda = &curPosInRows;
|
||||
int loopsLeft = 0;
|
||||
int* loopsLeftLambda = &loopsLeft;
|
||||
int totalLoops = 0;
|
||||
int* totalLoopsLambda = &totalLoops;
|
||||
int curFile = 0;
|
||||
int* curFileLambda = &curFile;
|
||||
if(e->isExporting())
|
||||
{
|
||||
e->lockEngine([this, progressLambda, curPosInRowsLambda, curFileLambda, loopsLeftLambda, totalLoopsLambda]()
|
||||
{
|
||||
int curRow = 0;
|
||||
int curOrder = 0;
|
||||
e->getCurSongPos(curRow, curOrder);
|
||||
*curFileLambda = 0;
|
||||
e->getCurFileIndex(*curFileLambda);
|
||||
float* progressLambda=&curProgress;
|
||||
int curPosInRows=0;
|
||||
int* curPosInRowsLambda=&curPosInRows;
|
||||
int loopsLeft=0;
|
||||
int* loopsLeftLambda=&loopsLeft;
|
||||
int totalLoops=0;
|
||||
int* totalLoopsLambda=&totalLoops;
|
||||
int curFile=0;
|
||||
int* curFileLambda=&curFile;
|
||||
if (e->isExporting()) {
|
||||
e->lockEngine([this, progressLambda, curPosInRowsLambda, curFileLambda,
|
||||
loopsLeftLambda, totalLoopsLambda] () {
|
||||
int curRow=0; int curOrder=0;
|
||||
e->getCurSongPos(curRow, curOrder); *curFileLambda=0;
|
||||
e->getCurFileIndex(*curFileLambda);
|
||||
*curPosInRowsLambda=curRow; for (int i=0; i<curOrder;
|
||||
i++) {
|
||||
*curPosInRowsLambda+=songOrdersLengths[i];}
|
||||
|
||||
*curPosInRowsLambda = curRow;
|
||||
|
||||
for(int i = 0; i < curOrder; i++)
|
||||
{
|
||||
*curPosInRowsLambda += songOrdersLengths[i];
|
||||
}
|
||||
|
||||
if(!songHasSongEndCommand)
|
||||
{
|
||||
e->getLoopsLeft(*loopsLeftLambda);
|
||||
e->getTotalLoops(*totalLoopsLambda);
|
||||
|
||||
if((*totalLoopsLambda) != (*loopsLeftLambda)) //we are going 2nd, 3rd, etc. time through the song
|
||||
{
|
||||
*curPosInRowsLambda -= (songLength - songLoopedSectionLength); //a hack so progress bar does not jump?
|
||||
}
|
||||
if(e->getIsFadingOut()) //we are in fadeout??? why it works like that bruh
|
||||
{
|
||||
*curPosInRowsLambda -= (songLength - songLoopedSectionLength); //a hack so progress bar does not jump?
|
||||
}
|
||||
}
|
||||
|
||||
*progressLambda = (float)((*curPosInRowsLambda) +
|
||||
((*totalLoopsLambda) - (*loopsLeftLambda)) * songLength +
|
||||
lengthOfOneFile * (*curFileLambda))
|
||||
/ (float)totalLength;
|
||||
});
|
||||
if (!songHasSongEndCommand) {
|
||||
e->getLoopsLeft(*loopsLeftLambda); e->getTotalLoops(*totalLoopsLambda); if ((*totalLoopsLambda)!=(*loopsLeftLambda)) //we are going 2nd, 3rd, etc. time through the song
|
||||
{
|
||||
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump?
|
||||
}
|
||||
if (e->getIsFadingOut()) //we are in fadeout??? why it works like that bruh
|
||||
{
|
||||
// LIVE WITH IT damn it
|
||||
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump?
|
||||
}
|
||||
}
|
||||
// this horrible indentation courtesy of `indent`
|
||||
*progressLambda=(float) ((*curPosInRowsLambda) + ((*totalLoopsLambda)- (*loopsLeftLambda)) * songLength + lengthOfOneFile * (*curFileLambda)) / (float) totalLength;});
|
||||
}
|
||||
|
||||
ImGui::Text(_("Row %d of %d"), curPosInRows +
|
||||
((totalLoops) - (loopsLeft)) * songLength, lengthOfOneFile);
|
||||
ImGui::Text(_("Row %d of %d"),curPosInRows+((totalLoops)-(loopsLeft))*songLength,lengthOfOneFile);
|
||||
|
||||
if(audioExportOptions.mode == DIV_EXPORT_MODE_MANY_CHAN)
|
||||
{
|
||||
ImGui::Text(_("Channel %d of %d"), curFile + 1, totalFiles);
|
||||
if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) {
|
||||
ImGui::Text(_("Channel %d of %d"),curFile+1,totalFiles);
|
||||
}
|
||||
|
||||
ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0), fmt::sprintf("%.2f%%", curProgress * 100.0f).c_str());
|
||||
ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0),fmt::sprintf("%.2f%%",curProgress*100.0f).c_str());
|
||||
|
||||
if (ImGui::Button(_("Abort"))) {
|
||||
if (e->haltAudioFile()) {
|
||||
|
|
|
@ -1723,13 +1723,13 @@ class FurnaceGUI {
|
|||
char emptyLabel[32];
|
||||
char emptyLabel2[32];
|
||||
|
||||
std::vector<int> songOrdersLengths; //lengths of all orders (for drawing song export progress)
|
||||
int songLength; //length of all the song in rows
|
||||
int songLoopedSectionLength; //length of looped part of the song
|
||||
int songFadeoutSectionLength; //length of fading part of the song
|
||||
bool songHasSongEndCommand; //song has "Song end" command (FFxx)
|
||||
int lengthOfOneFile; //length of one rendering pass. song length times num of loops + fadeout
|
||||
int totalLength; //total length of render (lengthOfOneFile times num of files for per-channel export)
|
||||
std::vector<int> songOrdersLengths; // lengths of all orders (for drawing song export progress)
|
||||
int songLength; // length of all the song in rows
|
||||
int songLoopedSectionLength; // length of looped part of the song
|
||||
int songFadeoutSectionLength; // length of fading part of the song
|
||||
bool songHasSongEndCommand; // song has "Song end" command (FFxx)
|
||||
int lengthOfOneFile; // length of one rendering pass. song length times num of loops + fadeout
|
||||
int totalLength; // total length of render (lengthOfOneFile times num of files for per-channel export)
|
||||
float curProgress;
|
||||
int totalFiles;
|
||||
|
||||
|
|
|
@ -253,16 +253,27 @@ void FurnaceGUI::drawSampleEdit() {
|
|||
SAMPLE_WARN(warnLength,"QSound: maximum sample length is 65535");
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
case DIV_SYSTEM_NES: {
|
||||
if (sample->loop) {
|
||||
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
|
||||
SAMPLE_WARN(warnLoopPos,_("NES: loop point ignored on DPCM (may only loop entire sample)"));
|
||||
if (sample->loopStart&511) {
|
||||
int tryWith=(sample->loopStart)&(~511);
|
||||
if (tryWith>(int)sample->samples) tryWith-=512;
|
||||
String alignHint=fmt::sprintf(_("NES: loop start must be a multiple of 512 (try with %d)"),tryWith);
|
||||
SAMPLE_WARN(warnLoopStart,alignHint);
|
||||
}
|
||||
if ((sample->loopEnd-8)&127) {
|
||||
int tryWith=(sample->loopEnd-8)&(~127);
|
||||
if (tryWith>(int)sample->samples) tryWith-=128;
|
||||
tryWith+=8; // +1 bc of how sample length is treated: https://www.nesdev.org/wiki/APU_DMC
|
||||
String alignHint=fmt::sprintf(_("NES: loop end must be a multiple of 128 (try with %d)"),tryWith);
|
||||
SAMPLE_WARN(warnLoopEnd,alignHint);
|
||||
}
|
||||
}
|
||||
if (sample->samples>32648) {
|
||||
SAMPLE_WARN(warnLength,_("NES: maximum DPCM sample length is 32648"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_X1_010:
|
||||
if (sample->loop) {
|
||||
SAMPLE_WARN(warnLoop,_("X1-010: samples can't loop"));
|
||||
|
|
Loading…
Reference in a new issue