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.
#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.
/* 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;
}