/* FXDEMO
 * A simple example for an EFFECTSPROCESSOR xapp application
 * Author : Dominique Lorre
 * $Id$
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/semaphores.h>
#include <intuition/gadgetclass.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/gadtools.h>
#include <clib/macros.h>

#include "/xappmsg.h"
//#include "mydebug.h"


struct FXNode*      n ;
struct List*        l ;
struct Window*      xappwin ;
struct Gadget*      glist ;
struct Gadget*      gad ;
APTR                vi ;
struct Screen       *ps ;
BOOL                winopen ;

struct MsgPort*    MyPort ;
struct MsgPort*    XAppPort ;
struct MsgPort*    replyPort ;

struct Hook        FXProcessor ;
char                MyPortName[XAPPNAMELENGTH] ;
APTR                xHandle ;

struct TagItem ti[] = {
    {XAPP_Name,     (ULONG)"FX-DEMO"},
    {XAPP_PortName, (ULONG)MyPortName},
    {XAPP_Type,     (ULONG)XAPPTYP_EFFECTSPROCESSOR},
    {FX_Hook,       (ULONG)&FXProcessor},
    {TAG_DONE}
};

struct TagItem th[] = {
    {XAPP_Handle,   NULL},
    {TAG_DONE}
};

struct fxDemoData {
    struct SignalSemaphore *fxd_Semaphore ;
    LONG                    fxd_Len ;
    WORD                    fxd_Repeat ;
    WORD                    fxd_Variation ;
    WORD                    fxd_Volume ;
    BOOL                    fxd_ByPass ;
} fData ;

struct SignalSemaphore *MySemaphore ;

static ULONG winsig, msig, signal ;
BOOL fin  ;
#define HIGHPRI 21

struct XAppMsg  *xmsg, *replymsg, *emsg ;

extern __stdargs ULONG HookEntry() ;

VOID main(VOID) ;
BOOL InitAll(void) ;
BOOL CloseAll(WORD level) ;
void ProcessEvents(void) ;
BOOL InitWin(void) ;
BOOL CloseWin(WORD level) ;
void ProcessWinEvents(void) ;
VOID StripWindow(struct Window *win) ;
VOID StripIntuiMessages( struct MsgPort *mp, struct Window *win ) ;
VOID StripMessages( struct MsgPort *mp ) ;

ULONG __saveds __asm FX(register __a0 struct Hook *h, 
                        register __a2 APTR Object, 
                        register __a1 struct FXParams *fp) ;

/****** fxdemo.c/FX ********************************************************
*
*   NAME
*   FX -- Hook function for effects processing
*
*   SYNOPSIS
*   result = FX(h, Object, fp)
*   ULONG __stdargs FX(struct Hook *, APTR, struct FXParams *)
*
*   FUNCTION
*   This is the Hook function that will be invoked by Euterpe when processing
*   events for effects.
*
*   INPUTS
*   h       The Hook initialized by this module
*   Object  Currently NULL
*   fp      A FXParams structure described in xappmsg.h
*
*   RESULTS
*   result  1 if everything ok and 0 if failure (other effects will be 
*   skipped)
*
*   NOTES
*   All the new events have to be placed on the same List, the best way
*   to do this is to use exec.library/AddHead(). Do NOT expect the list
*   to be sorted by time since effects might be chained. This function
*   will be called in the heart of a realtime process running at a high
*   priority (actually 21), so slow functions involving disk access, 
*   keyboard input or the like must be invoked externally, by signalling.
*   If your window is providing parameters, a Semaphore protection
*   will be needed.
*   NEVER EVER INSERT NOTEOFF EVENTS IN THE LIST
*   Inserting a NoteOff is a bad idea because these events must be linked to 
*   the correct NoteOn. For the control of NoteOff, use the x_Len field which 
*   set the duration of a NoteOn and the x_Data[3] field which set the 
*   velocity of the released note.
*   NoteOff events will be inserted by Euterpe at the end of the FX parsing.
*   NoteOn with x_Len = 0 are subject to crashes
*   DO NOT EXPECT TO UNDERSTAND ALL EVENTS
*   Events will be documented in the future because some are really special 
*   such as tempochanges. For allowing your effect to work in the future, just
*   keep the unknowned events unchanged. You can of course modify their pos
*   but timesig events might be moved by Euterpe at the beginning of a 
*   measure.
*   DO NOT MAKE ANY ASSUMPTIONS ON WHAT WILL BE DONE WITH THIS EFFECT
*   If you can. Because the effects might be modified in the future
*   to operate on input flow, to write themselves to a track or even to be 
*   lended as services to other tasks.
*   SEE ALSO
*   utility/hooks.h utility.library/CallHookPkt
****************************************************************************
*
*/

