diff src/Input/amidi-plug/amidi-plug.c @ 0:13389e613d67 trunk

[svn] - initial import of audacious-plugins tree (lots to do)
author nenolod
date Mon, 18 Sep 2006 01:11:49 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Input/amidi-plug/amidi-plug.c	Mon Sep 18 01:11:49 2006 -0700
@@ -0,0 +1,747 @@
+/*
+*
+* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2006
+*
+* 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.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+*
+*/
+
+#include "amidi-plug.h"
+
+
+InputPlugin *get_iplugin_info(void)
+{
+  amidiplug_ip.description = g_strdup_printf(N_("AMIDI-Plug %s (MIDI Player)"), AMIDIPLUG_VERSION);
+  return &amidiplug_ip;
+}
+
+
+static gint amidiplug_is_our_file( gchar * filename )
+{
+#if defined(MIDIFILE_PROBE_MAGICBYTES)
+    VFSFile * fp;
+    gchar magic_bytes[4];
+
+    fp = vfs_fopen( filename , "rb" );
+
+    if ( fp == NULL )
+      return FALSE;
+
+    vfs_fread( magic_bytes , 1 , 4 , fp );
+
+    if ( !strncmp( magic_bytes , "MThd" , 4 ) )
+    {
+      vfs_fclose( fp );
+      DEBUGMSG( "MIDI found, %s is a standard midi file\n" , filename );
+      return TRUE;
+    }
+
+    if ( !strncmp( magic_bytes , "RIFF" , 4 ) )
+    {
+      /* skip the four bytes after RIFF,
+         then read the next four */
+      vfs_fseek( fp , 4 , SEEK_CUR );
+      vfs_fread( magic_bytes , 1 , 4 , fp );
+      if ( !strncmp( magic_bytes , "RMID" , 4 ) )
+      {
+        vfs_fclose( fp );
+        DEBUGMSG( "MIDI found, %s is a riff midi file\n" , filename );
+        return TRUE;
+      }
+    }
+    vfs_fclose( fp );
+#else
+    gchar * ext = strrchr( filename, '.' );
+    /* check the filename extension */
+    if ( ( ext ) &&
+         (( !strcasecmp(ext,".mid") ) || ( !strcasecmp(ext,".midi") ) ||
+          ( !strcasecmp(ext,".rmi") ) || ( !strcasecmp(ext,".rmid") )) )
+      return TRUE;
+#endif
+  return FALSE;
+}
+
+
+static void amidiplug_init( void )
+{
+  g_log_set_handler(NULL , G_LOG_LEVEL_WARNING , g_log_default_handler , NULL);
+  DEBUGMSG( "init, read configuration\n" );
+  /* read configuration for amidi-plug */
+  i_configure_cfg_ap_read();
+  amidiplug_playing_status = AMIDIPLUG_STOP;
+  backend.gmodule = NULL;
+  /* load the backend selected by user */
+  i_backend_load( amidiplug_cfg_ap.ap_seq_backend );
+}
+
+
+static void amidiplug_cleanup( void )
+{
+  i_backend_unload(); /* unload currently loaded backend */
+  g_free( amidiplug_ip.description );
+}
+
+
+static void amidiplug_configure( void )
+{
+  /* display the nice config dialog */
+  DEBUGMSG( "opening config system\n" );
+  i_configure_gui();
+}
+
+
+static void amidiplug_aboutbox( void )
+{
+  i_about_gui();
+}
+
+
+static void amidiplug_file_info_box( gchar * filename )
+{
+  i_fileinfo_gui( filename );
+}
+
+
+static void amidiplug_stop( void )
+{
+  DEBUGMSG( "STOP request at tick: %i\n" , midifile.playing_tick );
+  pthread_mutex_lock( &amidiplug_playing_mutex );
+  if (( amidiplug_playing_status == AMIDIPLUG_PLAY ) ||
+      ( amidiplug_playing_status == AMIDIPLUG_STOP ))
+  {
+    amidiplug_playing_status = AMIDIPLUG_STOP;
+    pthread_mutex_unlock( &amidiplug_playing_mutex );
+    pthread_join( amidiplug_play_thread , NULL );
+    if ( backend.autonomous_audio == FALSE )
+      pthread_join( amidiplug_audio_thread , NULL );
+    DEBUGMSG( "STOP activated (play thread joined)\n" );
+  }
+  else if ( amidiplug_playing_status == AMIDIPLUG_PAUSE )
+  {
+    amidiplug_playing_status = AMIDIPLUG_STOP;
+    DEBUGMSG( "STOP activated (from PAUSE to STOP)\n" );
+    pthread_mutex_unlock( &amidiplug_playing_mutex );
+  }
+  else /* AMIDIPLUG_ERR */
+  {
+    DEBUGMSG( "STOP activated (in error handling, ok)\n" );
+    pthread_mutex_unlock( &amidiplug_playing_mutex );
+  }
+
+  /* kill the sequencer (while it may have been already killed if coming
+     from pause, it's safe to do anyway since it checks for multiple calls) */
+  if ( backend.gmodule != NULL )
+    backend.seq_off();
+
+  /* call seq_stop */
+  if ( backend.gmodule != NULL )
+    backend.seq_stop();
+
+  /* close audio if current backend works with output plugin */
+  if (( backend.gmodule != NULL ) && ( backend.autonomous_audio == FALSE ))
+  {
+    DEBUGMSG( "STOP activated, closing audio output plugin\n" );
+    amidiplug_ip.output->buffer_free();
+    amidiplug_ip.output->buffer_free();
+    amidiplug_ip.output->close_audio();
+  }
+  /* free midi data (if it has not been freed yet) */
+  i_midi_free( &midifile );
+}
+
+
+static void amidiplug_pause( gshort paused )
+{
+  if ( paused )
+  {
+    DEBUGMSG( "PAUSE request at tick: %i\n" , midifile.playing_tick );
+    pthread_mutex_lock( &amidiplug_playing_mutex );
+    /* this cond is used to avoid race conditions */
+    while ( amidiplug_playing_status != AMIDIPLUG_PLAY )
+      pthread_cond_wait( &amidiplug_playing_cond , &amidiplug_playing_mutex );
+    amidiplug_playing_status = AMIDIPLUG_PAUSE;
+    pthread_mutex_unlock( &amidiplug_playing_mutex );
+
+    pthread_join( amidiplug_play_thread , NULL );
+    if ( backend.autonomous_audio == FALSE )
+      pthread_join( amidiplug_audio_thread , NULL );
+    DEBUGMSG( "PAUSE activated (play thread joined)\n" , midifile.playing_tick );
+
+    if ( backend.autonomous_audio == FALSE )
+      amidiplug_ip.output->pause(paused);
+
+    /* kill the sequencer */
+    backend.seq_off();
+  }
+  else
+  {
+    DEBUGMSG( "PAUSE deactivated, returning to tick %i\n" , midifile.playing_tick );
+    /* revive the sequencer */
+    backend.seq_on();
+    /* re-set initial tempo */
+    i_midi_setget_tempo( &midifile );
+    backend.seq_queue_tempo( midifile.current_tempo , midifile.ppq );
+    /* get back to the previous state */
+    amidiplug_skipto( midifile.playing_tick );
+
+    if ( backend.autonomous_audio == FALSE )
+      amidiplug_ip.output->pause(paused);
+
+    pthread_mutex_lock( &amidiplug_playing_mutex );
+    /* play play play! */
+    DEBUGMSG( "PAUSE deactivated, starting play thread again\n" );
+    pthread_create(&amidiplug_play_thread, NULL, amidiplug_play_loop, NULL);
+    /* this cond is used to avoid race conditions */
+    while ( amidiplug_playing_status != AMIDIPLUG_PLAY )
+      pthread_cond_wait( &amidiplug_playing_cond , &amidiplug_playing_mutex );
+    pthread_mutex_unlock( &amidiplug_playing_mutex );
+  }
+}
+
+
+static void amidiplug_seek( gint time )
+{
+  DEBUGMSG( "SEEK requested (time %i), pausing song...\n" , time );
+  pthread_mutex_lock( &amidiplug_playing_mutex );
+  /* this cond is used to avoid race conditions */
+  while ( amidiplug_playing_status != AMIDIPLUG_PLAY )
+    pthread_cond_wait( &amidiplug_playing_cond , &amidiplug_playing_mutex );
+  amidiplug_playing_status = AMIDIPLUG_PAUSE;
+  pthread_mutex_unlock( &amidiplug_playing_mutex );
+
+  pthread_join( amidiplug_play_thread , NULL );
+  if ( backend.autonomous_audio == FALSE )
+    pthread_join( amidiplug_audio_thread , NULL );
+  DEBUGMSG( "SEEK requested (time %i), song paused\n" , time );
+  /* kill the sequencer */
+  backend.seq_off();
+  /* revive the sequencer */
+  backend.seq_on();
+  /* re-set initial tempo */
+  i_midi_setget_tempo( &midifile );
+  backend.seq_queue_tempo( midifile.current_tempo , midifile.ppq );
+  /* get back to the previous state */
+  DEBUGMSG( "SEEK requested (time %i), moving to tick %i of %i\n" ,
+            time , (gint)((time * 1000000) / midifile.avg_microsec_per_tick) , midifile.max_tick );
+  midifile.playing_tick = (gint)((time * 1000000) / midifile.avg_microsec_per_tick);
+  amidiplug_skipto( midifile.playing_tick );
+
+  if ( backend.autonomous_audio == FALSE )
+    amidiplug_ip.output->flush(time * 1000);
+
+  /* play play play! */
+  DEBUGMSG( "SEEK done, starting play thread again\n" );
+  pthread_create(&amidiplug_play_thread, NULL, amidiplug_play_loop, NULL);
+}
+
+
+static gint amidiplug_get_time( void )
+{
+  if ( backend.autonomous_audio == FALSE )
+  {
+    pthread_mutex_lock( &amidiplug_playing_mutex );
+    if (( amidiplug_playing_status == AMIDIPLUG_PLAY ) ||
+        ( amidiplug_playing_status == AMIDIPLUG_PAUSE ) ||
+        (( amidiplug_playing_status == AMIDIPLUG_STOP ) && ( amidiplug_ip.output->buffer_playing() )))
+    {
+      pthread_mutex_unlock( &amidiplug_playing_mutex );
+      return amidiplug_ip.output->output_time();
+    }
+    else if ( amidiplug_playing_status == AMIDIPLUG_STOP )
+    {
+      pthread_mutex_unlock( &amidiplug_playing_mutex );
+      DEBUGMSG( "GETTIME on stopped song, returning -1\n" , time );
+      return -1;
+    }
+    else /* AMIDIPLUG_ERR */
+    {
+      pthread_mutex_unlock( &amidiplug_playing_mutex );
+      DEBUGMSG( "GETTIME on halted song (an error occurred?), returning -1 and stopping the player\n" );
+      xmms_remote_stop(0);
+      return -1;
+    }
+  }
+  else
+  {
+    gint pt;
+    pthread_mutex_lock( &amidiplug_playing_mutex );
+    if (( amidiplug_playing_status == AMIDIPLUG_PLAY ) ||
+        ( amidiplug_playing_status == AMIDIPLUG_PAUSE ))
+    {
+      pthread_mutex_unlock( &amidiplug_playing_mutex );
+      pthread_mutex_lock(&amidiplug_gettime_mutex);
+      pt = midifile.playing_tick;
+      pthread_mutex_unlock(&amidiplug_gettime_mutex);
+      return (gint)((pt * midifile.avg_microsec_per_tick) / 1000);
+    }
+    else if ( amidiplug_playing_status == AMIDIPLUG_STOP )
+    {
+      pthread_mutex_unlock( &amidiplug_playing_mutex );
+      DEBUGMSG( "GETTIME on stopped song, returning -1\n" , time );
+      return -1;
+    }
+    else /* AMIDIPLUG_ERR */
+    {
+      pthread_mutex_unlock( &amidiplug_playing_mutex );
+      DEBUGMSG( "GETTIME on halted song (an error occurred?), returning -1 and stopping the player\n" , time );
+      xmms_remote_stop(0);
+      return -1;
+    }
+  }
+}
+
+
+static void amidiplug_get_volume( gint * l_p , gint * r_p )
+{
+  if ( backend.autonomous_audio == TRUE )
+    backend.audio_volume_get( l_p , r_p );
+  else
+    amidiplug_ip.output->get_volume( l_p , r_p );
+  return;
+}
+
+
+static void amidiplug_set_volume( gint  l , gint  r )
+{
+  if ( backend.autonomous_audio == TRUE )
+    backend.audio_volume_set( l , r );
+  else
+    amidiplug_ip.output->set_volume( l , r );
+  return;
+}
+
+
+static void amidiplug_get_song_info( gchar * filename , gchar ** title , gint * length )
+{
+  /* song title, get it from the filename */
+  *title = G_PATH_GET_BASENAME(filename);
+
+  /* sure, it's possible to calculate the length of a MIDI file anytime,
+     but the file must be entirely parsed to calculate it; this could
+     lead to a bit of performance loss, so let the user decide here */
+  if ( amidiplug_cfg_ap.ap_opts_length_precalc )
+  {
+    /* let's calculate the midi length, using this nice helper function that
+       will return 0 if a problem occurs and the length can't be calculated */
+    midifile_t mf;
+
+    if ( i_midi_parse_from_filename( filename , &mf ) )
+      *length = (gint)(mf.length / 1000);
+    else
+      *length = -1;
+
+    i_midi_free( &mf );
+  }
+  else  
+    *length = -1;
+
+  return;
+}
+
+
+static void amidiplug_play( gchar * filename )
+{
+  gint port_count = 0;
+  gint au_samplerate = -1, au_bitdepth = -1, au_channels = -1;
+
+  if ( backend.gmodule == NULL )
+  {
+    g_warning( "No sequencer backend selected\n" );
+    i_message_gui( _("AMIDI-Plug - warning") ,
+                   _("No sequencer backend has been selected!\nPlease configure AMIDI-Plug before playing.") ,
+                   AMIDIPLUG_MESSAGE_WARN , NULL , TRUE );
+    amidiplug_playing_status = AMIDIPLUG_ERR;
+    return;
+  }
+
+  /* get information about audio from backend, if available */
+  backend.audio_info_get( &au_channels , &au_bitdepth , &au_samplerate );
+  DEBUGMSG( "PLAY requested, audio details: channels -> %i , bitdepth -> %i , samplerate -> %i\n" ,
+            au_channels , au_bitdepth , au_samplerate );
+
+  if ( backend.autonomous_audio == FALSE )
+  {
+    DEBUGMSG( "PLAY requested, opening audio output plugin\n" );
+    amidiplug_ip.output->open_audio( FMT_S16_NE , au_samplerate , au_channels );
+  }
+
+  DEBUGMSG( "PLAY requested, midifile init\n" );
+  /* midifile init */
+  i_midi_init( &midifile );
+
+  /* get the number of selected ports */
+  port_count = backend.seq_get_port_count();
+  if ( port_count < 1 )
+  {
+    g_warning( "No ports selected\n" );
+    amidiplug_playing_status = AMIDIPLUG_ERR;
+    return;
+  }
+
+  DEBUGMSG( "PLAY requested, opening file: %s\n" , filename );
+  midifile.file_pointer = vfs_fopen( filename , "rb" );
+  if (!midifile.file_pointer)
+  {
+    g_warning( "Cannot open %s\n" , filename );
+    amidiplug_playing_status = AMIDIPLUG_ERR;
+    return;
+  }
+  midifile.file_name = filename;
+
+  switch( i_midi_file_read_id( &midifile ) )
+  {
+    case MAKE_ID('R', 'I', 'F', 'F'):
+    {
+      DEBUGMSG( "PLAY requested, RIFF chunk found, processing...\n" );
+      /* read riff chunk */
+      if ( !i_midi_file_parse_riff( &midifile ) )
+        WARNANDBREAKANDPLAYERR( "%s: invalid file format (riff parser)\n" , filename );
+
+      /* if that was read correctly, go ahead and read smf data */
+    }
+
+    case MAKE_ID('M', 'T', 'h', 'd'):
+    {
+      DEBUGMSG( "PLAY requested, MThd chunk found, processing...\n" );
+      if ( !i_midi_file_parse_smf( &midifile , port_count ) )
+        WARNANDBREAKANDPLAYERR( "%s: invalid file format (smf parser)\n" , filename );
+
+      if ( midifile.time_division < 1 )
+        WARNANDBREAKANDPLAYERR( "%s: invalid time division (%i)\n" , filename , midifile.time_division );
+
+      DEBUGMSG( "PLAY requested, setting ppq and tempo...\n" );
+      /* fill midifile.ppq and midifile.tempo using time_division */
+      if ( !i_midi_setget_tempo( &midifile ) )
+        WARNANDBREAKANDPLAYERR( "%s: invalid values while setting ppq and tempo\n" , filename );
+
+      DEBUGMSG( "PLAY requested, sequencer start\n" );
+      /* sequencer start */
+      if ( !backend.seq_start( filename ) )
+        WARNANDBREAKANDPLAYERR( "%s: problem with seq_start, play aborted\n" , filename );
+
+      DEBUGMSG( "PLAY requested, sequencer on\n" );
+      /* sequencer on */
+      if ( !backend.seq_on() )
+        WARNANDBREAKANDPLAYERR( "%s: problem with seq_on, play aborted\n" , filename );
+
+      DEBUGMSG( "PLAY requested, setting sequencer queue tempo...\n" );
+      /* set sequencer queue tempo using ppq and tempo (call only after i_midi_setget_tempo) */
+      if ( !backend.seq_queue_tempo( midifile.current_tempo , midifile.ppq ) )
+      {
+        backend.seq_off(); /* kill the sequencer */
+        WARNANDBREAKANDPLAYERR( "%s: ALSA queue problem, play aborted\n" , filename );
+      }
+
+      /* fill midifile.length, keeping in count tempo-changes */
+      i_midi_setget_length( &midifile );
+      DEBUGMSG( "PLAY requested, song length calculated: %i msec\n" , (gint)(midifile.length / 1000) );
+
+      /* our length is in microseconds, but the player wants milliseconds */
+      amidiplug_ip.set_info( G_PATH_GET_BASENAME(filename) ,
+                             (gint)(midifile.length / 1000) ,
+                             au_bitdepth * au_samplerate * au_channels / 8 ,
+                             au_samplerate , au_channels );
+
+      /* play play play! */
+      DEBUGMSG( "PLAY requested, starting play thread\n" );
+      amidiplug_playing_status = AMIDIPLUG_PLAY;
+      pthread_create(&amidiplug_play_thread, NULL, amidiplug_play_loop, NULL);
+      break;
+    }
+
+    default:
+    {
+      amidiplug_playing_status = AMIDIPLUG_ERR;
+      g_warning( "%s is not a Standard MIDI File\n" , filename );
+      break;
+    }
+  }
+
+  vfs_fclose( midifile.file_pointer );
+  return;
+}
+
+
+
+void * amidiplug_play_loop( void * arg )
+{
+  gint i = 0;
+  gboolean rewind = FALSE;
+
+  pthread_mutex_lock( &amidiplug_playing_mutex );
+  if ( amidiplug_playing_status != AMIDIPLUG_PAUSE )
+  {
+    DEBUGMSG( "PLAY thread, rewind tracks to their first event\n" );
+    rewind = TRUE;
+  }
+  else
+  {
+    DEBUGMSG( "PLAY thread, do not rewind tracks to their first event (coming from a PAUSE status)\n" );
+    amidiplug_playing_status = AMIDIPLUG_PLAY;
+    pthread_cond_signal( &amidiplug_playing_cond );
+  }
+  pthread_mutex_unlock( &amidiplug_playing_mutex );
+
+  if ( rewind )
+  {
+    /* initialize current position in each track */
+    for (i = 0; i < midifile.num_tracks; ++i)
+      midifile.tracks[i].current_event = midifile.tracks[i].first_event;
+    backend.seq_queue_start();
+  }
+
+  if ( backend.autonomous_audio == FALSE )
+  {
+    pthread_create(&amidiplug_audio_thread, NULL, amidiplug_audio_loop, NULL);
+  }
+
+  /* common settings for all our events */
+  backend.seq_event_init();
+
+  DEBUGMSG( "PLAY thread, start the play loop\n" );
+  for (;;)
+  {
+    midievent_t * event = NULL;
+    midifile_track_t * event_track = NULL;
+    gint i, min_tick = midifile.max_tick + 1;
+
+    /* search next event */
+    for (i = 0; i < midifile.num_tracks; ++i)
+    {
+      midifile_track_t * track = &midifile.tracks[i];
+      midievent_t * e2 = track->current_event;
+      if (( e2 ) && ( e2->tick < min_tick ))
+      {
+        min_tick = e2->tick;
+        event = e2;
+        event_track = track;
+      }
+    }
+
+    /* check if the song has been stopped */
+    pthread_mutex_lock( &amidiplug_playing_mutex );
+    if ( amidiplug_playing_status != AMIDIPLUG_PLAY )
+    {
+      DEBUGMSG( "PLAY thread, PAUSE or STOP requested, exiting from play loop\n" );
+      event = NULL;
+    }
+    pthread_mutex_unlock( &amidiplug_playing_mutex );
+
+    if (!event)
+      break; /* end of song reached */
+
+    /* advance pointer to next event */
+    event_track->current_event = event->next;
+    /* consider the midifile.skip_offset */
+    event->tick_real = event->tick - midifile.skip_offset;
+
+
+    switch (event->type)
+    {
+      case SND_SEQ_EVENT_NOTEON:
+        backend.seq_event_noteon( event );
+        break;
+      case SND_SEQ_EVENT_NOTEOFF:
+        backend.seq_event_noteoff( event );
+        break;
+      case SND_SEQ_EVENT_KEYPRESS:
+        backend.seq_event_keypress( event );
+        break;
+      case SND_SEQ_EVENT_CONTROLLER:
+        backend.seq_event_controller( event );
+        break;
+      case SND_SEQ_EVENT_PGMCHANGE:
+        backend.seq_event_pgmchange( event );
+        break;
+      case SND_SEQ_EVENT_CHANPRESS:
+        backend.seq_event_chanpress( event );
+        break;
+      case SND_SEQ_EVENT_PITCHBEND:
+        backend.seq_event_pitchbend( event );
+        break;
+      case SND_SEQ_EVENT_SYSEX:
+        backend.seq_event_sysex( event );
+        break;
+      case SND_SEQ_EVENT_TEMPO:
+        backend.seq_event_tempo( event );
+        DEBUGMSG( "PLAY thread, processing tempo event with value %i on tick %i\n" ,
+                  event->data.tempo , event->tick );
+        pthread_mutex_lock(&amidiplug_gettime_mutex);
+        midifile.current_tempo = event->data.tempo;
+        pthread_mutex_unlock(&amidiplug_gettime_mutex);
+        break;
+      case SND_SEQ_EVENT_META_TEXT:
+        /* do nothing */
+        break;
+      case SND_SEQ_EVENT_META_LYRIC:
+        /* do nothing */
+        break;
+      default:
+        DEBUGMSG( "PLAY thread, encountered invalid event type %i\n" , event->type );
+        break;
+    }
+
+    pthread_mutex_lock(&amidiplug_gettime_mutex);
+    midifile.playing_tick = event->tick;
+    pthread_mutex_unlock(&amidiplug_gettime_mutex);
+
+    if ( backend.autonomous_audio == TRUE )
+    {
+      /* these backends deal with audio production themselves (i.e. ALSA) */
+      backend.seq_output( NULL , NULL );
+    }
+  }
+
+  backend.seq_output_shut( midifile.max_tick , midifile.skip_offset );
+
+  pthread_mutex_lock( &amidiplug_playing_mutex );
+  if ( amidiplug_playing_status != AMIDIPLUG_PAUSE )
+  {
+    amidiplug_playing_status = AMIDIPLUG_STOP;
+    DEBUGMSG( "PLAY thread, song stopped/ended\n" );
+  }
+  pthread_mutex_unlock( &amidiplug_playing_mutex );
+
+  pthread_exit(NULL);
+}
+
+
+/* amidigplug_skipto: re-do all events that influence the playing of our
+   midi file; re-do them using a time-tick of 0, so they are processed
+   istantaneously and proceed this way until the playing_tick is reached;
+   also obtain the correct skip_offset from the playing_tick */
+void amidiplug_skipto( gint playing_tick )
+{
+  gint i;
+
+  /* this check is always made, for safety*/
+  if ( playing_tick >= midifile.max_tick )
+    playing_tick = midifile.max_tick - 1;
+
+  /* initialize current position in each track */
+  for (i = 0; i < midifile.num_tracks; ++i)
+    midifile.tracks[i].current_event = midifile.tracks[i].first_event;
+
+  /* common settings for all our events */
+  backend.seq_event_init();
+  backend.seq_queue_start();
+
+  DEBUGMSG( "SKIPTO request, starting skipto loop\n" );
+  for (;;)
+  {
+    midievent_t * event = NULL;
+    midifile_track_t * event_track = NULL;
+    gint i, min_tick = midifile.max_tick + 1;
+
+    /* search next event */
+    for (i = 0; i < midifile.num_tracks; ++i)
+    {
+      midifile_track_t * track = &midifile.tracks[i];
+      midievent_t * e2 = track->current_event;
+      if (( e2 ) && ( e2->tick < min_tick ))
+      {
+        min_tick = e2->tick;
+        event = e2;
+        event_track = track;
+      }
+    }
+
+    /* unlikely here... unless very strange MIDI files are played :) */
+    if (!event)
+    {
+      DEBUGMSG( "SKIPTO request, reached the last event but not the requested tick (!)\n" );
+      break; /* end of song reached */
+    }
+
+    /* reached the requested tick, job done */
+    if ( event->tick >= playing_tick )
+    {
+      DEBUGMSG( "SKIPTO request, reached the requested tick, exiting from skipto loop\n" );
+      break;
+    }
+
+    /* advance pointer to next event */
+    event_track->current_event = event->next;
+    /* set the time tick to 0 */
+    event->tick_real = 0;
+
+    switch (event->type)
+    {
+      /* do nothing for these
+      case SND_SEQ_EVENT_NOTEON:
+      case SND_SEQ_EVENT_NOTEOFF:
+      case SND_SEQ_EVENT_KEYPRESS:
+      {
+        break;
+      } */
+      case SND_SEQ_EVENT_CONTROLLER:
+        backend.seq_event_controller( event );
+        break;
+      case SND_SEQ_EVENT_PGMCHANGE:
+        backend.seq_event_pgmchange( event );
+        break;
+      case SND_SEQ_EVENT_CHANPRESS:
+        backend.seq_event_chanpress( event );
+        break;
+      case SND_SEQ_EVENT_PITCHBEND:
+        backend.seq_event_pitchbend( event );
+        break;
+      case SND_SEQ_EVENT_SYSEX:
+        backend.seq_event_sysex( event );
+        break;
+      case SND_SEQ_EVENT_TEMPO:
+        backend.seq_event_tempo( event );
+        pthread_mutex_lock(&amidiplug_gettime_mutex);
+        midifile.current_tempo = event->data.tempo;
+        pthread_mutex_unlock(&amidiplug_gettime_mutex);
+        break;
+    }
+
+    if ( backend.autonomous_audio == TRUE )
+    {
+      /* these backends deal with audio production themselves (i.e. ALSA) */
+      backend.seq_output( NULL , NULL );
+    }
+  }
+
+  midifile.skip_offset = playing_tick;
+
+  return;
+}
+
+
+void * amidiplug_audio_loop( void * arg )
+{
+  gboolean going = 1;
+  gpointer buffer = NULL;
+  gint buffer_size = 0;
+  while ( going )
+  {
+    if ( backend.seq_output( &buffer , &buffer_size ) )
+    {
+      while( ( amidiplug_ip.output->buffer_free() < buffer_size ) && ( going == TRUE ) )
+        G_USLEEP(10000);
+      produce_audio( amidiplug_ip.output->written_time() ,
+                     FMT_S16_NE , 2 , buffer_size , buffer , &going );
+    }
+    pthread_mutex_lock( &amidiplug_playing_mutex );
+    if ( amidiplug_playing_status != AMIDIPLUG_PLAY )
+      going = FALSE;
+    pthread_mutex_unlock( &amidiplug_playing_mutex );
+  }
+  if ( buffer != NULL )
+    g_free( buffer );
+  pthread_exit(NULL);
+}