Browse Source

Dmitry Baikov MIDI patch : alsa_seqmidi and alsa_rammidi drivers.

git-svn-id: http://subversion.jackaudio.org/jack/jack2/trunk/jackmp@1760 0c269be4-1314-0410-8aa9-9f06e86f4224
tags/0.69
sletz 18 years ago
parent
commit
bb63aa781e
10 changed files with 2370 additions and 14 deletions
  1. +4
    -0
      ChangeLog
  2. +6
    -4
      linux/Makefile
  3. +119
    -8
      linux/alsa/JackAlsaDriver.cpp
  4. +19
    -2
      linux/alsa/JackAlsaDriver.h
  5. +5
    -0
      linux/alsa/alsa_driver.h
  6. +44
    -0
      linux/alsa/alsa_midi.h
  7. +86
    -0
      linux/alsa/alsa_midi_impl.h
  8. +60
    -0
      linux/alsa/alsa_midi_jackmp.cpp
  9. +1172
    -0
      linux/alsa/alsa_rawmidi.c
  10. +855
    -0
      linux/alsa/alsa_seqmidi.c

+ 4
- 0
ChangeLog View File

@@ -17,6 +17,10 @@ Tim Blechmann
Jackdmp changes log
---------------------------

2008-01-03 Stephane Letz <letz@grame.fr>

* Dmitry Baikov MIDI patch : alsa_seqmidi and alsa_rammidi drivers.

2008-01-03 Stephane Letz <letz@grame.fr>

* Tim Blechmann patch for JackGraphManager::GetPortsAux memory leak, Tim Blechmann patch for scons install.


+ 6
- 4
linux/Makefile View File

@@ -44,7 +44,9 @@ objects_common_client_lib := JackActivationCount.o JackAPI.o JackClient.o JackCo
objects_linux_server := Jackdmp.o

objects_linux_alsa := JackAlsaDriver.o memops.o generic_hw.o hdsp.o hammerfall.o ice1712.o
objects_linux_alsamidi := alsa_rawmidi.o alsa_seqmidi.o alsa_midi_jackmp.o

objects_linux_alsa := JackAlsaDriver.o memops.o generic_hw.o hdsp.o hammerfall.o ice1712.o $(objects_linux_alsamidi)

objects_linux_freebob := JackFreebobDriver.o

@@ -52,8 +54,8 @@ objects_linux_firewire := JackFFADODriver.o

objects_linux_dummy := JackDummyDriver.o

CFLAGS := -g -fPIC -DUSE_POSIX_SHM $(addprefix -I, $(subprojects)) $(CFLAGS)
CXXFLAGS := -g -fPIC -DSOCKET_RPC_FIFO_SEMA -D__SMP__ -DADDON_DIR=\"$(prefix)\" -DLIB_DIR=\"$(libdir)\" -DJACK_LOCATION=\"$(prefix)/bin\" $(addprefix -I, $(subprojects)) $(CXXFLAGS)
CFLAGS := -g -fPIC -DJACKMP -DUSE_POSIX_SHM $(addprefix -I, $(subprojects)) $(CFLAGS)
CXXFLAGS := -g -fPIC -DJACKMP -DSOCKET_RPC_FIFO_SEMA -D__SMP__ -DADDON_DIR=\"$(prefix)\" -DLIB_DIR=\"$(libdir)\" -DJACK_LOCATION=\"$(prefix)/bin\" $(addprefix -I, $(subprojects)) $(CXXFLAGS)

#CFLAGS := -g -fPIC -DUSE_POSIX_SHM $(addprefix -I, $(subprojects)) $(CFLAGS)
#CXXFLAGS := -g -fPIC -DSOCKET_RPC_FIFO_SEMA -D__SMP__ -DADDON_DIR=\"$(prefix)\" -DLIB_DIR=\"$(libdir)\" -DJACK_LOCATION=\"$(prefix)/bin\" $(addprefix -I, $(subprojects)) $(CXXFLAGS)
@@ -110,7 +112,7 @@ $(TARGET_LINUX_SERVER) : $(objects_linux_server)
$(CXX) $(CXXFLAGS) $(objects_linux_server) $(LIB_LINUX) libjackdmp.so -o $(TARGET_LINUX_SERVER)

$(TARGET_LINUX_ALSA) : $(objects_linux_alsa)
$(CXX) $(CXXFLAGS) -shared $(objects_linux_alsa) $(LIB_LINUX) libjackdmp.so -o $(TARGET_LINUX_ALSA)
$(CXX) $(CXXFLAGS) -shared $(objects_linux_alsa) $(LIB_LINUX) ringbuffer.o libjackmp.so libjackdmp.so -o $(TARGET_LINUX_ALSA)

$(TARGET_LINUX_FREEBOB) : $(objects_linux_freebob)
$(CXX) $(CXXFLAGS) -shared $(objects_linux_freebob) $(LIB_LINUX) -lfreebob libjackdmp.so -o $(TARGET_LINUX_FREEBOB)


+ 119
- 8
linux/alsa/JackAlsaDriver.cpp View File

@@ -48,6 +48,8 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#include "generic.h"
#include "memops.h"

#include "JackPosixThread.h"

