From b971b9b4c19f01689d1871b9bce7d3840abfd60c Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sun, 24 Jan 2010 20:46:33 +0000 Subject: [PATCH] Initial commit --- CMakeLists.txt | 63 ++++++++++++++++++++++++++++++++ client.cpp | 48 ++++++++++++++++++++++++ client.h | 40 ++++++++++++++++++++ cvin.cpp | 80 ++++++++++++++++++++++++++++++++++++++++ cvin.h | 36 ++++++++++++++++++ cvout.cpp | 84 ++++++++++++++++++++++++++++++++++++++++++ cvout.h | 32 ++++++++++++++++ example.cfg | 16 ++++++++ jm2cv.cpp | 85 +++++++++++++++++++++++++++++++++++++++++++ mapping.h | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 583 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 client.cpp create mode 100644 client.h create mode 100644 cvin.cpp create mode 100644 cvin.h create mode 100644 cvout.cpp create mode 100644 cvout.h create mode 100644 example.cfg create mode 100644 jm2cv.cpp create mode 100644 mapping.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..66b234a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required(VERSION 2.0) + +PROJECT(jm2cv) + +INCLUDE( ${CMAKE_ROOT}/Modules/FindPkgConfig.cmake ) +INCLUDE( ${CMAKE_ROOT}/Modules/CheckIncludeFile.cmake ) + +#pkg_check_modules(LV2CORE lv2core) +pkg_check_modules(JACK jack>=0.118) +#pkg_check_modules(GTKMM gtkmm-2.4>=2.4) +#pkg_check_modules(CAIROMM cairomm-1.0>=1.0) +#pkg_check_modules(DBUS dbus-glib-1) +#pkg_check_modules(PCRE libpcrecpp) + +#ADD_CUSTOM_COMMAND( +# OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/jsweeper.ui +# COMMAND gtk-builder-convert ${CMAKE_CURRENT_SOURCE_DIR}/src/jsweeper.glade ${CMAKE_CURRENT_BINARY_DIR}/jsweeper.ui +# DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/jsweeper.glade +#) + +SET(SOURCES + client.cpp + client.h + cvin.cpp + cvin.h + cvout.cpp + cvout.h + jm2cv.cpp +) + +LINK_DIRECTORIES( +# ${LV2CORE_LIBRARY_DIRS} + ${JACK_LIBRARY_DIRS} +# ${GTKMM_LIBRARY_DIRS} +# ${CAIROMM_LIBRARY_DIRS} +# ${DBUS_LIBRARY_DIRS} +# ${PCRE_LIBRARY_DIRS} +) + +INCLUDE_DIRECTORIES( +# ${LV2CORE_INCLUDE_DIRS} + ${JACK_INCLUDE_DIRS} +# ${GTKMM_INCLUDE_DIRS} +# ${CAIROMM_INCLUDE_DIRS} +# ${DBUS_INCLUDE_DIRS} +# ${PCRE_INCLUDE_DIRS} +) + +ADD_DEFINITIONS(-g -Wall -Wextra -pedantic -D_GNU_SOURCE) +ADD_EXECUTABLE(jm2cv ${SOURCES}) + +TARGET_LINK_LIBRARIES(jm2cv + -lm +# ${LV2CORE_LIBRARIES} + ${JACK_LIBRARIES} +# ${GTKMM_LIBRARIES} +# ${CAIROMM_LIBRARIES} +# ${DBUS_LIBRARIES} +# ${PCRE_LIBRARIES} +) + +INSTALL(PROGRAMS jm2cv DESTINATION bin) +#INSTALL(FILES jsweeper.ui DESTINATION share/jsweeper) diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000..da5e6ce --- /dev/null +++ b/client.cpp @@ -0,0 +1,48 @@ +#include +#include "client.h" + +bool Client::open(const char *name) +{ + jack_status_t status; + m_client = jack_client_open(name, JackNoStartServer, &status); + if (m_client == NULL) { + if (status & JackServerFailed) { + std::cerr << "JACK server not running" << std::endl; + } else { + std::cerr << "jack_client_open() failed, status = " << status << std::endl; + } + + return false; + } + + m_name = jack_get_client_name(m_client); + m_sample_rate = jack_get_sample_rate(m_client); + + jack_on_shutdown(m_client, &shutdown, this); + jack_set_process_callback(m_client, &process, this); + + jack_activate(m_client); + + return true; +} + +void Client::close() +{ + jack_deactivate(m_client); + jack_client_close(m_client); +} + +void Client::shutdown(void *arg) +{ + ((Client *)arg)->shutdown(); +} + +int Client::process(jack_nframes_t nframes, void *arg) +{ + return ((Client *)arg)->process(nframes); +} + +jack_port_t *Client::port_register(const char *port_name, const char *port_type, unsigned long flags, unsigned long buffer_size) +{ + return jack_port_register(m_client, port_name, port_type, flags, buffer_size); +} diff --git a/client.h b/client.h new file mode 100644 index 0000000..1ad14d0 --- /dev/null +++ b/client.h @@ -0,0 +1,40 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include + +typedef jack_default_audio_sample_t sample_t; +typedef jack_nframes_t tick_t; + +class Client +{ +private: + jack_client_t *m_client; +protected: + jack_port_t **m_ports; + const char *m_name; + jack_nframes_t m_sample_rate; + +private: + static void shutdown(void *arg); + static int process(jack_nframes_t nframes, void *arg); + + virtual void shutdown() = 0; + virtual int process(jack_nframes_t) = 0; + +public: + Client() : m_client(NULL), m_ports(NULL), m_name(NULL), m_sample_rate(0) + { + } + + ~Client() + { + } + + bool open(const char *name); + void close(); + + jack_port_t *port_register(const char *port_name, const char *port_type, unsigned long flags, unsigned long buffer_size); +}; + +#endif // CLIENT_H diff --git a/cvin.cpp b/cvin.cpp new file mode 100644 index 0000000..85e10ba --- /dev/null +++ b/cvin.cpp @@ -0,0 +1,80 @@ +#include +#include +#include "cvin.h" + +void CVIn::shutdown() +{ +} + +int CVIn::process(jack_nframes_t nframes) +{ + if (!m_ready) return 0; + + void *midi_out = jack_port_get_buffer(m_midi_out, nframes); + jack_midi_clear_buffer(midi_out); + + for (unsigned i = 0; i < m_mapping_list.size(); i++) { + m_buffers[i] = static_cast(jack_port_get_buffer(m_ports[i], nframes)); + } + + for (jack_nframes_t f = 0; f < nframes; f++) { + int port = 0; + MappingList::iterator it; + for (it = m_it_begin; it != m_it_end; ++it, port++) { + Mapping *m = &(*it); + + m->cur_mv = m->to_mv(*m_buffers[port]++); + + if (m->tick_cvin()) { + if (m->cclsb == -1) { + jack_midi_data_t buf[3]; + buf[0] = 0xB0 | m->channel; + buf[1] = m->ccmsb; + buf[2] = m->last_mv; + jack_midi_event_write(midi_out, f, buf, sizeof buf); + } else { + jack_midi_data_t buf[3]; + buf[0] = 0xB0 | m->channel; + buf[1] = m->ccmsb; + buf[2] = m->last_mv >> 7; + jack_midi_event_write(midi_out, f, buf, sizeof buf); + buf[1] = m->cclsb; + buf[2] = m->last_mv & 0x7F; + jack_midi_event_write(midi_out, f, buf, sizeof buf); + } + } + } + } + return 0; +} + +void CVIn::start() +{ + m_ready = false; + m_tick = 0; + + if (m_mapping_list.size() == 0) return; + + m_ports = new jack_port_t *[m_mapping_list.size()]; + m_buffers = new sample_t *[m_mapping_list.size()]; + + open("m2cv_in"); + + m_midi_out = port_register("midi_out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); + + MappingList::iterator it_begin = m_mapping_list.begin(); + MappingList::iterator it_end = m_mapping_list.end(); + + int port = 0; + MappingList::iterator it; + for (it = it_begin; it != it_end; ++it, port++) { + Mapping *m = &(*it); + m_ports[port] = port_register(m->name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + + m->latency_ticks = m_sample_rate * m->latency / 1000.0f; + } + + m_it_end = it_end; + m_it_begin = it_begin; + m_ready = true; +} diff --git a/cvin.h b/cvin.h new file mode 100644 index 0000000..b1a0b77 --- /dev/null +++ b/cvin.h @@ -0,0 +1,36 @@ +#ifndef CVIN_H +#define CVIN_H + +#include "client.h" +#include "mapping.h" + +class CVIn : Client +{ +private: + MappingList m_mapping_list; + jack_port_t *m_midi_out; + bool m_ready; + tick_t m_tick; + + sample_t **m_buffers; + + MappingList::iterator m_it_begin; + MappingList::iterator m_it_end; + + void shutdown(); + int process(jack_nframes_t nframes); + +public: + void add_mapping(Mapping m) + { + m_mapping_list.push_back(m); + } + + void start(); + void stop() + { + close(); + } +}; + +#endif // CVIN_H diff --git a/cvout.cpp b/cvout.cpp new file mode 100644 index 0000000..923951f --- /dev/null +++ b/cvout.cpp @@ -0,0 +1,84 @@ +#include +#include +#include "cvout.h" + +void CVOut::shutdown() +{ +} + +int CVOut::process(jack_nframes_t nframes) +{ + if (!m_ready) return 0; + + void *midi_in = jack_port_get_buffer(m_midi_in, nframes); + + for (unsigned i = 0; i < m_mapping_list.size(); i++) { + m_buffers[i] = static_cast(jack_port_get_buffer(m_ports[i], nframes)); + } + + jack_nframes_t event_count = jack_midi_get_event_count(midi_in); + jack_nframes_t event_index = 0; + jack_midi_event_t ev; + + if (0 < event_count) jack_midi_event_get(&ev, midi_in, event_index); + + for (jack_nframes_t f = 0; f < nframes; f++) { + while (ev.time == f && event_index < event_count) { + if ((ev.buffer[0] & 0xF0) == 0xB0) { + // Do what + int channel = ev.buffer[0] & 0x0F; + + MappingList::iterator it; + for (it = m_it_begin; it != m_it_end; ++it) { + Mapping *m = &(*it); + m->handle_cvout(channel, ev.buffer[1], ev.buffer[2], m_tick); + } + } + + event_index++; + if (event_index < event_count) jack_midi_event_get(&ev, midi_in, event_index); + } + + int port = 0; + MappingList::iterator it; + for (it = m_it_begin; it != m_it_end; ++it, port++) { + const Mapping *m = &(*it); + + *m_buffers[port]++ = m->last_cv; + } + + m_tick++; + } + return 0; +} + +void CVOut::start() +{ + m_ready = false; + m_tick = 0; + + if (m_mapping_list.size() == 0) return; + + m_ports = new jack_port_t *[m_mapping_list.size()]; + m_buffers = new sample_t *[m_mapping_list.size()]; + + open("m2cv_out"); + + m_midi_in = port_register("midi_in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); + + MappingList::iterator it_begin = m_mapping_list.begin(); + MappingList::iterator it_end = m_mapping_list.end(); + + int port = 0; + MappingList::iterator it; + for (it = it_begin; it != it_end; ++it, port++) { + Mapping *m = &(*it); + m_ports[port] = port_register(m->name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + + m->latency_ticks = m_sample_rate * m->latency / 1000.0f; + } + + m_it_end = it_end; + m_it_begin = it_begin; + m_ready = true; +} diff --git a/cvout.h b/cvout.h new file mode 100644 index 0000000..346f707 --- /dev/null +++ b/cvout.h @@ -0,0 +1,32 @@ +#include "client.h" +#include "mapping.h" + +class CVOut : Client +{ +private: + MappingList m_mapping_list; + jack_port_t *m_midi_in; + bool m_ready; + tick_t m_tick; + + sample_t **m_buffers; + + MappingList::iterator m_it_begin; + MappingList::iterator m_it_end; + + void shutdown(); + int process(jack_nframes_t nframes); + +public: + void add_mapping(Mapping m) + { + m_mapping_list.push_back(m); + } + + void start(); + void stop() + { + close(); + } +}; + diff --git a/example.cfg b/example.cfg new file mode 100644 index 0000000..11d92ed --- /dev/null +++ b/example.cfg @@ -0,0 +1,16 @@ +#type name chan, cc MSB/LSB, midi range, cv range, latency +cvout pan -1 10 -1 0 127 -1.0 1.0 10 +cvout reverb -1 91 -1 0 127 -1.0 1.0 10 +cvout exp -1 11 -1 0 127 -1.0 1.0 10 +cvout vol1 0 7 -1 0 127 -1.0 1.0 10 +cvout vol2 1 7 -1 0 127 -1.0 1.0 10 +cvout vol3 2 7 -1 0 127 -1.0 1.0 10 +cvout vol4 3 7 -1 0 127 -1.0 1.0 10 +cvout vol5 4 7 -1 0 127 -1.0 1.0 10 +cvout vol6 5 7 -1 0 127 -1.0 1.0 10 +cvout vol7 6 7 -1 0 127 -1.0 1.0 10 +cvout vol8 7 7 -1 0 127 -1.0 1.0 10 +cvout mod -1 1 33 0 16383 -1.0 1.0 10 +cvin mod 0 1 33 0 16383 -1.0 1.0 10 +cvin vol1 0 7 -1 0 127 -1.0 1.0 10 + diff --git a/jm2cv.cpp b/jm2cv.cpp new file mode 100644 index 0000000..cd84ebc --- /dev/null +++ b/jm2cv.cpp @@ -0,0 +1,85 @@ +#include +#include +#include + +#include "cvout.h" +#include "cvin.h" + +static CVOut cvout; +static CVIn cvin; + +bool read_config(const char *filename) +{ + FILE *f = fopen(filename, "r"); + if (f == NULL) { + std::cerr << "Unable to open '" << filename << "' for reading" << std::endl; + return false; + } + + while (!feof(f)) { + char buf[80]; + fgets(buf, sizeof buf, f); + + /* Ignore comments */ + if (buf[0] == '#') continue; + + char type[80], name[80]; + int channel, ccmsb, cclsb, mrl, mru; + float crl, cru; + float latency; + if (sscanf(buf, "%s %s %d %d %d %d %d %f %f %f", type, name, &channel, &ccmsb, &cclsb, &mrl, &mru, &crl, &cru, &latency) == 10) { + + if (ccmsb < 0 || ccmsb > 127) continue; + if (cclsb < -1 || cclsb > 127) continue; + if (mrl < 0 || mrl > (cclsb == -1 ? 127 : 16383)) continue; + if (mru < 0 || mru > (cclsb == -1 ? 127 : 16383)) continue; + if (mrl > mru) continue; +// if (crl < -1.0f || crl > 1.0f) continue; +// if (cru < -1.0f || cru > 1.0f) continue; + if (crl > cru) continue; + if (latency < 0.0f) continue; + + if (!strcmp(type, "cvout")) { + if (channel < -1 || channel > 15) continue; + cvout.add_mapping(Mapping(name, channel, ccmsb, cclsb, mrl, mru, crl, cru, latency)); + } else if (!strcmp(type, "cvin")) { + if (channel < 0 || channel > 15) continue; + cvin.add_mapping(Mapping(name, channel, ccmsb, cclsb, mrl, mru, crl, cru, latency)); + } + } + } + + return true; +} + +static int _running = false; + +static void sigint_handler(int) +{ + _running = false; +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 0; + } + + if (!read_config(argv[1])) return 0; + + cvout.start(); + cvin.start(); + + _running = true; + + signal(SIGINT, &sigint_handler); + signal(SIGTERM, &sigint_handler); + + while (_running) { + sleep(1); + } + + cvin.stop(); + cvout.stop(); +} diff --git a/mapping.h b/mapping.h new file mode 100644 index 0000000..a6b353e --- /dev/null +++ b/mapping.h @@ -0,0 +1,99 @@ +#ifndef MAPPING_H +#define MAPPING_H + +#include +#include + +class Mapping +{ +public: + std::string name; + int channel; + int ccmsb, cclsb; + int mrl, mru; + float crl, cru; + float latency; + + float adj1; + float adj2; + tick_t latency_ticks; + + int cur_mv; + int last_mv; + sample_t cur_cv; + sample_t last_cv; + + tick_t last_tick; + tick_t since_last_tick; + + Mapping(const char *name, + int channel, int ccmsb, int cclsb, + int mrl, int mru, float crl, float cru, float latency) : + name(name), channel(channel), ccmsb(ccmsb), cclsb(cclsb), + mrl(mrl), mru(mru), crl(crl), cru(cru), latency(latency), + cur_mv(0), last_mv(0), + last_cv(0.0), + last_tick(0), since_last_tick(0) + { + /* Set up adjustment values for converting between + * MIDI values and CV values for this mapping. This + * results in scaling equations that are two ops. */ + adj1 = (cru - crl) / (mru - mrl); + adj2 = (-crl / adj1) + mrl; + } + + void interp_cvout(tick_t tick) + { + cur_cv = to_cv(cur_mv); + last_tick = tick; + last_cv = cur_cv; + } + + void tick_cvout() + { + if (last_cv == cur_cv) return; + since_last_tick++; + } + + bool tick_cvin() + { + since_last_tick++; + + if (last_mv == cur_mv) return false; + if (since_last_tick < latency_ticks) return false; + + since_last_tick = 0; + last_mv = cur_mv; + return true; + } + + void handle_cvout(int c, int cc, int val, tick_t tick) + { + if (c != channel && channel != -1) return; + if (cc == ccmsb) { + if (cclsb == -1) { + cur_mv = val; + interp_cvout(tick); + } else { + cur_mv = val << 7; + } + } else if (cc == cclsb) { + cur_mv |= val; + interp_cvout(tick); + } + } + + inline float to_cv(int mv) + { + return (mv - adj2) * adj1; + } + + inline int to_mv(float cv) + { + return cv / adj1 + adj2; + } +}; + +typedef std::list MappingList; + +#endif // MAPPING_H