/*
 *   FreeBob Backend for Jack
 *   FreeBob = Firewire (pro-)audio for linux
 *
 *   http://freebob.sf.net
 *   http://jackit.sf.net
 *
 *   Copyright (C) 2005-2007 Pieter Palmers <pieter.palmers@ffado.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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* 
 * Main Jack driver entry routines
 *
 */ 

#include <math.h>
#include <stdio.h>
#include <memory.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/mman.h>

#include <assert.h>

#include <jack/types.h"
#include <jack/engine.h>
#include <sysdeps/time.h>

#include "include/internal.h"

#include "freebob_driver.h"

#define SAMPLE_MAX_24BIT  8388608.0f
#define SAMPLE_MAX_16BIT  32768.0f

static int freebob_driver_stop (freebob_driver_t *driver);

#ifdef FREEBOB_DRIVER_WITH_ALSA_MIDI
	static freebob_driver_midi_handle_t *freebob_driver_midi_init(freebob_driver_t *driver);
	static void freebob_driver_midi_finish (freebob_driver_midi_handle_t *m);
	static int freebob_driver_midi_start (freebob_driver_midi_handle_t *m);
	static int freebob_driver_midi_stop (freebob_driver_midi_handle_t *m);
#endif

// enable verbose messages
static int g_verbose=0;

static int
freebob_driver_attach (freebob_driver_t *driver)
{
	char buf[64];
	char buf2[64];

	channel_t chn;
	jack_port_t *port=NULL;
	int port_flags;
	int error=0;
	jack_latency_range_t range;

	g_verbose=driver->engine->verbose;
	driver->device_options.verbose=g_verbose;

	if (driver->engine->set_buffer_size (driver->engine, driver->period_size)) {
		jack_error ("FREEBOB: cannot set engine buffer size to %d (check MIDI)", driver->period_size);
		return -1;
	}
	driver->engine->set_sample_rate (driver->engine, driver->sample_rate);

	/* packetizer thread options */
	driver->device_options.realtime=(driver->engine->control->real_time? 1 : 0);
	
	driver->device_options.packetizer_priority=driver->engine->control->client_priority +
		FREEBOB_RT_PRIORITY_PACKETIZER_RELATIVE;
	if (driver->device_options.packetizer_priority>98) {
		driver->device_options.packetizer_priority=98;
	}

	driver->dev=freebob_streaming_init(&driver->device_info,driver->device_options);

	if(!driver->dev) {
		printError("FREEBOB: Error creating virtual device");
		return -1;
	}

#ifdef FREEBOB_DRIVER_WITH_ALSA_MIDI
	driver->midi_handle=freebob_driver_midi_init(driver);
	if(!driver->midi_handle) {
		printError("-----------------------------------------------------------");
		printError("Error creating midi device!");
		printError("FreeBob will run without MIDI support.");
		printError("Consult the above error messages to solve the problem. ");
		printError("-----------------------------------------------------------\n\n");
	}
#endif

	if (driver->device_options.realtime) {
		printMessage("Streaming thread running with Realtime scheduling, priority %d",
		           driver->device_options.packetizer_priority);
	} else {
		printMessage("Streaming thread running without Realtime scheduling");
	}

	/* ports */
	port_flags = JackPortIsOutput|JackPortIsPhysical|JackPortIsTerminal;

	driver->capture_nchannels=freebob_streaming_get_nb_capture_streams(driver->dev);

#ifdef FREEBOB_DRIVER_WITH_JACK_MIDI
	// allocate midi structures
	driver->midi_in_ports=calloc(driver->capture_nchannels, sizeof(freebob_midi_input_port_t));
	if (driver->midi_in_ports == NULL) return -ENOMEM;
#endif

	for (chn = 0; chn < driver->capture_nchannels; chn++) {
		error=0;

		freebob_streaming_get_capture_stream_name(driver->dev, chn, buf, sizeof(buf) - 1);
		switch(freebob_streaming_get_capture_stream_type(driver->dev, chn)) {
		case freebob_stream_type_audio:
			snprintf(buf2, 64, "C%d_%s",(int)chn,buf); // needed to avoid duplicate names
			printMessage ("Registering audio capture port %s", buf2);
			if ((port = jack_port_register (driver->client, buf2,
							JACK_DEFAULT_AUDIO_TYPE,
							port_flags, 0)) == NULL) {
				printError (" cannot register port for %s", buf2);
				error=1;
				break;
			}
			driver->capture_ports =
				jack_slist_append (driver->capture_ports, port);
			break;

#ifdef FREEBOB_DRIVER_WITH_JACK_MIDI
		case freebob_stream_type_midi:
			snprintf(buf2, 64, "CM%d_%s",(int)chn,buf); // needed to avoid duplicate names
			printMessage ("Registering midi capture port %s", buf2);
			if ((port = jack_port_register (driver->client, buf2,
							JACK_DEFAULT_MIDI_TYPE,
							port_flags, 0)) == NULL) {
				printError (" cannot register port for %s", buf2);
				error=1;
				break;
			}

			// init the midi unpacker for this port
			midi_unpack_init(&driver->midi_in_ports[chn].unpack);

			driver->capture_ports =
				jack_slist_append (driver->capture_ports, port);
			break;
#endif

		default:
			printMessage ("Don't register capture port for %s", buf);
			// we have to add a NULL entry in the list to be able to loop over the channels in the read/write routines
			driver->capture_ports =
				jack_slist_append (driver->capture_ports, NULL);
			break;
		}

		if(error) break;

		range.min = range.max = driver->period_size + driver->capture_frame_latency;
		jack_port_set_latency_range (port, JackCaptureLatency, &range);
	}
	
	port_flags = JackPortIsInput|JackPortIsPhysical|JackPortIsTerminal;

	driver->playback_nchannels=freebob_streaming_get_nb_playback_streams(driver->dev);

#ifdef FREEBOB_DRIVER_WITH_JACK_MIDI
	// allocate midi structures
	driver->midi_out_ports=calloc(driver->playback_nchannels, sizeof(freebob_midi_output_port_t));
	if (driver->midi_out_ports == NULL) return -ENOMEM;
#endif

	for (chn = 0; chn < driver->playback_nchannels; chn++) {
		error=0;

		freebob_streaming_get_playback_stream_name(driver->dev, chn, buf, sizeof(buf) - 1);
		
		switch(freebob_streaming_get_playback_stream_type(driver->dev, chn)){
		case freebob_stream_type_audio:
			snprintf(buf2, 64, "P%d_%s",(int)chn,buf); // needed to avoid duplicate names
			printMessage ("Registering playback audio port %s", buf2);
			if ((port = jack_port_register (driver->client, buf2,
							JACK_DEFAULT_AUDIO_TYPE,
							port_flags, 0)) == NULL) {
				printError(" cannot register port for %s", buf2);
				error=1;
				break;
			}
			driver->playback_ports =
				jack_slist_append (driver->playback_ports, port);
			break;
#ifdef FREEBOB_DRIVER_WITH_JACK_MIDI
		case freebob_stream_type_midi:
			snprintf(buf2, 64, "PM%d_%s",(int)chn,buf); // needed to avoid duplicate names
			printMessage ("Registering playback midi port %s", buf2);
			if ((port = jack_port_register (driver->client, buf2,
							JACK_DEFAULT_MIDI_TYPE,
							port_flags, 0)) == NULL) {
				printError(" cannot register port for %s", buf2);
				error=1;
				break;
			}

			driver->playback_ports =
				jack_slist_append (driver->playback_ports, port);
			break;
#endif
		default:
			printMessage ("Don't register playback port %s", buf);
			// we have to add a NULL entry in the list to be able to loop over the channels in the read/write routines
			driver->playback_ports =
				jack_slist_append (driver->playback_ports, NULL);
		}

		if(error) break;

		range.min = range.max = (driver->period_size * (driver->device_options.nb_buffers - 1)) + driver->playback_frame_latency;
		jack_port_set_latency_range (port, JackPlaybackLatency, &range);
	}

	return jack_activate (driver->client);
}

static int
freebob_driver_detach (freebob_driver_t *driver)
{
	JSList *node;

	if (driver->engine == NULL) {
		return 0;
	}

	for (node = driver->capture_ports; node && node->data;
	     node = jack_slist_next (node)) {
		if(node->data != NULL) {
			jack_port_unregister (driver->client,
				      ((jack_port_t *) node->data));
		}
	}

	jack_slist_free (driver->capture_ports);
	driver->capture_ports = 0;
		
	for (node = driver->playback_ports; node && node->data;
	     node = jack_slist_next (node)) {
		if(node->data != NULL) {
			jack_port_unregister (driver->client,
				      ((jack_port_t *) node->data));
		}
	}

	jack_slist_free (driver->playback_ports);
	driver->playback_ports = 0;

	freebob_streaming_finish(driver->dev);
	driver->dev=NULL;

#ifdef FREEBOB_DRIVER_WITH_ALSA_MIDI
	if(driver->midi_handle) {
		freebob_driver_midi_finish(driver->midi_handle);	
	}
	driver->midi_handle=NULL;
#endif

#ifdef FREEBOB_DRIVER_WITH_JACK_MIDI
	free(driver->midi_in_ports);
	free(driver->midi_out_ports);
#endif
	return 0;
}

static int
freebob_driver_read (freebob_driver_t * driver, jack_nframes_t nframes)
{
	jack_default_audio_sample_t* buf;
	channel_t chn;
	JSList *node;
	jack_port_t* port;
	
	freebob_sample_t nullbuffer[nframes];
	void *addr_of_nullbuffer=(void *)nullbuffer;

	freebob_streaming_stream_type stream_type;
	
	printEnter();
	
	for (chn = 0, node = driver->capture_ports; node; node = jack_slist_next (node), chn++) {
		stream_type = freebob_streaming_get_capture_stream_type(driver->dev, chn);
		if(stream_type == freebob_stream_type_audio) {
			port = (jack_port_t *) node->data;
			buf = jack_port_get_buffer (port, nframes);

			if(!buf) buf=(jack_default_audio_sample_t *)addr_of_nullbuffer;
				
			freebob_streaming_set_capture_stream_buffer(
				driver->dev, chn, (char *)(buf), freebob_buffer_type_float);

#ifdef FREEBOB_DRIVER_WITH_JACK_MIDI
		} else if(stream_type == freebob_stream_type_midi) {
			unsigned int midibuff[64];
			unsigned char midibuff2[64];
			int samples_read;

			port = (jack_port_t *) node->data;
			buf = jack_port_get_buffer (port, nframes);

			jack_midi_clear_buffer(buf);

			samples_read=freebob_streaming_read(
				driver->dev, chn, midibuff, 64);

			while(samples_read) {
				int idx;
				int done;
				//printMessage("MIDI: ");
				for (idx=0;idx<samples_read;idx++) {
					midibuff2[idx]=(unsigned char)(midibuff[idx] & 0xFF);
					//printMessage(" %02X", midibuff2[idx]);
				}

				done = midi_unpack_buf(
					&driver->midi_in_ports[chn].unpack,
					midibuff2, samples_read, buf, 0 /* time */);

				samples_read=freebob_streaming_read(
					driver->dev, chn, midibuff, 64);
			}
#endif
		} else { // empty other buffers without doing something with them
			freebob_streaming_set_capture_stream_buffer(driver->dev, chn, (char *)(nullbuffer), freebob_buffer_type_uint24);
		}
	}

	// now transfer the buffers
	freebob_streaming_transfer_capture_buffers(driver->dev);
	
	printExit();
	
	return 0;
}

