Added playmidi1...

This commit is contained in:
Jaroslav Kysela 1999-01-24 12:00:39 +00:00
parent fa2745a3c5
commit 53cbbeee14
5 changed files with 2356 additions and 2 deletions

View file

@ -1,4 +1,4 @@
check_PROGRAMS=control mixer switches pause pcm latency seq
check_PROGRAMS=control mixer switches pause pcm latency seq playmidi1
control_LDADD=../src/libasound.la
mixer_LDADD=../src/libasound.la
@ -7,8 +7,10 @@ pause_LDADD=../src/libasound.la
pcm_LDADD=../src/libasound.la
latency_LDADD=../src/libasound.la
seq_LDADD=../src/libasound.la
playmidi1_LDADD=../src/libasound.la
INCLUDES=-I$(top_srcdir)/include
CFLAGS=-static -Wall -pipe -g
EXTRA_DIST=seq-decoder.c seq-sender.c
EXTRA_DIST=seq-decoder.c seq-sender.c midifile.h midifile.c midifile.3

336
test/midifile.3 Normal file
View file

@ -0,0 +1,336 @@
.TH MIDIFILE 3
.SH NAME
mfread,mfwrite - read and write a standard MIDI file
.SH SYNOPSIS
\fC#include "mfread.h"
mfread ()
.nf
int (*Mf_getc) ();
int (*Mf_putc) ();
int (*Mf_error) (char *msg);
int (*Mf_header) (int format, int ntrks, int division);
int (*Mf_trackstart) ();
int (*Mf_trackend) ();
int (*Mf_noteon) (int chan, int pitch, int vol);
int (*Mf_noteoff) (int chan, int pitch, int vol);
int (*Mf_pressure) (int chan, int pitch, int pressure);
int (*Mf_parameter) (int chan, int control, int value);
int (*Mf_pitchbend) (int chan, int msb, int lsb);
int (*Mf_program) (int chan, int program);
int (*Mf_chanpressure) (int chan, int pressure);
int (*Mf_sysex) (int leng, char *msg);
int (*Mf_metamisc) (int type, int leng, int msg);
int (*Mf_seqspecific) (int type, int leng, int msg);
int (*Mf_seqnum) (int num);
int (*Mf_text) (int type, int leng, int msg);
int (*Mf_eot) ();
int (*Mf_timesig) (int numer, int denom, int clocks, int qnotes);
int (*Mf_smpte) (int hour, int min, int sec, int frame, int fract);
int (*Mf_tempo) (int microsecs);
int (*Mf_keysig) (int sharpflat, int minor);
int (*Mf_arbitrary) (int leng, int msg);
int Mf_nomerge;
long Mf_currtime;
.fi
.sp 1
mfwrite(int format, int ntracks, int division, FILE *fp)
.sp 1
.nf
int (*Mf_writetrack)(int track);
int (*Mf_writetempotrack)();
void mf_write_midi_event(delta, type, chan, data, size)
unsigned long delta;
unsigned int type,chan,size;
char *data;
void mf_write_meta_event(delta, type, data, size)
unsigned long delta;
unsigned int type,chan,size;
char *data;
void mf_write_tempo(tempo)
unsigned long tempo;
unsigned long mf_sec2ticks(float seconds, int division, int tempo)
float seconds;
int division;
unsigned int tempo;
float mf_ticks2sec(ticks, division, tempo)
unsigned long ticks;
int division;
unsigned int tempo;
.fi
.SH DESCRIPTION
The \fCmfread\fR function reads and inteprets a standard MIDI file.
To use it you need to understand the general form of a
MIDI file and the type of information it contains, but you don't
need to know much, if anything, about the detailed format of the file
and the mechanics of reading it reliably and portably.
The \fCmfwrite\fR function writes a standard MIDI file making
use of user-defined functions that access the program's
data structure. To use it you need to define your own Mf_writetrack
routine and then make use of the write_* family of routines to
write out the MIDI data. The \fCmfwrite\fR routine takes
care of the file format and writing the file and track chunk headers.
.SH READING STANDARD MIDI FILES
A single call to \fCmfread\fR will read an entire MIDI file.
The interface to \fCmfread\fR is a set of external variables
named \fCMf_*\fR, most of which are function pointers to be called
from within \fCmfread\fR during the process of parsing the MIDI file.
Before calling \fCmfread\fR, the only
requirement is that you assign a value
to \fCMf_getc\fR - a pointer to a function that will return
characters from the MIDI file, using -1 to indicate EOF.
All the rest of the function
pointers are initialized to NULL, and the default action for each
is to do nothing. The following is a complete program using \fCmfread\fR
that could serve as a 'syntax checker' for MIDI files:
.in +1i
.ft C
.nf
#include <stdio.h>
#include "midifile.h"
mygetc()
{
/* use standard input */
return(getchar());
}
main()
{
Mf_getc = mygetc;
mfread();
exit(0);
}
.fi
.ft R
.in -1i
This takes advantage of the default action when an error is detected, which
is to exit silently with a return code of 1. An error function of your
own can be used by giving a value to \fCMf_error\fR; the function will be
called with the error message as an argument.
The other \fCMf_* variables can similarly be used to call arbitrary
functions while parsing the MIDI file. The descriptions below
of the information passed to these functions is sparse; refer to
the MIDI file standard for the complete descriptions.
\fCMf_header\fR is the first function to be called, and its arguments
contain information from the MIDI file's header; the format (0,1, or 2),
the number of tracks, and the division of a quarter-note that defines
the times units.
\fCMf_trackstart\fR and
\fCMf_trackend\fR are called at the beginning and end of each track.
Once inside a track, each separate message causes a function to be called.
For example, each note-on message causes \fCMf_noteon\fR to be called
with the channel, pitch, and volume as arguments. The time at which
the message occurred is stored in \fCMf_currtime\fR - one of the few
external variables that isn't a function pointer. The other channel messages
are handled in a similar and obvious fashion -
\fCMf_noteoff\fR,
\fCMf_pressure\fR,
\fCMf_parameter\fR,
\fCMf_pitchbend\fR,
\fCMf_program\fR,
and \fCMf_chanpressure\fR. See the declarations above for the arguments
that are passed to each.
System exclusive messages are handled by calling \fCMf_sysex\fR, passing
as arguments the message length and a pointer to a static buffer containing
the entire message.
The buffer is expanded when necessary; memory availability is the only limit
to its size. Normally, 'continued' system exclusives are automatically
merged, and \fCMf_sysex\fR is only called once. It you want to disable this
you can set \fCMf_nomerge\fR to 1, causing \fCMf_sysex\fR to be called
once for each part of the message.
\fCMf_seqnum\fR is called by the \fImeta\fR message that provides
a sequence number,
which if present must appear at the beginning of a track.
The tempo \fImeta\fR message causes \fCMf_tempo\fR to be called; its
argument is the number of microseconds per MIDI quarter-note (24 MIDI clocks).
The end-of-track \fImeta\fR message causes \fCMf_eot\fR to be called.
The key signature \fImeta\fR message causes \fCMf_keysig\fR to be called;
the first argument conveys the number of sharps or flats, the second
argument is 1 if the key is minor.
The \fCMf_timesig\fR and \fCMf_smpte\fR functions are called when the
corresponding \fImeta\fR messages are seen. See the MIDI file standard
for a description of their arguments.
The \fItext\fR messages in the MIDI file standard are of the following
types:
.in +1i
.nf
0x01 Text Event
0x02 Copyright
0x03 Sequence/Track Name
0x04 Instrument
0x05 Lyric
0x06 Marker
0x07 Cue Point
0x08-0x0F Reserverd but Undefined
.fi
.in -1i
\fCMf_text\fR is called for each of these; the arguments are
the type number, the message length, and a pointer to the message buffer.
Misceallaneous \fImeta\fR messages are handled by \fCMf_metamisc\fR,
sequencer-specific messages are handled by \fCMf_seqspecific\fR, and
arbitrary "escape" messages (started with 0xF7) are handled by
\fCMf_arbitrary\fR.
.SH READING EXAMPLE
The following is a \fCstrings\fR-like program for MIDI files:
.in +1i
.ft C
.nf
#include <stdio.h>
#include <ctype.h>
#include "midifile.h"
FILE *F;
mygetc() { return(getc(F)); }
mytext(type,leng,msg)
char *msg;
{
char *p;
char *ep = msg + leng;
for ( p=msg; p<ep ; p++ )
putchar( isprint(*p) ? *p : '?' );
putchar('\n');
}
main(argc,argv)
char **argv;
{
if ( argc > 1 )
F = fopen(argv[1],"r");
else
F = stdin;
Mf_getc = mygetc;
Mf_text = mytext;
mfread();
exit(0);
}
.fi
.ft R
.in -1i
.sp
.SH WRITING STANDARD MIDI FILES
A single call to \fCmfwrite\fR will write an entire MIDI file. Before
calling \fCmfwrite\fR, you must assign values to function pointers
\fCMf_writetrack\fR and \fCMf_putc\fR. The first is a routine to
access your MIDI data structure, which can make use of other library
routines to write the actual MIDI data. The routine
\fCMf_writetrack\fR will be passed a single parameter which is the
number of the track to be written. The pointer \fCMf_putc\fR should be
set to point to a routine that accepts a charcter as input, writes that
character to a file, and returns the value that was written. In the
case of a format 1 file, a routine has to be written to write a tempo
map, and assigned to the function pointer \fCMf_writetempotrack\fR.
This is because format 1 files assume the first track written is a
tempo track.
\fCmf_write_midi_event\fR and \fCmf_write_meta_event\fR are routines
that should be called from your \fCMf_writetrack\fR routine to write
out MIDI events. The delta time param is the number of ticks since the
last event. The int "type" is the type of MIDI message. The int "chan"
is the MIDI channel, which can be between 1 and 16. The char pointer
"data" points to an array containing the data bytes, if any exist. The
int "size" is the number of data bytes.
\fCmf_sec2ticks\fR and \fCmf_ticks2sec\fR are utility routines
to help you convert between the MIDI file parameter of ticks
and the more standard seconds. The int "division" is the same
division parameter from the file header, and tempo is expressed
in microseconds per MIDI quarter-note, or "24ths of a microsecond
per MIDI clock". The division has two meanings, depending on
whether bit 15 is set or not. If bit 15 of division is zero,
bits 14 through 0 represent the number of delta-time "ticks"
which make up a quarter note. If bit 15 of division is a one,
delta-times in a file correspond to subdivisions of a second
similiar to SMPTE and MIDI time code. In this format bits
14 through 8 contain one of four values - 24, -25, -29, or -30,
corresponding to the four standard SMPTE and MIDI time code
frame per second formats, where -29 represents 30 drop frame.
The second byte consisting of bits 7 through 0 corresponds
the the resolution within a frame. Refer the Standard MIDI Files
1.0 spec for more details.
.SH WRITING EXAMPLE
The following is a simple program to demonstrate writing MIDI files.
The track would consist of a series of quarter notes from lowest to
highest in pitch at constant velocity, each separted by a quarter-note
rest.
.sp
.in +1i
.ft C
.nf
#include <stdio.h>
#include <ctype.h>
#include "midifile.h"
FILE *fp;
myputc(c) { return(putc(c,fp));}
int mywritetrack(track)
int track;
{
int i;
char data[2];
/* 120 beats/per/second */
mf_write_tempo((long)500000);
for(i = 1 ; i < 128; i++){
data[0] = i; /* note number */
data[1] = 64; /* velocity */
if(!mf_write_midi_event(480,note_on,1,data,2))
return(-1);
if(!mf_write_midi_event(480,note_off,1,data,2))
return(-1);
}
return(1);
} /* end of write_track() */
main(argc,argv)
char **argv;
{
if((fp = fopen(argv[1],"w")) == 0L)
exit(1);
Mf_putc = myputc;
Mf_writetrack = mywritetrack;
/* write a single track */
mfwrite(0,1,480,fp);
}
.sp
.fi
.ft R
.in -1i
.sp
.SH AUTHOR
Tim Thompson (att!twitch!glimmer!tjt)
.SH CONTRIBUTORS
Michael Czeiszperger (mike@pan.com)

