@@ -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 | |||