You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
8.6 KiB
267 lines
8.6 KiB
2 years ago
|
//Standard lib
|
||
|
#include <string.h> //memcpy
|
||
|
#include <stdio.h> //print error messages
|
||
|
#include <unistd.h>
|
||
|
|
||
|
|
||
|
//Third party
|
||
|
#include <jack/jack.h>
|
||
|
#include <jack/midiport.h>
|
||
|
#include <jack/ringbuffer.h>
|
||
|
#include <jack/metadata.h>
|
||
|
|
||
|
//Our own files
|
||
|
#include "jackclient.h"
|
||
|
#include "constants.h"
|
||
|
#include "programstate.h"
|
||
|
|
||
|
extern ProgramState programState;
|
||
|
|
||
|
static jack_client_t* client;
|
||
|
//static jack_ringbuffer_t *ringbuffer = NULL;
|
||
|
|
||
|
jack_ringbuffer_t *allRingbuffers[VIS_PORTS];
|
||
|
jack_port_t *allPorts[VIS_PORTS];
|
||
|
|
||
|
static pthread_mutex_t msg_thread_lock = PTHREAD_MUTEX_INITIALIZER;
|
||
|
static pthread_cond_t data_ready = PTHREAD_COND_INITIALIZER;
|
||
|
|
||
|
#define RBSIZE 512
|
||
|
|
||
|
typedef struct {
|
||
|
uint8_t buffer[128];
|
||
|
uint32_t size;
|
||
|
} midimsg;
|
||
|
|
||
|
|
||
|
int process (jack_nframes_t frames, void* arg) {
|
||
|
void* buffer;
|
||
|
jack_nframes_t N;
|
||
|
jack_nframes_t i;
|
||
|
jack_port_t* port;
|
||
|
jack_ringbuffer_t* rb=NULL;
|
||
|
|
||
|
for (int portnum=0; portnum<VIS_PORTS; portnum++) {
|
||
|
port = allPorts[portnum];
|
||
|
rb = allRingbuffers[portnum];
|
||
|
buffer = jack_port_get_buffer (port, frames);
|
||
|
//assert (buffer);
|
||
|
|
||
|
N = jack_midi_get_event_count (buffer);
|
||
|
for (int i = 0; i < N; ++i) {
|
||
|
jack_midi_event_t event;
|
||
|
int r;
|
||
|
|
||
|
r = jack_midi_event_get (&event, buffer, i);
|
||
|
|
||
|
if (r == 0 && jack_ringbuffer_write_space(rb) >= sizeof(midimsg)) {
|
||
|
midimsg message;
|
||
|
message.size = event.size;
|
||
|
memcpy (message.buffer, event.buffer, MAX(sizeof(message.buffer), event.size));
|
||
|
jack_ringbuffer_write (rb, (void *) &message, sizeof(midimsg));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (pthread_mutex_trylock (&msg_thread_lock) == 0) {
|
||
|
pthread_cond_signal (&data_ready);
|
||
|
pthread_mutex_unlock (&msg_thread_lock);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void handleMessage (Note *allGUINotes, int portnum, midimsg* event) {
|
||
|
if (event->size == 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
uint8_t type = event->buffer[0] & 0xf0;
|
||
|
uint8_t channel = event->buffer[0] & 0xf;
|
||
|
Note * note;
|
||
|
|
||
|
switch (type) {
|
||
|
case 0x90:
|
||
|
//assert (event->size == 3);
|
||
|
//printf (" note on (channel %2d): pitch %3d, velocity %3d\n", channel, event->buffer[1], event->buffer[2]);
|
||
|
programState.portActivity[portnum] += 1;
|
||
|
note = &allGUINotes[portnum*128 + event->buffer[1]];
|
||
|
note->active = 1;
|
||
|
note->countdown = 1.0f;
|
||
|
note->velocity = event->buffer[2];
|
||
|
note->pitch = event->buffer[1];
|
||
|
note->port = portnum;
|
||
|
break;
|
||
|
case 0x80:
|
||
|
//assert (event->size == 3);
|
||
|
//printf (" note off (channel %2d): pitch %3d, velocity %3d\n", channel, event->buffer[1], event->buffer[2]);
|
||
|
programState.portActivity[portnum] -= 1;
|
||
|
if (programState.portActivity[portnum] < 1) { programState.portActivity[portnum] = 0;}
|
||
|
note = &allGUINotes[portnum*128 + event->buffer[1]];
|
||
|
note->countdown = 0.999f; //GUI will reduce that to 0 on its own as fade-out effect
|
||
|
//Do NOT set to inactive. Note-Off triggers a graphical fadeout, only when that is over it is inactive.
|
||
|
break;
|
||
|
//case 0xb0:
|
||
|
//assert (event->size == 3);
|
||
|
//printf (" control change (channel %2d): controller %3d, value %3d", channel, event->buffer[1], event->buffer[2]);
|
||
|
// break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void mainLoop_processMidiMessages(Note *allGUINotes) {
|
||
|
|
||
|
for (int portnum=0; portnum<VIS_PORTS; portnum++) {
|
||
|
int mqlen = jack_ringbuffer_read_space (allRingbuffers[portnum]) / sizeof(midimsg);
|
||
|
for (int i=0; i < mqlen; ++i) {
|
||
|
size_t j;
|
||
|
midimsg message;
|
||
|
jack_ringbuffer_read(allRingbuffers[portnum], (char*) &message, sizeof(midimsg));
|
||
|
handleMessage (allGUINotes, portnum, &message);
|
||
|
}
|
||
|
}
|
||
|
pthread_cond_wait (&data_ready, &msg_thread_lock);
|
||
|
|
||
|
}
|
||
|
|
||
|
void mainLoop_collectTimingAndTransportInProgramState() {
|
||
|
jack_position_t pos;
|
||
|
jack_transport_state_t state = jack_transport_query(client, &pos);
|
||
|
|
||
|
int totalSeconds = pos.frame / pos.frame_rate;
|
||
|
int hours = totalSeconds / 3600; //int devision drops decimal
|
||
|
int minutes = totalSeconds / 60 % 60;
|
||
|
int seconds = totalSeconds % 60;
|
||
|
|
||
|
snprintf(programState.clockString, (sizeof(char) * 255), "%i:%02i:%02i ( %is )", hours, minutes, seconds, totalSeconds);
|
||
|
|
||
|
programState.transportRolling = state != JackTransportStopped;
|
||
|
|
||
|
if (pos.beats_per_minute > 1.0d) { //comparing to 0 does not work
|
||
|
programState.bpm = pos.beats_per_minute;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
programState.bpm = 120.0d;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void ourConnectCallback(jack_port_id_t a, jack_port_id_t b, int connect, void *arg) {
|
||
|
|
||
|
//On each connection change go through our ports and check the situation.
|
||
|
//Only the first port is enough for our purposes
|
||
|
|
||
|
//TODO: optimisation opportunity: keep a list of our port ids and check that first.
|
||
|
|
||
|
for (int portnum=0; portnum<VIS_PORTS; portnum++) {
|
||
|
const char** connectedPorts;
|
||
|
connectedPorts = jack_port_get_connections(allPorts[portnum]);
|
||
|
|
||
|
if (connectedPorts && connectedPorts[0])
|
||
|
{
|
||
|
//We could use connectedPorts[0] directly as a name, this is the real portname.
|
||
|
//However, we try to get a pretty name from Jack Metadata first
|
||
|
jack_port_t * externalPort;
|
||
|
externalPort = jack_port_by_name(client, connectedPorts[0]);
|
||
|
jack_uuid_t externalId = jack_port_uuid(externalPort);
|
||
|
char * externalPrettyName;
|
||
|
char * externalType;
|
||
|
int returnCode = jack_get_property(externalId, JACK_METADATA_PRETTY_NAME, &externalPrettyName, &externalType);
|
||
|
if (!returnCode && externalPrettyName) {
|
||
|
//const char * prettyPortConst = externalPrettyName;
|
||
|
//strcpy(prettyPortNameCopy, externalPrettyName); //strcpy is const -> non-const
|
||
|
programState.connectedPortNames[portnum] = externalPrettyName;
|
||
|
//if (externalPrettyName) jack_free(externalPrettyName); //TODO: If I free that the name either vanishes, segfaults, is wrong or garbage or it is always the same. No matter what string copy, conversion etc. I tried
|
||
|
if (externalType) jack_free(externalType);
|
||
|
}
|
||
|
else { // No Metadata for this port found
|
||
|
programState.connectedPortNames[portnum] = connectedPorts[0];
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
programState.connectedPortNames[portnum] = ""; //Reset
|
||
|
}
|
||
|
jack_free(connectedPorts);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void createJackClient() {
|
||
|
|
||
|
int errorCode;
|
||
|
|
||
|
client = jack_client_open (programState.name, JackNullOption, NULL);
|
||
|
if (client == NULL) {
|
||
|
fprintf (stderr, "Could not create JACK client.\n");
|
||
|
exit (EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
jack_set_process_callback (client, process, 0);
|
||
|
|
||
|
for (int portnum=0; portnum<VIS_PORTS;portnum++) {
|
||
|
allRingbuffers[portnum] = jack_ringbuffer_create (RBSIZE * sizeof(midimsg)); //Communicate between RT and Non-RT
|
||
|
char name[64];
|
||
|
sprintf(name, "%i", portnum);
|
||
|
allPorts[portnum] = jack_port_register (client, name, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
|
||
|
if (allPorts[portnum] == NULL) {
|
||
|
fprintf (stderr, "Could not register port.\n");
|
||
|
exit (EXIT_FAILURE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Callbacks must be registered before activating the client
|
||
|
errorCode = jack_set_port_connect_callback(client, ourConnectCallback, 0);
|
||
|
if (errorCode != 0) {
|
||
|
fprintf (stderr, "Could not register port registration callback.\n");
|
||
|
exit (EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
//Now activate
|
||
|
errorCode = jack_activate (client);
|
||
|
if (errorCode != 0) {
|
||
|
fprintf (stderr, "Could not activate client.\n");
|
||
|
exit (EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
void closeJackClient() {
|
||
|
pthread_mutex_unlock (&msg_thread_lock);
|
||
|
jack_deactivate (client);
|
||
|
jack_client_close (client);
|
||
|
for (int portnum=0; portnum<VIS_PORTS;portnum++) {
|
||
|
jack_ringbuffer_free (allRingbuffers[portnum]);
|
||
|
}
|
||
|
printf("Jack Client closed succesfully\n");
|
||
|
}
|
||
|
|
||
|
void seekToStart() {
|
||
|
jack_transport_locate(client, 0);
|
||
|
}
|
||
|
|
||
|
void toggleTransport() {
|
||
|
//This is only the active toggle from our side. No reaction to external transport
|
||
|
|
||
|
//Called from non-rt thread
|
||
|
jack_position_t pos;
|
||
|
jack_transport_state_t state = jack_transport_query(client, &pos);
|
||
|
|
||
|
if (state == JackTransportStopped) {
|
||
|
jack_transport_start(client);
|
||
|
}
|
||
|
else if (state == JackTransportStarting) {
|
||
|
//Waiting for sync ready
|
||
|
}
|
||
|
else {
|
||
|
jack_transport_stop(client);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|