/* $Header: d:/cvsroot/tads/html/htmlsnd.h,v 1.2 1999/05/17 02:52:22 MJRoberts Exp $ */

/* 
 *   Copyright (c) 1998 by Michael J. Roberts.  All Rights Reserved.
 *   
 *   Please see the accompanying license file, LICENSE.TXT, for information
 *   on using and copying this software.  
 */
/*
Name
  htmlsnd.h - sound support
Function
  
Notes
  
Modified
  01/10/98 MJRoberts  - Creation
*/

#ifndef HTMLSND_H
#define HTMLSND_H

#ifndef HTML_OS_H
#include "html_os.h"
#endif
#ifndef TADSHTML_H
#include "tadshtml.h"
#endif
#ifndef TADSHTML_H
#include "tadshtml.h"
#endif
#ifndef HTMLATTR_H
#include "htmlattr.h"
#endif


/* ------------------------------------------------------------------------ */
/*
 *   Sound queue entry.  Each entry in a sound queue is represented by one
 *   of these objects.  
 */
class CHtmlSoundQueueEntry
{
    friend class CHtmlSoundQueue;
    
public:
    CHtmlSoundQueueEntry(class CHtmlResCacheObject *res,
                         int repeat_count, int random_start,
                         HTML_Attrib_id_t seq,
                         int vol, double fade_in, double fade_out,
                         int crossfade_in, int crossfade_out);
    ~CHtmlSoundQueueEntry();

    /* get the sound resource */
    class CHtmlResCacheObject *get_res() const { return res_; }

    /* get the sequence type */
    HTML_Attrib_id_t get_sequence() const { return seq_; }

    /* get the repeat count remaining - zero indicates looping forever */
    int get_repeat_count() const { return repeat_count_; }

    /*
     *   Decrement the repeat count.  This should be called when we finish
     *   playing the sound to determine how many more times the sound
     *   should be played.  This routine returns true if we've exhausted
     *   the repeat count. 
     */
    int dec_repeat_count(int count)
    {
        /* if the repeat count is zero, we play forever */
        if (repeat_count_ == 0)
            return FALSE;

        /* decrement the counter; return true if it's reached zero */
        repeat_count_ -= count;
        return (repeat_count_ == 0);
    }

    /*
     *   Get the number of times the playback has been completed.  This
     *   returns the number of iterations that we've completed, as indicated
     *   by calls to dec_repeat_count().  
     */
    int get_play_count() const { return orig_repeat_count_ - repeat_count_; }

    /*
     *   Exhaust the repeat count in preparation for repeated playback.
     *   This should be called when the sound is scheduled to be played
     *   for its full number of repetitions.  We'll set the repeat count
     *   to 1 if it's non-zero, in which case when the sound has finished
     *   playing it will be evident that it doesn't need to be played
     *   again, or leave it at zero if it's zero. 
     */
    void exhaust_repeat_count()
    {
        /* if the repeat count is zero, we play forever */
        if (repeat_count_ == 0)
            return;

        /* set the counter to 1 to indicate that it has no more plays left */
        repeat_count_ = 1;
    }

    /* get the random start probability */
    int get_random_start() const { return random_start_; }

    /*
     *   Mark the sound for cancellation.  When the end-of-sound callback
     *   fires, we'll remove the sound from its queue. 
     */
    void cancel() { cancel_ = TRUE; }

    /* check if the sound has been cancelled */
    int is_cancelled() const { return cancel_; }

    /* get the base volume (0-100) */
    int get_volume() const { return vol_; }

    /* get the fade durations */
    double get_fade_in() const { return fade_in_; }
    double get_fade_out() const { return fade_out_; }

    /* get the crossfade parameters */
    int get_crossfade_in() const { return crossfade_in_; }
    int get_crossfade_out() const { return crossfade_out_; }

    /* 
     *   Set a crossfade-out.  If we already have a fade, this sets the fade
     *   to the longer of the current fade and the new fade. 
     */
    void set_crossfade_out(double ms)
    {
        /* note that we have a crossfade on the fade-out */
        crossfade_out_ = TRUE;

        /* if the new fade is longer, use it as the fade time */
        if (ms > fade_out_)
            fade_out_ = ms;
    }

protected:
    /* this item's sound resource */
    class CHtmlResCacheObject *res_;

    /* sequencing type */
    HTML_Attrib_id_t seq_;

    /* repeat count - number of iterations remaining to be played */
    int repeat_count_;

    /* original repeat count */
    int orig_repeat_count_;

    /* random start probability */
    int random_start_;

    /* next and previous items in my queue */
    class CHtmlSoundQueueEntry *next_;
    class CHtmlSoundQueueEntry *prev_;

    /* volume level (0-100, 0=silence, 100=unattenuated) */
    int vol_;

    /* fade times */
    double fade_in_;
    double fade_out_;

    /* crossfade flags */
    int crossfade_in_ : 1;
    int crossfade_out_ : 1;

    /* flag indicating that the sound is to be cancelled */
    int cancel_ : 1;
};