1173
test/midifile.c Normal file

File diff suppressed because it is too large Load diff

132
test/midifile.h Normal file
View file

@ -0,0 +1,132 @@
/* definitions for MIDI file parsing code */
extern int (*Mf_getc)();
extern void (*Mf_header)();
extern void (*Mf_trackstart)();
extern void (*Mf_trackend)();
extern void (*Mf_noteon)();
extern void (*Mf_noteoff)();
extern void (*Mf_pressure)();
extern void (*Mf_parameter)();
extern void (*Mf_pitchbend)();
extern void (*Mf_program)();
extern void (*Mf_chanpressure)();
extern void (*Mf_sysex)();
extern void (*Mf_metamisc)();
extern void (*Mf_seqspecific)();
extern void (*Mf_seqnum)();
extern void (*Mf_text)();
extern void (*Mf_eot)();
extern void (*Mf_timesig)();
extern void (*Mf_smpte)();
extern void (*Mf_tempo)();
extern void (*Mf_keysig)();
extern void (*Mf_arbitrary)();
extern void (*Mf_error)();
extern unsigned long Mf_currtime;
extern unsigned long Mf_realtime;
extern unsigned long Mf_currtempo;
extern int Mf_division;
extern int Mf_nomerge;
#ifdef READ_MODS
extern unsigned char *Mf_file_contents;
extern int Mf_file_size;
#endif
/* definitions for MIDI file writing code */
extern int (*Mf_putc)();
extern int (*Mf_writetrack)();
extern int (*Mf_writetempotrack)();
extern void midifile();
extern unsigned long mf_sec2ticks();
extern void mfwrite();
extern int mf_write_meta_event();
extern int mf_write_midi_event(unsigned long delta_time, int type,
int chan, char *data, unsigned long size);
extern double mf_ticks2sec(unsigned long ticks,int division,unsigned long tempo);
extern void mf_write_tempo();
extern void mf_write_seqnum();
extern void mfread();
extern void mferror(char *s);
#ifndef NO_LC_DEFINES
/* MIDI status commands most significant bit is 1 */
#define note_off 0x80
#define note_on 0x90
#define poly_aftertouch 0xa0
#define control_change 0xb0
#define program_chng 0xc0
#define channel_aftertouch 0xd0
#define pitch_wheel 0xe0
#define system_exclusive 0xf0
#define delay_packet (1111)
/* 7 bit controllers */
#define damper_pedal 0x40
#define portamento 0x41
#define sostenuto 0x42
#define soft_pedal 0x43
#define general_4 0x44
#define hold_2 0x45
#define general_5 0x50
#define general_6 0x51
#define general_7 0x52
#define general_8 0x53
#ifndef PLAYMIDI
#define tremolo_depth 0x5c
#define ctrl_chorus_depth 0x5d
#define detune 0x5e
#define phaser_depth 0x5f
#endif
/* parameter values */
#define data_inc 0x60
#define data_dec 0x61
/* parameter selection */
#define non_reg_lsb 0x62
#define non_reg_msb 0x63
#define reg_lsb 0x64
#define reg_msb 0x65
/* Standard MIDI Files meta event definitions */
#define meta_event 0xFF
#define sequence_number 0x00
#define text_event 0x01
#define copyright_notice 0x02
#define sequence_name 0x03
#define instrument_name 0x04
#define lyric 0x05
#define marker 0x06
#define cue_point 0x07
#define channel_prefix 0x20
#define end_of_track 0x2f
#define set_tempo 0x51
#define smpte_offset 0x54
#define time_signature 0x58
#define key_signature 0x59
#define sequencer_specific 0x74
/* Manufacturer's ID number */
#define Seq_Circuits (0x01) /* Sequential Circuits Inc. */
#define Big_Briar (0x02) /* Big Briar Inc. */
#define Octave (0x03) /* Octave/Plateau */
#define Moog (0x04) /* Moog Music */
#define Passport (0x05) /* Passport Designs */
#define Lexicon (0x06) /* Lexicon */
#define Tempi (0x20) /* Bon Tempi */
#define Siel (0x21) /* S.I.E.L. */
#define Kawai (0x41)
#define Roland (0x42)
#define Korg (0x42)
#define Yamaha (0x43)
#endif
/* miscellaneous definitions */
#define MThd 0x4d546864
#define MTrk 0x4d54726b
#ifndef NO_LC_DEFINES
#define lowerbyte(x) ((unsigned char)(x & 0xff))
#define upperbyte(x) ((unsigned char)((x & 0xff00)>>8))
#endif

