diff --git a/.gitignore b/.gitignore index 39baf0d..c60f129 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ loris_library_win64.zip tmpFile Loris Toolbox/Binaries/Builds Loris Toolbox/Binaries +loris_library/docs/html diff --git a/loris_library/Source/Helpers.cpp b/loris_library/Source/Helpers.cpp index 3f248d8..c10e967 100644 --- a/loris_library/Source/Helpers.cpp +++ b/loris_library/Source/Helpers.cpp @@ -57,7 +57,6 @@ void Options::initLorisParameters() hoptime = analyzer_getHopTime(); croptime = analyzer_getCropTime(); freqfloor = analyzer_getFreqFloor(); - freqdrift = analyzer_getFreqDrift(); ampfloor = analyzer_getAmpFloor(); sidelobes = analyzer_getSidelobeLevel(); bwregionwidth = analyzer_getBwRegionWidth(); @@ -81,12 +80,11 @@ bool Options::update(const juce::Identifier& id, const juce::var& value) return true; } - return true; - if (id == OptionIds::freqfloor) { freqfloor = (double)value; analyzer_setFreqFloor(freqfloor); return true; } if (id == OptionIds::ampfloor) { ampfloor = (double)value; analyzer_setAmpFloor(ampfloor); return true; } if (id == OptionIds::sidelobes) { sidelobes = (double)value; analyzer_setSidelobeLevel(sidelobes); return true; } - if (id == OptionIds::freqdrift) { freqdrift = (double)value; analyzer_setFreqDrift(freqdrift); return true; } + if (id == OptionIds::freqdrift) { freqdrift = (double)value; + return true; } if (id == OptionIds::hoptime) { hoptime = (double)value; analyzer_setHopTime(hoptime); return true; } if (id == OptionIds::croptime) { croptime = (double)value; analyzer_setCropTime(croptime); return true; } if (id == OptionIds::bwregionwidth) { bwregionwidth = (double)value; analyzer_setBwRegionWidth(bwregionwidth); return true; } @@ -109,4 +107,4 @@ void Helpers::logMessage(const char* msg) LorisState::getCurrentInstance()->messages.add(juce::String(msg)); } -} \ No newline at end of file +} diff --git a/loris_library/Source/Helpers.h b/loris_library/Source/Helpers.h index 07608c2..43b3090 100644 --- a/loris_library/Source/Helpers.h +++ b/loris_library/Source/Helpers.h @@ -38,93 +38,129 @@ enum class TimeDomainType numTimeDomainTypes }; +/** The configuration settings for the LorisState. You can change these using loris_config(). */ struct Options { + /** Creates a JSON version of this object. */ var toJSON() const; + /** Returns all options for the timedomain property. Currently: `["seconds", "samples", "0oto1"]`*/ static StringArray getTimeDomainOptions(); + /** @internal: init the values. */ void initLorisParameters(); + /** updates the configuration of the given property with the value. */ bool update(const Identifier& id, const var& value); + /** The timedomain used for all time (x-axis) values. + + By default, loris uses seconds for all time values, but you can change it for more convenient + calculations. The possible options are: + + - "seconds" (default): the time in seconds (not milliseconds!) + - "samples": the time in samples depending on the sample rate of the file + - "0to1": the time in a normalized version from 0.0 (start of file) to 1.0 (end of file). + */ TimeDomainType currentDomainType = TimeDomainType::Seconds; - double freqfloor; + + /** The lowest frequency that is considered as harmonic content. */ + double freqfloor = 40.0; + + /** The lowest amplitude that is considered relevant, amplitudes below that value are considered below the noise floor. */ double ampfloor = 90.0; + + /** The gain of the side lobes of the analysis window. */ double sidelobes = 90.0; - double freqdrift; + + /** The max frequency drift that can occur in the sample (in cent). This defines a tolerance of pitch variations. */ + double freqdrift = 50.0; + + /** The time between analysis windows. */ double hoptime = 0.0129; + + /** the time between I don't know. */ double croptime = 0.0129; + + /** ??? */ double bwregionwidth = 1.0; + + /** Enables caching of the input file. If this is true, then analyze calls to previously analyzed files are skipped. */ bool enablecache = true; + + /** The window width scale factor. */ double windowwidth = 1.0; }; -struct Helpers +/** This struct will be used as argument for the custom function. */ +struct CustomFunctionArgs { - struct FunctionPOD - { - // Constants - int channelIndex = 0; - int partialIndex = 0; - double sampleRate = 44100.0; - double rootFrequency = 0.0; - void* obj = nullptr; - - // Variable properties - double time = 0.0; - double frequency = 0.0; - double phase = 0.0; - double gain = 1.0; - double bandwidth = 0.0; - - }; - - struct CustomFunctionArgs - { - CustomFunctionArgs(void* obj_, const Breakpoint& b, int channelIndex_, - int partialIndex_, - double sampleRate_, - double time_, - double rootFrequency_) : - channelIndex(channelIndex_), - partialIndex(partialIndex_), - sampleRate(sampleRate_), - rootFrequency(rootFrequency_), - obj(obj_), - time(time_) - { - static_assert(sizeof(CustomFunctionArgs) == sizeof(FunctionPOD), "not the same size"); - - frequency = b.frequency(); - phase = b.phase(); - gain = b.amplitude(); - bandwidth = b.bandwidth(); - }; - - // Constants - const int channelIndex = 0; - const int partialIndex = 0; - const double sampleRate = 44100.0; - const double rootFrequency = 0.0; - void* obj = nullptr; - - // Variable properties - double time = 0.0; - double frequency = 0.0; - double phase = 0.0; - double gain = 1.0; - double bandwidth = 0.0; - - - }; - - using CustomFunctionType = bool(*)(CustomFunctionArgs&); - using CustomFunction = std::function; + /** The function pointer type that is passed into loris_process_custom(). */ + using FunctionType = bool(*)(CustomFunctionArgs&); + + /** @internal The function object alias. */ + using Function = std::function; + + /** Creates a function args from the breakpoint, the channel index and the LorisState context. */ + CustomFunctionArgs(void* obj_, const Loris::Breakpoint& b, int channelIndex_, + int partialIndex_, + double sampleRate_, + double time_, + double rootFrequency_) : + channelIndex(channelIndex_), + partialIndex(partialIndex_), + sampleRate(sampleRate_), + rootFrequency(rootFrequency_), + obj(obj_), + time(time_) + { + frequency = b.frequency(); + phase = b.phase(); + gain = b.amplitude(); + bandwidth = b.bandwidth(); + }; + + // Constants ======================================== + + /** The channel in the supplied audio file. */ + int channelIndex = 0; + + /** The index of the partial. */ + int partialIndex = 0; + + /** The sample rate of the file. */ + double sampleRate = 44100.0; + + /** the root frequency that was passed into loris_analyze. */ + double rootFrequency = 0.0; + + /** @internal: a pointer to the state context. */ + void* obj = nullptr; + + // Variable properties ============================== + + /** The time of the breakpoint. The domain depends on the `timedomain` configuration option. */ + double time = 0.0; + + /** The frequency of partial at the breakpoint in Hz. */ + double frequency = 0.0; + + /** The phase of the the partial at the breakpoint in radians (0 ... 2*PI). */ + double phase = 0.0; + + /** The amplitude of the partial. */ + double gain = 1.0; + + /** The "noisiness" of the partial at the given breakpoint. (0.0 = pure sinusoidal, 1.0 = full noise). */ + double bandwidth = 0.0; +}; +/** @internal Some helper functions. */ +struct Helpers +{ static void reportError(const char* msg); static void logMessage(const char* msg); }; -} \ No newline at end of file +} diff --git a/loris_library/Source/LorisState.cpp b/loris_library/Source/LorisState.cpp index 23b0f0e..194b941 100644 --- a/loris_library/Source/LorisState.cpp +++ b/loris_library/Source/LorisState.cpp @@ -50,7 +50,16 @@ void LorisState::resetState(void* state) ((LorisState*)state)->lastError = juce::Result::ok(); } +MultichannelPartialList* LorisState::getExisting(const File& f) +{ + for (auto af : analysedFiles) + { + if (af->matches(f)) + return af; + } + return nullptr; +} void LorisState::reportError(const char* msg) { @@ -78,9 +87,11 @@ bool LorisState::analyse(const juce::File& audioFile, double rootFrequency) juce::AudioFormatManager m; m.registerBasicFormats(); + auto driftFactor = std::pow(2.0, currentOption.freqdrift / 1200.0); + analyzer_configure(rootFrequency * 0.8, rootFrequency); //analyzer_setWindowWidth(rootFrequency * currentOption.windowwidth); - analyzer_setFreqDrift(0.2 * rootFrequency); + analyzer_setFreqDrift(rootFrequency * 0.25); currentOption.initLorisParameters(); @@ -115,7 +126,8 @@ bool LorisState::analyse(const juce::File& audioFile, double rootFrequency) } newEntry->saveAsOriginal(); - + newEntry->prepareToMorph(); + analysedFiles.add(newEntry); messages.add("... Analysed OK"); @@ -125,6 +137,16 @@ bool LorisState::analyse(const juce::File& audioFile, double rootFrequency) return false; } +double LorisState::getOption(const juce::Identifier &id) const +{ + juce::String msg; + msg << "Get option " << id; + + Helpers::logMessage(msg.getCharPointer().getAddress()); + + return (double)currentOption.toJSON()[id]; +} + bool LorisState::setOption(const juce::Identifier& id, const juce::var& data) { juce::String msg; @@ -154,4 +176,7 @@ bool LorisState::setOption(const juce::Identifier& id, const juce::var& data) return true; } -} \ No newline at end of file + + + +} diff --git a/loris_library/Source/LorisState.h b/loris_library/Source/LorisState.h index 19b1dd3..21621c4 100644 --- a/loris_library/Source/LorisState.h +++ b/loris_library/Source/LorisState.h @@ -22,7 +22,7 @@ #include "Helpers.h" #include "MultichannelPartialList.h" -#include +#include "JuceHeader.h" namespace loris2hise { using namespace juce; @@ -34,35 +34,56 @@ using namespace juce; */ struct LorisState { - - - LorisState(); - - ~LorisState(); - - static LorisState* getCurrentInstance(bool forceCreate = false); - - static void resetState(void* state); - - void reportError(const char* msg); - - bool analyse(const juce::File& audioFile, double rootFrequency); - - bool setOption(const juce::Identifier& id, const juce::var& data); - - Options currentOption; - - juce::Result lastError; + LorisState(); + + ~LorisState(); + + static LorisState* getCurrentInstance(bool forceCreate = false); + + static void resetState(void* state); + + void reportError(const char* msg); + + bool analyse(const juce::File& audioFile, double rootFrequency); + + bool setOption(const juce::Identifier& id, const juce::var& data); + + double getOption(const juce::Identifier& id) const; + + MultichannelPartialList* getExisting(const File& f); + + const char* getLastError() const + { + return lastError.getErrorMessage().getCharPointer().getAddress(); + } + + String getLastMessage() + { + if(!messages.isEmpty()) + { + auto lastMessage = messages[messages.size()-1]; + messages.remove(messages.size()-1); + return lastMessage; + } + + return {}; + } + +private: - juce::OwnedArray analysedFiles; + friend class Helpers; + + Options currentOption; - juce::StringArray messages; + juce::Result lastError; -private: + juce::OwnedArray analysedFiles; + juce::StringArray messages; + static LorisState* currentInstance; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LorisState); }; -} \ No newline at end of file +} diff --git a/loris_library/Source/MultichannelPartialList.cpp b/loris_library/Source/MultichannelPartialList.cpp index 92adea0..876707f 100644 --- a/loris_library/Source/MultichannelPartialList.cpp +++ b/loris_library/Source/MultichannelPartialList.cpp @@ -132,7 +132,7 @@ void MultichannelPartialList::checkArgs(bool condition, const juce::String& erro } } -bool MultichannelPartialList::processCustom(void* obj, const Helpers::CustomFunction& f) +bool MultichannelPartialList::processCustom(void* obj, const CustomFunctionArgs::Function& f) { int channelIndex = 0; @@ -147,9 +147,9 @@ bool MultichannelPartialList::processCustom(void* obj, const Helpers::CustomFunc auto t = convertSecondsToTime(iter.time()); auto& b = iter.breakpoint(); - Helpers::CustomFunctionArgs a(obj, b, channelIndex, partialIndex, sampleRate, t, rootFrequency); + CustomFunctionArgs a(obj, b, channelIndex, partialIndex, sampleRate, t, rootFrequency); - if (f(a)) + if (f(a)) return true; iter.time(); @@ -168,6 +168,7 @@ bool MultichannelPartialList::processCustom(void* obj, const Helpers::CustomFunc return false; } + bool MultichannelPartialList::process(const juce::Identifier& command, const juce::var& data) { juce::String msg; @@ -337,6 +338,168 @@ juce::AudioSampleBuffer MultichannelPartialList::synthesize() return output; } +void MultichannelPartialList::prepareToMorph(bool removeUnlabeled) +{ + if(preparedForMorph) + return; + + Helpers::logMessage("Prepare partial list for morphing"); + + for(auto p: list) + { + LinearEnvelope* env = createF0Estimate(p, rootFrequency * (1.0 + options.freqdrift), rootFrequency / (1.0 + options.freqdrift), 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; +} + +juce::AudioSampleBuffer MultichannelPartialList::renderEnvelope(const juce::Identifier ¶meter, int partialIndex) +{ + AudioSampleBuffer b(getNumChannels(), getNumSamples()); + b.clear(); + + if(parameter == ParameterIds::rootFrequency) + { + for(auto e: rootFrequencyEnvelopes) + destroyLinearEnvelope(e); + + rootFrequencyEnvelopes.clear(); + + int c = 0; + + for(auto pl: list) + { + const var hoptimeSamples = options.hoptime * sampleRate; + + LinearEnvelope* env = createF0Estimate(pl, rootFrequency * (1.0 + options.freqdrift), rootFrequency / (1.0 + options.freqdrift), options.hoptime * 4.0); + + for(int i = 0; i < b.getNumSamples(); i++) + b.setSample(c, i, linearEnvelope_valueAt(env, i / sampleRate) / rootFrequency); + + rootFrequencyEnvelopes.add(env); + c++; + } + } + else + { + prepareToMorph(); + + int c = 0; + for(auto pl: list) + { + bool found = false; + + for(auto p: *pl) + { + if(partialIndex == (p.label() - 1)) + { + found = true; + + const int sampleDelta = options.hoptime * sampleRate; + SmoothedValue ramp; + std::function vf; + + ramp.reset(sampleRate, options.hoptime); + + if(parameter == ParameterIds::phase) + vf = partial_phaseAt; + if(parameter == ParameterIds::frequency) + vf = partial_frequencyAt; + if(parameter == ParameterIds::gain) + vf = partial_amplitudeAt; + if(parameter == ParameterIds::bandwidth) + vf = partial_bandwidthAt; + + for(int i = 0; i < getNumSamples(); i++) + { + if(i % sampleDelta == 0) + { + auto t = (double)i / sampleRate; + ramp.setTargetValue((float)vf(&p, t)); + } + + b.setSample(c, i, ramp.getNextValue()); + } + } + } + + if(!found) + { + String msg; + msg <<"Can't find partial with label " << String(partialIndex); + Helpers::reportError(msg.getCharPointer().getAddress()); + } + } + + c++; + } + + + + return b; +} + +bool MultichannelPartialList::createSnapshot(const juce::Identifier ¶meter, double timeSeconds, double *buffer, int& numChannels, int &numHarmonics) +{ + auto timeToUse = convertSecondsToTime(timeSeconds); + + numChannels = getNumChannels(); + + + + prepareToMorph(); + + std::function vf; + + if(parameter == ParameterIds::phase) + vf = partial_phaseAt; + if(parameter == ParameterIds::frequency) + vf = partial_frequencyAt; + if(parameter == ParameterIds::gain) + vf = partial_amplitudeAt; + if(parameter == ParameterIds::bandwidth) + vf = partial_bandwidthAt; + + int index = 1; + + int numMaxHarmonics = 0; + + for(auto& pl: list) + numMaxHarmonics = jmax(numMaxHarmonics, pl->size()); + + for(auto& pl: list) + { + int thisNum = 0; + + for(auto& p: *pl) + *buffer++ = vf(&p, timeToUse); + + for(int i = thisNum; i < numMaxHarmonics; i++) + *buffer++ = 0.0f; + } + + numHarmonics = numMaxHarmonics; + + return true; +} + bool MultichannelPartialList::matches(const juce::File& f) const { return f.getFullPathName() == filename; @@ -357,4 +520,13 @@ void MultichannelPartialList::setOptions(const Options& newOptions) options = newOptions; } -} \ No newline at end of file + + + + + + + + + +} diff --git a/loris_library/Source/MultichannelPartialList.h b/loris_library/Source/MultichannelPartialList.h index a13a856..176efa0 100644 --- a/loris_library/Source/MultichannelPartialList.h +++ b/loris_library/Source/MultichannelPartialList.h @@ -26,27 +26,73 @@ namespace loris2hise { using namespace juce; +/** This object contains a Loris::PartialList for each audio channel. */ struct MultichannelPartialList { - MultichannelPartialList(const juce::String& name, int numChannels); + /** @internal only used internally. */ + MultichannelPartialList(const juce::String& name, int numChannels); + /** @internal only used internally. */ ~MultichannelPartialList(); + /** @internal only used internally. */ double convertTimeToSeconds(double timeInput) const; + /** @internal only used internally. */ double convertSecondsToTime(double timeInput) const; + /** @internal only used internally. */ PartialList* get(int index) { return list[index]; } + /** @internal only used internally. */ void setMetadata(juce::AudioFormatReader* r, double root); + /** @internal This function creates a linear envelope from points supplied as JSON object. + + The JSON format is a two-dimensional array with `[x, y]` items. + + @example: `[ [0.0, 0.0], [0.5, 0.25], [1.0, 1.0] ]` + + This is used by multiple process algorithm that require a time-varying parameter. Note that the `x` + parameter (=time) is always converted using the global timedomain setting. + */ LinearEnvelope* createEnvelopeFromJSON(const juce::var& data); + /** @internal: argument sanitizer. Throws a juce::Result with the supplied error message. */ void checkArgs(bool condition, const juce::String& error, const std::function& additionalCleanupFunction = {}); - bool processCustom(void* obj, const Helpers::CustomFunction& f); - bool process(const juce::Identifier& command, const juce::var& data); + /** Iterates all channels, partials and breakpoints (3D!) and calls the given function for each data point. + + - state: the LorisState context. This is only used internally. + - f: a function with a single argument that contains a reference to the CustomFunctionArgs object that holds the data + for the breakpoint that should be modified. If the function returns true, the iteration will be aborted. + + returns true if the processing was sucessful and false if there was an error. + */ + bool processCustom(void* obj, const CustomFunctionArgs::Function& f); + + /** Processes the partials of each channel with a predefined function. + + - command: the function you want to execute (see below) + - args: a JSON object containing the data parameters for the algorithm. + + returns true if the processing was sucessful and false if there was an error. + + The list of available process commands is defined in the ProcessIds namespace. + + The JSON data for each command: + + - reset: an empty object `{}` + - + + */ + bool process(const juce::Identifier& command, const juce::var& args); + + bool createSnapshot(const juce::Identifier& id, double timeSeconds, double* buffer, int& numChannels, int& numHarmonics); + juce::AudioSampleBuffer synthesize(); + + juce::AudioSampleBuffer renderEnvelope(const Identifier& parameter, int partialIndex); bool matches(const juce::File& f) const; @@ -54,12 +100,18 @@ struct MultichannelPartialList int getNumSamples() const; int getNumChannels() const; + /**@ internal **/ void setOptions(const Options& newOptions); + /** @internal */ void saveAsOriginal(); + void prepareToMorph(bool removeUnlabeled=false); + private: + bool preparedForMorph = false; + Options options; juce::String filename; @@ -70,9 +122,11 @@ struct MultichannelPartialList juce::Array list; + juce::Array rootFrequencyEnvelopes; + juce::Array original; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MultichannelPartialList); }; -} \ No newline at end of file +} diff --git a/loris_library/Source/Properties.h b/loris_library/Source/Properties.h index 825b4f7..1b1085a 100644 --- a/loris_library/Source/Properties.h +++ b/loris_library/Source/Properties.h @@ -37,14 +37,31 @@ namespace OptionIds DECLARE_ID(croptime); DECLARE_ID(bwregionwidth); + /** @internal */ static juce::Array getAllIds() { return { timedomain, enablecache, windowwidth, freqfloor, ampfloor, sidelobes, freqdrift, hoptime, croptime, bwregionwidth }; - } + }; } +namespace ParameterIds +{ + DECLARE_ID(rootFrequency); + DECLARE_ID(frequency); + DECLARE_ID(phase); + DECLARE_ID(gain); + DECLARE_ID(bandwidth); + + static juce::Array getAllIds() + { + return { rootFrequency, frequency, phase, gain, bandwidth }; + }; +}; + +/** This contains a list of all available commands that can be passed into process. */ namespace ProcessIds { + /** Resets the partials to the original. Use this if you have cached the file and want*/ DECLARE_ID(reset); DECLARE_ID(shiftTime); DECLARE_ID(shiftPitch); @@ -53,6 +70,7 @@ namespace ProcessIds DECLARE_ID(dilate); DECLARE_ID(custom); + /** @internal */ static juce::Array getAllIds() { return { reset, shiftTime, shiftPitch, scaleFrequency, dilate, applyFilter, custom }; @@ -62,4 +80,4 @@ namespace ProcessIds #undef DECLARE_ID -} \ No newline at end of file +} diff --git a/loris_library/Source/public.cpp b/loris_library/Source/public.cpp index 846358c..c03dc9b 100644 --- a/loris_library/Source/public.cpp +++ b/loris_library/Source/public.cpp @@ -22,16 +22,9 @@ static loris2hise::MultichannelPartialList* getExisting(void* state, const char* file) { auto typed = (loris2hise::LorisState*)state; - - juce::File f(file); - - for (auto af : typed->analysedFiles) - { - if (af->matches(f)) - return af; - } - - return nullptr; + juce::File f(file); + + return typed->getExisting(f); } extern "C" @@ -103,7 +96,7 @@ bool loris_process(void* state, const char* file, const char* command, const cha bool loris_process_custom(void* state, const char* file, void* obj, void* function) { - loris2hise::Helpers::CustomFunction f = (loris2hise::Helpers::CustomFunctionType)function; + loris2hise::CustomFunctionArgs::Function f = (loris2hise::CustomFunctionArgs::FunctionType)function; loris2hise::LorisState::resetState(state); @@ -115,17 +108,27 @@ bool loris_process_custom(void* state, const char* file, void* obj, void* functi return false; } -bool loris_config(void* state, const char* setting, const char* value) +bool loris_set(void* state, const char* setting, const char* value) { - loris2hise::LorisState::resetState(state); + loris2hise::LorisState::resetState(state); + + auto typed = (loris2hise::LorisState*)state; + + juce::String v(value); + + return typed->setOption(juce::Identifier(setting), juce::var(v)); +} - auto typed = (loris2hise::LorisState*)state; +double loris_get(void* state, const char* setting) +{ + loris2hise::LorisState::resetState(state); - juce::String v(value); + auto typed = (loris2hise::LorisState*)state; - return typed->setOption(juce::Identifier(setting), juce::var(v)); + return typed->getOption(juce::Identifier(setting)); } + bool loris_synthesize(void* state, const char* file, float* dst, int& numChannels, int& numSamples) { loris2hise::LorisState::resetState(state); @@ -155,23 +158,82 @@ bool loris_synthesize(void* state, const char* file, float* dst, int& numChannel return false; } +bool loris_create_envelope(void* state, const char* file, const char* parameter, int label, float* dst, int& numChannels, int& numSamples) +{ + loris2hise::LorisState::resetState(state); + + numSamples = 0; + numChannels = 0; + + if (auto s = getExisting(state, file)) + { + jassert(s->getRequiredBytes() > 0); + + auto buffer = s->renderEnvelope(juce::Identifier(parameter), label); + + for (int i = 0; i < buffer.getNumChannels(); i++) + { + juce::FloatVectorOperations::copy(dst, buffer.getReadPointer(i), buffer.getNumSamples()); + dst += buffer.getNumSamples(); + } + + numSamples = s->getNumSamples(); + numChannels = s->getNumChannels(); + + return true; + } + + return false; + +} + +bool loris_snapshot(void* state, const char* file, double time, const char* parameter, double* buffer, int& numChannels, int& numHarmonics) +{ + loris2hise::LorisState::resetState(state); + + numChannels = 0; + numHarmonics = 0; + + if (auto s = getExisting(state, file)) + { + jassert(s->getRequiredBytes() > 0); + + juce::Identifier id(parameter); + + return s->createSnapshot(id, time, buffer, numChannels, numHarmonics); + } + + return false; +} + +bool loris_prepare(void* state, const char* file, bool removeUnlabeled) +{ + loris2hise::LorisState::resetState(state); + + if (auto s = getExisting(state, file)) + { + s->prepareToMorph(removeUnlabeled); + return true; + } + + return false; +} + bool getLastMessage(void* state, char* buffer, int maxlen) { auto typed = ((loris2hise::LorisState*)state); - auto hasSomething = !typed->messages.isEmpty(); - - if (hasSomething) + auto lastMessage = typed->getLastMessage(); + + if (lastMessage.isNotEmpty()) { - auto m = typed->messages[0]; - memset(buffer, 0, maxlen); - memcpy(buffer, m.getCharPointer().getAddress(), m.length()); + memcpy(buffer, lastMessage.getCharPointer().getAddress(), lastMessage.length()); - typed->messages.remove(0); + return true; } - return hasSomething; + return false; } void getIdList(char* buffer, int maxlen, bool getOptions) @@ -190,7 +252,8 @@ void getIdList(char* buffer, int maxlen, bool getOptions) const char* getLastError(void* state) { - return ((loris2hise::LorisState*)state)->lastError.getErrorMessage().getCharPointer().getAddress(); + auto typed = ((loris2hise::LorisState*)state); + return typed->getLastError(); } -} \ No newline at end of file +} diff --git a/loris_library/Source/public.h b/loris_library/Source/public.h index ee618a0..257d42d 100644 --- a/loris_library/Source/public.h +++ b/loris_library/Source/public.h @@ -50,17 +50,36 @@ DLL_EXPORT const char* getLibraryVersion(); DLL_EXPORT const char* getLorisVersion(); - +/** Analyses the given file with the given root frequency. You need to call this method before processing and synthesizing. + + - state the state context created with createLorisState(). + - file the full path name + - rootFrequency - the estimated frequency of the samples. This will be used for setting the frequency resolution and frequency drift + + Depending on the `enablecache` optiion, the analysed partials will be cached and reused when the function is called again + with the same filename. +*/ DLL_EXPORT bool loris_analyze(void* state, char* file, double rootFrequency); +/** Processes the analyzed partials with a predefined function. + + - state: the state context pointer + - file: the full path name + - command: the function you want to execute. + + For an overview of the available functions and JSON configuration take a look at ScopedPartialList::process. +*/ DLL_EXPORT bool loris_process(void* state, const char* file, const char* command, const char* json); +/** Processes the analyzed partials with a custom function. */ DLL_EXPORT bool loris_process_custom(void* state, const char* file, void* obj, void* function); -DLL_EXPORT bool loris_config(void* state, const char* setting, const char* value); +DLL_EXPORT bool loris_set(void* state, const char* setting, const char* value); -/** Returns the number of bytes that you need to allocate before calling synthesise. */ +DLL_EXPORT double loris_get(void* state, const char* setting); + +/** Returns the number of bytes that you need to allocate before calling loris_synthesize or loris_create_envelope. */ DLL_EXPORT size_t getRequiredBytes(void* state, const char* file); /** Synthesises the partial list for the given file. @@ -74,6 +93,26 @@ DLL_EXPORT size_t getRequiredBytes(void* state, const char* file); */ DLL_EXPORT bool loris_synthesize(void* state, const char* file, float* dst, int& numChannels, int& numSamples); +/** Creates a audio-rate envelope for each channel of the given parameter (bandwidth, phase, frequency, amp) for the given file. + + If you've channelized the file, you can also pass in "parameter[idx]" to get the envelope for the given harmonic for the given index. */ +DLL_EXPORT bool loris_create_envelope(void* state, const char* file, const char* parameter, int label, float* dst, int& numChannels, int& numSamples); + +/** Creates a snapshot (=list of values for all partials) of the given parameter at the given time. + + Useful for creating wavetables from the analysed data. +*/ +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); + DLL_EXPORT bool getLastMessage(void* state, char* buffer, int maxlen); DLL_EXPORT void getIdList(char* buffer, int maxlen, bool getOptions); diff --git a/loris_library/loris_library.jucer b/loris_library/loris_library.jucer index 076a56e..b99914a 100644 --- a/loris_library/loris_library.jucer +++ b/loris_library/loris_library.jucer @@ -1,7 +1,7 @@ @@ -156,7 +156,7 @@ + macOSDeploymentTarget="13.0" osxCompatibility="13.0 SDK" stripLocalSymbols="1"/> @@ -197,11 +197,11 @@ - - - - - - + + + + + +