// Module alsa_midieh.c - derived from aplaymidi.c from the Internet
// Copyright Dr. E.Huckert (EH) 07-2014
// Sample file for the Linux/Alsa seqwuencer API
// This is a strongly simplified MIDI player:
//   doesn't read MIDI files
//   no use of constructs like TRACK or special event structs
//   avoids Alsa macros
//   assumes that program Timidity is running om port 128:0
//   (find out via  commands "aconnect -i -o" and "ps ax | grep timidity")
//   C style improved (nearly no global variables)
// Note that the Alsa libraries and API include files must exist!
// Compile on Linux:
//   gcc -o alsa_midieh alsa_midieh.c -lasound

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <alsa/asoundlib.h>
//#include "aconfig.h"
//#include "version.h"
#include <alsa/version.h>

int port_count = 0;
snd_seq_addr_t *ports = NULL;
int end_delay = 2;

// --------------------------------------------------------------
/* prints an error message to stderr, and dies */
void fatal(const char *msg, ...)
{
  va_list ap;
  va_start(ap, msg);
  vfprintf(stderr, msg, ap);
  va_end(ap);
  fputc('\n', stderr);
  exit(EXIT_FAILURE);
}

// ----------------------------------------------------------------
// memory allocation error handling 
// terminates the program if an error is detected
void check_mem(void *p)
{
  if (p == NULL)
    fatal("Out of memory");
}

// --------------------------------------------------------------
// error handling for ALSA functions 
// terminates the program if an error is detected
void check_snd(const char *operation, int err)
{
   if (err < 0)
      fatal("Cannot %s - %s", operation, snd_strerror(err));
}

// --------------------------------------------------------------
// Initialize the sequencer
// Returns a pointer to a sequencer 
snd_seq_t * init_seq(void)
{
  int err;
  snd_seq_client_info_t *info;
  static snd_seq_t *seq = NULL;

  /* open sequencer (here for send and receive = duplex) */
  err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
  check_snd("open sequencer", err);

  /* set our name (otherwise it's "Client-xxx") */
  err = snd_seq_set_client_name(seq, "alsa_midieh");
  check_snd("set client name", err);
  // 
  return seq;
}   // end init_seq()

// --------------------------------------------------------------
/* parses one or more port addresses from the string */
void parse_ports(snd_seq_t *pSeq,
                        const char *arg)
{
  char *buf, *s, *port_name;
  int err;

  /* make a copy of the string because we're going to modify it */
  buf = strdup(arg);
  check_mem(buf);

  for (port_name = s = buf; s; port_name = s + 1)
  {
      s = strchr(port_name, ',');
      if (s != NULL)
            *s = '\0';
      ++port_count;
      ports = realloc(ports, port_count * sizeof(snd_seq_addr_t));
      check_mem(ports);
      err = snd_seq_parse_address(pSeq, 
                                  &ports[port_count - 1], 
                                  port_name);
      if (err < 0)
        fatal("Invalid port %s - %s", port_name, snd_strerror(err));
  }   // end for...
  free(buf);
}   // end parse_ports()

// --------------------------------------------------------------
// I don't know if this is necessary for every line...
void create_source_port(snd_seq_t *pSeq)
{
  snd_seq_port_info_t *pinfo;
  int err;
  //
  snd_seq_port_info_alloca(&pinfo);
  /* the first created port is 0 anyway, but let's make sure ... */
  snd_seq_port_info_set_port(pinfo, 0);
  snd_seq_port_info_set_port_specified(pinfo, 1);
  snd_seq_port_info_set_name(pinfo, "alsa_midieh");
  snd_seq_port_info_set_capability(pinfo, 0);
  snd_seq_port_info_set_type(pinfo,
         SND_SEQ_PORT_TYPE_MIDI_GENERIC |
         SND_SEQ_PORT_TYPE_APPLICATION);
  err = snd_seq_create_port(pSeq, pinfo);
  check_snd("create port", err);
}   // end create_source_port()

