/*      VU.C            VU Meter for the Sampler

        (c) 1987        Todor Fay

	AZTEC C version

*/

#include "exec/exec.h"
#include "exec/types.h"
#include "soundscape.h"
#include "intuition/intuition.h"

/*      Images for the LEDs.  A red LED for lighting, 
        a blue LED for turning it back off 
        (the background color.)
*/

UWORD redleddata[] = {        /* 16 x 3 */ 
65535,        
65535,        
65535,        
65535,        
65535,        
65535,        
};

struct Image redledimage = { 0,0,16,3,2,redleddata,3,0,0 };

UWORD blueleddata[] = {        /* 16 x 3 */ 
0,        
0,        
0,        
0,        
0,        
0,        
};

struct Image blueledimage = { 0,0,16,3,2,blueleddata,3,0,0 };

/*      We don't need any Intuition Gadgets, just a 
        window to display the LEDs in.
        This was designed with Power Windows.
*/

struct NewWindow NewWindowStructure = {
        0,0,
        250,20,
        0,1,
        NULL,
        WINDOWDRAG+WINDOWDEPTH,
        NULL,
        NULL,
        "VU Meter",
        NULL,
        NULL,
        5,5,
        0,0,
        WBENCHSCREEN
};

short thisport;                     /* This port's id. */
long SoundScapeBase;                /* SoundScape library. */
long IntuitionBase;

/*      We need a place to store the Sampler's
        output routine address, since we are replacing
        the code pointer in its port structure
        with our own.
*/

void (*sampleroutcode) ();
struct Window *vuwindow;        /* Display window. */

/*      We need an array of velocity values for each
        note that is being played.  These are zeroed
        for no note. 
        If you add up all the velocities in this 
        array, you should get totalvolume, which
        is used to determine the number of LEDs to
        light.
*/

unsigned char velocity[128];        
unsigned short totalvolume;

insertoutcode(note)

/*      This output routine is not given to the Patch
        Panel in the AddMidiPort call.  Instead, this
        routine replaces the Sampler output code, and
        then calls it.  So, this is inserted there by
        the open routine.

        Check the note status.  If it is NOTEON or NOTEOFF,
        we want to use it for the display.

        Start by decrementing the totalvolume by 
        velocity[note->value].  If this is a NOTEOFF
        event, this reduces totalvolume appropriately.
        If this is a NOTEON event, this clears any
        previous NOTEON that was stored.  This is
        neccessary because we aren't supporting multiple
        playing of the same note (for simplicity.)

        If this is a NOTEON event, increment totalvolume
        by the velocity of this note.  Store this
        velocity in the velocity array.  

        Else, for a NOTEOFF event, simply put zero in
        the velocity array - there is no note being
        played.

        Compute how many LEDs should be on from 
        totalvolume.  Each additional LED represents
        a doubling in volume, so figure what the most
        significant bit of totalvolume is.

        If the new top LED position (newvupos) is
        greater than the currently displayed one
        (vupos), draw in the extra LEDs.  Else,
        if newvupos is less than vupos, erase 
        LEDs down to newvupos.

        Finally, forward the note event to the 
        Sampler by calling its output routine.
*/
        
struct Note *note;

{
    unsigned char status = note->status & 0xF0;
    static short vupos = 0;
    short newvupos, index;
    if (sampleroutcode) {
        if ((status == NOTEON) || (status == NOTEOFF)) {
            totalvolume -= velocity[note->value];
            if ((status == NOTEON) && note->velocity) {
                totalvolume += note->velocity;
                velocity[note->value] = note->velocity;
            }
            else velocity[note->value] = 0;
            newvupos = 0;
            index = 4;
            for (;index < totalvolume; index = index << 1)
                 newvupos++;
            if (newvupos < vupos) {
                for (;vupos > newvupos; vupos--) 
                    DrawImage(vuwindow->RPort,
                        &blueledimage,-10 + vupos * 20,13);
            }
            else if (newvupos > vupos) {
                vupos++;
                for (;vupos <= newvupos; vupos++) 
                    DrawImage(vuwindow->RPort,
                        &redledimage,-10 + vupos * 20,13);
            }
            vupos = newvupos;
        }
        (*sampleroutcode) (note);
    }
    else FreeNode(note);
}

outcode(note)

/*      This, the outcode for this module, does
        nothing.  
*/

struct Note *note;

{
    enteraztec();
    FreeNode(note);
    leaveaztec();
}


opencode(direction)

/*      This installs the VU meter.
        
        If this is to install the output routine:

        Get the Port structure for the Sampler.
        If the Sampler already has its output
        code stolen, (samplerport->show has somebody's
        port id) do nothing.  Else, open the window,
        give samplerport->outcode insertoutcode 
        and put the Sampler's output code
        in samplercode.  Set samplerport->show to
        this port's id, so no other module will
        monkey with it.
*/

char direction;

{
    short i;
    struct Port *samplerport;
    enteraztec();
    if (!direction) {
        samplerport = (struct Port *) 
            MidiPort(FindMidiPort("sampler"));
        if (samplerport) {
            if (samplerport->show) {
                CloseMidiPort(samplerport->show,0);
            }
            if (!samplerport->show) {
                vuwindow = (struct Window *) 
                    OpenWindow(&NewWindowStructure);
                if (vuwindow) {
                    totalvolume = 0;
                    for (i=0; i < 128; i++) 
                        velocity[i] = 0;        
                    sampleroutcode = samplerport->outcode;
                    samplerport->outcode = insertoutcode;
                    samplerport->show = thisport;
		    leaveaztec();
                    return(1);
                }
            }
        }
    }
    leaveaztec();
    return(0);
}


closecode(direction)

/*      This deactivates the VU Meter.

        Simply return the Sampler's output routine 
        to its Port structure and clear 
        samplerport->show.  Close the display
        window.
*/

{ 
    struct Port *samplerport;
    enteraztec();
    if (!direction) {
        samplerport = (struct Port *) 
            MidiPort(FindMidiPort("sampler"));
        if (samplerport && sampleroutcode) {
            samplerport->outcode = sampleroutcode;
            samplerport->show = 0;
        }
        if (vuwindow) CloseWindow(vuwindow);
        vuwindow = 0;
    }
    leaveaztec();
    return(1);
}

/*      The main program is standard with one addition:
        After installing this port, call OpenMidiPort
        to activate it.  This is needed because there
        is no icon for the user to click on.  We
        could have added that icon, but it would be
        one more item on the Patch Panel, cluttering
        it and causing some confusion since nothing
        can be sent directly to it.
        There is a chance OpenMidiPort will fail.  This
        is because the Sampler's output routine was
        already stolen.  If so, there is no need to
        stick around, so call RemoveMidiPort and leave.
        Normally, RemoveMidiPort is done by SoundScape
        when it closes down, but this time we'll
        do it ourselves to get out early.
*/

main() {
    IntuitionBase = OpenLibrary("intuition.library",0);
    SoundScapeBase = OpenLibrary("soundscape.library",0);
    if (SoundScapeBase) {
        CloseLibrary(SoundScapeBase);
        thisport = AddMidiPort(opencode,closecode,0,
            outcode,0,0,-1,"vu meter");
        if (OpenMidiPort(thisport,0)) {
            SetTaskPri(FindTask(0),-20);
            while (MidiPort(thisport)) Delay(100);
        }
        else RemoveMidiPort(thisport);
    }
    CloseLibrary(IntuitionBase);
}
