commit
b971b9b4c1
10 changed files with 583 additions and 0 deletions
@ -0,0 +1,63 @@
@@ -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) |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
#include <iostream> |
||||
#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); |
||||
} |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
#ifndef CLIENT_H |
||||
#define CLIENT_H |
||||
|
||||
#include <jack/jack.h> |
||||
|
||||
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
|
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
#include <jack/jack.h> |
||||
#include <jack/midiport.h> |
||||
#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<sample_t *>(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; |
||||
} |
@ -0,0 +1,36 @@
@@ -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
|
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
#include <jack/jack.h> |
||||
#include <jack/midiport.h> |
||||
#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<sample_t *>(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; |
||||
} |
@ -0,0 +1,32 @@
@@ -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(); |
||||
} |
||||
}; |
||||
|
@ -0,0 +1,16 @@
@@ -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 |
||||
|
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
#include <iostream> |
||||
#include <cstring> |
||||
#include <csignal> |
||||
|
||||
#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] << " <config>" << 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(); |
||||
} |
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
#ifndef MAPPING_H |
||||
#define MAPPING_H |
||||
|
||||
#include <string> |
||||
#include <list> |
||||
|
||||
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<Mapping> MappingList; |
||||
|
||||
#endif // MAPPING_H
|
Loading…
Reference in new issue