ULONG __saveds __asm FX(register __a0 struct Hook *h, 
                        register __a2 APTR Object, 
                        register __a1 struct FXParams *fp)
{
struct FXNode       *n, *c, *s ;
struct fxDemoData   *fxd ;
UBYTE type ;
LONG    val, r ;

    fxd = (struct fxDemoData *)h->h_Data ;

    // Semaphore protection is a must if you want to allow the user
    // to modify the parameters while playing.

    ObtainSemaphore(fxd->fxd_Semaphore) ;

    if (!IsListEmpty((struct List *)fp->fp_List)) {

        // if we are the first effect the list will contain one event
        // the linked effects will found what has been put by their
        // predecessors

        n = (struct FXNode *)fp->fp_List->mlh_Head ;
        while (n->x_Node.mln_Succ) {
            s = (struct FXNode *)n->x_Node.mln_Succ ;
            if (!fxd->fxd_ByPass) {
                type = n->x_Data[0] & 0xF0 ;
                // This is a noteon
                if (type == 0x90) {
                    // Note transposition
                    val = n->x_Data[1] + fxd->fxd_Variation ;
                    n->x_Data[1] = (val > 0) ? MIN(val, 0x7F) : 0 ;
        
                    for (r = 0; (r < fxd->fxd_Repeat); r++) {

                        // Since we are in a RealTime application
                        // we do not lose time by doing some garbage
                        // collection. This code is safe because the array
                        // will be reset by Euterpe when a new event will
                        // appear
        
                        fp->fp_Pos++ ;
                        c = (fp->fp_Pos < fp->fp_Size) ? &(fp->fp_Array[fp->fp_Pos]) : NULL ;

                        if (c) { // We have the new event node
                            c->x_Msg = n->x_Msg ; // fast copy of the data

                            if (fxd->fxd_Len>1) {
                                c->x_Pos = n->x_Pos + fxd->fxd_Len ;
                                c->x_Len = n->x_Len ;
                                if (n->x_Len > fxd->fxd_Len) {
                                    n->x_Len = fxd->fxd_Len-1 ; // len > 0 !
                                }
                            }
                            // Volume handling
                            if (fxd->fxd_Volume > 0) {
                                val = c->x_Data[2] * (fxd->fxd_Volume+1) ;
                                c->x_Data[2] = MIN(val, 0x7F) ;
                                val = c->x_Data[3] * (fxd->fxd_Volume+1) ;
                                c->x_Data[3] = MIN(val, 0x7F) ;
                            }
                            else if (fxd->fxd_Volume < 0) {
                                c->x_Data[2] /= -fxd->fxd_Volume+1 ;
                                c->x_Data[3] /= -fxd->fxd_Volume+1 ;
                            }
                            // Add to list
                            // Keeping the list sorted might allow quicker
                            // effects if the sorting operation is really fast
                            AddHead((struct List *)fp->fp_List, (struct Node *)c) ;
                        }
                    }
                }
                
            }
            n = s ;
        }
    }
    ReleaseSemaphore(fxd->fxd_Semaphore) ;
    return 1 ;
}

VOID main(VOID)
{
int retval ;

    if (InitAll()) {
        ProcessEvents() ;
        retval =  CloseAll(0)?RETURN_OK:RETURN_FAIL;
    }
    else
        retval = RETURN_FAIL ;
    exit(retval) ;
}

/****** fxdemo.c/InitAll ***************************************************
*
*   NAME
*   InitAll -- Main Initializations
*
*   SYNOPSIS
*   success = InitAll()
*   BOOL InitAll(void)
*
*   FUNCTION
*       InitAll allocates all needed resources step by step and is responsible
*       for the initialization of the communication with Euterpe. CloseAll will
*       be called in case of failure.
*
*   INPUTS
*       None
*
*   RESULTS
*       success     TRUE if the initialization succeed, FALSE if not
*
*   BUGS
*       This function has a weak point because it may fail if Euterpe is closing
*       when this task is loading. Forbid()/Permit() might solve the problem.
*       Anyway, under normal use effects will not be loaded when Euterpe is
*       exiting and developers have been asked not to use Forbid(). If 
*       someone has an idea...
*
*   SEE ALSO
*       fxdemo.c/CloseAll
****************************************************************************
*
*/
BOOL InitAll(void)
{
    // First, create a ReplyPort for communication with Euterpe

    replyPort = CreateMsgPort() ;
    if (!replyPort)
        return CloseAll(1) ;

    // Now, initialize the Message

    if (xmsg = (struct XAppMsg *)AllocVec(sizeof(struct XAppMsg), MEMF_CLEAR|MEMF_PUBLIC)) {
        xmsg->xm_Message.mn_Node.ln_Type = NT_MESSAGE ;
        xmsg->xm_Message.mn_Length = sizeof( struct XAppMsg ) ;
        xmsg->xm_Message.mn_ReplyPort = replyPort ;
    }
    else
        return CloseAll(2) ;

    // Our private semaphore

    if (MySemaphore = (struct SignalSemaphore *)AllocVec(sizeof(struct SignalSemaphore), MEMF_CLEAR|MEMF_PUBLIC)) {
        InitSemaphore(MySemaphore) ;
    }
    else
        return CloseAll(3) ;

    // Parameters initialization
    fData.fxd_Semaphore = MySemaphore ;
    fData.fxd_Repeat = 2 ;
    fData.fxd_Len = 192 ;
    fData.fxd_Volume = -2 ;
    fData.fxd_Variation = 12 ;
    fData.fxd_ByPass = FALSE ;

    // Hook initialization
    FXProcessor.h_Entry = (HOOKFUNC)FX ;
    FXProcessor.h_SubEntry = NULL ;
    FXProcessor.h_Data = &fData ;

    // Look at Euterpe's xapp port

    if (XAppPort = FindPort((UBYTE *)XAPPPORTNAME)) {
        // Euterpe is here, let's register
        xmsg->xm_Action = XAPPACT_ADD ;
        xmsg->xm_Tags = ti ;
        PutMsg(XAppPort, (struct Message *)xmsg);
        WaitPort(replyPort) ;
        replymsg = (struct XAppMsg *)GetMsg(replyPort) ;
        // Euterpe has given us a name for our port
        xHandle = replymsg->xm_Result ;
        if (!xHandle)
            return CloseAll(4) ;
        th[0].ti_Data = (ULONG)xHandle ;
    }
    else
        return CloseAll(4) ;

    // Now, create our Port with the name given by Euterpe 

    MyPort = CreatePort(MyPortName, NULL);

    // We are ready now, tell Euterpe how the init was

    xmsg->xm_Action = MyPort ? XAPPACT_INIT : XAPPACT_FAILURE ;
    xmsg->xm_Tags = th ;
    PutMsg(XAppPort, (struct Message *)xmsg);
    WaitPort(replyPort) ;
    replymsg = (struct XAppMsg *)GetMsg(replyPort) ;

    if (!MyPort)
        return CloseAll(5) ;

     msig = 1 << MyPort->mp_SigBit ;
     return TRUE ;
}
/****** fxdemo.c/CloseAll **************************************************
*
*   NAME
*   CloseAll -- Deallocates the resources
*
*   SYNOPSIS
*   success = CloseAll(level)
*   BOOL CloseAll(WORD)
*
*   FUNCTION
*       CloseAll() will deallocate all the successfully allocated resources
*       
*   INPUTS
*       level   0 if normal deallocation or the step where allocation failed
*
*   RESULTS
*       success TRUE if called with level=0 FALSE in other cases
*
****************************************************************************
*
*/

BOOL CloseAll(WORD level)
{
    switch(level) {
        case 0:
            DeletePort(MyPort) ;
        case 5:
        case 4:
            FreeVec(MySemaphore) ;
        case 3:
            FreeVec(xmsg) ;
        case 2:
            DeleteMsgPort(replyPort) ;
    }
    return (BOOL) ( level ? FALSE : TRUE ) ;
}

void ProcessEvents(void)
{
ULONG           sigmask ;
BOOL            end ;
enum XAppAction action ;

    end = winopen = FALSE ;

    while (!end) {
        sigmask = msig ;
        if (winopen) sigmask |= winsig ;
        signal = Wait(sigmask) ;
        if (signal & winsig) {
            // A better way of doing this could be to place the semaphore
            // calls at the beginning of each modification of the fData fields
            ObtainSemaphore(MySemaphore) ;
            ProcessWinEvents() ;
            if (!winopen)
                CloseWin(0) ;
            ReleaseSemaphore(MySemaphore) ;
        }
        if (signal & msig) {
            while (emsg = (struct XAppMsg *)GetMsg(MyPort)) {
                action = emsg->xm_Action ;
                // Do not reply to a remove msg
                // since Euterpe do not wait for it
                if (action != XAPPACT_REMOVE)
                    ReplyMsg((struct Message *)emsg) ;
                switch (action) {
                    case XAPPACT_SHOW:
                        if (!winopen && InitWin())
                            winopen = TRUE ;
                        break ;
                    case XAPPACT_REMOVE:
                        end = TRUE ;
                    case XAPPACT_HIDE:
                        if (winopen) {
                            CloseWin(0) ;
                        }
                        break ;
                }
            }
        }
    }
}

