diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f137e13..f8c317d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,6 +435,7 @@ src/engine/platform/sound/ymz280b.cpp src/engine/platform/sound/rf5c68.cpp +src/engine/platform/sound/oki/msm5232.cpp src/engine/platform/sound/oki/okim6258.cpp src/engine/platform/sound/snes/SPC_DSP.cpp @@ -493,6 +494,7 @@ src/engine/platform/fds.cpp src/engine/platform/tia.cpp src/engine/platform/saa.cpp src/engine/platform/amiga.cpp +src/engine/platform/msm5232.cpp src/engine/platform/msm6258.cpp src/engine/platform/msm6295.cpp src/engine/platform/pcspkr.cpp diff --git a/papers/format.md b/papers/format.md index 440a6a40..019f6b1c 100644 --- a/papers/format.md +++ b/papers/format.md @@ -452,8 +452,8 @@ size | description 4 | size of this block 2 | format version (see header) 1 | instrument type - | - 0: standard - | - 1: FM (OPM/OPN) + | - 0: SN76489/standard + | - 1: FM (OPN) | - 2: Game Boy | - 3: C64 | - 4: Amiga/sample @@ -485,6 +485,17 @@ size | description | - 30: Sound Unit | - 31: Namco WSG | - 32: OPL (drums) + | - 33: FM (OPM) + | - 34: NES + | - 35: MSM6258 + | - 36: MSM6295 + | - 37: ADPCM-A + | - 38: ADPCM-B + | - 39: SegaPCM + | - 40: QSound + | - 41: YMZ280B + | - 42: RF5C68 + | - 43: MSM5232 1 | reserved STR | instrument name --- | **FM instrument data** diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 6a8438de..5614be63 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -21,6 +21,7 @@ #include "engine.h" #include "platform/genesis.h" #include "platform/genesisext.h" +#include "platform/msm5232.h" #include "platform/msm6258.h" #include "platform/msm6295.h" #include "platform/namcowsg.h" @@ -390,6 +391,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_MSM6295: dispatch=new DivPlatformMSM6295; break; + case DIV_SYSTEM_MSM5232: + dispatch=new DivPlatformMSM5232; + break; case DIV_SYSTEM_NAMCO: dispatch=new DivPlatformNamcoWSG; // Pac-Man (TODO: support Pole Position?) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index b01a2954..db882070 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2127,12 +2127,13 @@ void DivEngine::previewSample(int sample, int note, int pStart, int pEnd) { blip_clear(samp_bb); double rate=song.sample[sample]->rate; if (note>=0) { - rate=(song.tuning*pow(2.0,(double)(note+3)/12.0)*((double)song.sample[sample]->centerRate/8363.0)); + rate=(pow(2.0,(double)(note)/12.0)*((double)song.sample[sample]->centerRate)*0.0625); if (rate<=0) rate=song.sample[sample]->rate; } if (rate<100) rate=100; blip_set_rates(samp_bb,rate,got.rate); samp_prevSample=0; + sPreview.rate=rate; sPreview.pos=(sPreview.pBegin>=0)?sPreview.pBegin:0; sPreview.sample=sample; sPreview.wave=-1; @@ -2166,6 +2167,7 @@ void DivEngine::previewWave(int wave, int note) { if (rate<100) rate=100; blip_set_rates(samp_bb,rate,got.rate); samp_prevSample=0; + sPreview.rate=rate; sPreview.pos=0; sPreview.sample=-1; sPreview.wave=wave; @@ -2181,6 +2183,18 @@ void DivEngine::stopWavePreview() { BUSY_END; } +bool DivEngine::isPreviewingSample() { + return (sPreview.sample>=0 && sPreview.sample<(int)song.sample.size()); +} + +int DivEngine::getSamplePreviewPos() { + return sPreview.pos; +} + +double DivEngine::getSamplePreviewRate() { + return sPreview.rate; +} + String DivEngine::getConfigPath() { return configPath; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 4561ba92..3a03c427 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -387,12 +387,14 @@ class DivEngine { DivSystem sysFileMapDMF[256]; struct SamplePreview { + double rate; int sample; int wave; int pos; int pBegin, pEnd; bool dir; SamplePreview(): + rate(0.0), sample(-1), wave(-1), pos(0), @@ -601,6 +603,11 @@ class DivEngine { // reset playback state void syncReset(); + // sample preview query + bool isPreviewingSample(); + int getSamplePreviewPos(); + double getSamplePreviewRate(); + // trigger sample preview void previewSample(int sample, int note=-1, int pStart=-1, int pEnd=-1); void stopSamplePreview(); diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 020edb96..abb57edb 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1502,11 +1502,36 @@ bool DivInstrument::saveDMP(const char* path) { } w->writeC(std.arpMacro.len); + bool arpMacroMode=false; + int arpMacroHowManyFixed=0; + int realArpMacroLen=std.arpMacro.len; for (int i=0; iwriteI(std.arpMacro.val[i]+12); + if ((std.arpMacro.val[i]&0xc0000000)==0x40000000 || (std.arpMacro.val[i]&0xc0000000)==0x80000000) { + arpMacroHowManyFixed++; + } } - if (std.arpMacro.len>0) w->writeC(std.arpMacro.loop); - w->writeC(std.arpMacro.mode); + if (arpMacroHowManyFixed>=std.arpMacro.len-1) { + arpMacroMode=true; + } + if (std.arpMacro.len>0) { + if (arpMacroMode && std.arpMacro.val[std.arpMacro.len-1]==0 && std.arpMacro.loop>=std.arpMacro.len) { + realArpMacroLen--; + } + } + + if (arpMacroMode) { + for (int i=0; iwriteI(std.arpMacro.val[i]); + } + } else { + for (int i=0; iwriteI(std.arpMacro.val[i]+12); + } + } + if (realArpMacroLen>0) { + w->writeC(std.arpMacro.loop); + } + w->writeC(arpMacroMode); w->writeC(std.dutyMacro.len); for (int i=0; i + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } + +#define NOTE_LINEAR(x) ((x)<<7) + +const char* regCheatSheetMSM5232[]={ + "Select", "0", + "MasterVol", "1", + "FreqL", "2", + "FreqH", "3", + "DataCtl", "4", + "ChanVol", "5", + "WaveCtl", "6", + "NoiseCtl", "7", + "LFOFreq", "8", + "LFOCtl", "9", + NULL +}; + +const char** DivPlatformMSM5232::getRegisterSheet() { + return regCheatSheetMSM5232; +} + +void DivPlatformMSM5232::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hwrite(w.addr,w.val); + regPool[w.addr&0x0f]=w.val; + writes.pop(); + } + memset(temp,0,16*sizeof(short)); + + /*for (int i=0; i<8; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767); + }*/ + + msm->sound_stream_update(temp); + + //printf("tempL: %d tempR: %d\n",tempL,tempR); + bufL[h]=0; + for (int i=0; i<8; i++) { + bufL[h]+=(temp[i]*partVolume[i])>>8; + } + } +} + +const int attackMap[8]={ + 0, 1, 2, 3, 4, 5, 5, 5 +}; + +const int decayMap[16]={ + 0, 1, 2, 3, 8, 9, 4, 10, 5, 11, 12, 13, 13, 13, 13, 13 +}; + +void DivPlatformMSM5232::tick(bool sysTick) { + for (int i=0; i<8; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&127,MIN(127,chan[i].std.vol.val),127); + } + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_LINEAR(parent->calcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + if (chan[i].std.duty.had) { + groupControl[i>>2]=(chan[i].std.duty.val&0x1f)|(groupEnv[i>>2]?0x20:0); + updateGroup[i>>2]=true; + } + if (chan[i].std.ex1.had) { // attack + groupAR[i>>2]=attackMap[chan[i].std.ex1.val&7]; + updateGroupAR[i>>2]=true; + } + if (chan[i].std.ex2.had) { // decay + groupDR[i>>2]=decayMap[chan[i].std.ex2.val&15]; + updateGroupDR[i>>2]=true; + } + if (chan[i].std.ex3.had) { // noise + chan[i].noise=chan[i].std.ex3.val; + chan[i].freqChanged=true; + } + } + + for (int i=0; i<2; i++) { + if (updateGroup[i]) { + rWrite(12+i,groupControl[i]); + updateGroup[i]=false; + } + if (updateGroupAR[i]) { + rWrite(8+i,groupAR[i]); + updateGroupAR[i]=false; + } + if (updateGroupDR[i]) { + rWrite(10+i,groupDR[i]); + updateGroupDR[i]=false; + } + } + + for (int i=0; i<8; i++) { + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); + chan[i].freq=chan[i].baseFreq+chan[i].pitch+chan[i].pitch2-(12<<7); + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>0x2aff) chan[i].freq=0x2aff; + if (chan[i].keyOn) { + //rWrite(16+i*5,0x80); + //chWrite(i,0x04,0x80|chan[i].vol); + } + if (chan[i].active) { + rWrite(i,chan[i].noise?0xd8:(0x80|(chan[i].freq>>7))); + } + if (chan[i].keyOff) { + rWrite(i,0); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } + + msm->set_vol_input( + chan[0].active?((double)chan[0].outVol/127.0):0.0, + chan[1].active?((double)chan[1].outVol/127.0):0.0, + chan[2].active?((double)chan[2].outVol/127.0):0.0, + chan[3].active?((double)chan[3].outVol/127.0):0.0, + chan[4].active?((double)chan[4].outVol/127.0):0.0, + chan[5].active?((double)chan[5].outVol/127.0):0.0, + chan[6].active?((double)chan[6].outVol/127.0):0.0, + chan[7].active?((double)chan[7].outVol/127.0):0.0 + ); +} + +int DivPlatformMSM5232::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_LINEAR(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + } + 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_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_LINEAR(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value*parent->song.pitchSlideSpeed; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value*parent->song.pitchSlideSpeed; + 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_STD_NOISE_MODE: + chan[c.chan].noise=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_LINEAR(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformMSM5232::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + msm->mute(ch,mute); +} + +void DivPlatformMSM5232::forceIns() { + for (int i=0; i<8; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + } + for (int i=0; i<2; i++) { + updateGroup[i]=true; + updateGroupAR[i]=true; + updateGroupDR[i]=true; + } +} + +void* DivPlatformMSM5232::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformMSM5232::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +DivDispatchOscBuffer* DivPlatformMSM5232::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformMSM5232::getRegisterPool() { + return regPool; +} + +int DivPlatformMSM5232::getRegisterPoolSize() { + return 14; +} + +void DivPlatformMSM5232::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,128); + for (int i=0; i<8; i++) { + chan[i]=DivPlatformMSM5232::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + msm->device_start(); + msm->device_reset(); + memset(temp,0,16*sizeof(short)); + cycles=0; + curChan=-1; + delay=500; + + for (int i=0; i<2; i++) { + groupControl[i]=15|(groupEnv[i]?0x20:0); + groupAR[i]=0; + groupDR[i]=5; + + updateGroup[i]=true; + updateGroupAR[i]=true; + updateGroupDR[i]=true; + } + + for (int i=0; i<8; i++) { + rWrite(i,0); + partVolume[i]=initPartVolume[i]; + msm->mute(i,isMuted[i]); + } +} + +bool DivPlatformMSM5232::isStereo() { + return false; +} + +bool DivPlatformMSM5232::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformMSM5232::notifyInsDeletion(void* ins) { + for (int i=0; i<8; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformMSM5232::setFlags(const DivConfig& flags) { + chipClock=2119040; + detune=flags.getInt("detune",0); + msm->set_clock(chipClock+detune*1024); + rate=msm->get_rate(); + for (int i=0; i<8; i++) { + oscBuf[i]->rate=rate; + } + initPartVolume[0]=flags.getInt("partVolume0",255); + initPartVolume[1]=flags.getInt("partVolume1",255); + initPartVolume[2]=flags.getInt("partVolume2",255); + initPartVolume[3]=flags.getInt("partVolume3",255); + initPartVolume[4]=flags.getInt("partVolume4",255); + initPartVolume[5]=flags.getInt("partVolume5",255); + initPartVolume[6]=flags.getInt("partVolume6",255); + initPartVolume[7]=flags.getInt("partVolume7",255); + + capacitance[0]=flags.getFloat("capValue0",390.0f); + capacitance[1]=flags.getFloat("capValue1",390.0f); + capacitance[2]=flags.getFloat("capValue2",390.0f); + capacitance[3]=flags.getFloat("capValue3",390.0f); + capacitance[4]=flags.getFloat("capValue4",390.0f); + capacitance[5]=flags.getFloat("capValue5",390.0f); + capacitance[6]=flags.getFloat("capValue6",390.0f); + capacitance[7]=flags.getFloat("capValue7",390.0f); + + groupEnv[0]=flags.getBool("groupEnv0",true); + groupEnv[1]=flags.getBool("groupEnv1",true); + + msm->set_capacitors( + capacitance[0]*0.000000001, + capacitance[1]*0.000000001, + capacitance[2]*0.000000001, + capacitance[3]*0.000000001, + capacitance[4]*0.000000001, + capacitance[5]*0.000000001, + capacitance[6]*0.000000001, + capacitance[7]*0.000000001 + ); +} + +void DivPlatformMSM5232::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformMSM5232::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformMSM5232::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<8; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + msm=new msm5232_device(2119040); + msm->device_start(); + setFlags(flags); + reset(); + return 8; +} + +void DivPlatformMSM5232::quit() { + for (int i=0; i<8; i++) { + delete oscBuf[i]; + } + if (msm!=NULL) { + msm->device_stop(); + delete msm; + msm=NULL; + } +} + +DivPlatformMSM5232::~DivPlatformMSM5232() { +} diff --git a/src/engine/platform/msm5232.h b/src/engine/platform/msm5232.h new file mode 100644 index 00000000..34412b9a --- /dev/null +++ b/src/engine/platform/msm5232.h @@ -0,0 +1,106 @@ +/** + * 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 _MSM5232_H +#define _MSM5232_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "sound/oki/msm5232.h" + +class DivPlatformMSM5232: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2, note; + int ins; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise; + signed char vol, outVol; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + noise(false), + vol(127), + outVol(127) {} + }; + Channel chan[8]; + DivDispatchOscBuffer* oscBuf[8]; + int partVolume[8]; + int initPartVolume[8]; + double capacitance[8]; + bool isMuted[8]; + bool updateGroup[2]; + bool updateGroupAR[2]; + bool updateGroupDR[2]; + bool groupEnv[2]; + unsigned char groupControl[2]; + unsigned char groupAR[2]; + unsigned char groupDR[2]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + + int cycles, curChan, delay, detune; + short temp[16]; + msm5232_device* msm; + unsigned char regPool[128]; + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void setFlags(const DivConfig& flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + ~DivPlatformMSM5232(); +}; + +#endif diff --git a/src/engine/platform/sound/oki/msm5232.cpp b/src/engine/platform/sound/oki/msm5232.cpp new file mode 100644 index 00000000..59c001ae --- /dev/null +++ b/src/engine/platform/sound/oki/msm5232.cpp @@ -0,0 +1,735 @@ +// license:GPL-2.0+ +// copyright-holders:Jarek Burczynski, Hiromitsu Shioya +// additional modifications for Furnace by tildearrow +#include "msm5232.h" +#include +#include + +#define CLOCK_RATE_DIVIDER 16 + +/* + OKI MSM5232RS + 8 channel tone generator +*/ + +msm5232_device::msm5232_device(uint32_t clock) + : m_noise_cnt(0), m_noise_step(0), m_noise_rng(0), m_noise_clocks(0), m_UpdateStep(0), m_control1(0), m_control2(0), m_gate(0), m_chip_clock(0), m_rate(0), m_clock(clock) + , m_gate_handler_cb(NULL) +{ +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void msm5232_device::device_start() +{ + int rate = m_clock/CLOCK_RATE_DIVIDER; + + init(m_clock, rate); +} + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void msm5232_device::device_reset() +{ + for (int i=0; i<8; i++) + { + write(i, 0x80); + write(i, 0x00); + } + m_noise_cnt = 0; + m_noise_rng = 1; + m_noise_clocks = 0; + + m_control1 = 0; + m_EN_out16[0] = 0; + m_EN_out8[0] = 0; + m_EN_out4[0] = 0; + m_EN_out2[0] = 0; + + m_control2 = 0; + m_EN_out16[1] = 0; + m_EN_out8[1] = 0; + m_EN_out4[1] = 0; + m_EN_out2[1] = 0; + + gate_update(); +} + +//------------------------------------------------- +// device_stop - device-specific stop +//------------------------------------------------- + +void msm5232_device::device_stop() +{ +} + +void msm5232_device::set_capacitors(double cap1, double cap2, double cap3, double cap4, double cap5, double cap6, double cap7, double cap8) +{ + m_external_capacity[0] = cap1; + m_external_capacity[1] = cap2; + m_external_capacity[2] = cap3; + m_external_capacity[3] = cap4; + m_external_capacity[4] = cap5; + m_external_capacity[5] = cap6; + m_external_capacity[6] = cap7; + m_external_capacity[7] = cap8; +} + +void msm5232_device::set_vol_input(double v1, double v2, double v3, double v4, double v5, double v6, double v7, double v8) +{ + m_external_input[0] = v1; + m_external_input[1] = v2; + m_external_input[2] = v3; + m_external_input[3] = v4; + m_external_input[4] = v5; + m_external_input[5] = v6; + m_external_input[6] = v7; + m_external_input[7] = v8; +} + +/* Default chip clock is 2119040 Hz */ +/* At this clock chip generates exactly 440.0 Hz signal on 8' output when pitch data=0x21 */ + + +/* ROM table to convert from pitch data into data for programmable counter and binary counter */ +/* Chip has 88x12bits ROM (addressing (in hex) from 0x00 to 0x57) */ +#define ROM(counter,bindiv) (counter|(bindiv<<9)) + +static const uint16_t MSM5232_ROM[88]={ +/* higher values are Programmable Counter data (9 bits) */ +/* lesser values are Binary Counter shift data (3 bits) */ + +/* 0 */ ROM (506, 7), + +/* 1 */ ROM (478, 7),/* 2 */ ROM (451, 7),/* 3 */ ROM (426, 7),/* 4 */ ROM (402, 7), +/* 5 */ ROM (379, 7),/* 6 */ ROM (358, 7),/* 7 */ ROM (338, 7),/* 8 */ ROM (319, 7), +/* 9 */ ROM (301, 7),/* A */ ROM (284, 7),/* B */ ROM (268, 7),/* C */ ROM (253, 7), + +/* D */ ROM (478, 6),/* E */ ROM (451, 6),/* F */ ROM (426, 6),/*10 */ ROM (402, 6), +/*11 */ ROM (379, 6),/*12 */ ROM (358, 6),/*13 */ ROM (338, 6),/*14 */ ROM (319, 6), +/*15 */ ROM (301, 6),/*16 */ ROM (284, 6),/*17 */ ROM (268, 6),/*18 */ ROM (253, 6), + +/*19 */ ROM (478, 5),/*1A */ ROM (451, 5),/*1B */ ROM (426, 5),/*1C */ ROM (402, 5), +/*1D */ ROM (379, 5),/*1E */ ROM (358, 5),/*1F */ ROM (338, 5),/*20 */ ROM (319, 5), +/*21 */ ROM (301, 5),/*22 */ ROM (284, 5),/*23 */ ROM (268, 5),/*24 */ ROM (253, 5), + +/*25 */ ROM (478, 4),/*26 */ ROM (451, 4),/*27 */ ROM (426, 4),/*28 */ ROM (402, 4), +/*29 */ ROM (379, 4),/*2A */ ROM (358, 4),/*2B */ ROM (338, 4),/*2C */ ROM (319, 4), +/*2D */ ROM (301, 4),/*2E */ ROM (284, 4),/*2F */ ROM (268, 4),/*30 */ ROM (253, 4), + +/*31 */ ROM (478, 3),/*32 */ ROM (451, 3),/*33 */ ROM (426, 3),/*34 */ ROM (402, 3), +/*35 */ ROM (379, 3),/*36 */ ROM (358, 3),/*37 */ ROM (338, 3),/*38 */ ROM (319, 3), +/*39 */ ROM (301, 3),/*3A */ ROM (284, 3),/*3B */ ROM (268, 3),/*3C */ ROM (253, 3), + +/*3D */ ROM (478, 2),/*3E */ ROM (451, 2),/*3F */ ROM (426, 2),/*40 */ ROM (402, 2), +/*41 */ ROM (379, 2),/*42 */ ROM (358, 2),/*43 */ ROM (338, 2),/*44 */ ROM (319, 2), +/*45 */ ROM (301, 2),/*46 */ ROM (284, 2),/*47 */ ROM (268, 2),/*48 */ ROM (253, 2), + +/*49 */ ROM (478, 1),/*4A */ ROM (451, 1),/*4B */ ROM (426, 1),/*4C */ ROM (402, 1), +/*4D */ ROM (379, 1),/*4E */ ROM (358, 1),/*4F */ ROM (338, 1),/*50 */ ROM (319, 1), +/*51 */ ROM (301, 1),/*52 */ ROM (284, 1),/*53 */ ROM (268, 1),/*54 */ ROM (253, 1), + +/*55 */ ROM (253, 1),/*56 */ ROM (253, 1), + +/*57 */ ROM (13, 7) +}; +#undef ROM + + +#define STEP_SH (16) /* step calculations accuracy */ + +/* + * resistance values are guesswork, default capacity is mentioned in the datasheets + * + * charges external capacitor (default is 0.39uF) via R51 + * in approx. 5*1400 * 0.39e-6 + * + * external capacitor is discharged through R52 + * in approx. 5*28750 * 0.39e-6 + */ + + +#define R51 1400 /* charge resistance */ +#define R52 28750 /* discharge resistance */ + +#if 0 +/* + C24 = external capacity + + osd_printf_debug("Time constant T=R*C =%f sec.\n",R51*C24); + osd_printf_debug("Cap fully charged after 5T=%f sec (sample=%f). Level=%f\n",(R51*C24)*5,(R51*C24)*5*sample_rate , VMAX*0.99326 ); + osd_printf_debug("Cap charged after 5T=%f sec (sample=%f). Level=%20.16f\n",(R51*C24)*5,(R51*C24)*5*sample_rate , + VMAX*(1.0-pow(2.718,-0.0748/(R51*C24))) ); +*/ +#endif + + + + +void msm5232_device::init_tables() +{ + int i; + double scale; + + /* sample rate = chip clock !!! But : */ + /* highest possible frequency is chipclock/13/16 (pitch data=0x57) */ + /* at 2MHz : 2000000/13/16 = 9615 Hz */ + + i = ((double)(1< 0x0d) + return; + + if (offset < 0x08) /* pitch */ + { + int ch = offset&7; + + m_voi[ch].GF = ((data&0x80)>>7); + if (ch == 7) + gate_update(); + + if(data&0x80) + { + if(data >= 0xd8) + { + /*if ((data&0x7f) != 0x5f) logerror("MSM5232: WRONG PITCH CODE = %2x\n",data&0x7f);*/ + m_voi[ch].mode = 1; /* noise mode */ + m_voi[ch].eg_sect = 0; /* Key On */ + } + else + { + if ( m_voi[ch].pitch != (data&0x7f) ) + { + int n; + uint16_t pg; + + m_voi[ch].pitch = data&0x7f; + + pg = MSM5232_ROM[ data&0x7f ]; + + m_voi[ch].TG_count_period = (pg & 0x1ff) * m_UpdateStep / 2; + + n = (pg>>9) & 7; /* n = bit number for 16' output */ + m_voi[ch].TG_out16 = 1<0)? n-1: 0; + m_voi[ch].TG_out8 = 1<0)? n-1: 0; + m_voi[ch].TG_out4 = 1<0)? n-1: 0; + m_voi[ch].TG_out2 = 1< go to release */ + else /* arm = 1 */ + m_voi[ch].eg_sect = 1; /* Key Off -> go to decay */ + } + } + else + { + int i; + switch(offset) + { + case 0x08: /* group1 attack */ + for (i=0; i<4; i++) + m_voi[i].ar_rate = m_ar_tbl[data&0x7] * m_external_capacity[i]; + break; + + case 0x09: /* group2 attack */ + for (i=0; i<4; i++) + m_voi[i+4].ar_rate = m_ar_tbl[data&0x7] * m_external_capacity[i+4]; + break; + + case 0x0a: /* group1 decay */ + for (i=0; i<4; i++) { + m_voi[i].dr_rate = m_dr_tbl[data&0xf] * m_external_capacity[i]; + } + break; + + case 0x0b: /* group2 decay */ + for (i=0; i<4; i++) + m_voi[i+4].dr_rate = m_dr_tbl[data&0xf] * m_external_capacity[i+4]; + break; + + case 0x0c: /* group1 control */ + + /*if (m_control1 != data) + logerror("msm5232: control1 ctrl=%x OE=%x\n", data&0xf0, data&0x0f);*/ + + /*if (data & 0x10) + popmessage("msm5232: control1 ctrl=%2x\n", data);*/ + + m_control1 = data; + + for (i=0; i<4; i++) + { + if ( (data&0x10) && (m_voi[i].eg_sect == 1) ) + m_voi[i].eg_sect = 0; + m_voi[i].eg_arm = data&0x10; + m_voi[i].eg_ext = !(data&0x20); + } + + m_EN_out16[0] = (data&1) ? ~0:0; + m_EN_out8[0] = (data&2) ? ~0:0; + m_EN_out4[0] = (data&4) ? ~0:0; + m_EN_out2[0] = (data&8) ? ~0:0; + + break; + + case 0x0d: /* group2 control */ + + /*if (m_control2 != data) + logerror("msm5232: control2 ctrl=%x OE=%x\n", data&0xf0, data&0x0f);*/ + + /*if (data & 0x10) + popmessage("msm5232: control2 ctrl=%2x\n", data);*/ + + m_control2 = data; + gate_update(); + + for (i=0; i<4; i++) + { + if ( (data&0x10) && (m_voi[i+4].eg_sect == 1) ) + m_voi[i+4].eg_sect = 0; + m_voi[i+4].eg_arm = data&0x10; + m_voi[i+4].eg_ext = !(data&0x20); + } + + m_EN_out16[1] = (data&1) ? ~0:0; + m_EN_out8[1] = (data&2) ? ~0:0; + m_EN_out4[1] = (data&4) ? ~0:0; + m_EN_out2[1] = (data&8) ? ~0:0; + + break; + } + } +} + + + +#define VMIN 0 +#define VMAX 32768 + + +void msm5232_device::EG_voices_advance() +{ + VOICE *voi = &m_voi[0]; + int samplerate = m_rate; + int i; + + i = 8; + do + { + if (voi->eg_ext) { + voi->egvol=m_external_input[8-i]*2048.0; + } else { + switch(voi->eg_sect) + { + case 0: /* attack */ + + /* capacitor charge */ + if (voi->eg < VMAX) + { + voi->counter -= (int)((VMAX - voi->eg) / voi->ar_rate); + if ( voi->counter <= 0 ) + { + int n = -voi->counter / samplerate + 1; + voi->counter += n * samplerate; + if ( (voi->eg += n) > VMAX ) + voi->eg = VMAX; + } + } + + /* when ARM=0, EG switches to decay as soon as cap is charged to VT (EG inversion voltage; about 80% of MAX) */ + if (!voi->eg_arm) + { + if(voi->eg >= VMAX * 80/100 ) + { + voi->eg_sect = 1; + } + } + else + /* ARM=1 */ + { + /* when ARM=1, EG stays at maximum until key off */ + } + + voi->egvol = voi->eg / 16; /*32768/16 = 2048 max*/ + + break; + + case 1: /* decay */ + + /* capacitor discharge */ + if (voi->eg > VMIN) + { + voi->counter -= (int)((voi->eg - VMIN) / voi->dr_rate); + if ( voi->counter <= 0 ) + { + int n = -voi->counter / samplerate + 1; + voi->counter += n * samplerate; + if ( (voi->eg -= n) < VMIN ) + voi->eg = VMIN; + } + } + else /* voi->eg <= VMIN */ + { + voi->eg_sect =-1; + } + + voi->egvol = voi->eg / 16; /*32768/16 = 2048 max*/ + + break; + + case 2: /* release */ + + /* capacitor discharge */ + if (voi->eg > VMIN) + { + voi->counter -= (int)((voi->eg - VMIN) / voi->rr_rate); + if ( voi->counter <= 0 ) + { + int n = -voi->counter / samplerate + 1; + voi->counter += n * samplerate; + if ( (voi->eg -= n) < VMIN ) + voi->eg = VMIN; + } + } + else /* voi->eg <= VMIN */ + { + voi->eg_sect =-1; + } + + voi->egvol = voi->eg / 16; /*32768/16 = 2048 max*/ + + break; + + default: + break; + } + } + + voi++; + i--; + } while (i>0); + +} + +static int o2,o4,o8,o16,solo8,solo16; + +void msm5232_device::TG_group_advance(int groupidx) +{ + VOICE *voi = &m_voi[groupidx*4]; + int i; + + o2 = o4 = o8 = o16 = solo8 = solo16 = 0; + + i=4; + do + { + int out2, out4, out8, out16; + + out2 = out4 = out8 = out16 = 0; + + if (voi->mode==0) /* generate square tone */ + { + int left = 1<TG_cnt&voi->TG_out16) out16+=voi->TG_count; + if (voi->TG_cnt&voi->TG_out8) out8 +=voi->TG_count; + if (voi->TG_cnt&voi->TG_out4) out4 +=voi->TG_count; + if (voi->TG_cnt&voi->TG_out2) out2 +=voi->TG_count; + + voi->TG_count -= nextevent; + + while (voi->TG_count <= 0) + { + voi->TG_count += voi->TG_count_period; + voi->TG_cnt++; + if (voi->TG_cnt&voi->TG_out16) out16+=voi->TG_count_period; + if (voi->TG_cnt&voi->TG_out8 ) out8 +=voi->TG_count_period; + if (voi->TG_cnt&voi->TG_out4 ) out4 +=voi->TG_count_period; + if (voi->TG_cnt&voi->TG_out2 ) out2 +=voi->TG_count_period; + + if (voi->TG_count > 0) + break; + + voi->TG_count += voi->TG_count_period; + voi->TG_cnt++; + if (voi->TG_cnt&voi->TG_out16) out16+=voi->TG_count_period; + if (voi->TG_cnt&voi->TG_out8 ) out8 +=voi->TG_count_period; + if (voi->TG_cnt&voi->TG_out4 ) out4 +=voi->TG_count_period; + if (voi->TG_cnt&voi->TG_out2 ) out2 +=voi->TG_count_period; + } + if (voi->TG_cnt&voi->TG_out16) out16-=voi->TG_count; + if (voi->TG_cnt&voi->TG_out8 ) out8 -=voi->TG_count; + if (voi->TG_cnt&voi->TG_out4 ) out4 -=voi->TG_count; + if (voi->TG_cnt&voi->TG_out2 ) out2 -=voi->TG_count; + + left -=nextevent; + + }while (left>0); + } + else /* generate noise */ + { + if (m_noise_clocks&8) out16+=(1<mute) { + o16 += ( (out16-(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; + o8 += ( (out8 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; + o4 += ( (out4 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; + o2 += ( (out2 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; + + if (i == 1 && groupidx == 1) + { + solo16 += ( (out16-(1<<(STEP_SH-1))) << 11) >> STEP_SH; + solo8 += ( (out8 -(1<<(STEP_SH-1))) << 11) >> STEP_SH; + } + } + + voi++; + i--; + }while (i>0); + + /* cut off disabled output lines */ + o16 &= m_EN_out16[groupidx]; + o8 &= m_EN_out8 [groupidx]; + o4 &= m_EN_out4 [groupidx]; + o2 &= m_EN_out2 [groupidx]; +} + + +/* macro saves feet data to mono file */ +#ifdef SAVE_SEPARATE_CHANNELS + #define SAVE_SINGLE_CHANNEL(j,val) \ + { signed int pom= val; \ + if (pom > 32767) pom = 32767; else if (pom < -32768) pom = -32768; \ + fputc((unsigned short)pom&0xff,sample[j]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[j]); } +#else + #define SAVE_SINGLE_CHANNEL(j,val) +#endif + +/* first macro saves all 8 feet outputs to mixed (mono) file */ +/* second macro saves one group into left and the other in right channel */ +#if 1 /*MONO*/ + #ifdef SAVE_SAMPLE + #define SAVE_ALL_CHANNELS \ + { signed int pom = buf1[i] + buf2[i]; \ + fputc((unsigned short)pom&0xff,sample[8]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[8]); \ + } + #else + #define SAVE_ALL_CHANNELS + #endif +#else /*STEREO*/ + #ifdef SAVE_SAMPLE + #define SAVE_ALL_CHANNELS \ + { signed int pom = buf1[i]; \ + fputc((unsigned short)pom&0xff,sample[8]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[8]); \ + pom = buf2[i]; \ + fputc((unsigned short)pom&0xff,sample[8]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[8]); \ + } + #else + #define SAVE_ALL_CHANNELS + #endif +#endif + + +/* MAME Interface */ +void msm5232_device::device_post_load() +{ + init_tables(); +} + +void msm5232_device::set_clock(int clock) +{ + if (m_chip_clock != clock) + { + m_chip_clock = clock; + m_rate = clock/CLOCK_RATE_DIVIDER; + init_tables(); + } +} + + +//------------------------------------------------- +// sound_stream_update - handle a stream update +//------------------------------------------------- + +void msm5232_device::sound_stream_update(short* outputs) +{ + auto &buf1 = outputs[0]; + auto &buf2 = outputs[1]; + auto &buf3 = outputs[2]; + auto &buf4 = outputs[3]; + auto &buf5 = outputs[4]; + auto &buf6 = outputs[5]; + auto &buf7 = outputs[6]; + auto &buf8 = outputs[7]; + auto &bufsolo1 = outputs[8]; + auto &bufsolo2 = outputs[9]; + auto &bufnoise = outputs[10]; + + /* calculate all voices' envelopes */ + EG_voices_advance(); + + TG_group_advance(0); /* calculate tones group 1 */ + buf1=o2; + buf2=o4; + buf3=o8; + buf4=o16; + + TG_group_advance(1); /* calculate tones group 2 */ + buf5=o2; + buf6=o4; + buf7=o8; + buf8=o16; + + bufsolo1=solo8; + bufsolo2=solo16; + + /* update noise generator */ + { + int cnt = (m_noise_cnt+=m_noise_step) >> STEP_SH; + m_noise_cnt &= ((1< 0) + { + int tmp = m_noise_rng & (1<<16); /* store current level */ + + if (m_noise_rng&1) + m_noise_rng ^= 0x24000; + m_noise_rng>>=1; + + if ( (m_noise_rng & (1<<16)) != tmp ) /* level change detect */ + m_noise_clocks++; + + cnt--; + } + } + + bufnoise=(m_noise_rng & (1<<16)) ? 32767 : 0; +} diff --git a/src/engine/platform/sound/oki/msm5232.h b/src/engine/platform/sound/oki/msm5232.h new file mode 100644 index 00000000..ecf8d1e8 --- /dev/null +++ b/src/engine/platform/sound/oki/msm5232.h @@ -0,0 +1,106 @@ +// license:GPL-2.0+ +// copyright-holders:Jarek Burczynski, Hiromitsu Shioya +// additional modifications for Furnace by tildearrow +#ifndef MAME_SOUND_MSM5232_H +#define MAME_SOUND_MSM5232_H + +#pragma once + +#include +#include + +class msm5232_device +{ +public: + msm5232_device(uint32_t clock); + + void set_capacitors(double cap1, double cap2, double cap3, double cap4, double cap5, double cap6, double cap7, double cap8); + void set_vol_input(double v1, double v2, double v3, double v4, double v5, double v6, double v7, double v8); + //auto gate() { return m_gate_handler_cb.bind(); } + + void write(unsigned int offset, uint8_t data); + void set_clock(int clock); + void mute(int voice, bool mute); + + // device-level overrides + void device_start(); + void device_stop(); + void device_reset(); + void device_post_load(); + + // sound stream update overrides + void sound_stream_update(short* outputs); + + int get_rate(); + +private: + struct VOICE { + uint8_t mode; + bool mute; + + int TG_count_period; + int TG_count; + + uint8_t TG_cnt; /* 7 bits binary counter (frequency output) */ + uint8_t TG_out16; /* bit number (of TG_cnt) for 16' output */ + uint8_t TG_out8; /* bit number (of TG_cnt) for 8' output */ + uint8_t TG_out4; /* bit number (of TG_cnt) for 4' output */ + uint8_t TG_out2; /* bit number (of TG_cnt) for 2' output */ + + int egvol; + int eg_sect; + int counter; + int eg; + + uint8_t eg_arm; /* attack/release mode */ + uint8_t eg_ext; /* inhibit envelope generator */ + + double ar_rate; + double dr_rate; + double rr_rate; + + int pitch; /* current pitch data */ + + int GF; + }; + + VOICE m_voi[8]; + + uint32_t m_EN_out16[2]; /* enable 16' output masks for both groups (0-disabled ; ~0 -enabled) */ + uint32_t m_EN_out8[2]; /* enable 8' output masks */ + uint32_t m_EN_out4[2]; /* enable 4' output masks */ + uint32_t m_EN_out2[2]; /* enable 2' output masks */ + + int m_noise_cnt; + int m_noise_step; + int m_noise_rng; + int m_noise_clocks; /* number of the noise_rng (output) level changes */ + + unsigned int m_UpdateStep; + + /* rate tables */ + double m_ar_tbl[8]; + double m_dr_tbl[16]; + + uint8_t m_control1; + uint8_t m_control2; + + int m_gate; /* current state of the GATE output */ + + int m_chip_clock; /* chip clock in Hz */ + int m_rate; /* sample rate in Hz */ + uint32_t m_clock; + + double m_external_capacity[8]; /* in Farads, eg 0.39e-6 = 0.36 uF (microFarads) */ + double m_external_input[8]; + std::function m_gate_handler_cb;/* callback called when the GATE output pin changes state */ + + void init_tables(); + void init_voice(int i); + void gate_update(); + void init(int clock, int rate); + void EG_voices_advance(); + void TG_group_advance(int groupidx); +}; + +#endif // MAME_SOUND_MSM5232_H diff --git a/src/engine/song.h b/src/engine/song.h index 4217a325..bb69598a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -114,7 +114,7 @@ enum DivSystem { DIV_SYSTEM_NAMCO_CUS30, DIV_SYSTEM_YM2612_FRAC, DIV_SYSTEM_YM2612_FRAC_EXT, - DIV_SYSTEM_RESERVED_8, + DIV_SYSTEM_MSM5232, DIV_SYSTEM_T6W28, DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_DUMMY, diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 0e2f1e11..0344def0 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1646,14 +1646,13 @@ void DivEngine::registerSystems() { namcoEffectHandlerMap ); - // replace with an 8-channel chip in a future - sysDefs[DIV_SYSTEM_RESERVED_8]=new DivSysDef( - "Reserved", NULL, 0xbc, 0, 8, false, true, 0, false, 0, - "this was YM2612_FRAC, but due to changes this ID is reserved.", + sysDefs[DIV_SYSTEM_MSM5232]=new DivSysDef( + "OKI MSM5232", NULL, 0xbc, 0, 8, false, true, 0, false, 0, + "a square wave additive synthesis chip made by OKI. used in some arcade machines and instruments.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, - {DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE}, - {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD} + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, + {DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232, DIV_INS_MSM5232} ); sysDefs[DIV_SYSTEM_YM2612_FRAC]=new DivSysDef( diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 2a1c7a66..defb68d0 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -378,6 +378,10 @@ void FurnaceGUI::drawInsList(bool asChild) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_RF5C68]); name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); break; + case DIV_INS_MSM5232: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM5232]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.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); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 41771dd4..31d0ff48 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -822,6 +822,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_CUT: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; SAMPLE_OP_BEGIN; if (end-start<1) break; @@ -866,6 +867,7 @@ void FurnaceGUI::doAction(int what) { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (sampleClipboard==NULL || sampleClipboardLen<1) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; if (pos>=(int)sample->samples) pos=sample->samples-1; @@ -896,6 +898,7 @@ void FurnaceGUI::doAction(int what) { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (sampleClipboard==NULL || sampleClipboardLen<1) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart; if (pos>=(int)sample->samples) pos=sample->samples-1; @@ -926,6 +929,7 @@ void FurnaceGUI::doAction(int what) { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (sampleClipboard==NULL || sampleClipboardLen<1) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart; if (pos>=(int)sample->samples) pos=sample->samples-1; @@ -981,6 +985,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_NORMALIZE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -1028,6 +1033,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_FADE_IN: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -1058,6 +1064,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_FADE_OUT: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -1092,6 +1099,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_SILENCE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -1116,6 +1124,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_DELETE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -1133,6 +1142,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_TRIM: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -1150,6 +1160,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_REVERSE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -1182,6 +1193,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_INVERT: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -1208,6 +1220,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_SIGN: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) break; sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4c7850ad..63a6fbff 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2134,18 +2134,20 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { if (x1>=(int)sampleDragLen) x1=sampleDragLen-1; double y=0.5-double(dragY-sampleDragStart.y)/sampleDragAreaSize.y; if (sampleDragMode) { // draw - if (sampleDrag16) { - int val=y*65536; - if (val<-32768) val=-32768; - if (val>32767) val=32767; - for (int i=x; i<=x1; i++) ((short*)sampleDragTarget)[i]=val; - } else { - int val=y*256; - if (val<-128) val=-128; - if (val>127) val=127; - for (int i=x; i<=x1; i++) ((signed char*)sampleDragTarget)[i]=val; + if (sampleDragTarget) { + if (sampleDrag16) { + int val=y*65536; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + for (int i=x; i<=x1; i++) ((short*)sampleDragTarget)[i]=val; + } else { + int val=y*256; + if (val<-128) val=-128; + if (val>127) val=127; + for (int i=x; i<=x1; i++) ((signed char*)sampleDragTarget)[i]=val; + } + updateSampleTex=true; } - updateSampleTex=true; } else { // select if (sampleSelStart<0) { sampleSelStart=x; @@ -2836,7 +2838,7 @@ void FurnaceGUI::pointDown(int x, int y, int button) { } void FurnaceGUI::pointUp(int x, int y, int button) { - if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) { + if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode && sampleDragTarget)) { MARK_MODIFIED; } if (macroDragActive && macroDragLineMode && !macroDragMouseMoved) { @@ -5395,6 +5397,7 @@ FurnaceGUI::FurnaceGUI(): editOptsVisible(false), latchNibble(false), nonLatchNibble(false), + keepLoopAlive(false), curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), curWindowLast(GUI_WINDOW_NOTHING), diff --git a/src/gui/gui.h b/src/gui/gui.h index 6bb0d38f..fa00d76c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -170,6 +170,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_QSOUND, GUI_COLOR_INSTR_YMZ280B, GUI_COLOR_INSTR_RF5C68, + GUI_COLOR_INSTR_MSM5232, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, @@ -217,6 +218,15 @@ enum FurnaceGUIColors { GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_SAMPLE_BG, + GUI_COLOR_SAMPLE_FG, + GUI_COLOR_SAMPLE_LOOP, + GUI_COLOR_SAMPLE_CENTER, + GUI_COLOR_SAMPLE_GRID, + GUI_COLOR_SAMPLE_SEL, + GUI_COLOR_SAMPLE_SEL_POINT, + GUI_COLOR_SAMPLE_NEEDLE, + GUI_COLOR_PAT_MANAGER_NULL, GUI_COLOR_PAT_MANAGER_USED, GUI_COLOR_PAT_MANAGER_OVERUSED, @@ -1357,6 +1367,7 @@ class FurnaceGUI { SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd; bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; + bool keepLoopAlive; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index f761a2af..dd659e8c 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -113,8 +113,8 @@ const char* insTypes[DIV_INS_MAX+1]={ "Sound Unit", "Namco WSG", "OPL (drums)", - "FM (OPM)", // 33 - "NES", // 34 + "FM (OPM)", + "NES", "MSM6258", "MSM6295", "ADPCM-A", @@ -123,6 +123,7 @@ const char* insTypes[DIV_INS_MAX+1]={ "QSound", "YMZ280B", "RF5C68", + "MSM5232", NULL }; @@ -799,6 +800,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_QSOUND,"",ImVec4(1.0f,0.8f,0.3f,1.0f)), D(GUI_COLOR_INSTR_YMZ280B,"",ImVec4(0.4f,0.5f,1.0f,1.0f)), D(GUI_COLOR_INSTR_RF5C68,"",ImVec4(1.0f,0.3f,0.3f,1.0f)), + D(GUI_COLOR_INSTR_MSM5232,"",ImVec4(0.5f,0.9f,1.0f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)), @@ -846,6 +848,15 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"",ImVec4(0.0f,1.0f,0.5f,1.0f)), D(GUI_COLOR_PATTERN_EFFECT_MISC,"",ImVec4(0.3f,0.3f,1.0f,1.0f)), + D(GUI_COLOR_SAMPLE_BG,"",ImVec4(0.04f,0.13f,0.2f,1.0f)), + D(GUI_COLOR_SAMPLE_FG,"",ImVec4(0.7f,0.7f,0.7f,1.0f)), + D(GUI_COLOR_SAMPLE_LOOP,"",ImVec4(0.1f,0.22f,0.35f,1.0f)), + D(GUI_COLOR_SAMPLE_CENTER,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), + D(GUI_COLOR_SAMPLE_GRID,"",ImVec4(0.1f,0.1f,0.15f,1.0f)), + D(GUI_COLOR_SAMPLE_SEL,"",ImVec4(0.26f,0.59f,0.98f,0.25f)), + D(GUI_COLOR_SAMPLE_SEL_POINT,"",ImVec4(0.06f,0.53f,0.98f,0.5f)), + D(GUI_COLOR_SAMPLE_NEEDLE,"",ImVec4(1.0f,0.8f,0.0f,1.0f)), + D(GUI_COLOR_PAT_MANAGER_NULL,"",ImVec4(0.15f,0.15f,0.15f,1.0f)), D(GUI_COLOR_PAT_MANAGER_USED,"",ImVec4(0.15f,1.0f,0.15f,1.0f)), D(GUI_COLOR_PAT_MANAGER_OVERUSED,"",ImVec4(1.0f,1.0f,0.15f,1.0f)), @@ -944,6 +955,7 @@ const int availableSystems[]={ DIV_SYSTEM_MSM6295, DIV_SYSTEM_RF5C68, DIV_SYSTEM_SNES, + DIV_SYSTEM_MSM5232, DIV_SYSTEM_PCM_DAC, 0 // don't remove this last one! }; @@ -988,6 +1000,7 @@ const int chipsSquare[]={ DIV_SYSTEM_PCSPKR, DIV_SYSTEM_SAA1099, DIV_SYSTEM_VIC20, + DIV_SYSTEM_MSM5232, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 15421be1..96c0a43f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -243,6 +243,10 @@ const char* mikeyFeedbackBits[11] = { "0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL }; +const char* msm5232ControlBits[7]={ + "2'", "4'", "8'", "16'", "sustain", NULL +}; + const char* x1_010EnvBits[8]={ "enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL }; @@ -4513,7 +4517,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_SEGAPCM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || ins->type==DIV_INS_OPZ || - ins->type==DIV_INS_OPM || ins->type==DIV_INS_SNES) { + ins->type==DIV_INS_OPM || ins->type==DIV_INS_SNES || ins->type==DIV_INS_MSM5232) { volMax=127; } if (ins->type==DIV_INS_GB) { @@ -4576,6 +4580,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty/Int"; dutyMax=ins->amiga.useSample?0:10; } + if (ins->type==DIV_INS_MSM5232) { + dutyLabel="Group Ctrl"; + dutyMax=5; + } if (ins->type==DIV_INS_BEEPER) { dutyLabel="Pulse Width"; dutyMax=255; @@ -4684,6 +4692,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_ADPCMB) waveMax=0; if (ins->type==DIV_INS_QSOUND) waveMax=0; if (ins->type==DIV_INS_YMZ280B) waveMax=0; + if (ins->type==DIV_INS_MSM5232) waveMax=0; if (ins->type==DIV_INS_MSM6258) waveMax=0; if (ins->type==DIV_INS_MSM6295) waveMax=0; if (ins->type==DIV_INS_SEGAPCM) waveMax=0; @@ -4751,6 +4760,10 @@ void FurnaceGUI::drawInsEdit() { ex1Max=5; ex2Max=5; } + if (ins->type==DIV_INS_MSM5232) { + ex1Max=5; + ex2Max=11; + } int panMin=0; int panMax=0; @@ -4815,6 +4828,8 @@ void FurnaceGUI::drawInsEdit() { if (dutyMax>0) { if (ins->type==DIV_INS_MIKEY) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits)); + } else if (ins->type==DIV_INS_MSM5232) { + macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,msm5232ControlBits)); } else if (ins->type==DIV_INS_ES5506) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,dutyMin,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,¯oHoverES5506FilterMode)); } else { @@ -4845,7 +4860,9 @@ void FurnaceGUI::drawInsEdit() { } } } - macroList.push_back(FurnaceGUIMacroDesc("Pitch",&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); + if (ins->type!=DIV_INS_MSM5232) { + macroList.push_back(FurnaceGUIMacroDesc("Pitch",&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); + } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM || ins->type==DIV_INS_STD || @@ -4897,6 +4914,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Echo Feedback",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SNES) { macroList.push_back(FurnaceGUIMacroDesc("Special",&ins->std.ex1Macro,0,ex1Max,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,snesModeBits)); + } else if (ins->type==DIV_INS_MSM5232) { + macroList.push_back(FurnaceGUIMacroDesc("Group Attack",&ins->std.ex1Macro,0,ex1Max,96,uiColors[GUI_COLOR_MACRO_OTHER])); } else { macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } @@ -4916,6 +4935,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Echo Length",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SNES) { macroList.push_back(FurnaceGUIMacroDesc("Gain Mode",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,snesGainModes)); + } else if (ins->type==DIV_INS_MSM5232) { + macroList.push_back(FurnaceGUIMacroDesc("Group Decay",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else { macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits)); } @@ -4958,6 +4979,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_SNES) { macroList.push_back(FurnaceGUIMacroDesc("Gain Rate",&ins->std.ex3Macro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); } + if (ins->type==DIV_INS_MSM5232) { + macroList.push_back(FurnaceGUIMacroDesc("Noise",&ins->std.ex3Macro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + } drawMacros(macroList); ImGui::EndTabItem(); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index ca9fd9b0..a5acecb4 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -306,6 +306,12 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "OKI MSM5232", { + DIV_SYSTEM_MSM5232, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Sample","chips/systems which use PCM or ADPCM samples for sound synthesis."); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index d1909626..f04060e0 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -121,10 +121,14 @@ void FurnaceGUI::drawSampleEdit() { } updateSampleTex=true; } + if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) { + ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!"); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Length: %d",sample->samples); - if (doLoop) { + if (doLoop || keepLoopAlive) { + keepLoopAlive=false; ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Loop Mode"); @@ -156,6 +160,12 @@ void FurnaceGUI::drawSampleEdit() { } updateSampleTex=true; } + if (ImGui::IsItemActive()) { + keepLoopAlive=true; + } + if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) { + ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!"); + } ImGui::TableNextColumn(); ImGui::Text("Loop End"); ImGui::SameLine(); @@ -169,6 +179,12 @@ void FurnaceGUI::drawSampleEdit() { } updateSampleTex=true; } + if (ImGui::IsItemActive()) { + keepLoopAlive=true; + } + if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) { + ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!"); + } } ImGui::EndTable(); } @@ -181,8 +197,6 @@ void FurnaceGUI::drawSampleEdit() { */ ImGui::Separator(); - ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; @@ -200,6 +214,7 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } + ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); @@ -567,6 +582,7 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::EndPopup(); } + ImGui::EndDisabled(); ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); @@ -681,10 +697,14 @@ void FurnaceGUI::drawSampleEdit() { } updateSampleTex=true; } + if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) { + ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!"); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Length: %d",sample->samples); - if (doLoop) { + if (doLoop || keepLoopAlive) { + keepLoopAlive=false; ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Loop Mode"); @@ -717,6 +737,12 @@ void FurnaceGUI::drawSampleEdit() { } updateSampleTex=true; } + if (ImGui::IsItemActive()) { + keepLoopAlive=true; + } + if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) { + ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!"); + } ImGui::TableNextColumn(); ImGui::Text("Loop End"); ImGui::SameLine(); @@ -730,6 +756,12 @@ void FurnaceGUI::drawSampleEdit() { } updateSampleTex=true; } + if (ImGui::IsItemActive()) { + keepLoopAlive=true; + } + if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) { + ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!"); + } } ImGui::EndTable(); } @@ -742,8 +774,6 @@ void FurnaceGUI::drawSampleEdit() { */ ImGui::Separator(); - ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); - pushToggleColors(!sampleDragMode); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; @@ -761,6 +791,7 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } + ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); ImGui::SameLine(); ImGui::Dummy(ImVec2(7.0*dpiScale,dpiScale)); ImGui::SameLine(); @@ -1135,6 +1166,8 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SetTooltip("Trim"); } + ImGui::EndDisabled(); + if (ImGui::Button(ICON_FA_PLAY "##PreviewSample")) { e->previewSample(curSample); } @@ -1236,10 +1269,10 @@ void FurnaceGUI::drawSampleEdit() { if (SDL_LockTexture(sampleTex,NULL,(void**)&data,&pitch)!=0) { logE("error while locking sample texture! %s",SDL_GetError()); } else { - ImU32 bgColor=ImGui::GetColorU32(ImGuiCol_FrameBg); - ImU32 bgColorLoop=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_FrameBgHovered,0.5)); - ImU32 lineColor=ImGui::GetColorU32(ImGuiCol_PlotLines); - ImU32 centerLineColor=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_PlotLines,0.25)); + ImU32 bgColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_BG]); + ImU32 bgColorLoop=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_LOOP]); + ImU32 lineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_FG]); + ImU32 centerLineColor=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_CENTER]); for (int i=0; isamples; } else { - if (sample->samples>0 && (sample->depth==DIV_SAMPLE_DEPTH_16BIT || sample->depth==DIV_SAMPLE_DEPTH_8BIT)) { + if (sample->samples>0) { sampleDragStart=rectMin; sampleDragAreaSize=rectSize; - sampleDrag16=(sample->depth==DIV_SAMPLE_DEPTH_16BIT); - sampleDragTarget=(sample->depth==DIV_SAMPLE_DEPTH_16BIT)?((void*)sample->data16):((void*)sample->data8); + switch (sample->depth) { + case DIV_SAMPLE_DEPTH_8BIT: + sampleDrag16=false; + sampleDragTarget=(void*)sample->data8; + break; + case DIV_SAMPLE_DEPTH_16BIT: + sampleDrag16=true; + sampleDragTarget=(void*)sample->data16; + break; + default: + sampleDrag16=true; + sampleDragTarget=NULL; + break; + } sampleDragLen=sample->samples; sampleDragActive=true; sampleSelStart=-1; @@ -1334,12 +1379,15 @@ void FurnaceGUI::drawSampleEdit() { } if (ImGui::BeginPopup("SRightClick",ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_SAMPLE_CUT))) { doAction(GUI_ACTION_SAMPLE_CUT); } + ImGui::EndDisabled(); if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_SAMPLE_COPY))) { doAction(GUI_ACTION_SAMPLE_COPY); } + ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT); if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_SAMPLE_PASTE))) { doAction(GUI_ACTION_SAMPLE_PASTE); } @@ -1349,6 +1397,7 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::MenuItem("paste (mix)",BIND_FOR(GUI_ACTION_SAMPLE_PASTE_MIX))) { doAction(GUI_ACTION_SAMPLE_PASTE_MIX); } + ImGui::EndDisabled(); if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_SAMPLE_SELECT_ALL))) { doAction(GUI_ACTION_SAMPLE_SELECT_ALL); } @@ -1374,7 +1423,11 @@ void FurnaceGUI::drawSampleEdit() { end^=start; start^=end; } - statusBar+=fmt::sprintf(" (%d-%d)",start,end); + if (start==end) { + statusBar+=fmt::sprintf(" (%d)",start); + } else { + statusBar+=fmt::sprintf(" (%d-%d: %d samples)",start,end,end-start); + } drawSelection=true; } } @@ -1426,6 +1479,43 @@ void FurnaceGUI::drawSampleEdit() { } } + if (e->isPreviewingSample()) { + statusBar+=fmt::sprintf(" | %.2fHz",e->getSamplePreviewRate()); + + int start=sampleSelStart; + int end=sampleSelEnd; + if (start>end) { + start^=end; + end^=start; + start^=end; + } + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImVec2 p1=rectMin; + p1.x+=(e->getSamplePreviewPos()-samplePos)/sampleZoom; + ImVec4 posColor=uiColors[GUI_COLOR_SAMPLE_NEEDLE]; + ImVec4 posTrail1=posColor; + ImVec4 posTrail2=posColor; + posTrail1.w*=0.5f; + posTrail2.w=0.0f; + float trailDistance=(e->getSamplePreviewRate()/100.0f)/sampleZoom; + + if (p1.xrectMax.x) p1.x=rectMax.x; + + ImVec2 p2=p1; + p2.y=rectMax.y; + + dl->AddRectFilledMultiColor( + ImVec2(p1.x-trailDistance,p1.y), + p2, + ImGui::GetColorU32(posTrail2), + ImGui::GetColorU32(posTrail1), + ImGui::GetColorU32(posTrail1), + ImGui::GetColorU32(posTrail2) + ); + dl->AddLine(p1,p2,ImGui::GetColorU32(posColor)); + } + if (drawSelection) { int start=sampleSelStart; int end=sampleSelEnd; @@ -1439,10 +1529,8 @@ void FurnaceGUI::drawSampleEdit() { p1.x+=(start-samplePos)/sampleZoom; ImVec2 p2=ImVec2(rectMin.x+(end-samplePos)/sampleZoom,rectMax.y); - ImVec4 boundColor=uiColors[GUI_COLOR_ACCENT_PRIMARY]; - ImVec4 selColor=uiColors[GUI_COLOR_ACCENT_SECONDARY]; - boundColor.w*=0.5; - selColor.w*=0.25; + ImVec4 boundColor=uiColors[GUI_COLOR_SAMPLE_SEL_POINT]; + ImVec4 selColor=uiColors[GUI_COLOR_SAMPLE_SEL]; if (p1.xrectMax.x) p1.x=rectMax.x; @@ -1470,11 +1558,9 @@ void FurnaceGUI::drawSampleEdit() { } } - if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT && sampleDragMode) { statusBar="Non-8/16-bit samples cannot be edited without prior conversion."; } - - ImGui::EndDisabled(); ImGui::SetCursorPosY(ImGui::GetCursorPosY()+ImGui::GetStyle().ScrollbarSize); ImGui::Text("%s",statusBar.c_str()); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 26557acd..e4c40cae 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1773,6 +1773,17 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_EE_VALUE,"External command output"); ImGui::TreePop(); } + if (ImGui::TreeNode("Sample Editor")) { + UI_COLOR_CONFIG(GUI_COLOR_SAMPLE_BG,"Background"); + UI_COLOR_CONFIG(GUI_COLOR_SAMPLE_FG,"Waveform"); + UI_COLOR_CONFIG(GUI_COLOR_SAMPLE_LOOP,"Loop region"); + UI_COLOR_CONFIG(GUI_COLOR_SAMPLE_CENTER,"Center guide"); + UI_COLOR_CONFIG(GUI_COLOR_SAMPLE_GRID,"Grid"); + UI_COLOR_CONFIG(GUI_COLOR_SAMPLE_SEL,"Selection"); + UI_COLOR_CONFIG(GUI_COLOR_SAMPLE_SEL_POINT,"Selection points"); + UI_COLOR_CONFIG(GUI_COLOR_SAMPLE_NEEDLE,"Preview needle"); + ImGui::TreePop(); + } if (ImGui::TreeNode("Pattern Manager")) { UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_NULL,"Unallocated"); UI_COLOR_CONFIG(GUI_COLOR_PAT_MANAGER_UNUSED,"Unused"); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index bbc86b7a..a5e6b1c9 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -18,6 +18,7 @@ */ #include "gui.h" +#include bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool modifyOnChange) { bool altered=false; @@ -1306,6 +1307,113 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo break; } + case DIV_SYSTEM_MSM5232: { + int detune=flags.getInt("detune",0); + bool groupEnv[2]; + int groupVol[8]; + float capValue[8]; + char temp[64]; + groupEnv[0]=flags.getBool("groupEnv0",true); + groupEnv[1]=flags.getBool("groupEnv1",true); + groupVol[0]=flags.getInt("partVolume0",255); + groupVol[1]=flags.getInt("partVolume1",255); + groupVol[2]=flags.getInt("partVolume2",255); + groupVol[3]=flags.getInt("partVolume3",255); + groupVol[4]=flags.getInt("partVolume4",255); + groupVol[5]=flags.getInt("partVolume5",255); + groupVol[6]=flags.getInt("partVolume6",255); + groupVol[7]=flags.getInt("partVolume7",255); + capValue[0]=flags.getFloat("capValue0",390.0f); + capValue[1]=flags.getFloat("capValue1",390.0f); + capValue[2]=flags.getFloat("capValue2",390.0f); + capValue[3]=flags.getFloat("capValue3",390.0f); + capValue[4]=flags.getFloat("capValue4",390.0f); + capValue[5]=flags.getFloat("capValue5",390.0f); + capValue[6]=flags.getFloat("capValue6",390.0f); + capValue[7]=flags.getFloat("capValue7",390.0f); + + if (CWSliderInt("Detune",&detune,-127,127)) { + if (detune<-127) detune=-127; + if (detune>127) detune=127; + altered=true; + } rightClickable + + ImGui::Text("Capacitor values (nF):"); + for (int i=0; i<8; i++) { + snprintf(temp,63,"%d##CAPV%d",i+1,i); + if (CWSliderFloat(temp,&capValue[i],1.0f,1000.0f)) { + if (capValue[i]<0) capValue[i]=0; + if (capValue[i]>1000) capValue[i]=1000; + altered=true; + } rightClickable + } + + ImGui::Text("Initial part volume (channel 1-4):"); + for (int i=0; i<4; i++) { + snprintf(temp,63,"%d'##GRPV%d",16>>i,i); + if (CWSliderInt(temp,&groupVol[i],0,255)) { + if (groupVol[i]<0) groupVol[i]=0; + if (groupVol[i]>255) groupVol[i]=255; + altered=true; + } rightClickable + } + + ImGui::Text("Initial part volume (channel 5-8):"); + for (int i=4; i<8; i++) { + snprintf(temp,63,"%d'##GRPV%d",16>>(i-4),i); + if (CWSliderInt(temp,&groupVol[i],0,255)) { + if (groupVol[i]<0) groupVol[i]=0; + if (groupVol[i]>255) groupVol[i]=255; + altered=true; + } rightClickable + } + + ImGui::Text("Envelope mode (channel 1-4):"); + if (ImGui::RadioButton("Capacitor (attack/decay)##EM00",groupEnv[0])) { + groupEnv[0]=true; + altered=true; + } + if (ImGui::RadioButton("External (volume macro)##EM01",!groupEnv[0])) { + groupEnv[0]=false; + altered=true; + } + + ImGui::Text("Envelope mode (channel 5-8):"); + if (ImGui::RadioButton("Capacitor (attack/decay)##EM10",groupEnv[1])) { + groupEnv[1]=true; + altered=true; + } + if (ImGui::RadioButton("External (volume macro)##EM11",!groupEnv[1])) { + groupEnv[1]=false; + altered=true; + } + + if (altered) { + flags.set("detune",detune); + + flags.set("capValue0",capValue[0]); + flags.set("capValue1",capValue[1]); + flags.set("capValue2",capValue[2]); + flags.set("capValue3",capValue[3]); + flags.set("capValue4",capValue[4]); + flags.set("capValue5",capValue[5]); + flags.set("capValue6",capValue[6]); + flags.set("capValue7",capValue[7]); + + flags.set("partVolume0",groupVol[0]); + flags.set("partVolume1",groupVol[1]); + flags.set("partVolume2",groupVol[2]); + flags.set("partVolume3",groupVol[3]); + flags.set("partVolume4",groupVol[4]); + flags.set("partVolume5",groupVol[5]); + flags.set("partVolume6",groupVol[6]); + flags.set("partVolume7",groupVol[7]); + + flags.set("groupEnv0",groupEnv[0]); + flags.set("groupEnv1",groupEnv[1]); + } + break; + } case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: case DIV_SYSTEM_BUBSYS_WSG: