diff --git a/README.md b/README.md old mode 100644 new mode 100755 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..14b8f7e 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) { 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 old mode 100644 new mode 100755 diff --git a/sfzero/SFZReader.cpp b/sfzero/SFZReader.cpp old mode 100644 new mode 100755 diff --git a/sfzero/SFZReader.h b/sfzero/SFZReader.h old mode 100644 new mode 100755 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..5d49011 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) {} + 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); @@ -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/SFZSynth.cpp b/sfzero/SFZSynth.cpp index d798694..2d5eaa2 100644 --- a/sfzero/SFZSynth.cpp +++ b/sfzero/SFZSynth.cpp @@ -8,7 +8,16 @@ #include "SFZSound.h" #include "SFZVoice.h" -sfzero::Synth::Synth() : Synthesiser() {} +sfzero::Synth::Synth() : Synthesiser() { + threadCleaner = new SFZCleaner("Cleaner"); + threadCleaner->startThread(5); + for(int i=0; i<16; i++) + midiVolume_[i] = 127; +} + +sfzero::Synth::~Synth(){ + delete threadCleaner; +} void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) { @@ -91,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); } } @@ -125,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 ab126d4..dcb5108 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,17 +17,21 @@ 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; - + void handleController (int midiChannel, int controllerNumber, int controllerValue) override; + int numVoicesUsed(); juce::String voiceInfoString(); + SFZCleaner* GetCleaner(){return threadCleaner;} private: 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 b8efeca..faef8dc 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), midiVolume_(127) { 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) @@ -155,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) @@ -164,6 +188,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 +199,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(); @@ -182,10 +206,27 @@ 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) { 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; @@ -205,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? @@ -254,6 +295,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); @@ -327,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 e005593..e69de02 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; @@ -29,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(); @@ -48,7 +51,10 @@ class Voice : public juce::SynthesiserVoice EG ampeg_; juce::int64 sampleEnd_; juce::int64 loopStart_, loopEnd_; - + juce::AudioFormatManager *formatManager_; + SFZDiskStreamer *streamer_; + int midiVolume_; + // Info only. int numLoops_; int curVelocity_; @@ -56,6 +62,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) };