/***************************************************************************
 * sequencer.h: definition of SequencerAlsa class
 *
 * This file is part of KTabEdit, a KDE tabulature editor
 *
 * copyright (C) 2003-2006 the KTabEdit development team <mrgwen@gmail.com>
 ***************************************************************************/

/***************************************************************************
 * 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.
 *
 * See the file COPYING for more information.
 ***************************************************************************/

#include "sequencer.h"
#include <kdebug.h>

bool SequencerAlsa::init = false;

SequencerAlsa::SequencerAlsa() throw(const char*)
{
	if (init)
		throw "impossible to initialize the sequencer in SequencerAlsa::scanDevices";
	
	scanDevices();
	
	init = true;
}

uint SequencerAlsa::countDevice() const
{
	return listAlsaDevice.count();
}

QString SequencerAlsa::deviceName(uint n) const throw(const char*)
{
	if (n > listAlsaDevice.count())
		throw "impossible to give the name in SequencerAlsa::deviceName";
	
	return listAlsaDevice[n].name;
}

unsigned char SequencerAlsa::client(uint n) const throw(const char*)
{
	if (n > listAlsaDevice.count())
		throw "impossible to give the name in SequencerAlsa::client";
	
	return listAlsaDevice[n].client;
}

unsigned char SequencerAlsa::port(uint n) const throw(const char*)
{
	if (n > listAlsaDevice.count())
		throw "impossible to give the name in SequencerAlsa::port";
	
	return listAlsaDevice[n].port;
}

void SequencerAlsa::openDevice(uint n) throw(const char*)
{
	if (n > listAlsaDevice.count())
		throw "impossible to open device in SequencerAlsa::openDevice";
	
	if (listAlsaDevice[n].open == true)
		throw "impossible to open device in SequencerAlsa::openDevice";
	
	snd_seq_port_subscribe_alloca(&context.subs);
	
	snd_seq_port_subscribe_set_sender(context.subs, &context.address);
	snd_seq_port_subscribe_set_dest(context.subs, &listAlsaDevice[n].address);
	
	if (snd_seq_subscribe_port(context.handle, context.subs) < 0)
		throw "impossible to subscribe to port in SequencerAlsa::openDevice";
	
	listAlsaDevice[n].open = true;
}

void SequencerAlsa::closeDevice(uint n) throw(const char*)
{
	if (n > listAlsaDevice.count())
		throw "impossible to close device in SequencerAlsa::closeDevice";
	
	if (listAlsaDevice[n].open == false)
		throw "impossible to open device in SequencerAlsa::closeDevice";
	
	if (snd_seq_unsubscribe_port(context.handle, context.subs) < 0)
		throw "impossible to subscribe to port in SequencerAlsa::closeDevice";
	
	snd_seq_port_subscribe_free(context.subs);
	
	listAlsaDevice[n].open = false;
}

void SequencerAlsa::scanDevices() throw(const char*)
{
	snd_seq_client_info_t* clientInfo;
	uint devicesCount = 0;
	
	if (init) {
		if (snd_seq_close(context.handle) < 0)
			throw "impossible to close the sequencer in SequencerAlsa::scanDevices";
	}
	
	if (snd_seq_open(&context.handle, "default", SND_SEQ_OPEN_OUTPUT, 0) < 0)
		throw "impossible to open the sequencer in SequencerAlsa::scanDevices";
	
	snd_seq_client_info_alloca(&clientInfo);
	snd_seq_client_info_set_client(clientInfo, -1);
	
	listAlsaDevice.clear();
	
	// iterate through clients
	//
	while (snd_seq_query_next_client(context.handle, clientInfo) >= 0) {
		snd_seq_port_info_t* pinfo;
		
		snd_seq_port_info_alloca(&pinfo);
		snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(clientInfo));
		snd_seq_port_info_set_port(pinfo, -1);
		
		// and now through ports
		//
		while (snd_seq_query_next_port(context.handle, pinfo) >= 0) {
			unsigned int capability = snd_seq_port_info_get_capability(pinfo);
			AlsaDevice alsa;
			
			if ((capability & SND_SEQ_PORT_CAP_SUBS_WRITE) == 0)
				continue;
			
			alsa.open    = false;
			alsa.address = *snd_seq_port_info_get_addr(pinfo);
			alsa.name    = snd_seq_port_info_get_name(pinfo);
			alsa.client  = (snd_seq_port_info_get_addr(pinfo))->client;
			alsa.port    = (snd_seq_port_info_get_addr(pinfo))->port;
			
			listAlsaDevice.push_back(alsa);
			
			devicesCount++;
		}
	}
	
	if (snd_seq_set_client_name(context.handle, "KTabEdit") < 0)
		throw "impossible to set the client name in SequencerAlsa::scanDevices";
	
	context.address.port = snd_seq_create_simple_port(context.handle, "KTabEdit Port 0", SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION);
	context.address.client = snd_seq_client_id (context.handle);
	context.queue = snd_seq_alloc_queue (context.handle);
	
	snd_seq_set_client_pool_output (context.handle, 1024);
}

void SequencerAlsa::noteOn(char channel, char note, char velocity)
{
	snd_seq_event_t event;
	
	snd_seq_ev_clear(&event);
	
	event.queue  = SND_SEQ_QUEUE_DIRECT;
	event.source = context.address;
	
	snd_seq_ev_set_subs(&event);
	snd_seq_ev_set_direct(&event);
	snd_seq_ev_set_noteon(&event, channel, note, velocity);
	
	snd_seq_event_output_direct(context.handle, &event);
	snd_seq_drain_output(context.handle);
}

void SequencerAlsa::noteOff(char channel, char note, char velocity)
{
	snd_seq_event_t event;
	
	snd_seq_ev_clear(&event);
	
	event.queue  = SND_SEQ_QUEUE_DIRECT;
	event.source = context.address;
	
	snd_seq_ev_set_subs(&event);
	snd_seq_ev_set_direct(&event);
	snd_seq_ev_set_noteoff(&event, channel, note, velocity);
	
	snd_seq_event_output_direct(context.handle, &event);
	snd_seq_drain_output(context.handle);
}

void SequencerAlsa::changePatch(uchar channel, uchar patch)
{
	snd_seq_event_t event;
	
	snd_seq_ev_clear(&event);
	
	event.queue  = SND_SEQ_QUEUE_DIRECT;
	event.source = context.address;
	
	snd_seq_ev_set_subs(&event);
	snd_seq_ev_set_direct(&event);
	snd_seq_ev_set_pgmchange(&event, channel, patch);
	
	if (snd_seq_event_output_direct(context.handle, &event) < 0)
		kdDebug() << "error\n";
	
	snd_seq_drain_output(context.handle);
}

void SequencerAlsa::setTempo(uint tempo)
{
	snd_seq_queue_tempo_t* event;
	
	snd_seq_queue_tempo_alloca(&event);
	
	// The tempo in BPM
	//
	snd_seq_queue_tempo_set_tempo(event, 60*1000000/120);
	
	// The tempo in pulse per quarter note
	//
	snd_seq_queue_tempo_set_ppq(event, tempo);
	
	if (snd_seq_set_queue_tempo(context.handle, context.queue, event) < 0)
		kdDebug() << "error\n";
	
	timerSend(SND_SEQ_EVENT_START);
	snd_seq_start_queue(context.handle, context.queue, NULL);
}

void SequencerAlsa::timerSend(uint type)
{
	snd_seq_event_t event;
	
	snd_seq_ev_clear(&event);
	
	event.queue = context.queue;
	event.dest.client = SND_SEQ_CLIENT_SYSTEM;
	event.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
	
	event.data.queue.queue = context.queue;
	
	event.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL;
	event.time.time.tv_sec = 0;
	event.time.time.tv_nsec = 0;
	
	event.type = type;
	
	snd_seq_event_output(context.handle, &event);
	snd_seq_drain_output(context.handle);
}