namespace Jack
{

@@ -1030,6 +1032,9 @@ JackAlsaDriver::alsa_driver_start (alsa_driver_t *driver)
driver->pfd = (struct pollfd *)
malloc (sizeof (struct pollfd) *
(driver->playback_nfds + driver->capture_nfds + 2));
if (driver->midi && !driver->xrun_recovery)
(driver->midi->start)(driver->midi);

if (driver->playback_handle) {
/* fill playback buffer with zeroes, and mark
@@ -1143,6 +1148,9 @@ JackAlsaDriver::alsa_driver_stop (alsa_driver_t *driver)
if (driver->hw_monitoring) {
driver->hw->set_input_monitor_mask (driver->hw, 0);
}
if (driver->midi && !driver->xrun_recovery)
(driver->midi->stop)(driver->midi);

return 0;
}
@@ -1157,9 +1165,12 @@ JackAlsaDriver::alsa_driver_restart (alsa_driver_t *driver)
return driver->nt_start((struct _jack_driver_nt *) driver);
*/

if (Stop())
return -1;
return Start();
driver->xrun_recovery = 1;
int res = Stop();
if (!res)
res = Start();
driver->xrun_recovery = 0;
return res;
}

int
@@ -1547,6 +1558,9 @@ JackAlsaDriver::alsa_driver_read (alsa_driver_t *driver, jack_nframes_t nframes)
if (nframes > driver->frames_per_cycle) {
return -1;
}
if (driver->midi)
(driver->midi->read)(driver->midi, nframes);

nread = 0;
contiguous = 0;
@@ -1635,6 +1649,9 @@ JackAlsaDriver::alsa_driver_write (alsa_driver_t* driver, jack_nframes_t nframes
if (nframes > driver->frames_per_cycle) {
return -1;
}
if (driver->midi)
(driver->midi->write)(driver->midi, nframes);

nwritten = 0;
contiguous = 0;
@@ -1747,6 +1764,9 @@ JackAlsaDriver::alsa_driver_delete (alsa_driver_t *driver)
{
JSList *node;

if (driver->midi)
(driver->midi->destroy)(driver->midi);

for (node = driver->clock_sync_listeners; node;
node = jack_slist_next (node)) {
free (node->data);
@@ -1817,7 +1837,8 @@ JackAlsaDriver::alsa_driver_new (const char *name, char *playback_alsa_device,
int user_playback_nchnls,
int shorts_first,
jack_nframes_t capture_latency,
jack_nframes_t playback_latency
jack_nframes_t playback_latency,
alsa_midi_t *midi
)
{
int err;
@@ -1840,6 +1861,9 @@ JackAlsaDriver::alsa_driver_new (const char *name, char *playback_alsa_device,
driver = (alsa_driver_t *) calloc (1, sizeof (alsa_driver_t));

jack_driver_nt_init ((jack_driver_nt_t *) driver);
driver->midi = midi;
driver->xrun_recovery = 0;

//driver->nt_attach = (JackDriverNTAttachFunction) alsa_driver_attach;
//driver->nt_detach = (JackDriverNTDetachFunction) alsa_driver_detach;
@@ -2133,9 +2157,24 @@ int JackAlsaDriver::Attach()
}
}

if (alsa_driver->midi) {
int err = (alsa_driver->midi->attach)(alsa_driver->midi);
if (err)
jack_error ("ALSA: cannot attach MIDI: %d", err);
}

return 0;
}

int JackAlsaDriver::Detach()
{
alsa_driver_t* alsa_driver = (alsa_driver_t*)fDriver;
if (alsa_driver->midi)
(alsa_driver->midi->detach)(alsa_driver->midi);

return JackDriver::Detach();
}

int JackAlsaDriver::Open(jack_nframes_t nframes,
jack_nframes_t user_nperiods,
jack_nframes_t samplerate,
@@ -2152,7 +2191,8 @@ int JackAlsaDriver::Open(jack_nframes_t nframes,
const char* capture_driver_name,
const char* playback_driver_name,
jack_nframes_t capture_latency,
jack_nframes_t playback_latency)
jack_nframes_t playback_latency,
const char* midi_driver_name)
{
// Generic JackAudioDriver Open
if (JackAudioDriver::Open(nframes, samplerate, capturing, playing,
@@ -2160,6 +2200,12 @@ int JackAlsaDriver::Open(jack_nframes_t nframes,
capture_latency, playback_latency) != 0) {
return -1;
}
alsa_midi_t *midi = 0;
if (strcmp(midi_driver_name, "seq") == 0)
midi = alsa_seqmidi_new((jack_client_t*)this, 0);
else if (strcmp(midi_driver_name, "raw") == 0)
midi = alsa_rawmidi_new((jack_client_t*)this);

fDriver = alsa_driver_new ("alsa_pcm", (char*)playback_driver_name, (char*)capture_driver_name,
NULL,
@@ -2177,7 +2223,8 @@ int JackAlsaDriver::Open(jack_nframes_t nframes,
outchannels,
shorts_first,
capture_latency,
playback_latency);
playback_latency,
midi);
if (fDriver) {
// ALSA driver may have changed the in/out values
fCaptureChannels = ((alsa_driver_t *)fDriver)->capture_nchannels;
@@ -2294,6 +2341,57 @@ JackAlsaDriver::jack_driver_nt_init (jack_driver_nt_t * driver)
driver->nt_run_cycle = 0;
}


int JackAlsaDriver::is_realtime() const
{
return fEngineControl->fRealTime;
}

int JackAlsaDriver::create_thread(pthread_t *thread, int priority, int realtime, void *(*start_routine)(void*), void *arg)
{
return JackPosixThread::StartImp(thread, priority, realtime, start_routine, arg);
}

int JackAlsaDriver::port_register(const char *port_name, const char *port_type, unsigned long flags, unsigned long buf_size)
{
return fGraphManager->AllocatePort(fClientControl->fRefNum, port_name, port_type, (JackPortFlags) flags);
}

int JackAlsaDriver::port_unregister(int port)
{
fGraphManager->ReleasePort(fClientControl->fRefNum, port);
return 0;
}

void* JackAlsaDriver::port_get_buffer(int port, jack_nframes_t nframes)
{
return fGraphManager->GetBuffer(port, nframes);
}

jack_nframes_t JackAlsaDriver::get_sample_rate() const
{
return fEngineControl->fSampleRate;
}

jack_nframes_t JackAlsaDriver::frame_time() const
{
JackTimer timer;
fEngineControl->ReadFrameTime(&timer);
if (timer.fInitialized) {
return timer.fFrames +
(long) rint(((double) ((GetMicroSeconds() - timer.fCurrentWakeup)) /
((jack_time_t)(timer.fNextWakeUp - timer.fCurrentWakeup))) * fEngineControl->fBufferSize);
} else
return 0;
}

jack_nframes_t JackAlsaDriver::last_frame_time() const
{
JackTimer timer;
fEngineControl->ReadFrameTime(&timer);
return timer.fFrames;
}

} // end of namespace


@@ -2336,7 +2434,7 @@ extern "C"

desc = (jack_driver_desc_t*)calloc (1, sizeof (jack_driver_desc_t));
strcpy (desc->name, "alsa");
desc->nparams = 17;
desc->nparams = 18;
params = (jack_driver_param_desc_t*)calloc (desc->nparams, sizeof (jack_driver_param_desc_t));

i = 0;
@@ -2486,6 +2584,14 @@ extern "C"
strcpy (params[i].short_desc, "Extra output latency");
strcpy (params[i].long_desc, params[i].short_desc);

i++;
strcpy (params[i].name, "midi-driver");
params[i].character = 'X';
params[i].type = JackDriverParamString;
strcpy (params[i].value.str, "none");
strcpy (params[i].short_desc, "ALSA MIDI driver name");
strcpy (params[i].long_desc, params[i].short_desc);

desc->params = params;
return desc;
}
@@ -2510,6 +2616,7 @@ extern "C"
jack_nframes_t systemic_output_latency = 0;
const JSList * node;
const jack_driver_param_t * param;
char *midi_driver = "none";

for (node = params; node; node = jack_slist_next (node)) {
param = (const jack_driver_param_t *) node->data;
@@ -2601,6 +2708,10 @@ extern "C"
case 'O':
systemic_output_latency = param->value.ui;
break;
case 'X':
midi_driver = strdup(param->value.str);
break;

}
}
@@ -2616,7 +2727,7 @@ extern "C"
// Special open for ALSA driver...
if (alsa_driver->Open(frames_per_interrupt, user_nperiods, srate, hw_monitoring, hw_metering, capture, playback, dither, soft_mode, monitor,
user_capture_nchnls, user_playback_nchnls, shorts_first, capture_pcm_name, playback_pcm_name,
systemic_input_latency, systemic_output_latency) == 0) {
systemic_input_latency, systemic_output_latency, midi_driver) == 0) {
return threaded_driver;
} else {
delete threaded_driver; // Delete the decorated driver


+ 19
- 2
linux/alsa/JackAlsaDriver.h View File

@@ -95,7 +95,8 @@ class JackAlsaDriver : public JackAudioDriver
int user_playback_nchnls,
int shorts_first,
jack_nframes_t capture_latency,
jack_nframes_t playback_latency
jack_nframes_t playback_latency,
alsa_midi_t *midi
);

void alsa_driver_delete(alsa_driver_t *driver);
@@ -138,10 +139,12 @@ class JackAlsaDriver : public JackAudioDriver
const char* capture_driver_name,
const char* playback_driver_name,
jack_nframes_t capture_latency,
jack_nframes_t playback_latency);
jack_nframes_t playback_latency,
const char* midi_driver_name);

int Close();
int Attach();
int Detach();
int Start();
int Stop();
@@ -150,6 +153,20 @@ class JackAlsaDriver : public JackAudioDriver
int Write();

int SetBufferSize(jack_nframes_t nframes);


// jack api emulation for the midi driver
int is_realtime() const;
int create_thread(pthread_t *thread, int prio, int rt, void *(*start_func)(void*), void *arg);
int port_register(const char *port_name, const char *port_type, unsigned long flags, unsigned long buf_size);
int port_unregister(int port);
void* port_get_buffer(int port, jack_nframes_t nframes);
jack_nframes_t get_sample_rate() const;
jack_nframes_t frame_time() const;
jack_nframes_t last_frame_time() const;
};

} // end of namespace


+ 5
- 0
linux/alsa/alsa_driver.h View File

@@ -37,6 +37,8 @@
#include "driver.h"
#include "memops.h"

#include "alsa_midi.h"

typedef void (*ReadCopyFunction) (jack_default_audio_sample_t *dst, char *src,
unsigned long src_bytes,
unsigned long src_skip_bytes);
@@ -138,6 +140,9 @@ typedef struct _alsa_driver {
int poll_late;
int xrun_count;
int process_count;
alsa_midi_t *midi;
int xrun_recovery;

} alsa_driver_t;



+ 44
- 0
linux/alsa/alsa_midi.h View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2006 Dmitry S. Baikov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#ifndef __jack_alsa_midi_h__
#define __jack_alsa_midi_h__

#ifdef __cplusplus
extern "C" {
#endif

typedef struct alsa_midi_t alsa_midi_t;
struct alsa_midi_t {
void (*destroy)(alsa_midi_t *amidi);
int (*attach)(alsa_midi_t *amidi);
int (*detach)(alsa_midi_t *amidi);
int (*start)(alsa_midi_t *amidi);
int (*stop)(alsa_midi_t *amidi);
void (*read)(alsa_midi_t *amidi, jack_nframes_t nframes);
void (*write)(alsa_midi_t *amidi, jack_nframes_t nframes);
};

alsa_midi_t* alsa_rawmidi_new(jack_client_t *jack);
alsa_midi_t* alsa_seqmidi_new(jack_client_t *jack, const char* alsa_name);

#ifdef __cplusplus
} // extern "C"
#endif

#endif /* __jack_alsa_midi_h__ */

+ 86
- 0
linux/alsa/alsa_midi_impl.h View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2007 Dmitry S. Baikov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#ifndef __jack_alsa_midi_impl_h__
#define __jack_alsa_midi_impl_h__


#ifdef JACKMP

#include "types.h"

