/* play.c */ /* $Author: Espie $ * $Date: 91/05/16 15:05:39 $ * $Revision: 1.46 $ * $Log: play.c,v $ * Revision 1.46 91/05/16 15:05:39 Espie * Conditional asm, else use stub. * * Revision 1.45 91/05/12 22:39:01 Espie * Corrected a stupid bug in change_speed: * with speed 0, I was going through the OTHER cases, * with expected results (MUCH too fast). * * Revision 1.44 91/05/12 19:55:47 Espie * Split partly to commands. * Corrected oneshot logic, added new periods handling. * Other minor changes. * * Revision 1.43 91/05/12 15:59:33 Espie * Tried to correct the oneshot bug. * * Revision 1.42 91/05/11 14:58:37 Espie * Tried to correct the ``eaten note'' problem. Not done yet. * * Revision 1.41 91/05/09 17:37:01 Espie * Non standard speed modes. (Old and New). * Hopefully a temporary kludge, with better * loader, we could magically determine what * each speed change means... * * Revision 1.40 91/05/08 15:52:12 Espie * Apparent problems with volume latch, nothing changed, * problems were not coming from here. * * Revision 1.39 91/05/07 12:13:15 Espie * Speed or not speed ?? * * Revision 1.38 91/05/07 02:53:30 Espie * Corrected oneshot bug. * * Revision 1.37 91/05/06 15:15:35 Espie * Changed some more stuff from public to private status. * * Revision 1.36 91/05/05 19:07:14 Espie * New private fields, now we should manage all speed changes * by ourselves. * * Revision 1.35 91/05/05 15:40:15 Espie * Semi-automatic conversion. Works mostly * (bug in run the gauntlet at measure 6). * * Revision 1.34 91/05/02 23:33:09 Espie * *** empty log message *** * * Revision 1.33 91/05/02 01:32:31 Espie * New automaton for the player, simpler. * * Revision 1.32 91/04/30 16:52:58 Espie * Corrected speed bug: we only * set the new speed after all channels have * been processed. * * Revision 1.31 91/04/30 01:47:06 Espie * Fixed out some minor problems: now speed 0 is recognized as an end. * On start at a new pos, we send a ON_PATTERN message for people * wanting to update the pattern number dumbly. * Easier for us than for them ! * * Revision 1.30 91/04/30 00:35:50 Espie * Stable version III. * * Revision 1.29 91/04/29 15:06:49 Espie * Moved control of start/stop actions to interrupt.c * * Revision 1.28 91/04/28 22:52:54 Espie * Tranpose added. * * Revision 1.27 91/04/28 20:35:04 Espie * New check for speed (finespeed adjust). * * Revision 1.26 91/04/27 20:48:37 Espie * Dual speed tempo. * * Revision 1.25 91/04/27 16:44:27 Espie * Optimized again. * Changed part of the control, now all the commands are local functions. * Tried to optimize size of parameters, but Lattice won't let me... bug, Bug, BUG ! * * Revision 1.24 91/04/27 04:00:52 Espie * many new optimizations, some cleanup. * (constant folding, separation of arpeggio in 3 commands, etc). * * Revision 1.23 91/04/27 00:25:00 Espie * New timing routine (works). * Now times itself. * First try at constant folding for cases. * Optimize further... * * Revision 1.22 91/04/26 16:34:36 Espie * Completely new timing, far from perfect yet. * The interrupt routine is now a 3-state automaton. * The timing relies entirely on the wait() function, which * waits for intervals between each call. Smallest wait should * be reparameterized. * Should add an ``efficiency'' count: what percentage of the CPU time * do we use ? Since nobody is able to time us, we should time ourselves. * * Revision 1.21 91/04/26 01:29:16 Espie * Refixed the vibrato command once again. * Plays right, don't touch :-(. * * * Revision 1.20 91/04/25 02:05:52 Espie * Corrected vibrato.... should work correctly. * Added filter control (good idea ?). * * Revision 1.19 91/04/24 23:40:13 Espie * Fixed the vibrato routine. This is now the correct * depth/speed. * * Revision 1.18 91/04/24 15:26:07 Espie * Fixed up small problem, which had no chance to appear before. * The instrument_reload command does reset everything, including * the period now, since we can multitask with other users... * * Revision 1.17 91/04/23 21:24:54 Espie * Totally revised logic for the automaton. * It is now much easier to set up anything through the interrupt. * While it is in stop mode, the song pointers don't have to be valid. * The oneshot setting has been thoroughly tested, it seems necessary * to stop the audio hardware at that point (cleanup is not fast enough). * Added the reload_instrument stuff to try to restart from a pause. * Also, finetunes are now implemented. * Parameters not yet reduced to minimum size, logic between note and period * not completely alright too. * * Revision 1.16 91/04/21 20:04:17 Espie * Handles arpeggio, apparently correctly. * Has a crude ``oversampling'' mechanism. * Does know something about notes. * Not perfect in its handling of finetune instruments (examples ?). * * Revision 1.15 91/04/21 12:11:47 Espie * Stable version, known as bunch II. * Also features ``right'' log description. * * Revision 1.14 91/04/21 11:31:56 Espie * Try out for automatic log messages * * Revision 1.13 91/04/21 11:27:13 Espie * Added automatic log messages. * * Revision 1.12 91/04/21 11:16:17 Espie * Simplified player. * * Revision 1.11 91/04/20 18:12:59 Espie * Improved player: * simplified calls to audio routines. * Caught a subtle bug in the instrument number stuff. * Caught the volume change problems. * Seems to work great. * * Revision 1.10 91/04/20 03:55:31 Espie * Cleaned up version. * Does play mod.shadowfire correctly. * * Revision 1.9 91/04/20 03:07:08 Espie * Debugged version. Portamento is now working correctly. * Incorrect loading of instrument has been fixed: * while portamento, frequency should not be changed, but * volume should be anyway. Not too easy. * (To check, change volume always, actually). * * Revision 1.8 91/04/19 19:46:57 Espie * Tone portamento working better. Successfull. * * Revision 1.7 91/04/19 18:35:24 Espie * Version before an experiment. * * Revision 1.6 91/04/19 13:20:30 Espie * Augmented player: broken up in small functions, * signals task for events, recognizes many events, * but not everything quite yet... * * Revision 1.5 91/04/19 02:18:35 Espie * new player, plays about 90% of my files correctly. * Missing arpegios, volume slide and command 4, whatever that is. * * Revision 1.4 91/04/18 20:23:34 Espie * No assembly stub necessary under SAS C. * * Revision 1.3 91/04/18 02:26:14 Espie * bunch I. * * Revision 1.2 91/04/18 02:19:12 Espie * ``Working'' simple-minded player. * * Revision 1.1 91/04/18 01:34:53 Espie * Initial revision * */ #include #include #include #include #include #include #include "song.h" #include "player.h" #include "public_play.h" #include "int_play.h" #include "proto.h" #include "periods.h" #include "lproto.h" /*** * * The micro-timing package * ***/ /* get current timer value */ UWORD gettimer(struct play *play) { return *PRIVATE.latchlo | (*PRIVATE.latchhi<<8); } /* relatch with the correct time for an interval * between the start of this interrupt and the next * interrupt of value */ void relatch(struct play *play, int value) { PRIVATE.current = PRIVATE.latched - gettimer(play); value -= PRIVATE.current; *PRIVATE.latchlo = value & 255; *PRIVATE.latchhi = value>>8; /* IMPORTANT: force load of latched value ! */ *PRIVATE.control |= CIACRAF_LOAD; PRIVATE.latched = value; /* time ourselves, while we're at it */ PUBLIC.sleep+= PRIVATE.latched; PUBLIC.cpu += PRIVATE.current; } void rebuild_timers(struct play *play) { PRIVATE.maintimer = PUBLIC.timebase/PRIVATE.finespeed; PRIVATE.effecttimer = PUBLIC.effectbase/PRIVATE.finespeed; PRIVATE.smalltimer = 600; } #ifdef LATTICE void __interrupt __asm do_play(register __a1 struct play *play) #else void C_do_play(struct play *play) #endif { (*PRIVATE.state)(play); } void init_player(struct play *play) { int track; PRIVATE.state = wait_play; reset_player(play); rebuild_timers(play); PRIVATE.latched = PRIVATE.maintimer; for (track = 0; track < NUMBER_TRACKS; track++) { PRIVATE.track[track]->pursue = do_nothing; PRIVATE.track[track]->channel = track; } PRIVATE.setup[0] = setup_arpeggio; PRIVATE.setup[1] = setup_porta_up; PRIVATE.setup[2] = setup_porta_down; PRIVATE.setup[3] = setup_portamento; PRIVATE.setup[4] = setup_vibrato; PRIVATE.setup[5] = ignore; PRIVATE.setup[6] = ignore; PRIVATE.setup[7] = ignore; PRIVATE.setup[8] = ignore; PRIVATE.setup[9] = ignore; PRIVATE.setup[10]= setup_volume_slide; PRIVATE.setup[11]= do_fastskip; PRIVATE.setup[12]= do_change_volume; PRIVATE.setup[13]= do_skip; PRIVATE.setup[14]= do_change_filter; PRIVATE.setup[15]= do_change_speed; init_audio_hard(&play->private); } void wait_play(struct play *play) { if (PUBLIC.command) PRIVATE.state = normal_play; return; } void normal_play(struct play *play) { PRIVATE.replay = FALSE; PRIVATE.volume = PUBLIC.volume; play_next(play); /* setup for the new replay routine */ if (PRIVATE.tempo_change) { rebuild_timers(play); PRIVATE.tempo_change = FALSE; } if (PRIVATE.replay) { PRIVATE.state = latch_samples; PRIVATE.spent = PRIVATE.latched - gettimer(play); relatch(play, PRIVATE.smalltimer+PRIVATE.spent); } else relatch(play, PRIVATE.effecttimer); } void latch_samples(struct play *play) { /* dma was off, turn it back on */ turn_on_dma(&play->private); PRIVATE.state = install_replay; relatch(play, PRIVATE.smalltimer); } void install_replay(struct play *play) { int track; /* setup the replay section */ for (track = 0; track < NUMBER_TRACKS; track++) if (PRIVATE.track[track]->newnote) set_replay(&play->private, PRIVATE.track[track]->instr, PRIVATE.track[track]->channel); PRIVATE.state = normal_play; relatch(play, PRIVATE.maintimer - 2 * PRIVATE.smalltimer - PRIVATE.spent); } /* this function should be called every time a volume is to be set, * because the master volume changes according to the user's whim. */ int scaled_volume(int master, struct automaton *cst) { return (cst->volume * master) /256; } void new_volume(struct priv_play *private, struct automaton *cst) { change_volume(private, cst->channel, scaled_volume(private->volume, cst)); } void new_period(struct priv_play *private, struct automaton *cst) { change_period(private, cst->channel, cst->period); } /* what to do when play has just ended */ void ended_play(struct play *play) { PRIVATE.has_ended = TRUE; } void send_out(struct play *play) { PUBLIC.position = PRIVATE.position; PUBLIC.pattern = PRIVATE.pattern; PUBLIC.speed = PRIVATE.speed; PUBLIC.finespeed = PRIVATE.finespeed; } void install_filter(struct priv_play *private) { if (private->filter) filter_on(); else filter_off(); } void reset_player(struct play *play) { int track; struct automaton *cst; PRIVATE.filter = FALSE; install_filter(&play->private); reset_audio(); for (track = 0; track < NUMBER_TRACKS; track++) { cst = PRIVATE.track[track]; cst->instr = PUBLIC.sample[0]; cst->rate = 0; cst->depth = 0; cst->speed = 0; } /* dummy sample */ PRIVATE.counter = 0; send_out(play); /* turns out to be simpler for the display */ send(play, ON_PATTERN); PRIVATE.speed = 6; PRIVATE.finespeed = 100; rebuild_timers(play); } void play_next(struct play *play) { struct automaton *cst; int track; if (PUBLIC.resume) { clear_mask(&play->private); install_filter(&play->private); for (track = 0; track < NUMBER_TRACKS; track++) { cst = PRIVATE.track[track]; cst->p_table = PRIVATE.period_table[cst->instr->finetune] +PUBLIC.transpose; new_period(&play->private, cst); new_volume(&play->private, cst); set_replay(&play->private,cst->instr, cst->channel); } turn_on_dma(&play->private); } if (++PRIVATE.counter < PRIVATE.speed) { /* continue_notes */ for (track = 0; track < NUMBER_TRACKS; track++) (*PRIVATE.track[track]->pursue)(&play->private, PRIVATE.track[track]); } else { if (PRIVATE.has_ended) { send(play, ON_END); PRIVATE.has_ended = FALSE; if (PUBLIC.oneshot) { PUBLIC.command = STOP; PUBLIC.oneshot = FALSE; } } if (PUBLIC.command) { switch(PUBLIC.command) { case STOP: PRIVATE.state = wait_play; reset_player(play); break; case NEWPOS: PRIVATE.pattern = PUBLIC.pattern; PRIVATE.position = PUBLIC.position; reset_player(play); clear_mask(&play->private); break; default: break; } PUBLIC.command = NONE; send(play, ON_COMMAND); return; } PRIVATE.block = PUBLIC.info->pblocks[PRIVATE.pattern]; PRIVATE.counter = 0; clear_mask(&play->private); play_notes(play); } } UWORD compute_period(struct priv_play *private, struct automaton *cst) { if (cst->note > FINE_PERIOD) return private->period_table[NUMBER_TUNING - 1] [cst->note - FINE_PERIOD]; else { return cst->p_table[cst->note]; } } void play_notes(struct play *play) { struct automaton *cst; int track; struct priv_play *private; private = &play->private; private->fastskip = -1; private->skip = -1; private->newspeed = -1; for (track = 0; track < NUMBER_TRACKS; track++) { cst = private->track[track]; private->e = &private->block->e[track][PRIVATE.position]; /* We DO reload the volume each time we change the sample */ if (private->e->sample_number != 0) { cst->instr = PUBLIC.sample[private->e->sample_number]; cst->volume = cst->instr->volume; cst->p_table = private->period_table[cst->instr->finetune] +PUBLIC.transpose; if (cst->instr->finetune) send(play, ON_BLIP); new_volume(private, cst); } /* default next command, unless * there is something more interesting to do */ cst->pursue = do_nothing; /* there is a new note unless we don't have any period, * or this is the SPECIAL PORTAMENTO command */ if (cst->newnote = (private->e->note != NO_NOTE && private->setup[private->e->effect] != setup_portamento)) { cst->note = private->e->note; cst->period = compute_period(private, cst); set_note(private, cst->instr, cst->channel, cst->period); cst->offset = 0; private->replay = TRUE; } (*private->setup[private->e->effect])(private, cst); } if (private->newspeed != -1) change_speed(play); if (private->skip!=-1) { private->position = private->skip; private->pattern++; if (private->pattern >= PUBLIC.info->length) { private->pattern = 0; ended_play(play); } send(play, ON_PATTERN); } else { if (private->fastskip != -1) { private->position = 0; if (private->fastskip < private->pattern) ended_play(play); private->pattern = private->fastskip; send(play, ON_PATTERN); } else advance_position(play); } PUBLIC.pattern = private->pattern; PUBLIC.position = private->position; } #define STD 0 #define OLD 1 #define NEW 2 void change_speed(struct play *play) { if (PRIVATE.newspeed == 0) { switch(PUBLIC.mode) { case NEW: PRIVATE.speed = 6; PRIVATE.finespeed = 100; rebuild_timers(play); PRIVATE.fastskip = 0; break; case STD: ended_play(play); break; case OLD: break; } } else { if (PRIVATE.newspeed >= 32 && PUBLIC.mode != OLD) { PRIVATE.finespeed = PRIVATE.newspeed-31; if (PUBLIC.mode == STD) PRIVATE.speed = 6; rebuild_timers(play); } else { PRIVATE.speed = PRIVATE.newspeed; if (PRIVATE.finespeed != 100 && PUBLIC.mode != NEW) { PRIVATE.finespeed = 100; rebuild_timers(play); } } } send_out(play); send(play, ON_SPEED_CHANGE); } void send(struct play *play, ULONG event) { if (PUBLIC.on_signal & event && PUBLIC.task && PUBLIC.signal != 0) { Signal(PUBLIC.task, PUBLIC.signal); } PUBLIC.signaled |= event; } void advance_position(struct play *play) { PRIVATE.position++; if (PRIVATE.position < BLOCK_LENGTH) return; PRIVATE.position = 0; PRIVATE.pattern++; send(play, ON_PATTERN); if (PRIVATE.pattern >= PUBLIC.info->length) { PRIVATE.pattern = 0; ended_play(play); } }