/*-----------------------------------------------------------------------------

Volume.c v1.0  22.06.98 Matthew Hampton;
           
-----------------------------------------------------------------------------*/


#include <muistd.h>
#include <exec/exec.h>
#include "extproc.h"
#include "waveeditclass.c"

#define ID_ENV      1   /*keep things simple, use return id's*/
#define ID_STEN     2
#define ID_MAX      3
#define ID_HALF     4
#define ID_DOUBLE   5
#define ID_OFFSET   6
#define ID_ADJ      7
#define ID_ADDP     8
#define ID_DELP     9
#define ID_EXIT     10

void Set_Vol(BYTE *, ULONG, LONG, LONG, ULONG, UBYTE);    /*functions for altering volume*/
void Env_Vol(BYTE *, ULONG, APTR, ULONG, UBYTE);
void Max_Vol(BYTE *, ULONG, ULONG, UBYTE);
void OffSet (BYTE *, ULONG, WORD, ULONG, UBYTE);
void Adjust (BYTE *, ULONG, ULONG, UBYTE);
void cleanup(void);     /*quit function*/
BOOL Put_Message(void); /*put message function*/



SAVEDS ASM LONG Set_Env(REG(a1) VOID **addrs)   /*These two hooks link the start / end sliders with*/
{                                               /*the begining and end of the envelope*/
    APTR slider = *addrs;
    addrs++;
    APTR env = *addrs;
    addrs++;
    ULONG num = (ULONG)*addrs;
    ULONG lev;

    if (num)
    {
        get(env,MUIA_Envelope_Used,&num);
        num--;
    }

    nnset(env,MUIA_Envelope_Current,num);
    get(slider,MUIA_Numeric_Value,&lev);
    lev = lev *65535 /200;
    nnset(env,MUIA_Envelope_Level,lev);
}

SAVEDS ASM LONG Set_Sliders(REG(a1) VOID **addrs)
{
    APTR slider1 = *addrs;
    addrs++;
    APTR slider2 = *addrs;
    addrs++;
    APTR env = *addrs;
    ULONG num, used, lev;

    get(env,MUIA_Envelope_Current,&num);
    get(env,MUIA_Envelope_Used,&used);
    get(env,MUIA_Envelope_Level,&lev);
    lev = lev *200 /65536;

    if (num == 0)
        nnset(slider1,MUIA_Numeric_Value,lev);

    if (num ==(used -1))
        nnset(slider2,MUIA_Numeric_Value,lev);
}
static struct Hook setenv_hook = {{NULL, NULL}, (void *)Set_Env, NULL, NULL};
static struct Hook setsliders_hook = {{NULL, NULL}, (void *)Set_Sliders, NULL, NULL};

static struct interface{
    APTR app;
    APTR win;

    APTR add;
    APTR del;
    APTR envel;
    APTR start;
    APTR end;
    APTR offset;
    APTR useenv;
    APTR usesten;
    APTR max;
    APTR half;
    APTR double;
    APTR offs;
    APTR adj;
    APTR exit;
    APTR gauge;
} App;
struct MUI_CustomClass *env_mcc;

struct MsgPort *mp, *port;  /*mp is our MsgPort, port is the destination port*/
struct Ext_Proc_Msg msg;
struct sam_info *sample;    /*pointer to a sam_info structure, for collecting data*/
struct Ext_Proc_ID id_data; /*structure used to send task info, for EPC_AUTQUIT etc.*/
BYTE signal;
ULONG button_num;