#ifdef __cplusplus
extern "C" {
#endif

int JACK_is_realtime(jack_client_t *client);
int JACK_client_create_thread(jack_client_t *client, pthread_t *thread, int priority, int realtime, void *(*start_routine)(void*), void *arg);

jack_port_t* JACK_port_register(jack_client_t *client, const char *port_name, const char *port_type, unsigned long flags, unsigned long buffer_size);
int JACK_port_unregister(jack_client_t *, jack_port_t*);
void* JACK_port_get_buffer(jack_port_t*, jack_nframes_t);

jack_nframes_t JACK_get_sample_rate(jack_client_t *);
jack_nframes_t JACK_frame_time(jack_client_t *);
jack_nframes_t JACK_last_frame_time(jack_client_t *);

#define jack_is_realtime JACK_is_realtime
#define jack_client_create_thread JACK_client_create_thread

#define jack_port_register JACK_port_register
#define jack_port_unregister JACK_port_unregister
#define jack_port_get_buffer JACK_port_get_buffer

#define jack_get_sample_rate JACK_get_sample_rate
#define jack_frame_time JACK_frame_time
#define jack_last_frame_time JACK_last_frame_time

#ifdef __cplusplus
} // extern "C"
#endif

#else // usual jack

#include <jack/jack.h>
#include <jack/thread.h>

#endif


#if defined(STANDALONE)
#define MESSAGE(...) fprintf(stderr, __VA_ARGS__)
#elif defined(JACKMP)
#define MESSAGE(...) fprintf(stderr, __VA_ARGS__)
#else
#include <jack/messagebuffer.h>
#endif

#define info_log(...) MESSAGE(__VA_ARGS__)
#define error_log(...) MESSAGE(__VA_ARGS__)

#ifdef DEBUG
#define debug_log(...) MESSAGE(__VA_ARGS__)
#else
#define debug_log(...)
#endif


#include "alsa_midi.h"


#endif /* __jack_alsa_midi_impl_h__ */

+ 60
- 0
linux/alsa/alsa_midi_jackmp.cpp View File

@@ -0,0 +1,60 @@
#include "JackAlsaDriver.h"
#include "JackPort.h"
#include "alsa_midi_impl.h"

using Jack::JackAlsaDriver;

struct fake_port_t {
JackAlsaDriver* driver;
int port_id;
fake_port_t(JackAlsaDriver *d, int i) : driver(d), port_id(i) {}
};

int JACK_is_realtime(jack_client_t* client)
{
return ((JackAlsaDriver*)client)->is_realtime();
}

int JACK_client_create_thread(jack_client_t* client, pthread_t *thread, int priority, int realtime, void *(*start_routine)(void*), void *arg)
{
return ((JackAlsaDriver*)client)->create_thread(thread, priority, realtime, start_routine, arg);
}

jack_port_t* JACK_port_register(jack_client_t *client, const char *port_name, const char *port_type, unsigned long flags, unsigned long buffer_size)
{
JackAlsaDriver *driver = (JackAlsaDriver*)client;
int port_id = driver->port_register(port_name, port_type, flags, buffer_size);
if (port_id == NO_PORT)
return 0;
else
return (jack_port_t*) new fake_port_t(driver, port_id);
}

int JACK_port_unregister(jack_client_t *client, jack_port_t *port)
{
fake_port_t* real = (fake_port_t*)port;
int res = 0; //real->driver->port_unregister(real->port_id);
delete real;
return res;
}

void* JACK_port_get_buffer(jack_port_t *port, jack_nframes_t nframes)
{
fake_port_t* real = (fake_port_t*)port;
return real->driver->port_get_buffer(real->port_id, nframes);
}

jack_nframes_t JACK_get_sample_rate(jack_client_t *client)
{
return ((JackAlsaDriver*)client)->get_sample_rate();
}

jack_nframes_t JACK_frame_time(jack_client_t *client)
{
return ((JackAlsaDriver*)client)->frame_time();
}

jack_nframes_t JACK_last_frame_time(jack_client_t *client)
{
return ((JackAlsaDriver*)client)->last_frame_time();
}

+ 1172
- 0
linux/alsa/alsa_rawmidi.c
File diff suppressed because it is too large
View File


+ 855
- 0
linux/alsa/alsa_seqmidi.c View File

@@ -0,0 +1,855 @@
/*
* ALSA SEQ < - > JACK MIDI bridge
*
* Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

/*
* alsa_seqmidi_read:
* add new ports
* reads queued snd_seq_event's
* if PORT_EXIT: mark port as dead
* if PORT_ADD, PORT_CHANGE: send addr to port_thread (it also may mark port as dead)
* else process input event
* remove dead ports and send them to port_thread
*
* alsa_seqmidi_write:
* remove dead ports and send them to port_thread
* add new ports
* queue output events
*
* port_thread:
* wait for port_sem
* free deleted ports
* create new ports or mark existing as dead
*/
#include <alsa/asoundlib.h>
#include <jack/midiport.h>
#include <jack/ringbuffer.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <semaphore.h>
#include <pthread.h>
#include <time.h>
#include <ctype.h>

#include "alsa_midi_impl.h"

#define NSEC_PER_SEC ((int64_t)1000*1000*1000)

enum {
MAX_PORTS = 64,
MAX_EVENT_SIZE = 1024,
};

typedef struct port_t port_t;

enum {
PORT_HASH_BITS = 4,
PORT_HASH_SIZE = 1 << PORT_HASH_BITS
};

typedef port_t* port_hash_t[PORT_HASH_SIZE];

struct port_t {
port_t *next;
int is_dead;
char name[64];
snd_seq_addr_t remote;
jack_port_t *jack_port;

jack_ringbuffer_t *early_events; // alsa_midi_event_t + data
int64_t last_out_time;

void *jack_buf;
};

typedef struct {
snd_midi_event_t *codec;

jack_ringbuffer_t *new_ports;

port_t *ports[MAX_PORTS];
} stream_t;

