From 88ae14b9db714b07c50a60075ae5c9c8e68a10e0 Mon Sep 17 00:00:00 2001 From: Christoph Hart Date: Mon, 10 Jul 2023 12:24:33 +0200 Subject: [PATCH] - added thread controller - other minor improvements --- Loris Toolbox/Scripts/LorisProcessor.js | 38 +++- Loris Toolbox/Scripts/Manifest.js | 38 +++- Loris Toolbox/Scripts/PitchLock.js | 6 + Loris Toolbox/Scripts/UI/RectHelpers.js | 5 + .../XmlPresetBackups/Loris Toolbox.xml | 30 ++- .../Loris ToolboxDesktop.xml | 109 ++++++----- Loris Toolbox/project_info.xml | 1 + loris_library/Source/Helpers.cpp | 6 +- loris_library/Source/Helpers.h | 10 + loris_library/Source/LorisState.cpp | 17 +- loris_library/Source/LorisState.h | 5 + .../Source/MultichannelPartialList.cpp | 182 ++++++++++++++---- .../Source/MultichannelPartialList.h | 2 +- loris_library/Source/ThreadController.h | 165 ++++++++++++++++ loris_library/Source/public.cpp | 9 + loris_library/Source/public.h | 9 +- loris_library/Source/src/Analyzer.cpp | 7 + loris_library/Source/src/Analyzer.h | 3 + loris_library/Source/src/Fundamental.cpp | 5 + loris_library/Source/src/Fundamental.h | 3 +- loris_library/Source/src/loris.h | 6 +- loris_library/Source/src/lorisAnalyzer_pi.cpp | 8 +- loris_library/Source/src/lorisNonObj_pi.cpp | 3 +- loris_library/loris_library.jucer | 2 +- 24 files changed, 536 insertions(+), 133 deletions(-) create mode 100644 loris_library/Source/ThreadController.h diff --git a/Loris Toolbox/Scripts/LorisProcessor.js b/Loris Toolbox/Scripts/LorisProcessor.js index 2869361..506f11d 100644 --- a/Loris Toolbox/Scripts/LorisProcessor.js +++ b/Loris Toolbox/Scripts/LorisProcessor.js @@ -217,10 +217,25 @@ namespace LorisProcessor if(!Manifest.SaveButton.getValue()) return; + local suffixToUse = suffix; + + if(PitchLock.AutomatePitch.getValue()) + { + Console.print(rootNote.getValue()); + + local thisRoot = rootNote.getValue() + parseInt(PitchLock.PitchLockSliders[1].getValue()); + + Console.print(suffixToUse); + + suffixToUse += "_" + Engine.getMidiNoteName(thisRoot); + + + } + local sr = CURRENT_FILE.loadAudioMetadata().SampleRate; local prefix = CURRENT_FILE.toString(CURRENT_FILE.NoExtension); local ext = CURRENT_FILE.toString(CURRENT_FILE.Extension); - local target = outputDirectory.getChildFile(prefix + suffix + ext); + local target = outputDirectory.getChildFile(prefix + suffixToUse + ext); target.writeAudioFile(data, sr, 24); @@ -265,7 +280,7 @@ namespace LorisProcessor originalTone = lorisManager.synthesise(CURRENT_FILE); - saveBuffer(originalTone, "_tone"); + //saveBuffer(originalTone, "_tone"); if(isMultichannel) { @@ -279,7 +294,7 @@ namespace LorisProcessor original -= originalTone[0]; } - saveBuffer(original, "_noise"); + //saveBuffer(original, "_noise"); } worker.setStatusMessage("Processing..."); @@ -378,6 +393,9 @@ namespace LorisProcessor if(ALL_SAMPLES_MODE) { + Console.print("ALL SAMPLES"); + + for(s in CURRENT_SAMPLE_LIST) { OriginalWatcher.CURRENT_NOTE = s.get(Sampler1.Root); @@ -389,7 +407,19 @@ namespace LorisProcessor } else { - rebuildFile(CURRENT_FILE); + if(PitchLock.AutomatePitch.getValue()) + { + for(i = -12; i <= 12; i++) + { + PitchLock.PitchLockSliders[1].setValue(i); + PitchLock.PitchLockSliders[1].changed(); + rebuildFile(CURRENT_FILE); + } + } + else + { + rebuildFile(CURRENT_FILE); + } } PENDING = false; diff --git a/Loris Toolbox/Scripts/Manifest.js b/Loris Toolbox/Scripts/Manifest.js index 5e3f9b3..597e48c 100644 --- a/Loris Toolbox/Scripts/Manifest.js +++ b/Loris Toolbox/Scripts/Manifest.js @@ -189,18 +189,28 @@ namespace Manifest exampleLoad.setControlCallback(onExampleLoad); - + const var ON_COLOUR = 0x33000000; + const var OFF_COLOUR = 0x11000000; const var pageButtonLaf = Content.createLocalLookAndFeel(); pageButtonLaf.registerFunction("drawToggleButton", function(g, obj) { - g.setColour(0x22000000); - g.fillRoundedRectangle(obj.area, obj.area[3]/2); - g.setColour(obj.textColour); + g.setColour(ON_COLOUR); var circle = Rect.removeFromLeft(obj.area, obj.area[3]); + g.fillRoundedRectangle(circle, { "CornerSize": obj.area[3]/2, "Rounded": [true, false, true, false]}); + + g.setColour(OFF_COLOUR); + + g.fillRoundedRectangle(obj.area, { "CornerSize": obj.area[3]/2, "Rounded": [false, true, false, true]}); + + g.setColour(obj.textColour); + + + + var alpha = 0.3; if(obj.over) alpha += 0.1; @@ -216,6 +226,7 @@ namespace Manifest g.setColour(Colours.withAlpha(Colours.white, alpha)); + Rect.removeFromLeft(obj.area, 10); g.setFontWithSpacing(obj.value ? "Lato Bold" : "Lato", 12.0, 0.05); g.drawAlignedText(obj.text, obj.area, "left"); @@ -229,9 +240,24 @@ namespace Manifest "tags": ["page-handling"] }); + const var pageStates = [false, false, false, false]; + const var pageStateBroadcaster = Engine.createBroadcaster({ + "id": "pageStateBroadcaster", + "tags": ["page-handling"], + "args": ["component", "event"] + }); - + pageStateBroadcaster.addListener(pageStates, "update page states", function (component, event) + { + if(event.clicked) + { + var idx = component.get("id").getTrailingIntValue(); + Console.print(idx-1); + this[idx] = !this[idx]; + //component.set("bgCo") + } + }); inline function initPageButtons() { @@ -257,6 +283,8 @@ namespace Manifest pageButtons.push(b); } + pageStateBroadcaster.attachToComponentMouseEvents(pageButtons, "Clicks Only", ""); + pageBroadcaster.attachToRadioGroup(9000, "page group"); } diff --git a/Loris Toolbox/Scripts/PitchLock.js b/Loris Toolbox/Scripts/PitchLock.js index 522ceb1..e7bbf90 100644 --- a/Loris Toolbox/Scripts/PitchLock.js +++ b/Loris Toolbox/Scripts/PitchLock.js @@ -1,6 +1,12 @@ namespace PitchLock { + const var AutomatePitch = Content.getComponent("Automate Pitch"); + + AutomatePitch.setLocalLookAndFeel(Manifest.defaultLaf); + + + // UI Setup const var PitchLockSliders = [Content.getComponent("PitchLockAmount"), diff --git a/Loris Toolbox/Scripts/UI/RectHelpers.js b/Loris Toolbox/Scripts/UI/RectHelpers.js index 5657bfd..01393d2 100644 --- a/Loris Toolbox/Scripts/UI/RectHelpers.js +++ b/Loris Toolbox/Scripts/UI/RectHelpers.js @@ -13,6 +13,11 @@ namespace Rect return [ area[0], area[1], area[2], area[3]]; } + inline function getCentre(area) + { + return [area[0] + area[2]/2, area[1] + area[3]/2]; + } + inline function contains(area, point) { return point[0] >= area[0] && diff --git a/Loris Toolbox/XmlPresetBackups/Loris Toolbox.xml b/Loris Toolbox/XmlPresetBackups/Loris Toolbox.xml index 82b47d4..87669c0 100644 --- a/Loris Toolbox/XmlPresetBackups/Loris Toolbox.xml +++ b/Loris Toolbox/XmlPresetBackups/Loris Toolbox.xml @@ -10,8 +10,28 @@ NumDisplayBuffers="1"> + + + + + + + + + + + + + + + + + + + + @@ -26,11 +46,11 @@ - + Reversed="0.0" FileName="D:\Development\Projekte\wavetabletest\Samples\dorian_test\Temp Samples for Christoph\Jaw Harp\jawharp_CLOSE_15.wav" + min="0" max="157038" loopStart="0" loopEnd="157038"> @@ -91,14 +111,14 @@ - + Group7Table="" SampleMapID="LYRE"> diff --git a/Loris Toolbox/XmlPresetBackups/Loris ToolboxUIData/Loris ToolboxDesktop.xml b/Loris Toolbox/XmlPresetBackups/Loris ToolboxUIData/Loris ToolboxDesktop.xml index 1b24064..132ba9e 100644 --- a/Loris Toolbox/XmlPresetBackups/Loris ToolboxUIData/Loris ToolboxDesktop.xml +++ b/Loris Toolbox/XmlPresetBackups/Loris ToolboxUIData/Loris ToolboxDesktop.xml @@ -6,57 +6,49 @@ textColour="4294967295" itemColour2="4280690214" locked="1"> + itemColour2="0" textColour="0" borderSize="0.0" borderRadius="0.0" + visible="0"> + middlePosition="1.0" min="0.5" max="2.0" suffix=" st" defaultValue="1.0"/> + middlePosition="-60.0" min="-100.0" max="0.0" suffix=" dB" defaultValue="-100.0"/> + middlePosition="-60.0" min="-100.0" max="-40.0" suffix=" dB"/> + min="20.0" max="2000.0" suffix=" Hz"/> + middlePosition="1.0" min="0.5" max="2.0" suffix=" st" defaultValue="1.0"/> + middlePosition="0.75" suffix=" st" min="0.5"/> + itemColour2="0" textColour="0" borderSize="0.0" borderRadius="0.0"> + mode="NormalizedPercentage" suffix="%" middlePosition="0.5"/> + min="-12.0" max="12.0" suffix=" st"/> - + middlePosition="0.0" min="-100.0" max="36.0" stepSize="0.1000000014901161"/> + + textColour="268435455" tooltip="Set the gain attenuation for each harmonic"/> + min="0.25" max="4.0" suffix=" %" defaultValue="1.0" mode="NormalizedPercentage" + style="Horizontal" tooltip="The total duration scale (timestretch ratio)."/> + tooltip="Use this ruler to distort the timeline for the dilation process" + saveInPreset="1"/> + allowCallbacks="Clicks & Hover" itemColour="4278422015"/> + saveInPreset="0" tooltip="Test the resynthesize algorithm with different parameters." + enableMidiLearn="0"/> + saveInPreset="0" tooltip="Flatten the pitch of the sample to the given root note." + enableMidiLearn="0"/> + saveInPreset="0" tooltip="Change the harmonic spectrum of the sample." + enableMidiLearn="0"/> + saveInPreset="0" tooltip="Distort the timeline of the sample." + enableMidiLearn="0"/> + tooltip="Add the noise component from the original sound to the resynthesised sound"/> + tooltip="Previews the difference of the resynthesised and the original version"/> + height="24.0" parentComponent="BG" text="settings" itemColour="4294967295"/> + height="22.0" style="Horizontal" text="Volume" mode="Decibel" + min="-100.0" max="0.0" stepSize="0.1000000014901161" suffix=" dB" + middlePosition="-18.0" itemColour="1728053247" showTextBox="0"/> + itemColour="4278422015"/> - - + + + + text="D:\Development\Projekte\wavetabletest\Samples\jaw_m"/> + max="5" height="20.0" items="LYRE growl_bass jaw_harp piano_trumpet_vibra violin_musicbox" + width="188.0" visible="0"/> + height="20.0"/> + height="20.0" items="All Samples {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_60_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_62_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_64_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_66_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_68_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_70_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_72_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_74_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_76_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_78_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_80_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_82_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_84_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_86_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_88_101_127_MID.wav {PROJECT_FOLDER}dorian_test/Temp Samples for Christoph/Lyre/LYR_90_101_127_MID.wav" + width="188.0" saveInPreset="0" max="17" visible="0"/> diff --git a/Loris Toolbox/project_info.xml b/Loris Toolbox/project_info.xml index 09f10a3..710ddd6 100644 --- a/Loris Toolbox/project_info.xml +++ b/Loris Toolbox/project_info.xml @@ -35,4 +35,5 @@ + diff --git a/loris_library/Source/Helpers.cpp b/loris_library/Source/Helpers.cpp index 61be316..ff9ea0e 100644 --- a/loris_library/Source/Helpers.cpp +++ b/loris_library/Source/Helpers.cpp @@ -17,6 +17,7 @@ #include "Helpers.h" #include "LorisState.h" + namespace loris2hise { @@ -60,7 +61,7 @@ void Options::initLorisParameters() ampfloor = analyzer_getAmpFloor(); sidelobes = analyzer_getSidelobeLevel(); bwregionwidth = analyzer_getBwRegionWidth(); - windowwidth = analyzer_getWindowWidth(); + windowwidth = 1.0; enablecache = false; } @@ -89,8 +90,7 @@ bool Options::update(const juce::Identifier& id, const juce::var& value) if (id == OptionIds::croptime) { croptime = (double)value; if (initialised) analyzer_setCropTime(croptime); return true; } if (id == OptionIds::bwregionwidth) { bwregionwidth = (double)value; if (initialised) analyzer_setBwRegionWidth(bwregionwidth); return true; } if (id == OptionIds::enablecache) { enablecache = (bool)value; return true; } - if (id == OptionIds::windowwidth) { windowwidth = (double)value; } - if (id == OptionIds::windowwidth) { windowwidth = (double)value; } + if (id == OptionIds::windowwidth) { windowwidth = jlimit(0.125, 4.0, (double)value); return true; } throw juce::Result::fail("Invalid option: " + id.toString()); } diff --git a/loris_library/Source/Helpers.h b/loris_library/Source/Helpers.h index 8bbb418..49e0e32 100644 --- a/loris_library/Source/Helpers.h +++ b/loris_library/Source/Helpers.h @@ -17,8 +17,12 @@ #pragma once + + + #include "src/loris.h" +#include "ThreadController.h" #include "src/Breakpoint.h" #include "src/PartialUtils.h" @@ -29,6 +33,8 @@ namespace loris2hise using namespace juce; + + enum class TimeDomainType { Seconds, @@ -93,8 +99,12 @@ struct Options /** If this is true, it will also call the loris_setXXX methods. */ bool initialised = false; + + hise::ThreadController* threadController = nullptr; }; + + /** This struct will be used as argument for the custom function. */ struct CustomFunctionArgs { diff --git a/loris_library/Source/LorisState.cpp b/loris_library/Source/LorisState.cpp index cb816aa..ef5866f 100644 --- a/loris_library/Source/LorisState.cpp +++ b/loris_library/Source/LorisState.cpp @@ -89,10 +89,12 @@ bool LorisState::analyse(const juce::File& audioFile, double rootFrequency) auto driftFactor = std::pow(2.0, currentOption.freqdrift / 1200.0); - analyzer_configure(rootFrequency * 0.8, rootFrequency); + analyzer_configure(rootFrequency * 0.8, rootFrequency * currentOption.windowwidth, currentOption.threadController); //analyzer_setWindowWidth(rootFrequency * currentOption.windowwidth); analyzer_setFreqDrift(rootFrequency * 0.25); + analyzer_storeNoBandwidth(); + if (!currentOption.initialised) { currentOption.initLorisParameters(); @@ -121,15 +123,18 @@ bool LorisState::analyse(const juce::File& audioFile, double rootFrequency) for (int c = 0; c < bf.getNumChannels(); c++) { - for (int i = 0; i < bf.getNumSamples(); i++) + if (auto s = hise::ThreadController::ScopedStepScaler(currentOption.threadController, c, bf.getNumChannels())) { - buffer[i] = bf.getSample(c, i); - } + if (!s) + return false; - auto list = newEntry->get(c); + for (int i = 0; i < bf.getNumSamples(); i++) + buffer[i] = bf.getSample(c, i); - analyze(buffer, bf.getNumSamples(), r->sampleRate, list); + auto list = newEntry->get(c); + analyze(buffer, bf.getNumSamples(), r->sampleRate, list); + } } newEntry->saveAsOriginal(); diff --git a/loris_library/Source/LorisState.h b/loris_library/Source/LorisState.h index 21621c4..cbea30a 100644 --- a/loris_library/Source/LorisState.h +++ b/loris_library/Source/LorisState.h @@ -69,6 +69,11 @@ struct LorisState return {}; } + void setThreadController(hise::ThreadController* tc) + { + currentOption.threadController = tc; + } + private: friend class Helpers; diff --git a/loris_library/Source/MultichannelPartialList.cpp b/loris_library/Source/MultichannelPartialList.cpp index 8b50e85..a46d10e 100644 --- a/loris_library/Source/MultichannelPartialList.cpp +++ b/loris_library/Source/MultichannelPartialList.cpp @@ -16,6 +16,7 @@ */ #include "MultichannelPartialList.h" +#include "src/Resampler.h" namespace loris2hise { using namespace juce; @@ -338,39 +339,112 @@ juce::AudioSampleBuffer MultichannelPartialList::synthesize() return output; } -void MultichannelPartialList::prepareToMorph(bool removeUnlabeled) +bool MultichannelPartialList::prepareToMorph(bool removeUnlabeled) { if(preparedForMorph) - return; + return true; Helpers::logMessage("Prepare partial list for morphing"); - for(auto p: list) - { - auto l = std::pow(2.0, -1.0 * options.freqdrift / 1200.0); - auto h = std::pow(2.0, options.freqdrift / 1200.0); + if (auto s = hise::ThreadController::ScopedStepScaler(options.threadController, 0, 5)) + { + for (auto p : list) + { + if (auto s2 = hise::ThreadController::ScopedStepScaler(options.threadController, list.indexOf(p), list.size())) + { + auto l = std::pow(2.0, -1.0 * options.freqdrift / 1200.0); + auto h = std::pow(2.0, options.freqdrift / 1200.0); + + LinearEnvelope* env; + + { + hise::ThreadController::ScopedRangeScaler s(options.threadController, 0.0, 0.5); + env = createF0Estimate(p, rootFrequency * l, rootFrequency * h, options.hoptime * 10.0, options.threadController); + } + + + { + hise::ThreadController::ScopedRangeScaler s(options.threadController, 0.5, 1.0); + channelize(p, env, 1); + } + + destroyLinearEnvelope(env); + } + + } + } - LinearEnvelope* env = createF0Estimate(p, rootFrequency * l, rootFrequency * h, options.hoptime * 10.0); - - channelize(p, env, 1); - destroyLinearEnvelope(env); - } - - for(auto p: list) - { - collate(p); - - - sift(p); - distill(p); - - if(removeUnlabeled) - removeLabeled(p, 0); - - sortByLabel(p); - } - preparedForMorph = true; + + if (auto s = hise::ThreadController::ScopedRangeScaler(options.threadController, 0.5, 1.0)) + { + int idx = 0; + int length = list.size(); + + for (auto p : list) + { + if (auto ls = hise::ThreadController::ScopedStepScaler(options.threadController, idx++, length)) + { + hise::ThreadController::ScopedStepScaler(options.threadController, 0, 5); + collate(p); + + if (!options.threadController) + return false; + + hise::ThreadController::ScopedStepScaler(options.threadController, 1, 5); + sortByLabel(p); + + if (!options.threadController) + return false; + + hise::ThreadController::ScopedStepScaler(options.threadController, 2, 5); + sift(p); + + if (!options.threadController) + return false; + + hise::ThreadController::ScopedStepScaler(options.threadController, 3, 5); + distill(p); + + if (!options.threadController) + return false; + + if (removeUnlabeled) + removeLabeled(p, 0); + + if (!options.threadController) + return false; + + hise::ThreadController::ScopedStepScaler(options.threadController, 4, 5); + sortByLabel(p); + + if (!options.threadController) + return false; + + // better to compute this only once: + const double OneOverSrate = 1. / sampleRate; + + // use a Resampler to quantize the Breakpoint times and + // correct the phases: + Loris::Resampler quantizer(options.hoptime); + quantizer.setPhaseCorrect(true); + + for (auto& pr : *p) + { + if (!options.threadController) + return false; + + quantizer.quantize(pr); + } + } + } + + preparedForMorph = true; + return true; + } + else + return false; + } juce::AudioSampleBuffer MultichannelPartialList::renderEnvelope(const juce::Identifier ¶meter, int partialIndex) @@ -391,7 +465,7 @@ juce::AudioSampleBuffer MultichannelPartialList::renderEnvelope(const juce::Iden { const var hoptimeSamples = options.hoptime * sampleRate; - LinearEnvelope* env = createF0Estimate(pl, rootFrequency * (1.0 + options.freqdrift), rootFrequency / (1.0 + options.freqdrift), options.hoptime * 4.0); + LinearEnvelope* env = createF0Estimate(pl, rootFrequency * (1.0 + options.freqdrift), rootFrequency / (1.0 + options.freqdrift), options.hoptime * 4.0, options.threadController); for(int i = 0; i < b.getNumSamples(); i++) b.setSample(c, i, linearEnvelope_valueAt(env, i / sampleRate) / rootFrequency); @@ -465,10 +539,13 @@ bool MultichannelPartialList::createSnapshot(const juce::Identifier ¶meter, numChannels = getNumChannels(); - - - prepareToMorph(true); - + if (auto s = hise::ThreadController::ScopedRangeScaler(options.threadController, 0.0, 0.5)) + { + prepareToMorph(true); + } + + hise::ThreadController::ScopedRangeScaler s2(options.threadController, 0.5, 1.0); + std::function vf; if(parameter == ParameterIds::phase) @@ -484,21 +561,44 @@ bool MultichannelPartialList::createSnapshot(const juce::Identifier ¶meter, int numMaxHarmonics = 0; - for(auto& pl: list) - numMaxHarmonics = jmax(numMaxHarmonics, pl->size()); + for (auto& pl : list) + { + for (const auto& part : *pl) + { + numMaxHarmonics = jmax(numMaxHarmonics, part.label()-1); + } + } + + FloatVectorOperations::clear(buffer, numMaxHarmonics); + + int channelOffset = 0; - for(auto& pl: list) + int idx = 0; + int length = (double)list.size(); + + auto tc = options.threadController; + + for(auto& pl: list) { int thisNum = 0; - - for (auto& p : *pl) + + if (auto s = hise::ThreadController::ScopedStepScaler(tc, idx++, length)) { - *buffer++ = vf(&p, timeToUse); - thisNum++; + for (auto& p : *pl) + { + auto labelIndex = p.label() - 1; + + if (tc != nullptr) + tc->setProgress((double)labelIndex / (double)numMaxHarmonics); + + jassert(isPositiveAndBelow(labelIndex, numMaxHarmonics + 1)); + buffer[channelOffset + labelIndex] = vf(&p, timeToUse); + } + + channelOffset += numMaxHarmonics; } - - for(int i = thisNum; i < numMaxHarmonics; i++) - *buffer++ = 0.0f; + else + return false; } numHarmonics = numMaxHarmonics; diff --git a/loris_library/Source/MultichannelPartialList.h b/loris_library/Source/MultichannelPartialList.h index 176efa0..f63b06c 100644 --- a/loris_library/Source/MultichannelPartialList.h +++ b/loris_library/Source/MultichannelPartialList.h @@ -106,7 +106,7 @@ struct MultichannelPartialList /** @internal */ void saveAsOriginal(); - void prepareToMorph(bool removeUnlabeled=false); + bool prepareToMorph(bool removeUnlabeled=false); private: diff --git a/loris_library/Source/ThreadController.h b/loris_library/Source/ThreadController.h new file mode 100644 index 0000000..d03c832 --- /dev/null +++ b/loris_library/Source/ThreadController.h @@ -0,0 +1,165 @@ +/* + * This file is part of the HISE loris_library codebase (https://github.com/christophhart/loris-tools). + * Copyright (c) 2023 Christoph Hart + * + * 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, version 3. + * + * 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, see . + */ + +#pragma once + +#include + +namespace hise +{ + using namespace juce; + + /** A minimal POD that can be used to check the thread state across DLL boundaries. */ + class ThreadController : public ReferenceCountedObject + { + struct Scaler + { + Scaler(bool isStep_ = false) : + isStep(isStep_) + {} + + double getScaledProgress(double input) const + { + if (isStep) + return (v1 + input) / v2; + else + return v1 + (v2 - v1) * input; + } + + bool isStep = false; + double v1 = 0.0; + double v2 = 0.0; + }; + + template struct ScopedScaler + { + template ScopedScaler(ThreadController* parent_, T v1, T v2) : parent(parent_) + { + Scaler s(IsStep); + s.v1 = (double)v1; + s.v2 = (double)v2; + + if (parent != nullptr) + parent->pushProgressScaler(s); + }; + + ~ScopedScaler() + { + if (parent != nullptr) + parent->popProgressScaler(); + }; + + operator bool() const { return parent; } + ThreadController* parent; + }; + + public: + + using Ptr = ReferenceCountedObjectPtr; + using ScopedRangeScaler = ScopedScaler; + using ScopedStepScaler = ScopedScaler; + + ThreadController(Thread* t, double* p, int timeoutMs, uint32& lastTime_) : + juceThreadPointer(t), + progress(p), + timeout(timeoutMs), + lastTime(&lastTime_) + {}; + + ThreadController() : + juceThreadPointer(nullptr), + progress(nullptr), + lastTime(nullptr) + {}; + + operator bool() const + { + if (juceThreadPointer == nullptr) + return false; + + auto thisTime = Time::getMillisecondCounter(); + + if (lastTime != nullptr && *lastTime != 0 && thisTime - *lastTime > timeout) + { + // If this hits, it means that the timeout you've set is too low. + // Either increase the timeout or add more checks in between... + int x = 5; + + // prevent the jassert above to mess up subsequent timeouts... + thisTime = Time::getMillisecondCounter(); + } + + if (lastTime != nullptr) + *lastTime = thisTime; + + return !static_cast(juceThreadPointer)->threadShouldExit(); + } + + /** Allow a bigger time between calls. */ + void extendTimeout(uint32 milliSeconds) + { + if (lastTime != nullptr) + *lastTime += milliSeconds; + } + + + /** Set a progress. If you want to add a scaler to the progress (for indicating a subprocess, use either ScopedStepScaler or ScopedRangeScalers). */ + bool setProgress(double p) + { + if (progress == nullptr) + return true; + + for (int i = progressScalerIndex-1; i >= 0; i--) + { + p = jlimit(0.0, 1.0, progressScalers[i].getScaledProgress(p)); + } + + // If this hits, you might have forgot a scaler in the call stack... + jassert(*progress <= p); + + *progress = p; + + return *this; + } + + private: + + static constexpr int NumProgressScalers = 32; + + void pushProgressScaler(const Scaler& f) + { + progressScalers[progressScalerIndex++] = f; + jassert(isPositiveAndBelow(progressScalerIndex, NumProgressScalers)); + setProgress(0.0); + } + + void popProgressScaler() + { + progressScalers[progressScalerIndex--] = {}; + jassert(progressScalerIndex >= 0); + } + + void* juceThreadPointer = nullptr; + double* progress = nullptr; + mutable uint32* lastTime = nullptr; + uint32 timeout = 0; + int progressScalerIndex = 0; + Scaler progressScalers[NumProgressScalers]; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ThreadController); + }; +} \ No newline at end of file diff --git a/loris_library/Source/public.cpp b/loris_library/Source/public.cpp index c03dc9b..6c8b6ca 100644 --- a/loris_library/Source/public.cpp +++ b/loris_library/Source/public.cpp @@ -257,3 +257,12 @@ const char* getLastError(void* state) } } + +DLL_EXPORT void setThreadController(void* state, void* t) +{ + if (auto typed = ((loris2hise::LorisState*)state)) + { + if(auto tc = static_cast(t)) + typed->setThreadController(tc); + } +} diff --git a/loris_library/Source/public.h b/loris_library/Source/public.h index 257d42d..431fd21 100644 --- a/loris_library/Source/public.h +++ b/loris_library/Source/public.h @@ -104,12 +104,6 @@ DLL_EXPORT bool loris_create_envelope(void* state, const char* file, const char* */ DLL_EXPORT bool loris_snapshot(void* state, const char* file, double time, const char* parameter, double* buffer, int& numChannels, int& numHarmonics); -#if LATER -/** Sets a pointer to a progress value that will be updated inside the process call. Make sure to reset this to nullptr before the target object is destroyed! -*/ -DLL_EXPORT void setProgressCounter(void* state, double* progress); -#endif - /** Prepares an audio file for morphing. If removeUnlabeled is true, then all non-labeled partials will be removed. */ DLL_EXPORT bool loris_prepare(void* state, const char* file, bool removeUnlabeled); @@ -119,4 +113,7 @@ DLL_EXPORT void getIdList(char* buffer, int maxlen, bool getOptions); DLL_EXPORT const char* getLastError(void* state); +DLL_EXPORT void setThreadController(void* state, void* t); + + } // extern "C" diff --git a/loris_library/Source/src/Analyzer.cpp b/loris_library/Source/src/Analyzer.cpp index 7ed7469..89b2ab9 100644 --- a/loris_library/Source/src/Analyzer.cpp +++ b/loris_library/Source/src/Analyzer.cpp @@ -656,9 +656,16 @@ Analyzer::analyze( const double * bufBegin, const double * bufEnd, double srate, // loop over short-time analysis frames: while ( winMiddle < bufEnd ) { + + // compute the time of this analysis frame: const double currentFrameTime = long(winMiddle - bufBegin) / srate; + const double totalLength = (double)(bufEnd - bufBegin) / srate; + + if (threadController != nullptr && !threadController->setProgress(currentFrameTime / totalLength)) + throw Exception("cancelled"); + // compute reassigned spectrum: // sampsBegin is the position of the first sample to be transformed, // sampsEnd is the position after the last sample to be transformed. diff --git a/loris_library/Source/src/Analyzer.h b/loris_library/Source/src/Analyzer.h index 6bdfe52..17b36ed 100644 --- a/loris_library/Source/src/Analyzer.h +++ b/loris_library/Source/src/Analyzer.h @@ -34,6 +34,7 @@ */ #include #include +#include "../ThreadController.h" #include "LinearEnvelope.h" #include "Partial.h" #include "PartialList.h" @@ -515,6 +516,8 @@ class Analyzer // -- private member variables -- + hise::ThreadController* threadController = nullptr; + private: std::auto_ptr< Envelope > m_freqResolutionEnv; diff --git a/loris_library/Source/src/Fundamental.cpp b/loris_library/Source/src/Fundamental.cpp index cddb226..b1c2e8c 100644 --- a/loris_library/Source/src/Fundamental.cpp +++ b/loris_library/Source/src/Fundamental.cpp @@ -599,9 +599,14 @@ FundamentalFromPartials::buildEnvelope( PartialList::const_iterator begin_partia std::vector< double > amplitudes, frequencies; + + double time = tbeg; while ( time < tend ) { + if (!controller->setProgress((time - tbeg) / (tend - tbeg))) + return env; + collectFreqsAndAmps( begin_partials, end_partials, frequencies, amplitudes, time ); if (! amplitudes.empty() ) diff --git a/loris_library/Source/src/Fundamental.h b/loris_library/Source/src/Fundamental.h index 5971408..1865576 100644 --- a/loris_library/Source/src/Fundamental.h +++ b/loris_library/Source/src/Fundamental.h @@ -36,6 +36,7 @@ * */ +#include "../ThreadController.h" #include "LinearEnvelope.h" #include "PartialList.h" #include "F0Estimate.h" @@ -699,7 +700,7 @@ class FundamentalFromPartials : public FundamentalEstimator lowerFreqBound, upperFreqBound ); } - + hise::ThreadController* controller = nullptr; // -- private auxiliary functions -- diff --git a/loris_library/Source/src/loris.h b/loris_library/Source/src/loris.h index 26d0d15..3e1d117 100644 --- a/loris_library/Source/src/loris.h +++ b/loris_library/Source/src/loris.h @@ -48,7 +48,7 @@ * http://www.cerlsoundgroup.org/Loris/ * */ - + /* ---------------------------------------------------------------- */ /* Version /* @@ -136,7 +136,7 @@ void analyze( const double * buffer, unsigned int bufferSize, PartialList. */ -void analyzer_configure( double resolution, double windowWidth ); +void analyzer_configure( double resolution, double windowWidth, void* tc); /* Configure the sole Analyzer instance with the specified frequency resolution (minimum instantaneous frequency difference between Partials). All other Analyzer parameters @@ -550,7 +550,7 @@ createFreqReference( PartialList * partials, LinearEnvelope * createF0Estimate( PartialList * partials, double minFreq, double maxFreq, - double interval ); + double interval, void* threadController); /* Return a newly-constructed LinearEnvelope that estimates the time-varying fundamental frequency of the sound represented by the Partials in a PartialList. This uses diff --git a/loris_library/Source/src/lorisAnalyzer_pi.cpp b/loris_library/Source/src/lorisAnalyzer_pi.cpp index 9cafb05..192064f 100644 --- a/loris_library/Source/src/lorisAnalyzer_pi.cpp +++ b/loris_library/Source/src/lorisAnalyzer_pi.cpp @@ -97,7 +97,7 @@ static Analyzer * ptr_instance = 0; any of the other analyzer operations can be performed. */ extern "C" -void analyzer_configure( double resolution, double windowWidth ) +void analyzer_configure( double resolution, double windowWidth, void* tc) { try { @@ -111,6 +111,12 @@ void analyzer_configure( double resolution, double windowWidth ) debugger << "configuring Analyzer" << endl; ptr_instance->configure( resolution, windowWidth ); } + + if (tc != nullptr) + { + ptr_instance->threadController = static_cast(tc); + } + } catch( Exception & ex ) { diff --git a/loris_library/Source/src/lorisNonObj_pi.cpp b/loris_library/Source/src/lorisNonObj_pi.cpp index 605d19c..0a9f55d 100644 --- a/loris_library/Source/src/lorisNonObj_pi.cpp +++ b/loris_library/Source/src/lorisNonObj_pi.cpp @@ -267,7 +267,7 @@ createFreqReference( PartialList * partials, double minFreq, double maxFreq, extern "C" LinearEnvelope * createF0Estimate( PartialList * partials, double minFreq, double maxFreq, - double interval ) + double interval, void* threadController) { try { @@ -276,6 +276,7 @@ createF0Estimate( PartialList * partials, double minFreq, double maxFreq, const double Precision = 0.1; const double Confidence = 0.9; FundamentalFromPartials est( Precision ); + est.controller = static_cast(threadController); std::pair< double, double > span = PartialUtils::timeSpan( partials->begin(), partials->end() ); diff --git a/loris_library/loris_library.jucer b/loris_library/loris_library.jucer index ac0ef93..76aee81 100644 --- a/loris_library/loris_library.jucer +++ b/loris_library/loris_library.jucer @@ -1,7 +1,7 @@