Partially ES5506 support (not working yet!)

Add sample related enums
Add support for backward/pingpong loop, loop end position
Structize Notemap in sample instrument
This commit is contained in:
cam900 2022-04-21 01:52:37 +09:00
parent 96715ed88c
commit 29ea6dc360
50 changed files with 4501 additions and 248 deletions

View file

@ -292,6 +292,13 @@ src/engine/platform/sound/vic20sound.c
src/engine/platform/sound/vrcvi/vrcvi.cpp
src/engine/platform/sound/es550x/es550x.cpp
src/engine/platform/sound/es550x/es550x_alu.cpp
src/engine/platform/sound/es550x/es550x_filter.cpp
src/engine/platform/sound/es550x/es5504.cpp
src/engine/platform/sound/es550x/es5505.cpp
src/engine/platform/sound/es550x/es5506.cpp
src/engine/platform/ym2610Interface.cpp
src/engine/blip_buf.c
@ -348,6 +355,7 @@ src/engine/platform/n163.cpp
src/engine/platform/pet.cpp
src/engine/platform/vic20.cpp
src/engine/platform/vrc6.cpp
src/engine/platform/es5506.cpp
src/engine/platform/dummy.cpp
)

View file

@ -1,3 +1,11 @@
# to-do for ES5506
- make sound produces actually
- filter, envelope macro, commands
- envelope shape
- reversed playing flag in instrument/macro/commands
- transwave synthesizer (like ensoniq synths - 12 bit command and macro)
# to-do for 0.6pre1
- panning macro

View file

@ -53,6 +53,7 @@
#include "platform/vrc6.h"
#include "platform/fds.h"
#include "platform/mmc5.h"
#include "platform/es5506.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "song.h"
@ -311,6 +312,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_MMC5:
dispatch=new DivPlatformMMC5;
break;
case DIV_SYSTEM_ES5506:
dispatch=new DivPlatformES5506;
break;
default:
logW("this system is not supported yet! using dummy platform.");
dispatch=new DivPlatformDummy;

View file