static int
freebob_driver_write (freebob_driver_t * driver, jack_nframes_t nframes)
{
	channel_t chn;
	JSList *node;
	jack_default_audio_sample_t* buf;

	jack_port_t *port;

	freebob_streaming_stream_type stream_type;

	freebob_sample_t nullbuffer[nframes];
	void *addr_of_nullbuffer = (void*)nullbuffer;

	memset(&nullbuffer,0,nframes*sizeof(freebob_sample_t));

	printEnter();

	driver->process_count++;

	assert(driver->dev);

 	if (driver->engine->freewheeling) {
 		return 0;
 	}

	for (chn = 0, node = driver->playback_ports; node; node = jack_slist_next (node), chn++) {
		stream_type=freebob_streaming_get_playback_stream_type(driver->dev, chn);
		if(stream_type == freebob_stream_type_audio) {
			port = (jack_port_t *) node->data;

			buf = jack_port_get_buffer (port, nframes);
			if(!buf) buf=(jack_default_audio_sample_t*)addr_of_nullbuffer;
				
			freebob_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(buf), freebob_buffer_type_float);

#ifdef FREEBOB_DRIVER_WITH_JACK_MIDI
		} else if(stream_type == freebob_stream_type_midi) {
			int i,idx;
			int samples_written;

			port = (jack_port_t *) node->data;
			buf = jack_port_get_buffer (port, nframes);

			int nevents = jack_midi_get_event_count(buf);

			if (nevents)
				printMessage("jack_out: %d events\n", nevents);
			for (i=0; i<nevents; ++i) {
				jack_midi_event_t event;
		
				jack_midi_event_get(&event, buf, i);
		
				midi_pack_event(&driver->midi_out_ports[chn].packer, &event);

				freebob_sample_t midibuff[event.size];
				for (idx=0;idx<event.size;idx++) {
					midibuff[idx]=(freebob_sample_t)(event.buffer[idx]);
				}

				samples_written=freebob_streaming_write(
					driver->dev, chn, midibuff, event.size);

				if (samples_written!=event.size) {
					printMessage("midi out: buffer overrun\n");
					break;
				} else {
					printMessage("midi out: sent %d-byte event at %ld\n", (int)event.size, (long)event.time);
				}
			}
#endif
		} else { // empty other buffers without doing something with them
			freebob_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(nullbuffer), freebob_buffer_type_uint24);
		}
	}

	freebob_streaming_transfer_playback_buffers(driver->dev);

	printExit();
	
	return 0;
}

//static inline jack_nframes_t 
static jack_nframes_t 
freebob_driver_wait (freebob_driver_t *driver, int extra_fd, int *status,
		   float *delayed_usecs)
{
	int nframes;
	jack_time_t                   wait_enter;
	jack_time_t                   wait_ret;
	
	printEnter();

	wait_enter = driver->engine->get_microseconds ();
	if (wait_enter > driver->wait_next) {
		/*
			* This processing cycle was delayed past the
			* next due interrupt!  Do not account this as
			* a wakeup delay:
			*/
		driver->wait_next = 0;
		driver->wait_late++;
	}
// *status = -2; interrupt
// *status = -3; timeout
// *status = -4; extra FD

	nframes=freebob_streaming_wait(driver->dev);
	
	wait_ret = driver->engine->get_microseconds ();
	
	if (driver->wait_next && wait_ret > driver->wait_next) {
		*delayed_usecs = wait_ret - driver->wait_next;
	}
	driver->wait_last = wait_ret;
	driver->wait_next = wait_ret + driver->period_usecs;
	driver->engine->transport_cycle_start (driver->engine, wait_ret);
	
	// transfer the streaming buffers
	// we now do this in the read/write functions
// 	freebob_streaming_transfer_buffers(driver->dev);
	
	if (nframes < 0) {
		*status=0;
		
		return 0;
		//nframes=driver->period_size; //debug
	}

	*status = 0;
	driver->last_wait_ust = wait_ret;

	// FIXME: this should do something more usefull
	*delayed_usecs = 0;
	
	printExit();

	return nframes - nframes % driver->period_size;
	
}

static int
freebob_driver_run_cycle (freebob_driver_t *driver)
{
	jack_engine_t *engine = driver->engine;
	int wait_status=0;
	float delayed_usecs=0.0;

	jack_nframes_t nframes = freebob_driver_wait (driver, -1, &wait_status,
						   &delayed_usecs);
	
	if ((wait_status < 0)) {
		printError( "wait status < 0! (= %d)",wait_status);
		return -1;
	}
		
	if ((nframes == 0)) {
		int chn;
		chn=0; // avoid unused variable warning when compiling without jack midi

		/* we detected an xrun and restarted: notify
		 * clients about the delay. */
		printMessage("xrun detected");
		engine->delay (engine, delayed_usecs);

		// reset the midi stuff
		#ifdef FREEBOB_DRIVER_WITH_JACK_MIDI
			for (chn = 0; chn < driver->capture_nchannels; chn++) {
				// init the midi unpacker for this port
				midi_unpack_reset(&driver->midi_in_ports[chn].unpack);
			}
			
			for (chn = 0; chn < driver->playback_nchannels; chn++) {
				// init the midi unpacker for this port
				midi_pack_reset(&driver->midi_out_ports[chn].packer);
			}
		#endif

		return 0;
	} 
	
	return engine->run_cycle (engine, nframes, delayed_usecs);

}
/*
 * in a null cycle we should discard the input and write silence to the outputs
 */
static int
freebob_driver_null_cycle (freebob_driver_t* driver, jack_nframes_t nframes)
{
	channel_t chn;
	JSList *node;
	jack_nframes_t nwritten;

	freebob_streaming_stream_type stream_type;

	jack_default_audio_sample_t buff[nframes];
	jack_default_audio_sample_t* buffer=(jack_default_audio_sample_t*)buff;
	
	printEnter();

	memset(buffer,0,nframes*sizeof(jack_default_audio_sample_t));
	
	assert(driver->dev);

 	if (driver->engine->freewheeling) {
 		return 0;
 	}

	// write silence to buffer
	nwritten = 0;

	for (chn = 0, node = driver->playback_ports; node; node = jack_slist_next (node), chn++) {
		stream_type=freebob_streaming_get_playback_stream_type(driver->dev, chn);

		if(stream_type == freebob_stream_type_audio) {
			freebob_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(buffer), freebob_buffer_type_float);

		} else if(stream_type == freebob_stream_type_midi) {
			// these should be read/written with the per-stream functions

		} else { // empty other buffers without doing something with them
			freebob_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(buffer), freebob_buffer_type_uint24);
		}
	}

	freebob_streaming_transfer_playback_buffers(driver->dev);
	
	// read & discard from input ports
	for (chn = 0, node = driver->capture_ports; node; node = jack_slist_next (node), chn++) {
		stream_type=freebob_streaming_get_capture_stream_type(driver->dev, chn);
		if(stream_type == freebob_stream_type_audio) {
			freebob_streaming_set_capture_stream_buffer(driver->dev, chn, (char *)(buffer), freebob_buffer_type_float);

		} else if(stream_type == freebob_stream_type_midi) {

		} else { // empty other buffers without doing something with them
		}
	}

	// now transfer the buffers
	freebob_streaming_transfer_capture_buffers(driver->dev);
		
	printExit();
	return 0;
}

static int
freebob_driver_start (freebob_driver_t *driver)
{
	int retval=0;
	int chn;
	chn=0; // avoid unused variable warning when compiling without jack midi

#ifdef FREEBOB_DRIVER_WITH_ALSA_MIDI
	if(driver->midi_handle) {
		if((retval=freebob_driver_midi_start(driver->midi_handle))) {
			printError("Could not start MIDI threads");
			return retval;
		}
	}
#endif	

	// reset the midi stuff
#ifdef FREEBOB_DRIVER_WITH_JACK_MIDI
	for (chn = 0; chn < driver->capture_nchannels; chn++) {
		// init the midi unpacker for this port
		midi_unpack_reset(&driver->midi_in_ports[chn].unpack);
	}
	
	for (chn = 0; chn < driver->playback_nchannels; chn++) {
		// init the midi unpacker for this port
		midi_pack_reset(&driver->midi_out_ports[chn].packer);
	}
#endif

	if((retval=freebob_streaming_start(driver->dev))) {
		printError("Could not start streaming threads");
#ifdef FREEBOB_DRIVER_WITH_ALSA_MIDI
		if(driver->midi_handle) {
			freebob_driver_midi_stop(driver->midi_handle);
		}
#endif
		return retval;
	}

	return 0;

}

static int
freebob_driver_stop (freebob_driver_t *driver)
{
	int retval=0;
	
#ifdef FREEBOB_DRIVER_WITH_ALSA_MIDI
	if(driver->midi_handle) {
		if((retval=freebob_driver_midi_stop(driver->midi_handle))) {
			printError("Could not stop MIDI threads");
			return retval;
		}
	}
#endif	
	if((retval=freebob_streaming_stop(driver->dev))) {
		printError("Could not stop streaming threads");
		return retval;
	}

	return 0;
}


