Add a sfizz_render client in-tree
This commit is contained in:
parent
a8f863306f
commit
fc07833ec1
7 changed files with 2464 additions and 28 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -18,3 +18,6 @@
|
||||||
path = vst/external/VST_SDK/VST3_SDK/vstgui4
|
path = vst/external/VST_SDK/VST3_SDK/vstgui4
|
||||||
url = https://github.com/sfztools/vstgui.git
|
url = https://github.com/sfztools/vstgui.git
|
||||||
shallow = true
|
shallow = true
|
||||||
|
[submodule "clients/external/fmidi"]
|
||||||
|
path = clients/external/fmidi
|
||||||
|
url = https://github.com/jpcima/fmidi.git
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,10 @@ target_include_directories (sfizz_jack PRIVATE ${JACK_INCLUDE_DIRS})
|
||||||
target_link_libraries (sfizz_jack PRIVATE sfizz::sfizz jack absl::flags_parse ${JACK_LIBRARIES})
|
target_link_libraries (sfizz_jack PRIVATE sfizz::sfizz jack absl::flags_parse ${JACK_LIBRARIES})
|
||||||
sfizz_enable_lto_if_needed (sfizz_jack)
|
sfizz_enable_lto_if_needed (sfizz_jack)
|
||||||
install (TARGETS sfizz_jack DESTINATION ${CMAKE_INSTALL_BINDIR}
|
install (TARGETS sfizz_jack DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
COMPONENT "jack" OPTIONAL)
|
COMPONENT "jack" OPTIONAL)
|
||||||
|
|
||||||
|
add_subdirectory(external/fmidi EXCLUDE_FROM_ALL)
|
||||||
|
add_executable(sfizz_render sfizz_render.cpp)
|
||||||
|
target_link_libraries(sfizz_render sfizz::sfizz fmidi-player fmidi)
|
||||||
|
sfizz_enable_lto_if_needed (sfizz_render)
|
||||||
|
install (TARGETS sfizz_render DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
|
||||||
|
|
|
||||||
29
clients/MidiHelpers.h
Normal file
29
clients/MidiHelpers.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace midi {
|
||||||
|
constexpr uint8_t statusMask { 0b11110000 };
|
||||||
|
constexpr uint8_t channelMask { 0b00001111 };
|
||||||
|
constexpr uint8_t noteOff { 0x80 };
|
||||||
|
constexpr uint8_t noteOn { 0x90 };
|
||||||
|
constexpr uint8_t polyphonicPressure { 0xA0 };
|
||||||
|
constexpr uint8_t controlChange { 0xB0 };
|
||||||
|
constexpr uint8_t programChange { 0xC0 };
|
||||||
|
constexpr uint8_t channelPressure { 0xD0 };
|
||||||
|
constexpr uint8_t pitchBend { 0xE0 };
|
||||||
|
constexpr uint8_t systemMessage { 0xF0 };
|
||||||
|
|
||||||
|
constexpr uint8_t status(uint8_t midiStatusByte)
|
||||||
|
{
|
||||||
|
return midiStatusByte & statusMask;
|
||||||
|
}
|
||||||
|
constexpr uint8_t channel(uint8_t midiStatusByte)
|
||||||
|
{
|
||||||
|
return midiStatusByte & channelMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int buildAndCenterPitch(uint8_t firstByte, uint8_t secondByte)
|
||||||
|
{
|
||||||
|
return (int)(((unsigned int)secondByte << 7) + (unsigned int)firstByte) - 8192;
|
||||||
|
}
|
||||||
|
}
|
||||||
2215
clients/cxxopts.hpp
Normal file
2215
clients/cxxopts.hpp
Normal file
File diff suppressed because it is too large
Load diff
1
clients/external/fmidi
vendored
Submodule
1
clients/external/fmidi
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f12d08d6ccd27d2580ec4e21717fbc3ab4b73a20
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
#include "sfizz/Synth.h"
|
#include "sfizz/Synth.h"
|
||||||
#include "sfizz/Macros.h"
|
#include "sfizz/Macros.h"
|
||||||
|
#include "MidiHelpers.h"
|
||||||
#include <absl/flags/parse.h>
|
#include <absl/flags/parse.h>
|
||||||
#include <absl/flags/flag.h>
|
#include <absl/flags/flag.h>
|
||||||
#include <absl/types/span.h>
|
#include <absl/types/span.h>
|
||||||
|
|
@ -44,33 +45,6 @@ static jack_port_t* outputPort1;
|
||||||
static jack_port_t* outputPort2;
|
static jack_port_t* outputPort2;
|
||||||
static jack_client_t* client;
|
static jack_client_t* client;
|
||||||
|
|
||||||
namespace midi {
|
|
||||||
constexpr uint8_t statusMask { 0b11110000 };
|
|
||||||
constexpr uint8_t channelMask { 0b00001111 };
|
|
||||||
constexpr uint8_t noteOff { 0x80 };
|
|
||||||
constexpr uint8_t noteOn { 0x90 };
|
|
||||||
constexpr uint8_t polyphonicPressure { 0xA0 };
|
|
||||||
constexpr uint8_t controlChange { 0xB0 };
|
|
||||||
constexpr uint8_t programChange { 0xC0 };
|
|
||||||
constexpr uint8_t channelPressure { 0xD0 };
|
|
||||||
constexpr uint8_t pitchBend { 0xE0 };
|
|
||||||
constexpr uint8_t systemMessage { 0xF0 };
|
|
||||||
|
|
||||||
constexpr uint8_t status(uint8_t midiStatusByte)
|
|
||||||
{
|
|
||||||
return midiStatusByte & statusMask;
|
|
||||||
}
|
|
||||||
constexpr uint8_t channel(uint8_t midiStatusByte)
|
|
||||||
{
|
|
||||||
return midiStatusByte & channelMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr int buildAndCenterPitch(uint8_t firstByte, uint8_t secondByte)
|
|
||||||
{
|
|
||||||
return (int)(((unsigned int)secondByte << 7) + (unsigned int)firstByte) - 8192;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int process(jack_nframes_t numFrames, void* arg)
|
int process(jack_nframes_t numFrames, void* arg)
|
||||||
{
|
{
|
||||||
auto synth = reinterpret_cast<sfz::Synth*>(arg);
|
auto synth = reinterpret_cast<sfz::Synth*>(arg);
|
||||||
|
|
|
||||||
208
clients/sfizz_render.cpp
Normal file
208
clients/sfizz_render.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
|
||||||
|
#include <sndfile.hh>
|
||||||
|
#include "sfizz/Synth.h"
|
||||||
|
#include "sfizz/MathHelpers.h"
|
||||||
|
#include "sfizz/SfzHelpers.h"
|
||||||
|
#include "sfizz/SIMDHelpers.h"
|
||||||
|
#include "MidiHelpers.h"
|
||||||
|
#include "cxxopts.hpp"
|
||||||
|
#include <fmidi/fmidi.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define LOG_ERROR(ostream) std::cerr << ostream << '\n'
|
||||||
|
#define LOG_INFO(ostream) if (verbose) { std::cout << ostream << '\n'; }
|
||||||
|
#define ERROR_IF(check, ostream) if ((check)) { LOG_ERROR(ostream); std::exit(-1); }
|
||||||
|
|
||||||
|
struct CallbackData {
|
||||||
|
sfz::Synth& synth;
|
||||||
|
int trackIdx;
|
||||||
|
double lastCallTime;
|
||||||
|
double blockSizeInSeconds;
|
||||||
|
bool finished;
|
||||||
|
};
|
||||||
|
|
||||||
|
void midiCallback(const fmidi_event_t * event, void * cbdata)
|
||||||
|
{
|
||||||
|
auto data = reinterpret_cast<CallbackData*>(cbdata);
|
||||||
|
|
||||||
|
if (event->type != fmidi_event_type::fmidi_event_message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (midi::status(event->data[0])) {
|
||||||
|
case midi::noteOff:
|
||||||
|
data->synth.noteOff(event->delta, event->data[1], event->data[2]);
|
||||||
|
break;
|
||||||
|
case midi::noteOn:
|
||||||
|
data->synth.noteOn(event->delta, event->data[1], event->data[2]);
|
||||||
|
break;
|
||||||
|
case midi::polyphonicPressure:
|
||||||
|
break;
|
||||||
|
case midi::controlChange:
|
||||||
|
data->synth.cc(event->delta, event->data[1], event->data[2]);
|
||||||
|
break;
|
||||||
|
case midi::programChange:
|
||||||
|
break;
|
||||||
|
case midi::channelPressure:
|
||||||
|
break;
|
||||||
|
case midi::pitchBend:
|
||||||
|
data->synth.pitchWheel(event->delta, midi::buildAndCenterPitch(event->data[1], event->data[2]));
|
||||||
|
break;
|
||||||
|
case midi::systemMessage:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishedCallback(void * cbdata)
|
||||||
|
{
|
||||||
|
auto data = reinterpret_cast<CallbackData*>(cbdata);
|
||||||
|
data->finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
cxxopts::Options options("sfizz-render", "Render a midi file through an SFZ file using the sfizz library.");
|
||||||
|
|
||||||
|
unsigned blockSize { 1024 };
|
||||||
|
int sampleRate { 48000 };
|
||||||
|
int trackNumber { -1 };
|
||||||
|
bool verbose { false };
|
||||||
|
bool help { false };
|
||||||
|
bool useEOT { false };
|
||||||
|
int oversampling { 1 };
|
||||||
|
|
||||||
|
options.add_options()
|
||||||
|
("sfz", "SFZ file", cxxopts::value<std::string>())
|
||||||
|
("midi", "Input midi file", cxxopts::value<std::string>())
|
||||||
|
("wav", "Output wav file", cxxopts::value<std::string>())
|
||||||
|
("b,blocksize", "Block size for the sfizz callbacks", cxxopts::value(blockSize))
|
||||||
|
("s,samplerate", "Output sample rate", cxxopts::value(sampleRate))
|
||||||
|
("t,track", "Track number to use", cxxopts::value(trackNumber))
|
||||||
|
("oversampling", "Internal oversampling factor", cxxopts::value(oversampling))
|
||||||
|
("v,verbose", "Verbose output", cxxopts::value(verbose))
|
||||||
|
("log", "Produce logs", cxxopts::value<std::string>())
|
||||||
|
("use-eot", "End the rendering at the last End of Track Midi message", cxxopts::value(useEOT))
|
||||||
|
("h,help", "Show help", cxxopts::value(help))
|
||||||
|
;
|
||||||
|
auto params = [&]() {
|
||||||
|
try { return options.parse(argc, argv); }
|
||||||
|
catch (std::exception& e) {
|
||||||
|
LOG_ERROR(e.what());
|
||||||
|
std::exit(-1);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (help) {
|
||||||
|
std::cout << options.help();
|
||||||
|
std::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ERROR_IF(params.count("sfz") != 1, "Please specify a single SFZ file using --sfz");
|
||||||
|
ERROR_IF(params.count("wav") != 1, "Please specify a single WAV file using --wav");
|
||||||
|
ERROR_IF(params.count("midi") != 1, "Please specify a single MIDI file using --midi");
|
||||||
|
|
||||||
|
fs::path sfzPath = fs::current_path() / params["sfz"].as<std::string>();
|
||||||
|
fs::path outputPath = fs::current_path() / params["wav"].as<std::string>();
|
||||||
|
fs::path midiPath = fs::current_path() / params["midi"].as<std::string>();
|
||||||
|
|
||||||
|
ERROR_IF(!fs::exists(sfzPath) || !fs::is_regular_file(sfzPath),
|
||||||
|
"SFZ file " << sfzPath.string() << " does not exist or is not a regular file");
|
||||||
|
ERROR_IF(!fs::exists(midiPath) || !fs::is_regular_file(midiPath),
|
||||||
|
"MIDI file " << midiPath.string() << " does not exist or is not a regular file");
|
||||||
|
|
||||||
|
if (fs::exists(outputPath)) {
|
||||||
|
LOG_INFO("Output file " << outputPath.string() << " already exists and will be erased.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("SFZ file: " << sfzPath.string());
|
||||||
|
LOG_INFO("MIDI file: " << midiPath.string());
|
||||||
|
LOG_INFO("Output file: " << outputPath.string());
|
||||||
|
LOG_INFO("Block size: " << blockSize);
|
||||||
|
LOG_INFO("Sample rate: " << sampleRate);
|
||||||
|
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.setSamplesPerBlock(blockSize);
|
||||||
|
synth.setSampleRate(sampleRate);
|
||||||
|
synth.enableFreeWheeling();
|
||||||
|
|
||||||
|
if (params.count("log") > 0)
|
||||||
|
synth.enableLogging(params["log"].as<std::string>());
|
||||||
|
|
||||||
|
const auto osFactor = [oversampling] {
|
||||||
|
switch (oversampling){
|
||||||
|
case 1:
|
||||||
|
return sfz::Oversampling::x1;
|
||||||
|
case 2:
|
||||||
|
return sfz::Oversampling::x2;
|
||||||
|
case 4:
|
||||||
|
return sfz::Oversampling::x4;
|
||||||
|
case 5:
|
||||||
|
return sfz::Oversampling::x8;
|
||||||
|
default:
|
||||||
|
LOG_ERROR("Bad oversampling factor: " << oversampling);
|
||||||
|
std::exit(-1);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
synth.setOversamplingFactor(osFactor);
|
||||||
|
|
||||||
|
ERROR_IF(!synth.loadSfzFile(sfzPath), "There was an error loading the SFZ file.");
|
||||||
|
LOG_INFO(synth.getNumRegions() << " regions in the SFZ.");
|
||||||
|
|
||||||
|
fmidi_smf_u midiFile { fmidi_smf_file_read(midiPath.c_str()) };
|
||||||
|
ERROR_IF(!midiFile, "Can't read " << midiPath);
|
||||||
|
|
||||||
|
const auto* midiInfo = fmidi_smf_get_info(midiFile.get());
|
||||||
|
ERROR_IF(!midiInfo, "Can't get info on the midi file");
|
||||||
|
|
||||||
|
LOG_INFO( midiInfo->track_count << " tracks in the SMF.");
|
||||||
|
ERROR_IF (trackNumber > midiInfo->track_count, "The track number " << trackNumber << " requested does not exist in the SMF file.");
|
||||||
|
|
||||||
|
if (trackNumber >= 0) {
|
||||||
|
LOG_INFO("-- Rendering only track number " << trackNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useEOT) {
|
||||||
|
LOG_INFO("-- Cutting the rendering at the last MIDI End of Track message");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto trackIdx = trackNumber < 1 ? 0 : trackNumber - 1;
|
||||||
|
|
||||||
|
SndfileHandle outputFile (outputPath, SFM_WRITE, SF_FORMAT_WAV | SF_FORMAT_PCM_16, 2, sampleRate);
|
||||||
|
ERROR_IF(outputFile.error() != 0, "Error writing out the wav file: " << outputFile.strError());
|
||||||
|
|
||||||
|
auto sampleRateDouble = static_cast<double>(sampleRate);
|
||||||
|
double blockSizeInSeconds { blockSize / sampleRateDouble };
|
||||||
|
int numFramesWritten { 0 };
|
||||||
|
sfz::AudioBuffer<float> audioBuffer { 2, blockSize };
|
||||||
|
sfz::Buffer<float> interleavedBuffer { 2 * blockSize };
|
||||||
|
|
||||||
|
fmidi_player_u midiPlayer { fmidi_player_new(midiFile.get()) };
|
||||||
|
CallbackData callbackData { synth, trackIdx, 0.0, blockSizeInSeconds, false };
|
||||||
|
fmidi_player_event_callback(midiPlayer.get(), &midiCallback, &callbackData);
|
||||||
|
fmidi_player_finish_callback(midiPlayer.get(), &finishedCallback, &callbackData);
|
||||||
|
|
||||||
|
fmidi_player_start(midiPlayer.get());
|
||||||
|
while (!callbackData.finished) {
|
||||||
|
fmidi_player_tick(midiPlayer.get(), blockSizeInSeconds);
|
||||||
|
callbackData.lastCallTime += blockSizeInSeconds;
|
||||||
|
synth.renderBlock(audioBuffer);
|
||||||
|
sfz::writeInterleaved(audioBuffer.getConstSpan(0), audioBuffer.getConstSpan(1), absl::MakeSpan(interleavedBuffer));
|
||||||
|
numFramesWritten += outputFile.writef(interleavedBuffer.data(), blockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useEOT) {
|
||||||
|
auto averagePower = sfz::meanSquared<float>(interleavedBuffer);
|
||||||
|
while (averagePower > 1e-12f) {
|
||||||
|
callbackData.lastCallTime += blockSizeInSeconds;
|
||||||
|
synth.renderBlock(audioBuffer);
|
||||||
|
sfz::writeInterleaved(audioBuffer.getConstSpan(0), audioBuffer.getConstSpan(1), absl::MakeSpan(interleavedBuffer));
|
||||||
|
numFramesWritten += outputFile.writef(interleavedBuffer.data(), blockSize);
|
||||||
|
averagePower = sfz::meanSquared<float>(interleavedBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFile.writeSync();
|
||||||
|
LOG_INFO("Wrote " << callbackData.lastCallTime << " seconds of sound data in"
|
||||||
|
<< outputPath.string() << " (" << numFramesWritten << " frames)");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue