| @@ -7,9 +7,12 @@ | |||||
| #include "SFZDebug.h" | #include "SFZDebug.h" | ||||
| #include <stdarg.h> | #include <stdarg.h> | ||||
| namespace sfzero | |||||
| { | |||||
| #ifdef DEBUG | #ifdef DEBUG | ||||
| void sfzero::dbgprintf(const char *msg, ...) | |||||
| void dbgprintf(const char *msg, ...) | |||||
| { | { | ||||
| va_list args; | va_list args; | ||||
| @@ -22,3 +25,5 @@ void sfzero::dbgprintf(const char *msg, ...) | |||||
| } | } | ||||
| #endif // JUCE_DEBUG | #endif // JUCE_DEBUG | ||||
| } | |||||
| @@ -6,16 +6,19 @@ | |||||
| *************************************************************************************/ | *************************************************************************************/ | ||||
| #include "SFZEG.h" | #include "SFZEG.h" | ||||
| namespace sfzero | |||||
| { | |||||
| static const float fastReleaseTime = 0.01f; | static const float fastReleaseTime = 0.01f; | ||||
| sfzero::EG::EG() | |||||
| EG::EG() | |||||
| : segment_(), sampleRate_(0), exponentialDecay_(false), level_(0), slope_(0), samplesUntilNextSegment_(0), segmentIsExponential_(false) | : segment_(), sampleRate_(0), exponentialDecay_(false), level_(0), slope_(0), samplesUntilNextSegment_(0), segmentIsExponential_(false) | ||||
| { | { | ||||
| } | } | ||||
| void sfzero::EG::setExponentialDecay(bool newExponentialDecay) { exponentialDecay_ = newExponentialDecay; } | |||||
| void EG::setExponentialDecay(bool newExponentialDecay) { exponentialDecay_ = newExponentialDecay; } | |||||
| void sfzero::EG::startNote(const EGParameters *newParameters, float floatVelocity, double newSampleRate, | |||||
| void EG::startNote(const EGParameters *newParameters, float floatVelocity, double newSampleRate, | |||||
| const EGParameters *velMod) | const EGParameters *velMod) | ||||
| { | { | ||||
| parameters_ = *newParameters; | parameters_ = *newParameters; | ||||
| @@ -41,7 +44,7 @@ void sfzero::EG::startNote(const EGParameters *newParameters, float floatVelocit | |||||
| startDelay(); | startDelay(); | ||||
| } | } | ||||
| void sfzero::EG::nextSegment() | |||||
| void EG::nextSegment() | |||||
| { | { | ||||
| switch (segment_) | switch (segment_) | ||||
| { | { | ||||
| @@ -72,9 +75,9 @@ void sfzero::EG::nextSegment() | |||||
| } | } | ||||
| } | } | ||||
| void sfzero::EG::noteOff() { startRelease(); } | |||||
| void EG::noteOff() { startRelease(); } | |||||
| void sfzero::EG::fastRelease() | |||||
| void EG::fastRelease() | |||||
| { | { | ||||
| segment_ = Release; | segment_ = Release; | ||||
| samplesUntilNextSegment_ = static_cast<int>(fastReleaseTime * sampleRate_); | samplesUntilNextSegment_ = static_cast<int>(fastReleaseTime * sampleRate_); | ||||
| @@ -82,7 +85,7 @@ void sfzero::EG::fastRelease() | |||||
| segmentIsExponential_ = false; | segmentIsExponential_ = false; | ||||
| } | } | ||||
| void sfzero::EG::startDelay() | |||||
| void EG::startDelay() | |||||
| { | { | ||||
| if (parameters_.delay <= 0) | if (parameters_.delay <= 0) | ||||
| { | { | ||||
| @@ -98,7 +101,7 @@ void sfzero::EG::startDelay() | |||||
| } | } | ||||
| } | } | ||||
| void sfzero::EG::startAttack() | |||||
| void EG::startAttack() | |||||
| { | { | ||||
| if (parameters_.attack <= 0) | if (parameters_.attack <= 0) | ||||
| { | { | ||||
| @@ -114,7 +117,7 @@ void sfzero::EG::startAttack() | |||||
| } | } | ||||
| } | } | ||||
| void sfzero::EG::startHold() | |||||
| void EG::startHold() | |||||
| { | { | ||||
| if (parameters_.hold <= 0) | if (parameters_.hold <= 0) | ||||
| { | { | ||||
| @@ -131,7 +134,7 @@ void sfzero::EG::startHold() | |||||
| } | } | ||||
| } | } | ||||
| void sfzero::EG::startDecay() | |||||
| void EG::startDecay() | |||||
| { | { | ||||
| if (parameters_.decay <= 0) | if (parameters_.decay <= 0) | ||||
| { | { | ||||
| @@ -170,7 +173,7 @@ void sfzero::EG::startDecay() | |||||
| } | } | ||||
| } | } | ||||
| void sfzero::EG::startSustain() | |||||
| void EG::startSustain() | |||||
| { | { | ||||
| if (parameters_.sustain <= 0) | if (parameters_.sustain <= 0) | ||||
| { | { | ||||
| @@ -186,7 +189,7 @@ void sfzero::EG::startSustain() | |||||
| } | } | ||||
| } | } | ||||
| void sfzero::EG::startRelease() | |||||
| void EG::startRelease() | |||||
| { | { | ||||
| float release = parameters_.release; | float release = parameters_.release; | ||||
| @@ -212,4 +215,6 @@ void sfzero::EG::startRelease() | |||||
| } | } | ||||
| } | } | ||||
| const float sfzero::EG::BottomLevel = 0.001f; | |||||
| const float EG::BottomLevel = 0.001f; | |||||
| } | |||||
| @@ -10,11 +10,14 @@ | |||||
| #include "water/memory/MemoryBlock.h" | #include "water/memory/MemoryBlock.h" | ||||
| sfzero::Reader::Reader(sfzero::Sound *soundIn) : sound_(soundIn), line_(1) {} | |||||
| namespace sfzero | |||||
| { | |||||
| Reader::Reader(Sound *soundIn) : sound_(soundIn), line_(1) {} | |||||
| sfzero::Reader::~Reader() {} | |||||
| Reader::~Reader() {} | |||||
| void sfzero::Reader::read(const water::File &file) | |||||
| void Reader::read(const water::File &file) | |||||
| { | { | ||||
| water::MemoryBlock contents; | water::MemoryBlock contents; | ||||
| bool ok = file.loadFileAsData(contents); | bool ok = file.loadFileAsData(contents); | ||||
| @@ -28,15 +31,15 @@ void sfzero::Reader::read(const water::File &file) | |||||
| read(static_cast<const char *>(contents.getData()), static_cast<int>(contents.getSize())); | read(static_cast<const char *>(contents.getData()), static_cast<int>(contents.getSize())); | ||||
| } | } | ||||
| void sfzero::Reader::read(const char *text, unsigned int length) | |||||
| void Reader::read(const char *text, unsigned int length) | |||||
| { | { | ||||
| const char *p = text; | const char *p = text; | ||||
| const char *end = text + length; | const char *end = text + length; | ||||
| char c = 0; | char c = 0; | ||||
| sfzero::Region curGroup; | |||||
| sfzero::Region curRegion; | |||||
| sfzero::Region *buildingRegion = nullptr; | |||||
| Region curGroup; | |||||
| Region curRegion; | |||||
| Region *buildingRegion = nullptr; | |||||
| bool inControl = false; | bool inControl = false; | ||||
| water::String defaultPath; | water::String defaultPath; | ||||
| @@ -108,7 +111,7 @@ void sfzero::Reader::read(const char *text, unsigned int length) | |||||
| error("Unterminated tag"); | error("Unterminated tag"); | ||||
| goto fatalError; | goto fatalError; | ||||
| } | } | ||||
| sfzero::StringSlice tag(tagStart, p - 1); | |||||
| StringSlice tag(tagStart, p - 1); | |||||
| if (tag == "region") | if (tag == "region") | ||||
| { | { | ||||
| if (buildingRegion && (buildingRegion == &curRegion)) | if (buildingRegion && (buildingRegion == &curRegion)) | ||||
| @@ -176,7 +179,7 @@ void sfzero::Reader::read(const char *text, unsigned int length) | |||||
| error("Malformed parameter"); | error("Malformed parameter"); | ||||
| goto nextElement; | goto nextElement; | ||||
| } | } | ||||
| sfzero::StringSlice opcode(parameterStart, p - 1); | |||||
| StringSlice opcode(parameterStart, p - 1); | |||||
| if (inControl) | if (inControl) | ||||
| { | { | ||||
| if (opcode == "default_path") | if (opcode == "default_path") | ||||
| @@ -259,7 +262,7 @@ void sfzero::Reader::read(const char *text, unsigned int length) | |||||
| } | } | ||||
| else if (opcode == "trigger") | else if (opcode == "trigger") | ||||
| { | { | ||||
| buildingRegion->trigger = static_cast<sfzero::Region::Trigger>(triggerValue(value)); | |||||
| buildingRegion->trigger = static_cast<Region::Trigger>(triggerValue(value)); | |||||
| } | } | ||||
| else if (opcode == "group") | else if (opcode == "group") | ||||
| { | { | ||||
| @@ -290,7 +293,7 @@ void sfzero::Reader::read(const char *text, unsigned int length) | |||||
| bool modeIsSupported = value == "no_loop" || value == "one_shot" || value == "loop_continuous"; | bool modeIsSupported = value == "no_loop" || value == "one_shot" || value == "loop_continuous"; | ||||
| if (modeIsSupported) | if (modeIsSupported) | ||||
| { | { | ||||
| buildingRegion->loop_mode = static_cast<sfzero::Region::LoopMode>(loopModeValue(value)); | |||||
| buildingRegion->loop_mode = static_cast<Region::LoopMode>(loopModeValue(value)); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -432,7 +435,7 @@ fatalError: | |||||
| } | } | ||||
| } | } | ||||
| const char *sfzero::Reader::handleLineEnd(const char *p) | |||||
| const char *Reader::handleLineEnd(const char *p) | |||||
| { | { | ||||
| // Check for DOS-style line ending. | // Check for DOS-style line ending. | ||||
| char lineEndChar = *p++; | char lineEndChar = *p++; | ||||
| @@ -445,7 +448,7 @@ const char *sfzero::Reader::handleLineEnd(const char *p) | |||||
| return p; | return p; | ||||
| } | } | ||||
| const char *sfzero::Reader::readPathInto(water::String *pathOut, const char *pIn, const char *endIn) | |||||
| const char *Reader::readPathInto(water::String *pathOut, const char *pIn, const char *endIn) | |||||
| { | { | ||||
| // Paths are kind of funny to parse because they can contain whitespace. | // Paths are kind of funny to parse because they can contain whitespace. | ||||
| const char *p = pIn; | const char *p = pIn; | ||||
| @@ -497,7 +500,7 @@ const char *sfzero::Reader::readPathInto(water::String *pathOut, const char *pIn | |||||
| return p; | return p; | ||||
| } | } | ||||
| int sfzero::Reader::keyValue(const water::String &str) | |||||
| int Reader::keyValue(const water::String &str) | |||||
| { | { | ||||
| auto chars = str.toRawUTF8(); | auto chars = str.toRawUTF8(); | ||||
| @@ -542,56 +545,58 @@ int sfzero::Reader::keyValue(const water::String &str) | |||||
| return result; | return result; | ||||
| } | } | ||||
| int sfzero::Reader::triggerValue(const water::String &str) | |||||
| int Reader::triggerValue(const water::String &str) | |||||
| { | { | ||||
| if (str == "release") | if (str == "release") | ||||
| { | { | ||||
| return sfzero::Region::release; | |||||
| return Region::release; | |||||
| } | } | ||||
| if (str == "first") | if (str == "first") | ||||
| { | { | ||||
| return sfzero::Region::first; | |||||
| return Region::first; | |||||
| } | } | ||||
| if (str == "legato") | if (str == "legato") | ||||
| { | { | ||||
| return sfzero::Region::legato; | |||||
| return Region::legato; | |||||
| } | } | ||||
| return sfzero::Region::attack; | |||||
| return Region::attack; | |||||
| } | } | ||||
| int sfzero::Reader::loopModeValue(const water::String &str) | |||||
| int Reader::loopModeValue(const water::String &str) | |||||
| { | { | ||||
| if (str == "no_loop") | if (str == "no_loop") | ||||
| { | { | ||||
| return sfzero::Region::no_loop; | |||||
| return Region::no_loop; | |||||
| } | } | ||||
| if (str == "one_shot") | if (str == "one_shot") | ||||
| { | { | ||||
| return sfzero::Region::one_shot; | |||||
| return Region::one_shot; | |||||
| } | } | ||||
| if (str == "loop_continuous") | if (str == "loop_continuous") | ||||
| { | { | ||||
| return sfzero::Region::loop_continuous; | |||||
| return Region::loop_continuous; | |||||
| } | } | ||||
| if (str == "loop_sustain") | if (str == "loop_sustain") | ||||
| { | { | ||||
| return sfzero::Region::loop_sustain; | |||||
| return Region::loop_sustain; | |||||
| } | } | ||||
| return sfzero::Region::sample_loop; | |||||
| return Region::sample_loop; | |||||
| } | } | ||||
| void sfzero::Reader::finishRegion(sfzero::Region *region) | |||||
| void Reader::finishRegion(Region *region) | |||||
| { | { | ||||
| sfzero::Region *newRegion = new sfzero::Region(); | |||||
| Region *newRegion = new Region(); | |||||
| *newRegion = *region; | *newRegion = *region; | ||||
| sound_->addRegion(newRegion); | sound_->addRegion(newRegion); | ||||
| } | } | ||||
| void sfzero::Reader::error(const water::String &message) | |||||
| void Reader::error(const water::String &message) | |||||
| { | { | ||||
| water::String fullMessage = message; | water::String fullMessage = message; | ||||
| fullMessage += " (line " + water::String(line_) + ")."; | fullMessage += " (line " + water::String(line_) + ")."; | ||||
| sound_->addError(fullMessage); | sound_->addError(fullMessage); | ||||
| } | } | ||||
| } | |||||
| @@ -4,10 +4,14 @@ | |||||
| * Forked from https://github.com/stevefolta/SFZero | * Forked from https://github.com/stevefolta/SFZero | ||||
| * For license info please see the LICENSE file distributed with this source code | * For license info please see the LICENSE file distributed with this source code | ||||
| *************************************************************************************/ | *************************************************************************************/ | ||||
| #include "SFZRegion.h" | #include "SFZRegion.h" | ||||
| #include "SFZSample.h" | #include "SFZSample.h" | ||||
| void sfzero::EGParameters::clear() | |||||
| namespace sfzero | |||||
| { | |||||
| void EGParameters::clear() | |||||
| { | { | ||||
| delay = 0.0; | delay = 0.0; | ||||
| start = 0.0; | start = 0.0; | ||||
| @@ -18,15 +22,15 @@ void sfzero::EGParameters::clear() | |||||
| release = 0.0; | release = 0.0; | ||||
| } | } | ||||
| void sfzero::EGParameters::clearMod() | |||||
| void EGParameters::clearMod() | |||||
| { | { | ||||
| // Clear for velocity or other modification. | // Clear for velocity or other modification. | ||||
| delay = start = attack = hold = decay = sustain = release = 0.0; | delay = start = attack = hold = decay = sustain = release = 0.0; | ||||
| } | } | ||||
| sfzero::Region::Region() { clear(); } | |||||
| Region::Region() { clear(); } | |||||
| void sfzero::Region::clear() | |||||
| void Region::clear() | |||||
| { | { | ||||
| memset(this, 0, sizeof(*this)); | memset(this, 0, sizeof(*this)); | ||||
| hikey = 127; | hikey = 127; | ||||
| @@ -41,7 +45,7 @@ void sfzero::Region::clear() | |||||
| ampeg_veltrack.clearMod(); | ampeg_veltrack.clearMod(); | ||||
| } | } | ||||
| void sfzero::Region::clearForSF2() | |||||
| void Region::clearForSF2() | |||||
| { | { | ||||
| clear(); | clear(); | ||||
| pitch_keycenter = -1; | pitch_keycenter = -1; | ||||
| @@ -56,7 +60,7 @@ void sfzero::Region::clearForSF2() | |||||
| ampeg.release = -12000.0; | ampeg.release = -12000.0; | ||||
| } | } | ||||
| void sfzero::Region::clearForRelativeSF2() | |||||
| void Region::clearForRelativeSF2() | |||||
| { | { | ||||
| clear(); | clear(); | ||||
| pitch_keytrack = 0; | pitch_keytrack = 0; | ||||
| @@ -64,7 +68,7 @@ void sfzero::Region::clearForRelativeSF2() | |||||
| ampeg.sustain = 0.0; | ampeg.sustain = 0.0; | ||||
| } | } | ||||
| void sfzero::Region::addForSF2(sfzero::Region *other) | |||||
| void Region::addForSF2(Region *other) | |||||
| { | { | ||||
| offset += other->offset; | offset += other->offset; | ||||
| end += other->end; | end += other->end; | ||||
| @@ -84,7 +88,7 @@ void sfzero::Region::addForSF2(sfzero::Region *other) | |||||
| ampeg.release += other->ampeg.release; | ampeg.release += other->ampeg.release; | ||||
| } | } | ||||
| void sfzero::Region::sf2ToSFZ() | |||||
| void Region::sf2ToSFZ() | |||||
| { | { | ||||
| // EG times need to be converted from timecents to seconds. | // EG times need to be converted from timecents to seconds. | ||||
| ampeg.delay = timecents2Secs(static_cast<int>(ampeg.delay)); | ampeg.delay = timecents2Secs(static_cast<int>(ampeg.delay)); | ||||
| @@ -132,7 +136,7 @@ void sfzero::Region::sf2ToSFZ() | |||||
| } | } | ||||
| } | } | ||||
| water::String sfzero::Region::dump() | |||||
| water::String Region::dump() | |||||
| { | { | ||||
| water::String info = water::String::formatted("%d - %d, vel %d - %d", lokey, hikey, lovel, hivel); | water::String info = water::String::formatted("%d - %d, vel %d - %d", lokey, hikey, lovel, hivel); | ||||
| if (sample) | if (sample) | ||||
| @@ -143,4 +147,6 @@ water::String sfzero::Region::dump() | |||||
| return info; | return info; | ||||
| } | } | ||||
| float sfzero::Region::timecents2Secs(int timecents) { return static_cast<float>(pow(2.0, timecents / 1200.0)); } | |||||
| float Region::timecents2Secs(int timecents) { return static_cast<float>(pow(2.0, timecents / 1200.0)); } | |||||
| } | |||||
| @@ -90,6 +90,7 @@ struct Region | |||||
| static float timecents2Secs(int timecents); | static float timecents2Secs(int timecents); | ||||
| }; | }; | ||||
| } | } | ||||
| #endif // SFZREGION_H_INCLUDED | #endif // SFZREGION_H_INCLUDED | ||||
| @@ -4,11 +4,15 @@ | |||||
| * Forked from https://github.com/stevefolta/SFZero | * Forked from https://github.com/stevefolta/SFZero | ||||
| * For license info please see the LICENSE file distributed with this source code | * For license info please see the LICENSE file distributed with this source code | ||||
| *************************************************************************************/ | *************************************************************************************/ | ||||
| #include "SFZSample.h" | #include "SFZSample.h" | ||||
| #include "SFZDebug.h" | #include "SFZDebug.h" | ||||
| namespace sfzero | |||||
| { | |||||
| #if 0 | #if 0 | ||||
| bool sfzero::Sample::load(water::AudioFormatManager *formatManager) | |||||
| bool Sample::load(water::AudioFormatManager *formatManager) | |||||
| { | { | ||||
| water::AudioFormatReader *reader = formatManager->createReaderFor(file_); | water::AudioFormatReader *reader = formatManager->createReaderFor(file_); | ||||
| @@ -37,31 +41,31 @@ bool sfzero::Sample::load(water::AudioFormatManager *formatManager) | |||||
| } | } | ||||
| #endif | #endif | ||||
| sfzero::Sample::~Sample() { delete buffer_; } | |||||
| Sample::~Sample() { delete buffer_; } | |||||
| water::String sfzero::Sample::getShortName() { return (file_.getFileName()); } | |||||
| water::String Sample::getShortName() { return (file_.getFileName()); } | |||||
| void sfzero::Sample::setBuffer(water::AudioSampleBuffer *newBuffer) | |||||
| void Sample::setBuffer(water::AudioSampleBuffer *newBuffer) | |||||
| { | { | ||||
| buffer_ = newBuffer; | buffer_ = newBuffer; | ||||
| sampleLength_ = buffer_->getNumSamples(); | sampleLength_ = buffer_->getNumSamples(); | ||||
| } | } | ||||
| water::AudioSampleBuffer *sfzero::Sample::detachBuffer() | |||||
| water::AudioSampleBuffer *Sample::detachBuffer() | |||||
| { | { | ||||
| water::AudioSampleBuffer *result = buffer_; | water::AudioSampleBuffer *result = buffer_; | ||||
| buffer_ = nullptr; | buffer_ = nullptr; | ||||
| return result; | return result; | ||||
| } | } | ||||
| water::String sfzero::Sample::dump() { return file_.getFullPathName() + "\n"; } | |||||
| water::String Sample::dump() { return file_.getFullPathName() + "\n"; } | |||||
| #ifdef DEBUG | #ifdef DEBUG | ||||
| void sfzero::Sample::checkIfZeroed(const char *where) | |||||
| void Sample::checkIfZeroed(const char *where) | |||||
| { | { | ||||
| if (buffer_ == nullptr) | if (buffer_ == nullptr) | ||||
| { | { | ||||
| sfzero::dbgprintf("SFZSample::checkIfZeroed(%s): no buffer!", where); | |||||
| dbgprintf("SFZSample::checkIfZeroed(%s): no buffer!", where); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -81,12 +85,13 @@ void sfzero::Sample::checkIfZeroed(const char *where) | |||||
| } | } | ||||
| if (nonzero > 0) | if (nonzero > 0) | ||||
| { | { | ||||
| sfzero::dbgprintf("Buffer not zeroed at %s (%lu vs. %lu).", where, nonzero, zero); | |||||
| dbgprintf("Buffer not zeroed at %s (%lu vs. %lu).", where, nonzero, zero); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| sfzero::dbgprintf("Buffer zeroed at %s! (%lu zeros)", where, zero); | |||||
| dbgprintf("Buffer zeroed at %s! (%lu zeros)", where, zero); | |||||
| } | } | ||||
| } | } | ||||
| #endif // JUCE_DEBUG | #endif // JUCE_DEBUG | ||||
| } | |||||
| @@ -4,13 +4,17 @@ | |||||
| * Forked from https://github.com/stevefolta/SFZero | * Forked from https://github.com/stevefolta/SFZero | ||||
| * For license info please see the LICENSE file distributed with this source code | * For license info please see the LICENSE file distributed with this source code | ||||
| *************************************************************************************/ | *************************************************************************************/ | ||||
| #include "SFZSound.h" | #include "SFZSound.h" | ||||
| #include "SFZReader.h" | #include "SFZReader.h" | ||||
| #include "SFZRegion.h" | #include "SFZRegion.h" | ||||
| #include "SFZSample.h" | #include "SFZSample.h" | ||||
| sfzero::Sound::Sound(const water::File &fileIn) : file_(fileIn) {} | |||||
| sfzero::Sound::~Sound() | |||||
| namespace sfzero | |||||
| { | |||||
| Sound::Sound(const water::File &fileIn) : file_(fileIn) {} | |||||
| Sound::~Sound() | |||||
| { | { | ||||
| int numRegions = regions_.size(); | int numRegions = regions_.size(); | ||||
| @@ -20,21 +24,21 @@ sfzero::Sound::~Sound() | |||||
| regions_.set(i, nullptr); | regions_.set(i, nullptr); | ||||
| } | } | ||||
| for (water::HashMap<water::String, sfzero::Sample *>::Iterator i(samples_); i.next();) | |||||
| for (water::HashMap<water::String, Sample *>::Iterator i(samples_); i.next();) | |||||
| { | { | ||||
| delete i.getValue(); | delete i.getValue(); | ||||
| } | } | ||||
| } | } | ||||
| bool sfzero::Sound::appliesToNote(int /*midiNoteNumber*/) | |||||
| bool Sound::appliesToNote(int /*midiNoteNumber*/) | |||||
| { | { | ||||
| // Just say yes; we can't truly know unless we're told the velocity as well. | // Just say yes; we can't truly know unless we're told the velocity as well. | ||||
| return true; | return true; | ||||
| } | } | ||||
| bool sfzero::Sound::appliesToChannel(int /*midiChannel*/) { return true; } | |||||
| void sfzero::Sound::addRegion(sfzero::Region *region) { regions_.add(region); } | |||||
| sfzero::Sample *sfzero::Sound::addSample(water::String path, water::String defaultPath) | |||||
| bool Sound::appliesToChannel(int /*midiChannel*/) { return true; } | |||||
| void Sound::addRegion(Region *region) { regions_.add(region); } | |||||
| Sample *Sound::addSample(water::String path, water::String defaultPath) | |||||
| { | { | ||||
| path = path.replaceCharacter('\\', '/'); | path = path.replaceCharacter('\\', '/'); | ||||
| defaultPath = defaultPath.replaceCharacter('\\', '/'); | defaultPath = defaultPath.replaceCharacter('\\', '/'); | ||||
| @@ -49,18 +53,18 @@ sfzero::Sample *sfzero::Sound::addSample(water::String path, water::String defau | |||||
| sampleFile = defaultDir.getChildFile(path); | sampleFile = defaultDir.getChildFile(path); | ||||
| } | } | ||||
| water::String samplePath = sampleFile.getFullPathName(); | water::String samplePath = sampleFile.getFullPathName(); | ||||
| sfzero::Sample *sample = samples_[samplePath]; | |||||
| Sample *sample = samples_[samplePath]; | |||||
| if (sample == nullptr) | if (sample == nullptr) | ||||
| { | { | ||||
| sample = new sfzero::Sample(sampleFile); | |||||
| sample = new Sample(sampleFile); | |||||
| samples_.set(samplePath, sample); | samples_.set(samplePath, sample); | ||||
| } | } | ||||
| return sample; | return sample; | ||||
| } | } | ||||
| void sfzero::Sound::addError(const water::String &message) { errors_.add(message); } | |||||
| void Sound::addError(const water::String &message) { errors_.add(message); } | |||||
| void sfzero::Sound::addUnsupportedOpcode(const water::String &opcode) | |||||
| void Sound::addUnsupportedOpcode(const water::String &opcode) | |||||
| { | { | ||||
| if (!unsupportedOpcodes_.contains(opcode)) | if (!unsupportedOpcodes_.contains(opcode)) | ||||
| { | { | ||||
| @@ -71,14 +75,14 @@ void sfzero::Sound::addUnsupportedOpcode(const water::String &opcode) | |||||
| } | } | ||||
| } | } | ||||
| void sfzero::Sound::loadRegions() | |||||
| void Sound::loadRegions() | |||||
| { | { | ||||
| sfzero::Reader reader(this); | |||||
| Reader reader(this); | |||||
| reader.read(file_); | reader.read(file_); | ||||
| } | } | ||||
| void sfzero::Sound::loadSamples(water::AudioFormatManager* formatManager, double* progressVar, CarlaThread* thread) | |||||
| void Sound::loadSamples(water::AudioFormatManager* formatManager, double* progressVar, CarlaThread* thread) | |||||
| { | { | ||||
| if (progressVar) | if (progressVar) | ||||
| { | { | ||||
| @@ -86,9 +90,9 @@ void sfzero::Sound::loadSamples(water::AudioFormatManager* formatManager, double | |||||
| } | } | ||||
| double numSamplesLoaded = 1.0, numSamples = samples_.size(); | double numSamplesLoaded = 1.0, numSamples = samples_.size(); | ||||
| for (water::HashMap<water::String, sfzero::Sample *>::Iterator i(samples_); i.next();) | |||||
| for (water::HashMap<water::String, Sample *>::Iterator i(samples_); i.next();) | |||||
| { | { | ||||
| sfzero::Sample *sample = i.getValue(); | |||||
| Sample *sample = i.getValue(); | |||||
| #if 0 | #if 0 | ||||
| bool ok = sample->load(formatManager); | bool ok = sample->load(formatManager); | ||||
| if (!ok) | if (!ok) | ||||
| @@ -114,13 +118,13 @@ void sfzero::Sound::loadSamples(water::AudioFormatManager* formatManager, double | |||||
| } | } | ||||
| } | } | ||||
| sfzero::Region *sfzero::Sound::getRegionFor(int note, int velocity, sfzero::Region::Trigger trigger) | |||||
| Region *Sound::getRegionFor(int note, int velocity, Region::Trigger trigger) | |||||
| { | { | ||||
| int numRegions = regions_.size(); | int numRegions = regions_.size(); | ||||
| for (int i = 0; i < numRegions; ++i) | for (int i = 0; i < numRegions; ++i) | ||||
| { | { | ||||
| sfzero::Region *region = regions_[i]; | |||||
| Region *region = regions_[i]; | |||||
| if (region->matches(note, velocity, trigger)) | if (region->matches(note, velocity, trigger)) | ||||
| { | { | ||||
| return region; | return region; | ||||
| @@ -130,19 +134,19 @@ sfzero::Region *sfzero::Sound::getRegionFor(int note, int velocity, sfzero::Regi | |||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| int sfzero::Sound::getNumRegions() { return regions_.size(); } | |||||
| int Sound::getNumRegions() { return regions_.size(); } | |||||
| sfzero::Region *sfzero::Sound::regionAt(int index) { return regions_[index]; } | |||||
| Region *Sound::regionAt(int index) { return regions_[index]; } | |||||
| int sfzero::Sound::numSubsounds() { return 1; } | |||||
| int Sound::numSubsounds() { return 1; } | |||||
| water::String sfzero::Sound::subsoundName(int /*whichSubsound*/) { return water::String(); } | |||||
| water::String Sound::subsoundName(int /*whichSubsound*/) { return water::String(); } | |||||
| void sfzero::Sound::useSubsound(int /*whichSubsound*/) {} | |||||
| void Sound::useSubsound(int /*whichSubsound*/) {} | |||||
| int sfzero::Sound::selectedSubsound() { return 0; } | |||||
| int Sound::selectedSubsound() { return 0; } | |||||
| water::String sfzero::Sound::dump() | |||||
| water::String Sound::dump() | |||||
| { | { | ||||
| water::String info; | water::String info; | ||||
| auto &errors = getErrors(); | auto &errors = getErrors(); | ||||
| @@ -184,7 +188,7 @@ water::String sfzero::Sound::dump() | |||||
| if (samples_.size() > 0) | if (samples_.size() > 0) | ||||
| { | { | ||||
| info << samples_.size() << " samples: \n"; | info << samples_.size() << " samples: \n"; | ||||
| for (water::HashMap<water::String, sfzero::Sample *>::Iterator i(samples_); i.next();) | |||||
| for (water::HashMap<water::String, Sample *>::Iterator i(samples_); i.next();) | |||||
| { | { | ||||
| info << i.getValue()->dump(); | info << i.getValue()->dump(); | ||||
| } | } | ||||
| @@ -195,3 +199,5 @@ water::String sfzero::Sound::dump() | |||||
| } | } | ||||
| return info; | return info; | ||||
| } | } | ||||
| } | |||||
| @@ -9,23 +9,13 @@ | |||||
| #include "SFZRegion.h" | #include "SFZRegion.h" | ||||
| #include "water/containers/HashMap.h" | #include "water/containers/HashMap.h" | ||||
| #include "water/memory/ReferenceCountedObject.h" | |||||
| #include "water/synthesisers/Synthesiser.h" | |||||
| #include "water/text/StringArray.h" | #include "water/text/StringArray.h" | ||||
| #include "CarlaJuceUtils.hpp" | |||||
| #include "CarlaThread.hpp" | #include "CarlaThread.hpp" | ||||
| #if 1 | |||||
| namespace water { | |||||
| class SynthesiserSound { | |||||
| public: | |||||
| virtual bool appliesToNote(int midiNoteNumber) = 0; | |||||
| virtual bool appliesToChannel(int midiChannel) = 0; | |||||
| }; | |||||
| } | |||||
| #endif | |||||
| namespace sfzero | namespace sfzero | ||||
| { | { | ||||
| @@ -4,29 +4,32 @@ | |||||
| * Forked from https://github.com/stevefolta/SFZero | * Forked from https://github.com/stevefolta/SFZero | ||||
| * For license info please see the LICENSE file distributed with this source code | * For license info please see the LICENSE file distributed with this source code | ||||
| *************************************************************************************/ | *************************************************************************************/ | ||||
| #include "SFZSynth.h" | #include "SFZSynth.h" | ||||
| #include "SFZSound.h" | #include "SFZSound.h" | ||||
| #include "SFZVoice.h" | #include "SFZVoice.h" | ||||
| sfzero::Synth::Synth() : Synthesiser() {} | |||||
| namespace sfzero | |||||
| { | |||||
| Synth::Synth() : Synthesiser() {} | |||||
| #if 0 | |||||
| void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||||
| void Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||||
| { | { | ||||
| int i; | int i; | ||||
| const water::ScopedLock locker(lock); | |||||
| const CarlaMutexLocker locker(lock); | |||||
| int midiVelocity = static_cast<int>(velocity * 127); | int midiVelocity = static_cast<int>(velocity * 127); | ||||
| // First, stop any currently-playing sounds in the group. | // First, stop any currently-playing sounds in the group. | ||||
| //*** Currently, this only pays attention to the first matching region. | //*** Currently, this only pays attention to the first matching region. | ||||
| int group = 0; | int group = 0; | ||||
| sfzero::Sound *sound = dynamic_cast<sfzero::Sound *>(getSound(0)); | |||||
| Sound *sound = dynamic_cast<Sound *>(getSound(0)); | |||||
| if (sound) | if (sound) | ||||
| { | { | ||||
| sfzero::Region *region = sound->getRegionFor(midiNoteNumber, midiVelocity); | |||||
| Region *region = sound->getRegionFor(midiNoteNumber, midiVelocity); | |||||
| if (region) | if (region) | ||||
| { | { | ||||
| group = region->group; | group = region->group; | ||||
| @@ -36,7 +39,7 @@ void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||||
| { | { | ||||
| for (i = voices.size(); --i >= 0;) | for (i = voices.size(); --i >= 0;) | ||||
| { | { | ||||
| sfzero::Voice *voice = dynamic_cast<sfzero::Voice *>(voices.getUnchecked(i)); | |||||
| Voice *voice = dynamic_cast<Voice *>(voices.getUnchecked(i)); | |||||
| if (voice == nullptr) | if (voice == nullptr) | ||||
| { | { | ||||
| continue; | continue; | ||||
| @@ -53,7 +56,7 @@ void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||||
| bool anyNotesPlaying = false; | bool anyNotesPlaying = false; | ||||
| for (i = voices.size(); --i >= 0;) | for (i = voices.size(); --i >= 0;) | ||||
| { | { | ||||
| sfzero::Voice *voice = dynamic_cast<sfzero::Voice *>(voices.getUnchecked(i)); | |||||
| Voice *voice = dynamic_cast<Voice *>(voices.getUnchecked(i)); | |||||
| if (voice == nullptr) | if (voice == nullptr) | ||||
| { | { | ||||
| continue; | continue; | ||||
| @@ -78,17 +81,17 @@ void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||||
| } | } | ||||
| // Play *all* matching regions. | // Play *all* matching regions. | ||||
| sfzero::Region::Trigger trigger = (anyNotesPlaying ? sfzero::Region::legato : sfzero::Region::first); | |||||
| Region::Trigger trigger = (anyNotesPlaying ? Region::legato : Region::first); | |||||
| if (sound) | if (sound) | ||||
| { | { | ||||
| int numRegions = sound->getNumRegions(); | int numRegions = sound->getNumRegions(); | ||||
| for (i = 0; i < numRegions; ++i) | for (i = 0; i < numRegions; ++i) | ||||
| { | { | ||||
| sfzero::Region *region = sound->regionAt(i); | |||||
| Region *region = sound->regionAt(i); | |||||
| if (region->matches(midiNoteNumber, midiVelocity, trigger)) | if (region->matches(midiNoteNumber, midiVelocity, trigger)) | ||||
| { | { | ||||
| sfzero::Voice *voice = | |||||
| dynamic_cast<sfzero::Voice *>(findFreeVoice(sound, midiNoteNumber, midiChannel, isNoteStealingEnabled())); | |||||
| Voice *voice = | |||||
| dynamic_cast<Voice *>(findFreeVoice(sound, midiNoteNumber, midiChannel, isNoteStealingEnabled())); | |||||
| if (voice) | if (voice) | ||||
| { | { | ||||
| voice->setRegion(region); | voice->setRegion(region); | ||||
| @@ -100,23 +103,21 @@ void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||||
| noteVelocities_[midiNoteNumber] = midiVelocity; | noteVelocities_[midiNoteNumber] = midiVelocity; | ||||
| } | } | ||||
| #endif | |||||
| #if 0 | |||||
| void sfzero::Synth::noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) | |||||
| void Synth::noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) | |||||
| { | { | ||||
| const water::ScopedLock locker(lock); | |||||
| const CarlaMutexLocker locker(lock); | |||||
| Synthesiser::noteOff(midiChannel, midiNoteNumber, velocity, allowTailOff); | Synthesiser::noteOff(midiChannel, midiNoteNumber, velocity, allowTailOff); | ||||
| // Start release region. | // Start release region. | ||||
| sfzero::Sound *sound = dynamic_cast<sfzero::Sound *>(getSound(0)); | |||||
| Sound *sound = dynamic_cast<Sound *>(getSound(0)); | |||||
| if (sound) | if (sound) | ||||
| { | { | ||||
| sfzero::Region *region = sound->getRegionFor(midiNoteNumber, noteVelocities_[midiNoteNumber], sfzero::Region::release); | |||||
| Region *region = sound->getRegionFor(midiNoteNumber, noteVelocities_[midiNoteNumber], Region::release); | |||||
| if (region) | if (region) | ||||
| { | { | ||||
| sfzero::Voice *voice = dynamic_cast<sfzero::Voice *>(findFreeVoice(sound, midiNoteNumber, midiChannel, false)); | |||||
| Voice *voice = dynamic_cast<Voice *>(findFreeVoice(sound, midiNoteNumber, midiChannel, false)); | |||||
| if (voice) | if (voice) | ||||
| { | { | ||||
| // Synthesiser is too locked-down (ivars are private rt protected), so | // Synthesiser is too locked-down (ivars are private rt protected), so | ||||
| @@ -127,13 +128,11 @@ void sfzero::Synth::noteOff(int midiChannel, int midiNoteNumber, float velocity, | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| #endif | |||||
| int sfzero::Synth::numVoicesUsed() | |||||
| int Synth::numVoicesUsed() | |||||
| { | { | ||||
| int numUsed = 0; | int numUsed = 0; | ||||
| #if 0 | |||||
| for (int i = voices.size(); --i >= 0;) | for (int i = voices.size(); --i >= 0;) | ||||
| { | { | ||||
| if (voices.getUnchecked(i)->getCurrentlyPlayingNote() >= 0) | if (voices.getUnchecked(i)->getCurrentlyPlayingNote() >= 0) | ||||
| @@ -141,11 +140,11 @@ int sfzero::Synth::numVoicesUsed() | |||||
| numUsed += 1; | numUsed += 1; | ||||
| } | } | ||||
| } | } | ||||
| #endif | |||||
| return numUsed; | return numUsed; | ||||
| } | } | ||||
| water::String sfzero::Synth::voiceInfoString() | |||||
| water::String Synth::voiceInfoString() | |||||
| { | { | ||||
| enum | enum | ||||
| { | { | ||||
| @@ -154,10 +153,10 @@ water::String sfzero::Synth::voiceInfoString() | |||||
| water::StringArray lines; | water::StringArray lines; | ||||
| int numUsed = 0, numShown = 0; | int numUsed = 0, numShown = 0; | ||||
| #if 0 | |||||
| for (int i = voices.size(); --i >= 0;) | for (int i = voices.size(); --i >= 0;) | ||||
| { | { | ||||
| sfzero::Voice *voice = dynamic_cast<sfzero::Voice *>(voices.getUnchecked(i)); | |||||
| Voice *voice = dynamic_cast<Voice *>(voices.getUnchecked(i)); | |||||
| if (voice->getCurrentlyPlayingNote() < 0) | if (voice->getCurrentlyPlayingNote() < 0) | ||||
| { | { | ||||
| continue; | continue; | ||||
| @@ -169,7 +168,9 @@ water::String sfzero::Synth::voiceInfoString() | |||||
| } | } | ||||
| lines.add(voice->infoString()); | lines.add(voice->infoString()); | ||||
| } | } | ||||
| #endif | |||||
| lines.insert(0, "voices used: " + water::String(numUsed)); | lines.insert(0, "voices used: " + water::String(numUsed)); | ||||
| return lines.joinIntoString("\n"); | return lines.joinIntoString("\n"); | ||||
| } | } | ||||
| } | |||||
| @@ -4,22 +4,13 @@ | |||||
| * Forked from https://github.com/stevefolta/SFZero | * Forked from https://github.com/stevefolta/SFZero | ||||
| * For license info please see the LICENSE file distributed with this source code | * For license info please see the LICENSE file distributed with this source code | ||||
| *************************************************************************************/ | *************************************************************************************/ | ||||
| #ifndef SFZSYNTH_H_INCLUDED | #ifndef SFZSYNTH_H_INCLUDED | ||||
| #define SFZSYNTH_H_INCLUDED | #define SFZSYNTH_H_INCLUDED | ||||
| #include "SFZCommon.h" | #include "SFZCommon.h" | ||||
| #include "CarlaJuceUtils.hpp" | |||||
| #if 1 | |||||
| namespace water { | |||||
| class Synthesiser { | |||||
| public: | |||||
| virtual void noteOn(int midiChannel, int midiNoteNumber, float velocity) = 0; | |||||
| virtual void noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) = 0; | |||||
| }; | |||||
| } | |||||
| #endif | |||||
| #include "water/synthesisers/Synthesiser.h" | |||||
| namespace sfzero | namespace sfzero | ||||
| { | { | ||||
| @@ -4,6 +4,7 @@ | |||||
| * Forked from https://github.com/stevefolta/SFZero | * Forked from https://github.com/stevefolta/SFZero | ||||
| * For license info please see the LICENSE file distributed with this source code | * For license info please see the LICENSE file distributed with this source code | ||||
| *************************************************************************************/ | *************************************************************************************/ | ||||
| #include "SFZDebug.h" | #include "SFZDebug.h" | ||||
| #include "SFZRegion.h" | #include "SFZRegion.h" | ||||
| #include "SFZSample.h" | #include "SFZSample.h" | ||||
| @@ -14,23 +15,26 @@ | |||||
| #include <cmath> | #include <cmath> | ||||
| namespace sfzero | |||||
| { | |||||
| static const float globalGain = -1.0; | static const float globalGain = -1.0; | ||||
| sfzero::Voice::Voice() | |||||
| Voice::Voice() | |||||
| : region_(nullptr), trigger_(0), curMidiNote_(0), curPitchWheel_(0), pitchRatio_(0), noteGainLeft_(0), noteGainRight_(0), | : 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) | ||||
| { | { | ||||
| ampeg_.setExponentialDecay(true); | ampeg_.setExponentialDecay(true); | ||||
| } | } | ||||
| sfzero::Voice::~Voice() {} | |||||
| Voice::~Voice() {} | |||||
| bool sfzero::Voice::canPlaySound(water::SynthesiserSound *sound) { return dynamic_cast<sfzero::Sound *>(sound) != nullptr; } | |||||
| bool Voice::canPlaySound(water::SynthesiserSound *sound) { return dynamic_cast<Sound *>(sound) != nullptr; } | |||||
| void sfzero::Voice::startNote(int midiNoteNumber, float floatVelocity, water::SynthesiserSound *soundIn, | |||||
| void Voice::startNote(int midiNoteNumber, float floatVelocity, water::SynthesiserSound *soundIn, | |||||
| int currentPitchWheelPosition) | int currentPitchWheelPosition) | ||||
| { | { | ||||
| sfzero::Sound *sound = dynamic_cast<sfzero::Sound *>(soundIn); | |||||
| Sound *sound = dynamic_cast<Sound *>(soundIn); | |||||
| if (sound == nullptr) | if (sound == nullptr) | ||||
| { | { | ||||
| @@ -75,9 +79,7 @@ void sfzero::Voice::startNote(int midiNoteNumber, float floatVelocity, water::Sy | |||||
| double adjustedPan = (region_->pan + 100.0) / 200.0; | double adjustedPan = (region_->pan + 100.0) / 200.0; | ||||
| noteGainLeft_ *= static_cast<float>(sqrt(1.0 - adjustedPan)); | noteGainLeft_ *= static_cast<float>(sqrt(1.0 - adjustedPan)); | ||||
| noteGainRight_ *= static_cast<float>(sqrt(adjustedPan)); | noteGainRight_ *= static_cast<float>(sqrt(adjustedPan)); | ||||
| #if 0 | |||||
| ampeg_.startNote(®ion_->ampeg, floatVelocity, getSampleRate(), ®ion_->ampeg_veltrack); | ampeg_.startNote(®ion_->ampeg, floatVelocity, getSampleRate(), ®ion_->ampeg_veltrack); | ||||
| #endif | |||||
| // Offset/end. | // Offset/end. | ||||
| sourceSamplePosition_ = static_cast<double>(region_->offset); | sourceSamplePosition_ = static_cast<double>(region_->offset); | ||||
| @@ -89,19 +91,19 @@ void sfzero::Voice::startNote(int midiNoteNumber, float floatVelocity, water::Sy | |||||
| // Loop. | // Loop. | ||||
| loopStart_ = loopEnd_ = 0; | loopStart_ = loopEnd_ = 0; | ||||
| sfzero::Region::LoopMode loopMode = region_->loop_mode; | |||||
| if (loopMode == sfzero::Region::sample_loop) | |||||
| Region::LoopMode loopMode = region_->loop_mode; | |||||
| if (loopMode == Region::sample_loop) | |||||
| { | { | ||||
| if (region_->sample->getLoopStart() < region_->sample->getLoopEnd()) | if (region_->sample->getLoopStart() < region_->sample->getLoopEnd()) | ||||
| { | { | ||||
| loopMode = sfzero::Region::loop_continuous; | |||||
| loopMode = Region::loop_continuous; | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| loopMode = sfzero::Region::no_loop; | |||||
| loopMode = Region::no_loop; | |||||
| } | } | ||||
| } | } | ||||
| if ((loopMode != sfzero::Region::no_loop) && (loopMode != sfzero::Region::one_shot)) | |||||
| if ((loopMode != Region::no_loop) && (loopMode != Region::one_shot)) | |||||
| { | { | ||||
| if (region_->loop_start < region_->loop_end) | if (region_->loop_start < region_->loop_end) | ||||
| { | { | ||||
| @@ -117,7 +119,7 @@ void sfzero::Voice::startNote(int midiNoteNumber, float floatVelocity, water::Sy | |||||
| numLoops_ = 0; | numLoops_ = 0; | ||||
| } | } | ||||
| void sfzero::Voice::stopNote(float /*velocity*/, bool allowTailOff) | |||||
| void Voice::stopNote(float /*velocity*/, bool allowTailOff) | |||||
| { | { | ||||
| if (!allowTailOff || (region_ == nullptr)) | if (!allowTailOff || (region_ == nullptr)) | ||||
| { | { | ||||
| @@ -125,20 +127,20 @@ void sfzero::Voice::stopNote(float /*velocity*/, bool allowTailOff) | |||||
| return; | return; | ||||
| } | } | ||||
| if (region_->loop_mode != sfzero::Region::one_shot) | |||||
| if (region_->loop_mode != Region::one_shot) | |||||
| { | { | ||||
| ampeg_.noteOff(); | ampeg_.noteOff(); | ||||
| } | } | ||||
| if (region_->loop_mode == sfzero::Region::loop_sustain) | |||||
| if (region_->loop_mode == Region::loop_sustain) | |||||
| { | { | ||||
| // Continue playing, but stop looping. | // Continue playing, but stop looping. | ||||
| loopEnd_ = loopStart_; | loopEnd_ = loopStart_; | ||||
| } | } | ||||
| } | } | ||||
| void sfzero::Voice::stopNoteForGroup() | |||||
| void Voice::stopNoteForGroup() | |||||
| { | { | ||||
| if (region_->off_mode == sfzero::Region::fast) | |||||
| if (region_->off_mode == Region::fast) | |||||
| { | { | ||||
| ampeg_.fastRelease(); | ampeg_.fastRelease(); | ||||
| } | } | ||||
| @@ -148,8 +150,8 @@ void sfzero::Voice::stopNoteForGroup() | |||||
| } | } | ||||
| } | } | ||||
| void sfzero::Voice::stopNoteQuick() { ampeg_.fastRelease(); } | |||||
| void sfzero::Voice::pitchWheelMoved(int newValue) | |||||
| void Voice::stopNoteQuick() { ampeg_.fastRelease(); } | |||||
| void Voice::pitchWheelMoved(int newValue) | |||||
| { | { | ||||
| if (region_ == nullptr) | if (region_ == nullptr) | ||||
| { | { | ||||
| @@ -160,8 +162,8 @@ void sfzero::Voice::pitchWheelMoved(int newValue) | |||||
| calcPitchRatio(); | calcPitchRatio(); | ||||
| } | } | ||||
| void sfzero::Voice::controllerMoved(int /*controllerNumber*/, int /*newValue*/) { /***/} | |||||
| void sfzero::Voice::renderNextBlock(water::AudioSampleBuffer &outputBuffer, int startSample, int numSamples) | |||||
| void Voice::controllerMoved(int /*controllerNumber*/, int /*newValue*/) { /***/} | |||||
| void Voice::renderNextBlock(water::AudioSampleBuffer &outputBuffer, int startSample, int numSamples) | |||||
| { | { | ||||
| if (region_ == nullptr) | if (region_ == nullptr) | ||||
| { | { | ||||
| @@ -265,17 +267,17 @@ void sfzero::Voice::renderNextBlock(water::AudioSampleBuffer &outputBuffer, int | |||||
| ampeg_.setSamplesUntilNextSegment(samplesUntilNextAmpSegment); | ampeg_.setSamplesUntilNextSegment(samplesUntilNextAmpSegment); | ||||
| } | } | ||||
| bool sfzero::Voice::isPlayingNoteDown() { return region_ && region_->trigger != sfzero::Region::release; } | |||||
| bool Voice::isPlayingNoteDown() { return region_ && region_->trigger != Region::release; } | |||||
| bool sfzero::Voice::isPlayingOneShot() { return region_ && region_->loop_mode == sfzero::Region::one_shot; } | |||||
| bool Voice::isPlayingOneShot() { return region_ && region_->loop_mode == Region::one_shot; } | |||||
| int sfzero::Voice::getGroup() { return region_ ? region_->group : 0; } | |||||
| int Voice::getGroup() { return region_ ? region_->group : 0; } | |||||
| water::uint64 sfzero::Voice::getOffBy() { return region_ ? region_->off_by : 0; } | |||||
| water::int64 Voice::getOffBy() { return region_ ? region_->off_by : 0; } | |||||
| void sfzero::Voice::setRegion(sfzero::Region *nextRegion) { region_ = nextRegion; } | |||||
| void Voice::setRegion(Region *nextRegion) { region_ = nextRegion; } | |||||
| water::String sfzero::Voice::infoString() | |||||
| water::String Voice::infoString() | |||||
| { | { | ||||
| const char *egSegmentNames[] = {"delay", "attack", "hold", "decay", "sustain", "release", "done"}; | const char *egSegmentNames[] = {"delay", "attack", "hold", "decay", "sustain", "release", "done"}; | ||||
| @@ -294,7 +296,7 @@ water::String sfzero::Voice::infoString() | |||||
| return info; | return info; | ||||
| } | } | ||||
| void sfzero::Voice::calcPitchRatio() | |||||
| void Voice::calcPitchRatio() | |||||
| { | { | ||||
| double note = curMidiNote_; | double note = curMidiNote_; | ||||
| @@ -316,23 +318,21 @@ void sfzero::Voice::calcPitchRatio() | |||||
| } | } | ||||
| double targetFreq = fractionalMidiNoteInHz(adjustedPitch); | double targetFreq = fractionalMidiNoteInHz(adjustedPitch); | ||||
| double naturalFreq = water::MidiMessage::getMidiNoteInHertz(region_->pitch_keycenter); | double naturalFreq = water::MidiMessage::getMidiNoteInHertz(region_->pitch_keycenter); | ||||
| #if 0 | |||||
| pitchRatio_ = (targetFreq * region_->sample->getSampleRate()) / (naturalFreq * getSampleRate()); | pitchRatio_ = (targetFreq * region_->sample->getSampleRate()) / (naturalFreq * getSampleRate()); | ||||
| #endif | |||||
| } | } | ||||
| void sfzero::Voice::killNote() | |||||
| void Voice::killNote() | |||||
| { | { | ||||
| region_ = nullptr; | region_ = nullptr; | ||||
| #if 0 | |||||
| clearCurrentNote(); | clearCurrentNote(); | ||||
| #endif | |||||
| } | } | ||||
| double sfzero::Voice::fractionalMidiNoteInHz(double note, double freqOfA) | |||||
| double Voice::fractionalMidiNoteInHz(double note, double freqOfA) | |||||
| { | { | ||||
| // Like MidiMessage::getMidiNoteInHertz(), but with a float note. | // Like MidiMessage::getMidiNoteInHertz(), but with a float note. | ||||
| note -= 69; | note -= 69; | ||||
| // Now 0 = A | // Now 0 = A | ||||
| return freqOfA * pow(2.0, note / 12.0); | return freqOfA * pow(2.0, note / 12.0); | ||||
| } | } | ||||
| } | |||||
| @@ -4,29 +4,17 @@ | |||||
| * Forked from https://github.com/stevefolta/SFZero | * Forked from https://github.com/stevefolta/SFZero | ||||
| * For license info please see the LICENSE file distributed with this source code | * For license info please see the LICENSE file distributed with this source code | ||||
| *************************************************************************************/ | *************************************************************************************/ | ||||
| #ifndef SFZVOICE_H_INCLUDED | #ifndef SFZVOICE_H_INCLUDED | ||||
| #define SFZVOICE_H_INCLUDED | #define SFZVOICE_H_INCLUDED | ||||
| #include "SFZEG.h" | #include "SFZEG.h" | ||||
| #include "CarlaJuceUtils.hpp" | |||||
| #if 1 | |||||
| namespace water { | |||||
| class SynthesiserVoice { | |||||
| public: | |||||
| virtual bool canPlaySound(water::SynthesiserSound *sound) = 0; | |||||
| virtual void startNote(int midiNoteNumber, float velocity, water::SynthesiserSound *sound, int currentPitchWheelPosition) = 0; | |||||
| virtual void stopNote(float velocity, bool allowTailOff) = 0; | |||||
| virtual void pitchWheelMoved(int newValue) = 0; | |||||
| virtual void controllerMoved(int controllerNumber, int newValue) = 0; | |||||
| virtual void renderNextBlock(water::AudioSampleBuffer &outputBuffer, int startSample, int numSamples) = 0; | |||||
| }; | |||||
| } | |||||
| #endif | |||||
| #include "water/synthesisers/Synthesiser.h" | |||||
| namespace sfzero | namespace sfzero | ||||
| { | { | ||||
| struct Region; | struct Region; | ||||
| class Voice : public water::SynthesiserVoice | class Voice : public water::SynthesiserVoice | ||||
| @@ -47,7 +35,7 @@ public: | |||||
| bool isPlayingOneShot(); | bool isPlayingOneShot(); | ||||
| int getGroup(); | int getGroup(); | ||||
| water::uint64 getOffBy(); | |||||
| water::int64 getOffBy(); | |||||
| // Set the region to be used by the next startNote(). | // Set the region to be used by the next startNote(). | ||||
| void setRegion(Region *nextRegion); | void setRegion(Region *nextRegion); | ||||
| @@ -0,0 +1,240 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2016 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
| Permission to use, copy, modify, and/or distribute this software for any | |||||
| purpose with or without fee is hereby granted, provided that the above | |||||
| copyright notice and this permission notice appear in all copies. | |||||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
| OF THIS SOFTWARE. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include "BigInteger.h" | |||||
| namespace water | |||||
| { | |||||
| inline uint32 bitToMask (const int bit) noexcept { return (uint32) 1 << (bit & 31); } | |||||
| inline size_t bitToIndex (const int bit) noexcept { return (size_t) (bit >> 5); } | |||||
| inline size_t sizeNeededToHold (int highestBit) noexcept { return (size_t) (highestBit >> 5) + 1; } | |||||
| int findHighestSetBit (uint32 n) noexcept | |||||
| { | |||||
| jassert (n != 0); // (the built-in functions may not work for n = 0) | |||||
| #if defined(__GNUC__) || defined(__clang__) | |||||
| return 31 - __builtin_clz (n); | |||||
| #elif _MSVC_VER | |||||
| unsigned long highest; | |||||
| _BitScanReverse (&highest, n); | |||||
| return (int) highest; | |||||
| #else | |||||
| n |= (n >> 1); | |||||
| n |= (n >> 2); | |||||
| n |= (n >> 4); | |||||
| n |= (n >> 8); | |||||
| n |= (n >> 16); | |||||
| return countNumberOfBits (n >> 1); | |||||
| #endif | |||||
| } | |||||
| //============================================================================== | |||||
| BigInteger::BigInteger() noexcept | |||||
| : allocatedSize (numPreallocatedInts), | |||||
| highestBit (-1) | |||||
| { | |||||
| for (int i = 0; i < numPreallocatedInts; ++i) | |||||
| preallocated[i] = 0; | |||||
| } | |||||
| BigInteger::BigInteger (const int32 value) noexcept | |||||
| : allocatedSize (numPreallocatedInts), | |||||
| highestBit (31) | |||||
| { | |||||
| preallocated[0] = (uint32) std::abs (value); | |||||
| for (int i = 1; i < numPreallocatedInts; ++i) | |||||
| preallocated[i] = 0; | |||||
| highestBit = getHighestBit(); | |||||
| } | |||||
| BigInteger::BigInteger (const uint32 value) noexcept | |||||
| : allocatedSize (numPreallocatedInts), | |||||
| highestBit (31) | |||||
| { | |||||
| preallocated[0] = value; | |||||
| for (int i = 1; i < numPreallocatedInts; ++i) | |||||
| preallocated[i] = 0; | |||||
| highestBit = getHighestBit(); | |||||
| } | |||||
| BigInteger::BigInteger (int64 value) noexcept | |||||
| : allocatedSize (numPreallocatedInts), | |||||
| highestBit (63) | |||||
| { | |||||
| if (value < 0) | |||||
| value = -value; | |||||
| preallocated[0] = (uint32) value; | |||||
| preallocated[1] = (uint32) (value >> 32); | |||||
| for (int i = 2; i < numPreallocatedInts; ++i) | |||||
| preallocated[i] = 0; | |||||
| highestBit = getHighestBit(); | |||||
| } | |||||
| #if WATER_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
| BigInteger::BigInteger (BigInteger&& other) noexcept | |||||
| : heapAllocation (static_cast<HeapBlock<uint32>&&> (other.heapAllocation)), | |||||
| allocatedSize (other.allocatedSize), | |||||
| highestBit (other.highestBit) | |||||
| { | |||||
| std::memcpy (preallocated, other.preallocated, sizeof (preallocated)); | |||||
| } | |||||
| BigInteger& BigInteger::operator= (BigInteger&& other) noexcept | |||||
| { | |||||
| heapAllocation = static_cast<HeapBlock<uint32>&&> (other.heapAllocation); | |||||
| std::memcpy (preallocated, other.preallocated, sizeof (preallocated)); | |||||
| allocatedSize = other.allocatedSize; | |||||
| highestBit = other.highestBit; | |||||
| return *this; | |||||
| } | |||||
| #endif | |||||
| BigInteger::~BigInteger() noexcept | |||||
| { | |||||
| } | |||||
| uint32* BigInteger::getValues() const noexcept | |||||
| { | |||||
| CARLA_SAFE_ASSERT_RETURN(heapAllocation != nullptr || allocatedSize <= numPreallocatedInts, nullptr); | |||||
| return heapAllocation != nullptr ? heapAllocation | |||||
| : (uint32*) preallocated; | |||||
| } | |||||
| uint32* BigInteger::ensureSize (const size_t numVals) noexcept | |||||
| { | |||||
| if (numVals <= allocatedSize) | |||||
| return getValues(); | |||||
| size_t oldSize = allocatedSize; | |||||
| allocatedSize = ((numVals + 2) * 3) / 2; | |||||
| if (heapAllocation == nullptr) | |||||
| { | |||||
| CARLA_SAFE_ASSERT_RETURN(heapAllocation.calloc (allocatedSize), nullptr); | |||||
| std::memcpy (heapAllocation, preallocated, sizeof (uint32) * numPreallocatedInts); | |||||
| } | |||||
| else | |||||
| { | |||||
| CARLA_SAFE_ASSERT_RETURN(heapAllocation.realloc (allocatedSize), nullptr); | |||||
| for (uint32* values = heapAllocation; oldSize < allocatedSize; ++oldSize) | |||||
| values[oldSize] = 0; | |||||
| } | |||||
| return heapAllocation; | |||||
| } | |||||
| //============================================================================== | |||||
| bool BigInteger::operator[] (const int bit) const noexcept | |||||
| { | |||||
| CARLA_SAFE_ASSERT_RETURN(bit <= highestBit && bit >= 0, false); | |||||
| if (const uint32* const values = getValues()) | |||||
| return (values [bitToIndex (bit)] & bitToMask (bit)) != 0; | |||||
| return false; | |||||
| } | |||||
| //============================================================================== | |||||
| void BigInteger::clear() noexcept | |||||
| { | |||||
| heapAllocation.free(); | |||||
| allocatedSize = numPreallocatedInts; | |||||
| highestBit = -1; | |||||
| for (int i = 0; i < numPreallocatedInts; ++i) | |||||
| preallocated[i] = 0; | |||||
| } | |||||
| bool BigInteger::setBit (const int bit) noexcept | |||||
| { | |||||
| CARLA_SAFE_ASSERT_RETURN(bit >= 0, false); | |||||
| if (bit > highestBit) | |||||
| { | |||||
| if (ensureSize (sizeNeededToHold (bit)) == nullptr) | |||||
| return false; | |||||
| highestBit = bit; | |||||
| } | |||||
| if (uint32* const values = getValues()) | |||||
| return values [bitToIndex (bit)] |= bitToMask (bit); | |||||
| return false; | |||||
| } | |||||
| bool BigInteger::setBit (const int bit, const bool shouldBeSet) noexcept | |||||
| { | |||||
| if (shouldBeSet) | |||||
| { | |||||
| return setBit (bit); | |||||
| } | |||||
| else | |||||
| { | |||||
| clearBit (bit); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| bool BigInteger::clearBit (const int bit) noexcept | |||||
| { | |||||
| CARLA_SAFE_ASSERT_RETURN(bit <= highestBit && bit >= 0, false); | |||||
| uint32* const values = getValues(); | |||||
| CARLA_SAFE_ASSERT_RETURN(values != nullptr, false); | |||||
| values [bitToIndex (bit)] &= ~bitToMask (bit); | |||||
| if (bit == highestBit) | |||||
| highestBit = getHighestBit(); | |||||
| return true; | |||||
| } | |||||
| int BigInteger::getHighestBit() const noexcept | |||||
| { | |||||
| const uint32* values = getValues(); | |||||
| CARLA_SAFE_ASSERT_RETURN(values != nullptr, -1); | |||||
| for (int i = (int) bitToIndex (highestBit); i >= 0; --i) | |||||
| if (uint32 n = values[i]) | |||||
| return findHighestSetBit (n) + (i << 5); | |||||
| return -1; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,118 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2016 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
| Permission to use, copy, modify, and/or distribute this software for any | |||||
| purpose with or without fee is hereby granted, provided that the above | |||||
| copyright notice and this permission notice appear in all copies. | |||||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
| OF THIS SOFTWARE. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef WATER_BIGINTEGER_H_INCLUDED | |||||
| #define WATER_BIGINTEGER_H_INCLUDED | |||||
| #include "../memory/HeapBlock.h" | |||||
| #include "CarlaJuceUtils.hpp" | |||||
| namespace water { | |||||
| //============================================================================== | |||||
| /** | |||||
| An arbitrarily large integer class. | |||||
| A BigInteger can be used in a similar way to a normal integer, but has no size | |||||
| limit (except for memory and performance constraints). | |||||
| Negative values are possible, but the value isn't stored as 2s-complement, so | |||||
| be careful if you use negative values and look at the values of individual bits. | |||||
| */ | |||||
| class BigInteger | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates an empty BigInteger */ | |||||
| BigInteger() noexcept; | |||||
| /** Creates a BigInteger containing an integer value in its low bits. | |||||
| The low 32 bits of the number are initialised with this value. | |||||
| */ | |||||
| BigInteger (uint32 value) noexcept; | |||||
| /** Creates a BigInteger containing an integer value in its low bits. | |||||
| The low 32 bits of the number are initialised with the absolute value | |||||
| passed in, and its sign is set to reflect the sign of the number. | |||||
| */ | |||||
| BigInteger (int32 value) noexcept; | |||||
| /** Creates a BigInteger containing an integer value in its low bits. | |||||
| The low 64 bits of the number are initialised with the absolute value | |||||
| passed in, and its sign is set to reflect the sign of the number. | |||||
| */ | |||||
| BigInteger (int64 value) noexcept; | |||||
| #if WATER_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
| BigInteger (BigInteger&&) noexcept; | |||||
| BigInteger& operator= (BigInteger&&) noexcept; | |||||
| #endif | |||||
| /** Destructor. */ | |||||
| ~BigInteger() noexcept; | |||||
| //============================================================================== | |||||
| /** Returns the value of a specified bit in the number. | |||||
| If the index is out-of-range, the result will be false. | |||||
| */ | |||||
| bool operator[] (int bit) const noexcept; | |||||
| //============================================================================== | |||||
| /** Resets the value to 0. */ | |||||
| void clear() noexcept; | |||||
| /** Clears a particular bit in the number. */ | |||||
| bool clearBit (int bitNumber) noexcept; | |||||
| /** Sets a specified bit to 1. */ | |||||
| bool setBit (int bitNumber) noexcept; | |||||
| /** Sets or clears a specified bit. */ | |||||
| bool setBit (int bitNumber, bool shouldBeSet) noexcept; | |||||
| //============================================================================== | |||||
| /** Returns the index of the highest set bit in the number. | |||||
| If the value is zero, this will return -1. | |||||
| */ | |||||
| int getHighestBit() const noexcept; | |||||
| private: | |||||
| //============================================================================== | |||||
| enum { numPreallocatedInts = 4 }; | |||||
| HeapBlock<uint32> heapAllocation; | |||||
| uint32 preallocated[numPreallocatedInts]; | |||||
| size_t allocatedSize; | |||||
| int highestBit; | |||||
| uint32* getValues() const noexcept; | |||||
| uint32* ensureSize (size_t) noexcept; | |||||
| CARLA_LEAK_DETECTOR (BigInteger) | |||||
| }; | |||||
| } | |||||
| #endif // WATER_BIGINTEGER_H_INCLUDED | |||||
| @@ -0,0 +1,642 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2016 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
| Permission to use, copy, modify, and/or distribute this software for any | |||||
| purpose with or without fee is hereby granted, provided that the above | |||||
| copyright notice and this permission notice appear in all copies. | |||||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
| OF THIS SOFTWARE. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include "Synthesiser.h" | |||||
| #include "../buffers/AudioSampleBuffer.h" | |||||
| #include "../containers/Array.h" | |||||
| #include "../midi/MidiBuffer.h" | |||||
| #include "../midi/MidiMessage.h" | |||||
| namespace water { | |||||
| SynthesiserSound::SynthesiserSound() {} | |||||
| SynthesiserSound::~SynthesiserSound() {} | |||||
| //============================================================================== | |||||
| SynthesiserVoice::SynthesiserVoice() | |||||
| : currentSampleRate (44100.0), | |||||
| currentlyPlayingNote (-1), | |||||
| currentPlayingMidiChannel (0), | |||||
| noteOnTime (0), | |||||
| keyIsDown (false), | |||||
| sustainPedalDown (false), | |||||
| sostenutoPedalDown (false) | |||||
| { | |||||
| } | |||||
| SynthesiserVoice::~SynthesiserVoice() | |||||
| { | |||||
| } | |||||
| bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const | |||||
| { | |||||
| return currentPlayingMidiChannel == midiChannel; | |||||
| } | |||||
| void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate) | |||||
| { | |||||
| currentSampleRate = newRate; | |||||
| } | |||||
| bool SynthesiserVoice::isVoiceActive() const | |||||
| { | |||||
| return getCurrentlyPlayingNote() >= 0; | |||||
| } | |||||
| void SynthesiserVoice::clearCurrentNote() | |||||
| { | |||||
| currentlyPlayingNote = -1; | |||||
| currentlyPlayingSound = nullptr; | |||||
| currentPlayingMidiChannel = 0; | |||||
| } | |||||
| void SynthesiserVoice::aftertouchChanged (int) {} | |||||
| void SynthesiserVoice::channelPressureChanged (int) {} | |||||
| bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const noexcept | |||||
| { | |||||
| return noteOnTime < other.noteOnTime; | |||||
| } | |||||
| //============================================================================== | |||||
| Synthesiser::Synthesiser() | |||||
| : sampleRate (0), | |||||
| lastNoteOnCounter (0), | |||||
| minimumSubBlockSize (32), | |||||
| subBlockSubdivisionIsStrict (false), | |||||
| shouldStealNotes (true) | |||||
| { | |||||
| for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | |||||
| lastPitchWheelValues[i] = 0x2000; | |||||
| } | |||||
| Synthesiser::~Synthesiser() | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| SynthesiserVoice* Synthesiser::getVoice (const int index) const | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| return voices [index]; | |||||
| } | |||||
| void Synthesiser::clearVoices() | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| voices.clear(); | |||||
| } | |||||
| SynthesiserVoice* Synthesiser::addVoice (SynthesiserVoice* const newVoice) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| newVoice->setCurrentPlaybackSampleRate (sampleRate); | |||||
| return voices.add (newVoice); | |||||
| } | |||||
| void Synthesiser::removeVoice (const int index) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| voices.remove (index); | |||||
| } | |||||
| void Synthesiser::clearSounds() | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| sounds.clear(); | |||||
| } | |||||
| SynthesiserSound* Synthesiser::addSound (const SynthesiserSound::Ptr& newSound) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| return sounds.add (newSound); | |||||
| } | |||||
| void Synthesiser::removeSound (const int index) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| sounds.remove (index); | |||||
| } | |||||
| void Synthesiser::setNoteStealingEnabled (const bool shouldSteal) | |||||
| { | |||||
| shouldStealNotes = shouldSteal; | |||||
| } | |||||
| void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||||
| { | |||||
| jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | |||||
| minimumSubBlockSize = numSamples; | |||||
| subBlockSubdivisionIsStrict = shouldBeStrict; | |||||
| } | |||||
| //============================================================================== | |||||
| void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||||
| { | |||||
| if (sampleRate != newRate) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| allNotesOff (0, false); | |||||
| sampleRate = newRate; | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| voices.getUnchecked (i)->setCurrentPlaybackSampleRate (newRate); | |||||
| } | |||||
| } | |||||
| void Synthesiser::processNextBlock (AudioSampleBuffer& outputAudio, | |||||
| const MidiBuffer& midiData, | |||||
| int startSample, | |||||
| int numSamples) | |||||
| { | |||||
| // must set the sample rate before using this! | |||||
| jassert (sampleRate != 0); | |||||
| const int targetChannels = outputAudio.getNumChannels(); | |||||
| MidiBuffer::Iterator midiIterator (midiData); | |||||
| midiIterator.setNextSamplePosition (startSample); | |||||
| bool firstEvent = true; | |||||
| int midiEventPos; | |||||
| MidiMessage m; | |||||
| const CarlaMutexLocker sl (lock); | |||||
| while (numSamples > 0) | |||||
| { | |||||
| if (! midiIterator.getNextEvent (m, midiEventPos)) | |||||
| { | |||||
| if (targetChannels > 0) | |||||
| renderVoices (outputAudio, startSample, numSamples); | |||||
| return; | |||||
| } | |||||
| const int samplesToNextMidiMessage = midiEventPos - startSample; | |||||
| if (samplesToNextMidiMessage >= numSamples) | |||||
| { | |||||
| if (targetChannels > 0) | |||||
| renderVoices (outputAudio, startSample, numSamples); | |||||
| handleMidiEvent (m); | |||||
| break; | |||||
| } | |||||
| if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||||
| { | |||||
| handleMidiEvent (m); | |||||
| continue; | |||||
| } | |||||
| firstEvent = false; | |||||
| if (targetChannels > 0) | |||||
| renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||||
| handleMidiEvent (m); | |||||
| startSample += samplesToNextMidiMessage; | |||||
| numSamples -= samplesToNextMidiMessage; | |||||
| } | |||||
| while (midiIterator.getNextEvent (m, midiEventPos)) | |||||
| handleMidiEvent (m); | |||||
| } | |||||
| void Synthesiser::renderVoices (AudioSampleBuffer& buffer, int startSample, int numSamples) | |||||
| { | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); | |||||
| } | |||||
| void Synthesiser::handleMidiEvent (const MidiMessage& m) | |||||
| { | |||||
| const int channel = m.getChannel(); | |||||
| if (m.isNoteOn()) | |||||
| { | |||||
| noteOn (channel, m.getNoteNumber(), m.getFloatVelocity()); | |||||
| } | |||||
| else if (m.isNoteOff()) | |||||
| { | |||||
| noteOff (channel, m.getNoteNumber(), m.getFloatVelocity(), true); | |||||
| } | |||||
| else if (m.isAllNotesOff() || m.isAllSoundOff()) | |||||
| { | |||||
| allNotesOff (channel, true); | |||||
| } | |||||
| else if (m.isPitchWheel()) | |||||
| { | |||||
| const int wheelPos = m.getPitchWheelValue(); | |||||
| lastPitchWheelValues [channel - 1] = wheelPos; | |||||
| handlePitchWheel (channel, wheelPos); | |||||
| } | |||||
| else if (m.isAftertouch()) | |||||
| { | |||||
| handleAftertouch (channel, m.getNoteNumber(), m.getAfterTouchValue()); | |||||
| } | |||||
| else if (m.isChannelPressure()) | |||||
| { | |||||
| handleChannelPressure (channel, m.getChannelPressureValue()); | |||||
| } | |||||
| else if (m.isController()) | |||||
| { | |||||
| handleController (channel, m.getControllerNumber(), m.getControllerValue()); | |||||
| } | |||||
| else if (m.isProgramChange()) | |||||
| { | |||||
| handleProgramChange (channel, m.getProgramChangeNumber()); | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void Synthesiser::noteOn (const int midiChannel, | |||||
| const int midiNoteNumber, | |||||
| const float velocity) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| for (int i = sounds.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserSound* const sound = sounds.getUnchecked(i); | |||||
| if (sound->appliesToNote (midiNoteNumber) | |||||
| && sound->appliesToChannel (midiChannel)) | |||||
| { | |||||
| // If hitting a note that's still ringing, stop it first (it could be | |||||
| // still playing because of the sustain or sostenuto pedal). | |||||
| for (int j = voices.size(); --j >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (j); | |||||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||||
| && voice->isPlayingChannel (midiChannel)) | |||||
| stopVoice (voice, 1.0f, true); | |||||
| } | |||||
| startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes), | |||||
| sound, midiChannel, midiNoteNumber, velocity); | |||||
| } | |||||
| } | |||||
| } | |||||
| void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||||
| SynthesiserSound* const sound, | |||||
| const int midiChannel, | |||||
| const int midiNoteNumber, | |||||
| const float velocity) | |||||
| { | |||||
| if (voice != nullptr && sound != nullptr) | |||||
| { | |||||
| if (voice->currentlyPlayingSound != nullptr) | |||||
| voice->stopNote (0.0f, false); | |||||
| voice->currentlyPlayingNote = midiNoteNumber; | |||||
| voice->currentPlayingMidiChannel = midiChannel; | |||||
| voice->noteOnTime = ++lastNoteOnCounter; | |||||
| voice->currentlyPlayingSound = sound; | |||||
| voice->keyIsDown = true; | |||||
| voice->sostenutoPedalDown = false; | |||||
| voice->sustainPedalDown = sustainPedalsDown[midiChannel]; | |||||
| voice->startNote (midiNoteNumber, velocity, sound, | |||||
| lastPitchWheelValues [midiChannel - 1]); | |||||
| } | |||||
| } | |||||
| void Synthesiser::stopVoice (SynthesiserVoice* voice, float velocity, const bool allowTailOff) | |||||
| { | |||||
| jassert (voice != nullptr); | |||||
| voice->stopNote (velocity, allowTailOff); | |||||
| // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! | |||||
| jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); | |||||
| } | |||||
| void Synthesiser::noteOff (const int midiChannel, | |||||
| const int midiNoteNumber, | |||||
| const float velocity, | |||||
| const bool allowTailOff) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||||
| && voice->isPlayingChannel (midiChannel)) | |||||
| { | |||||
| if (SynthesiserSound* const sound = voice->getCurrentlyPlayingSound()) | |||||
| { | |||||
| if (sound->appliesToNote (midiNoteNumber) | |||||
| && sound->appliesToChannel (midiChannel)) | |||||
| { | |||||
| CARLA_SAFE_ASSERT_RETURN (! voice->keyIsDown || voice->sustainPedalDown == sustainPedalsDown [midiChannel],); | |||||
| voice->keyIsDown = false; | |||||
| if (! (voice->sustainPedalDown || voice->sostenutoPedalDown)) | |||||
| stopVoice (voice, velocity, allowTailOff); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
| voice->stopNote (1.0f, allowTailOff); | |||||
| } | |||||
| sustainPedalsDown.clear(); | |||||
| } | |||||
| void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
| voice->pitchWheelMoved (wheelValue); | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleController (const int midiChannel, | |||||
| const int controllerNumber, | |||||
| const int controllerValue) | |||||
| { | |||||
| switch (controllerNumber) | |||||
| { | |||||
| case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break; | |||||
| case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break; | |||||
| case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break; | |||||
| default: break; | |||||
| } | |||||
| const CarlaMutexLocker sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
| voice->controllerMoved (controllerNumber, controllerValue); | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||||
| && (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))) | |||||
| voice->aftertouchChanged (aftertouchValue); | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue) | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
| voice->channelPressureChanged (channelPressureValue); | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||||
| { | |||||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||||
| const CarlaMutexLocker sl (lock); | |||||
| if (isDown) | |||||
| { | |||||
| sustainPedalsDown.setBit (midiChannel); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown()) | |||||
| voice->sustainPedalDown = true; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->isPlayingChannel (midiChannel)) | |||||
| { | |||||
| voice->sustainPedalDown = false; | |||||
| if (! voice->isKeyDown()) | |||||
| stopVoice (voice, 1.0f, true); | |||||
| } | |||||
| } | |||||
| sustainPedalsDown.clearBit (midiChannel); | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||||
| { | |||||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||||
| const CarlaMutexLocker sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->isPlayingChannel (midiChannel)) | |||||
| { | |||||
| if (isDown) | |||||
| voice->sostenutoPedalDown = true; | |||||
| else if (voice->sostenutoPedalDown) | |||||
| stopVoice (voice, 1.0f, true); | |||||
| } | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||||
| { | |||||
| ignoreUnused (midiChannel); | |||||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||||
| } | |||||
| void Synthesiser::handleProgramChange (int midiChannel, int programNumber) | |||||
| { | |||||
| ignoreUnused (midiChannel, programNumber); | |||||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||||
| } | |||||
| //============================================================================== | |||||
| SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||||
| int midiChannel, int midiNoteNumber, | |||||
| const bool stealIfNoneAvailable) const | |||||
| { | |||||
| const CarlaMutexLocker sl (lock); | |||||
| for (int i = 0; i < voices.size(); ++i) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay)) | |||||
| return voice; | |||||
| } | |||||
| if (stealIfNoneAvailable) | |||||
| return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber); | |||||
| return nullptr; | |||||
| } | |||||
| struct VoiceAgeSorter | |||||
| { | |||||
| static int compareElements (SynthesiserVoice* v1, SynthesiserVoice* v2) noexcept | |||||
| { | |||||
| return v1->wasStartedBefore (*v2) ? -1 : (v2->wasStartedBefore (*v1) ? 1 : 0); | |||||
| } | |||||
| }; | |||||
| SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||||
| int /*midiChannel*/, int midiNoteNumber) const | |||||
| { | |||||
| // This voice-stealing algorithm applies the following heuristics: | |||||
| // - Re-use the oldest notes first | |||||
| // - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | |||||
| // apparently you are trying to render audio without having any voices... | |||||
| jassert (voices.size() > 0); | |||||
| // These are the voices we want to protect (ie: only steal if unavoidable) | |||||
| SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | |||||
| SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase | |||||
| // this is a list of voices we can steal, sorted by how long they've been running | |||||
| Array<SynthesiserVoice*> usableVoices; | |||||
| usableVoices.ensureStorageAllocated (voices.size()); | |||||
| for (int i = 0; i < voices.size(); ++i) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->canPlaySound (soundToPlay)) | |||||
| { | |||||
| jassert (voice->isVoiceActive()); // We wouldn't be here otherwise | |||||
| VoiceAgeSorter sorter; | |||||
| usableVoices.addSorted (sorter, voice); | |||||
| if (! voice->isPlayingButReleased()) // Don't protect released notes | |||||
| { | |||||
| const int note = voice->getCurrentlyPlayingNote(); | |||||
| if (low == nullptr || note < low->getCurrentlyPlayingNote()) | |||||
| low = voice; | |||||
| if (top == nullptr || note > top->getCurrentlyPlayingNote()) | |||||
| top = voice; | |||||
| } | |||||
| } | |||||
| } | |||||
| // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) | |||||
| if (top == low) | |||||
| top = nullptr; | |||||
| const int numUsableVoices = usableVoices.size(); | |||||
| // The oldest note that's playing with the target pitch is ideal.. | |||||
| for (int i = 0; i < numUsableVoices; ++i) | |||||
| { | |||||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber) | |||||
| return voice; | |||||
| } | |||||
| // Oldest voice that has been released (no finger on it and not held by sustain pedal) | |||||
| for (int i = 0; i < numUsableVoices; ++i) | |||||
| { | |||||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
| if (voice != low && voice != top && voice->isPlayingButReleased()) | |||||
| return voice; | |||||
| } | |||||
| // Oldest voice that doesn't have a finger on it: | |||||
| for (int i = 0; i < numUsableVoices; ++i) | |||||
| { | |||||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
| if (voice != low && voice != top && ! voice->isKeyDown()) | |||||
| return voice; | |||||
| } | |||||
| // Oldest voice that isn't protected | |||||
| for (int i = 0; i < numUsableVoices; ++i) | |||||
| { | |||||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
| if (voice != low && voice != top) | |||||
| return voice; | |||||
| } | |||||
| // We've only got "protected" voices now: lowest note takes priority | |||||
| jassert (low != nullptr); | |||||
| // Duophonic synth: give priority to the bass note: | |||||
| if (top != nullptr) | |||||
| return top; | |||||
| return low; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,626 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the Water library. | |||||
| Copyright (c) 2016 ROLI Ltd. | |||||
| Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> | |||||
| Permission is granted to use this software under the terms of the ISC license | |||||
| http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
| Permission to use, copy, modify, and/or distribute this software for any | |||||
| purpose with or without fee is hereby granted, provided that the above | |||||
| copyright notice and this permission notice appear in all copies. | |||||
| THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
| FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
| OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
| USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
| TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
| OF THIS SOFTWARE. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef WATER_SYNTHESISER_H_INCLUDED | |||||
| #define WATER_SYNTHESISER_H_INCLUDED | |||||
| #include "../buffers/AudioSampleBuffer.h" | |||||
| #include "../containers/OwnedArray.h" | |||||
| #include "../containers/ReferenceCountedArray.h" | |||||
| #include "../maths/BigInteger.h" | |||||
| #include "CarlaJuceUtils.hpp" | |||||
| #include "CarlaMutex.hpp" | |||||
| namespace water { | |||||
| //============================================================================== | |||||
| /** | |||||
| Describes one of the sounds that a Synthesiser can play. | |||||
| A synthesiser can contain one or more sounds, and a sound can choose which | |||||
| midi notes and channels can trigger it. | |||||
| The SynthesiserSound is a passive class that just describes what the sound is - | |||||
| the actual audio rendering for a sound is done by a SynthesiserVoice. This allows | |||||
| more than one SynthesiserVoice to play the same sound at the same time. | |||||
| @see Synthesiser, SynthesiserVoice | |||||
| */ | |||||
| class SynthesiserSound : public ReferenceCountedObject | |||||
| { | |||||
| protected: | |||||
| //============================================================================== | |||||
| SynthesiserSound(); | |||||
| public: | |||||
| /** Destructor. */ | |||||
| virtual ~SynthesiserSound(); | |||||
| //============================================================================== | |||||
| /** Returns true if this sound should be played when a given midi note is pressed. | |||||
| The Synthesiser will use this information when deciding which sounds to trigger | |||||
| for a given note. | |||||
| */ | |||||
| virtual bool appliesToNote (int midiNoteNumber) = 0; | |||||
| /** Returns true if the sound should be triggered by midi events on a given channel. | |||||
| The Synthesiser will use this information when deciding which sounds to trigger | |||||
| for a given note. | |||||
| */ | |||||
| virtual bool appliesToChannel (int midiChannel) = 0; | |||||
| /** The class is reference-counted, so this is a handy pointer class for it. */ | |||||
| typedef ReferenceCountedObjectPtr<SynthesiserSound> Ptr; | |||||
| private: | |||||
| //============================================================================== | |||||
| CARLA_LEAK_DETECTOR (SynthesiserSound) | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| Represents a voice that a Synthesiser can use to play a SynthesiserSound. | |||||
| A voice plays a single sound at a time, and a synthesiser holds an array of | |||||
| voices so that it can play polyphonically. | |||||
| @see Synthesiser, SynthesiserSound | |||||
| */ | |||||
| class SynthesiserVoice | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a voice. */ | |||||
| SynthesiserVoice(); | |||||
| /** Destructor. */ | |||||
| virtual ~SynthesiserVoice(); | |||||
| //============================================================================== | |||||
| /** Returns the midi note that this voice is currently playing. | |||||
| Returns a value less than 0 if no note is playing. | |||||
| */ | |||||
| int getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; } | |||||
| /** Returns the sound that this voice is currently playing. | |||||
| Returns nullptr if it's not playing. | |||||
| */ | |||||
| SynthesiserSound::Ptr getCurrentlyPlayingSound() const noexcept { return currentlyPlayingSound; } | |||||
| /** Must return true if this voice object is capable of playing the given sound. | |||||
| If there are different classes of sound, and different classes of voice, a voice can | |||||
| choose which ones it wants to take on. | |||||
| A typical implementation of this method may just return true if there's only one type | |||||
| of voice and sound, or it might check the type of the sound object passed-in and | |||||
| see if it's one that it understands. | |||||
| */ | |||||
| virtual bool canPlaySound (SynthesiserSound*) = 0; | |||||
| /** Called to start a new note. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| */ | |||||
| virtual void startNote (int midiNoteNumber, | |||||
| float velocity, | |||||
| SynthesiserSound* sound, | |||||
| int currentPitchWheelPosition) = 0; | |||||
| /** Called to stop a note. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| The velocity indicates how quickly the note was released - 0 is slowly, 1 is quickly. | |||||
| If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all | |||||
| sound immediately, and must call clearCurrentNote() to reset the state of this voice | |||||
| and allow the synth to reassign it another sound. | |||||
| If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to | |||||
| begin fading out its sound, and it can stop playing until it's finished. As soon as it | |||||
| finishes playing (during the rendering callback), it must make sure that it calls | |||||
| clearCurrentNote(). | |||||
| */ | |||||
| virtual void stopNote (float velocity, bool allowTailOff) = 0; | |||||
| /** Returns true if this voice is currently busy playing a sound. | |||||
| By default this just checks the getCurrentlyPlayingNote() value, but can | |||||
| be overridden for more advanced checking. | |||||
| */ | |||||
| virtual bool isVoiceActive() const; | |||||
| /** Called to let the voice know that the pitch wheel has been moved. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| */ | |||||
| virtual void pitchWheelMoved (int newPitchWheelValue) = 0; | |||||
| /** Called to let the voice know that a midi controller has been moved. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| */ | |||||
| virtual void controllerMoved (int controllerNumber, int newControllerValue) = 0; | |||||
| /** Called to let the voice know that the aftertouch has changed. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| */ | |||||
| virtual void aftertouchChanged (int newAftertouchValue); | |||||
| /** Called to let the voice know that the channel pressure has changed. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| */ | |||||
| virtual void channelPressureChanged (int newChannelPressureValue); | |||||
| //============================================================================== | |||||
| /** Renders the next block of data for this voice. | |||||
| The output audio data must be added to the current contents of the buffer provided. | |||||
| Only the region of the buffer between startSample and (startSample + numSamples) | |||||
| should be altered by this method. | |||||
| If the voice is currently silent, it should just return without doing anything. | |||||
| If the sound that the voice is playing finishes during the course of this rendered | |||||
| block, it must call clearCurrentNote(), to tell the synthesiser that it has finished. | |||||
| The size of the blocks that are rendered can change each time it is called, and may | |||||
| involve rendering as little as 1 sample at a time. In between rendering callbacks, | |||||
| the voice's methods will be called to tell it about note and controller events. | |||||
| */ | |||||
| virtual void renderNextBlock (AudioSampleBuffer& outputBuffer, | |||||
| int startSample, | |||||
| int numSamples) = 0; | |||||
| /** Changes the voice's reference sample rate. | |||||
| The rate is set so that subclasses know the output rate and can set their pitch | |||||
| accordingly. | |||||
| This method is called by the synth, and subclasses can access the current rate with | |||||
| the currentSampleRate member. | |||||
| */ | |||||
| virtual void setCurrentPlaybackSampleRate (double newRate); | |||||
| /** Returns true if the voice is currently playing a sound which is mapped to the given | |||||
| midi channel. | |||||
| If it's not currently playing, this will return false. | |||||
| */ | |||||
| virtual bool isPlayingChannel (int midiChannel) const; | |||||
| /** Returns the current target sample rate at which rendering is being done. | |||||
| Subclasses may need to know this so that they can pitch things correctly. | |||||
| */ | |||||
| double getSampleRate() const noexcept { return currentSampleRate; } | |||||
| /** Returns true if the key that triggered this voice is still held down. | |||||
| Note that the voice may still be playing after the key was released (e.g because the | |||||
| sostenuto pedal is down). | |||||
| */ | |||||
| bool isKeyDown() const noexcept { return keyIsDown; } | |||||
| /** Returns true if the sustain pedal is currently active for this voice. */ | |||||
| bool isSustainPedalDown() const noexcept { return sustainPedalDown; } | |||||
| /** Returns true if the sostenuto pedal is currently active for this voice. */ | |||||
| bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } | |||||
| /** Returns true if a voice is sounding in its release phase **/ | |||||
| bool isPlayingButReleased() const noexcept | |||||
| { | |||||
| return isVoiceActive() && ! (isKeyDown() || isSostenutoPedalDown() || isSustainPedalDown()); | |||||
| } | |||||
| /** Returns true if this voice started playing its current note before the other voice did. */ | |||||
| bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; | |||||
| protected: | |||||
| /** Resets the state of this voice after a sound has finished playing. | |||||
| The subclass must call this when it finishes playing a note and becomes available | |||||
| to play new ones. | |||||
| It must either call it in the stopNote() method, or if the voice is tailing off, | |||||
| then it should call it later during the renderNextBlock method, as soon as it | |||||
| finishes its tail-off. | |||||
| It can also be called at any time during the render callback if the sound happens | |||||
| to have finished, e.g. if it's playing a sample and the sample finishes. | |||||
| */ | |||||
| void clearCurrentNote(); | |||||
| private: | |||||
| //============================================================================== | |||||
| friend class Synthesiser; | |||||
| double currentSampleRate; | |||||
| int currentlyPlayingNote, currentPlayingMidiChannel; | |||||
| uint32 noteOnTime; | |||||
| SynthesiserSound::Ptr currentlyPlayingSound; | |||||
| bool keyIsDown, sustainPedalDown, sostenutoPedalDown; | |||||
| AudioSampleBuffer tempBuffer; | |||||
| CARLA_LEAK_DETECTOR (SynthesiserVoice) | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| Base class for a musical device that can play sounds. | |||||
| To create a synthesiser, you'll need to create a subclass of SynthesiserSound | |||||
| to describe each sound available to your synth, and a subclass of SynthesiserVoice | |||||
| which can play back one of these sounds. | |||||
| Then you can use the addVoice() and addSound() methods to give the synthesiser a | |||||
| set of sounds, and a set of voices it can use to play them. If you only give it | |||||
| one voice it will be monophonic - the more voices it has, the more polyphony it'll | |||||
| have available. | |||||
| Then repeatedly call the renderNextBlock() method to produce the audio. Any midi | |||||
| events that go in will be scanned for note on/off messages, and these are used to | |||||
| start and stop the voices playing the appropriate sounds. | |||||
| While it's playing, you can also cause notes to be triggered by calling the noteOn(), | |||||
| noteOff() and other controller methods. | |||||
| Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it | |||||
| what the target playback rate is. This value is passed on to the voices so that | |||||
| they can pitch their output correctly. | |||||
| */ | |||||
| class Synthesiser | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a new synthesiser. | |||||
| You'll need to add some sounds and voices before it'll make any sound. | |||||
| */ | |||||
| Synthesiser(); | |||||
| /** Destructor. */ | |||||
| virtual ~Synthesiser(); | |||||
| //============================================================================== | |||||
| /** Deletes all voices. */ | |||||
| void clearVoices(); | |||||
| /** Returns the number of voices that have been added. */ | |||||
| int getNumVoices() const noexcept { return voices.size(); } | |||||
| /** Returns one of the voices that have been added. */ | |||||
| SynthesiserVoice* getVoice (int index) const; | |||||
| /** Adds a new voice to the synth. | |||||
| All the voices should be the same class of object and are treated equally. | |||||
| The object passed in will be managed by the synthesiser, which will delete | |||||
| it later on when no longer needed. The caller should not retain a pointer to the | |||||
| voice. | |||||
| */ | |||||
| SynthesiserVoice* addVoice (SynthesiserVoice* newVoice); | |||||
| /** Deletes one of the voices. */ | |||||
| void removeVoice (int index); | |||||
| //============================================================================== | |||||
| /** Deletes all sounds. */ | |||||
| void clearSounds(); | |||||
| /** Returns the number of sounds that have been added to the synth. */ | |||||
| int getNumSounds() const noexcept { return sounds.size(); } | |||||
| /** Returns one of the sounds. */ | |||||
| SynthesiserSound* getSound (int index) const noexcept { return sounds [index]; } | |||||
| /** Adds a new sound to the synthesiser. | |||||
| The object passed in is reference counted, so will be deleted when the | |||||
| synthesiser and all voices are no longer using it. | |||||
| */ | |||||
| SynthesiserSound* addSound (const SynthesiserSound::Ptr& newSound); | |||||
| /** Removes and deletes one of the sounds. */ | |||||
| void removeSound (int index); | |||||
| //============================================================================== | |||||
| /** If set to true, then the synth will try to take over an existing voice if | |||||
| it runs out and needs to play another note. | |||||
| The value of this boolean is passed into findFreeVoice(), so the result will | |||||
| depend on the implementation of this method. | |||||
| */ | |||||
| void setNoteStealingEnabled (bool shouldStealNotes); | |||||
| /** Returns true if note-stealing is enabled. | |||||
| @see setNoteStealingEnabled | |||||
| */ | |||||
| bool isNoteStealingEnabled() const noexcept { return shouldStealNotes; } | |||||
| //============================================================================== | |||||
| /** Triggers a note-on event. | |||||
| The default method here will find all the sounds that want to be triggered by | |||||
| this note/channel. For each sound, it'll try to find a free voice, and use the | |||||
| voice to start playing the sound. | |||||
| Subclasses might want to override this if they need a more complex algorithm. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||||
| */ | |||||
| virtual void noteOn (int midiChannel, | |||||
| int midiNoteNumber, | |||||
| float velocity); | |||||
| /** Triggers a note-off event. | |||||
| This will turn off any voices that are playing a sound for the given note/channel. | |||||
| If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||||
| (if they can do). If this is false, the notes will all be cut off immediately. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||||
| */ | |||||
| virtual void noteOff (int midiChannel, | |||||
| int midiNoteNumber, | |||||
| float velocity, | |||||
| bool allowTailOff); | |||||
| /** Turns off all notes. | |||||
| This will turn off any voices that are playing a sound on the given midi channel. | |||||
| If midiChannel is 0 or less, then all voices will be turned off, regardless of | |||||
| which channel they're playing. Otherwise it represents a valid midi channel, from | |||||
| 1 to 16 inclusive. | |||||
| If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||||
| (if they can do). If this is false, the notes will all be cut off immediately. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| */ | |||||
| virtual void allNotesOff (int midiChannel, | |||||
| bool allowTailOff); | |||||
| /** Sends a pitch-wheel message to any active voices. | |||||
| This will send a pitch-wheel message to any voices that are playing sounds on | |||||
| the given midi channel. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||||
| @param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue() | |||||
| */ | |||||
| virtual void handlePitchWheel (int midiChannel, | |||||
| int wheelValue); | |||||
| /** Sends a midi controller message to any active voices. | |||||
| This will send a midi controller message to any voices that are playing sounds on | |||||
| the given midi channel. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||||
| @param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber() | |||||
| @param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue() | |||||
| */ | |||||
| virtual void handleController (int midiChannel, | |||||
| int controllerNumber, | |||||
| int controllerValue); | |||||
| /** Sends an aftertouch message. | |||||
| This will send an aftertouch message to any voices that are playing sounds on | |||||
| the given midi channel and note number. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||||
| @param midiNoteNumber the midi note number, 0 to 127 | |||||
| @param aftertouchValue the aftertouch value, between 0 and 127, | |||||
| as returned by MidiMessage::getAftertouchValue() | |||||
| */ | |||||
| virtual void handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue); | |||||
| /** Sends a channel pressure message. | |||||
| This will send a channel pressure message to any voices that are playing sounds on | |||||
| the given midi channel. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||||
| @param channelPressureValue the pressure value, between 0 and 127, as returned | |||||
| by MidiMessage::getChannelPressureValue() | |||||
| */ | |||||
| virtual void handleChannelPressure (int midiChannel, int channelPressureValue); | |||||
| /** Handles a sustain pedal event. */ | |||||
| virtual void handleSustainPedal (int midiChannel, bool isDown); | |||||
| /** Handles a sostenuto pedal event. */ | |||||
| virtual void handleSostenutoPedal (int midiChannel, bool isDown); | |||||
| /** Can be overridden to handle soft pedal events. */ | |||||
| virtual void handleSoftPedal (int midiChannel, bool isDown); | |||||
| /** Can be overridden to handle an incoming program change message. | |||||
| The base class implementation of this has no effect, but you may want to make your | |||||
| own synth react to program changes. | |||||
| */ | |||||
| virtual void handleProgramChange (int midiChannel, | |||||
| int programNumber); | |||||
| //============================================================================== | |||||
| /** Tells the synthesiser what the sample rate is for the audio it's being used to render. | |||||
| This value is propagated to the voices so that they can use it to render the correct | |||||
| pitches. | |||||
| */ | |||||
| virtual void setCurrentPlaybackSampleRate (double sampleRate); | |||||
| /** Creates the next block of audio output. | |||||
| This will process the next numSamples of data from all the voices, and add that output | |||||
| to the audio block supplied, starting from the offset specified. Note that the | |||||
| data will be added to the current contents of the buffer, so you should clear it | |||||
| before calling this method if necessary. | |||||
| The midi events in the inputMidi buffer are parsed for note and controller events, | |||||
| and these are used to trigger the voices. Note that the startSample offset applies | |||||
| both to the audio output buffer and the midi input buffer, so any midi events | |||||
| with timestamps outside the specified region will be ignored. | |||||
| */ | |||||
| inline void renderNextBlock (AudioSampleBuffer& outputAudio, | |||||
| const MidiBuffer& inputMidi, | |||||
| int startSample, | |||||
| int numSamples) | |||||
| { processNextBlock (outputAudio, inputMidi, startSample, numSamples); } | |||||
| /** Returns the current target sample rate at which rendering is being done. | |||||
| Subclasses may need to know this so that they can pitch things correctly. | |||||
| */ | |||||
| double getSampleRate() const noexcept { return sampleRate; } | |||||
| /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. | |||||
| When rendering, the audio blocks that are passed into renderNextBlock() will be split up | |||||
| into smaller blocks that lie between all the incoming midi messages, and it is these smaller | |||||
| sub-blocks that are rendered with multiple calls to renderVoices(). | |||||
| Obviously in a pathological case where there are midi messages on every sample, then | |||||
| renderVoices() could be called once per sample and lead to poor performance, so this | |||||
| setting allows you to set a lower limit on the block size. | |||||
| The default setting is 32, which means that midi messages are accurate to about < 1ms | |||||
| accuracy, which is probably fine for most purposes, but you may want to increase or | |||||
| decrease this value for your synth. | |||||
| If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||||
| If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||||
| to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||||
| (this can sometimes help to avoid quantisation or phasing issues). | |||||
| */ | |||||
| void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||||
| protected: | |||||
| //============================================================================== | |||||
| /** This is used to control access to the rendering callback and the note trigger methods. */ | |||||
| CarlaMutex lock; | |||||
| OwnedArray<SynthesiserVoice> voices; | |||||
| ReferenceCountedArray<SynthesiserSound> sounds; | |||||
| /** The last pitch-wheel values for each midi channel. */ | |||||
| int lastPitchWheelValues [16]; | |||||
| /** Renders the voices for the given range. | |||||
| By default this just calls renderNextBlock() on each voice, but you may need | |||||
| to override it to handle custom cases. | |||||
| */ | |||||
| virtual void renderVoices (AudioSampleBuffer& outputAudio, | |||||
| int startSample, int numSamples); | |||||
| /** Searches through the voices to find one that's not currently playing, and | |||||
| which can play the given sound. | |||||
| Returns nullptr if all voices are busy and stealing isn't enabled. | |||||
| To implement a custom note-stealing algorithm, you can either override this | |||||
| method, or (preferably) override findVoiceToSteal(). | |||||
| */ | |||||
| virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, | |||||
| int midiChannel, | |||||
| int midiNoteNumber, | |||||
| bool stealIfNoneAvailable) const; | |||||
| /** Chooses a voice that is most suitable for being re-used. | |||||
| The default method will attempt to find the oldest voice that isn't the | |||||
| bottom or top note being played. If that's not suitable for your synth, | |||||
| you can override this method and do something more cunning instead. | |||||
| */ | |||||
| virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay, | |||||
| int midiChannel, | |||||
| int midiNoteNumber) const; | |||||
| /** Starts a specified voice playing a particular sound. | |||||
| You'll probably never need to call this, it's used internally by noteOn(), but | |||||
| may be needed by subclasses for custom behaviours. | |||||
| */ | |||||
| void startVoice (SynthesiserVoice* voice, | |||||
| SynthesiserSound* sound, | |||||
| int midiChannel, | |||||
| int midiNoteNumber, | |||||
| float velocity); | |||||
| /** Stops a given voice. | |||||
| You should never need to call this, it's used internally by noteOff, but is protected | |||||
| in case it's useful for some custom subclasses. It basically just calls through to | |||||
| SynthesiserVoice::stopNote(), and has some assertions to sanity-check a few things. | |||||
| */ | |||||
| void stopVoice (SynthesiserVoice*, float velocity, bool allowTailOff); | |||||
| /** Can be overridden to do custom handling of incoming midi events. */ | |||||
| virtual void handleMidiEvent (const MidiMessage&); | |||||
| private: | |||||
| //============================================================================== | |||||
| void processNextBlock (AudioSampleBuffer& outputAudio, | |||||
| const MidiBuffer& inputMidi, | |||||
| int startSample, | |||||
| int numSamples); | |||||
| //============================================================================== | |||||
| double sampleRate; | |||||
| uint32 lastNoteOnCounter; | |||||
| int minimumSubBlockSize; | |||||
| bool subBlockSubdivisionIsStrict; | |||||
| bool shouldStealNotes; | |||||
| BigInteger sustainPedalsDown; | |||||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser) | |||||
| }; | |||||
| } | |||||
| #endif // WATER_SYNTHESISER_H_INCLUDED | |||||
| @@ -1,7 +1,7 @@ | |||||
| /* | /* | ||||
| * Cross-platform C++ library for Carla, based on Juce v4 | * Cross-platform C++ library for Carla, based on Juce v4 | ||||
| * Copyright (C) 2015 ROLI Ltd. | |||||
| * Copyright (C) 2017 Filipe Coelho <falktx@falktx.com> | |||||
| * Copyright (C) 2015-2016 ROLI Ltd. | |||||
| * Copyright (C) 2017-2018 Filipe Coelho <falktx@falktx.com> | |||||
| * | * | ||||
| * This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||
| * modify it under the terms of the GNU General Public License as | * modify it under the terms of the GNU General Public License as | ||||
| @@ -45,6 +45,8 @@ HINSTANCE water_getCurrentModuleInstanceHandle() noexcept | |||||
| #include "files/FileOutputStream.cpp" | #include "files/FileOutputStream.cpp" | ||||
| #include "files/TemporaryFile.cpp" | #include "files/TemporaryFile.cpp" | ||||
| #include "maths/BigInteger.cpp" | |||||
| #include "maths/Random.cpp" | #include "maths/Random.cpp" | ||||
| #include "memory/MemoryBlock.cpp" | #include "memory/MemoryBlock.cpp" | ||||
| @@ -65,6 +67,8 @@ HINSTANCE water_getCurrentModuleInstanceHandle() noexcept | |||||
| #include "streams/MemoryOutputStream.cpp" | #include "streams/MemoryOutputStream.cpp" | ||||
| #include "streams/OutputStream.cpp" | #include "streams/OutputStream.cpp" | ||||
| #include "synthesisers/Synthesiser.cpp" | |||||
| #include "text/CharacterFunctions.cpp" | #include "text/CharacterFunctions.cpp" | ||||
| #include "text/Identifier.cpp" | #include "text/Identifier.cpp" | ||||
| #include "text/StringArray.cpp" | #include "text/StringArray.cpp" | ||||
| @@ -1,6 +1,6 @@ | |||||
| /* | /* | ||||
| * Cross-platform C++ library for Carla, based on Juce v4 | * Cross-platform C++ library for Carla, based on Juce v4 | ||||
| * Copyright (C) 2015 ROLI Ltd. | |||||
| * Copyright (C) 2015-2016 ROLI Ltd. | |||||
| * Copyright (C) 2017-2018 Filipe Coelho <falktx@falktx.com> | * Copyright (C) 2017-2018 Filipe Coelho <falktx@falktx.com> | ||||
| * | * | ||||
| * This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||