static int
freebob_driver_bufsize (freebob_driver_t* driver, jack_nframes_t nframes)
{
	printError("Buffer size change requested but not supported!!!");

	/*
	 driver->period_size = nframes;  
	driver->period_usecs =
		(jack_time_t) floor ((((float) nframes) / driver->sample_rate)
				     * 1000000.0f);
	*/
	
	/* tell the engine to change its buffer size */
#if 0
	if (driver->engine->set_buffer_size (driver->engine, nframes)) {
		jack_error ("FREEBOB: cannot set engine buffer size to %d (check MIDI)", driver->period_size);
		return -1;
	}
#endif
	return -1; // unsupported
}

typedef void (*JackDriverFinishFunction) (jack_driver_t *);

freebob_driver_t *
freebob_driver_new (jack_client_t * client,
		  char *name,
		  freebob_jack_settings_t *params)
{
	freebob_driver_t *driver;

	assert(params);

	if(freebob_get_api_version() != 1) {
		printMessage("Incompatible libfreebob version! (%s)", freebob_get_version());
		return NULL;
	}

	printMessage("Starting Freebob backend (%s)", freebob_get_version());

	driver = calloc (1, sizeof (freebob_driver_t));

	/* Setup the jack interfaces */  
	jack_driver_nt_init ((jack_driver_nt_t *) driver);

	driver->nt_attach    = (JackDriverNTAttachFunction)   freebob_driver_attach;
	driver->nt_detach    = (JackDriverNTDetachFunction)   freebob_driver_detach;
	driver->nt_start     = (JackDriverNTStartFunction)    freebob_driver_start;
	driver->nt_stop      = (JackDriverNTStopFunction)     freebob_driver_stop;
	driver->nt_run_cycle = (JackDriverNTRunCycleFunction) freebob_driver_run_cycle;
	driver->null_cycle   = (JackDriverNullCycleFunction)  freebob_driver_null_cycle;
	driver->write        = (JackDriverReadFunction)       freebob_driver_write;
	driver->read         = (JackDriverReadFunction)       freebob_driver_read;
	driver->nt_bufsize   = (JackDriverNTBufSizeFunction)  freebob_driver_bufsize;
	
	/* copy command line parameter contents to the driver structure */
	memcpy(&driver->settings,params,sizeof(freebob_jack_settings_t));
	
	/* prepare all parameters */
	driver->sample_rate = params->sample_rate;
	driver->period_size = params->period_size;
	driver->last_wait_ust = 0;
	
	driver->period_usecs =
		(jack_time_t) floor ((((float) driver->period_size) * 1000000.0f) / driver->sample_rate);

	driver->client = client;
	driver->engine = NULL;

	memset(&driver->device_options,0,sizeof(driver->device_options));	
	driver->device_options.sample_rate=params->sample_rate;
	driver->device_options.period_size=params->period_size;
	driver->device_options.nb_buffers=params->buffer_size;
	driver->device_options.node_id=params->node_id;
	driver->device_options.port=params->port;
	driver->capture_frame_latency = params->capture_frame_latency;
	driver->playback_frame_latency = params->playback_frame_latency;

	if(!params->capture_ports) {
		driver->device_options.directions |= FREEBOB_IGNORE_CAPTURE;
	}

	if(!params->playback_ports) {
		driver->device_options.directions |= FREEBOB_IGNORE_PLAYBACK;
	}

	debugPrint(DEBUG_LEVEL_STARTUP, " Driver compiled on %s %s", __DATE__, __TIME__);
	debugPrint(DEBUG_LEVEL_STARTUP, " Created driver %s", name);
	debugPrint(DEBUG_LEVEL_STARTUP, "            period_size: %d", driver->period_size);
	debugPrint(DEBUG_LEVEL_STARTUP, "            period_usecs: %d", driver->period_usecs);
	debugPrint(DEBUG_LEVEL_STARTUP, "            sample rate: %d", driver->sample_rate);

	return (freebob_driver_t *) driver;

}

static void
freebob_driver_delete (freebob_driver_t *driver)
{
	if(driver->dev) {
		freebob_driver_detach(driver);
	}

	jack_driver_nt_finish ((jack_driver_nt_t *) driver);
	free (driver);
}

#ifdef FREEBOB_DRIVER_WITH_ALSA_MIDI
/*
 * MIDI support
 */ 

// the thread that will queue the midi events from the seq to the stream buffers

