Upto: Table of Contents of full book "Programming and Using Linux Sound"

Timidity

Timidity is designed as a standalone application. To add to this, you should build a new "interface". It can also be subverted to act as though it were a library that can be called. This chapter explains both ways.

Files

Source files in this chapter are here .

Timidity design

Timidty is designed as a standalone application. When built, you get a single executable but do not get a library of functions that can be called, unlike FluidSynth for example.

What you can do with Timidity is to add different interfaces. For example, there are ncurses, XaW and dumb interfaces which can be invoked at runtime by e.g.

	
timidity -in ...
timidity -ia ...
timidity -id ...
	
      

There are also others with more specialised uses such as WRD, emacs, ALSA and remote interfaces.

For example, the Xaw interface looks like

The idea seems to be that if you want something extra, perhaps you should build a custom interface and drive it from Timidity.

That doesn't always suit me, as I would prefer to be able to embed Timidity into my own applications in a simple way. The rest of this chapter looks at both ways:

Making TiMidity into a library

TiMidity is not designed as a library and you have to convince it otherwise. That isn't hard, by messing around with the build system.

Managed environments hook

A system whereby the application is in control doesn't work so well in a managed environment such as Windows (or probably the many more recent ones such as Android). In such environments you can't call Timidity's main, but rather the main function belonging to the framework. This in turn will call an appropriate function in the application.

To make use of such hooks, you need to download the source code of Timidity, either from a package manager or from the TiMidity++ web site.

For Timidity, the variations on the main function are in the file timidity/timidity.c. Controlled by various define's, you can have main or win_main. One of the more interesting defines is ANOTHER_MAIN. If this is defined, none of the versions of the main function are compiled and you end with main-free object modules.

If you build Timidity from the toplevel source directory in the following way, it will produce an error that the main function is not defined:

	
CFLAGS="-DANOTHER_MAIN" ./configure
make
	
      

That's the hook you need to take Timidity from being a standalone application to being able to be called as a library from another application. Note that you cannot just remove timidity/timidity.c from the build - that file contains too many other critical functions!

Building the library

To build Timidity as a static library, remove the main function as above and attempt to build timidity. I found I needed to also specify which output system I wanted to use, such as ALSA:

	
CFLAGS="-DANOTHER_MAIN" ./configure --enable-audio=alsa
nake clean
make
	
      

This builds several .ar files and lots of object .o modules, but fails to build the final timidity executable as (of course) there is no main function. It also leaves a bunch of unlinked files in the timidity subdirectory.

