Compare commits
No commits in common. "9a63041a5e0ff716aab2bff8617b8b663e158d80" and "518ba285b66a61d2fd25092b4cb6bdd10c0431f6" have entirely different histories.
9a63041a5e
...
518ba285b6
@ -8,7 +8,6 @@ add_subdirectory(external/magic_enum)
|
|||||||
add_subdirectory(external/argparse)
|
add_subdirectory(external/argparse)
|
||||||
add_subdirectory(external/log4cxx)
|
add_subdirectory(external/log4cxx)
|
||||||
|
|
||||||
|
|
||||||
file(GLOB_RECURSE sources src/*.cpp src/*.h)
|
file(GLOB_RECURSE sources src/*.cpp src/*.h)
|
||||||
file(GLOB_RECURSE data resources/*)
|
file(GLOB_RECURSE data resources/*)
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
#! /bin/sh
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
input_file="$1"
|
|
||||||
output_file="$2"
|
|
||||||
tempfile=$(mktemp /tmp/stereoXXXXXX.wav)
|
|
||||||
|
|
||||||
# Two step process:
|
|
||||||
# 1. Convert s16 PCM to a stereo wav file
|
|
||||||
# 2. Convert stereo wav file to mono
|
|
||||||
sox -r 44100 -e signed-integer -b 16 -c 2 ${input_file} ${tempfile}
|
|
||||||
sox ${tempfile} -c 1 ${output_file}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
from tones import SQUARE_WAVE, SINE_WAVE
|
from tones import SQUARE_WAVE
|
||||||
from tones.mixer import Mixer
|
from tones.mixer import Mixer
|
||||||
import csv
|
import csv
|
||||||
import math
|
import math
|
||||||
@ -14,8 +14,7 @@ def frequency_to_midi(frequency):
|
|||||||
f = 440 * 2 ^ (n - 69)/12
|
f = 440 * 2 ^ (n - 69)/12
|
||||||
'''
|
'''
|
||||||
note = 12 * (math.log(frequency/220)/math.log(2)) + 57
|
note = 12 * (math.log(frequency/220)/math.log(2)) + 57
|
||||||
#return round(note)
|
return round(note)
|
||||||
return note
|
|
||||||
|
|
||||||
tones = {}
|
tones = {}
|
||||||
with open('tones.csv', newline='') as csvfile:
|
with open('tones.csv', newline='') as csvfile:
|
||||||
@ -27,27 +26,6 @@ for tone in tones:
|
|||||||
print(f"{tone} is MIDI #{frequency_to_midi(tones[tone])}")
|
print(f"{tone} is MIDI #{frequency_to_midi(tones[tone])}")
|
||||||
|
|
||||||
mixer = Mixer(44100, 1)
|
mixer = Mixer(44100, 1)
|
||||||
mixer.create_track(0, SINE_WAVE)
|
mixer.create_track(0, SQUARE_WAVE)
|
||||||
mixer.add_tone(0, frequency=tones[tone], duration=1.0)
|
mixer.add_tone(0, frequency=tones[tone], duration=1.0)
|
||||||
mixer.write_wav(f'samples/{tone}.wav')
|
mixer.write_wav(f'samples/{tone}.wav')
|
||||||
|
|
||||||
|
|
||||||
mixer = Mixer(44100, 1)
|
|
||||||
mixer.create_track(0, SQUARE_WAVE)
|
|
||||||
mixer.add_tone(0, frequency=440, duration=1.0)
|
|
||||||
mixer.write_wav(f'samples/MIDI-69-square.wav')
|
|
||||||
|
|
||||||
mixer = Mixer(44100, 1)
|
|
||||||
mixer.create_track(0, SINE_WAVE)
|
|
||||||
mixer.add_tone(0, frequency=440, duration=1.0)
|
|
||||||
mixer.write_wav(f'samples/MIDI-69-sine.wav')
|
|
||||||
|
|
||||||
mixer = Mixer(44100, 1)
|
|
||||||
mixer.create_track(0, SQUARE_WAVE)
|
|
||||||
mixer.add_tone(0, frequency=261.6256, duration=1.0)
|
|
||||||
mixer.write_wav(f'samples/MIDI-60-square.wav')
|
|
||||||
|
|
||||||
mixer = Mixer(44100, 1)
|
|
||||||
mixer.create_track(0, SINE_WAVE)
|
|
||||||
mixer.add_tone(0, frequency=261.6256, duration=1.0)
|
|
||||||
mixer.write_wav(f'samples/MIDI-60-sine.wav')
|
|
||||||
@ -1,124 +0,0 @@
|
|||||||
#! /usr/bin/env python3
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import sys
|
|
||||||
import numpy as np
|
|
||||||
from scipy import signal
|
|
||||||
from scipy.io import wavfile
|
|
||||||
from scipy.signal import butter, lfilter
|
|
||||||
|
|
||||||
|
|
||||||
tones = {}
|
|
||||||
with open('tones.csv', newline='') as csvfile:
|
|
||||||
reader = csv.DictReader(csvfile)
|
|
||||||
for row in reader:
|
|
||||||
tones[row['designator']] = float(row['frequency'])
|
|
||||||
|
|
||||||
'''
|
|
||||||
def freq_of_key(midi_key):
|
|
||||||
return 440.0 * (2 ** ((midi_key - 69)/12))
|
|
||||||
|
|
||||||
tones = {}
|
|
||||||
for c in range(65, 90):
|
|
||||||
tones[c] = freq_of_key(c)
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Shamelessly lifted from
|
|
||||||
# https://scipy.github.io/old-wiki/pages/Cookbook/ButterworthBandpass
|
|
||||||
def butter_bandpass(lowcut, highcut, fs, order=5):
|
|
||||||
nyq = 0.5 * fs
|
|
||||||
low = lowcut / nyq
|
|
||||||
high = highcut / nyq
|
|
||||||
b, a = butter(order, [low, high], btype='band')
|
|
||||||
return b, a
|
|
||||||
|
|
||||||
def butter_bandpass_filter(data, lowcut, highcut, fs, order=5):
|
|
||||||
b, a = butter_bandpass(lowcut, highcut, fs, order=order)
|
|
||||||
y = lfilter(b, a, data)
|
|
||||||
return y
|
|
||||||
|
|
||||||
# tone synthesis
|
|
||||||
def note(freq, cycles, amp=32767.0, rate=44100):
|
|
||||||
len = cycles * (1.0/rate)
|
|
||||||
t = np.linspace(0, len, int(len * rate))
|
|
||||||
if freq == 0:
|
|
||||||
data = np.zeros(int(len * rate))
|
|
||||||
else:
|
|
||||||
data = np.sin(2 * np.pi * freq * t) * amp
|
|
||||||
return data.astype(int)
|
|
||||||
|
|
||||||
def decimate_from_sample_rate(sample_rate):
|
|
||||||
if sample_rate == 44100:
|
|
||||||
return 4 # rate = 11025, Fmax = 5512.5 Hz
|
|
||||||
elif sample_rate == 48000:
|
|
||||||
return 5 # rate = 9600, Fmax = 4800 Hz
|
|
||||||
elif sample_rate == 22050:
|
|
||||||
return 2 # rate = 11025, Fmax = 5512.5 Hz
|
|
||||||
elif sample_rate == 11025:
|
|
||||||
return 1 # rate = 11025, Fmax = 5512.5 Hz
|
|
||||||
else:
|
|
||||||
raise ValueError("Sample rate not supported")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# TODO JMT: What is this?
|
|
||||||
FLT_LEN = 2000
|
|
||||||
|
|
||||||
file_name = sys.argv[1]
|
|
||||||
sample_rate, data = wavfile.read(file_name)
|
|
||||||
|
|
||||||
print(f"{file_name}: {len(data)} samples @ {sample_rate} Hz")
|
|
||||||
|
|
||||||
decimate = decimate_from_sample_rate(sample_rate)
|
|
||||||
if decimate > 1:
|
|
||||||
data = signal.decimate(data, decimate)
|
|
||||||
sample_rate = sample_rate / decimate
|
|
||||||
|
|
||||||
print(f'Length after decimation: {len(data)} samples')
|
|
||||||
|
|
||||||
data = butter_bandpass_filter(data, 270, 1700, sample_rate, order=8)
|
|
||||||
|
|
||||||
pure_signals = {tone:note(freq, FLT_LEN, rate=sample_rate) for tone,freq in tones.items()}
|
|
||||||
correlations = {tone:np.abs(signal.correlate(data, pure, mode='same')) for tone,pure in pure_signals.items()}
|
|
||||||
|
|
||||||
N = FLT_LEN # Rolling average length
|
|
||||||
cumsum_convolution = np.ones(N)/N
|
|
||||||
massaged = {tone:np.convolve(correlation, cumsum_convolution, mode='valid') for tone,correlation in correlations.items()}
|
|
||||||
|
|
||||||
# Only import if we're actually plotting, these imports are pretty heavy.
|
|
||||||
import pyqtgraph as pg
|
|
||||||
from PyQt5 import QtWidgets
|
|
||||||
|
|
||||||
app = QtWidgets.QApplication([])
|
|
||||||
layout = pg.GraphicsLayoutWidget(show=True, title="SELCAL Tone Correlation")
|
|
||||||
layout.setGeometry(0, 0, 1600, 960)
|
|
||||||
|
|
||||||
plot = layout.addPlot(title=file_name)
|
|
||||||
legend_view = layout.addViewBox()
|
|
||||||
|
|
||||||
legend = pg.LegendItem(offset=(0, 0))
|
|
||||||
legend.setParentItem(legend_view)
|
|
||||||
|
|
||||||
color_map = pg.colormap.get('CET-C6s')
|
|
||||||
colors = color_map.getLookupTable(nPts=len(tones))
|
|
||||||
|
|
||||||
for (tone, correlation), color in zip(massaged.items(), colors):
|
|
||||||
line = plot.plot(correlation, pen=pg.mkPen(color=color), fillLevel=0.1, name=tone)
|
|
||||||
legend.addItem(line, tone)
|
|
||||||
|
|
||||||
y_max = max(line.getData()[1]) # Maximum y-value
|
|
||||||
x_max = line.getData()[0][np.argmax(line.getData()[1])] # Corresponding x-coordinate
|
|
||||||
|
|
||||||
label = pg.TextItem(html=f'<div style="text-align: center"><span style="color: #FFFFFF; font-size: 12pt;">{line.opts["name"]}</span></div>', anchor=(0.5, 0.5))
|
|
||||||
plot.addItem(label)
|
|
||||||
label.setPos(x_max, y_max)
|
|
||||||
label.setZValue(100) # Ensure label is above other items
|
|
||||||
|
|
||||||
plot.setLabel('left', 'Signal Correlation')
|
|
||||||
plot.setLabel('bottom', 'Time (samples)')
|
|
||||||
plot.showGrid(x=True, y=True)
|
|
||||||
|
|
||||||
legend_view.setFixedWidth(80)
|
|
||||||
layout.ci.layout.setColumnFixedWidth(1, 80)
|
|
||||||
|
|
||||||
app.exec_()
|
|
||||||
Binary file not shown.
Binary file not shown.
43
src/main.cpp
43
src/main.cpp
@ -7,12 +7,10 @@
|
|||||||
#include <argparse/argparse.hpp>
|
#include <argparse/argparse.hpp>
|
||||||
#include <log4cxx/logger.h>
|
#include <log4cxx/logger.h>
|
||||||
#include <log4cxx/basicconfigurator.h>
|
#include <log4cxx/basicconfigurator.h>
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
namespace {
|
|
||||||
// TODO JMT: Does fluidsynth define this itself?
|
// TODO JMT: Does fluidsynth define this itself?
|
||||||
constexpr int ALL_CHANNELS = -1;
|
constexpr int ALL_CHANNELS = -1;
|
||||||
constexpr double DEFAULT_TONE_DURATION = 1.0;
|
constexpr double DEFAULT_TONE_DURATION = 1.0;
|
||||||
@ -26,27 +24,24 @@ static auto logger = log4cxx::Logger::getLogger("tone-generator");
|
|||||||
* to exactly play the the SELCAL tones. Worse again, using the nearest standard MIDI note available
|
* to exactly play the the SELCAL tones. Worse again, using the nearest standard MIDI note available
|
||||||
* results in collisions for SELCAL tones.
|
* results in collisions for SELCAL tones.
|
||||||
* The way around this is to define a custom MIDI tuning. We're still limited to 128 frequencies,
|
* The way around this is to define a custom MIDI tuning. We're still limited to 128 frequencies,
|
||||||
* but we can make those frequencies anything we want.
|
* but we can make those frequencies anything we want. This method generates a tuning in the format
|
||||||
|
* that fluidsynth accepts which _only_ contains the SELCAL tones.
|
||||||
*/
|
*/
|
||||||
TuningFrequencies getSelcalFrequencies() {
|
Tuning getSelcalTunings() {
|
||||||
TuningFrequencies tunings;
|
Tuning tunings;
|
||||||
tunings.fill(0.0);
|
tunings.fill(0);
|
||||||
|
|
||||||
// As we only care about the SELCAL 32 tones, only tune those specific keys. We don't
|
|
||||||
// need to tune the full 128-key keyboard.
|
|
||||||
std::stringstream ss;
|
|
||||||
for (auto pair : SELCAL::KeyFrequencies) {
|
for (auto pair : SELCAL::KeyFrequencies) {
|
||||||
auto value = static_cast<int>(pair.first);
|
// fluidsynth wants tunings in MIDI cents, which effectively means MIDI key * 100.
|
||||||
tunings[value] = pair.second;
|
// As we only care about the SELCAL 32 tones, only tune those specific keys. We don't
|
||||||
ss << value << ":" << pair.second << ", ";
|
// need to tune the full 128-key keyboard.
|
||||||
|
constexpr double MIDI_CENT_SCALE = 100.0;
|
||||||
|
const int keyIndex = static_cast<int>(pair.first);
|
||||||
|
tunings[keyIndex] = frequencyToMidi(pair.second) * MIDI_CENT_SCALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG4CXX_DEBUG(logger, "SELCAL freqs {" << ss.str() << "}");
|
|
||||||
|
|
||||||
return tunings;
|
return tunings;
|
||||||
}
|
}
|
||||||
} // anonymous
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
@ -94,13 +89,11 @@ int main(int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle fluidsynth log messages before touching anything else in the lib
|
|
||||||
redirect_fluid_logs();
|
|
||||||
Synth synth;
|
Synth synth;
|
||||||
|
|
||||||
|
auto tunings = getSelcalTunings();
|
||||||
auto tuning = getSelcalFrequencies();
|
if (!synth.setTuning(tunings)) {
|
||||||
if (!synth.setTuning(tuning)) {
|
|
||||||
LOG4CXX_ERROR(logger, "Failed to set SELCAL tuning on synth");
|
LOG4CXX_ERROR(logger, "Failed to set SELCAL tuning on synth");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -134,11 +127,9 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
SELCAL::Code code{parser.get<std::string>("code")};
|
SELCAL::Code code{parser.get<std::string>("code")};
|
||||||
|
|
||||||
|
|
||||||
for (auto& group : code.getGroups()) {
|
for (auto& group : code.getGroups()) {
|
||||||
LOG4CXX_INFO(logger, "Playing MIDI " << static_cast<int>(group[0]) << " & " << static_cast<int>(group[1]));
|
fluid_synth_noteon(raw_synth, 0, static_cast<int>(group[0]), 127);
|
||||||
fluid_synth_noteon(raw_synth, 0, static_cast<int>(group[0]), 80);
|
fluid_synth_noteon(raw_synth, 1, static_cast<int>(group[1]), 127);
|
||||||
fluid_synth_noteon(raw_synth, 1, static_cast<int>(group[1]), 80);
|
|
||||||
std::this_thread::sleep_for(std::chrono::duration<double>(toneDuration));
|
std::this_thread::sleep_for(std::chrono::duration<double>(toneDuration));
|
||||||
|
|
||||||
// Silence between tone groups
|
// Silence between tone groups
|
||||||
@ -148,5 +139,5 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
// Wait long enough for the full tone to play before tearing down.
|
// Wait long enough for the full tone to play before tearing down.
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
//std::this_thread::sleep_for(500ms);
|
std::this_thread::sleep_for(500ms);
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/midi.h
23
src/midi.h
@ -1,26 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
// Applicable for standard (even temperament) MIDI tuning only.
|
double frequencyToMidi(double f) {
|
||||||
// The formula connecting the MIDI note number and the base frequency assume equal tuning based
|
return ((12 * std::log(f / 220.0) / std::log(2.0)) + 57.01);
|
||||||
// on A4 = 440 Hz.
|
|
||||||
constexpr int NUM_MIDI_KEYS = 128; // Also inclusive of 0 & 128, apparently.
|
|
||||||
constexpr double A4_FREQUENCY = 440.0; // Hz
|
|
||||||
constexpr int A4_MIDI_KEY = 69; // Hehe
|
|
||||||
constexpr int KEYS_PER_OCTAVE = 12;
|
|
||||||
|
|
||||||
// Template parameter allows for (non-standard) fractional keys.
|
|
||||||
template <typename T>
|
|
||||||
constexpr double MidiKeyToFrequency(T key) {
|
|
||||||
return A4_FREQUENCY * std::pow(2, (key - A4_MIDI_KEY) / KEYS_PER_OCTAVE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr double frequencyToMidiKey(double f) {
|
int frequencyToNearestMidi(double f) {
|
||||||
return ((KEYS_PER_OCTAVE * std::log2(f / A4_FREQUENCY)) + A4_MIDI_KEY);
|
return (int) frequencyToMidi(f);
|
||||||
}
|
|
||||||
|
|
||||||
constexpr int frequencyToNearestMidiKey(double f) {
|
|
||||||
return std::clamp(static_cast<int>(std::round(frequencyToMidiKey(f))), 0, NUM_MIDI_KEYS);
|
|
||||||
}
|
}
|
||||||
|
|||||||
106
src/synth.cpp
106
src/synth.cpp
@ -1,18 +1,6 @@
|
|||||||
#include "synth.h"
|
#include "synth.h"
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <sstream>
|
|
||||||
#include <log4cxx/logger.h>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
// Helpers for the C API stuff
|
|
||||||
constexpr int TRUE = 1;
|
|
||||||
constexpr int FALSE = 0;
|
|
||||||
|
|
||||||
static auto logger = log4cxx::Logger::getLogger("synth");
|
|
||||||
static auto fluidLogger = log4cxx::Logger::getLogger("synth.fluid");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Synth::Synth() {
|
Synth::Synth() {
|
||||||
try {
|
try {
|
||||||
@ -26,15 +14,6 @@ Synth::Synth() {
|
|||||||
throw new std::runtime_error("Unable to create fluid synth");
|
throw new std::runtime_error("Unable to create fluid synth");
|
||||||
}
|
}
|
||||||
|
|
||||||
fluid_settings_setstr(settings, "audio.driver", "file");
|
|
||||||
fluid_settings_setstr(settings, "audio.file.name", "output.raw");
|
|
||||||
fluid_settings_setstr(settings, "audio.file.format", "s16");
|
|
||||||
fluid_settings_setstr(settings, "audio.file.type", "raw");
|
|
||||||
fluid_settings_setnum(settings, "synth.sample-rate", 44100);
|
|
||||||
fluid_settings_setnum(settings, "synth.gain", 1.0);
|
|
||||||
//fluid_settings_setint(settings, "synth.chorus.active", 0);
|
|
||||||
//fluid_settings_setint(settings, "synth.reverb.active", 0);
|
|
||||||
|
|
||||||
adriver = new_fluid_audio_driver(settings, synth);
|
adriver = new_fluid_audio_driver(settings, synth);
|
||||||
if (adriver == nullptr) {
|
if (adriver == nullptr) {
|
||||||
throw new std::runtime_error("Unable to create audio driver");
|
throw new std::runtime_error("Unable to create audio driver");
|
||||||
@ -90,35 +69,33 @@ bool Synth::loadProgram(const Program& program) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Synth::setTuning(const TuningFrequencies& frequencies) {
|
bool Synth::setTuning(const std::array<double, NUM_MIDI_NOTES>& pitches) {
|
||||||
// fluidsynth wants tunings in MIDI cents, which effectively means MIDI key * 100.
|
int result = fluid_synth_activate_key_tuning(
|
||||||
// For example: normally note 0 is 0.0, 1 is 100.0, 60 is 6000.0, etc.
|
synth,
|
||||||
constexpr double MIDI_CENT_SCALE = 100.0;
|
0, 0,
|
||||||
std::array<double, NUM_MIDI_KEYS> pitchCents;
|
"default",
|
||||||
std::transform(
|
pitches.data(),
|
||||||
frequencies.begin(),
|
1
|
||||||
frequencies.end(),
|
|
||||||
pitchCents.begin(),
|
|
||||||
[] (double frequency) {
|
|
||||||
// Remap 0 Hz to MIDI 0, otherwise we get -inf which causes some interpolations between
|
|
||||||
// notes to result in zero sound.
|
|
||||||
return frequency == 0.0 ? 0.0 : frequencyToMidiKey(frequency) * MIDI_CENT_SCALE;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
std::stringstream ss;
|
if (result == FLUID_OK) {
|
||||||
for (auto p : pitchCents) {
|
result = fluid_synth_activate_tuning(synth, 0, 0, 0, 1);
|
||||||
ss << p << ", ";
|
result = fluid_synth_activate_tuning(synth, 1, 0, 0, 1);
|
||||||
}
|
}
|
||||||
LOG4CXX_DEBUG(logger, "Tuning pitches [" << ss.str() << "]");
|
|
||||||
|
|
||||||
auto result = fluid_synth_activate_key_tuning(synth, 0, 0, "default", pitchCents.data(), FALSE);
|
return result == FLUID_OK;
|
||||||
return (result == FLUID_OK) ? activateTuning() : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Synth::resetTuning() {
|
bool Synth::resetTuning() {
|
||||||
auto result = fluid_synth_activate_key_tuning(synth, 0, 0, "default", nullptr, 1);
|
int result = fluid_synth_activate_key_tuning(
|
||||||
return (result == FLUID_OK) ? activateTuning() : false;
|
synth,
|
||||||
|
0, 0,
|
||||||
|
"default",
|
||||||
|
nullptr,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
return result == FLUID_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<Synth::Program>& Synth::getPrograms() const {
|
const std::vector<Synth::Program>& Synth::getPrograms() const {
|
||||||
@ -128,46 +105,3 @@ const std::vector<Synth::Program>& Synth::getPrograms() const {
|
|||||||
fluid_synth_t* Synth::getSynth() {
|
fluid_synth_t* Synth::getSynth() {
|
||||||
return synth;
|
return synth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Synth::activateTuning() {
|
|
||||||
int channelIndex = 0;
|
|
||||||
int result;
|
|
||||||
|
|
||||||
do {
|
|
||||||
result = fluid_synth_activate_tuning(synth, channelIndex++, 0, 0, TRUE);
|
|
||||||
} while (result == FLUID_OK && channelIndex < NUM_MIDI_CHANNELS);
|
|
||||||
|
|
||||||
LOG4CXX_DEBUG(logger, "Tuning activation result: " << result);
|
|
||||||
|
|
||||||
return result == FLUID_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fluid logging redirector.
|
|
||||||
void fluid_log_function(int level, const char* message, void* data) {
|
|
||||||
switch (level) {
|
|
||||||
case FLUID_PANIC:
|
|
||||||
LOG4CXX_FATAL(fluidLogger, message);
|
|
||||||
break;
|
|
||||||
case FLUID_ERR:
|
|
||||||
LOG4CXX_ERROR(fluidLogger, message);
|
|
||||||
break;
|
|
||||||
case FLUID_WARN:
|
|
||||||
LOG4CXX_WARN(fluidLogger, message);
|
|
||||||
break;
|
|
||||||
case FLUID_INFO:
|
|
||||||
LOG4CXX_INFO(fluidLogger, message);
|
|
||||||
break;
|
|
||||||
case FLUID_DBG:
|
|
||||||
LOG4CXX_DEBUG(fluidLogger, message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void redirect_fluid_logs() {
|
|
||||||
fluid_set_log_function(FLUID_PANIC, fluid_log_function, NULL);
|
|
||||||
fluid_set_log_function(FLUID_ERR, fluid_log_function, NULL);
|
|
||||||
fluid_set_log_function(FLUID_WARN, fluid_log_function, NULL);
|
|
||||||
fluid_set_log_function(FLUID_INFO, fluid_log_function, NULL);
|
|
||||||
fluid_set_log_function(FLUID_DBG, fluid_log_function, NULL);
|
|
||||||
}
|
|
||||||
17
src/synth.h
17
src/synth.h
@ -5,13 +5,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include "midi.h"
|
constexpr int NUM_MIDI_NOTES = 128;
|
||||||
|
|
||||||
namespace {
|
typedef std::array<double, NUM_MIDI_NOTES> Tuning;
|
||||||
constexpr int NUM_MIDI_CHANNELS = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef std::array<double, NUM_MIDI_KEYS> TuningFrequencies;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C++ wrapper around the fluidsynth API.
|
* C++ wrapper around the fluidsynth API.
|
||||||
@ -36,7 +32,7 @@ public:
|
|||||||
bool loadSoundfont(const std::string& filepath);
|
bool loadSoundfont(const std::string& filepath);
|
||||||
bool loadProgram(const Program& program);
|
bool loadProgram(const Program& program);
|
||||||
|
|
||||||
bool setTuning(const TuningFrequencies& frequencies);
|
bool setTuning(const Tuning& pitches);
|
||||||
bool resetTuning();
|
bool resetTuning();
|
||||||
|
|
||||||
const std::vector<Program>& getPrograms() const;
|
const std::vector<Program>& getPrograms() const;
|
||||||
@ -45,7 +41,7 @@ public:
|
|||||||
fluid_synth_t* getSynth();
|
fluid_synth_t* getSynth();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool activateTuning();
|
|
||||||
|
|
||||||
std::vector<Soundfont> soundfonts;
|
std::vector<Soundfont> soundfonts;
|
||||||
std::vector<Program> programs;
|
std::vector<Program> programs;
|
||||||
@ -54,8 +50,3 @@ private:
|
|||||||
fluid_synth_t* synth = NULL;
|
fluid_synth_t* synth = NULL;
|
||||||
fluid_audio_driver_t* adriver = NULL;
|
fluid_audio_driver_t* adriver = NULL;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Fluid logging redirector.
|
|
||||||
void fluid_log_function(int level, const char* message, void* data);
|
|
||||||
void redirect_fluid_logs();
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user