/* ------------------------------------------------------------------------ */
/*
 *   Sound queue.  
 */

class CHtmlSoundQueue
{
public:
    /*
     *   Create a sound queue.  The queue is associated with the given
     *   system window. 'bgqueue' is the next queue further in the
     *   background; whenever we play a sound, we will tell each queue
     *   behind me about it so that they have a chance to suspend their
     *   playback if the sound device requires exclusive access.  At the
     *   end of each sound, we'll inform each background queue so that
     *   they can resume playback if they suspended a sound for us.  
     */
    CHtmlSoundQueue(class CHtmlSysWin *win, CHtmlSoundQueue *bgqueue);

    ~CHtmlSoundQueue();

    /* delete all entries in the queue */
    void clear();

    /* 
     *   Add an entry to the queue.
     *   
     *   'repeat' is the number of times to repeat the sound.  0 indicates
     *   that the sound is to be looped indefinitely.
     *   
     *   'random_start' is the random start probability.  0 indicates that
     *   the sound is to be played without randomization.  A value from 1 to
     *   100 is the probability that the sound will be started at any given
     *   time that the queue is idle.  Each time a sound is played, if the
     *   repeat count is one, we'll remove the sound from the queue, and if
     *   the repeat count is higher than one, we'll decrement it.
     *   
     *   'seq' is the sequence code.  HTML_Attrib_replace indicates that any
     *   previous sounds in the queue should be removed once this sound
     *   plays.  HTML_Attrib_random indicates that other sounds should be
     *   kept, and a new sound from the queue chosen randomly after this
     *   sound has been played.  HTML_Attrib_cycle indicates that other
     *   sounds should be kept; after this sound is finished playing, we
     *   should move on to the next sound, or back to the first sound in the
     *   queue if nothing's left.
     *   
     *   'fade_in' and 'fade_out' are the start-of-track and end-of-track
     *   fade intervals, in seconds.  A fade time of 0.0 means that the track
     *   simply starts and stops at full volume.  Non-zero fade times mean
     *   that we fade smoothly in and/or out over the given intervals.  
     */
    void add_sound(class CHtmlResCacheObject *res, int repeat_count,
                   int random_start, HTML_Attrib_id_t seq,
                   int vol, double fade_in, double fade_out,
                   int crossfade_in, int crossfade_out);

    /*
     *   Immediately stop the sound currently playing.  Do not start a new
     *   sound.  This halts the queue so that a transition can be made, such
     *   as clearing out all current music and starting a new set of music.
     *   
     *   If sync is true, we must not return until the sound has been stopped
     *   and the callback has been invoked.  Normally, we'll schedule the
     *   sound for cancellation and return; the sound may not actually be
     *   stopped by the time the function returns.  If sync is true, however,
     *   we won't return until after the callback has been invoked.
     *   
     *   If fade_out is non-zero, the sounds should be faded out gradually
     *   over the given interval in seconds.
     *   
     *   If fade_in_bg is true, the fade (if any) should occur in the
     *   background: this means that the current track continues to play for
     *   the duration of the fade, but is "detached" from the queue
     *   immediately, allowing the next track in the queue to start playing
     *   concurrently with the old track's fade.  If fade_in_bg is false, the
     *   current track will simply continue playing for the duration of the
     *   fade, and will then be stopped and removed from the queue as soon as
     *   the fade ends (or as soon as the track itself ends, if that happens
     *   earlier).  
     */
    void stop_current_sound(int sync, double fade_out, int fade_in_bg);

    /*
     *   Receive notification that a queue further in the foreground is
     *   about to start a sound.  If we're playing back a sound that uses
     *   the same device, and playback requires exclusive access to the
     *   device (whether this is true or not is system-dependent), we will
     *   suspend our playback until we hear that the foreground sound is
     *   finished.  The end-of-sound callback is not called here no matter
     *   what happens, because we're only suspending playback rather than
     *   cancelling playback.  
     */
    void start_fg_sound(class CHtmlSysSound *snd);

    /*
     *   Receive notification that a foreground sound is terminating.  If
     *   we suspended a sound on a previous call to start_fg_sound, we
     *   should resume playback now. 
     */
    void end_fg_sound(CHtmlSysSound *snd);
    
protected:
    /* create a new queue entry and add it at the end of the queue */
    void add_queue_entry(class CHtmlResCacheObject *res,
                         int repeat_count, int random_start,
                         HTML_Attrib_id_t seq,
                         int vol, double fade_in, double fade_out,
                         int crossfade_in, int crossfade_out);

    /* remove a queue entry from the queue and delete it */
    void delete_queue_entry(class CHtmlSoundQueueEntry *entry);

    /* process a timer callback */
    void on_timer();

    /* process an end-of-sound event */
    void on_sound_done(int repeat_count);

    /*
     *   Check to see if we need to start a new sound.  This routine is
     *   invoked whenever we add a new sound and nothing else is playing,
     *   whenever we end a sound, and at timer intervals when nothing else
     *   is playing. 
     */
    void maybe_start_sound();

