From 3cea5c8603a5f6cdfa911d14f18e95dc81c90ad1 Mon Sep 17 00:00:00 2001 From: Joel Blanco Berg Date: Fri, 27 Sep 2024 12:31:04 +0200 Subject: [PATCH] new 3D wavetable display (#7799) * new 3d wavetable display * 3d wavetable display fix Added override and removed virtual from processSamplesForDisplay * wt display additional fixes * replaced string comparison with @mkruselj diff * rebuilt the resize handling of the bitmap. * added setZoomFactor to waveformdisplay * resize bug fixed First resize event is now fired after display has been added to stage. --- src/common/SurgeStorage.cpp | 2 + src/common/dsp/Wavetable.h | 1 + src/common/dsp/oscillators/OscillatorBase.h | 3 + .../dsp/oscillators/WavetableOscillator.cpp | 77 ++++ .../dsp/oscillators/WavetableOscillator.h | 2 + .../dsp/oscillators/WindowOscillator.cpp | 80 ++++ src/common/dsp/oscillators/WindowOscillator.h | 2 + src/surge-xt/gui/SurgeGUIEditor.cpp | 21 +- .../gui/widgets/OscillatorWaveformDisplay.cpp | 428 ++++++++++-------- .../gui/widgets/OscillatorWaveformDisplay.h | 5 + 10 files changed, 425 insertions(+), 196 deletions(-) diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index bb8def61fb3..5a04f9ba92c 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -1272,6 +1272,7 @@ void SurgeStorage::perform_queued_wtloads() patch.isDirty = true; load_wt(patch.scene[sc].osc[o].wt.queue_id, &patch.scene[sc].osc[o].wt, &patch.scene[sc].osc[o]); + patch.scene[sc].osc[o].wt.is_dnd_imported = false; patch.scene[sc].osc[o].wt.refresh_display = true; } else if (patch.scene[sc].osc[o].wt.queue_filename[0]) @@ -1293,6 +1294,7 @@ void SurgeStorage::perform_queued_wtloads() patch.scene[sc].osc[o].wt.current_id = wtidx; load_wt(patch.scene[sc].osc[o].wt.queue_filename, &patch.scene[sc].osc[o].wt, &patch.scene[sc].osc[o]); + patch.scene[sc].osc[o].wt.is_dnd_imported = true; patch.scene[sc].osc[o].wt.refresh_display = true; if (patch.scene[sc].osc[o].wt.everBuilt) patch.isDirty = true; diff --git a/src/common/dsp/Wavetable.h b/src/common/dsp/Wavetable.h index ac8eb27ad58..6eeb00c5506 100644 --- a/src/common/dsp/Wavetable.h +++ b/src/common/dsp/Wavetable.h @@ -66,6 +66,7 @@ class Wavetable int current_id, queue_id; bool refresh_display; + bool is_dnd_imported; std::string queue_filename; std::string current_filename; int frame_size_if_absent{-1}; diff --git a/src/common/dsp/oscillators/OscillatorBase.h b/src/common/dsp/oscillators/OscillatorBase.h index 198fa1a74ba..c6011445998 100644 --- a/src/common/dsp/oscillators/OscillatorBase.h +++ b/src/common/dsp/oscillators/OscillatorBase.h @@ -49,6 +49,9 @@ class alignas(16) Oscillator float FMdepth = 0.f) { } + + virtual void processSamplesForDisplay(float *samples, int size, bool real){}; + virtual void assign_fm(float *master_osc) { this->master_osc = master_osc; } virtual bool allow_display() { return true; } inline double pitch_to_omega(float x) diff --git a/src/common/dsp/oscillators/WavetableOscillator.cpp b/src/common/dsp/oscillators/WavetableOscillator.cpp index 0eb8136a319..b8db0f56b3c 100644 --- a/src/common/dsp/oscillators/WavetableOscillator.cpp +++ b/src/common/dsp/oscillators/WavetableOscillator.cpp @@ -182,6 +182,83 @@ float WavetableOscillator::distort_level(float x) return x; } +void WavetableOscillator::processSamplesForDisplay(float *samples, int size, bool real) +{ + if (!real) + { + // saturate and skewY + for (int i = 0; i < size; i++) + { + samples[i] = distort_level(samples[i]); + } + + // formant + if (oscdata->p[wt_formant].val.f > 0.f) + { + float mult = pow(2, oscdata->p[wt_formant].val.f * 0.08333333333333333); + + for (int i = 0; i < size; i++) + { + float pos = limit_range((float)i * mult, 0.f, (float)size - 1.f); + int from = floor(pos); + int to = limit_range(from + 1, 0, size - 1); + + float proc = pos - (float)from; + + samples[i] = samples[from] * (1.f - proc) + samples[to] * proc; + } + } + + // skewX + /* + TODO + this is not even close to the correct inplementation of skewx. + */ + /* + float samplePos = 0.f; + + float mul = 1.f / (float)size; + float fsize = (float)size; + + float tempSamples[64]; + + float hskew = -oscdata->p[wt_skewh].val.f; + float taylorscale = sqrtf(27.f / 4); + + for (int i = 0; i < size; i++) + { + float xt = (i + 0.5) * mul; + xt = 1 + hskew * 4 * xt * (xt - 1) * (2 * xt - 1) * taylorscale; + samplePos = (samplePos + xt); + if (samplePos > fsize - 1.f) + samplePos -= fsize; + + int from = ((int)samplePos + size * 2) % size; + float proc = samplePos - from; + int to = (from + 1) % size; + + // interpolate samples + tempSamples[i] = samples[from] * (1.f - proc) + samples[to] * proc; + } + + for (int i = 0; i < size; i++) + { + samples[i] = tempSamples[i]; + } + */ + } + else + { + + // todo populate samples with process_block() + for (int i = 0; i < size; i++) + { + samples[i] = 0; + } + } + // saturation +}; + void WavetableOscillator::convolute(int voice, bool FM, bool stereo) { float block_pos = oscstate[voice] * BLOCK_SIZE_OS_INV * pitchmult_inv; diff --git a/src/common/dsp/oscillators/WavetableOscillator.h b/src/common/dsp/oscillators/WavetableOscillator.h index a3b11192575..6f6f87dae87 100644 --- a/src/common/dsp/oscillators/WavetableOscillator.h +++ b/src/common/dsp/oscillators/WavetableOscillator.h @@ -61,6 +61,8 @@ class WavetableOscillator : public AbstractBlitOscillator virtual void handleStreamingMismatches(int streamingRevision, int currentSynthStreamingRevision) override; + void processSamplesForDisplay(float *samples, int size, bool real) override; + private: void convolute(int voice, bool FM, bool stereo); template void update_lagvals(); diff --git a/src/common/dsp/oscillators/WindowOscillator.cpp b/src/common/dsp/oscillators/WindowOscillator.cpp index 97c0d17a81a..8775681b122 100644 --- a/src/common/dsp/oscillators/WindowOscillator.cpp +++ b/src/common/dsp/oscillators/WindowOscillator.cpp @@ -196,6 +196,86 @@ inline unsigned int BigMULr16(unsigned int a, unsigned int b) return c >> 16u; } +void WindowOscillator::processSamplesForDisplay(float *samples, int size, bool real) +{ + if (!real) + { + + // formant + + float formant = oscdata->p[win_formant].val.f; + float newSamples[64]; + + float mult = pow(2.f, formant / 12.f); + + for (int i = 0; i < size; i++) + { + float pos = (float)i * mult; + int from = (int)(pos); + + float proc = pos - (float)from; + from = from % (size); + int to = (from + 1) % size; + newSamples[i] = samples[from] * (1.f - proc) + samples[to] * proc; + } + + int window = limit_range(oscdata->p[win_window].val.i, 0, 8); + int windowSize = storage->WindowWT.size; + + for (int i = 0; i < size; i++) + { + samples[i] = newSamples[i]; + } + + // apply window + + for (int i = 0; i < size; i++) + { + float windowPos = ((float)i / (float)size); // correct + windowPos *= (float)windowSize; + float v = storage->WindowWT.TableI16WeakPointers[0][window][(int)windowPos]; + samples[i] = samples[i] * v * 0.00006103515625; // there is most likely a better way + // of doing this ( divide by 2pow13) + } + + // filters + + for (int k = 0; k < size; k += BLOCK_SIZE) + { + float blockSamples[BLOCK_SIZE]; + + for (int i = 0; i < BLOCK_SIZE; i++) + { + int s = i + k; + blockSamples[i] = s < size ? samples[s] : 0; + } + + if (!oscdata->p[win_lowcut].deactivated) + hp.process_block(blockSamples, samples); + if (!oscdata->p[win_highcut].deactivated) + lp.process_block(blockSamples, samples); + + for (int i = 0; i < BLOCK_SIZE; i++) + { + int s = i + k; + if (s < size) + { + samples[s] = blockSamples[i]; + } + } + } + } + else + { + + // todo populate samples with process_block() + for (int i = 0; i < size; i++) + { + samples[i] = 0; + } + } +}; + template void WindowOscillator::ProcessWindowOscs(bool stereo) { const unsigned int M0Mask = 0x07f8; diff --git a/src/common/dsp/oscillators/WindowOscillator.h b/src/common/dsp/oscillators/WindowOscillator.h index 25d293b58fe..1b3ebafe0c3 100644 --- a/src/common/dsp/oscillators/WindowOscillator.h +++ b/src/common/dsp/oscillators/WindowOscillator.h @@ -54,6 +54,8 @@ class WindowOscillator : public Oscillator virtual void handleStreamingMismatches(int streamingRevision, int currentSynthStreamingRevision) override; + void processSamplesForDisplay(float *samples, int size, bool real) override; + private: int IOutputL alignas(16)[BLOCK_SIZE_OS]; int IOutputR alignas(16)[BLOCK_SIZE_OS]; diff --git a/src/surge-xt/gui/SurgeGUIEditor.cpp b/src/surge-xt/gui/SurgeGUIEditor.cpp index 3d4dd10d36f..b0218dc15a4 100644 --- a/src/surge-xt/gui/SurgeGUIEditor.cpp +++ b/src/surge-xt/gui/SurgeGUIEditor.cpp @@ -771,23 +771,24 @@ void SurgeGUIEditor::idle() } auto ol = getOverlayIfOpenAs(FORMULA_EDITOR); + if (ol) { ol->updateDebuggerIfNeeded(); } - if (synth->storage.getPatch() - .scene[current_scene] - .osc[current_osc[current_scene]] - .wt.refresh_display) + auto wt = + &synth->storage.getPatch().scene[current_scene].osc[current_osc[current_scene]].wt; + if (wt->refresh_display) { - synth->storage.getPatch() - .scene[current_scene] - .osc[current_osc[current_scene]] - .wt.refresh_display = false; + wt->refresh_display = false; if (oscWaveform) { + if (wt->is_dnd_imported) + { + oscWaveform->repaintForceForWT(); + } oscWaveform->repaint(); } } @@ -3008,6 +3009,7 @@ void SurgeGUIEditor::setZoomFactor(float zf) { setZoomFactor(zf, false); } void SurgeGUIEditor::setZoomFactor(float zf, bool resizeWindow) { + zoomFactor = std::max(zf, static_cast(minimumZoom)); #if LINUX @@ -3035,6 +3037,9 @@ void SurgeGUIEditor::setZoomFactor(float zf, bool resizeWindow) frame->setTransform(juce::AffineTransform().scaled(zff)); } + if (oscWaveform) + oscWaveform->setZoomFactor(zoomFactor); + setBitmapZoomFactor(zoomFactor); rezoomOverlays(); } diff --git a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp index bc80a773464..8556daf0780 100644 --- a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp +++ b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp @@ -317,8 +317,13 @@ void OscillatorWaveformDisplay::paint(juce::Graphics &g) { // It's a bit unsatisfactory to put this here but we don't really get notified // once the wavetable change is done other than through repaint - if (oscdata->wt.current_id != lastWavetableId) + if (oscdata->wt.current_id != lastWavetableId || forceWTRepaint) // more unsatisfactory!) { + if (forceWTRepaint) + { + forceWTRepaint = false; + } + auto nd = std::string("Wavetable: ") + storage->getCurrentWavetableName(oscdata); menuOverlays[0]->setTitle(nd); @@ -421,6 +426,14 @@ void OscillatorWaveformDisplay::paint(juce::Graphics &g) } } +void OscillatorWaveformDisplay::setZoomFactor(int zoom) +{ + if (customEditor && uses_wavetabledata(oscdata->type.val.i)) + { + customEditor->resized(); + } +} + void OscillatorWaveformDisplay::resized() { auto wtr = getLocalBounds().withTop(getHeight() - wtbheight).withTrimmedBottom(1).toFloat(); @@ -1267,260 +1280,297 @@ struct WaveTable3DEditor : public juce::Component, std::unique_ptr backingImage; + /* + For Profiling! + + TRUE + All graphics will be produced by the oscillator.process_block. + + FALSE + Wt data will be used and processed by the oscillator + */ + + const bool PROCESS_WITH_OSC = false; + + float samplesCached[128][128]; + float morphValue = 0.0; + + int currentBreakingPoint = -1; + + union ParamCached + { + int i; + float f; + }; + + ParamCached paramCached[7]; + float zoomFactor = -1.0; + bool hasResized = false; + + /* Values exported from + * https://www.blancoberg.com/surgewt3d?json=%7B%22rendered_frames%22%3A40%2C%22rendered_samples%22%3A53%2C%22paddingX%22%3A0.017679932260795936%2C%22paddingY%22%3A0.2127688399661304%2C%22skew%22%3A0.3%2C%22perspective%22%3A0%2C%22amplitude%22%3A0.14970364098221847%2C%22adjustX%22%3A0%2C%22adjustY%22%3A0%2C%22thickness%22%3A0.3989839119390347%2C%22morph%22%3A0%7D + */ + + int w, h; + float wf, hf; + int wt_size = 0; + int wt_nframes = 64; + int rendered_frames = 40; + int rendered_samples = 60; + float paddingX = 0.017679932260795936; + float paddingY = 0.2127688399661304; + float skew = 0.3; + float perspective = 0; + float amplitude = 0.14970364098221847; + float morph = 0; + float thickness = 0.3191871295512278; + float adjustX = 0; + float adjustY = 0; + + ////////////////////////////////////////////////// + WaveTable3DEditor(OscillatorWaveformDisplay *pD, SurgeStorage *s, OscillatorStorage *osc, SurgeGUIEditor *ed) : parent(pD), storage(s), oscdata(osc), sge(ed) { + clearSampleCache(); } - void resized() override { backingImage = nullptr; } - - void paint(juce::Graphics &g) override + void cacheParams() { - auto wtlockguard = std::lock_guard(parent->storage->waveTableDataMutex); - auto &wt = oscdata->wt; - auto pos = -1.f; - bool off = false; - - if (uses_wavetabledata(oscdata->type.val.i)) + for (int i = 0; i < 7; i++) { - pos = oscdata->p[0].val.f; - off = oscdata->p[0].extend_range; + this->paramCached[i].i = oscdata->p[i].val.i; + this->paramCached[i].f = oscdata->p[i].val.f; } - else + } + + void clearSampleCache() + { + for (int i = 0; i < 128; i++) { - pos = 0.f; - }; + samplesCached[i][0] = 0.f; + } + } - auto tpos = pos * (wt.n_tables - off); + bool paramsHaveChanged() + { - // OK so now go backwards through the tables but also tilt and raise for the 3D effect - auto smp = wt.size; - auto smpinv = 1.0 / smp; - auto w = getWidth(); - auto h = getHeight(); + for (int i = 0; i < 7; i++) + { + if (oscdata->p[i].val.i != this->paramCached[i].i || + oscdata->p[i].val.f != this->paramCached[i].f) + { + cacheParams(); + return true; + } + } + return false; + } - // Now we have a sort of skew back and offset as we go. The skew is sort of a rotation - // and the depth is sort of how flattened it is. Finally the hCompress augments height. - auto skewPct = 0.4; - auto depthPct = 0.6; - auto hCompress = 0.55; + float getCurrentZoomFactor() + { + float zoom = (float)sge->getZoomFactor() * 0.01; + return zoom; + } - // calculate thinning factor for frame drawing - int thintbl = 1; - int nt = wt.n_tables; + void resized() override + { + float currentZoom = getCurrentZoomFactor(); - while (nt > 16) + if (currentZoom != zoomFactor) { - thintbl <<= 1; - nt >>= 1; - } + zoomFactor = currentZoom; + hasResized = true; + w = getWidth(); + h = getHeight(); - // calculate thinning factor for sample drawing - int thinsmp = 1; - int s = smp; + wf = (float)w * zoomFactor * 2; + hf = (float)h * zoomFactor * 2; - while (s > 128) - { - thinsmp <<= 1; - s >>= 1; + backingImage = nullptr; + backingImage = std::make_unique(juce::Image::PixelFormat::ARGB, (int)wf, + (int)(hf), true); } + } - static constexpr float backingScale = 2.f; + /* + create a interpolated frame based on a float frame value + */ - auto wxf = w; - auto hxf = h; + void getInterpFrame(float *samples, float frame, float frames, bool processForReal, + bool useCache) + { + auto &wt = oscdata->wt; + float actualFrame = (frame / (frames - 1.f)) * (float)(wt_nframes - 1); + int frameFrom = (int)(actualFrame); + float proc = actualFrame - (float)frameFrom; - if (!backingImage) + if (processForReal == false) { - backingImage = - std::make_unique(juce::Image::PixelFormat::ARGB, wxf, hxf, true); + if (samplesCached[(int)frame][0] == 0.f || useCache == false) + { + int frameTo = frameFrom + 1 > wt_nframes - 1 ? frameFrom : frameFrom + 1; - // shadow on purpose - auto g = juce::Graphics(*backingImage); + for (int i = 0; i < rendered_samples; i++) + { - // draw the wavetable frames - std::vector ts; + int pos = floor((((float)i) / (float)std::max(rendered_samples - 1, 1)) * + ((float)wt_size - 1.f)); - for (int t = wt.n_tables - 1; t >= 0; t = t - thintbl) - { - ts.push_back(t); - } + samples[i] = wt.TableF32WeakPointers[0][frameFrom][pos] * (1.f - proc) + + wt.TableF32WeakPointers[0][frameTo][pos] * proc; + if (useCache) + samplesCached[(int)frame][i + 1] = samples[i]; + } - if (ts.back() != 0) - { - ts.push_back(0); + samplesCached[(int)frame][0] = 1.f; } - - for (auto t : ts) + else { - auto tb = wt.TableF32WeakPointers[0][t]; - float tpct = 1.0 * t / std::max((int)(wt.n_tables - 1), 1); - - if (wt.n_tables == 1) + for (int i = 0; i < rendered_samples; i++) { - tpct = 0.f; + samples[i] = samplesCached[(int)frame][i + 1]; } + } + } + } - float x0 = tpct * skewPct * wxf; - float y0 = (1.0 - tpct) * depthPct * hxf; - auto lw = wxf * (1.0 - skewPct); - auto hw = hxf * depthPct * hCompress; + void drawWavetable(juce::Graphics &g) + { + auto osc = parent->setupOscillator(); + osc->init(0, true, false); - juce::Path p; - juce::Path ribbon; + float opacity = 1; - p.startNewSubPath(x0, y0 + (-tb[0] + 1) * 0.5 * hw); - ribbon.startNewSubPath(x0, y0 + (-tb[0] + 1) * 0.5 * hw); + float opacityMultiplier = parent->isMuted ? 0.5f : 1.f; - for (int s = 1; s < smp; s = s + thinsmp) - { - auto x = x0 + s * smpinv * lw; + /* + Draw the base layer + */ - p.lineTo(x, y0 + (-tb[s] + 1) * 0.5 * hw); - ribbon.lineTo(x, y0 + (-tb[s] + 1) * 0.5 * hw); - } + if (paramsHaveChanged() || hasResized) + { + hasResized = false; + juce::Colour color = + juce::Colour((unsigned char)0, (unsigned char)0, (unsigned char)0, 0.f); - if (t > 0) - { - nt = std::max(t - thintbl, 0); - tpct = 1.0 * nt / (wt.n_tables - 1); - tb = wt.TableF32WeakPointers[0][nt]; - x0 = tpct * skewPct * wxf; - y0 = (1.0 - tpct) * depthPct * hxf; - lw = w * (1.0 - skewPct); - - for (int s = smp - 1; s >= 0; s = s - thinsmp) - { - auto x = x0 + s * smpinv * lw; + backingImage->clear( + juce::Rectangle(0, 0, int(zoomFactor * w * 2), int(zoomFactor * h * 2)), color); + auto g = juce::Graphics(*backingImage); - ribbon.lineTo(x, y0 + (-tb[s] + 1) * 0.5 * hw); - } + for (int i = 0; i < rendered_frames; i++) + { + // on lower frames lower the opacity, as we will add the actual frames on top of the + // base shape for emphasis - g.setColour(skin->getColor(Colors::Osc::Display::WaveFillStart3D) - .interpolatedWith( - skin->getColor(Colors::Osc::Display::WaveFillEnd3D), tpct) - .withMultipliedAlpha(parent->isMuted ? 0.5f : 1.f)); - g.fillPath(ribbon); + if (wt_nframes <= 10 && wt_nframes > 1) + { + opacity = 0.5; } - g.setColour( - skin->getColor(Colors::Osc::Display::WaveStart3D) - .interpolatedWith(skin->getColor(Colors::Osc::Display::WaveEnd3D), tpct) - .withMultipliedAlpha((1.0 - abs(0.25 - (tpct * tpct * 0.5))) * - (parent->isMuted ? 0.5f : 1.f))); - g.strokePath(p, juce::PathStrokeType(0.75)); - } - } - - g.setOpacity(parent->isMuted ? 0.5f : 1.f); - g.drawImage(*backingImage, getLocalBounds().toFloat(), - juce::RectanglePlacement::fillDestination); - g.setOpacity(1.f); - - // draw currently selected frame - { - auto sel = std::clamp(tpos, 0.f, (wt.n_tables - 1.f)); - auto tb = wt.TableF32WeakPointers[0][(int)std::floor(sel)]; - float tpct = 1.0 * sel / std::max((int)(wt.n_tables - 1), 1); + float proc = (float)i / (float)rendered_frames; - if (wt.n_tables == 1) - { - tpct = 0.f; + drawWaveform(g, i, rendered_frames, + skin->getColor(Colors::Osc::Display::WaveCurrent3D), + (0.9 - 0.7 * proc) * opacityMultiplier * 0.5, 1, osc, 1.f, true); } - float x0 = tpct * skewPct * w; - float y0 = (1.0 - tpct) * depthPct * h; - auto lw = w * (1.0 - skewPct); - auto hw = h * depthPct * hCompress; + // On lower frames, draw the actual frames on top of the base shape - auto osc = parent->setupOscillator(); - - if (!osc) + if (wt_nframes <= 10 && wt_nframes > 1) { - return; + opacity = 0.7; + for (int i = 0; i < wt_nframes; i++) + { + float proc = (float)i / (float)rendered_frames; + int fr = (float)i / ((float)wt_nframes - 1) * ((float)rendered_frames - 1.f); + drawWaveform(g, fr, rendered_frames, + skin->getColor(Colors::Osc::Display::WaveCurrent3D), + (0.9 - 0.7 * proc) * opacityMultiplier * 0.6, 1.0, osc, 1.f, true); + } } - int totalSamples = getWidth(); - // empirically set up... don't ask! - float disp_pitch_rs = - 12.f * std::log2f((700.f * (storage->samplerate / 48000.f)) / 440.f) + 69.f; + /* + Draw the wavetable position (morph) + Do not use cache for a smoother motion + */ - if (!storage->isStandardTuning) - { - // OK so in this case we need to find a better version of the note which gets us - // that pitch. Only way is to search really. - auto pit = storage->note_to_pitch_ignoring_tuning(disp_pitch_rs); - int bracket = -1; + float pos = morphValue; + int position = pos * (128 - 1); + drawWaveform(g, position, 128, skin->getColor(Colors::Osc::Display::WaveCurrent3D), + 0.9 * opacityMultiplier, 1.5, osc, 1.f, false); + } - for (int i = 0; i < 128; ++i) - { - if (storage->note_to_pitch(i) < pit && storage->note_to_pitch(i + 1) > pit) - { - bracket = i; + // draw backingimage to graphics context + g.drawImage(*backingImage, getLocalBounds().toFloat(), + juce::RectanglePlacement::fillDestination); - break; - } - } + // osc->~Oscillator(); + // osc = nullptr; + } - if (bracket >= 0) - { - float f1 = storage->note_to_pitch(bracket); - float f2 = storage->note_to_pitch(bracket + 1); - float frac = (pit - f1) / (f2 - f1); + /* + draws the waveform + */ + void drawWaveform(juce::Graphics &g, int i, int renderedFrames, juce::Colour color, + float opacity, float thick, ::Oscillator *osc, float scale, bool useCache) + { - disp_pitch_rs = bracket + frac; - } + bool processForReal = PROCESS_WITH_OSC; - // That's a strange non-monotonic tuning. Oh well. - } + juce::Path p; + float procFrames = ((float)i) / (float)(renderedFrames - 1); - bool use_display = osc->allow_display(); + float samples[64]; - if (use_display) - { - osc->init(disp_pitch_rs, true, true); - } + getInterpFrame(samples, (float)i, (float)renderedFrames, processForReal, useCache); - int block_pos = BLOCK_SIZE_OS; - juce::Path wavePath; + osc->processSamplesForDisplay(samples, rendered_samples, processForReal); - wavePath.startNewSubPath(0.f, 0.f); + float xScaled = wf * scale; + float YScaled = hf * scale; - for (int i = 0; i < totalSamples; i++) - { - if (use_display && block_pos >= BLOCK_SIZE_OS) - { - osc->process_block(disp_pitch_rs); - block_pos = 0; - } + float skewCalc = skew; - float val = 0.f; + for (int k = 0; k < rendered_samples; k++) + { - if (use_display) - { - val = osc->output[block_pos]; - block_pos++; - } + float proc = (float)k / ((float)rendered_samples - 1.f); - if (i >= 4) - { - float xc = 1.f * (i - 4) / totalSamples; + float x = xScaled * + (adjustX + 0.5 * perspective * procFrames + paddingX + skewCalc * procFrames + + proc * (1 - paddingX * 2.0 - skewCalc - perspective * procFrames)); + x += perspective * skewCalc * wf * 0.5; - wavePath.lineTo(xc, val); - } + float y = adjustY * YScaled + YScaled - paddingY * YScaled - + (YScaled - paddingY * 2 * YScaled) * procFrames + + samples[k] * YScaled * -(amplitude * (1 - 0.2 * perspective * procFrames)) * + (1 - perspective * procFrames); + + if (k == 0) + { + p.startNewSubPath(x, y); } + else + { + p.lineTo(x, y); + } + } - osc->~Oscillator(); - osc = nullptr; + g.setColour(color.withMultipliedAlpha(opacity)); + g.strokePath(p, juce::PathStrokeType(thickness * xScaled * 0.01 * 2 * thick)); + } - auto tf = - juce::AffineTransform().scaled(w * 0.61, h * -0.17).translated(x0, y0 + (0.5 * hw)); + void paint(juce::Graphics &g) override + { + auto &wt = oscdata->wt; + wt_size = wt.size; + wt_nframes = wt.n_tables; - g.setColour(skin->getColor(Colors::Osc::Display::WaveCurrent3D) - .withMultipliedAlpha(parent->isMuted ? 0.5f : 1.f)); - g.strokePath(wavePath, juce::PathStrokeType(0.85), tf); - } + morphValue = oscdata->p[0].val.f; + + drawWavetable(g); } void mouseDown(const juce::MouseEvent &event) override @@ -1933,6 +1983,8 @@ void OscillatorWaveformDisplay::showCustomEditor() auto b = getLocalBounds().withTrimmedBottom(wtbheight); customEditor->setBounds(b); addAndMakeVisible(*customEditor); + customEditor->resized(); // Resize needs to happen after it has been added to the scene. + // Otherwise bounds will return 0 repaint(); customEditorAccOverlay->setTitle("Close Custom Editor"); diff --git a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.h b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.h index e3083a3fc4e..187a3c381ed 100644 --- a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.h +++ b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.h @@ -70,6 +70,7 @@ struct OscillatorWaveformDisplay : public juce::Component, } bool isMuted{false}; + bool forceWTRepaint{false}; SurgeGUIEditor *sge{nullptr}; void setSurgeGUIEditor(SurgeGUIEditor *s) { sge = s; } @@ -79,6 +80,7 @@ struct OscillatorWaveformDisplay : public juce::Component, void repaintIfIdIsInRange(int id); void repaintBasedOnOscMuteState(); + void repaintForceForWT() { forceWTRepaint = true; }; ::Oscillator *setupOscillator(); unsigned char oscbuffer alignas(16)[oscillator_buffer_size]; @@ -86,6 +88,8 @@ struct OscillatorWaveformDisplay : public juce::Component, void paint(juce::Graphics &g) override; void resized() override; + void setZoomFactor(int); + pdata tp[n_scene_params]; juce::Rectangle leftJog, rightJog, waveTableName; @@ -132,6 +136,7 @@ struct OscillatorWaveformDisplay : public juce::Component, std::unique_ptr customEditorAccOverlay; std::unique_ptr createAccessibilityHandler() override; int lastWavetableId{-1}; + std::string lastWavetableFilename; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OscillatorWaveformDisplay); };