mirror of
https://github.com/Ardour/ardour.git
synced 2026-01-07 14:15:46 +01:00
Replaces the list of points in TempoMap with TempoSection functions, which compute tempo-at or tick-at time relative to tempo section start. TempoMap consults them additively to determine things like bbt_time(), frame_time() get_grid() etc. This has a marked effect on scrolling speed along with the code simplification in the places it has been attempted. Several things are broken here. Currently every ramp except the last one is an exponential ramp. this may be simple to fix :). Mouse-over midi grid doesn't match mouse click grid. should also be simple. Many things seem to work, but their accuracy should be in question until each area has been addressed.
571 lines
16 KiB
C++
571 lines
16 KiB
C++
/*
|
|
Copyright (C) 2004
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
#ifndef COMPILER_MSVC
|
|
#include <stdbool.h>
|
|
#endif
|
|
#include <cstdio>
|
|
|
|
#include "ardour/audioengine.h"
|
|
#include "ardour/debug.h"
|
|
#include "ardour/session.h"
|
|
#include "ardour/tempo.h"
|
|
#include "ardour/plugin_insert.h"
|
|
#include "ardour/windows_vst_plugin.h"
|
|
#include "ardour/vestige/aeffectx.h"
|
|
#include "ardour/vst_types.h"
|
|
#ifdef WINDOWS_VST_SUPPORT
|
|
#include <fst.h>
|
|
#endif
|
|
|
|
#include "i18n.h"
|
|
|
|
using namespace ARDOUR;
|
|
|
|
#define SHOW_CALLBACK(MSG) DEBUG_TRACE (PBD::DEBUG::VSTCallbacks, string_compose (MSG " val = %1 idx = %2\n", index, value))
|
|
|
|
static double
|
|
vst_ppq (const TempoMetric& tm, const Timecode::BBT_Time& bbt, double& ppqBar)
|
|
{
|
|
|
|
/* PPQ = pulse per quarter
|
|
* VST's "pulse" is our "division".
|
|
*
|
|
* 8 divisions per bar, 1 division = quarter, so 8 quarters per bar, ppq = 1
|
|
* 8 divisions per bar, 1 division = eighth, so 4 quarters per bar, ppq = 2
|
|
* 4 divisions per bar, 1 division = quarter, so 4 quarters per bar, ppq = 1
|
|
* 4 divisions per bar, 1 division = half, so 8 quarters per bar, ppq = 0.5
|
|
* 4 divisions per bar, 1 division = fifth, so (4 * 5/4) quarters per bar, ppq = 5/4
|
|
*
|
|
* general: divs_per_bar / (note_type / 4.0)
|
|
*/
|
|
const double ppq_scaling = tm.meter().note_divisor() / 4.0;
|
|
|
|
/* Note that this assumes constant meter/tempo throughout the session. Stupid VST */
|
|
ppqBar = double(bbt.bars - 1) * tm.meter().divisions_per_bar();
|
|
double ppqBeat = double(bbt.beats - 1);
|
|
double ppqTick = double(bbt.ticks) / Timecode::BBT_Time::ticks_per_beat;
|
|
|
|
ppqBar *= ppq_scaling;
|
|
ppqBeat *= ppq_scaling;
|
|
ppqTick *= ppq_scaling;
|
|
|
|
return ppqBar + ppqBeat + ppqTick;
|
|
}
|
|
|
|
int Session::vst_current_loading_id = 0;
|
|
const char* Session::vst_can_do_strings[] = {
|
|
X_("supplyIdle"),
|
|
X_("sendVstTimeInfo"),
|
|
X_("sendVstEvents"),
|
|
X_("sendVstMidiEvent"),
|
|
X_("receiveVstEvents"),
|
|
X_("receiveVstMidiEvent"),
|
|
X_("supportShell"),
|
|
X_("shellCategory"),
|
|
X_("shellCategorycurID")
|
|
};
|
|
const int Session::vst_can_do_string_count = sizeof (vst_can_do_strings) / sizeof (char*);
|
|
|
|
intptr_t Session::vst_callback (
|
|
AEffect* effect,
|
|
int32_t opcode,
|
|
int32_t index,
|
|
intptr_t value,
|
|
void* ptr,
|
|
float opt
|
|
)
|
|
{
|
|
VSTPlugin* plug;
|
|
Session* session;
|
|
static VstTimeInfo _timeinfo; // only uses as fallback
|
|
VstTimeInfo* timeinfo;
|
|
int32_t newflags = 0;
|
|
|
|
if (effect && effect->user) {
|
|
plug = (VSTPlugin *) (effect->user);
|
|
session = &plug->session();
|
|
timeinfo = plug->timeinfo ();
|
|
DEBUG_TRACE (PBD::DEBUG::VSTCallbacks, string_compose ("am callback 0x%1%2, opcode = %3%4, plugin = \"%5\"\n",
|
|
std::hex, (void*) DEBUG_THREAD_SELF,
|
|
std::dec, opcode, plug->name()));
|
|
} else {
|
|
plug = 0;
|
|
session = 0;
|
|
timeinfo = &_timeinfo;
|
|
DEBUG_TRACE (PBD::DEBUG::VSTCallbacks, string_compose ("am callback 0x%1%2, opcode = %3%4\n",
|
|
std::hex, (void*) DEBUG_THREAD_SELF,
|
|
std::dec, opcode));
|
|
}
|
|
|
|
switch(opcode){
|
|
|
|
case audioMasterAutomate:
|
|
SHOW_CALLBACK ("audioMasterAutomate");
|
|
// index, value, returns 0
|
|
if (plug) {
|
|
plug->parameter_changed_externally (index, opt);
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterVersion:
|
|
SHOW_CALLBACK ("audioMasterVersion");
|
|
// vst version, currently 2 (0 for older)
|
|
return 2400;
|
|
|
|
case audioMasterCurrentId:
|
|
SHOW_CALLBACK ("audioMasterCurrentId");
|
|
// returns the unique id of a plug that's currently loading
|
|
return vst_current_loading_id;
|
|
|
|
case audioMasterIdle:
|
|
SHOW_CALLBACK ("audioMasterIdle");
|
|
#ifdef WINDOWS_VST_SUPPORT
|
|
fst_audio_master_idle();
|
|
#endif
|
|
if (effect) {
|
|
effect->dispatcher(effect, effEditIdle, 0, 0, NULL, 0.0f);
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterPinConnected:
|
|
SHOW_CALLBACK ("audioMasterPinConnected");
|
|
// inquire if an input or output is beeing connected;
|
|
// index enumerates input or output counting from zero:
|
|
// value is 0 for input and != 0 otherwise. note: the
|
|
// return value is 0 for <true> such that older versions
|
|
// will always return true.
|
|
if (!plug) {
|
|
// we don't know.
|
|
// but ardour always connects all buffers, so we're good
|
|
return 0;
|
|
}
|
|
switch (value) {
|
|
case 0:
|
|
if (plug->plugin_insert ()) {
|
|
bool valid;
|
|
const ChanMapping& map (plug->plugin_insert ()->input_map (plug->plugin_number ()));
|
|
map.get (DataType::AUDIO, index, &valid);
|
|
return valid ? 0 : 1;
|
|
}
|
|
if (index < plug->plugin()->numInputs) {
|
|
return 0;
|
|
}
|
|
break;
|
|
case 1:
|
|
#if 0 // investigate, the outputs *are* connected to scratch buffers
|
|
if (plug->plugin_insert ()) {
|
|
bool valid;
|
|
const ChanMapping& map (plug->plugin_insert ()->output_map (plug->plugin_number ()));
|
|
map.get (DataType::AUDIO, index, &valid);
|
|
return valid ? 0 : 1;
|
|
}
|
|
#endif
|
|
if (index < plug->plugin()->numOutputs) {
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 1;
|
|
|
|
case audioMasterWantMidi:
|
|
SHOW_CALLBACK ("audioMasterWantMidi");
|
|
// <value> is a filter which is currently ignored
|
|
if (plug && plug->get_info() != NULL) {
|
|
plug->get_info()->n_inputs.set_midi (1);
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterGetTime:
|
|
SHOW_CALLBACK ("audioMasterGetTime");
|
|
newflags = kVstNanosValid | kVstAutomationWriting | kVstAutomationReading;
|
|
|
|
timeinfo->nanoSeconds = g_get_monotonic_time () * 1000;
|
|
|
|
if (session) {
|
|
framepos_t now = session->transport_frame();
|
|
|
|
timeinfo->samplePos = now;
|
|
timeinfo->sampleRate = session->frame_rate();
|
|
|
|
const TempoMetric& tm (session->tempo_map().metric_at (now));
|
|
|
|
if (value & (kVstTempoValid)) {
|
|
const Tempo& t (tm.tempo());
|
|
timeinfo->tempo = t.beats_per_minute ();
|
|
newflags |= (kVstTempoValid);
|
|
}
|
|
if (value & (kVstTimeSigValid)) {
|
|
const Meter& m (tm.meter());
|
|
timeinfo->timeSigNumerator = m.divisions_per_bar ();
|
|
timeinfo->timeSigDenominator = m.note_divisor ();
|
|
newflags |= (kVstTimeSigValid);
|
|
}
|
|
if ((value & (kVstPpqPosValid)) || (value & (kVstBarsValid))) {
|
|
Timecode::BBT_Time bbt;
|
|
|
|
try {
|
|
session->tempo_map().bbt_time (now, bbt);
|
|
|
|
/* PPQ = pulse per quarter
|
|
* VST's "pulse" is our "division".
|
|
*
|
|
* 8 divisions per bar, 1 division = quarter, so 8 quarters per bar, ppq = 1
|
|
* 8 divisions per bar, 1 division = eighth, so 4 quarters per bar, ppq = 2
|
|
* 4 divisions per bar, 1 division = quarter, so 4 quarters per bar, ppq = 1
|
|
* 4 divisions per bar, 1 division = half, so 8 quarters per bar, ppq = 0.5
|
|
* 4 divisions per bar, 1 division = fifth, so (4 * 5/4) quarters per bar, ppq = 5/4
|
|
*
|
|
* general: divs_per_bar / (note_type / 4.0)
|
|
*/
|
|
double ppq_scaling = tm.meter().note_divisor() / 4.0;
|
|
|
|
/* Note that this assumes constant meter/tempo throughout the session. Stupid VST */
|
|
double ppqBar = double(bbt.bars - 1) * tm.meter().divisions_per_bar();
|
|
double ppqBeat = double(bbt.beats - 1);
|
|
double ppqTick = double(bbt.ticks) / Timecode::BBT_Time::ticks_per_beat;
|
|
|
|
ppqBar *= ppq_scaling;
|
|
ppqBeat *= ppq_scaling;
|
|
ppqTick *= ppq_scaling;
|
|
|
|
if (value & (kVstPpqPosValid)) {
|
|
timeinfo->ppqPos = ppqPos;
|
|
newflags |= kVstPpqPosValid;
|
|
}
|
|
|
|
if (value & (kVstBarsValid)) {
|
|
timeinfo->barStartPos = ppqBar;
|
|
newflags |= kVstBarsValid;
|
|
}
|
|
|
|
} catch (...) {
|
|
/* relax */
|
|
}
|
|
}
|
|
|
|
if (value & (kVstSmpteValid)) {
|
|
Timecode::Time t;
|
|
|
|
session->timecode_time (now, t);
|
|
|
|
timeinfo->smpteOffset = (t.hours * t.rate * 60.0 * 60.0) +
|
|
(t.minutes * t.rate * 60.0) +
|
|
(t.seconds * t.rate) +
|
|
(t.frames) +
|
|
(t.subframes);
|
|
|
|
timeinfo->smpteOffset *= 80.0; /* VST spec is 1/80th frames */
|
|
|
|
if (session->timecode_drop_frames()) {
|
|
if (session->timecode_frames_per_second() == 30.0) {
|
|
timeinfo->smpteFrameRate = 5;
|
|
} else {
|
|
timeinfo->smpteFrameRate = 4; /* 29.97 assumed, thanks VST */
|
|
}
|
|
} else {
|
|
if (session->timecode_frames_per_second() == 24.0) {
|
|
timeinfo->smpteFrameRate = 0;
|
|
} else if (session->timecode_frames_per_second() == 24.975) {
|
|
timeinfo->smpteFrameRate = 2;
|
|
} else if (session->timecode_frames_per_second() == 25.0) {
|
|
timeinfo->smpteFrameRate = 1;
|
|
} else {
|
|
timeinfo->smpteFrameRate = 3; /* 30 fps */
|
|
}
|
|
}
|
|
newflags |= (kVstSmpteValid);
|
|
}
|
|
|
|
if (session->actively_recording ()) {
|
|
newflags |= kVstTransportRecording;
|
|
}
|
|
|
|
if (session->transport_speed () != 0.0f) {
|
|
newflags |= kVstTransportPlaying;
|
|
}
|
|
|
|
if (session->get_play_loop ()) {
|
|
newflags |= kVstTransportCycleActive;
|
|
Location * looploc = session->locations ()->auto_loop_location ();
|
|
if (looploc) try {
|
|
double ppqBar;
|
|
Timecode::BBT_Time bbt;
|
|
|
|
session->tempo_map().bbt_time_rt (looploc->start (), bbt);
|
|
timeinfo->cycleStartPos = vst_ppq (tm, bbt, ppqBar);
|
|
|
|
session->tempo_map().bbt_time_rt (looploc->end (), bbt);
|
|
timeinfo->cycleEndPos = vst_ppq (tm, bbt, ppqBar);
|
|
|
|
newflags |= kVstCyclePosValid;
|
|
} catch (...) { }
|
|
}
|
|
|
|
} else {
|
|
timeinfo->samplePos = 0;
|
|
timeinfo->sampleRate = AudioEngine::instance()->sample_rate();
|
|
}
|
|
|
|
if ((timeinfo->flags & (kVstTransportPlaying | kVstTransportRecording | kVstTransportCycleActive))
|
|
!=
|
|
(newflags & (kVstTransportPlaying | kVstTransportRecording | kVstTransportCycleActive)))
|
|
{
|
|
newflags |= kVstTransportChanged;
|
|
}
|
|
|
|
timeinfo->flags = newflags;
|
|
return (intptr_t) timeinfo;
|
|
|
|
case audioMasterProcessEvents:
|
|
SHOW_CALLBACK ("audioMasterProcessEvents");
|
|
// VstEvents* in <ptr>
|
|
if (plug && plug->midi_buffer()) {
|
|
VstEvents* v = (VstEvents*)ptr;
|
|
for (int n = 0 ; n < v->numEvents; ++n) {
|
|
VstMidiEvent *vme = (VstMidiEvent*) (v->events[n]->dump);
|
|
if (vme->type == kVstMidiType) {
|
|
plug->midi_buffer()->push_back(vme->deltaFrames, 3, (uint8_t*)vme->midiData);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterSetTime:
|
|
SHOW_CALLBACK ("audioMasterSetTime");
|
|
// VstTimenfo* in <ptr>, filter in <value>, not supported
|
|
|
|
case audioMasterTempoAt:
|
|
SHOW_CALLBACK ("audioMasterTempoAt");
|
|
// returns tempo (in bpm * 10000) at sample frame location passed in <value>
|
|
if (session) {
|
|
const Tempo& t (session->tempo_map().tempo_at (value));
|
|
return t.beats_per_minute() * 1000;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case audioMasterGetNumAutomatableParameters:
|
|
SHOW_CALLBACK ("audioMasterGetNumAutomatableParameters");
|
|
return 0;
|
|
|
|
case audioMasterGetParameterQuantization:
|
|
SHOW_CALLBACK ("audioMasterGetParameterQuantization");
|
|
// returns the integer value for +1.0 representation,
|
|
// or 1 if full single float precision is maintained
|
|
// in automation. parameter index in <value> (-1: all, any)
|
|
return 0;
|
|
|
|
case audioMasterIOChanged:
|
|
SHOW_CALLBACK ("audioMasterIOChanged");
|
|
// numInputs and/or numOutputs has changed
|
|
return 0;
|
|
|
|
case audioMasterNeedIdle:
|
|
SHOW_CALLBACK ("audioMasterNeedIdle");
|
|
// plug needs idle calls (outside its editor window)
|
|
if (plug) {
|
|
plug->state()->wantIdle = 1;
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterSizeWindow:
|
|
SHOW_CALLBACK ("audioMasterSizeWindow");
|
|
if (plug && plug->state()) {
|
|
plug->state()->width = index;
|
|
plug->state()->height = value;
|
|
plug->state()->want_resize = 1;
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterGetSampleRate:
|
|
SHOW_CALLBACK ("audioMasterGetSampleRate");
|
|
if (session) {
|
|
return session->frame_rate();
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterGetBlockSize:
|
|
SHOW_CALLBACK ("audioMasterGetBlockSize");
|
|
if (session) {
|
|
return session->get_block_size();
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterGetInputLatency:
|
|
SHOW_CALLBACK ("audioMasterGetInputLatency");
|
|
return 0;
|
|
|
|
case audioMasterGetOutputLatency:
|
|
SHOW_CALLBACK ("audioMasterGetOutputLatency");
|
|
return 0;
|
|
|
|
case audioMasterGetPreviousPlug:
|
|
SHOW_CALLBACK ("audioMasterGetPreviousPlug");
|
|
// input pin in <value> (-1: first to come), returns cEffect*
|
|
return 0;
|
|
|
|
case audioMasterGetNextPlug:
|
|
SHOW_CALLBACK ("audioMasterGetNextPlug");
|
|
// output pin in <value> (-1: first to come), returns cEffect*
|
|
|
|
case audioMasterWillReplaceOrAccumulate:
|
|
SHOW_CALLBACK ("audioMasterWillReplaceOrAccumulate");
|
|
// returns: 0: not supported, 1: replace, 2: accumulate
|
|
return 0;
|
|
|
|
case audioMasterGetCurrentProcessLevel:
|
|
SHOW_CALLBACK ("audioMasterGetCurrentProcessLevel");
|
|
// returns: 0: not supported,
|
|
// 1: currently in user thread (gui)
|
|
// 2: currently in audio thread (where process is called)
|
|
// 3: currently in 'sequencer' thread (midi, timer etc)
|
|
// 4: currently offline processing and thus in user thread
|
|
// other: not defined, but probably pre-empting user thread.
|
|
return 0;
|
|
|
|
case audioMasterGetAutomationState:
|
|
SHOW_CALLBACK ("audioMasterGetAutomationState");
|
|
// returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write
|
|
// offline
|
|
return 0;
|
|
|
|
case audioMasterOfflineStart:
|
|
SHOW_CALLBACK ("audioMasterOfflineStart");
|
|
return 0;
|
|
|
|
case audioMasterOfflineRead:
|
|
SHOW_CALLBACK ("audioMasterOfflineRead");
|
|
// ptr points to offline structure, see below. return 0: error, 1 ok
|
|
return 0;
|
|
|
|
case audioMasterOfflineWrite:
|
|
SHOW_CALLBACK ("audioMasterOfflineWrite");
|
|
// same as read
|
|
return 0;
|
|
|
|
case audioMasterOfflineGetCurrentPass:
|
|
SHOW_CALLBACK ("audioMasterOfflineGetCurrentPass");
|
|
return 0;
|
|
|
|
case audioMasterOfflineGetCurrentMetaPass:
|
|
SHOW_CALLBACK ("audioMasterOfflineGetCurrentMetaPass");
|
|
return 0;
|
|
|
|
case audioMasterSetOutputSampleRate:
|
|
SHOW_CALLBACK ("audioMasterSetOutputSampleRate");
|
|
// for variable i/o, sample rate in <opt>
|
|
return 0;
|
|
|
|
case audioMasterGetSpeakerArrangement:
|
|
SHOW_CALLBACK ("audioMasterGetSpeakerArrangement");
|
|
// (long)input in <value>, output in <ptr>
|
|
return 0;
|
|
|
|
case audioMasterGetVendorString:
|
|
SHOW_CALLBACK ("audioMasterGetVendorString");
|
|
// fills <ptr> with a string identifying the vendor (max 64 char)
|
|
strcpy ((char*) ptr, "Linux Audio Systems");
|
|
return 0;
|
|
|
|
case audioMasterGetProductString:
|
|
SHOW_CALLBACK ("audioMasterGetProductString");
|
|
// fills <ptr> with a string with product name (max 64 char)
|
|
strcpy ((char*) ptr, PROGRAM_NAME);
|
|
return 0;
|
|
|
|
case audioMasterGetVendorVersion:
|
|
SHOW_CALLBACK ("audioMasterGetVendorVersion");
|
|
// returns vendor-specific version
|
|
return 900;
|
|
|
|
case audioMasterVendorSpecific:
|
|
SHOW_CALLBACK ("audioMasterVendorSpecific");
|
|
// no definition, vendor specific handling
|
|
return 0;
|
|
|
|
case audioMasterSetIcon:
|
|
SHOW_CALLBACK ("audioMasterSetIcon");
|
|
// void* in <ptr>, format not defined yet
|
|
return 0;
|
|
|
|
case audioMasterCanDo:
|
|
SHOW_CALLBACK ("audioMasterCanDo");
|
|
// string in ptr, (const char*)ptr
|
|
for (int i = 0; i < vst_can_do_string_count; i++) {
|
|
if (! strcmp(vst_can_do_strings[i], (const char*)ptr)) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterGetLanguage:
|
|
SHOW_CALLBACK ("audioMasterGetLanguage");
|
|
// see enum
|
|
return 0;
|
|
|
|
case audioMasterOpenWindow:
|
|
SHOW_CALLBACK ("audioMasterOpenWindow");
|
|
// returns platform specific ptr
|
|
return 0;
|
|
|
|
case audioMasterCloseWindow:
|
|
SHOW_CALLBACK ("audioMasterCloseWindow");
|
|
// close window, platform specific handle in <ptr>
|
|
return 0;
|
|
|
|
case audioMasterGetDirectory:
|
|
SHOW_CALLBACK ("audioMasterGetDirectory");
|
|
// get plug directory, FSSpec on MAC, else char*
|
|
return 0;
|
|
|
|
case audioMasterUpdateDisplay:
|
|
SHOW_CALLBACK ("audioMasterUpdateDisplay");
|
|
// something has changed, update 'multi-fx' display
|
|
if (effect) {
|
|
effect->dispatcher(effect, effEditIdle, 0, 0, NULL, 0.0f);
|
|
}
|
|
return 0;
|
|
|
|
case audioMasterBeginEdit:
|
|
SHOW_CALLBACK ("audioMasterBeginEdit");
|
|
// begin of automation session (when mouse down), parameter index in <index>
|
|
return 0;
|
|
|
|
case audioMasterEndEdit:
|
|
SHOW_CALLBACK ("audioMasterEndEdit");
|
|
// end of automation session (when mouse up), parameter index in <index>
|
|
return 0;
|
|
|
|
case audioMasterOpenFileSelector:
|
|
SHOW_CALLBACK ("audioMasterOpenFileSelector");
|
|
// open a fileselector window with VstFileSelect* in <ptr>
|
|
return 0;
|
|
|
|
default:
|
|
DEBUG_TRACE (PBD::DEBUG::VSTCallbacks, string_compose ("VST master dispatcher: undefed: %1\n", opcode));
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|