LONG main(int argc, char *argv[])
{
    if (argc <3) return(0);
    if (atol(argv[1]) != PROC_MAGIC) return(0); /*make sure SamEd launched us*/

    button_num = atol(argv[2]);

    init();

    /*create our message port*/
    if (!(mp = (struct MsgPort *)CreatePort(NULL,0)))
        cleanup();

    /*allocate a signal bit, for the auto quit function*/
    if (-1 == (signal = AllocSignal(-1)))
        cleanup();

    /*set up msg structure*/
    msg.epm_Msg.mn_Node.ln_Type = NT_MESSAGE;
    msg.epm_Msg.mn_ReplyPort = mp;
    msg.epm_Msg.mn_Length = sizeof(struct Ext_Proc_Msg);

    /*ask for auto quit function. later we will test for 'signal' which may tell us to quit*/
    msg.epm_Command = EPC_AUTOQUIT;
    msg.epm_Data = &id_data;
    id_data.task = FindTask(0);
    id_data.number = signal;
    if (!Put_Message())
        cleanup();

    /*create the MUI interface*/

    if (!(env_mcc = MUI_CreateCustomClass(NULL,MUIC_Area,NULL,sizeof(struct WaveData),WaveDispatcher)))
                fail(NULL,"Could not create custom class.");

    App.app = ApplicationObject,
    MUIA_Application_Title      , "Volume ExtProc",
    MUIA_Application_Version    , "$VER: 0.01 (8.6.98)",
    MUIA_Application_Copyright  , "©1998, Matthew Hampton",
    MUIA_Application_Author     , "Hampy",
    MUIA_Application_Description, "Change volume ExtProc for SamED",
    MUIA_Application_Base       , "VOLEP",

    SubWindow, App.win = WindowObject,
        MUIA_Window_Title, "Volume",
        MUIA_Window_ID   , MAKE_ID('V','O','L','U'),

        WindowContents, VGroup,
            Child, HGroup,
                Child, App.envel    =   NewObject(env_mcc->mcc_Class, NULL,
                    TextFrame,
                    MUIA_Waveform_Draw, WDM_LINE | WDM_HGRID | WDM_VGRID | WDM_ENVBLOCKS | WDM_ENVLINES,
                    MUIA_Waveform_EditMode, EM_ENV,
                    MUIA_Waveform_VLineGap, 10,
                    TAG_DONE),
                Child, VGroup, MUIA_Weight, 0,
                    Child, App.add  = SimpleButton("+"),
                    Child, App.del  = SimpleButton("-"),
                    Child, RectangleObject, MUIA_Rectangle_HBar, TRUE, End,
                    End,
                End,
            Child, ColGroup(2),
                Child, Label2("Start:"),
                Child, App.start    = Slider(0,200,100),
                Child, Label2("End:"),
                Child, App.end      = Slider(0,200,100),
                Child, Label2("OffSet:"),
                Child, App.offset   = Slider(-128,127,0),
                End,
            
            Child, ColGroup(4),
                Child, App.useenv   = SimpleButton("UseEnv"),
                Child, App.half     = SimpleButton("Half"),
                Child, App.offs     = SimpleButton("OffSet"),
                Child, App.max      = SimpleButton("Max"),
                Child, App.usesten  = SimpleButton("UseS/E"),
                Child, App.double   = SimpleButton("Double"),
                Child, App.adj      = SimpleButton("Adjust"),
                Child, App.exit     = SimpleButton("Exit"),
                End,
            End,
        End,
    End;

    if(!App.app) cleanup();

    /*using return id's instead of hooks will keep the code simpler*/
    DoMethod(App.useenv,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_ENV);
    DoMethod(App.usesten,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_STEN);
    DoMethod(App.max,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_MAX);
    DoMethod(App.half,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_HALF);
    DoMethod(App.double,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_DOUBLE);
    DoMethod(App.offs,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_OFFSET);
    DoMethod(App.adj,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_ADJ);
    DoMethod(App.add,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_ADDP);
    DoMethod(App.del,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_DELP);
    DoMethod(App.start,MUIM_Notify,MUIA_Numeric_Value,MUIV_EveryTime,App.app,5,MUIM_CallHook,&setenv_hook,App.start,App.envel,0);
    DoMethod(App.end,MUIM_Notify,MUIA_Numeric_Value,MUIV_EveryTime,App.app,5,MUIM_CallHook,&setenv_hook,App.end,App.envel,1);
    DoMethod(App.envel,MUIM_Notify,MUIA_Envelope_Level,MUIV_EveryTime,App.app,5,MUIM_CallHook,&setsliders_hook,App.start,App.end,App.envel);
    DoMethod(App.exit,MUIM_Notify,MUIA_Pressed,FALSE,App.app,2,MUIM_Application_ReturnID,ID_EXIT);
    DoMethod(App.win,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,App.app,2,MUIM_Application_ReturnID,ID_EXIT);

    /*open the window*/
    set(App.win,MUIA_Window_Open,TRUE);

    /*check for user inputs*/
    ULONG sigs = 0, ret = 0, quitsig = 1L << signal;    /*use signal bit number to make a mask*/
    while (TRUE)    /*repeat until break*/
    {
        /*update MUI*/
        ret = DoMethod(App.app,MUIM_Application_NewInput,&sigs);

        /*check signals for MUI, CTRL_C, and quitsig*/
        if (sigs)
        {
            sigs = Wait(sigs | SIGBREAKF_CTRL_C | quitsig);
            if (sigs & SIGBREAKF_CTRL_C) break;
            if (sigs & quitsig) break;
        }

        /*check for return id's*/
        if (ret)
        {
            if (ret == ID_EXIT) break;      /*quit if cancelled*/

            if (ret == ID_ADDP)
            {
                DoMethod(App.envel, MUIM_Envelope_AddPnt, NULL);
                continue;
            }
            if (ret == ID_DELP)
            {
                DoMethod(App.envel, MUIM_Envelope_DelPnt, NULL);
                continue;
            }

            msg.epm_Command = EPC_LOCK;     /*obtain the lock so we can alter data safely*/
            Put_Message();
            if (!msg.epm_Error)            /*check for any errors, eg. the system is already locked*/
            {
                msg.epm_Command = EPC_GETSAMPLE;    /*get pointer to current sam_info structure*/
                Put_Message();
                if (!msg.epm_Error)        /*make sure we have it, ie no errors*/
                {
                    sample = msg.epm_Data;  /*copy address to proper pointer*/

                    msg.epm_Command = EPC_PIOPEN;
                    Put_Message();
                    set(App.app, MUIA_Application_Sleep, TRUE);
                    
                    switch (ret)
                    {
                        case ID_ENV:
                            msg.epm_Command = EPC_PISTRING;
                            msg.epm_Data = "Setting volume to envelope...";
                            Put_Message();
                            Env_Vol(sample->waveform, sample->length, App.envel, sample->type, 0);
                            if (sample->type & 2)
                                Env_Vol(sample->waveform, sample->length, App.envel, sample->type, 1);
                            break;

                        case ID_STEN:       /*use start + end values*/
                            msg.epm_Command = EPC_PISTRING;
                            msg.epm_Data = "Setting volume, using start and end values...";
                            Put_Message();
                            ULONG start, end;
                            get(App.start, MUIA_Numeric_Value, &start);
                            get(App.end, MUIA_Numeric_Value, &end);
                            Set_Vol(sample->waveform, sample->length, start, end, sample->type, 0);
                            if (sample->type & 2)
                                Set_Vol(sample->waveform, sample->length, start, end, sample->type, 1);
                            break;

                        case ID_MAX:        /*maximise volume*/
                            Max_Vol(sample->waveform, sample->length, sample->type, 0);
                            if (sample->type & 2)
                                Max_Vol(sample->waveform, sample->length, sample->type, 1);
                            break;

                        case ID_HALF:       /*half volume*/
                            msg.epm_Command = EPC_PISTRING;
                            msg.epm_Data = "Halfing volume...";
                            Put_Message();
                            Set_Vol(sample->waveform, sample->length, 50, 50, sample->type, 0);
                            if (sample->type & 2)
                                Set_Vol(sample->waveform, sample->length, 50, 50, sample->type, 1);
                            break;

                        case ID_DOUBLE:     /*double volume*/
                            msg.epm_Command = EPC_PISTRING;
                            msg.epm_Data = "Doubling volume...";
                            Put_Message();
                            Set_Vol(sample->waveform, sample->length, 200, 200, sample->type, 0);
                            if (sample->type & 2)
                                Set_Vol(sample->waveform, sample->length, 200, 200, sample->type, 1);
                            break;

                        case ID_OFFSET:
                            msg.epm_Command = EPC_PISTRING;
                            msg.epm_Data = "Manual adjust...";
                            Put_Message();
                            LONG offset;
                            get(App.offset, MUIA_Numeric_Value, &offset);
                            OffSet(sample->waveform, sample->length, offset *256, sample->type, 0);
                            if (sample->type & 2)
                                OffSet(sample->waveform, sample->length, offset *256, sample->type, 1);
                            break;

                        case ID_ADJ:
                            Adjust(sample->waveform, sample->length, sample->type, 0);
                            if (sample->type & 2)
                                Adjust(sample->waveform, sample->length, sample->type, 1);
                            break;
                    }
                    

                    msg.epm_Command = EPC_PICLOSE;
                    Put_Message();
                    set(App.app, MUIA_Application_Sleep, FALSE);
                }
                msg.epm_Command = EPC_UNLOCK;   /*unlock the system*/
                while (!Put_Message());         /*make sure it is unlocked*/
            }
        }
    }
    cleanup();  /*quit*/
}


/*cleanup all system allocations, tell samed we have quit*/
void cleanup(void)
{
    /*tell SamEd we are quiting. We can send a pointer to our task even if EPC_AUTOQUIT failed*/
    msg.epm_Command = EPC_QUIT;
    msg.epm_Data = &id_data;
    id_data.task = FindTask(0);     /*send our task pointer to cancel auto quit*/
    id_data.number = button_num;    /*send our button number so it can be used again*/
    if(!Put_Message()) printf("could not find message port\n");

    /*when the message returns, clear up the port and delete*/
    if(mp)
    {
        while(GetMsg(mp));
        DeletePort(mp);
    }
    else printf("could not init. message port\n");

    /*deallocate the quit signal*/
    if(signal != -1)
        FreeSignal(signal);
    else printf("could not allocate signal\n");

    /*close the MUI application*/
    if (App.app)
    {
        set(App.win,MUIA_Window_Open,FALSE);
        MUI_DisposeObject(App.app);          /* dispose all objects. */
        MUI_DeleteCustomClass(env_mcc); /* delete the custom class. */
        fail(NULL,NULL);
    }
    else
        fail(App.app,"could not open interface (MUI)");
}


/*put message to port 'port' safely. wait for reply at port 'mp'. return true / false*/
BOOL Put_Message(void)
{
    Forbid();
    if (port = (struct MsgPort *)FindPort(EPMPORT_NAME))
        PutMsg(port, (struct Message *)&msg);
    Permit();
    if (port)
    {
        WaitPort(mp);
        while(GetMsg(mp));
    }
    return(port ? TRUE : FALSE);
}


