From 4197fa44fb0779d1260e26fcc0a82ec4c6915307 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 30 Apr 2022 03:58:30 -0500 Subject: [PATCH] GUI: prepare for a per-channel oscilloscope? currently supported systems: - Amiga - AY-3-8910 - Dummy System - OPLL - SegaPCM no trigger supported yet! --- CMakeLists.txt | 1 + src/engine/dispatch.h | 19 ++++++ src/engine/engine.cpp | 10 +++ src/engine/engine.h | 6 ++ src/engine/platform/abstract.cpp | 4 ++ src/engine/platform/amiga.cpp | 30 +++++++-- src/engine/platform/amiga.h | 2 + src/engine/platform/ay.cpp | 19 +++++- src/engine/platform/ay.h | 2 + src/engine/platform/dummy.cpp | 22 ++++++- src/engine/platform/dummy.h | 2 + src/engine/platform/opll.cpp | 16 ++++- src/engine/platform/opll.h | 2 + src/engine/platform/segapcm.cpp | 15 +++++ src/engine/platform/segapcm.h | 2 + src/gui/chanOsc.cpp | 101 +++++++++++++++++++++++++++++++ src/gui/doAction.cpp | 6 ++ src/gui/gui.cpp | 9 ++- src/gui/gui.h | 12 +++- src/gui/guiConst.cpp | 3 +- 20 files changed, 269 insertions(+), 14 deletions(-) create mode 100644 src/gui/chanOsc.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 75a3d3350..b757062f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,6 +389,7 @@ src/gui/guiConst.cpp src/gui/about.cpp src/gui/channels.cpp +src/gui/chanOsc.cpp src/gui/compatFlags.cpp src/gui/cursor.cpp src/gui/dataList.cpp diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 21598442b..7e5f998b9 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -21,6 +21,7 @@ #define _DISPATCH_H #include +#include #include #define ONE_SEMITONE 2200 @@ -205,6 +206,18 @@ struct DivRegWrite { addr(a), val(v) {} }; +struct DivDispatchOscBuffer { + unsigned int rate; + unsigned short needle; + short data[65536]; + + DivDispatchOscBuffer(): + rate(65536), + needle(0) { + memset(data,0,65536*sizeof(short)); + } +}; + class DivEngine; class DivMacroInt; @@ -268,6 +281,12 @@ class DivDispatch { * @return a pointer, or NULL. */ virtual DivMacroInt* getChanMacroInt(int chan); + + /** + * get an oscilloscope buffer for a channel. + * @return a pointer to a DivDispatchOscBuffer, or NULL if not supported. + */ + virtual DivDispatchOscBuffer* getOscBuffer(int chan); /** * get the register pool of this dispatch. diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 47d70093b..f8f518933 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -986,6 +986,16 @@ unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) { return disCont[sys].dispatch->getRegisterPool(); } +DivMacroInt* DivEngine::getMacroInt(int chan) { + if (chan<0 || chan>=chans) return NULL; + return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]); +} + +DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) { + if (chan<0 || chan>=chans) return NULL; + return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]); +} + void DivEngine::enableCommandStream(bool enable) { cmdStreamEnabled=enable; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 7dd7489cb..2ca301c53 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -722,6 +722,12 @@ class DivEngine { // get register pool unsigned char* getRegisterPool(int sys, int& size, int& depth); + // get macro interpreter + DivMacroInt* getMacroInt(int chan); + + // get osc buffer + DivDispatchOscBuffer* getOscBuffer(int chan); + // enable command stream dumping void enableCommandStream(bool enable); diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index 56f2aa3d7..91f61fc62 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -33,6 +33,10 @@ DivMacroInt* DivDispatch::getChanMacroInt(int chan) { return NULL; } +DivDispatchOscBuffer* DivDispatch::getOscBuffer(int chan) { + return NULL; +} + unsigned char* DivDispatch::getRegisterPool() { return NULL; } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 406b3d406..e78c3a77b 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -91,12 +91,15 @@ const char* DivPlatformAmiga::getEffectName(unsigned char effect) { } void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t len) { - static int outL, outR; + static int outL, outR, output; for (size_t h=start; hdata[oscBuf[i]->needle++]=0; + continue; + } if (chan[i].useWave || (chan[i].sample>=0 && chan[i].samplesong.sampleLen)) { chan[i].audSub-=AMIGA_DIVIDER; if (chan[i].audSub<0) { @@ -139,13 +142,17 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } } if (!isMuted[i]) { + output=chan[i].audDat*chan[i].outVol; if (i==0 || i==3) { - outL+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; - outR+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; + outL+=(output*sep1)>>7; + outR+=(output*sep2)>>7; } else { - outL+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; - outR+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; + outL+=(output*sep2)>>7; + outR+=(output*sep1)>>7; } + oscBuf[i]->data[oscBuf[i]->needle++]=output<<2; + } else { + oscBuf[i]->data[oscBuf[i]->needle++]=0; } } filter[0][0]+=(filtConst*(outL-filter[0][0]))>>12; @@ -419,6 +426,10 @@ void* DivPlatformAmiga::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformAmiga::getOscBuffer(int ch) { + return oscBuf[ch]; +} + void DivPlatformAmiga::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformAmiga::Channel(); @@ -469,6 +480,9 @@ void DivPlatformAmiga::setFlags(unsigned int flags) { chipClock=COLOR_NTSC; } rate=chipClock/AMIGA_DIVIDER; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate; + } sep1=((flags>>8)&127)+127; sep2=127-((flags>>8)&127); amigaModel=flags&2; @@ -487,6 +501,7 @@ int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, unsigned int dumpWrites=false; skipRegisterWrites=false; for (int i=0; i<4; i++) { + oscBuf[i]=new DivDispatchOscBuffer; isMuted[i]=false; } setFlags(flags); @@ -495,4 +510,7 @@ int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformAmiga::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } } diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index 0777100d7..539f7830c 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -74,6 +74,7 @@ class DivPlatformAmiga: public DivDispatch { outVol(64) {} }; Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; bool bypassLimits; bool amigaModel; @@ -91,6 +92,7 @@ class DivPlatformAmiga: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); void reset(); void forceIns(); void tick(bool sysTick=true); diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 12ea0f64a..37108452e 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -146,6 +146,12 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l bufR[i+start]=bufL[i+start]; } } + + for (int ch=0; ch<3; ch++) { + for (size_t i=0; idata[oscBuf[ch]->needle++]=ayBuf[ch][i]; + } + } } void DivPlatformAY8910::updateOutSel(bool immediate) { @@ -500,6 +506,10 @@ void* DivPlatformAY8910::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformAY8910::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformAY8910::getRegisterPool() { return regPool; } @@ -615,6 +625,9 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { break; } rate=chipClock/8; + for (int i=0; i<3; i++) { + oscBuf[i]->rate=rate; + } if (ay!=NULL) delete ay; switch ((flags>>4)&3) { @@ -650,6 +663,7 @@ int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned in skipRegisterWrites=false; for (int i=0; i<3; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } ay=NULL; setFlags(flags); @@ -660,6 +674,9 @@ int DivPlatformAY8910::init(DivEngine* p, int channels, int sugRate, unsigned in } void DivPlatformAY8910::quit() { - for (int i=0; i<3; i++) delete[] ayBuf[i]; + for (int i=0; i<3; i++) { + delete oscBuf[i]; + delete[] ayBuf[i]; + } if (ay!=NULL) delete ay; } diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index 2bc4a03d3..b257e3bbc 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -56,6 +56,7 @@ class DivPlatformAY8910: public DivDispatch { }; std::queue writes; ay8910_device* ay; + DivDispatchOscBuffer* oscBuf[3]; unsigned char regPool[16]; unsigned char lastBusy; @@ -90,6 +91,7 @@ class DivPlatformAY8910: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void flushWrites(); diff --git a/src/engine/platform/dummy.cpp b/src/engine/platform/dummy.cpp index 9b972ac18..34d614eb3 100644 --- a/src/engine/platform/dummy.cpp +++ b/src/engine/platform/dummy.cpp @@ -25,12 +25,21 @@ #define CHIP_FREQBASE 2048 void DivPlatformDummy::acquire(short* bufL, short* bufR, size_t start, size_t len) { + int chanOut; for (size_t i=start; i>12; + if (!isMuted[j]) { + chanOut=(((signed short)chan[j].pos)*chan[j].amp*chan[j].vol)>>12; + oscBuf[j]->data[oscBuf[j]->needle++]=chanOut; + out+=chanOut; + } else { + oscBuf[j]->data[oscBuf[j]->needle++]=0; + } chan[j].pos+=chan[j].freq; + } else { + oscBuf[j]->data[oscBuf[j]->needle++]=0; } } if (out<-32768) out=-32768; @@ -61,6 +70,10 @@ void* DivPlatformDummy::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformDummy::getOscBuffer(int ch) { + return oscBuf[ch]; +} + int DivPlatformDummy::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: @@ -131,6 +144,10 @@ int DivPlatformDummy::init(DivEngine* p, int channels, int sugRate, unsigned int skipRegisterWrites=false; for (int i=0; irate=65536; + } } rate=65536; chipClock=65536; @@ -140,6 +157,9 @@ int DivPlatformDummy::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformDummy::quit() { + for (int i=0; i=6 && properDrums) || !isMuted[nextOut]) { + oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6; os+=(o[0]+o[1]); + } else { + oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0; } } os*=50; @@ -731,6 +734,10 @@ void* DivPlatformOPLL::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformOPLL::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformOPLL::getRegisterPool() { return regPool; } @@ -842,6 +849,9 @@ void DivPlatformOPLL::setFlags(unsigned int flags) { } rate=chipClock/36; patchSet=flags>>4; + for (int i=0; i<11; i++) { + oscBuf[i]->rate=rate/2; + } } int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -851,14 +861,18 @@ int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int patchSet=0; for (int i=0; i<11; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); reset(); - return 10; + return 11; } void DivPlatformOPLL::quit() { + for (int i=0; i<11; i++) { + delete oscBuf[i]; + } } DivPlatformOPLL::~DivPlatformOPLL() { diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index 96cb1a914..7a06bbb77 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -63,6 +63,7 @@ class DivPlatformOPLL: public DivDispatch { }; Channel chan[11]; bool isMuted[11]; + DivDispatchOscBuffer* oscBuf[11]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -100,6 +101,7 @@ class DivPlatformOPLL: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index eb506ff36..346daeae1 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -46,9 +46,11 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t DivSample* s=parent->getSample(chan[i].pcm.sample); if (s->samples<=0) { chan[i].pcm.sample=-1; + oscBuf[i]->data[oscBuf[i]->needle++]=0; continue; } if (!isMuted[i]) { + oscBuf[i]->data[oscBuf[i]->needle++]=s->data8[chan[i].pcm.pos>>8]*(chan[i].chVolL+chan[i].chVolR)>>1; pcmL+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolL); pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR); } @@ -60,6 +62,8 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t chan[i].pcm.sample=-1; } } + } else { + oscBuf[i]->data[oscBuf[i]->needle++]=0; } } @@ -359,6 +363,10 @@ void* DivPlatformSegaPCM::getChanState(int ch) { return &chan[ch]; } +DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) { + return oscBuf[ch]; +} + unsigned char* DivPlatformSegaPCM::getRegisterPool() { return regPool; } @@ -408,6 +416,9 @@ void DivPlatformSegaPCM::reset() { void DivPlatformSegaPCM::setFlags(unsigned int flags) { chipClock=8000000.0; rate=31250; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } } bool DivPlatformSegaPCM::isStereo() { @@ -420,6 +431,7 @@ int DivPlatformSegaPCM::init(DivEngine* p, int channels, int sugRate, unsigned i skipRegisterWrites=false; for (int i=0; i<16; i++) { isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; } setFlags(flags); reset(); @@ -428,6 +440,9 @@ int DivPlatformSegaPCM::init(DivEngine* p, int channels, int sugRate, unsigned i } void DivPlatformSegaPCM::quit() { + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } } DivPlatformSegaPCM::~DivPlatformSegaPCM() { diff --git a/src/engine/platform/segapcm.h b/src/engine/platform/segapcm.h index 7ef09892a..32cd22c29 100644 --- a/src/engine/platform/segapcm.h +++ b/src/engine/platform/segapcm.h @@ -49,6 +49,7 @@ class DivPlatformSegaPCM: public DivDispatch { Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), note(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), portaPause(false), furnacePCM(false), vol(0), outVol(0), chVolL(127), chVolR(127) {} }; Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -77,6 +78,7 @@ class DivPlatformSegaPCM: public DivDispatch { void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); void* getChanState(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp new file mode 100644 index 000000000..c4924e7ef --- /dev/null +++ b/src/gui/chanOsc.cpp @@ -0,0 +1,101 @@ +/** + * 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 "gui.h" +#include "imgui.h" +#include "imgui_internal.h" + +void FurnaceGUI::drawChanOsc() { + if (nextWindow==GUI_WINDOW_CHAN_OSC) { + chanOscOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!chanOscOpen) return; + ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen)) { + if (ImGui::InputInt("Columns",&chanOscCols,1,1)) { + if (chanOscCols<1) chanOscCols=1; + if (chanOscCols>64) chanOscCols=64; + } + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); + float availY=ImGui::GetContentRegionAvail().y; + if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) { + int chans=e->getTotalChannelCount(); + int rows=(chans+(chanOscCols-1))/chanOscCols; + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 waveform[512]; + + ImGuiStyle& style=ImGui::GetStyle(); + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_OSC_WAVE]); + + for (int i=0; igetOscBuffer(i); + if (buf==NULL) { + ImGui::Text("Not Available"); + } else { + ImVec2 size=ImGui::GetContentRegionAvail(); + size.y=availY/rows; + + int displaySize=(buf->rate)/30; + + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImRect inRect=rect; + inRect.Min.x+=dpiScale; + inRect.Min.y+=dpiScale; + inRect.Max.x-=dpiScale; + inRect.Max.y-=dpiScale; + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { + if (!e->isPlaying()) { + for (unsigned short i=0; i<512; i++) { + float x=(float)i/512.0f; + waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f)); + } + } else { + unsigned short needlePos=buf->needle-displaySize; + for (unsigned short i=0; i<512; i++) { + float x=(float)i/512.0f; + float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + if (y<-0.5f) y=-0.5f; + if (y>0.5f) y=0.5f; + waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); + } + } + dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); + } + } + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHAN_OSC; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 4020dfc7b..92ce732f9 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -217,6 +217,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WINDOW_EFFECT_LIST: nextWindow=GUI_WINDOW_EFFECT_LIST; break; + case GUI_ACTION_WINDOW_CHAN_OSC: + nextWindow=GUI_WINDOW_CHAN_OSC; + break; case GUI_ACTION_COLLAPSE_WINDOW: collapseWindow=true; @@ -295,6 +298,9 @@ void FurnaceGUI::doAction(int what) { case GUI_WINDOW_EFFECT_LIST: effectListOpen=false; break; + case GUI_WINDOW_CHAN_OSC: + chanOscOpen=false; + break; default: break; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index dd37ceae1..6d63d0f50 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2757,7 +2757,8 @@ bool FurnaceGUI::loop() { ImGui::Separator(); if (ImGui::MenuItem("play/edit controls",BIND_FOR(GUI_ACTION_WINDOW_EDIT_CONTROLS),editControlsOpen)) editControlsOpen=!editControlsOpen; if (ImGui::MenuItem("piano/input pad",BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen; - if (ImGui::MenuItem("oscilloscope",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; + if (ImGui::MenuItem("oscilloscope (master)",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; + if (ImGui::MenuItem("oscilloscope (per-channel)",BIND_FOR(GUI_ACTION_WINDOW_CHAN_OSC),chanOscOpen)) chanOscOpen=!chanOscOpen; if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; @@ -2860,6 +2861,7 @@ bool FurnaceGUI::loop() { readOsc(); drawOsc(); + drawChanOsc(); drawVolMeter(); drawSettings(); drawDebug(); @@ -3556,6 +3558,7 @@ bool FurnaceGUI::init() { settingsOpen=e->getConfBool("settingsOpen",false); mixerOpen=e->getConfBool("mixerOpen",false); oscOpen=e->getConfBool("oscOpen",true); + chanOscOpen=e->getConfBool("chanOscOpen",false); volMeterOpen=e->getConfBool("volMeterOpen",true); statsOpen=e->getConfBool("statsOpen",false); compatFlagsOpen=e->getConfBool("compatFlagsOpen",false); @@ -3731,6 +3734,7 @@ bool FurnaceGUI::finish() { e->setConf("settingsOpen",settingsOpen); e->setConf("mixerOpen",mixerOpen); e->setConf("oscOpen",oscOpen); + e->setConf("chanOscOpen",chanOscOpen); e->setConf("volMeterOpen",volMeterOpen); e->setConf("statsOpen",statsOpen); e->setConf("compatFlagsOpen",compatFlagsOpen); @@ -3851,6 +3855,7 @@ FurnaceGUI::FurnaceGUI(): regViewOpen(false), logOpen(false), effectListOpen(false), + chanOscOpen(false), /* editControlsDocked(false), ordersDocked(false), @@ -3877,6 +3882,7 @@ FurnaceGUI::FurnaceGUI(): regViewDocked(false), logDocked(false), effectListDocked(false), + chanOscDocked(false), */ selecting(false), curNibble(false), @@ -3993,6 +3999,7 @@ FurnaceGUI::FurnaceGUI(): oscTotal(0), oscZoom(0.5f), oscZoomSlider(false), + chanOscCols(3), followLog(true), pianoOctaves(7), pianoOptions(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 9608e5eb7..cf532eef5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -230,7 +230,8 @@ enum FurnaceGUIWindows { GUI_WINDOW_CHANNELS, GUI_WINDOW_REGISTER_VIEW, GUI_WINDOW_LOG, - GUI_WINDOW_EFFECT_LIST + GUI_WINDOW_EFFECT_LIST, + GUI_WINDOW_CHAN_OSC }; enum FurnaceGUIFileDialogs { @@ -330,6 +331,7 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_REGISTER_VIEW, GUI_ACTION_WINDOW_LOG, GUI_ACTION_WINDOW_EFFECT_LIST, + GUI_ACTION_WINDOW_CHAN_OSC, GUI_ACTION_COLLAPSE_WINDOW, GUI_ACTION_CLOSE_WINDOW, @@ -948,13 +950,13 @@ class FurnaceGUI { bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; - bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen; + bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; /* there ought to be a better way... bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked; bool waveListDocked, waveEditDocked, sampleListDocked, sampleEditDocked, aboutDocked, settingsDocked; bool mixerDocked, debugDocked, inspectorDocked, oscDocked, volMeterDocked, statsDocked, compatFlagsDocked; - bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked; + bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked, chanOscDocked; */ SelectionPoint selStart, selEnd, cursor; @@ -1097,6 +1099,9 @@ class FurnaceGUI { float oscZoom; bool oscZoomSlider; + // per-channel oscilloscope + int chanOscCols; + // visualizer float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; @@ -1151,6 +1156,7 @@ class FurnaceGUI { void drawSampleEdit(); void drawMixer(); void drawOsc(); + void drawChanOsc(); void drawVolMeter(); void drawStats(); void drawCompatFlags(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index ec157a9eb..0cc1894ab 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -475,7 +475,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_SETTINGS", "Settings", 0), D("WINDOW_MIXER", "Mixer", 0), D("WINDOW_DEBUG", "Debug Menu", 0), - D("WINDOW_OSCILLOSCOPE", "Oscilloscope", 0), + D("WINDOW_OSCILLOSCOPE", "Oscilloscope (master)", 0), D("WINDOW_VOL_METER", "Volume Meter", 0), D("WINDOW_STATS", "Statistics", 0), D("WINDOW_COMPAT_FLAGS", "Compatibility Flags", 0), @@ -485,6 +485,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_REGISTER_VIEW", "Register View", 0), D("WINDOW_LOG", "Log Viewer", 0), D("EFFECT_LIST", "Effect List", 0), + D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0), D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE),