Compare commits

...
Sign in to create a new pull request.

6 commits

Author SHA1 Message Date
Natt Akuma
9ff7439627 Kurumitsu-8L: channel redesign, fix export 2025-09-26 23:30:51 +07:00
Natt Akuma
33fa17ff46 Kurumitsu-8L: channel redesign, remove echo 2025-09-22 04:10:41 +07:00
Natt Akuma
cf4d72dd7c Kurumitsu-8L: add (nonstandard) VGM export 2025-09-18 00:30:51 +07:00
Natt Akuma
1755e5b371 Kurumitsu-8L: add echo and PCM sample 2025-09-18 00:29:39 +07:00
Natt Akuma
8817ebbac3 Kurumitsu-8L: change channel volume handling
In order to keep the same modulation output, it now scales with pan instead of envelope
2025-09-18 00:27:06 +07:00
Natt Akuma
3831bc249c Add Kurumitsu-8L support
No sample playback yet
2025-09-18 00:27:06 +07:00
17 changed files with 903 additions and 56 deletions

View file

@ -815,6 +815,7 @@ src/engine/platform/bifurcator.cpp
src/engine/platform/sid2.cpp
src/engine/platform/sid3.cpp
src/engine/platform/multipcm.cpp
src/engine/platform/kurumitsu8l.cpp
src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp

View file

@ -94,6 +94,7 @@
#include "platform/sid2.h"
#include "platform/sid3.h"
#include "platform/multipcm.h"
#include "platform/kurumitsu8l.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "song.h"
@ -791,6 +792,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_MULTIPCM:
dispatch=new DivPlatformMultiPCM;
break;
case DIV_SYSTEM_KURUMITSU_8L:
dispatch=new DivPlatformKurumitsu8L;
break;
case DIV_SYSTEM_DUMMY:
dispatch=new DivPlatformDummy;
break;

View file

@ -583,7 +583,7 @@ class DivEngine {
void processRow(int i, bool afterDelay);
void nextOrder();
void nextRow();
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable, bool dpcm07, DivDispatch** writeNES, int rateCorrection);
void performVGMWrite(SafeWriter* w, int disIdx, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable, bool dpcm07, DivDispatch** writeNES, int rateCorrection);
// returns true if end of song.
bool nextTick(bool noAccum=false, bool inhibitLowLat=false);
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);

View file

@ -34,6 +34,7 @@ enum DivROMExportOptions {
DIV_ROM_SAP_R,
DIV_ROM_IPOD,
DIV_ROM_GRUB,
DIV_ROM_KMM,
DIV_ROM_MAX
};

View file

@ -1444,6 +1444,8 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
break;
case DIV_INS_UPD1771C:
break;
case DIV_INS_KURUMITSU_8L:
break;
case DIV_INS_MAX:
break;
case DIV_INS_NULL:

View file

@ -99,6 +99,7 @@ enum DivInstrumentType: unsigned short {
DIV_INS_SUPERVISION=64,
DIV_INS_UPD1771C=65,
DIV_INS_SID3=66,
DIV_INS_KURUMITSU_8L=67,
DIV_INS_MAX,
DIV_INS_NULL
};

View file

@ -0,0 +1,660 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "kurumitsu8l.h"
#include "../engine.h"
#include <math.h>
#define chRead(c,a) regPool[(c)*32+(a)]
#define chWrite(c,a,v) if (!skipRegisterWrites) {regPool[(c)*32+(a)]=v; if (dumpWrites) {addWrite((c)*32+(a),v);} }
#define chDWrite(c,a,v) regPool[(c)*32+(a)]=v;
#define CHIP_FREQBASE 67108864
const char* regCheatSheetKurumitsu8L[]={
"CHxWPtr", "00+x*20",
"CHxSPtr", "04+x*20",
"CHxVol", "09+x*20",
"CHxModF", "0A+x*20",
"CHxModI", "0B+x*20",
"CHxPanL", "0C+x*20",
"CHxPanR", "0E+x*20",
"CHxLoopE", "10+x*20",
"CHxLoopL", "12+x*20",
"CHxFreq", "14+x*20",
"CHxAcc", "18+x*20",
"CHxOut", "1C+x*20",
NULL
};
const char** DivPlatformKurumitsu8L::getRegisterSheet() {
return regCheatSheetKurumitsu8L;
}
void DivPlatformKurumitsu8L::acquire(short** buf, size_t len) {
// channel diagram:
// .---[ModF]-------+-[PanL]-> DryL
// V |
// [Freq]->(+)->[Wave]-[Env]-+-[PanR]-> DryR
// ^ |
// Wet ---[ModI]---' '--------> Wet
for (int i=0; i<8; i++) {
oscBuf[i]->begin(len);
}
for (size_t h=0; h<len; h++) {
int outAL=0;
int outAR=0;
int modData=0;
for (int i=0; i<8; i++) {
unsigned char vol=chRead(i,9);
unsigned char fb=chRead(i,10);
unsigned char mod=chRead(i,11);
short panL=chRead(i,12)|((short)chRead(i,13)<<8);
short panR=chRead(i,14)|((short)chRead(i,15)<<8);
unsigned freq=chRead(i,20)|((unsigned)chRead(i,21)<<8)|((unsigned)chRead(i,22)<<16)|((unsigned)chRead(i,23)<<24);
unsigned phase=chRead(i,24)|((unsigned)chRead(i,25)<<8)|((unsigned)chRead(i,26)<<16)|((unsigned)chRead(i,27)<<24);
unsigned loopE=chRead(i,18)|((unsigned)chRead(i,19)<<8);
short fbData1=chRead(i,28)|((short)chRead(i,29)<<8);
short fbData2=chRead(i,30)|((short)chRead(i,31)<<8);
int data=0;
int phase_inc=(((int)fbData1+fbData2)*fb)+((modData*mod)<<3);
if (loopE==0) {
// wavetable, does stable phasemod
data=wtMem[i][((phase+phase_inc)>>16)&255];
phase+=freq;
} else {
// samples, does unstable pitchmod
unsigned base=chRead(i,4)|((unsigned)chRead(i,5)<<8)|((unsigned)chRead(i,6)<<16)|((unsigned)chRead(i,7)<<24);
unsigned loopL=chRead(i,16)|((unsigned)chRead(i,17)<<8);
data=sampleMem[base+(phase>>16)];
phase_inc=(phase_inc>>9)+freq;
// ROM symphony prevention
if (phase_inc<0) phase_inc=0;
if ((unsigned)phase_inc>(loopL<<16)) phase_inc=loopL<<16;
int64_t newPhase=phase+phase_inc;
if (newPhase>=0x100000000LL || newPhase>=(loopE<<16)) {
newPhase-=loopL<<16;
}
phase=newPhase;
}
int out=data*vol; // 16 bits
fbData2=fbData1;
fbData1=out;
modData=out;
outAL+=out*panL; // 29 bits
outAR+=out*panR;
oscBuf[i]->putSample(h,(out*((int)abs(panL)+abs(panR)))>>15);
chDWrite(i,24,phase&0xff);
chDWrite(i,25,(phase>>8)&0xff);
chDWrite(i,26,(phase>>16)&0xff);
chDWrite(i,27,(phase>>24)&0xff);
chDWrite(i,28,fbData1&0xff);
chDWrite(i,29,(fbData1>>8)&0xff);
chDWrite(i,30,fbData2&0xff);
chDWrite(i,31,(fbData2>>24)&0xff);
} // 32 bits
buf[0][h]=outAL>>16; // 16 bits
buf[1][h]=outAR>>16;
}
for (int i=0; i<8; i++) {
oscBuf[i]->end(len);
}
}
void DivPlatformKurumitsu8L::updateWave(int ch) {
for (int i=0; i<256; i++) {
wtMem[ch][i]=chan[ch].ws.output[i]^128;
}
if (!skipRegisterWrites && dumpWrites) {
addWrite(0x10000+ch*256,256);
}
}
void DivPlatformKurumitsu8L::writePan(int ch) {
if (!isMuted[ch] && chan[ch].active) {
int valL=chan[ch].panL*chan[ch].vol*(chan[ch].invertL?-1:1)/8;
int valR=chan[ch].panR*chan[ch].vol*(chan[ch].invertR?-1:1)/8;
chWrite(ch,12,valL&0xff);
chWrite(ch,13,valL>>8);
chWrite(ch,14,valR&0xff);
chWrite(ch,15,valR>>8);
} else {
chWrite(ch,12,0);
chWrite(ch,13,0);
chWrite(ch,14,0);
chWrite(ch,15,0);
}
}
void DivPlatformKurumitsu8L::tick(bool sysTick) {
for (int i=0; i<8; i++) {
bool panChanged=false;
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=chan[i].std.vol.val*(chan[i].isAmiga?4:1);
if (chan[i].outVol>255) chan[i].outVol=255;
if (!isMuted[i] && chan[i].active) {
chWrite(i,9,chan[i].outVol);
}
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
}
chan[i].freqChanged=true;
}
if (chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
}
if (chan[i].std.panL.had) {
chan[i].panL=chan[i].std.panL.val;
panChanged=true;
}
if (chan[i].std.panR.had) {
chan[i].panR=chan[i].std.panR.val;
panChanged=true;
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-1048575,1048575);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1 && chan[i].active) {
if (chan[i].pcm) {
chan[i].audPos=0;
chan[i].setPos=true;
} else {
chWrite(i,24,0);
chWrite(i,25,0);
chWrite(i,26,0);
chWrite(i,27,0);
}
}
}
if (chan[i].std.ex1.had) {
chWrite(i,10,chan[i].std.ex1.val);
}
if (chan[i].std.ex2.had) {
chWrite(i,11,chan[i].std.ex2.val);
}
if (chan[i].std.ex3.had) {
chan[i].invertL=chan[i].std.ex3.val&2;
chan[i].invertR=chan[i].std.ex3.val&1;
panChanged=true;
}
if (chan[i].setPos) {
// force keyon
chan[i].keyOn=true;
chan[i].setPos=false;
} else {
chan[i].audPos=0;
}
if (chan[i].active) {
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
updateWave(i);
}
}
if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].pcm) {
DivSample* s=parent->getSample(chan[i].sample);
double off=(s->centerRate>=1)?((double)s->centerRate/parent->getCenterRate()):1.0;
chan[i].freq*=off/16.;
}
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>8388608) chan[i].freq=8388608;
chWrite(i,20,chan[i].freq&0xff);
chWrite(i,21,(chan[i].freq>>8)&0xff);
chWrite(i,22,(chan[i].freq>>16)&0xff);
chan[i].freqChanged=false;
}
if (chan[i].keyOn || chan[i].keyOff) {
if (!chan[i].std.vol.had) {
chan[i].outVol=255;
}
if (!isMuted[i] && chan[i].active) {
chWrite(i,9,chan[i].outVol);
} else {
chWrite(i,9,0);
}
panChanged=true;
if (chan[i].keyOn) {
chan[i].keyOn=false;
if (chan[i].pcm) {
DivSample* s=parent->getSample(chan[i].sample);
unsigned int start, loopEnd, loopLength;
start=sampleOff[chan[i].sample];
if (s->isLoopable()) {
loopEnd=s->loopEnd;
loopLength=s->loopEnd-s->loopStart;
} else {
loopEnd=s->samples+16;
loopLength=16;
}
if (loopEnd>65535) loopEnd=65535;
chWrite(i,4,start&0xff);
chWrite(i,5,(start>>8)&0xff);
chWrite(i,6,(start>>16)&0xff);
chWrite(i,16,loopLength&0xff);
chWrite(i,17,(loopLength>>8)&0xff);
chWrite(i,18,loopEnd&0xff);
chWrite(i,19,(loopEnd>>8)&0xff);
chWrite(i,24,0);
chWrite(i,25,0);
chWrite(i,26,chan[i].audPos);
chWrite(i,27,(chan[i].audPos>>8)&0xff);
} else {
chWrite(i,18,0);
chWrite(i,19,0);
}
}
if (chan[i].keyOff) chan[i].keyOff=false;
}
if (panChanged) writePan(i);
}
}
int DivPlatformKurumitsu8L::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].isAmiga=ins->type==DIV_INS_AMIGA;
chan[c.chan].pcm=ins->type==DIV_INS_AMIGA || ins->amiga.useSample;
if (chan[c.chan].pcm && c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
} else {
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.init(ins,256,255,chan[c.chan].insChanged);
}
if (!chan[c.chan].pcm || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=255;
if (!isMuted[c.chan] && chan[c.chan].active) {
chWrite(c.chan,9,chan[c.chan].outVol);
}
}
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].sample=-1;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
writePan(c.chan);
}
break;
case DIV_CMD_GET_VOLUME:
return chan[c.chan].vol;
break;
case DIV_CMD_SNES_INVERT:
chan[c.chan].invertL=(c.value>>4);
chan[c.chan].invertR=c.value&15;
writePan(c.chan);
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
chan[c.chan].keyOn=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:8);
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:8);
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_PANNING: {
chan[c.chan].panL=c.value;
chan[c.chan].panR=c.value2;
writePan(c.chan);
break;
}
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
}
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].audPos=c.value;
chan[c.chan].setPos=true;
break;
case DIV_CMD_FM_FB: {
chWrite(c.chan,10,c.value);
break;
}
case DIV_CMD_FM_PM_DEPTH: {
chWrite(c.chan,11,c.value);
break;
}
case DIV_CMD_GET_VOLMAX:
return 255;
break;
case DIV_CMD_MACRO_OFF:
chan[c.chan].std.mask(c.value,true);
break;
case DIV_CMD_MACRO_ON:
chan[c.chan].std.mask(c.value,false);
break;
case DIV_CMD_MACRO_RESTART:
chan[c.chan].std.restart(c.value);
break;
default:
break;
}
return 1;
}
void DivPlatformKurumitsu8L::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (!mute && chan[ch].active) {
chWrite(ch,9,chan[ch].outVol);
writePan(ch);
} else {
chWrite(ch,9,0);
}
}
void DivPlatformKurumitsu8L::forceIns() {
for (int i=0; i<8; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].audPos=0;
chan[i].sample=-1;
updateWave(i);
writePan(i);
}
}
void* DivPlatformKurumitsu8L::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformKurumitsu8L::getChanMacroInt(int ch) {
return &chan[ch].std;
}
unsigned short DivPlatformKurumitsu8L::getPan(int ch) {
short panL=abs(chRead(ch,12)|((short)chRead(ch,13)<<8));
short panR=abs(chRead(ch,14)|((short)chRead(ch,15)<<8));
return ((panL/128)<<8)|(panR/128);
}
void DivPlatformKurumitsu8L::getPaired(int ch, std::vector<DivChannelPair>& ret) {
if (ch==0) return;
unsigned char mod=chRead(ch,11);
if (mod!=0) {
sprintf(modLabel[ch],"%d",mod);
ret.push_back(DivChannelPair(modLabel[ch],ch-1));
}
}
DivSamplePos DivPlatformKurumitsu8L::getSamplePos(int ch) {
if (!chan[ch].pcm || chan[ch].sample<0 ||
chan[ch].sample>=parent->song.sampleLen || !chan[ch].active
) {
return DivSamplePos();
}
return DivSamplePos(
chan[ch].sample,
(((int)regPool[0x7a]|((int)regPool[0x7b]<<8)|((int)regPool[0x73]<<16)))-sampleOff[chan[ch].sample],
(int64_t)chan[ch].freq*rate/0x10000
);
}
DivDispatchOscBuffer* DivPlatformKurumitsu8L::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformKurumitsu8L::getRegisterPool() {
return regPool;
}
int DivPlatformKurumitsu8L::getRegisterPoolSize() {
return 32*8;
}
const void* DivPlatformKurumitsu8L::getSampleMem(int index) {
if (index == 0) return sampleMem;
if (index == 1) return wtMem;
return NULL;
}
size_t DivPlatformKurumitsu8L::getSampleMemCapacity(int index) {
return index == 0 ? 16777216 : 0;
}
size_t DivPlatformKurumitsu8L::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
bool DivPlatformKurumitsu8L::isSampleLoaded(int index, int sample) {
if (index!=0) return false;
if (sample<0 || sample>255) return false;
return sampleLoaded[sample];
}
const DivMemoryComposition* DivPlatformKurumitsu8L::getMemCompo(int index) {
switch (index) {
case 0: return &romMemCompo;
case 1: return &wtMemCompo;
}
return NULL;
}
void DivPlatformKurumitsu8L::renderSamples(int sysID) {
int maxPos=getSampleMemCapacity();
memset(sampleMem,0,maxPos);
romMemCompo.entries.clear();
romMemCompo.capacity=maxPos;
int memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
if (!s->renderOn[0][sysID]) {
sampleOff[i]=0;
continue;
}
if (memPos>=maxPos) {
logW("out of Kurumitsu-8L PCM memory for sample %d!",i);
break;
}
int length=s->isLoopable()?s->loopEnd:s->samples;
int actualLength=length;
// if it's one-shot, add 16 silent samples for looping area
// this should be enough for most cases even though the
// frequency register can make position jump by up to 256 samples
int oneShotLength=length;
if (!s->isLoopable()) actualLength+=16;
if (actualLength>65535) actualLength=65535;
if (actualLength>(maxPos-memPos)) actualLength=maxPos-memPos;
if (!s->isLoopable()) oneShotLength=actualLength-16;
sampleOff[i]=memPos;
memcpy(&sampleMem[memPos],s->data8,oneShotLength);
memPos+=actualLength;
romMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"PCM",i,sampleOff[i],memPos));
sampleLoaded[i]=true;
}
sampleMemLen=memPos;
romMemCompo.used=sampleMemLen;
}
void DivPlatformKurumitsu8L::reset() {
memset(regPool,0,sizeof(regPool));
memset(wtMem,0,sizeof(wtMem));
if (dumpWrites) {
addWrite(0xffffffff,0);
}
for (int i=0; i<8; i++) {
chan[i]=DivPlatformKurumitsu8L::Channel();
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,256,255,false);
}
}
int DivPlatformKurumitsu8L::getOutputCount() {
return 2;
}
bool DivPlatformKurumitsu8L::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformKurumitsu8L::notifyWaveChange(int wave) {
for (int i=0; i<8; i++) {
if (chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
updateWave(i);
}
}
}
void DivPlatformKurumitsu8L::notifyInsDeletion(void* ins) {
for (int i=0; i<8; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformKurumitsu8L::setFlags(const DivConfig& flags) {
chipClock=3072000;
CHECK_CUSTOM_CLOCK;
rate=chipClock/64;
for (int i=0; i<8; i++) {
oscBuf[i]->setRate(rate);
}
}
void DivPlatformKurumitsu8L::poke(unsigned int addr, unsigned short val) {
if (addr < (unsigned int)getRegisterPoolSize()) chWrite(addr/32,addr%32,val);
}
void DivPlatformKurumitsu8L::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) {
if (i.addr < (unsigned int)getRegisterPoolSize()) chWrite(i.addr/32,i.addr%32,i.val);
}
}
int DivPlatformKurumitsu8L::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
memset(modLabel,0,sizeof(modLabel));
for (int i=0; i<8; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
sampleMem=new signed char[getSampleMemCapacity()];
sampleMemLen=0;
romMemCompo=DivMemoryComposition();
romMemCompo.name="Sample ROM";
wtMemCompo=DivMemoryComposition();
wtMemCompo.name="Wavetable RAM";
wtMemCompo.capacity=256*8;
wtMemCompo.memory=(unsigned char*)wtMem;
wtMemCompo.waveformView=DIV_MEMORY_WAVE_8BIT_SIGNED;
wtMemCompo.used=256*8;
wtMemCompo.entries.clear();
for (int i=0; i<8; i++) {
wtMemCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_RAM, fmt::sprintf("Channel %d",i+1),-1,i*256,i*256+256));
}
setFlags(flags);
reset();
return 8;
}
void DivPlatformKurumitsu8L::quit() {
for (int i=0; i<8; i++) {
delete oscBuf[i];
}
}
DivPlatformKurumitsu8L::~DivPlatformKurumitsu8L() {
}

View file

@ -0,0 +1,99 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _KURUMITSU_8L_H
#define _KURUMITSU_8L_H
#include "../dispatch.h"
#include "../../fixedQueue.h"
#include "../waveSynth.h"
class DivPlatformKurumitsu8L: public DivDispatch {
struct Channel: public SharedChannel<int> {
unsigned char panL, panR;
unsigned int audPos;
int sample, wave;
bool invertL, invertR;
bool pcm, setPos, isAmiga;
DivWaveSynth ws;
Channel():
SharedChannel<int>(255),
panL(255),
panR(255),
audPos(0),
sample(-1),
wave(-1),
invertL(false),
invertR(false),
pcm(false),
setPos(false),
isAmiga(false) {}
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];
bool isMuted[8];
unsigned int sampleOff[256];
bool sampleLoaded[256];
unsigned char regPool[8*32];
signed char wtMem[8][256];
char modLabel[8][4];
signed char* sampleMem;
size_t sampleMemLen;
DivMemoryComposition romMemCompo;
DivMemoryComposition wtMemCompo;
void updateWave(int ch);
void writePan(int ch);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short** buf, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
unsigned short getPan(int chan);
void getPaired(int ch, std::vector<DivChannelPair>& ret);
DivSamplePos getSamplePos(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
int getOutputCount();
bool keyOffAffectsArp(int ch);
void setFlags(const DivConfig& flags);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const void* getSampleMem(int index = 0);
size_t getSampleMemCapacity(int index = 0);
size_t getSampleMemUsage(int index = 0);
bool isSampleLoaded(int index, int sample);
const DivMemoryComposition* getMemCompo(int index);
void renderSamples(int chipID);
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformKurumitsu8L();
};
#endif

View file

@ -146,6 +146,7 @@ enum DivSystem {
DIV_SYSTEM_UPD1771C,
DIV_SYSTEM_SID3,
DIV_SYSTEM_C64_PCM,
DIV_SYSTEM_KURUMITSU_8L,
DIV_SYSTEM_MAX
};

View file

@ -314,7 +314,7 @@ const char* DivEngine::getChannelName(int chan) {
if (chan<0 || chan>chans) return "??";
if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str();
if (sysDefs[sysOfChan[chan]]==NULL) return "??";
const char* ret=sysDefs[sysOfChan[chan]]->chanNames[dispatchChanOfChan[chan]];
if (ret==NULL) return "??";
return ret;
@ -324,7 +324,7 @@ const char* DivEngine::getChannelShortName(int chan) {
if (chan<0 || chan>chans) return "??";
if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str();
if (sysDefs[sysOfChan[chan]]==NULL) return "??";
const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]];
if (ret==NULL) return "??";
return ret;
@ -2174,7 +2174,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_ESFM]=new DivSysDef(
_("ESS ES1xxx series (ESFM)"), NULL, 0xd1, 0, 18, true, false, 0, false, 0, 0, 0,
_("ESS ES1xxx series (ESFM)"), NULL, 0xd1, 0, 18, true, false, 0, false, 0, 0, 0,
_("a unique FM synth featured in PC sound cards.\nbased on the OPL3 design, but with lots of its features extended."),
{_("FM 1"), _("FM 2"), _("FM 3"), _("FM 4"), _("FM 5"), _("FM 6"), _("FM 7"), _("FM 8"), _("FM 9"), _("FM 10"), _("FM 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("FM 16"), _("FM 17"), _("FM 18")},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"},
@ -2186,9 +2186,9 @@ void DivEngine::registerSystems() {
},
fmESFMPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_POWERNOISE]=new DivSysDef(
_("PowerNoise"), NULL, 0xd4, 0, 4, false, false, 0, false, 0, 0, 0,
_("PowerNoise"), NULL, 0xd4, 0, 4, false, false, 0, false, 0, 0, 0,
_("a fantasy sound chip designed by jvsTSX and The Beesh-Spweesh!\nused in the Hexheld fantasy console."),
{_("Noise 1"), _("Noise 2"), _("Noise 3"), _("Slope")},
{"N1", "N2", "N3", "SL"},
@ -2222,7 +2222,7 @@ void DivEngine::registerSystems() {
{0x16, {DIV_CMD_DAVE_CLOCK_DIV, _("16xx: Set clock divider (0: /2; 1: /3)")}},
}
);
sysDefs[DIV_SYSTEM_GBA_DMA]=new DivSysDef(
_("Game Boy Advance DMA Sound"), NULL, 0xd7, 0, 2, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 256,
_("additional PCM FIFO channels in Game Boy Advance driven directly by its DMA hardware."),
@ -2305,7 +2305,7 @@ void DivEngine::registerSystems() {
}
);
sysDefs[DIV_SYSTEM_SID2]=new DivSysDef(
sysDefs[DIV_SYSTEM_SID2]=new DivSysDef(
_("SID2"), NULL, 0xf0, 0, 3, false, true, 0, false, 0, 0, 0,
_("a fantasy sound chip created by LTVA. it is similar to the SID chip, but with many of its problems fixed."),
{_("Channel 1"), _("Channel 2"), _("Channel 3")},
@ -2313,11 +2313,11 @@ void DivEngine::registerSystems() {
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
{DIV_INS_SID2, DIV_INS_SID2, DIV_INS_SID2},
{},
{},
{},
SID2PostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_SID3]=new DivSysDef(
sysDefs[DIV_SYSTEM_SID3]=new DivSysDef(
_("SID3"), NULL, 0xf5, 0, 7, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 256, 256,
_("a fantasy sound chip created by LTVA. it is a big rework of SID chip with probably too many features added on top."),
{_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5"), _("Channel 6"), _("Wave")},
@ -2325,7 +2325,7 @@ void DivEngine::registerSystems() {
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_WAVE},
{DIV_INS_SID3, DIV_INS_SID3, DIV_INS_SID3, DIV_INS_SID3, DIV_INS_SID3, DIV_INS_SID3, DIV_INS_SID3},
{},
{},
{},
SID3PostEffectHandlerMap
);
@ -2350,6 +2350,22 @@ void DivEngine::registerSystems() {
{DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}
);
sysDefs[DIV_SYSTEM_KURUMITSU_8L]=new DivSysDef(
_("Kurumitsu-8L"), NULL, 0xfe, 0, 8, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 256, 256,
_(":zrobilusmiech:"),
{_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5"), _("Channel 6"), _("Channel 7"), _("Channel 8")},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
{DIV_INS_KURUMITSU_8L, DIV_INS_KURUMITSU_8L, DIV_INS_KURUMITSU_8L, DIV_INS_KURUMITSU_8L, DIV_INS_KURUMITSU_8L, DIV_INS_KURUMITSU_8L, DIV_INS_KURUMITSU_8L, DIV_INS_KURUMITSU_8L},
{},
{
{0x10, {DIV_CMD_WAVE, _("10xx: Set waveform")}},
{0x11, {DIV_CMD_FM_FB, _("11xx: Set feedback")}},
{0x12, {DIV_CMD_FM_PM_DEPTH, _("12xx: Set modulation")}},
{0x14, {DIV_CMD_SNES_INVERT, _("14xy: Toggle invert (x: left; y: right)")}},
}
);
for (int i=0; i<DIV_MAX_CHIP_DEFS; i++) {
if (sysDefs[i]==NULL) continue;
if (sysDefs[i]->id!=0) {

View file

@ -24,7 +24,7 @@
// this function is so long
// may as well make it something else
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable, bool dpcm07, DivDispatch** writeNES, int rateCorrection) {
void DivEngine::performVGMWrite(SafeWriter* w, int disIdx, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable, bool dpcm07, DivDispatch** writeNES, int rateCorrection) {
unsigned char baseAddr1=isSecond?0xa0:0x50;
unsigned char baseAddr2=isSecond?0x80:0;
unsigned short baseAddr2S=isSecond?0x8000:0;
@ -226,7 +226,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0x28);
w->writeC(5+i);
}
// reset AY
w->writeC(8|baseAddr1);
w->writeC(7);
@ -970,7 +970,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
} else {
w->writeC(baseAddr2|(write.addr&0xff));
}
w->writeC(write.val);
break;
case DIV_SYSTEM_YM2151:
@ -1236,6 +1236,23 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(baseAddr2|(write.addr&0x7f));
w->writeC(write.val);
break;
case DIV_SYSTEM_KURUMITSU_8L: // HACK
if (write.addr>=0x10000) {
// wavetable writes
const signed char* wtMem=(const signed char*)disCont[disIdx].dispatch->getSampleMem(1);
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0xc0);
w->writeI(write.val+2);
w->writeS(write.addr&0xffff);
w->write(&wtMem[write.addr&0xffff],write.val);
} else {
// register writes
w->writeC(0xa0);
w->writeC(write.addr&0xff);
w->writeC(write.val);
}
break;
default:
logW("write not handled!");
break;
@ -1419,7 +1436,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
DivDispatch* writeNES[2]={NULL,NULL};
DivDispatch* writePCM_OPL4[2]={NULL,NULL};
DivDispatch* writeMultiPCM[2]={NULL,NULL};
int writeNESIndex[2]={0,0};
size_t bankOffsetNESCurrent=0;
@ -2054,6 +2071,14 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
howManyChips++;
}
break;
// HACK
case DIV_SYSTEM_KURUMITSU_8L:
if (!hasAY) {
hasAY=disCont[i].dispatch->chipClock|0x40000000;
willExport[i]=true;
writeSegaPCM[0]=disCont[i].dispatch;
}
break;
default:
break;
}
@ -2140,7 +2165,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
w->writeC(0); // loop count
// 1.51
w->writeC(0); // loop modifier
if (version>=0x161) {
w->writeI(hasGB);
w->writeI(hasNES);
@ -2905,7 +2930,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
lastOne=i.second.time;
}
// write write
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream,sampleStoppable,dpcm07,writeNES,correctedRate);
performVGMWrite(w,i.first,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream,sampleStoppable,dpcm07,writeNES,correctedRate);
writeCount++;
}
sortedWrites.clear();

View file

@ -373,6 +373,7 @@ enum FurnaceGUIColors {
GUI_COLOR_INSTR_SUPERVISION,
GUI_COLOR_INSTR_UPD1771C,
GUI_COLOR_INSTR_SID3,
GUI_COLOR_INSTR_KURUMITSU_8L,
GUI_COLOR_INSTR_UNKNOWN,
GUI_COLOR_CHANNEL_BG,

View file

@ -187,6 +187,7 @@ const char* insTypes[DIV_INS_MAX+1][3]={
{"Watara Supervision",ICON_FA_GAMEPAD,ICON_FUR_INS_SUPERVISION},
{"NEC μPD1771C",ICON_FA_BAR_CHART,ICON_FUR_INS_UPD1771C},
{"SID3",ICON_FA_KEYBOARD_O,ICON_FUR_INS_SID3},
{"Kurumitsu-8L",ICON_FA_CALCULATOR,ICON_FA_QUESTION},
{NULL,ICON_FA_QUESTION,ICON_FA_QUESTION}
};
@ -248,7 +249,7 @@ const char* chanNames[CHANNEL_TYPE_MAX+1]={
_N("Square"),
_N("Triangle"), // NES
_N("Saw"), // VRC6
_N("Ext. Operator"),
_N("Ext. Operator"),
_N("Drums"),
_N("Slope"), // PowerNoise
_N("Wave"), // not wavetable (VERA, 5E01)
@ -1069,6 +1070,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
D(GUI_COLOR_INSTR_SUPERVISION,"",ImVec4(0.52f,1.0f,0.6f,1.0f)),
D(GUI_COLOR_INSTR_UPD1771C,"",ImVec4(0.94f,0.52f,0.6f,1.0f)),
D(GUI_COLOR_INSTR_SID3,"",ImVec4(0.6f,0.75f,0.6f,1.0f)),
D(GUI_COLOR_INSTR_KURUMITSU_8L,"",ImVec4(0.9f,0.3f,0.3f,1.0f)),
D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)),
D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)),
@ -1331,6 +1333,7 @@ const int availableSystems[]={
DIV_SYSTEM_UPD1771C,
DIV_SYSTEM_SID3,
DIV_SYSTEM_MULTIPCM,
DIV_SYSTEM_KURUMITSU_8L,
0 // don't remove this last one!
};
@ -1404,6 +1407,7 @@ const int chipsWave[]={
DIV_SYSTEM_NAMCO,
DIV_SYSTEM_NAMCO_15XX,
DIV_SYSTEM_NAMCO_CUS30,
DIV_SYSTEM_KURUMITSU_8L,
0 // don't remove this last one!
};

