GUI: per-chan osc improvements

This commit is contained in:
tildearrow 2023-06-18 04:27:22 -05:00
parent b9414de49d
commit 4776020d05
5 changed files with 265 additions and 132 deletions

View File

@ -74,7 +74,7 @@ void DivPlatformGB::acquire(short** buf, size_t len) {
buf[1][i]=gb->apu_output.final_sample.right; buf[1][i]=gb->apu_output.final_sample.right;
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<5; oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<6;
} }
} }
} }

View File

@ -58,9 +58,9 @@ void DivPlatformSM8521::acquire(short** buf, size_t len) {
sm8521_sound_tick(&sm8521,8); sm8521_sound_tick(&sm8521,8);
buf[0][h]=sm8521.out<<6; buf[0][h]=sm8521.out<<6;
for (int i=0; i<2; i++) { for (int i=0; i<2; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<5; oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<7;
} }
oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<5; oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<7;
} }
} }

View File

@ -22,6 +22,7 @@
#include "../ta-log.h" #include "../ta-log.h"
#include "imgui.h" #include "imgui.h"
#include "imgui_internal.h" #include "imgui_internal.h"
#include "misc/cpp/imgui_stdlib.h"
#define FURNACE_FFT_SIZE 4096 #define FURNACE_FFT_SIZE 4096
#define FURNACE_FFT_RATE 80.0 #define FURNACE_FFT_RATE 80.0
@ -284,159 +285,266 @@ void FurnaceGUI::drawChanOsc() {
ImGui::ColorPicker4("Color",(float*)&chanOscColor); ImGui::ColorPicker4("Color",(float*)&chanOscColor);
} }
ImGui::Text("Text format:");
ImGui::SameLine();
ImGui::InputText("##TextFormat",&chanOscTextFormat);
if (ImGui::IsItemHovered()) {
if (ImGui::BeginTooltip()) {
ImGui::TextUnformatted(
"format guide:\n"
"- %c: channel name\n"
"- %C: channel short name\n"
"- %d: channel number (starting from 0)\n"
"- %D: channel number (starting from 1)\n"
"- %i: instrument name\n"
"- %I: instrument number (decimal)\n"
"- %x: instrument number (hex)\n"
"- %s: chip name\n"
"- %S: chip ID\n"
"- %v: volume (decimal)\n"
"- %V: volume (percentage)\n"
"- %b: volume (hex)\n"
"- %%: percent sign"
);
ImGui::EndTooltip();
}
}
ImGui::ColorEdit4("Text color",(float*)&chanOscTextColor);
if (ImGui::Button("OK")) { if (ImGui::Button("OK")) {
chanOscOptions=false; chanOscOptions=false;
} }
} } else {
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f));
float availY=ImGui::GetContentRegionAvail().y;
if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) {
std::vector<DivDispatchOscBuffer*> oscBufs;
std::vector<ChanOscStatus*> oscFFTs;
std::vector<int> oscChans;
int chans=e->getTotalChannelCount();
ImGuiWindow* window=ImGui::GetCurrentWindow();
ImVec2 waveform[512];
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); ImGuiStyle& style=ImGui::GetStyle();
float availY=ImGui::GetContentRegionAvail().y;
if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) {
std::vector<DivDispatchOscBuffer*> oscBufs;
std::vector<ChanOscStatus*> oscFFTs;
std::vector<int> oscChans;
int chans=e->getTotalChannelCount();
ImGuiWindow* window=ImGui::GetCurrentWindow();
ImVec2 waveform[512];
ImGuiStyle& style=ImGui::GetStyle(); for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=e->getOscBuffer(i);
for (int i=0; i<chans; i++) { if (buf!=NULL && e->curSubSong->chanShow[i]) {
DivDispatchOscBuffer* buf=e->getOscBuffer(i); oscBufs.push_back(buf);
if (buf!=NULL && e->curSubSong->chanShow[i]) { oscFFTs.push_back(&chanOscChan[i]);
oscBufs.push_back(buf); oscChans.push_back(i);
oscFFTs.push_back(&chanOscChan[i]); }
oscChans.push_back(i);
} }
} int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols;
int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols;
for (size_t i=0; i<oscBufs.size(); i++) { for (size_t i=0; i<oscBufs.size(); i++) {
if (i%chanOscCols==0) ImGui::TableNextRow(); if (i%chanOscCols==0) ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
DivDispatchOscBuffer* buf=oscBufs[i]; DivDispatchOscBuffer* buf=oscBufs[i];
ChanOscStatus* fft=oscFFTs[i]; ChanOscStatus* fft=oscFFTs[i];
int ch=oscChans[i]; int ch=oscChans[i];
if (buf==NULL) { if (buf==NULL) {
ImGui::Text("Error!"); ImGui::Text("Error!");
} else { } else {
ImVec2 size=ImGui::GetContentRegionAvail(); ImVec2 size=ImGui::GetContentRegionAvail();
size.y=availY/rows; size.y=availY/rows;
if (centerSettingReset) { if (centerSettingReset) {
buf->readNeedle=buf->needle; buf->readNeedle=buf->needle;
} }
// check FFT status existence // check FFT status existence
if (fft->plan==NULL) { if (fft->plan==NULL) {
logD("creating FFT plan for channel %d",ch); logD("creating FFT plan for channel %d",ch);
fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double));
fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex)); fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex));
fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE); fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE);
} }
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f); int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
ImVec2 minArea=window->DC.CursorPos; ImVec2 minArea=window->DC.CursorPos;
ImVec2 maxArea=ImVec2( ImVec2 maxArea=ImVec2(
minArea.x+size.x, minArea.x+size.x,
minArea.y+size.y minArea.y+size.y
); );
ImRect rect=ImRect(minArea,maxArea); ImRect rect=ImRect(minArea,maxArea);
ImRect inRect=rect; ImRect inRect=rect;
inRect.Min.x+=dpiScale; inRect.Min.x+=dpiScale;
inRect.Min.y+=2.0*dpiScale; inRect.Min.y+=2.0*dpiScale;
inRect.Max.x-=dpiScale; inRect.Max.x-=dpiScale;
inRect.Max.y-=2.0*dpiScale; inRect.Max.y-=2.0*dpiScale;
int precision=inRect.Max.x-inRect.Min.x; int precision=inRect.Max.x-inRect.Min.x;
if (precision<1) precision=1; if (precision<1) precision=1;
if (precision>512) precision=512; if (precision>512) precision=512;
ImGui::ItemSize(size,style.FramePadding.y); ImGui::ItemSize(size,style.FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) {
if (!e->isRunning()) { if (!e->isRunning()) {
for (unsigned short i=0; i<precision; i++) { for (unsigned short i=0; i<precision; i++) {
float x=(float)i/(float)precision; float x=(float)i/(float)precision;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f)); waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
} }
} else { } else {
float minLevel=1.0f; float minLevel=1.0f;
float maxLevel=-1.0f; float maxLevel=-1.0f;
float dcOff=0.0f; float dcOff=0.0f;
unsigned short needlePos=buf->needle; unsigned short needlePos=buf->needle;
for (int i=0; i<FURNACE_FFT_SIZE; i++) { for (int i=0; i<FURNACE_FFT_SIZE; i++) {
fft->inBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; fft->inBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0;
} }
fftw_execute(fft->plan); fftw_execute(fft->plan);
// find origin frequency // find origin frequency
int point=1; int point=1;
double candAmp=0.0; double candAmp=0.0;
for (unsigned short i=1; i<512; i++) { for (unsigned short i=1; i<512; i++) {
fftw_complex& f=fft->outBuf[i]; fftw_complex& f=fft->outBuf[i];
// AMPLITUDE // AMPLITUDE
double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8);
if (amp>candAmp) { if (amp>candAmp) {
point=i; point=i;
candAmp=amp; candAmp=amp;
}
}
// PHASE
fftw_complex& candPoint=fft->outBuf[point];
double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2)));
if (chanOscWaveCorr) {
needlePos-=phase;
}
chanOscPitch[ch]=(float)point/32.0f;
/*
String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]);
dl->AddText(inRect.Min,0xffffffff,cPhase.c_str());
*/
needlePos-=displaySize;
for (unsigned short i=0; i<precision; i++) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f;
if (minLevel>y) minLevel=y;
if (maxLevel<y) maxLevel=y;
}
dcOff=(minLevel+maxLevel)*0.5f;
for (unsigned short i=0; i<precision; i++) {
float x=(float)i/(float)precision;
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f;
y-=dcOff;
if (y<-0.5f) y=-0.5f;
if (y>0.5f) y=0.5f;
y*=chanOscAmplify;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y));
} }
} }
ImU32 color=ImGui::GetColorU32(chanOscColor);
if (chanOscUseGrad) {
float xVal=computeGradPos(chanOscColorX,ch);
float yVal=computeGradPos(chanOscColorY,ch);
// PHASE xVal=CLAMP(xVal,0.0f,1.0f);
fftw_complex& candPoint=fft->outBuf[point]; yVal=CLAMP(yVal,0.0f,1.0f);
double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2)));
if (chanOscWaveCorr) { color=chanOscGrad.get(xVal,1.0f-yVal);
needlePos-=phase;
} }
chanOscPitch[ch]=(float)point/32.0f; ImGui::PushClipRect(inRect.Min,inRect.Max,false);
/*
String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]);
dl->AddText(inRect.Min,0xffffffff,cPhase.c_str());
*/
needlePos-=displaySize; dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale);
for (unsigned short i=0; i<precision; i++) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; if (!chanOscTextFormat.empty()) {
if (minLevel>y) minLevel=y; String text;
if (maxLevel<y) maxLevel=y; bool inFormat=false;
}
dcOff=(minLevel+maxLevel)*0.5f; for (char i: chanOscTextFormat) {
for (unsigned short i=0; i<precision; i++) { if (inFormat) {
float x=(float)i/(float)precision; switch (i) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; case 'c':
y-=dcOff; text+=e->getChannelName(ch);
if (y<-0.5f) y=-0.5f; break;
if (y>0.5f) y=0.5f; case 'C':
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); text+=e->getChannelShortName(ch);
break;
case 'd':
text+=fmt::sprintf("%d",ch);
break;
case 'D':
text+=fmt::sprintf("%d",ch+1);
break;
case 'i': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL) break;
DivInstrument* ins=e->getIns(chanState->lastIns);
text+=ins->name;
break;
}
case 'I': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL) break;
text+=fmt::sprintf("%d",chanState->lastIns);
break;
}
case 'x': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL) break;
if (chanState->lastIns<0) {
text+="??";
} else {
text+=fmt::sprintf("%.2X",chanState->lastIns);
}
break;
}
case 's': {
text+=e->getSystemName(e->sysOfChan[ch]);
break;
}
case 'S': {
text+=fmt::sprintf("%d",e->dispatchOfChan[ch]);
break;
}
case 'v':
break;
case 'V':
break;
case 'b':
break;
case '%':
text+='%';
break;
default:
text+='%';
text+=i;
break;
}
inFormat=false;
} else {
if (i=='%') {
inFormat=true;
} else {
text+=i;
}
}
}
dl->AddText(ImLerp(inRect.Min,inRect.Max,ImVec2(0.0f,0.0f)),ImGui::GetColorU32(chanOscTextColor),text.c_str());
} }
ImGui::PopClipRect();
} }
ImU32 color=ImGui::GetColorU32(chanOscColor);
if (chanOscUseGrad) {
float xVal=computeGradPos(chanOscColorX,ch);
float yVal=computeGradPos(chanOscColorY,ch);
xVal=CLAMP(xVal,0.0f,1.0f);
yVal=CLAMP(yVal,0.0f,1.0f);
color=chanOscGrad.get(xVal,1.0f-yVal);
}
ImGui::PushClipRect(inRect.Min,inRect.Max,false);
dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale);
ImGui::PopClipRect();
} }
} }
} ImGui::EndTable();
ImGui::EndTable();
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
chanOscOptions=!chanOscOptions; chanOscOptions=!chanOscOptions;
}
} }
ImGui::PopStyleVar();
} }
ImGui::PopStyleVar();
} }
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHAN_OSC; if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHAN_OSC;
ImGui::End(); ImGui::End();