typedef struct alsa_seqmidi {
alsa_midi_t ops;
jack_client_t *jack;

snd_seq_t *seq;
int client_id;
int port_id;
int queue;

int keep_walking;

pthread_t port_thread;
sem_t port_sem;
jack_ringbuffer_t *port_add; // snd_seq_addr_t
jack_ringbuffer_t *port_del; // port_t*

stream_t stream[2];

char alsa_name[32];
} alsa_seqmidi_t;

struct alsa_midi_event {
int64_t time;
int size;
};
typedef struct alsa_midi_event alsa_midi_event_t;

struct process_info {
int dir;
int64_t nframes;
int64_t period_start;
uint32_t sample_rate;
int64_t cur_frames;
int64_t alsa_time;
};

enum PortType { PORT_INPUT = 0, PORT_OUTPUT = 1 };

typedef void (*port_jack_func)(alsa_seqmidi_t *self, port_t *port,struct process_info* info);
static void do_jack_input(alsa_seqmidi_t *self, port_t *port, struct process_info* info);
static void do_jack_output(alsa_seqmidi_t *self, port_t *port, struct process_info* info);

typedef struct {
int alsa_mask;
int jack_caps;
char name[4];
port_jack_func jack_func;
} port_type_t;

static port_type_t port_type[2] = {
{
SND_SEQ_PORT_CAP_SUBS_READ,
JackPortIsOutput|JackPortIsPhysical|JackPortIsTerminal,
"in",
do_jack_input
},
{
SND_SEQ_PORT_CAP_SUBS_WRITE,
JackPortIsInput|JackPortIsPhysical|JackPortIsTerminal,
"out",
do_jack_output
}
};

static void alsa_seqmidi_delete(alsa_midi_t *m);
static int alsa_seqmidi_attach(alsa_midi_t *m);
static int alsa_seqmidi_detach(alsa_midi_t *m);
static int alsa_seqmidi_start(alsa_midi_t *m);
static int alsa_seqmidi_stop(alsa_midi_t *m);
static void alsa_seqmidi_read(alsa_midi_t *m, jack_nframes_t nframes);
static void alsa_seqmidi_write(alsa_midi_t *m, jack_nframes_t nframes);

static
void stream_init(alsa_seqmidi_t *self, int dir)
{
stream_t *str = &self->stream[dir];

str->new_ports = jack_ringbuffer_create(MAX_PORTS*sizeof(port_t*));
snd_midi_event_new(MAX_EVENT_SIZE, &str->codec);
}

static void port_free(alsa_seqmidi_t *self, port_t *port);
static void free_ports(alsa_seqmidi_t *self, jack_ringbuffer_t *ports);

static
void stream_attach(alsa_seqmidi_t *self, int dir)
{
}

static
void stream_detach(alsa_seqmidi_t *self, int dir)
{
stream_t *str = &self->stream[dir];
int i;

free_ports(self, str->new_ports);

// delete all ports from hash
for (i=0; i<PORT_HASH_SIZE; ++i) {
port_t *port = str->ports[i];
while (port) {
port_t *next = port->next;
port_free(self, port);
port = next;
}
str->ports[i] = NULL;
}
}

static
void stream_close(alsa_seqmidi_t *self, int dir)
{
stream_t *str = &self->stream[dir];

if (str->codec)
snd_midi_event_free(str->codec);
if (str->new_ports)
jack_ringbuffer_free(str->new_ports);
}

alsa_midi_t* alsa_seqmidi_new(jack_client_t *client, const char* alsa_name)
{
alsa_seqmidi_t *self = calloc(1, sizeof(alsa_seqmidi_t));
debug_log("midi: new\n");
if (!self)
return NULL;
self->jack = client;
if (!alsa_name)
alsa_name = "jack_midi";
snprintf(self->alsa_name, sizeof(self->alsa_name), "%s", alsa_name);

self->port_add = jack_ringbuffer_create(2*MAX_PORTS*sizeof(snd_seq_addr_t));
self->port_del = jack_ringbuffer_create(2*MAX_PORTS*sizeof(port_t*));
sem_init(&self->port_sem, 0, 0);

stream_init(self, PORT_INPUT);
stream_init(self, PORT_OUTPUT);

self->ops.destroy = alsa_seqmidi_delete;
self->ops.attach = alsa_seqmidi_attach;
self->ops.detach = alsa_seqmidi_detach;
self->ops.start = alsa_seqmidi_start;
self->ops.stop = alsa_seqmidi_stop;
self->ops.read = alsa_seqmidi_read;
self->ops.write = alsa_seqmidi_write;
return &self->ops;
}

static
void alsa_seqmidi_delete(alsa_midi_t *m)
{
alsa_seqmidi_t *self = (alsa_seqmidi_t*) m;

debug_log("midi: delete\n");
alsa_seqmidi_detach(m);

stream_close(self, PORT_OUTPUT);
stream_close(self, PORT_INPUT);

jack_ringbuffer_free(self->port_add);
jack_ringbuffer_free(self->port_del);
sem_close(&self->port_sem);

free(self);
}

static
int alsa_seqmidi_attach(alsa_midi_t *m)
{
alsa_seqmidi_t *self = (alsa_seqmidi_t*) m;
int err;

debug_log("midi: attach\n");

if (self->seq)
return -EALREADY;

if ((err = snd_seq_open(&self->seq, "hw", SND_SEQ_OPEN_DUPLEX, 0)) < 0) {
error_log("failed to open alsa seq");
return err;
}
snd_seq_set_client_name(self->seq, self->alsa_name);
self->port_id = snd_seq_create_simple_port(self->seq, "port",
SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_WRITE
#ifndef DEBUG
|SND_SEQ_PORT_CAP_NO_EXPORT
#endif
,SND_SEQ_PORT_TYPE_APPLICATION);
self->client_id = snd_seq_client_id(self->seq);

self->queue = snd_seq_alloc_queue(self->seq);
snd_seq_start_queue(self->seq, self->queue, 0);

stream_attach(self, PORT_INPUT);
stream_attach(self, PORT_OUTPUT);

snd_seq_nonblock(self->seq, 1);

return 0;
}