You can collect all of the object modules into an archive file by running this from the top of the TiMidity source directory:

	
ar cru  libtimidity.a */*.o
ranlib libtimidity.a
	
      

Since you will have to build Timidity from the source, check that it is working in normal mode before you try to build this alternative library version. That way you can find out that you need, say, the libasound-dev library in order to use ALSA, before you get mixed up in this other stuff!

Library entry points

Timidity built with ANOTHER_MAIN exposes these public entry points

	
void timidity_start_initialize(void);
int timidity_pre_load_configuration(void);
int timidity_post_load_configuration(void);
void timidity_init_player(void);
int timidity_play_main(int nfiles, char **files);
int got_a_configuration;
	
      

They do not seem to be defined in any convenient header file.

A minimal application

The real Timidity application is coded to work on many different operating systems with many different versions of libraries. Most of those dependencies are taken care of in building the object files and library as above.

A minimal application just wraps our own main around the library entry points in my_main.c:


#include <stdio.h>

extern void timidity_start_initialize(void);
extern int timidity_pre_load_configuration(void);
extern int timidity_post_load_configuration(void);
extern void timidity_init_player(void);
extern int timidity_play_main(int nfiles, char **files);
extern int got_a_configuration;

int main(int argc, char **argv)
{
    int err, main_ret;

    timidity_start_initialize();




    if ((err = timidity_pre_load_configuration()) != 0)
	return err;

    err += timidity_post_load_configuration();

    if (err) {
	printf("couldn't load configuration file\n");
	exit(1);
    }

    timidity_init_player();

    main_ret = timidity_play_main(argc, argv);

    return main_ret;
}


      

The compile command needs to bring in the Timidity library and any other required library, and is for an ALSA application

	
my_timidity: my_main.o
        gcc -g -o my_timidity my_main.o libtimidity.a  -lasound -lm
	
      

Playing a background video to a MIDI file

As a more complex example, we look at playing a video file while also playing a MIDI file. We assume that the video file has no audio component and there is no attempt to perform any synchronisation between the two streams - that is an extra order of complexity!

To play a video file we make use of the FFMpeg library to decode a video stream into video frames. We then need to display the frames in some kind of GUI object, and there are very, very many toolkits for doing this :-(. I've chosen the GTK toolkit as it underlies Gnome, is in C, supports many other things such as i18n and so on. I've based my code on An ffmpeg and SDL Tutorial by Stephen Dranger which uses the SDL toolkit for display.

This runs the video and the MIDI in separate threads using the pthreads package. I've cheated a bit by hard-coding the names of the files and fixing the size of the video frames. It was a real b*mmer getting it to work under GTK3.0 as that has removed pixmaps and it took too, too long to find out what was going on.

I've split the code into two files, one to play the video using Gtk and the other to play the TiMidity library and invoke the video. The video playing file is video_code.c:



#include <gtk/gtk.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#define MIDI_FILE "54154.mid"
#define VIDEO_FILE "short.mpg"

GtkWidget *image;
GdkPixbuf *pixbuf;

#if GTK_MAJOR_VERSION == 2
GdkPixmap *dbuf_pixmap;
#endif

/* FFMpeg vbls */
AVFormatContext *pFormatCtx = NULL;
AVCodecContext *pCodecCtx = NULL;
int videoStream;
struct SwsContext *sws_ctx = NULL;
AVCodec *pCodec = NULL;

static void pixmap_destroy_notify(guchar *pixels,
				  gpointer data) {
    printf("Ddestroy pixmap\n");
}

#if   GTK_MAJOR_VERSION == 3
static gboolean draw_image(gpointer user_data) {
    gtk_image_set_from_pixbuf((GtkImage *) image, pixbuf);
    gtk_widget_queue_draw(image);

    return G_SOURCE_REMOVE;
}
#endif

static void *play_background(void *args) {
    /* based on code from
       http://www.cs.dartmouth.edu/~xy/cs23/gtk.html
       http://cdry.wordpress.com/2009/09/09/using-custom-io-callbacks-with-ffmpeg/
    */

    int i;
    AVPacket packet;
    int frameFinished;
    AVFrame *pFrame = NULL;

    int oldSize;
    char *oldData;
    int bytesDecoded;
 
    AVFrame *picture_RGB;
    char *buffer;

#if GTK_MAJOR_VERSION == 2
    GdkPixmap *pixmap;
#endif

    pFrame=av_frame_alloc();

    i=0;
    picture_RGB = av_frame_alloc();
    buffer = malloc (avpicture_get_size(PIX_FMT_RGB24, 720, 576));
    avpicture_fill((AVPicture *)picture_RGB, buffer, PIX_FMT_RGB24, 720, 576);

    while(av_read_frame(pFormatCtx, &packet)>=0) {
	if(packet.stream_index==videoStream) {
	    printf("Frame %d\n", i++);
	    usleep(33670);  // 29.7 frames per second
	    // Decode video frame
	    avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished,   &packet);
	    
	    sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, 
				     pCodecCtx->pix_fmt, pCodecCtx->width, 
				     pCodecCtx->height, PIX_FMT_RGB24, 
				     SWS_BICUBIC, NULL, NULL, NULL);

	    if (frameFinished) {

		sws_scale(sws_ctx,  (uint8_t const * const *) pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picture_RGB->data, picture_RGB->linesize);

		pixbuf = gdk_pixbuf_new_from_data(picture_RGB->data[0], GDK_COLORSPACE_RGB, 0, 8, 720, 480, picture_RGB->linesize[0], pixmap_destroy_notify, NULL);

		
		/* start GTK thread lock for drawing.
		   deprecated under GTK 3.0 but no clear idea
		   of what to do :-(
		*/

#if GTK_MAJOR_VERSION == 3
		gdk_threads_add_idle(draw_image, NULL);
		//gtk_image_set_from_pixbuf((GtkImage*) image, pixbuf);
#elif GTK_MAJOR_VERSION == 2
		gdk_threads_enter();
 		gdk_draw_pixbuf((GdkDrawable *) dbuf_pixmap, NULL,
				pixbuf, 
				0, 0, 0, 0, 720, 480,
				GDK_RGB_DITHER_NORMAL, 0, 0);
		gtk_image_set_from_pixmap((GtkImage*) image, dbuf_pixmap, NULL);

		gtk_widget_queue_draw(image);

		/* release GTK thread lock */
		gdk_threads_leave();
#endif
	    }
	}
	av_free_packet(&packet);
    }
    printf("Video over!\n");
    exit(0);
}

/* Called when the windows are realized
 */
static void realize_cb (GtkWidget *widget, gpointer data) {
    /* start the video playing in its own thread */
    pthread_t tid_video;
    pthread_create(&tid_video, NULL, play_background, NULL);
}

static gboolean delete_event( GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer   data )
{
    /* If you return FALSE in the "delete-event" signal handler,
     * GTK will emit the "destroy" signal. Returning TRUE means
     * you don't want the window to be destroyed.
     * This is useful for popping up 'are you sure you want to quit?'
     * type dialogs. */

    g_print ("delete event occurred\n");

    /* Change TRUE to FALSE and the main window will be destroyed with
     * a "delete-event". */
    return FALSE;
}

/* Another callback */
static void destroy( GtkWidget *widget,
                     gpointer   data )
{
    gtk_main_quit ();
}

void init_ffmpeg() {
    /* FFMpeg stuff */

    AVFrame *pFrame = NULL;
    AVPacket packet;

    AVDictionary *optionsDict = NULL;


    av_register_all();

    if(avformat_open_input(&pFormatCtx, VIDEO_FILE, NULL, NULL)!=0)
	return; // Couldn't open file
  
    // Retrieve stream information
    if(avformat_find_stream_info(pFormatCtx, NULL)<0)
	return; // Couldn't find stream information
  
    // Dump information about file onto standard error
    av_dump_format(pFormatCtx, 0, VIDEO_FILE, 0);
  
    // Find the first video stream
    videoStream=-1;
    int i;
    for(i = 0; i < pFormatCtx->nb_streams; i++)
	if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
	    videoStream=i;
	    break;
	}
    if(videoStream==-1)
	return; // Didn't find a video stream

    for(i=0; i<pFormatCtx->nb_streams; i++)
	if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO) {
	    printf("Found an audio stream too\n");
	    break;
	}

  
    // Get a pointer to the codec context for the video stream
    pCodecCtx=pFormatCtx->streams[videoStream]->codec;
  
    // Find the decoder for the video stream
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
    if(pCodec==NULL) {
	fprintf(stderr, "Unsupported codec!\n");
	return; // Codec not found
    }
  
    // Open codec
    if(avcodec_open2(pCodecCtx, pCodec, &optionsDict)<0)
	return; // Could not open codec
}

void *play_gtk(void *args);

void init_gtk(int argc, char **argv) {
    /* GTK stuff now */

    printf(" GTK_MAJOR_VERSION is %d\n",  GTK_MAJOR_VERSION);

   /* GtkWidget is the storage type for widgets */
    GtkWidget *window;
    GtkWidget *box;
    
    /* This is called in all GTK applications. Arguments are parsed
     * from the command line and are returned to the application. */
    printf("Initing gtk\n");
    if ( !gtk_init_check (0, NULL)) {
	printf("Gtk init failed\n");
    } else {
	printf("Inited Gtk ok\n");
    }

    /* create a new window */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    
    /* When the window is given the "delete-event" signal (this is given
     * by the window manager, usually by the "close" option, or on the
     * titlebar), we ask it to call the delete_event () function
     * as defined above. The data passed to the callback
     * function is NULL and is ignored in the callback function. */
    g_signal_connect (window, "delete-event",
		      G_CALLBACK (delete_event), NULL);
    
    /* Here we connect the "destroy" event to a signal handler.  
     * This event occurs when we call gtk_widget_destroy() on the window,
     * or if we return FALSE in the "delete-event" callback. */
    g_signal_connect (window, "destroy",
		      G_CALLBACK (destroy), NULL);

    g_signal_connect (window, "realize", G_CALLBACK (realize_cb), NULL);
    
    /* Sets the border width of the window. */
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);

#if GTK_MAJOR_VERSION == 3
    box = gtk_box_new(TRUE, 1);
#else
    box = gtk_vbox_new(TRUE, 1);
#endif

    gtk_widget_show(box);

    image = gtk_image_new();

    gtk_widget_show (image);
    
    // gtk_box_pack_start (GTK_BOX (box), lyric_labels[0], TRUE, TRUE, 0);

    gtk_box_pack_start (GTK_BOX (box), image, TRUE, TRUE, 0);
   
    /* This packs the button into the window (a gtk container). */
    gtk_container_add (GTK_CONTAINER (window), box);
        
    /* and the window */
    gtk_widget_show (window);
    
    /* All GTK applications must have a gtk_main(). Control ends here
     * and waits for an event to occur (like a key press or
     * mouse event). */

    /* start Gtk in its own thread */
    pthread_t tid_gtk;
    pthread_create(&tid_gtk, NULL, play_gtk, NULL);
    // play_gtk();
    //gtk_main ();
    printf("Finished initing gtk\n");
}


void *play_gtk(void *args) {
    printf("About to start gtk_main\n");
    gtk_main();
}

      

The file video_player.c is

#include <string.h>



#define MIDI_FILE "54154.mid"

static void *play_midi(void *args) {
    char *argv[1];
    argv[0] = MIDI_FILE;
    int argc = 1;

    timidity_play_main(argc, argv);

    printf("Audio finished\n");
    exit(0);
}


int main(int argc, char** argv)
{

    int i;

    /* make X happy */
    XInitThreads();

    /* Timidity stuff */
    int err;

    timidity_start_initialize();
    if ((err = timidity_pre_load_configuration()) == 0) {
	err = timidity_post_load_configuration();
    }
    if (err) {
        printf("couldn't load configuration file\n");
        exit(1);
    }

    timidity_init_player();

    init_ffmpeg();
    init_gtk(argc, argv);

    play_midi(NULL);    
    return 0;
}

      

Building a new interface

Shared objects

You can build your own interfaces and add them to TiMidity without changing or recompiling TiMidity. Such interfaces are built as dynamically loadable shared libraries, and are loaded when TiMidity starts.

You have to be a little careful with compile and link flags to build these libraries (see Building shared objects in Linux ). To build the shared object if_my_interface.so from my_interface.c I use

	
gcc  -fPIC $(CFLAGS) -c -o my_interface.o my_interface.c
gcc -shared -o if_my_interface.so my_interface.o
	
      

TiMidity will only load files that begin with if_. They can reside in any directory, with the default being something like /usr/lib/timidity or /usr/local/lib/timidity (see the "Supported dynamic load interfaces" directory from timidity -h).

The defaulty directory to load dynamic modules can be overridden by the option -d as in

	
timidity -d. -ik --trace 54154.mid
	
      

Entry point

Each interface must have a unique function that can be called by the dynamic loader. Recall that interfaces are selected by the command line option -i, such as timidity -iT ... to choose the VT100 interface. Your interface must have a single ASCII letter identifier which isn't used by any other interface, say m for "my interface". The loader will then look for a function

	
ControlMode *interface_m_loader(void)
	
      

where the "m" in the function name is the identifier. This function is simple: it just returns the address of a structure of type ControlMode which is defined elsewhere in the interface's code:

	
ControlMode *interface_m_loader(void)
{
    return &ctl;
}
	
      

ControlMode

The ControlMode structure is

	
typedef struct {
  char *id_name, id_character;
  char *id_short_name;
  int verbosity, trace_playing, opened;

  int32 flags;

  int  (*open)(int using_stdin, int using_stdout);
  void (*close)(void);
  int (*pass_playing_list)(int number_of_files, char *list_of_files[]);
  int  (*read)(int32 *valp);
  int  (*write)(char *buf, int32 size);
  int  (*cmsg)(int type, int verbosity_level, char *fmt, ...);
  void (*event)(CtlEvent *ev);  /* Control events */
} ControlMode;
	
      