    /* 
     *   Timer callback function.  When we're created, we register with
     *   our associated window to receive periodic timer callbacks.  This
     *   is the function invoked by the window for these callbacks.  
     */
    static void timer_func_cb(void *ctx);

    /*
     *   End-of-sound callback function.  When we play a sound, we'll tell
     *   the sound to call this function when it's finished playing. 
     */
    static void sound_done_cb(void *ctx, int repeat_count);

    /* 
     *   The window I'm associated with.  A sound queue is always
     *   associated with a window; the window provides access to the
     *   system services that allow us to play sounds. 
     */
    class CHtmlSysWin *win_;

    /* next queue in background behind me */
    CHtmlSoundQueue *next_bg_queue_;

    /* list of sound queue entries */
    class CHtmlSoundQueueEntry *first_entry_;
    class CHtmlSoundQueueEntry *last_entry_;

    /* 
     *   Current queue entry that's playing.  If the queue is currently
     *   idle (i.e., no sound is currently playing), this member is null. 
     */
    class CHtmlSoundQueueEntry *playing_;

    /*
     *   Previous sound that we played.  We use this for sequencing to
     *   determine the next sound to play. 
     */
    class CHtmlSoundQueueEntry *last_played_;

    /*
     *   Current sequence mode.  The sequence mode is selected by the last
     *   sound we played back. 
     */
    HTML_Attrib_id_t seq_mode_;

    /*
     *   Foreground sound for which we're suspended.  If the system
     *   requires that a particular type of sound be played exclusively
     *   (i.e, the sound can't play simultaneously with any other sounds
     *   of certain types), we'll suspend our sound whenever a queue
     *   further in the foreground needs to start a sound and we're
     *   playing an incompatible sound.  This member records the
     *   foreground sound that suspended us so that we can resume playback
     *   when the foreground sound ends. 
     */
    class CHtmlSysSound *fg_suspender_;

    /* system time of last timer event */
    os_timer_t last_timer_event_time_;
};

/* ------------------------------------------------------------------------ */
/*
 *   Sound loader.  The sound loaders are very simple; they merely record
 *   the file name, start seek offset of the sound data, and length of the
 *   sound data.  The system sound objects are responsible for reading the
 *   data.
 *   
 *   Note that we don't read the data into memory, because most systems
 *   will want to stream sounds off of disk rather than loading them all
 *   at once.  Sound files tend to be quite large, so it's usually more
 *   efficient to stream a sound off of disk as needed during playback
 *   than to keep the entire file loaded throughout playback.  
 */

class CHtmlSound
{
public:
    CHtmlSound()
    {
        seekpos_ = 0;
        filesize_ = 0;
    }
    
    ~CHtmlSound() { }

    /*
     *   Set file information.  This merely records the file information
     *   for later use. 
     */
    void set_file(const textchar_t *fname, unsigned long seekpos,
                  unsigned long filesize)
    {
        fname_.set(fname);
        seekpos_ = seekpos;
        filesize_ = filesize;
    }

    /* get the file information */
    const textchar_t *get_fname() const { return fname_.get(); }
    unsigned long get_seekpos() const { return seekpos_; }
    unsigned long get_filesize() const { return filesize_; }

protected:
    CStringBuf fname_;
    unsigned long seekpos_;
    unsigned long filesize_;
};

/*
 *   MIDI sound loader - trivial subclass of generic sound loader
 */
class CHtmlMidi: public CHtmlSound
{
public:
    CHtmlMidi() { }

    /* read a MIDI file */
    int read_midi_file(const textchar_t *fname, unsigned long seekpos,
                       unsigned long filesize)
    {
        /* remember the file information */
        set_file(fname, seekpos, filesize);
        return 0;
    }
};

/*
 *   WAV sound loader - trivial subclass of generic sound loader 
 */
class CHtmlWav: public CHtmlSound
{
public:
    CHtmlWav() { }
    
    /* read a WAV file */
    int read_wav_file(const textchar_t *fname, unsigned long seekpos,
                      unsigned long filesize)
    {
        /* remember the file information */
        set_file(fname, seekpos, filesize);
        return 0;
    }
};

/*
 *   MPEG 2.0 audio sound loader - trivial subclass of generic sound
 *   loader 
 */
class CHtmlMpeg: public CHtmlSound
{
public:
    CHtmlMpeg() { }

    /* read an MPEG file */
    int read_mpeg_file(const textchar_t *fname, unsigned long seekpos,
                       unsigned long filesize)
    {
        /* remember the file information */
        set_file(fname, seekpos, filesize);
        return 0;
    }
};

/*
 *   Ogg Vorbis sound loader 
 */
class CHtmlOgg: public CHtmlSound
{
public:
    CHtmlOgg() { }

    /* read an .ogg file */
    int read_ogg_file(const textchar_t *fname, unsigned long seekpos,
                      unsigned long filesize)
    {
        /* remember the file information */
        set_file(fname, seekpos, filesize);
        return 0;
    }
};

#endif /* HTMLSND_H */