static
int alsa_seqmidi_detach(alsa_midi_t *m)
{
alsa_seqmidi_t *self = (alsa_seqmidi_t*) m;

debug_log("midi: detach\n");

if (!self->seq)
return -EALREADY;

alsa_seqmidi_stop(m);

jack_ringbuffer_reset(self->port_add);
free_ports(self, self->port_del);

stream_detach(self, PORT_INPUT);
stream_detach(self, PORT_OUTPUT);

snd_seq_close(self->seq);
self->seq = NULL;

return 0;
}

static void* port_thread(void *);

static void add_existing_ports(alsa_seqmidi_t *self);
static void update_ports(alsa_seqmidi_t *self);
static void add_ports(stream_t *str);

static
int alsa_seqmidi_start(alsa_midi_t *m)
{
alsa_seqmidi_t *self = (alsa_seqmidi_t*) m;
int err;

debug_log("midi: start\n");

if (!self->seq)
return -EBADF;

if (self->keep_walking)
return -EALREADY;

snd_seq_connect_from(self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);
snd_seq_drop_input(self->seq);

add_existing_ports(self);
update_ports(self);
add_ports(&self->stream[PORT_INPUT]);
add_ports(&self->stream[PORT_OUTPUT]);

self->keep_walking = 1;

if ((err = pthread_create(&self->port_thread, NULL, port_thread, self))) {
self->keep_walking = 0;
return -errno;
}

return 0;
}

static
int alsa_seqmidi_stop(alsa_midi_t *m)
{
alsa_seqmidi_t *self = (alsa_seqmidi_t*) m;

debug_log("midi: stop\n");

if (!self->keep_walking)
return -EALREADY;

snd_seq_disconnect_from(self->seq, self->port_id, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE);

self->keep_walking = 0;

sem_post(&self->port_sem);
pthread_join(self->port_thread, NULL);
self->port_thread = 0;

return 0;
}

static
int alsa_connect_from(alsa_seqmidi_t *self, int client, int port)
{
snd_seq_port_subscribe_t* sub;
snd_seq_addr_t seq_addr;
int err;

snd_seq_port_subscribe_alloca(&sub);
seq_addr.client = client;
seq_addr.port = port;
snd_seq_port_subscribe_set_sender(sub, &seq_addr);
seq_addr.client = self->client_id;
seq_addr.port = self->port_id;
snd_seq_port_subscribe_set_dest(sub, &seq_addr);

snd_seq_port_subscribe_set_time_update(sub, 1);
snd_seq_port_subscribe_set_queue(sub, self->queue);
snd_seq_port_subscribe_set_time_real(sub, 1);

if ((err=snd_seq_subscribe_port(self->seq, sub)))
error_log("can't subscribe to %d:%d - %s\n", client, port, snd_strerror(err));
return err;
}

/*
* ==================== Port routines =============================
*/
static inline
int port_hash(snd_seq_addr_t addr)
{
return (addr.client + addr.port) % PORT_HASH_SIZE;
}

static
port_t* port_get(port_hash_t hash, snd_seq_addr_t addr)
{
port_t **pport = &hash[port_hash(addr)];
while (*pport) {
port_t *port = *pport;
if (port->remote.client == addr.client && port->remote.port == addr.port)
return port;
pport = &port->next;
}
return NULL;
}

static
void port_insert(port_hash_t hash, port_t *port)
{
port_t **pport = &hash[port_hash(port->remote)];
port->next = *pport;
*pport = port;
}

static
void port_setdead(port_hash_t hash, snd_seq_addr_t addr)
{
port_t *port = port_get(hash, addr);
if (port)
port->is_dead = 1; // see jack_process
else
debug_log("port_setdead: not found (%d:%d)\n", addr.client, addr.port);
}

static
void port_free(alsa_seqmidi_t *self, port_t *port)
{
//snd_seq_disconnect_from(self->seq, self->port_id, port->remote.client, port->remote.port);
//snd_seq_disconnect_to(self->seq, self->port_id, port->remote.client, port->remote.port);
if (port->early_events)
jack_ringbuffer_free(port->early_events);
if (port->jack_port)
jack_port_unregister(self->jack, port->jack_port);
info_log("port deleted: %s\n", port->name);

free(port);
}

static
port_t* port_create(alsa_seqmidi_t *self, int type, snd_seq_addr_t addr, const snd_seq_port_info_t *info)
{
port_t *port;
char *c;
int err;

port = calloc(1, sizeof(port_t));
if (!port)
return NULL;

port->remote = addr;

snprintf(port->name, sizeof(port->name), "%s-%d-%d-%s",
port_type[type].name, addr.client, addr.port, snd_seq_port_info_get_name(info));

// replace all offending characters by -
for (c = port->name; *c; ++c)
if (!isalnum(*c))
*c = '-';

port->jack_port = jack_port_register(self->jack,
port->name, JACK_DEFAULT_MIDI_TYPE, port_type[type].jack_caps, 0);
if (!port->jack_port)
goto failed;

if (type == PORT_INPUT)
err = alsa_connect_from(self, port->remote.client, port->remote.port);
else
err = snd_seq_connect_to(self->seq, self->port_id, port->remote.client, port->remote.port);
if (err)
goto failed;

port->early_events = jack_ringbuffer_create(MAX_EVENT_SIZE*16);

info_log("port created: %s\n", port->name);
return port;

failed:
port_free(self, port);
return NULL;
}

/*
* ==================== Port add/del handling thread ==============================
*/
static
void update_port_type(alsa_seqmidi_t *self, int type, snd_seq_addr_t addr, int caps, const snd_seq_port_info_t *info)
{
stream_t *str = &self->stream[type];
int alsa_mask = port_type[type].alsa_mask;
port_t *port = port_get(str->ports, addr);

debug_log("update_port_type(%d:%d)\n", addr.client, addr.port);

if (port && (caps & alsa_mask)!=alsa_mask) {
debug_log("setdead: %s\n", port->name);
port->is_dead = 1;
}

if (!port && (caps & alsa_mask)==alsa_mask) {
assert (jack_ringbuffer_write_space(str->new_ports) >= sizeof(port));
port = port_create(self, type, addr, info);
if (port)
jack_ringbuffer_write(str->new_ports, (char*)&port, sizeof(port));
}
}