View file

@ -1146,14 +1146,14 @@ WaveFunc waveFuncsIns[]={
quartSin,
squiSin,
squiAbsSin,
rectSaw,
absSaw,
cubSaw,
rectCubSaw,
absCubSaw,
cubSine,
rectCubSin,
absCubSin,
@ -1934,7 +1934,7 @@ void FurnaceGUI::drawGBEnv(unsigned char vol, unsigned char len, unsigned char s
ImGui::ItemSize(size,style.FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("gbEnv"))) {
ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding);
float volY=1.0-((float)vol/15.0);
float lenPos=(sLen>62)?1.0:((float)sLen/384.0);
float envEndPoint=((float)len/7.0)*((float)(dir?(15-vol):vol)/15.0);
@ -2450,7 +2450,7 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text(_("Release"));
@ -2757,7 +2757,7 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros, FurnaceGUI
ImGui::TableNextColumn();
if (++curColumn>=columns) curColumn=0;
float availableWidth=ImGui::GetContentRegionAvail().x-reservedSpace;
int totalFit=i.macro->len;
if (totalFit<1) totalFit=1;
@ -3459,7 +3459,8 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) {
ins->type==DIV_INS_SU ||
ins->type==DIV_INS_NDS ||
ins->type==DIV_INS_SUPERVISION ||
ins->type==DIV_INS_SID3) {
ins->type==DIV_INS_SID3 ||
ins->type==DIV_INS_KURUMITSU_8L) {
P(ImGui::Checkbox(_("Use sample"),&ins->amiga.useSample));
if (ins->type==DIV_INS_X1_010) {
if (ImGui::InputInt(_("Sample bank slot##BANKSLOT"),&ins->x1_010.bankSlot,1,4)) { PARAMETER
@ -4110,7 +4111,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) {
if (ImGui::Button(_("Request from TX81Z"))) {
doAction(GUI_ACTION_TX81Z_REQUEST);
}
/*
/*
ImGui::SameLine();
if (ImGui::Button("Send to TX81Z")) {
showError("Coming soon!");
@ -5128,7 +5129,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) {
}
ImGui::SetCursorPos(prevCurPos);
ImGui::TableNextColumn();
switch (ins->type) {
case DIV_INS_FM: {
@ -5145,7 +5146,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) {
if (CWSliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,_(ssgEnvTypes[ssgEnv]))) { PARAMETER
op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7);
}
// params
ImGui::Separator();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -5219,7 +5220,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) {
if (ImGui::Checkbox(FM_NAME(FM_EGS),&ssgOn)) { PARAMETER
op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3);
}
ImGui::EndTable();
}
@ -5265,11 +5266,11 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) {
if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER
op.sus=susOn;
}
ImGui::EndTable();
}
pushWarningColor(ins->type==DIV_INS_OPL_DRUMS && i==0);
pushWarningColor(ins->type==DIV_INS_OPL_DRUMS && i==0);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
snprintf(tempID,1024,"%s: %%d",FM_NAME(FM_MULT));
P(CWSliderScalar("##MULT",ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN,tempID)); rightClickable
@ -5314,7 +5315,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) {
if (block>7) block=7;
op.dt=block;
}
ImGui::Text(_("Freq"));
ImGui::SameLine();
ImGui::SetCursorPos(ImVec2(cursorAlign.x,ImGui::GetCursorPosY()));
@ -5871,7 +5872,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) {
ImGui::Separator();
ImGui::TableNextColumn();
ImGui::Separator();
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -5953,7 +5954,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) {
} else {
ImGui::TableNextRow();
ImGui::TableNextColumn();
pushWarningColor(ins->type==DIV_INS_OPL_DRUMS && i==0);
pushWarningColor(ins->type==DIV_INS_OPL_DRUMS && i==0);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
P(CWSliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable
if (ins->type==DIV_INS_OPL_DRUMS && i==0) {
@ -5965,7 +5966,7 @@ void FurnaceGUI::insTabFM(DivInstrument* ins) {
ImGui::TableNextColumn();
ImGui::Text("%s",FM_NAME(FM_MULT));
}
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) {
if (!(ins->type==DIV_INS_OPZ && op.egt)) {
int detune=detuneMap[settings.unsignedDetune?1:0][op.dt&7];
@ -6275,7 +6276,7 @@ void FurnaceGUI::drawInsSID3(DivInstrument* ins) {
ImGui::EndTable();
}
if (!ins->sid3.doWavetable) {
strncpy(buffer,macroSID3WaveMixMode(0,(float)ins->sid3.mixMode,NULL).c_str(),40);
P(CWSliderScalar(_("Wave Mix Mode"),ImGuiDataType_U8,&ins->sid3.mixMode,&_ZERO,&_FOUR,buffer));
@ -6764,7 +6765,7 @@ void FurnaceGUI::drawInsEdit() {
ImGui::EndTable();
}
if (ImGui::BeginTabBar("insEditTab")) {
std::vector<FurnaceGUIMacroDesc> macroList;
@ -6926,7 +6927,7 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc(_("Op. Pitch"),&ins->std.opMacros[ordi].susMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode,NULL,false,NULL,false,NULL,false,true));
}
}
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_AR),&ins->std.opMacros[ordi].arMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_ENVELOPE]));
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_DR),&ins->std.opMacros[ordi].drMacro,0,maxArDr,64,uiColors[GUI_COLOR_MACRO_ENVELOPE]));
macroList.push_back(FurnaceGUIMacroDesc(FM_NAME(FM_D2R),&ins->std.opMacros[ordi].d2rMacro,0,31,64,uiColors[GUI_COLOR_MACRO_ENVELOPE]));
@ -7284,7 +7285,7 @@ void FurnaceGUI::drawInsEdit() {
P(ImGui::Checkbox(_("Enable filter"),&ins->c64.toFilter));
P(ImGui::Checkbox(_("Initialize filter"),&ins->c64.initFilter));
if (ins->type==DIV_INS_SID2) {
P(CWSliderScalar(_("Cutoff"),ImGuiDataType_U16,&ins->c64.cut,&_ZERO,&_FOUR_THOUSAND_NINETY_FIVE)); rightClickable
P(CWSliderScalar(_("Resonance"),ImGuiDataType_U8,&ins->c64.res,&_ZERO,&_TWO_HUNDRED_FIFTY_FIVE)); rightClickable
@ -7769,7 +7770,7 @@ void FurnaceGUI::drawInsEdit() {
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
ImGui::InhibitInertialScroll();
}
if (ImGui::Button(modTableHex?"Hex##MTHex":"Dec##MTHex")) {
modTableHex=!modTableHex;
}
@ -8060,7 +8061,8 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_SNES ||
ins->type==DIV_INS_NAMCO ||
ins->type==DIV_INS_SM8521 ||
(ins->type==DIV_INS_GBA_MINMOD && ins->amiga.useWave))
(ins->type==DIV_INS_GBA_MINMOD && ins->amiga.useWave) ||
ins->type==DIV_INS_KURUMITSU_8L)
{
insTabWavetable(ins);
}
@ -8719,7 +8721,7 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc(_("Ring Mod Source"),&ins->std.fmsMacro,0,SID3_NUM_CHANNELS,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroSID3SourceChan));
macroList.push_back(FurnaceGUIMacroDesc(_("Hard Sync Source"),&ins->std.amsMacro,0,SID3_NUM_CHANNELS - 1,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroSID3SourceChan));
macroList.push_back(FurnaceGUIMacroDesc(_("Phase Mod Source"),&ins->std.fbMacro,0,SID3_NUM_CHANNELS - 1,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,macroSID3SourceChan));
if (!ins->sid3.doWavetable) {
macroList.push_back(FurnaceGUIMacroDesc(_("Feedback"),&ins->std.opMacros[3].arMacro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER]));
}
@ -8745,6 +8747,18 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc(_("Sample Mode"),&ins->std.opMacros[1].arMacro,0,1,32,uiColors[GUI_COLOR_MACRO_NOISE],false,NULL,NULL,true));
}
break;
case DIV_INS_KURUMITSU_8L:
macroList.push_back(FurnaceGUIMacroDesc(_("Envelope"),&ins->std.volMacro,0,255,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
macroList.push_back(FurnaceGUIMacroDesc(_("Arpeggio"),&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,true,ins->std.arpMacro.val));
macroList.push_back(FurnaceGUIMacroDesc(_("Waveform"),&ins->std.waveMacro,0,waveCount,160,uiColors[GUI_COLOR_MACRO_WAVE],false,NULL,NULL,false,NULL));
macroList.push_back(FurnaceGUIMacroDesc(_("Panning (left)"),&ins->std.panLMacro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL));
macroList.push_back(FurnaceGUIMacroDesc(_("Panning (right)"),&ins->std.panRMacro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER]));
macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode));
macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
macroList.push_back(FurnaceGUIMacroDesc(_("Feedback"),&ins->std.ex1Macro,0,255,160,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,NULL));
macroList.push_back(FurnaceGUIMacroDesc(_("Modulation"),&ins->std.ex2Macro,0,255,160,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,NULL));
macroList.push_back(FurnaceGUIMacroDesc(_("Special"),&ins->std.ex3Macro,0,2,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,minModModeBits));
break;
case DIV_INS_MAX:
case DIV_INS_NULL:
break;
@ -8792,7 +8806,8 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_PCE ||
ins->type==DIV_INS_X1_010 ||
ins->type==DIV_INS_SWAN ||
ins->type==DIV_INS_VRC6) {
ins->type==DIV_INS_VRC6 ||
ins->type==DIV_INS_KURUMITSU_8L) {
insTabSample(ins);
}
if (ins->type>=DIV_INS_MAX) {
@ -8934,7 +8949,7 @@ void FurnaceGUI::drawInsEdit() {
}
ImGui::EndMenu();
}
ImGui::EndPopup();
}
}

View file

@ -3784,6 +3784,11 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_X1_010, 1.0f, 0, "")
}
);
ENTRY(
_("Kurumitsu-8L"), {
CH(DIV_SYSTEM_KURUMITSU_8L, 1.0f, 0, "")
}
);
CATEGORY_END;
CATEGORY_BEGIN(_("Specialized"),_("chips/systems with unique sound synthesis methods."));

View file

@ -580,6 +580,17 @@ void FurnaceGUI::drawSampleEdit() {
SAMPLE_WARN(warnLength,_("MultiPCM: maximum sample length is 65535"));
}
break;
case DIV_SYSTEM_KURUMITSU_8L:
if (sample->isLoopable()) {
if (sample->samples>65535) {
SAMPLE_WARN(warnLength,"Kurumitsu-8L: maximum sample length is 65535");
}
} else {
if (sample->samples>65519) {
SAMPLE_WARN(warnLength,"Kurumitsu-8L: maximum sample length is 65519");
}
}
break;
default:
break;
}
@ -669,7 +680,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::TableNextColumn();
ImGui::Text(_("Chips"));
}
if (sampleInfo) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
@ -774,7 +785,7 @@ void FurnaceGUI::drawSampleEdit() {
sample->centerRate=targetRate;
}
}
ImGui::AlignTextToFramePadding();
ImGui::Text(_("Note"));
ImGui::SameLine();
@ -1053,7 +1064,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::EndTable();
}
}
}
ImGui::EndTable();
@ -2125,7 +2136,7 @@ void FurnaceGUI::drawSampleEdit() {
start^=end;
}
ImVec2 p1=rectMin;
p1.x+=(e->getSamplePreviewPos()-samplePos)/sampleZoom;
p1.x+=(e->getSamplePreviewPos()-samplePos)/sampleZoom;
ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE];
ImVec4 posTrail1=posColor;
ImVec4 posTrail2=posColor;
@ -2163,7 +2174,7 @@ void FurnaceGUI::drawSampleEdit() {
start^=end;
}
ImVec2 p1=rectMin;
p1.x+=(chanPos.pos-samplePos)/sampleZoom;
p1.x+=(chanPos.pos-samplePos)/sampleZoom;
ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE_PLAYING];
ImVec4 posTrail1=posColor;
ImVec4 posTrail2=posColor;
@ -2427,7 +2438,7 @@ void FurnaceGUI::drawSampleEdit() {
if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT && sampleDragMode) {
statusBar=_("Non-8/16-bit samples cannot be edited without prior conversion.");
}
ImGui::SetCursorPosY(ImGui::GetCursorPosY()+ImGui::GetStyle().ScrollbarSize);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0,0));
if (ImGui::BeginTable("SEStatus",3,ImGuiTableFlags_BordersInnerV)) {

View file

@ -31,7 +31,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
switch (type) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
case DIV_SYSTEM_YM2612_CSM: {
@ -108,7 +108,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
if (interruptSimCycles>256) interruptSimCycles=256;
altered=true;
} rightClickable
if (altered) {
e->lockSave([&]() {
flags.set("clockSel",clockSel);
@ -761,7 +761,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
altered=true;
}
popWarningColor();
ImGui::Text(_("- 0 disables envelope reset. not recommended!\n- 1 may trigger SID envelope bugs.\n- values that are too high may result in notes being skipped."));
if (ImGui::Checkbox(_("Disable 1Exy env update (compatibility)"),&no1EUpdate)) {
@ -1591,7 +1591,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
altered=true;
}
ImGui::Unindent();
int chipClock=flags.getInt("customClock",0);
if (!chipClock) {
switch (clockSel) {
@ -2044,7 +2044,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
if (ImGui::Checkbox(_("Enable echo"),&echo)) {
altered=true;
}
ImGui::Text(_("Initial echo state:"));
for (int i=0; i<8; i++) {
bool echoChan=(bool)(echoMask&(1<<i));
@ -2221,7 +2221,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
altered=true;
} rightClickable
}
ImGui::Text(_("Initial part volume (channel 1-4):"));
for (int i=0; i<4; i++) {
snprintf(temp,63,"%d'##GRPV%d",2<<i,i);
@ -2729,6 +2729,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
case DIV_SYSTEM_POWERNOISE:
case DIV_SYSTEM_UPD1771C:
case DIV_SYSTEM_MULTIPCM:
case DIV_SYSTEM_KURUMITSU_8L:
break;
case DIV_SYSTEM_YMU759:
case DIV_SYSTEM_ESFM: