/* interrupt.c */

/* set up a CIA interrupt to play the music
 */

/* $Author: Espie $
 * $Date: 91/05/20 22:44:37 $
 * $Revision: 1.32 $
 * $Log:	interrupt.c,v $
 * Revision 1.32  91/05/20  22:44:37  Espie
 * *** empty log message ***
 * 
 * Revision 1.31  91/05/16  15:05:08  Espie
 * Modified far stuff.
 *
 * Revision 1.30  91/05/12  19:55:31  Espie
 * correct handling of resume.
 *
 * Revision 1.29  91/05/12  15:59:08  Espie
 * Suppressed some unnecessary assignments... More cleanup in order.
 *
 * Revision 1.28  91/05/09  17:36:26  Espie
 * Support for non standard speed modes.
 *
 * Revision 1.27  91/05/08  15:51:18  Espie
 * Added set_volume command for slider.
 *
 * Revision 1.26  91/05/07  12:13:06  Espie
 * *** empty log message ***
 *
 * Revision 1.25  91/05/06  23:38:32  Espie
 * Changed some includes ???
 *
 * Revision 1.24  91/05/06  15:14:46  Espie
 * Speed is now entirely the responsibility of the player itself.
 *
 * Revision 1.23  91/05/05  19:05:38  Espie
 * Moved most of the player stuff which should be private
 * to the player itself.
 *
 * Revision 1.22  91/05/05  15:38:21  Espie
 * Play is now private/public...
 *
 * Revision 1.21  91/05/02  23:26:38  Espie
 * Almost tested, reliable. Needs some cleanup.
 *
 * Revision 1.20  91/05/02  11:19:22  Espie
 * Added some more tests... Not incredibly reliable.
 *
 * Revision 1.19  91/05/02  01:29:22  Espie
 * Completely new interface, much safer.
 * The hardware part is now completely isolated from the software part.
 *
 * Revision 1.18  91/04/30  00:35:35  Espie
 * Stable version III.
 *
 * Revision 1.17  91/04/30  00:24:28  Espie
 * Modified launch_play() slightly: now resets the speed.
 *
 * Revision 1.16  91/04/29  15:07:26  Espie
 * Cleaned-up, now allocates cia timer on the fly.
 * Important: always ask for audio first, because the system
 * avoids deadlock in that case.
 *
 * Revision 1.15  91/04/29  02:21:39  Espie
 * Suppressed ``critical sections''.
 *
 * Revision 1.14  91/04/28  20:34:43  Espie
 * Added fine speed control, definitely needs some cleanup now.
 *
 * Revision 1.13  91/04/27  20:48:26  Espie
 * New dual speed tempo.
 *
 * Revision 1.12  91/04/27  16:44:17  Espie
 * Slight changes.
 *
 * Revision 1.11  91/04/27  04:00:39  Espie
 * Little as changed.
 *
 * Revision 1.10  91/04/26  16:30:36  Espie
 * Now the interrupt routines times itself, so there
 * are new fields to set up correctly.
 *
 * Revision 1.9  91/04/24  15:25:35  Espie
 * Minor changes ??
 *
 * Revision 1.8  91/04/23  21:28:33  Espie
 * New interrupt settings: since the player knows when not to play anything,
 * it is no longer necessary to give a valid song to establish the interrupt.
 * Also, the memory for interrupt and play structure is now dynamically allocated
 * as public memory, which seems more reasonable.
 *
 * Revision 1.7  91/04/21  20:05:16  Espie
 * Oversampling.
 *
 * Revision 1.6  91/04/21  12:11:40  Espie
 * Stable version, known as bunch II.
 * Also features ``right'' log description.
 *
 * Revision 1.5  91/04/20  18:14:04  Espie
 * Added symbolic constants everywhere.
 *
 * Revision 1.4  91/04/19  13:21:27  Espie
 * New interrupt setup, still needs to dynamically allocate
 * interrupt structure, plus some symbolic constants.
 *
 * Revision 1.3  91/04/19  02:19:17  Espie
 * Still many things to do. Concept of a ``play'' structure working.
 * Timing ok.
 *
 * Revision 1.2  91/04/18  20:24:09  Espie
 * Complete interrupt driver, speed is correct (small bug in play.c),
 * setup is correct. Take care that you can't change is_Data after
 * you've added the server.
 *
 * Revision 1.1  91/04/18  16:38:05  Espie
 * Initial revision
 *
 */

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <hardware/cia.h>
#include <custom/cleanup.h>
#include <proto/exec.h>
#include <proto/cia.h>
#include <stdio.h>
#include <dos/dos.h>
#include <proto/exec.h>
#include "song.h"
#include "proto.h"
#include "player.h"
#include "public_play.h"
#include "int_play.h"

extern struct ExecBase *SysBase;

#ifdef LATTICE
#define FAR_SUPPORT __far
#else
#error
#endif
extern volatile struct CIA FAR_SUPPORT ciab;

struct BattMemBase *BattMemBase;
LOCAL CLEAN int_clean;

/* this is the logical interface structure with the interrupt
 * you send things to the interrupt through its data structure (play).
 * If you specify it, the interrupt will signal you when something
 * interesting happens (specifiy a task/signal mask...)
 *
 * In that case, you can check the play oflags to know what triggered
 * the signal. Don't forget to reset it yourself if you want to know
 * what's going on next time. Also, don't forget that the interrupt
 * won't stop for you, i.e., while you're processing that signal,
 * the interrupt might send other signals to you.
 * As a general rule, unless you disable/enable, don't expect any
 * field in the play structure to stay constant. That's generally not
 * a problem.
 */
LOCAL struct play *play;
LOCAL struct pub_play *public;
LOCAL struct priv_play *private;
LOCAL struct Interrupt *timerinterrupt;

LOCAL ULONG timerbase;
LOCAL ULONG current_tempo = 256, current_effect = 256;
LOCAL BOOL available = FALSE, running = FALSE;


LOCAL void latch_tempo(void)
        {
                Disable();
                public->timebase = timerbase*current_tempo/128;
                public->effectbase = timerbase*current_effect/128;
                private->tempo_change = TRUE;
                Enable();
        }


/* country dependency: the frequency of the timers is different
 * when you change countries. The precise values are listed
 * in the hardware manual.
 */
LOCAL void set_timerbase(void)
        {
                switch(SysBase->PowerSupplyFrequency)
                        {
                                case 50:
                                        timerbase = 709379;
                                        break;
                                case 60:
                                        timerbase = 715909;
                                        break;
                                default:
                                        break;
                        }
        }


LOCAL void timer_on(void)
        {
                *private->control |= CIACRAF_START;
        }


LOCAL void timer_off(void)
        {
                *private->control &= ~CIACRAF_START;
        }


/* if you want to modify play yourself, don't forget to Disable()/Enable()
 * all the changes, so that the interrupt routine will always see something
 * coherent. Don't forget to make it fast
 */

/* sine table for vibrato command */

BYTE sine_table[32] =
        {0,25,49,71,90,106,117,125,127,125,117,106,90,
         71,49,25,0,-25,-49,-71,-90,-106,-117,-125,-127,
         -125,-117,-106,-90,-71,-49,-25};



LOCAL void init_structures()
        {
        int i;
                play = AllocMem(sizeof(struct play), MEMF_PUBLIC | MEMF_CLEAR);
                if (play)
                        ToClean2(FreeMem, play, sizeof(struct play));
                else
                        mayPanic("Could not allocate data structure for replay");

                public = &(play->public);
                private = &(play->private);

                for (i = 0; i < 32; i++)
                        play->sine_table[i] = sine_table[i];
                private->sine_table = play->sine_table;
                for (i = 0; i < NUMBER_TUNING; i++)
                        {
                                play->period_table[i] = play->periods + NUMBER_NOTES * i;
                        }

                init_periods(play->period_table);
                private->period_table = play->period_table;
                for (i = 0; i < NUMBER_TRACKS; i++)
                        private->track[i] = play->tracks + i;

                private->channel_mask = play->channel_mask;
                private->setup = play->setup;
                private->state = init_player;


        /* don't need that */
/*              public->volume = 256;
 */
                public->command = STOP;
        /* done by the MEMF_CLEAR, but keep as comments that this is a good idea.
                public->resume = FALSE;
                public->info = NULL;
*/


                timerinterrupt = AllocMem(sizeof(struct Interrupt), MEMF_PUBLIC|MEMF_CLEAR);
                if (timerinterrupt)
                        ToClean2(FreeMem, timerinterrupt, sizeof(struct Interrupt));
                else
                        mayPanic("Could not allocate interrupt structure");

                timerinterrupt->is_Node.ln_Type = NT_INTERRUPT;
/*              timerinterrupt->is_Node.ln_Pri = 0;
 */
                timerinterrupt->is_Node.ln_Name = "player";
                timerinterrupt->is_Data = play;
                timerinterrupt->is_Code = &do_play;
        }

LOCAL void open_cia(void)
        {
                BattMemBase = OpenResource("ciab.resource");
                if (!BattMemBase)
                        mayPanic("Could not open ciab resource");
        }

LOCAL void init_interrupt(void)
        {
        int signal;
                set_timerbase();
                init_structures();
                open_cia();
                signal = AllocSignal(-1);
                if (signal == -1)
                        mayPanic("No signals available");
                else
                        ToClean(FreeSignal, signal);
                public->signal = 1<<signal;
                public->task = FindTask(0L);
        }


/* the install timer now puts everything back in the
 * state it should be
 */
LOCAL void install_timer(void)
        {
        int old;
                int_clean = AllocClean(NIL);
                old = AddICRVector(BattMemBase, CIAICRB_TB, timerinterrupt);
                if (!old)
                        {
                                ToClean3L(int_clean, RemICRVector, BattMemBase, CIAICRB_TB,
                                                timerinterrupt);
                                private->control = &(ciab.ciacrb);
                                private->latchlo = &(ciab.ciatblo);
                                private->latchhi = &(ciab.ciatbhi);
                        }
                else
                        {
                                old = AddICRVector(BattMemBase, CIAICRB_TA, timerinterrupt);
                                if (!old)
                                        {
                                                ToClean3L(int_clean, RemICRVector, BattMemBase, CIAICRB_TA,
                                                        timerinterrupt);
                                                private->control = &(ciab.ciacra);
                                                private->latchlo = &(ciab.ciatalo);
                                                private->latchhi = &(ciab.ciatahi);
                                        }
                                else
                                        mayPanic("Sorry, no timer available");
                        }
                ToClean0L(int_clean, timer_off);
                        /* keep the alarm running */
                *private->control &= CIACRBF_ALARM;
                latch_tempo();
        }


struct pub_play *obtain_player()
        {
                init_interrupt();
                return public;
        }

void start_timer(void)
        {
                if (!available)
                        {
                                install_timer();
                                timer_on();
                                available = TRUE;
                        }
        }

void stop_timer(void)
        {
                if (available)
                        {
                                timer_off();
                                CleanUp(int_clean);
                                public->resume = TRUE;
                                available = FALSE;
                        }
        }

/* ``standard'' frequency is 50Hz.
 * tempo is in 256th.
 */
void set_tempo(int tempo, int effect)
        {
                current_tempo = tempo;
                current_effect = effect;
                if (available)
                        latch_tempo();
        }

void set_volume(int new_value)
        {
                public->volume = new_value;
        }

void set_mode(int mode)
        {
                public->mode = mode;
        }

void setup_song(struct song *s)
        {
                Disable();
                public->command = STOP;
                public->pattern = 0;
                public->position = 0;
                public->info = s->info;
                public->sample = s->samples;
                public->resume = FALSE;
                Enable();
        }

void launch_play(int patt)
        {
                if (public->info)
                        {
                                Disable();
                                public->command = NEWPOS;
                                public->pattern = patt;
                                public->position = 0;
                                Enable();
                        }
        }
