mirror of
https://codeberg.org/armin/justasample.git
synced 2026-09-01 04:10:48 +02:00
init
This commit is contained in:
commit
d21bc831e1
178 changed files with 24136 additions and 0 deletions
958
External/MTS/libMTSClient.cpp
vendored
Normal file
958
External/MTS/libMTSClient.cpp
vendored
Normal file
|
|
@ -0,0 +1,958 @@
|
|||
/*
|
||||
Copyright (C) 2021 by ODDSound Ltd. info@oddsound.com
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 "libMTSClient.h"
|
||||
#include <math.h>
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__TOS_WIN__) || defined(_MSC_VER)
|
||||
#define MTS_ESP_WIN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
typedef HRESULT (WINAPI* SHGetKnownFolderPathFunc) (const GUID*, DWORD, HANDLE, PWSTR*);
|
||||
typedef void (WINAPI* CoTaskMemFreeFunc) (LPVOID);
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
const static int libMTSVersion = 0x00010003;
|
||||
|
||||
const static double ln2 = 0.693147180559945309417;
|
||||
const static double ratioToSemitones = 17.31234049066756088832; // 12.0 / log(2.0)
|
||||
|
||||
typedef void (*mts_void__void)(void);
|
||||
typedef bool (*mts_bool__void)(void);
|
||||
typedef int (*mts_int__void)(void);
|
||||
typedef bool (*mts_bool__char_char)(char, char);
|
||||
typedef const double *(*mts_pConstDouble__void)(void);
|
||||
typedef const double *(*mts_pConstDouble__char)(char);
|
||||
typedef bool (*mts_bool__char)(char);
|
||||
typedef const char *(*mts_pConstChar__void)(void);
|
||||
typedef double (*mts_double__void)(void);
|
||||
typedef char (*mts_char__void)(void);
|
||||
|
||||
struct mtsclientglobal
|
||||
{
|
||||
mtsclientglobal()
|
||||
: RegisterClient(0)
|
||||
, DeregisterClient(0)
|
||||
, HasMaster(0)
|
||||
, GetVersionNumber(0)
|
||||
, ShouldFilterNote(0)
|
||||
, ShouldFilterNoteMultiChannel(0)
|
||||
, GetTuning(0)
|
||||
, GetMultiChannelTuning(0)
|
||||
, UseMultiChannelTuning(0)
|
||||
, GetScaleName(0)
|
||||
, GetPeriodRatio(0)
|
||||
, GetMapSize(0)
|
||||
, GetMapStartKey(0)
|
||||
, GetRefKey(0)
|
||||
, esp_retuning(0)
|
||||
, handle(0)
|
||||
{
|
||||
for (int i = 0; i < 128; i++)
|
||||
iet[i] = 1. / (440.0 * pow(2.0, (i - 69.0) / 12.0));
|
||||
|
||||
load_lib();
|
||||
|
||||
if (GetTuning)
|
||||
esp_retuning = GetTuning();
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
multi_channel_esp_retuning[i] = GetMultiChannelTuning ? GetMultiChannelTuning(static_cast<char>(i)) : 0;
|
||||
}
|
||||
|
||||
inline bool isOnline() const {return esp_retuning && HasMaster && HasMaster();}
|
||||
|
||||
// interface to lib
|
||||
mts_void__void RegisterClient;
|
||||
mts_void__void DeregisterClient;
|
||||
mts_bool__void HasMaster;
|
||||
mts_int__void GetVersionNumber;
|
||||
mts_bool__char_char ShouldFilterNote;
|
||||
mts_bool__char_char ShouldFilterNoteMultiChannel;
|
||||
mts_pConstDouble__void GetTuning;
|
||||
mts_pConstDouble__char GetMultiChannelTuning;
|
||||
mts_bool__char UseMultiChannelTuning;
|
||||
mts_pConstChar__void GetScaleName;
|
||||
mts_double__void GetPeriodRatio;
|
||||
mts_char__void GetMapSize;
|
||||
mts_char__void GetMapStartKey;
|
||||
mts_char__void GetRefKey;
|
||||
|
||||
// tuning tables
|
||||
double iet[128];
|
||||
const double *esp_retuning;
|
||||
const double *multi_channel_esp_retuning[16];
|
||||
|
||||
#ifdef MTS_ESP_WIN
|
||||
void load_lib()
|
||||
{
|
||||
SHGetKnownFolderPathFunc SHGetKnownFolderPath = 0;
|
||||
CoTaskMemFreeFunc CoTaskMemFree = 0;
|
||||
|
||||
HMODULE shell32Module = GetModuleHandleW(L"Shell32.dll");
|
||||
HMODULE ole32Module = GetModuleHandleW(L"Ole32.dll");
|
||||
|
||||
if (shell32Module)
|
||||
SHGetKnownFolderPath = (SHGetKnownFolderPathFunc)GetProcAddress(shell32Module, "SHGetKnownFolderPath");
|
||||
|
||||
if (ole32Module)
|
||||
CoTaskMemFree = (CoTaskMemFreeFunc)GetProcAddress(ole32Module, "CoTaskMemFree");
|
||||
|
||||
if (SHGetKnownFolderPath && CoTaskMemFree)
|
||||
{
|
||||
const GUID FOLDERID_ProgramFilesCommonGUID = {0xF7F1ED05, 0x9F6D, 0x47A2, 0xAA, 0xAE, 0x29, 0xD3, 0x17, 0xC6, 0xF0, 0x66};
|
||||
PWSTR cf = NULL;
|
||||
if (SHGetKnownFolderPath(&FOLDERID_ProgramFilesCommonGUID, 0, 0, &cf) >= 0)
|
||||
{
|
||||
WCHAR buffer[MAX_PATH];
|
||||
buffer[0] = L'\0';
|
||||
if (cf)
|
||||
wcsncpy(buffer, cf, MAX_PATH);
|
||||
CoTaskMemFree(cf);
|
||||
buffer[MAX_PATH - 1] = L'\0';
|
||||
const WCHAR *libpath = L"\\MTS-ESP\\LIBMTS.dll";
|
||||
DWORD cfLen = wcslen(buffer);
|
||||
wcsncat(buffer, libpath, MAX_PATH - cfLen - 1);
|
||||
handle = LoadLibraryW(buffer);
|
||||
if (!handle)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
CoTaskMemFree(cf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterClient = (mts_void__void) GetProcAddress(handle, "MTS_RegisterClient");
|
||||
DeregisterClient = (mts_void__void) GetProcAddress(handle, "MTS_DeregisterClient");
|
||||
HasMaster = (mts_bool__void) GetProcAddress(handle, "MTS_HasMaster");
|
||||
GetVersionNumber = (mts_int__void) GetProcAddress(handle, "MTS_GetVersionNumber");
|
||||
ShouldFilterNote = (mts_bool__char_char) GetProcAddress(handle, "MTS_ShouldFilterNote");
|
||||
ShouldFilterNoteMultiChannel = (mts_bool__char_char) GetProcAddress(handle, "MTS_ShouldFilterNoteMultiChannel");
|
||||
GetTuning = (mts_pConstDouble__void) GetProcAddress(handle, "MTS_GetTuningTable");
|
||||
GetMultiChannelTuning = (mts_pConstDouble__char) GetProcAddress(handle, "MTS_GetMultiChannelTuningTable");
|
||||
UseMultiChannelTuning = (mts_bool__char) GetProcAddress(handle, "MTS_UseMultiChannelTuning");
|
||||
GetScaleName = (mts_pConstChar__void) GetProcAddress(handle, "MTS_GetScaleName");
|
||||
GetPeriodRatio = (mts_double__void) GetProcAddress(handle, "MTS_GetPeriodRatio");
|
||||
GetMapSize = (mts_char__void) GetProcAddress(handle, "MTS_GetMapSize");
|
||||
GetMapStartKey = (mts_char__void) GetProcAddress(handle, "MTS_GetMapStartKey");
|
||||
GetRefKey = (mts_char__void) GetProcAddress(handle, "MTS_GetRefKey");
|
||||
}
|
||||
|
||||
~mtsclientglobal()
|
||||
{
|
||||
if (handle)
|
||||
FreeLibrary(handle);
|
||||
}
|
||||
|
||||
HINSTANCE handle;
|
||||
#else
|
||||
void load_lib()
|
||||
{
|
||||
if (!(handle = dlopen("/Library/Application Support/MTS-ESP/libMTS.dylib", RTLD_NOW)) &&
|
||||
!(handle = dlopen("/usr/local/lib/libMTS.so", RTLD_NOW)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterClient = (mts_void__void) dlsym(handle, "MTS_RegisterClient");
|
||||
DeregisterClient = (mts_void__void) dlsym(handle, "MTS_DeregisterClient");
|
||||
HasMaster = (mts_bool__void) dlsym(handle, "MTS_HasMaster");
|
||||
GetVersionNumber = (mts_int__void) dlsym(handle, "MTS_GetVersionNumber");
|
||||
ShouldFilterNote = (mts_bool__char_char) dlsym(handle, "MTS_ShouldFilterNote");
|
||||
ShouldFilterNoteMultiChannel = (mts_bool__char_char) dlsym(handle, "MTS_ShouldFilterNoteMultiChannel");
|
||||
GetTuning = (mts_pConstDouble__void) dlsym(handle, "MTS_GetTuningTable");
|
||||
GetMultiChannelTuning = (mts_pConstDouble__char) dlsym(handle, "MTS_GetMultiChannelTuningTable");
|
||||
UseMultiChannelTuning = (mts_bool__char) dlsym(handle, "MTS_UseMultiChannelTuning");
|
||||
GetScaleName = (mts_pConstChar__void) dlsym(handle, "MTS_GetScaleName");
|
||||
GetPeriodRatio = (mts_double__void) dlsym(handle, "MTS_GetPeriodRatio");
|
||||
GetMapSize = (mts_char__void) dlsym(handle, "MTS_GetMapSize");
|
||||
GetMapStartKey = (mts_char__void) dlsym(handle, "MTS_GetMapStartKey");
|
||||
GetRefKey = (mts_char__void) dlsym(handle, "MTS_GetRefKey");
|
||||
}
|
||||
|
||||
~mtsclientglobal()
|
||||
{
|
||||
if (handle)
|
||||
dlclose(handle);
|
||||
}
|
||||
|
||||
void *handle;
|
||||
#endif
|
||||
};
|
||||
|
||||
static mtsclientglobal global;
|
||||
|
||||
struct MTSClient
|
||||
{
|
||||
struct Tuning
|
||||
{
|
||||
enum {eRatioValid = 1, eSemitonesValid = 1 << 1};
|
||||
int flags;
|
||||
double freq; // always valid
|
||||
double ratio;
|
||||
double semitones;
|
||||
};
|
||||
|
||||
MTSClient()
|
||||
: tuningName("12-TET")
|
||||
, periodRatioLocal(2.0)
|
||||
, periodSemitones(12.0)
|
||||
, mapSizeLocal(static_cast<char>(-1))
|
||||
, mapStartKeyLocal(static_cast<char>(-1))
|
||||
, supportsNoteFiltering(false)
|
||||
, supportsMultiChannelNoteFiltering(false)
|
||||
, supportsMultiChannelTuning(false)
|
||||
, freqRequestReceived(false)
|
||||
, receivedMTSSysEx(false)
|
||||
{
|
||||
for (int i = 0; i < 128; i++)
|
||||
{
|
||||
localFreqs[i] = 440.0 * pow(2.0, (i - 69.0) / 12.0);
|
||||
localTunings[i].flags = 0;
|
||||
localTunings[i].freq = localFreqs[i];
|
||||
globalTunings[i].flags = 0;
|
||||
globalTunings[i].freq = localFreqs[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
for (int j = 0; j < 128; j++)
|
||||
{
|
||||
globalMultichannelTunings[i][j].flags = 0;
|
||||
globalMultichannelTunings[i][j].freq = localFreqs[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (global.RegisterClient)
|
||||
global.RegisterClient();
|
||||
}
|
||||
|
||||
~MTSClient()
|
||||
{
|
||||
if (global.DeregisterClient)
|
||||
global.DeregisterClient();
|
||||
}
|
||||
|
||||
inline bool hasMaster() {return global.isOnline();}
|
||||
inline bool shouldUpdateLibrary() {return global.GetVersionNumber ? (global.GetVersionNumber() < libMTSVersion) : false;}
|
||||
|
||||
inline double freq(char midinote, char midichannel)
|
||||
{
|
||||
int note = midinote & 127;
|
||||
int channel = midichannel & 15;
|
||||
|
||||
freqRequestReceived = true;
|
||||
supportsMultiChannelTuning = !(midichannel & ~15);
|
||||
|
||||
if (!global.isOnline())
|
||||
return localTunings[note].freq;
|
||||
|
||||
if ((!supportsNoteFiltering || supportsMultiChannelNoteFiltering) &&
|
||||
supportsMultiChannelTuning &&
|
||||
global.UseMultiChannelTuning &&
|
||||
global.UseMultiChannelTuning(midichannel) &&
|
||||
global.multi_channel_esp_retuning[channel])
|
||||
{
|
||||
globalMultichannelTunings[channel][note].freq = global.multi_channel_esp_retuning[channel][note];
|
||||
globalMultichannelTunings[channel][note].flags = 0;
|
||||
return globalMultichannelTunings[channel][note].freq;
|
||||
}
|
||||
|
||||
globalTunings[note].freq = global.esp_retuning[note];
|
||||
globalTunings[note].flags = 0;
|
||||
return globalTunings[note].freq;
|
||||
}
|
||||
|
||||
inline double ratio(char midinote, char midichannel)
|
||||
{
|
||||
int note = midinote & 127;
|
||||
int channel = midichannel & 15;
|
||||
|
||||
freqRequestReceived = true;
|
||||
supportsMultiChannelTuning = !(midichannel & ~15);
|
||||
|
||||
if (!global.isOnline())
|
||||
{
|
||||
if (!receivedMTSSysEx)
|
||||
return 1.0;
|
||||
|
||||
if (localTunings[note].flags & Tuning::eRatioValid)
|
||||
return localTunings[note].ratio;
|
||||
|
||||
localTunings[note].ratio = localTunings[note].freq * global.iet[note];
|
||||
localTunings[note].flags |= Tuning::eRatioValid;
|
||||
return localTunings[note].ratio;
|
||||
}
|
||||
|
||||
if ((!supportsNoteFiltering || supportsMultiChannelNoteFiltering) &&
|
||||
supportsMultiChannelTuning &&
|
||||
global.UseMultiChannelTuning &&
|
||||
global.UseMultiChannelTuning(midichannel) &&
|
||||
global.multi_channel_esp_retuning[channel])
|
||||
{
|
||||
double freq = global.multi_channel_esp_retuning[channel][note];
|
||||
|
||||
if (globalMultichannelTunings[channel][note].freq == freq &&
|
||||
(globalMultichannelTunings[channel][note].flags & Tuning::eRatioValid))
|
||||
{
|
||||
return globalMultichannelTunings[channel][note].ratio;
|
||||
}
|
||||
|
||||
globalMultichannelTunings[channel][note].freq = global.multi_channel_esp_retuning[channel][note];
|
||||
globalMultichannelTunings[channel][note].ratio = globalMultichannelTunings[channel][note].freq * global.iet[note];
|
||||
globalMultichannelTunings[channel][note].flags = Tuning::eRatioValid;
|
||||
return globalMultichannelTunings[channel][note].ratio;
|
||||
}
|
||||
|
||||
double freq = global.esp_retuning[note];
|
||||
|
||||
if (globalTunings[note].freq == freq &&
|
||||
(globalTunings[note].flags & Tuning::eRatioValid))
|
||||
{
|
||||
return globalTunings[note].ratio;
|
||||
}
|
||||
|
||||
globalTunings[note].freq = global.esp_retuning[note];
|
||||
globalTunings[note].ratio = globalTunings[note].freq * global.iet[note];
|
||||
globalTunings[note].flags = Tuning::eRatioValid;
|
||||
return globalTunings[note].ratio;
|
||||
}
|
||||
|
||||
inline double semitones(char midinote, char midichannel)
|
||||
{
|
||||
int note = midinote & 127;
|
||||
int channel = midichannel & 15;
|
||||
|
||||
freqRequestReceived = true;
|
||||
supportsMultiChannelTuning = !(midichannel & ~15);
|
||||
|
||||
if (!global.isOnline())
|
||||
{
|
||||
if (!receivedMTSSysEx)
|
||||
return 0.0;
|
||||
|
||||
if (localTunings[note].flags & Tuning::eSemitonesValid)
|
||||
return localTunings[note].semitones;
|
||||
|
||||
if (localTunings[note].flags & Tuning::eRatioValid)
|
||||
{
|
||||
localTunings[note].semitones = ratioToSemitones * log(localTunings[note].ratio);
|
||||
localTunings[note].flags |= Tuning::eSemitonesValid;
|
||||
return localTunings[note].semitones;
|
||||
}
|
||||
|
||||
localTunings[note].ratio = localTunings[note].freq * global.iet[note];
|
||||
localTunings[note].semitones = ratioToSemitones * log(localTunings[note].ratio);
|
||||
localTunings[note].flags |= Tuning::eRatioValid | Tuning::eSemitonesValid;
|
||||
return localTunings[note].semitones;
|
||||
}
|
||||
|
||||
if ((!supportsNoteFiltering || supportsMultiChannelNoteFiltering) &&
|
||||
supportsMultiChannelTuning &&
|
||||
global.UseMultiChannelTuning &&
|
||||
global.UseMultiChannelTuning(midichannel) &&
|
||||
global.multi_channel_esp_retuning[channel])
|
||||
{
|
||||
double freq = global.multi_channel_esp_retuning[channel][note];
|
||||
|
||||
if (globalMultichannelTunings[channel][note].freq == freq)
|
||||
{
|
||||
if (globalMultichannelTunings[channel][note].flags & Tuning::eSemitonesValid)
|
||||
return globalMultichannelTunings[channel][note].semitones;
|
||||
|
||||
if (globalMultichannelTunings[channel][note].flags & Tuning::eRatioValid)
|
||||
{
|
||||
globalMultichannelTunings[channel][note].semitones = ratioToSemitones * log(globalMultichannelTunings[channel][note].ratio);
|
||||
globalMultichannelTunings[channel][note].flags |= Tuning::eSemitonesValid;
|
||||
return globalMultichannelTunings[channel][note].semitones;
|
||||
}
|
||||
}
|
||||
|
||||
globalMultichannelTunings[channel][note].freq = freq;
|
||||
globalMultichannelTunings[channel][note].ratio = freq * global.iet[note];
|
||||
globalMultichannelTunings[channel][note].semitones = ratioToSemitones * log(globalMultichannelTunings[channel][note].ratio);
|
||||
globalMultichannelTunings[channel][note].flags = Tuning::eRatioValid | Tuning::eSemitonesValid;
|
||||
return globalMultichannelTunings[channel][note].semitones;
|
||||
}
|
||||
|
||||
double freq = global.esp_retuning[note];
|
||||
|
||||
if (globalTunings[note].freq == freq)
|
||||
{
|
||||
if (globalTunings[note].flags & Tuning::eSemitonesValid)
|
||||
return globalTunings[note].semitones;
|
||||
|
||||
if (globalTunings[note].flags & Tuning::eRatioValid)
|
||||
{
|
||||
globalTunings[note].semitones = ratioToSemitones * log(globalTunings[note].ratio);
|
||||
globalTunings[note].flags |= Tuning::eSemitonesValid;
|
||||
return globalTunings[note].semitones;
|
||||
}
|
||||
}
|
||||
|
||||
globalTunings[note].freq = freq;
|
||||
globalTunings[note].ratio = freq * global.iet[note];
|
||||
globalTunings[note].semitones = ratioToSemitones * log(globalTunings[note].ratio);
|
||||
globalTunings[note].flags = Tuning::eRatioValid | Tuning::eSemitonesValid;
|
||||
return globalTunings[note].semitones;
|
||||
}
|
||||
|
||||
inline bool shouldFilterNote(char midinote, char midichannel)
|
||||
{
|
||||
supportsNoteFiltering = true;
|
||||
supportsMultiChannelNoteFiltering = !(midichannel & ~15);
|
||||
|
||||
if (!freqRequestReceived)
|
||||
supportsMultiChannelTuning = supportsMultiChannelNoteFiltering; // assume it supports multi channel tuning until a request is received for a frequency and can verify
|
||||
|
||||
if (!global.isOnline())
|
||||
return false;
|
||||
|
||||
if (supportsMultiChannelNoteFiltering &&
|
||||
supportsMultiChannelTuning &&
|
||||
global.UseMultiChannelTuning &&
|
||||
global.UseMultiChannelTuning(midichannel))
|
||||
{
|
||||
return global.ShouldFilterNoteMultiChannel ? global.ShouldFilterNoteMultiChannel(midinote & 127, midichannel) : false;
|
||||
}
|
||||
|
||||
return global.ShouldFilterNote ? global.ShouldFilterNote(midinote & 127, midichannel) : false;
|
||||
}
|
||||
|
||||
inline char freqToNote(double freq, char midichannel)
|
||||
{
|
||||
bool online = global.isOnline();
|
||||
bool multiChannel = false;
|
||||
const double *freqs = online ? global.esp_retuning : localFreqs;
|
||||
|
||||
if (online &&
|
||||
!(midichannel & ~15) &&
|
||||
global.UseMultiChannelTuning &&
|
||||
global.UseMultiChannelTuning(midichannel) &&
|
||||
global.multi_channel_esp_retuning[midichannel & 15])
|
||||
{
|
||||
freqs = global.multi_channel_esp_retuning[midichannel & 15];
|
||||
multiChannel = true;
|
||||
}
|
||||
|
||||
int iLower = 0;
|
||||
int iUpper = 0;
|
||||
double dLower = 0.0;
|
||||
double dUpper = 0.0;
|
||||
|
||||
for (int i = 0; i < 128; i++)
|
||||
{
|
||||
if (online)
|
||||
{
|
||||
if (multiChannel &&
|
||||
global.ShouldFilterNoteMultiChannel &&
|
||||
global.ShouldFilterNoteMultiChannel(static_cast<char>(i), midichannel))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!multiChannel &&
|
||||
global.ShouldFilterNote &&
|
||||
global.ShouldFilterNote(static_cast<char>(i), midichannel))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
double d = freqs[i] - freq;
|
||||
|
||||
if (d == 0.0)
|
||||
return static_cast<char>(i);
|
||||
|
||||
if (d < 0.0)
|
||||
{
|
||||
if (dLower == 0.0 || d > dLower)
|
||||
{
|
||||
dLower=d;
|
||||
iLower=i;
|
||||
}
|
||||
}
|
||||
else if (dUpper == 0.0 || d < dUpper)
|
||||
{
|
||||
dUpper = d;
|
||||
iUpper = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (dLower == 0.0)
|
||||
return static_cast<char>(iUpper);
|
||||
|
||||
if (dUpper == 0.0 || iLower == iUpper)
|
||||
return static_cast<char>(iLower);
|
||||
|
||||
double fmid = freqs[iLower] * pow(2.0, 0.5 * (log(freqs[iUpper] / freqs[iLower]) / ln2));
|
||||
return freq < fmid ? static_cast<char>(iLower) : static_cast<char>(iUpper);
|
||||
}
|
||||
|
||||
inline char freqToNote(double freq, char *midichannel)
|
||||
{
|
||||
if (!midichannel)
|
||||
return freqToNote(freq, static_cast<char>(-1));
|
||||
|
||||
if (global.isOnline() && global.UseMultiChannelTuning)
|
||||
{
|
||||
int channelsInUse[16];
|
||||
int nMultiChannels = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
if (global.UseMultiChannelTuning(i) && global.multi_channel_esp_retuning[i])
|
||||
channelsInUse[nMultiChannels++] = i;
|
||||
|
||||
if (nMultiChannels > 0)
|
||||
{
|
||||
const int nFreqs = 128 * nMultiChannels;
|
||||
int iLower = 0;
|
||||
int iUpper = 0;
|
||||
int channel = 0;
|
||||
int note = 0;
|
||||
double dLower = 0.0;
|
||||
double dUpper = 0.0;
|
||||
|
||||
for (int i = 0; i < nFreqs; i++)
|
||||
{
|
||||
channel = channelsInUse[i >> 7];
|
||||
note = i & 127;
|
||||
|
||||
if (global.ShouldFilterNoteMultiChannel &&
|
||||
global.ShouldFilterNoteMultiChannel(static_cast<char>(note), static_cast<char>(channel)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double d = global.multi_channel_esp_retuning[channel][note] - freq;
|
||||
|
||||
if (d == 0.0)
|
||||
{
|
||||
*midichannel = static_cast<char>(channel);
|
||||
return static_cast<char>(note);
|
||||
}
|
||||
|
||||
if (d < 0.0)
|
||||
{
|
||||
if (dLower == 0.0 || d > dLower)
|
||||
{
|
||||
dLower = d;
|
||||
iLower = i;
|
||||
}
|
||||
}
|
||||
else if (dUpper == 0.0 || d < dUpper)
|
||||
{
|
||||
dUpper = d;
|
||||
iUpper = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (dLower==0.0)
|
||||
{
|
||||
*midichannel = static_cast<char>(channelsInUse[iUpper >> 7]);
|
||||
return static_cast<char>(iUpper & 127);
|
||||
}
|
||||
|
||||
if (dUpper == 0.0 || iLower == iUpper)
|
||||
{
|
||||
*midichannel = static_cast<char>(channelsInUse[iLower >> 7]);
|
||||
return static_cast<char>(iLower & 127);
|
||||
}
|
||||
|
||||
double fLower = global.multi_channel_esp_retuning[channelsInUse[iLower >> 7]][iLower & 127];
|
||||
double fUpper = global.multi_channel_esp_retuning[channelsInUse[iUpper >> 7]][iUpper & 127];
|
||||
double fmid = fLower * pow(2.0, 0.5 * (log(fUpper / fLower) / ln2));
|
||||
|
||||
if (freq < fmid)
|
||||
{
|
||||
*midichannel = static_cast<char>(channelsInUse[iLower >> 7]);
|
||||
return static_cast<char>(iLower & 127);
|
||||
}
|
||||
|
||||
*midichannel = static_cast<char>(channelsInUse[iUpper >> 7]);
|
||||
return static_cast<char>(iUpper & 127);
|
||||
}
|
||||
}
|
||||
|
||||
*midichannel = static_cast<char>(0);
|
||||
return freqToNote(freq, static_cast<char>(0));
|
||||
}
|
||||
|
||||
inline void parseMIDIData(const unsigned char *buffer, int len)
|
||||
{
|
||||
int sysex_ctr = 0;
|
||||
int sysex_value = 0;
|
||||
int note = 0;
|
||||
int numTunings = 0;
|
||||
/*int bank = -1, prog = 0, checksum = 0, deviceID = 0; short int channelBitmap = 0; bool realtime = false;*/ // unused for now
|
||||
|
||||
eSysexState state = eIgnoring;
|
||||
eMTSFormat format = eBulk;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
unsigned char b = buffer[i];
|
||||
if (b == 0xF7)
|
||||
{
|
||||
state = eIgnoring;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (b > 0x7F && b != 0xF0)
|
||||
continue;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case eIgnoring:
|
||||
if (b == 0xF0)
|
||||
state = eMatchingSysex;
|
||||
break;
|
||||
case eMatchingSysex:
|
||||
sysex_ctr = 0;
|
||||
if (b == 0x7E)
|
||||
state = eSysexValid;
|
||||
else if (b == 0x7F)
|
||||
{
|
||||
/*realtime = true;*/
|
||||
state = eSysexValid;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = eIgnoring;
|
||||
}
|
||||
break;
|
||||
case eSysexValid:
|
||||
switch (sysex_ctr++) // handle device ID
|
||||
{
|
||||
case 0:
|
||||
/*deviceID = b;*/
|
||||
break;
|
||||
case 1:
|
||||
if (b == 0x08)
|
||||
state = eMatchingMTS;
|
||||
break;
|
||||
default: // it's not an MTS message
|
||||
state = eIgnoring;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case eMatchingMTS:
|
||||
sysex_ctr = 0;
|
||||
switch (b)
|
||||
{
|
||||
case 0:
|
||||
format = eRequest;
|
||||
state = eMatchingProg;
|
||||
break;
|
||||
case 1:
|
||||
format = eBulk;
|
||||
state = eMatchingProg;
|
||||
break;
|
||||
case 2:
|
||||
format = eSingle;
|
||||
state = eMatchingProg;
|
||||
break;
|
||||
case 3:
|
||||
format = eRequest;
|
||||
state = eMatchingBank;
|
||||
break;
|
||||
case 4:
|
||||
format = eBulk;
|
||||
state = eMatchingBank;
|
||||
break;
|
||||
case 5:
|
||||
format = eScaleOctOneByte;
|
||||
state = eMatchingBank;
|
||||
break;
|
||||
case 6:
|
||||
format = eScaleOctTwoByte;
|
||||
state = eMatchingBank;
|
||||
break;
|
||||
case 7:
|
||||
format = eSingle;
|
||||
state = eMatchingBank;
|
||||
break;
|
||||
case 8:
|
||||
format = eScaleOctOneByteExt;
|
||||
state = eMatchingChannel;
|
||||
break;
|
||||
case 9:
|
||||
format = eScaleOctTwoByteExt;
|
||||
state = eMatchingChannel;
|
||||
break;
|
||||
default: // it's not a valid MTS format
|
||||
state = eIgnoring;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case eMatchingBank:
|
||||
/*bank = b;*/
|
||||
state = eMatchingProg;
|
||||
break;
|
||||
case eMatchingProg:
|
||||
/*prog = b;*/
|
||||
if (format == eSingle)
|
||||
{
|
||||
state = eNumTunings;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = eTuningName;
|
||||
tuningName[0] = '\0';
|
||||
}
|
||||
break;
|
||||
case eTuningName:
|
||||
tuningName[sysex_ctr] = static_cast<char>(b);
|
||||
if (++sysex_ctr >= 16)
|
||||
{
|
||||
tuningName[16] = '\0';
|
||||
sysex_ctr = 0;
|
||||
state = eTuningData;
|
||||
}
|
||||
break;
|
||||
case eNumTunings:
|
||||
numTunings = b;
|
||||
sysex_ctr = 0;
|
||||
state = eTuningData;
|
||||
break;
|
||||
case eMatchingChannel:
|
||||
switch (sysex_ctr++)
|
||||
{
|
||||
case 0:
|
||||
/*for (int j = 14; j < 16; j++) channelBitmap |= (1 << j);*/
|
||||
break;
|
||||
case 1:
|
||||
/*for (int j = 7; j < 14; j++) channelBitmap |= (1 << j);*/
|
||||
break;
|
||||
case 2:
|
||||
/*for (int j = 0; j < 7; j++) channelBitmap |= (1 << j);*/
|
||||
sysex_ctr = 0;
|
||||
state = eTuningData;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case eTuningData:
|
||||
switch (format)
|
||||
{
|
||||
case eBulk:
|
||||
sysex_value = (sysex_value << 7) | b;
|
||||
sysex_ctr++;
|
||||
if ((sysex_ctr & 3) == 3)
|
||||
{
|
||||
if (!(note == 0x7F && sysex_value == 16383))
|
||||
updateTuning(note, (sysex_value >> 14) & 127, (sysex_value & 16383) / 16383.0);
|
||||
sysex_value = 0;
|
||||
sysex_ctr++;
|
||||
if (++note >= 128)
|
||||
state = eCheckSum;
|
||||
}
|
||||
break;
|
||||
case eSingle:
|
||||
sysex_value = (sysex_value << 7) | b;
|
||||
sysex_ctr++;
|
||||
if (!(sysex_ctr & 3))
|
||||
{
|
||||
if (!(note == 0x7F && sysex_value == 16383))
|
||||
updateTuning((sysex_value >> 21) & 127, (sysex_value >> 14) & 127, (sysex_value & 16383) / 16383.0);
|
||||
sysex_value = 0;
|
||||
if (++note >= numTunings)
|
||||
state = eIgnoring;
|
||||
}
|
||||
break;
|
||||
case eScaleOctOneByte:
|
||||
case eScaleOctOneByteExt:
|
||||
for (int j = sysex_ctr; j < 128; j += 12)
|
||||
updateTuning(j, j, (static_cast<double>(b) - 64.0) * 0.01);
|
||||
if (++sysex_ctr >= 12)
|
||||
state = format == eScaleOctOneByte ? eCheckSum : eIgnoring;
|
||||
break;
|
||||
case eScaleOctTwoByte:
|
||||
case eScaleOctTwoByteExt:
|
||||
sysex_value = (sysex_value << 7) | b;
|
||||
sysex_ctr++;
|
||||
if (!(sysex_ctr & 1))
|
||||
{
|
||||
double detune = (static_cast<double>(sysex_value & 16383) - 8192.0) / (sysex_value > 8192 ? 8191.0 : 8192.0);
|
||||
for (int j = note; j < 128; j += 12)
|
||||
updateTuning(j, j, detune);
|
||||
if (++note >= 12)
|
||||
state = format == eScaleOctTwoByte ? eCheckSum : eIgnoring;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
state = eIgnoring;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case eCheckSum:
|
||||
/*checksum = b;*/
|
||||
state = eIgnoring;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (format == eScaleOctOneByte || format == eScaleOctTwoByte || format == eScaleOctOneByteExt || format == eScaleOctTwoByteExt)
|
||||
{
|
||||
mapSizeLocal = static_cast<char>(12);
|
||||
mapStartKeyLocal = static_cast<char>(60);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapSizeLocal = static_cast<char>(-1);
|
||||
mapStartKeyLocal = static_cast<char>(-1);
|
||||
}
|
||||
}
|
||||
|
||||
inline void updateTuning(int note, int retuneNote, double detune)
|
||||
{
|
||||
if (note < 0 || note > 127 || retuneNote < 0 || retuneNote > 127)
|
||||
return;
|
||||
receivedMTSSysEx = true;
|
||||
localFreqs[note] = 440.0 * pow(2.0, ((retuneNote + detune) - 69.0) / 12.0);
|
||||
if (localFreqs[note] != localTunings[note].freq)
|
||||
{
|
||||
localTunings[note].freq = localFreqs[note];
|
||||
localTunings[note].flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool hasReceivedMTSSysEx() {return receivedMTSSysEx;}
|
||||
|
||||
const char *getScaleName() {return (global.isOnline() && global.GetScaleName) ? global.GetScaleName() : tuningName;}
|
||||
|
||||
double getPeriodRatio() {return (global.isOnline() && global.GetPeriodRatio) ? global.GetPeriodRatio() : 2.0;}
|
||||
double getPeriodSemitones()
|
||||
{
|
||||
double periodRatio = getPeriodRatio();
|
||||
if (periodRatio != periodRatioLocal)
|
||||
{
|
||||
periodSemitones = ratioToSemitones * log(periodRatio);
|
||||
periodRatioLocal = periodRatio;
|
||||
}
|
||||
return periodSemitones;
|
||||
}
|
||||
|
||||
char getMapSize() {return (global.isOnline() && global.GetMapSize) ? global.GetMapSize() : mapSizeLocal;}
|
||||
char getMapStartKey() {return (global.isOnline() && global.GetMapStartKey) ? global.GetMapStartKey() : mapStartKeyLocal;}
|
||||
char getRefKey() {return (global.isOnline() && global.GetRefKey) ? global.GetRefKey() : static_cast<char>(-1);}
|
||||
|
||||
enum eSysexState {eIgnoring = 0, eMatchingSysex, eSysexValid, eMatchingMTS, eMatchingBank, eMatchingProg, eMatchingChannel, eTuningName, eNumTunings, eTuningData, eCheckSum};
|
||||
enum eMTSFormat {eRequest = 0, eBulk, eSingle, eScaleOctOneByte, eScaleOctTwoByte, eScaleOctOneByteExt, eScaleOctTwoByteExt};
|
||||
|
||||
double localFreqs[128];
|
||||
Tuning localTunings[128];
|
||||
Tuning globalTunings[128];
|
||||
Tuning globalMultichannelTunings[16][128];
|
||||
|
||||
char tuningName[17];
|
||||
|
||||
double periodRatioLocal;
|
||||
double periodSemitones;
|
||||
|
||||
char mapSizeLocal;
|
||||
char mapStartKeyLocal;
|
||||
|
||||
bool supportsNoteFiltering;
|
||||
bool supportsMultiChannelNoteFiltering;
|
||||
bool supportsMultiChannelTuning;
|
||||
bool freqRequestReceived;
|
||||
bool receivedMTSSysEx;
|
||||
};
|
||||
|
||||
static char freqToNoteET(double freq)
|
||||
{
|
||||
static double freqs[128];
|
||||
static bool init = false;
|
||||
if (!init)
|
||||
{
|
||||
for (int i = 0; i < 128; i++)
|
||||
freqs[i] = 440.0 * pow(2.0, (i - 69.0) / 12.0);
|
||||
init = true;
|
||||
}
|
||||
|
||||
if (freq <= freqs[0])
|
||||
return 0;
|
||||
if (freq >= freqs[127])
|
||||
return 127;
|
||||
|
||||
int mid = 0;
|
||||
int n = -1;
|
||||
int n2 = -1;
|
||||
|
||||
for (int first = 0, last=127;
|
||||
freq != freqs[(mid = first + (last - first) / 2)];
|
||||
(freq < freqs[mid]) ? last = mid - 1 : first = mid + 1)
|
||||
{
|
||||
if (first > last)
|
||||
{
|
||||
if (!mid)
|
||||
{
|
||||
n = mid;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mid > 127)
|
||||
mid = 127;
|
||||
|
||||
n = mid - ((freq - freqs[mid - 1]) < (freqs[mid] - freq));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (n == -1)
|
||||
{
|
||||
if (freq == freqs[mid])
|
||||
n = mid;
|
||||
else
|
||||
return 60;
|
||||
}
|
||||
|
||||
if (!n)
|
||||
n2 = 1;
|
||||
else if (n == 127)
|
||||
n2 = 126;
|
||||
else
|
||||
n2 = n + (fabs(freqs[n - 1] - freq) < fabs(freqs[n + 1] - freq) ? -1 : 1);
|
||||
|
||||
if (n2 < n)
|
||||
{
|
||||
int t = n;
|
||||
n = n2;
|
||||
n2 = t;
|
||||
}
|
||||
|
||||
double fmid = freqs[n] * pow(2.0, 0.5 * (log(freqs[n2] / freqs[n]) / ln2));
|
||||
return freq < fmid ? static_cast<char>(n) : static_cast<char>(n2);
|
||||
}
|
||||
|
||||
// exported functions:
|
||||
MTSClient* MTS_RegisterClient() {return new MTSClient;}
|
||||
void MTS_DeregisterClient(MTSClient *c) {delete c;}
|
||||
bool MTS_HasMaster(MTSClient *c) {return c ? c->hasMaster() : false;}
|
||||
bool MTS_Client_ShouldUpdateLibrary(MTSClient *c) {return c ? c->shouldUpdateLibrary() : false;}
|
||||
bool MTS_ShouldFilterNote(MTSClient *c, char midinote, char midichannel) {return c ? c->shouldFilterNote(midinote & 127, midichannel) : false;}
|
||||
double MTS_NoteToFrequency(MTSClient *c, char midinote, char midichannel) {return c ? c->freq(midinote, midichannel) : (1.0 / global.iet[midinote & 127]);}
|
||||
double MTS_RetuningAsRatio(MTSClient *c, char midinote, char midichannel) {return c ? c->ratio(midinote, midichannel) : 1.0;}
|
||||
double MTS_RetuningInSemitones(MTSClient *c, char midinote, char midichannel) {return c ? c->semitones(midinote, midichannel) : 0.0;}
|
||||
char MTS_FrequencyToNote(MTSClient *c, double freq, char midichannel) {return c ? c->freqToNote(freq, midichannel) : freqToNoteET(freq);}
|
||||
char MTS_FrequencyToNoteAndChannel(MTSClient *c, double freq, char *midichannel) {if (c) return c->freqToNote(freq, midichannel); if (midichannel) *midichannel = 0; return freqToNoteET(freq);}
|
||||
const char *MTS_GetScaleName(MTSClient *c) {return c ? c->getScaleName() : "";}
|
||||
double MTS_GetPeriodRatio(MTSClient *c) {return c ? c->getPeriodRatio() : 2.0;}
|
||||
double MTS_GetPeriodSemitones(MTSClient *c) {return c ? c->getPeriodSemitones() : 12.0;}
|
||||
char MTS_GetMapSize(MTSClient *c) {return c ? c->getMapSize() : static_cast<char>(-1);}
|
||||
char MTS_GetMapStartKey(MTSClient *c) {return c ? c->getMapStartKey() : static_cast<char>(-1);}
|
||||
char MTS_GetRefKey(MTSClient *c) {return c ? c->getRefKey() : static_cast<char>(-1);}
|
||||
void MTS_ParseMIDIDataU(MTSClient *c, const unsigned char *buffer, int len) {if (c) c->parseMIDIData(buffer, len);}
|
||||
void MTS_ParseMIDIData(MTSClient *c, const char *buffer, int len) {if (c) c->parseMIDIData(reinterpret_cast<const unsigned char*>(buffer), len);}
|
||||
bool MTS_HasReceivedMTSSysEx(MTSClient *c) {return c ? c->hasReceivedMTSSysEx() : false;}
|
||||
189
External/MTS/libMTSClient.h
vendored
Normal file
189
External/MTS/libMTSClient.h
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
Copyright (C) 2021 by ODDSound Ltd. info@oddsound.com
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 libMTSClient_h
|
||||
#define libMTSClient_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
Steps for using the MTS-ESP client API to add microtuning support to a plug-in.
|
||||
Steps 1 and 2 are required, however it is recommended to include further steps when
|
||||
integrating:
|
||||
|
||||
|
||||
1. REQUIRED: Register and de-register a plug-in instance as a client with MTS-ESP.
|
||||
On startup in the plug-in constructor call:
|
||||
|
||||
MTSClient *client = MTS_RegisterClient();
|
||||
|
||||
Store the returned MTSClient pointer to supply when calling other MTS-ESP client API
|
||||
functions. On shutdown in the plug-in destructor call:
|
||||
|
||||
MTS_DeregisterClient(client);
|
||||
|
||||
|
||||
2. REQUIRED: Query retuning when a note-on message is received and adjust tuning accordingly.
|
||||
When given a note call:
|
||||
|
||||
double freq = MTS_NoteToFrequency(client, midinote, midichannel);
|
||||
OR
|
||||
double retune_semitones = MTS_RetuningInSemitones(client, midinote, midichannel);
|
||||
OR
|
||||
double retune_ratio = MTS_RetuningAsRatio(client, midinote, midichannel);
|
||||
|
||||
MIDI channel arguments should use the range [0,15] however if you don’t know the MIDI
|
||||
channel, use -1 (see step 6 for more on MIDI channels).
|
||||
|
||||
|
||||
3. RECOMMENDED: Continuously query retuning whilst a note is held, allowing tuning to change
|
||||
along the flight of a note. Do this if you can and as often as possible, ideally at the same
|
||||
time as processing any other pitch modulation sources (envelopes, MIDI controllers, LFOs etc.).
|
||||
|
||||
|
||||
4. RECOMMENDED: Provide an option to the user to select whether tuning is queried at note-on
|
||||
only, as in step 2, or continuously, as in step 3. There are creative and practical
|
||||
advantages to both, depending on the use case, and offering an option to the user will
|
||||
provide the most useful MTS-ESP integration. If not offering such an option, continuous
|
||||
retuning should be preferred over note-on only retuning.
|
||||
|
||||
|
||||
5. RECOMMENDED: Query whether a note should be sounded when a note-on message is received.
|
||||
The Scala .kbm keyboard mapping format allows for MIDI keys to be unmapped i.e. no frequency
|
||||
is specified for them, and the MTS-ESP library supports this too. You can query whether a note
|
||||
is unmapped and should be ignored with:
|
||||
|
||||
bool should_ignore_note = MTS_ShouldFilterNote(client, midinote, midichannel);
|
||||
|
||||
If this returns true, ignore the note-on and don’t play anything. Calling this function is
|
||||
recommended but optional and a valid value for frequency/retuning will be returned for an
|
||||
unmapped note. MIDI channel arguments should use the range [0,15] however if you don’t
|
||||
know the MIDI channel, use -1.
|
||||
|
||||
|
||||
6. RECOMMENDED: Always supply a MIDI channel when querying retuning or note filtering. Doing
|
||||
so allows your plug-in to use multi-channel tuning tables, useful for microtonal MIDI controllers
|
||||
with more than 128 keys or working with large scales. Even if multi-channel tables are not
|
||||
in use, a master may still make use of channel-specific note filtering for functions such as
|
||||
key switches to change tunings. If your plug-in supports MPE and has a switch for enabling MPE
|
||||
support, it is recommended to NOT supply a MIDI channel if MPE is enabled.
|
||||
|
||||
|
||||
7. RECOMMENDED: If you are adding MTS-ESP support to a plug-in that already has some kind
|
||||
of microtuning support, e.g. loading .scl or .tun files, let the local tuning automatically
|
||||
override MTS-ESP, or provide an option for MTS-ESP retuning to be explicitly disabled.
|
||||
This affords a user the option to use a different tuning to the global MTS-ESP table
|
||||
for a specific plug-in instance.
|
||||
|
||||
|
||||
8. OPTIONAL: Add support for MIDI Tuning Standard (or MTS, from the MIDI specification) SysEx
|
||||
messages to your plug-in. When not connected to an MTS-ESP master plug-in, these can be used
|
||||
to retune it instead, providing microtuning support even when MTS-ESP is not in use.
|
||||
When a SysEx message is received, call:
|
||||
|
||||
MTS_ParseMIDIData(client, buffer, len); // if buffer is signed char *
|
||||
OR
|
||||
MTS_ParseMIDIDataU(client, buffer, len); // if buffer is unsigned char *
|
||||
|
||||
These will update a local tuning table which is used when querying retuning as in steps 2
|
||||
and 3. Check whether a valid MTS SysEx message has been received with:
|
||||
|
||||
bool MTS_SysEx_received = MTS_HasReceivedMTSSysEx(client);
|
||||
|
||||
|
||||
9. OPTIONAL: If you want to display to the user whether the plug-in is "connected" to an
|
||||
MTS-ESP master plug-in, call:
|
||||
|
||||
bool has_master = MTS_HasMaster(client);
|
||||
|
||||
|
||||
10: OPTIONAL: It is possible to query the name of the current scale. This function is necessarily
|
||||
supplied for the case where a client is sending MTS SysEx messages, however it can be used
|
||||
to display the current scale name to the user on your UI too:
|
||||
|
||||
const char *name = MTS_GetScaleName(client);
|
||||
|
||||
|
||||
11: OPTIONAL: After registering, let the user know if they have an older version of the libMTS dynamic library
|
||||
installed which may not support some features in this version of the API:
|
||||
|
||||
bool should_update = MTS_Client_ShouldUpdateLibrary(client);
|
||||
|
||||
The latest version of libMTS will always be backward compatible with clients built with
|
||||
an older version of the API. Users can update libMTS using the installers at
|
||||
https://github.com/ODDSound/MTS-ESP/tree/main/libMTS.
|
||||
|
||||
|
||||
12: EXTRAS: Helper functions are available which return the MIDI note whose pitch is nearest
|
||||
a given frequency. The MIDI note returned is guaranteed to be mapped. If you intend to
|
||||
generate a note-on message using the returned note number, you may already know which MIDI
|
||||
channel it will be sent on, in which case you must specify this in the call, else the client
|
||||
library can prescribe a channel for you. This is done so that multi-channel mapping
|
||||
and note filtering can be respected. See below for further details.
|
||||
*/
|
||||
|
||||
// Opaque datatype for MTSClient.
|
||||
typedef struct MTSClient MTSClient;
|
||||
|
||||
// Register/deregister as a client. Call from the plug-in constructor and destructor.
|
||||
extern MTSClient *MTS_RegisterClient();
|
||||
extern void MTS_DeregisterClient(MTSClient *client);
|
||||
|
||||
// Check if the client is currently connected to a master plug-in.
|
||||
extern bool MTS_HasMaster(MTSClient *client);
|
||||
|
||||
// Check if the MTS-ESP dynamic library needs to be updated to use all features in this version of the API.
|
||||
extern bool MTS_Client_ShouldUpdateLibrary(MTSClient *client);
|
||||
|
||||
// Returns true if note should not be played. MIDI channel argument should be included if possible (0-15), else set to -1.
|
||||
extern bool MTS_ShouldFilterNote(MTSClient *client, char midinote, char midichannel);
|
||||
|
||||
// Retuning a midi note. Pick the version that makes your life easiest! MIDI channel argument should be included if possible (0-15), else set to -1.
|
||||
extern double MTS_NoteToFrequency(MTSClient *client, char midinote, char midichannel);
|
||||
extern double MTS_RetuningInSemitones(MTSClient *client, char midinote, char midichannel);
|
||||
extern double MTS_RetuningAsRatio(MTSClient *client, char midinote, char midichannel);
|
||||
|
||||
// MTS_FrequencyToNote() is a helper function returning the note number whose pitch is closest to the supplied frequency. Two versions are provided:
|
||||
// The first is for the simplest case: supply a frequency and get a note number back.
|
||||
// If you intend to use the returned note number to generate a note-on message on a specific, pre-determined MIDI channel, set the midichannel argument to the destination channel (0-15), else set to -1.
|
||||
// If a MIDI channel is supplied, the corresponding multi-channel tuning table will be queried if in use, else multi-channel tables are ignored.
|
||||
extern char MTS_FrequencyToNote(MTSClient *client, double freq, char midichannel);
|
||||
// Use the second version if you intend to use the returned note number to generate a note-on message and where you have the possibility to send it on any MIDI channel.
|
||||
// The midichannel argument is a pointer to a char which will receive the MIDI channel on which the note message should be sent (0-15).
|
||||
// Multi-channel tuning tables are queried if in use.
|
||||
extern char MTS_FrequencyToNoteAndChannel(MTSClient *client, double freq, char *midichannel);
|
||||
|
||||
// Returns the name of the current scale.
|
||||
extern const char *MTS_GetScaleName(MTSClient *client);
|
||||
|
||||
// Returns the period of the current scale, or 2.0 (12 semitones) if not supplied by a master.
|
||||
extern double MTS_GetPeriodRatio(MTSClient *client);
|
||||
extern double MTS_GetPeriodSemitones(MTSClient *client);
|
||||
|
||||
// Query information about keyboard mapping.
|
||||
// NOTE: negative values are invalid and these functions will return -1 if the information has not been supplied by a master.
|
||||
// The return value must therefore be checked it is valid before being used.
|
||||
extern char MTS_GetMapSize(MTSClient *client);
|
||||
extern char MTS_GetMapStartKey(MTSClient *client);
|
||||
extern char MTS_GetRefKey(MTSClient *client);
|
||||
|
||||
// Parse incoming MIDI data to update local tuning. All formats of MTS SysEx message accepted.
|
||||
extern void MTS_ParseMIDIDataU(MTSClient *client, const unsigned char *buffer, int len);
|
||||
extern void MTS_ParseMIDIData(MTSClient *client, const char *buffer, int len);
|
||||
|
||||
// Check if the client has received any valid MTS SysEx messages and will use local tuning if not connected to a master plug-in.
|
||||
extern bool MTS_HasReceivedMTSSysEx(MTSClient *client);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue