commit b971b9b4c19f01689d1871b9bce7d3840abfd60c Author: Peter Nelson Date: Sun Jan 24 20:46:33 2010 +0000 Initial commit 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