Tap Tempo, ToobAmp v1.1.72
This commit is contained in:
+2
-1
@@ -1686,7 +1686,8 @@ namespace pipedal
|
||||
continue;
|
||||
}
|
||||
MidiEvent *pEvent = midiEvents.data() + midiEventCount++;
|
||||
pEvent->time = audioFrame;
|
||||
pEvent->timeStamp = MidiTimestamp(message.realtime_sec, message.realtime_nsec);
|
||||
pEvent->frame = audioFrame;
|
||||
pEvent->size = messageSize;
|
||||
pEvent->buffer = midiEventMemory.data() + midiEventMemoryIndex;
|
||||
|
||||
|
||||
+1
-9
@@ -26,6 +26,7 @@
|
||||
#include "JackConfiguration.hpp"
|
||||
#include <functional>
|
||||
#include "AlsaSequencer.hpp"
|
||||
#include "MidiEvent.hpp"
|
||||
|
||||
|
||||
|
||||
@@ -35,15 +36,6 @@ namespace pipedal {
|
||||
using ProcessCallback = std::function<void (size_t)>;
|
||||
|
||||
|
||||
|
||||
struct MidiEvent
|
||||
{
|
||||
uint32_t time; /**< Sample frame at which event is valid */
|
||||
uint32_t size; /**< Number of bytes of data in \a buffer */
|
||||
uint8_t *buffer; /**< Raw MIDI data */
|
||||
};
|
||||
|
||||
|
||||
class AudioDriverHost {
|
||||
public:
|
||||
virtual void OnProcess(size_t nFrames) = 0;
|
||||
|
||||
+4
-2
@@ -365,6 +365,7 @@ bool SystemMidiBinding::IsMatch(const MidiEvent &event)
|
||||
switch (currentBinding.bindingType())
|
||||
{
|
||||
case BINDING_TYPE_NOTE:
|
||||
case BINDING_TYPE_TAP_TEMPO:
|
||||
{
|
||||
if (event.size != 3)
|
||||
return false;
|
||||
@@ -926,7 +927,8 @@ private:
|
||||
{
|
||||
// eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
|
||||
|
||||
this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
|
||||
this->realtimeActivePedalboard->OnMidiMessage(
|
||||
event, this, fnMidiValueChanged);
|
||||
if (listenForMidiEvent)
|
||||
{
|
||||
if (event.size >= 3)
|
||||
@@ -1077,7 +1079,7 @@ private:
|
||||
int8_t messageCount = deferredMidiMessages[i++];
|
||||
event.size = messageCount;
|
||||
event.buffer = deferredMidiMessages + i;
|
||||
event.time = 0;
|
||||
event.frame = 0;
|
||||
|
||||
ProcessMidiEvent(eventBufferWriter, iterator, event);
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ namespace pipedal
|
||||
continue;
|
||||
}
|
||||
MidiEvent *pEvent = midiEvents.data() + midiEventCount++;
|
||||
pEvent->time = audioFrame;
|
||||
pEvent->frame = audioFrame;
|
||||
pEvent->size = messageSize;
|
||||
pEvent->buffer = midiEventMemory.data() + midiEventMemoryIndex;
|
||||
|
||||
|
||||
+104
-10
@@ -188,13 +188,15 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
{
|
||||
pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardInputBuffers[i]);
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
// just use the first output buffer for all sidechain inputs.
|
||||
pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardInputBuffers[0]);
|
||||
}
|
||||
}
|
||||
} else if (item.sideChainInputId() != -1) {
|
||||
}
|
||||
else if (item.sideChainInputId() != -1)
|
||||
{
|
||||
IEffect *pSideChainInput = GetEffect(item.sideChainInputId());
|
||||
|
||||
if (pSideChainInput)
|
||||
@@ -205,13 +207,15 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
{
|
||||
pLv2Effect->SetAudioSidechainBuffer(i, pSideChainInput->GetAudioOutputBuffer(i));
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
// just use the first output buffer for all sidechain inputs.
|
||||
pLv2Effect->SetAudioSidechainBuffer(i, pSideChainInput->GetAudioOutputBuffer(0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Internal error: Sidechain input IEffect not found.");
|
||||
}
|
||||
}
|
||||
@@ -418,11 +422,14 @@ void Lv2Pedalboard::PrepareMidiMap(const PedalboardItem &pedalboardItem)
|
||||
{
|
||||
mapping.mappingType = MidiControlType::Select;
|
||||
}
|
||||
else if (binding.bindingType() == BINDING_TYPE_TAP_TEMPO) {
|
||||
mapping.mappingType = MidiControlType::TapTempo;
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping.mappingType = MidiControlType::Dial;
|
||||
}
|
||||
if (binding.bindingType() == BINDING_TYPE_NOTE)
|
||||
if (binding.bindingType() == BINDING_TYPE_NOTE || binding.bindingType() == BINDING_TYPE_TAP_TEMPO)
|
||||
{
|
||||
mapping.key = 0x9000 | binding.note(); // i.e. midi note on.
|
||||
}
|
||||
@@ -539,6 +546,8 @@ bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t sa
|
||||
outputBuffers[c][i] = this->pedalboardOutputBuffers[c][i] * volume;
|
||||
}
|
||||
}
|
||||
this->currentFrameOffset += samples;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -715,14 +724,17 @@ void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParamet
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
|
||||
void *callbackHandle,
|
||||
MidiCallbackFn *pfnCallback)
|
||||
void Lv2Pedalboard::OnMidiMessage(
|
||||
const MidiEvent&event,
|
||||
void *callbackHandle,
|
||||
MidiCallbackFn *pfnCallback)
|
||||
|
||||
{
|
||||
if (midiMappings.size() == 0)
|
||||
return;
|
||||
|
||||
size_t size = event.size;
|
||||
const uint8_t *message = event.buffer;
|
||||
if (size < 2)
|
||||
return;
|
||||
uint8_t cmd = message[0];
|
||||
@@ -791,7 +803,7 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
|
||||
|
||||
for (int i = min; i < midiMappings.size(); ++i)
|
||||
{
|
||||
auto &mapping = midiMappings[i];
|
||||
MidiMapping &mapping = midiMappings[i];
|
||||
if (mapping.key != searchKey)
|
||||
break;
|
||||
|
||||
@@ -914,6 +926,11 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MidiControlType::TapTempo:
|
||||
{
|
||||
handleTapTempo(value, event.timeStamp, mapping, callbackHandle, pfnCallback);
|
||||
break;
|
||||
}
|
||||
case MidiControlType::None:
|
||||
default:
|
||||
break;
|
||||
@@ -921,4 +938,81 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Pedalboard::handleTapTempo(uint8_t value, const MidiTimestamp& timestamp, MidiMapping &mapping, void *callbackHandle, MidiCallbackFn *pfnSetControlCallback)
|
||||
{
|
||||
if (value != 0) // only on note on
|
||||
{
|
||||
if (!mapping.lastTapTimestamp.isEmpty())
|
||||
{
|
||||
double seconds = timestamp.timeDiff(mapping.lastTapTimestamp);
|
||||
if (seconds > 60.0/450.0) // debounce check. (~= 450bpm)
|
||||
{
|
||||
auto units = mapping.pPortInfo->units();
|
||||
float controlValue = -1;
|
||||
switch (units)
|
||||
{
|
||||
case Units::bpm:
|
||||
{
|
||||
controlValue = 60.0f / (float)seconds;
|
||||
break;
|
||||
}
|
||||
case Units::hz:
|
||||
{
|
||||
controlValue = 1.0f / (float)seconds;
|
||||
break;
|
||||
}
|
||||
case Units::s:
|
||||
{
|
||||
controlValue = (float)(seconds);
|
||||
break;
|
||||
}
|
||||
case Units::ms:
|
||||
{
|
||||
controlValue = (float)(seconds * 1000.0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
controlValue = -1;
|
||||
}
|
||||
}
|
||||
if (mapping.pPortInfo->min_value() < mapping.pPortInfo->max_value())
|
||||
{
|
||||
if (controlValue < mapping.pPortInfo->min_value())
|
||||
{
|
||||
controlValue = -1;
|
||||
}
|
||||
else if (controlValue > mapping.pPortInfo->max_value())
|
||||
{
|
||||
controlValue = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (controlValue > mapping.pPortInfo->min_value())
|
||||
{
|
||||
controlValue = -1;
|
||||
}
|
||||
else if (controlValue < mapping.pPortInfo->max_value())
|
||||
{
|
||||
controlValue = -1;
|
||||
}
|
||||
}
|
||||
if (controlValue != -1)
|
||||
{
|
||||
|
||||
IEffect *pEffect = this->realtimeEffects[mapping.effectIndex];
|
||||
if (pEffect->IsLv2Effect())
|
||||
{
|
||||
Lv2Effect *pLv2Effect = dynamic_cast<Lv2Effect *>(pEffect);
|
||||
pEffect->SetControl(mapping.controlIndex, controlValue);
|
||||
pfnSetControlCallback(callbackHandle, mapping.instanceId, mapping.pPortInfo->index(), controlValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mapping.lastTapTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-8
@@ -19,6 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "Pedalboard.hpp"
|
||||
#include "MidiEvent.hpp"
|
||||
#include "PluginHost.hpp"
|
||||
#include "Lv2Effect.hpp"
|
||||
#include "BufferPool.hpp"
|
||||
@@ -49,8 +50,9 @@ namespace pipedal
|
||||
|
||||
class Lv2Pedalboard
|
||||
{
|
||||
private:
|
||||
IHost *pHost = nullptr;
|
||||
|
||||
size_t currentFrameOffset = 0;
|
||||
DbDezipper inputVolume;
|
||||
DbDezipper outputVolume;
|
||||
|
||||
@@ -82,7 +84,8 @@ namespace pipedal
|
||||
Dial,
|
||||
Toggle,
|
||||
Trigger,
|
||||
MomentarySwitch
|
||||
MomentarySwitch,
|
||||
TapTempo
|
||||
};
|
||||
class MidiMapping
|
||||
{
|
||||
@@ -93,6 +96,7 @@ namespace pipedal
|
||||
int effectIndex = -1;
|
||||
int controlIndex = -1;
|
||||
int key; // key to the note or control. internal use only.
|
||||
MidiTimestamp lastTapTimestamp;
|
||||
bool hasLastValue = false;
|
||||
bool lastValueIncreasing = false;
|
||||
float lastValue = 0;
|
||||
@@ -119,13 +123,11 @@ namespace pipedal
|
||||
Lv2Pedalboard() {}
|
||||
~Lv2Pedalboard() {}
|
||||
|
||||
|
||||
void Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList, ExistingEffectMap *existingEffects = nullptr);
|
||||
|
||||
std::vector<IEffect *> &GetEffects() { return realtimeEffects; }
|
||||
std::vector<std::shared_ptr<IEffect>> &GetSharedEffectList() { return effects; }
|
||||
|
||||
|
||||
int GetIndexOfInstanceId(uint64_t instanceId)
|
||||
{
|
||||
for (int i = 0; i < this->realtimeEffects.size(); ++i)
|
||||
@@ -149,7 +151,7 @@ namespace pipedal
|
||||
void Activate();
|
||||
void Deactivate();
|
||||
void UpdateAudioPorts();
|
||||
|
||||
|
||||
bool Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter *realtimeWriter);
|
||||
|
||||
void ResetAtomBuffers();
|
||||
@@ -172,9 +174,19 @@ namespace pipedal
|
||||
float GetControlOutputValue(int effectIndex, int portIndex);
|
||||
|
||||
typedef void(MidiCallbackFn)(void *data, uint64_t intanceId, int controlIndex, float value);
|
||||
void OnMidiMessage(size_t size, uint8_t *data,
|
||||
void *callbackHandle,
|
||||
MidiCallbackFn *pfnCallback);
|
||||
void OnMidiMessage(
|
||||
const MidiEvent&message,
|
||||
void *callbackHandle,
|
||||
MidiCallbackFn *pfnCallback);
|
||||
private:
|
||||
void handleTapTempo(
|
||||
uint8_t value,
|
||||
const MidiTimestamp& timestamp,
|
||||
MidiMapping &mapping,
|
||||
void *callbackHandle,
|
||||
MidiCallbackFn *pfnCallback);
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -53,6 +53,7 @@ class MapFeature;
|
||||
const int BINDING_TYPE_NONE = 0;
|
||||
const int BINDING_TYPE_NOTE = 1;
|
||||
const int BINDING_TYPE_CONTROL = 2;
|
||||
const int BINDING_TYPE_TAP_TEMPO = 3;
|
||||
|
||||
const int LINEAR_CONTROL_TYPE = 0;
|
||||
const int CIRCULAR_CONTROL_TYPE = 1;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2026 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#pragma once
|
||||
#ifndef PIPEDAL_MidiEvent_HPP
|
||||
#define PIPEDAL_MidiEvent_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace pipedal {
|
||||
struct MidiTimestamp
|
||||
{
|
||||
uint64_t seconds;
|
||||
uint32_t nanoseconds;
|
||||
|
||||
MidiTimestamp()
|
||||
: seconds(0), nanoseconds(0)
|
||||
{
|
||||
}
|
||||
MidiTimestamp(uint64_t sec, uint32_t nsec)
|
||||
: seconds(sec), nanoseconds(nsec)
|
||||
{
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return seconds == 0 && nanoseconds == 0;
|
||||
}
|
||||
double timeDiff(const MidiTimestamp &other) const
|
||||
{
|
||||
double secDiff = (double)(int64_t)(this->seconds - other.seconds);
|
||||
double nsecDiff = (double)((int64_t)this->nanoseconds - (int64_t)other.nanoseconds) * 1e-9;
|
||||
return secDiff + nsecDiff;
|
||||
}
|
||||
};
|
||||
|
||||
struct MidiEvent
|
||||
{
|
||||
MidiTimestamp timeStamp; /**< Real-time timestamp of the event */
|
||||
uint32_t frame; /**< Sample frame at which event is valid */
|
||||
uint32_t size; /**< Number of bytes of data in \a buffer */
|
||||
uint8_t *buffer; /**< Raw MIDI data */
|
||||
};
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -50,7 +50,7 @@ static std::vector<ModFileTypes::ModDirectory> *CreateModDirectories()
|
||||
// extensions observed in the field.
|
||||
{"audio", "shared/audio", "Audio", {"audio/*"}}, // all audio files (Ratatouille)
|
||||
{"nammodel", "NeuralAmpModels", "Neural Amp Models", {".nam" ,".aidax"}}, // Ratatouille, Mike's NAM.
|
||||
{"aidadspmodel", "shared/aidaaix", "AIDA IAX Models", {".json", ".aidaiax"}}, // Ratatouille
|
||||
{"aidadspmodel", "shared/aidaaix", "AIDA IAX Models", {".json", ".aidax",".aidaiax"}}, // Ratatouille
|
||||
{"mlmodel", "ToobMlModels", "ML Models", {".json"}}, //
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user