void * freebob_driver_midi_queue_thread(void *arg)
{
	freebob_driver_midi_handle_t *m=(freebob_driver_midi_handle_t *)arg;
	assert(m);
	snd_seq_event_t *ev;
	unsigned char work_buffer[MIDI_TRANSMIT_BUFFER_SIZE];
	int bytes_to_send;
	int b;
	int i;

	printMessage("MIDI queue thread started");

	while(1) {
		// get next event, if one is present
		while ((snd_seq_event_input(m->seq_handle, &ev) > 0)) {
			if (ev->source.client == SND_SEQ_CLIENT_SYSTEM)
				continue;

			// get the port this event is originated from
			freebob_midi_port_t *port=NULL;
			for (i=0;i<m->nb_output_ports;i++) {
				if(m->output_ports[i]->seq_port_nr == ev->dest.port) {
					port=m->output_ports[i];
					break;
				}
			}
	
			if(!port) {
				printError(" Could not find target port for event: dst=%d src=%d", ev->dest.port, ev->source.port);

				break;
			}
			
			// decode it to the work buffer
			if((bytes_to_send = snd_midi_event_decode ( port->parser, 
				work_buffer,
				MIDI_TRANSMIT_BUFFER_SIZE, 
				ev))<0) 
			{ // failed
				printError(" Error decoding event for port %d (errcode=%d)", port->seq_port_nr,bytes_to_send);
				bytes_to_send=0;
				//return -1;
			}
	
			for(b=0;b<bytes_to_send;b++) {
				freebob_sample_t tmp_event=work_buffer[b];
				if(freebob_streaming_write(m->dev, port->stream_nr, &tmp_event, 1)<1) {
					printError(" Midi send buffer overrun");
				}
			}
	
		}

		// sleep for some time
		usleep(MIDI_THREAD_SLEEP_TIME_USECS);
	}
	return NULL;
}

// the dequeue thread (maybe we need one thread per stream)
void *freebob_driver_midi_dequeue_thread (void *arg) {
	freebob_driver_midi_handle_t *m=(freebob_driver_midi_handle_t *)arg;

	int i;
	int s;
	
	int samples_read;

	assert(m);

	while(1) {
		// read incoming events
	
		for (i=0;i<m->nb_input_ports;i++) {
			unsigned int buff[64];
	
			freebob_midi_port_t *port=m->input_ports[i];
		
			if(!port) {
				printError(" something went wrong when setting up the midi input port map (%d)",i);
			}
		
			do {
				samples_read=freebob_streaming_read(m->dev, port->stream_nr, buff, 64);
			
				for (s=0;s<samples_read;s++) {
					unsigned int *byte=(buff+s) ;
					snd_seq_event_t ev;
					if ((snd_midi_event_encode_byte(port->parser,(*byte) & 0xFF, &ev)) > 0) {
						// a midi message is complete, send it out to ALSA
						snd_seq_ev_set_subs(&ev);  
						snd_seq_ev_set_direct(&ev);
						snd_seq_ev_set_source(&ev, port->seq_port_nr);
						snd_seq_event_output_direct(port->seq_handle, &ev);						
					}
				}
			} while (samples_read>0);
		}

		// sleep for some time
		usleep(MIDI_THREAD_SLEEP_TIME_USECS);
	}
	return NULL;
}

static freebob_driver_midi_handle_t *freebob_driver_midi_init(freebob_driver_t *driver) {
// 	int err;

	char buf[256];
	channel_t chn;
	int nchannels;
	int i=0;

	freebob_device_t *dev=driver->dev;

	assert(dev);

	freebob_driver_midi_handle_t *m=calloc(1,sizeof(freebob_driver_midi_handle_t));
	if (!m) {
		printError("not enough memory to create midi structure");
		return NULL;
	}

	if (snd_seq_open(&m->seq_handle, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK) < 0) {
		printError("Error opening ALSA sequencer.");
		free(m);
		return NULL;
	}

	snd_seq_set_client_name(m->seq_handle, "FreeBoB Jack MIDI");

	// find out the number of midi in/out ports we need to setup
	nchannels=freebob_streaming_get_nb_capture_streams(dev);

	m->nb_input_ports=0;

	for (chn = 0; chn < nchannels; chn++) {	
		if(freebob_streaming_get_capture_stream_type(dev, chn) == freebob_stream_type_midi) {
			m->nb_input_ports++;
		}
	}

	m->input_ports=calloc(m->nb_input_ports,sizeof(freebob_midi_port_t *));
	if(!m->input_ports) {
		printError("not enough memory to create midi structure");
		free(m);
		return NULL;
	}

	i=0;
	for (chn = 0; chn < nchannels; chn++) {
		if(freebob_streaming_get_capture_stream_type(dev, chn) == freebob_stream_type_midi) {
			m->input_ports[i]=calloc(1,sizeof(freebob_midi_port_t));
			if(!m->input_ports[i]) {
				// fixme
				printError("Could not allocate memory for seq port");
				continue;
			}

	 		freebob_streaming_get_capture_stream_name(dev, chn, buf, sizeof(buf) - 1);
			printMessage("Register MIDI IN port %s", buf);

			m->input_ports[i]->seq_port_nr=snd_seq_create_simple_port(m->seq_handle, buf,
				SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
				SND_SEQ_PORT_TYPE_MIDI_GENERIC);

			if(m->input_ports[i]->seq_port_nr<0) {
				printError("Could not create seq port");
				m->input_ports[i]->stream_nr=-1;
				m->input_ports[i]->seq_port_nr=-1;
			} else {
				m->input_ports[i]->stream_nr=chn;
				m->input_ports[i]->seq_handle=m->seq_handle;
				if (snd_midi_event_new  ( ALSA_SEQ_BUFF_SIZE, &(m->input_ports[i]->parser)) < 0) {
					printError("could not init parser for MIDI IN port %d",i);
					m->input_ports[i]->stream_nr=-1;
					m->input_ports[i]->seq_port_nr=-1;
				}
			}

			i++;
		}
	}

	// playback
	nchannels=freebob_streaming_get_nb_playback_streams(dev);

	m->nb_output_ports=0;

	for (chn = 0; chn < nchannels; chn++) {	
		if(freebob_streaming_get_playback_stream_type(dev, chn) == freebob_stream_type_midi) {
			m->nb_output_ports++;
		}
	}

	m->output_ports=calloc(m->nb_output_ports,sizeof(freebob_midi_port_t *));
	if(!m->output_ports) {
		printError("not enough memory to create midi structure");
		for (i = 0; i < m->nb_input_ports; i++) {	
			free(m->input_ports[i]);
		}
		free(m->input_ports);
		free(m);
		return NULL;
	}

	i=0;
	for (chn = 0; chn < nchannels; chn++) {
		if(freebob_streaming_get_playback_stream_type(dev, chn) == freebob_stream_type_midi) {
			m->output_ports[i]=calloc(1,sizeof(freebob_midi_port_t));
			if(!m->output_ports[i]) {
				// fixme
				printError("Could not allocate memory for seq port");
				continue;
			}

	 		freebob_streaming_get_playback_stream_name(dev, chn, buf, sizeof(buf) - 1);
			printMessage("Register MIDI OUT port %s", buf);

			m->output_ports[i]->seq_port_nr=snd_seq_create_simple_port(m->seq_handle, buf,
				SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
              			SND_SEQ_PORT_TYPE_MIDI_GENERIC);


			if(m->output_ports[i]->seq_port_nr<0) {
				printError("Could not create seq port");
				m->output_ports[i]->stream_nr=-1;
				m->output_ports[i]->seq_port_nr=-1;
			} else {
				m->output_ports[i]->stream_nr=chn;
				m->output_ports[i]->seq_handle=m->seq_handle;
				if (snd_midi_event_new  ( ALSA_SEQ_BUFF_SIZE, &(m->output_ports[i]->parser)) < 0) {
					printError("could not init parser for MIDI OUT port %d",i);
					m->output_ports[i]->stream_nr=-1;
					m->output_ports[i]->seq_port_nr=-1;
				}
			}

			i++;
		}
	}

	m->dev=dev;
	m->driver=driver;

	return m;
}

