| @@ -7,9 +7,12 @@ | |||
| #include "SFZDebug.h" | |||
| #include <stdarg.h> | |||
| namespace sfzero | |||
| { | |||
| #ifdef DEBUG | |||
| void sfzero::dbgprintf(const char *msg, ...) | |||
| void dbgprintf(const char *msg, ...) | |||
| { | |||
| va_list args; | |||
| @@ -22,3 +25,5 @@ void sfzero::dbgprintf(const char *msg, ...) | |||
| } | |||
| #endif // JUCE_DEBUG | |||
| } | |||
| @@ -6,16 +6,19 @@ | |||
| *************************************************************************************/ | |||
| #include "SFZEG.h" | |||
| namespace sfzero | |||
| { | |||
| static const float fastReleaseTime = 0.01f; | |||
| sfzero::EG::EG() | |||
| EG::EG() | |||
| : 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) | |||
| { | |||
| parameters_ = *newParameters; | |||
| @@ -41,7 +44,7 @@ void sfzero::EG::startNote(const EGParameters *newParameters, float floatVelocit | |||
| startDelay(); | |||
| } | |||
| void sfzero::EG::nextSegment() | |||
| void EG::nextSegment() | |||
| { | |||
| 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; | |||
| samplesUntilNextSegment_ = static_cast<int>(fastReleaseTime * sampleRate_); | |||
| @@ -82,7 +85,7 @@ void sfzero::EG::fastRelease() | |||
| segmentIsExponential_ = false; | |||
| } | |||
| void sfzero::EG::startDelay() | |||
| void EG::startDelay() | |||
| { | |||
| if (parameters_.delay <= 0) | |||
| { | |||
| @@ -98,7 +101,7 @@ void sfzero::EG::startDelay() | |||
| } | |||
| } | |||
| void sfzero::EG::startAttack() | |||
| void EG::startAttack() | |||
| { | |||
| if (parameters_.attack <= 0) | |||
| { | |||
| @@ -114,7 +117,7 @@ void sfzero::EG::startAttack() | |||
| } | |||
| } | |||
| void sfzero::EG::startHold() | |||
| void EG::startHold() | |||
| { | |||
| if (parameters_.hold <= 0) | |||
| { | |||
| @@ -131,7 +134,7 @@ void sfzero::EG::startHold() | |||
| } | |||
| } | |||
| void sfzero::EG::startDecay() | |||
| void EG::startDecay() | |||
| { | |||
| if (parameters_.decay <= 0) | |||
| { | |||
| @@ -170,7 +173,7 @@ void sfzero::EG::startDecay() | |||
| } | |||
| } | |||
| void sfzero::EG::startSustain() | |||
| void EG::startSustain() | |||
| { | |||
| if (parameters_.sustain <= 0) | |||
| { | |||
| @@ -186,7 +189,7 @@ void sfzero::EG::startSustain() | |||
| } | |||
| } | |||
| void sfzero::EG::startRelease() | |||
| void EG::startRelease() | |||
| { | |||
| 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" | |||
| 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; | |||
| 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())); | |||
| } | |||
| 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 *end = text + length; | |||
| char c = 0; | |||
| sfzero::Region curGroup; | |||
| sfzero::Region curRegion; | |||
| sfzero::Region *buildingRegion = nullptr; | |||
| Region curGroup; | |||
| Region curRegion; | |||
| Region *buildingRegion = nullptr; | |||
| bool inControl = false; | |||
| water::String defaultPath; | |||
| @@ -108,7 +111,7 @@ void sfzero::Reader::read(const char *text, unsigned int length) | |||
| error("Unterminated tag"); | |||
| goto fatalError; | |||
| } | |||
| sfzero::StringSlice tag(tagStart, p - 1); | |||
| StringSlice tag(tagStart, p - 1); | |||
| if (tag == "region") | |||
| { | |||
| if (buildingRegion && (buildingRegion == &curRegion)) | |||
| @@ -176,7 +179,7 @@ void sfzero::Reader::read(const char *text, unsigned int length) | |||
| error("Malformed parameter"); | |||
| goto nextElement; | |||
| } | |||
| sfzero::StringSlice opcode(parameterStart, p - 1); | |||
| StringSlice opcode(parameterStart, p - 1); | |||
| if (inControl) | |||
| { | |||
| if (opcode == "default_path") | |||
| @@ -259,7 +262,7 @@ void sfzero::Reader::read(const char *text, unsigned int length) | |||
| } | |||
| 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") | |||
| { | |||
| @@ -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"; | |||
| if (modeIsSupported) | |||
| { | |||
| buildingRegion->loop_mode = static_cast<sfzero::Region::LoopMode>(loopModeValue(value)); | |||
| buildingRegion->loop_mode = static_cast<Region::LoopMode>(loopModeValue(value)); | |||
| } | |||
| 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. | |||
| char lineEndChar = *p++; | |||
| @@ -445,7 +448,7 @@ const char *sfzero::Reader::handleLineEnd(const char *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. | |||
| const char *p = pIn; | |||
| @@ -497,7 +500,7 @@ const char *sfzero::Reader::readPathInto(water::String *pathOut, const char *pIn | |||
| return p; | |||
| } | |||
| int sfzero::Reader::keyValue(const water::String &str) | |||
| int Reader::keyValue(const water::String &str) | |||
| { | |||
| auto chars = str.toRawUTF8(); | |||
| @@ -542,56 +545,58 @@ int sfzero::Reader::keyValue(const water::String &str) | |||
| return result; | |||
| } | |||
| int sfzero::Reader::triggerValue(const water::String &str) | |||
| int Reader::triggerValue(const water::String &str) | |||
| { | |||
| if (str == "release") | |||
| { | |||
| return sfzero::Region::release; | |||
| return Region::release; | |||
| } | |||
| if (str == "first") | |||
| { | |||
| return sfzero::Region::first; | |||
| return Region::first; | |||
| } | |||
| 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") | |||
| { | |||
| return sfzero::Region::no_loop; | |||
| return Region::no_loop; | |||
| } | |||
| if (str == "one_shot") | |||
| { | |||
| return sfzero::Region::one_shot; | |||
| return Region::one_shot; | |||
| } | |||
| if (str == "loop_continuous") | |||
| { | |||
| return sfzero::Region::loop_continuous; | |||
| return Region::loop_continuous; | |||
| } | |||
| 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; | |||
| sound_->addRegion(newRegion); | |||
| } | |||
| void sfzero::Reader::error(const water::String &message) | |||
| void Reader::error(const water::String &message) | |||
| { | |||
| water::String fullMessage = message; | |||
| fullMessage += " (line " + water::String(line_) + ")."; | |||
| sound_->addError(fullMessage); | |||
| } | |||
| } | |||
| @@ -4,10 +4,14 @@ | |||
| * Forked from https://github.com/stevefolta/SFZero | |||
| * For license info please see the LICENSE file distributed with this source code | |||
| *************************************************************************************/ | |||
| #include "SFZRegion.h" | |||
| #include "SFZSample.h" | |||
| void sfzero::EGParameters::clear() | |||
| namespace sfzero | |||
| { | |||
| void EGParameters::clear() | |||
| { | |||
| delay = 0.0; | |||
| start = 0.0; | |||
| @@ -18,15 +22,15 @@ void sfzero::EGParameters::clear() | |||
| release = 0.0; | |||
| } | |||
| void sfzero::EGParameters::clearMod() | |||
| void EGParameters::clearMod() | |||
| { | |||
| // Clear for velocity or other modification. | |||
| 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)); | |||
| hikey = 127; | |||
| @@ -41,7 +45,7 @@ void sfzero::Region::clear() | |||
| ampeg_veltrack.clearMod(); | |||
| } | |||
| void sfzero::Region::clearForSF2() | |||
| void Region::clearForSF2() | |||
| { | |||
| clear(); | |||
| pitch_keycenter = -1; | |||
| @@ -56,7 +60,7 @@ void sfzero::Region::clearForSF2() | |||
| ampeg.release = -12000.0; | |||
| } | |||
| void sfzero::Region::clearForRelativeSF2() | |||
| void Region::clearForRelativeSF2() | |||
| { | |||
| clear(); | |||
| pitch_keytrack = 0; | |||
| @@ -64,7 +68,7 @@ void sfzero::Region::clearForRelativeSF2() | |||
| ampeg.sustain = 0.0; | |||
| } | |||
| void sfzero::Region::addForSF2(sfzero::Region *other) | |||
| void Region::addForSF2(Region *other) | |||
| { | |||
| offset += other->offset; | |||
| end += other->end; | |||
| @@ -84,7 +88,7 @@ void sfzero::Region::addForSF2(sfzero::Region *other) | |||
| ampeg.release += other->ampeg.release; | |||
| } | |||
| void sfzero::Region::sf2ToSFZ() | |||
| void Region::sf2ToSFZ() | |||
| { | |||
| // EG times need to be converted from timecents to seconds. | |||
| 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); | |||
| if (sample) | |||
| @@ -143,4 +147,6 @@ water::String sfzero::Region::dump() | |||
| 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); | |||
| }; | |||
| } | |||
| #endif // SFZREGION_H_INCLUDED | |||
| @@ -4,11 +4,15 @@ | |||
| * Forked from https://github.com/stevefolta/SFZero | |||
| * For license info please see the LICENSE file distributed with this source code | |||
| *************************************************************************************/ | |||
| #include "SFZSample.h" | |||
| #include "SFZDebug.h" | |||
| namespace sfzero | |||
| { | |||
| #if 0 | |||
| bool sfzero::Sample::load(water::AudioFormatManager *formatManager) | |||
| bool Sample::load(water::AudioFormatManager *formatManager) | |||
| { | |||
| water::AudioFormatReader *reader = formatManager->createReaderFor(file_); | |||
| @@ -37,31 +41,31 @@ bool sfzero::Sample::load(water::AudioFormatManager *formatManager) | |||
| } | |||
| #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; | |||
| sampleLength_ = buffer_->getNumSamples(); | |||
| } | |||
| water::AudioSampleBuffer *sfzero::Sample::detachBuffer() | |||
| water::AudioSampleBuffer *Sample::detachBuffer() | |||
| { | |||
| water::AudioSampleBuffer *result = buffer_; | |||
| buffer_ = nullptr; | |||
| return result; | |||
| } | |||
| water::String sfzero::Sample::dump() { return file_.getFullPathName() + "\n"; } | |||
| water::String Sample::dump() { return file_.getFullPathName() + "\n"; } | |||
| #ifdef DEBUG | |||
| void sfzero::Sample::checkIfZeroed(const char *where) | |||
| void Sample::checkIfZeroed(const char *where) | |||
| { | |||
| if (buffer_ == nullptr) | |||
| { | |||
| sfzero::dbgprintf("SFZSample::checkIfZeroed(%s): no buffer!", where); | |||
| dbgprintf("SFZSample::checkIfZeroed(%s): no buffer!", where); | |||
| return; | |||
| } | |||
| @@ -81,12 +85,13 @@ void sfzero::Sample::checkIfZeroed(const char *where) | |||
| } | |||
| 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 | |||
| { | |||
| sfzero::dbgprintf("Buffer zeroed at %s! (%lu zeros)", where, zero); | |||
| dbgprintf("Buffer zeroed at %s! (%lu zeros)", where, zero); | |||
| } | |||
| } | |||
| #endif // JUCE_DEBUG | |||
| } | |||
| @@ -4,13 +4,17 @@ | |||
| * Forked from https://github.com/stevefolta/SFZero | |||
| * For license info please see the LICENSE file distributed with this source code | |||
| *************************************************************************************/ | |||
| #include "SFZSound.h" | |||
| #include "SFZReader.h" | |||
| #include "SFZRegion.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(); | |||
| @@ -20,21 +24,21 @@ sfzero::Sound::~Sound() | |||
| 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(); | |||
| } | |||
| } | |||
| 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. | |||
| 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('\\', '/'); | |||
| defaultPath = defaultPath.replaceCharacter('\\', '/'); | |||
| @@ -49,18 +53,18 @@ sfzero::Sample *sfzero::Sound::addSample(water::String path, water::String defau | |||
| sampleFile = defaultDir.getChildFile(path); | |||
| } | |||
| water::String samplePath = sampleFile.getFullPathName(); | |||
| sfzero::Sample *sample = samples_[samplePath]; | |||
| Sample *sample = samples_[samplePath]; | |||
| if (sample == nullptr) | |||
| { | |||
| sample = new sfzero::Sample(sampleFile); | |||
| sample = new Sample(sampleFile); | |||
| samples_.set(samplePath, 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)) | |||
| { | |||
| @@ -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_); | |||
| } | |||
| void sfzero::Sound::loadSamples(water::AudioFormatManager* formatManager, double* progressVar, CarlaThread* thread) | |||
| void Sound::loadSamples(water::AudioFormatManager* formatManager, double* progressVar, CarlaThread* thread) | |||
| { | |||
| if (progressVar) | |||
| { | |||
| @@ -86,9 +90,9 @@ void sfzero::Sound::loadSamples(water::AudioFormatManager* formatManager, double | |||
| } | |||
| 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 | |||
| bool ok = sample->load(formatManager); | |||
| 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(); | |||
| for (int i = 0; i < numRegions; ++i) | |||
| { | |||
| sfzero::Region *region = regions_[i]; | |||
| Region *region = regions_[i]; | |||
| if (region->matches(note, velocity, trigger)) | |||
| { | |||
| return region; | |||
| @@ -130,19 +134,19 @@ sfzero::Region *sfzero::Sound::getRegionFor(int note, int velocity, sfzero::Regi | |||
| 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; | |||
| auto &errors = getErrors(); | |||
| @@ -184,7 +188,7 @@ water::String sfzero::Sound::dump() | |||
| if (samples_.size() > 0) | |||
| { | |||
| 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(); | |||
| } | |||
| @@ -195,3 +199,5 @@ water::String sfzero::Sound::dump() | |||
| } | |||
| return info; | |||
| } | |||
| } | |||
| @@ -9,23 +9,13 @@ | |||
| #include "SFZRegion.h" | |||
| #include "water/containers/HashMap.h" | |||
| #include "water/memory/ReferenceCountedObject.h" | |||
| #include "water/synthesisers/Synthesiser.h" | |||
| #include "water/text/StringArray.h" | |||
| #include "CarlaJuceUtils.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 | |||
| { | |||
| @@ -4,29 +4,32 @@ | |||
| * Forked from https://github.com/stevefolta/SFZero | |||
| * For license info please see the LICENSE file distributed with this source code | |||
| *************************************************************************************/ | |||
| #include "SFZSynth.h" | |||
| #include "SFZSound.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; | |||
| const water::ScopedLock locker(lock); | |||
| const CarlaMutexLocker locker(lock); | |||
| int midiVelocity = static_cast<int>(velocity * 127); | |||
| // First, stop any currently-playing sounds in the group. | |||
| //*** Currently, this only pays attention to the first matching region. | |||
| int group = 0; | |||
| sfzero::Sound *sound = dynamic_cast<sfzero::Sound *>(getSound(0)); | |||
| Sound *sound = dynamic_cast<Sound *>(getSound(0)); | |||
| if (sound) | |||
| { | |||
| sfzero::Region *region = sound->getRegionFor(midiNoteNumber, midiVelocity); | |||
| Region *region = sound->getRegionFor(midiNoteNumber, midiVelocity); | |||
| if (region) | |||
| { | |||
| group = region->group; | |||
| @@ -36,7 +39,7 @@ void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||
| { | |||
| 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) | |||
| { | |||
| continue; | |||
| @@ -53,7 +56,7 @@ void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||
| bool anyNotesPlaying = false; | |||
| 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) | |||
| { | |||
| continue; | |||
| @@ -78,17 +81,17 @@ void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||
| } | |||
| // 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) | |||
| { | |||
| int numRegions = sound->getNumRegions(); | |||
| for (i = 0; i < numRegions; ++i) | |||
| { | |||
| sfzero::Region *region = sound->regionAt(i); | |||
| Region *region = sound->regionAt(i); | |||
| 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) | |||
| { | |||
| voice->setRegion(region); | |||
| @@ -100,23 +103,21 @@ void sfzero::Synth::noteOn(int midiChannel, int midiNoteNumber, float velocity) | |||
| 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); | |||
| // Start release region. | |||
| sfzero::Sound *sound = dynamic_cast<sfzero::Sound *>(getSound(0)); | |||
| Sound *sound = dynamic_cast<Sound *>(getSound(0)); | |||
| if (sound) | |||
| { | |||
| sfzero::Region *region = sound->getRegionFor(midiNoteNumber, noteVelocities_[midiNoteNumber], sfzero::Region::release); | |||
| Region *region = sound->getRegionFor(midiNoteNumber, noteVelocities_[midiNoteNumber], Region::release); | |||
| 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) | |||
| { | |||
| // 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; | |||
| #if 0 | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| if (voices.getUnchecked(i)->getCurrentlyPlayingNote() >= 0) | |||
| @@ -141,11 +140,11 @@ int sfzero::Synth::numVoicesUsed() | |||
| numUsed += 1; | |||
| } | |||
| } | |||
| #endif | |||
| return numUsed; | |||
| } | |||
| water::String sfzero::Synth::voiceInfoString() | |||
| water::String Synth::voiceInfoString() | |||
| { | |||
| enum | |||
| { | |||
| @@ -154,10 +153,10 @@ water::String sfzero::Synth::voiceInfoString() | |||
| water::StringArray lines; | |||
| int numUsed = 0, numShown = 0; | |||
| #if 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) | |||
| { | |||
| continue; | |||
| @@ -169,7 +168,9 @@ water::String sfzero::Synth::voiceInfoString() | |||
| } | |||
| lines.add(voice->infoString()); | |||
| } | |||
| #endif | |||
| lines.insert(0, "voices used: " + water::String(numUsed)); | |||
| return lines.joinIntoString("\n"); | |||
| } | |||
| } | |||
| @@ -4,22 +4,13 @@ | |||
| * Forked from https://github.com/stevefolta/SFZero | |||
| * For license info please see the LICENSE file distributed with this source code | |||
| *************************************************************************************/ | |||
| #ifndef SFZSYNTH_H_INCLUDED | |||
| #define SFZSYNTH_H_INCLUDED | |||
| #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 | |||
| { | |||
| @@ -4,6 +4,7 @@ | |||
| * Forked from https://github.com/stevefolta/SFZero | |||
| * For license info please see the LICENSE file distributed with this source code | |||
| *************************************************************************************/ | |||
| #include "SFZDebug.h" | |||
| #include "SFZRegion.h" | |||
| #include "SFZSample.h" | |||
| @@ -14,23 +15,26 @@ | |||
| #include <cmath> | |||
| namespace sfzero | |||
| { | |||
| 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), | |||
| sourceSamplePosition_(0), sampleEnd_(0), loopStart_(0), loopEnd_(0), numLoops_(0), curVelocity_(0) | |||
| { | |||
| 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) | |||
| { | |||
| sfzero::Sound *sound = dynamic_cast<sfzero::Sound *>(soundIn); | |||
| Sound *sound = dynamic_cast<Sound *>(soundIn); | |||
| 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; | |||
| noteGainLeft_ *= static_cast<float>(sqrt(1.0 - adjustedPan)); | |||
| noteGainRight_ *= static_cast<float>(sqrt(adjustedPan)); | |||
| #if 0 | |||
| ampeg_.startNote(®ion_->ampeg, floatVelocity, getSampleRate(), ®ion_->ampeg_veltrack); | |||
| #endif | |||
| // Offset/end. | |||
| sourceSamplePosition_ = static_cast<double>(region_->offset); | |||
| @@ -89,19 +91,19 @@ void sfzero::Voice::startNote(int midiNoteNumber, float floatVelocity, water::Sy | |||
| // Loop. | |||
| 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()) | |||
| { | |||
| loopMode = sfzero::Region::loop_continuous; | |||
| loopMode = Region::loop_continuous; | |||
| } | |||
| 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) | |||
| { | |||
| @@ -117,7 +119,7 @@ void sfzero::Voice::startNote(int midiNoteNumber, float floatVelocity, water::Sy | |||
| numLoops_ = 0; | |||
| } | |||
| void sfzero::Voice::stopNote(float /*velocity*/, bool allowTailOff) | |||
| void Voice::stopNote(float /*velocity*/, bool allowTailOff) | |||
| { | |||
| if (!allowTailOff || (region_ == nullptr)) | |||
| { | |||
| @@ -125,20 +127,20 @@ void sfzero::Voice::stopNote(float /*velocity*/, bool allowTailOff) | |||
| return; | |||
| } | |||
| if (region_->loop_mode != sfzero::Region::one_shot) | |||
| if (region_->loop_mode != Region::one_shot) | |||
| { | |||
| ampeg_.noteOff(); | |||
| } | |||
| if (region_->loop_mode == sfzero::Region::loop_sustain) | |||
| if (region_->loop_mode == Region::loop_sustain) | |||
| { | |||
| // Continue playing, but stop looping. | |||
| loopEnd_ = loopStart_; | |||
| } | |||
| } | |||
| void sfzero::Voice::stopNoteForGroup() | |||
| void Voice::stopNoteForGroup() | |||
| { | |||
| if (region_->off_mode == sfzero::Region::fast) | |||
| if (region_->off_mode == Region::fast) | |||
| { | |||
| 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) | |||
| { | |||
| @@ -160,8 +162,8 @@ void sfzero::Voice::pitchWheelMoved(int newValue) | |||
| 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) | |||
| { | |||
| @@ -265,17 +267,17 @@ void sfzero::Voice::renderNextBlock(water::AudioSampleBuffer &outputBuffer, int | |||
| 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"}; | |||
| @@ -294,7 +296,7 @@ water::String sfzero::Voice::infoString() | |||
| return info; | |||
| } | |||
| void sfzero::Voice::calcPitchRatio() | |||
| void Voice::calcPitchRatio() | |||
| { | |||
| double note = curMidiNote_; | |||
| @@ -316,23 +318,21 @@ void sfzero::Voice::calcPitchRatio() | |||
| } | |||
| double targetFreq = fractionalMidiNoteInHz(adjustedPitch); | |||
| double naturalFreq = water::MidiMessage::getMidiNoteInHertz(region_->pitch_keycenter); | |||
| #if 0 | |||
| pitchRatio_ = (targetFreq * region_->sample->getSampleRate()) / (naturalFreq * getSampleRate()); | |||
| #endif | |||
| } | |||
| void sfzero::Voice::killNote() | |||
| void Voice::killNote() | |||
| { | |||
| region_ = nullptr; | |||
| #if 0 | |||
| 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. | |||
| note -= 69; | |||
| // Now 0 = A | |||
| return freqOfA * pow(2.0, note / 12.0); | |||
| } | |||
| } | |||
| @@ -4,29 +4,17 @@ | |||
| * Forked from https://github.com/stevefolta/SFZero | |||
| * For license info please see the LICENSE file distributed with this source code | |||
| *************************************************************************************/ | |||
| #ifndef SFZVOICE_H_INCLUDED | |||
| #define SFZVOICE_H_INCLUDED | |||
| #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 | |||
| { | |||
| struct Region; | |||
| class Voice : public water::SynthesiserVoice | |||
| @@ -47,7 +35,7 @@ public: | |||
| bool isPlayingOneShot(); | |||
| int getGroup(); | |||
| water::uint64 getOffBy(); | |||
| water::int64 getOffBy(); | |||
| // Set the region to be used by the next startNote(). | |||
| 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 | |||
| * 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 | |||
| * 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/TemporaryFile.cpp" | |||
| #include "maths/BigInteger.cpp" | |||
| #include "maths/Random.cpp" | |||
| #include "memory/MemoryBlock.cpp" | |||
| @@ -65,6 +67,8 @@ HINSTANCE water_getCurrentModuleInstanceHandle() noexcept | |||
| #include "streams/MemoryOutputStream.cpp" | |||
| #include "streams/OutputStream.cpp" | |||
| #include "synthesisers/Synthesiser.cpp" | |||
| #include "text/CharacterFunctions.cpp" | |||
| #include "text/Identifier.cpp" | |||
| #include "text/StringArray.cpp" | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * 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> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||