which defines information about the interface and a set of functions which are called by TiMidity in response to events and actions within TiMidity. For example, for "my interface" this structure is

	
ControlMode ctl=
    {
	"my interface", 'm',
	"my iface",
	1,          /* verbosity */
	0,          /* trace playing */
	0,          /* opened */
	0,          /* flags */
	ctl_open,
	ctl_close,
	pass_playing_list,
	ctl_read,
	NULL,       /* write */
	cmsg,
	ctl_event
    };
	
      

Some of these fields are obvious, some are less so:

open
This is called to set which files are used for I/O...
close
... and to close them
pass_playing_list
This function is passed a list of files to play. The most likely action is to walk through this list, calling play_midi_file on each
read
Not sure yet
write
Not sure yet
cmsg
Called with information messages
event
This is the major function for handling MIDI control events. Typically it will be a big switch for each type of control event

Include files

This is messy. A typical interface will need to know some of the constants and functions used by TiMidity. While these are organised logically for TiMidity, they are not organised conveniently for a new interface. So you have to keep pulling in extra includes, which point to other externals, which require more includes, etc. These may be in different directories such timidity and utils so you have to point to many different include directories.

Note that you will need the TiMidity source code to get these include files, downloaded from SourceForge TiMidity++ .

My simple interface

This basically does the same as the "dumb" interface built into TiMidity. It is loaded from the current directory and invoked by

	
timidity -im -d. 54154.mid
	
      

The code is in one file, my_interface.c:

/*
  my_interface.c
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#ifndef NO_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include "support.h"
#include "timidity.h"
#include "output.h"
#include "controls.h"
#include "instrum.h"
#include "playmidi.h"
#include "readmidi.h"

static int ctl_open(int using_stdin, int using_stdout);
static void ctl_close(void);
static int ctl_read(int32 *valp);
static int cmsg(int type, int verbosity_level, char *fmt, ...);
static void ctl_total_time(long tt);
static void ctl_file_name(char *name);
static void ctl_current_time(int ct);
static void ctl_lyric(int lyricid);
static void ctl_event(CtlEvent *e);
static int pass_playing_list(int number_of_files, char *list_of_files[]);


/**********************************/
/* export the interface functions */

#define ctl karaoke_control_mode

ControlMode ctl=
    {
	"my interface", 'm',
	"my iface",
	1,          /* verbosity */
	0,          /* trace playing */
	0,          /* opened */
	0,          /* flags */
	ctl_open,
	ctl_close,
	pass_playing_list,
	ctl_read,
	NULL,       /* write */
	cmsg,
	ctl_event
    };

static FILE *outfp;
int karaoke_error_count;
static char *current_file;
struct midi_file_info *current_file_info;

static int pass_playing_list(int number_of_files, char *list_of_files[]) {
    int n;

    for (n = 0; n < number_of_files; n++) {
	printf("Playing list %s\n", list_of_files[n]);
	
	current_file = list_of_files[n];
	/*
	  current_file_info = get_midi_file_info(current_file, 1);
	  if (current_file_info != NULL) {
	  printf("file info not NULL\n");
	  } else {
	  printf("File info is NULL\n");
	  }
	*/
	play_midi_file( list_of_files[n]);
    }
    return 0;
}

/*ARGSUSED*/
static int ctl_open(int using_stdin, int using_stdout)
{
    if(using_stdout)
	outfp=stderr;
    else
	outfp=stdout;
    ctl.opened=1;
    return 0;

    /*
    // dont know what this function does
    if (current_file != NULL) {
	current_file_info = get_midi_file_info(current_file, 1);
	printf("Opening info for %s\n", current_file);
    } else {
	printf("Current is NULL\n");
    }
    return 0;
    */
}

static void ctl_close(void)
{
    fflush(outfp);
    ctl.opened=0;
}

/*ARGSUSED*/
static int ctl_read(int32 *valp)
{
    return RC_NONE;
}

static int cmsg(int type, int verbosity_level, char *fmt, ...)
{
    va_list ap;

    if ((type==CMSG_TEXT || type==CMSG_INFO || type==CMSG_WARNING) &&
	ctl.verbosity<verbosity_level)
	return 0;
    va_start(ap, fmt);
    if(type == CMSG_WARNING || type == CMSG_ERROR || type == CMSG_FATAL)
	karaoke_error_count++;
    if (!ctl.opened)
	{
	    vfprintf(stderr, fmt, ap);
	    fputs(NLS, stderr);
	}
    else
	{
	    vfprintf(outfp, fmt, ap);
	    fputs(NLS, outfp);
	    fflush(outfp);
	}
    va_end(ap);
    return 0;
}

static void ctl_total_time(long tt)
{
    int mins, secs;
    if (ctl.trace_playing)
	{
	    secs=(int)(tt/play_mode->rate);
	    mins=secs/60;
	    secs-=mins*60;
	    cmsg(CMSG_INFO, VERB_NORMAL,
		 "Total playing time: %3d min %02d s", mins, secs);
	}
}

static void ctl_file_name(char *name)
{
    current_file = name;

    if (ctl.verbosity>=0 || ctl.trace_playing)
	cmsg(CMSG_INFO, VERB_NORMAL, "Playing %s", name);
}

static void ctl_current_time(int secs)
{
    int mins;
    static int prev_secs = -1;

#ifdef __W32__
    if(wrdt->id == 'w')
	return;
#endif /* __W32__ */
    if (ctl.trace_playing && secs != prev_secs)
	{
	    prev_secs = secs;
	    mins=secs/60;
	    secs-=mins*60;
	    //fprintf(outfp, "\r%3d:%02d", mins, secs);
	    //fflush(outfp);
	}
}

static void ctl_lyric(int lyricid)
{
    char *lyric;

    current_file_info = get_midi_file_info(current_file, 1);

    lyric = event2string(lyricid);
    if(lyric != NULL)
	{
	    if(lyric[0] == ME_KARAOKE_LYRIC)
		{
		    if(lyric[1] == '/' || lyric[1] == '\\')
			{
			    fprintf(outfp, "\n%s", lyric + 2);
			    fflush(outfp);
			}
		    else if(lyric[1] == '@')
			{
			    if(lyric[2] == 'L')
				fprintf(outfp, "\nLanguage: %s\n", lyric + 3);
			    else if(lyric[2] == 'T')
				fprintf(outfp, "Title: %s\n", lyric + 3);
			    else
				fprintf(outfp, "%s\n", lyric + 1);
			}
		    else
			{
			    fputs(lyric + 1, outfp);
			    fflush(outfp);
			}
		}
	    else
		{
		    if(lyric[0] == ME_CHORUS_TEXT || lyric[0] == ME_INSERT_TEXT)
			fprintf(outfp, "\r");
		    fputs(lyric + 1, outfp);
		    fflush(outfp);
		}
	}
}


static void ctl_event(CtlEvent *e)
{
    switch(e->type)
	{
	case CTLE_NOW_LOADING:
	    ctl_file_name((char *)e->v1);
	    break;
	case CTLE_LOADING_DONE:
	    // MIDI file is loaded, about to play
	    current_file_info = get_midi_file_info(current_file, 1);
	    if (current_file_info != NULL) {
		printf("file info not NULL\n");
	    } else {
		printf("File info is NULL\n");
	    }
	    break;
	case CTLE_PLAY_START:

	    ctl_total_time(e->v1);
	    break;
	case CTLE_CURRENT_TIME:
	    ctl_current_time((int)e->v1);
	    break;
#ifndef CFG_FOR_SF
	case CTLE_LYRIC:
	    ctl_lyric((int)e->v1);
	    break;
#endif
	}
}

/*
 * interface_<id>_loader();
 */
ControlMode *interface_k_loader(void)
{
    return &ctl;
}



      

Running my simple interface

