Tap Tempo, ToobAmp v1.1.72

This commit is contained in:
Robin E. R. Davies
2026-01-10 10:41:20 -05:00
parent 6e1657933b
commit fdeb1fef5e
49 changed files with 397 additions and 94 deletions
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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;
+68
View File
@@ -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
+1 -1
View File
@@ -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"}}, //