mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-22 12:35:11 +00:00
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:
parent
96715ed88c
commit
29ea6dc360
50 changed files with 4501 additions and 248 deletions
|
@ -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
|
||||
)
|
||||
|
||||
|
|
8
TODO.md
8
TODO.md
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
638
src/engine/platform/es5506.cpp
Normal file
638
src/engine/platform/es5506.cpp
Normal 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() {
|
||||
}
|
173
src/engine/platform/es5506.h
Normal file
173
src/engine/platform/es5506.h
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
444
src/engine/platform/sound/es550x/es5504.cpp
Normal file
444
src/engine/platform/sound/es550x/es5504.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
src/engine/platform/sound/es550x/es5504.hpp
Normal file
87
src/engine/platform/sound/es550x/es5504.hpp
Normal 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
|
599
src/engine/platform/sound/es550x/es5505.cpp
Normal file
599
src/engine/platform/sound/es550x/es5505.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
141
src/engine/platform/sound/es550x/es5505.hpp
Normal file
141
src/engine/platform/sound/es550x/es5505.hpp
Normal 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
|
787
src/engine/platform/sound/es550x/es5506.cpp
Normal file
787
src/engine/platform/sound/es550x/es5506.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
179
src/engine/platform/sound/es550x/es5506.hpp
Normal file
179
src/engine/platform/sound/es550x/es5506.hpp
Normal 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
|
75
src/engine/platform/sound/es550x/es550x.cpp
Normal file
75
src/engine/platform/sound/es550x/es550x.cpp
Normal 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();
|
||||
}
|
391
src/engine/platform/sound/es550x/es550x.hpp
Normal file
391
src/engine/platform/sound/es550x/es550x.hpp
Normal 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
|
116
src/engine/platform/sound/es550x/es550x_alu.cpp
Normal file
116
src/engine/platform/sound/es550x/es550x_alu.cpp
Normal 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);
|
||||
}
|
70
src/engine/platform/sound/es550x/es550x_filter.cpp
Normal file
70
src/engine/platform/sound/es550x/es550x_filter.cpp
Normal 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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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!
|
||||
};
|
||||
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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.";
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue