C++-ified everything
This commit is contained in:
parent
7282a7d04d
commit
e3335005d0
@ -4,6 +4,7 @@ project(SELCALToneGenerator VERSION 0.1.0)
|
|||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
add_subdirectory(fluidsynth)
|
add_subdirectory(fluidsynth)
|
||||||
|
add_subdirectory(magic_enum)
|
||||||
|
|
||||||
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/*)
|
||||||
@ -11,3 +12,4 @@ file(GLOB_RECURSE data resources/*)
|
|||||||
add_executable(tone-generator ${sources} ${data})
|
add_executable(tone-generator ${sources} ${data})
|
||||||
|
|
||||||
target_link_libraries(tone-generator PRIVATE libfluidsynth)
|
target_link_libraries(tone-generator PRIVATE libfluidsynth)
|
||||||
|
target_link_libraries(tone-generator PRIVATE magic_enum::magic_enum)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ Assumes that the fluidsynth sources are in the fluidsynth/ directory.
|
|||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/FluidSynth/fluidsynth.git
|
git clone https://github.com/FluidSynth/fluidsynth.git
|
||||||
|
git clone https://github.com/Neargye/magic_enum.git
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake ..
|
cmake ..
|
||||||
@ -35,4 +36,4 @@ fluid-soundfont-gm/focal,focal 3.1-5.1 all
|
|||||||
|
|
||||||
fluid-soundfont-gs/focal,focal 3.1-5.1 all
|
fluid-soundfont-gs/focal,focal 3.1-5.1 all
|
||||||
Fluid (R3) General MIDI SoundFont (GS)
|
Fluid (R3) General MIDI SoundFont (GS)
|
||||||
```
|
```
|
||||||
|
|||||||
14
src/code.cpp
Normal file
14
src/code.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include "code.h"
|
||||||
|
|
||||||
|
namespace SELCAL {
|
||||||
|
|
||||||
|
Code::Code(std::string s) : code(s) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Code::isValid() const {
|
||||||
|
// TODO JMT
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // SELCAL
|
||||||
61
src/code.h
Normal file
61
src/code.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
namespace SELCAL {
|
||||||
|
|
||||||
|
constexpr int KEYS_PER_GROUP = 2;
|
||||||
|
constexpr char GROUP_SEPARATOR = '-';
|
||||||
|
|
||||||
|
enum class Key : char {
|
||||||
|
KEY_1 = '1',
|
||||||
|
KEY_2 = '2',
|
||||||
|
KEY_3 = '3',
|
||||||
|
KEY_4 = '4',
|
||||||
|
KEY_5 = '5',
|
||||||
|
KEY_6 = '6',
|
||||||
|
KEY_7 = '7',
|
||||||
|
KEY_8 = '8',
|
||||||
|
KEY_9 = '9',
|
||||||
|
KEY_A = 'A',
|
||||||
|
KEY_B = 'B',
|
||||||
|
KEY_C = 'C',
|
||||||
|
KEY_D = 'D',
|
||||||
|
KEY_E = 'E',
|
||||||
|
KEY_F = 'F',
|
||||||
|
KEY_G = 'G',
|
||||||
|
KEY_H = 'H',
|
||||||
|
KEY_J = 'J',
|
||||||
|
KEY_K = 'K',
|
||||||
|
KEY_L = 'L',
|
||||||
|
KEY_M = 'M',
|
||||||
|
KEY_P = 'P',
|
||||||
|
KEY_Q = 'Q',
|
||||||
|
KEY_R = 'R',
|
||||||
|
KEY_S = 'S',
|
||||||
|
KEY_T = 'T',
|
||||||
|
KEY_U = 'U',
|
||||||
|
KEY_V = 'V',
|
||||||
|
KEY_W = 'W',
|
||||||
|
KEY_X = 'X',
|
||||||
|
KEY_Y = 'Y',
|
||||||
|
KEY_Z = 'Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::array<Key, KEYS_PER_GROUP> Group;
|
||||||
|
|
||||||
|
class Code {
|
||||||
|
public:
|
||||||
|
Code(std::string s);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
const std::string code;
|
||||||
|
const std::vector<Group> groups;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // SELCAL
|
||||||
203
src/main.cpp
203
src/main.cpp
@ -1,126 +1,107 @@
|
|||||||
/*
|
#include "synth.h"
|
||||||
|
#include "code.h"
|
||||||
An example of how to use FluidSynth.
|
|
||||||
|
|
||||||
To compile it on Linux:
|
|
||||||
$ gcc -o example example.c `pkg-config fluidsynth --libs`
|
|
||||||
|
|
||||||
To compile it on Windows:
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
Author: Peter Hanappe.
|
|
||||||
This code is in the public domain. Use it as you like.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <fluidsynth.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <magic_enum.hpp>
|
||||||
|
#include "midi.h"
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#include <thread>
|
||||||
#include <windows.h>
|
#include <chrono>
|
||||||
#define sleep(_t) Sleep(_t * 1000)
|
|
||||||
#include <process.h>
|
int main(int argc, char** argv) {
|
||||||
#define getpid _getpid
|
|
||||||
#else
|
Synth synth;
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
for(int i = 1; i < argc; ++i) {
|
||||||
#endif
|
synth.loadSoundfont(argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const Synth::Program& program : synth.getPrograms()) {
|
||||||
|
std::cout << program.name << " " << program.soundfontId
|
||||||
|
<< ":(" << program.bankNumber << ", " << program.programNumber << ")"
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
if (program.name == "Tuba") {
|
||||||
|
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;
|
||||||
|
tunings.fill(0);
|
||||||
|
|
||||||
|
|
||||||
|
for (auto pair : keyFrequencies) {
|
||||||
|
std::cout
|
||||||
|
<< magic_enum::enum_name(pair.first) << " = "
|
||||||
|
<< pair.second << " Hz "
|
||||||
|
<< " or MIDI key " << frequencyToMidi(pair.second)
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = synth.setTuning(tunings);
|
||||||
|
std::cout << "Set tuning: " << (success ? "success" : "failed") << std::endl;
|
||||||
|
|
||||||
|
|
||||||
|
fluid_synth_t* raw_synth = synth.getSynth();
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
fluid_settings_t *settings = NULL;
|
|
||||||
fluid_synth_t *synth = NULL;
|
|
||||||
fluid_audio_driver_t *adriver = NULL;
|
|
||||||
int sfont_id;
|
|
||||||
int i, key;
|
int i, key;
|
||||||
|
|
||||||
fluid_sfont_t* sfont = nullptr;
|
|
||||||
fluid_preset_t* preset = nullptr;
|
|
||||||
|
|
||||||
/* Create the settings. */
|
|
||||||
settings = new_fluid_settings();
|
|
||||||
if(settings == NULL)
|
|
||||||
{
|
|
||||||
puts("Failed to create the settings!");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Change the settings if necessary*/
|
|
||||||
|
|
||||||
/* Create the synthesizer. */
|
|
||||||
synth = new_fluid_synth(settings);
|
|
||||||
if(synth == NULL)
|
|
||||||
{
|
|
||||||
puts("Failed to create the synth!");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Load a SoundFont and reset presets (so that new instruments
|
|
||||||
* get used from the SoundFont)
|
|
||||||
* Depending on the size of the SoundFont, this will take some time to complete...
|
|
||||||
*/
|
|
||||||
sfont_id = fluid_synth_sfload(synth, argv[1], 1);
|
|
||||||
if(sfont_id == FLUID_FAILED)
|
|
||||||
{
|
|
||||||
puts("Loading the SoundFont failed!");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the SoundFont
|
|
||||||
sfont = fluid_synth_get_sfont_by_id(synth, sfont_id);
|
|
||||||
|
|
||||||
if (!sfont) {
|
|
||||||
std::cerr << "Failed to get SoundFont by ID" << std::endl;
|
|
||||||
delete_fluid_synth(synth);
|
|
||||||
delete_fluid_settings(settings);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enumerate the presets (programs) in the SoundFont
|
|
||||||
|
|
||||||
std::cout << "Programs in SoundFont " << argv[1] << ":" << std::endl;
|
|
||||||
fluid_sfont_iteration_start(sfont);
|
|
||||||
while ((preset = fluid_sfont_iteration_next(sfont)) != nullptr) {
|
|
||||||
std::cout << "Bank: " << fluid_preset_get_banknum(preset)
|
|
||||||
<< ", Program: " << fluid_preset_get_num(preset)
|
|
||||||
<< ", Name: " << fluid_preset_get_name(preset) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
fluid_synth_program_select(synth, 0, sfont_id, 0, 0);
|
|
||||||
|
|
||||||
/* Create the audio driver. The synthesizer starts playing as soon
|
|
||||||
as the driver is created. */
|
|
||||||
adriver = new_fluid_audio_driver(settings, synth);
|
|
||||||
if(adriver == NULL)
|
|
||||||
{
|
|
||||||
puts("Failed to create the audio driver!");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize the random number generator */
|
|
||||||
srand(getpid());
|
|
||||||
|
|
||||||
for(i = 0; i < 12; i++)
|
for(i = 0; i < 12; i++)
|
||||||
{
|
{
|
||||||
/* Generate a random key */
|
/* Generate a random key */
|
||||||
key = 60 + (int)(12.0f * rand() / (float) RAND_MAX);
|
key = 30 + (int)(12.0f * rand() / (float) RAND_MAX);
|
||||||
|
|
||||||
/* Play a note */
|
/* Play a note */
|
||||||
fluid_synth_noteon(synth, 0, key, 100);
|
fluid_synth_noteon(raw_synth, 0, key, 100);
|
||||||
|
|
||||||
/* Sleep for 1 second */
|
/* Sleep for 1 second */
|
||||||
sleep(1);
|
std::this_thread::sleep_for(1s);
|
||||||
|
|
||||||
/* Stop the note */
|
/* Stop the note */
|
||||||
fluid_synth_noteoff(synth, 0, key);
|
fluid_synth_noteoff(raw_synth, 0, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
err:
|
}
|
||||||
/* Clean up */
|
|
||||||
delete_fluid_audio_driver(adriver);
|
|
||||||
delete_fluid_synth(synth);
|
|
||||||
delete_fluid_settings(settings);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|||||||
11
src/midi.h
Normal file
11
src/midi.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
double frequencyToMidi(double f) {
|
||||||
|
return ((12 * std::log(f / 220.0) / std::log(2.0)) + 57.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
int frequencyToNearestMidi(double f) {
|
||||||
|
return (int) frequencyToMidi(f);
|
||||||
|
}
|
||||||
101
src/synth.cpp
Normal file
101
src/synth.cpp
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#include "synth.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
Synth::Synth() {
|
||||||
|
try {
|
||||||
|
settings = new_fluid_settings();
|
||||||
|
if (settings == nullptr) {
|
||||||
|
throw new std::runtime_error("Unable to create fluid settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
synth = new_fluid_synth(settings);
|
||||||
|
if (synth == nullptr) {
|
||||||
|
throw new std::runtime_error("Unable to create fluid synth");
|
||||||
|
}
|
||||||
|
|
||||||
|
adriver = new_fluid_audio_driver(settings, synth);
|
||||||
|
if (adriver == nullptr) {
|
||||||
|
throw new std::runtime_error("Unable to create audio driver");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::runtime_error& e) {
|
||||||
|
// Do a mem-safe cleanup and rethrow.
|
||||||
|
// TODO JMT: Do these handle receiving nullptr for deletes?
|
||||||
|
delete_fluid_audio_driver(adriver);
|
||||||
|
delete_fluid_synth(synth);
|
||||||
|
delete_fluid_settings(settings);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Synth::~Synth() {
|
||||||
|
// TODO JMT: Do I need to manually clear all of the soundfonts and programs I have handles for?
|
||||||
|
delete_fluid_audio_driver(adriver);
|
||||||
|
delete_fluid_synth(synth);
|
||||||
|
delete_fluid_settings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Synth::loadSoundfont(const std::string& filepath) {
|
||||||
|
int soundfontId = fluid_synth_sfload(synth, filepath.c_str(), 1);
|
||||||
|
if(soundfontId == FLUID_FAILED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluid_sfont_t* soundfontHandle = fluid_synth_get_sfont_by_id(synth, soundfontId);
|
||||||
|
const std::string soundfontName{fluid_sfont_get_name(soundfontHandle)};
|
||||||
|
|
||||||
|
Synth::Soundfont soundfont{soundfontName, soundfontId};
|
||||||
|
soundfonts.push_back(soundfont);
|
||||||
|
|
||||||
|
fluid_preset_t* preset = nullptr;
|
||||||
|
fluid_sfont_iteration_start(soundfontHandle);
|
||||||
|
while ((preset = fluid_sfont_iteration_next(soundfontHandle)) != nullptr) {
|
||||||
|
const std::string presetName{fluid_preset_get_name(preset)};
|
||||||
|
const int bankNumber = fluid_preset_get_banknum(preset);
|
||||||
|
const int programNumber = fluid_preset_get_num(preset);
|
||||||
|
|
||||||
|
Synth::Program program {presetName, soundfontId, bankNumber, programNumber};
|
||||||
|
programs.push_back(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Synth::loadProgram(const Program& program) {
|
||||||
|
// TODO JMT: fix this
|
||||||
|
fluid_synth_program_select(synth, 0, program.soundfontId, program.bankNumber, program.programNumber);
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
return result == FLUID_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Synth::resetTuning() {
|
||||||
|
int result = fluid_synth_activate_key_tuning(
|
||||||
|
synth,
|
||||||
|
0, 0,
|
||||||
|
"default",
|
||||||
|
nullptr,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
return result == FLUID_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Synth::Program>& Synth::getPrograms() const {
|
||||||
|
return programs;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluid_synth_t* Synth::getSynth() {
|
||||||
|
return synth;
|
||||||
|
}
|
||||||
50
src/synth.h
Normal file
50
src/synth.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fluidsynth.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
constexpr int NUM_MIDI_NOTES = 128;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C++ wrapper around the fluidsynth API.
|
||||||
|
*/
|
||||||
|
class Synth {
|
||||||
|
public:
|
||||||
|
Synth();
|
||||||
|
~Synth();
|
||||||
|
|
||||||
|
struct Soundfont {
|
||||||
|
std::string name;
|
||||||
|
int id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Program {
|
||||||
|
std::string name;
|
||||||
|
int soundfontId;
|
||||||
|
int bankNumber;
|
||||||
|
int programNumber;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool loadSoundfont(const std::string& filepath);
|
||||||
|
bool loadProgram(const Program& program);
|
||||||
|
|
||||||
|
bool setTuning(const std::array<double, NUM_MIDI_NOTES>& pitches);
|
||||||
|
bool resetTuning();
|
||||||
|
|
||||||
|
const std::vector<Program>& getPrograms() const;
|
||||||
|
|
||||||
|
// TODO JMT: remove this
|
||||||
|
fluid_synth_t* getSynth();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Soundfont> soundfonts;
|
||||||
|
std::vector<Program> programs;
|
||||||
|
|
||||||
|
fluid_settings_t* settings = NULL;
|
||||||
|
fluid_synth_t* synth = NULL;
|
||||||
|
fluid_audio_driver_t* adriver = NULL;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user