@ -495,6 +495,7 @@ void DivEngine::renderSamplesP() {
void DivEngine::renderSamples() {
sPreview.sample=-1;
sPreview.pos=0;
sPreview.dir=false;
// step 1: render samples
for (int i=0; i<song.sampleLen; i++) {
@ -613,6 +614,36 @@ void DivEngine::renderSamples() {
memPos+=paddedLen;
}
x1_010MemLen=memPos+256;
// step 5: allocate ES5506 pcm samples (forces depth for all samples to 16 bit due to chip limitation, compressed sample just LSB disconnected)
if (es5506Mem==NULL) es5506Mem=new signed short[16777216/sizeof(short)]; // 2Mword * 4 banks
memset(es5506Mem,0,16777216);
memPos=0;
for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
unsigned int length=s->length16;
// fit sample size to single bank size
if (length>2097152*sizeof(short)) {
length=2097152*sizeof(short);
}
if ((memPos&0xc00000)!=((memPos+length)&0xc00000)) {
memPos=(memPos+0x3fffff)&0xc00000;
}
if (memPos>=16777216) {
logW("out of ES5506 memory for sample %d!",i);
break;
}
if (memPos+length>=16777216) {
memcpy(es5506Mem+memPos,s->data16,16777216-memPos);
logW("out of ES5506 memory for sample %d!",i);
} else {
memcpy(es5506Mem+memPos,s->data16,length);
}
s->offES5506=memPos;
memPos+=length;
}
es5506MemLen=memPos+256;
}
void DivEngine::createNew(const int* description) {
@ -897,6 +928,7 @@ void DivEngine::play() {
sPreview.sample=-1;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
if (stepPlay==0) {
freelance=false;
playSub(false);
@ -919,6 +951,7 @@ void DivEngine::playToRow(int row) {
sPreview.sample=-1;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
freelance=false;
playSub(false,row);
for (int i=0; i<DIV_MAX_CHANS; i++) {
@ -953,6 +986,7 @@ void DivEngine::stop() {
sPreview.sample=-1;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyPlaybackStop();
}
@ -1093,6 +1127,8 @@ int DivEngine::getEffectiveSampleRate(int rate) {
return (48828*MIN(128,(rate*128/48828)))/128;
case DIV_SYSTEM_X1_010:
return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case
case DIV_SYSTEM_ES5506:
return (31250*MIN(131071,(rate*2048/31250)))/2048; // TODO: support variable clock, channel limit case
default:
break;
}
@ -1104,6 +1140,7 @@ void DivEngine::previewSample(int sample, int note) {
if (sample<0 || sample>=(int)song.sample.size()) {
sPreview.sample=-1;
sPreview.pos=0;
sPreview.dir=false;
BUSY_END;
return;
}
@ -1119,6 +1156,7 @@ void DivEngine::previewSample(int sample, int note) {
sPreview.pos=0;
sPreview.sample=sample;
sPreview.wave=-1;
sPreview.dir=false;
BUSY_END;
}
@ -1126,6 +1164,7 @@ void DivEngine::stopSamplePreview() {
BUSY_BEGIN;
sPreview.sample=-1;
sPreview.pos=0;
sPreview.dir=false;
BUSY_END;
}
@ -1134,6 +1173,7 @@ void DivEngine::previewWave(int wave, int note) {
if (wave<0 || wave>=(int)song.wave.size()) {
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
BUSY_END;
return;
}
@ -1149,6 +1189,7 @@ void DivEngine::previewWave(int wave, int note) {
sPreview.pos=0;
sPreview.sample=-1;
sPreview.wave=wave;
sPreview.dir=false;
BUSY_END;
}
@ -1156,6 +1197,7 @@ void DivEngine::stopWavePreview() {
BUSY_BEGIN;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
BUSY_END;
}
@ -1571,7 +1613,7 @@ int DivEngine::addSampleFromFile(const char* path) {
sample->rate=33144;
sample->centerRate=33144;
sample->depth=1;
sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM;
sample->init(len*8);
if (fread(sample->dataDPCM,1,len,f)==0) {
@ -1620,9 +1662,9 @@ int DivEngine::addSampleFromFile(const char* path) {
int index=0;
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
sample->depth=8;
sample->depth=DIV_SAMPLE_DEPTH_8BIT;
} else {
sample->depth=16;
sample->depth=DIV_SAMPLE_DEPTH_16BIT;
}
sample->init(si.frames);
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
@ -1652,9 +1694,11 @@ int DivEngine::addSampleFromFile(const char* path) {
inst.detune = inst.detune - 100;
short pitch = ((0x3c-inst.basenote)*100) + inst.detune;
sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0));
if(inst.loop_count && inst.loops[0].mode == SF_LOOP_FORWARD)
if(inst.loop_count && inst.loops[0].mode >= SF_LOOP_FORWARD)
{
sample->loopStart=inst.loops[0].start;
sample->loopEnd=inst.loops[0].end;
sample->loopMode=DivSampleLoopMode(int(inst.loops[0].mode-SF_LOOP_NONE));
if(inst.loops[0].end < (unsigned int)sampleCount)
sampleCount=inst.loops[0].end;
}

View file

@ -234,10 +234,12 @@ class DivEngine {
int sample;
int wave;
unsigned int pos;
bool dir;
SamplePreview():
sample(-1),
wave(-1),
pos(0) {}
pos(0),
dir(false) {}
} sPreview;
short vibTable[64];
@ -723,6 +725,8 @@ class DivEngine {
size_t dpcmMemLen;
unsigned char* x1_010Mem;
size_t x1_010MemLen;
signed short* es5506Mem;
size_t es5506MemLen;
DivEngine():
output(NULL),
@ -805,7 +809,9 @@ class DivEngine {
dpcmMem(NULL),
dpcmMemLen(0),
x1_010Mem(NULL),
x1_010MemLen(0) {
x1_010MemLen(0),
es5506Mem(NULL),
es5506MemLen(0) {
memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool));
memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool));
memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int));

View file

@ -772,17 +772,17 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
sample->rate=ymuSampleRate*400;
}
if (ds.version>0x15) {
sample->depth=reader.readC();
if (sample->depth!=8 && sample->depth!=16) {
sample->depth=DivSampleDepth(reader.readC());
if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) {
logW("%d: sample depth is wrong! (%d)",i,sample->depth);
sample->depth=16;
sample->depth=DIV_SAMPLE_DEPTH_16BIT;
}
} else {
if (ds.version>0x05) {
sample->depth=16;
sample->depth=DIV_SAMPLE_DEPTH_16BIT;
} else {
// it appears samples were stored as ADPCM back then
sample->depth=6;
sample->depth=DIV_SAMPLE_DEPTH_ADPCM_B;
}
}
if (length>0) {
@ -811,7 +811,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
if (k>=sample->samples) {
break;
}
if (sample->depth==8) {
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
float next=(float)(data[(unsigned int)j]-0x80)*mult;
sample->data8[k++]=fmin(fmax(next,-128),127);
} else {
@ -1414,6 +1414,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
sample->name=reader.readString();
sample->samples=reader.readI();
sample->loopEnd=sample->samples;
sample->rate=reader.readI();
if (ds.version<58) {
vol=reader.readS();
@ -1421,7 +1422,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} else {
reader.readI();
}
sample->depth=reader.readC();
sample->depth=DivSampleDepth(reader.readC());
// reserved
reader.readC();
@ -1435,6 +1436,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version>=19) {
sample->loopStart=reader.readI();
if (sample->loopStart<0) {
sample->loopMode=DIV_SAMPLE_LOOPMODE_ONESHOT;
sample->loopStart=0;
} else {
sample->loopMode=DIV_SAMPLE_LOOPMODE_FOWARD;
}
} else {
reader.readI();
}
@ -1452,9 +1459,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
// render data
if (sample->depth!=8 && sample->depth!=16) {
if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) {
logW("%d: sample depth is wrong! (%d)",i,sample->depth);
sample->depth=16;
sample->depth=DIV_SAMPLE_DEPTH_16BIT;
}
sample->samples=(double)sample->samples/samplePitches[pitch];
sample->init(sample->samples);
@ -1465,7 +1472,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (k>=sample->samples) {
break;
}
if (sample->depth==8) {
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
float next=(float)(data[(unsigned int)j]-0x80)*mult;
sample->data8[k++]=fmin(fmax(next,-128),127);
} else {
@ -1645,7 +1652,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
logD("reading samples... (%d)",insCount);
for (int i=0; i<insCount; i++) {
DivSample* sample=new DivSample;
sample->depth=8;
sample->depth=DIV_SAMPLE_DEPTH_8BIT;
sample->name=reader.readString(22);
logD("%d: %s",i+1,sample->name);
int slen=((unsigned short)reader.readS_BE())*2;
@ -1667,6 +1674,12 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
if (loopLen>=2) {
if (loopEnd<slen) slen=loopEnd;
sample->loopStart=loopStart;
if (sample->loopStart<0) {
sample->loopMode=DIV_SAMPLE_LOOPMODE_ONESHOT;
sample->loopStart=0;
} else {
sample->loopMode=DIV_SAMPLE_LOOPMODE_FOWARD;
}
}
sample->init(slen);
ds.sample.push_back(sample);
@ -2303,10 +2316,10 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(sample->samples);
w->writeI(sample->rate);
w->writeI(0); // reserved (for now)
w->writeC(sample->depth);
w->writeC((unsigned char)(sample->depth));
w->writeC(0);
w->writeS(sample->centerRate);
w->writeI(sample->loopStart);
w->writeI((sample->loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)?-1:sample->loopStart);
w->write(sample->getCurBuf(),sample->getCurBufLen());
}

View file

@ -384,8 +384,12 @@ void DivInstrument::putInsData(SafeWriter* w) {
// sample map
w->writeC(amiga.useNoteMap);
if (amiga.useNoteMap) {
w->write(amiga.noteFreq,120*sizeof(unsigned int));
w->write(amiga.noteMap,120*sizeof(short));
for (int i=0; i<120; i++) {
w->writeI(amiga.noteMap[i].freq);
}
for (int i=0; i<120; i++) {
w->writeS(amiga.noteMap[i].ind);
}
}
// N163
@ -862,8 +866,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
if (version>=67) {
amiga.useNoteMap=reader.readC();
if (amiga.useNoteMap) {
reader.read(amiga.noteFreq,120*sizeof(unsigned int));
reader.read(amiga.noteMap,120*sizeof(short));
for (int i=0; i<120; i++) {
amiga.noteMap[i].freq=reader.readI();
}
for (int i=0; i<120; i++) {
amiga.noteMap[i].ind=reader.readS();
}
}
}

View file

@ -54,6 +54,7 @@ enum DivInstrumentType: unsigned short {
DIV_INS_VERA=24,
DIV_INS_X1_010=25,
DIV_INS_VRC6_SAW=26,
DIV_INS_ES5506=27,
DIV_INS_MAX,
};
@ -291,21 +292,26 @@ struct DivInstrumentC64 {
};
struct DivInstrumentAmiga {
struct NoteMap {
int freq;
short ind;
NoteMap():
freq(0),
ind(-1) {}
};
short initSample;
bool useNoteMap;
bool useWave;
unsigned char waveLen;
int noteFreq[120];
short noteMap[120];
NoteMap noteMap[120];
DivInstrumentAmiga():
initSample(0),
useNoteMap(false),
useWave(false),
waveLen(31) {
memset(noteMap,-1,120*sizeof(short));
memset(noteFreq,0,120*sizeof(int));
}
waveLen(31) {}
};
struct DivInstrumentN163 {
@ -332,6 +338,43 @@ struct DivInstrumentFDS {
}
};
struct DivInstrumentES5506 {
struct Filter {
enum FilterMode: unsigned char { // filter mode for pole 4,3
FILTER_MODE_HPK2_HPK2,
FILTER_MODE_HPK2_LPK1,
FILTER_MODE_LPK2_LPK2,
FILTER_MODE_LPK2_LPK1,
};
FilterMode mode;
unsigned short k1, k2;
Filter():
mode(FILTER_MODE_LPK2_LPK1),
k1(0xffff),
k2(0xffff) {}
};
struct Envelope {
unsigned short ecount;
signed char lVRamp, rVRamp;
signed char k1Ramp, k2Ramp;
bool k1Slow, k2Slow;
Envelope():
ecount(0),
lVRamp(0),
rVRamp(0),
k1Ramp(0),
k2Ramp(0),
k1Slow(false),
k2Slow(false) {}
};
signed int lVol, rVol;
Filter filter;
Envelope envelope;
DivInstrumentES5506():
lVol(0xffff),
rVol(0xffff) {}
};
enum DivWaveSynthEffects {
DIV_WS_NONE=0,
// one waveform effects
@ -387,6 +430,7 @@ struct DivInstrument {
DivInstrumentAmiga amiga;
DivInstrumentN163 n163;
DivInstrumentFDS fds;
DivInstrumentES5506 es5506;
DivInstrumentWaveSynth ws;
/**

View file

@ -109,8 +109,8 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
DivSample* s=parent->getSample(chan[i].sample);
if (s->samples>0) {
writeAudDat(s->data8[chan[i].audPos++]);
if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].audPos>=s->loopEnd) || (chan[i].audPos>=s->samples) || (chan[i].audPos>=131071)) {
if (s->isLoopable()) {
chan[i].audPos=s->loopStart;
} else {
chan[i].sample=-1;

View file

@ -0,0 +1,638 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "es5506.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#include <map>
#define CHIP_FREQBASE (16*2048)
#define NOTE_ES5506(c,note) (chan[c].pcm.freqOffs*NOTE_FREQUENCY(note))
#define rWrite(a,...) {if(!skipRegisterWrites) {hostIntf32.emplace(4,(a),__VA_ARGS__); }}
#define rRead(a,...) {hostIntf32.emplace(4,(a),__VA_ARGS__);}
#define immWrite(a,...) {hostIntf32.emplace(4,(a),__VA_ARGS__);}
#define pageWrite(p,a,...) \
if (!skipRegisterWrites) { \
if (curPage!=(p)) { \
curPage=(p); \
rWrite(0xf,curPage); \
} \
rWrite((a),__VA_ARGS__); \
}
#define pageWriteMask(p,pm,a,...) \
if (!skipRegisterWrites) { \
if ((curPage&(pm))!=((p)&(pm))) { \
curPage=(curPage&~(pm))|((p)&(pm)); \
rWrite(0xf,curPage,(pm)); \
} \
rWrite((a),__VA_ARGS__); \
}
const char* regCheatSheetES5506[]={
"CR", "00|00",
"FC", "00|01",
"LVOL", "00|02",
"LVRAMP", "00|03",
"RVOL", "00|04",
"RVRAMP", "00|05",
"ECOUNT", "00|06",
"K2", "00|07",
"K2RAMP", "00|08",
"K1", "00|09",
"K1RAMP", "00|0A",
"ACTV", "00|0B",
"MODE", "00|0C",
"POT", "00|0D",
"IRQV", "00|0E",
"PAGE", "00|0F",
"CR", "20|00",
"START", "20|01",
"END", "20|02",
"ACCUM", "20|03",
"O4(n-1)", "20|04",
"O3(n-2)", "20|05",
"O3(n-1)", "20|06",
"O2(n-2)", "20|07",
"O2(n-1)", "20|08",
"O1(n-1)", "20|09",
"W_ST", "20|0A",
"W_END", "20|0B",
"LR_END", "20|0C",
"POT", "20|0D",
"IRQV", "20|0E",
"PAGE", "20|0F",
"CH0L", "40|00",
"CH0R", "40|01",
"CH1L", "40|02",
"CH1R", "40|03",
"CH2L", "40|04",
"CH2R", "40|05",
"CH3L", "40|06",
"CH3R", "40|07",
"CH4L", "40|08",
"CH4R", "40|09",
"CH5L", "40|0A",
"CH5R", "40|0B",
"POT", "40|0D",
"IRQV", "40|0E",
"PAGE", "40|0F",
NULL
};
const char** DivPlatformES5506::getRegisterSheet() {
return regCheatSheetES5506;
}
const char* DivPlatformES5506::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Set echo feedback level (00 to FF)";
break;
case 0x11:
return "11xx: Set channel echo level (00 to FF)";
break;
default:
if ((effect & 0xf0) == 0x30) {
return "3xxx: Set echo delay buffer length (000 to AA5)";
}
}
return NULL;
}
void DivPlatformES5506::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t h=start; h<start+len; h++) {
// convert 32 bit access to 8 bit host interface
while (!hostIntf32.empty()) {
QueuedHostIntf w=hostIntf32.front();
if (w.isRead && (w.read!=NULL)) {
hostIntf8.emplace(0,w.addr,w.read,w.mask);
hostIntf8.emplace(1,w.addr,w.read,w.mask);
hostIntf8.emplace(2,w.addr,w.read,w.mask);
hostIntf8.emplace(3,w.addr,w.read,w.mask,w.delay);
} else {
hostIntf8.emplace(0,w.addr,w.val,w.mask);
hostIntf8.emplace(1,w.addr,w.val,w.mask);
hostIntf8.emplace(2,w.addr,w.val,w.mask);
hostIntf8.emplace(3,w.addr,w.val,w.mask,w.delay);
}
hostIntf32.pop();
}
es5506.tick_perf();
bufL[h]=es5506.lout(0);
bufR[h]=es5506.rout(0);
}
}
void DivPlatformES5506::e(bool state)
{
if (es5506.e_rising_edge()) {
if (cycle) { // wait until delay
cycle--;
} else if (!hostIntf8.empty()) {
QueuedHostIntf w=hostIntf8.front();
unsigned char shift=24-(w.step<<3);
if (w.isRead) {
*w.read=((*w.read)&(~((0xff<<shift)&w.mask)))|((es5506.host_r((w.addr<<2)+w.step)<<shift)&w.mask);
if (w.step==3) {
if (w.delay>0) {
cycle+=w.delay;
}
isReaded=true;
} else {
isReaded=false;
}
hostIntf8.pop();
} else {
isReaded=false;
unsigned int mask=(w.mask>>shift)&0xff;
if ((mask==0xff) || isMasked) {
if (mask==0xff) {
maskedVal=(w.val>>shift)&0xff;
}
es5506.host_w((w.addr<<2)+w.step,maskedVal);
if(dumpWrites) {
addWrite((w.addr<<2)+w.step,maskedVal);
}
isMasked=false;
if ((w.step==3) && (w.delay>0)) {
cycle+=w.delay;
}
hostIntf8.pop();
} else if (!isMasked) {
maskedVal=((w.val>>shift)&mask)|(es5506.host_r((w.addr<<2)+w.step)&~mask);
isMasked=true;
}
}
}
}
if (isReaded) {
isReaded=false;
if (irqTrigger) {
irqTrigger=false;
if ((irqv&0x80)==0) {
unsigned char ch=irqv&0x1f;
if (chan[ch].isReversed) { // Reversed loop
pageWriteMask(0x00|ch,0x5f,0x00,0x48,0x78);
chan[ch].isReversed=false;
}
}
}
}
}
void DivPlatformES5506::irqb(bool state) {
rRead(0x0e,&irqv,0x9f);
irqTrigger=true;
}
void DivPlatformES5506::tick() {
for (int i=0; i<=chanMax; i++) {
chan[i].std.next();
DivInstrument* ins=parent->getIns(chan[i].ins);
// volume/panning macros
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&0xff)*MIN(0xffff,chan[i].std.vol.val))/0xff;
if (!isMuted[i]) {
chan[i].volChanged=true;
}
}
if (chan[i].std.panL.had) {
chan[i].outLVol=(((ins->es5506.lVol*(chan[i].lVol&0xf))/0xf)*MIN(0xffff,chan[i].std.panL.val))/0xffff;
if (!isMuted[i]) {
chan[i].volChanged=true;
}
}
if (chan[i].std.panR.had) {
chan[i].outRVol=(((ins->es5506.rVol*(chan[i].rVol&0xf))/0xf)*MIN(0xffff,chan[i].std.panR.val))/0xffff;
if (!isMuted[i]) {
chan[i].volChanged=true;
}
}
// arpeggio/pitch macros, frequency related
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_ES5506(i,chan[i].std.arp.val);
} else {
chan[i].baseFreq=NOTE_ES5506(i,chan[i].note+chan[i].std.arp.val);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_ES5506(i,chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.pitch.had) {
chan[i].freqChanged=true;
}
// phase reset macro
if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) {
chan[i].keyOn=true;
}
}
// update registers
if (chan[i].volChanged) {
if (!isMuted[i]) { // calculate volume (16 bit)
chan[i].resLVol=(chan[i].outVol*chan[i].outLVol)/0xffff;
chan[i].resRVol=(chan[i].outVol*chan[i].outRVol)/0xffff;
if (!chan[i].keyOn) {
pageWrite(0x00|i,0x02,chan[i].resLVol);
pageWrite(0x00|i,0x04,chan[i].resRVol);
}
} else { // mute
pageWrite(0x00|i,0x02,0);
pageWrite(0x00|i,0x04,0);
}
chan[i].volChanged=false;
}
if (chan[i].filterChanged) {
pageWriteMask(0x00|i,0x5f,0x00,(chan[i].filter.mode<<8),0x0300);
if (!chan[i].keyOn) {
pageWrite(0x00|i,0x07,chan[i].filter.k2);
pageWrite(0x00|i,0x09,chan[i].filter.k1);
}
chan[i].filterChanged=false;
}
if (chan[i].envChanged) {
if (!chan[i].keyOn) {
pageWrite(0x00|i,0x06,chan[i].envelope.ecount);
}
chan[i].envChanged=false;
}
if (chan[i].rampChanged) {
if (!chan[i].keyOn) {
pageWrite(0x00|i,0x03,((unsigned char)chan[i].envelope.lVRamp)<<8);
pageWrite(0x00|i,0x05,((unsigned char)chan[i].envelope.rVRamp)<<8);
pageWrite(0x00|i,0x0a,(((unsigned char)chan[i].envelope.k1Ramp)<<8)|(chan[i].envelope.k1Slow?1:0));
pageWrite(0x00|i,0x08,(((unsigned char)chan[i].envelope.k2Ramp)<<8)|(chan[i].envelope.k2Slow?1:0));
}
chan[i].rampChanged=false;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq*(chanMax+1),chan[i].pitch,false)+chan[i].std.pitch.val;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>0x1ffff) chan[i].freq=0x1ffff;
if (chan[i].keyOn) {
if (chan[i].pcm.index>=0) {
pageWriteMask(0x00|i,0x5f,0x00,0x0303); // Wipeout CR
pageWrite(0x00|i,0x06,0); // Clear ECOUNT
pageWrite(0x20|i,0x03,chan[i].pcm.base); // Set ACCUM to start address
pageWrite(0x00|i,0x09,0xffff); // Set K1 and K2 to 0xffff
pageWrite(0x00|i,0x07,0xffff,~0,(chanMax+1)*4*2); // needs to 4 sample period delay
pageWrite(0x00|i,0x01,chan[i].freq);
if (chan[i].pcm.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
pageWrite(0x20|i,0x01,chan[i].pcm.loopStart);
}
pageWrite(0x20|i,0x02,chan[i].pcm.loopEnd);
// initialize envelope
pageWrite(0x00|i,0x03,((unsigned char)chan[i].envelope.lVRamp)<<8);
pageWrite(0x00|i,0x05,((unsigned char)chan[i].envelope.rVRamp)<<8);
pageWrite(0x00|i,0x0a,(((unsigned char)chan[i].envelope.k1Ramp)<<8)|(chan[i].envelope.k1Slow?1:0));
pageWrite(0x00|i,0x08,(((unsigned char)chan[i].envelope.k2Ramp)<<8)|(chan[i].envelope.k2Slow?1:0));
// initialize filter
pageWriteMask(0x00|i,0x5f,0x00,(chan[i].pcm.bank<<14)|(chan[i].filter.mode<<8),0xc300);
pageWrite(0x00|i,0x09,chan[i].filter.k1);
pageWrite(0x00|i,0x07,chan[i].filter.k2);
pageWrite(0x00|i,0x02,chan[i].resLVol);
pageWrite(0x00|i,0x04,chan[i].resRVol);
unsigned int loopFlag=0x0000;
chan[i].isReversed=false;
switch (chan[i].pcm.loopMode) {
case DIV_SAMPLE_LOOPMODE_ONESHOT: // One shot (no loop)
default:
loopFlag=0x0000;
break;
case DIV_SAMPLE_LOOPMODE_FOWARD: // Foward loop
loopFlag=0x0008;
break;
case DIV_SAMPLE_LOOPMODE_BACKWARD: // Backward loop: IRQ enable
loopFlag=0x0038;
chan[i].isReversed=true;
break;
case DIV_SAMPLE_LOOPMODE_PINGPONG: // Pingpong loop: Hardware support
loopFlag=0x0018;
break;
}
// Run sample
pageWrite(0x00|i,0x06,chan[i].envelope.ecount); // Clear ECOUNT
pageWriteMask(0x00|i,0x5f,0x00,loopFlag,0x3cff);
}
}
if (chan[i].keyOff) {
pageWriteMask(0x00|i,0x5f,0x00,0x0003); // Wipeout CR
} else if (chan[i].active) {
pageWrite(0x00|i,0x01,chan[i].freq);
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
}
}
int DivPlatformES5506::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
chan[c.chan].sample=ins->amiga.useNoteMap?ins->amiga.noteMap[c.value].ind:ins->amiga.initSample;
double off=1.0;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
chan[c.chan].pcm.index=chan[c.chan].sample;
DivSample* s=parent->getSample(chan[c.chan].sample);
if (s->centerRate<1) {
off=1.0;
} else {
off=ins->amiga.useNoteMap?((double)ins->amiga.noteMap[c.value].freq/((double)s->centerRate*pow(2.0,((double)c.value-48.0)/12.0))):((double)s->centerRate/8363.0);
}
unsigned int base=s->offES5506<<10;
chan[c.chan].pcm.loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
chan[c.chan].pcm.freqOffs=off;
chan[c.chan].pcm.bank=(s->offES5506>>22)&3;
chan[c.chan].pcm.base=base;
chan[c.chan].pcm.loopStart=(base+(s->loopStart<<11))&0xfffff800;
chan[c.chan].pcm.loopEnd=((base+(s->loopEnd<<11))-0x800)&0xffffff80;
chan[c.chan].filter=ins->es5506.filter;
chan[c.chan].envelope=ins->es5506.envelope;
} else {
chan[c.chan].sample=-1;
chan[c.chan].pcm.index=-1;
chan[c.chan].filter=DivInstrumentES5506::Filter();
chan[c.chan].envelope=DivInstrumentES5506::Envelope();
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_ES5506(c.chan,c.value);
chan[c.chan].freqChanged=true;
chan[c.chan].volChanged=true;
chan[c.chan].note=c.value;
}
if (!chan[c.chan].std.vol.will) {
chan[c.chan].outVol=(0xffff*chan[c.chan].vol)/0xff;
}
if (!chan[c.chan].std.panL.will) {
chan[c.chan].outLVol=(ins->es5506.lVol*chan[c.chan].lVol)/0xf;
}
if (!chan[c.chan].std.panR.will) {
chan[c.chan].outRVol=(ins->es5506.rVol*chan[c.chan].rVol)/0xf;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].std.init(ins);
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].filter=DivInstrumentES5506::Filter();
chan[c.chan].envelope=DivInstrumentES5506::Envelope();
chan[c.chan].sample=-1;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].std.init(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=(0xffff*c.value)/0xff;
if (!isMuted[c.chan]) {
chan[c.chan].volChanged=true;
}
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PANNING: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
// 08LR, each nibble means volume multipler for each channels
// Left volume
unsigned char lVol=(c.value>>4)&0xf;
if (chan[c.chan].lVol!=lVol) {
chan[c.chan].lVol=lVol;
if (!chan[c.chan].std.panL.has) {
chan[c.chan].outLVol=(ins->es5506.lVol*lVol)/0xf;
if (!isMuted[c.chan]) {
chan[c.chan].volChanged=true;
}
}
}
// Right volume
unsigned char rVol=(c.value>>0)&0xf;
if (chan[c.chan].rVol!=rVol) {
chan[c.chan].rVol=rVol;
if (!chan[c.chan].std.panR.has) {
chan[c.chan].outRVol=(ins->es5506.rVol*rVol)/0xf;
if (!isMuted[c.chan]) {
chan[c.chan].volChanged=true;
}
}
}
break;
}
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_ES5506(c.chan,c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=NOTE_ES5506(c.chan,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
}
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 255;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformES5506::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
es5506.set_mute(ch,mute);
}
void DivPlatformES5506::forceIns() {
for (int i=0; i<=chanMax; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].volChanged=true;
chan[i].sample=-1;
}
}
void* DivPlatformES5506::getChanState(int ch) {
return &chan[ch];
}
void DivPlatformES5506::reset() {
while (!hostIntf32.empty()) hostIntf32.pop();
while (!hostIntf8.empty()) hostIntf8.pop();
for (int i=0; i<32; i++) {
chan[i]=DivPlatformES5506::Channel();
chan[i].std.setEngine(parent);
}
es5506.reset();
for (int i=0; i<32; i++) {
es5506.set_mute(i,isMuted[i]);
}
cycle=0;
curPage=0;
maskedVal=0;
irqv=0x80;
isMasked=false;
isReaded=false;
irqTrigger=false;
chanMax=initChanMax;
pageWriteMask(0x00,0x60,0x0b,chanMax);
pageWriteMask(0x00,0x60,0x0b,0x1f);
pageWriteMask(0x20,0x60,0x0a,0x01);
pageWriteMask(0x20,0x60,0x0b,0x11);
pageWriteMask(0x20,0x60,0x0c,0x20);
pageWriteMask(0x00,0x60,0x0c,0x08); // Reset serial output
}
bool DivPlatformES5506::isStereo() {
return true;
}
bool DivPlatformES5506::keyOffAffectsArp(int ch) {
return true;
}
void DivPlatformES5506::notifyInsChange(int ins) {
for (int i=0; i<32; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformES5506::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformES5506::notifyInsDeletion(void* ins) {
for (int i=0; i<32; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformES5506::setFlags(unsigned int flags) {
initChanMax=MAX(4,flags&0x1f);
chanMax=initChanMax;
pageWriteMask(0x00,0x60,0x0b,chanMax);
}
void DivPlatformES5506::poke(unsigned int addr, unsigned short val) {
immWrite(addr, val);
}
void DivPlatformES5506::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
}
unsigned char* DivPlatformES5506::getRegisterPool() {
unsigned char* regPoolPtr = regPool;
for (unsigned char p=0; p<128; p++) {
for (unsigned char r=0; r<16; r++) {
unsigned int reg=es5506.regs_r(p,r,false);
for (int b=0; b<4; b++) {
*regPoolPtr++ = reg>>(24-(b<<3));
}
}
}
return regPool;
}
int DivPlatformES5506::getRegisterPoolSize() {
return 4*16*128; // 7 bit page x 16 registers per page x 32 bit per registers
}
int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<32; i++) {
isMuted[i]=false;
}
setFlags(flags);
chipClock=16000000;
rate=chipClock/16;
reset();
return 32;
}
void DivPlatformES5506::quit() {
}

View file

@ -0,0 +1,173 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _ES5506_H
#define _ES5506_H
#pragma once
#include "../dispatch.h"
#include "../engine.h"
#include <queue>
#include "../macroInt.h"
#include "../sample.h"
#include "sound/es550x/es5506.hpp"
class DivPlatformES5506: public DivDispatch, public es550x_intf {
struct Channel {
struct PCM {
double freqOffs;
int index;
unsigned int bank;
unsigned int base;
unsigned int loopStart;
unsigned int loopEnd;
DivSampleLoopMode loopMode;
PCM():
freqOffs(1.0),
index(-1),
bank(0),
base(0),
loopStart(0),
loopEnd(0),
loopMode(DIV_SAMPLE_LOOPMODE_ONESHOT) {}
} pcm;
int freq, baseFreq, pitch;
unsigned short audLen;
unsigned int audPos;
int sample, wave;
unsigned char ins;
int note;
int panning;
bool active, insChanged, freqChanged, volChanged, filterChanged, envChanged, rampChanged, keyOn, keyOff, inPorta, useWave, isReversed;
int vol, outVol;
int lVol, outLVol;
int rVol, outRVol;
int resLVol, resRVol;
DivInstrumentES5506::Filter filter;
DivInstrumentES5506::Envelope envelope;
DivMacroInt std;
Channel():
freq(0),
baseFreq(0),
pitch(0),
audLen(0),
audPos(0),
sample(-1),
ins(-1),
note(0),
panning(0x10),
active(false),
insChanged(true),
freqChanged(false),
volChanged(false),
filterChanged(false),
envChanged(false),
rampChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
vol(0xffff),
outVol(0xffff),
lVol(0xffff),
outLVol(0xffff),
rVol(0xffff),
outRVol(0xffff),
resLVol(0xffff),
resRVol(0xffff) {}
};
Channel chan[32];
bool isMuted[32];
struct QueuedHostIntf {
unsigned char step;
unsigned char addr;
unsigned int val;
unsigned int mask;
unsigned int* read;
unsigned short delay;
bool isRead;
QueuedHostIntf(unsigned char s, unsigned char a, unsigned int v, unsigned int m=(unsigned int)(~0), unsigned short d=0):
step(s),
addr(a),
val(v),
mask(m),
read(NULL),
delay(0),
isRead(false) {}
QueuedHostIntf(unsigned char s, unsigned char a, unsigned int* r, unsigned int m=(unsigned int)(~0), unsigned short d=0):
step(s),
addr(a),
val(0),
mask(m),
read(r),
delay(d),
isRead(true) {}
};
std::queue<QueuedHostIntf> hostIntf32;
std::queue<QueuedHostIntf> hostIntf8;
int cycle, curPage;
unsigned char maskedVal;
unsigned int irqv;
bool isMasked, isReaded;
bool irqTrigger;
unsigned char initChanMax, chanMax;
es5506_core es5506;
unsigned char regPool[4*16*128]; // 7 bit page x 16 registers per page x 32 bit per registers
friend void putDispatchChan(void*,int,int);
public:
virtual void e(bool state) override; // E output
virtual void irqb(bool state) override; // IRQB output
virtual s16 read_sample(u8 voice, u8 bank, u32 address) override {
if (parent->es5506Mem==NULL) return 0;
return parent->es5506Mem[((bank&3)<<21)|(address&0x1fffff)];
}
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override;
virtual int dispatch(DivCommand c) override;
virtual void* getChanState(int chan) override;
virtual unsigned char* getRegisterPool() override;
virtual int getRegisterPoolSize() override;
virtual void reset() override;
virtual void forceIns() override;
virtual void tick() override;
virtual void muteChannel(int ch, bool mute) override;
virtual bool isStereo() override;
virtual bool keyOffAffectsArp(int ch) override;
virtual void setFlags(unsigned int flags) override;
virtual void notifyInsChange(int ins) override;
virtual void notifyWaveChange(int wave) override;
virtual void notifyInsDeletion(void* ins) override;
virtual void poke(unsigned int addr, unsigned short val) override;
virtual void poke(std::vector<DivRegWrite>& wlist) override;
virtual const char** getRegisterSheet() override;
virtual const char* getEffectName(unsigned char effect) override;
virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override;
virtual void quit() override;
DivPlatformES5506():
DivDispatch(),
es550x_intf(),
es5506(*this) {}
};
#endif

View file

@ -96,8 +96,9 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
}
}
if (++dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
dacPos++;
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
if (s->isLoopable()) {
dacPos=s->loopStart;
} else {
dacSample=-1;
@ -165,8 +166,9 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
}
}
if (++dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
dacPos++;
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
if (s->isLoopable()) {
dacPos=s->loopStart;
} else {
dacSample=-1;

View file

@ -62,8 +62,9 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len
if (!isMuted[4]) {
rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80));
}
if (++dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
dacPos++;
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
if (s->isLoopable()) {
dacPos=s->loopStart;
} else {
dacSample=-1;

View file

@ -89,8 +89,9 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len)
rWrite(0x4011,next);
}
}
if (++dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
dacPos++;
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
if (s->isLoopable()) {
dacPos=s->loopStart;
} else {
dacSample=-1;

View file

@ -90,8 +90,8 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
chWrite(i,0x04,0xdf);
chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3));
chan[i].dacPos++;
if (chan[i].dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].dacPos>=s->loopEnd) || (chan[i].dacPos>=s->samples)) {
if (s->isLoopable()) {
chan[i].dacPos=s->loopStart;
} else {
chan[i].dacSample=-1;

View file

@ -299,11 +299,11 @@ void DivPlatformQSound::tick() {
qsound_bank = 0x8000 | (s->offQSound >> 16);
qsound_addr = s->offQSound & 0xffff;
int length = s->samples;
int length = s->isLoopable()?s->loopEnd:s->samples;
if (length > 65536 - 16) {
length = 65536 - 16;
}
if (s->loopStart == -1 || s->loopStart >= length) {
if ((!s->isLoopable()) || s->loopStart>=length) {
qsound_end = s->offQSound + length + 15;
qsound_loop = 15;
} else {

View file

@ -53,8 +53,8 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t
pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR);
}
chan[i].pcm.pos+=chan[i].pcm.freq;
if (chan[i].pcm.pos>=(s->samples<<8)) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].pcm.pos>=(s->loopEnd<<8)) || (chan[i].pcm.pos>=(s->samples<<8))) {
if (s->isLoopable()) {
chan[i].pcm.pos=s->loopStart<<8;
} else {
chan[i].pcm.sample=-1;
@ -153,7 +153,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff);
addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8));
if (s->loopStart<0 || s->loopStart>=(int)s->length8) {
if (!s->isLoopable()) {
addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3));
} else {
int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP;
@ -183,7 +183,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff);
addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8));
if (s->loopStart<0 || s->loopStart>=(int)s->length8) {
if (!s->isLoopable()) {
addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3));
} else {
int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP;

View file

@ -0,0 +1,444 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504 emulation core
see es550x.cpp for more info
*/
#include "es5504.hpp"
// Internal functions
void es5504_core::tick()
{
// /CAS, E
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
{
// /CAS
if (m_cas.tick())
{
// /CAS high, E low: get sample address
if (m_cas.falling_edge())
{
// /CAS low, E low: fetch sample
if (!m_e.current_edge())
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
// E
if (m_clkin.falling_edge()) // falling edge triggers E clock
{
if (m_e.tick())
{
m_intf.e(m_e.current_edge());
if (m_e.rising_edge()) // Host access
{
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
voice_tick();
}
if (m_e.falling_edge()) // Voice memory
{
m_host_intf.m_host_access = false;
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
if (m_e.current_edge()) // Host interface
{
if (m_host_intf.m_host_access)
{
if (m_host_intf.m_rw && (m_e.cycle() == 2)) // Read
{
m_hd = read(m_ha);
m_host_intf.m_host_access = false;
}
else if ((!m_host_intf.m_rw) && (m_e.cycle() == 2)) // Write
write(m_ha, m_hd);
}
}
else if (!m_e.current_edge())
{
if (m_e.cycle() == 2)
{
// reset host access state
m_hd = 0;
m_host_intf.m_host_access_strobe = false;
}
}
}
}
}
// less cycle accurate, but less CPU heavy routine
void es5504_core::tick_perf()
{
// update
// falling edge
m_e.m_edge.set(false);
m_intf.e(false);
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.m_edge.set(true);
m_intf.e(true);
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
// falling edge
m_e.m_edge.set(false);
m_intf.e(false);
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.m_edge.set(true);
m_intf.e(true);
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
}
void es5504_core::voice_tick()
{
// Voice updates every 2 E clock cycle (= 1 CHSTRB cycle or 4 BCLK clock cycle)
if (bitfield(m_voice_fetch++, 0))
{
// Update voice
m_voice[m_voice_cycle].tick(m_voice_cycle);
// Refresh output (Multiplexed analog output)
m_ch[m_voice[m_voice_cycle].m_cr.ca] = m_voice[m_voice_cycle].m_ch;
if ((++m_voice_cycle) > std::min<u8>(24, m_active)) // ~ 25 voices
m_voice_cycle = 0;
m_voice_fetch = 0;
}
}
void es5504_core::voice_t::fetch(u8 voice, u8 cycle)
{
m_alu.m_sample[cycle] = m_host.m_intf.read_sample(voice, bitfield(m_cr.ca, 0, 3), bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer));
}
void es5504_core::voice_t::tick(u8 voice)
{
m_ch = 0;
// Filter execute
m_filter.tick(m_alu.interpolation());
if (m_alu.busy())
{
// Send to output
m_ch = ((sign_ext<s32>(m_filter.m_o4_1, 16) >> 3) * m_volume) >> 12; // Analog multiplied in real chip, 13/12 bit ladder DAC
// ALU execute
if (m_alu.tick())
{
m_alu.loop_exec();
}
// ADC check
adc_exec();
}
// Update IRQ
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
}
// ADC; Correct?
void es5504_core::voice_t::adc_exec()
{
if (m_cr.adc)
m_host.m_adc = m_host.m_intf.adc_r() & ~0x7;
}
void es5504_core::reset()
{
es550x_shared_core::reset();
for (auto & elem : m_voice)
elem.reset();
m_adc = 0;
std::fill(std::begin(m_ch), std::end(m_ch), 0);
}
void es5504_core::voice_t::reset()
{
es550x_shared_core::es550x_voice_t::reset();
m_volume = 0;
m_ch = 0;
}
// Accessors
u16 es5504_core::host_r(u8 address)
{
if (!m_host_intf.m_host_access)
{
m_ha = address;
if (m_e.rising_edge()) // update directly
m_hd = read(m_ha, true);
else
{
m_host_intf.m_rw_strobe = true;
m_host_intf.m_host_access_strobe = true;
}
}
return m_hd;
}
void es5504_core::host_w(u8 address, u16 data)
{
if (!m_host_intf.m_host_access)
{
m_ha = address;
m_hd = data;
if (m_e.rising_edge()) // update directly
write(m_ha, m_hd, true);
else
{
m_host_intf.m_rw_strobe = false;
m_host_intf.m_host_access_strobe = true;
}
}
}
u16 es5504_core::read(u8 address, bool cpu_access)
{
return regs_r(m_page, address, cpu_access);
}
void es5504_core::write(u8 address, u16 data, bool cpu_access)
{
regs_w(m_page, address, data, cpu_access);
}
u16 es5504_core::regs_r(u8 page, u8 address, bool cpu_access)
{
u16 ret = 0xffff;
address = bitfield(address, 0, 4); // 4 bit address for CPU access
if (address >= 12) // Global registers
{
switch (address)
{
case 12: // A/D (A to D Convert/Test)
ret = (ret & ~0xfffb) | (m_adc & 0xfffb);
break;
case 13: // ACT (Number of voices)
ret = (ret & ~0x1f) | bitfield(m_active, 0, 5);
break;
case 14: // IRQV (Interrupting voice vector)
ret = (ret & ~0x9f) | (m_irqv.irqb ? 0x80 : 0) | bitfield(m_irqv.voice, 0, 5);
if (cpu_access)
{
m_irqv.clear();
if (bitfield(ret, 7) != m_irqv.irqb)
m_voice[m_irqv.voice].m_alu.irq_update(m_intf, m_irqv);
}
break;
case 15: // PAGE (Page select register)
ret = (ret & ~0x3f) | bitfield(m_page, 0, 6);
break;
}
}
else // Voice specific registers
{
const u8 voice = bitfield(page, 0, 5); // Voice select
if (voice < 25)
{
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 56
{
switch (address)
{
case 1: // O4(n-1) (Filter 4 Temp Register)
ret = v.m_filter.m_o4_1;
break;
case 2: // O3(n-2) (Filter 3 Temp Register #2)
ret = v.m_filter.m_o3_2;
break;
case 3: // O3(n-1) (Filter 3 Temp Register #1)
ret = v.m_filter.m_o3_1;
break;
case 4: // O2(n-2) (Filter 2 Temp Register #2)
ret = v.m_filter.m_o2_2;
break;
case 5: // O2(n-1) (Filter 2 Temp Register #1)
ret = v.m_filter.m_o2_1;
break;
case 6: // O1(n-1) (Filter 1 Temp Register)
ret = v.m_filter.m_o1_1;
break;
}
}
else // Page 0 - 24
{
switch (address)
{
case 0: // CR (Control Register)
ret = (ret & ~0xff) |
(v.m_alu.m_cr.stop0 ? 0x01 : 0x00)
| (v.m_alu.m_cr.stop1 ? 0x02 : 0x00)
| (v.m_cr.adc ? 0x04 : 0x00)
| (v.m_alu.m_cr.lpe ? 0x08 : 0x00)
| (v.m_alu.m_cr.ble ? 0x10 : 0x00)
| (v.m_alu.m_cr.irqe ? 0x20 : 0x00)
| (v.m_alu.m_cr.dir ? 0x40 : 0x00)
| (v.m_alu.m_cr.irq ? 0x80 : 0x00);
break;
case 1: // FC (Frequency Control)
ret = (ret & ~0xfffe) | (v.m_alu.m_fc << 1);
break;
case 2: // STRT-H (Loop Start Register High)
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_start, 16, 13);
break;
case 3: // STRT-L (Loop Start Register Low)
ret = (ret & ~0xffe0) | (v.m_alu.m_start & 0xffe0);
break;
case 4: // END-H (Loop End Register High)
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_end, 16, 13);
break;
case 5: // END-L (Loop End Register Low)
ret = (ret & ~0xffe0) | (v.m_alu.m_end & 0xffe0);
break;
case 6: // K2 (Filter Cutoff Coefficient #2)
ret = (ret & ~0xfff0) | (v.m_filter.m_k2 & 0xfff0);
break;
case 7: // K1 (Filter Cutoff Coefficient #1)
ret = (ret & ~0xfff0) | (v.m_filter.m_k1 & 0xfff0);
break;
case 8: // Volume
ret = (ret & ~0xfff0) | ((v.m_volume << 4) & 0xfff0);
break;
case 9: // CA (Filter Config, Channel Assign)
ret = (ret & ~0x3f) |
bitfield(v.m_cr.ca, 0, 4)
| (bitfield(v.m_filter.m_lp, 0, 2) << 4);
break;
case 10: // ACCH (Accumulator High)
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_accum, 16, 13);
break;
case 11: // ACCL (Accumulator Low)
ret = bitfield(v.m_alu.m_accum, 0, 16);
break;
}
}
}
}
return ret;
}
void es5504_core::regs_w(u8 page, u8 address, u16 data, bool cpu_access)
{
address = bitfield(address, 0, 4); // 4 bit address for CPU access
if (address >= 12) // Global registers
{
switch (address)
{
case 12: // A/D (A to D Convert/Test)
if (bitfield(m_adc, 0)) // Writable ADC
{
m_adc = (m_adc & 7) | (data & ~7);
m_intf.adc_w(m_adc & ~7);
}
m_adc = (m_adc & ~3) | (data & 3);
break;
case 13: // ACT (Number of voices)
m_active = std::min<u8>(24, bitfield(data, 0, 5));
break;
case 14: // IRQV (Interrupting voice vector)
// Read only
break;
case 15: // PAGE (Page select register)
m_page = bitfield(data, 0, 6);
break;
}
}
else // Voice specific registers
{
const u8 voice = bitfield(page, 0, 5); // Voice select
if (voice < 25)
{
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 56
{
switch (address)
{
case 1: // O4(n-1) (Filter 4 Temp Register)
v.m_filter.m_o4_1 = sign_ext<s32>(data, 16);
break;
case 2: // O3(n-2) (Filter 3 Temp Register #2)
v.m_filter.m_o3_2 = sign_ext<s32>(data, 16);
break;
case 3: // O3(n-1) (Filter 3 Temp Register #1)
v.m_filter.m_o3_1 = sign_ext<s32>(data, 16);
break;
case 4: // O2(n-2) (Filter 2 Temp Register #2)
v.m_filter.m_o2_2 = sign_ext<s32>(data, 16);
break;
case 5: // O2(n-1) (Filter 2 Temp Register #1)
v.m_filter.m_o2_1 = sign_ext<s32>(data, 16);
break;
case 6: // O1(n-1) (Filter 1 Temp Register)
v.m_filter.m_o1_1 = sign_ext<s32>(data, 16);
break;
}
}
else // Page 0 - 24
{
switch (address)
{
case 0: // CR (Control Register)
v.m_alu.m_cr.stop0 = bitfield(data, 0);
v.m_alu.m_cr.stop1 = bitfield(data, 1);
v.m_cr.adc = bitfield(data, 2);
v.m_alu.m_cr.lpe = bitfield(data, 3);
v.m_alu.m_cr.ble = bitfield(data, 4);
v.m_alu.m_cr.irqe = bitfield(data, 5);
v.m_alu.m_cr.dir = bitfield(data, 6);
v.m_alu.m_cr.irq = bitfield(data, 7);
break;
case 1: // FC (Frequency Control)
v.m_alu.m_fc = bitfield(data, 1, 15);
break;
case 2: // STRT-H (Loop Start Register High)
v.m_alu.m_start = (v.m_alu.m_start & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
break;
case 3: // STRT-L (Loop Start Register Low)
v.m_alu.m_start = (v.m_alu.m_start & ~0xffe0) | (data & 0xffe0);
break;
case 4: // END-H (Loop End Register High)
v.m_alu.m_end = (v.m_alu.m_end & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
break;
case 5: // END-L (Loop End Register Low)
v.m_alu.m_end = (v.m_alu.m_end & ~0xffe0) | (data & 0xffe0);
break;
case 6: // K2 (Filter Cutoff Coefficient #2)
v.m_filter.m_k2 = data & 0xfff0;
break;
case 7: // K1 (Filter Cutoff Coefficient #1)
v.m_filter.m_k1 = data & 0xfff0;
break;
case 8: // Volume
v.m_volume = bitfield(data, 4, 12);
break;
case 9: // CA (Filter Config, Channel Assign)
v.m_cr.ca = bitfield(data, 0, 4);
v.m_filter.m_lp = bitfield(data, 4, 2);
break;
case 10: // ACCH (Accumulator High)
v.m_alu.m_accum = (v.m_alu.m_accum & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
break;
case 11: // ACCL (Accumulator Low)
v.m_alu.m_accum = (v.m_alu.m_accum & ~0xffff) | data;
break;
}
}
}
}
}

View file

@ -0,0 +1,87 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504 emulation core
See es550x.cpp for more info
*/
#include "es550x.hpp"
#ifndef _VGSOUND_EMU_ES5504_HPP
#define _VGSOUND_EMU_ES5504_HPP
#pragma once
// ES5504 specific
class es5504_core : public es550x_shared_core
{
public:
// constructor
es5504_core(es550x_intf &intf)
: es550x_shared_core(intf)
, m_voice{*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this}
{
}
// host interface
u16 host_r(u8 address);
void host_w(u8 address, u16 data);
// internal state
virtual void reset() override;
virtual void tick() override;
// less cycle accurate, but also less cpu heavy update routine
void tick_perf();
// 16 analog output channels
s32 out(u8 ch) { return m_ch[ch & 0xf]; }
// bypass chips host interface for debug purpose only
u16 read(u8 address, bool cpu_access = false);
void write(u8 address, u16 data, bool cpu_access = false);
u16 regs_r(u8 page, u8 address, bool cpu_access = false);
void regs_w(u8 page, u8 address, u16 data, bool cpu_access = false);
u16 regs_r(u8 page, u8 address) { u8 prev = m_page; m_page = page; u16 ret = read(address, false); m_page = prev; return ret; }
protected:
virtual inline u8 max_voices() override { return 25; }
virtual void voice_tick() override;
private:
// es5504 voice structs
struct voice_t : es550x_voice_t
{
// constructor
voice_t(es5504_core &host)
: es550x_voice_t(20, 9, false)
, m_host(host)
{}
// internal state
virtual void reset() override;
virtual void fetch(u8 voice, u8 cycle) override;
virtual void tick(u8 voice) override;
void adc_exec();
// registers
es5504_core &m_host;
u16 m_volume = 0; // 12 bit Volume
s32 m_ch = 0; // channel outputs
};
voice_t m_voice[25]; // 25 voices
u16 m_adc = 0; // ADC register
s32 m_ch[16] = {0}; // 16 channel outputs
};
#endif

View file

@ -0,0 +1,599 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5505 emulation core
see es550x.cpp for more info
*/
#include "es5505.hpp"
// Internal functions
void es5505_core::tick()
{
// CLKIN
if (m_clkin.tick())
{
// SERBCLK
if (m_clkin.m_edge.m_changed) // BCLK is freely running clock
{
if (m_bclk.tick())
{
m_intf.bclk(m_bclk.current_edge());
// Serial output
if (m_bclk.falling_edge())
{
// SERLRCLK
if (m_lrclk.tick())
m_intf.lrclk(m_lrclk.current_edge());
}
// SERWCLK
if (m_lrclk.m_edge.m_changed)
m_wclk = 0;
if (m_bclk.falling_edge())
{
if (m_wclk == ((m_sermode.sony_bb) ? 1 : 0))
{
if (m_lrclk.current_edge())
{
for (int i = 0; i < 4; i++)
{
// copy output
m_output[i] = m_output_temp[i];
m_output_latch[i] = m_ch[i];
m_output_temp[i].reset();
// clamp to 16 bit (upper 5 bits are overflow guard bits)
m_output_latch[i].m_left = clamp<s32>(m_output_latch[i].m_left, -0x8000, 0x7fff);
m_output_latch[i].m_right = clamp<s32>(m_output_latch[i].m_right, -0x8000, 0x7fff);
// set signed
if (m_output_latch[i].m_left < 0)
m_output_temp[i].m_left = -1;
if (m_output_latch[i].m_right < 0)
m_output_temp[i].m_right = -1;
}
}
m_wclk_lr = m_lrclk.current_edge();
m_output_bit = 16;
}
s8 output_bit = --m_output_bit;
if (m_output_bit >= 0)
{
for (int i = 0; i < 4; i++)
{
if (m_wclk_lr) // Right output
m_output_temp[i].m_right = (m_output_temp[i].m_left << 1) | bitfield(m_output_latch[i].m_right, output_bit);
else // Left output
m_output_temp[i].m_left = (m_output_temp[i].m_left << 1) | bitfield(m_output_latch[i].m_left, output_bit);
}
}
m_wclk++;
}
}
}
// /CAS, E
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
{
// /CAS
if (m_cas.tick())
{
// /CAS high, E low: get sample address
if (m_cas.falling_edge())
{
// /CAS low, E low: fetch sample
if (!m_e.current_edge())
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
// E
if (m_e.tick())
{
m_intf.e(m_e.current_edge());
if (m_e.rising_edge()) // Host access
{
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
voice_tick();
}
else if (m_e.falling_edge()) // Voice memory
{
m_host_intf.m_host_access = false;
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
if (m_e.current_edge()) // Host interface
{
if (m_host_intf.m_host_access)
{
if (m_host_intf.m_rw && (m_e.cycle() == 2)) // Read
{
m_hd = read(m_ha);
m_host_intf.m_host_access = false;
}
else if ((!m_host_intf.m_rw) && (m_e.cycle() == 2)) // Write
write(m_ha, m_hd);
}
}
else if (!m_e.current_edge())
{
if (m_e.cycle() == 2)
{
// reset host access state
m_hd = 0;
m_host_intf.m_host_access_strobe = false;
}
}
}
}
}
}
// less cycle accurate, but less CPU heavy routine
void es5505_core::tick_perf()
{
// output
for (int c = 0; c < 4; c++)
{
m_output[c].m_left = clamp<s32>(m_ch[c].m_left, -0x8000, 0x7fff);
m_output[c].m_right = clamp<s32>(m_ch[c].m_right, -0x8000, 0x7fff);
}
// update
// falling edge
m_e.m_edge.set(false);
m_intf.e(false);
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.m_edge.set(true);
m_intf.e(true);
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
// falling edge
m_e.m_edge.set(false);
m_intf.e(false);
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.m_edge.set(true);
m_intf.e(true);
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
}
void es5505_core::voice_tick()
{
// Voice updates every 2 E clock cycle (or 4 BCLK clock cycle)
if (bitfield(m_voice_fetch++, 0))
{
// Update voice
m_voice[m_voice_cycle].tick(m_voice_cycle);
// Refresh output
if ((++m_voice_cycle) > clamp<u8>(m_active, 7, 31)) // 8 ~ 32 voices
{
m_voice_cycle = 0;
for (auto & elem : m_ch)
elem.reset();
for (auto & elem : m_voice)
{
m_ch[bitfield(elem.m_cr.ca, 0, 2)].m_left += elem.m_ch.m_left;
m_ch[bitfield(elem.m_cr.ca, 0, 2)].m_right += elem.m_ch.m_right;
elem.m_ch.reset();
}
}
m_voice_fetch = 0;
}
}
void es5505_core::voice_t::fetch(u8 voice, u8 cycle)
{
m_alu.m_sample[cycle] = m_host.m_intf.read_sample(voice, bitfield(m_cr.bs, 0), bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer));
}
void es5505_core::voice_t::tick(u8 voice)
{
m_ch.reset();
// Filter execute
m_filter.tick(m_alu.interpolation());
if (m_alu.busy())
{
// Send to output
m_ch.m_left = volume_calc(m_lvol, sign_ext<s32>(m_filter.m_o4_1, 16));
m_ch.m_right = volume_calc(m_rvol, sign_ext<s32>(m_filter.m_o4_1, 16));
// ALU execute
if (m_alu.tick())
m_alu.loop_exec();
}
// Update IRQ
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
}
// volume calculation
s32 es5505_core::voice_t::volume_calc(u8 volume, s32 in)
{
u8 exponent = bitfield(volume, 4, 4);
u8 mantissa = bitfield(volume, 0, 4);
return exponent ? (in * s32(0x10 | mantissa)) >> (20 - exponent) : 0;
}
void es5505_core::reset()
{
es550x_shared_core::reset();
for (auto & elem : m_voice)
elem.reset();
m_sermode.reset();
m_bclk.reset();
m_lrclk.reset();
m_wclk = 0;
m_wclk_lr = false;
m_output_bit = 0;
for (auto & elem : m_ch)
elem.reset();
for (auto & elem : m_output)
elem.reset();
for (auto & elem : m_output_temp)
elem.reset();
for (auto & elem : m_output_latch)
elem.reset();
}
void es5505_core::voice_t::reset()
{
es550x_shared_core::es550x_voice_t::reset();
m_lvol = 0;
m_rvol = 0;
m_ch.reset();
}
// Accessors
u16 es5505_core::host_r(u8 address)
{
if (!m_host_intf.m_host_access)
{
m_ha = address;
if (m_e.rising_edge()) // update directly
m_hd = read(m_ha, true);
else
{
m_host_intf.m_rw_strobe = true;
m_host_intf.m_host_access_strobe = true;
}
}
return m_hd;
}
void es5505_core::host_w(u8 address, u16 data)
{
if (!m_host_intf.m_host_access)
{
m_ha = address;
m_hd = data;
if (m_e.rising_edge()) // update directly
write(m_ha, m_hd, true);
else
{
m_host_intf.m_rw_strobe = false;
m_host_intf.m_host_access_strobe = true;
}
}
}
u16 es5505_core::read(u8 address, bool cpu_access)
{
return regs_r(m_page, address, cpu_access);
}
void es5505_core::write(u8 address, u16 data, bool cpu_access)
{
regs_w(m_page, address, data, cpu_access);
}
u16 es5505_core::regs_r(u8 page, u8 address, bool cpu_access)
{
u16 ret = 0xffff;
address = bitfield(address, 0, 4); // 4 bit address for CPU access
if (address >= 13) // Global registers
{
switch (address)
{
case 13: // ACT (Number of voices)
ret = (ret & ~0x1f) | bitfield(m_active, 0, 5);
break;
case 14: // IRQV (Interrupting voice vector)
ret = (ret & ~0x9f) | (m_irqv.irqb ? 0x80 : 0) | bitfield(m_irqv.voice, 0, 5);
if (cpu_access)
{
m_irqv.clear();
if (bitfield(ret, 7) != m_irqv.irqb)
m_voice[m_irqv.voice].m_alu.irq_update(m_intf, m_irqv);
}
break;
case 15: // PAGE (Page select register)
ret = (ret & ~0x7f) | bitfield(m_page, 0, 7);
break;
}
}
else
{
if (bitfield(page, 6)) // Channel registers
{
switch (address)
{
case 0: // CH0L (Channel 0 Left)
case 2: // CH1L (Channel 1 Left)
case 4: // CH2L (Channel 2 Left)
if (!cpu_access) // CPU can't read here
ret = m_ch[bitfield(address, 0, 2)].m_left;
break;
case 1: // CH0R (Channel 0 Right)
case 3: // CH1R (Channel 1 Right)
case 5: // CH2R (Channel 2 Right)
if (!cpu_access) // CPU can't read here
ret = m_ch[bitfield(address, 0, 2)].m_right;
break;
case 6: // CH3L (Channel 3 Left)
if ((!cpu_access) || m_sermode.adc)
ret = m_ch[3].m_left;
break;
case 7: // CH3R (Channel 3 Right)
if ((!cpu_access) || m_sermode.adc)
ret = m_ch[3].m_right;
break;
case 8: // SERMODE (Serial Mode)
ret = (ret & ~0xf807) |
(m_sermode.adc ? 0x01 : 0x00)
| (m_sermode.test ? 0x02 : 0x00)
| (m_sermode.sony_bb ? 0x04 : 0x00)
| (bitfield(m_sermode.msb, 0, 5) << 11);
break;
case 9: // PAR (Port A/D Register)
ret = (ret & ~0x3f) | (m_intf.adc_r() & ~0x3f);
break;
}
}
else // Voice specific registers
{
const u8 voice = bitfield(page, 0, 5); // Voice select
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 63
{
switch (address)
{
case 1: // O4(n-1) (Filter 4 Temp Register)
ret = v.m_filter.m_o4_1;
break;
case 2: // O3(n-2) (Filter 3 Temp Register #2)
ret = v.m_filter.m_o3_2;
break;
case 3: // O3(n-1) (Filter 3 Temp Register #1)
ret = v.m_filter.m_o3_1;
break;
case 4: // O2(n-2) (Filter 2 Temp Register #2)
ret = v.m_filter.m_o2_2;
break;
case 5: // O2(n-1) (Filter 2 Temp Register #1)
ret = v.m_filter.m_o2_1;
break;
case 6: // O1(n-1) (Filter 1 Temp Register)
ret = v.m_filter.m_o1_1;
break;
}
}
else // Page 0 - 31
{
switch (address)
{
case 0: // CR (Control Register)
ret = (ret & ~0xfff) |
(v.m_alu.m_cr.stop0 ? 0x01 : 0x00)
| (v.m_alu.m_cr.stop1 ? 0x02 : 0x00)
| (bitfield(v.m_cr.bs, 0) ? 0x04 : 0x00)
| (v.m_alu.m_cr.lpe ? 0x08 : 0x00)
| (v.m_alu.m_cr.ble ? 0x10 : 0x00)
| (v.m_alu.m_cr.irqe ? 0x20 : 0x00)
| (v.m_alu.m_cr.dir ? 0x40 : 0x00)
| (v.m_alu.m_cr.irq ? 0x80 : 0x00)
| (bitfield(v.m_cr.ca, 0, 2) << 8)
| (bitfield(v.m_filter.m_lp, 0, 2) << 10);
break;
case 1: // FC (Frequency Control)
ret = (ret & ~0xfffe) | (bitfield(v.m_alu.m_fc, 0, 15) << 1);
break;
case 2: // STRT-H (Loop Start Register High)
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_start, 16, 13);
break;
case 3: // STRT-L (Loop Start Register Low)
ret = (ret & ~0xffe0) | (v.m_alu.m_start & 0xffe0);
break;
case 4: // END-H (Loop End Register High)
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_end, 16, 13);
break;
case 5: // END-L (Loop End Register Low)
ret = (ret & ~0xffe0) | (v.m_alu.m_end & 0xffe0);
break;
case 6: // K2 (Filter Cutoff Coefficient #2)
ret = (ret & ~0xfff0) | (v.m_filter.m_k2 & 0xfff0);
break;
case 7: // K1 (Filter Cutoff Coefficient #1)
ret = (ret & ~0xfff0) | (v.m_filter.m_k1 & 0xfff0);
break;
case 8: // LVOL (Left Volume)
ret = (ret & ~0xff00) | ((v.m_lvol << 8) & 0xff00);
break;
case 9: // RVOL (Right Volume)
ret = (ret & ~0xff00) | ((v.m_rvol << 8) & 0xff00);
break;
case 10: // ACCH (Accumulator High)
ret = (ret & ~0x1fff) | bitfield(v.m_alu.m_accum, 16, 13);
break;
case 11: // ACCL (Accumulator Low)
ret = bitfield(v.m_alu.m_accum, 0, 16);
break;
}
}
}
}
return ret;
}
void es5505_core::regs_w(u8 page, u8 address, u16 data, bool cpu_access)
{
address = bitfield(address, 0, 4); // 4 bit address for CPU access
if (address >= 12) // Global registers
{
switch (address)
{
case 13: // ACT (Number of voices)
m_active = clamp<u8>(bitfield(data, 0, 5), 7, 31);
break;
case 14: // IRQV (Interrupting voice vector)
// Read only
break;
case 15: // PAGE (Page select register)
m_page = bitfield(data, 0, 7);
break;
}
}
else // Voice specific registers
{
if (bitfield(page, 6)) // Channel registers
{
switch (address)
{
case 0: // CH0L (Channel 0 Left)
if (m_sermode.test)
m_ch[0].m_left = data;
break;
case 1: // CH0R (Channel 0 Right)
if (m_sermode.test)
m_ch[0].m_right = data;
break;
case 2: // CH1L (Channel 1 Left)
if (m_sermode.test)
m_ch[1].m_left = data;
break;
case 3: // CH1R (Channel 1 Right)
if (m_sermode.test)
m_ch[1].m_right = data;
break;
case 4: // CH2L (Channel 2 Left)
if (m_sermode.test)
m_ch[2].m_left = data;
break;
case 5: // CH2R (Channel 2 Right)
if (m_sermode.test)
m_ch[2].m_right = data;
break;
case 6: // CH3L (Channel 3 Left)
if (m_sermode.test)
m_ch[3].m_left = data;
break;
case 7: // CH3R (Channel 3 Right)
if (m_sermode.test)
m_ch[3].m_right = data;
break;
case 8: // SERMODE (Serial Mode)
m_sermode.adc = bitfield(data, 0);
m_sermode.test = bitfield(data, 1);
m_sermode.sony_bb = bitfield(data, 2);
m_sermode.msb = bitfield(data, 11, 5);
break;
case 9: // PAR (Port A/D Register)
// Read only
break;
}
}
else // Voice specific registers
{
const u8 voice = bitfield(page, 0, 5); // Voice select
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 56
{
switch (address)
{
case 1: // O4(n-1) (Filter 4 Temp Register)
v.m_filter.m_o4_1 = sign_ext<s32>(data, 16);
break;
case 2: // O3(n-2) (Filter 3 Temp Register #2)
v.m_filter.m_o3_2 = sign_ext<s32>(data, 16);
break;
case 3: // O3(n-1) (Filter 3 Temp Register #1)
v.m_filter.m_o3_1 = sign_ext<s32>(data, 16);
break;
case 4: // O2(n-2) (Filter 2 Temp Register #2)
v.m_filter.m_o2_2 = sign_ext<s32>(data, 16);
break;
case 5: // O2(n-1) (Filter 2 Temp Register #1)
v.m_filter.m_o2_1 = sign_ext<s32>(data, 16);
break;
case 6: // O1(n-1) (Filter 1 Temp Register)
v.m_filter.m_o1_1 = sign_ext<s32>(data, 16);
break;
}
}
else // Page 0 - 24
{
switch (address)
{
case 0: // CR (Control Register)
v.m_alu.m_cr.stop0 = bitfield(data, 0);
v.m_alu.m_cr.stop1 = bitfield(data, 1);
v.m_cr.bs = bitfield(data, 2);
v.m_alu.m_cr.lpe = bitfield(data, 3);
v.m_alu.m_cr.ble = bitfield(data, 4);
v.m_alu.m_cr.irqe = bitfield(data, 5);
v.m_alu.m_cr.dir = bitfield(data, 6);
v.m_alu.m_cr.irq = bitfield(data, 7);
v.m_cr.ca = bitfield(data, 8, 2);
v.m_filter.m_lp = bitfield(data, 10, 2);
break;
case 1: // FC (Frequency Control)
v.m_alu.m_fc = bitfield(data, 1, 15);
break;
case 2: // STRT-H (Loop Start Register High)
v.m_alu.m_start = (v.m_alu.m_start & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
break;
case 3: // STRT-L (Loop Start Register Low)
v.m_alu.m_start = (v.m_alu.m_start & ~0xffe0) | (data & 0xffe0);
break;
case 4: // END-H (Loop End Register High)
v.m_alu.m_end = (v.m_alu.m_end & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
break;
case 5: // END-L (Loop End Register Low)
v.m_alu.m_end = (v.m_alu.m_end & ~0xffe0) | (data & 0xffe0);
break;
case 6: // K2 (Filter Cutoff Coefficient #2)
v.m_filter.m_k2 = data & 0xfff0;
break;
case 7: // K1 (Filter Cutoff Coefficient #1)
v.m_filter.m_k1 = data & 0xfff0;
break;
case 8: // LVOL (Left Volume)
v.m_lvol = bitfield(data, 8, 8);
break;
case 9: // RVOL (Right Volume)
v.m_rvol = bitfield(data, 8, 8);
break;
case 10: // ACCH (Accumulator High)
v.m_alu.m_accum = (v.m_alu.m_accum & ~0x1fff0000) | (bitfield<u32>(data, 0, 13) << 16);
break;
case 11: // ACCL (Accumulator Low)
v.m_alu.m_accum = (v.m_alu.m_accum & ~0xffff) | data;
break;
}
}
}
}
}

View file

@ -0,0 +1,141 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504 emulation core
See es550x.cpp for more info
*/
#include "es550x.hpp"
#ifndef _VGSOUND_EMU_ES5505_HPP
#define _VGSOUND_EMU_ES5505_HPP
#pragma once
// ES5505 specific
class es5505_core : public es550x_shared_core
{
public:
// constructor
es5505_core(es550x_intf &intf)
: es550x_shared_core(intf)
, m_voice{*this,*this,*this,*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this,*this,*this,*this}
{
}
// host interface
u16 host_r(u8 address);
void host_w(u8 address, u16 data);
// internal state
virtual void reset() override;
virtual void tick() override;
// less cycle accurate, but also less cpu heavy update routine
void tick_perf();
// clock outputs
bool bclk() { return m_bclk.current_edge(); }
bool bclk_rising_edge() { return m_bclk.rising_edge(); }
bool bclk_falling_edge() { return m_bclk.falling_edge(); }
// Input mode for Channel 3
void lin(s32 in) { if (m_sermode.adc) { m_ch[3].m_left = in; } }
void rin(s32 in) { if (m_sermode.adc) { m_ch[3].m_right = in; } }
// 4 stereo output channels
s32 lout(u8 ch) { return m_ch[ch & 0x3].m_left; }
s32 rout(u8 ch) { return m_ch[ch & 0x3].m_right; }
// bypass chips host interface for debug purpose only
u16 read(u8 address, bool cpu_access = false);
void write(u8 address, u16 data, bool cpu_access = false);
u16 regs_r(u8 page, u8 address, bool cpu_access = false);
void regs_w(u8 page, u8 address, u16 data, bool cpu_access = false);
u16 regs_r(u8 page, u8 address) { u8 prev = m_page; m_page = page; u16 ret = read(address, false); m_page = prev; return ret; }
protected:
virtual inline u8 max_voices() override { return 32; }
virtual void voice_tick() override;
private:
struct output_t
{
void reset()
{
m_left = 0;
m_right = 0;
};
s32 m_left = 0;
s32 m_right = 0;
};
// es5505 voice structs
struct voice_t : es550x_voice_t
{
// constructor
voice_t(es5505_core &host)
: es550x_voice_t(20, 9, false)
, m_host(host)
{}
// internal state
virtual void reset() override;
virtual void fetch(u8 voice, u8 cycle) override;
virtual void tick(u8 voice) override;
s32 volume_calc(u8 volume, s32 in);
// registers
es5505_core &m_host;
u8 m_lvol = 0; // Left volume
u8 m_rvol = 0; // Right volume
output_t m_ch; // channel output
};
struct sermode_t
{
sermode_t()
: adc(0)
, test(0)
, sony_bb(0)
, msb(0)
{};
void reset()
{
adc = 0;
test = 0;
sony_bb = 0;
msb = 0;
}
u8 adc : 1; // A/D
u8 test : 1; // Test
u8 sony_bb : 1; // Sony/BB format serial output
u8 msb : 5; // Serial output MSB
};
voice_t m_voice[32]; // 32 voices
// Serial related stuffs
sermode_t m_sermode; // Serial mode register
clock_pulse_t<s8, 4, 0> m_bclk; // BCLK clock (CLKIN / 4), freely running clock
clock_pulse_t<s8, 16, 1> m_lrclk; // LRCLK
s16 m_wclk = 0; // WCLK
bool m_wclk_lr = false; // WCLK, L/R output select
u8 m_output_bit = 0; // Bit position in output
output_t m_ch[4]; // 4 stereo output channels
output_t m_output[4]; // Serial outputs
output_t m_output_temp[4]; // temporary signal for serial output
output_t m_output_latch[4]; // output latch
};
#endif

View file

@ -0,0 +1,787 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5506 emulation core
see es550x.cpp for more info
*/
#include "es5506.hpp"
// Internal functions
void es5506_core::tick()
{
// CLKIN
if (m_clkin.tick())
{
// BCLK
if (m_clkin.m_edge.m_changed && (!m_mode.bclk_en)) // BCLK is freely running clock
{
if (m_bclk.tick())
{
m_intf.bclk(m_bclk.current_edge());
// Serial output
if (!m_mode.lrclk_en)
{
if (m_bclk.falling_edge())
{
// LRCLK
if (m_lrclk.tick())
{
m_intf.lrclk(m_lrclk.current_edge());
if (m_lrclk.rising_edge())
{
m_w_st_curr = m_w_st;
m_w_end_curr = m_w_end;
}
if (m_lrclk.falling_edge()) // update width
m_lrclk.set_width_latch(m_lr_end);
}
}
}
// WCLK
if (!m_mode.wclk_en)
{
if (!m_mode.lrclk_en)
{
if (m_lrclk.m_edge.m_changed)
m_wclk = 0;
}
if (m_bclk.falling_edge())
{
if (m_wclk == m_w_st_curr)
{
m_intf.wclk(true);
if (m_lrclk.current_edge())
{
for (int i = 0; i < 6; i++)
{
// copy output
m_output[i] = m_output_temp[i];
m_output_latch[i] = m_ch[i];
m_output_temp[i].reset();
// clamp to 20 bit (upper 3 bits are overflow guard bits)
m_output_latch[i].m_left = clamp<s32>(m_output_latch[i].m_left, -0x80000, 0x7ffff);
m_output_latch[i].m_right = clamp<s32>(m_output_latch[i].m_right, -0x80000, 0x7ffff);
// set signed
if (m_output_latch[i].m_left < 0)
m_output_temp[i].m_left = -1;
if (m_output_latch[i].m_right < 0)
m_output_temp[i].m_right = -1;
}
}
m_wclk_lr = m_lrclk.current_edge();
m_output_bit = 20;
}
if (m_wclk < m_w_end_curr)
{
s8 output_bit = --m_output_bit;
if (m_output_bit >= 0)
{
for (int i = 0; i < 6; i++)
{
if (m_wclk_lr) // Right output
m_output_temp[i].m_right = (m_output_temp[i].m_left << 1) | bitfield(m_output_latch[i].m_right, output_bit);
else // Left output
m_output_temp[i].m_left = (m_output_temp[i].m_left << 1) | bitfield(m_output_latch[i].m_left, output_bit);
}
}
}
if (m_wclk == m_w_end_curr)
m_intf.wclk(false);
m_wclk++;
}
}
}
}
// /CAS, E
if (m_clkin.falling_edge()) // falling edge triggers /CAS, E clock
{
// /CAS
if (m_cas.tick())
{
// single OTTO master mode, /CAS high, E low: get sample address
// single OTTO early mode, /CAS falling, E high: get sample address
if (m_cas.falling_edge())
{
if (!m_e.current_edge())
{
// single OTTO master mode, /CAS low, E low: fetch sample
if (m_mode.master)
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
else if (m_e.current_edge())
{
// dual OTTO slave mode, /CAS low, E high: fetch sample
if (m_mode.dual && (!m_mode.master)) // Dual OTTO, slave mode
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
}
}
}
// E
if (m_e.tick())
{
m_intf.e(m_e.current_edge());
if (m_e.rising_edge())
{
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
}
else if (m_e.falling_edge())
{
m_host_intf.m_host_access = false;
voice_tick();
}
if (m_e.current_edge()) // Host interface
{
if (m_host_intf.m_host_access)
{
if (m_host_intf.m_rw && (m_e.cycle() == 0)) // Read
{
m_hd = read(m_ha);
m_host_intf.m_host_access = false;
}
else if ((!m_host_intf.m_rw) && (m_e.cycle() == 2)) // Write
write(m_ha, m_hd);
}
}
else if (!m_e.current_edge())
{
if (m_e.cycle() == 2)
{
// reset host access state
m_hd = 0;
m_host_intf.m_host_access_strobe = false;
}
}
}
}
}
}
// less cycle accurate, but less CPU heavy routine
void es5506_core::tick_perf()
{
// output
if (((!m_mode.lrclk_en) && (!m_mode.bclk_en) && (!m_mode.bclk_en)) && (m_w_st < m_w_end))
{
const int output_bits = 20 - (m_w_end - m_w_st);
if (output_bits < 20)
{
for (int c = 0; c < 6; c++)
{
m_output[c].m_left = clamp<s32>(m_ch[c].m_left, -0x80000, 0x7ffff) >> output_bits;
m_output[c].m_right = clamp<s32>(m_ch[c].m_right, -0x80000, 0x7ffff) >> output_bits;
}
}
}
else
{
for (int c = 0; c < 6; c++)
{
m_output[c].m_left = 0;
m_output[c].m_right = 0;
}
}
// update
// falling edge
m_e.m_edge.set(false);
m_intf.e(false);
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.m_edge.set(true);
m_intf.e(true);
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
// falling edge
m_e.m_edge.set(false);
m_intf.e(false);
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe = false;
m_voice[m_voice_cycle].fetch(m_voice_cycle, m_voice_fetch);
voice_tick();
// rising edge
m_e.m_edge.set(true);
m_intf.e(true);
m_host_intf.m_rw = m_host_intf.m_rw_strobe;
m_host_intf.m_host_access = m_host_intf.m_host_access_strobe;
}
void es5506_core::voice_tick()
{
// Voice updates every 2 E clock cycle (or 4 BCLK clock cycle)
if (bitfield(m_voice_fetch++, 0))
{
// Update voice
m_voice[m_voice_cycle].tick(m_voice_cycle);
// Refresh output
if ((++m_voice_cycle) > clamp<u8>(m_active, 4, 31)) // 5 ~ 32 voices
{
m_voice_cycle = 0;
for (auto & elem : m_ch)
elem.reset();
for (auto & elem : m_voice)
{
const u8 ca = bitfield(elem.m_cr.ca, 0, 3);
if (ca < 6)
{
m_ch[ca].m_left += elem.m_ch.m_left;
m_ch[ca].m_right += elem.m_ch.m_right;
}
elem.m_ch.reset();
}
}
m_voice_fetch = 0;
}
}
void es5506_core::voice_t::fetch(u8 voice, u8 cycle)
{
m_alu.m_sample[cycle] = m_host.m_intf.read_sample(voice, bitfield(m_cr.bs, 0, 1), bitfield(m_alu.get_accum_integer() + cycle, 0, m_alu.m_integer));
if (m_cr.cmpd) // Decompress (Upper 8 bit is used for compressed format)
m_alu.m_sample[cycle] = decompress(bitfield(m_alu.m_sample[cycle], 8, 8));
}
void es5506_core::voice_t::tick(u8 voice)
{
m_ch.reset();
// Filter execute
m_filter.tick(m_alu.interpolation());
if (m_alu.busy())
{
if (!m_mute)
{
// Send to output
m_ch.m_left = volume_calc(sign_ext<s32>(m_filter.m_o4_1, 16), m_lvol);
m_ch.m_right = volume_calc(sign_ext<s32>(m_filter.m_o4_1, 16), m_rvol);
}
// ALU execute
if (m_alu.tick())
m_alu.loop_exec();
}
// Envelope
if (m_ecount != 0)
{
// Left and Right volume
if (bitfield(m_lvramp, 0, 8) != 0)
m_lvol = clamp<s32>(m_lvol + sign_ext<s32>(bitfield(m_lvramp, 0, 8), 8), 0, 0xffff);
if (bitfield(m_rvramp, 0, 8) != 0)
m_rvol = clamp<s32>(m_rvol + sign_ext<s32>(bitfield(m_rvramp, 0, 8), 8), 0, 0xffff);
// Filter coeffcient
if ((m_k1ramp.ramp != 0) && ((m_k1ramp.slow == 0) || (bitfield(m_filtcount, 0, 3) == 0)))
m_filter.m_k1 = clamp<s32>(m_filter.m_k1 + sign_ext<s32>(m_k1ramp.ramp, 8), 0, 0xffff);
if ((m_k2ramp.ramp != 0) && ((m_k2ramp.slow == 0) || (bitfield(m_filtcount, 0, 3) == 0)))
m_filter.m_k2 = clamp<s32>(m_filter.m_k2 + sign_ext<s32>(m_k2ramp.ramp, 8), 0, 0xffff);
m_ecount--;
}
m_filtcount = bitfield(m_filtcount + 1, 0, 3);
// Update IRQ
m_alu.irq_exec(m_host.m_intf, m_host.m_irqv, voice);
}
// Compressed format
s16 es5506_core::voice_t::decompress(u8 sample)
{
u8 exponent = bitfield(sample, 5, 3);
u8 mantissa = bitfield(sample, 0, 5);
return (exponent > 0) ?
s16(((bitfield(mantissa, 4) ? 0x10 : ~0x1f) | bitfield(mantissa, 0, 4)) << (4 + (exponent - 1))) :
s16(((bitfield(mantissa, 4) ? ~0xf : 0) | bitfield(mantissa, 0, 4)) << 4);
}
// volume calculation
s32 es5506_core::voice_t::volume_calc(u16 volume, s32 in)
{
u8 exponent = bitfield(volume, 12, 4);
u8 mantissa = bitfield(volume, 4, 8);
return (in * s32(0x100 | mantissa)) >> (20 - exponent);
}
void es5506_core::reset()
{
es550x_shared_core::reset();
for (auto & elem : m_voice)
elem.reset();
m_read_latch = 0xffffffff;
m_write_latch = 0xffffffff;
m_w_st = 0;
m_w_end = 0;
m_lr_end = 0;
m_w_st_curr = 0;
m_w_end_curr = 0;
m_mode.reset();
m_bclk.reset();
m_lrclk.reset(32);
m_wclk = 0;
m_wclk_lr = false;
m_output_bit = 0;
for (auto & elem : m_ch)
elem.reset();
for (auto & elem : m_output)
elem.reset();
for (auto & elem : m_output_temp)
elem.reset();
for (auto & elem : m_output_latch)
elem.reset();
}
void es5506_core::voice_t::reset()
{
es550x_shared_core::es550x_voice_t::reset();
m_lvol = 0;
m_lvramp = 0;
m_rvol = 0;
m_rvramp = 0;
m_ecount = 0;
m_k2ramp.reset();
m_k1ramp.reset();
m_filtcount = 0;
m_ch.reset();
m_mute = false;
}
// Accessors
u8 es5506_core::host_r(u8 address)
{
if (!m_host_intf.m_host_access)
{
m_ha = address;
if (m_e.rising_edge()) // update directly
m_hd = read(m_ha, true);
else
{
m_host_intf.m_rw_strobe = true;
m_host_intf.m_host_access_strobe = true;
}
}
return m_hd;
}
void es5506_core::host_w(u8 address, u8 data)
{
if (!m_host_intf.m_host_access)
{
m_ha = address;
m_hd = data;
if (m_e.rising_edge()) // update directly
write(m_ha, m_hd, true);
else
{
m_host_intf.m_rw_strobe = false;
m_host_intf.m_host_access_strobe = true;
}
}
}
u8 es5506_core::read(u8 address, bool cpu_access)
{
const u8 byte = bitfield(address, 0, 2); // byte select
const u8 shift = 24 - (byte << 3);
if (byte != 0) // Return already latched register if not highest byte is accessing
return bitfield(m_read_latch, shift, 8);
address = bitfield(address, 2, 4); // 4 bit address for CPU access
// get read register
m_read_latch = regs_r(m_page, address, cpu_access);
return bitfield(m_read_latch, 24, 8);
}
void es5506_core::write(u8 address, u8 data, bool cpu_access)
{
const u8 byte = bitfield(address, 0, 2); // byte select
const u8 shift = 24 - (byte << 3);
address = bitfield(address, 2, 4); // 4 bit address for CPU access
// Update register latch
m_write_latch = (m_write_latch & ~(0xff << shift)) | (u32(data) << shift);
if (byte != 3) // Wait until lowest byte is writed
return;
regs_w(m_page, address, m_write_latch, cpu_access);
// Reset latch
m_write_latch = 0;
}
u32 es5506_core::regs_r(u8 page, u8 address, bool cpu_access)
{
u32 read_latch = 0xffffffff;
if (address >= 13) // Global registers
{
switch (address)
{
case 13: // POT (Pot A/D Register)
read_latch = (read_latch & ~0x3ff) | bitfield(m_intf.adc_r(), 0, 10);
break;
case 14: // IRQV (Interrupting voice vector)
read_latch = (read_latch & ~0x9f) | (m_irqv.irqb ? 0x80 : 0) | bitfield(m_irqv.voice, 0, 5);
if (cpu_access)
{
m_irqv.clear();
if (bitfield(read_latch, 7) != m_irqv.irqb)
m_voice[m_irqv.voice].m_alu.irq_update(m_intf, m_irqv);
}
break;
case 15: // PAGE (Page select register)
read_latch = (read_latch & ~0x7f) | bitfield(m_page, 0, 7);
break;
}
}
else
{
if (bitfield(page, 6)) // Channel registers are Write only
{
if (!cpu_access) // CPU can't read here
{
switch (address)
{
case 0: // CH0L (Channel 0 Left)
case 2: // CH1L (Channel 1 Left)
case 4: // CH2L (Channel 2 Left)
case 6: // CH3L (Channel 3 Left)
case 8: // CH4L (Channel 4 Left)
case 10: // CH5L (Channel 5 Left)
read_latch = m_ch[bitfield(address, 1, 3)].m_left;
break;
case 1: // CH0R (Channel 0 Right)
case 3: // CH1R (Channel 1 Right)
case 5: // CH2R (Channel 2 Right)
case 7: // CH3R (Channel 3 Right)
case 9: // CH4R (Channel 4 Right)
case 11: // CH5R (Channel 5 Right)
read_latch = m_ch[bitfield(address, 1, 3)].m_right;
break;
}
}
}
else
{
const u8 voice = bitfield(page, 0, 5); // Voice select
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 63
{
switch (address)
{
case 0: // CR (Control Register)
read_latch = (read_latch & ~0xffff) |
(v.m_alu.m_cr.stop0 ? 0x0001 : 0x0000)
| (v.m_alu.m_cr.stop1 ? 0x0002 : 0x0000)
| (v.m_alu.m_cr.lei ? 0x0004 : 0x0000)
| (v.m_alu.m_cr.lpe ? 0x0008 : 0x0000)
| (v.m_alu.m_cr.ble ? 0x0010 : 0x0000)
| (v.m_alu.m_cr.irqe ? 0x0020 : 0x0000)
| (v.m_alu.m_cr.dir ? 0x0040 : 0x0000)
| (v.m_alu.m_cr.irq ? 0x0080 : 0x0000)
| (bitfield(v.m_filter.m_lp, 0, 2) << 8)
| (bitfield(v.m_cr.ca, 0, 3) << 10)
| (v.m_cr.cmpd ? 0x2000 : 0x0000)
| (bitfield(v.m_cr.bs, 0, 2) << 14);
break;
case 1: // START (Loop Start Register)
read_latch = (read_latch & ~0xfffff800) | (v.m_alu.m_start & 0xfffff800);
break;
case 2: // END (Loop End Register)
read_latch = (read_latch & ~0xffffff80) | (v.m_alu.m_end & 0xffffff80);
break;
case 3: // ACCUM (Accumulator Register)
read_latch = v.m_alu.m_accum;
break;
case 4: // O4(n-1) (Filter 4 Temp Register)
if (cpu_access)
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o4_1, 0, 18);
else
read_latch = v.m_filter.m_o4_1;
break;
case 5: // O3(n-2) (Filter 3 Temp Register #2)
if (cpu_access)
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o3_2, 0, 18);
else
read_latch = v.m_filter.m_o3_2;
break;
case 6: // O3(n-1) (Filter 3 Temp Register #1)
if (cpu_access)
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o3_1, 0, 18);
else
read_latch = v.m_filter.m_o3_1;
break;
case 7: // O2(n-2) (Filter 2 Temp Register #2)
if (cpu_access)
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o2_2, 0, 18);
else
read_latch = v.m_filter.m_o2_2;
break;
case 8: // O2(n-1) (Filter 2 Temp Register #1)
if (cpu_access)
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o2_1, 0, 18);
else
read_latch = v.m_filter.m_o2_1;
break;
case 9: // O1(n-1) (Filter 1 Temp Register)
if (cpu_access)
read_latch = (read_latch & ~0x3ffff) | bitfield(v.m_filter.m_o1_1, 0, 18);
else
read_latch = v.m_filter.m_o1_1;
break;
case 10: // W_ST (Word Clock Start Register)
read_latch = (read_latch & ~0x7f) | bitfield(m_w_st, 0, 7);
break;
case 11: // W_END (Word Clock End Register)
read_latch = (read_latch & ~0x7f) | bitfield(m_w_end, 0, 7);
break;
case 12: // LR_END (Left/Right Clock End Register)
read_latch = (read_latch & ~0x7f) | bitfield(m_lr_end, 0, 7);
break;
}
}
else // Page 0 - 31
{
switch (address)
{
case 0: // CR (Control Register)
read_latch = (read_latch & ~0xffff) |
(v.m_alu.m_cr.stop0 ? 0x0001 : 0x0000)
| (v.m_alu.m_cr.stop1 ? 0x0002 : 0x0000)
| (v.m_alu.m_cr.lei ? 0x0004 : 0x0000)
| (v.m_alu.m_cr.lpe ? 0x0008 : 0x0000)
| (v.m_alu.m_cr.ble ? 0x0010 : 0x0000)
| (v.m_alu.m_cr.irqe ? 0x0020 : 0x0000)
| (v.m_alu.m_cr.dir ? 0x0040 : 0x0000)
| (v.m_alu.m_cr.irq ? 0x0080 : 0x0000)
| (bitfield(v.m_filter.m_lp, 0, 2) << 8)
| (bitfield(v.m_cr.ca, 0, 3) << 10)
| (v.m_cr.cmpd ? 0x2000 : 0x0000)
| (bitfield(v.m_cr.bs, 0, 2) << 14);
break;
case 1: // FC (Frequency Control)
read_latch = (read_latch & ~0x1ffff) | bitfield(v.m_alu.m_fc, 0, 17);
break;
case 2: // LVOL (Left Volume)
read_latch = (read_latch & ~0xffff) | bitfield(v.m_lvol, 0, 16);
break;
case 3: // LVRAMP (Left Volume Ramp)
read_latch = (read_latch & ~0xff00) | (bitfield(v.m_lvramp, 0, 8) << 8);
break;
case 4: // RVOL (Right Volume)
read_latch = (read_latch & ~0xffff) | bitfield(v.m_rvol, 0, 16);
break;
case 5: // RVRAMP (Right Volume Ramp)
read_latch = (read_latch & ~0xff00) | (bitfield(v.m_rvramp, 0, 8) << 8);
break;
case 6: // ECOUNT (Envelope Counter)
read_latch = (read_latch & ~0x01ff) | bitfield(v.m_ecount, 0, 9);
break;
case 7: // K2 (Filter Cutoff Coefficient #2)
read_latch = (read_latch & ~0xffff) | bitfield(v.m_filter.m_k2, 0, 16);
break;
case 8: // K2RAMP (Filter Cutoff Coefficient #2 Ramp)
read_latch = (read_latch & ~0xff01) | (bitfield(v.m_k2ramp.ramp, 0, 8) << 8) | (v.m_k2ramp.slow ? 0x0001 : 0x0000);
break;
case 9: // K1 (Filter Cutoff Coefficient #1)
read_latch = (read_latch & ~0xffff) | bitfield(v.m_filter.m_k1, 0, 16);
break;
case 10: // K1RAMP (Filter Cutoff Coefficient #1 Ramp)
read_latch = (read_latch & ~0xff01) | (bitfield(v.m_k1ramp.ramp, 0, 8) << 8) | (v.m_k1ramp.slow ? 0x0001 : 0x0000);
break;
case 11: // ACT (Number of voices)
read_latch = (read_latch & ~0x1f) | bitfield(m_active, 0, 5);
break;
case 12: // MODE (Global Mode)
read_latch = (read_latch & ~0x1f) |
(m_mode.lrclk_en ? 0x01 : 0x00)
| (m_mode.wclk_en ? 0x02 : 0x00)
| (m_mode.bclk_en ? 0x04 : 0x00)
| (m_mode.master ? 0x08 : 0x00)
| (m_mode.dual ? 0x10 : 0x00);
break;
}
}
}
}
return read_latch;
}
void es5506_core::regs_w(u8 page, u8 address, u32 data, bool cpu_access)
{
if (address >= 13) // Global registers
{
switch (address)
{
case 13: // POT (Pot A/D Register)
// Read only
break;
case 14: // IRQV (Interrupting voice vector)
// Read only
break;
case 15: // PAGE (Page select register)
m_page = bitfield(data, 0, 7);
break;
}
}
else
{
if (bitfield(page, 6)) // Channel registers are Write only, and for test purposes
{
switch (address)
{
case 0: // CH0L (Channel 0 Left)
case 2: // CH1L (Channel 1 Left)
case 4: // CH2L (Channel 2 Left)
case 6: // CH3L (Channel 3 Left)
case 8: // CH4L (Channel 4 Left)
case 10: // CH5L (Channel 5 Left)
m_ch[bitfield(address, 1, 3)].m_left = sign_ext<s32>(bitfield(data, 0, 23), 23);
break;
case 1: // CH0R (Channel 0 Right)
case 3: // CH1R (Channel 1 Right)
case 5: // CH2R (Channel 2 Right)
case 7: // CH3R (Channel 3 Right)
case 9: // CH4R (Channel 4 Right)
case 11: // CH5R (Channel 5 Right)
m_ch[bitfield(address, 1, 3)].m_right = sign_ext<s32>(bitfield(data, 0, 23), 23);
break;
}
}
else
{
const u8 voice = bitfield(page, 0, 5); // Voice select
voice_t &v = m_voice[voice];
if (bitfield(page, 5)) // Page 32 - 63
{
switch (address)
{
case 0: // CR (Control Register)
v.m_alu.m_cr.stop0 = bitfield(data, 0);
v.m_alu.m_cr.stop1 = bitfield(data, 1);
v.m_alu.m_cr.lei = bitfield(data, 2);
v.m_alu.m_cr.lpe = bitfield(data, 3);
v.m_alu.m_cr.ble = bitfield(data, 4);
v.m_alu.m_cr.irqe = bitfield(data, 5);
v.m_alu.m_cr.dir = bitfield(data, 6);
v.m_alu.m_cr.irq = bitfield(data, 7);
v.m_filter.m_lp = bitfield(data, 8, 2);
v.m_cr.ca = std::min<u8>(5, bitfield(data, 10, 3));
v.m_cr.cmpd = bitfield(data, 13);
v.m_cr.bs = bitfield(data, 14, 2);
break;
case 1: // START (Loop Start Register)
v.m_alu.m_start = data & 0xfffff800;
break;
case 2: // END (Loop End Register)
v.m_alu.m_end = data & 0xffffff80;
break;
case 3: // ACCUM (Accumulator Register)
v.m_alu.m_accum = data;
break;
case 4: // O4(n-1) (Filter 4 Temp Register)
v.m_filter.m_o4_1 = sign_ext<s32>(bitfield(data, 0, 18), 18);
break;
case 5: // O3(n-2) (Filter 3 Temp Register #2)
v.m_filter.m_o3_2 = sign_ext<s32>(bitfield(data, 0, 18), 18);
break;
case 6: // O3(n-1) (Filter 3 Temp Register #1)
v.m_filter.m_o3_1 = sign_ext<s32>(bitfield(data, 0, 18), 18);
break;
case 7: // O2(n-2) (Filter 2 Temp Register #2)
v.m_filter.m_o2_2 = sign_ext<s32>(bitfield(data, 0, 18), 18);
break;
case 8: // O2(n-1) (Filter 2 Temp Register #1)
v.m_filter.m_o2_1 = sign_ext<s32>(bitfield(data, 0, 18), 18);
break;
case 9: // O1(n-1) (Filter 1 Temp Register)
v.m_filter.m_o1_1 = sign_ext<s32>(bitfield(data, 0, 18), 18);
break;
case 10: // W_ST (Word Clock Start Register)
m_w_st = bitfield(data, 0, 7);
break;
case 11: // W_END (Word Clock End Register)
m_w_end = bitfield(data, 0, 7);
break;
case 12: // LR_END (Left/Right Clock End Register)
m_lr_end = bitfield(data, 0, 7);
m_lrclk.set_width(m_lr_end);
break;
}
}
else // Page 0 - 31
{
switch (address)
{
case 0: // CR (Control Register)
v.m_alu.m_cr.stop0 = bitfield(data, 0);
v.m_alu.m_cr.stop1 = bitfield(data, 1);
v.m_alu.m_cr.lei = bitfield(data, 2);
v.m_alu.m_cr.lpe = bitfield(data, 3);
v.m_alu.m_cr.ble = bitfield(data, 4);
v.m_alu.m_cr.irqe = bitfield(data, 5);
v.m_alu.m_cr.dir = bitfield(data, 6);
v.m_alu.m_cr.irq = bitfield(data, 7);
v.m_filter.m_lp = bitfield(data, 8, 2);
v.m_cr.ca = std::min<u8>(5, bitfield(data, 10, 3));
v.m_cr.cmpd = bitfield(data, 13);
v.m_cr.bs = bitfield(data, 14, 2);
break;
case 1: // FC (Frequency Control)
v.m_alu.m_fc = bitfield(data, 0, 17);
break;
case 2: // LVOL (Left Volume)
v.m_lvol = bitfield(data, 0, 16);
break;
case 3: // LVRAMP (Left Volume Ramp)
v.m_lvramp = bitfield(data, 8, 8);
break;
case 4: // RVOL (Right Volume)
v.m_rvol = bitfield(data, 0, 16);
break;
case 5: // RVRAMP (Right Volume Ramp)
v.m_rvramp = bitfield(data, 8, 8);
break;
case 6: // ECOUNT (Envelope Counter)
v.m_ecount = bitfield(data, 0, 9);
break;
case 7: // K2 (Filter Cutoff Coefficient #2)
v.m_filter.m_k2 = bitfield(data, 0, 16);
break;
case 8: // K2RAMP (Filter Cutoff Coefficient #2 Ramp)
v.m_k2ramp.slow = bitfield(data, 0);
v.m_k2ramp.ramp = bitfield(data, 8, 8);
break;
case 9: // K1 (Filter Cutoff Coefficient #1)
v.m_filter.m_k1 = bitfield(data, 0, 16);
break;
case 10: // K1RAMP (Filter Cutoff Coefficient #1 Ramp)
v.m_k1ramp.slow = bitfield(data, 0);
v.m_k1ramp.ramp = bitfield(data, 8, 8);
break;
case 11: // ACT (Number of voices)
m_active = std::min<u8>(4, bitfield(data, 0, 5));
break;
case 12: // MODE (Global Mode)
m_mode.lrclk_en = bitfield(data, 0);
m_mode.wclk_en = bitfield(data, 1);
m_mode.bclk_en = bitfield(data, 2);
m_mode.master = bitfield(data, 3);
m_mode.dual = bitfield(data, 4);
break;
}
}
}
}
}

View file

@ -0,0 +1,179 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504 emulation core
See es550x.cpp for more info
*/
#include "es550x.hpp"
#ifndef _VGSOUND_EMU_ES5506_HPP
#define _VGSOUND_EMU_ES5506_HPP
#pragma once
// ES5506 specific
class es5506_core : public es550x_shared_core
{
public:
// constructor
es5506_core(es550x_intf &intf)
: es550x_shared_core(intf)
, m_voice{*this,*this,*this,*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this,*this,*this,*this,
*this,*this,*this,*this,*this,*this,*this,*this}
{
}
// host interface
u8 host_r(u8 address);
void host_w(u8 address, u8 data);
// internal state
virtual void reset() override;
virtual void tick() override;
// less cycle accurate, but also less cpu heavy update routine
void tick_perf();
// clock outputs
bool bclk() { return m_bclk.current_edge(); }
bool bclk_rising_edge() { return m_bclk.rising_edge(); }
bool bclk_falling_edge() { return m_bclk.falling_edge(); }
// 6 stereo output channels
s32 lout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].m_left; }
s32 rout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].m_right; }
// bypass chips host interface for debug purpose only
u8 read(u8 address, bool cpu_access = false);
void write(u8 address, u8 data, bool cpu_access = false);
u32 regs_r(u8 page, u8 address, bool cpu_access = false);
void regs_w(u8 page, u8 address, u32 data, bool cpu_access = false);
u8 regs8_r(u8 page, u8 address) { u8 prev = m_page; m_page = page; u8 ret = read(address, false); m_page = prev; return ret; }
void set_mute(u8 ch, bool mute) { m_voice[ch & 0x1f].m_mute = mute; }
protected:
virtual inline u8 max_voices() override { return 32; }
virtual void voice_tick() override;
private:
struct output_t
{
void reset()
{
m_left = 0;
m_right = 0;
};
s32 m_left = 0;
s32 m_right = 0;
};
// es5506 voice structs
struct voice_t : es550x_voice_t
{
// constructor
voice_t(es5506_core &host)
: es550x_voice_t(21, 11, true)
, m_host(host) {}
// internal state
virtual void reset() override;
virtual void fetch(u8 voice, u8 cycle) override;
virtual void tick(u8 voice) override;
// accessors, getters, setters
s16 decompress(u8 sample);
s32 volume_calc(u16 volume, s32 in);
struct filter_ramp_t
{
filter_ramp_t()
: slow(0)
, ramp(0)
{ };
void reset()
{
slow = 0;
ramp = 0;
};
u16 slow : 1; // Slow mode flag
u16 ramp = 8; // Ramp value
};
// registers
es5506_core &m_host;
s32 m_lvol = 0; // Left volume - 4 bit exponent, 8 bit mantissa, 4 LSBs are used for fine control of ramp increment for hardware envelope
s32 m_lvramp = 0; // Left volume ramp
s32 m_rvol = 0; // Right volume
s32 m_rvramp = 0; // Righr volume ramp
s16 m_ecount = 0; // Envelope counter
filter_ramp_t m_k2ramp; // Filter coefficient 2 Ramp
filter_ramp_t m_k1ramp; // Filter coefficient 1 Ramp
u8 m_filtcount = 0; // Internal counter for slow mode
output_t m_ch; // channel output
bool m_mute = false; // mute flag (for debug purpose)
};
// 5 bit mode
struct mode_t
{
mode_t()
: bclk_en(0)
, wclk_en(0)
, lrclk_en(0)
, master(0)
, dual(0)
{ };
void reset()
{
bclk_en = 1;
wclk_en = 1;
lrclk_en = 1;
master = 0;
dual = 0;
}
u8 bclk_en : 1; // Set BCLK to output
u8 wclk_en : 1; // Set WCLK to output
u8 lrclk_en : 1; // Set LRCLK to output
u8 master : 1; // Set memory mode to master
u8 dual : 1; // Set dual chip config
};
voice_t m_voice[32]; // 32 voices
// Host interfaces
u32 m_read_latch = 0; // 32 bit register latch for host read
u32 m_write_latch = 0; // 32 bit register latch for host write
// Serial register
u8 m_w_st = 0; // Word clock start register
u8 m_w_end = 0; // Word clock end register
u8 m_lr_end = 0; // Left/Right clock end register
mode_t m_mode; // Global mode
// Serial related stuffs
u8 m_w_st_curr = 0; // Word clock start, current status
u8 m_w_end_curr = 0; // Word clock end register
clock_pulse_t<s8, 4, 0> m_bclk; // BCLK clock (CLKIN / 4), freely running clock
clock_pulse_t<s8, 32, 1> m_lrclk; // LRCLK
s16 m_wclk = 0; // WCLK
bool m_wclk_lr = false; // WCLK, L/R output select
u8 m_output_bit = 0; // Bit position in output
output_t m_ch[6]; // 6 stereo output channels
output_t m_output[6]; // Serial outputs
output_t m_output_temp[6]; // temporary signal for serial output
output_t m_output_latch[6]; // output latch
};
#endif

View file

@ -0,0 +1,75 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 emulation core
After ES5503 DOC's appeared, Ensoniq announces ES5504 DOC II, ES5505 OTIS, ES5506 OTTO.
These are not just PCM chip; but with built-in 4 pole filters and variable voice limits.
It can be trades higher sample rate and finer frequency and Tons of voices, or vice-versa.
These are mainly used with their synthesizers, musical stuffs. It's also mainly paired with ES5510 ESP/ES5511 ESP2 for post processing.
ES5506 can be paired with itself, It's called Dual chip configuration and Both chips are can be shares same memory spaces.
ES5505 was also mainly used on Taito's early- to late-90s arcade hardware for their PCM sample based sound system,
paired with ES5510 ESP for post processing. It's configuration is borrowed from Ensoniq's 32 Voice synths powered by these chips.
It's difference is external logic to adds per-voice bankswitching looks like what Konami doing on K007232.
Atari Panther was will be use ES5505, but finally canceled.
Ensoniq's ISA Sound Card for PC, Soundscape used ES5506, "Elite" model has optional daughterboard with ES5510 for digital effects.
Related chips:
ES5530 "OPUS" variant is 2-in-one chip with built-in ES5506 and Sequoia.
ES5540 "OTTOFX" variant is ES5506 and ES5510 merged in single package.
ES5548 "OTTO48" variant is used at late-90s ensoniq synths and musical instruments, 2 ES5506s are merged in single package, or with 48 voices in chip?
Chip difference:
ES5504 to ES5505:
Total voice amount is expanded to 32, rather than 25.
ADC and DAC is completely redesigned. it's has now voice-independent 10 bit and Sony/Burr-Brown format DAC.
Output channel and Volume is changed to 16 mono to 4 stereo, 12 bit Analog to 8 bit Stereo digital, also Floating point-ish format and independent per left and right output.
Channel 3 is can be Input/Output.
Channel output is can be accessible at host for test purpose.
Max sample memory is expanded to 2MWords (1MWords * 2 Banks)
ES5505 to ES5506:
Frequency is more finer now: 11 bit fraction rather than 9 bit.
Output channel and Volume is changed to 4 stereo to 6 stereo, 8 bit to 16 bit, but only 12 bit is used for calculation; 4 LSB is used for envelope ramping.
Transwave flag is added - its helpful for transwave process, with interrupt per voices.
Hardware envelope is added - K1, K2, Volume value is can be modified in run-time. also K1, K2 is expanded to 16 bit for finer envelope ramping.
Filter calculation resolution is expanded to 18 bit.
All channels are output, Serial output is now partially programmable.
Max sample memory is expanded to 8MWords (2MWords * 4 Banks)
Register format between these chips are incompatible.
*/
#include "es550x.hpp"
// Shared functions
void es550x_shared_core::reset()
{
m_host_intf.reset();
m_ha = 0;
m_hd = 0;
m_page = 0;
m_irqv.reset();
m_active = max_voices() - 1;
m_voice_cycle = 0;
m_voice_fetch = 0;
m_clkin.reset();
m_cas.reset();
m_e.reset();
}
void es550x_shared_core::es550x_voice_t::reset()
{
m_cr.reset();
m_alu.reset();
m_filter.reset();
}

View file

@ -0,0 +1,391 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 emulation core
See es550x.cpp for more info
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_ES550X_HPP
#define _VGSOUND_EMU_ES550X_HPP
#pragma once
namespace es550x
{
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef signed char s8;
typedef signed short s16;
typedef signed int s32;
// get bitfield, bitfield(input, position, len)
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
// get sign extended value, sign_ext<type>(input, len)
template<typename T> T sign_ext(T in, u8 len)
{
len = std::max<u8>(0, (8 * sizeof(T)) - len);
return T(T(in) << len) >> len;
}
// std::clamp is only for C++17 or later; I use my own code
template<typename T> T clamp(T in, T min, T max)
{
return (in > max) ? max : ((in < min) ? min : in);
}
template<typename T, T InitWidth, u8 InitEdge = 0>
struct clock_pulse_t
{
void reset(T init = InitWidth)
{
m_edge.reset();
m_width = m_width_latch = m_counter = init;
m_cycle = 0;
}
bool tick(T width = 0)
{
bool carry = ((--m_counter) <= 0);
if (carry)
{
if (!width)
m_width = m_width_latch;
else
m_width = width; // reset width
m_counter = m_width;
m_cycle = 0;
}
else
m_cycle++;
m_edge.tick(carry);
return carry;
}
void set_width(T width) { m_width = width; }
void set_width_latch(T width) { m_width_latch = width; }
// Accessors
bool current_edge() { return m_edge.m_current; }
bool rising_edge() { return m_edge.m_rising; }
bool falling_edge() { return m_edge.m_rising; }
T cycle() { return m_cycle; }
struct edge_t
{
edge_t()
: m_current(InitEdge ^ 1)
, m_previous(InitEdge)
, m_rising(0)
, m_falling(0)
, m_changed(0)
{
set(InitEdge);
}
void tick(bool toggle)
{
u8 current = m_current;
if (toggle)
current ^= 1;
set(current);
}
void set(u8 edge)
{
edge &= 1;
m_rising = m_falling = m_changed = 0;
if (m_current != edge)
{
m_changed = 1;
if (m_current && (!edge))
m_falling = 1;
else if ((!m_current) && edge)
m_rising = 1;
m_current = edge;
}
m_previous = m_current;
}
void reset()
{
m_previous = InitEdge;
m_current = InitEdge ^ 1;
set(InitEdge);
}
u8 m_current : 1; // current edge
u8 m_previous : 1; // previous edge
u8 m_rising : 1; // rising edge
u8 m_falling : 1; // falling edge
u8 m_changed : 1; // changed flag
};
edge_t m_edge;
T m_width = InitWidth; // clock pulse width
T m_width_latch = InitWidth; // clock pulse width latch
T m_counter = InitWidth; // clock counter
T m_cycle = 0; // clock cycle
};
};
// ES5504/ES5505/ES5506 interface
using namespace es550x;
class es550x_intf
{
public:
virtual void e(bool state) {} // E output
virtual void bclk(bool state) {} // BCLK output (serial specific)
virtual void lrclk(bool state) {} // LRCLK output (serial specific)
virtual void wclk(bool state) {} // WCLK output (serial specific)
virtual void irqb(bool state) {} // IRQB output
virtual u16 adc_r() { return 0; } // ADC input
virtual void adc_w(u16 data) {} // ADC output
virtual s16 read_sample(u8 voice, u8 bank, u32 address) { return 0; }
};
// Shared functions for ES5504/ES5505/ES5506
using namespace es550x;
class es550x_shared_core
{
friend class es550x_intf; // es550x specific memory interface
public:
// constructor
es550x_shared_core(es550x_intf &intf)
: m_intf(intf)
{ };
// internal state
virtual void reset();
virtual void tick() {}
// clock outputs
bool _cas() { return m_cas.current_edge(); }
bool _cas_rising_edge() { return m_cas.rising_edge(); }
bool _cas_falling_edge() { return m_cas.falling_edge(); }
bool e() { return m_e.current_edge(); }
bool e_rising_edge() { return m_e.rising_edge(); }
bool e_falling_edge() { return m_e.falling_edge(); }
protected:
// Constants
virtual inline u8 max_voices() { return 32; }
// Shared registers, functions
virtual void voice_tick() {} // voice tick
// Interrupt bits
struct es550x_irq_t
{
es550x_irq_t()
: voice(0)
, irqb(1)
{ };
void reset()
{
voice = 0;
irqb = 1;
}
void set(u8 index)
{
irqb = 0;
voice = index;
}
void clear()
{
irqb = 1;
voice = 0;
}
u8 voice : 5;
u8 irqb : 1;
};
// Common control bits
struct es550x_control_t
{
es550x_control_t()
: ca(0)
, adc(0)
, bs(0)
, cmpd(0)
{ };
void reset()
{
ca = 0;
adc = 0;
bs = 0;
cmpd = 0;
}
u8 ca : 4; // Channel assign (4 bit (16 channel or Bank) for ES5504, 2 bit (4 stereo channels) for ES5505, 3 bit (6 stereo channels) for ES5506)
// ES5504 Specific
u8 adc : 1; // Start ADC
// ES5505/ES5506 Specific
u8 bs : 2; // Bank bit (1 bit for ES5505, 2 bit for ES5506)
u8 cmpd : 1; // Use compressed sample format
};
// Accumulator
struct es550x_alu_t
{
es550x_alu_t(u8 integer, u8 fraction, bool transwave)
: m_integer(integer)
, m_fraction(fraction)
, m_total_bits(integer + fraction)
, m_transwave(transwave)
{}
const u8 m_integer;
const u8 m_fraction;
const u8 m_total_bits;
const bool m_transwave;
void reset();
bool busy();
bool tick();
void loop_exec();
s32 interpolation();
u32 get_accum_integer();
void irq_exec(es550x_intf &intf, es550x_irq_t &irqv, u8 index);
void irq_update(es550x_intf &intf, es550x_irq_t &irqv) { intf.irqb(irqv.irqb ? false : true); }
struct es550x_alu_cr_t
{
es550x_alu_cr_t()
: stop0(0)
, stop1(0)
, lpe(0)
, ble(0)
, irqe(0)
, dir(0)
, irq(0)
, lei(0)
{ };
void reset()
{
stop0 = 0;
stop1 = 0;
lpe = 0;
ble = 0;
irqe = 0;
dir = 0;
irq = 0;
lei = 0;
}
u8 stop0 : 1; // Stop with ALU
u8 stop1 : 1; // Stop with processor
u8 lpe : 1; // Loop enable
u8 ble : 1; // Bidirectional loop enable
u8 irqe : 1; // IRQ enable
u8 dir : 1; // Playback direction
u8 irq : 1; // IRQ bit
u8 lei : 1; // Loop end ignore (ES5506 specific)
};
es550x_alu_cr_t m_cr;
u32 m_fc = 0; // Frequency - 6 integer, 9 fraction for ES5506/ES5505, 6 integer, 11 fraction for ES5506
u32 m_start = 0; // Start register
u32 m_end = 0; // End register
u32 m_accum = 0; // Accumulator - 20 integer, 9 fraction for ES5506/ES5505, 21 integer, 11 fraction for ES5506
s32 m_sample[2] = {0}; // Samples
};
// Filter
struct es550x_filter_t
{
void reset();
void tick(s32 in);
s32 lp_exec(s32 coeff, s32 in, s32 prev_out);
s32 hp_exec(s32 coeff, s32 in, s32 prev_out, s32 prev_in);
// Registers
u8 m_lp = 0; // Filter mode
// Filter coefficient registers
s32 m_k2 = 0; // Filter coefficient 2 - 12 bit for filter calculation, 4 LSBs are used for fine control of ramp increment for hardware envelope (ES5506)
s32 m_k1 = 0; // Filter coefficient 1
// Filter storage registers
s32 m_o1_1 = 0; // First stage
s32 m_o2_1 = 0; // Second stage
s32 m_o2_2 = 0; // Second stage HP
s32 m_o3_1 = 0; // Third stage
s32 m_o3_2 = 0; // Third stage HP
s32 m_o4_1 = 0; // Final stage
};
// Common voice struct
struct es550x_voice_t
{
es550x_voice_t(u8 integer, u8 fraction, bool transwave)
: m_alu(integer, fraction, transwave)
{}
// internal state
virtual void reset();
virtual void fetch(u8 voice, u8 cycle) = 0;
virtual void tick(u8 voice) = 0;
es550x_control_t m_cr;
es550x_alu_t m_alu;
es550x_filter_t m_filter;
};
// Host interfaces
struct host_interface_flag_t
{
host_interface_flag_t()
: m_host_access(0)
, m_host_access_strobe(0)
, m_rw(0)
, m_rw_strobe(0)
{}
void reset()
{
m_host_access = 0;
m_host_access_strobe = 0;
m_rw = 0;
m_rw_strobe = 0;
}
u8 m_host_access : 1; // Host access trigger
u8 m_host_access_strobe : 1; // Host access strobe
u8 m_rw : 1; // R/W state
u8 m_rw_strobe : 1; // R/W strobe
};
host_interface_flag_t m_host_intf; // Host interface flag
u8 m_ha = 0; // Host address (4 bit)
u16 m_hd = 0; // Host data (16 bit for ES5504/ES5505, 8 bit for ES5506)
u8 m_page = 0; // Page
es550x_irq_t m_irqv; // Voice interrupt vector registers
// Internal states
u8 m_active = max_voices() - 1; // Activated voices (-1, ~25 for ES5504, ~32 for ES5505/ES5506)
u8 m_voice_cycle = 0; // Voice cycle
u8 m_voice_fetch = 0; // Voice fetch cycle
es550x_intf &m_intf; // es550x specific memory interface
clock_pulse_t<s8, 1, 0> m_clkin; // CLKIN clock
clock_pulse_t<s8, 2, 1> m_cas; // /CAS clock (CLKIN / 4), falling edge of CLKIN trigger this clock
clock_pulse_t<s8, 4, 0> m_e; // E clock (CLKIN / 8), falling edge of CLKIN trigger this clock
};
#endif

View file

@ -0,0 +1,116 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 Shared Accumulator emulation core
see es550x.cpp for more info
*/
#include "es550x.hpp"
// Accumulator functions
void es550x_shared_core::es550x_alu_t::reset()
{
m_cr.reset();
m_fc = 0;
m_start = 0;
m_end = 0;
m_accum = 0;
m_sample[0] = m_sample[1] = 0;
}
bool es550x_shared_core::es550x_alu_t::busy()
{
return ((!m_cr.stop0) && (!m_cr.stop1));
}
bool es550x_shared_core::es550x_alu_t::tick()
{
if (m_cr.dir)
{
m_accum = bitfield(m_accum - m_fc, 0, m_total_bits);
return ((!m_cr.lei) && (m_accum < m_start)) ? true : false;
}
else
{
m_accum = bitfield(m_accum + m_fc, 0, m_total_bits);
return ((!m_cr.lei) && (m_accum > m_end)) ? true : false;
}
}
void es550x_shared_core::es550x_alu_t::loop_exec()
{
if (m_cr.irqe) // Set IRQ
m_cr.irq = 1;
if (m_cr.dir) // Reverse playback
{
if (m_cr.lpe) // Loop enable
{
if (m_cr.ble) // Bidirectional
{
m_cr.dir = 0;
m_accum = m_start + (m_start - m_accum);
}
else// Normal
m_accum = (m_accum + m_start) - m_end;
}
else if (m_cr.ble && m_transwave) // m_transwave
{
m_cr.lpe = m_cr.ble = 0;
m_cr.lei = 1; // Loop end ignore
m_accum = (m_accum + m_start) - m_end;
}
else // Stop
m_cr.stop0 = 1;
}
else
{
if (m_cr.lpe) // Loop enable
{
if (m_cr.ble) // Bidirectional
{
m_cr.dir = 1;
m_accum = m_end - (m_end - m_accum);
}
else // Normal
m_accum = (m_accum - m_end) + m_start;
}
else if (m_cr.ble && m_transwave) // m_transwave
{
m_cr.lpe = m_cr.ble = 0;
m_cr.lei = 1; // Loop end ignore
m_accum = (m_accum - m_end) + m_start;
}
else // Stop
m_cr.stop0 = 1;
}
}
s32 es550x_shared_core::es550x_alu_t::interpolation()
{
// SF = S1 + ACCfr * (S2 - S1)
return m_sample[0] + ((bitfield(m_accum, std::min<u8>(0, m_fraction - 9), 9) * (m_sample[1] - m_sample[0])) >> 9);
}
u32 es550x_shared_core::es550x_alu_t::get_accum_integer()
{
return bitfield(m_accum, m_fraction, m_integer);
}
void es550x_shared_core::es550x_alu_t::irq_exec(es550x_intf &intf, es550x_irq_t &irqv, u8 index)
{
const u8 prev = irqv.irqb;
if (m_cr.irq)
{
if (irqv.irqb)
{
irqv.set(index);
m_cr.irq = 0;
}
}
if (prev != irqv.irqb)
irq_update(intf, irqv);
}

View file

@ -0,0 +1,70 @@
/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 Shared Filter emulation core
see es550x.cpp for more info
*/
#include "es550x.hpp"
// Filter functions
void es550x_shared_core::es550x_filter_t::reset()
{
m_lp = 0;
m_k2 = 0;
m_k1 = 0;
m_o1_1 = 0;
m_o2_1 = 0;
m_o2_2 = 0;
m_o3_1 = 0;
m_o3_2 = 0;
m_o4_1 = 0;
}
void es550x_shared_core::es550x_filter_t::tick(s32 in)
{
s32 coeff_k1 = s32(bitfield(m_k1,4,12)); // 12 MSB used
s32 coeff_k2 = s32(bitfield(m_k2,4,12)); // 12 MSB used
// Store previous filter data
m_o2_2 = m_o2_1;
m_o3_2 = m_o3_1;
// First and second stage: LP/K1, LP/K1 Fixed
m_o1_1 = lp_exec(coeff_k1, in, m_o1_1);
m_o2_1 = lp_exec(coeff_k1, m_o1_1, m_o2_1);
switch (m_lp)
{
case 0: // LP3 = 0, LP4 = 0: HP/K2, HP/K2
default:
m_o3_1 = hp_exec(coeff_k2, m_o2_1, m_o3_1, m_o2_2);
m_o4_1 = hp_exec(coeff_k2, m_o3_1, m_o4_1, m_o3_2);
break;
case 1: // LP3 = 0, LP4 = 1: HP/K2, LP/K1
m_o4_1 = lp_exec(coeff_k1, m_o2_1, m_o3_1);
m_o3_1 = hp_exec(coeff_k2, m_o3_1, m_o4_1, m_o3_2);
break;
case 2: // LP3 = 1, LP4 = 0: LP/K2, LP/K2
m_o3_1 = lp_exec(coeff_k2, m_o2_1, m_o3_1);
m_o4_1 = lp_exec(coeff_k2, m_o3_1, m_o4_1);
break;
case 3: // LP3 = 1, LP4 = 1: LP/K2, LP/K1
m_o4_1 = lp_exec(coeff_k1, m_o2_1, m_o3_1);
m_o3_1 = lp_exec(coeff_k2, m_o3_1, m_o4_1);
break;
}
}
s32 es550x_shared_core::es550x_filter_t::lp_exec(s32 coeff, s32 in, s32 prev_out)
{
// Yn = K*(Xn - Yn-1) + Yn-1
return ((coeff * (in - prev_out)) / 4096) + prev_out;
}
s32 es550x_shared_core::es550x_filter_t::hp_exec(s32 coeff, s32 in, s32 prev_out, s32 prev_in)
{
// Yn = Xn - Xn-1 + K*Yn-1
return in - prev_in + ((coeff * prev_out) / 8192) * (prev_out / 2);
}

View file

@ -83,8 +83,8 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len
continue;
}
rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80);
if (dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || dacPos>=s->samples) {
if (s->isLoopable()) {
dacPos=s->loopStart;
} else {
dacSample=-1;

View file

@ -96,8 +96,8 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len
rWritePCMData(tmp_r&0xff);
}
chan[16].pcm.pos++;
if (chan[16].pcm.pos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[16].pcm.pos>=s->loopEnd) || (chan[16].pcm.pos>=s->samples)) {
if (s->isLoopable()) {
chan[16].pcm.pos=s->loopStart;
} else {
chan[16].pcm.sample=-1;

View file

@ -77,8 +77,8 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len
chWrite(i,0,0x80|chan[i].dacOut);
}
chan[i].dacPos++;
if (chan[i].dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && chan[i].dacPos>=s->loopEnd) || (chan[i].dacPos>=s->samples)) {
if (s->isLoopable()) {
chan[i].dacPos=s->loopStart;
} else {
chan[i].dacSample=-1;

View file

@ -644,11 +644,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(s->offB>>8)&0xff);
immWrite(0x13,s->offB>>16);
int end=s->offB+s->lengthB-1;
int end=((s->offB+s->lengthB+0xff)&~0xff)-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
@ -679,11 +679,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
immWrite(0x12,(s->offB>>8)&0xff);
immWrite(0x13,s->offB>>16);
int end=s->offB+s->lengthB-1;
int end=((s->offB+s->lengthB+0xff)&~0xff)-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144);
chan[c.chan].freqChanged=true;
}

View file

@ -707,11 +707,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(s->offB>>8)&0xff);
immWrite(0x13,s->offB>>16);
int end=s->offB+s->lengthB-1;
int end=((s->offB+s->lengthB+0xff)&~0xff)-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
@ -742,11 +742,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
immWrite(0x12,(s->offB>>8)&0xff);
immWrite(0x13,s->offB>>16);
int end=s->offB+s->lengthB-1;
int end=((s->offB+s->lengthB+0xff)&~0xff)-1;
immWrite(0x14,(end>>8)&0xff);
immWrite(0x15,end>>16);
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
immWrite(0x10,((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (s->loopStart>=0))?0x90:0x80); // start/repeat
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144);
chan[c.chan].freqChanged=true;
}

View file

@ -511,6 +511,23 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
return false;
}
break;
case DIV_SYSTEM_ES5506:
switch (effect) {
case 0x10: // echo feedback
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_FEEDBACK,ch,effectVal));
break;
case 0x11: // echo level
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_LEVEL,ch,effectVal));
break;
default:
if ((effect&0xf0)==0x30) {
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_DELAY,ch,((effect & 0x0f) << 8) | effectVal));
} else {
return false;
}
break;
}
break;
default:
return false;
}
@ -1293,6 +1310,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
sPreview.sample=-1;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
break;
}
}
@ -1744,22 +1762,107 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
if (sPreview.pos>=s->samples) {
samp_temp=0;
} else {
samp_temp=s->data16[sPreview.pos++];
samp_temp=s->data16[sPreview.pos];
if (sPreview.dir) {
sPreview.pos--;
} else {
sPreview.pos++;
}
}
blip_add_delta(samp_bb,i,samp_temp-samp_prevSample);
samp_prevSample=samp_temp;
if (sPreview.pos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
sPreview.pos=s->loopStart;
if (sPreview.dir) {
if (s->isLoopable() && ((int)sPreview.pos)<s->loopStart) {
switch (s->loopMode) {
case DIV_SAMPLE_LOOPMODE_FOWARD:
sPreview.dir=false;
sPreview.pos=s->loopStart+1;
break;
case DIV_SAMPLE_LOOPMODE_BACKWARD:
sPreview.dir=true;
sPreview.pos=s->loopEnd-1;
break;
case DIV_SAMPLE_LOOPMODE_PINGPONG:
sPreview.dir=false;
sPreview.pos=s->loopStart+1;
break;
case DIV_SAMPLE_LOOPMODE_ONESHOT:
default:
break;
}
}
} else {
if (s->isLoopable() && sPreview.pos>=s->loopEnd) {
switch (s->loopMode) {
case DIV_SAMPLE_LOOPMODE_FOWARD:
sPreview.dir=false;
sPreview.pos=s->loopStart;
break;
case DIV_SAMPLE_LOOPMODE_BACKWARD:
sPreview.dir=true;
sPreview.pos=s->loopEnd-1;
break;
case DIV_SAMPLE_LOOPMODE_PINGPONG:
sPreview.dir=true;
sPreview.pos=s->loopEnd-1;
break;
case DIV_SAMPLE_LOOPMODE_ONESHOT:
default:
break;
}
}
}
}
if (sPreview.pos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
sPreview.pos=s->loopStart;
} else {
if (sPreview.dir) {
if (s->isLoopable() && ((int)sPreview.pos)<s->loopStart) {
switch (s->loopMode) {
case DIV_SAMPLE_LOOPMODE_FOWARD:
sPreview.dir=false;
sPreview.pos=s->loopStart+1;
break;
case DIV_SAMPLE_LOOPMODE_BACKWARD:
sPreview.dir=true;
sPreview.pos=s->loopEnd-1;
break;
case DIV_SAMPLE_LOOPMODE_PINGPONG:
sPreview.dir=false;
sPreview.pos=s->loopStart+1;
break;
case DIV_SAMPLE_LOOPMODE_ONESHOT:
default:
if (sPreview.pos<0) {
sPreview.sample=-1;
}
break;
}
} else if (sPreview.pos<0) {
sPreview.sample=-1;
}
} else {
if (s->isLoopable() && sPreview.pos>=s->loopEnd) {
switch (s->loopMode) {
case DIV_SAMPLE_LOOPMODE_FOWARD:
sPreview.dir=false;
sPreview.pos=s->loopStart;
break;
case DIV_SAMPLE_LOOPMODE_BACKWARD:
sPreview.dir=true;
sPreview.pos=s->loopEnd-1;
break;
case DIV_SAMPLE_LOOPMODE_PINGPONG:
sPreview.dir=true;
sPreview.pos=s->loopEnd-1;
break;
case DIV_SAMPLE_LOOPMODE_ONESHOT:
default:
if (sPreview.pos>=s->samples) {
sPreview.sample=-1;
}
break;
}
} else if (sPreview.pos>=s->samples) {
sPreview.sample=-1;
}
}

View file

@ -36,6 +36,10 @@ DivSampleHistory::~DivSampleHistory() {
if (data!=NULL) delete[] data;
}
bool DivSample::isLoopable() {
return ((loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && (loopStart>=0 && loopStart<(int)samples) && loopEnd<=samples);
}
bool DivSample::save(const char* path) {
SNDFILE* f;
SF_INFO si;
@ -62,12 +66,12 @@ bool DivSample::save(const char* path) {
inst.detune = 50 - (pitch % 100);
inst.velocity_hi = 0x7f;
inst.key_hi = 0x7f;
if(loopStart != -1)
if(isLoopable())
{
inst.loop_count = 1;
inst.loops[0].mode = SF_LOOP_FORWARD;
inst.loops[0].mode = SF_LOOP_NONE+loopMode;
inst.loops[0].start = loopStart;
inst.loops[0].end = samples;
inst.loops[0].end = loopEnd;
}
sf_command(f, SFC_SET_INSTRUMENT, &inst, sizeof(inst));
@ -79,64 +83,64 @@ bool DivSample::save(const char* path) {
}
// 16-bit memory is padded to 512, to make things easier for ADPCM-A/B.
bool DivSample::initInternal(unsigned char d, int count) {
bool DivSample::initInternal(DivSampleDepth d, int count) {
switch (d) {
case 0: // 1-bit
case DIV_SAMPLE_DEPTH_1BIT: // 1-bit
if (data1!=NULL) delete[] data1;
length1=(count+7)/8;
data1=new unsigned char[length1];
memset(data1,0,length1);
break;
case 1: // DPCM
case DIV_SAMPLE_DEPTH_1BIT_DPCM: // DPCM
if (dataDPCM!=NULL) delete[] dataDPCM;
lengthDPCM=(count+7)/8;
dataDPCM=new unsigned char[lengthDPCM];
memset(dataDPCM,0,lengthDPCM);
break;
case 4: // QSound ADPCM
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM
if (dataQSoundA!=NULL) delete[] dataQSoundA;
lengthQSoundA=(count+1)/2;
dataQSoundA=new unsigned char[lengthQSoundA];
memset(dataQSoundA,0,lengthQSoundA);
break;
case 5: // ADPCM-A
case DIV_SAMPLE_DEPTH_ADPCM_A: // ADPCM-A
if (dataA!=NULL) delete[] dataA;
lengthA=(count+1)/2;
dataA=new unsigned char[(lengthA+255)&(~0xff)];
memset(dataA,0,(lengthA+255)&(~0xff));
break;
case 6: // ADPCM-B
case DIV_SAMPLE_DEPTH_ADPCM_B: // ADPCM-B
if (dataB!=NULL) delete[] dataB;
lengthB=(count+1)/2;
dataB=new unsigned char[(lengthB+255)&(~0xff)];
memset(dataB,0,(lengthB+255)&(~0xff));
break;
case 7: // X68000 ADPCM
case DIV_SAMPLE_DEPTH_X68K_ADPCM: // X68000 ADPCM
if (dataX68!=NULL) delete[] dataX68;
lengthX68=(count+1)/2;
dataX68=new unsigned char[lengthX68];
memset(dataX68,0,lengthX68);
break;
case 8: // 8-bit
case DIV_SAMPLE_DEPTH_8BIT: // 8-bit
if (data8!=NULL) delete[] data8;
length8=count;
// for padding X1-010 sample
data8=new signed char[(count+4095)&(~0xfff)];
memset(data8,0,(count+4095)&(~0xfff));
break;
case 9: // BRR
case DIV_SAMPLE_DEPTH_BRR: // BRR
if (dataBRR!=NULL) delete[] dataBRR;
lengthBRR=9*((count+15)/16);
dataBRR=new unsigned char[lengthBRR];
memset(dataBRR,0,lengthBRR);
break;
case 10: // VOX
case DIV_SAMPLE_DEPTH_VOX: // VOX
if (dataVOX!=NULL) delete[] dataVOX;
lengthVOX=(count+1)/2;
dataVOX=new unsigned char[lengthVOX];
memset(dataVOX,0,lengthVOX);
break;
case 16: // 16-bit
case DIV_SAMPLE_DEPTH_16BIT: // 16-bit
if (data16!=NULL) delete[] data16;
length16=count*2;
data16=new short[(count+511)&(~0x1ff)];
@ -151,31 +155,34 @@ bool DivSample::initInternal(unsigned char d, int count) {
bool DivSample::init(unsigned int count) {
if (!initInternal(depth,count)) return false;
samples=count;
if (loopEnd>samples) {
loopEnd=samples;
}
return true;
}
bool DivSample::resize(unsigned int count) {
if (depth==8) {
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
if (data8!=NULL) {
signed char* oldData8=data8;
data8=NULL;
initInternal(8,count);
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
memcpy(data8,oldData8,MIN(count,samples));
delete[] oldData8;
} else {
initInternal(8,count);
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
}
samples=count;
return true;
} else if (depth==16) {
} else if (depth==DIV_SAMPLE_DEPTH_16BIT) {
if (data16!=NULL) {
short* oldData16=data16;
data16=NULL;
initInternal(16,count);
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
memcpy(data16,oldData16,sizeof(short)*MIN(count,samples));
delete[] oldData16;
} else {
initInternal(16,count);
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
}
samples=count;
return true;
@ -188,11 +195,11 @@ bool DivSample::strip(unsigned int begin, unsigned int end) {
if (end>samples) end=samples;
int count=samples-(end-begin);
if (count<=0) return resize(0);
if (depth==8) {
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
if (data8!=NULL) {
signed char* oldData8=data8;
data8=NULL;
initInternal(8,count);
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
if (begin>0) {
memcpy(data8,oldData8,begin);
}
@ -206,11 +213,11 @@ bool DivSample::strip(unsigned int begin, unsigned int end) {
}
samples=count;
return true;
} else if (depth==16) {
} else if (depth==DIV_SAMPLE_DEPTH_16BIT) {
if (data16!=NULL) {
short* oldData16=data16;
data16=NULL;
initInternal(16,count);
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
if (begin>0) {
memcpy(data16,oldData16,sizeof(short)*begin);
}
@ -232,11 +239,11 @@ bool DivSample::trim(unsigned int begin, unsigned int end) {
int count=end-begin;
if (count==0) return true;
if (begin==0 && end==samples) return true;
if (depth==8) {
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
if (data8!=NULL) {
signed char* oldData8=data8;
data8=NULL;
initInternal(8,count);
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
memcpy(data8,oldData8+begin,count);
delete[] oldData8;
} else {
@ -245,11 +252,11 @@ bool DivSample::trim(unsigned int begin, unsigned int end) {
}
samples=count;
return true;
} else if (depth==16) {
} else if (depth==DIV_SAMPLE_DEPTH_16BIT) {
if (data16!=NULL) {
short* oldData16=data16;
data16=NULL;
initInternal(16,count);
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
memcpy(data16,&(oldData16[begin]),sizeof(short)*count);
delete[] oldData16;
} else {
@ -264,11 +271,11 @@ bool DivSample::trim(unsigned int begin, unsigned int end) {
bool DivSample::insert(unsigned int pos, unsigned int length) {
unsigned int count=samples+length;
if (depth==8) {
if (depth==DIV_SAMPLE_DEPTH_8BIT) {
if (data8!=NULL) {
signed char* oldData8=data8;
data8=NULL;
initInternal(8,count);
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
if (pos>0) {
memcpy(data8,oldData8,pos);
}
@ -277,15 +284,15 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
}
delete[] oldData8;
} else {
initInternal(8,count);
initInternal(DIV_SAMPLE_DEPTH_8BIT,count);
}
samples=count;
return true;
} else if (depth==16) {
} else if (depth==DIV_SAMPLE_DEPTH_16BIT) {
if (data16!=NULL) {
short* oldData16=data16;
data16=NULL;
initInternal(16,count);
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
if (pos>0) {
memcpy(data16,oldData16,sizeof(short)*pos);
}
@ -294,7 +301,7 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
}
delete[] oldData16;
} else {
initInternal(16,count);
initInternal(DIV_SAMPLE_DEPTH_16BIT,count);
}
samples=count;
return true;
@ -307,15 +314,15 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
int finalCount=(double)samples*(r/(double)rate); \
signed char* oldData8=data8; \
short* oldData16=data16; \
if (depth==16) { \
if (depth==DIV_SAMPLE_DEPTH_16BIT) { \
if (data16!=NULL) { \
data16=NULL; \
initInternal(16,finalCount); \
initInternal(DIV_SAMPLE_DEPTH_16BIT,finalCount); \
} \
} else if (depth==8) { \
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) { \
if (data8!=NULL) { \
data8=NULL; \
initInternal(8,finalCount); \
initInternal(DIV_SAMPLE_DEPTH_8BIT,finalCount); \
} \
} else { \
return false; \
@ -323,19 +330,20 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
#define RESAMPLE_END \
if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \
if (loopEnd>=0) loopEnd=(double)loopEnd*(r/(double)rate); \
centerRate=(int)((double)centerRate*(r/(double)rate)); \
rate=r; \
samples=finalCount; \
if (depth==16) { \
if (depth==DIV_SAMPLE_DEPTH_16BIT) { \
delete[] oldData16; \
} else if (depth==8) { \
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) { \
delete[] oldData8; \
}
bool DivSample::resampleNone(double r) {
RESAMPLE_BEGIN;
if (depth==16) {
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
for (int i=0; i<finalCount; i++) {
unsigned int pos=(unsigned int)((double)i*((double)rate/r));
if (pos>=samples) {
@ -344,7 +352,7 @@ bool DivSample::resampleNone(double r) {
data16[i]=oldData16[pos];
}
}
} else if (depth==8) {
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
for (int i=0; i<finalCount; i++) {
unsigned int pos=(unsigned int)((double)i*((double)rate/r));
if (pos>=samples) {
@ -366,10 +374,10 @@ bool DivSample::resampleLinear(double r) {
unsigned int posInt=0;
double factor=(double)rate/r;
if (depth==16) {
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
for (int i=0; i<finalCount; i++) {
short s1=(posInt>=samples)?0:oldData16[posInt];
short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+1];
short s2=(posInt+1>=samples)?(isLoopable()?oldData16[loopStart]:0):oldData16[posInt+1];
data16[i]=s1+(float)(s2-s1)*posFrac;
@ -379,10 +387,10 @@ bool DivSample::resampleLinear(double r) {
posInt++;
}
}
} else if (depth==8) {
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
for (int i=0; i<finalCount; i++) {
short s1=(posInt>=samples)?0:oldData8[posInt];
short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+1];
short s2=(posInt+1>=samples)?(isLoopable()?oldData8[loopStart]:0):oldData8[posInt+1];
data8[i]=s1+(float)(s2-s1)*posFrac;
@ -406,14 +414,14 @@ bool DivSample::resampleCubic(double r) {
double factor=(double)rate/r;
float* cubicTable=DivFilterTables::getCubicTable();
if (depth==16) {
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
for (int i=0; i<finalCount; i++) {
unsigned int n=((unsigned int)(posFrac*1024.0))&1023;
float* t=&cubicTable[n<<2];
float s0=(posInt<1)?0:oldData16[posInt-1];
float s1=(posInt>=samples)?0:oldData16[posInt];
float s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+1];
float s3=(posInt+2>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+2];
float s2=(posInt+1>=samples)?(isLoopable()?oldData16[loopStart]:0):oldData16[posInt+1];
float s3=(posInt+2>=samples)?(isLoopable()?oldData16[loopStart]:0):oldData16[posInt+2];
float result=s0*t[0]+s1*t[1]+s2*t[2]+s3*t[3];
if (result<-32768) result=-32768;
@ -426,14 +434,14 @@ bool DivSample::resampleCubic(double r) {
posInt++;
}
}
} else if (depth==8) {
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
for (int i=0; i<finalCount; i++) {
unsigned int n=((unsigned int)(posFrac*1024.0))&1023;
float* t=&cubicTable[n<<2];
float s0=(posInt<1)?0:oldData8[posInt-1];
float s1=(posInt>=samples)?0:oldData8[posInt];
float s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+1];
float s3=(posInt+2>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+2];
float s2=(posInt+1>=samples)?(isLoopable()?oldData8[loopStart]:0):oldData8[posInt+1];
float s3=(posInt+2>=samples)?(isLoopable()?oldData8[loopStart]:0):oldData8[posInt+2];
float result=s0*t[0]+s1*t[1]+s2*t[2]+s3*t[3];
if (result<-128) result=-128;
@ -463,7 +471,7 @@ bool DivSample::resampleBlep(double r) {
memset(s,0,16*sizeof(float));
if (depth==16) {
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
memset(data16,0,finalCount*sizeof(short));
for (int i=0; i<finalCount; i++) {
if (posInt<samples) {
@ -499,7 +507,7 @@ bool DivSample::resampleBlep(double r) {
}
}
}
} else if (depth==8) {
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
memset(data8,0,finalCount);
for (int i=0; i<finalCount; i++) {
if (posInt<samples) {
@ -552,7 +560,7 @@ bool DivSample::resampleSinc(double r) {
memset(s,0,16*sizeof(float));
if (depth==16) {
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
for (int i=0; i<finalCount+8; i++) {
unsigned int n=((unsigned int)(posFrac*8192.0))&8191;
float result=0;
@ -577,7 +585,7 @@ bool DivSample::resampleSinc(double r) {
s[15]=(posInt>=samples)?0:oldData16[posInt];
}
}
} else if (depth==8) {
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
for (int i=0; i<finalCount+8; i++) {
unsigned int n=((unsigned int)(posFrac*8192.0))&8191;
float result=0;
@ -609,7 +617,7 @@ bool DivSample::resampleSinc(double r) {
}
bool DivSample::resample(double r, int filter) {
if (depth!=8 && depth!=16) return false;
if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) return false;
switch (filter) {
case DIV_RESAMPLE_NONE:
return resampleNone(r);
@ -639,15 +647,15 @@ bool DivSample::resample(double r, int filter) {
void DivSample::render() {
// step 1: convert to 16-bit if needed
if (depth!=16) {
if (!initInternal(16,samples)) return;
if (depth!=DIV_SAMPLE_DEPTH_16BIT) {
if (!initInternal(DIV_SAMPLE_DEPTH_16BIT,samples)) return;
switch (depth) {
case 0: // 1-bit
case DIV_SAMPLE_DEPTH_1BIT: // 1-bit
for (unsigned int i=0; i<samples; i++) {
data16[i]=((data1[i>>3]>>(i&7))&1)?0x7fff:-0x7fff;
}
break;
case 1: { // DPCM
case DIV_SAMPLE_DEPTH_1BIT_DPCM: { // DPCM
int accum=0;
for (unsigned int i=0; i<samples; i++) {
accum+=((dataDPCM[i>>3]>>(i&7))&1)?1:-1;
@ -657,27 +665,27 @@ void DivSample::render() {
}
break;
}
case 4: // QSound ADPCM
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM
bs_decode(dataQSoundA,data16,samples);
break;
case 5: // ADPCM-A
case DIV_SAMPLE_DEPTH_ADPCM_A: // ADPCM-A
yma_decode(dataA,data16,samples);
break;
case 6: // ADPCM-B
case DIV_SAMPLE_DEPTH_ADPCM_B: // ADPCM-B
ymb_decode(dataB,data16,samples);
break;
case 7: // X6800 ADPCM
case DIV_SAMPLE_DEPTH_X68K_ADPCM: // X6800 ADPCM
oki6258_decode(dataX68,data16,samples);
break;
case 8: // 8-bit PCM
case DIV_SAMPLE_DEPTH_8BIT: // 8-bit PCM
for (unsigned int i=0; i<samples; i++) {
data16[i]=data8[i]<<8;
}
break;
case 9: // BRR
case DIV_SAMPLE_DEPTH_BRR: // BRR
// TODO!
break;
case 10: // VOX
case DIV_SAMPLE_DEPTH_VOX: // VOX
oki_decode(dataVOX,data16,samples);
break;
default:
@ -686,16 +694,16 @@ void DivSample::render() {
}
// step 2: render to other formats
if (depth!=0) { // 1-bit
if (!initInternal(0,samples)) return;
if (depth!=DIV_SAMPLE_DEPTH_1BIT) { // 1-bit
if (!initInternal(DIV_SAMPLE_DEPTH_1BIT,samples)) return;
for (unsigned int i=0; i<samples; i++) {
if (data16[i]>0) {
data1[i>>3]|=1<<(i&7);
}
}
}
if (depth!=1) { // DPCM
if (!initInternal(1,samples)) return;
if (depth!=DIV_SAMPLE_DEPTH_1BIT_DPCM) { // DPCM
if (!initInternal(DIV_SAMPLE_DEPTH_1BIT_DPCM,samples)) return;
int accum=63;
for (unsigned int i=0; i<samples; i++) {
int next=((unsigned short)(data16[i]^0x8000))>>9;
@ -709,84 +717,88 @@ void DivSample::render() {
if (accum>127) accum=127;
}
}
if (depth!=4) { // QSound ADPCM
if (!initInternal(4,samples)) return;
if (depth!=DIV_SAMPLE_DEPTH_QSOUND_ADPCM) { // QSound ADPCM
if (!initInternal(DIV_SAMPLE_DEPTH_QSOUND_ADPCM,samples)) return;
bs_encode(data16,dataQSoundA,samples);
}
// TODO: pad to 256.
if (depth!=5) { // ADPCM-A
if (!initInternal(5,samples)) return;
if (depth!=DIV_SAMPLE_DEPTH_ADPCM_A) { // ADPCM-A
if (!initInternal(DIV_SAMPLE_DEPTH_ADPCM_A,samples)) return;
yma_encode(data16,dataA,(samples+511)&(~0x1ff));
}
if (depth!=6) { // ADPCM-B
if (!initInternal(6,samples)) return;
if (depth!=DIV_SAMPLE_DEPTH_ADPCM_B) { // ADPCM-B
if (!initInternal(DIV_SAMPLE_DEPTH_ADPCM_B,samples)) return;
ymb_encode(data16,dataB,(samples+511)&(~0x1ff));
}
if (depth!=7) { // X68000 ADPCM
if (!initInternal(7,samples)) return;
if (depth!=DIV_SAMPLE_DEPTH_X68K_ADPCM) { // X68000 ADPCM
if (!initInternal(DIV_SAMPLE_DEPTH_X68K_ADPCM,samples)) return;
oki6258_encode(data16,dataX68,samples);
}
if (depth!=8) { // 8-bit PCM
if (!initInternal(8,samples)) return;
if (depth!=DIV_SAMPLE_DEPTH_8BIT) { // 8-bit PCM
if (!initInternal(DIV_SAMPLE_DEPTH_8BIT,samples)) return;
for (unsigned int i=0; i<samples; i++) {
data8[i]=data16[i]>>8;
}
}
// TODO: BRR!
if (depth!=10) { // VOX
if (!initInternal(10,samples)) return;
if (depth!=DIV_SAMPLE_DEPTH_VOX) { // VOX
if (!initInternal(DIV_SAMPLE_DEPTH_VOX,samples)) return;
oki_encode(data16,dataVOX,samples);
}
}
void* DivSample::getCurBuf() {
switch (depth) {
case 0:
case DIV_SAMPLE_DEPTH_1BIT:
return data1;
case 1:
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
return dataDPCM;
case 4:
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
return dataQSoundA;
case 5:
case DIV_SAMPLE_DEPTH_ADPCM_A:
return dataA;
case 6:
case DIV_SAMPLE_DEPTH_ADPCM_B:
return dataB;
case 7:
case DIV_SAMPLE_DEPTH_X68K_ADPCM:
return dataX68;
case 8:
case DIV_SAMPLE_DEPTH_8BIT:
return data8;
case 9:
case DIV_SAMPLE_DEPTH_BRR:
return dataBRR;
case 10:
case DIV_SAMPLE_DEPTH_VOX:
return dataVOX;
case 16:
case DIV_SAMPLE_DEPTH_16BIT:
return data16;
default:
break;
}
return NULL;
}
unsigned int DivSample::getCurBufLen() {
switch (depth) {
case 0:
case DIV_SAMPLE_DEPTH_1BIT:
return length1;
case 1:
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
return lengthDPCM;
case 4:
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
return lengthQSoundA;
case 5:
case DIV_SAMPLE_DEPTH_ADPCM_A:
return lengthA;
case 6:
case DIV_SAMPLE_DEPTH_ADPCM_B:
return lengthB;
case 7:
case DIV_SAMPLE_DEPTH_X68K_ADPCM:
return lengthX68;
case 8:
case DIV_SAMPLE_DEPTH_8BIT:
return length8;
case 9:
case DIV_SAMPLE_DEPTH_BRR:
return lengthBRR;
case 10:
case DIV_SAMPLE_DEPTH_VOX:
return lengthVOX;
case 16:
case DIV_SAMPLE_DEPTH_16BIT:
return length16;
default:
break;
}
return 0;
}
@ -801,9 +813,9 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
duplicate=new unsigned char[getCurBufLen()];
memcpy(duplicate,getCurBuf(),getCurBufLen());
}
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart);
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart,loopEnd,loopMode);
} else {
h=new DivSampleHistory(depth,rate,centerRate,loopStart);
h=new DivSampleHistory(depth,rate,centerRate,loopStart,loopEnd,loopMode);
}
if (!doNotPush) {
while (!redoHist.empty()) {
@ -833,7 +845,9 @@ DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
} \
rate=h->rate; \
centerRate=h->centerRate; \
loopStart=h->loopStart;
loopStart=h->loopStart; \
loopEnd=h->loopEnd; \
loopMode=h->loopMode;
int DivSample::undo() {

View file

@ -17,6 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _SAMPLE_H
#define _SAMPLE_H
#pragma once
#include "../ta-utils.h"
#include <deque>
@ -26,16 +31,41 @@ enum DivResampleFilters {
DIV_RESAMPLE_CUBIC,
DIV_RESAMPLE_BLEP,
DIV_RESAMPLE_SINC,
DIV_RESAMPLE_BEST
DIV_RESAMPLE_BEST,
DIV_RESAMPLE_MAX // for identify boundary
};
enum DivSampleDepth: unsigned char {
DIV_SAMPLE_DEPTH_1BIT=0,
DIV_SAMPLE_DEPTH_1BIT_DPCM=1,
DIV_SAMPLE_DEPTH_QSOUND_ADPCM=4,
DIV_SAMPLE_DEPTH_ADPCM_A=5,
DIV_SAMPLE_DEPTH_ADPCM_B=6,
DIV_SAMPLE_DEPTH_X68K_ADPCM=7,
DIV_SAMPLE_DEPTH_8BIT=8,
DIV_SAMPLE_DEPTH_BRR=9,
DIV_SAMPLE_DEPTH_VOX=10,
DIV_SAMPLE_DEPTH_16BIT=16,
DIV_SAMPLE_DEPTH_MAX // for identify boundary
};
enum DivSampleLoopMode: unsigned char {
DIV_SAMPLE_LOOPMODE_ONESHOT=0,
DIV_SAMPLE_LOOPMODE_FOWARD,
DIV_SAMPLE_LOOPMODE_BACKWARD,
DIV_SAMPLE_LOOPMODE_PINGPONG,
DIV_SAMPLE_LOOPMODE_MAX // for identify boundary
};
struct DivSampleHistory {
unsigned char* data;
unsigned int length, samples;
unsigned char depth;
DivSampleDepth depth;
int rate, centerRate, loopStart;
unsigned int loopEnd;
DivSampleLoopMode loopMode;
bool hasSample;
DivSampleHistory(void* d, unsigned int l, unsigned int s, unsigned char de, int r, int cr, int ls):
DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, unsigned int le, DivSampleLoopMode lm):
data((unsigned char*)d),
length(l),
samples(s),
@ -43,8 +73,10 @@ struct DivSampleHistory {
rate(r),
centerRate(cr),
loopStart(ls),
loopEnd(le),
loopMode(lm),
hasSample(true) {}
DivSampleHistory(unsigned char de, int r, int cr, int ls):
DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, unsigned int le, DivSampleLoopMode lm):
data(NULL),
length(0),
samples(0),
@ -52,6 +84,8 @@ struct DivSampleHistory {
rate(r),
centerRate(cr),
loopStart(ls),
loopEnd(le),
loopMode(lm),
hasSample(false) {}
~DivSampleHistory();
};
@ -59,6 +93,13 @@ struct DivSampleHistory {
struct DivSample {
String name;
int rate, centerRate, loopStart, loopOffP;
unsigned int loopEnd;
// valid values are:
// - 0: One Shot (Loop disable)
// - 1: Foward loop
// - 2: Backward loop
// - 3: Pingpong loop
DivSampleLoopMode loopMode;
// valid values are:
// - 0: ZX Spectrum overlay drum (1-bit)
// - 1: 1-bit NES DPCM (1-bit)
@ -70,7 +111,7 @@ struct DivSample {
// - 9: BRR (SNES)
// - 10: VOX
// - 16: 16-bit PCM
unsigned char depth;
DivSampleDepth depth;
// these are the new data structures.
signed char* data8; // 8
@ -86,7 +127,7 @@ struct DivSample {
unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX;
unsigned int offSegaPCM, offQSound, offX1_010;
unsigned int offSegaPCM, offQSound, offX1_010, offES5506;
unsigned int samples;
@ -102,6 +143,12 @@ struct DivSample {
bool resampleBlep(double rate);
bool resampleSinc(double rate);
/**
* check if sample is loopable.
* @return whether it was loopable.
*/
bool isLoopable();
/**
* save this sample to a file.
* @param path a path.
@ -116,7 +163,7 @@ struct DivSample {
* @param count number of samples.
* @return whether it was successful.
*/
bool initInternal(unsigned char d, int count);
bool initInternal(DivSampleDepth d, int count);
/**
* initialize sample data. make sure you have set `depth` before doing so.
@ -211,9 +258,11 @@ struct DivSample {
name(""),
rate(32000),
centerRate(8363),
loopStart(-1),
loopStart(0),
loopOffP(0),
depth(16),
loopEnd(~0),
loopMode(DIV_SAMPLE_LOOPMODE_ONESHOT),
depth(DIV_SAMPLE_DEPTH_16BIT),
data8(NULL),
data16(NULL),
data1(NULL),
@ -250,3 +299,5 @@ struct DivSample {
samples(0) {}
~DivSample();
};
#endif

View file

@ -95,7 +95,8 @@ enum DivSystem {
DIV_SYSTEM_YM2610B_EXT,
DIV_SYSTEM_SEGAPCM_COMPAT,
DIV_SYSTEM_X1_010,
DIV_SYSTEM_BUBSYS_WSG
DIV_SYSTEM_BUBSYS_WSG,
DIV_SYSTEM_ES5506
};
struct DivSong {

View file

@ -140,6 +140,8 @@ DivSystem DivEngine::systemFromFileFur(unsigned char val) {
return DIV_SYSTEM_BUBSYS_WSG;
case 0xb0:
return DIV_SYSTEM_X1_010;
case 0xb1:
return DIV_SYSTEM_ES5506;
case 0xde:
return DIV_SYSTEM_YM2610B_EXT;
case 0xe0:
@ -270,6 +272,8 @@ unsigned char DivEngine::systemToFileFur(DivSystem val) {
return 0xad;
case DIV_SYSTEM_X1_010:
return 0xb0;
case DIV_SYSTEM_ES5506:
return 0xb1;
case DIV_SYSTEM_YM2610B_EXT:
return 0xde;
case DIV_SYSTEM_QSOUND:
@ -475,6 +479,8 @@ int DivEngine::getChannelCount(DivSystem sys) {
return 17;
case DIV_SYSTEM_BUBSYS_WSG:
return 2;
case DIV_SYSTEM_ES5506:
return 32;
}
return 0;
}
@ -760,6 +766,8 @@ const char* DivEngine::getSystemName(DivSystem sys) {
return "Seta/Allumer X1-010";
case DIV_SYSTEM_BUBSYS_WSG:
return "Konami Bubble System WSG";
case DIV_SYSTEM_ES5506:
return "Ensoniq ES5506 OTTO";
}
return "Unknown";
}
@ -892,6 +900,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) {
return "Seta/Allumer X1-010";
case DIV_SYSTEM_BUBSYS_WSG:
return "Konami Bubble System WSG";
case DIV_SYSTEM_ES5506:
return "Ensoniq ES5506";
}
return "Unknown";
}
@ -1002,7 +1012,7 @@ const char* chanNames[40][32]={
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28", "Channel 29", "Channel 30", "Channel 31", "Channel 32"}, // MultiPCM/ES5506
{"Square"}, // PC Speaker/Pokémon Mini
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B
@ -1045,7 +1055,7 @@ const char* chanShortNames[38][32]={
{"F1", "F2", "F3", "S1", "S2", "S3"}, // OPN
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, // PC-98
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, // OPL3
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, // MultiPCM
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"}, // MultiPCM/ES5506
{"SQ"}, // PC Speaker/Pokémon Mini
{"CH1", "CH2", "CH3", "CH4", "CH5", "NO"}, // Virtual Boy/SCC
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B
@ -1086,7 +1096,7 @@ const int chanTypes[41][32]={
{0, 0, 0, 1, 1, 1}, // OPN
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98
{5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0}, // OPL3
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound/ES5506
{1}, // PC Speaker/Pokémon Mini
{3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC
{0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B
@ -1101,7 +1111,7 @@ const int chanTypes[41][32]={
{3, 4, 3, 2}, // Swan
};
const DivInstrumentType chanPrefType[47][28]={
const DivInstrumentType chanPrefType[48][32]={
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3)
@ -1149,6 +1159,7 @@ const DivInstrumentType chanPrefType[47][28]={
{DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA
{DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010
{DIV_INS_SCC, DIV_INS_SCC}, // Bubble System WSG
{DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506, DIV_INS_ES5506}, // ES5506
};
const char* DivEngine::getChannelName(int chan) {
@ -1258,6 +1269,7 @@ const char* DivEngine::getChannelName(int chan) {
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
case DIV_SYSTEM_X1_010:
case DIV_SYSTEM_ES5506:
return chanNames[28][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_PCSPKR:
@ -1403,6 +1415,7 @@ const char* DivEngine::getChannelShortName(int chan) {
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
case DIV_SYSTEM_X1_010:
case DIV_SYSTEM_ES5506:
return chanShortNames[28][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_PCSPKR:
@ -1544,6 +1557,7 @@ int DivEngine::getChannelType(int chan) {
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
case DIV_SYSTEM_QSOUND:
case DIV_SYSTEM_ES5506:
return chanTypes[28][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_PCSPKR:
@ -1748,6 +1762,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
case DIV_SYSTEM_BUBSYS_WSG:
return chanPrefType[46][dispatchChanOfChan[chan]];
break;
case DIV_SYSTEM_ES5506:
return chanPrefType[47][dispatchChanOfChan[chan]];
break;
}
return DIV_INS_FM;
}

View file

@ -317,6 +317,40 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0xd6+i);
}
break;
case DIV_SYSTEM_ES5506:
for (int i=0; i<32; i++) {
for (int b=0; b<4; b++) {
w->writeC(0xbe);
w->writeC((0xf<<2)+b);
w->writeC(i);
}
unsigned int init_cr=0x0303;
for (int b=0; b<4; b++) {
w->writeC(0xbe);
w->writeC(b);
w->writeC(init_cr>>(24-(b<<3)));
}
for (int r=1; r<11; r++) {
for (int b=0; b<4; b++) {
w->writeC(0xbe);
w->writeC((r<<2)+b);
w->writeC(((r==7 || r==9) && b&2)?0xff:0);
}
}
for (int b=0; b<4; b++) {
w->writeC(0xbe);
w->writeC((0xf<<2)+b);
w->writeC(0x20|i);
}
for (int r=1; r<10; r++) {
for (int b=0; b<4; b++) {
w->writeC(0xbe);
w->writeC((r<<2)+b);
w->writeC(0);
}
}
}
break;
// TODO: it's 3:35am
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:
@ -425,7 +459,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeS(write.val); // sample number
w->writeC((sample->loopStart==0)); // flags
if (sample->loopStart>0) {
loopTimer[streamID]=sample->length8;
loopTimer[streamID]=(double)sample->loopEnd;
loopSample[streamID]=write.val;
}
}
@ -571,6 +605,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(write.val&0xff);
}
break;
case DIV_SYSTEM_ES5506:
w->writeC(0xbe);
w->writeC(write.addr&0xff);
w->writeC(write.val&0xff);
break;
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:
w->writeC(0x0b|baseAddr1);
@ -717,6 +756,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
bool writeSegaPCM=false;
bool writeX1010=false;
bool writeQSound=false;
bool writeES5506=false;
for (int i=0; i<song.systemLen; i++) {
willExport[i]=false;
@ -948,6 +988,19 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++;
}
break;
case DIV_SYSTEM_ES5506:
if (!hasES5505) {
// VGM identifies ES5506 if highest bit sets, otherwise ES5505
hasES5505=0x80000000|disCont[i].dispatch->chipClock;
willExport[i]=true;
writeES5506=true;
} else if (!(hasES5505&0x40000000)) {
isSecond[i]=true;
willExport[i]=false;
hasES5505|=0xc0000000;
howManyChips++;
}
break;
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:
if (!hasOPL) {
@ -1120,7 +1173,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
w->writeI(hasES5503);
w->writeI(hasES5505);
w->writeC(0); // 5503 chans
w->writeC(0); // 5505 chans
w->writeC(hasES5505?1:0); // 5505 chans
w->writeC(0); // C352 clock divider
w->writeC(0); // reserved
w->writeI(hasX1);
@ -1215,8 +1268,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
unsigned int readPos=0;
if (alignedSize>65536) alignedSize=65536;
for (unsigned int j=0; j<alignedSize; j++) {
if (readPos>=sample->length8) {
if (sample->loopStart>=0 && sample->loopStart<(int)sample->length8) {
if ((sample->loopMode && readPos>=sample->loopEnd) || readPos>=sample->length8) {
if (sample->isLoopable()) {
readPos=sample->loopStart;
pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
} else {
@ -1288,6 +1341,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
w->write(x1_010Mem,x1_010MemLen);
}
if (writeES5506 && es5506MemLen>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x8F);
w->writeI(es5506MemLen+8);
w->writeI(es5506MemLen);
w->writeI(0);
w->write(es5506Mem,es5506MemLen);
}
// initialize streams
int streamID=0;
for (int i=0; i<song.systemLen; i++) {
@ -1454,12 +1517,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
if (loopSample[nextToTouch]<song.sampleLen) {
DivSample* sample=song.sample[loopSample[nextToTouch]];
// insert loop
if (sample->loopStart<(int)sample->length8) {
if (sample->loopStart<(int)sample->loopEnd) {
w->writeC(0x93);
w->writeC(nextToTouch);
w->writeI(sample->off8+sample->loopStart);
w->writeC(0x81);
w->writeI(sample->length8-sample->loopStart);
w->writeI(sample->loopEnd-sample->loopStart);
}
}
loopSample[nextToTouch]=-1;

View file

@ -184,6 +184,10 @@ void FurnaceGUI::drawInsList() {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]);
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i);
break;
case DIV_INS_ES5506:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ES5506]);
name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i);
break;
default:
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]);
name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i);

View file

@ -578,6 +578,8 @@ void FurnaceGUI::doAction(int what) {
sample->centerRate=prevSample->centerRate;
sample->name=prevSample->name;
sample->loopStart=prevSample->loopStart;
sample->loopEnd=prevSample->loopEnd;
sample->loopMode=prevSample->loopMode;
sample->depth=prevSample->depth;
if (sample->init(prevSample->samples)) {
if (prevSample->getCurBuf()!=NULL) {

View file

@ -144,6 +144,7 @@ enum FurnaceGUIColors {
GUI_COLOR_INSTR_VERA,
GUI_COLOR_INSTR_X1_010,
GUI_COLOR_INSTR_VRC6_SAW,
GUI_COLOR_INSTR_ES5506,
GUI_COLOR_INSTR_UNKNOWN,
GUI_COLOR_CHANNEL_FM,

View file

@ -106,10 +106,11 @@ const char* insTypes[DIV_INS_MAX]={
"Atari Lynx",
"VERA",
"X1-010",
"VRC6 (saw)"
"VRC6 (saw)",
"ES5506"
};
const char* sampleDepths[17]={
const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={
"1-bit PCM",
"1-bit DPCM",
NULL,
@ -129,7 +130,7 @@ const char* sampleDepths[17]={
"16-bit PCM"
};
const char* resampleStrats[]={
const char* resampleStrats[DIV_RESAMPLE_MAX]={
"none",
"linear",
"cubic spline",
@ -138,6 +139,13 @@ const char* resampleStrats[]={
"best possible"
};
const char* loopMode[DIV_SAMPLE_LOOPMODE_MAX]={
"Disable",
"Foward",
"Backward",
"Pingpong"
};
#define D FurnaceGUIActionDef
#define NOT_AN_ACTION -1
@ -449,6 +457,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
D(GUI_COLOR_INSTR_VERA,"",ImVec4(0.4f,0.6f,1.0f,1.0f)),
D(GUI_COLOR_INSTR_X1_010,"",ImVec4(0.3f,0.5f,1.0f,1.0f)),
D(GUI_COLOR_INSTR_VRC6_SAW,"",ImVec4(0.8f,0.3f,0.0f,1.0f)),
D(GUI_COLOR_INSTR_ES5506,"",ImVec4(1.0f,0.5f,0.5f,1.0f)),
D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)),
D(GUI_COLOR_CHANNEL_FM,"",ImVec4(0.2f,0.8f,1.0f,1.0f)),
@ -552,6 +561,7 @@ const int availableSystems[]={
DIV_SYSTEM_VRC6,
DIV_SYSTEM_FDS,
DIV_SYSTEM_MMC5,
DIV_SYSTEM_ES5506,
0 // don't remove this last one!
};

View file

@ -40,8 +40,9 @@ extern const char* noteNames[180];
extern const char* noteNamesG[180];
extern const char* pitchLabel[11];
extern const char* insTypes[];
extern const char* sampleDepths[17];
extern const char* sampleDepths[];
extern const char* resampleStrats[];
extern const char* loopMode[];
extern const int availableSystems[];
extern const FurnaceGUIActionDef guiActions[];
extern const FurnaceGUIColorDef guiColors[];

View file

@ -175,6 +175,10 @@ const char* n163UpdateBits[8]={
"now", "every waveform changed", NULL
};
const char* es5506FilterModes[4]={
"HP/K2, HP/K2", "HP/K2, LP/K1", "LP/K2, LP/K2", "LP/K2, LP/K1",
};
const char* oneBit[2]={
"on", NULL
};
@ -2319,7 +2323,7 @@ void FurnaceGUI::drawInsEdit() {
P(ImGui::Checkbox("Absolute Duty Macro",&ins->c64.dutyIsAbs));
ImGui::EndTabItem();
}
if (ins->type==DIV_INS_AMIGA) if (ImGui::BeginTabItem("Amiga/Sample")) {
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ES5506) if (ImGui::BeginTabItem("Amiga/Sample")) {
String sName;
if (ins->amiga.initSample<0 || ins->amiga.initSample>=e->song.sampleLen) {
sName="none selected";
@ -2368,32 +2372,32 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
ImGui::Text("%s",noteNames[60+i]);
ImGui::TableNextColumn();
if (ins->amiga.noteMap[i]<0 || ins->amiga.noteMap[i]>=e->song.sampleLen) {
if (ins->amiga.noteMap[i].ind<0 || ins->amiga.noteMap[i].ind>=e->song.sampleLen) {
sName="-- empty --";
ins->amiga.noteMap[i]=-1;
ins->amiga.noteMap[i].ind=-1;
} else {
sName=e->song.sample[ins->amiga.noteMap[i]]->name;
sName=e->song.sample[ins->amiga.noteMap[i].ind]->name;
}
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::BeginCombo("##SM",sName.c_str())) {
String id;
if (ImGui::Selectable("-- empty --",ins->amiga.noteMap[i]==-1)) { PARAMETER
ins->amiga.noteMap[i]=-1;
if (ImGui::Selectable("-- empty --",ins->amiga.noteMap[i].ind==-1)) { PARAMETER
ins->amiga.noteMap[i].ind=-1;
}
for (int j=0; j<e->song.sampleLen; j++) {
id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name);
if (ImGui::Selectable(id.c_str(),ins->amiga.noteMap[i]==j)) { PARAMETER
ins->amiga.noteMap[i]=j;
if (ins->amiga.noteFreq[i]<=0) ins->amiga.noteFreq[i]=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-48.0)/12.0));
if (ImGui::Selectable(id.c_str(),ins->amiga.noteMap[i].ind==j)) { PARAMETER
ins->amiga.noteMap[i].ind=j;
if (ins->amiga.noteMap[i].freq<=0) ins->amiga.noteMap[i].freq=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-48.0)/12.0));
}
}
ImGui::EndCombo();
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##SF",&ins->amiga.noteFreq[i],50,500)) { PARAMETER
if (ins->amiga.noteFreq[i]<0) ins->amiga.noteFreq[i]=0;
if (ins->amiga.noteFreq[i]>262144) ins->amiga.noteFreq[i]=262144;
if (ImGui::InputInt("##SF",&ins->amiga.noteMap[i].freq,50,500)) { PARAMETER
if (ins->amiga.noteMap[i].freq<0) ins->amiga.noteMap[i].freq=0;
if (ins->amiga.noteMap[i].freq>262144) ins->amiga.noteMap[i].freq=262144;
}
ImGui::PopID();
}
@ -2468,6 +2472,48 @@ void FurnaceGUI::drawInsEdit() {
}
ImGui::EndTabItem();
}
if (ins->type==DIV_INS_ES5506) if (ImGui::BeginTabItem("ES5506")) {
if (ImGui::BeginTable("ESParams",2,ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0);
// volume
ImGui::TableNextRow();
ImGui::TableNextColumn();
P(CWSliderScalar("Left volume",ImGuiDataType_S32,&ins->es5506.lVol,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable
ImGui::TableNextColumn();
P(CWSliderScalar("Right volume",ImGuiDataType_S32,&ins->es5506.rVol,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable
// 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
ImGui::TableNextRow();
ImGui::TableNextColumn();
P(CWSliderScalar("Filter K1",ImGuiDataType_U16,&ins->es5506.filter.k1,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable
ImGui::TableNextColumn();
P(CWSliderScalar("Filter K2",ImGuiDataType_U16,&ins->es5506.filter.k2,&_ZERO,&_SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE)); rightClickable
// envelope
ImGui::TableNextRow();
ImGui::TableNextColumn();
P(CWSliderScalar("Envelope count",ImGuiDataType_U16,&ins->es5506.envelope.ecount,&_ZERO,&_FIVE_HUNDRED_ELEVEN)); rightClickable
ImGui::TableNextRow();
ImGui::TableNextColumn();
P(CWSliderScalar("Left Volume Ramp",ImGuiDataType_S8,&ins->es5506.envelope.lVRamp,&_MINUS_ONE_HUNDRED_TWENTY_EIGHT,&_ONE_HUNDRED_TWENTY_SEVEN)); rightClickable
ImGui::TableNextColumn();
P(CWSliderScalar("Right Volume Ramp",ImGuiDataType_S8,&ins->es5506.envelope.rVRamp,&_MINUS_ONE_HUNDRED_TWENTY_EIGHT,&_ONE_HUNDRED_TWENTY_SEVEN)); rightClickable
ImGui::TableNextRow();
ImGui::TableNextColumn();
P(CWSliderScalar("Filter K1 Ramp",ImGuiDataType_S8,&ins->es5506.envelope.k1Ramp,&_MINUS_ONE_HUNDRED_TWENTY_EIGHT,&_ONE_HUNDRED_TWENTY_SEVEN)); rightClickable
ImGui::TableNextColumn();
P(CWSliderScalar("Filter K2 Ramp",ImGuiDataType_S8,&ins->es5506.envelope.k2Ramp,&_MINUS_ONE_HUNDRED_TWENTY_EIGHT,&_ONE_HUNDRED_TWENTY_SEVEN)); rightClickable
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Checkbox("K1 Ramp Slowdown",&ins->es5506.envelope.k1Slow);
ImGui::TableNextColumn();
ImGui::Checkbox("K2 Ramp Slowdown",&ins->es5506.envelope.k2Slow);
ImGui::EndTable();
}
ImGui::EndTabItem();
}
if (ins->type==DIV_INS_GB ||
(ins->type==DIV_INS_AMIGA && ins->amiga.useWave) ||
ins->type==DIV_INS_X1_010 ||
@ -2611,6 +2657,9 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_FDS) {
volMax=32;
}
if (ins->type==DIV_INS_ES5506) {
volMax=65535;
}
bool arpMode=ins->std.arpMacro.mode;
@ -2665,6 +2714,10 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="Duty";
dutyMax=7;
}
if (ins->type==DIV_INS_ES5506) {
dutyLabel="Filter Mode";
dutyMax=3;
}
bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs);
const char* waveLabel="Waveform";
@ -2679,6 +2732,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_SAA1099) waveMax=2;
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0;
if (ins->type==DIV_INS_MIKEY) waveMax=0;
if (ins->type==DIV_INS_ES5506) waveMax=255;
if (ins->type==DIV_INS_PET) {
waveMax=8;
bitMode=true;

View file

@ -30,6 +30,9 @@ const int _SIXTY_FOUR=64;
const int _ONE_HUNDRED=100;
const int _ONE_HUNDRED_TWENTY_SEVEN=127;
const int _TWO_HUNDRED_FIFTY_FIVE=255;
const int _FIVE_HUNDRED_ELEVEN=511;
const int _TWO_THOUSAND_FORTY_SEVEN=2047;
const int _FOUR_THOUSAND_NINETY_FIVE=4095;
const int _SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE=65535;
const int _MINUS_ONE_HUNDRED_TWENTY_SEVEN=-127;
const int _MINUS_ONE_HUNDRED_TWENTY_EIGHT=-128;

View file

@ -32,6 +32,9 @@ extern const int _SIXTY_FOUR;
extern const int _ONE_HUNDRED;
extern const int _ONE_HUNDRED_TWENTY_SEVEN;
extern const int _TWO_HUNDRED_FIFTY_FIVE;
extern const int _FIVE_HUNDRED_ELEVEN;
extern const int _TWO_THOUSAND_FORTY_SEVEN;
extern const int _FOUR_THOUSAND_NINETY_FIVE;
extern const int _SIXTY_FIVE_THOUSAND_FIVE_HUNDRED_THIRTY_FIVE;
extern const int _MINUS_ONE_HUNDRED_TWENTY_SEVEN;
extern const int _MINUS_ONE_HUNDRED_TWENTY_EIGHT;

View file

@ -178,6 +178,12 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Ensoniq ES5506", {
DIV_SYSTEM_ES5506, 64, 0, 31,
0
}
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Game consoles");

View file

@ -41,7 +41,7 @@ void FurnaceGUI::drawSampleEdit() {
} else {
DivSample* sample=e->song.sample[curSample];
String sampleType="Invalid";
if (sample->depth<17) {
if (sample->depth<DIV_SAMPLE_DEPTH_MAX) {
if (sampleDepths[sample->depth]!=NULL) {
sampleType=sampleDepths[sample->depth];
}
@ -61,11 +61,11 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) {
for (int i=0; i<17; i++) {
for (int i=0; i<DIV_SAMPLE_DEPTH_MAX; i++) {
if (sampleDepths[i]==NULL) continue;
if (ImGui::Selectable(sampleDepths[i])) {
sample->prepareUndo(true);
sample->depth=i;
sample->depth=DivSampleDepth(i);
e->renderSamplesP();
updateSampleTex=true;
MARK_MODIFIED;
@ -93,22 +93,54 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::TableNextColumn();
bool doLoop=(sample->loopStart>=0);
if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED
if (doLoop) {
ImGui::Text("Loop Mode");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
int mode=(int)sample->loopMode;
if (ImGui::Combo("##LoopMode",&mode,loopMode,DIV_SAMPLE_LOOPMODE_MAX,DIV_SAMPLE_LOOPMODE_MAX)) { MARK_MODIFIED
if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) {
sample->loopStart=0;
} else {
sample->loopStart=-1;
}
if (sample->loopEnd>sample->samples) {
sample->loopEnd=sample->samples;
}
sample->loopMode=DivSampleLoopMode(mode);
updateSampleTex=true;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Length: %d",sample->samples);
bool doLoop=(sample->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT);
if (doLoop) {
ImGui::TableNextColumn();
ImGui::Text("Loop start");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED
if (ImGui::InputInt("##LoopStart",&sample->loopStart,1,10)) { MARK_MODIFIED
if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) {
sample->loopStart=0;
}
if (sample->loopStart>=(int)sample->loopEnd) {
sample->loopStart=sample->loopEnd;
}
updateSampleTex=true;
}
ImGui::TableNextColumn();
ImGui::Text("Loop End");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
int end=(int)sample->loopEnd;
if (ImGui::InputInt("##LoopEnd",&end,1,10)) { MARK_MODIFIED
if (end<0) {
end=0;
}
if (end<sample->loopStart) {
end=sample->loopStart;
}
if (end>sample->samples) {
end=sample->samples;
}
sample->loopEnd=end;
updateSampleTex=true;
}
}
@ -123,7 +155,7 @@ void FurnaceGUI::drawSampleEdit() {
*/
ImGui::Separator();
ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16);
ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT);
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode));
if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) {
@ -275,14 +307,14 @@ void FurnaceGUI::drawSampleEdit() {
SAMPLE_OP_BEGIN;
float vol=amplifyVol/100.0f;
if (sample->depth==16) {
if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) {
for (unsigned int i=start; i<end; i++) {
float val=sample->data16[i]*vol;
if (val<-32768) val=-32768;
if (val>32767) val=32767;
sample->data16[i]=val;
}
} else if (sample->depth==8) {
} else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
for (unsigned int i=start; i<end; i++) {
float val=sample->data8[i]*vol;
if (val<-128) val=-128;
@ -466,7 +498,7 @@ void FurnaceGUI::drawSampleEdit() {
double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0;
if (sample->depth==16) {
if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) {
for (unsigned int i=start; i<end; i++) {
double freq=sampleFilterCutStart+(sampleFilterCutEnd-sampleFilterCutStart)*pow(double(i-start)/double(end-start),power);
double cut=sin((freq/double(sample->rate))*M_PI);
@ -482,7 +514,7 @@ void FurnaceGUI::drawSampleEdit() {
if (val>32767) val=32767;
sample->data16[i]=val;
}
} else if (sample->depth==8) {
} else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
for (unsigned int i=start; i<end; i++) {
double freq=sampleFilterCutStart+(sampleFilterCutEnd-sampleFilterCutStart)*pow(double(i-start)/double(end-start),power);
double cut=sin((freq/double(sample->rate))*M_PI);
@ -574,11 +606,11 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) {
for (int i=0; i<17; i++) {
for (int i=0; i<DIV_SAMPLE_DEPTH_MAX; i++) {
if (sampleDepths[i]==NULL) continue;
if (ImGui::Selectable(sampleDepths[i])) {
sample->prepareUndo(true);
sample->depth=i;
sample->depth=DivSampleDepth(i);
e->renderSamplesP();
updateSampleTex=true;
MARK_MODIFIED;
@ -607,22 +639,55 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::TableNextColumn();
bool doLoop=(sample->loopStart>=0);
if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED
if (doLoop) {
ImGui::Text("Loop Mode");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
int mode=(int)sample->loopMode;
if (ImGui::Combo("##LoopMode",&mode,loopMode,DIV_SAMPLE_LOOPMODE_MAX,DIV_SAMPLE_LOOPMODE_MAX)) { MARK_MODIFIED
if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) {
sample->loopStart=0;
} else {
sample->loopStart=-1;
}
if (sample->loopEnd>sample->samples) {
sample->loopEnd=sample->samples;
}
sample->loopMode=DivSampleLoopMode(mode);
updateSampleTex=true;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Length: %d",sample->samples);
bool doLoop=(sample->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT);
if (doLoop) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Loop start");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED
if (ImGui::InputInt("##LoopStart",&sample->loopStart,1,10)) { MARK_MODIFIED
if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) {
sample->loopStart=0;
}
if (sample->loopStart>=(int)sample->loopEnd) {
sample->loopStart=loopEnd;
}
updateSampleTex=true;
}
ImGui::TableNextColumn();
ImGui::Text("Loop End");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
int end=(int)sample->loopEnd;
if (ImGui::InputInt("##LoopEnd",&end,1,10)) { MARK_MODIFIED
if (end<0) {
end=0;
}
if (end<sample->loopStart) {
end=sample->loopStart;
}
if (end>sample->samples) {
end=sample->samples;
}
sample->loopEnd=end;
updateSampleTex=true;
}
}
@ -637,7 +702,7 @@ void FurnaceGUI::drawSampleEdit() {
*/
ImGui::Separator();
ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16);
ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT);
ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode));
if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) {
@ -820,7 +885,7 @@ void FurnaceGUI::drawSampleEdit() {
double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0;
if (sample->depth==16) {
if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) {
for (unsigned int i=start; i<end; i++) {
double freq=sampleFilterCutStart+(sampleFilterCutEnd-sampleFilterCutStart)*pow(double(i-start)/double(end-start),power);
double cut=sin((freq/double(sample->rate))*M_PI);
@ -836,7 +901,7 @@ void FurnaceGUI::drawSampleEdit() {
if (val>32767) val=32767;
sample->data16[i]=val;
}
} else if (sample->depth==8) {
} else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
for (unsigned int i=start; i<end; i++) {
double freq=sampleFilterCutStart+(sampleFilterCutEnd-sampleFilterCutStart)*pow(double(i-start)/double(end-start),power);
double cut=sin((freq/double(sample->rate))*M_PI);
@ -890,14 +955,14 @@ void FurnaceGUI::drawSampleEdit() {
SAMPLE_OP_BEGIN;
float vol=amplifyVol/100.0f;
if (sample->depth==16) {
if (sample->depth==DIV_SAMPLE_DEPTH_16BIT) {
for (unsigned int i=start; i<end; i++) {
float val=sample->data16[i]*vol;
if (val<-32768) val=-32768;
if (val>32767) val=32767;
sample->data16[i]=val;
}
} else if (sample->depth==8) {
} else if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
for (unsigned int i=start; i<end; i++) {
float val=sample->data8[i]*vol;
if (val<-128) val=-128;
@ -1137,7 +1202,8 @@ void FurnaceGUI::drawSampleEdit() {
ImU32 centerLineColor=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_PlotLines,0.25));
for (int i=0; i<availY; i++) {
for (int j=0; j<availX; j++) {
if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples && ((j+samplePos)*sampleZoom)>sample->loopStart) {
int scaledPos=samplePos+(j*sampleZoom);
if (sample->isLoopable() && ((scaledPos>=sample->loopStart) && (scaledPos<sample->loopEnd))) {
data[i*availX+j]=bgColorLoop;
} else {
data[i*availX+j]=bgColor;
@ -1157,7 +1223,7 @@ void FurnaceGUI::drawSampleEdit() {
if (xCoarse>=sample->samples) break;
int y1, y2;
int totalAdvance=0;
if (sample->depth==8) {
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
y1=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256;
} else {
y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536;
@ -1170,7 +1236,7 @@ void FurnaceGUI::drawSampleEdit() {
totalAdvance+=xAdvanceCoarse;
if (xCoarse>=sample->samples) break;
do {
if (sample->depth==8) {
if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
y2=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256;
} else {
y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536;
@ -1208,11 +1274,11 @@ void FurnaceGUI::drawSampleEdit() {
sampleSelStart=0;
sampleSelEnd=sample->samples;
} else {
if (sample->samples>0 && (sample->depth==16 || sample->depth==8)) {
if (sample->samples>0 && (sample->depth==DIV_SAMPLE_DEPTH_16BIT || sample->depth==DIV_SAMPLE_DEPTH_8BIT)) {
sampleDragStart=rectMin;
sampleDragAreaSize=rectSize;
sampleDrag16=(sample->depth==16);
sampleDragTarget=(sample->depth==16)?((void*)sample->data16):((void*)sample->data8);
sampleDrag16=(sample->depth==DIV_SAMPLE_DEPTH_16BIT);
sampleDragTarget=(sample->depth==DIV_SAMPLE_DEPTH_16BIT)?((void*)sample->data16):((void*)sample->data8);
sampleDragLen=sample->samples;
sampleDragActive=true;
sampleSelStart=-1;
@ -1277,7 +1343,7 @@ void FurnaceGUI::drawSampleEdit() {
posX=samplePos+pos.x*sampleZoom;
if (posX>(int)sample->samples) posX=-1;
}
posY=(0.5-pos.y/rectSize.y)*((sample->depth==8)?255:32767);
posY=(0.5-pos.y/rectSize.y)*((sample->depth==DIV_SAMPLE_DEPTH_8BIT)?255:32767);
if (posX>=0) {
statusBar+=fmt::sprintf(" | (%d, %d)",posX,posY);
}
@ -1325,7 +1391,7 @@ void FurnaceGUI::drawSampleEdit() {
}
}
if (sample->depth!=8 && sample->depth!=16) {
if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) {
statusBar="Non-8/16-bit samples cannot be edited without prior conversion.";
}

View file

@ -32,6 +32,7 @@ void FurnaceGUI::drawStats() {
String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024);
String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024);
String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024);
String es5506Usage=fmt::sprintf("%d/16384KB",e->es5506MemLen/1024);
ImGui::Text("ADPCM-A");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str());
@ -44,6 +45,9 @@ void FurnaceGUI::drawStats() {
ImGui::Text("X1-010");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str());
ImGui::Text("ES5506");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->es5506MemLen)/16777216.0f,ImVec2(-FLT_MIN,0),es5506Usage.c_str());
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS;
ImGui::End();

View file

@ -366,7 +366,7 @@ void FurnaceGUI::drawSysConf(int i) {
}
ImGui::Text("Initial channel limit:");
int initialChannelLimit=((flags>>4)&7)+1;
if (CWSliderInt("##InitialChannelLimit",&initialChannelLimit,1,8)) {
if (CWSliderInt("##N163_InitialChannelLimit",&initialChannelLimit,1,8)) {
if (initialChannelLimit<1) initialChannelLimit=1;
if (initialChannelLimit>8) initialChannelLimit=8;
e->setSysFlags(i,(flags & ~(7 << 4)) | (((initialChannelLimit-1) & 7) << 4),restart);
@ -379,6 +379,17 @@ void FurnaceGUI::drawSysConf(int i) {
}
break;
}
case DIV_SYSTEM_ES5506: {
ImGui::Text("Initial channel limit:");
int initialChannelLimit=(flags&31)+1;
if (CWSliderInt("##OTTO_InitialChannelLimit",&initialChannelLimit,5,32)) {
if (initialChannelLimit<5) initialChannelLimit=5;
if (initialChannelLimit>32) initialChannelLimit=32;
e->setSysFlags(i,(flags & ~31) | ((initialChannelLimit-1) & 31),restart);
updateWindowTitle();
} rightClickable
break;
}
case DIV_SYSTEM_GB:
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_VERA: