Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement an internal CLI for loading soundfonts and setting engine parameters. #973

Merged
merged 9 commits into from
Sep 21, 2021
197 changes: 146 additions & 51 deletions clients/jack_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
#include <thread>
#include <mutex>
#include <algorithm>
#include <vector>


sfz::Sfizz synth;

static jack_port_t* midiInputPort;
static jack_port_t* outputPort1;
Expand Down Expand Up @@ -149,30 +153,146 @@ static void done(int sig)
// exit(0);
}


bool load_instrument(const char *fpath) {
const char *importFormat = nullptr;
if (!sfizz_load_or_import_file(synth.handle(), fpath, &importFormat)) {
std::cout << "Could not load the instrument file: " << fpath << '\n';
return false;
}

std::cout << "Instrument loaded: " << fpath << '\n';
std::cout << "===========================" << '\n';
std::cout << "Total:" << '\n';
std::cout << "\tMasters: " << synth.getNumMasters() << '\n';
std::cout << "\tGroups: " << synth.getNumGroups() << '\n';
std::cout << "\tRegions: " << synth.getNumRegions() << '\n';
std::cout << "\tCurves: " << synth.getNumCurves() << '\n';
std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n';
#if 0 // not currently in public API
std::cout << "===========================" << '\n';
std::cout << "Included files:" << '\n';
for (auto& file : synth.getParser().getIncludedFiles())
std::cout << '\t' << file << '\n';
std::cout << "===========================" << '\n';
std::cout << "Defines:" << '\n';
for (auto& define : synth.getParser().getDefines())
std::cout << '\t' << define.first << '=' << define.second << '\n';
#endif
std::cout << "===========================" << '\n';
std::cout << "Unknown opcodes:";
for (auto& opcode : synth.getUnknownOpcodes())
std::cout << opcode << ',';
std::cout << '\n';
if (importFormat) {
std::cout << "===========================" << '\n';
std::cout << "Import format: " << importFormat << '\n';
}
// std::cout << std::flush;

return true;
}


std::vector<std::string> string_tokenize(std::string str) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

str could be const ref here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix ...

std::vector<std::string> tokens;
std::string part = "";
for (size_t i=0; i<str.length(); i++) {
char c = str[i];
if (c==' ' && part!="") {
tokens.push_back(part);
part= "";
} else if (c== '\"'){
i++;
while (str[i]!='\"') { part += str[i]; i++; }
tokens.push_back(part);
part= "";
} else {
part += c;
}
}
if (part!="") {
tokens.push_back(part);
}
return tokens;
}


void cli_thread_proc() {
while (!shouldClose) {
std::cout << "\n> ";

std::string command;
std::getline(std::cin, command);
std::size_t pos = command.find(" ");
std::string kw = command.substr(0, pos);
std::string args = command.substr(pos+1);
std::vector<std::string> tokens = string_tokenize(args);

if (kw=="load_instrument") {
//args.erase(std::remove(str.begin(), str.end(), '\"'), str.end());
load_instrument(tokens[0].c_str());
}
else if (kw=="set_oversampling") {
try {
synth.setOversamplingFactor(stoi(args));
} catch (...) {
std::cout << "ERROR: Can't set oversampling!\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of these throw, however they're not thread-safe so we need a mutex in the callback. You can check the SfizzVstProcessor.cpp file for an example, especially the doBackgroundWork() and process() functions. Basically you need to fully lock in this cli_thread_proc() method, and use try_lock() in the real-time thread (and exit if you can't take the lock).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I've been testing and the "random breaks" when loading soundfonts seems to be fixed.
Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may still have breaks for big soundfonts. If you want to avoid them you could somehow fade out the current loaded file before loading a new one in, but none of our plugins does this currently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate a little bit what you mean for "fade out" the "currently loaded soundfont"? ;-)
Do you mean ending active notes before loading the new soundfont?

}
}
else if (kw=="set_preload_size") {
try {
synth.setPreloadSize(stoi(args));
} catch (...) {
std::cout << "ERROR: Can't set preload size!\n";
}
}
else if (kw=="set_voices") {
try {
synth.setNumVoices(stoi(args));
} catch (...) {
std::cout << "ERROR: Can't set num of voices!\n";
}
}
else if (kw=="quit") {
shouldClose = true;
}
else if (kw.size()>0){
std::cout << "ERROR: Unknown command '" << kw <<"'!\n";
}
}
}


ABSL_FLAG(std::string, client_name, "sfizz", "Jack client name");
ABSL_FLAG(std::string, oversampling, "1x", "Internal oversampling factor (value values are x1, x2, x4, x8)");
ABSL_FLAG(uint32_t, preload_size, 8192, "Preloaded value");
ABSL_FLAG(uint32_t, preload_size, 8192, "Preloaded size");
ABSL_FLAG(uint32_t, num_voices, 32, "Num of voices");
ABSL_FLAG(bool, jack_autoconnect, false, "Autoconnect audio output");
ABSL_FLAG(bool, state, false, "Output the synth state in the jack loop");

