From 6fa2aad4e97bcbd9ea065f79a7470830234a821c Mon Sep 17 00:00:00 2001 From: Malcolm Haylock Date: Sat, 16 Dec 2017 22:56:00 +0100 Subject: [PATCH 1/4] Add disk streaming for sfz files --- README.md | 12 +++------ SFZero.cpp | 2 ++ SFZero.h | 2 ++ sfzero/SF2Reader.cpp | 12 +++++---- sfzero/SF2Sound.cpp | 4 +-- sfzero/SF2Sound.h | 2 +- sfzero/SFZCleaner.cpp | 52 ++++++++++++++++++++++++++++++++++++++ sfzero/SFZCleaner.h | 33 ++++++++++++++++++++++++ sfzero/SFZDiskStreamer.cpp | 47 ++++++++++++++++++++++++++++++++++ sfzero/SFZDiskStreamer.h | 33 ++++++++++++++++++++++++ sfzero/SFZEG.cpp | 14 +++++----- sfzero/SFZReader.cpp | 10 ++++---- sfzero/SFZReader.h | 2 +- sfzero/SFZSample.cpp | 5 ++-- sfzero/SFZSample.h | 9 ++++--- sfzero/SFZSound.h | 2 +- sfzero/SFZSynth.cpp | 9 ++++++- sfzero/SFZSynth.h | 5 +++- sfzero/SFZVoice.cpp | 52 +++++++++++++++++++++++++++++++++++--- sfzero/SFZVoice.h | 9 +++++-- 20 files changed, 273 insertions(+), 43 deletions(-) create mode 100644 sfzero/SFZCleaner.cpp create mode 100644 sfzero/SFZCleaner.h create mode 100644 sfzero/SFZDiskStreamer.cpp create mode 100644 sfzero/SFZDiskStreamer.h diff --git a/README.md b/README.md index 8b46efe..7b5cb4a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SFZero, the Juce module version (module only) +# SFZero, the Juce module version This is a fork of the [original SFZero by Steve Folta](https://github.com/stevefolta/SFZero), with the following changes: @@ -8,19 +8,15 @@ This is a fork of the [original SFZero by Steve Folta](https://github.com/stevef * now also supports new Juce 4.2 module format (thanks to Loki Davison) * conveniently sits within its own `sfzero::` namespace * has a tidied-up code base, so it now builds with as few warnings as possible on all platforms and on both 32/64 bit architectures. I also simplified logging, added support for synchronous sample loading, and fixed a few bugs. -* the SFZero Juce module and sample plugin have been separated and the Juce module is now available as a git submodule for easy inclusion in other repositories For more information, please see also this [blog article](http://www.mucoder.net/blog/2016/03/24/sfzero.html) Please note that, in order to build, SFZero requires [Juce](http://www.juce.com). -Before building the sample plugin, it's necessary to +Before building the plugin, it's necessary to -* get the sample plugin source code from [https://github.com/altalogix/SFZero](https://github.com/altalogix/SFZero) -* get the module source code from [https://github.com/altalogix/SFZeroModule](https://github.com/altalogix/SFZeroModule) -* copy the SFZeroModule folder as a childfolder to your Juce modules folder. +* copy the modules/SFZero folder as a childfolder to your Juce modules folder. * load `plugin/SFZero.jucer` into your IntroJucer tool and save the project again. This should regenerate the project build definitions with the proper links to your Juce module location. -If you just want to use the Juce module and not the sample plugin, it suffices to include the contents of [https://github.com/altalogix/SFZeroModule](https://github.com/altalogix/SFZeroModule) within a SFZero child folder of your Juce modules folder. - +You can find this fork's source code at: [https://github.com/altalogix/SFZero](https://github.com/altalogix/SFZero) diff --git a/SFZero.cpp b/SFZero.cpp index c687f92..0b64d6f 100644 --- a/SFZero.cpp +++ b/SFZero.cpp @@ -16,3 +16,5 @@ #include "sfzero/SFZSound.cpp" #include "sfzero/SFZSynth.cpp" #include "sfzero/SFZVoice.cpp" +#include "sfzero/SFZDiskStreamer.cpp" +#include "sfzero/SFZCleaner.cpp" diff --git a/SFZero.h b/SFZero.h index 01ad118..911b9fe 100644 --- a/SFZero.h +++ b/SFZero.h @@ -29,6 +29,8 @@ END_JUCE_MODULE_DECLARATION #include "sfzero/SFZSound.h" #include "sfzero/SFZSynth.h" #include "sfzero/SFZVoice.h" +#include "sfzero/SFZDiskStreamer.h" +#include "sfzero/SFZCleaner.h" #endif // INCLUDED_SFZERO_H diff --git a/sfzero/SF2Reader.cpp b/sfzero/SF2Reader.cpp index 37ea84c..cb2011f 100644 --- a/sfzero/SF2Reader.cpp +++ b/sfzero/SF2Reader.cpp @@ -9,6 +9,8 @@ #include "SF2.h" #include "SF2Generator.h" #include "SF2Sound.h" +#include "SFZDebug.h" +#include "SFZSample.h" sfzero::SF2Reader::SF2Reader(sfzero::SF2Sound *soundIn, const juce::File &fileIn) : sound_(soundIn) { @@ -253,16 +255,16 @@ juce::AudioSampleBuffer *sfzero::SF2Reader::readSamples(double *progressVar, juc if (progressVar) { - *progressVar = static_cast(numSamples - samplesLeft) / numSamples; + *progressVar = (float)(numSamples - samplesLeft) / numSamples; } if (thread && thread->threadShouldExit()) { - delete[] buffer; + delete buffer; delete sampleBuffer; return nullptr; } } - delete[] buffer; + delete buffer; if (progressVar) { @@ -374,7 +376,7 @@ void sfzero::SF2Reader::addGeneratorToRegion(sfzero::word genOper, sfzero::SF2:: case sfzero::SF2Generator::exclusiveClass: region->off_by = amount->wordAmount; - region->group = static_cast(region->off_by); + region->group = (int)region->off_by; break; case sfzero::SF2Generator::overridingRootKey: @@ -424,7 +426,7 @@ void sfzero::SF2Reader::addGeneratorToRegion(sfzero::word genOper, sfzero::SF2:: case sfzero::SF2Generator::reserved3: case sfzero::SF2Generator::unused5: { - const sfzero::SF2Generator *generator = sfzero::GeneratorFor(static_cast(genOper)); + const sfzero::SF2Generator *generator = sfzero::GeneratorFor((int)genOper); sound_->addUnsupportedOpcode(generator->name); } break; diff --git a/sfzero/SF2Sound.cpp b/sfzero/SF2Sound.cpp index 5bc8015..6232e39 100644 --- a/sfzero/SF2Sound.cpp +++ b/sfzero/SF2Sound.cpp @@ -105,12 +105,12 @@ int sfzero::SF2Sound::selectedSubsound() { return selectedPreset_; } sfzero::Sample *sfzero::SF2Sound::sampleFor(double sampleRate) { - sfzero::Sample *sample = samplesByRate_[static_cast(sampleRate)]; + sfzero::Sample *sample = samplesByRate_[(int)sampleRate]; if (sample == nullptr) { sample = new sfzero::Sample(sampleRate); - samplesByRate_.set(static_cast(sampleRate), sample); + samplesByRate_.set((int)sampleRate, sample); } return sample; } diff --git a/sfzero/SF2Sound.h b/sfzero/SF2Sound.h index 70b00e2..8f1373b 100644 --- a/sfzero/SF2Sound.h +++ b/sfzero/SF2Sound.h @@ -15,7 +15,7 @@ namespace sfzero class SF2Sound : public Sound { public: - explicit SF2Sound(const juce::File &file); + SF2Sound(const juce::File &file); virtual ~SF2Sound(); void loadRegions() override; diff --git a/sfzero/SFZCleaner.cpp b/sfzero/SFZCleaner.cpp new file mode 100644 index 0000000..b530d83 --- /dev/null +++ b/sfzero/SFZCleaner.cpp @@ -0,0 +1,52 @@ +/* + ============================================================================== + + SFZCleaner.cpp + Created: 4 Dec 2017 10:22:24pm + Author: malcolm + + ============================================================================== +*/ + +#include "SFZCleaner.h" + +//============================================================================== +sfzero::SFZCleaner::SFZCleaner(const juce::String& threadName) : juce::Thread(threadName) +{ + lastCount=-1; +} + +sfzero::SFZCleaner::~SFZCleaner() +{ + stopThread(3000); + checkForBuffersToFree(); +} + +void sfzero::SFZCleaner::run(){ + while (!threadShouldExit()) + { + if(lastCount!=buffers.size()){ + std::cout << "cleaning " << buffers.size() << "\n"; + lastCount=buffers.size(); + } + checkForBuffersToFree(); + wait (1000); + } +} + +void sfzero::SFZCleaner::checkForBuffersToFree() +{ + for (int i = buffers.size(); --i >= 0;) + { + sfzero::SFZDiskStreamer* buffer = buffers[i]; + if (!buffer->isThreadRunning()){ + delete buffer; + buffers.remove(i); + //std::cout << "Buffer cleaned " << buffers.size() << "\n"; + } + } +} + +void sfzero::SFZCleaner::addThread(SFZDiskStreamer* thread){ + buffers.add(thread); +} diff --git a/sfzero/SFZCleaner.h b/sfzero/SFZCleaner.h new file mode 100644 index 0000000..9c49adc --- /dev/null +++ b/sfzero/SFZCleaner.h @@ -0,0 +1,33 @@ +/* + ============================================================================== + + SFZCleaner.h + Created: 4 Dec 2017 10:22:24pm + Author: malcolm + + ============================================================================== +*/ + +#ifndef SFZCLEANER_H_INCLUDED +#define SFZCLEANER_H_INCLUDED + +#include "SFZDiskStreamer.h" + +namespace sfzero +{ + class SFZCleaner : public juce::Thread + { + public: + SFZCleaner(const juce::String& threadName); + ~SFZCleaner(); + void run() override; + void addThread(SFZDiskStreamer* thread); + private: + juce::Array buffers; + void checkForBuffersToFree(); + int lastCount; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SFZCleaner) + }; +} + +#endif // SFZCLEANER_H_INCLUDED diff --git a/sfzero/SFZDiskStreamer.cpp b/sfzero/SFZDiskStreamer.cpp new file mode 100644 index 0000000..31187ae --- /dev/null +++ b/sfzero/SFZDiskStreamer.cpp @@ -0,0 +1,47 @@ +/************************************************************************************* +* Original code copyright (C) 2012 Steve Folta +* Converted to Juce module (C) 2016 Leo Olivers +* Forked from https://github.com/stevefolta/SFZero +* For license info please see the LICENSE file distributed with this source code +*************************************************************************************/ +#include "SFZDiskStreamer.h" +#include + +sfzero::SFZDiskStreamer::SFZDiskStreamer(const juce::String& threadName, juce::File file, juce::AudioFormatManager *formatManager, int numChannels, juce::uint32 numSamples, int streamBufferSize): juce::Thread(threadName) +{ + file_=file; + buffer_ = new juce::AudioSampleBuffer(numChannels, numSamples); + formatManager_=formatManager; + reader_ = formatManager_->createReaderFor(file_); + numSamplesFilled=0; +} + +sfzero::SFZDiskStreamer::~SFZDiskStreamer() { + delete buffer_; + delete reader_; +} + +void sfzero::SFZDiskStreamer::run(){ + //std::cout << "stream " << file_.getFileName() << "\n"; + int samplesToRead = std::min(blockSize_, (int)(buffer_->getNumSamples()-numSamplesFilled)); + reader_->read(buffer_, numSamplesFilled, samplesToRead, numSamplesFilled, true, true); + numSamplesFilled+=samplesToRead; +} + +void sfzero::SFZDiskStreamer::copyBuffer(juce::AudioSampleBuffer *inBuffer){ + for(int ichnl=0; ichnlgetNumChannels(); ichnl++) + buffer_->copyFrom(ichnl, + 0, //int destStartSample, + *inBuffer, //const AudioBuffer& source, + ichnl, //int sourceChannel, + 0, //int sourceStartSample, + inBuffer->getNumSamples());// int numSamples) + numSamplesFilled+=inBuffer->getNumSamples(); +} + +void sfzero::SFZDiskStreamer::setCurrentSample(double pos, int blockSize){ + if(!isThreadRunning() && numSamplesFilled<=pos+blockSize/2){ + blockSize_=blockSize; + startThread(); + } +} diff --git a/sfzero/SFZDiskStreamer.h b/sfzero/SFZDiskStreamer.h new file mode 100644 index 0000000..066ab01 --- /dev/null +++ b/sfzero/SFZDiskStreamer.h @@ -0,0 +1,33 @@ +/************************************************************************************* +* Original code copyright (C) 2012 Steve Folta +* Converted to Juce module (C) 2016 Leo Olivers +* Forked from https://github.com/stevefolta/SFZero +* For license info please see the LICENSE file distributed with this source code +*************************************************************************************/ +#ifndef SFZDISKSTREAMER_H_INCLUDED +#define SFZDISKSTREAMER_H_INCLUDED + +namespace sfzero +{ + + class SFZDiskStreamer : public juce::Thread + { + public: + SFZDiskStreamer(const juce::String& threadName, juce::File file, juce::AudioFormatManager *formatManager, int numChannels, juce::uint32 numSamples, int streamBufferSize); + virtual ~SFZDiskStreamer(); + void run() override; + juce::AudioSampleBuffer* GetVoiceBuffer(){return buffer_;} + void copyBuffer(juce::AudioSampleBuffer *buffer); + void setCurrentSample(double pos, int blockSize); + juce::uint32 getNumSamplesFilled(){return(numSamplesFilled);} + private: + juce::File file_; + juce::AudioSampleBuffer *buffer_; + juce::AudioFormatManager *formatManager_; + juce::uint32 numSamplesFilled; + juce::AudioFormatReader *reader_; + int blockSize_; + }; +} + +#endif // SFZDISKSTREAMER_H_INCLUDED diff --git a/sfzero/SFZEG.cpp b/sfzero/SFZEG.cpp index 70cdf6c..ca9c0b3 100644 --- a/sfzero/SFZEG.cpp +++ b/sfzero/SFZEG.cpp @@ -77,7 +77,7 @@ void sfzero::EG::noteOff() { startRelease(); } void sfzero::EG::fastRelease() { segment_ = Release; - samplesUntilNextSegment_ = static_cast(fastReleaseTime * sampleRate_); + samplesUntilNextSegment_ = (int)(fastReleaseTime * sampleRate_); slope_ = -level_ / samplesUntilNextSegment_; segmentIsExponential_ = false; } @@ -93,7 +93,7 @@ void sfzero::EG::startDelay() segment_ = Delay; level_ = 0.0; slope_ = 0.0; - samplesUntilNextSegment_ = static_cast(parameters_.delay * sampleRate_); + samplesUntilNextSegment_ = (int)(parameters_.delay * sampleRate_); segmentIsExponential_ = false; } } @@ -108,7 +108,7 @@ void sfzero::EG::startAttack() { segment_ = Attack; level_ = parameters_.start / 100.0f; - samplesUntilNextSegment_ = static_cast(parameters_.attack * sampleRate_); + samplesUntilNextSegment_ = (int)(parameters_.attack * sampleRate_); slope_ = 1.0f / samplesUntilNextSegment_; segmentIsExponential_ = false; } @@ -124,7 +124,7 @@ void sfzero::EG::startHold() else { segment_ = Hold; - samplesUntilNextSegment_ = static_cast(parameters_.hold * sampleRate_); + samplesUntilNextSegment_ = (int)(parameters_.hold * sampleRate_); level_ = 1.0; slope_ = 0.0; segmentIsExponential_ = false; @@ -140,7 +140,7 @@ void sfzero::EG::startDecay() else { segment_ = Decay; - samplesUntilNextSegment_ = static_cast(parameters_.decay * sampleRate_); + samplesUntilNextSegment_ = (int)(parameters_.decay * sampleRate_); level_ = 1.0; if (exponentialDecay_) { @@ -155,7 +155,7 @@ void sfzero::EG::startDecay() // get to zero, not to the sustain level. The SFZ spec is not that // specific about what "decay" means, so perhaps it's really supposed // to specify the time to reach the sustain level. - samplesUntilNextSegment_ = static_cast(log((parameters_.sustain / 100.0) / level_) / mysterySlope); + samplesUntilNextSegment_ = (int)(log((parameters_.sustain / 100.0) / level_) / mysterySlope); if (samplesUntilNextSegment_ <= 0) { startSustain(); @@ -197,7 +197,7 @@ void sfzero::EG::startRelease() } segment_ = Release; - samplesUntilNextSegment_ = static_cast(release * sampleRate_); + samplesUntilNextSegment_ = (int)(release * sampleRate_); if (exponentialDecay_) { // I don't truly understand this; just following what LinuxSampler does. diff --git a/sfzero/SFZReader.cpp b/sfzero/SFZReader.cpp index efb55bc..10c6c9d 100644 --- a/sfzero/SFZReader.cpp +++ b/sfzero/SFZReader.cpp @@ -546,11 +546,11 @@ int sfzero::Reader::triggerValue(const juce::String &str) { return sfzero::Region::release; } - if (str == "first") + else if (str == "first") { return sfzero::Region::first; } - if (str == "legato") + else if (str == "legato") { return sfzero::Region::legato; } @@ -563,15 +563,15 @@ int sfzero::Reader::loopModeValue(const juce::String &str) { return sfzero::Region::no_loop; } - if (str == "one_shot") + else if (str == "one_shot") { return sfzero::Region::one_shot; } - if (str == "loop_continuous") + else if (str == "loop_continuous") { return sfzero::Region::loop_continuous; } - if (str == "loop_sustain") + else if (str == "loop_sustain") { return sfzero::Region::loop_sustain; } diff --git a/sfzero/SFZReader.h b/sfzero/SFZReader.h index 201af63..f46fcde 100644 --- a/sfzero/SFZReader.h +++ b/sfzero/SFZReader.h @@ -18,7 +18,7 @@ class Sound; class Reader { public: - explicit Reader(Sound *sound); + Reader(Sound *sound); ~Reader(); void read(const juce::File &file); diff --git a/sfzero/SFZSample.cpp b/sfzero/SFZSample.cpp index db321f2..a8ce718 100644 --- a/sfzero/SFZSample.cpp +++ b/sfzero/SFZSample.cpp @@ -21,8 +21,8 @@ bool sfzero::Sample::load(juce::AudioFormatManager *formatManager) // can be done without having to check for the edge all the time. jassert(sampleLength_ < std::numeric_limits::max()); - buffer_ = new juce::AudioSampleBuffer(reader->numChannels, static_cast(sampleLength_ + 4)); - reader->read(buffer_, 0, static_cast(sampleLength_ + 4), 0, true, true); + buffer_ = new juce::AudioSampleBuffer(reader->numChannels, static_cast(preBufferSize)); + reader->read(buffer_, 0, static_cast(preBufferSize), 0, true, true); juce::StringPairArray *metadata = &reader->metadataValues; int numLoops = metadata->getValue("NumSampleLoops", "0").getIntValue(); @@ -31,6 +31,7 @@ bool sfzero::Sample::load(juce::AudioFormatManager *formatManager) loopStart_ = metadata->getValue("Loop0Start", "0").getLargeIntValue(); loopEnd_ = metadata->getValue("Loop0End", "0").getLargeIntValue(); } + doStream_=true; delete reader; return true; } diff --git a/sfzero/SFZSample.h b/sfzero/SFZSample.h index dc5e2e5..b75160c 100644 --- a/sfzero/SFZSample.h +++ b/sfzero/SFZSample.h @@ -15,8 +15,8 @@ namespace sfzero class Sample { public: - explicit Sample(const juce::File &fileIn) : file_(fileIn), buffer_(nullptr), sampleRate_(0), sampleLength_(0), loopStart_(0), loopEnd_(0) {} - explicit Sample(double sampleRateIn) : buffer_(nullptr), sampleRate_(sampleRateIn), sampleLength_(0), loopStart_(0), loopEnd_(0) {} + Sample(const juce::File &fileIn) : file_(fileIn), buffer_(nullptr), sampleRate_(0), sampleLength_(0), loopStart_(0), loopEnd_(0), doStream_(false){} + Sample(double sampleRateIn) : buffer_(nullptr), sampleRate_(sampleRateIn), sampleLength_(0), loopStart_(0), loopEnd_(0), doStream_(false) {} virtual ~Sample(); bool load(juce::AudioFormatManager *formatManager); @@ -31,7 +31,9 @@ class Sample juce::uint64 getSampleLength() const { return sampleLength_; } juce::uint64 getLoopStart() const { return loopStart_; } juce::uint64 getLoopEnd() const { return loopEnd_; } - + bool CanStream() const { return doStream_; } + static const int preBufferSize=44100; + #ifdef JUCE_DEBUG void checkIfZeroed(const char *where); @@ -42,6 +44,7 @@ class Sample juce::AudioSampleBuffer *buffer_; double sampleRate_; juce::uint64 sampleLength_, loopStart_, loopEnd_; + bool doStream_; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Sample) }; diff --git a/sfzero/SFZSound.h b/sfzero/SFZSound.h index 4c69ff5..1dbc037 100644 --- a/sfzero/SFZSound.h +++ b/sfzero/SFZSound.h @@ -17,7 +17,7 @@ class Sample; class Sound : public juce::SynthesiserSound { public: - explicit Sound(const juce::File &file); + Sound(const juce::File &file); virtual ~Sound(); typedef juce::ReferenceCountedObjectPtr Ptr; diff --git a/sfzero/SFZSynth.cpp b/sfzero/SFZSynth.cpp index d798694..b60af33 100644 --- a/sfzero/SFZSynth.cpp +++ b/sfzero/SFZSynth.cpp @@ -8,7 +8,14 @@ #include "SFZSound.h" #include "SFZVoice.h" -sfzero::Synth::Synth() : Synthesiser() {} +sfzero::Synth::Synth() : Synthesiser() { + threadCleaner = new SFZCleaner("Cleaner"); + threadCleaner->startThread(5); +} + +sfzero::Synth::~Synth(){ + delete threadCleaner; +} void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) { diff --git a/sfzero/SFZSynth.h b/sfzero/SFZSynth.h index ab126d4..7eaab29 100644 --- a/sfzero/SFZSynth.h +++ b/sfzero/SFZSynth.h @@ -8,6 +8,7 @@ #define SFZSYNTH_H_INCLUDED #include "SFZCommon.h" +#include "SFZCleaner.h" namespace sfzero { @@ -16,16 +17,18 @@ class Synth : public juce::Synthesiser { public: Synth(); - virtual ~Synth() {} + virtual ~Synth(); void noteOn(int midiChannel, int midiNoteNumber, float velocity) override; void noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) override; int numVoicesUsed(); juce::String voiceInfoString(); + SFZCleaner* GetCleaner(){return threadCleaner;} private: int noteVelocities_[128]; + SFZCleaner* threadCleaner; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Synth) }; } diff --git a/sfzero/SFZVoice.cpp b/sfzero/SFZVoice.cpp index b8efeca..150e01c 100644 --- a/sfzero/SFZVoice.cpp +++ b/sfzero/SFZVoice.cpp @@ -13,14 +13,21 @@ static const float globalGain = -1.0; -sfzero::Voice::Voice() +sfzero::Voice::Voice(juce::AudioFormatManager *formatManager, SFZCleaner* cleaner) : region_(nullptr), trigger_(0), curMidiNote_(0), curPitchWheel_(0), pitchRatio_(0), noteGainLeft_(0), noteGainRight_(0), - sourceSamplePosition_(0), sampleEnd_(0), loopStart_(0), loopEnd_(0), numLoops_(0), curVelocity_(0) + sourceSamplePosition_(0), sampleEnd_(0), loopStart_(0), loopEnd_(0), numLoops_(0), curVelocity_(0), streamer_(nullptr) { ampeg_.setExponentialDecay(true); + formatManager_=formatManager; + threadCleaner = cleaner; } -sfzero::Voice::~Voice() {} +sfzero::Voice::~Voice() { + if(streamer_){ + streamer_->stopThread(2000); + delete streamer_; + } +} bool sfzero::Voice::canPlaySound(juce::SynthesiserSound *sound) { return dynamic_cast(sound) != nullptr; } @@ -110,6 +117,15 @@ void sfzero::Voice::startNote(int midiNoteNumber, float floatVelocity, juce::Syn } } numLoops_ = 0; + + // init disk streaming + if(streamer_){ + if(!streamer_->isThreadRunning()) + delete streamer_; // for now abandon thread if it's still running - aim to pass this to a garbage collector + else + threadCleaner->addThread(streamer_); + streamer_=nullptr; + } } void sfzero::Voice::stopNote(float /*velocity*/, bool allowTailOff) @@ -164,6 +180,7 @@ void sfzero::Voice::renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int s } juce::AudioSampleBuffer *buffer = region_->sample->getBuffer(); + double sourceSamplePosition = this->sourceSamplePosition_; const float *inL = buffer->getReadPointer(0, 0); const float *inR = buffer->getNumChannels() > 1 ? buffer->getReadPointer(1, 0) : nullptr; @@ -174,7 +191,6 @@ void sfzero::Voice::renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int s // Cache some values, to give them at least some chance of ending up in // registers. - double sourceSamplePosition = this->sourceSamplePosition_; float ampegGain = ampeg_.getLevel(); float ampegSlope = ampeg_.getSlope(); int samplesUntilNextAmpSegment = ampeg_.getSamplesUntilNextSegment(); @@ -186,6 +202,21 @@ void sfzero::Voice::renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int s while (--numSamples >= 0) { int pos = static_cast(sourceSamplePosition); + // switch to streaming buffer + if(streamer_){ + if(region_->sample->CanStream() && buffer != streamer_->GetVoiceBuffer() && pos>=sfzero::Sample::preBufferSize){ + buffer = streamer_->GetVoiceBuffer(); + bufferNumSamples = buffer->getNumSamples(); + //std::cout << "switch to stream buffer " << bufferNumSamples << "\n"; + inL = buffer->getReadPointer(0, 0); + inR = buffer->getNumChannels() > 1 ? buffer->getReadPointer(1, 0) : nullptr; + } + if(buffer == streamer_->GetVoiceBuffer() && pos>=streamer_->getNumSamplesFilled()-2){ + killNote(); + std::cout << "kill streamed note - buffer not ready 2\n"; + break; + } + } jassert(pos >= 0 && pos < bufferNumSamples); // leoo float alpha = static_cast(sourceSamplePosition - pos); float invAlpha = 1.0f - alpha; @@ -254,6 +285,19 @@ void sfzero::Voice::renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int s break; } } + + // stream + if(region_!=nullptr){ + if(region_->sample->CanStream() && sampleEnd_>sfzero::Sample::preBufferSize){ + if(streamer_==nullptr && sourceSamplePosition>sfzero::Sample::preBufferSize/2){ + streamer_=new SFZDiskStreamer("SFZStreamer", region_->sample->getFile(), formatManager_, region_->sample->getBuffer()->getNumChannels(), static_cast(sampleEnd_), sfzero::Sample::preBufferSize); + streamer_ -> copyBuffer(buffer); + } + if(streamer_!=nullptr){ + streamer_->setCurrentSample(sourceSamplePosition, sfzero::Sample::preBufferSize); + } + } + } this->sourceSamplePosition_ = sourceSamplePosition; ampeg_.setLevel(ampegGain); diff --git a/sfzero/SFZVoice.h b/sfzero/SFZVoice.h index e005593..9a19a24 100644 --- a/sfzero/SFZVoice.h +++ b/sfzero/SFZVoice.h @@ -8,6 +8,8 @@ #define SFZVOICE_H_INCLUDED #include "SFZEG.h" +#include "SFZDiskStreamer.h" +#include "SFZCleaner.h" namespace sfzero { @@ -16,7 +18,7 @@ struct Region; class Voice : public juce::SynthesiserVoice { public: - Voice(); + Voice(juce::AudioFormatManager *formatManager, SFZCleaner* cleaner); virtual ~Voice(); bool canPlaySound(juce::SynthesiserSound *sound) override; @@ -48,7 +50,9 @@ class Voice : public juce::SynthesiserVoice EG ampeg_; juce::int64 sampleEnd_; juce::int64 loopStart_, loopEnd_; - + juce::AudioFormatManager *formatManager_; + SFZDiskStreamer *streamer_; + // Info only. int numLoops_; int curVelocity_; @@ -56,6 +60,7 @@ class Voice : public juce::SynthesiserVoice void calcPitchRatio(); void killNote(); double fractionalMidiNoteInHz(double note, double freqOfA = 440.0); + SFZCleaner* threadCleaner; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Voice) }; From 111c165d68881c374264e47775368d24b4c09979 Mon Sep 17 00:00:00 2001 From: Malcolm Haylock Date: Sun, 31 Dec 2017 11:03:52 +0100 Subject: [PATCH 2/4] MIDI Volume Add support for realtime ctrl#7 (volume) --- sfzero/SFZSynth.cpp | 13 +++++++++++++ sfzero/SFZSynth.h | 4 +++- sfzero/SFZVoice.cpp | 23 +++++++++++++++++++---- sfzero/SFZVoice.h | 2 ++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/sfzero/SFZSynth.cpp b/sfzero/SFZSynth.cpp index b60af33..2d5eaa2 100644 --- a/sfzero/SFZSynth.cpp +++ b/sfzero/SFZSynth.cpp @@ -11,6 +11,8 @@ sfzero::Synth::Synth() : Synthesiser() { threadCleaner = new SFZCleaner("Cleaner"); threadCleaner->startThread(5); + for(int i=0; i<16; i++) + midiVolume_[i] = 127; } sfzero::Synth::~Synth(){ @@ -98,6 +100,7 @@ void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) if (voice) { voice->setRegion(region); + voice->setMidiVolume(midiVolume_[midiChannel-1]); startVoice(voice, sound, midiChannel, midiNoteNumber, velocity); } } @@ -132,6 +135,16 @@ void sfzero::Synth::noteOff(int midiChannel, int midiNoteNumber, float velocity, } } +void sfzero::Synth::handleController (int midiChannel, int controllerNumber, int controllerValue) +{ + switch(controllerNumber){ + case 7: + midiVolume_[midiChannel-1] = controllerValue; + break; + } + Synthesiser::handleController (midiChannel, controllerNumber, controllerValue); +} + int sfzero::Synth::numVoicesUsed() { int numUsed = 0; diff --git a/sfzero/SFZSynth.h b/sfzero/SFZSynth.h index 7eaab29..dcb5108 100644 --- a/sfzero/SFZSynth.h +++ b/sfzero/SFZSynth.h @@ -21,7 +21,8 @@ class Synth : public juce::Synthesiser void noteOn(int midiChannel, int midiNoteNumber, float velocity) override; void noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) override; - + void handleController (int midiChannel, int controllerNumber, int controllerValue) override; + int numVoicesUsed(); juce::String voiceInfoString(); SFZCleaner* GetCleaner(){return threadCleaner;} @@ -30,6 +31,7 @@ class Synth : public juce::Synthesiser int noteVelocities_[128]; SFZCleaner* threadCleaner; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Synth) + int midiVolume_[16]; }; } diff --git a/sfzero/SFZVoice.cpp b/sfzero/SFZVoice.cpp index 150e01c..faef8dc 100644 --- a/sfzero/SFZVoice.cpp +++ b/sfzero/SFZVoice.cpp @@ -15,7 +15,7 @@ static const float globalGain = -1.0; sfzero::Voice::Voice(juce::AudioFormatManager *formatManager, SFZCleaner* cleaner) : region_(nullptr), trigger_(0), curMidiNote_(0), curPitchWheel_(0), pitchRatio_(0), noteGainLeft_(0), noteGainRight_(0), - sourceSamplePosition_(0), sampleEnd_(0), loopStart_(0), loopEnd_(0), numLoops_(0), curVelocity_(0), streamer_(nullptr) + sourceSamplePosition_(0), sampleEnd_(0), loopStart_(0), loopEnd_(0), numLoops_(0), curVelocity_(0), streamer_(nullptr), midiVolume_(127) { ampeg_.setExponentialDecay(true); formatManager_=formatManager; @@ -171,7 +171,15 @@ void sfzero::Voice::pitchWheelMoved(int newValue) calcPitchRatio(); } -void sfzero::Voice::controllerMoved(int /*controllerNumber*/, int /*newValue*/) { /***/} +void sfzero::Voice::controllerMoved(int controllerNumber, int newValue) +{ + switch(controllerNumber){ + case 7: + setMidiVolume(newValue); + break; + } +} + void sfzero::Voice::renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int startSample, int numSamples) { if (region_ == nullptr) @@ -198,6 +206,8 @@ void sfzero::Voice::renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int s float loopStart = static_cast(this->loopStart_); float loopEnd = static_cast(this->loopEnd_); float sampleEnd = static_cast(this->sampleEnd_); + float midiVolumeGainDB = -20.0 * log10((127.0 * 127.0) / (midiVolume_ * midiVolume_)); + float midiVolumeGain = static_cast(juce::Decibels::decibelsToGain(midiVolumeGainDB)); while (--numSamples >= 0) { @@ -236,8 +246,8 @@ void sfzero::Voice::renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int s // float l = (inL[pos] * invAlpha + inL[nextPos] * alpha); // float r = inR ? (inR[pos] * invAlpha + inR[nextPos] * alpha) : l; - float gainLeft = noteGainLeft_ * ampegGain; - float gainRight = noteGainRight_ * ampegGain; + float gainLeft = noteGainLeft_ * ampegGain * midiVolumeGain; + float gainRight = noteGainRight_ * ampegGain * midiVolumeGain; l *= gainLeft; r *= gainRight; // Shouldn't we dither here? @@ -371,3 +381,8 @@ double sfzero::Voice::fractionalMidiNoteInHz(double note, double freqOfA) // Now 0 = A return freqOfA * pow(2.0, note / 12.0); } + +void sfzero::Voice::setMidiVolume(int volume) +{ + midiVolume_=volume; +} diff --git a/sfzero/SFZVoice.h b/sfzero/SFZVoice.h index 9a19a24..e69de02 100644 --- a/sfzero/SFZVoice.h +++ b/sfzero/SFZVoice.h @@ -31,6 +31,7 @@ class Voice : public juce::SynthesiserVoice void renderNextBlock(juce::AudioSampleBuffer &outputBuffer, int startSample, int numSamples) override; bool isPlayingNoteDown(); bool isPlayingOneShot(); + void setMidiVolume(int volume); int getGroup(); juce::uint64 getOffBy(); @@ -52,6 +53,7 @@ class Voice : public juce::SynthesiserVoice juce::int64 loopStart_, loopEnd_; juce::AudioFormatManager *formatManager_; SFZDiskStreamer *streamer_; + int midiVolume_; // Info only. int numLoops_; From 83936ea25c2d11ffae905704b8e095a0a8dbbc70 Mon Sep 17 00:00:00 2001 From: Malcolm Haylock Date: Sun, 31 Dec 2017 11:27:59 +0100 Subject: [PATCH 3/4] Merge differences with altalogix fork --- sfzero/SF2Reader.cpp | 10 +++++----- sfzero/SF2Sound.cpp | 4 ++-- sfzero/SF2Sound.h | 2 +- sfzero/SFZEG.cpp | 14 +++++++------- sfzero/SFZReader.cpp | 10 +++++----- sfzero/SFZReader.h | 2 +- sfzero/SFZSample.h | 4 ++-- sfzero/SFZSound.h | 2 +- 8 files changed, 24 insertions(+), 24 deletions(-) mode change 100644 => 100755 sfzero/SFZEG.cpp mode change 100644 => 100755 sfzero/SFZReader.cpp mode change 100644 => 100755 sfzero/SFZReader.h diff --git a/sfzero/SF2Reader.cpp b/sfzero/SF2Reader.cpp index cb2011f..14b8f7e 100644 --- a/sfzero/SF2Reader.cpp +++ b/sfzero/SF2Reader.cpp @@ -255,16 +255,16 @@ juce::AudioSampleBuffer *sfzero::SF2Reader::readSamples(double *progressVar, juc if (progressVar) { - *progressVar = (float)(numSamples - samplesLeft) / numSamples; + *progressVar = static_cast(numSamples - samplesLeft) / numSamples; } if (thread && thread->threadShouldExit()) { - delete buffer; + delete[] buffer; delete sampleBuffer; return nullptr; } } - delete buffer; + delete[] buffer; if (progressVar) { @@ -376,7 +376,7 @@ void sfzero::SF2Reader::addGeneratorToRegion(sfzero::word genOper, sfzero::SF2:: case sfzero::SF2Generator::exclusiveClass: region->off_by = amount->wordAmount; - region->group = (int)region->off_by; + region->group = static_cast(region->off_by); break; case sfzero::SF2Generator::overridingRootKey: @@ -426,7 +426,7 @@ void sfzero::SF2Reader::addGeneratorToRegion(sfzero::word genOper, sfzero::SF2:: case sfzero::SF2Generator::reserved3: case sfzero::SF2Generator::unused5: { - const sfzero::SF2Generator *generator = sfzero::GeneratorFor((int)genOper); + const sfzero::SF2Generator *generator = sfzero::GeneratorFor(static_cast(genOper)); sound_->addUnsupportedOpcode(generator->name); } break; diff --git a/sfzero/SF2Sound.cpp b/sfzero/SF2Sound.cpp index 6232e39..5bc8015 100644 --- a/sfzero/SF2Sound.cpp +++ b/sfzero/SF2Sound.cpp @@ -105,12 +105,12 @@ int sfzero::SF2Sound::selectedSubsound() { return selectedPreset_; } sfzero::Sample *sfzero::SF2Sound::sampleFor(double sampleRate) { - sfzero::Sample *sample = samplesByRate_[(int)sampleRate]; + sfzero::Sample *sample = samplesByRate_[static_cast(sampleRate)]; if (sample == nullptr) { sample = new sfzero::Sample(sampleRate); - samplesByRate_.set((int)sampleRate, sample); + samplesByRate_.set(static_cast(sampleRate), sample); } return sample; } diff --git a/sfzero/SF2Sound.h b/sfzero/SF2Sound.h index 8f1373b..70b00e2 100644 --- a/sfzero/SF2Sound.h +++ b/sfzero/SF2Sound.h @@ -15,7 +15,7 @@ namespace sfzero class SF2Sound : public Sound { public: - SF2Sound(const juce::File &file); + explicit SF2Sound(const juce::File &file); virtual ~SF2Sound(); void loadRegions() override; diff --git a/sfzero/SFZEG.cpp b/sfzero/SFZEG.cpp old mode 100644 new mode 100755 index ca9c0b3..70cdf6c --- a/sfzero/SFZEG.cpp +++ b/sfzero/SFZEG.cpp @@ -77,7 +77,7 @@ void sfzero::EG::noteOff() { startRelease(); } void sfzero::EG::fastRelease() { segment_ = Release; - samplesUntilNextSegment_ = (int)(fastReleaseTime * sampleRate_); + samplesUntilNextSegment_ = static_cast(fastReleaseTime * sampleRate_); slope_ = -level_ / samplesUntilNextSegment_; segmentIsExponential_ = false; } @@ -93,7 +93,7 @@ void sfzero::EG::startDelay() segment_ = Delay; level_ = 0.0; slope_ = 0.0; - samplesUntilNextSegment_ = (int)(parameters_.delay * sampleRate_); + samplesUntilNextSegment_ = static_cast(parameters_.delay * sampleRate_); segmentIsExponential_ = false; } } @@ -108,7 +108,7 @@ void sfzero::EG::startAttack() { segment_ = Attack; level_ = parameters_.start / 100.0f; - samplesUntilNextSegment_ = (int)(parameters_.attack * sampleRate_); + samplesUntilNextSegment_ = static_cast(parameters_.attack * sampleRate_); slope_ = 1.0f / samplesUntilNextSegment_; segmentIsExponential_ = false; } @@ -124,7 +124,7 @@ void sfzero::EG::startHold() else { segment_ = Hold; - samplesUntilNextSegment_ = (int)(parameters_.hold * sampleRate_); + samplesUntilNextSegment_ = static_cast(parameters_.hold * sampleRate_); level_ = 1.0; slope_ = 0.0; segmentIsExponential_ = false; @@ -140,7 +140,7 @@ void sfzero::EG::startDecay() else { segment_ = Decay; - samplesUntilNextSegment_ = (int)(parameters_.decay * sampleRate_); + samplesUntilNextSegment_ = static_cast(parameters_.decay * sampleRate_); level_ = 1.0; if (exponentialDecay_) { @@ -155,7 +155,7 @@ void sfzero::EG::startDecay() // get to zero, not to the sustain level. The SFZ spec is not that // specific about what "decay" means, so perhaps it's really supposed // to specify the time to reach the sustain level. - samplesUntilNextSegment_ = (int)(log((parameters_.sustain / 100.0) / level_) / mysterySlope); + samplesUntilNextSegment_ = static_cast(log((parameters_.sustain / 100.0) / level_) / mysterySlope); if (samplesUntilNextSegment_ <= 0) { startSustain(); @@ -197,7 +197,7 @@ void sfzero::EG::startRelease() } segment_ = Release; - samplesUntilNextSegment_ = (int)(release * sampleRate_); + samplesUntilNextSegment_ = static_cast(release * sampleRate_); if (exponentialDecay_) { // I don't truly understand this; just following what LinuxSampler does. diff --git a/sfzero/SFZReader.cpp b/sfzero/SFZReader.cpp old mode 100644 new mode 100755 index 10c6c9d..efb55bc --- a/sfzero/SFZReader.cpp +++ b/sfzero/SFZReader.cpp @@ -546,11 +546,11 @@ int sfzero::Reader::triggerValue(const juce::String &str) { return sfzero::Region::release; } - else if (str == "first") + if (str == "first") { return sfzero::Region::first; } - else if (str == "legato") + if (str == "legato") { return sfzero::Region::legato; } @@ -563,15 +563,15 @@ int sfzero::Reader::loopModeValue(const juce::String &str) { return sfzero::Region::no_loop; } - else if (str == "one_shot") + if (str == "one_shot") { return sfzero::Region::one_shot; } - else if (str == "loop_continuous") + if (str == "loop_continuous") { return sfzero::Region::loop_continuous; } - else if (str == "loop_sustain") + if (str == "loop_sustain") { return sfzero::Region::loop_sustain; } diff --git a/sfzero/SFZReader.h b/sfzero/SFZReader.h old mode 100644 new mode 100755 index f46fcde..201af63 --- a/sfzero/SFZReader.h +++ b/sfzero/SFZReader.h @@ -18,7 +18,7 @@ class Sound; class Reader { public: - Reader(Sound *sound); + explicit Reader(Sound *sound); ~Reader(); void read(const juce::File &file); diff --git a/sfzero/SFZSample.h b/sfzero/SFZSample.h index b75160c..5d49011 100644 --- a/sfzero/SFZSample.h +++ b/sfzero/SFZSample.h @@ -15,8 +15,8 @@ namespace sfzero class Sample { public: - Sample(const juce::File &fileIn) : file_(fileIn), buffer_(nullptr), sampleRate_(0), sampleLength_(0), loopStart_(0), loopEnd_(0), doStream_(false){} - Sample(double sampleRateIn) : buffer_(nullptr), sampleRate_(sampleRateIn), sampleLength_(0), loopStart_(0), loopEnd_(0), doStream_(false) {} + explicit Sample(const juce::File &fileIn) : file_(fileIn), buffer_(nullptr), sampleRate_(0), sampleLength_(0), loopStart_(0), loopEnd_(0), doStream_(false){} + explicit Sample(double sampleRateIn) : buffer_(nullptr), sampleRate_(sampleRateIn), sampleLength_(0), loopStart_(0), loopEnd_(0), doStream_(false) {} virtual ~Sample(); bool load(juce::AudioFormatManager *formatManager); diff --git a/sfzero/SFZSound.h b/sfzero/SFZSound.h index 1dbc037..4c69ff5 100644 --- a/sfzero/SFZSound.h +++ b/sfzero/SFZSound.h @@ -17,7 +17,7 @@ class Sample; class Sound : public juce::SynthesiserSound { public: - Sound(const juce::File &file); + explicit Sound(const juce::File &file); virtual ~Sound(); typedef juce::ReferenceCountedObjectPtr Ptr; From 64435b3b16503e0088ab802109c7147288d71b74 Mon Sep 17 00:00:00 2001 From: Malcolm Haylock Date: Sun, 31 Dec 2017 11:31:35 +0100 Subject: [PATCH 4/4] Sync README.md with altalogix --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) mode change 100644 => 100755 README.md diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 7b5cb4a..8b46efe --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SFZero, the Juce module version +# SFZero, the Juce module version (module only) This is a fork of the [original SFZero by Steve Folta](https://github.com/stevefolta/SFZero), with the following changes: @@ -8,15 +8,19 @@ This is a fork of the [original SFZero by Steve Folta](https://github.com/stevef * now also supports new Juce 4.2 module format (thanks to Loki Davison) * conveniently sits within its own `sfzero::` namespace * has a tidied-up code base, so it now builds with as few warnings as possible on all platforms and on both 32/64 bit architectures. I also simplified logging, added support for synchronous sample loading, and fixed a few bugs. +* the SFZero Juce module and sample plugin have been separated and the Juce module is now available as a git submodule for easy inclusion in other repositories For more information, please see also this [blog article](http://www.mucoder.net/blog/2016/03/24/sfzero.html) Please note that, in order to build, SFZero requires [Juce](http://www.juce.com). -Before building the plugin, it's necessary to +Before building the sample plugin, it's necessary to -* copy the modules/SFZero folder as a childfolder to your Juce modules folder. +* get the sample plugin source code from [https://github.com/altalogix/SFZero](https://github.com/altalogix/SFZero) +* get the module source code from [https://github.com/altalogix/SFZeroModule](https://github.com/altalogix/SFZeroModule) +* copy the SFZeroModule folder as a childfolder to your Juce modules folder. * load `plugin/SFZero.jucer` into your IntroJucer tool and save the project again. This should regenerate the project build definitions with the proper links to your Juce module location. -You can find this fork's source code at: [https://github.com/altalogix/SFZero](https://github.com/altalogix/SFZero) +If you just want to use the Juce module and not the sample plugin, it suffices to include the contents of [https://github.com/altalogix/SFZeroModule](https://github.com/altalogix/SFZeroModule) within a SFZero child folder of your Juce modules folder. +