static int
freebob_driver_midi_start (freebob_driver_midi_handle_t *m)
{
	assert(m);
	// start threads

	m->queue_thread_realtime=(m->driver->engine->control->real_time? 1 : 0);
 	m->queue_thread_priority=
		m->driver->engine->control->client_priority +
		FREEBOB_RT_PRIORITY_MIDI_RELATIVE;

	if (m->queue_thread_priority>98) {
		m->queue_thread_priority=98;
	}
	if (m->queue_thread_realtime) {
		printMessage("MIDI threads running with Realtime scheduling, priority %d",
		           m->queue_thread_priority);
	} else {
		printMessage("MIDI threads running without Realtime scheduling");
	}

	if (jack_client_create_thread(NULL, &m->queue_thread, m->queue_thread_priority, m->queue_thread_realtime, freebob_driver_midi_queue_thread, (void *)m)) {
		printError(" cannot create midi queueing thread");
		return -1;
	}

	if (jack_client_create_thread(NULL, &m->dequeue_thread, m->queue_thread_priority, m->queue_thread_realtime, freebob_driver_midi_dequeue_thread, (void *)m)) {
		printError(" cannot create midi dequeueing thread");
		return -1;
	}
	return 0;
}

static int
freebob_driver_midi_stop (freebob_driver_midi_handle_t *m)
{
	assert(m);

	pthread_cancel (m->queue_thread);
	pthread_join (m->queue_thread, NULL);

	pthread_cancel (m->dequeue_thread);
	pthread_join (m->dequeue_thread, NULL);
	return 0;

}

static void
freebob_driver_midi_finish (freebob_driver_midi_handle_t *m)
{
	assert(m);

	int i;
	// TODO: add state info here, if not stopped then stop

	for (i=0;i<m->nb_input_ports;i++) {
		free(m->input_ports[i]);

	}
	free(m->input_ports);

	for (i=0;i<m->nb_output_ports;i++) {
		free(m->output_ports[i]);
	}
	free(m->output_ports);

	free(m);
}
#endif	
/*
 * dlopen plugin stuff
 */

const char driver_client_name[] = "freebob_pcm";

