jack-tools

A handful of JACK audio tools
git clone git://henryandlizzy.uk/jack-tools
Log | Files | Refs

commit 1de453e029fad9a1c5a43a15eba306d0cf62bcb4
parent 9ba76539b372bfc6f6215e0a665ebbbd80d598c1
Author: Henry Wilson <henry@henryandlizzy.uk>
Date:   Thu, 25 Nov 2021 23:01:26 +0000

Consolidate JACK library

Diffstat:
M.gitignore | 3++-
Mjack-cc-map | 0
Mjack-cc-map.cpp | 121+++++++------------------------------------------------------------------------
Ajack-organ.cpp | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajack.cpp | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajack.hpp | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dmain.cpp | 329-------------------------------------------------------------------------------
Mmakefile | 5+++--
8 files changed, 391 insertions(+), 443 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,3 @@ *.[od] -main +jack-organ +jack-cc-map diff --git a/jack-cc-map b/jack-cc-map Binary files differ. diff --git a/jack-cc-map.cpp b/jack-cc-map.cpp @@ -1,6 +1,6 @@ -#include <jack/jack.h> #include <jack/midiport.h> -#include <jack/ringbuffer.h> + +#include "jack.hpp" #include <memory> #include <cassert> @@ -27,110 +27,6 @@ using namespace std::literals::chrono_literals; -namespace jack { - -struct client -{ - client() = default; - client(char const* name, jack_options_t options) - : handle(create(name, options), deleter) - {} - - void activate(void) - { - assert(not jack_activate(**this)); - } - - void set_process_callback(JackProcessCallback cb, void* arg) - { - jack_set_process_callback(**this, cb, arg); - } - void set_buffer_size_callback(JackBufferSizeCallback cb, void* arg) - { - jack_set_buffer_size_callback(**this, cb, arg); - } - void set_sample_rate_callback(JackSampleRateCallback cb, void* arg) - { - jack_set_sample_rate_callback(**this, cb, arg); - } - -private: - static jack_client_t* create(char const* name, jack_options_t options) - { - jack_status_t status; - jack_client_t *ptr = jack_client_open(name, options, &status);; - if (ptr) - return ptr; - - std::exit(1); - } - - static void deleter(jack_client_t* const p) - { - assert(not jack_deactivate(p)); - } - - std::unique_ptr<jack_client_t, void (*)(jack_client_t*)> handle = {nullptr, deleter}; - - jack_client_t* operator *(void) - { - return handle.get(); - } - - friend struct port; -}; - -struct port -{ - port() = default; - - port(client& _c, char const* name, char const* type, unsigned long flags, unsigned long buffer_size) - : c(*_c) - , p(jack_port_register(c, name, type, flags, buffer_size)) - { - assert(p); - } - ~port() - { - if (p) - jack_port_unregister(c, p); - } - port(port const&) = delete; - port& operator =(port const&) = delete; - - port(port&& old) - : c(old.c) - , p(old.p) - { - old.p = nullptr; - } - port& operator =(port&& old) - { - if (this != &old) - { - if (p) - jack_port_unregister(c, p); - - c = old.c; - p = old.p; - - old.p = nullptr; - } - return *this; - } - - jack_port_t* operator *(void) - { - return p; - } - -private: - jack_client_t* c = nullptr; - jack_port_t* p = nullptr; -}; - -} - jack::client client("CC-map", JackNoStartServer); jack::port p1(client, "in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); jack::port p2(client, "out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); @@ -195,7 +91,7 @@ void check_regerror(int err, regex_t* preg) char buf[256]; regerror(err, preg, buf, sizeof(buf)); - puts(buf); + std::cerr << buf << '\n'; exit(1); } @@ -211,13 +107,14 @@ int main(int argc, char* argv[]) if (argc < 2) { - puts("Usage:\n" + std::cerr << "Usage:\n" " jack-cc-map IN=OUT ...\n\n" "IN and OUT can either be:\n" " a number from 0 to 127\n" " OR\n" " a symbolic name\n\n" - "CC messages will be routed from IN to OUT"); + "CC messages will be routed from IN to OUT\n"; + return 1; } while (*++argv) @@ -228,11 +125,13 @@ int main(int argc, char* argv[]) int res = regexec(&preg, str, ARRAY_SIZE(pmatch), pmatch, 0); if (res == REG_NOMATCH) - continue; + { + std::cerr << "Bad mapping '" << str << "' is not in format IN=OUT\n"; + return 1; + } check_regerror(res, &preg); - for (unsigned j = 0; j < ARRAY_SIZE(pmatch); ++j) { if (pmatch[j].rm_so == -1) diff --git a/jack-organ.cpp b/jack-organ.cpp @@ -0,0 +1,202 @@ +#include "jack.hpp" + +#include <jack/midiport.h> + +#include <cassert> + +#include <cmath> + +#include <thread> +#include <chrono> +#include <iostream> +#include <iomanip> + +using namespace std::literals::chrono_literals; + +namespace jack { + +} + +jack::client client("h-synth", JackNoStartServer); +jack::port p1(client, "in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); +jack::port p2(client, "out", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + +jack::ringbuffer midi_data(1024); + +std::atomic_int midi_events; +std::atomic<jack_nframes_t> buffer_size, sample_rate; + +int cb_buffer_size(jack_nframes_t nframes, void* arg) +{ + buffer_size = nframes; + return 0; +} +int cb_sample_rate(jack_nframes_t nframes, void* arg) +{ + sample_rate = nframes; + return 0; +} + +static struct voice +{ + float envelope, freq, sample; + unsigned short active : 1, velocity : 7, state : 3; +} voices[128]; + +int cb_process(jack_nframes_t nframes, void* arg) +{ + static char sustain; + void* buf = jack_port_get_buffer(*p1, nframes); + + jack_midi_event_t event; + for (uint32_t i = 0; not jack_midi_event_get(&event, buf, i); ++i) + { + auto& voice = voices[event.buffer[1]]; + switch (event.buffer[0] >> 4 & 0x7) + { + case 0: // Note off + voice.active = 0; + continue; + case 1: // Note on + voice.active = 1; + voice.state = 4; + voice.velocity = event.buffer[2]; + continue; + case 3: // Controller change + switch (event.buffer[1]) + { + case 0x40: // Sustain + sustain = event.buffer[2] > 63; + continue; + } + /* FALLTHROUGH */ + default: + midi_data.write(event.buffer, event.size); + ++midi_events; + } + } + + auto audio_buf = static_cast<float*>(jack_port_get_buffer(*p2, nframes)); + + int srate = sample_rate; + + for (unsigned i = 0; i < buffer_size; ++i) + { + float accum = 0; + for (auto& v : voices) + { + if (not v.state) + continue; + + float pos = v.sample * 6.28318530718f / srate; + float sample = std::sin(pos / 2) + std::sin(pos) + std::sin(pos) + std::sin(pos * 2)*0.7f + std::sin(pos * 3)*.7f + std::sin(pos * 4)/2; + v.sample += v.freq; + if (v.sample >= srate * 6) + v.sample -= srate * 6; + + float velocity = v.velocity / 127.f; + + switch (v.state) + { + case 4: + v.envelope += velocity / 1000.f; + if (v.envelope >= velocity) + { + v.envelope = velocity; + v.state = 3; + } + break; + + case 3: + v.envelope -= (v.envelope - velocity * 0.2f) * 0.00005f; + if (not (sustain || v.active)) + v.state = 2; + break; + + case 2: + v.envelope *= 0.9998f; + if (v.envelope < 0.01f && sample < 0) + v.state = 1; + break; + + case 1: + v.envelope *= 0.9998f; + if (sample >= 0) + { + v.envelope = 0; + v.sample = 0; + v.state = 0; + } + break; + } + accum += sample * v.envelope * .3; + } + audio_buf[i] = 2 / (1 + std::exp(accum)) - 1; + } + return 0; +} + +void print_midi_events(void) +{ + static int last = 0; + int current = midi_events; + + if (current == last) + return; + + last = current; + std::cout << "processed " << current << " MIDI events:\n" << std::hex; + + for (unsigned char buf; midi_data.read(&buf, sizeof(buf)); /**/) + std::cout << std::setw(3) << +buf; + + std::cout << std::dec << '\n'; +} + +void print_buffer_size(void) +{ + static int last = 0; + int current = buffer_size; + + if (current == last) + return; + + last = current; + std::cout << "Buffer size changed to: " << current << '\n'; +} +void print_sample_rate(void) +{ + static int last = 0; + int current = sample_rate; + + if (current == last) + return; + + last = current; + std::cout << "Sample rate changed to: " << current << '\n'; +} + +int main() +{ + int note = 0; + for (auto& v : voices) + { + v.envelope = 0; + v.freq = 440 * std::pow(std::pow(2.f, 1.f/12), note++ - 69); + } + + client.set_process_callback(cb_process, nullptr); + client.set_buffer_size_callback(cb_buffer_size, nullptr); + client.set_sample_rate_callback(cb_sample_rate, nullptr); + client.activate(); + + for (;;) + { + std::this_thread::sleep_for(10ms); + print_buffer_size(); + print_sample_rate(); + print_midi_events(); + } + + return 0; +} diff --git a/jack.cpp b/jack.cpp @@ -0,0 +1,114 @@ +#include "jack.hpp" + +#include <cassert> + +namespace jack { + +// client + +client::client(char const* name, jack_options_t options) +: handle(create(name, options), deleter) +{} + +void client::activate(void) +{ + assert(not jack_activate(**this)); +} + +void client::set_process_callback(JackProcessCallback cb, void* arg) +{ + jack_set_process_callback(**this, cb, arg); +} +void client::set_buffer_size_callback(JackBufferSizeCallback cb, void* arg) +{ + jack_set_buffer_size_callback(**this, cb, arg); +} +void client::set_sample_rate_callback(JackSampleRateCallback cb, void* arg) +{ + jack_set_sample_rate_callback(**this, cb, arg); +} + +jack_client_t* client::create(char const* name, jack_options_t options) +{ + jack_status_t status; + jack_client_t *ptr = jack_client_open(name, options, &status);; + if (ptr) + return ptr; + + std::exit(1); +} + +void client::deleter(jack_client_t* const p) +{ + assert(not jack_deactivate(p)); +} + +jack_client_t* client::operator *(void) +{ + return handle.get(); +} + +// port + +port::port(client& _c, char const* name, char const* type, unsigned long flags, unsigned long buffer_size) +: c(*_c) +, p(jack_port_register(c, name, type, flags, buffer_size)) +{ + assert(p); +} +port::~port() +{ + if (p) + jack_port_unregister(c, p); +} + +port::port(port&& old) +: c(old.c) +, p(old.p) +{ + old.p = nullptr; +} + +port& port::operator =(port&& old) +{ + if (this != &old) + { + if (p) + jack_port_unregister(c, p); + + c = old.c; + p = old.p; + + old.p = nullptr; + } + return *this; +} + +jack_port_t* port::operator *(void) +{ + return p; +} + +// ringbuffer + +ringbuffer::ringbuffer(size_t size) +: handle(jack_ringbuffer_create(size), jack_ringbuffer_free) +{} + +size_t ringbuffer::write(void const* src, size_t size) +{ + return jack_ringbuffer_write(**this, static_cast<char const*>(src), size); +} + +size_t ringbuffer::read(void* dst, size_t size) +{ + return jack_ringbuffer_read(**this, static_cast<char*>(dst), size); +} + +jack_ringbuffer_t* ringbuffer::operator *(void) +{ + return handle.get(); +} + + +} diff --git a/jack.hpp b/jack.hpp @@ -0,0 +1,60 @@ +#pragma once +#include <memory> +#include <jack/jack.h> +#include <jack/ringbuffer.h> + +namespace jack { + +struct client +{ + client() = default; + client(char const* name, jack_options_t options); + + void activate(void); + void set_process_callback(JackProcessCallback cb, void* arg); + void set_buffer_size_callback(JackBufferSizeCallback cb, void* arg); + void set_sample_rate_callback(JackSampleRateCallback cb, void* arg); + +private: + jack_client_t* operator *(void); + + static jack_client_t* create(char const* name, jack_options_t options); + static void deleter(jack_client_t* const p); + + std::unique_ptr<jack_client_t, void (*)(jack_client_t*)> handle = {nullptr, deleter}; + + friend struct port; +}; + +struct port +{ + port() = default; + port(client& _c, char const* name, char const* type, unsigned long flags, unsigned long buffer_size); + ~port(); + port(port const&) = delete; + port& operator =(port const&) = delete; + + port(port&& old); + port& operator =(port&& old); + + jack_port_t* operator *(void); + +private: + jack_client_t* c = nullptr; + jack_port_t* p = nullptr; +}; + +struct ringbuffer +{ + ringbuffer(size_t size); + + size_t write(void const* src, size_t size); + size_t read(void* dst, size_t size); + +private: + std::unique_ptr<jack_ringbuffer_t, void (*)(jack_ringbuffer_t*)> handle = {nullptr, jack_ringbuffer_free}; + + jack_ringbuffer_t* operator *(void); +}; + +} diff --git a/main.cpp b/main.cpp @@ -1,329 +0,0 @@ -#include <jack/jack.h> -#include <jack/midiport.h> -#include <jack/ringbuffer.h> - -#include <memory> -#include <cassert> - -#include <cmath> - -#include <thread> -#include <chrono> -#include <iostream> -#include <iomanip> - -using namespace std::literals::chrono_literals; - -namespace jack { - -struct client -{ - client() = default; - client(char const* name, jack_options_t options) - : handle(create(name, options), deleter) - {} - - void activate(void) - { - assert(not jack_activate(**this)); - } - - void set_process_callback(JackProcessCallback cb, void* arg) - { - jack_set_process_callback(**this, cb, arg); - } - void set_buffer_size_callback(JackBufferSizeCallback cb, void* arg) - { - jack_set_buffer_size_callback(**this, cb, arg); - } - void set_sample_rate_callback(JackSampleRateCallback cb, void* arg) - { - jack_set_sample_rate_callback(**this, cb, arg); - } - -private: - static jack_client_t* create(char const* name, jack_options_t options) - { - jack_status_t status; - jack_client_t *ptr = jack_client_open(name, options, &status);; - if (ptr) - return ptr; - - std::exit(1); - } - - static void deleter(jack_client_t* const p) - { - assert(not jack_deactivate(p)); - } - - std::unique_ptr<jack_client_t, void (*)(jack_client_t*)> handle = {nullptr, deleter}; - - jack_client_t* operator *(void) - { - return handle.get(); - } - - friend struct port; -}; - -struct port -{ - port() = default; - - port(client& _c, char const* name, char const* type, unsigned long flags, unsigned long buffer_size) - : c(*_c) - , p(jack_port_register(c, name, type, flags, buffer_size)) - { - assert(p); - } - ~port() - { - if (p) - jack_port_unregister(c, p); - } - port(port const&) = delete; - port& operator =(port const&) = delete; - - port(port&& old) - : c(old.c) - , p(old.p) - { - old.p = nullptr; - } - port& operator =(port&& old) - { - if (this != &old) - { - if (p) - jack_port_unregister(c, p); - - c = old.c; - p = old.p; - - old.p = nullptr; - } - return *this; - } - - jack_port_t* operator *(void) - { - return p; - } - -private: - jack_client_t* c = nullptr; - jack_port_t* p = nullptr; -}; - -struct ringbuffer -{ - ringbuffer() = default; - ringbuffer(size_t size) - : handle(jack_ringbuffer_create(size), jack_ringbuffer_free) - {} - - size_t write(void const* src, size_t size) - { - return jack_ringbuffer_write(**this, static_cast<char const*>(src), size); - } - - size_t read(void* dst, size_t size) - { - return jack_ringbuffer_read(**this, static_cast<char*>(dst), size); - } - -private: - std::unique_ptr<jack_ringbuffer_t, void (*)(jack_ringbuffer_t*)> handle = {nullptr, jack_ringbuffer_free}; - - jack_ringbuffer_t* operator *(void) - { - return handle.get(); - } -}; - -} - -jack::client client("h-synth", JackNoStartServer); -jack::port p1(client, "in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); -jack::port p2(client, "out", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - -jack::ringbuffer midi_data(1024); - -std::atomic_int midi_events; -std::atomic<jack_nframes_t> buffer_size, sample_rate; - -int cb_buffer_size(jack_nframes_t nframes, void* arg) -{ - buffer_size = nframes; - return 0; -} -int cb_sample_rate(jack_nframes_t nframes, void* arg) -{ - sample_rate = nframes; - return 0; -} - -static struct voice -{ - float envelope, freq, sample; - unsigned short active : 1, velocity : 7, state : 3; -} voices[128]; - -int cb_process(jack_nframes_t nframes, void* arg) -{ - static char sustain; - void* buf = jack_port_get_buffer(*p1, nframes); - - jack_midi_event_t event; - for (uint32_t i = 0; not jack_midi_event_get(&event, buf, i); ++i) - { - auto& voice = voices[event.buffer[1]]; - switch (event.buffer[0] >> 4 & 0x7) - { - case 0: // Note off - voice.active = 0; - continue; - case 1: // Note on - voice.active = 1; - voice.state = 4; - voice.velocity = event.buffer[2]; - continue; - case 3: // Controller change - switch (event.buffer[1]) - { - case 0x40: // Sustain - sustain = event.buffer[2] > 63; - continue; - } - /* FALLTHROUGH */ - default: - midi_data.write(event.buffer, event.size); - ++midi_events; - } - } - - auto audio_buf = static_cast<float*>(jack_port_get_buffer(*p2, nframes)); - - int srate = sample_rate; - - for (unsigned i = 0; i < buffer_size; ++i) - { - float accum = 0; - for (auto& v : voices) - { - if (not v.state) - continue; - - float pos = v.sample * 6.28318530718f / srate; - float sample = std::sin(pos / 2) + std::sin(pos) + std::sin(pos) + std::sin(pos * 2)*0.7f + std::sin(pos * 3)*.7f + std::sin(pos * 4)/2; - v.sample += v.freq; - if (v.sample >= srate * 6) - v.sample -= srate * 6; - - float velocity = v.velocity / 127.f; - - switch (v.state) - { - case 4: - v.envelope += velocity / 1000.f; - if (v.envelope >= velocity) - { - v.envelope = velocity; - v.state = 3; - } - break; - - case 3: - v.envelope -= (v.envelope - velocity * 0.2f) * 0.00005f; - if (not (sustain || v.active)) - v.state = 2; - break; - - case 2: - v.envelope *= 0.9998f; - if (v.envelope < 0.01f && sample < 0) - v.state = 1; - break; - - case 1: - v.envelope *= 0.9998f; - if (sample >= 0) - { - v.envelope = 0; - v.sample = 0; - v.state = 0; - } - break; - } - accum += sample * v.envelope * .3; - } - audio_buf[i] = 2 / (1 + std::exp(accum)) - 1; - } - return 0; -} - -void print_midi_events(void) -{ - static int last = 0; - int current = midi_events; - - if (current == last) - return; - - last = current; - std::cout << "processed " << current << " MIDI events:\n" << std::hex; - - for (unsigned char buf; midi_data.read(&buf, sizeof(buf)); /**/) - std::cout << std::setw(3) << +buf; - - std::cout << std::dec << '\n'; -} - -void print_buffer_size(void) -{ - static int last = 0; - int current = buffer_size; - - if (current == last) - return; - - last = current; - std::cout << "Buffer size changed to: " << current << '\n'; -} -void print_sample_rate(void) -{ - static int last = 0; - int current = sample_rate; - - if (current == last) - return; - - last = current; - std::cout << "Sample rate changed to: " << current << '\n'; -} - -int main() -{ - int note = 0; - for (auto& v : voices) - { - v.envelope = 0; - v.freq = 440 * std::pow(std::pow(2.f, 1.f/12), note++ - 69); - } - - client.set_process_callback(cb_process, nullptr); - client.set_buffer_size_callback(cb_buffer_size, nullptr); - client.set_sample_rate_callback(cb_sample_rate, nullptr); - client.activate(); - - for (;;) - { - std::this_thread::sleep_for(10ms); - print_buffer_size(); - print_sample_rate(); - print_midi_events(); - } - - return 0; -} diff --git a/makefile b/makefile @@ -1,11 +1,12 @@ CXXFLAGS := -std=c++20 LDLIBS := -ljack -targets := main jack-cc-map +targets := jack-organ jack-cc-map all: $(targets) -jack-cc-map: jack-cc-map.cpp names.o +jack-cc-map: jack-cc-map.cpp names.o jack.cpp +jack-organ: jack.cpp clean: rm $(targets) *.o *.d