/*set the volume to a percentage level at start + end*/
void Set_Vol(BYTE *waveform, ULONG length, LONG start, LONG end, ULONG type, UBYTE chan)
{
    if (!waveform || !length) return(); /*dont bother if there is no sample*/
    LONG level;

    length = Bytes_To_Sams (length, type);

    msg.epm_Command = EPC_PIMAX;
    msg.epm_Data = &length;
    Put_Message();

    for (ULONG n = 0; n < length; n++)
    {
        if (!(n & 16383))
        {
            msg.epm_Command = EPC_PIVAL;
            msg.epm_Data = &n;
            Put_Message();
            DoMethod(App.app,MUIM_Application_InputBuffered,NULL);
        }

        level = GET_LEVEL(waveform, n, type, chan) * (start *(length - n) /length);
        level += GET_LEVEL(waveform, n, type, chan) * (end *n /length);
        level /= 100;

        SET_LEVEL(waveform, n, level, type, chan);

    }
}


/*set the volume according to the defined envelope*/
void Env_Vol(BYTE *waveform, ULONG length, APTR envobj, ULONG type, UBYTE chan)
{
    if (!waveform || !length) return(); /*dont bother if there is no sample*/

    length = Bytes_To_Sams (length, type);

    msg.epm_Command = EPC_PIMAX;
    msg.epm_Data = &length;
    Put_Message();

    LONG level, used, x, nextx, lev, nextlev, n, prevoffset = 0, offset, nextoffset, seg_length, seg_offset, oldcurrent;
    get(envobj, MUIA_Envelope_Current, &oldcurrent);
    get(envobj, MUIA_Envelope_Used, &used);

    for(n = 0; n < (used -1); n++)
    {
        set(envobj, MUIA_Envelope_Current, n);
        get(envobj, MUIA_Envelope_X, &x);
        get(envobj, MUIA_Envelope_Level, &lev);
        set(envobj, MUIA_Envelope_Current, n+1);
        get(envobj, MUIA_Envelope_X, &nextx);
        get(envobj, MUIA_Envelope_Level, &nextlev);
        nextoffset = nextx /256.0 * length /256.0;
        offset = x /256.0 * length /256.0;
        seg_length = nextoffset - offset;
        seg_offset = offset - prevoffset;
        printf("%d\t%d\n",lev,nextlev);

        for (; offset < nextoffset; offset++, seg_offset++)
        {
            if (!(offset & 16383))
            {
                msg.epm_Command = EPC_PIVAL;
                msg.epm_Data = &offset;
                Put_Message();
                DoMethod(App.app,MUIM_Application_InputBuffered,NULL);
            }

            level = GET_LEVEL (waveform, offset, type, chan);
            
            level = level * (seg_length - seg_offset) /seg_length * lev / 32768;
            level = level + (GET_LEVEL (waveform, offset, type, chan)) * seg_offset / seg_length * nextlev / 32768;

            SET_LEVEL(waveform, offset, level, type, chan);
        }
        prevoffset = offset;
    }
    set(envobj, MUIA_Envelope_Current, oldcurrent);
}

/*scan and maximise volume*/
void Max_Vol(BYTE *waveform, ULONG length, ULONG type, UBYTE chan)
{
    if (!waveform || !length) return(); /*dont bother if there is no sample*/

    Adjust(waveform, length, type, chan); /*adjust first*/

    length = Bytes_To_Sams (length, type);

    msg.epm_Command = EPC_PIMAX;
    msg.epm_Data = &length;
    Put_Message();
    msg.epm_Command = EPC_PISTRING;
    msg.epm_Data = "Scanning...";
    Put_Message();

    LONG max =0, lev =0;

    for (ULONG n = 0; n < length; n++)
    {
        if (!(n & 16383))
        {
            msg.epm_Command = EPC_PIVAL;
            msg.epm_Data = &n;
            Put_Message();
            DoMethod(App.app,MUIM_Application_InputBuffered,NULL);
        }

        lev = GET_LEVEL(waveform, n, type, chan);
        if (lev < 0) lev = -lev;
        if (lev > max) max = (LONG)lev;
    }
    printf("Max:%ld, Lev:%ld\n", max, lev);

    if (!max) return();                 /*don't bother if the sample is silent*/

    msg.epm_Command = EPC_PISTRING;
    msg.epm_Data = "Maximising volume...";
    Put_Message();

    for (ULONG n = 0; n < length; n++)
    {
        if (!(n & 16383))
        {
            msg.epm_Command = EPC_PIVAL;
            msg.epm_Data = &n;
            Put_Message();
            DoMethod(App.app,MUIM_Application_InputBuffered,NULL);
        }

        SET_LEVEL(waveform, n, GET_LEVEL(waveform, n, type, chan) *32767 / max, type, chan);

        /*BYTE *ptr = waveform + n;
        double maximum = max, temp = *ptr;
        temp = temp *127;
        temp = temp / max;
        *ptr = temp;*/
    }
}


void OffSet(BYTE *waveform, ULONG length, WORD offset, ULONG type, UBYTE chan)
{
    length = Bytes_To_Sams (length, type);

    msg.epm_Command = EPC_PIMAX;
    msg.epm_Data = &length;
    Put_Message();

    for (ULONG n = 0; n < length; n++)
    {
        if (!(n & 16383))
        {
            msg.epm_Command = EPC_PIVAL;
            msg.epm_Data = &n;
            Put_Message();
            DoMethod(App.app,MUIM_Application_InputBuffered,NULL);
        }

        SET_LEVEL(waveform, n, GET_LEVEL(waveform, n, type, chan) - offset, type, chan);
    }
}


void Adjust(BYTE *waveform, ULONG length, ULONG type, UBYTE chan)
{
    length = Bytes_To_Sams (length, type);

    msg.epm_Command = EPC_PIMAX;
    msg.epm_Data = &length;
    Put_Message();
    msg.epm_Command = EPC_PISTRING;
    msg.epm_Data = "Scanning...";
    Put_Message();

    WORD max, min, offset, lev;

    for (ULONG n = 0; n < length; n++)
    {
        if (!(n & 16383))
        {
            msg.epm_Command = EPC_PIVAL;
            msg.epm_Data = &n;
            Put_Message();
            DoMethod(App.app,MUIM_Application_InputBuffered,NULL);
        }

        lev = GET_LEVEL(waveform, n, type, chan);
        if (lev > max) max = lev;
        if (lev < min) min = lev;
    }
    offset = (max + min) /2;
    printf("Max:%ld, Min:%ld, Offset:%ld\n", max, min, offset);

    msg.epm_Command = EPC_PISTRING;
    msg.epm_Data = "Adjusting...";
    Put_Message();
    OffSet(waveform, Sams_To_Bytes(length, type), offset, type, chan);
}

/*                                  THE END  ;)                                                     */
