diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index d5471a12..3cd57684 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -90,6 +90,10 @@ void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversamplin sfz::FilePool::FilePool(sfz::Logger& logger) : logger(logger) { + FilePromise promise; + if (!promise.dataStatus.is_lock_free()) + DBG("atomic is not lock-free; could cause issues with locking"); + for (int i = 0; i < config::numBackgroundThreads; ++i) threadPool.emplace_back( &FilePool::loadingThread, this ); @@ -166,13 +170,16 @@ bool sfz::FilePool::checkSample(std::string& filename) const noexcept #endif } -absl::optional sfz::FilePool::getFileInformation(const std::string& filename) noexcept +absl::optional sfz::FilePool::getFileInformation(const std::string& filename) noexcept { fs::path file { rootDirectory / filename }; + if (!fs::exists(file)) + return {}; + SndfileHandle sndFile(file.string().c_str()); if (sndFile.channels() != 1 && sndFile.channels() != 2) { - DBG("Missing logic for " << sndFile.channels() << " channels, discarding sample " << filename); + DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << filename); return {}; } @@ -194,13 +201,11 @@ absl::optional sfz::FilePool::getFileInformation bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) noexcept { fs::path file { rootDirectory / filename }; - - if (!fs::exists(file)) + auto fileInformation = getFileInformation(filename); + if (!fileInformation) return false; SndfileHandle sndFile(file.string().c_str()); - if (sndFile.channels() != 1 && sndFile.channels() != 2) - return false; // FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same const auto frames = static_cast(sndFile.frames()); @@ -211,19 +216,47 @@ bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) return min(frames, maxOffset + preloadSize); }(); - if (preloadedFiles.contains(filename)) { - if (framesToLoad > preloadedFiles[filename].preloadedData->getNumFrames()) { + const auto existingFile = preloadedFiles.find(filename); + if (existingFile != preloadedFiles.end()) { + if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) { preloadedFiles[filename].preloadedData = readFromFile(sndFile, framesToLoad, oversamplingFactor); } } else { - const float sourceSampleRate { static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()) }; - PreloadedFileHandle handle { readFromFile(sndFile, framesToLoad, oversamplingFactor), sourceSampleRate }; + fileInformation->sampleRate = static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()); + FileDataHandle handle { + readFromFile(sndFile, framesToLoad, oversamplingFactor), + *fileInformation + }; preloadedFiles.insert_or_assign(filename, handle); } - return true; } +absl::optional sfz::FilePool::loadFile(const std::string& filename) noexcept +{ + fs::path file { rootDirectory / filename }; + auto fileInformation = getFileInformation(filename); + if (!fileInformation) + return {}; + + SndfileHandle sndFile(file.string().c_str()); + + // FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same + const auto frames = static_cast(sndFile.frames()); + const auto existingFile = loadedFiles.find(filename); + if (existingFile != loadedFiles.end()) { + return existingFile->second; + } else { + fileInformation->sampleRate = static_cast(oversamplingFactor) * static_cast(sndFile.samplerate()); + FileDataHandle handle { + readFromFile(sndFile, frames, oversamplingFactor), + *fileInformation + }; + loadedFiles.insert_or_assign(filename, handle); + return handle; + } +} + sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept { if (emptyPromises.empty()) { @@ -240,7 +273,7 @@ sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) n auto promise = emptyPromises.back(); promise->filename = preloaded->first; promise->preloadedData = preloaded->second.preloadedData; - promise->sampleRate = preloaded->second.sampleRate; + promise->sampleRate = preloaded->second.information.sampleRate; promise->oversamplingFactor = oversamplingFactor; promise->creationTime = std::chrono::high_resolution_clock::now(); @@ -274,7 +307,7 @@ void sfz::FilePool::tryToClearPromises() std::this_thread::sleep_for(std::chrono::milliseconds(1)); for (auto& promise: promisesToClear) { - if (promise->dataReady) + if (promise->dataStatus != FilePromise::DataStatus::Wait) promise->reset(); } } @@ -313,11 +346,12 @@ void sfz::FilePool::loadingThread() noexcept SndfileHandle sndFile(file.string().c_str()); if (sndFile.error() != 0) { DBG("[sfizz] libsndfile errored for " << promise->filename << " with message " << sndFile.strError()); + promise->dataStatus = FilePromise::DataStatus::Error; continue; } const auto frames = static_cast(sndFile.frames()); streamFromFile(sndFile, frames, oversamplingFactor, promise->fileData, &promise->availableFrames); - promise->dataReady = true; + promise->dataStatus = FilePromise::DataStatus::Ready; const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime; logger.logFileTime(waitDuration, loadDuration, frames, promise->filename); @@ -352,7 +386,7 @@ void sfz::FilePool::cleanupPromises() noexcept auto clearedIterator = promisesToClear.begin(); auto clearedSentinel = promisesToClear.rbegin(); while (clearedIterator < clearedSentinel.base()) { - if (clearedIterator->get()->dataReady == false) { + if (clearedIterator->get()->dataStatus == FilePromise::DataStatus::Wait) { emptyPromises.push_back(*clearedIterator); std::iter_swap(clearedIterator, clearedSentinel); ++clearedSentinel; @@ -391,7 +425,7 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept fs::path file { rootDirectory / std::string(preloadedFile.first) }; SndfileHandle sndFile(file.string().c_str()); preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, factor); - preloadedFile.second.sampleRate *= samplerateChange; + preloadedFile.second.information.sampleRate *= samplerateChange; } this->oversamplingFactor = factor; diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index c75e6c75..450a4c24 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -38,24 +38,31 @@ #include "Logger.h" #include #include -#include namespace sfz { using AudioBufferPtr = std::shared_ptr>; +struct FileInformation { + uint32_t end { Default::sampleEndRange.getEnd() }; + uint32_t loopBegin { Default::loopRange.getStart() }; + uint32_t loopEnd { Default::loopRange.getEnd() }; + double sampleRate { config::defaultSampleRate }; + int numChannels { 0 }; +}; + // Strict C++11 disallows member initialization if aggregate initialization is to be used... -struct PreloadedFileHandle +struct FileDataHandle { std::shared_ptr> preloadedData; - float sampleRate; + FileInformation information; }; struct FilePromise { AudioSpan getData() { - if (dataReady) + if (dataStatus == DataStatus::Ready) return AudioSpan(fileData); else if (availableFrames > preloadedData->getNumFrames()) return AudioSpan(fileData).first(availableFrames); @@ -69,18 +76,30 @@ struct FilePromise preloadedData.reset(); filename = ""; availableFrames = 0; - dataReady = false; + dataStatus = DataStatus::Wait; oversamplingFactor = config::defaultOversamplingFactor; sampleRate = config::defaultSampleRate; } + void waitCompletion() + { + while (dataStatus == DataStatus::Wait) + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + enum class DataStatus { + Wait = 0, + Ready, + Error, + }; + absl::string_view filename {}; AudioBufferPtr preloadedData {}; AudioBuffer fileData {}; float sampleRate { config::defaultSampleRate }; Oversampling oversamplingFactor { config::defaultOversamplingFactor }; std::atomic availableFrames { 0 }; - std::atomic dataReady { false }; + std::atomic dataStatus { DataStatus::Wait }; std::chrono::time_point creationTime; LEAK_DETECTOR(FilePromise); @@ -131,14 +150,6 @@ public: */ size_t getNumPreloadedSamples() const noexcept { return preloadedFiles.size(); } - struct FileInformation { - uint32_t end { Default::sampleEndRange.getEnd() }; - uint32_t loopBegin { Default::loopRange.getStart() }; - uint32_t loopEnd { Default::loopRange.getEnd() }; - double sampleRate { config::defaultSampleRate }; - int numChannels { 0 }; - }; - /** * @brief Get metadata information about a file. * @@ -148,7 +159,7 @@ public: absl::optional getFileInformation(const std::string& filename) noexcept; /** - * @brief Check that a file is preloaded with the proper offset bounds + * @brief Preload a file with the proper offset bounds * * @param filename * @param offset the maximum offset to consider for preloading. The total preloaded @@ -158,6 +169,15 @@ public: */ bool preloadFile(const std::string& filename, uint32_t maxOffset) noexcept; + /** + * @brief Load a file and return its information. The file pool will store this + * data for future requests so use this function responsibly. + * + * @param filename + * @return A handle on the file data + */ + absl::optional loadFile(const std::string& filename) noexcept; + /** * @brief Check that the sample exists. If not, try to find it in a case insensitive way. * @@ -247,7 +267,9 @@ private: std::atomic addingPromisesToClear { false }; std::atomic canAddPromisesToClear { true }; - absl::flat_hash_map preloadedFiles; + // Preloaded data + absl::flat_hash_map preloadedFiles; + absl::flat_hash_map loadedFiles; std::vector threadPool { }; LEAK_DETECTOR(FilePool); }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 8d8f14ee..808f031e 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -52,14 +52,12 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("delay_random"): setValueFromOpcode(opcode, delayRandom, Default::delayRange); - delayDistribution.param(std::uniform_real_distribution::param_type(0, delayRandom)); break; case hash("offset"): setValueFromOpcode(opcode, offset, Default::offsetRange); break; case hash("offset_random"): setValueFromOpcode(opcode, offsetRandom, Default::offsetRange); - offsetDistribution.param(std::uniform_int_distribution::param_type(0, offsetRandom)); break; case hash("end"): setValueFromOpcode(opcode, sampleEnd, Default::sampleEndRange); @@ -99,6 +97,10 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("oscillator_phase"): setValueFromOpcode(opcode, oscillatorPhase, Default::oscillatorPhaseRange); break; + case hash("oscillator"): + if (auto value = readBooleanFromOpcode(opcode)) + oscillator = *value; + break; // Instrument settings: voice lifecycle case hash("group"): // fallthrough @@ -297,7 +299,6 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("amp_random"): setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange); - volumeDistribution.param(std::uniform_real_distribution::param_type(0, ampRandom)); break; case hash("amp_velcurve_&"): { @@ -639,7 +640,6 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("pitch_random"): setValueFromOpcode(opcode, pitchRandom, Default::pitchRandomRange); - pitchDistribution.param(std::uniform_int_distribution::param_type(-pitchRandom, pitchRandom)); break; case hash("transpose"): setValueFromOpcode(opcode, transpose, Default::transposeRange); @@ -891,8 +891,9 @@ void sfz::Region::registerTempo(float secondsPerQuarter) noexcept bpmSwitched = false; } -float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) noexcept +float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) const noexcept { + std::uniform_int_distribution pitchDistribution { -pitchRandom, pitchRandom }; auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center pitchVariationInCents += tune; // sample tuning pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose @@ -901,26 +902,42 @@ float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) noexc return centsFactor(pitchVariationInCents); } -float sfz::Region::getBaseVolumedB(int noteNumber) noexcept +float sfz::Region::getBaseVolumedB(int noteNumber) const noexcept { + std::uniform_real_distribution volumeDistribution { -ampRandom, ampRandom }; auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator); if (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key) baseVolumedB -= rtDecay * midiState.getNoteDuration(noteNumber); return baseVolumedB; } -float sfz::Region::getBaseGain() noexcept +float sfz::Region::getBaseGain() const noexcept { return normalizePercents(amplitude); } -uint32_t sfz::Region::getOffset(Oversampling factor) noexcept +float sfz::Region::getPhase() const noexcept { + float phase; + if (oscillatorPhase >= 0) { + phase = oscillatorPhase * (1.0f / 360.0f); + phase -= static_cast(phase); + } else { + std::uniform_real_distribution phaseDist { 0.0001f, 0.9999f }; + phase = phaseDist(Random::randomGenerator); + } + return phase; +} + +uint32_t sfz::Region::getOffset(Oversampling factor) const noexcept +{ + std::uniform_int_distribution offsetDistribution { 0, offsetRandom }; return (offset + offsetDistribution(Random::randomGenerator)) * static_cast(factor); } -float sfz::Region::getDelay() noexcept +float sfz::Region::getDelay() const noexcept { + std::uniform_real_distribution delayDistribution { 0, delayRandom }; return delay + delayDistribution(Random::randomGenerator); } @@ -971,7 +988,7 @@ float crossfadeOut(const sfz::Range& crossfadeRange, U value, SfzCrossfadeCur return 1.0f; } -float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) noexcept +float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) const noexcept { float baseGain { 1.0f }; @@ -992,7 +1009,7 @@ float sfz::Region::getNoteGain(int noteNumber, uint8_t velocity) noexcept return baseGain; } -float sfz::Region::getCrossfadeGain(const sfz::SfzCCArray& ccState) noexcept +float sfz::Region::getCrossfadeGain(const sfz::SfzCCArray& ccState) const noexcept { float gain { 1.0f }; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 3ef284d0..1fd0fa67 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -135,7 +135,7 @@ struct Region { * @param velocity * @return float */ - float getBasePitchVariation(int noteNumber, uint8_t velocity) noexcept; + float getBasePitchVariation(int noteNumber, uint8_t velocity) const noexcept; /** * @brief Get the note-related gain of the region depending on which note has been * pressed and at which velocity. @@ -144,7 +144,7 @@ struct Region { * @param velocity * @return float */ - float getNoteGain(int noteNumber, uint8_t velocity) noexcept; + float getNoteGain(int noteNumber, uint8_t velocity) const noexcept; /** * @brief Get the additional crossfade gain of the region depending on the * CC values @@ -152,7 +152,7 @@ struct Region { * @param ccState * @return float */ - float getCrossfadeGain(const SfzCCArray& ccState) noexcept; + float getCrossfadeGain(const SfzCCArray& ccState) const noexcept; /** * @brief Get the base volume of the region depending on which note has been * pressed to trigger the region. @@ -160,13 +160,19 @@ struct Region { * @param noteNumber * @return float */ - float getBaseVolumedB(int noteNumber) noexcept; + float getBaseVolumedB(int noteNumber) const noexcept; /** * @brief Get the base gain of the region. * * @return float */ - float getBaseGain() noexcept; + float getBaseGain() const noexcept; + /** + * @brief Get the base gain of the region. + * + * @return float + */ + float getPhase() const noexcept; /** * @brief Computes the gain value related to the velocity of the note * @@ -178,13 +184,13 @@ struct Region { * * @return uint32_t */ - uint32_t getOffset(Oversampling factor = Oversampling::x1) noexcept; + uint32_t getOffset(Oversampling factor = Oversampling::x1) const noexcept; /** * @brief Get the region delay in seconds * * @return float */ - float getDelay() noexcept; + float getDelay() const noexcept; /** * @brief Get the index of the sample end, either natural end or forced * loop. @@ -228,6 +234,7 @@ struct Region { // Wavetable oscillator float oscillatorPhase { Default::oscillatorPhase }; + bool oscillator = false; // Instrument settings: voice lifecycle uint32_t group { Default::group }; // group @@ -326,11 +333,6 @@ private: absl::string_view defaultPath { "" }; int sequenceCounter { 0 }; - - std::uniform_real_distribution volumeDistribution { -sfz::Default::ampRandom, sfz::Default::ampRandom }; - std::uniform_real_distribution delayDistribution { 0, sfz::Default::delayRandom }; - std::uniform_int_distribution offsetDistribution { 0, sfz::Default::offsetRandom }; - std::uniform_int_distribution pitchDistribution { -sfz::Default::pitchRandom, sfz::Default::pitchRandom }; LEAK_DETECTOR(Region); }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 930e1c9e..9ef64406 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -143,6 +143,7 @@ void sfz::Synth::clear() effectBuses[0]->setSampleRate(sampleRate); curves = CurveSet::createPredefined(); resources.filePool.clear(); + resources.wavePool.clearFileWaves(); resources.logger.clear(); numGroups = 0; numMasters = 0; @@ -324,7 +325,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) while (currentRegion < lastRegion.base()) { auto region = currentRegion->get(); - if (!region->isGenerator()) { + if (!region->oscillator && !region->isGenerator()) { if (!resources.filePool.checkSample(region->sample)) { removeCurrentRegion(); continue; @@ -360,6 +361,17 @@ bool sfz::Synth::loadSfzFile(const fs::path& file) if (!resources.filePool.preloadFile(region->sample, maxOffset)) removeCurrentRegion(); } + else if (region->oscillator && !region->isGenerator()) { + if (!resources.filePool.checkSample(region->sample)) { + removeCurrentRegion(); + continue; + } + + if (!resources.wavePool.createFileWave(resources.filePool, region->sample)) { + removeCurrentRegion(); + continue; + } + } for (auto note = 0; note < 128; note++) { if (region->keyRange.containsWithEnd(note) || diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 743c00f7..e86375f2 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -58,17 +58,11 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value break; } waveOscillator.setWavetable(wave); - - float phase; - const float phaseParam = region->oscillatorPhase; - if (phaseParam >= 0) { - phase = phaseParam * (1.0f / 360.0f); - phase -= static_cast(phase); - } else { - std::uniform_real_distribution phaseDist { 0.0001f, 0.9999f }; - phase = phaseDist(Random::randomGenerator); - } - waveOscillator.setPhase(phase); + waveOscillator.setPhase(region->getPhase()); + } else if (region->oscillator) { + const WavetableMulti* wave = resources.wavePool.getFileWave(region->sample); + waveOscillator.setWavetable(wave); + waveOscillator.setPhase(region->getPhase()); } else { currentPromise = resources.filePool.getFilePromise(region->sample); if (currentPromise == nullptr) { @@ -77,6 +71,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value } speedRatio = static_cast(currentPromise->sampleRate / this->sampleRate); } + pitchRatio = region->getBasePitchVariation(number, value); baseVolumedB = region->getBaseVolumedB(number); @@ -294,7 +289,7 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept { // Fill buffer with raw data ScopedTiming logger { dataDuration }; - if (region->isGenerator()) + if (region->isGenerator() || region->oscillator) fillWithGenerator(delayed_buffer); else fillWithData(delayed_buffer); diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index 355b9fca..c65b244a 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -5,18 +5,16 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "Wavetables.h" +#include "FilePool.h" #include "MathHelpers.h" #include -#include namespace sfz { -static WavetableMulti silenceMulti = WavetableMulti::createSilence(); - void WavetableOscillator::init(double sampleRate) { _sampleInterval = 1.0 / sampleRate; - _multi = &silenceMulti; + _multi = WavetableMulti::getSilenceWavetable(); clear(); } @@ -27,7 +25,7 @@ void WavetableOscillator::clear() void WavetableOscillator::setWavetable(const WavetableMulti* wave) { - _multi = wave ? wave : &silenceMulti; + _multi = wave ? wave : WavetableMulti::getSilenceWavetable(); } void WavetableOscillator::setPhase(float phase) @@ -252,12 +250,12 @@ WavetableMulti WavetableMulti::createForHarmonicProfile( return wm; } -WavetableMulti WavetableMulti::createSilence() +const WavetableMulti* WavetableMulti::getSilenceWavetable() { - WavetableMulti wm; + static WavetableMulti wm; wm.allocateStorage(1); wm.fillExtra(); - return wm; + return &wm; } void WavetableMulti::allocateStorage(unsigned tableSize) @@ -279,6 +277,31 @@ void WavetableMulti::fillExtra() } } +//------------------------------------------------------------------------------ + +/** + * @brief Harmonic profile which takes its values from a table. + */ +class TabulatedHarmonicProfile : public HarmonicProfile { +public: + explicit TabulatedHarmonicProfile(absl::Span> harmonics) + : _harmonics(harmonics) + { + } + + std::complex getHarmonic(size_t index) const override + { + if (index >= _harmonics.size()) + return {}; + + return _harmonics[index]; + } + +private: + absl::Span> _harmonics; +}; + +//------------------------------------------------------------------------------ WavetablePool::WavetablePool() { @@ -313,4 +336,60 @@ const WavetableMulti* WavetablePool::getWaveSquare() return &wave; } +const WavetableMulti* WavetablePool::getFileWave(const std::string& filename) +{ + auto it = _fileWaves.find(filename); + if (it == _fileWaves.end()) + return nullptr; + + return it->second.get(); +} + +void WavetablePool::clearFileWaves() +{ + _fileWaves.clear(); +} + +bool WavetablePool::createFileWave(FilePool& filePool, const std::string& filename) +{ + if (_fileWaves.contains(filename)) + return true; + + auto fileHandle = filePool.loadFile(filename); + if (!fileHandle) + return false; + + if (fileHandle->information.numChannels > 1) + DBG("[sfizz] Only the first channel of " << filename << " will be used to create the wavetable"); + + auto audioData = fileHandle->preloadedData->getConstSpan(0); + size_t fftSize = audioData.size(); + size_t specSize = fftSize / 2 + 1; + + typedef std::complex cpx; + std::unique_ptr spec { new cpx[specSize] }; + + kiss_fftr_cfg cfg = kiss_fftr_alloc(fftSize, false, nullptr, nullptr); + if (!cfg) + throw std::bad_alloc(); + + kiss_fftr(cfg, audioData.data(), reinterpret_cast(spec.get())); + kiss_fftr_free(cfg); + + // scale transform, and normalize amplitude and phase + const std::complex k = std::polar(2.0 / fftSize, -M_PI / 2); + for (size_t i = 0; i < specSize; ++i) + spec[i] *= k; + + TabulatedHarmonicProfile hp { + absl::Span> { spec.get(), specSize } + }; + + auto wave = std::make_shared( + WavetableMulti::createForHarmonicProfile(hp, 1.0)); + + _fileWaves[filename] = wave; + return true; +} + } // namespace sfz diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index bdc715bc..6290f01f 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -9,10 +9,12 @@ #include "LeakDetector.h" #include "Buffer.h" #include +#include #include #include namespace sfz { +class FilePool; class WavetableMulti; @@ -157,8 +159,8 @@ public: static WavetableMulti createForHarmonicProfile( const HarmonicProfile& hp, double amplitude, unsigned tableSize = config::tableSize, double refSampleRate = 44100.0); - // create the tiniest wavetable with null content for use with oscillators - static WavetableMulti createSilence(); + // get a tiny silent wavetable with null content for use with oscillators + static const WavetableMulti* getSilenceWavetable(); private: // get a pointer to the beginning of the N-th table @@ -185,15 +187,42 @@ private: }; /** - * @brief Holds predefined wavetables. + * @brief Holds predefined and loaded wavetables. * */ struct WavetablePool { WavetablePool(); + + /** + * @brief Get a file wave. Return a silent table if the wave does not exist yet. + * Use createFileWave to preload file waves before calling this function. + * This function is real-time safe. + * + * @param filename the name of the file wave + * @return the wavetable, or a silent table + */ + const WavetableMulti* getFileWave(const std::string& filename); + /** + * @brief Load a file wave from the filepool and use it to create a wavetable. + * This function is not real-time safe. + * + * @param filePool the file pool to use to load the file + * @param filename the file name to load + * @return true if the wavetable was correctly created (or existed already) + */ + bool createFileWave(FilePool& filePool, const std::string& filename); + /** + * @brief Removes all the stored file waves from the wavetable pool. + */ + void clearFileWaves(); + static const WavetableMulti* getWaveSin(); static const WavetableMulti* getWaveTriangle(); static const WavetableMulti* getWaveSaw(); static const WavetableMulti* getWaveSquare(); + +private: + absl::flat_hash_map> _fileWaves; }; } // namespace sfz