Browse Source

Import more water code, almost all sfzero builds now

tags/v1.9.11
falkTX 6 years ago
parent
commit
d19547ca12
18 changed files with 1825 additions and 192 deletions
  1. +6
    -1
      source/modules/sfzero/sfzero/SFZDebug.cpp
  2. +18
    -13
      source/modules/sfzero/sfzero/SFZEG.cpp
  3. +33
    -28
      source/modules/sfzero/sfzero/SFZReader.cpp
  4. +16
    -10
      source/modules/sfzero/sfzero/SFZRegion.cpp
  5. +1
    -0
      source/modules/sfzero/sfzero/SFZRegion.h
  6. +16
    -11
      source/modules/sfzero/sfzero/SFZSample.cpp
  7. +32
    -26
      source/modules/sfzero/sfzero/SFZSound.cpp
  8. +2
    -12
      source/modules/sfzero/sfzero/SFZSound.h
  9. +28
    -27
      source/modules/sfzero/sfzero/SFZSynth.cpp
  10. +2
    -11
      source/modules/sfzero/sfzero/SFZSynth.h
  11. +34
    -34
      source/modules/sfzero/sfzero/SFZVoice.cpp
  12. +4
    -16
      source/modules/sfzero/sfzero/SFZVoice.h
  13. +240
    -0
      source/modules/water/maths/BigInteger.cpp
  14. +118
    -0
      source/modules/water/maths/BigInteger.h
  15. +642
    -0
      source/modules/water/synthesisers/Synthesiser.cpp
  16. +626
    -0
      source/modules/water/synthesisers/Synthesiser.h
  17. +6
    -2
      source/modules/water/water.cpp
  18. +1
    -1
      source/modules/water/water.h

+ 6
- 1
source/modules/sfzero/sfzero/SFZDebug.cpp View File

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

}

+ 18
- 13
source/modules/sfzero/sfzero/SFZEG.cpp View File

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

}

+ 33
- 28
source/modules/sfzero/sfzero/SFZReader.cpp View File

@@ -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);
}

}

+ 16
- 10
source/modules/sfzero/sfzero/SFZRegion.cpp View File

@@ -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)); }

}

+ 1
- 0
source/modules/sfzero/sfzero/SFZRegion.h View File

@@ -90,6 +90,7 @@ struct Region

static float timecents2Secs(int timecents);
};

}

#endif // SFZREGION_H_INCLUDED

+ 16
- 11
source/modules/sfzero/sfzero/SFZSample.cpp View File

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

}

+ 32
- 26
source/modules/sfzero/sfzero/SFZSound.cpp View File

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

}

+ 2
- 12
source/modules/sfzero/sfzero/SFZSound.h View File

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



+ 28
- 27
source/modules/sfzero/sfzero/SFZSynth.cpp View File

@@ -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");
}

}

+ 2
- 11
source/modules/sfzero/sfzero/SFZSynth.h View File

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


+ 34
- 34
source/modules/sfzero/sfzero/SFZVoice.cpp View File

@@ -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(&region_->ampeg, floatVelocity, getSampleRate(), &region_->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
- 16
source/modules/sfzero/sfzero/SFZVoice.h View File

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


+ 240
- 0
source/modules/water/maths/BigInteger.cpp View File

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

+ 118
- 0
source/modules/water/maths/BigInteger.h View File

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

+ 642
- 0
source/modules/water/synthesisers/Synthesiser.cpp View File

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

+ 626
- 0
source/modules/water/synthesisers/Synthesiser.h View File

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

+ 6
- 2
source/modules/water/water.cpp View File

@@ -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
- 1
source/modules/water/water.h View File

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


Loading…
Cancel
Save