// --------------------------------------------------------------
void connect_ports(snd_seq_t *pSeq)
{
  int i, err;
  /*
   * We send MIDI events with explicit destination addresses, so we don't
   * need any connections to the playback ports.  But we connect to those
   * anyway to force any underlying RawMIDI ports to remain open while
   * we're playing - otherwise, ALSA would reset the port after every
   * event.
   */
  for (i = 0; i < port_count; ++i) 
  {
    err = snd_seq_connect_to(pSeq, 0, ports[i].client, ports[i].port);
    if (err < 0)
      fatal("Cannot connect to port %d:%d - %s",
             ports[i].client, ports[i].port, snd_strerror(err));
  }
}   // end connect_ports()

// --------------------------------------------------------------------------
// set up an event and play it directly - without using a queue!
void direct_delivery(snd_seq_t *pSeq,
                     int p_type,
                     int p_channel,
                     int p_note,
                     int p_velocity)
{
  snd_seq_event_t ev;
  snd_seq_ev_note_t  l_note;
  //
  snd_seq_ev_clear(&ev);
  ev.type = p_type;
  ev.source.client   = 0;
  ev.source.port     = 0;
  snd_seq_ev_set_subs(&ev);
  snd_seq_ev_set_direct(&ev);
  // set event type, data, so on..
  l_note.channel = p_channel;
  l_note.note    = p_note;
  l_note.velocity = p_velocity;
  l_note.off_velocity = 0;
  l_note.duration = 50000;  // not necessary!
  ev.data.note    = l_note;
  //
  snd_seq_event_output(pSeq, &ev);
  snd_seq_drain_output(pSeq);
}   // end direct_delivery()

// ----------------------------------------------------------------------
// Generates an ascending chromatic scale and a chord
// Plays the notes directly - no use of queues or linked lists
// The timing is handles via simply usleep() or sleep() calls
// Returns: the number of events submitted
int fill_and_play(snd_seq_t *seq)
{
  int n;
  int channel  = 0;    // must be between 0 and 15
  int note     = 40;   // an arbitrary start value - must be between 0 and 127
  int velocity = 100;  // must be between 0 and 127 
  int num_events = 0;
  //
  for (n=0; n < 16; n++)
  {
    // output a NOTEON event
    direct_delivery(seq, SND_SEQ_EVENT_NOTEON, channel, note, velocity);
    usleep(250L * 1000L);
    // output the corresponding NOTEOFF event
    direct_delivery(seq, SND_SEQ_EVENT_NOTEOFF, channel, note, 0);
    usleep(50L * 1000L);
    //
    num_events++;
    note += 1;
  }
  //
  // play a major chord (a tetra-chord)
  sleep(1);
  direct_delivery(seq, SND_SEQ_EVENT_NOTEON, channel, 50, velocity);
  direct_delivery(seq, SND_SEQ_EVENT_NOTEON, channel, 54, velocity);
  direct_delivery(seq, SND_SEQ_EVENT_NOTEON, channel, 57, velocity);
  num_events += 3;
  sleep(2);
  direct_delivery(seq, SND_SEQ_EVENT_NOTEOFF, channel, 50, 0);
  direct_delivery(seq, SND_SEQ_EVENT_NOTEOFF, channel, 54, 0);
  direct_delivery(seq, SND_SEQ_EVENT_NOTEOFF, channel, 57, 0);
  usleep(50L * 1000L);
  num_events += 3;
  //
  return num_events;
}   // end fill_and_play()

// --------------------------------------------------------------
// no command line options needed/evaluated!
int main(int argc, char *argv[])
{
  int cRet;
  snd_seq_t *pSeq = NULL;
  //
  pSeq = init_seq();
  printf("init_seq() OK\n");
  //
  parse_ports(pSeq, "128:0");      // must be!
  printf("parse_ports() OK\n");
  //
  create_source_port(pSeq);
  printf("create_source_port() OK\n");
  //
  connect_ports(pSeq);           //  must be!
  printf("connect_ports() OK\n");
  //
  // play some notes and a chord (just for demo)
  cRet = fill_and_play(pSeq);
  printf("fill_and_play() OK\n");
  //
  snd_seq_close(pSeq);
  //
  printf("snd_seq_close() OK\n");
  return 0;
}   // end main()