static
void update_port(alsa_seqmidi_t *self, snd_seq_addr_t addr, const snd_seq_port_info_t *info)
{
unsigned int port_caps = snd_seq_port_info_get_capability(info);
if (port_caps & SND_SEQ_PORT_CAP_NO_EXPORT)
return;
update_port_type(self, PORT_INPUT, addr, port_caps, info);
update_port_type(self, PORT_OUTPUT, addr, port_caps, info);
}

static
void free_ports(alsa_seqmidi_t *self, jack_ringbuffer_t *ports)
{
port_t *port;
int sz;
while ((sz = jack_ringbuffer_read(ports, (char*)&port, sizeof(port)))) {
assert (sz == sizeof(port));
port_free(self, port);
}
}

static
void update_ports(alsa_seqmidi_t *self)
{
snd_seq_addr_t addr;
int size;

while ((size = jack_ringbuffer_read(self->port_add, (char*)&addr, sizeof(addr)))) {
snd_seq_port_info_t *info;
int err;

snd_seq_port_info_alloca(&info);
assert (size == sizeof(addr));
assert (addr.client != self->client_id);
if ((err=snd_seq_get_any_port_info(self->seq, addr.client, addr.port, info))>=0) {
update_port(self, addr, info);
} else {
//port_setdead(self->stream[PORT_INPUT].ports, addr);
//port_setdead(self->stream[PORT_OUTPUT].ports, addr);
}
}
}

static
void* port_thread(void *arg)
{
alsa_seqmidi_t *self = arg;

while (self->keep_walking) {
sem_wait(&self->port_sem);
free_ports(self, self->port_del);
update_ports(self);
}
debug_log("port_thread exited\n");
return NULL;
}

static
void add_existing_ports(alsa_seqmidi_t *self)
{
snd_seq_addr_t addr;
snd_seq_client_info_t *client_info;
snd_seq_port_info_t *port_info;

snd_seq_client_info_alloca(&client_info);
snd_seq_port_info_alloca(&port_info);
snd_seq_client_info_set_client(client_info, -1);
while (snd_seq_query_next_client(self->seq, client_info) >= 0)
{
addr.client = snd_seq_client_info_get_client(client_info);
if (addr.client == SND_SEQ_CLIENT_SYSTEM || addr.client == self->client_id)
continue;
snd_seq_port_info_set_client(port_info, addr.client);
snd_seq_port_info_set_port(port_info, -1);
while (snd_seq_query_next_port(self->seq, port_info) >= 0)
{
addr.port = snd_seq_port_info_get_port(port_info);
update_port(self, addr, port_info);
}
}
}

/*
* =================== Input/output port handling =========================
*/
static
void set_process_info(struct process_info *info, alsa_seqmidi_t *self, int dir, jack_nframes_t nframes)
{
const snd_seq_real_time_t* alsa_time;
snd_seq_queue_status_t *status;

snd_seq_queue_status_alloca(&status);

info->dir = dir;

info->period_start = jack_last_frame_time(self->jack);
info->nframes = nframes;
info->sample_rate = jack_get_sample_rate(self->jack);

info->cur_frames = jack_frame_time(self->jack);

// immediately get alsa'a real time (uhh, why everybody has their own 'real' time)
snd_seq_get_queue_status(self->seq, self->queue, status);
alsa_time = snd_seq_queue_status_get_real_time(status);
info->alsa_time = alsa_time->tv_sec * NSEC_PER_SEC + alsa_time->tv_nsec;
}

static
void add_ports(stream_t *str)
{
port_t *port;
while (jack_ringbuffer_read(str->new_ports, (char*)&port, sizeof(port))) {
debug_log("jack: inserted port %s\n", port->name);
port_insert(str->ports, port);
}
}

static
void jack_process(alsa_seqmidi_t *self, struct process_info *info)
{
stream_t *str = &self->stream[info->dir];
port_jack_func process = port_type[info->dir].jack_func;
int i, del=0;

add_ports(str);

// process ports
for (i=0; i<PORT_HASH_SIZE; ++i) {
port_t **pport = &str->ports[i];
while (*pport) {
port_t *port = *pport;
port->jack_buf = jack_port_get_buffer(port->jack_port, info->nframes);
if (info->dir == PORT_INPUT)
jack_midi_clear_buffer(port->jack_buf, 0);

if (!port->is_dead)
(*process)(self, port, info);
else if (jack_ringbuffer_write_space(self->port_del) >= sizeof(port)) {
debug_log("jack: removed port %s\n", port->name);
*pport = port->next;
jack_ringbuffer_write(self->port_del, (char*)&port, sizeof(port));
del++;
continue;
}

pport = &port->next;
}
}

if (del)
sem_post(&self->port_sem);
}

/*
* ============================ Input ==============================
*/
static
void do_jack_input(alsa_seqmidi_t *self, port_t *port, struct process_info *info)
{
// process port->early_events
alsa_midi_event_t ev;
while (jack_ringbuffer_read(port->early_events, (char*)&ev, sizeof(ev))) {
jack_midi_data_t* buf;
int64_t time = ev.time - info->period_start;
if (time < 0)
time = 0;
else if (time >= info->nframes)
time = info->nframes - 1;
buf = jack_midi_event_reserve(port->jack_buf, (jack_nframes_t)time, ev.size, 0);
if (buf)
jack_ringbuffer_read(port->early_events, (char*)buf, ev.size);
else
jack_ringbuffer_read_advance(port->early_events, ev.size);
debug_log("input: it's time for %d bytes at %lld\n", ev.size, time);
}
}