When I tried to run the interface using the standard package TiMidity v2.13.2-40.1 it crashed in a memory free call. The code is stripped, so tracking down why is not easy and I haven't bothered to do so yet - I'm not sure what libraries, versions of code, etc, the package distro was compiled against.

I had built my own copy of TiMidity from source. This worked fine. Note that when you build TiMidity from source, you will need to specify that it can load dynamic modules, for example by

	
congfigure --enable-audio=alsa --enable-vt100 --enable-debug --enable-dynamic
	
      

Playing a background video to a MIDI file

We can take the code from playing a video given earlier and put it as the "back end" of a TiMidity systems as a "video" interface. Essentially all that needs to be done is to change ctl_open from the simple interface to call the Gtk code to play the video, and change the identity of the interface.

The new "video" interface is video_player_interface.c

      /*
  video_player_interface.c
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#ifndef NO_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include "support.h"
#include "timidity.h"
#include "output.h"
#include "controls.h"
#include "instrum.h"
#include "playmidi.h"
#include "readmidi.h"

static int ctl_open(int using_stdin, int using_stdout);
static void ctl_close(void);
static int ctl_read(int32 *valp);
static int cmsg(int type, int verbosity_level, char *fmt, ...);
static void ctl_total_time(long tt);
static void ctl_file_name(char *name);
static void ctl_current_time(int ct);
static void ctl_lyric(int lyricid);
static void ctl_event(CtlEvent *e);
static int pass_playing_list(int number_of_files, char *list_of_files[]);


/**********************************/
/* export the interface functions */

#define ctl karaoke_control_mode

ControlMode ctl=
    {
	"video player interface", 'v',
	"video player",
	1,          /* verbosity */
	0,          /* trace playing */
	0,          /* opened */
	0,          /* flags */
	ctl_open,
	ctl_close,
	pass_playing_list,
	ctl_read,
	NULL,       /* write */
	cmsg,
	ctl_event
    };

static FILE *outfp;
int video_player_error_count;
static char *current_file;
struct midi_file_info *current_file_info;

static int pass_playing_list(int number_of_files, char *list_of_files[]) {
    int n;

    for (n = 0; n < number_of_files; n++) {
	printf("Playing list %s\n", list_of_files[n]);
	
	current_file = list_of_files[n];
	/*
	  current_file_info = get_midi_file_info(current_file, 1);
	  if (current_file_info != NULL) {
	  printf("file info not NULL\n");
	  } else {
	  printf("File info is NULL\n");
	  }
	*/
	play_midi_file( list_of_files[n]);
    }
    return 0;
}

extern void *play_gtk(void *args);

/*ARGSUSED*/
static int ctl_open(int using_stdin, int using_stdout)
{
    if(using_stdout)
	outfp=stderr;
    else
	outfp=stdout;
    ctl.opened=1;

    init_ffmpeg();

    /* start Gtk in its own thread */
    pthread_t tid_gtk;
    init_gtk(0, NULL);
    //pthread_create(&tid_gtk, NULL, play_gtk, NULL);

    return 0;
}

static void ctl_close(void)
{
    fflush(outfp);
    ctl.opened=0;
}

/*ARGSUSED*/
static int ctl_read(int32 *valp)
{
    return RC_NONE;
}

static int cmsg(int type, int verbosity_level, char *fmt, ...)
{
    va_list ap;

    if ((type==CMSG_TEXT || type==CMSG_INFO || type==CMSG_WARNING) &&
	ctl.verbosity<verbosity_level)
	return 0;
    va_start(ap, fmt);
    if(type == CMSG_WARNING || type == CMSG_ERROR || type == CMSG_FATAL)
	video_player_error_count++;
    if (!ctl.opened)
	{
	    vfprintf(stderr, fmt, ap);
	    fputs(NLS, stderr);
	}
    else
	{
	    vfprintf(outfp, fmt, ap);
	    fputs(NLS, outfp);
	    fflush(outfp);
	}
    va_end(ap);
    return 0;
}

static void ctl_total_time(long tt)
{
    int mins, secs;
    if (ctl.trace_playing)
	{
	    secs=(int)(tt/play_mode->rate);
	    mins=secs/60;
	    secs-=mins*60;
	    cmsg(CMSG_INFO, VERB_NORMAL,
		 "Total playing time: %3d min %02d s", mins, secs);
	}
}

static void ctl_file_name(char *name)
{
    current_file = name;

    if (ctl.verbosity>=0 || ctl.trace_playing)
	cmsg(CMSG_INFO, VERB_NORMAL, "Playing %s", name);
}

static void ctl_current_time(int secs)
{
    int mins;
    static int prev_secs = -1;

#ifdef __W32__
    if(wrdt->id == 'w')
	return;
#endif /* __W32__ */
    if (ctl.trace_playing && secs != prev_secs)
	{
	    prev_secs = secs;
	    mins=secs/60;
	    secs-=mins*60;
	    //fprintf(outfp, "\r%3d:%02d", mins, secs);
	    //fflush(outfp);
	}
}

static void ctl_lyric(int lyricid)
{
    char *lyric;

    current_file_info = get_midi_file_info(current_file, 1);

    lyric = event2string(lyricid);
    if(lyric != NULL)
	{
	    if(lyric[0] == ME_KARAOKE_LYRIC)
		{
		    if(lyric[1] == '/' || lyric[1] == '\\')
			{
			    fprintf(outfp, "\n%s", lyric + 2);
			    fflush(outfp);
			}
		    else if(lyric[1] == '@')
			{
			    if(lyric[2] == 'L')
				fprintf(outfp, "\nLanguage: %s\n", lyric + 3);
			    else if(lyric[2] == 'T')
				fprintf(outfp, "Title: %s\n", lyric + 3);
			    else
				fprintf(outfp, "%s\n", lyric + 1);
			}
		    else
			{
			    fputs(lyric + 1, outfp);
			    fflush(outfp);
			}
		}
	    else
		{
		    if(lyric[0] == ME_CHORUS_TEXT || lyric[0] == ME_INSERT_TEXT)
			fprintf(outfp, "\r");
		    fputs(lyric + 1, outfp);
		    fflush(outfp);
		}
	}
}


static void ctl_event(CtlEvent *e)
{
    switch(e->type)
	{
	case CTLE_NOW_LOADING:
	    ctl_file_name((char *)e->v1);
	    break;
	case CTLE_LOADING_DONE:
	    // MIDI file is loaded, about to play
	    current_file_info = get_midi_file_info(current_file, 1);
	    if (current_file_info != NULL) {
		printf("file info not NULL\n");
	    } else {
		printf("File info is NULL\n");
	    }
	    break;
	case CTLE_PLAY_START:

	    ctl_total_time(e->v1);
	    break;
	case CTLE_CURRENT_TIME:
	    ctl_current_time((int)e->v1);
	    break;
#ifndef CFG_FOR_SF
	case CTLE_LYRIC:
	    ctl_lyric((int)e->v1);
	    break;
#endif
	}
}

/*
 * interface_<id>_loader();
 */
ControlMode *interface_v_loader(void)
{
    return &ctl;
}


      

The build command is

	
video_code.o: video_code.c
        gcc  -fPIC $(CFLAGS) -c -o video_code.o video_code.c $(LIBS3)

if_video_player.so: video_player_interface.c video_code.o
        gcc  -fPIC $(CFLAGS) -c -o video_player_interface.o video_player_interface.c
        gcc -shared -o if_video_player.so video_player_interface.o video_code.o $(LIBS3)
	
      

You may hit a hiccup with running this Gtk-based interface: Gtk version mismatch :-(. The current builds of TiMidity either do not have the Gtk interface compiled in, or have Gtk version 2. If Gtk is not compiled in, you should have no problem. Otherwise, if you have compiled this interface with Gtk version 3, you will get runtime errors about type mismatches, inability to load widgets and no visual display.

Check for Gtk in the executable by

	
nm timidity | grep gtk
	
      

Summary

Timidity is not designed for use by other applications. Either you add a new interface or work around the TiMidity design to produce a library. This chapter has shown both mechanisms and illustrated them with simple and more complex examples.


Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
"Programming and Using Linux Sound - in depth" by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/LinuxSound/ .

If you like this book, please contribute using PayPal

Or Flattr me:
Flattr this book