View File

@ -5994,13 +5994,22 @@ bool FurnaceGUI::init() {
chanOscCols=e->getConfInt("chanOscCols",3); chanOscCols=e->getConfInt("chanOscCols",3);
chanOscColorX=e->getConfInt("chanOscColorX",GUI_OSCREF_CENTER); chanOscColorX=e->getConfInt("chanOscColorX",GUI_OSCREF_CENTER);
chanOscColorY=e->getConfInt("chanOscColorY",GUI_OSCREF_CENTER); chanOscColorY=e->getConfInt("chanOscColorY",GUI_OSCREF_CENTER);
chanOscTextX=e->getConfFloat("chanOscTextX",0.0f);
chanOscTextY=e->getConfFloat("chanOscTextY",0.0f);
chanOscAmplify=e->getConfFloat("chanOscAmplify",0.95f);
chanOscWindowSize=e->getConfFloat("chanOscWindowSize",20.0f); chanOscWindowSize=e->getConfFloat("chanOscWindowSize",20.0f);
chanOscWaveCorr=e->getConfBool("chanOscWaveCorr",true); chanOscWaveCorr=e->getConfBool("chanOscWaveCorr",true);
chanOscOptions=e->getConfBool("chanOscOptions",false); chanOscOptions=e->getConfBool("chanOscOptions",false);
chanOscNormalize=e->getConfBool("chanOscNormalize",false);
chanOscTextFormat=e->getConfString("chanOscTextFormat","%c");
chanOscColor.x=e->getConfFloat("chanOscColorR",1.0f); chanOscColor.x=e->getConfFloat("chanOscColorR",1.0f);
chanOscColor.y=e->getConfFloat("chanOscColorG",1.0f); chanOscColor.y=e->getConfFloat("chanOscColorG",1.0f);
chanOscColor.z=e->getConfFloat("chanOscColorB",1.0f); chanOscColor.z=e->getConfFloat("chanOscColorB",1.0f);
chanOscColor.w=e->getConfFloat("chanOscColorA",1.0f); chanOscColor.w=e->getConfFloat("chanOscColorA",1.0f);
chanOscTextColor.x=e->getConfFloat("chanOscTextColorR",1.0f);
chanOscTextColor.y=e->getConfFloat("chanOscTextColorG",1.0f);
chanOscTextColor.z=e->getConfFloat("chanOscTextColorB",1.0f);
chanOscTextColor.w=e->getConfFloat("chanOscTextColorA",0.75f);
chanOscUseGrad=e->getConfBool("chanOscUseGrad",false); chanOscUseGrad=e->getConfBool("chanOscUseGrad",false);
chanOscGrad.fromString(e->getConfString("chanOscGrad","")); chanOscGrad.fromString(e->getConfString("chanOscGrad",""));
chanOscGrad.render(); chanOscGrad.render();
@ -6487,13 +6496,22 @@ void FurnaceGUI::commitState() {
e->setConf("chanOscCols",chanOscCols); e->setConf("chanOscCols",chanOscCols);
e->setConf("chanOscColorX",chanOscColorX); e->setConf("chanOscColorX",chanOscColorX);
e->setConf("chanOscColorY",chanOscColorY); e->setConf("chanOscColorY",chanOscColorY);
e->setConf("chanOscTextX",chanOscTextX);
e->setConf("chanOscTextY",chanOscTextY);
e->setConf("chanOscAmplify",chanOscAmplify);
e->setConf("chanOscWindowSize",chanOscWindowSize); e->setConf("chanOscWindowSize",chanOscWindowSize);
e->setConf("chanOscWaveCorr",chanOscWaveCorr); e->setConf("chanOscWaveCorr",chanOscWaveCorr);
e->setConf("chanOscOptions",chanOscOptions); e->setConf("chanOscOptions",chanOscOptions);
e->setConf("chanOscNormalize",chanOscNormalize);
e->setConf("chanOscTextFormat",chanOscTextFormat);
e->setConf("chanOscColorR",chanOscColor.x); e->setConf("chanOscColorR",chanOscColor.x);
e->setConf("chanOscColorG",chanOscColor.y); e->setConf("chanOscColorG",chanOscColor.y);
e->setConf("chanOscColorB",chanOscColor.z); e->setConf("chanOscColorB",chanOscColor.z);
e->setConf("chanOscColorA",chanOscColor.w); e->setConf("chanOscColorA",chanOscColor.w);
e->setConf("chanOscTextColorR",chanOscTextColor.x);
e->setConf("chanOscTextColorG",chanOscTextColor.y);
e->setConf("chanOscTextColorB",chanOscTextColor.z);
e->setConf("chanOscTextColorA",chanOscTextColor.w);
e->setConf("chanOscUseGrad",chanOscUseGrad); e->setConf("chanOscUseGrad",chanOscUseGrad);
e->setConf("chanOscGrad",chanOscGrad.toString()); e->setConf("chanOscGrad",chanOscGrad.toString());
@ -6915,11 +6933,17 @@ FurnaceGUI::FurnaceGUI():
chanOscColorX(GUI_OSCREF_CENTER), chanOscColorX(GUI_OSCREF_CENTER),
chanOscColorY(GUI_OSCREF_CENTER), chanOscColorY(GUI_OSCREF_CENTER),
chanOscWindowSize(20.0f), chanOscWindowSize(20.0f),
chanOscTextX(0.0f),
chanOscTextY(0.0f),
chanOscAmplify(0.95f),
chanOscWaveCorr(true), chanOscWaveCorr(true),
chanOscOptions(false), chanOscOptions(false),
updateChanOscGradTex(true), updateChanOscGradTex(true),
chanOscUseGrad(false), chanOscUseGrad(false),
chanOscNormalize(false),
chanOscTextFormat("%c"),
chanOscColor(1.0f,1.0f,1.0f,1.0f), chanOscColor(1.0f,1.0f,1.0f,1.0f),
chanOscTextColor(1.0f,1.0f,1.0f,0.75f),
chanOscGrad(64,64), chanOscGrad(64,64),
chanOscGradTex(NULL), chanOscGradTex(NULL),
followLog(true), followLog(true),

View File

@ -1947,9 +1947,10 @@ class FurnaceGUI {
// per-channel oscilloscope // per-channel oscilloscope
int chanOscCols, chanOscColorX, chanOscColorY; int chanOscCols, chanOscColorX, chanOscColorY;
float chanOscWindowSize; float chanOscWindowSize, chanOscTextX, chanOscTextY, chanOscAmplify;
bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad; bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad, chanOscNormalize;
ImVec4 chanOscColor; String chanOscTextFormat;
ImVec4 chanOscColor, chanOscTextColor;
Gradient2D chanOscGrad; Gradient2D chanOscGrad;
void* chanOscGradTex; void* chanOscGradTex;
float chanOscLP0[DIV_MAX_CHANS]; float chanOscLP0[DIV_MAX_CHANS];