LONG __saveds VolumeLevel(struct Gadget *g,  WORD level)
{
static char s[30] ;
static char v[8] ;

    if (level < 0) {
        stci_d(v, -level+1) ;
        strcpy(s, "Divide Volume by ") ;
        strcat(s, v) ;
    }
    else if (level > 0) {
        stci_d(v, level+1) ;
        strcpy(s, "Multiply Volume by ") ;
        strcat(s, v) ;
    }
    else strcpy(s, "Keep Volume") ;
    return (LONG)s ;
}
        
BOOL InitWin(void)
{      
struct NewGadget ng ;
UWORD winZoom[4] = { 50, 50, 300, 20 } ;

    ps = LockPubScreen((UBYTE *)"Euterpe") ; // Get the Euterpe public screen
    if (!ps)
        return CloseWin(1) ;

    winZoom[0] = winZoom[3] = ps->BarHeight ;  // cosmetic details

    vi = GetVisualInfo(ps, NULL) ;
    if (!vi)
        return CloseWin(2) ;

    glist = NULL ;
    gad = CreateContext(&glist) ;
    if (!gad)
        return CloseWin(3) ;

    ng.ng_VisualInfo = vi ;
    ng.ng_TextAttr = ps->Font ;

    ng.ng_TopEdge = ps->BarHeight + 8 ; // font adaptative method
    ng.ng_LeftEdge =  20 ;
    ng.ng_Width = 10 ;
    ng.ng_Height = ng.ng_TextAttr->ta_YSize * 3 / 2 ;
    ng.ng_GadgetID = 1 ;
    ng.ng_GadgetText = (UBYTE *)"ByPass" ;
    ng.ng_Flags = PLACETEXT_RIGHT ;

    gad  = CreateGadget(CHECKBOX_KIND, gad, &ng,
        GTCB_Checked,   fData.fxd_ByPass,
        TAG_DONE);

    if (!gad)
        return CloseWin(4);

    ng.ng_TopEdge += ng.ng_Height + 8 ;
    ng.ng_LeftEdge =  20 ;
    ng.ng_Width = 128 ;
    ng.ng_Height = 20 ;
    ng.ng_GadgetID = 2 ;
    ng.ng_Flags = PLACETEXT_RIGHT ;

    gad  = CreateGadget(SLIDER_KIND, gad, &ng,
    GTSL_Min,           -64,
    GTSL_Max,           64,
    GTSL_Level,         fData.fxd_Variation,
    GTSL_LevelFormat,   "Transposition : %ld",
    GTSL_MaxLevelLen,   20,
    GTSL_LevelPlace,    PLACETEXT_RIGHT,
    GA_RelVerify,       TRUE,
    TAG_DONE);
    if (!gad)
        return CloseWin(4);

    ng.ng_TopEdge += ng.ng_Height + 8 ;
    ng.ng_GadgetID = 3 ;

    gad  = CreateGadget(SLIDER_KIND, gad, &ng,
    GTSL_Min,           0,
    GTSL_Max,           192*4,
    GTSL_Level,         fData.fxd_Len,
    GTSL_LevelFormat,   "Delay Length : %ld",
    GTSL_MaxLevelLen,   19,
    GTSL_LevelPlace,    PLACETEXT_RIGHT,
    GA_RelVerify,       TRUE,
    TAG_DONE);
    if (!gad)
        return CloseWin(4);

    ng.ng_TopEdge += ng.ng_Height + 8 ;
    ng.ng_GadgetID = 4 ;
    ng.ng_Flags = PLACETEXT_RIGHT ;

    gad  = CreateGadget(SLIDER_KIND, gad, &ng,
    GTSL_Min,           0,
    GTSL_Max,           10,
    GTSL_Level,         fData.fxd_Repeat,
    GTSL_LevelFormat,   "Repeat : %ld times",
    GTSL_MaxLevelLen,   19,
    GTSL_LevelPlace,    PLACETEXT_RIGHT,
    GA_RelVerify,       TRUE,
    TAG_DONE);
    if (!gad)
        return CloseWin(4);

    ng.ng_TopEdge += ng.ng_Height + 8 ;
    ng.ng_GadgetID = 5 ;
    ng.ng_Flags = PLACETEXT_RIGHT ;

    gad  = CreateGadget(SLIDER_KIND, gad, &ng,
    GTSL_Min,           -8,
    GTSL_Max,           8,
    GTSL_Level,         fData.fxd_Volume,
    GTSL_LevelFormat,   "%s",             /* Did you knew this trick ? :) */
    GTSL_DispFunc,      VolumeLevel,
    GTSL_MaxLevelLen,   25,
    GTSL_LevelPlace,    PLACETEXT_RIGHT,
    GA_RelVerify,       TRUE,
    TAG_DONE);
    if (!gad)
        return CloseWin(4);

    /* Open a simple window */
    xappwin = OpenWindowTags(NULL,
    WA_Left,            50,
    WA_Top,             ps->BarHeight,
    WA_Width,           300,
    WA_Height,          ng.ng_TopEdge + ng.ng_Height + 8, // font adaptative
    WA_Title,           MyPortName,
    WA_CloseGadget,     TRUE,
    WA_DepthGadget,     TRUE,
    WA_Gadgets,         glist,
    WA_IDCMP,           IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW|
                        CHECKBOXIDCMP|SLIDERIDCMP,
    WA_DragBar,         TRUE,
    WA_Activate,        TRUE,
    WA_PubScreen,       ps,
    WA_Zoom,            winZoom,
    TAG_DONE) ;

    if (xappwin) {
        GT_RefreshWindow(xappwin, NULL) ;
        winsig = 1 << xappwin->UserPort->mp_SigBit ;
        return TRUE ;
    }
    else
        return CloseWin(5) ;

}

BOOL CloseWin(WORD level)
{
    winopen = FALSE ;
    switch(level) {
    case 0:
        StripWindow(xappwin) ;
        CloseWindow(xappwin) ;
        xappwin = NULL ;
        winsig = 0 ;
    case 5:
    case 4:
        FreeGadgets(glist) ;
    case 3:
        FreeVisualInfo(vi) ;
    case 2:
        UnlockPubScreen(NULL, ps) ;
    }
    return (BOOL) ( level ? FALSE : TRUE ) ;
}

void ProcessWinEvents(void)
{
ULONG                   classe ;
UWORD                   code ;
struct IntuiMessage*    imsg ;
struct Gadget*          gad ;

    while (imsg = GT_GetIMsg(xappwin->UserPort)) {
        classe = imsg->Class ;
        code = imsg->Code ;
        gad = (struct Gadget *)imsg->IAddress ;
        GT_ReplyIMsg(imsg) ;
        switch (classe) {
        case IDCMP_REFRESHWINDOW:
            GT_BeginRefresh(xappwin) ;
            GT_EndRefresh(xappwin, TRUE) ;
            break ;
        case IDCMP_GADGETUP:
            switch (gad->GadgetID) {
            case 1:
                fData.fxd_ByPass = code ;
                break ;
            case 2:
                fData.fxd_Variation = code ;
                break ;
            case 3:
                fData.fxd_Len = code ;
                break ;
            case 4:
                fData.fxd_Repeat = code ;
                break ;
            case 5:
                fData.fxd_Volume = code ;
                break ;
            }
            break ;
        case IDCMP_CLOSEWINDOW:
            winopen = FALSE ;
            break ;
        }
    }
}

/* Useful C= functions */

VOID StripWindow(struct Window *win)
{
    Forbid();                                   
    StripIntuiMessages(win->UserPort, win);
    win->UserPort = NULL;
    ModifyIDCMP(win, NULL);
    Permit();                                   
}

VOID    StripIntuiMessages( struct MsgPort *mp, struct Window *win )
        {
            struct IntuiMessage *msg;
            struct Node *succ;

            msg = (struct IntuiMessage *) mp->mp_MsgList.lh_Head;

            while( succ =  msg->ExecMessage.mn_Node.ln_Succ ) {

                if( msg->IDCMPWindow ==  win ) {

                    /* Intuition is about to free this message.
                     * Make sure that we have politely sent it back.
                     */
                    Remove( (struct Node *)msg );

                    ReplyMsg( (struct Message *)msg );
                }

                msg = (struct IntuiMessage *) succ;
            }
        }

/* Another one for a private MsgPort */

VOID    StripMessages( struct MsgPort *mp )
        {
            struct Message *msg;
            struct Node *succ;

            msg = (struct Message *) mp->mp_MsgList.lh_Head;

            while( succ =  msg->mn_Node.ln_Succ ) {

                    Remove( (struct Node *)msg );

                    ReplyMsg( msg );

                msg = (struct Message *) succ;
            }
        }
