Compare commits
5 Commits
cfc8cb2b51
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dd9bd5511e | |||
| c7432320fe | |||
| 5bb1bbe56c | |||
| 4643c681f3 | |||
| f4d855b36b |
@@ -10,7 +10,7 @@ Logger = (
|
|||||||
"Error"
|
"Error"
|
||||||
);
|
);
|
||||||
|
|
||||||
ShowTime = true;
|
ShowTime = false;
|
||||||
ShowSourceTrace = false;
|
ShowSourceTrace = false;
|
||||||
CoutEnabled = true;
|
CoutEnabled = true;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import numpy as np
|
|||||||
# www.halvorsen.blog/documents/programming/python/resources/powerpoints/State Space Models with Python.pdf
|
# www.halvorsen.blog/documents/programming/python/resources/powerpoints/State Space Models with Python.pdf
|
||||||
|
|
||||||
# simulation Parameters
|
# simulation Parameters
|
||||||
|
|
||||||
x0 = [0, 0]
|
x0 = [0, 0]
|
||||||
start = 0
|
start = 0
|
||||||
stop = 30
|
stop = 30
|
||||||
168
scripts/string_model_de.py
Normal file
168
scripts/string_model_de.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import math
|
||||||
|
|
||||||
|
# https://people.cs.uchicago.edu/~ridg/stabil/pianostring.pdf
|
||||||
|
# a differential equations model for a piano string
|
||||||
|
|
||||||
|
# eq 1: governing wave equation
|
||||||
|
# d2y/dt2 = c^2*d2y/dx2 - stiffness*c^2*L^2*d4y/dx4 - 2*b_1*dy/dt + 2*b_3*d3y/dt3 + f(x, x_0, t)
|
||||||
|
# where y = string's transverse displacement, b_1, b_3 = damping coefficients, f = force density
|
||||||
|
# c = sqrt(T/mu), transverse wave velocity , T = string tension, mu = string linear mass density)
|
||||||
|
|
||||||
|
# eq 2: string stiffness
|
||||||
|
# stiffness = K^2*(E*S/(T*L^2))
|
||||||
|
# where K = string's radius of gyration (r/2 for a circular string), E = string's Young's modulus,
|
||||||
|
# S = cross sectional string area, T = string tension, L = string length
|
||||||
|
|
||||||
|
# eq 3: decay
|
||||||
|
# sigma = 1/tau = b_1 + b_3*omega^2
|
||||||
|
# where sigma = decay rate, tau = decay time, omega = angular frequency
|
||||||
|
|
||||||
|
# eq 4: excitation
|
||||||
|
# f(x, x_0, t) = f_H(t) * g(x, x_0)
|
||||||
|
# where f_H(t) = hammer force, g(x, x_0) = hammer dimensional effect
|
||||||
|
|
||||||
|
# eq 5: hammer time history
|
||||||
|
# read the paper if you care, useful only for derivation
|
||||||
|
|
||||||
|
# eq 6: power law
|
||||||
|
# F_H(t) = K*|eta(t) - y(x_0, t)|^p
|
||||||
|
# where eta(t) is the transverse displacement of the hammer head
|
||||||
|
# and p = stiffness nonlinear exponent
|
||||||
|
|
||||||
|
# eq 7: hammer displacement
|
||||||
|
# M_H*d2eta/dt2 = -F_H(t)
|
||||||
|
# where M_H is the mass of the hamemr head
|
||||||
|
|
||||||
|
# eq 8: boundary conditions
|
||||||
|
# y(0, t) = y(L, t) = 0, fixed ends dont move
|
||||||
|
# d2y/dx2(0, t) = d2y/dx2(L, t) = 0, displacement along the string approaching the ends is continuous
|
||||||
|
|
||||||
|
# discrete time implementation
|
||||||
|
|
||||||
|
# eq 9: continuous to discrete
|
||||||
|
# y(x, t) -> y(x_i, t_n) -> y(i, n)
|
||||||
|
# divide the string into i segments and iterate over n timesteps
|
||||||
|
# where x_i = delta_x * i and t_n = delta_t * n
|
||||||
|
|
||||||
|
# eq 10: recurrence derivation
|
||||||
|
# y(i, n+1) = a_1*y(i, n) + a_2*y(i, n-1) + a_3*[y(i+1, n) + y(i-1, n)] + a_4*[y(i+2,n) + y(i-2,n)]
|
||||||
|
# + a_5*[y(i+1, n-1) + y(i-1, n-1) + y(i, n-2)]
|
||||||
|
# + [delta_t^2 * N*F_H(n) * g(i, i_0)]/M_S
|
||||||
|
# where a_1 through a_5 are defined as follows:
|
||||||
|
# a_1 = [2 - 2*r^2 + b_1/delta_t - 6*stiffness*N^2*r^2]/D
|
||||||
|
# a_2 = [-1 + b_1*delta_t + 2*b_3/delta_t]/D
|
||||||
|
# a_3 = [r^2*(1 + 4*stiffness*N^2)]/D
|
||||||
|
# a_4 = [b_3/delta_t - stiffness*N^2*r^2]/D
|
||||||
|
# a_5 = [-b_3/delta_t]/D
|
||||||
|
# where D = 1 + b_1*delta_t + 2*b_3/delta_t
|
||||||
|
# and r = c*delta_t/delta_x
|
||||||
|
|
||||||
|
# eq 11: stability condition
|
||||||
|
# N_max = sqrt{[-1 + sqrt(1+16*stiffness*gamma^2)]/(8*stiffness)}
|
||||||
|
# where
|
||||||
|
# eq 12: idk what gamma represents
|
||||||
|
# gamma = f_e/(2*f_1), f_e = sampling frequency and f_1 = fundamental frequency
|
||||||
|
# or
|
||||||
|
# eq 13: if neglecting stiffness
|
||||||
|
# N_max = gamma
|
||||||
|
|
||||||
|
# eq 14: rest condition
|
||||||
|
# y(i, 0) = 0
|
||||||
|
|
||||||
|
# eq 15: discrete hammer displacement
|
||||||
|
# at t = delta_t (n = 1)
|
||||||
|
# eta(1) = V_H_0 * delta_t
|
||||||
|
|
||||||
|
# eq 16: discrete truncated taylor series
|
||||||
|
# y(i, 1) = [y(i + 1, 0) + y(i - 1, 0)]/2
|
||||||
|
|
||||||
|
# eq 17: hammer force exertion
|
||||||
|
# F_H(1) = K*|eta(1) - y(i_0, 1)|^p
|
||||||
|
|
||||||
|
# eq 18: string displacement iteration
|
||||||
|
# y(i, 2) = y(i-1, 1)] + y(i + 1, 1) - y(i, 0) + [delta_t^2 * N*F_H(1) * g(i, i_0)]/M_S
|
||||||
|
|
||||||
|
# eq 19: hammer displacement iteration
|
||||||
|
# eta(2) = 2*eta(1) - eta(0) - [delta_t^2 * F_H(1)]/M_H
|
||||||
|
|
||||||
|
# eq 20: hammer force iteration
|
||||||
|
# F_H(2) = K*|eta(2) - y(i_0, 2)|^p
|
||||||
|
|
||||||
|
# eq 21: hammer rest condition
|
||||||
|
# eta(n + 1) < y(i_0, n + 1)
|
||||||
|
|
||||||
|
# eq 22: spacial boundary conditions
|
||||||
|
# y(0, n) = y(N, n) = 0
|
||||||
|
|
||||||
|
# eq 23: temporal boundary conditions
|
||||||
|
# y(-1, n) = -y(1, n)
|
||||||
|
# y(N + 1, n) = -y(N - 1, n)
|
||||||
|
|
||||||
|
# string parameters
|
||||||
|
E = 1 # youngs modulus
|
||||||
|
mu = 1 # linear mass density
|
||||||
|
kappa = 1 # radius of gyration
|
||||||
|
L = 1 # string length
|
||||||
|
M_S = mu*L # string mass
|
||||||
|
S = 1 # string cross sectional area
|
||||||
|
T = 1 # string tension
|
||||||
|
c = math.sqrt(T/mu) # transverse wave velocity
|
||||||
|
stiffness = 1 # string stiffness parameter
|
||||||
|
sigma = 1 # decay rate
|
||||||
|
tau = 1/sigma # decay time
|
||||||
|
omega = 1 # angular frequency
|
||||||
|
|
||||||
|
# hammer parameters
|
||||||
|
M_H = 1 # hammer mass
|
||||||
|
HSMR = M_H/M_S # hammer-mass string ratio
|
||||||
|
V_H_0 = 1 # initial hammer velocity at t=0
|
||||||
|
x_0 = 1 # distance of hammer from agraffe
|
||||||
|
alpha = x_0 / L # relative hammer striking position
|
||||||
|
|
||||||
|
# simulation parameters
|
||||||
|
f1 = 440 # fundamental frequency
|
||||||
|
f_e = 44100 # sampling frequency
|
||||||
|
N = 100 # number of string segments
|
||||||
|
delta_t = 1/f_e # time step
|
||||||
|
delta_x = L/N # spatial step
|
||||||
|
H = f_e * 10 # length of simulation in time
|
||||||
|
|
||||||
|
# empirical constants
|
||||||
|
b_1 = 1 # some constant
|
||||||
|
b_3 = 1 # some constant
|
||||||
|
K = 1 # hammer stiffness
|
||||||
|
p = 1 # stiffness nonlinear exponent
|
||||||
|
|
||||||
|
# derived components
|
||||||
|
D = 1 + b_1*delta_t + 2*b_3/delta_t
|
||||||
|
r = c*delta_t/delta_x
|
||||||
|
a_1 = (2 - 2*r**2 + b_1/delta_t - 6*stiffness*N**2*r**2)/D
|
||||||
|
a_2 = (-1 + b_1*delta_t + 2*b_3/delta_t)/D
|
||||||
|
a_3 = (r**2*(1 + 4*stiffness*N**2))/D
|
||||||
|
a_4 = (b_3/delta_t - stiffness*N**2*r**2)/D
|
||||||
|
a_5 = (-b_3/delta_t)/D
|
||||||
|
|
||||||
|
|
||||||
|
x = [0] * N # current string position
|
||||||
|
last_x1 = [0] * N # string position from last timestep
|
||||||
|
last_x2 = [0] * N # string position from two timesteps ago
|
||||||
|
x_next = [0] * N # buffer for next string position
|
||||||
|
|
||||||
|
x_out = np.zeros(H) # taking this as the sound output at some artibraty point along the string
|
||||||
|
t = np.arange(0, H/f_e, delta_t)
|
||||||
|
|
||||||
|
for n in range(H): # time
|
||||||
|
|
||||||
|
for i in range(N): # space
|
||||||
|
x_out[n] = math.sin(n/10000)
|
||||||
|
|
||||||
|
# plotting
|
||||||
|
plt.plot(t, x_out)
|
||||||
|
plt.title("Step Response")
|
||||||
|
plt.xlabel("t")
|
||||||
|
plt.ylabel("y")
|
||||||
|
plt.grid()
|
||||||
|
plt.show()
|
||||||
82
scripts/string_model_ss.py
Normal file
82
scripts/string_model_ss.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
|
||||||
|
import scipy.signal as sig
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import math
|
||||||
|
|
||||||
|
# simple first order step response simulation
|
||||||
|
# www.halvorsen.blog/documents/programming/python/resources/powerpoints/State Space Models with Python.pdf
|
||||||
|
|
||||||
|
# simulation Parameters
|
||||||
|
|
||||||
|
# string pde (wave equation)
|
||||||
|
# rho*A*d2y(x,t)/dt2 + c*dy(x,t)/dt - T*d2y(x,t)/dx2 = f(x,t)
|
||||||
|
# where rho*A = 1 dimensional string density, c = string damping, T = string tension, y(x,t) = displacement, f(x,t) = exitation force
|
||||||
|
|
||||||
|
# assume fixed ends: y(0, t) = y(L, t) = 0
|
||||||
|
|
||||||
|
# displacement y(x,t) can be represented as the sum from n=1 to N of qn(t)*sin(n*pi*x/L) where q_n(t) is the modal coordinate of mode n (displacement is the sum of all resonating modes)
|
||||||
|
# therefore:
|
||||||
|
# q..n + 2*zeta_n*omega_n*q.n + omega_n2*q_n = b_n*u(t)
|
||||||
|
# where omega_n = n*pi/L * sqrt(T/(rho*A)) for an ideal string
|
||||||
|
# and b_n = sin(n*pi*x_h/L) but assuming the hammer strikes at midpoint, x_h = L/2 ( b_n = sin(n*pi/2) )
|
||||||
|
|
||||||
|
# state space representation:
|
||||||
|
# each mode:
|
||||||
|
#「 q_n_dot | =「 0 1 | 「 q_n | + 「 0 |* u
|
||||||
|
# L q_n_dot_dot 」= L -omega_n^2 -2*zeta_n*omega_n 」 L q_ndot 」 L b_n 」
|
||||||
|
# and x_dot = Ax + Bu
|
||||||
|
# where A = diagonal matrix of A_n and B = [ 0 b_1 0 b_2 ... b_n]^T
|
||||||
|
|
||||||
|
# lets start with 3 nodes where the string is tuned to 440hz (we'll get to arbitrary modes evantually)
|
||||||
|
f_1 = 440 # fundamental frequency
|
||||||
|
def f_n(n):
|
||||||
|
return f_1 * n
|
||||||
|
def omega_n(n):
|
||||||
|
return 2*math.pi*f_n(n) # a cooler option would be omega_n = c*n*omega_1*sqrt(1+B*n^2) to factor in string stiffness to its vibration mode
|
||||||
|
|
||||||
|
# x = [ q1, q1dot, q2, q2dot, q3, q3dot ]^T < --state vector
|
||||||
|
omega_1 = omega_n(1)
|
||||||
|
omega_2 = omega_n(2)
|
||||||
|
omega_3 = omega_n(3)
|
||||||
|
zeta_1 = 0.0001 # i guessed
|
||||||
|
zeta_2 = 2 * zeta_1
|
||||||
|
zeta_3 = 3 * zeta_1
|
||||||
|
A = [
|
||||||
|
[ 0, 1, 0, 0, 0, 0],
|
||||||
|
[-omega_1**2, -2*zeta_1*omega_1, 0, 0, 0, 0],
|
||||||
|
[ 0, 0, 0, 1, 0, 0],
|
||||||
|
[ 0, 0, -omega_2**2, -2*zeta_2*omega_2, 0, 0],
|
||||||
|
[ 0, 0, 0, 0, 0, 1],
|
||||||
|
[ 0, 0, 0, 0, -omega_3**2, -2*zeta_3*omega_3]
|
||||||
|
] # isnt this formatting gorgeous
|
||||||
|
|
||||||
|
B = [ [0], [0.707], [0], [0], [0], [-0.707] ]
|
||||||
|
c_1 = 0.001
|
||||||
|
c_2 = c_1 / 2
|
||||||
|
c_3 = c_1 / 3
|
||||||
|
C = [[-c_1*omega_1**2, -2*c_1*zeta_1*omega_1, -c_2*omega_2**2, -2*c_2*zeta_2*omega_2, -c_3*omega_3**2, -2*c_3*zeta_3*omega_3]]
|
||||||
|
D = c_1 * 0.707 + c_2 * 0 + c_3 * (-0.707)
|
||||||
|
|
||||||
|
# input
|
||||||
|
#v_h = 100 # hammer velocity
|
||||||
|
#u = v_h * impulse(t)
|
||||||
|
|
||||||
|
x0 = [[0], [0], [0], [0], [0], [0]]
|
||||||
|
start = 0
|
||||||
|
stop = 10
|
||||||
|
step = 1/44100
|
||||||
|
|
||||||
|
t = np.arange(start,stop,step)
|
||||||
|
sys = sig.StateSpace(A, B, C, D)
|
||||||
|
|
||||||
|
# step Response
|
||||||
|
t, y = sig.impulse(sys, T=t)
|
||||||
|
|
||||||
|
# plotting
|
||||||
|
plt.plot(t, y)
|
||||||
|
plt.title("Step Response")
|
||||||
|
plt.xlabel("t")
|
||||||
|
plt.ylabel("y")
|
||||||
|
plt.grid()
|
||||||
|
plt.show()
|
||||||
@@ -22,8 +22,8 @@ int main(int argc, char* argv[]) {
|
|||||||
// create app objects
|
// create app objects
|
||||||
ConfigService config = ConfigService("config/sonobulus.cfg");
|
ConfigService config = ConfigService("config/sonobulus.cfg");
|
||||||
LoggerService logger = LoggerService(&config, "Engine");
|
LoggerService logger = LoggerService(&config, "Engine");
|
||||||
NoteQueue queue = NoteQueue();
|
NoteQueue queue = NoteQueue(&config, &logger);
|
||||||
ScopeBuffer scopeBuffer = ScopeBuffer(512);
|
ScopeBuffer scopeBuffer = ScopeBuffer(&config, &logger, 2048);
|
||||||
KeyboardController keyboard(&config, &logger, &queue);
|
KeyboardController keyboard(&config, &logger, &queue);
|
||||||
MidiController midi(&config, &logger, &queue);
|
MidiController midi(&config, &logger, &queue);
|
||||||
Synth synth(&config, &logger, &scopeBuffer, &queue);
|
Synth synth(&config, &logger, &scopeBuffer, &queue);
|
||||||
@@ -43,7 +43,7 @@ int main(int argc, char* argv[]) {
|
|||||||
engine.load(QUrl::fromLocalFile("ui/Main.qml")); // ugh
|
engine.load(QUrl::fromLocalFile("ui/Main.qml")); // ugh
|
||||||
|
|
||||||
if(engine.rootObjects().isEmpty()) {
|
if(engine.rootObjects().isEmpty()) {
|
||||||
std::cout << "engine is empty" << std::endl;
|
logger.log("Main", LogFlag::Error, "Engine is empty.");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,8 @@
|
|||||||
AudioEngine::AudioEngine(ConfigService* config, LoggerService* logger, Synth* synth) : config_(config), logger_(logger), synth_(synth) {
|
AudioEngine::AudioEngine(ConfigService* config, LoggerService* logger, Synth* synth) : config_(config), logger_(logger), synth_(synth) {
|
||||||
|
|
||||||
if(audioDevice_.getDeviceCount() < 1) {
|
if(audioDevice_.getDeviceCount() < 1) {
|
||||||
std::cout << "No audio devices found" << std::endl;
|
logger_->log("Audio", LogFlag::Error, "No audio devices found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(logger_ == nullptr) std::cout << "err: logger nullptr" << std::endl;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioEngine::~AudioEngine() {
|
AudioEngine::~AudioEngine() {
|
||||||
@@ -33,13 +30,13 @@ bool AudioEngine::start() {
|
|||||||
|
|
||||||
RtAudioErrorType status = audioDevice_.openStream(¶ms, nullptr, RTAUDIO_FLOAT32, sampleRate_, &bufferFrames_, &AudioEngine::audioCallback, this, &options);
|
RtAudioErrorType status = audioDevice_.openStream(¶ms, nullptr, RTAUDIO_FLOAT32, sampleRate_, &bufferFrames_, &AudioEngine::audioCallback, this, &options);
|
||||||
if(status != RTAUDIO_NO_ERROR) {
|
if(status != RTAUDIO_NO_ERROR) {
|
||||||
std::cout << "Error opening RtAudio stream" << std::endl;
|
logger_->log("Audio", LogFlag::Error, "Error opening RtAudio stream.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = audioDevice_.startStream();
|
status = audioDevice_.startStream();
|
||||||
if(status != RTAUDIO_NO_ERROR) {
|
if(status != RTAUDIO_NO_ERROR) {
|
||||||
std::cout << "Error starting RtAudio stream" << std::endl;
|
logger_->log("Audio", LogFlag::Error, "Error starting RtAudio stream.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,17 +30,19 @@ float Instrument::process(bool& scopeTrigger) {
|
|||||||
if(active_ && envelope_ < 1.0f) envelope_ += 0.01f;
|
if(active_ && envelope_ < 1.0f) envelope_ += 0.01f;
|
||||||
if(!active_ && envelope_ > 0.0f) envelope_ -= 0.0004f;
|
if(!active_ && envelope_ > 0.0f) envelope_ -= 0.0004f;
|
||||||
|
|
||||||
|
if(!isActive()) return 0.0f;
|
||||||
|
|
||||||
phase_ += phaseIncrement_;
|
phase_ += phaseIncrement_;
|
||||||
if(phase_ > 2.0f * pi) {
|
if(phase_ > 2.0f * pi) {
|
||||||
phase_ -= 2.0f * pi;
|
phase_ -= 2.0f * pi;
|
||||||
scopeTrigger = true;
|
scopeTrigger = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!isActive()) return 0.0f;
|
|
||||||
|
|
||||||
// float sample = sin(phase_);
|
// float sample = sin(phase_);
|
||||||
float sample = phase_ / pi - 1.0f; // saw
|
targetSample = phase_ / pi - 1.0f; // saw
|
||||||
|
|
||||||
return sample * envelope_;
|
currentSample = (1.0f - responsiveness_) * currentSample + responsiveness_ * targetSample;
|
||||||
|
|
||||||
|
return currentSample * envelope_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,4 +34,8 @@ private:
|
|||||||
float phaseIncrement_ = 0.0f;
|
float phaseIncrement_ = 0.0f;
|
||||||
float envelope_ = 0.0f;
|
float envelope_ = 0.0f;
|
||||||
|
|
||||||
|
float targetSample = 0.0f;
|
||||||
|
float currentSample = 0.0f;
|
||||||
|
static constexpr float responsiveness_ = 0.1f;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ MidiController::MidiController(ConfigService* config, LoggerService* logger, Not
|
|||||||
#endif
|
#endif
|
||||||
midiIn_->ignoreTypes(false, false, false);
|
midiIn_->ignoreTypes(false, false, false);
|
||||||
} catch (RtMidiError& e) {
|
} catch (RtMidiError& e) {
|
||||||
std::cout << "RtMidi init failed: " << e.getMessage() << std::endl;
|
std::string msg = "RtMidi init failed: " + e.getMessage();
|
||||||
|
logger_->log("MIDI", LogFlag::Warning, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
openDefaultPort();
|
openDefaultPort();
|
||||||
@@ -25,18 +26,21 @@ MidiController::~MidiController() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// open the first for thats successful
|
// open the first for thats successful
|
||||||
|
// we could also add an option in the config for a preferred port name
|
||||||
bool MidiController::openDefaultPort() {
|
bool MidiController::openDefaultPort() {
|
||||||
if (!midiIn_) return false;
|
if (!midiIn_) return false;
|
||||||
if (midiIn_->getPortCount() == 0) {
|
if (midiIn_->getPortCount() == 0) {
|
||||||
std::cout << "No MIDI input ports available" << std::endl;
|
logger_->log("MIDI", LogFlag::Warning, "No MIDI input ports available.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: the ui will eventually need this class to expose the available midi ports so they can be chosen dynamically
|
||||||
uint32_t portCount = midiIn_->getPortCount();
|
uint32_t portCount = midiIn_->getPortCount();
|
||||||
std::cout << "Available MidiIn ports: " << portCount << std::endl;
|
std::string msg = "Available MidiIn ports: " + std::to_string(portCount);
|
||||||
|
logger_->log("MIDI", LogFlag::Info, msg);
|
||||||
for (int i = 0; i < portCount; i++) {
|
for (int i = 0; i < portCount; i++) {
|
||||||
std::cout << "#" << i << " : " << midiIn_->getPortName(i) << std::endl;
|
msg = "\t#" + std::to_string(i) + " : " + midiIn_->getPortName(i);
|
||||||
|
logger_->log("MIDI", LogFlag::Info, msg);
|
||||||
if(openPort(i)) return true;
|
if(openPort(i)) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,11 +53,13 @@ bool MidiController::openPort(unsigned int index) {
|
|||||||
try {
|
try {
|
||||||
midiIn_->openPort(index);
|
midiIn_->openPort(index);
|
||||||
midiIn_->setCallback(&MidiController::midiCallback, this);
|
midiIn_->setCallback(&MidiController::midiCallback, this);
|
||||||
std::cout << "Opened MIDI port: " << midiIn_->getPortName(index) << std::endl;
|
|
||||||
|
std::string msg = "Opened MIDI port: " + midiIn_->getPortName(index);
|
||||||
|
logger_->log("MIDI", LogFlag::Info, msg);
|
||||||
return true;
|
return true;
|
||||||
} catch (RtMidiError& e) {
|
} catch (RtMidiError& e) {
|
||||||
std::cout << "Midi Port error" << std::endl;
|
std::string msg = "Midi Port error: " + e.getMessage();
|
||||||
std::cerr << e.getMessage() << std::endl;
|
logger_->log("MIDI", LogFlag::Error, msg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,6 +88,18 @@ void MidiController::handleMessage(const std::vector<unsigned char>& msg) {
|
|||||||
if(status == 0xFE) return; // "Active Sensing" -> 300ms heartbeat. could be useful to sense if this is missing for device failure detection
|
if(status == 0xFE) return; // "Active Sensing" -> 300ms heartbeat. could be useful to sense if this is missing for device failure detection
|
||||||
if(status == 0xF8) return; // "Timing Clock" -> 24 pulses per quarter note, for steady rhythm. not useful for this instrument
|
if(status == 0xF8) return; // "Timing Clock" -> 24 pulses per quarter note, for steady rhythm. not useful for this instrument
|
||||||
|
|
||||||
|
if(status == 0xB0) { // channel mode change
|
||||||
|
if((data1 & 0xF0) == 0x70) { // all notes off for this channel
|
||||||
|
for(uint8_t i = 0; i < UINT8_MAX; i++) {
|
||||||
|
noteOff(i);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: msg[0] contains the channel to turn all notes off for
|
||||||
|
// since the current implementation is channel agnostic, we just turn all notes off
|
||||||
|
// eventually we might have noteOff(channelLookup(msg[0]), i);
|
||||||
|
}
|
||||||
|
|
||||||
// sustain pedal message event
|
// sustain pedal message event
|
||||||
if(status == 0xB0 && data1 == 64) {
|
if(status == 0xB0 && data1 == 64) {
|
||||||
handleSustain(data2 >= 64);
|
handleSustain(data2 >= 64);
|
||||||
|
|||||||
@@ -2,6 +2,15 @@
|
|||||||
#include "NoteQueue.hpp"
|
#include "NoteQueue.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
NoteQueue::NoteQueue() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteQueue::NoteQueue(ConfigService* config, LoggerService* logger) :
|
||||||
|
config_(config), logger_(logger) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// add event to noteQueue, called by MidiController or keyboardController
|
// add event to noteQueue, called by MidiController or keyboardController
|
||||||
bool NoteQueue::push(const NoteEvent& event) {
|
bool NoteQueue::push(const NoteEvent& event) {
|
||||||
size_t head = head_.load(std::memory_order_relaxed);
|
size_t head = head_.load(std::memory_order_relaxed);
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "ConfigService.hpp"
|
||||||
|
#include "LoggerService.hpp"
|
||||||
|
|
||||||
enum NoteEventType {
|
enum NoteEventType {
|
||||||
NoteOn = 0,
|
NoteOn = 0,
|
||||||
NoteOff
|
NoteOff
|
||||||
@@ -22,7 +25,8 @@ struct NoteEvent {
|
|||||||
class NoteQueue {
|
class NoteQueue {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NoteQueue() = default;
|
NoteQueue();
|
||||||
|
NoteQueue(ConfigService* config, LoggerService* logger);
|
||||||
~NoteQueue() = default;
|
~NoteQueue() = default;
|
||||||
|
|
||||||
bool push(const NoteEvent& event);
|
bool push(const NoteEvent& event);
|
||||||
@@ -30,7 +34,10 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static constexpr size_t SYNTH_NOTE_QUEUE_SIZE = 128;
|
ConfigService* config_;
|
||||||
|
LoggerService* logger_;
|
||||||
|
|
||||||
|
static constexpr size_t SYNTH_NOTE_QUEUE_SIZE = 128; // TODO: config
|
||||||
|
|
||||||
std::array<NoteEvent, SYNTH_NOTE_QUEUE_SIZE> buffer_;
|
std::array<NoteEvent, SYNTH_NOTE_QUEUE_SIZE> buffer_;
|
||||||
std::atomic<size_t> head_{ 0 };
|
std::atomic<size_t> head_{ 0 };
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ ScopeBuffer::ScopeBuffer(QObject *parent) : QObject(parent) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScopeBuffer::ScopeBuffer(size_t size) : buffer_(size) {
|
ScopeBuffer::ScopeBuffer(ConfigService* config, LoggerService* logger, size_t size) :
|
||||||
|
config_(config), logger_(logger), buffer_(size) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "ConfigService.hpp"
|
||||||
|
#include "LoggerService.hpp"
|
||||||
|
|
||||||
class ScopeBuffer : public QObject {
|
class ScopeBuffer : public QObject {
|
||||||
|
|
||||||
Q_OBJECT // needed to attach to a qml component
|
Q_OBJECT // needed to attach to a qml component
|
||||||
@@ -15,7 +18,7 @@ class ScopeBuffer : public QObject {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
explicit ScopeBuffer(QObject* parent = nullptr);
|
explicit ScopeBuffer(QObject* parent = nullptr);
|
||||||
ScopeBuffer(size_t size);
|
ScopeBuffer(ConfigService* config, LoggerService* logger, size_t size);
|
||||||
~ScopeBuffer() = default;
|
~ScopeBuffer() = default;
|
||||||
|
|
||||||
void push(float sample);
|
void push(float sample);
|
||||||
@@ -31,6 +34,9 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
ConfigService* config_;
|
||||||
|
LoggerService* logger_;
|
||||||
|
|
||||||
std::vector<float> buffer_;
|
std::vector<float> buffer_;
|
||||||
std::atomic<size_t> writeIndex_{0};
|
std::atomic<size_t> writeIndex_{0};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user