Merge pull request #1007 from paulfd/remember-midistate
Differentiate between loading and reloading
This commit is contained in:
commit
672cede46f
17 changed files with 410 additions and 75 deletions
|
|
@ -88,6 +88,8 @@ std::string getDescriptionBlob(sfizz_synth_t* handle)
|
||||||
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
||||||
bufferedStrCat(cdata.pathbuf, "/cc", cc, "/default");
|
bufferedStrCat(cdata.pathbuf, "/cc", cc, "/default");
|
||||||
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
||||||
|
bufferedStrCat(cdata.pathbuf, "/cc", cc, "/value");
|
||||||
|
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +165,8 @@ InstrumentDescription parseDescriptionBlob(absl::string_view blob)
|
||||||
desc.ccLabel[indices[0]] = args[0].s;
|
desc.ccLabel[indices[0]] = args[0].s;
|
||||||
else if (Messages::matchOSC("/cc&/default", path, indices) && !strcmp(sig, "f"))
|
else if (Messages::matchOSC("/cc&/default", path, indices) && !strcmp(sig, "f"))
|
||||||
desc.ccDefault[indices[0]] = args[0].f;
|
desc.ccDefault[indices[0]] = args[0].f;
|
||||||
|
else if (Messages::matchOSC("/cc&/value", path, indices) && !strcmp(sig, "f"))
|
||||||
|
desc.ccValue[indices[0]] = args[0].f;
|
||||||
|
|
||||||
src += byteCount;
|
src += byteCount;
|
||||||
srcSize -= byteCount;
|
srcSize -= byteCount;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ struct InstrumentDescription {
|
||||||
std::array<std::string, 128> keyswitchLabel {};
|
std::array<std::string, 128> keyswitchLabel {};
|
||||||
std::array<std::string, sfz::config::numCCs> ccLabel {};
|
std::array<std::string, sfz::config::numCCs> ccLabel {};
|
||||||
std::array<float, sfz::config::numCCs> ccDefault {};
|
std::array<float, sfz::config::numCCs> ccDefault {};
|
||||||
|
std::array<float, sfz::config::numCCs> ccValue {};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1388,10 +1388,10 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self)
|
||||||
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
|
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
|
||||||
if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) {
|
if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) {
|
||||||
// Mark all the used CCs for automation with default values
|
// Mark all the used CCs for automation with default values
|
||||||
self->ccauto[cc] = desc.ccDefault[cc];
|
self->ccauto[cc] = desc.ccValue[cc];
|
||||||
self->have_ccauto = true;
|
self->have_ccauto = true;
|
||||||
// Update the current CCs
|
// Update the current CCs
|
||||||
self->cc_current[cc] = desc.ccDefault[cc];
|
self->cc_current[cc] = desc.ccValue[cc];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1476,7 +1476,7 @@ restore(LV2_Handle instance,
|
||||||
|
|
||||||
if (path)
|
if (path)
|
||||||
{
|
{
|
||||||
strncpy(self->sfz_file_path, path, MAX_PATH_SIZE);
|
strncpy(self->sfz_file_path, path, MAX_PATH_SIZE - 1);
|
||||||
self->sfz_file_path[MAX_PATH_SIZE - 1] = '\0';
|
self->sfz_file_path[MAX_PATH_SIZE - 1] = '\0';
|
||||||
|
|
||||||
if (map_path)
|
if (map_path)
|
||||||
|
|
@ -1497,7 +1497,7 @@ restore(LV2_Handle instance,
|
||||||
|
|
||||||
if (path)
|
if (path)
|
||||||
{
|
{
|
||||||
strncpy(self->scala_file_path, path, MAX_PATH_SIZE);
|
strncpy(self->scala_file_path, path, MAX_PATH_SIZE - 1);
|
||||||
self->scala_file_path[MAX_PATH_SIZE - 1] = '\0';
|
self->scala_file_path[MAX_PATH_SIZE - 1] = '\0';
|
||||||
|
|
||||||
if (map_path)
|
if (map_path)
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
|
||||||
_synth->setBroadcastCallback(onMessage, this);
|
_synth->setBroadcastCallback(onMessage, this);
|
||||||
|
|
||||||
_currentStretchedTuning = 0.0;
|
_currentStretchedTuning = 0.0;
|
||||||
loadSfzFileOrDefault({}, false);
|
loadSfzFileOrDefault({});
|
||||||
|
|
||||||
_synth->bpmTempo(0, 120);
|
_synth->bpmTempo(0, 120);
|
||||||
_timeSigNumerator = 4;
|
_timeSigNumerator = 4;
|
||||||
|
|
@ -220,7 +220,7 @@ void SfizzVstProcessor::syncStateToSynth()
|
||||||
if (!synth)
|
if (!synth)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
loadSfzFileOrDefault(_state.sfzFile, true);
|
loadSfzFileOrDefault(_state.sfzFile);
|
||||||
synth->setVolume(_state.volume);
|
synth->setVolume(_state.volume);
|
||||||
synth->setNumVoices(_state.numVoices);
|
synth->setNumVoices(_state.numVoices);
|
||||||
synth->setOversamplingFactor(1 << _state.oversamplingLog2);
|
synth->setOversamplingFactor(1 << _state.oversamplingLog2);
|
||||||
|
|
@ -590,7 +590,7 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message)
|
||||||
|
|
||||||
std::unique_lock<SpinMutex> lock(_processMutex);
|
std::unique_lock<SpinMutex> lock(_processMutex);
|
||||||
_state.sfzFile.assign(static_cast<const char *>(data), size);
|
_state.sfzFile.assign(static_cast<const char *>(data), size);
|
||||||
loadSfzFileOrDefault(_state.sfzFile, false);
|
loadSfzFileOrDefault(_state.sfzFile);
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
else if (!std::strcmp(id, "LoadScala")) {
|
else if (!std::strcmp(id, "LoadScala")) {
|
||||||
|
|
@ -705,7 +705,7 @@ void SfizzVstProcessor::receiveOSC(int delay, const char* path, const char* sig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState)
|
void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath)
|
||||||
{
|
{
|
||||||
sfz::Sfizz& synth = *_synth;
|
sfz::Sfizz& synth = *_synth;
|
||||||
|
|
||||||
|
|
@ -725,16 +725,7 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i
|
||||||
const InstrumentDescription desc = parseDescriptionBlob(descBlob);
|
const InstrumentDescription desc = parseDescriptionBlob(descBlob);
|
||||||
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
|
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
|
||||||
if (desc.ccUsed.test(cc))
|
if (desc.ccUsed.test(cc))
|
||||||
newControllers[cc] = desc.ccDefault[cc];
|
newControllers[cc] = desc.ccValue[cc];
|
||||||
}
|
|
||||||
// set CC from existing state
|
|
||||||
if (initParametersFromState) {
|
|
||||||
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
|
|
||||||
if (absl::optional<float> value = oldControllers[cc]) {
|
|
||||||
newControllers[cc] = *value;
|
|
||||||
synth.automateHdcc(0, int(cc), *value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_state.controllers = std::move(newControllers);
|
_state.controllers = std::move(newControllers);
|
||||||
}
|
}
|
||||||
|
|
@ -838,7 +829,7 @@ void SfizzVstProcessor::doBackgroundIdle(size_t idleCounter)
|
||||||
if (_synth->shouldReloadFile()) {
|
if (_synth->shouldReloadFile()) {
|
||||||
fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n");
|
fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n");
|
||||||
std::lock_guard<SpinMutex> lock(_processMutex);
|
std::lock_guard<SpinMutex> lock(_processMutex);
|
||||||
loadSfzFileOrDefault(_state.sfzFile, false);
|
loadSfzFileOrDefault(_state.sfzFile);
|
||||||
}
|
}
|
||||||
if (_synth->shouldReloadScala()) {
|
if (_synth->shouldReloadScala()) {
|
||||||
fprintf(stderr, "[Sfizz] scala file has changed, reloading\n");
|
fprintf(stderr, "[Sfizz] scala file has changed, reloading\n");
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ private:
|
||||||
void receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args);
|
void receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args);
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
void loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState);
|
void loadSfzFileOrDefault(const std::string& filePath);
|
||||||
|
|
||||||
// note event tracking
|
// note event tracking
|
||||||
std::array<float, 128> _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change
|
std::array<float, 128> _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change
|
||||||
|
|
|
||||||
|
|
@ -320,6 +320,7 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
|
||||||
preloadedFiles[fileId].information.maxOffset = maxOffset;
|
preloadedFiles[fileId].information.maxOffset = maxOffset;
|
||||||
preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad);
|
preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad);
|
||||||
}
|
}
|
||||||
|
existingFile->second.preloadCallCount++;
|
||||||
} else {
|
} else {
|
||||||
fileInformation->sampleRate = static_cast<double>(reader->sampleRate());
|
fileInformation->sampleRate = static_cast<double>(reader->sampleRate());
|
||||||
auto insertedPair = preloadedFiles.insert_or_assign(fileId, {
|
auto insertedPair = preloadedFiles.insert_or_assign(fileId, {
|
||||||
|
|
@ -327,14 +328,30 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
|
||||||
*fileInformation
|
*fileInformation
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!insertedPair.second)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
insertedPair.first->second.status = FileData::Status::Preloaded;
|
insertedPair.first->second.status = FileData::Status::Preloaded;
|
||||||
|
insertedPair.first->second.preloadCallCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::FilePool::resetPreloadCallCounts() noexcept
|
||||||
|
{
|
||||||
|
for (auto& preloadedFile: preloadedFiles)
|
||||||
|
preloadedFile.second.preloadCallCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::FilePool::removeUnusedPreloadedData() noexcept
|
||||||
|
{
|
||||||
|
for (auto it = preloadedFiles.begin(), end = preloadedFiles.end(); it != end; ) {
|
||||||
|
auto copyIt = it++;
|
||||||
|
if (copyIt->second.preloadCallCount == 0) {
|
||||||
|
DBG("[sfizz] Removing unused preloaded data: " << copyIt->first.filename());
|
||||||
|
preloadedFiles.erase(copyIt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept
|
sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept
|
||||||
{
|
{
|
||||||
auto fileInformation = getFileInformation(fileId);
|
auto fileInformation = getFileInformation(fileId);
|
||||||
|
|
@ -363,7 +380,7 @@ sfz::FileDataHolder sfz::FilePool::getFilePromise(const std::shared_ptr<FileId>&
|
||||||
{
|
{
|
||||||
const auto preloaded = preloadedFiles.find(*fileId);
|
const auto preloaded = preloadedFiles.find(*fileId);
|
||||||
if (preloaded == preloadedFiles.end()) {
|
if (preloaded == preloadedFiles.end()) {
|
||||||
DBG("[sfizz] File not found in the preloaded files: " << fileId);
|
DBG("[sfizz] File not found in the preloaded files: " << fileId->filename());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
QueuedFileData queuedData { fileId, &preloaded->second, std::chrono::high_resolution_clock::now() };
|
QueuedFileData queuedData { fileId, &preloaded->second, std::chrono::high_resolution_clock::now() };
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ struct FileData
|
||||||
FileAudioBuffer preloadedData;
|
FileAudioBuffer preloadedData;
|
||||||
FileInformation information;
|
FileInformation information;
|
||||||
FileAudioBuffer fileData {};
|
FileAudioBuffer fileData {};
|
||||||
|
int preloadCallCount { 0 };
|
||||||
std::atomic<Status> status { Status::Invalid };
|
std::atomic<Status> status { Status::Invalid };
|
||||||
std::atomic<size_t> availableFrames { 0 };
|
std::atomic<size_t> availableFrames { 0 };
|
||||||
std::atomic<int> readerCount { 0 };
|
std::atomic<int> readerCount { 0 };
|
||||||
|
|
@ -258,6 +259,17 @@ public:
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the number of preloadFile counts for each sample.
|
||||||
|
*/
|
||||||
|
void resetPreloadCallCounts() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear all files with a preload call count of 0.
|
||||||
|
*/
|
||||||
|
void removeUnusedPreloadedData() noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get a handle on a file, which triggers background loading
|
* @brief Get a handle on a file, which triggers background loading
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@
|
||||||
|
|
||||||
sfz::MidiState::MidiState()
|
sfz::MidiState::MidiState()
|
||||||
{
|
{
|
||||||
reset();
|
resetEventStates();
|
||||||
|
resetNoteStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noexcept
|
void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noexcept
|
||||||
|
|
@ -213,11 +214,36 @@ float sfz::MidiState::getCCValueAt(int ccNumber, int delay) const noexcept
|
||||||
return ccEvents[ccNumber].back().value;
|
return ccEvents[ccNumber].back().value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::MidiState::reset() noexcept
|
void sfz::MidiState::resetNoteStates() noexcept
|
||||||
{
|
{
|
||||||
for (auto& velocity: lastNoteVelocities)
|
for (auto& velocity: lastNoteVelocities)
|
||||||
velocity = 0.0f;
|
velocity = 0.0f;
|
||||||
|
|
||||||
|
velocityOverride = 0.0f;
|
||||||
|
activeNotes = 0;
|
||||||
|
internalClock = 0;
|
||||||
|
lastNotePlayed = 0;
|
||||||
|
alternate = 0.0f;
|
||||||
|
|
||||||
|
auto setEvents = [] (EventVector& events, float value) {
|
||||||
|
events.clear();
|
||||||
|
events.push_back({ 0, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
setEvents(ccEvents[ExtendedCCs::noteOnVelocity], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::keyboardNoteNumber], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::unipolarRandom], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::bipolarRandom], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::keyboardNoteGate], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::alternate], 0.0f);
|
||||||
|
|
||||||
|
noteStates.reset();
|
||||||
|
absl::c_fill(noteOnTimes, 0);
|
||||||
|
absl::c_fill(noteOffTimes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::MidiState::resetEventStates() noexcept
|
||||||
|
{
|
||||||
auto clearEvents = [] (EventVector& events) {
|
auto clearEvents = [] (EventVector& events) {
|
||||||
events.clear();
|
events.clear();
|
||||||
events.push_back({ 0, 0.0f });
|
events.push_back({ 0, 0.0f });
|
||||||
|
|
@ -231,14 +257,6 @@ void sfz::MidiState::reset() noexcept
|
||||||
|
|
||||||
clearEvents(pitchEvents);
|
clearEvents(pitchEvents);
|
||||||
clearEvents(channelAftertouchEvents);
|
clearEvents(channelAftertouchEvents);
|
||||||
|
|
||||||
velocityOverride = 0.0f;
|
|
||||||
activeNotes = 0;
|
|
||||||
internalClock = 0;
|
|
||||||
lastNotePlayed = 0;
|
|
||||||
noteStates.reset();
|
|
||||||
absl::c_fill(noteOnTimes, 0);
|
|
||||||
absl::c_fill(noteOffTimes, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept
|
const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept
|
||||||
|
|
|
||||||
|
|
@ -184,15 +184,20 @@ public:
|
||||||
float getCCValueAt(int ccNumber, int delay) const noexcept;
|
float getCCValueAt(int ccNumber, int delay) const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reset the midi state (does not impact the last note on time)
|
* @brief Reset the midi note states
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void reset() noexcept;
|
void resetNoteStates() noexcept;
|
||||||
|
|
||||||
const EventVector& getCCEvents(int ccIdx) const noexcept;
|
const EventVector& getCCEvents(int ccIdx) const noexcept;
|
||||||
const EventVector& getPolyAftertouchEvents(int noteNumber) const noexcept;
|
const EventVector& getPolyAftertouchEvents(int noteNumber) const noexcept;
|
||||||
const EventVector& getPitchEvents() const noexcept;
|
const EventVector& getPitchEvents() const noexcept;
|
||||||
const EventVector& getChannelAftertouchEvents() const noexcept;
|
const EventVector& getChannelAftertouchEvents() const noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Reset the midi event states (CC, AT, and pitch bend)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void resetEventStates() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,19 +61,25 @@ void Resources::setSamplesPerBlock(int samplesPerBlock)
|
||||||
impl.beatClock.setSamplesPerBlock(samplesPerBlock);
|
impl.beatClock.setSamplesPerBlock(samplesPerBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resources::clear()
|
void Resources::clearNonState()
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
impl.curves = CurveSet::createPredefined();
|
impl.curves = CurveSet::createPredefined();
|
||||||
impl.filePool.clear();
|
impl.filePool.clear();
|
||||||
impl.wavePool.clearFileWaves();
|
impl.wavePool.clearFileWaves();
|
||||||
impl.logger.clear();
|
impl.logger.clear();
|
||||||
impl.midiState.reset();
|
|
||||||
impl.modMatrix.clear();
|
impl.modMatrix.clear();
|
||||||
impl.beatClock.clear();
|
|
||||||
impl.metronome.clear();
|
impl.metronome.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Resources::clearState()
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
impl.midiState.resetNoteStates();
|
||||||
|
impl.midiState.resetEventStates();
|
||||||
|
impl.beatClock.clear();
|
||||||
|
}
|
||||||
|
|
||||||
const SynthConfig& Resources::getSynthConfig() const noexcept
|
const SynthConfig& Resources::getSynthConfig() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->synthConfig;
|
return impl_->synthConfig;
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,17 @@ public:
|
||||||
|
|
||||||
void setSampleRate(float samplerate);
|
void setSampleRate(float samplerate);
|
||||||
void setSamplesPerBlock(int samplesPerBlock);
|
void setSamplesPerBlock(int samplesPerBlock);
|
||||||
void clear();
|
/**
|
||||||
|
* @brief Clear resources that are related to a currently loaded SFZ file
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void clearNonState();
|
||||||
|
/**
|
||||||
|
* @brief Clear resources that are unrelated to the currently loaded SFZ file,
|
||||||
|
* i.e. midi state and beat clock.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void clearState();
|
||||||
|
|
||||||
#define ACCESSOR_RW(Accessor, RetTy) \
|
#define ACCESSOR_RW(Accessor, RetTy) \
|
||||||
RetTy const& Accessor() const noexcept; \
|
RetTy const& Accessor() const noexcept; \
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,8 @@ Synth::Impl::Impl()
|
||||||
effectFactory_.registerStandardEffectTypes();
|
effectFactory_.registerStandardEffectTypes();
|
||||||
initEffectBuses();
|
initEffectBuses();
|
||||||
resetVoices(config::numVoices);
|
resetVoices(config::numVoices);
|
||||||
|
resetDefaultCCValues();
|
||||||
|
resetAllControllers(0);
|
||||||
|
|
||||||
// modulation sources
|
// modulation sources
|
||||||
MidiState& midiState = resources_.getMidiState();
|
MidiState& midiState = resources_.getMidiState();
|
||||||
|
|
@ -274,7 +276,7 @@ void Synth::Impl::clear()
|
||||||
currentSet_ = nullptr;
|
currentSet_ = nullptr;
|
||||||
sets_.clear();
|
sets_.clear();
|
||||||
layers_.clear();
|
layers_.clear();
|
||||||
resources_.clear();
|
resources_.clearNonState();
|
||||||
rootPath_.clear();
|
rootPath_.clear();
|
||||||
numGroups_ = 0;
|
numGroups_ = 0;
|
||||||
numMasters_ = 0;
|
numMasters_ = 0;
|
||||||
|
|
@ -284,8 +286,8 @@ void Synth::Impl::clear()
|
||||||
currentSwitch_ = absl::nullopt;
|
currentSwitch_ = absl::nullopt;
|
||||||
defaultPath_ = "";
|
defaultPath_ = "";
|
||||||
image_ = "";
|
image_ = "";
|
||||||
midiState.reset();
|
midiState.resetNoteStates();
|
||||||
filePool.clear();
|
midiState.flushEvents();
|
||||||
filePool.setRamLoading(config::loadInRam);
|
filePool.setRamLoading(config::loadInRam);
|
||||||
clearCCLabels();
|
clearCCLabels();
|
||||||
currentUsedCCs_.clear();
|
currentUsedCCs_.clear();
|
||||||
|
|
@ -303,18 +305,6 @@ void Synth::Impl::clear()
|
||||||
modificationTime_ = absl::nullopt;
|
modificationTime_ = absl::nullopt;
|
||||||
playheadMoved_ = false;
|
playheadMoved_ = false;
|
||||||
|
|
||||||
// set default controllers
|
|
||||||
// midistate is reset above
|
|
||||||
fill(absl::MakeSpan(defaultCCValues_), 0.0f);
|
|
||||||
setDefaultHdcc(7, normalizeCC(100));
|
|
||||||
setDefaultHdcc(10, 0.5f);
|
|
||||||
setDefaultHdcc(11, 1.0f);
|
|
||||||
|
|
||||||
// set default controller labels
|
|
||||||
setCCLabel(7, "Volume");
|
|
||||||
setCCLabel(10, "Pan");
|
|
||||||
setCCLabel(11, "Expression");
|
|
||||||
|
|
||||||
initEffectBuses();
|
initEffectBuses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,12 +391,20 @@ void Synth::Impl::handleControlOpcodes(const std::vector<Opcode>& members)
|
||||||
switch (member.lettersOnlyHash) {
|
switch (member.lettersOnlyHash) {
|
||||||
case hash("set_cc&"):
|
case hash("set_cc&"):
|
||||||
if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) {
|
if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) {
|
||||||
setDefaultHdcc(member.parameters.back(), member.read(Default::loCC));
|
const auto ccNumber = member.parameters.back();
|
||||||
|
const auto value = member.read(Default::loCC);
|
||||||
|
setDefaultHdcc(ccNumber, value);
|
||||||
|
if (!reloading)
|
||||||
|
resources_.getMidiState().ccEvent(0, ccNumber, value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case hash("set_hdcc&"):
|
case hash("set_hdcc&"):
|
||||||
if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) {
|
if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) {
|
||||||
setDefaultHdcc(member.parameters.back(), member.read(Default::loNormalized));
|
const auto ccNumber = member.parameters.back();
|
||||||
|
const auto value = member.read(Default::loNormalized);
|
||||||
|
setDefaultHdcc(ccNumber, value);
|
||||||
|
if (!reloading)
|
||||||
|
resources_.getMidiState().ccEvent(0, ccNumber, value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case hash("label_cc&"):
|
case hash("label_cc&"):
|
||||||
|
|
@ -550,15 +548,53 @@ void Synth::Impl::handleEffectOpcodes(const std::vector<Opcode>& rawMembers)
|
||||||
getOrCreateBus(busIndex).addEffect(std::move(fx));
|
getOrCreateBus(busIndex).addEffect(std::move(fx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Synth::Impl::resetDefaultCCValues() noexcept
|
||||||
|
{
|
||||||
|
fill(absl::MakeSpan(defaultCCValues_), 0.0f);
|
||||||
|
setDefaultHdcc(7, normalizeCC(100));
|
||||||
|
setDefaultHdcc(10, 0.5f);
|
||||||
|
setDefaultHdcc(11, 1.0f);
|
||||||
|
|
||||||
|
setCCLabel(7, "Volume");
|
||||||
|
setCCLabel(10, "Pan");
|
||||||
|
setCCLabel(11, "Expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::Impl::prepareSfzLoad(const fs::path& path)
|
||||||
|
{
|
||||||
|
auto newPath_ = path.string();
|
||||||
|
reloading = (lastPath_ == newPath_);
|
||||||
|
|
||||||
|
clear();
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (reloading) {
|
||||||
|
DBG("[sfizz] Reloading the current file");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!reloading) {
|
||||||
|
|
||||||
|
// Clear the background queues and clear the filePool
|
||||||
|
auto& filePool = resources_.getFilePool();
|
||||||
|
filePool.waitForBackgroundLoading();
|
||||||
|
filePool.clear();
|
||||||
|
|
||||||
|
// Set the default hdcc to their default
|
||||||
|
resetDefaultCCValues();
|
||||||
|
|
||||||
|
// Store the new path
|
||||||
|
lastPath_ = std::move(newPath_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Synth::loadSfzFile(const fs::path& file)
|
bool Synth::loadSfzFile(const fs::path& file)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
impl.prepareSfzLoad(file);
|
||||||
impl.clear();
|
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
fs::path realFile = fs::canonical(file, ec);
|
fs::path realFile = fs::canonical(file, ec);
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
Parser& parser = impl.parser_;
|
Parser& parser = impl.parser_;
|
||||||
parser.parseFile(ec ? file : realFile);
|
parser.parseFile(ec ? file : realFile);
|
||||||
|
|
@ -570,7 +606,10 @@ bool Synth::loadSfzFile(const fs::path& file)
|
||||||
success = success && !impl.layers_.empty();
|
success = success && !impl.layers_.empty();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
DBG("[sfizz] Loading failed");
|
||||||
|
auto& filePool = impl.resources_.getFilePool();
|
||||||
parser.clear();
|
parser.clear();
|
||||||
|
filePool.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -581,8 +620,7 @@ bool Synth::loadSfzFile(const fs::path& file)
|
||||||
bool Synth::loadSfzString(const fs::path& path, absl::string_view text)
|
bool Synth::loadSfzString(const fs::path& path, absl::string_view text)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
impl.prepareSfzLoad(path);
|
||||||
impl.clear();
|
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
Parser& parser = impl.parser_;
|
Parser& parser = impl.parser_;
|
||||||
|
|
@ -595,7 +633,10 @@ bool Synth::loadSfzString(const fs::path& path, absl::string_view text)
|
||||||
success = success && !impl.layers_.empty();
|
success = success && !impl.layers_.empty();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
auto& filePool = impl.resources_.getFilePool();
|
||||||
|
DBG("[sfizz] Loading failed");
|
||||||
parser.clear();
|
parser.clear();
|
||||||
|
filePool.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -605,8 +646,10 @@ bool Synth::loadSfzString(const fs::path& path, absl::string_view text)
|
||||||
|
|
||||||
void Synth::Impl::finalizeSfzLoad()
|
void Synth::Impl::finalizeSfzLoad()
|
||||||
{
|
{
|
||||||
const fs::path& rootDirectory = parser_.originalDirectory();
|
|
||||||
FilePool& filePool = resources_.getFilePool();
|
FilePool& filePool = resources_.getFilePool();
|
||||||
|
WavetablePool& wavePool = resources_.getWavePool();
|
||||||
|
|
||||||
|
const fs::path& rootDirectory = parser_.originalDirectory();
|
||||||
filePool.setRootDirectory(rootDirectory);
|
filePool.setRootDirectory(rootDirectory);
|
||||||
|
|
||||||
// a string representation used for OSC purposes
|
// a string representation used for OSC purposes
|
||||||
|
|
@ -642,9 +685,6 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
|
|
||||||
absl::optional<FileInformation> fileInformation;
|
absl::optional<FileInformation> fileInformation;
|
||||||
|
|
||||||
FilePool& filePool = resources_.getFilePool();
|
|
||||||
WavetablePool& wavePool = resources_.getWavePool();
|
|
||||||
|
|
||||||
if (!region.isGenerator()) {
|
if (!region.isGenerator()) {
|
||||||
if (!filePool.checkSampleId(*region.sampleId)) {
|
if (!filePool.checkSampleId(*region.sampleId)) {
|
||||||
removeCurrentRegion();
|
removeCurrentRegion();
|
||||||
|
|
@ -797,10 +837,19 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
++currentRegionIndex;
|
++currentRegionIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& toLoad: filesToLoad) {
|
// Reset the preload call count to check for unused preloaded samples
|
||||||
filePool.preloadFile(toLoad.first, toLoad.second);
|
// when reloading
|
||||||
}
|
if (reloading)
|
||||||
|
filePool.resetPreloadCallCounts();
|
||||||
|
|
||||||
|
for (const auto& toLoad: filesToLoad)
|
||||||
|
filePool.preloadFile(toLoad.first, toLoad.second);
|
||||||
|
|
||||||
|
// Remove preloaded data with no linked regions
|
||||||
|
if (reloading)
|
||||||
|
filePool.removeUnusedPreloadedData();
|
||||||
|
|
||||||
|
// Remove bad regions with unknown files
|
||||||
if (currentRegionCount < layers_.size()) {
|
if (currentRegionCount < layers_.size()) {
|
||||||
DBG("Removing " << (layers_.size() - currentRegionCount)
|
DBG("Removing " << (layers_.size() - currentRegionCount)
|
||||||
<< " out of " << layers_.size() << " regions");
|
<< " out of " << layers_.size() << " regions");
|
||||||
|
|
@ -1393,7 +1442,6 @@ void Synth::Impl::setDefaultHdcc(int ccNumber, float value)
|
||||||
ASSERT(ccNumber >= 0);
|
ASSERT(ccNumber >= 0);
|
||||||
ASSERT(ccNumber < config::numCCs);
|
ASSERT(ccNumber < config::numCCs);
|
||||||
defaultCCValues_[ccNumber] = value;
|
defaultCCValues_[ccNumber] = value;
|
||||||
resources_.getMidiState().ccEvent(0, ccNumber, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float Synth::getHdcc(int ccNumber)
|
float Synth::getHdcc(int ccNumber)
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,22 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
||||||
client.receive<'b'>(delay, path, &blob);
|
client.receive<'b'>(delay, path, &blob);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
MATCH("/aftertouch", "") {
|
||||||
|
client.receive<'f'>(delay, path, impl.resources_.getMidiState().getChannelAftertouch());
|
||||||
|
} break;
|
||||||
|
|
||||||
|
MATCH("/poly_aftertouch/&", "") {
|
||||||
|
if (indices[0] > 127)
|
||||||
|
break;
|
||||||
|
// Note: result value is not frame-exact
|
||||||
|
client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPolyAftertouch(indices[0]));
|
||||||
|
} break;
|
||||||
|
|
||||||
|
MATCH("/pitch_bend", "") {
|
||||||
|
// Note: result value is not frame-exact
|
||||||
|
client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPitchBend());
|
||||||
|
} break;
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
MATCH("/mem/buffers", "") {
|
MATCH("/mem/buffers", "") {
|
||||||
|
|
|
||||||
|
|
@ -176,9 +176,24 @@ struct Synth::Impl final: public Parser::Listener {
|
||||||
*/
|
*/
|
||||||
void startDelayedSostenutoReleases(Layer* layer, int delay, SisterVoiceRingBuilder& ring) noexcept;
|
void startDelayedSostenutoReleases(Layer* layer, int delay, SisterVoiceRingBuilder& ring) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the default CCs
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void resetDefaultCCValues() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prepare before loading a new SFZ file. The behavior of this function
|
||||||
|
* is changed by the reloading state.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
*/
|
||||||
|
void prepareSfzLoad(const fs::path& path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Finalize SFZ loading, following a successful execution of the
|
* @brief Finalize SFZ loading, following a successful execution of the
|
||||||
* parsing step.
|
* parsing step. The behavior of this function is changed by the reloading
|
||||||
|
* state.
|
||||||
*/
|
*/
|
||||||
void finalizeSfzLoad();
|
void finalizeSfzLoad();
|
||||||
|
|
||||||
|
|
@ -330,7 +345,9 @@ struct Synth::Impl final: public Parser::Listener {
|
||||||
std::chrono::time_point<std::chrono::high_resolution_clock> lastGarbageCollection_;
|
std::chrono::time_point<std::chrono::high_resolution_clock> lastGarbageCollection_;
|
||||||
|
|
||||||
Parser parser_;
|
Parser parser_;
|
||||||
|
std::string lastPath_;
|
||||||
absl::optional<fs::file_time_type> modificationTime_ { };
|
absl::optional<fs::file_time_type> modificationTime_ { };
|
||||||
|
bool reloading { false };
|
||||||
|
|
||||||
std::array<float, config::numCCs> defaultCCValues_ { };
|
std::array<float, config::numCCs> defaultCCValues_ { };
|
||||||
BitArray<config::numCCs> currentUsedCCs_;
|
BitArray<config::numCCs> currentUsedCCs_;
|
||||||
|
|
|
||||||
|
|
@ -699,3 +699,19 @@ TEST_CASE("[Files] Key center from audio file")
|
||||||
REQUIRE(synth.getRegionView(4)->pitchKeycenter == 10);
|
REQUIRE(synth.getRegionView(4)->pitchKeycenter == 10);
|
||||||
REQUIRE(synth.getRegionView(5)->pitchKeycenter == 62);
|
REQUIRE(synth.getRegionView(5)->pitchKeycenter == 62);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Unused samples are cleared on reloading")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/unused_samples.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
<region> sample=kick.wav
|
||||||
|
)");
|
||||||
|
REQUIRE(synth.getNumPreloadedSamples() == 1);
|
||||||
|
|
||||||
|
// Same file path to reload
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/unused_samples.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
REQUIRE(synth.getNumPreloadedSamples() == 0);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,16 +45,67 @@ TEST_CASE("[MidiState] Set and get pitch bends")
|
||||||
REQUIRE(state.getPitchBend() == 0.0f);
|
REQUIRE(state.getPitchBend() == 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MidiState] Reset")
|
TEST_CASE("[MidiState] Resetting things")
|
||||||
{
|
{
|
||||||
sfz::MidiState state;
|
sfz::MidiState state;
|
||||||
state.pitchBendEvent(0, 0.7f);
|
state.pitchBendEvent(0, 0.7f);
|
||||||
state.noteOnEvent(0, 64, 24_norm);
|
state.noteOnEvent(0, 64, 24_norm);
|
||||||
state.ccEvent(0, 123, 124_norm);
|
state.ccEvent(0, 123, 124_norm);
|
||||||
state.reset();
|
state.channelAftertouchEvent(0, 56_norm);
|
||||||
REQUIRE(state.getPitchBend() == 0.0f);
|
state.polyAftertouchEvent(0, 64, 43_norm);
|
||||||
|
state.advanceTime(1024);
|
||||||
|
|
||||||
|
// Only reset note stuff
|
||||||
|
state.resetNoteStates();
|
||||||
REQUIRE(state.getNoteVelocity(64) == 0_norm);
|
REQUIRE(state.getNoteVelocity(64) == 0_norm);
|
||||||
|
REQUIRE(state.getNoteDuration(64) == 0_norm);
|
||||||
|
REQUIRE(state.getActiveNotes() == 0);
|
||||||
|
|
||||||
|
// Extended CCs too
|
||||||
|
REQUIRE(state.getCCValue(131) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(132) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(133) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(134) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(135) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(136) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(137) == 0.0f);
|
||||||
|
|
||||||
|
// State isn't reset
|
||||||
|
REQUIRE(state.getPitchBend() != 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(123) != 0_norm);
|
||||||
|
REQUIRE(state.getChannelAftertouch() != 0_norm);
|
||||||
|
REQUIRE(state.getPolyAftertouch(64) != 0_norm);
|
||||||
|
|
||||||
|
state.resetEventStates(); // But now it is
|
||||||
|
REQUIRE(state.getPitchBend() == 0.0f);
|
||||||
REQUIRE(state.getCCValue(123) == 0_norm);
|
REQUIRE(state.getCCValue(123) == 0_norm);
|
||||||
|
REQUIRE(state.getChannelAftertouch() == 0_norm);
|
||||||
|
REQUIRE(state.getPolyAftertouch(64) == 0_norm);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[MidiState] Flushing state")
|
||||||
|
{
|
||||||
|
sfz::MidiState state;
|
||||||
|
state.pitchBendEvent(40, 0.7f);
|
||||||
|
state.ccEvent(100, 123, 124_norm);
|
||||||
|
state.channelAftertouchEvent(20, 56_norm);
|
||||||
|
state.polyAftertouchEvent(80, 64, 43_norm);
|
||||||
|
|
||||||
|
REQUIRE(state.getCCEvents(123).size() > 1);
|
||||||
|
REQUIRE(state.getChannelAftertouchEvents().size() > 1);
|
||||||
|
REQUIRE(state.getPolyAftertouchEvents(64).size() > 1);
|
||||||
|
REQUIRE(state.getPitchEvents().size() > 1);
|
||||||
|
|
||||||
|
state.flushEvents();
|
||||||
|
REQUIRE(state.getCCEvents(123).size() == 1);
|
||||||
|
REQUIRE(state.getChannelAftertouchEvents().size() == 1);
|
||||||
|
REQUIRE(state.getPolyAftertouchEvents(64).size() == 1);
|
||||||
|
REQUIRE(state.getPitchEvents().size() == 1);
|
||||||
|
|
||||||
|
REQUIRE(state.getCCValue(123) == 124_norm);
|
||||||
|
REQUIRE(state.getChannelAftertouch() == 56_norm);
|
||||||
|
REQUIRE(state.getPolyAftertouch(64) == 43_norm);
|
||||||
|
REQUIRE(state.getPitchBend() == 0.7f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MidiState] Set and get note velocities")
|
TEST_CASE("[MidiState] Set and get note velocities")
|
||||||
|
|
|
||||||
125
tests/SynthT.cpp
125
tests/SynthT.cpp
|
|
@ -1758,7 +1758,7 @@ TEST_CASE("[Synth] Initial values of CC")
|
||||||
REQUIRE(synth.getHdcc(10) == 0.7f);
|
REQUIRE(synth.getHdcc(10) == 0.7f);
|
||||||
REQUIRE(synth.getDefaultHdcc(10) == 0.5f);
|
REQUIRE(synth.getDefaultHdcc(10) == 0.5f);
|
||||||
|
|
||||||
synth.loadSfzString(fs::current_path() / "init_cc.sfz", R"(
|
synth.loadSfzString(fs::current_path() / "init_cc_new_file.sfz", R"(
|
||||||
<control> set_hdcc111=0.1234 set_cc112=77
|
<control> set_hdcc111=0.1234 set_cc112=77
|
||||||
<region> sample=*sine
|
<region> sample=*sine
|
||||||
)");
|
)");
|
||||||
|
|
@ -2017,3 +2017,126 @@ TEST_CASE("[Synth] Loading resets note and octave offsets")
|
||||||
};
|
};
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Default CC values")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
std::vector<std::string> messageList;
|
||||||
|
sfz::Client client(&messageList);
|
||||||
|
client.setReceiveCallback(&simpleMessageReceiver);
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/default.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc7/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc10/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc11/value", "", nullptr);
|
||||||
|
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/cc7/value,f : { 0.787402 }",
|
||||||
|
"/cc10/value,f : { 0.5 }",
|
||||||
|
"/cc11/value,f : { 1 }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Loading a new file doesn't reset the midi state")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
std::vector<std::string> messageList;
|
||||||
|
sfz::Client client(&messageList);
|
||||||
|
client.setReceiveCallback(&simpleMessageReceiver);
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sine.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
|
||||||
|
synth.hdcc(10, 63, 0.4f);
|
||||||
|
synth.hdcc(10, 7, 0.41f);
|
||||||
|
synth.hdcc(10, 10, 0.42f);
|
||||||
|
synth.hdcc(10, 11, 0.43f);
|
||||||
|
synth.hdChannelAftertouch(20, 0.1f);
|
||||||
|
synth.hdPolyAftertouch(30, 64, 0.2f);
|
||||||
|
synth.hdPitchWheel(40, 0.3f);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc63/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc7/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc10/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc11/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/aftertouch", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/poly_aftertouch/64", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/pitch_bend", "", nullptr);
|
||||||
|
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/saw.sfz", R"(
|
||||||
|
<region> sample=*saw
|
||||||
|
)");
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc63/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc7/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc10/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc11/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/aftertouch", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/poly_aftertouch/64", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/pitch_bend", "", nullptr);
|
||||||
|
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/cc63/value,f : { 0.4 }",
|
||||||
|
"/cc7/value,f : { 0.41 }",
|
||||||
|
"/cc10/value,f : { 0.42 }",
|
||||||
|
"/cc11/value,f : { 0.43 }",
|
||||||
|
"/aftertouch,f : { 0.1 }",
|
||||||
|
"/poly_aftertouch/64,f : { 0.2 }",
|
||||||
|
"/pitch_bend,f : { 0.3 }",
|
||||||
|
"/cc63/value,f : { 0.4 }",
|
||||||
|
"/cc7/value,f : { 0.41 }",
|
||||||
|
"/cc10/value,f : { 0.42 }",
|
||||||
|
"/cc11/value,f : { 0.43 }",
|
||||||
|
"/aftertouch,f : { 0.1 }",
|
||||||
|
"/poly_aftertouch/64,f : { 0.2 }",
|
||||||
|
"/pitch_bend,f : { 0.3 }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Reloading a file ignores the `set_ccN` opcodes")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
std::vector<std::string> messageList;
|
||||||
|
sfz::Client client(&messageList);
|
||||||
|
client.setReceiveCallback(&simpleMessageReceiver);
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sine.sfz", R"(
|
||||||
|
<control> set_cc1=0
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
|
||||||
|
synth.hdcc(10, 1, 0.4f);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc1/value", "", nullptr);
|
||||||
|
|
||||||
|
// Same file
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sine.sfz", R"(
|
||||||
|
<control> set_cc1=0
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc1/value", "", nullptr);
|
||||||
|
|
||||||
|
// Different file
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/saw.sfz", R"(
|
||||||
|
<control> set_cc1=0
|
||||||
|
<region> sample=*saw
|
||||||
|
)");
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc1/value", "", nullptr);
|
||||||
|
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/cc1/value,f : { 0.4 }",
|
||||||
|
"/cc1/value,f : { 0.4 }",
|
||||||
|
"/cc1/value,f : { 0 }",
|
||||||
|
};
|
||||||
|
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue