/* 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 <exec/types.h>
#include <hardware/cia.h>
#include <proto/exec.h>
#include <dos/dos.h>
#include <stdlib.h>
#include <custom/cleanup.h>
#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);
                        }
        }
