#include <alsa/asoundlib.h>

#define CLIENT 72
#define PORT    0

void subscribe_to(snd_seq_t *seq, int port, int client, int c_port);
void start_queue(snd_seq_t *seq, int client, int port, int q);
void set_queue_tempo(snd_seq_t *seq, int client, int port, int q,
                     unsigned int tempo);
void set_queue_position(snd_seq_t *seq, int client, int port, int q,
                        unsigned int position);

int main() {
    snd_seq_t       *seq;
    int              port, p_timer;
    snd_seq_event_t *ev;

    /* Open the sequencer */
    snd_seq_open(&seq, "default", SND_SEQ_OPEN_INPUT, 0);
    snd_seq_set_client_name(seq, "test");

    /* Our port */
    port = snd_seq_create_simple_port(seq, "test port",
                                      SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
                                      SND_SEQ_PORT_TYPE_APPLICATION);


    /* Thought this might be something... but it's not */
    //snd_seq_connect_to(seq, port, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_TIMER);
    //subscribe_to(seq, port, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_TIMER);

    subscribe_to(seq, port, CLIENT, PORT);

    for(;;) {
        snd_seq_event_input(seq, &ev);
        printf("Type: %d Time: %d %d\n", ev->type, ev->time.time.tv_sec,
               ev->time.time.tv_nsec);
        /* This prints 0 0 ... what do I do to receive time? */
    }
}

void subscribe_to(snd_seq_t *seq, int port, int client, int c_port) {
    snd_seq_addr_t sender, dest;
    snd_seq_port_subscribe_t *subs;
    int id, queue;
    snd_seq_event_t ev;

    id            = snd_seq_client_id(seq);
    sender.client = client;
    sender.port   = c_port;
    dest.client   = id;
    dest.port     = port;
    queue         = snd_seq_alloc_queue(seq);

    snd_seq_port_subscribe_alloca(&subs);
    snd_seq_port_subscribe_set_sender(subs, &sender);
    snd_seq_port_subscribe_set_dest(subs, &dest);
    snd_seq_port_subscribe_set_queue(subs, queue);
    snd_seq_port_subscribe_set_time_update(subs, 1);
    snd_seq_port_subscribe_set_time_real(subs, 1);
    snd_seq_subscribe_port(seq, subs);

    /* Uncommenting these seems to have no visible effect either */
    //set_queue_tempo(seq, id, port, queue, 10000);
    //set_queue_position(seq, id, port, queue, 5);
    //start_queue(seq, id, port, queue);
    
    snd_seq_start_queue(seq, queue, NULL);
    snd_seq_drain_output(seq);
}

void start_queue(snd_seq_t *seq, int client, int port, int q) {
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    ev.type = SND_SEQ_EVENT_START;
    ev.dest.client = SND_SEQ_CLIENT_SYSTEM;
    ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
    ev.source.client = client;
    ev.source.port = port;
    ev.queue = SND_SEQ_QUEUE_DIRECT; // no scheduling
    ev.data.queue.queue = q;	// affected queue id
    snd_seq_event_output(seq, &ev);    
}


void set_queue_tempo(snd_seq_t *seq, int client, int port, int q,
                     unsigned int tempo) {
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    ev.type = SND_SEQ_EVENT_TEMPO;
    ev.dest.client = SND_SEQ_CLIENT_SYSTEM;
    ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
    ev.source.client = client;
    ev.source.port = port;
    ev.queue = SND_SEQ_QUEUE_DIRECT; // no scheduling
    ev.data.queue.queue = q;	// affected queue id
    ev.data.queue.param.value = tempo;	// new tempo in microsec.
    snd_seq_event_output(seq, &ev);    
}

void set_queue_position(snd_seq_t *seq, int client, int port, int q,
                        unsigned int position) {
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    ev.type = SND_SEQ_EVENT_TEMPO;
    ev.dest.client = SND_SEQ_CLIENT_SYSTEM;
    ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
    ev.source.client = client;
    ev.source.port = port;
    ev.queue = SND_SEQ_QUEUE_DIRECT; // no scheduling
    ev.data.queue.queue = q;	// affected queue id
    ev.data.queue.param.position = position;	// new tempo in microsec.
    snd_seq_event_output(seq, &ev);    
}