static
void port_event(alsa_seqmidi_t *self, snd_seq_event_t *ev)
{
const snd_seq_addr_t addr = ev->data.addr;

if (addr.client == self->client_id)
return;

if (ev->type == SND_SEQ_EVENT_PORT_START || ev->type == SND_SEQ_EVENT_PORT_CHANGE) {
assert (jack_ringbuffer_write_space(self->port_add) >= sizeof(addr));

debug_log("port_event: add/change %d:%d\n", addr.client, addr.port);
jack_ringbuffer_write(self->port_add, (char*)&addr, sizeof(addr));
sem_post(&self->port_sem);
} else if (ev->type == SND_SEQ_EVENT_PORT_EXIT) {
debug_log("port_event: del %d:%d\n", addr.client, addr.port);
port_setdead(self->stream[PORT_INPUT].ports, addr);
port_setdead(self->stream[PORT_OUTPUT].ports, addr);
}
}

static
void input_event(alsa_seqmidi_t *self, snd_seq_event_t *alsa_event, struct process_info* info)
{
jack_midi_data_t data[MAX_EVENT_SIZE];
stream_t *str = &self->stream[PORT_INPUT];
long size;
int64_t alsa_time, time_offset;
int64_t frame_offset, event_frame;
port_t *port;

port = port_get(str->ports, alsa_event->source);
if (!port)
return;

/*
* RPNs, NRPNs, Bank Change, etc. need special handling
* but seems, ALSA does it for us already.
*/
snd_midi_event_reset_decode(str->codec);
if ((size = snd_midi_event_decode(str->codec, data, sizeof(data), alsa_event))<0)
return;

// fixup NoteOn with vel 0
if (data[0] == 0x90 && data[2] == 0x00) {
data[0] = 0x80;
data[2] = 0x40;
}

alsa_time = alsa_event->time.time.tv_sec * NSEC_PER_SEC + alsa_event->time.time.tv_nsec;
time_offset = info->alsa_time - alsa_time;
frame_offset = (info->sample_rate * time_offset) / NSEC_PER_SEC;
event_frame = info->cur_frames - info->period_start - frame_offset + info->nframes;

debug_log("input: %d bytes at event_frame=%d\n", (int)size, (int)event_frame);

if (event_frame >= info->nframes &&
jack_ringbuffer_write_space(port->early_events) >= (sizeof(alsa_midi_event_t) + size)) {
alsa_midi_event_t ev;
ev.time = event_frame + info->period_start;
ev.size = size;
jack_ringbuffer_write(port->early_events, (char*)&ev, sizeof(ev));
jack_ringbuffer_write(port->early_events, (char*)data, size);
debug_log("postponed to next frame +%d\n", (int) (event_frame - info->nframes));
return;
}

if (event_frame < 0)
event_frame = 0;
else if (event_frame >= info->nframes)
event_frame = info->nframes - 1;

jack_midi_event_write(port->jack_buf, event_frame, data, size, 0);
}

static
void alsa_seqmidi_read(alsa_midi_t *m, jack_nframes_t nframes)
{
alsa_seqmidi_t *self = (alsa_seqmidi_t*) m;
int res;
snd_seq_event_t *event;
struct process_info info;

if (!self->keep_walking)
return;

set_process_info(&info, self, PORT_INPUT, nframes);
jack_process(self, &info);

while ((res = snd_seq_event_input(self->seq, &event))>0) {
if (event->source.client == SND_SEQ_CLIENT_SYSTEM)
port_event(self, event);
else
input_event(self, event, &info);
}
}

/*
* ============================ Output ==============================
*/

static
void do_jack_output(alsa_seqmidi_t *self, port_t *port, struct process_info* info)
{
stream_t *str = &self->stream[info->dir];
int nevents = jack_midi_get_event_count(port->jack_buf, 0);
int i;
for (i=0; i<nevents; ++i) {
jack_midi_event_t jack_event;
snd_seq_event_t alsa_event;
int64_t frame_offset;
int64_t out_time;
snd_seq_real_time_t out_rt;
int err;

jack_midi_event_get(&jack_event, port->jack_buf, i, 0);

snd_seq_ev_clear(&alsa_event);
snd_midi_event_reset_encode(str->codec);
if (!snd_midi_event_encode(str->codec, jack_event.buffer, jack_event.size, &alsa_event))
continue; // invalid event

snd_seq_ev_set_source(&alsa_event, self->port_id);
snd_seq_ev_set_dest(&alsa_event, port->remote.client, port->remote.port);

frame_offset = jack_event.time + info->period_start + info->nframes - info->cur_frames;
out_time = info->alsa_time + (frame_offset * NSEC_PER_SEC) / info->sample_rate;

debug_log("alsa_out: frame_offset = %lld, info->alsa_time = %lld, out_time = %lld, port->last_out_time = %lld\n",
frame_offset, info->alsa_time, out_time, port->last_out_time);

// we should use absolute time to prevent reordering caused by rounding errors
if (out_time < port->last_out_time)
out_time = port->last_out_time;
else
port->last_out_time = out_time;

out_rt.tv_nsec = out_time % NSEC_PER_SEC;
out_rt.tv_sec = out_time / NSEC_PER_SEC;
snd_seq_ev_schedule_real(&alsa_event, self->queue, 0, &out_rt);

err = snd_seq_event_output(self->seq, &alsa_event);
debug_log("alsa_out: written %d bytes to %s at %+d (%lld): %d\n", (int)jack_event.size, port->name, (int)frame_offset, out_time, err);
}
}

static
void alsa_seqmidi_write(alsa_midi_t *m, jack_nframes_t nframes)
{
alsa_seqmidi_t *self = (alsa_seqmidi_t*) m;
struct process_info info;

if (!self->keep_walking)
return;

set_process_info(&info, self, PORT_OUTPUT, nframes);
jack_process(self, &info);
snd_seq_drain_output(self->seq);
}

Loading…
Cancel
Save