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; }