Raylib 3
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

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);
}
}