Added proper command line args
This commit is contained in:
parent
2f1b5018a6
commit
ab9ee2e493
@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
add_subdirectory(fluidsynth)
|
||||
add_subdirectory(magic_enum)
|
||||
add_subdirectory(argparse)
|
||||
|
||||
file(GLOB_RECURSE sources src/*.cpp src/*.h)
|
||||
file(GLOB_RECURSE data resources/*)
|
||||
@ -13,3 +14,4 @@ add_executable(tone-generator ${sources} ${data})
|
||||
|
||||
target_link_libraries(tone-generator PRIVATE libfluidsynth)
|
||||
target_link_libraries(tone-generator PRIVATE magic_enum::magic_enum)
|
||||
target_link_libraries(tone-generator PRIVATE argparse)
|
||||
@ -8,6 +8,7 @@ Assumes that the fluidsynth sources are in the fluidsynth/ directory.
|
||||
```
|
||||
git clone https://github.com/FluidSynth/fluidsynth.git
|
||||
git clone https://github.com/Neargye/magic_enum.git
|
||||
git clone https://github.com/p-ranav/argparse.git
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
|
||||
36
src/code.h
36
src/code.h
@ -3,6 +3,7 @@
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
|
||||
namespace SELCAL {
|
||||
@ -45,6 +46,41 @@ enum class Key : char {
|
||||
KEY_Z = 'Z',
|
||||
};
|
||||
|
||||
const std::map<Key, double> KeyFrequencies = {
|
||||
{Key::KEY_1, 680.0},
|
||||
{Key::KEY_2, 754.2},
|
||||
{Key::KEY_3, 836.6},
|
||||
{Key::KEY_4, 927.9},
|
||||
{Key::KEY_5, 1029.2},
|
||||
{Key::KEY_6, 1141.6},
|
||||
{Key::KEY_7, 1266.2},
|
||||
{Key::KEY_8, 1404.4},
|
||||
{Key::KEY_9, 1557.8},
|
||||
{Key::KEY_A, 312.6},
|
||||
{Key::KEY_B, 346.7},
|
||||
{Key::KEY_C, 384.6},
|
||||
{Key::KEY_D, 426.6},
|
||||
{Key::KEY_E, 473.2},
|
||||
{Key::KEY_F, 524.8},
|
||||
{Key::KEY_G, 582.1},
|
||||
{Key::KEY_H, 645.7},
|
||||
{Key::KEY_J, 716.1},
|
||||
{Key::KEY_K, 794.3},
|
||||
{Key::KEY_L, 881.0},
|
||||
{Key::KEY_M, 977.2},
|
||||
{Key::KEY_P, 1083.9},
|
||||
{Key::KEY_Q, 1202.3},
|
||||
{Key::KEY_R, 1333.5},
|
||||
{Key::KEY_S, 1479.1},
|
||||
{Key::KEY_T, 329.2},
|
||||
{Key::KEY_U, 365.2},
|
||||
{Key::KEY_V, 405.0},
|
||||
{Key::KEY_W, 449.3},
|
||||
{Key::KEY_X, 498.3},
|
||||
{Key::KEY_Y, 552.7},
|
||||
{Key::KEY_Z, 613.1},
|
||||
};
|
||||
|
||||
typedef std::array<Key, KEYS_PER_GROUP> Group;
|
||||
|
||||
class Code {
|
||||
|
||||
178
src/main.cpp
178
src/main.cpp
@ -4,84 +4,31 @@
|
||||
#include <map>
|
||||
#include <magic_enum.hpp>
|
||||
#include "midi.h"
|
||||
#include <argparse/argparse.hpp>
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
SELCAL::Code code{argv[1]};
|
||||
|
||||
for (auto& group : code.getGroups()) {
|
||||
std::cout
|
||||
<< magic_enum::enum_name(group[0]) << " + "
|
||||
<< magic_enum::enum_name(group[1])
|
||||
<< std::endl;
|
||||
}
|
||||
// TODO JMT: Does fluidsynth define this itself?
|
||||
constexpr int ALL_CHANNELS = -1;
|
||||
constexpr double DEFAULT_TONE_DURATION = 1.0;
|
||||
constexpr double DEFAULT_SILENCE_DURATION = 0.2;
|
||||
|
||||
|
||||
Synth synth;
|
||||
synth.loadSoundfont("../soundfonts/square-equaltemperament.sf2");
|
||||
//synth.loadSoundfont("../soundfonts/TimGM6mb.sf2");
|
||||
synth.loadSoundfont("../soundfonts/GU-1.471.sf2");
|
||||
|
||||
/*
|
||||
for(int i = 1; i < argc; ++i) {
|
||||
synth.loadSoundfont(argv[i]);
|
||||
}
|
||||
/**
|
||||
* The MIDI format only has a fixed number of notes it can play - 128 individual frequencies.
|
||||
* The default tuning is an equal temperament around middle C, but that means that it is impossible
|
||||
* 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.
|
||||
*/
|
||||
|
||||
for (const Synth::Program& program : synth.getPrograms()) {
|
||||
std::cout << program.name << " " << program.soundfontId
|
||||
<< ":(" << program.bankNumber << ", " << program.programNumber << ")"
|
||||
<< std::endl;
|
||||
|
||||
if (program.name == "Night Vision") {
|
||||
std::cout << "Loading program!" << std::endl;
|
||||
synth.loadProgram(program);
|
||||
}
|
||||
}
|
||||
|
||||
std::map<SELCAL::Key, double> keyFrequencies = {
|
||||
{SELCAL::Key::KEY_1, 680.0},
|
||||
{SELCAL::Key::KEY_2, 754.2},
|
||||
{SELCAL::Key::KEY_3, 836.6},
|
||||
{SELCAL::Key::KEY_4, 927.9},
|
||||
{SELCAL::Key::KEY_5, 1029.2},
|
||||
{SELCAL::Key::KEY_6, 1141.6},
|
||||
{SELCAL::Key::KEY_7, 1266.2},
|
||||
{SELCAL::Key::KEY_8, 1404.4},
|
||||
{SELCAL::Key::KEY_9, 1557.8},
|
||||
{SELCAL::Key::KEY_A, 312.6},
|
||||
{SELCAL::Key::KEY_B, 346.7},
|
||||
{SELCAL::Key::KEY_C, 384.6},
|
||||
{SELCAL::Key::KEY_D, 426.6},
|
||||
{SELCAL::Key::KEY_E, 473.2},
|
||||
{SELCAL::Key::KEY_F, 524.8},
|
||||
{SELCAL::Key::KEY_G, 582.1},
|
||||
{SELCAL::Key::KEY_H, 645.7},
|
||||
{SELCAL::Key::KEY_J, 716.1},
|
||||
{SELCAL::Key::KEY_K, 794.3},
|
||||
{SELCAL::Key::KEY_L, 881.0},
|
||||
{SELCAL::Key::KEY_M, 977.2},
|
||||
{SELCAL::Key::KEY_P, 1083.9},
|
||||
{SELCAL::Key::KEY_Q, 1202.3},
|
||||
{SELCAL::Key::KEY_R, 1333.5},
|
||||
{SELCAL::Key::KEY_S, 1479.1},
|
||||
{SELCAL::Key::KEY_T, 329.2},
|
||||
{SELCAL::Key::KEY_U, 365.2},
|
||||
{SELCAL::Key::KEY_V, 405.0},
|
||||
{SELCAL::Key::KEY_W, 449.3},
|
||||
{SELCAL::Key::KEY_X, 498.3},
|
||||
{SELCAL::Key::KEY_Y, 552.7},
|
||||
{SELCAL::Key::KEY_Z, 613.1},
|
||||
};
|
||||
|
||||
std::array<double, NUM_MIDI_NOTES> tunings;
|
||||
Tuning getSelcalTunings() {
|
||||
Tuning tunings;
|
||||
tunings.fill(0);
|
||||
|
||||
|
||||
for (auto pair : keyFrequencies) {
|
||||
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.
|
||||
@ -90,24 +37,101 @@ int main(int argc, char** argv) {
|
||||
tunings[keyIndex] = frequencyToMidi(pair.second) * MIDI_CENT_SCALE;
|
||||
}
|
||||
|
||||
return tunings;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
double toneDuration = DEFAULT_TONE_DURATION;
|
||||
double silenceDuration = DEFAULT_SILENCE_DURATION;
|
||||
bool listInstruments = false;
|
||||
|
||||
argparse::ArgumentParser parser("tone-generator");
|
||||
|
||||
parser.add_argument("-d", "--duration")
|
||||
.default_value(DEFAULT_TONE_DURATION)
|
||||
.store_into(toneDuration)
|
||||
.help("Duration of the tone for each group in the SELCAL code.");
|
||||
|
||||
parser.add_argument("-s", "--silence")
|
||||
.default_value(DEFAULT_SILENCE_DURATION)
|
||||
.store_into(silenceDuration)
|
||||
.help("Duration of the silence between tone groups.");
|
||||
|
||||
parser.add_argument("--soundfont")
|
||||
.nargs(argparse::nargs_pattern::any)
|
||||
.help("Additional soundfont(s) to load into the synth. Accepts files in .sf2 format.");
|
||||
|
||||
parser.add_argument("--instrument")
|
||||
.default_value("midi")
|
||||
.help("The soundfont synth profile to use when generating tones.");
|
||||
|
||||
parser.add_argument("--list-instruments")
|
||||
.flag()
|
||||
.store_into(listInstruments)
|
||||
.help("List available instrument profiles and exit.");
|
||||
|
||||
parser.add_argument("code")
|
||||
.help("Selcal code in the format AB-CD.");
|
||||
|
||||
try {
|
||||
parser.parse_args(argc, argv);
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << err.what() << std::endl;
|
||||
std::cerr << parser;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
Synth synth;
|
||||
|
||||
auto tunings = getSelcalTunings();
|
||||
if (!synth.setTuning(tunings)) {
|
||||
std::cerr << "Failed to set SELCAL tuning on synth" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto files = parser.get<std::vector<std::string>>("--soundfont");
|
||||
for (auto file : files) {
|
||||
std::cout << "Loading soundfont from " << file << std::endl;
|
||||
synth.loadSoundfont(file);
|
||||
}
|
||||
|
||||
if (listInstruments) {
|
||||
for (const Synth::Program& program : synth.getPrograms()) {
|
||||
std::cout << program.name << ", ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
for (const Synth::Program& program : synth.getPrograms()) {
|
||||
if (program.name == parser.get("--instrument")) {
|
||||
std::cout << "Loading program " << program.name << std::endl;
|
||||
synth.loadProgram(program);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO JMT: Hacky, find a better way to interact with the synth
|
||||
fluid_synth_t* raw_synth = synth.getSynth();
|
||||
constexpr int ALL_CHANNELS = -1;
|
||||
bool success;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
|
||||
success = synth.setTuning(tunings);
|
||||
std::cout << "Set tuning: " << (success ? "success" : "failed") << std::endl;
|
||||
SELCAL::Code code{parser.get<std::string>("code")};
|
||||
|
||||
for (auto& group : code.getGroups()) {
|
||||
fluid_synth_noteon(raw_synth, 0, static_cast<int>(group[0]), 127);
|
||||
fluid_synth_noteon(raw_synth, 1, static_cast<int>(group[1]), 127);
|
||||
std::this_thread::sleep_for(1s);
|
||||
std::this_thread::sleep_for(std::chrono::duration<double>(toneDuration));
|
||||
|
||||
// Silence between tone groups
|
||||
fluid_synth_all_notes_off(raw_synth, ALL_CHANNELS);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
std::this_thread::sleep_for(std::chrono::duration<double>(silenceDuration));
|
||||
}
|
||||
|
||||
// Wait long enough for the full tone to play before tearing down.
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(500ms);
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
|
||||
constexpr int NUM_MIDI_NOTES = 128;
|
||||
|
||||
typedef std::array<double, NUM_MIDI_NOTES> Tuning;
|
||||
|
||||
/**
|
||||
* C++ wrapper around the fluidsynth API.
|
||||
*/
|
||||
@ -30,7 +32,7 @@ public:
|
||||
bool loadSoundfont(const std::string& filepath);
|
||||
bool loadProgram(const Program& program);
|
||||
|
||||
bool setTuning(const std::array<double, NUM_MIDI_NOTES>& pitches);
|
||||
bool setTuning(const Tuning& pitches);
|
||||
bool resetTuning();
|
||||
|
||||
const std::vector<Program>& getPrograms() const;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user