int main(int argc, char** argv)
{
// std::ios::sync_with_stdio(false);
auto arguments = absl::ParseCommandLine(argc, argv);
if (arguments.size() < 2) {
std::cout << "You need to specify an SFZ file to load." << '\n';
return -1;
}


auto filesToParse = absl::MakeConstSpan(arguments).subspan(1);
const std::string clientName = absl::GetFlag(FLAGS_client_name);
const std::string oversampling = absl::GetFlag(FLAGS_oversampling);
const uint32_t preload_size = absl::GetFlag(FLAGS_preload_size);
const uint32_t num_voices = absl::GetFlag(FLAGS_num_voices);
const bool jack_autoconnect = absl::GetFlag(FLAGS_jack_autoconnect);
const bool verboseState = absl::GetFlag(FLAGS_state);

std::cout << "Flags" << '\n';
std::cout << "- Client name: " << clientName << '\n';
std::cout << "- Oversampling: " << oversampling << '\n';
std::cout << "- Preloaded Size: " << preload_size << '\n';
std::cout << "- Preloaded size: " << preload_size << '\n';
std::cout << "- Num of voices: " << num_voices << '\n';
std::cout << "- Audio Autoconnect: " << jack_autoconnect << '\n';
std::cout << "- Verbose State: " << verboseState << '\n';

const auto factor = [&]() {
if (oversampling == "x1") return 1;
if (oversampling == "x2") return 2;
Expand All @@ -186,43 +306,9 @@ int main(int argc, char** argv)
std::cout << " " << file << ',';
std::cout << '\n';

sfz::Sfizz synth;
synth.setOversamplingFactor(factor);
synth.setPreloadSize(preload_size);

const char *importFormat = nullptr;
if (!sfizz_load_or_import_file(synth.handle(), filesToParse[0], &importFormat)) {
std::cout << "Could not load the instrument file: " << filesToParse[0] << '\n';
return 1;
}

std::cout << "==========" << '\n';
std::cout << "Total:" << '\n';
std::cout << "\tMasters: " << synth.getNumMasters() << '\n';
std::cout << "\tGroups: " << synth.getNumGroups() << '\n';
std::cout << "\tRegions: " << synth.getNumRegions() << '\n';
std::cout << "\tCurves: " << synth.getNumCurves() << '\n';
std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n';
#if 0 // not currently in public API
std::cout << "==========" << '\n';
std::cout << "Included files:" << '\n';
for (auto& file : synth.getParser().getIncludedFiles())
std::cout << '\t' << file << '\n';
std::cout << "==========" << '\n';
std::cout << "Defines:" << '\n';
for (auto& define : synth.getParser().getDefines())
std::cout << '\t' << define.first << '=' << define.second << '\n';
#endif
std::cout << "==========" << '\n';
std::cout << "Unknown opcodes:";
for (auto& opcode : synth.getUnknownOpcodes())
std::cout << opcode << ',';
std::cout << '\n';
if (importFormat) {
std::cout << "==========" << '\n';
std::cout << "Import format: " << importFormat << '\n';
}
// std::cout << std::flush;
synth.setNumVoices(num_voices);

jack_status_t status;
client = jack_client_open(clientName.c_str(), JackNullOption, &status);
Expand Down Expand Up @@ -265,26 +351,35 @@ int main(int argc, char** argv)
return 1;
}

auto systemPorts = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput);
if (systemPorts == nullptr) {
std::cerr << "No physical output ports found" << '\n';
return 1;
}
if (jack_autoconnect) {
auto systemPorts = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput);
if (systemPorts == nullptr) {
std::cerr << "No physical output ports found" << '\n';
return 1;
}

if (jack_connect(client, jack_port_name(outputPort1), systemPorts[0])) {
std::cerr << "Cannot connect to physical output ports (0)" << '\n';
if (jack_connect(client, jack_port_name(outputPort1), systemPorts[0])) {
std::cerr << "Cannot connect to physical output ports (0)" << '\n';
}

if (jack_connect(client, jack_port_name(outputPort2), systemPorts[1])) {
std::cerr << "Cannot connect to physical output ports (1)" << '\n';
}
jack_free(systemPorts);
}

if (jack_connect(client, jack_port_name(outputPort2), systemPorts[1])) {
std::cerr << "Cannot connect to physical output ports (1)" << '\n';
if (filesToParse[0]) {
load_instrument(filesToParse[0]);
}
jack_free(systemPorts);

std::thread cli_thread(cli_thread_proc);

signal(SIGHUP, done);
signal(SIGINT, done);
signal(SIGTERM, done);
signal(SIGQUIT, done);


while (!shouldClose){
if (verboseState) {
std::cout << "Active voices: " << synth.getNumActiveVoices() << '\n';
Expand Down