711
test/playmidi1.c Normal file
View file

@ -0,0 +1,711 @@
/*
* MIDI file player for ALSA sequencer
* (type 0 only!, the library that is used doesn't support merging of tracks)
*
* Copyright (c) 1998 by Frank van de Pol <F.K.W.van.de.Pol@inter.nl.net>
*
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
/* define this if you want to send real-time time stamps instead of midi ticks to the ALSA sequencer */
/*#define USE_REALTIME */
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#include "midifile.h" /* SMF library header */
#include "midifile.c" /* SMF library code */
#include "sys/asoundlib.h"
//#define DEST_QUEUE_NUMBER 0
#define DEST_QUEUE_NUMBER 7
#define DEST_CLIENT_NUMBER 64
//#define DEST_CLIENT_NUMBER 72
//#define DEST_CLIENT_NUMBER 128
//#define DEST_CLIENT_NUMBER 255
//#define DEST_CLIENT_NUMBER SND_SEQ_ADDRESS_BROADCAST
FILE *F;
void* seq_handle = NULL;
int ppq = 96;
double local_secs = 0;
int local_ticks = 0;
int local_tempo = 500000;
static int dest_queue = DEST_QUEUE_NUMBER;
static int dest_client = DEST_CLIENT_NUMBER;
static int dest_port = 0;
//static int dest_port = 1;
static int source_channel = 0;
static int source_port = 0;
extern void alsa_start_timer(void);
extern void alsa_stop_timer(void);
static inline double tick2time_dbl(int tick)
{
return local_secs + ((double) (tick - local_ticks) * (double) local_tempo * 1.0E-9 / (double) ppq);
}
#ifdef USE_REALTIME
static void tick2time(snd_seq_real_time_t * tm, int tick)
{
double secs = tick2time_dbl(tick);
//double secs = ((double) tick * (double) local_tempo * 1.0E-6 / (double) ppq);
tm->tv_sec = secs;
tm->tv_nsec = (secs - tm->tv_sec) * 1.0E9;
//printf("secs = %lf = %d.%09d\n", secs, tm->tv_sec, tm->tv_nsec);
}
#endif
/* sleep until sequencer has reached specified timestamp, to guard that we play too much events ahead */
void sleep_seq(int tick)
{
#if 0
snd_seq_queue_info_t queue_info;
const int COUNT_MAX = 500;
const int COUNT_MIN = 50;
static int count = 0;
count++;
if (count >= COUNT_MAX)
{
while (snd_seq_flush_output(seq_handle) > COUNT_MIN)
sched_yield ();
count = 0;
}
#endif
}
/* write event to ALSA sequencer */
void write_ev_im(snd_seq_event_t * ev)
{
int written;
ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SND_SEQ_EVENT_LENGTH_FIXED;
written = -ENOMEM;
while (written<0) {
written = snd_seq_event_output (seq_handle, ev);
if (written<0) {
printf("written = %i (%s)\n", written, strerror(-written));
sleep(1);
}
}
}
/* write event to ALSA sequencer */
void write_ev(snd_seq_event_t * ev)
{
sleep_seq(ev->time.tick-ppq);
write_ev_im(ev);
}
/* write variable length event to ALSA sequencer */
void write_ev_var(snd_seq_event_t * ev, int len, void *ptr)
{
int bytes;
int written;
unsigned char *buf;
sleep_seq(ev->time.tick+ppq);
ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SND_SEQ_EVENT_LENGTH_VARIABLE;
ev->data.ext.len = len;
ev->data.ext.ptr = ptr;
written = 0;
while (!written) {
written = snd_seq_event_output (seq_handle, ev);
if (!written)
sleep(1);
}
}
int mygetc(void)
{
return (getc(F));
}
void mytext(int type, int leng, char *msg)
{
char *p;
char *ep = msg + leng;
for (p = msg; p < ep; p++)
putchar(isprint(*p) ? *p : '?');
putchar('\n');
}
void do_header(int format, int ntracks, int division)
{
printf("smf format %d, %d tracks, %d ppq\n", format, ntracks, division);
ppq = division;
if ((format != 0) || (ntracks != 1)) {
printf("This player does not support merging of tracks.\n");
alsa_stop_timer();
exit(1);
}
/* set ppq */
{
snd_seq_queue_info_t queue_info;
queue_info.queue = 1; /* queue we're using */
queue_info.ppq = ppq;
//queue_info.tempo = -1; /* don't change */
queue_info.tempo = 500000; /* don't change */
if (snd_seq_set_queue_info (seq_handle, dest_queue, &queue_info) < 0) {
perror("ioctl");
exit(1);
}
printf("ALSA Timer updated, PPQ = %d\n", queue_info.ppq);
}
/* start playing... */
alsa_start_timer();
}
void do_tempo(int us)
{
double bpm;
snd_seq_event_t ev;
bpm = 60.0E6 / (double) us;
printf("tempo = %d us/beat\n", us);
printf("tempo = %.2f bpm\n", bpm);
/* store new tempo and timestamp of tempo change */
local_secs = tick2time_dbl(Mf_currtime);
local_ticks = Mf_currtime;
local_tempo = us;
/* and send tempo change event to the sequencer.... */
ev.source.port = dest_port;
ev.source.channel = source_channel;
ev.dest.queue = dest_queue;
//ev.dest.client = dest_client; /* broadcast */
ev.dest.client = 255; /* broadcast */
ev.dest.port = 0;
ev.dest.channel = 0; /* don't care */
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_TEMPO;
ev.data.control.value = us;
write_ev_im(&ev);
}
void do_noteon(int chan, int pitch, int vol)
{
snd_seq_event_t ev;
ev.source.port = dest_port;
ev.source.channel = source_channel;
ev.dest.queue = dest_queue;
ev.dest.client = dest_client;
ev.dest.port = dest_port;
ev.dest.channel = chan;
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_NOTEON;
ev.data.note.note = pitch;
ev.data.note.velocity = vol;
write_ev(&ev);
}
void do_noteoff(int chan, int pitch, int vol)
{
snd_seq_event_t ev;
ev.source.port = dest_port;
ev.source.channel = source_channel;
ev.dest.queue = dest_queue;
ev.dest.client = dest_client;
ev.dest.port = dest_port;
ev.dest.channel = chan;
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_NOTEOFF;
ev.data.note.note = pitch;
ev.data.note.velocity = vol;
write_ev(&ev);
}
void do_program(int chan, int program)
{
snd_seq_event_t ev;
ev.source.port = dest_port;
ev.source.channel = source_channel;
ev.dest.queue = dest_queue;
ev.dest.client = dest_client;
ev.dest.port = dest_port;
ev.dest.channel = chan;
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_PGMCHANGE;
ev.data.control.value = program;
write_ev_im(&ev);
}
void do_parameter(int chan, int control, int value)
{
snd_seq_event_t ev;
ev.source.port = dest_port;
ev.source.channel = source_channel;
ev.dest.queue = dest_queue;
ev.dest.client = dest_client;
ev.dest.port = dest_port;
ev.dest.channel = chan;
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_CONTROLLER;
ev.data.control.param = control;
ev.data.control.value = value;
write_ev(&ev);
}
void do_pitchbend(int chan, int lsb, int msb)
{ /* !@#$% lsb & msb are in wrong order in docs */
snd_seq_event_t ev;
ev.source.port = dest_port;
ev.source.channel = source_channel;
ev.dest.queue = dest_queue;
ev.dest.client = dest_client;
ev.dest.port = dest_port;
ev.dest.channel = chan;
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_PITCHBEND;
ev.data.control.value = (lsb + (msb << 7)) - 8192;
write_ev(&ev);
}
void do_pressure(int chan, int pitch, int pressure)
{
snd_seq_event_t ev;
ev.source.port = dest_port;
ev.source.channel = source_channel;
ev.dest.queue = dest_queue;
ev.dest.client = dest_client;
ev.dest.port = dest_port;
ev.dest.channel = chan;
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_KEYPRESS;
ev.data.control.param = pitch;
ev.data.control.value = pressure;
write_ev(&ev);
}
void do_chanpressure(int chan, int pressure)
{
snd_seq_event_t ev;
ev.source.port = dest_port;
ev.source.channel = source_channel;
ev.dest.queue = dest_queue;
ev.dest.client = dest_client;
ev.dest.port = dest_port;
ev.dest.channel = chan;
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_CHANPRESS;
ev.data.control.value = pressure;
write_ev(&ev);
}
void do_sysex(int len, char *msg)
{
snd_seq_event_t ev;
#if 0
int c;
printf("Sysex, len=%d\n", len);
for (c = 0; c < len; c++) {
printf(" %3d : %02x\n", c, (unsigned char) msg[c]);
}
#endif
ev.source.port = dest_port;
ev.source.channel = source_channel;
ev.dest.queue = dest_queue;
ev.dest.client = dest_client;
ev.dest.port = dest_port;
ev.dest.channel = 0; /* don't care */
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_SYSEX;
write_ev_var(&ev, len, msg);
}
/**/
void alsa_sync ()
{
//dump buffer
int left = snd_seq_flush_output (seq_handle);
printf ("alsa_sync syncing\n");
while (left > 0)
{
sched_yield ();
left = snd_seq_flush_output (seq_handle);
if (left < 0)
{
printf ("alsa_sync error!:%s\n", snd_strerror (left));
return;
}
}
for (;;)
{
snd_seq_queue_info_t queue_info;
int tmp = snd_seq_get_queue_info (seq_handle, dest_queue, &queue_info);
if (tmp < 0)
{
printf ("AlsaClient::sync snd_seq_get_queue_info:%s\n",
snd_strerror (tmp));
break;
}
if (Mf_currtime < queue_info.tick)
break;
sched_yield ();
}
}
/* start timer */
void alsa_start_timer(void)
{
snd_seq_event_t ev;
ev.source.port = SND_SEQ_PORT_SYSTEM_TIMER;
ev.source.channel = 0;
ev.dest.queue = dest_queue;
//ev.dest.client = 0; /* system */
ev.dest.client = SND_SEQ_CLIENT_SYSTEM; /* system */
//ev.dest.client = dest_client; /* broadcast */
ev.dest.client = 255; /* broadcast */
ev.dest.port = 0; /* timer */
ev.dest.channel = 0; /* don't care */
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL;
//ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_REL;
ev.time.real.tv_sec = 0;
ev.time.real.tv_nsec = 0;
ev.type = SND_SEQ_EVENT_START;
write_ev_im(&ev);
usleep(0.1E6);
}
/* stop timer */
void alsa_stop_timer(void)
{
snd_seq_event_t ev;
ev.source.port = 0;
ev.source.channel = 0;
ev.dest.queue = dest_queue;
ev.dest.client = 0; /* system */
ev.dest.client = dest_client; /* broadcast */
ev.dest.port = 0; /* timer */
ev.dest.channel = 0; /* don't care */
#ifdef USE_REALTIME
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
tick2time(&ev.time.real, Mf_currtime);
#else
ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
ev.time.tick = Mf_currtime;
#endif
ev.type = SND_SEQ_EVENT_STOP;
write_ev_im(&ev);
}
int main(int argc, char *argv[])
{
char *name;
snd_seq_client_info_t inf;
snd_seq_port_info_t src_port_info;
snd_seq_queue_client_t queue_info;
snd_seq_port_subscribe_t subscribe;
int tmp;
#ifdef USE_REALTIME
printf("ALSA MIDI Player, feeding events to real-time queue\n");
#else
printf("ALSA MIDI Player, feeding events to song queue\n");
#endif
/* open sequencer device */
//tmp = snd_seq_open (&seq_handle, SND_SEQ_OPEN_OUT);
tmp = snd_seq_open (&seq_handle, SND_SEQ_OPEN);
if (tmp < 0) {
perror("open /dev/snd/seq");
exit(1);
}
tmp = snd_seq_block_mode (seq_handle, 0);
if (tmp < 0)
{
perror ("block_mode");
exit (1);
}
/* set name */
memset(&inf, 0, sizeof(snd_seq_client_info_t));
strcpy(inf.name, "MIDI file player");
if (snd_seq_set_client_info (seq_handle, &inf) < 0) {
perror("ioctl");
exit(1);
}
//create port
memset (&src_port_info, 0, sizeof (snd_seq_port_info_t));
src_port_info.capability = SND_SEQ_PORT_CAP_OUT | SND_SEQ_PORT_CAP_SUBSCRIPTION;
src_port_info.type = SND_SEQ_PORT_TYPE_MIDI_GENERIC;
src_port_info.midi_channels = 16;
src_port_info.synth_voices = 0;
src_port_info.use = 0;
src_port_info.kernel = NULL;
tmp = snd_seq_create_port (seq_handle, &src_port_info);
if (tmp < 0)
{
perror ("creat port");
exit (1);
}
//setup queue
queue_info.used = 1;
//queue_info.low = 100;//???
//queue_info.low = SND_SEQ_MAX_EVENTS-1;//???
//queue_info.low = 500-1;//???
//queue_info.low = 1;//???
queue_info.low = 0;
//queue_info.low = 0x7ffffff;//???
//queue_info.high = 0x7ffffff;//???
queue_info.high = 500-100;//???
//queue_info.high = 50;//???
//queue_info.high = 0;//???
//queue_info.high = 1;//???
tmp = snd_seq_set_queue_client (seq_handle, dest_queue,
&queue_info);
if (tmp < 0)
{
perror ("queue_client");
exit (1);
}
#if (DEST_CLIENT_NUMBER == 64) || (DEST_CLIENT_NUMBER == 72)
//setup subscriber
printf ("debug subscribe src_port_info.client=%d\n",
src_port_info.client);
subscribe.sender.client = snd_seq_client_id (seq_handle);
subscribe.sender.queue = dest_queue;
subscribe.sender.port = src_port_info.port;
subscribe.dest.client = dest_client;
subscribe.dest.port = dest_port;
subscribe.dest.queue = dest_queue;
subscribe.realtime = 1;
subscribe.exclusive = 0;
tmp = snd_seq_subscribe_port (seq_handle, &subscribe);
if (tmp < 0)
{
perror ("subscribe");
exit (1);
}
#endif
if (argc > 1)
F = fopen(argv[1], "r");
else
F = stdin;
Mf_header = do_header;
Mf_tempo = do_tempo;
Mf_getc = mygetc;
Mf_text = mytext;
Mf_noteon = do_noteon;
Mf_noteoff = do_noteoff;
Mf_program = do_program;
Mf_parameter = do_parameter;
Mf_pitchbend = do_pitchbend;
Mf_pressure = do_pressure;
Mf_chanpressure = do_chanpressure;
Mf_sysex = do_sysex;
/* stop timer in case it was left running by a previous client */
{
snd_seq_event_t ev;
ev.source.port = 0;
ev.source.channel = 0;
ev.dest.queue = dest_queue;
ev.dest.client = 0; /* system */
//ev.dest.client = dest_client; /* broadcast */
ev.dest.client = 255; /* broadcast */
ev.dest.port = 0; /* timer */
ev.dest.channel = 0; /* don't care */
ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL;
//ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_REL;
ev.time.real.tv_sec = 0;
ev.time.real.tv_nsec = 0;
//ev.type = SND_SEQ_EVENT_STOP;
ev.type = SND_SEQ_EVENT_START;
write_ev(&ev);
}
alsa_start_timer ();
/* go.. go.. go.. */
mfread();
alsa_sync ();
alsa_stop_timer();
snd_seq_close (seq_handle);
printf("Stopping at %lf s, tick %d\n", tick2time_dbl(Mf_currtime + 1), Mf_currtime + 1);
exit(0);
}