Fixed weird stuff about tones not registering properly - was an audio conversion stereo/mono bug
This commit is contained in:
parent
ec52422955
commit
9a63041a5e
@ -4,5 +4,10 @@ set -eux
|
||||
|
||||
input_file="$1"
|
||||
output_file="$2"
|
||||
tempfile=$(mktemp /tmp/stereoXXXXXX.wav)
|
||||
|
||||
sox -r 44100 -e signed-integer -b 16 -c 1 ${input_file} ${output_file}
|
||||
# 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}
|
||||
|
||||
Binary file not shown.
49
src/main.cpp
49
src/main.cpp
@ -7,10 +7,12 @@
|
||||
#include <argparse/argparse.hpp>
|
||||
#include <log4cxx/logger.h>
|
||||
#include <log4cxx/basicconfigurator.h>
|
||||
#include <sstream>
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
namespace {
|
||||
// TODO JMT: Does fluidsynth define this itself?
|
||||
constexpr int ALL_CHANNELS = -1;
|
||||
constexpr double DEFAULT_TONE_DURATION = 1.0;
|
||||
@ -24,24 +26,27 @@ static auto logger = log4cxx::Logger::getLogger("tone-generator");
|
||||
* to exactly play the the SELCAL tones. Worse again, using the nearest standard MIDI note available
|
||||
* results in collisions for SELCAL tones.
|
||||
* 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. This method generates a tuning in the format
|
||||
* that fluidsynth accepts which _only_ contains the SELCAL tones.
|
||||
* but we can make those frequencies anything we want.
|
||||
*/
|
||||
Tuning getSelcalTunings() {
|
||||
Tuning tunings;
|
||||
tunings.fill(0);
|
||||
TuningFrequencies getSelcalFrequencies() {
|
||||
TuningFrequencies tunings;
|
||||
tunings.fill(0.0);
|
||||
|
||||
for (auto pair : SELCAL::KeyFrequencies) {
|
||||
// fluidsynth wants tunings in MIDI cents, which effectively means MIDI key * 100.
|
||||
// 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.
|
||||
constexpr double MIDI_CENT_SCALE = 100.0;
|
||||
const int keyIndex = static_cast<int>(pair.first);
|
||||
tunings[keyIndex] = frequencyToMidi(pair.second) * MIDI_CENT_SCALE;
|
||||
std::stringstream ss;
|
||||
for (auto pair : SELCAL::KeyFrequencies) {
|
||||
auto value = static_cast<int>(pair.first);
|
||||
tunings[value] = pair.second;
|
||||
ss << value << ":" << pair.second << ", ";
|
||||
}
|
||||
|
||||
LOG4CXX_DEBUG(logger, "SELCAL freqs {" << ss.str() << "}");
|
||||
|
||||
return tunings;
|
||||
}
|
||||
} // anonymous
|
||||
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@ -89,12 +94,13 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// Handle fluidsynth log messages before touching anything else in the lib
|
||||
redirect_fluid_logs();
|
||||
Synth synth;
|
||||
|
||||
|
||||
auto tunings = getSelcalTunings();
|
||||
if (!synth.setTuning(tunings)) {
|
||||
auto tuning = getSelcalFrequencies();
|
||||
if (!synth.setTuning(tuning)) {
|
||||
LOG4CXX_ERROR(logger, "Failed to set SELCAL tuning on synth");
|
||||
return 1;
|
||||
}
|
||||
@ -130,10 +136,9 @@ int main(int argc, char** argv) {
|
||||
|
||||
|
||||
for (auto& group : code.getGroups()) {
|
||||
int midi = static_cast<int>(group[0]);
|
||||
std::cout << "Playing MIDI " << midi << std::endl;
|
||||
fluid_synth_noteon(raw_synth, 0, static_cast<int>(group[0]), 127);
|
||||
fluid_synth_noteon(raw_synth, 1, static_cast<int>(group[1]), 127);
|
||||
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]), 80);
|
||||
fluid_synth_noteon(raw_synth, 1, static_cast<int>(group[1]), 80);
|
||||
std::this_thread::sleep_for(std::chrono::duration<double>(toneDuration));
|
||||
|
||||
// Silence between tone groups
|
||||
@ -141,16 +146,6 @@ int main(int argc, char** argv) {
|
||||
std::this_thread::sleep_for(std::chrono::duration<double>(silenceDuration));
|
||||
}
|
||||
|
||||
/*
|
||||
for (int i = 65; i < 90; ++i) {
|
||||
fluid_synth_noteon(raw_synth, 0, i, 80); // Soundfont root key is TWELVE OUT??
|
||||
std::this_thread::sleep_for(std::chrono::duration<double>(0.1));
|
||||
|
||||
// Silence between tone groups
|
||||
fluid_synth_all_notes_off(raw_synth, ALL_CHANNELS);
|
||||
std::this_thread::sleep_for(std::chrono::duration<double>(0.01));
|
||||
}*/
|
||||
|
||||
// Wait long enough for the full tone to play before tearing down.
|
||||
using namespace std::chrono_literals;
|
||||
//std::this_thread::sleep_for(500ms);
|
||||
|
||||
23
src/midi.h
23
src/midi.h
@ -1,11 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
double frequencyToMidi(double f) {
|
||||
return ((12 * std::log(f / 220.0) / std::log(2.0)) + 57.01);
|
||||
// Applicable for standard (even temperament) MIDI tuning only.
|
||||
// The formula connecting the MIDI note number and the base frequency assume equal tuning based
|
||||
// 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);
|
||||
}
|
||||
|
||||
int frequencyToNearestMidi(double f) {
|
||||
return (int) frequencyToMidi(f);
|
||||
constexpr double frequencyToMidiKey(double f) {
|
||||
return ((KEYS_PER_OCTAVE * std::log2(f / A4_FREQUENCY)) + A4_MIDI_KEY);
|
||||
}
|
||||
|
||||
constexpr int frequencyToNearestMidiKey(double f) {
|
||||
return std::clamp(static_cast<int>(std::round(frequencyToMidiKey(f))), 0, NUM_MIDI_KEYS);
|
||||
}
|
||||
|
||||
101
src/synth.cpp
101
src/synth.cpp
@ -1,6 +1,18 @@
|
||||
#include "synth.h"
|
||||
|
||||
#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() {
|
||||
try {
|
||||
@ -20,8 +32,8 @@ Synth::Synth() {
|
||||
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);
|
||||
//fluid_settings_setint(settings, "synth.chorus.active", 0);
|
||||
//fluid_settings_setint(settings, "synth.reverb.active", 0);
|
||||
|
||||
adriver = new_fluid_audio_driver(settings, synth);
|
||||
if (adriver == nullptr) {
|
||||
@ -78,33 +90,35 @@ bool Synth::loadProgram(const Program& program) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Synth::setTuning(const std::array<double, NUM_MIDI_NOTES>& pitches) {
|
||||
int result = fluid_synth_activate_key_tuning(
|
||||
synth,
|
||||
0, 0,
|
||||
"default",
|
||||
pitches.data(),
|
||||
1
|
||||
bool Synth::setTuning(const TuningFrequencies& frequencies) {
|
||||
// fluidsynth wants tunings in MIDI cents, which effectively means MIDI key * 100.
|
||||
// For example: normally note 0 is 0.0, 1 is 100.0, 60 is 6000.0, etc.
|
||||
constexpr double MIDI_CENT_SCALE = 100.0;
|
||||
std::array<double, NUM_MIDI_KEYS> pitchCents;
|
||||
std::transform(
|
||||
frequencies.begin(),
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
if (result == FLUID_OK) {
|
||||
result = fluid_synth_activate_tuning(synth, 0, 0, 0, 1);
|
||||
result = fluid_synth_activate_tuning(synth, 1, 0, 0, 1);
|
||||
std::stringstream ss;
|
||||
for (auto p : pitchCents) {
|
||||
ss << p << ", ";
|
||||
}
|
||||
LOG4CXX_DEBUG(logger, "Tuning pitches [" << ss.str() << "]");
|
||||
|
||||
return result == FLUID_OK;
|
||||
auto result = fluid_synth_activate_key_tuning(synth, 0, 0, "default", pitchCents.data(), FALSE);
|
||||
return (result == FLUID_OK) ? activateTuning() : false;
|
||||
}
|
||||
|
||||
bool Synth::resetTuning() {
|
||||
int result = fluid_synth_activate_key_tuning(
|
||||
synth,
|
||||
0, 0,
|
||||
"default",
|
||||
nullptr,
|
||||
1
|
||||
);
|
||||
|
||||
return result == FLUID_OK;
|
||||
auto result = fluid_synth_activate_key_tuning(synth, 0, 0, "default", nullptr, 1);
|
||||
return (result == FLUID_OK) ? activateTuning() : false;
|
||||
}
|
||||
|
||||
const std::vector<Synth::Program>& Synth::getPrograms() const {
|
||||
@ -114,3 +128,46 @@ const std::vector<Synth::Program>& Synth::getPrograms() const {
|
||||
fluid_synth_t* Synth::getSynth() {
|
||||
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,9 +5,13 @@
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
constexpr int NUM_MIDI_NOTES = 128;
|
||||
#include "midi.h"
|
||||
|
||||
typedef std::array<double, NUM_MIDI_NOTES> Tuning;
|
||||
namespace {
|
||||
constexpr int NUM_MIDI_CHANNELS = 2;
|
||||
}
|
||||
|
||||
typedef std::array<double, NUM_MIDI_KEYS> TuningFrequencies;
|
||||
|
||||
/**
|
||||
* C++ wrapper around the fluidsynth API.
|
||||
@ -32,7 +36,7 @@ public:
|
||||
bool loadSoundfont(const std::string& filepath);
|
||||
bool loadProgram(const Program& program);
|
||||
|
||||
bool setTuning(const Tuning& pitches);
|
||||
bool setTuning(const TuningFrequencies& frequencies);
|
||||
bool resetTuning();
|
||||
|
||||
const std::vector<Program>& getPrograms() const;
|
||||
@ -41,7 +45,7 @@ public:
|
||||
fluid_synth_t* getSynth();
|
||||
|
||||
private:
|
||||
|
||||
bool activateTuning();
|
||||
|
||||
std::vector<Soundfont> soundfonts;
|
||||
std::vector<Program> programs;
|
||||
@ -50,3 +54,8 @@ private:
|
||||
fluid_synth_t* synth = 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