Fixed weird stuff about tones not registering properly - was an audio conversion stereo/mono bug

This commit is contained in:
Jono Targett 2024-05-26 00:00:49 +09:30
parent ec52422955
commit 9a63041a5e
6 changed files with 140 additions and 59 deletions

View File

@ -4,5 +4,10 @@ set -eux
input_file="$1" input_file="$1"
output_file="$2" 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.

View File

@ -7,10 +7,12 @@
#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;
@ -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 * 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. This method generates a tuning in the format * but we can make those frequencies anything we want.
* that fluidsynth accepts which _only_ contains the SELCAL tones.
*/ */
Tuning getSelcalTunings() { TuningFrequencies getSelcalFrequencies() {
Tuning tunings; TuningFrequencies tunings;
tunings.fill(0); 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 // 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. // need to tune the full 128-key keyboard.
constexpr double MIDI_CENT_SCALE = 100.0; std::stringstream ss;
const int keyIndex = static_cast<int>(pair.first); for (auto pair : SELCAL::KeyFrequencies) {
tunings[keyIndex] = frequencyToMidi(pair.second) * MIDI_CENT_SCALE; auto value = static_cast<int>(pair.first);
tunings[value] = pair.second;
ss << value << ":" << pair.second << ", ";
} }
LOG4CXX_DEBUG(logger, "SELCAL freqs {" << ss.str() << "}");
return tunings; return tunings;
} }
} // anonymous
int main(int argc, char** argv) { int main(int argc, char** argv) {
@ -89,12 +94,13 @@ 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;
} }
@ -130,10 +136,9 @@ int main(int argc, char** argv) {
for (auto& group : code.getGroups()) { for (auto& group : code.getGroups()) {
int midi = static_cast<int>(group[0]); LOG4CXX_INFO(logger, "Playing MIDI " << static_cast<int>(group[0]) << " & " << static_cast<int>(group[1]));
std::cout << "Playing MIDI " << midi << std::endl; fluid_synth_noteon(raw_synth, 0, static_cast<int>(group[0]), 80);
fluid_synth_noteon(raw_synth, 0, static_cast<int>(group[0]), 127); fluid_synth_noteon(raw_synth, 1, static_cast<int>(group[1]), 80);
fluid_synth_noteon(raw_synth, 1, static_cast<int>(group[1]), 127);
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
@ -141,16 +146,6 @@ int main(int argc, char** argv) {
std::this_thread::sleep_for(std::chrono::duration<double>(silenceDuration)); 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. // 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);

View File

@ -1,11 +1,26 @@
#pragma once #pragma once
#include <cmath> #include <cmath>
#include <algorithm>
double frequencyToMidi(double f) { // Applicable for standard (even temperament) MIDI tuning only.
return ((12 * std::log(f / 220.0) / std::log(2.0)) + 57.01); // 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) { constexpr double frequencyToMidiKey(double f) {
return (int) frequencyToMidi(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);
} }

View File

@ -1,6 +1,18 @@
#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 {
@ -20,8 +32,8 @@ Synth::Synth() {
fluid_settings_setstr(settings, "audio.file.type", "raw"); fluid_settings_setstr(settings, "audio.file.type", "raw");
fluid_settings_setnum(settings, "synth.sample-rate", 44100); fluid_settings_setnum(settings, "synth.sample-rate", 44100);
fluid_settings_setnum(settings, "synth.gain", 1.0); fluid_settings_setnum(settings, "synth.gain", 1.0);
fluid_settings_setint(settings, "synth.chorus.active", 0); //fluid_settings_setint(settings, "synth.chorus.active", 0);
fluid_settings_setint(settings, "synth.reverb.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) {
@ -78,33 +90,35 @@ bool Synth::loadProgram(const Program& program) {
return true; return true;
} }
bool Synth::setTuning(const std::array<double, NUM_MIDI_NOTES>& pitches) { bool Synth::setTuning(const TuningFrequencies& frequencies) {
int result = fluid_synth_activate_key_tuning( // fluidsynth wants tunings in MIDI cents, which effectively means MIDI key * 100.
synth, // For example: normally note 0 is 0.0, 1 is 100.0, 60 is 6000.0, etc.
0, 0, constexpr double MIDI_CENT_SCALE = 100.0;
"default", std::array<double, NUM_MIDI_KEYS> pitchCents;
pitches.data(), std::transform(
1 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) { std::stringstream ss;
result = fluid_synth_activate_tuning(synth, 0, 0, 0, 1); for (auto p : pitchCents) {
result = fluid_synth_activate_tuning(synth, 1, 0, 0, 1); 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() { bool Synth::resetTuning() {
int result = fluid_synth_activate_key_tuning( auto result = fluid_synth_activate_key_tuning(synth, 0, 0, "default", nullptr, 1);
synth, return (result == FLUID_OK) ? activateTuning() : false;
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 {
@ -114,3 +128,46 @@ 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);
}

View File

@ -5,9 +5,13 @@
#include <string> #include <string>
#include <array> #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. * C++ wrapper around the fluidsynth API.
@ -32,7 +36,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 Tuning& pitches); bool setTuning(const TuningFrequencies& frequencies);
bool resetTuning(); bool resetTuning();
const std::vector<Program>& getPrograms() const; const std::vector<Program>& getPrograms() const;
@ -41,7 +45,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;
@ -50,3 +54,8 @@ 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();