BeatMaster PC-MIDI Sequencer


The Ticking Heart of BeatMaster


The following fragment of Turbo C source code is the generic mechanism used by BeatMaster to control the PC's 8253 timer chip and handle the high-speed timer-tick (int 08h) interrupts. Every PC has such a clock-chip which can be set to interrupt the processor after a specified period of time has elapsed. By default, the timer generates an interrupt 18.2 times a second to update the time-of-day counter and perform system-maintennance operations. This is quite a fast tick, but nothing like fast enough for a MIDI sequencer ticking 192 times a beat at 500bpm! Luckily, the 8253 is perfectly able to supply the necessary time-resolution - with a little careful prodding. The code below does just that.

Functions

static void interrupt fast_tick(void)
The interrupt handler for the high-speed timer-tick.

void fasttick(unsigned mpt, int (far *user_tick)(void))
Installs the fast_tick() handler. Note that the handler un-installs itself in respone to a flag-setting.

void setfasttick(unsigned mpt)
Set a new tick duration in microseconds per tick.

void slowtick(void)
Disable (un-install) the fast_tick() handler by setting a flag.

int tickstatus(void)
Returns the installed-status of the fast_tick() handler.

#include <dos.h>

/*** 80x86 CPU Control ***/

/* CPU Carry-Flag bit */
#define CPU_CF		0x0001

/* Get flags in register AX */
#define axflags()	__emit__(0x9C, 0x58)	/* PUSHF, POP AX */

/* Exchange AH with AL */
#define xchgax()	__emit__(0x86, 0xE0)


/*** BIOS Timer-Tick Interrupt Functions ***/

#define TICK_INT	0x08	/* Timer-tick interrupt number */

#define TPM		1190	/* Ticks Per Millisecond for timer 0 */
#define numticks(mpt) \
	(unsigned)((TPM*(long)(mpt))/1000)	/* Counter value */

static int _FastTick;		/* Fast tick handler installed flag */
static int _TickStop;		/* Stop (uninstall) timer handler flag */
static void interrupt (*Timer_tick)(void);	/* Original timer vector */
static int (far *User_tick)(void);		/* User-defined tick handler */
static unsigned _Tpu;		/* Ticks per user-tick */

/* Fast tick interrupt handler - uses 8253 Interrupt on Terminal Count mode */
/* Calls a user handler and, when necessary, the original tick handler */
static void interrupt fast_tick(void)
{
	static unsigned ticks = 0;
	static int intick = 0;
	register int f, stop = _TickStop;

	ticks -= _Tpu;	axflags();		/* Decrement tick counter, get flags */
	f = (_AX&CPU_CF);			/* Set flag on counter rollover */
	if (f&&stop) {				/* Remove handler (on Timer tick) */
		_FastTick = 0;			/* Reset installed flag */
		setvect(8, Timer_tick);		/* Restore original timer vector */
		outportb(0x43, 0x36);		/* Timer 0, lo/hi, square-wave, binary */
		outportb(0x40, 0);		/* Counter low byte */
		outportb(0x40, 0);		/* Counter high byte */
		ticks = 0;			/* Reset rollover flag for next install */
	} else {				/* Reload handler */
		outportb(0x43, 0x30);		/* Timer 0, lo/hi, ITC, binary */
		_AX = _Tpu;	outportb(0x40, _AL);	/* Counter low byte */
		xchgax();	outportb(0x40, _AL);	/* Counter high byte */
	}
	if (f) (*Timer_tick)();			/* Call original tick handler */
	else outportb(0x20, 0x20);		/* Send EOI to PIC */
	if (!stop) {				/* Not waiting to stop (uninstall)? */
		if (!intick++) {		/* Already in user handler? */
			enable(); (*User_tick)(); disable();	/* Call user handler */
			if (_AX) _TickStop = 1;	/* Nonzero? (stop timer on next tick) */
		}
		--intick;
	}
}

/* Install ITC mode tick interrupt handler */
void fasttick(unsigned mpt, int (far *user_tick)(void))
{
	_TickStop = 0;	_FastTick = 1;		/* Set installed flag */
	_Tpu = numticks(mpt);			/* Set ticks per user-tick */
	User_tick = user_tick;			/* Set user tick-handler */
	Timer_tick = getvect(TICK_INT);		/* Store original tick-handler */
	setvect(TICK_INT, fast_tick);		/* Set new tick-handler */
}