const jack_driver_desc_t *
driver_get_descriptor ()
{
	jack_driver_desc_t * desc;
	jack_driver_param_desc_t * params;
	unsigned int i;

	desc = calloc (1, sizeof (jack_driver_desc_t));

	strcpy (desc->name, "freebob");
	desc->nparams = 11;
  
	params = calloc (desc->nparams, sizeof (jack_driver_param_desc_t));
	desc->params = params;

	i = 0;
	strcpy (params[i].name, "device");
	params[i].character  = 'd';
	params[i].type       = JackDriverParamString;
	strcpy (params[i].value.str,  "hw:0");
	strcpy (params[i].short_desc, "The FireWire device to use. Format is: 'hw:port[,node]'.");
	strcpy (params[i].long_desc,  params[i].short_desc);
	
	i++;
	strcpy (params[i].name, "period");
	params[i].character  = 'p';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 1024;
	strcpy (params[i].short_desc, "Frames per period");
	strcpy (params[i].long_desc, params[i].short_desc);
	
	i++;
	strcpy (params[i].name, "nperiods");
	params[i].character  = 'n';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 3;
	strcpy (params[i].short_desc, "Number of periods of playback latency");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "rate");
	params[i].character  = 'r';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 48000U;
	strcpy (params[i].short_desc, "Sample rate");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "capture");
	params[i].character  = 'C';
	params[i].type       = JackDriverParamBool;
	params[i].value.i    = 0;
	strcpy (params[i].short_desc, "Provide capture ports.");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "playback");
	params[i].character  = 'P';
	params[i].type       = JackDriverParamBool;
	params[i].value.i    = 0;
	strcpy (params[i].short_desc, "Provide playback ports.");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "duplex");
	params[i].character  = 'D';
	params[i].type       = JackDriverParamBool;
	params[i].value.i    = 1;
	strcpy (params[i].short_desc, "Provide both capture and playback ports.");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "input-latency");
	params[i].character  = 'I';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui    = 0;
	strcpy (params[i].short_desc, "Extra input latency (frames)");
	strcpy (params[i].long_desc, params[i].short_desc);
	
	i++;
	strcpy (params[i].name, "output-latency");
	params[i].character  = 'O';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui    = 0;
	strcpy (params[i].short_desc, "Extra output latency (frames)");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "inchannels");
	params[i].character  = 'i';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui    = 0;
	strcpy (params[i].short_desc, "Number of input channels to provide (note: currently ignored)");
	strcpy (params[i].long_desc, params[i].short_desc);
	
	i++;
	strcpy (params[i].name, "outchannels");
	params[i].character  = 'o';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui    = 0;
	strcpy (params[i].short_desc, "Number of output channels to provide (note: currently ignored)");
	strcpy (params[i].long_desc, params[i].short_desc); 

	return desc;
}


jack_driver_t *
driver_initialize (jack_client_t *client, JSList * params)
{
	jack_driver_t *driver;

    unsigned int port=0;
    unsigned int node_id=-1;
    int nbitems;
      
	const JSList * node;
	const jack_driver_param_t * param;

	freebob_jack_settings_t cmlparams;
	
    char *device_name="hw:0"; 
      
	cmlparams.period_size_set=0;
	cmlparams.sample_rate_set=0;
	cmlparams.buffer_size_set=0;
	cmlparams.port_set=0;
	cmlparams.node_id_set=0;

	/* default values */
	cmlparams.period_size=1024;
	cmlparams.sample_rate=48000;
	cmlparams.buffer_size=3;
	cmlparams.port=0;
	cmlparams.node_id=-1;
	cmlparams.playback_ports=0;
	cmlparams.capture_ports=0;
	cmlparams.playback_frame_latency=0;
	cmlparams.capture_frame_latency=0;
	
	for (node = params; node; node = jack_slist_next (node))
	{
		param = (jack_driver_param_t *) node->data;

		switch (param->character)
		{
		case 'd':
			device_name = strdup (param->value.str);
			break;
		case 'p':
			cmlparams.period_size = param->value.ui;
			cmlparams.period_size_set = 1;
			break;
		case 'n':
			cmlparams.buffer_size = param->value.ui;
			cmlparams.buffer_size_set = 1;
			break;        
		case 'r':
			cmlparams.sample_rate = param->value.ui;
			cmlparams.sample_rate_set = 1;
			break;
		case 'C':
			cmlparams.capture_ports = 1;
			break;
		case 'P':
			cmlparams.playback_ports = 1;
			break;
		case 'D':
			cmlparams.capture_ports = 1;
			cmlparams.playback_ports = 1;
			break;
		case 'I':
			cmlparams.capture_frame_latency = param->value.ui;
			break;
		case 'O':
			cmlparams.playback_frame_latency = param->value.ui;
			break;
		// ignore these for now
		case 'i':
			break;
		case 'o':
			break;
		}
	}

	/* duplex is the default */
	if (!cmlparams.playback_ports && !cmlparams.capture_ports) {
		cmlparams.playback_ports = TRUE;
		cmlparams.capture_ports = TRUE;
	}

    nbitems=sscanf(device_name,"hw:%u,%u",&port,&node_id);
    if (nbitems<2) {
        nbitems=sscanf(device_name,"hw:%u",&port);
      
        if(nbitems < 1) {
            free(device_name);
            printError("device (-d) argument not valid\n");
            return NULL;
        } else {
            cmlparams.port = port;
            cmlparams.port_set=1;
            
            cmlparams.node_id = -1;
            cmlparams.node_id_set=0;
        }
     } else {
        cmlparams.port = port;
        cmlparams.port_set=1;
        
        cmlparams.node_id = node_id;
        cmlparams.node_id_set=1;
     }

    jack_error("Freebob using Firewire port %d, node %d",cmlparams.port,cmlparams.node_id);
    
	driver=(jack_driver_t *)freebob_driver_new (client, "freebob_pcm", &cmlparams);

	return driver;
}

void
driver_finish (jack_driver_t *driver)
{
	freebob_driver_t *drv=(freebob_driver_t *) driver;
	
	freebob_driver_delete (drv);

}
