Compare commits
10 Commits
bca9e0381d
...
3f779532ac
Author | SHA1 | Date |
---|---|---|
wan-may | 3f779532ac | |
wan-may | d9c98c59b3 | |
wan-may | eb1a82ac93 | |
yaw-man | 994ff19596 | |
yaw-man | f60f589359 | |
yaw-man | cbdddd6bbc | |
yaw-man | a9d112d757 | |
yaw-man | ef0eec2721 | |
yaw-man | b9ae12167d | |
yaw-man | ca9e6acbe8 |
|
@ -1,4 +1,3 @@
|
||||||
[submodule "lib/DPF"]
|
[submodule "lib/DPF"]
|
||||||
ignore = all
|
|
||||||
path = lib/DPF
|
path = lib/DPF
|
||||||
url = https://github.com/DISTRHO/DPF.git
|
url = https://github.com/DISTRHO/DPF.git
|
2
lib/DPF
2
lib/DPF
|
@ -1 +1 @@
|
||||||
Subproject commit 86a621bfd86922a49ce593fec2a618a1e0cc6ef3
|
Subproject commit 23692d024e57c617bd0692beb1835d48f98eb914
|
|
@ -0,0 +1,27 @@
|
||||||
|
option(TAB_WINTAB true)
|
||||||
|
option(TAB_OTD false)
|
||||||
|
|
||||||
|
if(TAB_WINTAB)
|
||||||
|
add_library(rasa STATIC
|
||||||
|
wintab.cpp
|
||||||
|
wtutil.cpp
|
||||||
|
)
|
||||||
|
target_compile_definitions(rasa PUBLIC TAB_WINTAB)
|
||||||
|
target_include_directories(rasa
|
||||||
|
"."
|
||||||
|
"../wintab")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(TAB_OTD)
|
||||||
|
add_library(rasa STATIC
|
||||||
|
cs.cpp )
|
||||||
|
target_compile_definitions(rasa PUBLIC TAB_OTD)
|
||||||
|
target_include_directories(rasa
|
||||||
|
"."
|
||||||
|
"../otd")
|
||||||
|
# default compiler flags from CMAKE conflict with managed code compliation.
|
||||||
|
# probably a better way of doing this.
|
||||||
|
set_target_properties(rasa PROPERTIES VS_DOTNET_REFERENCES "System")
|
||||||
|
set_target_properties(rasa PROPERTIES COMPILE_FLAGS "/clr")
|
||||||
|
set_target_properties(rasa PROPERTIES COMPILE_FLAGS "/Zc:twoPhase-")
|
||||||
|
endif()
|
|
@ -0,0 +1,25 @@
|
||||||
|
//File for C# <-> C++/CLI interface
|
||||||
|
|
||||||
|
#ifdef YAW_USE_OTD
|
||||||
|
// text_write.cpp
|
||||||
|
// compile with: /clr
|
||||||
|
using namespace System;
|
||||||
|
using namespace System::IO;
|
||||||
|
|
||||||
|
int func()
|
||||||
|
{
|
||||||
|
String^ fileName = "textfile.txt";
|
||||||
|
|
||||||
|
StreamWriter^ sw = gcnew StreamWriter(fileName);
|
||||||
|
sw->WriteLine("A text file is born!");
|
||||||
|
sw->Write("You can use WriteLine");
|
||||||
|
sw->WriteLine("...or just Write");
|
||||||
|
sw->WriteLine("and do {0} output too.", "formatted");
|
||||||
|
sw->WriteLine("You can also send non-text objects:");
|
||||||
|
sw->WriteLine(DateTime::Now);
|
||||||
|
sw->Close();
|
||||||
|
Console::WriteLine("a new file ('{0}') has been written", fileName);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,3 @@
|
||||||
|
Minimal tablet API.
|
||||||
|
Just the bare minimum for polling for x, y, z, and normal pressure.
|
||||||
|
Compile time switches determine whether to use OpenTabletDriver (C# interface required) or Wintab (Win32 platform required)
|
|
@ -0,0 +1,46 @@
|
||||||
|
//Interface to supported tablet APIs.
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
typedef unsigned long ButtonMask;
|
||||||
|
|
||||||
|
#ifdef TAB_WINTAB
|
||||||
|
#include <Windows.h>
|
||||||
|
#include "MSGPACK.H"
|
||||||
|
#include "wintab.h"
|
||||||
|
typedef unsigned long ButtonMask;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
struct Packet {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float z;
|
||||||
|
float p;
|
||||||
|
ButtonMask buttons;
|
||||||
|
bool operator==(const Packet& p)
|
||||||
|
{ return
|
||||||
|
this->x == p.x &&
|
||||||
|
this->y == p.y &&
|
||||||
|
this->z == p.z &&
|
||||||
|
this->p == p.p &&
|
||||||
|
this->buttons == p.buttons;
|
||||||
|
};
|
||||||
|
bool operator!=(const Packet& p) { return !(*this == p); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Tablet {
|
||||||
|
public:
|
||||||
|
Tablet(uintptr_t handle);
|
||||||
|
~Tablet();
|
||||||
|
bool GetPacket( Packet &pkt );
|
||||||
|
bool initialized;
|
||||||
|
std::string errormsg = "";
|
||||||
|
Packet ext = { 0 };
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef TAB_WINTAB
|
||||||
|
void NewContext(HWND hwnd);
|
||||||
|
HCTX hctx = NULL;
|
||||||
|
#endif
|
||||||
|
};
|
|
@ -0,0 +1,81 @@
|
||||||
|
#ifdef TAB_WINTAB
|
||||||
|
#include "tablet.h"
|
||||||
|
#define PACKETDATA (PK_X | PK_Y | PK_Z | PK_BUTTONS | PK_NORMAL_PRESSURE)
|
||||||
|
//#define PACKETMODE
|
||||||
|
#include "pktdef.h"
|
||||||
|
#include "wtutil.h"
|
||||||
|
|
||||||
|
Tablet::Tablet(uintptr_t handle)
|
||||||
|
{
|
||||||
|
HWND hwnd = reinterpret_cast<HWND>(handle);
|
||||||
|
if (!hwnd) { return; }
|
||||||
|
|
||||||
|
if (!LoadWintab() || !gpWTInfoA(0, 0, NULL)) {
|
||||||
|
errormsg = "Wintab not installed.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NewContext(hwnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tablet::~Tablet() {
|
||||||
|
if (hctx) { gpWTClose(hctx); }
|
||||||
|
UnloadWintab();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tablet::GetPacket( Packet& packet ) {
|
||||||
|
//Serial number of newest packet.
|
||||||
|
UINT oldest, newest;
|
||||||
|
//This function returns false when it fails
|
||||||
|
//which may happen with a full or empty queue.
|
||||||
|
if (!gpWTQueuePacketsEx(hctx, &oldest, &newest)) {
|
||||||
|
//Queue may be full, flush it all just in case.
|
||||||
|
gpWTPacketsGet(hctx, gpWTQueueSizeGet(hctx), nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Store newest packet in pkt, flush older packets.
|
||||||
|
PACKET pkt;
|
||||||
|
bool newData = gpWTPacket(hctx, newest, &pkt);
|
||||||
|
if (!newData) return false;
|
||||||
|
packet.x = static_cast<float>(pkt.pkX) / ext.x;
|
||||||
|
packet.y = static_cast<float>(pkt.pkY) / ext.y;
|
||||||
|
packet.z = static_cast<float>(pkt.pkZ) / ext.z;
|
||||||
|
packet.p = static_cast<float>(pkt.pkNormalPressure) / ext.p;
|
||||||
|
packet.buttons = pkt.pkButtons;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tablet::NewContext(HWND hwnd) {
|
||||||
|
if (hctx) { gpWTClose(hctx); }
|
||||||
|
LOGCONTEXT ctx = {};
|
||||||
|
AXIS TabletX = { 0 };
|
||||||
|
AXIS TabletY = { 0 };
|
||||||
|
AXIS TabletZ = { 0 };
|
||||||
|
AXIS TabletPressure = { 0 };
|
||||||
|
gpWTInfoA(WTI_DEFCONTEXT, 0, &ctx);
|
||||||
|
ctx.lcOptions |= CXO_MESSAGES; //TODO: checker <20>ela
|
||||||
|
ctx.lcPktData = PACKETDATA;
|
||||||
|
ctx.lcPktMode = 0;
|
||||||
|
|
||||||
|
//Tablet extents.
|
||||||
|
gpWTInfoA(WTI_DEVICES, DVC_X, &TabletX);
|
||||||
|
gpWTInfoA(WTI_DEVICES, DVC_Y, &TabletY);
|
||||||
|
gpWTInfoA(WTI_DEVICES, DVC_Z, &TabletZ);
|
||||||
|
gpWTInfoA(WTI_DEVICES, DVC_NPRESSURE, &TabletPressure);
|
||||||
|
|
||||||
|
ext.x = static_cast<float>(TabletX.axMax);
|
||||||
|
ext.y = static_cast<float>(TabletY.axMax);
|
||||||
|
ext.z = static_cast<float>(TabletZ.axMax);
|
||||||
|
ext.p = static_cast<float>(TabletPressure.axMax);
|
||||||
|
|
||||||
|
hctx = gpWTOpenA(hwnd, &ctx, TRUE);
|
||||||
|
|
||||||
|
if (!hctx)
|
||||||
|
{
|
||||||
|
errormsg = "Could not open Wintab context.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -0,0 +1,143 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DESCRIPTION
|
||||||
|
// Some general-purpose functions for the WinTab demos.
|
||||||
|
//
|
||||||
|
// COPYRIGHT
|
||||||
|
// Copyright (c) 2014-2020 Wacom Co., Ltd.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "wtutil.h"
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
HINSTANCE ghWintab = nullptr;
|
||||||
|
|
||||||
|
WTINFOA gpWTInfoA = nullptr;
|
||||||
|
WTOPENA gpWTOpenA = nullptr;
|
||||||
|
WTGETA gpWTGetA = nullptr;
|
||||||
|
WTSETA gpWTSetA = nullptr;
|
||||||
|
WTCLOSE gpWTClose = nullptr;
|
||||||
|
WTPACKET gpWTPacket = nullptr;
|
||||||
|
WTENABLE gpWTEnable = nullptr;
|
||||||
|
WTOVERLAP gpWTOverlap = nullptr;
|
||||||
|
WTSAVE gpWTSave = nullptr;
|
||||||
|
WTCONFIG gpWTConfig = nullptr;
|
||||||
|
WTRESTORE gpWTRestore = nullptr;
|
||||||
|
WTEXTSET gpWTExtSet = nullptr;
|
||||||
|
WTEXTGET gpWTExtGet = nullptr;
|
||||||
|
WTQUEUESIZESET gpWTQueueSizeSet = nullptr;
|
||||||
|
WTDATAPEEK gpWTDataPeek = nullptr;
|
||||||
|
WTPACKETSGET gpWTPacketsGet = nullptr;
|
||||||
|
WTMGROPEN gpWTMgrOpen = nullptr;
|
||||||
|
WTMGRCLOSE gpWTMgrClose = nullptr;
|
||||||
|
WTMGRDEFCONTEXT gpWTMgrDefContext = nullptr;
|
||||||
|
WTMGRDEFCONTEXTEX gpWTMgrDefContextEx = nullptr;
|
||||||
|
|
||||||
|
// TODO - add more wintab32 function pointers as needed
|
||||||
|
WTQPACKETSEX gpWTQueuePacketsEx = nullptr;
|
||||||
|
WTQSIZEGET gpWTQueueSizeGet = nullptr;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Purpose
|
||||||
|
// Find wintab32.dll and load it.
|
||||||
|
// Find the exported functions we need from it.
|
||||||
|
//
|
||||||
|
// Returns
|
||||||
|
// TRUE on success.
|
||||||
|
// FALSE on failure.
|
||||||
|
//
|
||||||
|
BOOL LoadWintab(void)
|
||||||
|
{
|
||||||
|
// load the wintab32 dll
|
||||||
|
ghWintab = LoadLibraryA("Wintab32.dll");
|
||||||
|
|
||||||
|
if (!ghWintab)
|
||||||
|
{
|
||||||
|
const DWORD err = GetLastError();
|
||||||
|
ShowError("Could not load Wintab32.dll: " + std::to_string(err));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
// Explicitly find the exported Wintab functions in which we are interested.
|
||||||
|
// We are using the ASCII, not unicode versions (where applicable).
|
||||||
|
gpWTOpenA = (WTOPENA)GetProcAddress(ghWintab, "WTOpenA");
|
||||||
|
gpWTInfoA = (WTINFOA)GetProcAddress(ghWintab, "WTInfoA");
|
||||||
|
gpWTGetA = (WTGETA)GetProcAddress(ghWintab, "WTGetA");
|
||||||
|
gpWTSetA = (WTSETA)GetProcAddress(ghWintab, "WTSetA");
|
||||||
|
gpWTPacket = (WTPACKET)GetProcAddress(ghWintab, "WTPacket");
|
||||||
|
gpWTClose = (WTCLOSE)GetProcAddress(ghWintab, "WTClose");
|
||||||
|
gpWTEnable = (WTENABLE)GetProcAddress(ghWintab, "WTEnable");
|
||||||
|
gpWTOverlap = (WTOVERLAP)GetProcAddress(ghWintab, "WTOverlap");
|
||||||
|
gpWTSave = (WTSAVE)GetProcAddress(ghWintab, "WTSave");
|
||||||
|
gpWTConfig = (WTCONFIG)GetProcAddress(ghWintab, "WTConfig");
|
||||||
|
gpWTRestore = (WTRESTORE)GetProcAddress(ghWintab, "WTRestore");
|
||||||
|
gpWTExtSet = (WTEXTSET)GetProcAddress(ghWintab, "WTExtSet");
|
||||||
|
gpWTExtGet = (WTEXTGET)GetProcAddress(ghWintab, "WTExtGet");
|
||||||
|
gpWTQueueSizeSet = (WTQUEUESIZESET)GetProcAddress(ghWintab, "WTQueueSizeSet");
|
||||||
|
gpWTDataPeek = (WTDATAPEEK)GetProcAddress(ghWintab, "WTDataPeek");
|
||||||
|
gpWTPacketsGet = (WTPACKETSGET)GetProcAddress(ghWintab, "WTPacketsGet");
|
||||||
|
gpWTMgrOpen = (WTMGROPEN)GetProcAddress(ghWintab, "WTMgrOpen");
|
||||||
|
gpWTMgrClose = (WTMGRCLOSE)GetProcAddress(ghWintab, "WTMgrClose");
|
||||||
|
gpWTMgrDefContext = (WTMGRDEFCONTEXT)GetProcAddress(ghWintab, "WTMgrDefContext");
|
||||||
|
gpWTMgrDefContextEx = (WTMGRDEFCONTEXTEX)GetProcAddress(ghWintab, "WTMgrDefContextEx");
|
||||||
|
gpWTQueuePacketsEx = (WTQPACKETSEX)GetProcAddress(ghWintab, "WTQueuePacketsEx");
|
||||||
|
gpWTQueueSizeGet = (WTQSIZEGET)GetProcAddress(ghWintab, "WTQueueSizeGet");
|
||||||
|
|
||||||
|
// TODO - don't forget to NULL out pointers in UnloadWintab().
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Purpose
|
||||||
|
// Uninitializes use of wintab32.dll
|
||||||
|
//
|
||||||
|
// Returns
|
||||||
|
// Nothing.
|
||||||
|
//
|
||||||
|
void UnloadWintab(void)
|
||||||
|
{
|
||||||
|
if (ghWintab)
|
||||||
|
{
|
||||||
|
FreeLibrary(ghWintab);
|
||||||
|
ghWintab = nullptr;
|
||||||
|
}
|
||||||
|
gpWTOpenA = nullptr;
|
||||||
|
gpWTClose = nullptr;
|
||||||
|
gpWTInfoA = nullptr;
|
||||||
|
gpWTPacket = nullptr;
|
||||||
|
gpWTEnable = nullptr;
|
||||||
|
gpWTOverlap = nullptr;
|
||||||
|
gpWTSave = nullptr;
|
||||||
|
gpWTConfig = nullptr;
|
||||||
|
gpWTGetA = nullptr;
|
||||||
|
gpWTSetA = nullptr;
|
||||||
|
gpWTRestore = nullptr;
|
||||||
|
gpWTExtSet = nullptr;
|
||||||
|
gpWTExtGet = nullptr;
|
||||||
|
gpWTQueueSizeSet = nullptr;
|
||||||
|
gpWTDataPeek = nullptr;
|
||||||
|
gpWTPacketsGet = nullptr;
|
||||||
|
gpWTMgrOpen = nullptr;
|
||||||
|
gpWTMgrClose = nullptr;
|
||||||
|
gpWTMgrDefContext = nullptr;
|
||||||
|
gpWTMgrDefContextEx = nullptr;
|
||||||
|
gpWTQueuePacketsEx = nullptr;
|
||||||
|
gpWTQueueSizeGet = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Purpose
|
||||||
|
// Display error to user.
|
||||||
|
//
|
||||||
|
void ShowError(const std::string &pszErrorMessage_I)
|
||||||
|
{
|
||||||
|
//TODO: post error message to buffer in Tablet class
|
||||||
|
//which in turn can post to TabUI
|
||||||
|
//MessageBoxA(NULL, pszErrorMessage_I.c_str(), "Error", MB_OK | MB_ICONERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DESCRIPTION
|
||||||
|
// Defines for the general-purpose functions for the WinTab demos.
|
||||||
|
//
|
||||||
|
// COPYRIGHT
|
||||||
|
// Copyright (c) 2014-2020 Wacom Co., Ltd.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include <wintab.h> // NOTE: get from wactab header package
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Ignore warnings about using unsafe string functions.
|
||||||
|
#pragma warning( disable : 4996 )
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Function pointers to Wintab functions exported from wintab32.dll.
|
||||||
|
using WTINFOA = UINT (API*)(UINT, UINT, LPVOID);
|
||||||
|
using WTOPENA = HCTX (API*)(HWND, LPLOGCONTEXTA, BOOL);
|
||||||
|
using WTGETA = BOOL (API*)(HCTX, LPLOGCONTEXT);
|
||||||
|
using WTSETA = BOOL (API*)(HCTX, LPLOGCONTEXT);
|
||||||
|
using WTCLOSE = BOOL (API*)(HCTX);
|
||||||
|
using WTENABLE = BOOL (API*)(HCTX, BOOL);
|
||||||
|
using WTPACKET = BOOL (API*)(HCTX, UINT, LPVOID);
|
||||||
|
using WTOVERLAP = BOOL (API*)(HCTX, BOOL);
|
||||||
|
using WTSAVE = BOOL (API*)(HCTX, LPVOID);
|
||||||
|
using WTCONFIG = BOOL (API*)(HCTX, HWND);
|
||||||
|
using WTRESTORE = HCTX (API*)(HWND, LPVOID, BOOL);
|
||||||
|
using WTEXTSET = BOOL (API*)(HCTX, UINT, LPVOID);
|
||||||
|
using WTEXTGET = BOOL (API*)(HCTX, UINT, LPVOID);
|
||||||
|
using WTQUEUESIZESET = BOOL (API*)(HCTX, int);
|
||||||
|
using WTDATAPEEK = int (API*)(HCTX, UINT, UINT, int, LPVOID, LPINT);
|
||||||
|
using WTPACKETSGET = int (API*)(HCTX, int, LPVOID);
|
||||||
|
using WTMGROPEN = HMGR (API*)(HWND, UINT);
|
||||||
|
using WTMGRCLOSE = BOOL (API*)(HMGR);
|
||||||
|
using WTMGRDEFCONTEXT = HCTX (API*)(HMGR, BOOL);
|
||||||
|
using WTMGRDEFCONTEXTEX = HCTX (API*)(HMGR, UINT, BOOL);
|
||||||
|
|
||||||
|
// TODO - add more wintab32 function defs as needed
|
||||||
|
using WTQPACKETSEX = BOOL(API*)(HCTX, UINT FAR*, UINT FAR*);
|
||||||
|
using WTQSIZEGET = int (API*)(HCTX);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Loaded Wintab32 API functions.
|
||||||
|
extern HINSTANCE ghWintab;
|
||||||
|
|
||||||
|
extern WTINFOA gpWTInfoA;
|
||||||
|
extern WTOPENA gpWTOpenA;
|
||||||
|
extern WTGETA gpWTGetA;
|
||||||
|
extern WTSETA gpWTSetA;
|
||||||
|
extern WTCLOSE gpWTClose;
|
||||||
|
extern WTPACKET gpWTPacket;
|
||||||
|
extern WTENABLE gpWTEnable;
|
||||||
|
extern WTOVERLAP gpWTOverlap;
|
||||||
|
extern WTSAVE gpWTSave;
|
||||||
|
extern WTCONFIG gpWTConfig;
|
||||||
|
extern WTRESTORE gpWTRestore;
|
||||||
|
extern WTEXTSET gpWTExtSet;
|
||||||
|
extern WTEXTGET gpWTExtGet;
|
||||||
|
extern WTQUEUESIZESET gpWTQueueSizeSet;
|
||||||
|
extern WTDATAPEEK gpWTDataPeek;
|
||||||
|
extern WTPACKETSGET gpWTPacketsGet;
|
||||||
|
extern WTMGROPEN gpWTMgrOpen;
|
||||||
|
extern WTMGRCLOSE gpWTMgrClose;
|
||||||
|
extern WTMGRDEFCONTEXT gpWTMgrDefContext;
|
||||||
|
extern WTMGRDEFCONTEXTEX gpWTMgrDefContextEx;
|
||||||
|
|
||||||
|
// TODO - add more wintab32 function pointers as needed
|
||||||
|
extern WTQPACKETSEX gpWTQueuePacketsEx;
|
||||||
|
extern WTQSIZEGET gpWTQueueSizeGet;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
BOOL LoadWintab(void);
|
||||||
|
void UnloadWintab(void);
|
||||||
|
|
||||||
|
void ShowError(const std::string &pszErrorMessage_I);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -3,3 +3,5 @@ add_subdirectory(yaw-tab)
|
||||||
add_subdirectory(yaw-shepard)
|
add_subdirectory(yaw-shepard)
|
||||||
add_subdirectory(yaw-vowel)
|
add_subdirectory(yaw-vowel)
|
||||||
add_subdirectory(yaw-totune)
|
add_subdirectory(yaw-totune)
|
||||||
|
add_subdirectory(yaw-shep)
|
||||||
|
add_subdirectory(yaw-brick)
|
|
@ -0,0 +1,9 @@
|
||||||
|
dpf_add_plugin(yaw-brick
|
||||||
|
TARGETS vst2
|
||||||
|
FILES_DSP
|
||||||
|
dsp.cpp)
|
||||||
|
|
||||||
|
|
||||||
|
target_include_directories(yaw-brick PUBLIC
|
||||||
|
"."
|
||||||
|
"../../lib")
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
|
||||||
|
#define DISTRHO_PLUGIN_INFO_H_INCLUDED
|
||||||
|
|
||||||
|
#define DISTRHO_PLUGIN_BRAND "yaw-audio"
|
||||||
|
#define DISTRHO_PLUGIN_NAME "yaw-brick"
|
||||||
|
#define DISTRHO_PLUGIN_URI "https://yaw.man/plugins/yaw-brick"
|
||||||
|
|
||||||
|
#define DISTRHO_PLUGIN_IS_RT_SAFE 1
|
||||||
|
#define DISTRHO_PLUGIN_NUM_INPUTS 2
|
||||||
|
#define DISTRHO_PLUGIN_NUM_OUTPUTS 2
|
||||||
|
|
||||||
|
enum Parameters {
|
||||||
|
kParamLimit = 0, //Maximum gain.
|
||||||
|
kParamGain //Pre-amplification
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,77 @@
|
||||||
|
#include "DistrhoPlugin.hpp"
|
||||||
|
|
||||||
|
START_NAMESPACE_DISTRHO
|
||||||
|
|
||||||
|
class Limiter : public Plugin
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Limiter()
|
||||||
|
: Plugin(2, 0, 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const char *getLabel() const override { return "yaw-brick"; }
|
||||||
|
const char *getDescription() const override { return "Sigma Male Limiter"; }
|
||||||
|
const char *getMaker() const override { return "yaw-audio"; }
|
||||||
|
const char *getHomePage() const override { return "https://yaw.man/plugins/yaw-brick"; }
|
||||||
|
const char *getLicense() const override { return "Fuck you pay me"; }
|
||||||
|
uint32_t getVersion() const override { return d_version(1, 0, 0); }
|
||||||
|
int64_t getUniqueId() const override { return d_cconst('y', 'b', 'r', 'k'); }
|
||||||
|
|
||||||
|
void initParameter(uint32_t index, Parameter ¶meter) override
|
||||||
|
{
|
||||||
|
parameter.hints = kParameterIsAutomatable;
|
||||||
|
parameter.ranges.def = 0.5f;
|
||||||
|
parameter.ranges.min = 0.0f;
|
||||||
|
parameter.ranges.max = 1.0f;
|
||||||
|
if( index > 0 ) {
|
||||||
|
parameter.name = "limit";
|
||||||
|
parameter.symbol = "l";
|
||||||
|
parameter.ranges.max = 2.0f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parameter.name = "gain";
|
||||||
|
parameter.symbol = "g";
|
||||||
|
parameter.ranges.max = 20.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sampleRateChanged(double newRate) override
|
||||||
|
{}
|
||||||
|
|
||||||
|
float getParameterValue(uint32_t index) const override
|
||||||
|
{
|
||||||
|
if( index > 0 ) { return limit; }
|
||||||
|
else { return gain; }
|
||||||
|
}
|
||||||
|
|
||||||
|
void setParameterValue(uint32_t idx, float val) override
|
||||||
|
{
|
||||||
|
if( idx > 0 ) { limit = val; }
|
||||||
|
else { gain = val; }
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(const float **inputs, float **outputs, uint32_t frames) override
|
||||||
|
{
|
||||||
|
for (int chn = 0; chn < 2; ++chn)
|
||||||
|
{
|
||||||
|
for( uint32_t i = 0; i < frames; ++i) {
|
||||||
|
|
||||||
|
outputs[chn][i] = limit * (2.f / ( 1.f + expf(-inputs[chn][i] * gain)) - 1.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float limit = 0.f;
|
||||||
|
float gain = 0.f;
|
||||||
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Limiter)
|
||||||
|
};
|
||||||
|
|
||||||
|
Plugin *createPlugin()
|
||||||
|
{
|
||||||
|
return new Limiter();
|
||||||
|
}
|
||||||
|
|
||||||
|
END_NAMESPACE_DISTRHO
|
|
@ -0,0 +1,11 @@
|
||||||
|
dpf_add_plugin(yaw-shep
|
||||||
|
TARGETS vst2 vst3
|
||||||
|
FILES_DSP
|
||||||
|
dsp.cpp
|
||||||
|
synth.cpp
|
||||||
|
FILES_UI
|
||||||
|
ui.cpp)
|
||||||
|
|
||||||
|
target_include_directories(yaw-shep PUBLIC
|
||||||
|
"."
|
||||||
|
"../../lib/scale")
|
|
@ -0,0 +1,26 @@
|
||||||
|
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
|
||||||
|
#define DISTRHO_PLUGIN_INFO_H_INCLUDED
|
||||||
|
|
||||||
|
#define DISTRHO_PLUGIN_BRAND "yaw-audio"
|
||||||
|
#define DISTRHO_PLUGIN_NAME "yaw-shep"
|
||||||
|
#define DISTRHO_PLUGIN_URI "https://yaw.man/plugins/yaw-shep"
|
||||||
|
|
||||||
|
|
||||||
|
#define DISTRHO_PLUGIN_IS_RT_SAFE 1
|
||||||
|
#define DISTRHO_PLUGIN_NUM_INPUTS 0
|
||||||
|
#define DISTRHO_PLUGIN_NUM_OUTPUTS 1
|
||||||
|
#define DISTRHO_PLUGIN_WANT_MIDI_INPUT 1
|
||||||
|
#define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0
|
||||||
|
|
||||||
|
#define DISTRHO_PLUGIN_HAS_UI 1
|
||||||
|
#define DISTRHO_UI_USE_NANOVG 1
|
||||||
|
|
||||||
|
enum Parameters {
|
||||||
|
kMouseX = 0,
|
||||||
|
kMouseY,
|
||||||
|
kVolume,
|
||||||
|
kHz,
|
||||||
|
kParameterCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DISTRHO_PLUGIN_INFO_H_INCLUDED
|
|
@ -0,0 +1,153 @@
|
||||||
|
#include "DistrhoPlugin.hpp"
|
||||||
|
#include "synth.h"
|
||||||
|
|
||||||
|
START_NAMESPACE_DISTRHO
|
||||||
|
|
||||||
|
class ShepPlug : public Plugin
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ShepPlug()
|
||||||
|
: Plugin(kParameterCount, 0, 0),
|
||||||
|
sampleRate(getSampleRate()),
|
||||||
|
synth(sampleRate),
|
||||||
|
fParameters { 0 }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const char* getLabel() const override { return "yaw-shep"; }
|
||||||
|
const char* getDescription() const override { return "Generalized Shepard tone, mouse UI"; }
|
||||||
|
const char* getMaker() const override { return "yaw-audio"; }
|
||||||
|
const char* getHomePage() const override { return "https://yaw.man/plugins/yaw-shep"; }
|
||||||
|
const char* getLicense() const override { return "Fuck You Pay Me"; }
|
||||||
|
uint32_t getVersion() const override { return d_version(1, 0, 0); }
|
||||||
|
int64_t getUniqueId() const override { return d_cconst('y', 's', 'p', 'm'); }
|
||||||
|
|
||||||
|
|
||||||
|
void initParameter(uint32_t index, Parameter& parameter) override
|
||||||
|
{
|
||||||
|
parameter.hints = kParameterIsAutomatable;
|
||||||
|
parameter.ranges.def = 0.0f;
|
||||||
|
parameter.ranges.min = 0.0f;
|
||||||
|
parameter.ranges.max = 1.0f;
|
||||||
|
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case kHz:
|
||||||
|
parameter.name = "hz";
|
||||||
|
parameter.symbol = "hz";
|
||||||
|
parameter.hints = kParameterIsOutput;
|
||||||
|
parameter.ranges.def = 55.0f;
|
||||||
|
parameter.ranges.min = 55.0f;
|
||||||
|
parameter.ranges.max = 880.0f;
|
||||||
|
case kMouseX:
|
||||||
|
parameter.name = "x";
|
||||||
|
parameter.symbol = "x";
|
||||||
|
break;
|
||||||
|
case kMouseY:
|
||||||
|
parameter.name = "y";
|
||||||
|
parameter.symbol = "y";
|
||||||
|
break;
|
||||||
|
case kVolume:
|
||||||
|
parameter.name = "Volume";
|
||||||
|
parameter.symbol = "vol";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sampleRateChanged(double newRate) override
|
||||||
|
{
|
||||||
|
synth.setSampleRate(sampleRate, newRate);
|
||||||
|
sampleRate = newRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getParameterValue(uint32_t index) const override
|
||||||
|
{
|
||||||
|
return fParameters[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void setParameterValue(uint32_t idx, float val) override
|
||||||
|
{
|
||||||
|
fParameters[idx] = val;
|
||||||
|
switch (idx) {
|
||||||
|
case kMouseX:
|
||||||
|
synth.setPhaseVelocity(val);
|
||||||
|
break;
|
||||||
|
case kMouseY:
|
||||||
|
synth.setTone(val);
|
||||||
|
break;
|
||||||
|
case kVolume:
|
||||||
|
synth.setVolume(val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(const float** inputs, float** outputs, uint32_t frames,
|
||||||
|
const MidiEvent* midiEvents, uint32_t midiEventCount) override
|
||||||
|
{
|
||||||
|
|
||||||
|
uint32_t currentFrame = 0;
|
||||||
|
if( midiEventCount > 1 )
|
||||||
|
{
|
||||||
|
currentFrame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < midiEventCount; ++i)
|
||||||
|
{
|
||||||
|
if (midiEvents[i].size <= 3)
|
||||||
|
{
|
||||||
|
|
||||||
|
uint8_t status = midiEvents[i].data[0];
|
||||||
|
uint8_t data = midiEvents[i].data[1] & 127;
|
||||||
|
uint8_t velocity = midiEvents[i].data[2] & 127;
|
||||||
|
|
||||||
|
float vol = velocity / 127.0f;
|
||||||
|
|
||||||
|
switch (status & 0xf0)
|
||||||
|
{
|
||||||
|
//Controller change.
|
||||||
|
case 0xb0:
|
||||||
|
//Channel Volume.
|
||||||
|
if( data == 7 ) setParameterValue(kVolume, vol);
|
||||||
|
//Breath Control.
|
||||||
|
if( data == 2 ) setParameterValue(kVolume, vol);
|
||||||
|
break;
|
||||||
|
//Aftertouch.
|
||||||
|
case 0xd0:
|
||||||
|
setParameterValue(kVolume, vol);
|
||||||
|
break;
|
||||||
|
//Note On.
|
||||||
|
case 0x90:
|
||||||
|
synth.setNote(data);
|
||||||
|
/* fall through */
|
||||||
|
//Note Off.
|
||||||
|
case 0x80:
|
||||||
|
setParameterValue(kVolume, vol);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
synth.process(*outputs, midiEvents[i].frame - currentFrame);
|
||||||
|
currentFrame = midiEvents[i].frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synth.process(*outputs, frames - currentFrame);
|
||||||
|
fParameters[kHz] = synth.hzFund;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float fParameters[kParameterCount];
|
||||||
|
double sampleRate;
|
||||||
|
bool parity = false;
|
||||||
|
|
||||||
|
Synth synth;
|
||||||
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ShepPlug)
|
||||||
|
};
|
||||||
|
|
||||||
|
Plugin* createPlugin()
|
||||||
|
{
|
||||||
|
return new ShepPlug();
|
||||||
|
}
|
||||||
|
|
||||||
|
END_NAMESPACE_DISTRHO
|
|
@ -0,0 +1,119 @@
|
||||||
|
#include "synth.h"
|
||||||
|
|
||||||
|
Synth::Synth(double sampleRate){
|
||||||
|
setSampleRate(48000.0, sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::resetPhase(){
|
||||||
|
spectrumPhase = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define frac(x) ((x) - ((long)x))
|
||||||
|
constexpr double MIN_VOLUME = 0.00001;
|
||||||
|
|
||||||
|
static constexpr double amin = 50;
|
||||||
|
static constexpr double apiq = 500;
|
||||||
|
static constexpr double amax = 10000;
|
||||||
|
//Volume of voice as a function of sample rate independent frequency.
|
||||||
|
static constexpr inline float getAmplitude( double hz )
|
||||||
|
{
|
||||||
|
if( hz < amin ) return 0.0;
|
||||||
|
if( hz < apiq ) {
|
||||||
|
double a = (hz - amin) / (apiq - amin);
|
||||||
|
return a * a;}
|
||||||
|
if( hz < amax ) {
|
||||||
|
double a = 1.0 - (hz - apiq) / (amax - apiq);
|
||||||
|
return a * a;
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Interpolation function on unit interval with good regularity as a function on S^1.
|
||||||
|
static constexpr inline float smooth( double x )
|
||||||
|
{
|
||||||
|
return x * x * x;//(3.0 * x - 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sanity checks: voices should become silent outside audible frequencies.
|
||||||
|
static_assert( MIN_VOLUME > getAmplitude(10.0) );
|
||||||
|
static_assert( MIN_VOLUME > getAmplitude(20000.0));
|
||||||
|
|
||||||
|
//New fundamental is twice as high as old fundamental.
|
||||||
|
//Even overtones become plain overtones.
|
||||||
|
void Synth::shiftUp()
|
||||||
|
{
|
||||||
|
tablePhase *= 2.0;
|
||||||
|
spectrumPhase += 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//New fundamental is half as high as old fundamental.
|
||||||
|
//Overtones become even overtones.
|
||||||
|
void Synth::shiftDown()
|
||||||
|
{
|
||||||
|
tablePhase /= 2.0;
|
||||||
|
spectrumPhase -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::process(float* output, const uint32_t frames)
|
||||||
|
{
|
||||||
|
//Render.
|
||||||
|
for(uint32_t i = 0; i < frames; i++){
|
||||||
|
|
||||||
|
//Set pitch.
|
||||||
|
hzFund = exp2(spectrumPhase) * fMin;
|
||||||
|
|
||||||
|
tablePhase += hzFund * sampleInterval;
|
||||||
|
tablePhase = frac(tablePhase);
|
||||||
|
|
||||||
|
bool isOddHarmonic = true;
|
||||||
|
|
||||||
|
for(uint voice = 0; voice < NUM_VOICES; ++voice){
|
||||||
|
|
||||||
|
double voicePhase = 2.0 * M_PI * tablePhase * (voice + 1.0);
|
||||||
|
//Anti-aliasing: don't bother rendering anything over the Nyquist rate.
|
||||||
|
if( hzFund * (voice + 1.0) > hzNyq ) break;
|
||||||
|
|
||||||
|
output[i] += (!isOddHarmonic + isOddHarmonic * smooth(spectrumPhase)) //Fade in odd harmonics.
|
||||||
|
* getAmplitude(hzFund * (voice + 1.0)) //Frequency response.
|
||||||
|
* sinf(static_cast<float>(voicePhase)); //Additives.
|
||||||
|
|
||||||
|
isOddHarmonic = !isOddHarmonic;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[i] *= fMin * volume / static_cast<float>(8 * NUM_VOICES);
|
||||||
|
|
||||||
|
//Wrapping.
|
||||||
|
spectrumPhase += spectrumPhaseVelocity * sampleInterval;
|
||||||
|
if( spectrumPhase > 1.0) shiftDown();
|
||||||
|
if( spectrumPhase < 0.0) shiftUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::setSampleRate(double oldRate, double newRate){
|
||||||
|
sampleInterval = 1.0 / newRate;
|
||||||
|
hzNyq = newRate / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Takes value from 0 to 1 representing rate of wrapping pitch increase.
|
||||||
|
//0 : lower one octave per second
|
||||||
|
//1 : raise one octave per second
|
||||||
|
void Synth::setPhaseVelocity(double in){
|
||||||
|
spectrumPhaseVelocity = 2.0 * (in - 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Vary pitch of fundamental in a non-wrapping kinda way.
|
||||||
|
//Current range is six octaves, current bottom is 13.75 Hz (A0)
|
||||||
|
void Synth::setTone(double in){
|
||||||
|
falloff = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Synth::setVolume(double in){
|
||||||
|
volume = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::setNote(uint8_t step){
|
||||||
|
fMin = 440.0 * exp2((step - 69.0) / 12.0);
|
||||||
|
Synth::resetPhase();
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
#include "DistrhoPlugin.hpp"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
constexpr unsigned int NUM_VOICES = 512;
|
||||||
|
constexpr unsigned int VOICE_MASK = NUM_VOICES - 1;
|
||||||
|
|
||||||
|
class Synth
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Synth(double sampleRate);
|
||||||
|
void process(float *output, uint32_t frames);
|
||||||
|
void setPhaseVelocity(double in);
|
||||||
|
void setTone(double in);
|
||||||
|
void setSampleRate(double oldRate, double newRate);
|
||||||
|
void setVolume(double in);
|
||||||
|
void setNote(uint8_t step);
|
||||||
|
|
||||||
|
//For recordable performances.
|
||||||
|
//For now it requires a button push, but perhaps
|
||||||
|
//we should require time position and have the phase reset automatically
|
||||||
|
//on song start?
|
||||||
|
void resetPhase();
|
||||||
|
|
||||||
|
|
||||||
|
//Current fundamental frequency of blit.
|
||||||
|
double hzFund = fMin;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void shiftUp();
|
||||||
|
void shiftDown();
|
||||||
|
//Phase of wavetable.
|
||||||
|
double tablePhase = 0.0;
|
||||||
|
float volume = 0.0f;
|
||||||
|
//Parameter in unit circle controlling pitch (varies by one octave).
|
||||||
|
double spectrumPhase = 1.0f;
|
||||||
|
//Revolutions per second of spectrum phase.
|
||||||
|
double spectrumPhaseVelocity = 0.0;
|
||||||
|
//Lowest fundamental frequency of blit.
|
||||||
|
double fMin = 55.0;
|
||||||
|
|
||||||
|
double falloff = 1.0;
|
||||||
|
|
||||||
|
double hzNyq = 24000.0;
|
||||||
|
double sampleInterval = 1.0 / 48000.0;
|
||||||
|
};
|
|
@ -0,0 +1,144 @@
|
||||||
|
#include "ui.h"
|
||||||
|
#include <format>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
START_NAMESPACE_DISTRHO
|
||||||
|
|
||||||
|
static constexpr uint kInitialWidth = 800;
|
||||||
|
static constexpr uint kInitialHeight = 600;
|
||||||
|
|
||||||
|
MouseUI::MouseUI()
|
||||||
|
: UI(kInitialWidth, kInitialHeight)
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef DGL_NO_SHARED_RESOURCES
|
||||||
|
createFontFromFile("sans", "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf");
|
||||||
|
#else
|
||||||
|
loadSharedResources();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MouseUI::parameterChanged(uint32_t index, float value)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (index < kParameterCount)
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case (kHz):
|
||||||
|
hz = value; break;
|
||||||
|
case (kMouseX):
|
||||||
|
x = value; break;
|
||||||
|
case (kMouseY):
|
||||||
|
y = value; break;
|
||||||
|
case (kVolume):
|
||||||
|
vol = value; break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MouseUI::uiIdle()
|
||||||
|
{
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MouseUI::onMouse(const MouseEvent &ev)
|
||||||
|
{
|
||||||
|
record = ev.press;
|
||||||
|
repaint();
|
||||||
|
return false; // Allow event to propagate.
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MouseUI::onMotion(const MotionEvent &ev)
|
||||||
|
{
|
||||||
|
if(!record) return false;
|
||||||
|
|
||||||
|
x = std::clamp(ev.pos.getX() / (1.0 * getWidth()), 0.0, 1.0);
|
||||||
|
y = std::clamp(ev.pos.getY() / (1.0 * getHeight()), 0.0, 1.0);
|
||||||
|
setParameterValue(kMouseX, x);
|
||||||
|
setParameterValue(kMouseY, y);
|
||||||
|
repaint();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MouseUI::onResize(const ResizeEvent &ev)
|
||||||
|
{
|
||||||
|
return UI::onResize(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MouseUI::onNanoDisplay()
|
||||||
|
{
|
||||||
|
fontSize(15.0f);
|
||||||
|
textLineHeight(1.f);
|
||||||
|
|
||||||
|
// Numerical feedback.
|
||||||
|
beginPath();
|
||||||
|
fillColor(200, 200, 200);
|
||||||
|
textBox(0.f, 15.f, 250.f,
|
||||||
|
std::format("Frequency: {:.3f}\nNearest: {:.3f}\n",
|
||||||
|
hz, scale.getNearestNoteNumber(hz))
|
||||||
|
.c_str(),
|
||||||
|
nullptr);
|
||||||
|
closePath();
|
||||||
|
|
||||||
|
beginPath();
|
||||||
|
fillColor(200, 200, 200);
|
||||||
|
textBox(0.f, 45.f, 250.f,
|
||||||
|
std::format("x: {:.3f}\ny: {:.3f}\n",
|
||||||
|
x * getWidth(), y * getHeight())
|
||||||
|
.c_str(),
|
||||||
|
nullptr);
|
||||||
|
closePath();
|
||||||
|
|
||||||
|
|
||||||
|
// Pen position and pressure.
|
||||||
|
drawCircle(x, y, 0.f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MouseUI::drawCircle(float x, float y, float z = 0.f, float p = 1.f)
|
||||||
|
{
|
||||||
|
|
||||||
|
static constexpr float circleRadius = 25.f;
|
||||||
|
x *= getWidth();
|
||||||
|
y *= getHeight();
|
||||||
|
z = 1.f - z;
|
||||||
|
|
||||||
|
beginPath();
|
||||||
|
strokeColor(1.f, 1.f, 1.f, 0.5f);
|
||||||
|
moveTo(x - z * circleRadius, y);
|
||||||
|
lineTo(x + z * circleRadius, y);
|
||||||
|
stroke();
|
||||||
|
closePath();
|
||||||
|
|
||||||
|
beginPath();
|
||||||
|
strokeColor(1.f, 1.f, 1.f, 0.5f);
|
||||||
|
moveTo(x, y - z * circleRadius);
|
||||||
|
lineTo(x, y + z * circleRadius);
|
||||||
|
stroke();
|
||||||
|
closePath();
|
||||||
|
|
||||||
|
beginPath();
|
||||||
|
fillColor(1.f, 1.f, 1.f, p);
|
||||||
|
strokeColor(255, 255, 255, 255);
|
||||||
|
circle(x, y, circleRadius);
|
||||||
|
fill();
|
||||||
|
stroke();
|
||||||
|
closePath();
|
||||||
|
|
||||||
|
beginPath();
|
||||||
|
strokeColor(1.f, 1.f, 1.f, z);
|
||||||
|
circle(x, y, z * circleRadius);
|
||||||
|
stroke();
|
||||||
|
closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
UI *createUI()
|
||||||
|
{
|
||||||
|
return new MouseUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
END_NAMESPACE_DISTRHO
|
|
@ -0,0 +1,37 @@
|
||||||
|
#include "DistrhoUI.hpp"
|
||||||
|
#include "scale.h"
|
||||||
|
#ifdef DEBUG
|
||||||
|
#include <format>
|
||||||
|
#include <optional>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
START_NAMESPACE_DISTRHO
|
||||||
|
|
||||||
|
class MouseUI : public UI
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit MouseUI();
|
||||||
|
|
||||||
|
//void buttonClicked(SubWidget *const widget, int) override;
|
||||||
|
void parameterChanged(uint32_t index, float value) override;
|
||||||
|
|
||||||
|
void uiIdle() override;
|
||||||
|
bool onMouse(const MouseEvent &ev) override;
|
||||||
|
bool onMotion(const MotionEvent &ev) override;
|
||||||
|
void onResize(const ResizeEvent &ev) override;
|
||||||
|
void onNanoDisplay() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drawCircle(float x, float y, float z, float p);
|
||||||
|
|
||||||
|
bool record = false;
|
||||||
|
float x = 0.f;
|
||||||
|
float y = 0.f;
|
||||||
|
float vol = 0.f;
|
||||||
|
float hz = 55.f;
|
||||||
|
Scale scale{440.0};
|
||||||
|
|
||||||
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MouseUI)
|
||||||
|
};
|
||||||
|
|
||||||
|
END_NAMESPACE_DISTRHO
|
|
@ -1,5 +1,5 @@
|
||||||
dpf_add_plugin(yaw-tab-shepard
|
dpf_add_plugin(yaw-tab-shepard
|
||||||
TARGETS vst2
|
TARGETS vst2 jack
|
||||||
FILES_DSP
|
FILES_DSP
|
||||||
dsp.cpp
|
dsp.cpp
|
||||||
synth.cpp
|
synth.cpp
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
|
|
||||||
START_NAMESPACE_DISTRHO
|
START_NAMESPACE_DISTRHO
|
||||||
|
|
||||||
class TabPlugin : public Plugin
|
class ShepPlug : public Plugin
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TabPlugin()
|
ShepPlug()
|
||||||
: Plugin(kParameterCount, 0, 0),
|
: Plugin(kParameterCount, 0, 0),
|
||||||
sampleRate(getSampleRate()),
|
sampleRate(getSampleRate()),
|
||||||
synth(sampleRate),
|
synth(sampleRate),
|
||||||
|
@ -93,7 +93,7 @@ protected:
|
||||||
synth.setPhaseVelocity(val);
|
synth.setPhaseVelocity(val);
|
||||||
break;
|
break;
|
||||||
case ktpay:
|
case ktpay:
|
||||||
synth.setTimbre(val);
|
synth.setPitchOffset(val);
|
||||||
break;
|
break;
|
||||||
case ktpaz:
|
case ktpaz:
|
||||||
break;
|
break;
|
||||||
|
@ -101,6 +101,7 @@ protected:
|
||||||
synth.setVolume(val);
|
synth.setVolume(val);
|
||||||
break;
|
break;
|
||||||
case kParameterButtonA:
|
case kParameterButtonA:
|
||||||
|
if(val > 0.5f) synth.resetPhase();
|
||||||
break;
|
break;
|
||||||
case kParameterButtonB:
|
case kParameterButtonB:
|
||||||
break;
|
break;
|
||||||
|
@ -121,12 +122,12 @@ private:
|
||||||
bool parity = false;
|
bool parity = false;
|
||||||
|
|
||||||
Synth synth;
|
Synth synth;
|
||||||
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabPlugin)
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ShepPlug)
|
||||||
};
|
};
|
||||||
|
|
||||||
Plugin* createPlugin()
|
Plugin* createPlugin()
|
||||||
{
|
{
|
||||||
return new TabPlugin();
|
return new ShepPlug();
|
||||||
}
|
}
|
||||||
|
|
||||||
END_NAMESPACE_DISTRHO
|
END_NAMESPACE_DISTRHO
|
||||||
|
|
|
@ -4,6 +4,10 @@ Synth::Synth(double sampleRate){
|
||||||
setSampleRate(48000.0, sampleRate);
|
setSampleRate(48000.0, sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Synth::resetPhase(){
|
||||||
|
spectrumPhase = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
#define frac(x) ((x) - ((long)x))
|
#define frac(x) ((x) - ((long)x))
|
||||||
constexpr double MIN_VOLUME = 0.00001;
|
constexpr double MIN_VOLUME = 0.00001;
|
||||||
|
|
||||||
|
@ -11,7 +15,7 @@ static constexpr double amin = 50;
|
||||||
static constexpr double apiq = 500;
|
static constexpr double apiq = 500;
|
||||||
static constexpr double amax = 10000;
|
static constexpr double amax = 10000;
|
||||||
//Volume of voice as a function of sample rate independent frequency.
|
//Volume of voice as a function of sample rate independent frequency.
|
||||||
static constexpr double getAmplitude( double hz )
|
static constexpr inline float getAmplitude( double hz )
|
||||||
{
|
{
|
||||||
if( hz < amin ) return 0.0;
|
if( hz < amin ) return 0.0;
|
||||||
if( hz < apiq ) {
|
if( hz < apiq ) {
|
||||||
|
@ -24,8 +28,13 @@ static constexpr double getAmplitude( double hz )
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Interpolation function on unit interval with good regularity as a function on S^1.
|
||||||
|
static constexpr inline float smooth( double x )
|
||||||
|
{
|
||||||
|
return x * x * x;//(3.0 * x - 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
//Sanity checks: voices should become silent outside audible frequencies.
|
//Sanity checks: voices should become silent outside audible frequencies.
|
||||||
//Voice index code will loop infinitely if these fail.
|
|
||||||
static_assert( MIN_VOLUME > getAmplitude(10.0) );
|
static_assert( MIN_VOLUME > getAmplitude(10.0) );
|
||||||
static_assert( MIN_VOLUME > getAmplitude(20000.0));
|
static_assert( MIN_VOLUME > getAmplitude(20000.0));
|
||||||
|
|
||||||
|
@ -33,54 +42,45 @@ static_assert( MIN_VOLUME > getAmplitude(20000.0));
|
||||||
//Even overtones become plain overtones.
|
//Even overtones become plain overtones.
|
||||||
void Synth::shiftUp()
|
void Synth::shiftUp()
|
||||||
{
|
{
|
||||||
|
tablePhase *= 2.0;
|
||||||
spectrumPhase += 1.0;
|
spectrumPhase += 1.0;
|
||||||
for(uint voice = 0; voice < NUM_VOICES; ++voice){
|
|
||||||
phases[voice] = phases[(2 * voice + 1) & VOICE_MASK];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//New fundamental is half as high as old fundamental.
|
//New fundamental is half as high as old fundamental.
|
||||||
//Overtones become even overtones.
|
//Overtones become even overtones.
|
||||||
void Synth::shiftDown()
|
void Synth::shiftDown()
|
||||||
{
|
{
|
||||||
|
tablePhase /= 2.0;
|
||||||
spectrumPhase -= 1.0;
|
spectrumPhase -= 1.0;
|
||||||
for(uint voice = NUM_VOICES / 2 - 1; voice < NUM_VOICES; --voice){
|
|
||||||
phases[voice * 2 + 1] = phases[voice];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::process(float* output, const uint32_t frames)
|
void Synth::process(float* output, const uint32_t frames)
|
||||||
{
|
{
|
||||||
double hz;
|
|
||||||
|
|
||||||
//Render.
|
//Render.
|
||||||
for(uint32_t i = 0; i < frames; i++){
|
for(uint32_t i = 0; i < frames; i++){
|
||||||
|
|
||||||
//Set pitch.
|
//Set pitch.
|
||||||
hz = hzFund = exp2(spectrumPhase) * fMin;
|
hzFund = exp2(spectrumPhase) * fMin;
|
||||||
|
|
||||||
|
tablePhase += hzFund * sampleInterval;
|
||||||
|
tablePhase = frac(tablePhase);
|
||||||
|
|
||||||
bool isOddHarmonic = true;
|
bool isOddHarmonic = true;
|
||||||
|
|
||||||
for(uint voice = 0; voice < NUM_VOICES; ++voice){
|
for(uint voice = 0; voice < NUM_VOICES; ++voice){
|
||||||
//Get new phase.
|
|
||||||
double phase = phases[voice];
|
|
||||||
phase += hz * sampleInterval;
|
|
||||||
phase = frac(phase);
|
|
||||||
|
|
||||||
|
double voicePhase = 2.0 * M_PI * tablePhase * (voice + 1.0);
|
||||||
//Anti-aliasing: don't bother rendering anything over the Nyquist rate.
|
//Anti-aliasing: don't bother rendering anything over the Nyquist rate.
|
||||||
if( hz > hzNyq ) break;
|
if( hzFund * (voice + 1.0) > hzNyq ) break;
|
||||||
|
|
||||||
output[i] += (!isOddHarmonic + isOddHarmonic * spectrumPhase * spectrumPhase) //Fade in odd harmonics.
|
output[i] += (!isOddHarmonic + isOddHarmonic * smooth(spectrumPhase)) //Fade in odd harmonics.
|
||||||
* getAmplitude(hz) //Frequency response.
|
* getAmplitude(hzFund * (voice + 1.0)) //Frequency response.
|
||||||
* sin(2.0 * M_PI * phase); //Additives.
|
* sinf(static_cast<float>(voicePhase)); //Additives.
|
||||||
|
|
||||||
//Remember phase, move to higher overtone.
|
|
||||||
phases[voice] = phase;
|
|
||||||
hz *= (voice + 2.0) / (voice + 1.0);
|
|
||||||
isOddHarmonic = !isOddHarmonic;
|
isOddHarmonic = !isOddHarmonic;
|
||||||
}
|
}
|
||||||
|
|
||||||
output[i] *= 32 * volume / static_cast<double>(NUM_VOICES);
|
output[i] *= fMin * volume / static_cast<float>(8 * NUM_VOICES);
|
||||||
|
|
||||||
//Wrapping.
|
//Wrapping.
|
||||||
spectrumPhase += spectrumPhaseVelocity * sampleInterval;
|
spectrumPhase += spectrumPhaseVelocity * sampleInterval;
|
||||||
|
@ -95,7 +95,7 @@ void Synth::setSampleRate(double oldRate, double newRate){
|
||||||
hzNyq = newRate / 2;
|
hzNyq = newRate / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Takes value from 0 to 1 representing frequency shift factor.
|
//Takes value from 0 to 1 representing rate of wrapping pitch increase.
|
||||||
//0 : lower one octave per second
|
//0 : lower one octave per second
|
||||||
//1 : raise one octave per second
|
//1 : raise one octave per second
|
||||||
void Synth::setPhaseVelocity(double in){
|
void Synth::setPhaseVelocity(double in){
|
||||||
|
@ -103,11 +103,12 @@ void Synth::setPhaseVelocity(double in){
|
||||||
}
|
}
|
||||||
|
|
||||||
//Vary pitch of fundamental in a non-wrapping kinda way.
|
//Vary pitch of fundamental in a non-wrapping kinda way.
|
||||||
//Current range is six octaves, current bottom is 110.0 Hz (A3)
|
//Current range is six octaves, current bottom is 13.75 Hz (A0)
|
||||||
void Synth::setTimbre(double in){
|
void Synth::setPitchOffset(double in){
|
||||||
fMin = exp2(in * 2.0) * 110.0;
|
fMin = exp2(in * 6.0) * 13.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Synth::setVolume(double in){
|
void Synth::setVolume(double in){
|
||||||
volume = in;
|
volume = in;
|
||||||
}
|
}
|
|
@ -1,12 +1,6 @@
|
||||||
#include "DistrhoPlugin.hpp"
|
#include "DistrhoPlugin.hpp"
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
struct Voice
|
|
||||||
{
|
|
||||||
double hz;
|
|
||||||
double phase;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr unsigned int NUM_VOICES = 512;
|
constexpr unsigned int NUM_VOICES = 512;
|
||||||
constexpr unsigned int VOICE_MASK = NUM_VOICES - 1;
|
constexpr unsigned int VOICE_MASK = NUM_VOICES - 1;
|
||||||
|
|
||||||
|
@ -16,19 +10,25 @@ public:
|
||||||
explicit Synth(double sampleRate);
|
explicit Synth(double sampleRate);
|
||||||
void process(float *output, uint32_t frames);
|
void process(float *output, uint32_t frames);
|
||||||
void setPhaseVelocity(double in);
|
void setPhaseVelocity(double in);
|
||||||
void setTimbre(double in);
|
void setPitchOffset(double in);
|
||||||
void setSampleRate(double oldRate, double newRate);
|
void setSampleRate(double oldRate, double newRate);
|
||||||
void setVolume(double in);
|
void setVolume(double in);
|
||||||
void shiftUp();
|
|
||||||
void shiftDown();
|
//For recordable performances.
|
||||||
|
//For now it requires a button push, but perhaps
|
||||||
|
//we should require time position and have the phase reset automatically
|
||||||
|
//on song start?
|
||||||
|
void resetPhase();
|
||||||
|
|
||||||
|
|
||||||
//Current fundamental frequency of blit.
|
//Current fundamental frequency of blit.
|
||||||
double hzFund = fMin;
|
double hzFund = fMin;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//Phase must persist between run invocation.
|
void shiftUp();
|
||||||
//Phase of each voice, considered a function on the unit circle.
|
void shiftDown();
|
||||||
std::array<double, NUM_VOICES> phases = {0};
|
//Phase of wavetable.
|
||||||
|
double tablePhase = 0.0;
|
||||||
float volume = 0.0f;
|
float volume = 0.0f;
|
||||||
//Parameter in unit circle controlling pitch (varies by one octave).
|
//Parameter in unit circle controlling pitch (varies by one octave).
|
||||||
double spectrumPhase = 1.0f;
|
double spectrumPhase = 1.0f;
|
||||||
|
@ -37,6 +37,9 @@ private:
|
||||||
//Lowest fundamental frequency of blit.
|
//Lowest fundamental frequency of blit.
|
||||||
double fMin = 55.0;
|
double fMin = 55.0;
|
||||||
|
|
||||||
|
//Parameter stretching the overtone series.
|
||||||
|
double inharmonicity = 1.001;
|
||||||
|
|
||||||
double hzNyq = 24000.0;
|
double hzNyq = 24000.0;
|
||||||
double sampleInterval = 1.0 / 48000.0;
|
double sampleInterval = 1.0 / 48000.0;
|
||||||
};
|
};
|
|
@ -177,7 +177,7 @@ void TabUI::onNanoDisplay()
|
||||||
beginPath();
|
beginPath();
|
||||||
fillColor(200, 200, 200);
|
fillColor(200, 200, 200);
|
||||||
textBox(0.f, 15.f, 250.f,
|
textBox(0.f, 15.f, 250.f,
|
||||||
std::format("Frequency: {:.3f}\nNearest: {}\n",
|
std::format("Frequency: {:.3f}\nNearest: {:.3f}\n",
|
||||||
hz, scale.getNearestNoteNumber(hz))
|
hz, scale.getNearestNoteNumber(hz))
|
||||||
.c_str(),
|
.c_str(),
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
START_NAMESPACE_DISTRHO
|
START_NAMESPACE_DISTRHO
|
||||||
|
|
||||||
class TabPlugin : public Plugin
|
class ShepPlug : public Plugin
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TabPlugin()
|
ShepPlug()
|
||||||
: Plugin(kParameterCount, 0, 0),
|
: Plugin(kParameterCount, 0, 0),
|
||||||
sampleRate(getSampleRate())
|
sampleRate(getSampleRate())
|
||||||
{
|
{
|
||||||
|
@ -124,12 +124,12 @@ private:
|
||||||
double sampleRate;
|
double sampleRate;
|
||||||
bool parity = false;
|
bool parity = false;
|
||||||
|
|
||||||
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabPlugin)
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ShepPlug)
|
||||||
};
|
};
|
||||||
|
|
||||||
Plugin* createPlugin()
|
Plugin* createPlugin()
|
||||||
{
|
{
|
||||||
return new TabPlugin();
|
return new ShepPlug();
|
||||||
}
|
}
|
||||||
|
|
||||||
END_NAMESPACE_DISTRHO
|
END_NAMESPACE_DISTRHO
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
|
|
||||||
START_NAMESPACE_DISTRHO
|
START_NAMESPACE_DISTRHO
|
||||||
|
|
||||||
class TabPlugin : public Plugin
|
class ShepPlug : public Plugin
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TabPlugin()
|
ShepPlug()
|
||||||
: Plugin(kParameterCount, 0, 0),
|
: Plugin(kParameterCount, 0, 0),
|
||||||
hzNyq(0.5 * getSampleRate())
|
hzNyq(0.5 * getSampleRate())
|
||||||
{
|
{
|
||||||
|
@ -101,12 +101,12 @@ private:
|
||||||
bool parity = false;
|
bool parity = false;
|
||||||
Filter filter;
|
Filter filter;
|
||||||
|
|
||||||
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabPlugin)
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ShepPlug)
|
||||||
};
|
};
|
||||||
|
|
||||||
Plugin* createPlugin()
|
Plugin* createPlugin()
|
||||||
{
|
{
|
||||||
return new TabPlugin();
|
return new ShepPlug();
|
||||||
}
|
}
|
||||||
|
|
||||||
END_NAMESPACE_DISTRHO
|
END_NAMESPACE_DISTRHO
|
||||||
|
|
|
@ -7,9 +7,7 @@ void Resonator::clear()
|
||||||
|
|
||||||
inline float Resonator::process(float x)
|
inline float Resonator::process(float x)
|
||||||
{
|
{
|
||||||
// Apply biquad filter.
|
float y = cx * x + cxp * xp - cypp * ypp - cyp * yp;
|
||||||
float y = scale * x - xpp - app * ypp + ap * yp;
|
|
||||||
// Update biquad memory.
|
|
||||||
xpp = xp;
|
xpp = xp;
|
||||||
xp = x;
|
xp = x;
|
||||||
ypp = yp;
|
ypp = yp;
|
||||||
|
@ -17,16 +15,18 @@ inline float Resonator::process(float x)
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resonator::set(double _app, double _ap, double _scale)
|
void Resonator::set(double _cx, double _cxp, double _cyp, double _cypp)
|
||||||
{
|
{
|
||||||
app = _app;
|
cx = _cx;
|
||||||
ap = _ap;
|
cxp = _cxp;
|
||||||
scale = _scale;
|
cyp = _cyp;
|
||||||
|
cypp = _cypp;
|
||||||
}
|
}
|
||||||
|
|
||||||
Filter::Filter(){};
|
static constexpr double sampleRate = 48000.0;
|
||||||
|
|
||||||
void Filter::process(float **outputs, const float **inputs, uint32_t frames)
|
template <uint N>
|
||||||
|
void ParallelResonators<N>::process(float **outputs, const float **inputs, uint32_t frames)
|
||||||
{
|
{
|
||||||
for (uint chn = 0; chn < 2; ++chn)
|
for (uint chn = 0; chn < 2; ++chn)
|
||||||
{
|
{
|
||||||
|
@ -38,22 +38,56 @@ void Filter::process(float **outputs, const float **inputs, uint32_t frames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Compute filter coefficients from
|
template <uint N>
|
||||||
// frequency as a proportion of the sample rate
|
void set(std::array<double, N> &angle, std::array<double, N> &radius)
|
||||||
// REMEMBER: THAT'S DOUBLE THE Nyquist RATE, dummy
|
|
||||||
// and resonance in [0, 1]
|
|
||||||
void Filter::set(double x, double y, double p)
|
|
||||||
{
|
{
|
||||||
double r = p * 0.049 + 0.95; // Filter unstable at r = 1.
|
|
||||||
double app = r * r;
|
//Get poles.
|
||||||
double xap = (1.0 + r * r) * cos(M_PI * x);
|
for ( uint i = 0; i < N; ++i )
|
||||||
double yap = (1.0 + r * r) * cos(M_PI * y);
|
|
||||||
double scale = sqrt(0.5 - 0.5 * r * r);
|
|
||||||
for (auto &channelFilter : res)
|
|
||||||
{
|
{
|
||||||
channelFilter[0].set(app, xap, scale);
|
radius[i] = exp(-M_PI * radius[i] / sampleRate);
|
||||||
channelFilter[1].set(app, yap, scale);
|
angle[i] = M_PI * 2.0 * angle[i] / sampleRate ;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
//Partial fraction expansion.
|
||||||
|
//Compute residue of each pole, then combine with complex conjugate
|
||||||
|
//to form a real second-order section.
|
||||||
|
//Pass this to the associated filter.
|
||||||
|
for ( uint i = 0; i < N; ++i )
|
||||||
|
{
|
||||||
|
|
||||||
|
//Residue
|
||||||
|
double x = 0.0;
|
||||||
|
double y = 0.0;
|
||||||
|
for (uint j = 0; j < N; ++j)
|
||||||
|
{
|
||||||
|
if ( i == j ) continue;
|
||||||
|
|
||||||
|
// 1 - p_j / p_i
|
||||||
|
double re = 1.0 - radius[j] * cos(angle[j] - angle[i]) / radius[i];
|
||||||
|
double im = 0.0 - radius[j] * sin(angle[j] - angle[i]) / radius[i];
|
||||||
|
|
||||||
|
// (x + iy) * (re + i im) = re * x - y * im
|
||||||
|
x = re * x - y * im;
|
||||||
|
y = im * x + y * re;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Pole
|
||||||
|
double u = radius[i] * cos(angle[i]);
|
||||||
|
double v = radius[i] * sin(angle[i]);
|
||||||
|
|
||||||
|
//Multiply complex conjugate pairs to get real biquad coefficients.
|
||||||
|
double cxp = -2.0 * (u * x + v * y);
|
||||||
|
double cx = 2.0 * x;
|
||||||
|
double cypp = radius[i] * radius[i];
|
||||||
|
double cyp = -2.0 * u;
|
||||||
|
|
||||||
|
//Save biquad coefs.
|
||||||
|
res[0][i].set(cx, cxp, cyp, cypp);
|
||||||
|
res[1][i].set(cx, cxp, cyp, cypp);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -5,21 +5,21 @@
|
||||||
class Resonator
|
class Resonator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Resonator():
|
|
||||||
xp(0), xpp(0), yp(0), ypp(0), app(0), ap(0), scale(1){};
|
|
||||||
|
|
||||||
inline float process(float x);
|
inline float process(float x);
|
||||||
void clear();
|
void clear();
|
||||||
void set(double _app, double _ap, double _scale);
|
void set(double, double, double, double);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float xp;
|
float xp = 0.0;
|
||||||
float xpp;
|
float xpp = 0.0;
|
||||||
float yp;
|
float yp = 0.0;
|
||||||
float ypp;
|
float ypp = 0.0;
|
||||||
double app;
|
|
||||||
double ap;
|
double cypp = 0.0;
|
||||||
double scale;
|
double cyp = 0.0;
|
||||||
|
double cxp = 0.0;
|
||||||
|
double cx = 1.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Filter
|
class Filter
|
||||||
|
@ -32,3 +32,19 @@ public:
|
||||||
private:
|
private:
|
||||||
std::array<std::array<Resonator, 2>, 2> res;
|
std::array<std::array<Resonator, 2>, 2> res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//Uncapped resonators
|
||||||
|
//Implemented in parallel
|
||||||
|
//Use partial fraction expansion to ensure constant numerator.
|
||||||
|
template< uint N >
|
||||||
|
class ParallelResonators
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void process(float **outputs, const float **inputs, uint32_t frames);
|
||||||
|
void set(std::array<double, N> &hz, std::array<double, N> &bw);
|
||||||
|
|
||||||
|
private:
|
||||||
|
double scale;
|
||||||
|
std::array<std::array<Resonator, N>, 2> res{};
|
||||||
|
};
|
Loading…
Reference in New Issue