/* Set new tick duration (in microseconds) */
void setfasttick(unsigned mpt) { disable(); _Tpu = numticks(mpt); enable(); }

/* Disable timer handler */
void slowtick(void) { _TickStop = 1; while (_FastTick); }

/* Return tick handler installed status - nonzero=installed */
int tickstatus(void) { return _FastTick; }

The following functions are all part of BeatMaster itself and therefore refer to application-specific variables whose purposes may be unclear. The tickint() function in particular may seem very obscure indeed. They are included here to give a general idea of how BeatMaster uses the timer-tick functions above to customize the PC clock.

Functions

void ticker(void)
Acts as a switch to start and stop the MIDI clock.

void setbpm(int bpm)
Sets the clock tempo in beats per minute.

void setmpb(long mpb)
Sets the clock tempo in microseconds per beat.

int far tickint(void)
The custom tick-handler passed as the user_tick parameter to fasttick(). This function is called by the generic fast_tick() handler to do application-specific work - in this case, transmit and receive MIDI messages, update counters etc.

/* Install/remove tick interrupt (INT 08h) handler */
void ticker(void)
{
	static int tickon = 0;

	if (!tickon) {	/* Install handler */
		++tickon;	Mplay = 0;	Ticks = 0;
		Start = 1;	Sys.stop = 0;	Memrep = 0;
		RtTpc = Tpb/clockrate(ClockRate);	/* Set external clock rate */
		fasttick(getmpt(), tickint);		/* Install tick handler */
	} else { slowtick(); --tickon; }		/* Remove tick handler */
}

/* Set clock using beats per minute */
void setbpm(int bpm)
{
	if (!Play) Seq->bpm = bpm;			/* Set initial bpm */
	else {						/* Set current bpm */
		Sys.bpm = bpm;
		setfasttick(getmpt());
	}
	puttempo();
}

/* Set clock using microseconds per beat (Meta-51h) */
void setmpb(long mpb)
{
	if (!Play) Seq->bpm = (unsigned)(MPM/mpb);	/* Set initial bpm */
	else {						/* Set current bpm */
		Sys.bpm = (unsigned)(MPM/mpb);
		setfasttick(getmpt());
	}
	puttempo();
}

/* Tick processing section of tick interrupt (INT 08h) handler */
int far tickint(void)
{
	int loop, rtin, rtout;

	if (Sys.stop) return 0;			/* Waiting to stop? */
	_processmidi(Tick);			/* Process MIDI-IN (record & MIDI-THRU) */
	if (Mplay) if (!--Mplay) metrobeep(0);	/* Stop metronome? */
	rtin = RtClock&&(Realtime==RT_SLAVE);	/* Real-time slave clock */
	if (rtin)	/* Slave mode - Proceed only if a clock event was received */
		if (Realflags&RT_CLOCK) Realflags ^= RT_CLOCK; else return 0;
	rtout = RtClock&&(Realtime==RT_MASTER);	/* Real-time master clock */
	loop = RtTpc;	RtLoop:			/* Start of real-time slave loop */
	if (!Start) ++Tick;			/* Increment global tick counter */
	if (--Ticks<1) {			/* End/start of beat */
		if (!Start) ++Beat;		/* Increment global beat counter */
		if (Beat>MAXBEAT)		/* Reached maximum beat? */
			{ Sys.stop = stoptype(E_STOP); return 0; }
		if (!Ticks) if (View&(VU_BEAT|VU_TICK))	/* Update display? */
			{ Newbeat = Beat+1; Newtick = 0; }	/* Flag new beat/tick update */
		if (Metro) { Mplay = METRO_PLAY; metrobeep(1); } /* Metronome on? */
		Ticks = Tpb;			/* Reset ticks-per-beat counter */
	} else if (istickview(View))		/* Off-beat - check tick cursor */
		if (Ticks%TPC==0) { Newbeat = Beat+1; Newtick = Tpb-Ticks; }
	if (rtout) if (!(Ticks%RtTpc)) sendrealtime(CLOCK_STATUS, 0);
	process_tick();	Start = 0;		/* Send MIDI messages etc */
	if (rtin) if (--loop) if (!Sys.stop) goto RtLoop; /* Repeat for RT clock */
	return 0;
}

Go To: BeatMaster Support