Initial commit. master
authorBernie Innocenti <bernie@codewiz.org>
Fri, 11 Mar 2011 04:45:00 +0000 (23:45 -0500)
committerBernie Innocenti <bernie@codewiz.org>
Fri, 11 Mar 2011 04:45:00 +0000 (23:45 -0500)
113 files changed:
App.c [new file with mode: 0644]
App.h [new file with mode: 0644]
Audio.c [new file with mode: 0644]
Autodocs/songclass [new file with mode: 0644]
Autodocs/songclass.doc [new file with mode: 0644]
Autodocs/xmodule [new file with mode: 0644]
Autodocs/xmodule.doc [new file with mode: 0644]
BoopsiStubs.h [new file with mode: 0644]
Catalogs/HowToTranslate.doc [new file with mode: 0644]
Catalogs/Makefile [new file with mode: 0644]
Catalogs/deutsch.ct [new file with mode: 0644]
Catalogs/empty.ct [new file with mode: 0644]
Catalogs/français.ct [new file with mode: 0644]
Catalogs/italiano.ct [new file with mode: 0644]
Catalogs/nederlands.ct [new file with mode: 0644]
Catalogs/xmodule.cd [new file with mode: 0644]
ClearWin.c [new file with mode: 0644]
CompilerSpecific.h [new file with mode: 0644]
Compress.c [new file with mode: 0644]
CustomClasses.c [new file with mode: 0644]
CustomClasses.h [new file with mode: 0644]
Cx.c [new file with mode: 0644]
Debug.h [new file with mode: 0644]
FFT.c [new file with mode: 0644]
FakePlayer.asm [new file with mode: 0644]
Gadgets/Makefile [new file with mode: 0644]
Gadgets/PattEditClass.c [new file with mode: 0644]
Gadgets/PattEditClassAsm.asm [new file with mode: 0644]
Gadgets/SCOPTIONS [new file with mode: 0644]
Gadgets/SampEditClass.c [new file with mode: 0644]
Gadgets/pattedit.gadget [new file with mode: 0644]
Gst.c [new file with mode: 0644]
Gui.c [new file with mode: 0644]
Gui.h [new file with mode: 0644]
Guru.i [new file with mode: 0644]
Help.c [new file with mode: 0644]
Hooks/FastTracker2.h [new file with mode: 0644]
Hooks/GetFT2.c [new file with mode: 0644]
Hooks/MMDHook.c [new file with mode: 0644]
Hooks/Makefile [new file with mode: 0644]
Hooks/OctaMed.h [new file with mode: 0644]
Hooks/OktalyzerHook.c [new file with mode: 0644]
Hooks/SCOPTIONS [new file with mode: 0644]
Hooks/SaveMIDI.c [new file with mode: 0644]
Hooks/ScreamTrackerHook.c [new file with mode: 0644]
Hooks/oktalyzer.xmhook [new file with mode: 0644]
Hooks/screamtracker.xmhook [new file with mode: 0644]
Instr.c [new file with mode: 0644]
InstrumentsWin.c [new file with mode: 0644]
Library.c [new file with mode: 0644]
ListMacros.h [new file with mode: 0644]
Locale.c [new file with mode: 0644]
LocaleStrings.h [new file with mode: 0644]
LocaleStrings_h.sd [new file with mode: 0644]
Locale_c.sd [new file with mode: 0644]
Main.c [new file with mode: 0644]
Makefile [new file with mode: 0644]
Misc.c [new file with mode: 0644]
Operators.c [new file with mode: 0644]
OptimizationWin.c [new file with mode: 0644]
OptimizationWin.s [new file with mode: 0644]
PattPrefsWin.c [new file with mode: 0644]
PatternWin.c [new file with mode: 0644]
PlayWin.c [new file with mode: 0644]
Player.asm [new file with mode: 0644]
Player.h [new file with mode: 0644]
Player.i [new file with mode: 0644]
Players/32Channels.player [new file with mode: 0644]
Prefs.c [new file with mode: 0644]
PrefsWin.c [new file with mode: 0644]
ProgressWin.c [new file with mode: 0644]
Requesters.c [new file with mode: 0644]
Requesters.h [new file with mode: 0644]
Rexx.c [new file with mode: 0644]
RomTag.asm [new file with mode: 0644]
SCOPTIONS [new file with mode: 0644]
SampleWin.c [new file with mode: 0644]
SaversWin.c [new file with mode: 0644]
SequenceWin.c [new file with mode: 0644]
SongClass.c [new file with mode: 0644]
SongClass.i [new file with mode: 0644]
SongInfoWin.c [new file with mode: 0644]
Startup.asm [new file with mode: 0644]
ToolBoxWin.c [new file with mode: 0644]
TrackerHook.c [new file with mode: 0644]
XModule [new file with mode: 0644]
XModule.i [new file with mode: 0644]
XModule.info [new file with mode: 0644]
XModule.prefs [new file with mode: 0644]
XModuleHook.c [new file with mode: 0644]
XModulePriv.h [new file with mode: 0644]
XModule_final.c [new file with mode: 0644]
XModule_rev.h [new file with mode: 0644]
XModule_rev.rev [new file with mode: 0644]
config.mk [new file with mode: 0644]
delta.asm [new file with mode: 0644]
include/clib/xmodule_protos.h [new file with mode: 0644]
include/libraries/patteditclass.h [new file with mode: 0644]
include/libraries/sampeditclass.h [new file with mode: 0644]
include/libraries/songclass.h [new file with mode: 0644]
include/libraries/songclass.i [new file with mode: 0644]
include/libraries/xmodule.h [new file with mode: 0644]
include/libraries/xmodule.i [new file with mode: 0644]
include/libraries/xmodule_lib.i [new file with mode: 0644]
include/libraries/xmoduleclass.h [new file with mode: 0644]
include/pragmas/xmodule_pragmas.h [new file with mode: 0644]
include/proto/xmodule.h [new file with mode: 0644]
manuals/HISTORY [new file with mode: 0644]
manuals/Makefile [new file with mode: 0644]
manuals/README [new file with mode: 0644]
manuals/TODO [new file with mode: 0644]
manuals/XModule.texi [new file with mode: 0644]
xmodule_lib.fd [new file with mode: 0644]

diff --git a/App.c b/App.c
new file mode 100644 (file)
index 0000000..bee18e7
--- /dev/null
+++ b/App.c
@@ -0,0 +1,184 @@
+/*
+**     App.c
+**
+**     Copyright (C) 1994,95,96 by Bernardo Innocenti
+**
+**     Handle AppIcons & AppWindows
+*/
+
+#include <exec/memory.h>
+#include <workbench/workbench.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/wb.h>
+#include <proto/icon.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+XDEF ULONG     AppSig = 0;
+XDEF LONG      IconX = NO_ICON_POSITION;
+XDEF LONG      IconY = NO_ICON_POSITION;
+XDEF UBYTE     IconName[16];
+XDEF BOOL      Iconified = FALSE;
+
+
+static struct MsgPort          *AppPort        = NULL;
+static struct AppIcon          *MyAppIcon      = NULL;
+static struct DiskObject       *AppDObj        = NULL;
+
+
+
+GLOBALCALL void HandleAppMessage (void)
+
+/* App Window event handler.  Get Workbench message and call server */
+{
+       struct AppMessage *am;
+
+       while (am = (struct AppMessage *) GetMsg (AppPort))
+       {
+               switch (am->am_Type)
+               {
+                       case AMTYPE_APPWINDOW:
+                               (((struct WinUserData *) am->am_UserData)->DropIcon) (am);
+                               break;
+
+                       case AMTYPE_APPICON:
+                               if (am->am_NumArgs == 0)
+                                       DeIconify();
+                               else if (am->am_UserData)
+                                       ((void (*) (struct AppMessage *am))(am->am_UserData)) (am);
+
+                               break;
+
+                       default:
+                               break;
+               }
+
+               ReplyMsg ((struct Message *) am);
+       }
+}
+
+
+
+GLOBALCALL void AddAppWin (struct WinUserData *wud)
+{
+       wud->AppWin = AddAppWindowA (0, (ULONG)wud, wud->Win, AppPort, NULL);
+}
+
+
+
+GLOBALCALL void RemAppWin (struct WinUserData *wud)
+{
+       struct Node             *succ;
+       struct Message  *msg;
+
+       RemoveAppWindow (wud->AppWin);
+       wud->AppWin = NULL;
+
+       /* Reply all pending messages for this window */
+
+       Forbid();
+
+       msg = (struct Message *) AppPort->mp_MsgList.lh_Head;
+
+       while (succ = msg->mn_Node.ln_Succ)
+       {
+               if ((struct WinUserData *)(((struct AppMessage *)msg)->am_UserData) == wud)
+               {
+                       Remove ((struct Node *)msg);
+                       ReplyMsg (msg);
+               }
+               msg = (struct Message *) succ;
+       }
+
+       Permit();
+}
+
+
+
+GLOBALCALL LONG CreateAppIcon (void (*handler) (struct AppMessage *am))
+{
+       if (!AppPort) return RETURN_FAIL;
+
+       if (MyAppIcon) return RETURN_OK;
+
+       /* Get icon */
+       if ( !(AppDObj = GetProgramIcon() ))
+               AppDObj = GetDefDiskObject (WBTOOL);
+
+       if (!AppDObj) return RETURN_FAIL;
+
+       /* Initialize AppIcon */
+       AppDObj->do_CurrentX = IconX;
+       AppDObj->do_CurrentY = IconY;
+
+       if (MyAppIcon = AddAppIconA (0, (ULONG)handler, IconName, AppPort, NULL, AppDObj, NULL))
+               return RETURN_OK;
+
+       FreeDiskObject (AppDObj); AppDObj = NULL;
+       return RETURN_FAIL;
+}
+
+
+
+GLOBALCALL void DeleteAppIcon (void)
+{
+       if (MyAppIcon)
+       {
+               RemoveAppIcon (MyAppIcon); MyAppIcon = NULL;
+               FreeDiskObject (AppDObj); AppDObj = NULL;
+       }
+}
+
+
+
+GLOBALCALL void Iconify (void)
+{
+       if (!CreateAppIcon (ToolBoxDropIcon))
+       {
+               CloseDownScreen();
+               Iconified = TRUE;
+       }
+}
+
+
+
+GLOBALCALL void DeIconify (void)
+{
+       if (!SetupScreen())
+       {
+               Iconified = FALSE;
+               if (!GuiSwitches.ShowAppIcon) DeleteAppIcon();
+       }
+}
+
+
+GLOBALCALL LONG SetupApp (void)
+{
+       if (!(AppPort = CreateMsgPort()))
+               return ERROR_NO_FREE_STORE;
+       AppSig = 1 << AppPort->mp_SigBit;
+       Signals |= AppSig;
+
+       if (GuiSwitches.ShowAppIcon)
+               CreateAppIcon (ToolBoxDropIcon);
+
+       return RETURN_OK;
+}
+
+
+
+GLOBALCALL void CleanupApp (void)
+{
+       if (AppPort)
+       {
+               DeleteAppIcon();
+
+               KillMsgPort (AppPort); AppPort = NULL;
+               Signals &= ~AppSig; AppSig = 0;
+       }
+}
diff --git a/App.h b/App.h
new file mode 100644 (file)
index 0000000..f6c7409
--- /dev/null
+++ b/App.h
@@ -0,0 +1,17 @@
+/*
+**     $Id:$
+**
+**     Copyright (C) 1993,94,95,96,98,99 by Bernardo Innocenti
+**
+**     AppWindow support functions.
+*/
+
+GLOBALCALL void                HandleAppMessage                (void);
+GLOBALCALL void                AddAppWin                               (struct WinUserData *wud);
+GLOBALCALL void                RemAppWin                               (struct WinUserData *wud);
+GLOBALCALL LONG                CreateAppIcon                   (void (*handler) (struct AppMessage *am));
+GLOBALCALL void                DeleteAppIcon                   (void);
+GLOBALCALL void                Iconify                                 (void);
+GLOBALCALL void                DeIconify                               (void);
+GLOBALCALL LONG                SetupApp                                (void);
+GLOBALCALL void                CleanupApp                              (void);
diff --git a/Audio.c b/Audio.c
new file mode 100644 (file)
index 0000000..fc65a8b
--- /dev/null
+++ b/Audio.c
@@ -0,0 +1,532 @@
+/*
+**     Audio.c
+**
+**     Copyright (C) 1994,95 by Bernardo Innocenti
+**
+**     audio.device interface routines.
+**
+**
+**     A separate Process gets the AudioMsg messages from its pr_MsgPort
+**     and dialogues with audio.device.  Once executed, the AudioMsg's are
+**     replied to the AudioReply port, and the main program frees them.
+**     Each IOAudio request has a special USERDATA field which points back
+**     to the original AudioMsg structure.  This way it is possible to easly
+**     track the status of each command being executed.
+**
+**     +------------+                +-------------+               +------------+
+**     |Main Process|<-->AudioMsg<-->|Audio Process|<-->IOAudio<-->|audio.device|
+**     +------------+                +-------------+               +------------+
+*/
+
+#include <exec/memory.h>
+#include <devices/audio.h>
+#include <dos/dos.h>
+#include <dos/dostags.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* This is the (maximum) size of sample chunks sent to the audio.device */
+
+#define SAMPBUFSIZE            16768
+#define MAXAUDIOSIZE   65534
+
+/* Get/store pointer to original AudioMsg into an extended IOAudio structure */
+
+#define AUDIOUSERDATA(ioa) (*((struct AudioMsg **)(((struct IOAudio *)ioa) + 1)))
+
+
+struct AudioMsg
+{
+       struct Message am_Message;
+       void *am_Data;
+       UBYTE am_Command;
+       UBYTE am_Error;
+       UWORD am_Status;
+       ULONG am_Len;
+       ULONG am_Actual;
+       UWORD am_Per;
+       UWORD am_Vol;
+       BYTE *am_TmpBuf[2];
+       struct IOAudio *am_IOA[2];
+};
+
+
+/* Values for AudioMsg->am_Command */
+#define ACMD_PLAY_SAMPLE       1       /* am_Data points to 8 bit sample data                  */
+#define ACMD_PLAY_INSTR                1       /* am_Data points to an Instrument structure    */
+
+
+
+/* Local functions prototypes */
+
+static void            ReplyAudioMsg   (struct AudioMsg *am);
+static void            _PlaySample             (struct AudioMsg *am);
+static UWORD   FindFreeChannel (void);
+static ULONG   AllocChannels   (void);
+static void            FreeChannels    (void);
+
+
+
+/* Local data */
+
+static struct Process *AudioTask = NULL;
+/* Audio IO requests.  Two for each channel (double buffer). */
+static struct IOAudio *AudioReq[4][2] = { 0 };
+static struct MsgPort *AudioReply = NULL;
+
+
+
+/* Global data */
+
+XDEF ULONG AudioSig = 0;
+
+
+
+HOOKCALL static void AudioProcess (void)
+{
+       ULONG audiosig, cmdsig, recsig, signals, err = 0;
+       LONG i, j;
+       struct IOAudio *ioa;
+       struct AudioMsg *am;
+       struct MsgPort *AudioPort;      /* Audio reply port */
+
+
+       struct Process *thistask = (struct Process *)FindTask (NULL);
+
+       if (AudioPort = CreateMsgPort())
+       {
+               /* Create IOAudio requests */
+
+               for (i = 0; i < 4 ; i++)
+                       for (j = 0; j < 2; j++)
+                               if (!(AudioReq[i][j] = (struct IOAudio *)CreateIORequest (AudioPort, sizeof (struct IOAudio) + 4)))
+                                       err = ERROR_NO_FREE_STORE;
+
+               if (!err)
+               {
+                       /* Open audio.device */
+                       if (!(err = OpenDevice ("audio.device", 0, (struct IORequest *)AudioReq[0][0], 0)))
+                               err = AllocChannels();
+               }
+       }
+       else err = ERROR_NO_FREE_STORE;
+
+
+       /* Wait for startup message */
+
+       WaitPort (&thistask->pr_MsgPort);
+       am = (struct AudioMsg *) GetMsg (&thistask->pr_MsgPort);
+
+       /* Reply startup message */
+       am->am_Error = err;
+       ReplyMsg ((struct Message *)am);
+
+       if (err)
+       {
+               Wait (SIGBREAKF_CTRL_C);
+               goto exit;
+       }
+
+       cmdsig = 1 << thistask->pr_MsgPort.mp_SigBit;
+       audiosig = 1 << AudioPort->mp_SigBit;
+
+       signals = cmdsig | audiosig | SIGBREAKF_CTRL_C;
+
+       do
+       {
+               recsig = Wait (signals);
+
+               if (recsig & audiosig)
+               {
+                       /* Collect previously sent requests */
+                       while (ioa = (struct IOAudio *) GetMsg (AudioPort))
+                       {
+                               if (am = AUDIOUSERDATA (ioa))
+                               {
+                                       if (am->am_Actual < am->am_Len)
+                                       {
+                                               BYTE *samp;
+                                               ULONG len;
+
+                                               if (am->am_TmpBuf[am->am_Status])
+                                               {
+                                                       len = min (SAMPBUFSIZE, am->am_Len - am->am_Actual);
+                                                       samp = am->am_TmpBuf[am->am_Status];
+                                                       CopyMem (((BYTE *)am->am_Data) + am->am_Actual, samp, len);
+                                               }
+                                               else
+                                               {
+                                                       samp = ((BYTE *)am->am_Data) + am->am_Actual;
+                                                       len = min (MAXAUDIOSIZE, am->am_Len - am->am_Actual);
+                                               }
+
+                                               /**/ioa->io_Command = CMD_WRITE;
+                                               /**/ioa->io_Flags = ADIOF_PERVOL;
+                                               ioa->ioa_Data = samp;
+                                               ioa->ioa_Length = len;
+                                               /**/ioa->ioa_Period = am->am_Per;
+                                               /**/ioa->ioa_Volume = am->am_Vol;
+                                               /**/ioa->ioa_Cycles = 1;
+
+                                               BeginIO ((struct IORequest *)ioa);
+                                               am->am_Actual += len;
+                                               am->am_Status ^= 1;
+                                       }
+                                       else
+                                       {
+                                               am = AUDIOUSERDATA(ioa);
+                                               AUDIOUSERDATA(am->am_IOA[0]) = NULL;
+                                               AUDIOUSERDATA(am->am_IOA[1]) = NULL;
+                                               ReplyAudioMsg (am);
+                                       }
+                               }
+                       }
+               }
+
+
+               if (recsig & cmdsig)
+               {
+                       /* Get Command and execute it */
+                       while (am = (struct AudioMsg *)GetMsg (&thistask->pr_MsgPort))
+                       {
+                               switch (am->am_Command)
+                               {
+                                       case ACMD_PLAY_SAMPLE:
+                                               _PlaySample (am);
+                                               break;
+
+                                       default:
+                                               break;
+                               }
+                       }
+               }
+       }
+       while (!(recsig & SIGBREAKF_CTRL_C));
+
+
+
+exit:
+
+       if (AudioPort)
+       {
+               FreeChannels();
+
+               for (i = 3; i >= 0 ; i--)
+                       for (j = 1; j >= 0; j--)
+                               if (AudioReq[i][j])
+                               {
+                                       if ((j == 0) && (AudioReq[i][j]->io_Device))
+                                       {
+                                               if (am = AUDIOUSERDATA(AudioReq[i][j]))
+                                                       ReplyAudioMsg (am);
+
+                                               if (i == 0)
+                                                       CloseDevice ((struct IORequest *)AudioReq[0][0]);
+                                       }
+                                       DeleteIORequest ((struct IORequest *)AudioReq[i][j]); AudioReq[i][j] = NULL;
+                               }
+
+               DeleteMsgPort (AudioPort);
+       }
+
+       /* Signal that we are done.
+        * We Forbid() here to avoid being unloaded until exit.
+        */
+       Forbid();
+       Signal ((struct Task *)ThisTask, SIGF_SINGLE);
+}
+
+
+
+static void ReplyAudioMsg (struct AudioMsg *am)
+{
+       FreeVec (am->am_TmpBuf[0]);
+       FreeVec (am->am_TmpBuf[1]);
+       AUDIOUSERDATA(am->am_IOA[0]) = NULL;
+       AUDIOUSERDATA(am->am_IOA[1]) = NULL;
+       ReplyMsg ((struct Message *)am);
+}
+
+
+
+static void _PlaySample (struct AudioMsg *am)
+{
+       BYTE *samp = am->am_Data;
+       ULONG len = am->am_Len;
+       ULONG ch = FindFreeChannel ();
+       BOOL multi = FALSE, transfer = FALSE;
+
+       am->am_TmpBuf[0] = NULL;
+       am->am_TmpBuf[1] = NULL;
+       am->am_Status = 0;
+       am->am_IOA[0] = AudioReq[ch][0];
+       am->am_IOA[1] = AudioReq[ch][1];
+
+       if (!(TypeOfMem (samp) & MEMF_CHIP))
+               transfer = TRUE;
+
+       if (am->am_Len > MAXAUDIOSIZE)
+       {
+               multi = TRUE;
+               if (transfer)
+                       len = SAMPBUFSIZE;
+               else
+                       len = MAXAUDIOSIZE;
+       }
+
+       if (transfer)
+               if (am->am_TmpBuf[0] = AllocVec (len, MEMF_CHIP))
+               {
+                       CopyMem (samp, am->am_TmpBuf[0], len);
+                       samp = am->am_TmpBuf[0];
+               }
+               else return;
+
+       am->am_IOA[0]->io_Command = CMD_WRITE;
+       am->am_IOA[0]->io_Flags = ADIOF_PERVOL;
+       am->am_IOA[0]->ioa_Data = samp;
+       am->am_IOA[0]->ioa_Length = len;
+       am->am_IOA[0]->ioa_Period = am->am_Per;
+       am->am_IOA[0]->ioa_Volume = am->am_Vol;
+       am->am_IOA[0]->ioa_Cycles = 1;
+       AUDIOUSERDATA(am->am_IOA[0]) = am;
+
+       BeginIO ((struct IORequest *)am->am_IOA[0]);
+       am->am_Actual = len;
+
+       if (multi)
+       {
+               samp += len;
+
+               if (transfer)
+               {
+                       len = min(SAMPBUFSIZE, am->am_Len - SAMPBUFSIZE);
+
+                       if (am->am_TmpBuf[1] = AllocVec (len, MEMF_CHIP))
+                       {
+                               CopyMem (samp, am->am_TmpBuf[1], len);
+                               samp = am->am_TmpBuf[1];
+                       }
+                       else return;
+               }
+               else len = min (MAXAUDIOSIZE, am->am_Len - MAXAUDIOSIZE);
+
+               am->am_IOA[1]->io_Command = CMD_WRITE;
+               am->am_IOA[1]->io_Flags = ADIOF_PERVOL;
+               am->am_IOA[1]->ioa_Data = samp;
+               am->am_IOA[1]->ioa_Length = len;
+               am->am_IOA[1]->ioa_Period = am->am_Per;
+               am->am_IOA[1]->ioa_Volume = am->am_Vol;
+               am->am_IOA[1]->ioa_Cycles = 1;
+               AUDIOUSERDATA(am->am_IOA[1]) = am;
+
+               BeginIO ((struct IORequest *)am->am_IOA[1]);
+               am->am_Actual += len;
+       }
+}
+
+
+
+GLOBALCALL void HandleAudio (void)
+{
+       struct Message *msg;
+
+       while (msg = GetMsg (AudioReply))
+               FreeMem (msg, sizeof (struct AudioMsg));
+}
+
+
+
+GLOBALCALL void PlaySample (BYTE *samp, ULONG len, UWORD vol, UWORD per)
+{
+       struct AudioMsg *am;
+
+       if (!samp) return;
+
+       if (!AudioTask)
+               if (SetupAudio()) return;
+
+       if (!(am = AllocMem (sizeof (struct AudioMsg), MEMF_PUBLIC)))
+               return;
+
+       am->am_Message.mn_ReplyPort = AudioReply;
+       am->am_Command = ACMD_PLAY_SAMPLE;
+
+       am->am_Data = samp;
+       am->am_Len = len;
+       am->am_Vol = vol;
+       am->am_Per = per;
+
+       PutMsg (&AudioTask->pr_MsgPort, (struct Message *)am);
+}
+
+
+
+static UWORD FindFreeChannel (void)
+{
+       UWORD ch;
+
+       for (ch = 0; ch < 4 ; ch++)
+               if (CheckIO ((struct IORequest *)AudioReq[ch][0]) &&
+                       CheckIO ((struct IORequest *)AudioReq[ch][1]))
+                       return ch;
+
+       {
+               struct AudioMsg *am;
+
+               AbortIO ((struct IORequest *)AudioReq[0][0]);
+               AbortIO ((struct IORequest *)AudioReq[0][1]);
+               WaitIO ((struct IORequest *)AudioReq[0][0]);
+               WaitIO ((struct IORequest *)AudioReq[0][1]);
+
+               if (am = AUDIOUSERDATA(AudioReq[0][0]))
+               {
+                       ReplyAudioMsg (am);
+                       AUDIOUSERDATA(AudioReq[0][0]) = NULL;
+                       AUDIOUSERDATA(AudioReq[0][1]) = NULL;
+               }
+       }
+
+       return 0;
+}
+
+
+static ULONG AllocChannels (void)
+{
+       struct IOAudio *ioa;
+       ULONG i;
+
+       /* Allocate channels */
+
+       for (i = 0 ; i < 4; i++)
+       {
+               static UBYTE channels[] = {1, 2, 4, 8};
+
+               ioa = AudioReq[i][0];
+
+               ioa->ioa_Request.ln_Pri = 1;
+               ioa->io_Device = AudioReq[0][0]->io_Device;
+               ioa->io_Command = ADCMD_ALLOCATE;
+               ioa->io_Flags = ADIOF_NOWAIT | IOF_QUICK;
+               ioa->ioa_AllocKey = AudioReq[0][0]->ioa_AllocKey;
+               ioa->ioa_Data = channels;
+               ioa->ioa_Length = 4;
+
+               /* Using DoIO() here is not possible because the
+                * io_Flags field would be cleared.
+                */
+               BeginIO ((struct IORequest *)ioa);
+               WaitIO ((struct IORequest *)ioa);
+
+               /* Initailize other request */
+               CopyMem (ioa, AudioReq[i][1], sizeof (struct IOAudio));
+
+               if (ioa->io_Error)
+                       return ioa->io_Error;
+       }
+
+       return RETURN_OK;
+}
+
+
+
+static void FreeChannels (void)
+{
+       LONG i;
+
+       for (i = 3; i >= 0; i--)
+       {
+               if (AudioReq[i][0])
+               {
+                       AbortIO ((struct IORequest *)AudioReq[i][0]);
+                       WaitIO ((struct IORequest *)AudioReq[i][0]);
+
+                       if (AudioReq[i][1])
+                       {
+                               AbortIO ((struct IORequest *)AudioReq[i][1]);
+                               WaitIO ((struct IORequest *)AudioReq[i][1]);
+                       }
+
+                       AudioReq[i][0]->io_Command = ADCMD_FREE;
+                       DoIO ((struct IORequest *)AudioReq[i][0]);
+               }
+       }
+}
+
+
+
+GLOBALCALL ULONG SetupAudio (void)
+{
+       struct AudioMsg audiomsg;
+
+       if (!(AudioReply = CreateMsgPort ()))
+               return ERROR_NO_FREE_STORE;
+
+       AudioSig = 1 << AudioReply->mp_SigBit;
+       Signals |= AudioSig;
+
+       /* Create Audio Process */
+       if (!(AudioTask = CreateNewProcTags (
+               NP_Entry,               AudioProcess,
+               NP_Name,                "XModule Audio Process",
+               NP_WindowPtr,   ThisTask->pr_WindowPtr,
+               NP_Priority,    15,
+               NP_CopyVars,    FALSE,
+               // NP_Input,    NULL,
+               // NP_Output,   NULL,
+               // NP_Error,    NULL,
+               TAG_DONE)))
+       {
+               CleanupAudio();
+               return ERROR_NO_FREE_STORE;
+       }
+
+
+       /* Send Startup Message */
+
+       audiomsg.am_Message.mn_ReplyPort = AudioReply;
+       PutMsg (&(AudioTask->pr_MsgPort), (struct Message *)&audiomsg);
+       WaitPort (AudioReply);
+       GetMsg (AudioReply);
+
+       if (audiomsg.am_Error)
+       {
+               CleanupAudio();
+               return (audiomsg.am_Error);
+       }
+
+       return RETURN_OK;
+}
+
+
+
+GLOBALCALL void CleanupAudio (void)
+{
+       if (AudioTask)
+       {
+               /* Tell audio task to give up */
+               SetSignal (0, SIGF_SINGLE);
+               Signal ((struct Task *)AudioTask, SIGBREAKF_CTRL_C);
+
+               /* Wait until the audio task quits */
+               Wait (SIGF_SINGLE);
+               AudioTask = NULL;
+       }
+
+       if (AudioReply)
+       {
+               struct Message *msg;
+
+               while (msg = GetMsg (AudioReply))
+                       FreeMem (msg, sizeof (struct AudioMsg));
+
+               DeleteMsgPort (AudioReply); AudioReply = NULL;
+               Signals &= ~AudioSig; AudioSig = 0;
+       }
+}
diff --git a/Autodocs/songclass b/Autodocs/songclass
new file mode 100644 (file)
index 0000000..8541f95
--- /dev/null
@@ -0,0 +1,57 @@
+@DATABASE "songclass"
+@MASTER   "Work:SC/XM/Autodocs/songclass.doc"
+@REMARK   This file was created by ADtoHT 2.1 on 08-Apr-97 12:17:15
+@REMARK   Do not edit
+@REMARK   ADtoHT is © 1993-1995 Christian Stieber
+
+@NODE MAIN "songclass.doc"
+
+                                  @{b}songclass@{ub}
+
+@{"background" LINK "background"}
+
+
+@ENDNODE
+@NODE "background" "songclass/background (information)"
+
+
+    NAME
+        songclass -- XModule 'boopsi'-oriented song implementation.
+
+    DESCRIPTION
+        The song class is an object oriented way to handle a song.  The song
+        class handles all data storing mechanisms for you and adds a layer
+        of abstraction between the song internal data structures and the
+        application.  The advantage is that the internal structures can be
+        changed while keeping compatibility with existing software.
+
+        Another great advantage of being a 'boopsi' class is that the song
+        can notify other boopsi objects whenever its attributes change.
+        This simplifies the task of keeping the user interface updated each
+        time the user (or an ARexx macro, or whatever) changes something.
+
+        For speed reasons, the song class does also allow 'white box'
+        istance access.  This means that you can also directly access
+        the internal data structures of the song, without using standard
+        boopsi methods.  You are ONLY allowed to READ public fields, but not
+        to write any of them.  The main reason to forbid direct writing is
+        that the song class must send notifications to its targets, but it
+        does also allow the song implementation to change in future version
+        without breaking existing applications.
+
+        When you create a new istance of the song class, the object handle
+        you get is actually a SongInfo structure.  This is only possible
+        because the song class is a subclass of the rootclass, whose istance
+        is placed at a negative offset in the object handle.
+        Future song class implementations could require to be subclasses
+        of other classes, such as the gadget class or even the datatypes
+        class.  This problem will be probably got around by keeping the
+        root class as the real superclass of the song and creating an
+        istance of the other superclass which will be passed all the
+        methods which are not recognized by the song its-self.  Call this
+        boopsi polymorphism, if you like to :-)
+
+    QUOTATION
+        Don't be a tuna head.
+
+@ENDNODE
diff --git a/Autodocs/songclass.doc b/Autodocs/songclass.doc
new file mode 100644 (file)
index 0000000..7259095
--- /dev/null
@@ -0,0 +1,45 @@
+TABLE OF CONTENTS
+
+songclass/--background--
+\fsongclass/--background--                             songclass/--background--
+
+    NAME
+        songclass -- XModule 'boopsi'-oriented song implementation.
+
+    DESCRIPTION
+        The song class is an object oriented way to handle a song.  The song
+        class handles all data storing mechanisms for you and adds a layer
+        of abstraction between the song internal data structures and the
+        application.  The advantage is that the internal structures can be
+        changed while keeping compatibility with existing software.
+
+        Another great advantage of being a 'boopsi' class is that the song
+        can notify other boopsi objects whenever its attributes change.
+        This simplifies the task of keeping the user interface updated each
+        time the user (or an ARexx macro, or whatever) changes something.
+
+        For speed reasons, the song class does also allow 'white box'
+        istance access.  This means that you can also directly access
+        the internal data structures of the song, without using standard
+        boopsi methods.  You are ONLY allowed to READ public fields, but not
+        to write any of them.  The main reason to forbid direct writing is
+        that the song class must send notifications to its targets, but it
+        does also allow the song implementation to change in future version
+        without breaking existing applications.
+
+        When you create a new istance of the song class, the object handle
+        you get is actually a SongInfo structure.  This is only possible
+        because the song class is a subclass of the rootclass, whose istance
+        is placed at a negative offset in the object handle.
+        Future song class implementations could require to be subclasses
+        of other classes, such as the gadget class or even the datatypes
+        class.  This problem will be probably got around by keeping the
+        root class as the real superclass of the song and creating an
+        istance of the other superclass which will be passed all the
+        methods which are not recognized by the song its-self.  Call this
+        boopsi polymorphism, if you like to :-)
+
+    QUOTATION
+        Don't be a tuna head.
+
+\f
\ No newline at end of file
diff --git a/Autodocs/xmodule b/Autodocs/xmodule
new file mode 100644 (file)
index 0000000..b9a3e4c
--- /dev/null
@@ -0,0 +1,1090 @@
+@DATABASE "xmodule"
+@MASTER   "Work:SC/XM/Autodocs/xmodule.doc"
+@REMARK   This file was created by ADtoHT 2.1 on 08-Apr-97 12:17:15
+@REMARK   Do not edit
+@REMARK   ADtoHT is © 1993-1995 Christian Stieber
+
+@NODE MAIN "xmodule.doc"
+
+                                   @{b}xmodule@{ub}
+
+@{"background" LINK "background"}
+
+@{"xmActivateSong()" LINK "xmActivateSong"}    @{"xmAddHook()" LINK "xmAddHook"}          @{"xmAddHookA()" LINK "xmAddHook"}
+@{"xmAddInstrument()" LINK "xmAddInstrumentA"}   @{"xmAddInstrumentA()" LINK "xmAddInstrumentA"}   @{"xmAddPattern()" LINK "xmAddPatternA"}
+@{"xmAddPatternA()" LINK "xmAddPatternA"}     @{"xmAddSong()" LINK "xmAddSongA"}          @{"xmAddSongA()" LINK "xmAddSongA"}
+@{"xmCreateSong()" LINK "xmCreateSong"}      @{"xmCreateSongA()" LINK "xmCreateSong"}      @{"xmDeleteSong()" LINK "xmDeleteSong"}
+@{"xmDisplayMessage()" LINK "xmDisplayMessage"}  @{"xmDisplayMessageA()" LINK "xmDisplayMessage"}  @{"xmDisplayProgress()" LINK "xmDisplayProgress"}
+@{"xmIdentifyModule()" LINK "xmIdentifyModule"}  @{"xmLoadModule()" LINK "xmLoadModuleA"}       @{"xmLoadModuleA()" LINK "xmLoadModuleA"}
+@{"xmLockActiveSong()" LINK "xmLockActiveSong"}  @{"xmProcessSong()" LINK "xmProcessSongA"}      @{"xmProcessSongA()" LINK "xmProcessSongA"}
+@{"xmRemHook()" LINK "xmRemHook"}         @{"xmRemHookA()" LINK "xmRemHook"}         @{"xmRemInstrument()" LINK "xmRemInstrument"}
+@{"xmRemPattern()" LINK "xmRemPattern"}      @{"xmRemSong()" LINK "xmRemSong"}          @{"xmSaveModule()" LINK "xmSaveModuleA"}
+@{"xmSaveModuleA()" LINK "xmSaveModuleA"}     @{"xmSetInstrument()" LINK "xmSetInstrumentA"}    @{"xmSetInstrumentA()" LINK "xmSetInstrumentA"}
+@{"xmSetPattern()" LINK "xmSetPatternA"}      @{"xmSetPatternA()" LINK "xmSetPatternA"}      @{"xmSetSongLen()" LINK "xmSetSongLen"}
+
+@ENDNODE
+@NODE "background" "xmodule/background (information)"
+
+
+    INTRODUCTION
+        The xmodule.library is an API that provides access to the XModule
+        song management engine, as well as other general pourpose
+        services.  Hooks and external applications can use this library
+        to create, load, save and modify songs.
+
+        The xmodule.library does not exist as a stand alone shared libray.
+        Instead, it's code is contained inside the main XModule executable
+        (altrough it's logically independant from the rest of the XModule
+        code).
+
+        XModule adds the xmodule.library to the exec libray list at
+        startup time, unless it finds one already in the list (this might
+        happen when multiple copies of XModule are running at the same
+        time).
+
+    PUBLIC SONGS
+        The xmodule.library maintains a list of public songs which can be
+        used by all applications which opened the library.  Each song in
+        the list is protected from multiple task access by a
+        SignalSemaphore, and whole list is also protected by another
+        semaphore.  The locking mechanism is very simple: before you can
+        access a song you must obtain its semaphore, and you must release
+        it as soon as you are done with it.  If you are locking a song to
+        just to read some information (i.e.: you don't want to modify
+        anything), you should obtain a shared lock instead of an exclusive
+        one.  The list must be locked whenever you want to add or remove
+        a song, or when you scan it in any way.
+
+        Since public songs could be modified or even deleted by other
+        tasks, do not make your songs public unless your code is smart
+        enough to handle all possible situations.
+
+
+    HOOKS
+        Actually, most modular programs call them `modules', but it would
+        have created a lot of confusion with a program like XModule :-).
+
+        Hooks are programs that add some kind of functionality
+        to XModule.  External hook files are standard shared libraries
+        which will open the xmodule.library when they are loaded and
+        call @{"xmAddHookA()" LINK "xmodule/xmAddHook"} to add themselves to a list of hooks maintained
+        by the library.
+
+        Currently, XModule supports two kinds of hooks: loaders and
+        savers.  Loader hooks can also provide a function which
+        identifies a particular module format.
+
+        An external hook libray may also contain several hooks.
+        Putting a loader and a saver for one particolar format together
+        in one libray is generally a good idea, while making a hook
+        with hundereds of loaders and savers isn't a good move because
+        it makes the whole concept of external hooks quite useless.
+        Grouping different versions or variants of one format together
+        in one external hook is acceptable.
+
+    SEE ALSO
+        songclass/--background--, exec/ObtainSemaphore()
+
+@ENDNODE
+@NODE "xmActivateSong" "xmodule/xmActivateSong()"
+@{b}
+
+    NAME@{ub}
+        xmActivateSong -- Makes a song the active one@{b}
+
+    SYNOPSIS@{ub}
+        success = xmActivateSong(songInfo);
+        D0                       A0
+
+        ULONG xmActivateSong(@{"struct SongInfo" LINK "songclass.h/File" 284} *);@{b}
+
+    FUNCTION@{ub}
+        Makes the passed song the currently active one.  It's pointer
+        will be stored in the public song list and most actions
+        will happen on this song by default.@{b}
+
+    INPUTS@{ub}
+        songInfo - song to be activated. If NULL, this function will
+            take no action.@{b}
+
+    RESULT@{ub}
+        success - Will be 0 for failure, in which case the song will
+            not be removed from the song list. Currently,
+            xmActivateSong() will never fail.@{b}
+
+    NOTE@{ub}
+        In order to activate a song, you must own a shared lock
+        on it.  Please do not hog this lock for a long time when
+        xmActivateSong() returns, because most internal routines
+        try to lock the current song before taking any action.@{b}
+
+    SEE ALSO@{ub}
+
+@ENDNODE
+@NODE "xmAddHook" "xmodule/xmAddHook()"
+@{b}
+
+    NAME@{ub}
+        xmAddHookA -- Creates a new XModule Hook
+        xmAddHook -- Varargs stub for xmAddHookA@{b}
+
+    SYNOPSIS@{ub}
+        hook = xmAddHookA(tagList)
+        D0                A0
+
+        @{"struct XMHook" LINK "xmodule.h/File" 92} *xmAddHookA(struct TagItem *);
+
+
+        hook = xmAddHook(Tag1,...)
+
+        @{"struct XMHook" LINK "xmodule.h/File" 92} *xmAddHook(ULONG,...);@{b}
+
+    FUNCTION@{ub}
+        Creates a new XMHook structure and fills it in with data supplied
+        with the TagList. Adds the newly created Hook to the appropriate
+        list.@{b}
+
+    INPUTS@{ub}
+        tagList - pointer to a tag list specifying how to initialize the
+            XMHook structure.@{b}
+
+    TAGS@{ub}
+        @{"XMHOOK_Type" LINK "xmodule.h/File" 235} - (ULONG) Defines the pourpose of this hook. Possible
+            values are currently @{"NT_XMLOADER" LINK "xmodule.h/File" 138} and @{"NT_XMSAVER" LINK "xmodule.h/File" 139}. (This
+            tag is REQUIRED).
+
+        @{"XMHOOK_Name" LINK "xmodule.h/File" 236} - (STRPTR) ti_Data contains a short name for the
+            hook (e.g: "SoundTracker").  This name will appear in the
+            Savers list if this hook is a saver and will be passed as an
+            argument for some ARexx commands, so please use a single
+            word name.  (This tag is REQUIRED).
+
+        @{"XMHOOK_Priority" LINK "xmodule.h/File" 237} - (BYTE) Priority to give to this hook.  Hooks
+            with higher priorities will be used before lower priority
+            ones and will come first in lists shown to the user. Valid
+            range is -128..+127, but please restrict to a -20..+20
+            interval for normal cases. (Defaults to 0).
+
+        @{"XMHOOK_Descr" LINK "xmodule.h/File" 238} - (STRPTR) Verbose description of the hook
+            (without newlines). (Defaults to NULL).
+
+        @{"XMHOOK_Author" LINK "xmodule.h/File" 239} - (STRPTR) Author's name.  Please, just put
+            your full name here; no greetings, copyright notices,
+            etc. (Defaults to NULL).
+
+        @{"XMHOOK_ID" LINK "xmodule.h/File" 240} - (ULONG) This is a unique, IFF-style identifier for
+            the format.  If the format is an IFF format, it must be the
+            same of the FORM ID. (Defaults to 0, this tag is required
+            for IFF loaders and savers).
+
+        @{"XMHOOK_Flags" LINK "xmodule.h/File" 241} - (ULONG) Sets miscellaneous flags for this hook.
+            See xmodule.h for possible flags.
+
+        @{"XMHOOK_LibraryBase" LINK "xmodule.h/File" 243} - (struct Library *) Pointer to the library
+            base for external hooks.  This pointer will be used to open
+            the library when the hook is created and to close it when
+            the hook is deleted.  If you do not pass this tag, you must
+            find some other way to keep your code in memory until one
+            or more of your hooks are active.  XModule will close your
+            library just after calling the SetupXMHook() function.
+            (Defaults to NULL).
+
+        @{"XMHOOK_UserData" LINK "xmodule.h/File" 242} - (APTR) ti_Data will be stored in the
+            xmh_UserData field of the XMHook structure. This field can
+            come andy to store private data. (Defaults to NULL).
+
+        XMHOOK_RemoveHookFunc - (APTR) Pointer to a function which will be
+            called upon removing the hook.  This function can be used to
+            free any private resources allocated by the hook when it was
+            created. The template for the function is:
+
+                void RemoveHookFunc (@{"struct XMHook" LINK "xmodule.h/File" 92} *hook);
+                                     A0
+
+        @{"XMHOOK_LoadModFunc" LINK "xmodule.h/File" 245} - (APTR) Pointer to the hook function which
+            loads a module. The template for the function is:
+
+                LONG LoadModFunc (BPTR fileHandle, @{"struct SongInfo" LINK "songclass.h/File" 284} *song,
+                                  D0               A0
+                    @{"struct XMHook" LINK "xmodule.h/File" 92} *loader);
+                    A1
+
+            `fileHandle' is an open file to load the module from. The
+            caller will take care to close it for you. The loader should
+            return RETURN_OK for success, or any other AmigaDOS error
+            code to mean failure.  In particular, RETURN_WARN indicates
+            that something went wrong, but the song is ok.  The special
+            error code @{"ERROR_IOERR" LINK "xmodule.h/File" 194} can be used when some dos.library
+            call (e.g.: Read()) failed.  In the latter case, the
+            AmigaDOS IoErr value should be set to explain the specific
+            cause of the problem.
+            (This tag is required by all @{"NT_XMLOADER" LINK "xmodule.h/File" 138} type hooks).
+
+        @{"XMHOOK_SaveModFunc" LINK "xmodule.h/File" 246} - (APTR) Pointer to the hook function which
+            saves a module. The template for the function is:
+
+                LONG SaveModFunc (BPTR fileHandle, @{"struct SongInfo" LINK "songclass.h/File" 284} *song,
+                                  D0               A0
+                    @{"struct XMHook" LINK "xmodule.h/File" 92} *saver);
+                    A1
+
+            fileHandle is an open file to save the module to. The caller
+            will take care to close it for you. The saver should return
+            RETURN_OK for success, or any other AmigaDOS error code to
+            mean failure.  In particular, RETURN_WARN indicates that
+            something went wrong, but the song is ok.  The special
+            error code @{"ERROR_IOERR" LINK "xmodule.h/File" 194} can be used when some dos.library
+            call (e.g.: Read()) failed.  In the latter case, the
+            AmigaDOS IoErr value should be set to explain the specific
+            cause of the problem.
+            (This tag is required by all @{"NT_XMSAVER" LINK "xmodule.h/File" 139} type hooks).
+
+
+        @{"XMHOOK_IdentifyModFunc" LINK "xmodule.h/File" 247} - (APTR) Pointer to the hook function
+            which identifies a module format. The template for the
+            function is:
+
+                @{"struct XMHook" LINK "xmodule.h/File" 92} *IdentifyModFunc (BPTR fileHandle,
+                                                D0
+                    @{"struct XMHook" LINK "xmodule.h/File" 92} *hook,struct TagItem *tagList);
+                    A0                  A1
+
+            fileHandle is an open file to try the identification on. The
+            caller will take care to close it. NOTE: Do not make assumptions
+            on the initial file position (e.g: seek yourself to position 0
+            if you need to). This funtion should return a pointer to a valid
+            loader (usually the one passed in) if the format is recognized,
+            NULL otherwhise.  (This tag is required by all @{"NT_XMLOADER" LINK "xmodule.h/File" 138} type
+            hooks).@{b}
+
+    RESULT@{ub}
+        hook - Pointer to the newly allocated XMHook structure, or
+            NULL for failure.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmRemHook()" LINK "xmRemHook"}, @{"xmIdentifyModule()" LINK "xmIdentifyModule"}, xmLoadSong(), xmSaveSong()
+
+@ENDNODE
+@NODE "xmAddInstrumentA" "xmodule/xmAddInstrumentA()"
+@{b}
+
+    NAME@{ub}
+        xmAddInstrumentA -- Adds a instrument to a song
+        xmAddInstrument -- Varargs stub for xmAddInstrumentA@{b}
+
+    SYNOPSIS@{ub}
+        instrument = xmAddInstrumentA(si, instrNum, tagList)
+        D0                            A0  D0        A1
+
+        @{"struct Instrument" LINK "songclass.h/File" 183} *xmAddInstrumentA(@{"struct SongInfo" LINK "songclass.h/File" 284} *,LONG,
+            struct TagItem *);
+
+
+        instrument = xmAddInstrument(si,instrNum,tag1,...)
+
+        @{"struct Instrument" LINK "songclass.h/File" 183} *xmAddInstrument(@{"struct SongInfo" LINK "songclass.h/File" 284} *,LONG,LONG,...);@{b}
+
+    FUNCTION@{ub}
+        Adds an instrument to a song by calling the @{"SNGM_ADDINSTRUMENT" LINK "songclass.h/File" 32} method .@{b}
+
+    INPUTS@{ub}
+        si - pointer to the song to which the instrument should be added.
+        tagList - optional TagList.  See @{"SNGM_ADDINSTRUMENT" LINK "songclass.h/File" 32} for possible tags .@{b}
+
+    RESULT@{ub}
+        Pointer to the new instrument or NULL for failure.@{b}
+
+    NOTE@{ub}
+        In order to add instruments, you should have exclusive access to
+        the song.  Always obtain a lock before you call this function on
+        public songs.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmRemInstrument()" LINK "xmRemInstrument"}, @{"songclass" LINK "songclass/MAIN"}/SNGM_REMINSTRUMENT
+
+@ENDNODE
+@NODE "xmAddPatternA" "xmodule/xmAddPatternA()"
+@{b}
+
+    NAME@{ub}
+        xmAddPatternA -- Adds a pattern to a song
+        xmAddPattern -- Varargs stub for xmAddPatternA@{b}
+
+    SYNOPSIS@{ub}
+        pattern = xmAddPatternA(si, tagList)
+        D0                      A0  A1
+
+        @{"struct Pattern" LINK "songclass.h/File" 229} *xmAddPatternA(@{"struct SongInfo" LINK "songclass.h/File" 284} *,struct TagItem *);
+
+
+        pattern = xmAddPattern (si,tag1,...)
+
+        @{"struct Pattern" LINK "songclass.h/File" 229} *xmAddPattern(@{"struct SongInfo" LINK "songclass.h/File" 284} *,LONG Tag1,...);@{b}
+
+    FUNCTION@{ub}
+        Adds a pattern to a song by calling the @{"SNGM_ADDPATTERN" LINK "songclass.h/File" 29} method.@{b}
+
+    INPUTS@{ub}
+        si - pointer to the song to which the pattern should be added.
+        tagList - optional TagList.  See @{"SNGM_ADDPATTERN" LINK "songclass.h/File" 29} for possible tags.@{b}
+
+    RESULT@{ub}
+        Pointer to the new pattern or NULL for failure.@{b}
+
+    NOTE@{ub}
+        In order to add patterns, you should have exclusive access to
+        the song.  Always obtain a lock before you call this function on
+        public songs.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmRemPattern()" LINK "xmRemPattern"}, @{"songclass" LINK "songclass/MAIN"}/SNGM_ADDPATTERN
+
+@ENDNODE
+@NODE "xmAddSongA" "xmodule/xmAddSongA()"
+@{b}
+
+    NAME@{ub}
+        xmAddSongA -- Add a song to the song list
+        xmAddSong -- Varargs stub for xmAddSongA@{b}
+
+    SYNOPSIS@{ub}
+        success = xmAddSongA(songInfo,position,tagList);
+        D0                   A0       A1       A2
+
+        ULONG xmAddSongA(@{"struct SongInfo" LINK "songclass.h/File" 284} *,@{"struct SongInfo" LINK "songclass.h/File" 284} *,
+            struct TagItem *);
+
+
+        success = xmAddSong(songInfo,position,tag1,...);
+
+        ULONG xmAddSong(@{"struct SongInfo" LINK "songclass.h/File" 284} *,@{"struct SongInfo" LINK "songclass.h/File" 284} *,
+            LONG,...);@{b}
+
+    FUNCTION@{ub}
+        Adds a song to the public song list. Trying to add a song
+        twice is harmless.@{b}
+
+    INPUTS@{ub}
+        songInfo - song to be added.  Passing a NULL pointer is safe.
+        position - Position to add the song in.  Passing NULL adds the
+            song in the head of the list, while ~0 adds it in the tail.
+            passing a pointer to a SongInfo already in the list inserts
+            the first song _after_ the other one.
+        tagList - pointer to a TagList for more arguments.@{b}
+
+    TAGS@{ub}
+        @{"XMSNG_Active" LINK "xmodule.h/File" 209} - (BOOL) Makes this song the active one. The
+            default is to just add the song without activating it.@{b}
+
+    RESULT@{ub}
+        success - Will be 0 for failure, in which case the song will
+            not be added to the songs list. Currently, xmAddSongA()
+            will never fail.@{b}
+
+    NOTE@{ub}
+        The song list is protected from multiple tasks access by a
+        SignalSemaphore in XModuleBase. This call will wait if the
+        song list has been locked by another task. Beware of deadlock
+        conditions!@{b}
+
+    SEE ALSO@{ub}
+        @{"xmRemSong()" LINK "xmRemSong"}
+
+@ENDNODE
+@NODE "xmCreateSong" "xmodule/xmCreateSong()"
+@{b}
+
+    NAME@{ub}
+        xmCreateSongA -- Create and initialize a new SongInfo structure
+        xmCreateSong -- Varargs stub for xmCreateSongA@{b}
+
+    SYNOPSIS@{ub}
+        songInfo = xmCreateSongA(tagList);
+        D0                      A0
+
+        @{"struct SongInfo" LINK "songclass.h/File" 284} *xmCreateSongA(struct TagItem *);
+
+
+        songInfo = xmCreateSong(Tag1,...);
+
+        @{"struct SongInfo" LINK "songclass.h/File" 284} *xmCreateSong(ULONG,...);@{b}
+
+    FUNCTION@{ub}
+        Allocates and initializes a new SongInfo structure.  The song
+        can then be added to the song list via @{"xmAddSongA()" LINK "xmAddSongA"}, in which
+        case, it is no longer required to free it with @{"xmDeleteSong()" LINK "xmDeleteSong"}.@{b}
+
+    INPUTS@{ub}
+        tagList - pointer to an optional tag list specifying how to
+            initialize the SongInfo structure.@{b}
+
+    TAGS@{ub}
+        @{"SNGA_DefaultTracks" LINK "songclass.h/File" 121} - Sets the default number of pattern tracks for
+            the song. Defaults to 4.
+
+        @{"SNGA_DefaultPattLen" LINK "songclass.h/File" 122} - Sets the default number of pattern lines for
+            the song. Defaults to 64.
+
+        @{"SNGA_ReadyToUse" LINK "songclass.h/File" 127} - Adds one pattern and one position to the song.
+            Defaults to FALSE.
+
+        @{"XMSNG_AddToList" LINK "xmodule.h/File" 205} - (@{"struct SongInfo" LINK "songclass.h/File" 284} *) Add the song to the song list.
+            When a song is being added to the list, a shared lock is
+            obtained on it to prevent other tasks from modifying (or even
+            remove) the song before you can do your job on it. IT IS YOUR
+            DUTY TO UNLOCK THE SONG as soon as you are done modifying it.
+            Passing NULL adds the song on the head of the list, while -1
+            (~0) will add it to the end of the SongList. Passing in a
+            pointer to another song which is already in the list, will
+            add the new song _after_ the latter.
+            (Default is not adding the song).
+
+        @{"XMSNG_Active" LINK "xmodule.h/File" 209} - (BOOL) Makes this song the active one. This tag is
+            considered only if the @{"XMSNG_AddToList" LINK "xmodule.h/File" 205} tag is also passed.@{b}
+
+    RESULT@{ub}
+        songInfo - pointer to the newly allocated SongInfo structure, or
+            NULL for failure.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmDeleteSong()" LINK "xmDeleteSong"}, @{"xmAddSongA()" LINK "xmAddSongA"}
+
+@ENDNODE
+@NODE "xmDeleteSong" "xmodule/xmDeleteSong()"
+@{b}
+
+    NAME@{ub}
+        xmDeleteSong -- Deletes a song@{b}
+
+    SYNOPSIS@{ub}
+        xmDeleteSong(songInfo)
+                     A0
+
+        void xmDeleteSong(@{"struct SongInfo" LINK "songclass.h/File" 284} *);@{b}
+
+    FUNCTION@{ub}
+        Deletes a song and all the items attached to it (patterns,
+        instruments, etc.).  This call will also remove the song from
+        the song list if it had been added to it.@{b}
+
+    INPUTS@{ub}
+        songInfo - pointer to the SongInfo structure to be deleted.
+            Passing a NULL pointer is harmless.@{b}
+
+    NOTE@{ub}
+        In order to delete a song which has been added to the public
+        song list, you must first obtain an exclusive lock on it to
+        prevent other tasks from walking on it while you are freeing
+        it's data structures.  The semaphore does NOT need to be
+        relinquished, because the SongInfo structure won't exist any
+        more when this call returns.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmCreateSong()" LINK "xmCreateSong"}, @{"xmRemSong()" LINK "xmRemSong"}
+
+@ENDNODE
+@NODE "xmDisplayMessage" "xmodule/xmDisplayMessage()"
+@{b}
+
+    NAME@{ub}
+        xmDisplayMessageA -- Displays a message to the user
+        xmDisplayMessage -- Varargs stub for xmDisplayMessageA()@{b}
+
+    SYNOPSIS@{ub}
+        xmDisplayMessageA(level, message, args)
+                          DO     A0       A1
+
+        void xmDisplayMessageA(ULONG,APTR,LONG *);
+
+
+        xmDisplayMessage(level, message, ...)
+
+        void xmDisplayMessage(ULONG,APTR,...);@{b}
+
+    FUNCTION@{ub}
+        Outputs a string in the XModule log window or in the 'action' field
+        of the progress indicator.  The string is printf-formatted before
+        being output.@{b}
+
+    INPUTS@{ub}
+        level - a number from 0 to 7 which indicates the importance of the
+            message.  7 is used for very annoying messages (eg: debug output) ,
+            0 for very important things (eg: disaster).
+            If you set the @{"XMDMF_USECATALOG" LINK "xmodule.h/File" 187} bit in the level parameter, you
+            can pass a catalog string number instead of a pointer to a string .
+            Specifying @{"XMDMF_ACTION" LINK "xmodule.h/File" 185}, the message will be put in the 'action'
+            field of the progress indicator.
+            If the flag @{"XMDMF_DOSFAULT" LINK "xmodule.h/File" 184} is specified, a Fault() output is
+            formatted using the message as an header.  In this case, the
+            level parameter takes another meaing: The lower word can contain
+            an AmigaDOS error code. If it is 0, IoErr() will be used to get
+            the error code.
+        message - pointer to a string or catalog message number,
+            when the @{"XMDMF_USECATALOG" LINK "xmodule.h/File" 187} flag is set.
+        args - arguments for formatting.@{b}
+
+    EXAMPLES@{ub}
+        xmDisplayMessage (@{"XMDMF_ALERT" LINK "xmodule.h/File" 173},
+            "The application `%s' fucked up Windoze95 because %s.",
+            "(unknown name)", "an error occurred");
+
+        xmDisplayMessage (XMDMF_USE_CATALOG | @{"XMDMF_COMMENT" LINK "xmodule.h/File" 179},
+            MSG_LIFE_UNIVERSE_AND_ALLTHAT, 42, "Fortytwo", "For tea too");
+
+        xmDisplayMessageA (@{"XMDMF_ACTION" LINK "xmodule.h/File" 185} | @{"XMDMF_USECATALOG" LINK "xmodule.h/File" 187},
+            MSG_READING_COMICS, NULL);
+
+        xmDisplayMessage (@{"XMDMF_DOSFAULT" LINK "xmodule.h/File" 184} | @{"XMDMF_USECATALOG" LINK "xmodule.h/File" 187},
+            MSG_CANT_LOAD_MODULE, ModuleName);@{b}
+
+    SEE ALSO@{ub}
+        @{"xmDisplayProgress()" LINK "xmDisplayProgress"}
+
+@ENDNODE
+@NODE "xmDisplayProgress" "xmodule/xmDisplayProgress()"
+@{b}
+
+    NAME@{ub}
+        xmDisplayProgress -- Uptdates the progress bar indicator@{b}
+
+    SYNOPSIS@{ub}
+        abort = xmDisplayProgress(done, total)
+                                  D0    D1
+
+        LONG @{"xmDisplayMessageA" LINK "xmDisplayMessage"}(ULONG,ULONG);@{b}
+
+    FUNCTION@{ub}
+        Updates the position of the fuel gauge in the progress window to
+        show the progress of an operation.  Additionally, it checks for
+        user abort.@{b}
+
+    INPUTS@{ub}
+        done - a number which indicates how much of the work has
+            been already completed
+        total - Total number of operations to do.@{b}
+
+    RESULT@{ub}
+        0 for success, ERROR_BREAK if user abort was detected.  You should
+        always check this return code to abort what you were doing.
+
+@ENDNODE
+@NODE "xmIdentifyModule" "xmodule/xmIdentifyModule()"
+@{b}
+
+    NAME@{ub}
+        xmIdentifyModule -- Returns the type of a module@{b}
+
+    SYNOPSIS@{ub}
+        loader = xmIdentifyModule(fh, tagList)
+        D0                        D0  A0
+
+        @{"struct XMHook" LINK "xmodule.h/File" 92} *xmIdentifyModule(BPTR,struct TagItem *);@{b}
+
+    FUNCTION@{ub}
+        Finds out a loader which is able to read the given module.@{b}
+
+    INPUTS@{ub}
+        fh - AmigaDOS FileHandle to examine.
+        tagList - Additional parameters. Leave it NULL for now.@{b}
+
+    RESULT@{ub}
+        loader - Pointer to the first loader which knows how to load this
+            module, or NULL otherwise.@{b}
+
+    NOTE@{ub}
+        Before you call this function, you must first obtain a lock on the
+        loaders list to protect yourself from other tasks which might remove
+        the returned loader before you can actually use it.@{b}
+
+    SEE ALSO@{ub}
+
+@ENDNODE
+@NODE "xmLoadModuleA" "xmodule/xmLoadModuleA()"
+@{b}
+
+    NAME@{ub}
+        xmLoadModuleA -- Loads a module and converts it in XModule format
+        xmLoadModule -- Varargs stub for xmLoadModuleA@{b}
+
+    SYNOPSIS@{ub}
+        songInfo = xmLoadModuleA(fileName,tagList)
+        D0                       A0       A1
+
+        @{"struct SongInfo" LINK "songclass.h/File" 284} *xmLoadModuleA(STRPTR,struct TagItem *);
+
+
+        songInfo = xmLoadModule(fileName,tag,...)
+
+        @{"struct SongInfo" LINK "songclass.h/File" 284} *xmLoadModule(STRPTR,LONG,...);@{b}
+
+    FUNCTION@{ub}
+        Loads fileName using the correct module loader.@{b}
+
+    INPUTS@{ub}
+        fileName - File to read. Can be NULL if the @{"XMSNG_FileHandle" LINK "xmodule.h/File" 208}
+            tag is passed.
+        tagList - Additional parameters (see below).  Can be NULL.@{b}
+
+    TAGS@{ub}
+        @{"XMSNG_OldSong" LINK "xmodule.h/File" 206} - ti_Data is the pointer to a SongInfo which will be
+            freed as soon as the module format has been determined.  This
+            is useful when the user wants to replace a song with another
+            one.  Passing NULL uses the currently active song.
+
+        @{"XMSNG_AddToList" LINK "xmodule.h/File" 205} - (@{"struct SongInfo" LINK "songclass.h/File" 284} *) Add the song to the song list.
+            When a song is being added to the list, a shared lock is
+            obtained on it to prevent other tasks from modifying (or even
+            remove) the song before you can do your job on it. IT IS YOUR
+            DUTY TO UNLOCK THE SONG as soon as you are done modifying it.
+            Passing NULL adds the song on the head of the list, while -1
+            (~0) will add it to the end of the SongList. Passing in a
+            pointer to another song which is already in the list, will
+            add the new song _after_ the latter.
+            (Default is not adding the song).
+
+        @{"XMSNG_Loader" LINK "xmodule.h/File" 207} - (@{"struct XMHook" LINK "xmodule.h/File" 92} *) Disables automatic format
+            checking and forces loading the module with the given
+            loader. (Defaults to NULL).
+
+        @{"XMSNG_FileHandle" LINK "xmodule.h/File" 208} - (BPTR) pointer to an open AmigaDOS
+            FileHandle to read the module from.  The file must
+            already be positioned over the beginning of the module data.
+            NOTE: Even if this tag is passed, the fileName parameter is
+                still used to fill in the SongName field in case it is
+                missing inside the module AND the filesystem does not
+                support the ACTION_EXAMINE_FH packet.
+            NOTE: Some loaders may require a fully seekable file, so be
+                careful when using pipes.
+            NOTE: automatic data decompression is not possible when
+                @{"XMSNG_FileHandle" LINK "xmodule.h/File" 208} is passed.
+            (Defaults to NULL).
+
+        XMSNG_IFFHandle - (BPTR) pointer to an already initialized
+            IFFHandle to read the module from.  The IFF must
+            already be positioned over the beginning of the module FORM.
+            Even if this tag is passed, the fileName parameter is still
+            used to fill in the SongName field in case it is missing
+            inside the module.
+            NOTE: The iffparse.library can deal with non-seekable files,
+                but some loaders may require a fully seekable file, so be
+                careful when using pipes.
+            NOTE: automatic file decompression is not possible when
+                XMSNG_IFFHandle is passed.
+            (Defaults to NULL).
+
+        @{"XMSNG_Active" LINK "xmodule.h/File" 209} - (BOOL) Makes this song the active one. This tag is
+            considered only if the @{"XMSNG_AddToList" LINK "xmodule.h/File" 205} tag is also passed.@{b}
+
+    RESULT@{ub}
+        songInfo - Pointer to the newly allocated SongInfo structure, or
+            NULL for failure.  This function will not fail if the module is
+            partially corrupted. Anyway, the returned SongInfo will always
+            be valid. You can check IoErr() to know if there were problems.@{b}
+
+    EXAMPLE@{ub}
+        /* Load a song and add it to the SongList */
+        si = xmLoadSongTags (file, NULL,
+            @{"XMSNG_AddToList" LINK "xmodule.h/File" 205}, (@{"struct SongInfo" LINK "songclass.h/File" 284} *)~0,
+            TAG_DONE);
+
+        /* Check for errors even if si is not NULL */
+        error = IoErr();
+
+        /* Release Semaphore got by xmLoadSong() */
+        if (si) ReleaseSemaphore (&si->Lock);@{b}
+
+    SEE ALSO@{ub}
+        @{"xmAddSongA()" LINK "xmAddSongA"}, @{"xmIdentifyModule()" LINK "xmIdentifyModule"}
+
+@ENDNODE
+@NODE "xmLockActiveSong" "xmodule/xmLockActiveSong()"
+@{b}
+
+    NAME@{ub}
+        xmLockActiveSong -- Obtains an lock on the active song@{b}
+
+    SYNOPSIS@{ub}
+        song = xmLockActiveSong(mode);
+        D0                        D0:16
+
+        @{"struct SongInfo" LINK "songclass.h/File" 284} *@{"xmActivateSong" LINK "xmActivateSong"}(UWORD);@{b}
+
+    FUNCTION@{ub}
+        Obtains an exclusive or shared lock on the active song,
+        waiting if needed.  This call is a shortcut to:
+
+            ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+            ObtainSemaphore[Shared] (&XModuleBase->xm_CurrentSong.Lock);
+            ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+        To unlock a song obtained in this way, you just need to
+        ReleaseSemaphore() it.
+
+        You MUST always lock a song before you even think to
+        read from -or write to- its data!@{b}
+
+    INPUTS@{ub}
+        mode - one of SM_SHARED or SM_EXCLUSIVE.@{b}
+
+    RESULT@{ub}
+        song - Pointer to the song which *was* active at the time
+            you called xmLockActiveSong().  The song will be
+            locked for you.  The result will be NULL if no song
+            is active.@{b}
+
+    NOTE@{ub}
+        Please be careful if you call this function while holding
+        locks on other songs.  Doing so, you run the risk of causing
+        deadlock condition.
+        This function does only lock the song; it is NOT guaranteed
+        that it will remain the active one.@{b}
+
+    SEE ALSO@{ub}
+
+@ENDNODE
+@NODE "xmProcessSongA" "xmodule/xmProcessSongA()"
+@{b}
+
+    NAME@{ub}
+        xmProcessSongA -- Performs complex processing on a song
+        xmProcessSong -- Varargs stub for xmProcessSongA()@{b}
+
+    SYNOPSIS@{ub}
+        result = xmProcessSongA(si, reserved, tagList)
+                                A0  A1,       A2
+
+        LONG xmProcessSongA(@{"struct SongInfo" LINK "songclass.h/File" 284} *,void *,struct TagItem *);
+
+
+        result = xmProcessSong(si, reserved, tag1, ...)
+
+        LONG xmProcessSongA(@{"struct SongInfo" LINK "songclass.h/File" 284} *,void *,LONG,...);@{b}
+
+    FUNCTION@{ub}
+        Performs complex processing operations on a song.@{b}
+
+    INPUTS@{ub}
+        si - pointer to the song to be processed.
+        reserved - Reserved for future use.
+        tagList - List of arguments.  See below for possible tags.@{b}
+
+    RESULT@{ub}
+        The result depends on the kind of operation done.  For most
+        operations, it's value will be 0 to indicate success and
+        an AmigaDOS error code which indicates the cause for failure.@{b}
+
+    TAGS@{ub}
+        @{"XMSNG_Optimize" LINK "xmodule.h/File" 213} - (LONG).  Tries to reduce the song size by removing
+            all unused data.  Possible optimizations are:
+                - @{"XMOF_REM_UNUSED_PATTERNS" LINK "xmodule.h/File" 221}
+                - @{"XMOF_REM_DUP_PATTERNS" LINK "xmodule.h/File" 222}
+                - @{"XMOF_CUT_PATTERNS" LINK "xmodule.h/File" 227}
+                - @{"XMOF_REM_UNUSED_INSTRS" LINK "xmodule.h/File" 223}
+                - @{"XMOF_CUT_INSTR_LOOPS" LINK "xmodule.h/File" 225}
+                - @{"XMOF_CUT_INSTR_TAILS" LINK "xmodule.h/File" 226}
+                - @{"XMOF_DEFAULT" LINK "xmodule.h/File" 220}
+            @{"XMOF_DEFAULT" LINK "xmodule.h/File" 220} will select all the optimizations choosen by
+            the user in addition to the ones specified with the other flags.
+
+        @{"XMSNG_RemapInstruments" LINK "xmodule.h/File" 214} - (BOOL) Performs instruments remapping.
+
+        @{"XMSNG_LimitPatterns" LINK "xmodule.h/File" 215} - (UWORD,UWORD) Limits the length all the
+            patterns inside a minimum and maximum value.  The upper 16bits
+            of the argument are the minimum value, the lower ones are
+            the maximum value.  Patterns outside this limits will be grown
+            or split as appropriate.
+
+        @{"XMSNG_Join" LINK "xmodule.h/File" 216} - (@{"struct SongInfo" LINK "songclass.h/File" 284} *) - Joins the song with another one.
+
+        @{"XMSNG_Merge" LINK "xmodule.h/File" 217} - (@{"struct SongInfo" LINK "songclass.h/File" 284} *) - Merges the song with another one.@{b}
+
+    NOTE@{ub}
+        In order to process a song, you should have exclusive access to
+        it.  Always obtain a lock before you call this function on
+        public songs.
+
+@ENDNODE
+@NODE "xmRemHook" "xmodule/xmRemHook()"
+@{b}
+
+    NAME@{ub}
+        xmRemHook -- Removes an XModule Hook@{b}
+
+    SYNOPSIS@{ub}
+        xmRemHookA(xmHook)
+                   A0
+
+        void xmRemHook(@{"struct XMHook" LINK "xmodule.h/File" 92} *);@{b}
+
+    FUNCTION@{ub}
+        Removes an XModule Hook, calls its RemoveHookFunc() and then
+        frees its memory.@{b}
+
+    INPUTS@{ub}
+        xmHook - pointer to the hook to be removed.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmAddHook()" LINK "xmAddHook"}
+
+@ENDNODE
+@NODE "xmRemInstrument" "xmodule/xmRemInstrument()"
+@{b}
+
+    NAME@{ub}
+        xmRemInstrument -- Removes an instrument from a song@{b}
+
+    SYNOPSIS@{ub}
+        xmRemInstrument(si, instrNum)
+                        A0  D0
+
+        void xmRemInstrument(@{"struct SongInfo" LINK "songclass.h/File" 284} *,LONG);@{b}
+
+    FUNCTION@{ub}
+        Removes an instrument from a song by calling the @{"SNGM_REMINSTRUMENT" LINK "songclass.h/File" 34}
+        method.@{b}
+
+    INPUTS@{ub}
+        si - pointer to the song to which the instrument should be removed.
+        mum - Number of the instrument to be removed.@{b}
+
+    NOTE@{ub}
+        In order to remove instruments, you should have exclusive access to
+        the song.  Always obtain a lock before you call this function on
+        public songs.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmAddInstrumentA()" LINK "xmAddInstrumentA"}, @{"songclass" LINK "songclass/MAIN"}/SNGM_REMINSTRUMENT
+
+@ENDNODE
+@NODE "xmRemPattern" "xmodule/xmRemPattern()"
+@{b}
+
+    NAME@{ub}
+        xmRemPattern -- Removes a pattern from a song@{b}
+
+    SYNOPSIS@{ub}
+        xmRemPattern(si, pattNum, replaceWith)
+                     A0  D0,      D1
+
+        void xmRemPattern(@{"struct SongInfo" LINK "songclass.h/File" 284} *,LONG,LONG);@{b}
+
+    FUNCTION@{ub}
+        Removes a pattern from a song by calling the @{"SNGM_REMPATTERN" LINK "songclass.h/File" 31} method.@{b}
+
+    INPUTS@{ub}
+        si - pointer to the song to which the pattern should be removed.
+        mum - Number of the pattern to be removed.
+        replaceWith - What to put in the song sequence in place of the
+            deleted pattern.@{b}
+
+    NOTE@{ub}
+        In order to remove patterns, you should have exclusive access to
+        the song.  Always obtain a lock before you call this function on
+        public songs.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmAddPatternA()" LINK "xmAddPatternA"}, @{"songclass" LINK "songclass/MAIN"}/SNGM_REMPATTERN
+
+@ENDNODE
+@NODE "xmRemSong" "xmodule/xmRemSong()"
+@{b}
+
+    NAME@{ub}
+        xmRemSong -- Remove a song from the song list@{b}
+
+    SYNOPSIS@{ub}
+        success = xmRemSong(songInfo);
+        D0                  A0
+
+        ULONG xmRemSong(@{"struct SongInfo" LINK "songclass.h/File" 284} *);@{b}
+
+    FUNCTION@{ub}
+        Removes a song from the public song list. It is safe to call this
+        function even if the song has not been added to the song list. If
+        the passed SongInfo is the active one, another song will be
+        selected automatically.@{b}
+
+    INPUTS@{ub}
+        songInfo - song to be removed. If NULL, this function will take
+            no action.@{b}
+
+    RESULT@{ub}
+        success - Will be 0 for failure, in which case the song will
+            not be removed from the song list. Currently,
+            xmRemSong() will never fail.@{b}
+
+    NOTE@{ub}
+        In order to remove a song, you must first obtain an exclusive
+        lock on it.
+
+        The song list is also protected from multiple task access by
+        a SignalSemaphore. This call will wait if the song list has
+        been locked by another task. Beware of deadlock conditions!@{b}
+
+    SEE ALSO@{ub}
+        @{"xmAddSongA()" LINK "xmAddSongA"}
+
+@ENDNODE
+@NODE "xmSaveModuleA" "xmodule/xmSaveModuleA()"
+@{b}
+
+    NAME@{ub}
+        xmSaveModuleA -- Saves a module to the specified file format
+        xmSaveModule -- Varargs stub for xmSaveModuleA@{b}
+
+    SYNOPSIS@{ub}
+        error = xmSaveModuleA(songInfo, fileName, saver, tagList)
+        D0                    A0        A1        A2     A3
+
+        LONG xmSaveModuleA(@{"struct SongInfo" LINK "songclass.h/File" 284} *,STRPTR,@{"struct XMHook" LINK "xmodule.h/File" 92} *,
+            struct TagItem *);
+
+
+        error = xmSaveModule(songInfo, fileName, saver, tag1,...)
+
+        LONG xmSaveModule(@{"struct SongInfo" LINK "songclass.h/File" 284} *,STRPTR,@{"struct XMHook" LINK "xmodule.h/File" 92} *,
+            LONG,...);@{b}
+
+    FUNCTION@{ub}
+        Saves songInfo to fileName using the specified saver.@{b}
+
+    INPUTS@{ub}
+        songInfo - Song to save.
+        fileName - AmigaDOS filename to write the song to.
+        saver - Pointer to the saver to use.  Pass NULL to use
+            the default saver.@{b}
+
+    TAGS@{ub}
+        No tags are defined for this call.@{b}
+
+    RESULT@{ub}
+        error - RETURN_OK for success, or any other AmigaDOS error code
+            to mean failure.  In particular, RETURN_WARN indicates that
+            something went wrong, but the song is still ok. The special
+            error code @{"ERROR_IOERR" LINK "xmodule.h/File" 194} means that some dos.library
+            call (e.g.: Read()) failed.  In the latter case, the
+            AmigaDOS IoErr() value will be set to explain the specific
+            cause of the problem.@{b}
+
+    SEE ALSO@{ub}
+
+@ENDNODE
+@NODE "xmSetInstrumentA" "xmodule/xmSetInstrumentA()"
+@{b}
+
+    NAME@{ub}
+        xmSetInstrumentA -- Sets an instrument's attributes
+        xmSetInstrument -- Varargs stub for xmSetInstrumentA@{b}
+
+    SYNOPSIS@{ub}
+        success = xmSetInstrumentA(si, instrNum, tagList)
+        D0                         A0  D0        A1
+
+        ULONG xmSetInstrumentA(@{"struct SongInfo" LINK "songclass.h/File" 284} *,LONG,
+            struct TagItem *);
+
+
+        success = xmSetInstrument(si,instrNum,tag1,...)
+
+        ULONG xmSetInstrument(@{"struct SongInfo" LINK "songclass.h/File" 284} *,LONG,LONG,...);@{b}
+
+    FUNCTION@{ub}
+        Sets an instrument's attributes by calling the @{"SNGM_SETINSTRUMENT" LINK "songclass.h/File" 33}
+        method.@{b}
+
+    INPUTS@{ub}
+        si - pointer to the song which contains the instrument to be set.
+        tagList - instrument attributes to set.  See @{"SNGM_SETINSTRUMENT" LINK "songclass.h/File" 33}
+            for possible tags.@{b}
+
+    RESULT@{ub}
+        non zero for success.@{b}
+
+    NOTE@{ub}
+        In order to set instruments' attributes, you should have
+        exclusive access to the song.  Always obtain a lock before
+        you call this function on public songs.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmAddInstrument()" LINK "xmAddInstrumentA"}, @{"songclass" LINK "songclass/MAIN"}/SNGM_SETINSTRUMENT
+
+@ENDNODE
+@NODE "xmSetPatternA" "xmodule/xmSetPatternA()"
+@{b}
+
+    NAME@{ub}
+        xmSetPatternA -- Sets pattern attributes
+        xmSetPattern -- Varargs stub for xmSetPatternA@{b}
+
+    SYNOPSIS@{ub}
+        success = xmSetPatternA(si, pattNum, tagList)
+        D0                      A0  D0       A1
+
+        ULONG xmSetPatternA(@{"struct SongInfo" LINK "songclass.h/File" 284} *,ULONG,struct TagItem *);
+
+
+        success = xmSetPattern (si,pattNum,tag1,...)
+
+        ULONG xmSetPattern(@{"struct SongInfo" LINK "songclass.h/File" 284} *,ULONG,LONG Tag1,...);@{b}
+
+    FUNCTION@{ub}
+        Sets attributes of a pattern by calling the @{"SNGM_SETPATTERN" LINK "songclass.h/File" 30} method.@{b}
+
+    INPUTS@{ub}
+        si - pointer to the song which contains the pattern to be set.
+        tagList - list of attributes to set.  See @{"SNGM_SETPATTERN" LINK "songclass.h/File" 30} for
+            possible tags.@{b}
+
+    RESULT@{ub}
+        Non zero for success@{b}
+
+    NOTE@{ub}
+        In order to set patterns attributes, you should have exclusive
+        access to the song.  Always obtain a lock before you call this
+        function on public songs.@{b}
+
+    SEE ALSO@{ub}
+        @{"xmAddPattern()" LINK "xmAddPatternA"}, @{"songclass" LINK "songclass/MAIN"}/SNGM_SETPATTERN
+
+@ENDNODE
+@NODE "xmSetSongLen" "xmodule/xmSetSongLen()"
+@{b}
+
+    NAME@{ub}
+        xmSetSongLen -- Set the number of song positions@{b}
+
+    SYNOPSIS@{ub}
+        sequence = xmSetSongLen(songInfo, length)
+        D0                      A0        D0:16
+
+        UWORD *xmSetSongLen(@{"struct SongInfo" LINK "songclass.h/File" 284} *, UWORD);@{b}
+
+    FUNCTION@{ub}
+        Allocates space in the song for a sequence table of the given
+        length. If no sequence exists yet, a new one is allocated.
+        Increasing the song length may require the sequence table to be
+        expanded, in which case it will be re-allocated and the old sequence
+        entries will be copied. Decreasing the song length could also cause
+        a reallocation of the sequence table if the size drops down enough.
+        Setting the song length to 0 will free the sequence table and return
+        NULL.@{b}
+
+    INPUTS@{ub}
+        songInfo - pointer to a SongInfo structure.
+        length - new length of song sequence table.@{b}
+
+    NOTE@{ub}
+        This function will also adjust the CurrentPos field if it is found
+        beyond the song end@{b}
+
+    BUGS@{ub}
+        This funtion is quite useless because all it does is setting the
+        @{"SNGA_Length" LINK "songclass.h/File" 88} attribute in the song object.  Setting the @{"SNGA_Length" LINK "songclass.h/File" 88}
+        attribute yourself is easier and faster if you are already setting
+        other attributes in the same song.@{b}
+
+   RESULT@{ub}
+        Pointer to the newly allocated sequence table or NULL for failure,
+        in which case the previous sequence table is left untouched.
+
+@ENDNODE
diff --git a/Autodocs/xmodule.doc b/Autodocs/xmodule.doc
new file mode 100644 (file)
index 0000000..c4bb47f
--- /dev/null
@@ -0,0 +1,1050 @@
+TABLE OF CONTENTS
+
+xmodule/--background--
+xmodule/xmActivateSong
+xmodule/xmAddHook
+xmodule/xmAddInstrumentA
+xmodule/xmAddPatternA
+xmodule/xmAddSongA
+xmodule/xmCreateSong
+xmodule/xmDeleteSong
+xmodule/xmDisplayMessage
+xmodule/xmDisplayProgress
+xmodule/xmIdentifyModule
+xmodule/xmLoadModuleA
+xmodule/xmLockActiveSong
+xmodule/xmProcessSongA
+xmodule/xmRemHook
+xmodule/xmRemInstrument
+xmodule/xmRemPattern
+xmodule/xmRemSong
+xmodule/xmSaveModuleA
+xmodule/xmSetInstrumentA
+xmodule/xmSetPatternA
+xmodule/xmSetSongLen
+\fxmodule/--background--                                 xmodule/--background--
+
+    INTRODUCTION
+        The xmodule.library is an API that provides access to the XModule
+        song management engine, as well as other general pourpose
+        services.  Hooks and external applications can use this library
+        to create, load, save and modify songs.
+
+        The xmodule.library does not exist as a stand alone shared libray.
+        Instead, it's code is contained inside the main XModule executable
+        (altrough it's logically independant from the rest of the XModule
+        code).
+
+        XModule adds the xmodule.library to the exec libray list at
+        startup time, unless it finds one already in the list (this might
+        happen when multiple copies of XModule are running at the same
+        time).
+
+    PUBLIC SONGS
+        The xmodule.library maintains a list of public songs which can be
+        used by all applications which opened the library.  Each song in
+        the list is protected from multiple task access by a
+        SignalSemaphore, and whole list is also protected by another
+        semaphore.  The locking mechanism is very simple: before you can
+        access a song you must obtain its semaphore, and you must release
+        it as soon as you are done with it.  If you are locking a song to
+        just to read some information (i.e.: you don't want to modify
+        anything), you should obtain a shared lock instead of an exclusive
+        one.  The list must be locked whenever you want to add or remove
+        a song, or when you scan it in any way.
+
+        Since public songs could be modified or even deleted by other
+        tasks, do not make your songs public unless your code is smart
+        enough to handle all possible situations.
+
+
+    HOOKS
+        Actually, most modular programs call them `modules', but it would
+        have created a lot of confusion with a program like XModule :-).
+
+        Hooks are programs that add some kind of functionality
+        to XModule.  External hook files are standard shared libraries
+        which will open the xmodule.library when they are loaded and
+        call xmAddHookA() to add themselves to a list of hooks maintained
+        by the library.
+
+        Currently, XModule supports two kinds of hooks: loaders and
+        savers.  Loader hooks can also provide a function which
+        identifies a particular module format.
+
+        An external hook libray may also contain several hooks.
+        Putting a loader and a saver for one particolar format together
+        in one libray is generally a good idea, while making a hook
+        with hundereds of loaders and savers isn't a good move because
+        it makes the whole concept of external hooks quite useless.
+        Grouping different versions or variants of one format together
+        in one external hook is acceptable.
+
+    SEE ALSO
+        songclass/--background--, exec/ObtainSemaphore()
+
+\fxmodule/xmActivateSong                                 xmodule/xmActivateSong
+
+    NAME
+        xmActivateSong -- Makes a song the active one
+
+    SYNOPSIS
+        success = xmActivateSong(songInfo);
+        D0                       A0
+
+        ULONG xmActivateSong(struct SongInfo *);
+
+    FUNCTION
+        Makes the passed song the currently active one.  It's pointer
+        will be stored in the public song list and most actions
+        will happen on this song by default.
+
+    INPUTS
+        songInfo - song to be activated. If NULL, this function will
+            take no action.
+
+    RESULT
+        success - Will be 0 for failure, in which case the song will
+            not be removed from the song list. Currently,
+            xmActivateSong() will never fail.
+
+    NOTE
+        In order to activate a song, you must own a shared lock
+        on it.  Please do not hog this lock for a long time when
+        xmActivateSong() returns, because most internal routines
+        try to lock the current song before taking any action.
+
+    SEE ALSO
+
+\fxmodule/xmAddHook                                           xmodule/xmAddHook
+
+    NAME
+        xmAddHookA -- Creates a new XModule Hook
+        xmAddHook -- Varargs stub for xmAddHookA
+
+    SYNOPSIS
+        hook = xmAddHookA(tagList)
+        D0                A0
+
+        struct XMHook *xmAddHookA(struct TagItem *);
+
+
+        hook = xmAddHook(Tag1,...)
+
+        struct XMHook *xmAddHook(ULONG,...);
+
+    FUNCTION
+        Creates a new XMHook structure and fills it in with data supplied
+        with the TagList. Adds the newly created Hook to the appropriate
+        list.
+
+    INPUTS
+        tagList - pointer to a tag list specifying how to initialize the
+            XMHook structure.
+
+    TAGS
+        XMHOOK_Type - (ULONG) Defines the pourpose of this hook. Possible
+            values are currently NT_XMLOADER and NT_XMSAVER. (This
+            tag is REQUIRED).
+
+        XMHOOK_Name - (STRPTR) ti_Data contains a short name for the
+            hook (e.g: "SoundTracker").  This name will appear in the
+            Savers list if this hook is a saver and will be passed as an
+            argument for some ARexx commands, so please use a single
+            word name.  (This tag is REQUIRED).
+
+        XMHOOK_Priority - (BYTE) Priority to give to this hook.  Hooks
+            with higher priorities will be used before lower priority
+            ones and will come first in lists shown to the user. Valid
+            range is -128..+127, but please restrict to a -20..+20
+            interval for normal cases. (Defaults to 0).
+
+        XMHOOK_Descr - (STRPTR) Verbose description of the hook
+            (without newlines). (Defaults to NULL).
+
+        XMHOOK_Author - (STRPTR) Author's name.  Please, just put
+            your full name here; no greetings, copyright notices,
+            etc. (Defaults to NULL).
+
+        XMHOOK_ID - (ULONG) This is a unique, IFF-style identifier for
+            the format.  If the format is an IFF format, it must be the
+            same of the FORM ID. (Defaults to 0, this tag is required
+            for IFF loaders and savers).
+
+        XMHOOK_Flags - (ULONG) Sets miscellaneous flags for this hook.
+            See xmodule.h for possible flags.
+
+        XMHOOK_LibraryBase - (struct Library *) Pointer to the library
+            base for external hooks.  This pointer will be used to open
+            the library when the hook is created and to close it when
+            the hook is deleted.  If you do not pass this tag, you must
+            find some other way to keep your code in memory until one
+            or more of your hooks are active.  XModule will close your
+            library just after calling the SetupXMHook() function.
+            (Defaults to NULL).
+
+        XMHOOK_UserData - (APTR) ti_Data will be stored in the
+            xmh_UserData field of the XMHook structure. This field can
+            come andy to store private data. (Defaults to NULL).
+
+        XMHOOK_RemoveHookFunc - (APTR) Pointer to a function which will be
+            called upon removing the hook.  This function can be used to
+            free any private resources allocated by the hook when it was
+            created. The template for the function is:
+
+                void RemoveHookFunc (struct XMHook *hook);
+                                     A0
+
+        XMHOOK_LoadModFunc - (APTR) Pointer to the hook function which
+            loads a module. The template for the function is:
+
+                LONG LoadModFunc (BPTR fileHandle, struct SongInfo *song,
+                                  D0               A0
+                    struct XMHook *loader);
+                    A1
+
+            `fileHandle' is an open file to load the module from. The
+            caller will take care to close it for you. The loader should
+            return RETURN_OK for success, or any other AmigaDOS error
+            code to mean failure.  In particular, RETURN_WARN indicates
+            that something went wrong, but the song is ok.  The special
+            error code ERROR_IOERR can be used when some dos.library
+            call (e.g.: Read()) failed.  In the latter case, the
+            AmigaDOS IoErr value should be set to explain the specific
+            cause of the problem.
+            (This tag is required by all NT_XMLOADER type hooks).
+
+        XMHOOK_SaveModFunc - (APTR) Pointer to the hook function which
+            saves a module. The template for the function is:
+
+                LONG SaveModFunc (BPTR fileHandle, struct SongInfo *song,
+                                  D0               A0
+                    struct XMHook *saver);
+                    A1
+
+            fileHandle is an open file to save the module to. The caller
+            will take care to close it for you. The saver should return
+            RETURN_OK for success, or any other AmigaDOS error code to
+            mean failure.  In particular, RETURN_WARN indicates that
+            something went wrong, but the song is ok.  The special
+            error code ERROR_IOERR can be used when some dos.library
+            call (e.g.: Read()) failed.  In the latter case, the
+            AmigaDOS IoErr value should be set to explain the specific
+            cause of the problem.
+            (This tag is required by all NT_XMSAVER type hooks).
+
+
+        XMHOOK_IdentifyModFunc - (APTR) Pointer to the hook function
+            which identifies a module format. The template for the
+            function is:
+
+                struct XMHook *IdentifyModFunc (BPTR fileHandle,
+                                                D0
+                    struct XMHook *hook,struct TagItem *tagList);
+                    A0                  A1
+
+            fileHandle is an open file to try the identification on. The
+            caller will take care to close it. NOTE: Do not make assumptions
+            on the initial file position (e.g: seek yourself to position 0
+            if you need to). This funtion should return a pointer to a valid
+            loader (usually the one passed in) if the format is recognized,
+            NULL otherwhise.  (This tag is required by all NT_XMLOADER type
+            hooks).
+
+    RESULT
+        hook - Pointer to the newly allocated XMHook structure, or
+            NULL for failure.
+
+    SEE ALSO
+        xmRemHook(), xmIdentifyModule(), xmLoadSong(), xmSaveSong()
+
+\fxmodule/xmAddInstrumentA                             xmodule/xmAddInstrumentA
+
+    NAME
+        xmAddInstrumentA -- Adds a instrument to a song
+        xmAddInstrument -- Varargs stub for xmAddInstrumentA
+
+    SYNOPSIS
+        instrument = xmAddInstrumentA(si, instrNum, tagList)
+        D0                            A0  D0        A1
+
+        struct Instrument *xmAddInstrumentA(struct SongInfo *,LONG,
+            struct TagItem *);
+
+
+        instrument = xmAddInstrument(si,instrNum,tag1,...)
+
+        struct Instrument *xmAddInstrument(struct SongInfo *,LONG,LONG,...);
+
+    FUNCTION
+        Adds an instrument to a song by calling the SNGM_ADDINSTRUMENT method
+.
+
+    INPUTS
+        si - pointer to the song to which the instrument should be added.
+        tagList - optional TagList.  See SNGM_ADDINSTRUMENT for possible tags
+.
+
+    RESULT
+        Pointer to the new instrument or NULL for failure.
+
+    NOTE
+        In order to add instruments, you should have exclusive access to
+        the song.  Always obtain a lock before you call this function on
+        public songs.
+
+    SEE ALSO
+        xmRemInstrument(), songclass/SNGM_REMINSTRUMENT
+
+\fxmodule/xmAddPatternA                                   xmodule/xmAddPatternA
+
+    NAME
+        xmAddPatternA -- Adds a pattern to a song
+        xmAddPattern -- Varargs stub for xmAddPatternA
+
+    SYNOPSIS
+        pattern = xmAddPatternA(si, tagList)
+        D0                      A0  A1
+
+        struct Pattern *xmAddPatternA(struct SongInfo *,struct TagItem *);
+
+
+        pattern = xmAddPattern (si,tag1,...)
+
+        struct Pattern *xmAddPattern(struct SongInfo *,LONG Tag1,...);
+
+    FUNCTION
+        Adds a pattern to a song by calling the SNGM_ADDPATTERN method.
+
+    INPUTS
+        si - pointer to the song to which the pattern should be added.
+        tagList - optional TagList.  See SNGM_ADDPATTERN for possible tags.
+
+    RESULT
+        Pointer to the new pattern or NULL for failure.
+
+    NOTE
+        In order to add patterns, you should have exclusive access to
+        the song.  Always obtain a lock before you call this function on
+        public songs.
+
+    SEE ALSO
+        xmRemPattern(), songclass/SNGM_ADDPATTERN
+
+\fxmodule/xmAddSongA                                         xmodule/xmAddSongA
+
+    NAME
+        xmAddSongA -- Add a song to the song list
+        xmAddSong -- Varargs stub for xmAddSongA
+
+    SYNOPSIS
+        success = xmAddSongA(songInfo,position,tagList);
+        D0                   A0       A1       A2
+
+        ULONG xmAddSongA(struct SongInfo *,struct SongInfo *,
+            struct TagItem *);
+
+
+        success = xmAddSong(songInfo,position,tag1,...);
+
+        ULONG xmAddSong(struct SongInfo *,struct SongInfo *,
+            LONG,...);
+
+    FUNCTION
+        Adds a song to the public song list. Trying to add a song
+        twice is harmless.
+
+    INPUTS
+        songInfo - song to be added.  Passing a NULL pointer is safe.
+        position - Position to add the song in.  Passing NULL adds the
+            song in the head of the list, while ~0 adds it in the tail.
+            passing a pointer to a SongInfo already in the list inserts
+            the first song _after_ the other one.
+        tagList - pointer to a TagList for more arguments.
+
+    TAGS
+        XMSNG_Active - (BOOL) Makes this song the active one. The
+            default is to just add the song without activating it.
+
+    RESULT
+        success - Will be 0 for failure, in which case the song will
+            not be added to the songs list. Currently, xmAddSongA()
+            will never fail.
+
+    NOTE
+        The song list is protected from multiple tasks access by a
+        SignalSemaphore in XModuleBase. This call will wait if the
+        song list has been locked by another task. Beware of deadlock
+        conditions!
+
+    SEE ALSO
+        xmRemSong()
+
+\fxmodule/xmCreateSong                                     xmodule/xmCreateSong
+
+    NAME
+        xmCreateSongA -- Create and initialize a new SongInfo structure
+        xmCreateSong -- Varargs stub for xmCreateSongA
+
+    SYNOPSIS
+        songInfo = xmCreateSongA(tagList);
+        D0                      A0
+
+        struct SongInfo *xmCreateSongA(struct TagItem *);
+
+
+        songInfo = xmCreateSong(Tag1,...);
+
+        struct SongInfo *xmCreateSong(ULONG,...);
+
+    FUNCTION
+        Allocates and initializes a new SongInfo structure.  The song
+        can then be added to the song list via xmAddSongA(), in which
+        case, it is no longer required to free it with xmDeleteSong().
+
+    INPUTS
+        tagList - pointer to an optional tag list specifying how to
+            initialize the SongInfo structure.
+
+    TAGS
+        SNGA_DefaultTracks - Sets the default number of pattern tracks for
+            the song. Defaults to 4.
+
+        SNGA_DefaultPattLen - Sets the default number of pattern lines for
+            the song. Defaults to 64.
+
+        SNGA_ReadyToUse - Adds one pattern and one position to the song.
+            Defaults to FALSE.
+
+        XMSNG_AddToList - (struct SongInfo *) Add the song to the song list.
+            When a song is being added to the list, a shared lock is
+            obtained on it to prevent other tasks from modifying (or even
+            remove) the song before you can do your job on it. IT IS YOUR
+            DUTY TO UNLOCK THE SONG as soon as you are done modifying it.
+            Passing NULL adds the song on the head of the list, while -1
+            (~0) will add it to the end of the SongList. Passing in a
+            pointer to another song which is already in the list, will
+            add the new song _after_ the latter.
+            (Default is not adding the song).
+
+        XMSNG_Active - (BOOL) Makes this song the active one. This tag is
+            considered only if the XMSNG_AddToList tag is also passed.
+
+    RESULT
+        songInfo - pointer to the newly allocated SongInfo structure, or
+            NULL for failure.
+
+    SEE ALSO
+        xmDeleteSong(), xmAddSongA()
+
+\fxmodule/xmDeleteSong                                     xmodule/xmDeleteSong
+
+    NAME
+        xmDeleteSong -- Deletes a song
+
+    SYNOPSIS
+        xmDeleteSong(songInfo)
+                     A0
+
+        void xmDeleteSong(struct SongInfo *);
+
+    FUNCTION
+        Deletes a song and all the items attached to it (patterns,
+        instruments, etc.).  This call will also remove the song from
+        the song list if it had been added to it.
+
+    INPUTS
+        songInfo - pointer to the SongInfo structure to be deleted.
+            Passing a NULL pointer is harmless.
+
+    NOTE
+        In order to delete a song which has been added to the public
+        song list, you must first obtain an exclusive lock on it to
+        prevent other tasks from walking on it while you are freeing
+        it's data structures.  The semaphore does NOT need to be
+        relinquished, because the SongInfo structure won't exist any
+        more when this call returns.
+
+    SEE ALSO
+        xmCreateSong(), xmRemSong()
+
+\fxmodule/xmDisplayMessage                             xmodule/xmDisplayMessage
+
+    NAME
+        xmDisplayMessageA -- Displays a message to the user
+        xmDisplayMessage -- Varargs stub for xmDisplayMessageA()
+
+    SYNOPSIS
+        xmDisplayMessageA(level, message, args)
+                          DO     A0       A1
+
+        void xmDisplayMessageA(ULONG,APTR,LONG *);
+
+
+        xmDisplayMessage(level, message, ...)
+
+        void xmDisplayMessage(ULONG,APTR,...);
+
+    FUNCTION
+        Outputs a string in the XModule log window or in the 'action' field
+        of the progress indicator.  The string is printf-formatted before
+        being output.
+
+    INPUTS
+        level - a number from 0 to 7 which indicates the importance of the
+            message.  7 is used for very annoying messages (eg: debug output)
+,
+            0 for very important things (eg: disaster).
+            If you set the XMDMF_USECATALOG bit in the level parameter, you
+            can pass a catalog string number instead of a pointer to a string
+.
+            Specifying XMDMF_ACTION, the message will be put in the 'action'
+            field of the progress indicator.
+            If the flag XMDMF_DOSFAULT is specified, a Fault() output is
+            formatted using the message as an header.  In this case, the
+            level parameter takes another meaing: The lower word can contain
+            an AmigaDOS error code. If it is 0, IoErr() will be used to get
+            the error code.
+        message - pointer to a string or catalog message number,
+            when the XMDMF_USECATALOG flag is set.
+        args - arguments for formatting.
+
+    EXAMPLES
+        xmDisplayMessage (XMDMF_ALERT,
+            "The application `%s' fucked up Windoze95 because %s.",
+            "(unknown name)", "an error occurred");
+
+        xmDisplayMessage (XMDMF_USE_CATALOG | XMDMF_COMMENT,
+            MSG_LIFE_UNIVERSE_AND_ALLTHAT, 42, "Fortytwo", "For tea too");
+
+        xmDisplayMessageA (XMDMF_ACTION | XMDMF_USECATALOG,
+            MSG_READING_COMICS, NULL);
+
+        xmDisplayMessage (XMDMF_DOSFAULT | XMDMF_USECATALOG,
+            MSG_CANT_LOAD_MODULE, ModuleName);
+
+    SEE ALSO
+        xmDisplayProgress()
+
+\fxmodule/xmDisplayProgress                           xmodule/xmDisplayProgress
+
+    NAME
+        xmDisplayProgress -- Uptdates the progress bar indicator
+
+    SYNOPSIS
+        abort = xmDisplayProgress(done, total)
+                                  D0    D1
+
+        LONG xmDisplayMessageA(ULONG,ULONG);
+
+    FUNCTION
+        Updates the position of the fuel gauge in the progress window to
+        show the progress of an operation.  Additionally, it checks for
+        user abort.
+
+    INPUTS
+        done - a number which indicates how much of the work has
+            been already completed
+        total - Total number of operations to do.
+
+    RESULT
+        0 for success, ERROR_BREAK if user abort was detected.  You should
+        always check this return code to abort what you were doing.
+
+\fxmodule/xmIdentifyModule                             xmodule/xmIdentifyModule
+
+    NAME
+        xmIdentifyModule -- Returns the type of a module
+
+    SYNOPSIS
+        loader = xmIdentifyModule(fh, tagList)
+        D0                        D0  A0
+
+        struct XMHook *xmIdentifyModule(BPTR,struct TagItem *);
+
+    FUNCTION
+        Finds out a loader which is able to read the given module.
+
+    INPUTS
+        fh - AmigaDOS FileHandle to examine.
+        tagList - Additional parameters. Leave it NULL for now.
+
+    RESULT
+        loader - Pointer to the first loader which knows how to load this
+            module, or NULL otherwise.
+
+    NOTE
+        Before you call this function, you must first obtain a lock on the
+        loaders list to protect yourself from other tasks which might remove
+        the returned loader before you can actually use it.
+
+    SEE ALSO
+
+\fxmodule/xmLoadModuleA                                   xmodule/xmLoadModuleA
+
+    NAME
+        xmLoadModuleA -- Loads a module and converts it in XModule format
+        xmLoadModule -- Varargs stub for xmLoadModuleA
+
+    SYNOPSIS
+        songInfo = xmLoadModuleA(fileName,tagList)
+        D0                       A0       A1
+
+        struct SongInfo *xmLoadModuleA(STRPTR,struct TagItem *);
+
+
+        songInfo = xmLoadModule(fileName,tag,...)
+
+        struct SongInfo *xmLoadModule(STRPTR,LONG,...);
+
+    FUNCTION
+        Loads fileName using the correct module loader.
+
+    INPUTS
+        fileName - File to read. Can be NULL if the XMSNG_FileHandle
+            tag is passed.
+        tagList - Additional parameters (see below).  Can be NULL.
+
+    TAGS
+        XMSNG_OldSong - ti_Data is the pointer to a SongInfo which will be
+            freed as soon as the module format has been determined.  This
+            is useful when the user wants to replace a song with another
+            one.  Passing NULL uses the currently active song.
+
+        XMSNG_AddToList - (struct SongInfo *) Add the song to the song list.
+            When a song is being added to the list, a shared lock is
+            obtained on it to prevent other tasks from modifying (or even
+            remove) the song before you can do your job on it. IT IS YOUR
+            DUTY TO UNLOCK THE SONG as soon as you are done modifying it.
+            Passing NULL adds the song on the head of the list, while -1
+            (~0) will add it to the end of the SongList. Passing in a
+            pointer to another song which is already in the list, will
+            add the new song _after_ the latter.
+            (Default is not adding the song).
+
+        XMSNG_Loader - (struct XMHook *) Disables automatic format
+            checking and forces loading the module with the given
+            loader. (Defaults to NULL).
+
+        XMSNG_FileHandle - (BPTR) pointer to an open AmigaDOS
+            FileHandle to read the module from.  The file must
+            already be positioned over the beginning of the module data.
+            NOTE: Even if this tag is passed, the fileName parameter is
+                still used to fill in the SongName field in case it is
+                missing inside the module AND the filesystem does not
+                support the ACTION_EXAMINE_FH packet.
+            NOTE: Some loaders may require a fully seekable file, so be
+                careful when using pipes.
+            NOTE: automatic data decompression is not possible when
+                XMSNG_FileHandle is passed.
+            (Defaults to NULL).
+
+        XMSNG_IFFHandle - (BPTR) pointer to an already initialized
+            IFFHandle to read the module from.  The IFF must
+            already be positioned over the beginning of the module FORM.
+            Even if this tag is passed, the fileName parameter is still
+            used to fill in the SongName field in case it is missing
+            inside the module.
+            NOTE: The iffparse.library can deal with non-seekable files,
+                but some loaders may require a fully seekable file, so be
+                careful when using pipes.
+            NOTE: automatic file decompression is not possible when
+                XMSNG_IFFHandle is passed.
+            (Defaults to NULL).
+
+        XMSNG_Active - (BOOL) Makes this song the active one. This tag is
+            considered only if the XMSNG_AddToList tag is also passed.
+
+    RESULT
+        songInfo - Pointer to the newly allocated SongInfo structure, or
+            NULL for failure.  This function will not fail if the module is
+            partially corrupted. Anyway, the returned SongInfo will always
+            be valid. You can check IoErr() to know if there were problems.
+
+    EXAMPLE
+        /* Load a song and add it to the SongList */
+        si = xmLoadSongTags (file, NULL,
+            XMSNG_AddToList, (struct SongInfo *)~0,
+            TAG_DONE);
+
+        /* Check for errors even if si is not NULL */
+        error = IoErr();
+
+        /* Release Semaphore got by xmLoadSong() */
+        if (si) ReleaseSemaphore (&si->Lock);
+
+    SEE ALSO
+        xmAddSongA(), xmIdentifyModule()
+
+\fxmodule/xmLockActiveSong                             xmodule/xmLockActiveSong
+
+    NAME
+        xmLockActiveSong -- Obtains an lock on the active song
+
+    SYNOPSIS
+        song = xmLockActiveSong(mode);
+        D0                        D0:16
+
+        struct SongInfo *xmActivateSong(UWORD);
+
+    FUNCTION
+        Obtains an exclusive or shared lock on the active song,
+        waiting if needed.  This call is a shortcut to:
+
+            ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+            ObtainSemaphore[Shared] (&XModuleBase->xm_CurrentSong.Lock);
+            ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+        To unlock a song obtained in this way, you just need to
+        ReleaseSemaphore() it.
+
+        You MUST always lock a song before you even think to
+        read from -or write to- its data!
+
+    INPUTS
+        mode - one of SM_SHARED or SM_EXCLUSIVE.
+
+    RESULT
+        song - Pointer to the song which *was* active at the time
+            you called xmLockActiveSong().  The song will be
+            locked for you.  The result will be NULL if no song
+            is active.
+
+    NOTE
+        Please be careful if you call this function while holding
+        locks on other songs.  Doing so, you run the risk of causing
+        deadlock condition.
+        This function does only lock the song; it is NOT guaranteed
+        that it will remain the active one.
+
+    SEE ALSO
+
+\fxmodule/xmProcessSongA                                 xmodule/xmProcessSongA
+
+    NAME
+        xmProcessSongA -- Performs complex processing on a song
+        xmProcessSong -- Varargs stub for xmProcessSongA()
+
+    SYNOPSIS
+        result = xmProcessSongA(si, reserved, tagList)
+                                A0  A1,       A2
+
+        LONG xmProcessSongA(struct SongInfo *,void *,struct TagItem *);
+
+
+        result = xmProcessSong(si, reserved, tag1, ...)
+
+        LONG xmProcessSongA(struct SongInfo *,void *,LONG,...);
+
+    FUNCTION
+        Performs complex processing operations on a song.
+
+    INPUTS
+        si - pointer to the song to be processed.
+        reserved - Reserved for future use.
+        tagList - List of arguments.  See below for possible tags.
+
+    RESULT
+        The result depends on the kind of operation done.  For most
+        operations, it's value will be 0 to indicate success and
+        an AmigaDOS error code which indicates the cause for failure.
+
+    TAGS
+        XMSNG_Optimize - (LONG).  Tries to reduce the song size by removing
+            all unused data.  Possible optimizations are:
+                - XMOF_REM_UNUSED_PATTERNS
+                - XMOF_REM_DUP_PATTERNS
+                - XMOF_CUT_PATTERNS
+                - XMOF_REM_UNUSED_INSTRS
+                - XMOF_CUT_INSTR_LOOPS
+                - XMOF_CUT_INSTR_TAILS
+                - XMOF_DEFAULT
+            XMOF_DEFAULT will select all the optimizations choosen by
+            the user in addition to the ones specified with the other flags.
+
+        XMSNG_RemapInstruments - (BOOL) Performs instruments remapping.
+
+        XMSNG_LimitPatterns - (UWORD,UWORD) Limits the length all the
+            patterns inside a minimum and maximum value.  The upper 16bits
+            of the argument are the minimum value, the lower ones are
+            the maximum value.  Patterns outside this limits will be grown
+            or split as appropriate.
+
+        XMSNG_Join - (struct SongInfo *) - Joins the song with another one.
+
+        XMSNG_Merge - (struct SongInfo *) - Merges the song with another one.
+
+    NOTE
+        In order to process a song, you should have exclusive access to
+        it.  Always obtain a lock before you call this function on
+        public songs.
+
+\fxmodule/xmRemHook                                           xmodule/xmRemHook
+
+    NAME
+        xmRemHook -- Removes an XModule Hook
+
+    SYNOPSIS
+        xmRemHookA(xmHook)
+                   A0
+
+        void xmRemHook(struct XMHook *);
+
+    FUNCTION
+        Removes an XModule Hook, calls its RemoveHookFunc() and then
+        frees its memory.
+
+    INPUTS
+        xmHook - pointer to the hook to be removed.
+
+    SEE ALSO
+        xmAddHook()
+
+\fxmodule/xmRemInstrument                               xmodule/xmRemInstrument
+
+    NAME
+        xmRemInstrument -- Removes an instrument from a song
+
+    SYNOPSIS
+        xmRemInstrument(si, instrNum)
+                        A0  D0
+
+        void xmRemInstrument(struct SongInfo *,LONG);
+
+    FUNCTION
+        Removes an instrument from a song by calling the SNGM_REMINSTRUMENT
+        method.
+
+    INPUTS
+        si - pointer to the song to which the instrument should be removed.
+        mum - Number of the instrument to be removed.
+
+    NOTE
+        In order to remove instruments, you should have exclusive access to
+        the song.  Always obtain a lock before you call this function on
+        public songs.
+
+    SEE ALSO
+        xmAddInstrumentA(), songclass/SNGM_REMINSTRUMENT
+
+\fxmodule/xmRemPattern                                     xmodule/xmRemPattern
+
+    NAME
+        xmRemPattern -- Removes a pattern from a song
+
+    SYNOPSIS
+        xmRemPattern(si, pattNum, replaceWith)
+                     A0  D0,      D1
+
+        void xmRemPattern(struct SongInfo *,LONG,LONG);
+
+    FUNCTION
+        Removes a pattern from a song by calling the SNGM_REMPATTERN method.
+
+    INPUTS
+        si - pointer to the song to which the pattern should be removed.
+        mum - Number of the pattern to be removed.
+        replaceWith - What to put in the song sequence in place of the
+            deleted pattern.
+
+    NOTE
+        In order to remove patterns, you should have exclusive access to
+        the song.  Always obtain a lock before you call this function on
+        public songs.
+
+    SEE ALSO
+        xmAddPatternA(), songclass/SNGM_REMPATTERN
+
+\fxmodule/xmRemSong                                           xmodule/xmRemSong
+
+    NAME
+        xmRemSong -- Remove a song from the song list
+
+    SYNOPSIS
+        success = xmRemSong(songInfo);
+        D0                  A0
+
+        ULONG xmRemSong(struct SongInfo *);
+
+    FUNCTION
+        Removes a song from the public song list. It is safe to call this
+        function even if the song has not been added to the song list. If
+        the passed SongInfo is the active one, another song will be
+        selected automatically.
+
+    INPUTS
+        songInfo - song to be removed. If NULL, this function will take
+            no action.
+
+    RESULT
+        success - Will be 0 for failure, in which case the song will
+            not be removed from the song list. Currently,
+            xmRemSong() will never fail.
+
+    NOTE
+        In order to remove a song, you must first obtain an exclusive
+        lock on it.
+
+        The song list is also protected from multiple task access by
+        a SignalSemaphore. This call will wait if the song list has
+        been locked by another task. Beware of deadlock conditions!
+
+    SEE ALSO
+        xmAddSongA()
+
+\fxmodule/xmSaveModuleA                                   xmodule/xmSaveModuleA
+
+    NAME
+        xmSaveModuleA -- Saves a module to the specified file format
+        xmSaveModule -- Varargs stub for xmSaveModuleA
+
+    SYNOPSIS
+        error = xmSaveModuleA(songInfo, fileName, saver, tagList)
+        D0                    A0        A1        A2     A3
+
+        LONG xmSaveModuleA(struct SongInfo *,STRPTR,struct XMHook *,
+            struct TagItem *);
+
+
+        error = xmSaveModule(songInfo, fileName, saver, tag1,...)
+
+        LONG xmSaveModule(struct SongInfo *,STRPTR,struct XMHook *,
+            LONG,...);
+
+    FUNCTION
+        Saves songInfo to fileName using the specified saver.
+
+    INPUTS
+        songInfo - Song to save.
+        fileName - AmigaDOS filename to write the song to.
+        saver - Pointer to the saver to use.  Pass NULL to use
+            the default saver.
+
+    TAGS
+        No tags are defined for this call.
+
+    RESULT
+        error - RETURN_OK for success, or any other AmigaDOS error code
+            to mean failure.  In particular, RETURN_WARN indicates that
+            something went wrong, but the song is still ok. The special
+            error code ERROR_IOERR means that some dos.library
+            call (e.g.: Read()) failed.  In the latter case, the
+            AmigaDOS IoErr() value will be set to explain the specific
+            cause of the problem.
+
+    SEE ALSO
+
+\fxmodule/xmSetInstrumentA                             xmodule/xmSetInstrumentA
+
+    NAME
+        xmSetInstrumentA -- Sets an instrument's attributes
+        xmSetInstrument -- Varargs stub for xmSetInstrumentA
+
+    SYNOPSIS
+        success = xmSetInstrumentA(si, instrNum, tagList)
+        D0                         A0  D0        A1
+
+        ULONG xmSetInstrumentA(struct SongInfo *,LONG,
+            struct TagItem *);
+
+
+        success = xmSetInstrument(si,instrNum,tag1,...)
+
+        ULONG xmSetInstrument(struct SongInfo *,LONG,LONG,...);
+
+    FUNCTION
+        Sets an instrument's attributes by calling the SNGM_SETINSTRUMENT
+        method.
+
+    INPUTS
+        si - pointer to the song which contains the instrument to be set.
+        tagList - instrument attributes to set.  See SNGM_SETINSTRUMENT
+            for possible tags.
+
+    RESULT
+        non zero for success.
+
+    NOTE
+        In order to set instruments' attributes, you should have
+        exclusive access to the song.  Always obtain a lock before
+        you call this function on public songs.
+
+    SEE ALSO
+        xmAddInstrument(), songclass/SNGM_SETINSTRUMENT
+
+\fxmodule/xmSetPatternA                                   xmodule/xmSetPatternA
+
+    NAME
+        xmSetPatternA -- Sets pattern attributes
+        xmSetPattern -- Varargs stub for xmSetPatternA
+
+    SYNOPSIS
+        success = xmSetPatternA(si, pattNum, tagList)
+        D0                      A0  D0       A1
+
+        ULONG xmSetPatternA(struct SongInfo *,ULONG,struct TagItem *);
+
+
+        success = xmSetPattern (si,pattNum,tag1,...)
+
+        ULONG xmSetPattern(struct SongInfo *,ULONG,LONG Tag1,...);
+
+    FUNCTION
+        Sets attributes of a pattern by calling the SNGM_SETPATTERN method.
+
+    INPUTS
+        si - pointer to the song which contains the pattern to be set.
+        tagList - list of attributes to set.  See SNGM_SETPATTERN for
+            possible tags.
+
+    RESULT
+        Non zero for success
+
+    NOTE
+        In order to set patterns attributes, you should have exclusive
+        access to the song.  Always obtain a lock before you call this
+        function on public songs.
+
+    SEE ALSO
+        xmAddPattern(), songclass/SNGM_SETPATTERN
+
+\fxmodule/xmSetSongLen                                     xmodule/xmSetSongLen
+
+    NAME
+        xmSetSongLen -- Set the number of song positions
+
+    SYNOPSIS
+        sequence = xmSetSongLen(songInfo, length)
+        D0                      A0        D0:16
+
+        UWORD *xmSetSongLen(struct SongInfo *, UWORD);
+
+    FUNCTION
+        Allocates space in the song for a sequence table of the given
+        length. If no sequence exists yet, a new one is allocated.
+        Increasing the song length may require the sequence table to be
+        expanded, in which case it will be re-allocated and the old sequence
+        entries will be copied. Decreasing the song length could also cause
+        a reallocation of the sequence table if the size drops down enough.
+        Setting the song length to 0 will free the sequence table and return
+        NULL.
+
+    INPUTS
+        songInfo - pointer to a SongInfo structure.
+        length - new length of song sequence table.
+
+    NOTE
+        This function will also adjust the CurrentPos field if it is found
+        beyond the song end
+
+    BUGS
+        This funtion is quite useless because all it does is setting the
+        SNGA_Length attribute in the song object.  Setting the SNGA_Length
+        attribute yourself is easier and faster if you are already setting
+        other attributes in the same song.
+
+   RESULT
+        Pointer to the newly allocated sequence table or NULL for failure,
+        in which case the previous sequence table is left untouched.
+
+\f
\ No newline at end of file
diff --git a/BoopsiStubs.h b/BoopsiStubs.h
new file mode 100644 (file)
index 0000000..a8202c7
--- /dev/null
@@ -0,0 +1,181 @@
+#ifndef BOOPSISTUBS_H
+#define BOOPSISTUBS_H
+/*
+**     $VER: BoopsiStubs.h 1.2 (1.9.97)
+**
+**     Copyright (C) 1997 Bernardo Innocenti. All rights reserved.
+**
+**     Use 4 chars wide TABs to read this file
+**
+**     Using these inline versions of the amiga.lib boopsi support functions
+**     results in faster and smaller code against their linked library
+**     counterparts. When debug is active, these functions will also
+**     validate the parameters you pass in.
+**
+*/
+
+#ifndef COMPILERSPECIFIC_H
+#include "CompilerSpecific.h"
+#endif /* COMPILERSPECIFIC_H */
+
+#ifndef DEBUG_H
+#include "Debug.h"
+#endif /* DEBUG_H */
+
+/* This definition will prevent the redefinition of the following stubs with
+ * their amiga.lib equivalents. This only works if you are using a patched
+ * version of <clib/alib_protos.h>
+ */
+#define USE_BOOPSI_STUBS
+
+
+
+/* the _HookPtr type is a shortcut for a pointer to a hook function */
+
+typedef ASMCALL ULONG (*_HookPtr)
+       (REG(a0, Class *), REG(a2, Object *), REG(a1, APTR));
+
+INLINE ULONG CoerceMethodA (struct IClass *cl, Object *o, Msg message)
+{
+       ASSERT_VALIDNO0(cl)
+       ASSERT_VALID(o)
+
+       return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)message);
+}
+
+INLINE ULONG DoSuperMethodA (struct IClass *cl, Object *o, Msg message)
+{
+       ASSERT_VALIDNO0(cl)
+       ASSERT_VALID(o)
+
+       cl = cl->cl_Super;
+       return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)message);
+}
+
+INLINE ULONG DoMethodA (Object *o, Msg message)
+{
+       Class *cl;
+       ASSERT_VALIDNO0(o)
+       cl = OCLASS (o);
+       ASSERT_VALIDNO0(cl)
+
+       return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)message);
+}
+
+
+
+/* Var-args versions of the above functions.  SAS/C is clever enough to inline these,
+ * while gcc and egcs refuse to inline a function with '...' (yikes!).  The GCC
+ * versions of these functions are macro blocks similar to those  used in the
+ * inline/#?.h headers.
+ */
+#if defined(__SASC) || defined (__STORM__)
+
+       INLINE ULONG CoerceMethod (struct IClass *cl, Object *o, ULONG MethodID, ...)
+       {
+               ASSERT_VALIDNO0(cl)
+               ASSERT_VALID(o)
+
+               return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)&MethodID);
+       }
+
+       INLINE ULONG DoSuperMethod (struct IClass *cl, Object *o, ULONG MethodID, ...)
+       {
+               ASSERT_VALIDNO0(cl)
+               ASSERT_VALID(o)
+
+               cl = cl->cl_Super;
+               return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)&MethodID);
+       }
+
+       INLINE ULONG DoMethod (Object *o, ULONG MethodID, ...)
+       {
+               Class *cl;
+
+               ASSERT_VALIDNO0(o)
+               cl = OCLASS (o);
+               ASSERT_VALIDNO0(cl)
+
+               return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)&MethodID);
+       }
+
+       /* varargs stub for the OM_NOTIFY method */
+       INLINE void NotifyAttrs (Object *o, struct GadgetInfo *gi, ULONG flags, Tag attr1, ...)
+       {
+               ASSERT_VALIDNO0(o)
+               ASSERT_VALID(gi)
+
+               DoMethod (o, OM_NOTIFY, &attr1, gi, flags);
+       }
+
+       /* varargs stub for the OM_UPDATE method */
+       INLINE void UpdateAttrs (Object *o, struct GadgetInfo *gi, ULONG flags, Tag attr1, ...)
+       {
+               ASSERT_VALIDNO0(o)
+               ASSERT_VALID(gi)
+
+               DoMethod (o, OM_UPDATE, &attr1, gi, flags);
+       }
+
+#elif defined(__GNUC__)
+
+       #define CoerceMethod(cl, o, msg...)                                                                                             \
+       ({                                                                                                                                                              \
+               ULONG _msg[] = { msg };                                                                                                         \
+               ASSERT_VALIDNO0(cl)                                                                                                                     \
+               ASSERT_VALID(o)                                                                                                                         \
+               ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)_msg);          \
+       })
+
+       #define DoSuperMethod(cl, o, msg...)                                                                                    \
+       ({                                                                                                                                                              \
+               Class *_cl;                                                                                                                                     \
+               ULONG _msg[] = { msg };                                                                                                         \
+               ASSERT_VALID(o)                                                                                                                         \
+               ASSERT_VALIDNO0(cl)                                                                                                                     \
+               _cl = cl = cl->cl_Super;                                                                                                        \
+               ASSERT_VALIDNO0(_cl)                                                                                                            \
+               ((_HookPtr)_cl->cl_Dispatcher.h_Entry) ((APTR)_cl, (APTR)o, (APTR)_msg);        \
+       })
+
+       #define DoMethod(o, msg...)                                                                                                             \
+       ({                                                                                                                                                              \
+               Class *_cl;                                                                                                                                     \
+               ULONG _msg[] = { msg };                                                                                                         \
+               ASSERT_VALIDNO0(o)                                                                                                                      \
+               _cl = OCLASS(o);                                                                                                                        \
+               ASSERT_VALIDNO0(_cl)                                                                                                            \
+               ((_HookPtr)_cl->cl_Dispatcher.h_Entry) ((APTR)_cl, (APTR)o, (APTR)_msg);        \
+       })
+
+       /* Var-args stub for the OM_NOTIFY method */
+       #define NotifyAttrs(o, gi, flags, attrs...)                                                                             \
+       ({                                                                                                                                                              \
+               Class *_cl;                                                                                                                                     \
+               ULONG _attrs[] = { attrs };                                                                                                     \
+               ULONG _msg[] = { OM_NOTIFY, (ULONG)_attrs, (ULONG)gi, flags };                          \
+               ASSERT_VALIDNO0(o)                                                                                                                      \
+               _cl = OCLASS(o);                                                                                                                        \
+               ASSERT_VALIDNO0(_cl)                                                                                                            \
+               ASSERT_VALID(gi)                                                                                                                        \
+               ((_HookPtr)_cl->cl_Dispatcher.h_Entry) ((APTR)_cl, (APTR)o, (APTR)_msg);        \
+       })
+
+       /* Var-args stub for the OM_UPDATE method */
+       #define UpdateAttrs(o, gi, flags, attrs...)                                                                             \
+       ({                                                                                                                                                              \
+               Class *_cl;                                                                                                                                     \
+               ULONG _attrs[] = { attrs };                                                                                                     \
+               ULONG _msg[] = { OM_UPDATE, (ULONG)_attrs, (ULONG)gi, flags };                          \
+               ASSERT_VALIDNO0(o)                                                                                                                      \
+               _cl = OCLASS(o);                                                                                                                        \
+               ASSERT_VALIDNO0(_cl)                                                                                                            \
+               ASSERT_VALID(gi)                                                                                                                        \
+               ((_HookPtr)_cl->cl_Dispatcher.h_Entry) ((APTR)_cl, (APTR)o, (APTR)_msg);        \
+       })
+#endif
+
+/* Nobody else needs this anymore... */
+#undef _HookPtr
+
+#endif /* !BOOPSISTUBS_H */
diff --git a/Catalogs/HowToTranslate.doc b/Catalogs/HowToTranslate.doc
new file mode 100644 (file)
index 0000000..016bdce
--- /dev/null
@@ -0,0 +1,61 @@
+
+How to make a new catalog for XModule
+*************************************
+
+ This file contains all the strings used in XModule.  Lines beginning with
+a semicolon (;) are comments and are ignored by the catalog generator.
+Every string is made up two lines:
+
+    MSG_READING_TYPE_MODULE_CHN (//)
+    Reading %s module with %ld tracks...
+
+ The first line is a symbol which identifies its meaning inside XModule.
+You do not need to modify it.  The characters "(//)" are required by the
+catalog generator and shouldn't be modified.
+
+ The second line is the message itself.  You should insert the
+translation in your own language here.  In this example, the sequences
+"%s" and "%d" have a special meaning.  At run time, they will be replaced
+by a value, let's say "TakeTracker" and "12", so that the resulting
+string would become "Reading TakeTracker module with 12 tracks...".
+
+ Your language might require a different ordering of the % symbols in the
+sentence, for instance "12 tracks TakeTracker module...".  In this case,
+you can translate the string as follows:
+
+    %2$ld tracks %1$s module...
+
+ The template is %<position>$<sequence>, where <position> is the original
+position of the % command, <sequence> is the original command sequence.
+
+ For those amongst you who are not programmers, the meaning of some strings
+may be obscure.  For instance, you might find a string like "Ok|Cancel",
+which describes the possible answers to a requesters.  You should leave the
+'|' character alone and avoid inserting additional spaces before and after
+it.
+
+ Gadget labels have a "_GAD" postfix, while menu lables end with "_MEN".
+Some gadget labels will have underscore characters ('_') before their key
+shortcut.  When you translate these labels, you shoud try to keep the same
+key (ie: "_Good Morning" -> "Buon _Giorno").  If it isn't possible,
+choose another meaningful letter, but avoid using the same key for two
+different gadgets in the same window, otherwise the user will get confused.
+
+ To help you with the translation, the template catalog "Empty.ct" is
+provided.  This file has blank lines where the translated strings should
+be inserted, followed by comment lines with the original english string.
+Just rename it to <language>.ct and fill in all the empty lines.
+
+ When you are done with the translation, use a catalog generator like
+FlexCat, KitKat or CatComp to make the xxx.catalog file.  Then, test the
+new catalog with XModule and check that every message is correct.  To make
+a catalog with FlexCat, you can use the following command line:
+
+    FlexCat Catalogs/XModule.cd Catalogs/<language>.ct
+        CATALOG=Catalogs/<language>/XModule.catalog
+
+ Each time a new version of XModule gets released, the old catalogs will
+probably have to be updated to include all the new strings.  In this case,
+I'll send you the new catalog translation file and ask you to update it.
+Of course, you are free to refuse if you do not want to do it, but, please,
+let me know this in time, so I can look around for another translator.
diff --git a/Catalogs/Makefile b/Catalogs/Makefile
new file mode 100644 (file)
index 0000000..53b2951
--- /dev/null
@@ -0,0 +1,55 @@
+##
+## Make the stuff in the catalogs/ subdirectory
+##
+## NOTE: FlexCat is a locale catalog generator made by Jochen Wiedmann.
+## It can be found on Aminet in the archive "dev/misc/FlexCat.lha".
+
+
+include /config.mk
+
+LANGUAGES      = deutsch français italiano nederlands
+CATALOGS = $(LANGUAGES:%=%/$(PROJNAME).catalog)
+
+all: $(CATALOGS)
+
+clean:
+       -Delete $(CATALOGS)
+
+.PHONY: all clean NewCTFiles
+
+
+$(CATALOGS): %/$(PROJNAME).catalog : %.ct
+       FlexCat $(PROJNAME).cd $@ CATALOG=$<
+       @FileNote $< "$(@:%.ct=%) catalog for $(PROJNAME)"
+
+# All the catalogs depend on the master catalog descriptor
+$(CATALOGS): $(PROJNAME).cd
+
+
+# Make NewCTFiles to create updated CT files for all supported languages
+#
+NewCTFiles:
+       $(FLEXCAT) $(PROJNAME).cd italiano.ct NEWCTFILE italiano.ct
+       $(FLEXCAT) $(PROJNAME).cd deutsch.ct NEWCTFILE deutsch.ct
+       $(FLEXCAT) $(PROJNAME).cd français.ct NEWCTFILE français.ct
+       $(FLEXCAT) $(PROJNAME).cd nederlands.ct NEWCTFILE nederlands.ct
+       $(FLEXCAT) $(PROJNAME).cd NEWCTFILE empty.ct
+
+
+# old make rules, not used any more
+#
+#Catalogs/Italiano/XModule.catalog: Catalogs/italiano.ct
+#      FlexCat Catalogs/XModule.cd Catalogs/italiano.ct CATALOG=Catalogs/Italiano/XModule.catalog
+#      @FileNote Catalogs/Italiano/XModule.catalog "Italiano catalog for XModule translated by Steven Cantini"
+#
+#Catalogs/Deutsch/XModule.catalog: Catalogs/deutsch.ct
+#      FlexCat Catalogs/XModule.cd Catalogs/deutsch.ct CATALOG=Catalogs/Deutsch/XModule.catalog
+#      @FileNote Catalogs/Deutsch/XModule.catalog "Deutsch catalog for XModule translated by Michael Reichenbach"
+#
+#Catalogs/Français/XModule.catalog: Catalogs/français.ct
+#      FlexCat Catalogs/XModule.cd Catalogs/français.ct CATALOG=Catalogs/Français/XModule.catalog
+#      @FileNote Catalogs/Français/XModule.catalog "Français catalog for XModule translated by Julien Wilk"
+#
+#Catalogs/Nederlands/XModule.catalog: Catalogs/nederlands.ct
+#      FlexCat Catalogs/XModule.cd Catalogs/nederlands.ct CATALOG=Catalogs/Nederlands/XModule.catalog
+#      @FileNote Catalogs/Nederlands/XModule.catalog "Dutch catalog for XModule translated by Ji Yong Dijkhuis"
diff --git a/Catalogs/deutsch.ct b/Catalogs/deutsch.ct
new file mode 100644 (file)
index 0000000..bc36eb7
--- /dev/null
@@ -0,0 +1,1491 @@
+## version $VER: Deutsch.ct 1.0 (12.6.95)
+## language deutsch
+## codeset 0
+;
+## chunk AUTH Deutsch catalog translation by Michael Reichenbach <htw564@htw-dresden.de>
+;**
+;** XModule.cd 3.9
+;**
+;** Copyright (C) 1995,96 Bernardo Innocenti
+;**
+;
+; **********************************
+; * Strings for all source modules *
+; **********************************
+;
+;
+MSG_NULL
+
+;
+; This string should remain blank.
+;
+MSG_OK
+Ok
+;Ok
+;
+MSG_UNDERSCORE_USE_GAD
+
+;_Use
+;
+MSG_UNDERSCORE_OK_GAD
+
+;_Ok
+;
+MSG_UNDERSCORE_CANCEL_GAD
+
+;_Cancel
+;
+MSG_YES_OR_NO
+Ja|Nein
+;Yes|No
+;
+MSG_RETRY_OR_CANCEL
+Nochmal versuchen|Abbrechen
+;Retry|Cancel
+;
+MSG_PROCEED_OR_CANCEL
+
+;Proceed|Cancel
+;
+MSG_CONTINUE
+Weiter
+;Continue
+;
+MSG_NO_FREE_STORE
+Nicht genug freier Speicher.
+;Insufficient memory.
+;
+MSG_BREAK
+Abgebrochen.
+;Aborted.
+;
+MSG_ERR_LOAD
+Kann \"%s\" nicht laden: %s.
+;Unable to load \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_CANT_OPEN
+Kann Datei \"%s\" nicht öffnen: %s.
+;Cannot open file \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_READING
+Fehler beim Lesen von \"%s\": %s.
+;Error reading \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_WRITING
+Fehler beim Schreiben von \"%s\": %s.
+;Error writing \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+;
+; *****************************
+; * Strings for Compression.c *
+; *****************************
+;
+MSG_DECRUNCHING
+Entpacke...
+;Decrunching...
+;
+MSG_NOTHING_IN_ARC
+Im Archiv \"%s\" nichts gefunden.
+;Nothing found in archive \"%s\".
+;
+MSG_CANT_LOAD_COMPRESSED
+Kann komprimierte Datei nicht laden.
+;Unable to load compressed file.
+;
+MSG_ERROR_DECOMPRESSING
+Fehler beim Entpacken der Datei \"%s\": %s.
+;Error decompressing file \"%s\": %s.
+;
+;
+; ********************
+; * Strings for Cx.c *
+; ********************
+;
+MSG_BAD_HOTKEY
+Falsche Commodity HotKey Beschreibung.
+;Bad Commodity HotKey description.
+;
+;
+; ***************************
+; * Strings for all loaders *
+; ***************************
+;
+MSG_READING_PATTS
+Lese Pattern...
+;Reading Patterns...
+;
+MSG_READING_INSTS_INFO
+Lese Informationen über Instrumente...
+;Reading Instruments Info...
+;
+MSG_READING_INSTS
+Lese Instrumente...
+;Reading Instruments...
+;
+MSG_ERR_CANT_LOAD_PATT
+FEHLER: Kann Pattern %ld nicht laden.
+;ERROR: Couldn't load pattern %ld.
+;
+MSG_ERR_CANT_LOAD_INST
+FEHLER: Kann Instrument %lx nicht laden.
+;ERROR: Couldn't load instrument %lx.
+;
+MSG_ERR_NO_MEM_FOR_INST
+FEHLER: Nicht genug freier Speicher für Instrument %lx.
+;ERROR: Not enough memory for instrument %lx.
+;
+MSG_ERR_NOT_A_SAMPLE
+FEHLER: Instrument %lx ist kein Sample.
+;ERROR: Instrument %lx is not a sample.
+;
+MSG_SONG_TOO_LONG
+WARNUNG: Songlänge überschreitet Maximum. Wird abgeschnitten.
+;WARNING: Song length exceeds maximum. Will be truncated.
+;
+MSG_SONG_HAS_TOO_MANY_PATT
+WARNUNG: Song überschreitet maximale Patternzahl.
+;WARNING: Song exceeds maximum number of patterns.
+;
+MSG_PATT_TOO_MANY_TRACKS
+WARNUNG: Pattern %ld hat zu viele Tracks. Verkürze auf %ld Tracks.
+;WARNING: Pattern %ld has too many tracks. Cropping to %ld tracks.
+;
+MSG_PATT_TOO_MANY_LINES
+WARNING: Pattern %ld hat zu viele Linien. Verkürze auf %ld Linien.
+;WARNING: Pattern %ld has too many lines. Cropping to %ld lines.
+;
+MSG_INVALID_NOTE
+WARNUNG: Ungültiger Ton %ld (Patt %ld Track %ld Linie %ld).
+;WARNING: Invalid note %ld (Patt %ld Track %ld Line %ld).
+;
+MSG_UNKNOWN_EFF
+Unbekannter Effekt: $%lx (Patt %ld Track %ld Linie %ld).
+;Unknown effect: $%lx (Patt %ld Track %ld Line %ld).
+;
+MSG_EXTRA_DATA_AFTER_MOD
+WARNUNG: Zusätzliche Daten hinter regulärem Modul: Werden ignoriert.
+;WARNING: Extra data found after valid module: Will be ignored.
+;
+MSG_WRITING_HEADER
+Schreibe Modul Header...
+;Writing Header...
+;
+MSG_WRITING_PATTS
+Schreibe Pattern...
+;Writing Patterns...
+;
+MSG_WRITING_INSTS
+Schreibe Instrumente...
+;Writing Instruments...
+;
+MSG_WRITING_INSTINFO
+Schreibe Informationen über Instrumente...
+;Writing Instruments Info...
+;
+MSG_WRITING_INSTDATA
+Schreibe Instrumentdaten...
+;Writing Instruments Data...
+;
+MSG_NOTE_TOO_LOW
+WARNUNG: Ton in Patt %ld Track %ld Linie %ld ist zu tief.
+;WARNING: Note at Patt %ld Track %ld Line %ld is too low.
+;
+MSG_NOTE_TOO_HIGH
+WARNUNG: Ton in Patt %ld Track %ld Linie %ld ist zu hoch.
+;WARNING: Note at Patt %ld Track %ld Line %ld is too high.
+;
+MSG_NO_MEM_TO_HALVE
+WARNUNG: Nicht genug freier Speicher, um Lautstärke des Instruments %lx zu halbieren.
+;WARNING: Not enough memory to halve volume of instrument %lx.
+;
+;
+; *****************************
+; * Strings for Get/SaveMED.c *
+; *****************************
+;
+MSG_READING_MMD
+Lade MMD%lc Modul...
+;Loading MMD%lc module...
+; The %lc is the MED format (0, 1, 2, ...)
+;
+MSG_UNSUPPORTED_MMD_FORMAT
+FEHLER: Nicht unterstütztes OctaMED Format.
+;ERROR: Unsupported OctaMED format.
+;
+MSG_WRONG_EFFECT_IN_MMD0
+WARNUNG: Der Effekt %lx wird im MMD0 Format nicht unterstützt. Benutze MMD1 oder höher.
+;WARNING: Effect %lx is not supported in MMD0 format. Use MMD1 or better.
+;
+;
+; ***********************************
+; * Strings for ScreamTrackerHook.c *
+; ***********************************
+;
+MSG_ADLIB_INSTR
+FEHLER: Instrument %lx ist ein ADLib %s.
+;ERROR: Instrument %lx is an ADLib %s.
+; %s is the name of the ADLib instrument (eg: Snare).
+;
+MSG_TRACK_OUT_OF_RANGE
+WARNUNG: Track %lx ist außerhalb des Speicherbereichs.
+;WARNING: Track %lx is out of range.
+;
+MSG_UNKNOWN_SAMPLE_COMPRESSION
+WARNUNG: Unbekannte Samplekompression des Instruments %lx.
+;WARNING: Unknown sample compression for instrument %lx.
+;
+MSG_INST_IS_STEREO
+WARNUNG: Instrument %lx ist ein Stereo Sample.
+;WARNING: Instrument %lx is a stereo sample.
+;
+MSG_INST_IS_16BIT
+WARNING: Instrument %lx ist 16bit: kann es nicht laden.
+;WARNING: Instrument %lx is 16bit: unable to load it.
+;
+;
+; *****************************
+; * Strings for TrackerHook.c *
+; *****************************
+;
+MSG_READING_TYPE_MODULE
+Lese %s Modul...
+;Reading %s module...
+; (%s is the module format)
+;
+MSG_EXCEEDS_64_PATTS
+WARNUNG: Modul überschreitet 64 Pattern. ProTracker 2.3 wird benötigt, um es abzuspielen.
+;NOTE: Module exceeds 64 patterns. You need ProTracker 2.3 to play it.
+;
+MSG_MODULE_ID
+
+;Tracker ID: \"%s\"
+;
+MSG_MODULE_HAS_N_CHN
+
+;Module has %lu tracks...
+;
+MSG_SONG_HAS_NO_INSTS
+Diese Datei ist ein Song und enthält keine Instrumente.
+;This file is a song and doesn't contain instruments.
+;
+MSG_EXCEEDS_MAXPAATTS
+WARNUNG: Modul überschreitet %ld Pattern.
+;WARNING: Module execeeds %ld patterns.
+;
+MSG_PATT_WILL_GROW
+Pattern %ld wird auf 64 Linien verlängert (war vorher %ld Linien lang).
+;Pattern %ld will grow to 64 lines (was %ld lines long).
+;
+MSG_SPLITTING_PATT
+Teile Pattern %ld (war vorher %ld Linien lang).
+;Splitting pattern %ld (was %ld lines long).
+;
+; **************************
+; * Strings for SaveMIDI.c *
+; **************************
+;
+MSG_CHOOSING_CHANNELS
+Wähle Kanäle...
+;Choosing Channels...
+;
+MSG_WRITING_MIDI_TRACKS
+Sichere MIDI Tracks...
+;Writing MIDI Tracks...
+;
+MSG_TOO_MANY_CHANNELS
+FEHLER: Song benötigt zu viele MIDI Kanäle.
+;ERROR: Song requires too many MIDI channels.
+;
+;
+; *********************
+; * Strings for Gui.c *
+; *********************
+;
+MSG_REALLY_QUIT_XMODULE
+XModule wirklich beenden?
+;Really Quit XModule?
+;
+MSG_CLOSE_ALL_WINDOWS
+Bitte alle fremden Fenster schließen\n\
+und dann `Weiter' wählen.
+;Please close all visitor windows\n\
+;and then select `Continue'.
+;
+;
+; ***********************
+; * Strings for Instr.c *
+; ***********************
+;
+MSG_UNKNOWN_IFF
+Unbekanntes IFF Format %s.
+;Unknown IFF format %s.
+;
+MSG_ILLEGAL_IFF_STRUCTURE
+Ungültige IFF Struktur.
+;Illegal IFF structure.
+;
+MSG_SELECT_RAW_MODE
+Instrument Format nicht erkannt.\n\
+Bitte RAW Modus wählen.
+;Unrecognized instrument format.\n\
+;Please select RAW mode.
+;
+MSG_RAW_MODES
+Signed 8bit|Unsigned 8bit|Abbrechen
+;Signed 8bit|Unsigned 8bit|Cancel
+;
+MSG_DATATYPES_ERROR
+DataType Fehler: %s.
+;DataTypes error: %s.
+; %s is a detailed description of the problem.
+;
+MSG_UNKNOWN_COMPRESSION
+Unbekannter Kompressionstyp.
+;Unknown compression type.
+;
+MSG_SAMPLE_WRONG_SIZE
+%lu Bit Samples werden nicht unterstützt.
+;%lu bit samples are not supported.
+; %lu is the number of bits per sample.
+;
+MSG_SAMPLE_NOT_MONO
+Andere Samples außer MONO werden nicht unterstützt.
+;Samples other than MONO are not supported.
+;
+MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS
+Samples mit %ld Kanälen werden nicht unterstützt.
+;Samples with %ld channels are not supported.
+;
+MSG_NO_MEMORY_TO_OPTIMIZE_INSTR
+WARNUNG: Nicht genug freier Speicher, um Instrument %lx zu optimieren.
+;WARNING: insufficient memory to optimize instrument %lx.
+;
+MSG_INSTR_WILL_SHRINK
+Instrument %lx wird von %ld auf %ld gekürzt.
+;Instrument %lx will shrink from %ld to %ld.
+;
+MSG_INSTR_DUPES_REMOVED
+Doppelte Instrumente gefunden und entfernt: %lx == %lx.
+;Duplicate instruments found and removed: %lx == %lx.
+;
+MSG_INSTR_UNUSED
+Instrument %lx wird nie benutzt und deshalb entfernt.
+;Instrument %lx was never used and it's being removed.
+;
+;
+; **********************
+; * Strings for Misc.c *
+; **********************
+;
+MSG_OPENLIB_VER_FAIL
+Konnte %s Version %ld oder höher nicht öffnen.
+;Couldn't open \"%s\" version %ld or greater.
+;
+MSG_OPENLIB_FAIL
+Konnte %s nicht öffnen.
+;Couldn't open \"%s\".
+;
+;
+; ***************************
+; * Strings for Operators.c *
+; ***************************
+;
+MSG_CANT_INSERT_PATT
+Kann Pattern nicht einfügen: Maximale Anzahl erreicht.
+;Unable to insert pattern: Maximum number of patterns reached.
+;
+MSG_PATT_UNUSED
+Pattern %ld wird nicht benutzt und deshalb gelöscht.
+;Pattern %ld is not used and is beeing deleted.
+;
+MSG_PATT_CUT
+Pattern %ld wird in Linie %ld abgeschnitten.
+;Pattern %ld will be cut at line %ld.
+;
+MSG_PATT_DUPE
+Doppelte Pattern gefunden und entfernt: %ld == %ld.
+;Duplicate patterns found and removed: %ld == %ld.
+; The two %ld are the pattern numbers.
+;
+MSG_SONG_LEN_DIFFERENT
+Songlängen sind verschieden. Benutze kürzere.
+;WARNING: Song lengths are different. Using shorter one.
+;
+MSG_PATT_LEN_DIFFERENT
+Verschiedene Patternlängen an Position %ld. Benutze kürzere.
+;WARNING: Different pattern lengths at position %ld. Using shorter one.
+;
+MSG_ERR_INSTR_OVERFLOW
+
+;ERROR: Instruments overflow. Try remapping the instruments.
+;
+;
+; ***********************
+; * Strings for Prefs.c *
+; ***********************
+;
+MSG_BAD_PREFS_VERSION
+Falsche Version der Voreinstellungsdatei.
+;Incorrect version of preferences file
+;
+;
+; ****************************
+; * Strings for Requesters.c *
+; ****************************
+;
+MSG_XMODULE_REQUEST
+XModule Anfrage
+;XModule Request
+; This is the title of requesters put out by XModule.
+;
+MSG_CLONE_WB
+Klone Workbench Bildschirm
+;Clone Workbench Screen
+;
+MSG_CLOSE_FILEREQUESTER
+Bitte Dateiauswahlfenster schließen\n und dann `Weiter' wählen.
+;Please close FileRequester\n and then select `Continue'.
+;
+MSG_SELECT_MODULES
+Modul(e) auswählen...
+;Select Module(s)...
+;
+MSG_SELECT_INSTRUMENTS
+Instrument(e) auswählen...
+;Select Instrument(s)...
+;
+MSG_SELECT_PATTERN
+Pattern auswählen...
+;Select Pattern...
+;
+MSG_SAVE_MODULE
+Sichere Modul...
+;Save Module...
+;
+MSG_SAVE_INSTRUMENT
+Sichere Instrument...
+;Save Instrument...
+;
+MSG_SAVE_PATTERN
+Sichere Pattern...
+;Save Pattern...
+;
+MSG_FILE_EXISTS
+Datei \"%s\"\nexistiert bereits.
+;File \"%s\"\nalready exists.
+;
+MSG_OVERWRITE
+Überschreiben|Andere auswählen|Abbrechen
+;Overwrite|Choose Another|Abort
+;
+;
+; **********************
+; * Strings for Song.c *
+; **********************
+;
+MSG_CLOSE_ALL_SONGS
+
+;Please close all open songs\n\
+;and then select `Continue'.
+;
+MSG_UNESPECTED_EOF
+Unerwartetes Dateiende.
+;Unespected end of file.
+;
+MSG_MODULE_LOADED_OK
+Modul geladen OK.
+;Module loaded OK.
+;
+MSG_MODULE_SAVED_OK
+Modul gesichert OK.
+;Module saved OK.
+;
+MSG_UNKNOWN_SAVE_FORMAT
+FEHLER: Unbekanntes Speicherformat.
+;ERROR: Unrecognized save format.
+;
+MSG_INVALID_LOOP_REMOVED
+Ungültige Schleife für Instrument %lx entfernt.
+;Removed invalid loop for instrument %lx.
+;
+MSG_INVALID_LOOP_FIXED
+Ungültige Schleife für Instrument %lx repariert.
+;Fixed invalid loop for instrument %lx.
+;
+MSG_SONG_HAS_NO_PATTS
+WARNUNG: Song hat keine Pattern.
+;WARNING: Song has no patterns.
+;
+MSG_SONG_HAS_NO_SEQ
+WARNUNG: Song hat keine Sequence.
+;WARNING: Song has no sequence.
+;
+MSG_INVALID_SONG_POS
+WARNUNG: Songposition %ld bezieht sich auf Pattern %ld, das nicht existiert.
+;WARNING: Song position %ld references pattern %ld, which doesn't exist.
+;
+MSG_UNKNOWN_MOD_FORMAT
+Kann Modul Format nicht identifizieren.\n\
+(Eine Datendatei als Modul zu laden ist unklug)
+;Unable to identify module format.\n\
+;(Loading a data file as a module is unwise)
+;
+MSG_SOUND_PRO_CANCEL
+SoundTracker 15|ProTracker|Abbrechen
+;SoundTracker 15|ProTracker|Cancel
+;
+MSG_AUTHOR_UNKNOWN
+Unbekannt
+;Unknown
+;
+MSG_SONG_UNTITLED
+Unbenannt
+;Untitled
+;
+;
+; **********************
+; * Strings for Help.c *
+; **********************
+;
+MSG_AMIGAGUIDE_ERROR
+AmigaGuide Fehler:
+;AmigaGuide error:
+; Note one blank space at the end of this line!
+;
+;
+; ***************************
+; * Strings for PattPrefs.c *
+; ***************************
+;
+MSG_PATTPREFS_TITLE
+
+;Pattern Editor Settings
+;
+;
+MSG_ADVANCE_TRACKS_GAD
+
+;Advance _Tracks
+;
+MSG_ADVANCE_LINES_GAD
+
+;Advance _Lines
+;
+MSG_MAX_UNDO_LEVELS_GAD
+
+;Max _Undo Levels
+;
+MSG_MAX_UNDO_MEM_GAD
+
+;Max Undo _Memory
+;
+MSG_CLIPBOARD_UNIT_GAD
+
+;Cli_pboard Unit
+;
+MSG_SCROLLER_POS_GAD
+
+;_Scroller Position
+;
+MSG_VERT_WRAP_GAD
+
+;_Vertical Wrap
+;
+MSG_HORIZ_WRAP_GAD
+
+;_Horizontal Wrap
+;
+MSG_HEX_LINE_NUMBERS_GAD
+
+;He_x Line Numbers
+;
+MSG_BLANK_ZERO_GAD
+
+;Blank _Zero Digits
+;
+MSG_BACKDROP_GAD
+
+;Backdrop _Window
+;
+MSG_HORIZ_SCROLLER_GAD
+
+;Ho_rizontal Scroller
+;
+MSG_DO_RULER_GAD
+
+;Display Cursor Ruler
+;
+MSG_DO_TINY_LINES_GAD
+
+;Display Tiny Lines
+;
+MSG_EDITOR_FONT_GAD
+
+;_Editor Font
+;
+MSG_BACKGROUND_PEN_GAD
+
+;Background
+;
+MSG_TEXT_PEN_GAD
+
+;T_ext
+;
+MSG_LINES_PEN_GAD
+
+;Li_nes
+;
+MSG_TINY_LINES_PEN_GAD
+
+;Tin_y Lines
+;
+MSG_RIGHT_GAD
+
+;Right
+;
+MSG_LEFT_GAD
+
+;Left
+;
+MSG_OFF_GAD
+
+;Off
+;
+MSG_PATT_TOO_LONG
+FEHLER: Pattern würde die maximale Linienanzahl überschreiten.
+;ERROR: Pattern would exceed the maximum number of lines.
+;
+;
+; *****************************
+; * Strings for SequenceWin.c *
+; *****************************
+;
+;
+MSG_SEQUENCE_TITLE
+
+;Sequence Editor
+;
+;
+MSG_SEQUENCE_GAD
+
+;Sequence
+;
+MSG_UNDERSCORE_ADD_GAD
+
+;_Add
+;
+MSG_UP_GAD
+
+;Up
+;
+MSG_DOWN_GAD
+
+;Down
+;
+MSG_UNDERSCORE_NAME_GAD
+
+;_Name
+;
+MSG_PATTERNS_GAD
+
+;Patterns
+;
+MSG_UNDERSCORE_UP_GAD
+
+;_Up
+;
+MSG_UNDERSCORE_DOWN_GAD
+
+;_Down
+;
+MSG_UNDERSCORE_INS_GAD
+
+;_Ins
+;
+;
+MSG_UNNAMED
+
+;-- unnamed --
+;
+;
+; ********************************
+; * Strings for InstrumentsWin.c *
+; ********************************
+;
+MSG_INSTRUMENTS_TITLE
+
+;Instruments
+;
+;
+MSG_VOLUME_GAD
+
+;_Volume
+;
+MSG_FINETUNE_GAD
+
+;_FineTune
+;
+MSG_LENGTH_GAD
+
+;Length
+;
+MSG_KIND_GAD
+
+;_Kind
+;
+MSG_EDIT_DOTS_GAD
+
+;_Edit...
+;
+MSG_SAMPLE_GAD
+
+;Sample
+;
+MSG_SYNTH_GAD
+
+;Synth
+;
+MSG_HYBRID_GAD
+
+;Hybrid
+;
+;
+MSG_INSTRUMENTS_MEN
+
+;Instruments
+;
+MSG_LOAD_MEN
+
+;Load...
+;
+MSG_REMAP_MEN
+
+;Remap
+;
+MSG_SAVE_COMPRESSED_MEN
+
+;Save Compressed
+;
+MSG_SAVE_RAW_MEN
+
+;Save Raw
+;
+;
+MSG_EMPTY
+
+;-- empty --
+;
+;
+; *************************
+; * Strings for PlayWin.c *
+; *************************
+;
+MSG_PLAY_TITLE
+
+;Play
+;
+;
+MSG_VOL_GAD
+
+;_Vol
+;
+MSG_POS_GAD
+
+;Pos
+;
+MSG_TIME_GAD
+
+;Time
+;
+MSG_RST_GAD
+
+;_Rst
+; Stands for Reset/Restart
+;
+MSG_PLAYER_INIT_ERR
+Abspielprogramm Initialisierungsfehler: %ld.
+;Player initialization error: %ld.
+;
+;
+; *****************************
+; * Strings for ProgressWin.c *
+; *****************************
+;
+MSG_PROGRESS_TITLE
+
+;XModule is working...
+;
+MSG_LOG_TITLE
+
+;XModule Log
+;
+;
+MSG_UNDERSCORE_ABORT_GAD
+
+;_Abort
+;
+;
+MSG_PERCENT_DONE
+%ld of %ld (%ld%% erledigt)
+;%ld of %ld (%ld%% done)
+; This string appears inside the progress window gauge.
+; eg: "5 of 20 (25% done)".
+; Leave that double percent (%%) alone! :-)
+;
+;
+; *****************************
+; * Strings for SongInfoWin.c *
+; *****************************
+;
+MSG_SONGINFO_TITLE
+
+;Song Information
+;
+;
+MSG_UNDERSCORE_NEW_GAD
+
+;Ne_w
+;
+MSG_OPEN_GAD
+
+;_Open...
+;
+MSG_SAVE_GAD
+
+;_Save
+;
+MSG_DEL_GAD
+
+;Del
+;
+MSG_SONG_NAME_GAD
+
+;Song _Name
+;
+MSG_AUTHOR_NAME_GAD
+
+;_Author
+;
+MSG_DEF_TEMPO_GAD
+
+;_Tempo
+;
+MSG_DEF_SPEED_GAD
+
+;S_peed
+;
+MSG_RESTART_GAD
+
+;_Restart
+;
+MSG_LENGHT_GAD
+
+;Length
+;
+MSG_NUM_PATTS_GAD
+
+;Num Patterns
+;
+MSG_NUM_TRACKS_GAD
+
+;Tracks
+;
+MSG_TOT_MOD_SIZE_GAD
+
+;Total Module Size
+;
+MSG_TOT_INST_SIZE_GAD
+
+;Total Instruments Size
+;
+;
+MSG_SONG_MEN
+
+;Song
+;
+MSG_MERGE_SONGS_MEN
+
+;Merge Songs
+;
+MSG_JOIN_SONGS_MEN
+
+;Join Songs
+;
+;
+MSG_JOIN_REQUIRES_TWO_SONGS
+Verbinden benötigt zwei Songs.
+;ERROR: Join requires two songs.
+;
+MSG_MERGE_REQUIRES_TWO_SONGS
+Verschmelzen benötigt zwei Songs.
+;ERROR: Merge requires two songs.
+;
+MSG_DISCARD_CURRENT_SONG
+Aktuellen Song verwerfen?
+;Discard current song?
+;
+;
+; ****************************
+; * Strings for ToolBoxWin.c *
+; ****************************
+;
+MSG_TOOLBOX_TITLE
+
+;ToolBox
+;
+;
+MSG_PLAY_GAD
+
+;Play...
+;
+MSG_SONGS_GAD
+
+;_Songs...
+;
+MSG_PATTERNS_DOTS_GAD
+
+;_Patterns...
+;
+MSG_INSTRUMENTS_GAD
+
+;_Instruments...
+;
+MSG_SEQUENCE_DOTS_GAD
+
+;Se_quence...
+;
+MSG_OPTIMIZATION_GAD
+
+;_Optimization...
+;
+MSG_PROJECT_MEN
+
+;Project
+;
+MSG_NEW_MEN
+
+;New
+;
+MSG_OPEN_MEN
+
+;Open...
+;
+MSG_OPEN_NEW_MEN
+
+;Open New...
+;
+MSG_SAVE_MEN
+
+;Save
+;
+MSG_SAVE_AS_MEN
+
+;Save As...
+;
+MSG_CLEAR_MEN
+
+;Clear...
+;
+MSG_ABOUT_MEN
+
+;About...
+;
+MSG_HELP_MEN
+
+;Help...
+;
+MSG_ICONIFY_MEN
+
+;Iconify...
+;
+MSG_QUIT_MEN
+
+;Quit
+;
+MSG_SETTINGS_MEN
+
+;Settings
+;
+MSG_SAVE_FORMAT_MEN
+
+;Save Format...
+;
+MSG_USER_INTERFACE_MEN
+
+;User Interface...
+;
+MSG_SAVE_ICONS_MEN
+
+;Save Icons
+;
+MSG_CONFIRM_OVERWRITE_MEN
+
+;Confirm Overwrite
+;
+MSG_CONFIRM_EXIT_MEN
+
+;Confirm Exit
+;
+MSG_VERBOSE_MEN
+
+;Verbose Log
+;
+MSG_OPEN_SETTINGS_MEN
+
+;Open Settings...
+;
+MSG_SAVE_SETTINGS_MEN
+
+;Save Settings
+;
+MSG_SAVE_SETTINGS_AS_MEN
+
+;Save Settings As...
+;
+;
+MSG_ABOUT_TEXT
+%s %s\n\
+Ein Programm zum Bearbeiten von Musikmodulen\n\n\
+\
+%s\n\
+Alle Rechte vorbehalten.\n\n\
+\
+Internet: bernie@shock.cosmos.it\n\n\
+Fidonet:  2:332/125.1\n\
+\
+Freier CHIP Speicher: %ldKB\n\
+Freier FAST Speicher: %ldKB\n\n\
+Öffentlicher Bildschirm: %s\n\
+ARexx Port: %s\n\
+CX HotKey: %s\n\
+Sprache: %s
+;%s\n\
+;A Music Module Processing Utility\n\n\
+;\
+;%s\n\
+;All rights reserved.\n\n\
+;\
+;Internet: bernie@shock.cosmos.it\n\n\
+;FidoNet:  2:332/125.1\n\
+;\
+;Free CHIP Memory: %ldKB\n\
+;Free FAST Memory: %ldKB\n\n\
+;Public Screen: %s\n\
+;ARexx Port: %s\n\
+;Cx HotKey: %s\n\
+;Language: %s
+;
+MSG_DEFAULT
+--Voreingestellt--
+;-- Default --
+;
+MSG_DISABLED
+--Gesperrt--
+;-- Disabled --
+;
+;
+; *********************************
+; * Strings for OptimizationWin.c *
+; *********************************
+;
+MSG_OPTIMIZATION_TITLE
+
+;Module Optimization
+;
+;
+MSG_OPTIMIZE_GAD
+
+;_Optimize
+;
+MSG_REM_UNUSED_PATTS_GAD
+
+;Remove Unused _Patterns
+;
+MSG_REM_DUPLICATE_PATTS_GAD
+
+;Remove _Duplicate Patterns
+;
+MSG_REM_UNUSED_INSTR_GAD
+
+;Remove Unused _Instruments
+;
+MSG_REM_DUP_INSTR_GAD
+
+;Remove Duplicate I_nstruments
+;
+MSG_CUT_AFTER_LOOP_GAD
+
+;Cut Instruments After _Loop
+;
+MSG_CUT_ZERO_TAILS_GAD
+
+;Cut Instrument _Zero Tails
+;
+MSG_CUT_PATTERNS_GAD
+
+;Cut _Breaked Patterns
+;
+MSG_REMAP_INSTRUMENTS_GAD
+
+;_Remap Instruments
+;
+;
+MSG_SAVED_X_BYTES
+%ld Bytes eingespart (%ld%%)
+;Saved %ld bytes (%ld%%)
+;
+;
+; **************************
+; * Strings for PrefsWin.c *
+; **************************
+;
+;
+MSG_PREFS_TITLE
+
+;User Interface Settings
+;
+;
+MSG_PUBLIC_SCREEN_GAD
+
+;_Public Screen
+;
+MSG_DISPLAY_MODE_GAD
+
+;Display _Mode
+;
+MSG_WINDOW_FONT_GAD
+
+;_Window Font
+;
+MSG_LISTVIEW_FONT_GAD
+
+;_ListView Font
+;
+MSG_REQUESTERS_GAD
+
+;_Requesters
+;
+MSG_USE_DATATYPES_GAD
+
+;Use _DataTypes
+;
+MSG_APPICON_GAD
+
+;Put App_Icon
+;
+MSG_REFRESH_GAD
+
+;Refres_h
+;
+MSG_LOG_TO_FILE_GAD
+
+;Log To File
+;
+MSG_LOG_LEVEL_GAD
+
+;_Log Level
+;
+MSG_ASK_AUTOSAVE_GAD
+
+;Confirm _Autosave
+;
+MSG_AUTOSAVE_TIME_GAD
+
+;Autosave Mi_nutes
+;
+MSG_DO_BACKUPS_GAD
+
+;Create _Backups
+;
+MSG_BACKUP_TEMPLATE_GAD
+
+;Bac_kup Template
+;
+MSG_BACKUP_VERSIONS_GAD
+
+;Backup _Versions
+;
+MSG_ASL_GAD
+
+;Asl
+;
+MSG_REQTOOLS_GAD
+
+;ReqTools
+;
+MSG_SMART_GAD
+
+;Smart
+;
+MSG_SIMPLE_GAD
+
+;Simple
+;
+MSG_CLONE_DEF_SCREEN
+
+;--Clone Default Screen--
+;
+;
+; ****************************
+; * Strings for PatternWin.c *
+; ****************************
+;
+MSG_PATTERN_TITLE
+
+;Pattern Editor
+;
+;
+MSG_PATTERNS_MEN
+
+;Patterns
+;
+MSG_SIZE_MEN
+
+;Size...
+;
+MSG_EDIT_MEN
+
+;Edit
+;
+MSG_MARK_MEN
+
+;Mark
+;
+MSG_CUT_MEN
+
+;Cut
+;
+MSG_COPY_MEN
+
+;Copy
+;
+MSG_PASTE_MEN
+
+;Paste
+;
+MSG_ERASE_MEN
+
+;Erase
+;
+MSG_UNDO_MEN
+
+;Undo
+;
+MSG_REDO_MEN
+
+;Redo
+;
+MSG_EDITOR_SETTINGS_MEN
+
+;Editor Settings...
+;
+;
+; ***************************
+; * Strings for SaversWin.c *
+; ***************************
+;
+MSG_SAVERS_TITLE
+
+;Savers
+;
+;
+MSG_SF_SEQUENCE_GAD
+
+;Se_quence
+;
+MSG_SF_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_SF_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_SF_NAMES_GAD
+
+;Names
+;
+MSG_ADD_ICON_GAD
+
+;Add I_con
+;
+MSG_MODE_GAD
+
+;_Mode
+;
+MSG_OPTIONS_DOTS_GAD
+
+;Options...
+;
+MSG_NONE_GAD
+
+;None
+;
+MSG_XPK_GAD
+
+;XPK
+;
+MSG_LHA_GAD
+
+;LhA
+;
+;
+MSG_DESCRIPTION
+
+;Description
+;
+MSG_AUTHOR
+
+;Author
+;
+MSG_MAXLENGTH
+
+;Max Length
+;
+MSG_MAXTRACKS
+
+;Max Tracks
+;
+MSG_MAXINSTRUMENTS
+
+;Max Instruments
+;
+MSG_MAXPATTERNS
+
+;Max Patterns
+;
+MSG_MAXPATTLEN
+
+;Max Pattern Length
+;
+MSG_MAXSAMPLELEN
+
+;Max Sample Length
+;
+;
+; **********************
+; * Strings ClearWin.c *
+; **********************
+;
+MSG_CLEAR_TITLE
+
+;Clear Module
+;
+;
+MSG_CLR_SEQUENCE_GAD
+
+;_Sequence
+;
+MSG_CLR_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_CLR_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_CLEARMOD_GAD
+
+;_Clear
+;
+;
+; ***************************
+; * Strings for SampleWin.c *
+; ***************************
+;
+MSG_SAMPLE_TITLE
+
+;Sample Editor
+;
+;
+MSG_RENDER_MEN
+
+;Render
+;
+MSG_POINTS_MEN
+
+;Points
+;
+MSG_LINES_MEN
+
+;Lines
+;
+MSG_FILLED_MEN
+
+;Filled
+;
+;
+; *****************************
+; * Strings for PattSizeWin.c *
+; *****************************
+;
+MSG_PATTSIZE_TITLE
+
+;Pattern Attributes
+;
+;
+MSG_LINES_GAD
+
+;_Lines
+;
+MSG_TRACKS_GAD
+
+;_Tracks
+;
+MSG_DOUBLE_GAD
+
+;_Double
+;
+MSG_HALVE_GAD
+
+;_Halve
+;
+;
+; *************************
+; * Strings for Library.c *
+; *************************
+;
+MSG_SONG_TOO_MANY_TRACKS
+
+;WARNING: The selected saver supports max %lu tracks, but the song has %lu tracks.
+;
+MSG_SONG_TOO_MANY_PATTS
+
+;WARNING: The selected saver supports max %lu patterns; the song has %lu patterns.
+;
+MSG_SONG_TOO_MANY_INSTRS
+
+;WARNING: The selected saver supports max %lu instruments, last instrument is %lu.
+;
+MSG_SONG_TOO_MANY_POS
+
+;WARNING: The selected saver supports max %lu positions, but the song has %lu positions.
+;
+MSG_PATT_LENGTH_INVALID
+
+;WARNING: The length of pattern %ld ($%lx lines) isn't allowed with the selected saver.
+;
+MSG_INSTR_TOO_LONG
+WARNUNG: Instrument %lx ist zu lang.
+;WARNING: Instrument %lx is too long and will be cropped to %lu bytes.
+;
+MSG_WILL_MODIFY_SONG
+
+;Some modifications need to be performed on this song\n\
+;in order to adapt it to the limitations of the\n\
+;destination format.
+;
+MSG_TRY_REMAPPING_INSTRUMENTS
+
+;Some instruments are placed beyond the limit for\n\
+;the selected format.\n\
+;Remapping the instruments now could help saving\n\
+;all them along with the selected format.
+;
+MSG_SAVING_MODULE
+
+;Saving %s module \"%s\"...
+; the first %s is the format name, the second %s is the song name.
diff --git a/Catalogs/empty.ct b/Catalogs/empty.ct
new file mode 100644 (file)
index 0000000..31dd947
--- /dev/null
@@ -0,0 +1,1473 @@
+## version $VER: XX.catalog XX.XX (XX.XX.XX)
+## language X
+## codeset 0
+;
+;**
+;** XModule.cd 3.9
+;**
+;** Copyright (C) 1995,96 Bernardo Innocenti
+;**
+;
+; **********************************
+; * Strings for all source modules *
+; **********************************
+;
+;
+MSG_NULL
+
+;
+; This string should remain blank.
+;
+MSG_OK
+
+;Ok
+;
+MSG_UNDERSCORE_USE_GAD
+
+;_Use
+;
+MSG_UNDERSCORE_OK_GAD
+
+;_Ok
+;
+MSG_UNDERSCORE_CANCEL_GAD
+
+;_Cancel
+;
+MSG_YES_OR_NO
+
+;Yes|No
+;
+MSG_RETRY_OR_CANCEL
+
+;Retry|Cancel
+;
+MSG_PROCEED_OR_CANCEL
+
+;Proceed|Cancel
+;
+MSG_CONTINUE
+
+;Continue
+;
+MSG_NO_FREE_STORE
+
+;Insufficient memory.
+;
+MSG_BREAK
+
+;Aborted.
+;
+MSG_ERR_LOAD
+
+;Unable to load \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_CANT_OPEN
+
+;Cannot open file \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_READING
+
+;Error reading \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_WRITING
+
+;Error writing \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+;
+; *****************************
+; * Strings for Compression.c *
+; *****************************
+;
+MSG_DECRUNCHING
+
+;Decrunching...
+;
+MSG_NOTHING_IN_ARC
+
+;Nothing found in archive \"%s\".
+;
+MSG_CANT_LOAD_COMPRESSED
+
+;Unable to load compressed file.
+;
+MSG_ERROR_DECOMPRESSING
+
+;Error decompressing file \"%s\": %s.
+;
+;
+; ********************
+; * Strings for Cx.c *
+; ********************
+;
+MSG_BAD_HOTKEY
+
+;Bad Commodity HotKey description.
+;
+;
+; ***************************
+; * Strings for all loaders *
+; ***************************
+;
+MSG_READING_PATTS
+
+;Reading Patterns...
+;
+MSG_READING_INSTS_INFO
+
+;Reading Instruments Info...
+;
+MSG_READING_INSTS
+
+;Reading Instruments...
+;
+MSG_ERR_CANT_LOAD_PATT
+
+;ERROR: Couldn't load pattern %ld.
+;
+MSG_ERR_CANT_LOAD_INST
+
+;ERROR: Couldn't load instrument %lx.
+;
+MSG_ERR_NO_MEM_FOR_INST
+
+;ERROR: Not enough memory for instrument %lx.
+;
+MSG_ERR_NOT_A_SAMPLE
+
+;ERROR: Instrument %lx is not a sample.
+;
+MSG_SONG_TOO_LONG
+
+;WARNING: Song length exceeds maximum. Will be truncated.
+;
+MSG_SONG_HAS_TOO_MANY_PATT
+
+;WARNING: Song exceeds maximum number of patterns.
+;
+MSG_PATT_TOO_MANY_TRACKS
+
+;WARNING: Pattern %ld has too many tracks. Cropping to %ld tracks.
+;
+MSG_PATT_TOO_MANY_LINES
+
+;WARNING: Pattern %ld has too many lines. Cropping to %ld lines.
+;
+MSG_INVALID_NOTE
+
+;WARNING: Invalid note %ld (Patt %ld Track %ld Line %ld).
+;
+MSG_UNKNOWN_EFF
+
+;Unknown effect: $%lx (Patt %ld Track %ld Line %ld).
+;
+MSG_EXTRA_DATA_AFTER_MOD
+
+;WARNING: Extra data found after valid module: Will be ignored.
+;
+MSG_WRITING_HEADER
+
+;Writing Header...
+;
+MSG_WRITING_PATTS
+
+;Writing Patterns...
+;
+MSG_WRITING_INSTS
+
+;Writing Instruments...
+;
+MSG_WRITING_INSTINFO
+
+;Writing Instruments Info...
+;
+MSG_WRITING_INSTDATA
+
+;Writing Instruments Data...
+;
+MSG_NOTE_TOO_LOW
+
+;WARNING: Note at Patt %ld Track %ld Line %ld is too low.
+;
+MSG_NOTE_TOO_HIGH
+
+;WARNING: Note at Patt %ld Track %ld Line %ld is too high.
+;
+MSG_NO_MEM_TO_HALVE
+
+;WARNING: Not enough memory to halve volume of instrument %lx.
+;
+;
+; *****************************
+; * Strings for Get/SaveMED.c *
+; *****************************
+;
+MSG_READING_MMD
+
+;Loading MMD%lc module...
+; The %lc is the MED format (0, 1, 2, ...)
+;
+MSG_UNSUPPORTED_MMD_FORMAT
+
+;ERROR: Unsupported OctaMED format.
+;
+MSG_WRONG_EFFECT_IN_MMD0
+
+;WARNING: Effect %lx is not supported in MMD0 format. Use MMD1 or better.
+;
+;
+; ***********************************
+; * Strings for ScreamTrackerHook.c *
+; ***********************************
+;
+MSG_ADLIB_INSTR
+
+;ERROR: Instrument %lx is an ADLib %s.
+; %s is the name of the ADLib instrument (eg: Snare).
+;
+MSG_TRACK_OUT_OF_RANGE
+
+;WARNING: Track %lx is out of range.
+;
+MSG_UNKNOWN_SAMPLE_COMPRESSION
+
+;WARNING: Unknown sample compression for instrument %lx.
+;
+MSG_INST_IS_STEREO
+
+;WARNING: Instrument %lx is a stereo sample.
+;
+MSG_INST_IS_16BIT
+
+;WARNING: Instrument %lx is 16bit: unable to load it.
+;
+;
+; *****************************
+; * Strings for TrackerHook.c *
+; *****************************
+;
+MSG_READING_TYPE_MODULE
+
+;Reading %s module...
+; (%s is the module format)
+;
+MSG_EXCEEDS_64_PATTS
+
+;NOTE: Module exceeds 64 patterns. You need ProTracker 2.3 to play it.
+;
+MSG_MODULE_ID
+
+;Tracker ID: \"%s\"
+;
+MSG_MODULE_HAS_N_CHN
+
+;Module has %lu tracks...
+;
+MSG_SONG_HAS_NO_INSTS
+
+;This file is a song and doesn't contain instruments.
+;
+MSG_EXCEEDS_MAXPAATTS
+
+;WARNING: Module execeeds %ld patterns.
+;
+MSG_PATT_WILL_GROW
+
+;Pattern %ld will grow to 64 lines (was %ld lines long).
+;
+MSG_SPLITTING_PATT
+
+;Splitting pattern %ld (was %ld lines long).
+;
+; **************************
+; * Strings for SaveMIDI.c *
+; **************************
+;
+MSG_CHOOSING_CHANNELS
+
+;Choosing Channels...
+;
+MSG_WRITING_MIDI_TRACKS
+
+;Writing MIDI Tracks...
+;
+MSG_TOO_MANY_CHANNELS
+
+;ERROR: Song requires too many MIDI channels.
+;
+;
+; *********************
+; * Strings for Gui.c *
+; *********************
+;
+MSG_REALLY_QUIT_XMODULE
+
+;Really Quit XModule?
+;
+MSG_CLOSE_ALL_WINDOWS
+
+;Please close all visitor windows\n\
+;and then select `Continue'.
+;
+;
+; ***********************
+; * Strings for Instr.c *
+; ***********************
+;
+MSG_UNKNOWN_IFF
+
+;Unknown IFF format %s.
+;
+MSG_ILLEGAL_IFF_STRUCTURE
+
+;Illegal IFF structure.
+;
+MSG_SELECT_RAW_MODE
+
+;Unrecognized instrument format.\n\
+;Please select RAW mode.
+;
+MSG_RAW_MODES
+
+;Signed 8bit|Unsigned 8bit|Cancel
+;
+MSG_DATATYPES_ERROR
+
+;DataTypes error: %s.
+; %s is a detailed description of the problem.
+;
+MSG_UNKNOWN_COMPRESSION
+
+;Unknown compression type.
+;
+MSG_SAMPLE_WRONG_SIZE
+
+;%lu bit samples are not supported.
+; %lu is the number of bits per sample.
+;
+MSG_SAMPLE_NOT_MONO
+
+;Samples other than MONO are not supported.
+;
+MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS
+
+;Samples with %ld channels are not supported.
+;
+MSG_NO_MEMORY_TO_OPTIMIZE_INSTR
+
+;WARNING: insufficient memory to optimize instrument %lx.
+;
+MSG_INSTR_WILL_SHRINK
+
+;Instrument %lx will shrink from %ld to %ld.
+;
+MSG_INSTR_DUPES_REMOVED
+
+;Duplicate instruments found and removed: %lx == %lx.
+;
+MSG_INSTR_UNUSED
+
+;Instrument %lx was never used and it's being removed.
+;
+;
+; **********************
+; * Strings for Misc.c *
+; **********************
+;
+MSG_OPENLIB_VER_FAIL
+
+;Couldn't open \"%s\" version %ld or greater.
+;
+MSG_OPENLIB_FAIL
+
+;Couldn't open \"%s\".
+;
+;
+; ***************************
+; * Strings for Operators.c *
+; ***************************
+;
+MSG_CANT_INSERT_PATT
+
+;Unable to insert pattern: Maximum number of patterns reached.
+;
+MSG_PATT_UNUSED
+
+;Pattern %ld is not used and is beeing deleted.
+;
+MSG_PATT_CUT
+
+;Pattern %ld will be cut at line %ld.
+;
+MSG_PATT_DUPE
+
+;Duplicate patterns found and removed: %ld == %ld.
+; The two %ld are the pattern numbers.
+;
+MSG_SONG_LEN_DIFFERENT
+
+;WARNING: Song lengths are different. Using shorter one.
+;
+MSG_PATT_LEN_DIFFERENT
+
+;WARNING: Different pattern lengths at position %ld. Using shorter one.
+;
+MSG_ERR_INSTR_OVERFLOW
+
+;ERROR: Instruments overflow. Try remapping the instruments.
+;
+;
+; ***********************
+; * Strings for Prefs.c *
+; ***********************
+;
+MSG_BAD_PREFS_VERSION
+
+;Incorrect version of preferences file
+;
+;
+; ****************************
+; * Strings for Requesters.c *
+; ****************************
+;
+MSG_XMODULE_REQUEST
+
+;XModule Request
+; This is the title of requesters put out by XModule.
+;
+MSG_CLONE_WB
+
+;Clone Workbench Screen
+;
+MSG_CLOSE_FILEREQUESTER
+
+;Please close FileRequester\n and then select `Continue'.
+;
+MSG_SELECT_MODULES
+
+;Select Module(s)...
+;
+MSG_SELECT_INSTRUMENTS
+
+;Select Instrument(s)...
+;
+MSG_SELECT_PATTERN
+
+;Select Pattern...
+;
+MSG_SAVE_MODULE
+
+;Save Module...
+;
+MSG_SAVE_INSTRUMENT
+
+;Save Instrument...
+;
+MSG_SAVE_PATTERN
+
+;Save Pattern...
+;
+MSG_FILE_EXISTS
+
+;File \"%s\"\nalready exists.
+;
+MSG_OVERWRITE
+
+;Overwrite|Choose Another|Abort
+;
+;
+; **********************
+; * Strings for Song.c *
+; **********************
+;
+MSG_CLOSE_ALL_SONGS
+
+;Please close all open songs\n\
+;and then select `Continue'.
+;
+MSG_UNESPECTED_EOF
+
+;Unespected end of file.
+;
+MSG_MODULE_LOADED_OK
+
+;Module loaded OK.
+;
+MSG_MODULE_SAVED_OK
+
+;Module saved OK.
+;
+MSG_UNKNOWN_SAVE_FORMAT
+
+;ERROR: Unrecognized save format.
+;
+MSG_INVALID_LOOP_REMOVED
+
+;Removed invalid loop for instrument %lx.
+;
+MSG_INVALID_LOOP_FIXED
+
+;Fixed invalid loop for instrument %lx.
+;
+MSG_SONG_HAS_NO_PATTS
+
+;WARNING: Song has no patterns.
+;
+MSG_SONG_HAS_NO_SEQ
+
+;WARNING: Song has no sequence.
+;
+MSG_INVALID_SONG_POS
+
+;WARNING: Song position %ld references pattern %ld, which doesn't exist.
+;
+MSG_UNKNOWN_MOD_FORMAT
+
+;Unable to identify module format.\n\
+;(Loading a data file as a module is unwise)
+;
+MSG_SOUND_PRO_CANCEL
+
+;SoundTracker 15|ProTracker|Cancel
+;
+MSG_AUTHOR_UNKNOWN
+
+;Unknown
+;
+MSG_SONG_UNTITLED
+
+;Untitled
+;
+;
+; **********************
+; * Strings for Help.c *
+; **********************
+;
+MSG_AMIGAGUIDE_ERROR
+
+;AmigaGuide error:
+; Note one blank space at the end of this line!
+;
+;
+; ***************************
+; * Strings for PattPrefs.c *
+; ***************************
+;
+MSG_PATTPREFS_TITLE
+
+;Pattern Editor Settings
+;
+;
+MSG_ADVANCE_TRACKS_GAD
+
+;Advance _Tracks
+;
+MSG_ADVANCE_LINES_GAD
+
+;Advance _Lines
+;
+MSG_MAX_UNDO_LEVELS_GAD
+
+;Max _Undo Levels
+;
+MSG_MAX_UNDO_MEM_GAD
+
+;Max Undo _Memory
+;
+MSG_CLIPBOARD_UNIT_GAD
+
+;Cli_pboard Unit
+;
+MSG_SCROLLER_POS_GAD
+
+;_Scroller Position
+;
+MSG_VERT_WRAP_GAD
+
+;_Vertical Wrap
+;
+MSG_HORIZ_WRAP_GAD
+
+;_Horizontal Wrap
+;
+MSG_HEX_LINE_NUMBERS_GAD
+
+;He_x Line Numbers
+;
+MSG_BLANK_ZERO_GAD
+
+;Blank _Zero Digits
+;
+MSG_BACKDROP_GAD
+
+;Backdrop _Window
+;
+MSG_HORIZ_SCROLLER_GAD
+
+;Ho_rizontal Scroller
+;
+MSG_DO_RULER_GAD
+
+;Display Cursor Ruler
+;
+MSG_DO_TINY_LINES_GAD
+
+;Display Tiny Lines
+;
+MSG_EDITOR_FONT_GAD
+
+;_Editor Font
+;
+MSG_BACKGROUND_PEN_GAD
+
+;Background
+;
+MSG_TEXT_PEN_GAD
+
+;T_ext
+;
+MSG_LINES_PEN_GAD
+
+;Li_nes
+;
+MSG_TINY_LINES_PEN_GAD
+
+;Tin_y Lines
+;
+MSG_RIGHT_GAD
+
+;Right
+;
+MSG_LEFT_GAD
+
+;Left
+;
+MSG_OFF_GAD
+
+;Off
+;
+MSG_PATT_TOO_LONG
+
+;ERROR: Pattern would exceed the maximum number of lines.
+;
+;
+; *****************************
+; * Strings for SequenceWin.c *
+; *****************************
+;
+;
+MSG_SEQUENCE_TITLE
+
+;Sequence Editor
+;
+;
+MSG_SEQUENCE_GAD
+
+;Sequence
+;
+MSG_UNDERSCORE_ADD_GAD
+
+;_Add
+;
+MSG_UP_GAD
+
+;Up
+;
+MSG_DOWN_GAD
+
+;Down
+;
+MSG_UNDERSCORE_NAME_GAD
+
+;_Name
+;
+MSG_PATTERNS_GAD
+
+;Patterns
+;
+MSG_UNDERSCORE_UP_GAD
+
+;_Up
+;
+MSG_UNDERSCORE_DOWN_GAD
+
+;_Down
+;
+MSG_UNDERSCORE_INS_GAD
+
+;_Ins
+;
+;
+MSG_UNNAMED
+
+;-- unnamed --
+;
+;
+; ********************************
+; * Strings for InstrumentsWin.c *
+; ********************************
+;
+MSG_INSTRUMENTS_TITLE
+
+;Instruments
+;
+;
+MSG_VOLUME_GAD
+
+;_Volume
+;
+MSG_FINETUNE_GAD
+
+;_FineTune
+;
+MSG_LENGTH_GAD
+
+;Length
+;
+MSG_KIND_GAD
+
+;_Kind
+;
+MSG_EDIT_DOTS_GAD
+
+;_Edit...
+;
+MSG_SAMPLE_GAD
+
+;Sample
+;
+MSG_SYNTH_GAD
+
+;Synth
+;
+MSG_HYBRID_GAD
+
+;Hybrid
+;
+;
+MSG_INSTRUMENTS_MEN
+
+;Instruments
+;
+MSG_LOAD_MEN
+
+;Load...
+;
+MSG_REMAP_MEN
+
+;Remap
+;
+MSG_SAVE_COMPRESSED_MEN
+
+;Save Compressed
+;
+MSG_SAVE_RAW_MEN
+
+;Save Raw
+;
+;
+MSG_EMPTY
+
+;-- empty --
+;
+;
+; *************************
+; * Strings for PlayWin.c *
+; *************************
+;
+MSG_PLAY_TITLE
+
+;Play
+;
+;
+MSG_VOL_GAD
+
+;_Vol
+;
+MSG_POS_GAD
+
+;Pos
+;
+MSG_TIME_GAD
+
+;Time
+;
+MSG_RST_GAD
+
+;_Rst
+; Stands for Reset/Restart
+;
+MSG_PLAYER_INIT_ERR
+
+;Player initialization error: %ld.
+;
+;
+; *****************************
+; * Strings for ProgressWin.c *
+; *****************************
+;
+MSG_PROGRESS_TITLE
+
+;XModule is working...
+;
+MSG_LOG_TITLE
+
+;XModule Log
+;
+;
+MSG_UNDERSCORE_ABORT_GAD
+
+;_Abort
+;
+;
+MSG_PERCENT_DONE
+
+;%ld of %ld (%ld%% done)
+; This string appears inside the progress window gauge.
+; eg: "5 of 20 (25% done)".
+; Leave that double percent (%%) alone! :-)
+;
+;
+; *****************************
+; * Strings for SongInfoWin.c *
+; *****************************
+;
+MSG_SONGINFO_TITLE
+
+;Song Information
+;
+;
+MSG_UNDERSCORE_NEW_GAD
+
+;Ne_w
+;
+MSG_OPEN_GAD
+
+;_Open...
+;
+MSG_SAVE_GAD
+
+;_Save
+;
+MSG_DEL_GAD
+
+;Del
+;
+MSG_SONG_NAME_GAD
+
+;Song _Name
+;
+MSG_AUTHOR_NAME_GAD
+
+;_Author
+;
+MSG_DEF_TEMPO_GAD
+
+;_Tempo
+;
+MSG_DEF_SPEED_GAD
+
+;S_peed
+;
+MSG_RESTART_GAD
+
+;_Restart
+;
+MSG_LENGHT_GAD
+
+;Length
+;
+MSG_NUM_PATTS_GAD
+
+;Num Patterns
+;
+MSG_NUM_TRACKS_GAD
+
+;Tracks
+;
+MSG_TOT_MOD_SIZE_GAD
+
+;Total Module Size
+;
+MSG_TOT_INST_SIZE_GAD
+
+;Total Instruments Size
+;
+;
+MSG_SONG_MEN
+
+;Song
+;
+MSG_MERGE_SONGS_MEN
+
+;Merge Songs
+;
+MSG_JOIN_SONGS_MEN
+
+;Join Songs
+;
+;
+MSG_JOIN_REQUIRES_TWO_SONGS
+
+;ERROR: Join requires two songs.
+;
+MSG_MERGE_REQUIRES_TWO_SONGS
+
+;ERROR: Merge requires two songs.
+;
+MSG_DISCARD_CURRENT_SONG
+
+;Discard current song?
+;
+;
+; ****************************
+; * Strings for ToolBoxWin.c *
+; ****************************
+;
+MSG_TOOLBOX_TITLE
+
+;ToolBox
+;
+;
+MSG_PLAY_GAD
+
+;Play...
+;
+MSG_SONGS_GAD
+
+;_Songs...
+;
+MSG_PATTERNS_DOTS_GAD
+
+;_Patterns...
+;
+MSG_INSTRUMENTS_GAD
+
+;_Instruments...
+;
+MSG_SEQUENCE_DOTS_GAD
+
+;Se_quence...
+;
+MSG_OPTIMIZATION_GAD
+
+;_Optimization...
+;
+MSG_PROJECT_MEN
+
+;Project
+;
+MSG_NEW_MEN
+
+;New
+;
+MSG_OPEN_MEN
+
+;Open...
+;
+MSG_OPEN_NEW_MEN
+
+;Open New...
+;
+MSG_SAVE_MEN
+
+;Save
+;
+MSG_SAVE_AS_MEN
+
+;Save As...
+;
+MSG_CLEAR_MEN
+
+;Clear...
+;
+MSG_ABOUT_MEN
+
+;About...
+;
+MSG_HELP_MEN
+
+;Help...
+;
+MSG_ICONIFY_MEN
+
+;Iconify...
+;
+MSG_QUIT_MEN
+
+;Quit
+;
+MSG_SETTINGS_MEN
+
+;Settings
+;
+MSG_SAVE_FORMAT_MEN
+
+;Save Format...
+;
+MSG_USER_INTERFACE_MEN
+
+;User Interface...
+;
+MSG_SAVE_ICONS_MEN
+
+;Save Icons
+;
+MSG_CONFIRM_OVERWRITE_MEN
+
+;Confirm Overwrite
+;
+MSG_CONFIRM_EXIT_MEN
+
+;Confirm Exit
+;
+MSG_VERBOSE_MEN
+
+;Verbose Log
+;
+MSG_OPEN_SETTINGS_MEN
+
+;Open Settings...
+;
+MSG_SAVE_SETTINGS_MEN
+
+;Save Settings
+;
+MSG_SAVE_SETTINGS_AS_MEN
+
+;Save Settings As...
+;
+;
+MSG_ABOUT_TEXT
+
+;%s\n\
+;A Music Module Processing Utility\n\n\
+;\
+;%s\n\
+;All rights reserved.\n\n\
+;\
+;Internet: bernie@shock.cosmos.it\n\n\
+;FidoNet:  2:332/125.1\n\
+;\
+;Free CHIP Memory: %ldKB\n\
+;Free FAST Memory: %ldKB\n\n\
+;Public Screen: %s\n\
+;ARexx Port: %s\n\
+;Cx HotKey: %s\n\
+;Language: %s
+;
+MSG_DEFAULT
+
+;-- Default --
+;
+MSG_DISABLED
+
+;-- Disabled --
+;
+;
+; *********************************
+; * Strings for OptimizationWin.c *
+; *********************************
+;
+MSG_OPTIMIZATION_TITLE
+
+;Module Optimization
+;
+;
+MSG_OPTIMIZE_GAD
+
+;_Optimize
+;
+MSG_REM_UNUSED_PATTS_GAD
+
+;Remove Unused _Patterns
+;
+MSG_REM_DUPLICATE_PATTS_GAD
+
+;Remove _Duplicate Patterns
+;
+MSG_REM_UNUSED_INSTR_GAD
+
+;Remove Unused _Instruments
+;
+MSG_REM_DUP_INSTR_GAD
+
+;Remove Duplicate I_nstruments
+;
+MSG_CUT_AFTER_LOOP_GAD
+
+;Cut Instruments After _Loop
+;
+MSG_CUT_ZERO_TAILS_GAD
+
+;Cut Instrument _Zero Tails
+;
+MSG_CUT_PATTERNS_GAD
+
+;Cut _Breaked Patterns
+;
+MSG_REMAP_INSTRUMENTS_GAD
+
+;_Remap Instruments
+;
+;
+MSG_SAVED_X_BYTES
+
+;Saved %ld bytes (%ld%%)
+;
+;
+; **************************
+; * Strings for PrefsWin.c *
+; **************************
+;
+;
+MSG_PREFS_TITLE
+
+;User Interface Settings
+;
+;
+MSG_PUBLIC_SCREEN_GAD
+
+;_Public Screen
+;
+MSG_DISPLAY_MODE_GAD
+
+;Display _Mode
+;
+MSG_WINDOW_FONT_GAD
+
+;_Window Font
+;
+MSG_LISTVIEW_FONT_GAD
+
+;_ListView Font
+;
+MSG_REQUESTERS_GAD
+
+;_Requesters
+;
+MSG_USE_DATATYPES_GAD
+
+;Use _DataTypes
+;
+MSG_APPICON_GAD
+
+;Put App_Icon
+;
+MSG_REFRESH_GAD
+
+;Refres_h
+;
+MSG_LOG_TO_FILE_GAD
+
+;Log To File
+;
+MSG_LOG_LEVEL_GAD
+
+;_Log Level
+;
+MSG_ASK_AUTOSAVE_GAD
+
+;Confirm _Autosave
+;
+MSG_AUTOSAVE_TIME_GAD
+
+;Autosave Mi_nutes
+;
+MSG_DO_BACKUPS_GAD
+
+;Create _Backups
+;
+MSG_BACKUP_TEMPLATE_GAD
+
+;Bac_kup Template
+;
+MSG_BACKUP_VERSIONS_GAD
+
+;Backup _Versions
+;
+MSG_ASL_GAD
+
+;Asl
+;
+MSG_REQTOOLS_GAD
+
+;ReqTools
+;
+MSG_SMART_GAD
+
+;Smart
+;
+MSG_SIMPLE_GAD
+
+;Simple
+;
+MSG_CLONE_DEF_SCREEN
+
+;--Clone Default Screen--
+;
+;
+; ****************************
+; * Strings for PatternWin.c *
+; ****************************
+;
+MSG_PATTERN_TITLE
+
+;Pattern Editor
+;
+;
+MSG_PATTERNS_MEN
+
+;Patterns
+;
+MSG_SIZE_MEN
+
+;Size...
+;
+MSG_EDIT_MEN
+
+;Edit
+;
+MSG_MARK_MEN
+
+;Mark
+;
+MSG_CUT_MEN
+
+;Cut
+;
+MSG_COPY_MEN
+
+;Copy
+;
+MSG_PASTE_MEN
+
+;Paste
+;
+MSG_ERASE_MEN
+
+;Erase
+;
+MSG_UNDO_MEN
+
+;Undo
+;
+MSG_REDO_MEN
+
+;Redo
+;
+MSG_EDITOR_SETTINGS_MEN
+
+;Editor Settings...
+;
+;
+; ***************************
+; * Strings for SaversWin.c *
+; ***************************
+;
+MSG_SAVERS_TITLE
+
+;Savers
+;
+;
+MSG_SF_SEQUENCE_GAD
+
+;Se_quence
+;
+MSG_SF_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_SF_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_SF_NAMES_GAD
+
+;Names
+;
+MSG_ADD_ICON_GAD
+
+;Add I_con
+;
+MSG_MODE_GAD
+
+;_Mode
+;
+MSG_OPTIONS_DOTS_GAD
+
+;Options...
+;
+MSG_NONE_GAD
+
+;None
+;
+MSG_XPK_GAD
+
+;XPK
+;
+MSG_LHA_GAD
+
+;LhA
+;
+;
+MSG_DESCRIPTION
+
+;Description
+;
+MSG_AUTHOR
+
+;Author
+;
+MSG_MAXLENGTH
+
+;Max Length
+;
+MSG_MAXTRACKS
+
+;Max Tracks
+;
+MSG_MAXINSTRUMENTS
+
+;Max Instruments
+;
+MSG_MAXPATTERNS
+
+;Max Patterns
+;
+MSG_MAXPATTLEN
+
+;Max Pattern Length
+;
+MSG_MAXSAMPLELEN
+
+;Max Sample Length
+;
+;
+; **********************
+; * Strings ClearWin.c *
+; **********************
+;
+MSG_CLEAR_TITLE
+
+;Clear Module
+;
+;
+MSG_CLR_SEQUENCE_GAD
+
+;_Sequence
+;
+MSG_CLR_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_CLR_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_CLEARMOD_GAD
+
+;_Clear
+;
+;
+; ***************************
+; * Strings for SampleWin.c *
+; ***************************
+;
+MSG_SAMPLE_TITLE
+
+;Sample Editor
+;
+;
+MSG_RENDER_MEN
+
+;Render
+;
+MSG_POINTS_MEN
+
+;Points
+;
+MSG_LINES_MEN
+
+;Lines
+;
+MSG_FILLED_MEN
+
+;Filled
+;
+;
+; *****************************
+; * Strings for PattSizeWin.c *
+; *****************************
+;
+MSG_PATTSIZE_TITLE
+
+;Pattern Attributes
+;
+;
+MSG_LINES_GAD
+
+;_Lines
+;
+MSG_TRACKS_GAD
+
+;_Tracks
+;
+MSG_DOUBLE_GAD
+
+;_Double
+;
+MSG_HALVE_GAD
+
+;_Halve
+;
+;
+; *************************
+; * Strings for Library.c *
+; *************************
+;
+MSG_SONG_TOO_MANY_TRACKS
+
+;WARNING: The selected saver supports max %lu tracks, but the song has %lu tracks.
+;
+MSG_SONG_TOO_MANY_PATTS
+
+;WARNING: The selected saver supports max %lu patterns; the song has %lu patterns.
+;
+MSG_SONG_TOO_MANY_INSTRS
+
+;WARNING: The selected saver supports max %lu instruments, last instrument is %lu.
+;
+MSG_SONG_TOO_MANY_POS
+
+;WARNING: The selected saver supports max %lu positions, but the song has %lu positions.
+;
+MSG_PATT_LENGTH_INVALID
+
+;WARNING: The length of pattern %ld ($%lx lines) isn't allowed with the selected saver.
+;
+MSG_INSTR_TOO_LONG
+
+;WARNING: Instrument %lx is too long and will be cropped to %lu bytes.
+;
+MSG_WILL_MODIFY_SONG
+
+;Some modifications need to be performed on this song\n\
+;in order to adapt it to the limitations of the\n\
+;destination format.
+;
+MSG_TRY_REMAPPING_INSTRUMENTS
+
+;Some instruments are placed beyond the limit for\n\
+;the selected format.\n\
+;Remapping the instruments now could help saving\n\
+;all them along with the selected format.
+;
+MSG_SAVING_MODULE
+
+;Saving %s module \"%s\"...
+; the first %s is the format name, the second %s is the song name.
diff --git a/Catalogs/français.ct b/Catalogs/français.ct
new file mode 100644 (file)
index 0000000..5b6f2a6
--- /dev/null
@@ -0,0 +1,1491 @@
+## version $VER: Français.ct 1.0 (10.9.95)
+## language français
+## codeset 0
+;
+## chunk AUTH Français catalog translation by Julien Wilk <wilkj@esiee.fr>
+;**
+;** XModule.cd 3.9
+;**
+;** Copyright (C) 1995,96 Bernardo Innocenti
+;**
+;
+; **********************************
+; * Strings for all source modules *
+; **********************************
+;
+;
+MSG_NULL
+
+;
+; This string should remain blank.
+;
+MSG_OK
+Ok
+;Ok
+;
+MSG_UNDERSCORE_USE_GAD
+
+;_Use
+;
+MSG_UNDERSCORE_OK_GAD
+
+;_Ok
+;
+MSG_UNDERSCORE_CANCEL_GAD
+
+;_Cancel
+;
+MSG_YES_OR_NO
+Oui|Non
+;Yes|No
+;
+MSG_RETRY_OR_CANCEL
+Réessayer|Abandonner
+;Retry|Cancel
+;
+MSG_PROCEED_OR_CANCEL
+
+;Proceed|Cancel
+;
+MSG_CONTINUE
+Continuer
+;Continue
+;
+MSG_NO_FREE_STORE
+Mémoire insuffisante !
+;Insufficient memory.
+;
+MSG_BREAK
+Tache abandonnée.
+;Aborted.
+;
+MSG_ERR_LOAD
+Impossible de charger \"%s\": %s.
+;Unable to load \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_CANT_OPEN
+Impossible d'ouvrir le fichier \"%s\" %s.
+;Cannot open file \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_READING
+Erreur en lecture de \"%s\": %s.
+;Error reading \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_WRITING
+Erreur d'écriture de \"%s\" %s.
+;Error writing \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+;
+; *****************************
+; * Strings for Compression.c *
+; *****************************
+;
+MSG_DECRUNCHING
+Décompression...
+;Decrunching...
+;
+MSG_NOTHING_IN_ARC
+Aucun module trouvé dans l'archive \"%s\".
+;Nothing found in archive \"%s\".
+;
+MSG_CANT_LOAD_COMPRESSED
+Impossible de charger le fichier compressé.
+;Unable to load compressed file.
+;
+MSG_ERROR_DECOMPRESSING
+Erreur de décompression de \"%s\": %s.
+;Error decompressing file \"%s\": %s.
+;
+;
+; ********************
+; * Strings for Cx.c *
+; ********************
+;
+MSG_BAD_HOTKEY
+Mauvaise définition de la HotKey.
+;Bad Commodity HotKey description.
+;
+;
+; ***************************
+; * Strings for all loaders *
+; ***************************
+;
+MSG_READING_PATTS
+Lecture des Patterns...
+;Reading Patterns...
+;
+MSG_READING_INSTS_INFO
+Lecture des Info-Instruments...
+;Reading Instruments Info...
+;
+MSG_READING_INSTS
+Lecture des Instruments...
+;Reading Instruments...
+;
+MSG_ERR_CANT_LOAD_PATT
+ERREUR: Ne peut charger la pattern %ld.
+;ERROR: Couldn't load pattern %ld.
+;
+MSG_ERR_CANT_LOAD_INST
+ERREUR: Ne peut lire l'instrument %lx.
+;ERROR: Couldn't load instrument %lx.
+;
+MSG_ERR_NO_MEM_FOR_INST
+ERREUR: Pas assez de mémoire pour l'instrument %lx.
+;ERROR: Not enough memory for instrument %lx.
+;
+MSG_ERR_NOT_A_SAMPLE
+ERREUR: L'instrument %lx n'est pas un sample.
+;ERROR: Instrument %lx is not a sample.
+;
+MSG_SONG_TOO_LONG
+ATTENTION: Chanson trop longue. (excédent tronqué).
+;WARNING: Song length exceeds maximum. Will be truncated.
+;
+MSG_SONG_HAS_TOO_MANY_PATT
+ATTENTION: Trop de patterns.
+;WARNING: Song exceeds maximum number of patterns.
+;
+MSG_PATT_TOO_MANY_TRACKS
+ATENTION: La pattern %ld posséde trop de pistes. (Réduite à %ld pistes).
+;WARNING: Pattern %ld has too many tracks. Cropping to %ld tracks.
+;
+MSG_PATT_TOO_MANY_LINES
+ATTENTION: La pattern %ld posséde trop de lignes. (Réduite à %ld lignes).
+;WARNING: Pattern %ld has too many lines. Cropping to %ld lines.
+;
+MSG_INVALID_NOTE
+ATTENTION: Note %ld invalide (Pattern %ld Piste %ld Ligne %ld).
+;WARNING: Invalid note %ld (Patt %ld Track %ld Line %ld).
+;
+MSG_UNKNOWN_EFF
+Effet inconnu : $%lx (Pattern %ld Piste %ld Ligne %ld).
+;Unknown effect: $%lx (Patt %ld Track %ld Line %ld).
+;
+MSG_EXTRA_DATA_AFTER_MOD
+ATTENTION: Données supplémentaire après le module: Elles seront ignorées.
+;WARNING: Extra data found after valid module: Will be ignored.
+;
+MSG_WRITING_HEADER
+Ecriture de l'en-tête...
+;Writing Header...
+;
+MSG_WRITING_PATTS
+Ecriture des Patterns...
+;Writing Patterns...
+;
+MSG_WRITING_INSTS
+Ecriture des Instruments...
+;Writing Instruments...
+;
+MSG_WRITING_INSTINFO
+Ecriture des Info-Instruments...
+;Writing Instruments Info...
+;
+MSG_WRITING_INSTDATA
+Ecriture des Data-Instruments...
+;Writing Instruments Data...
+;
+MSG_NOTE_TOO_LOW
+ATTENTION: La note en Pattern %ld Piste %ld Ligne %ld est trop basse.
+;WARNING: Note at Patt %ld Track %ld Line %ld is too low.
+;
+MSG_NOTE_TOO_HIGH
+ATTENTION: La note en Pattern %ld Piste %ld Ligne %ld est trop haute.
+;WARNING: Note at Patt %ld Track %ld Line %ld is too high.
+;
+MSG_NO_MEM_TO_HALVE
+ATTENTION: Pas assez de mémoire pour réduire le volume de l'instrument %lx.
+;WARNING: Not enough memory to halve volume of instrument %lx.
+;
+;
+; *****************************
+; * Strings for Get/SaveMED.c *
+; *****************************
+;
+MSG_READING_MMD
+Chargement du module MMD%lc...
+;Loading MMD%lc module...
+; The %lc is the MED format (0, 1, 2, ...)
+;
+MSG_UNSUPPORTED_MMD_FORMAT
+ERREUR: Format OctaMED non reconnu.
+;ERROR: Unsupported OctaMED format.
+;
+MSG_WRONG_EFFECT_IN_MMD0
+ATTENTION: L'effet %lx n'est pas reconnu dans le format MMD0. Utilisez le MMD1 ou mieux.
+;WARNING: Effect %lx is not supported in MMD0 format. Use MMD1 or better.
+;
+;
+; ***********************************
+; * Strings for ScreamTrackerHook.c *
+; ***********************************
+;
+MSG_ADLIB_INSTR
+ERREUR: L'instrument %lx est un %s ADLIB.
+;ERROR: Instrument %lx is an ADLib %s.
+; %s is the name of the ADLib instrument (eg: Snare).
+;
+MSG_TRACK_OUT_OF_RANGE
+ATTENTION: La piste %lx est hors de portée.
+;WARNING: Track %lx is out of range.
+;
+MSG_UNKNOWN_SAMPLE_COMPRESSION
+ATTENTION: Type de compression inconnue pour l'intrument %lx.
+;WARNING: Unknown sample compression for instrument %lx.
+;
+MSG_INST_IS_STEREO
+ATTENTION: L'instrument %lx est un sample stéréo.
+;WARNING: Instrument %lx is a stereo sample.
+;
+MSG_INST_IS_16BIT
+ATTENTION: L'instrument %lx est en 16 bits: impossible de le charger.
+;WARNING: Instrument %lx is 16bit: unable to load it.
+;
+;
+; *****************************
+; * Strings for TrackerHook.c *
+; *****************************
+;
+MSG_READING_TYPE_MODULE
+Chargement du module en %s...
+;Reading %s module...
+; (%s is the module format)
+;
+MSG_EXCEEDS_64_PATTS
+ATTENTION: Le module contient plus de 64 patterns. Il nécessite au moins ProTracker 2.3.
+;NOTE: Module exceeds 64 patterns. You need ProTracker 2.3 to play it.
+;
+MSG_MODULE_ID
+
+;Tracker ID: \"%s\"
+;
+MSG_MODULE_HAS_N_CHN
+
+;Module has %lu tracks...
+;
+MSG_SONG_HAS_NO_INSTS
+Ce fichier est une "song" et ne contient aucun instruments.
+;This file is a song and doesn't contain instruments.
+;
+MSG_EXCEEDS_MAXPAATTS
+ATTENTION: Le module contient plus de %ld patterns.
+;WARNING: Module execeeds %ld patterns.
+;
+MSG_PATT_WILL_GROW
+La pattern %ld est rallongée à 64 lignes. Elle n'était que de %ld lignes.
+;Pattern %ld will grow to 64 lines (was %ld lines long).
+;
+MSG_SPLITTING_PATT
+Morcelage de la pattern %ld (longue de %ld lignes).
+;Splitting pattern %ld (was %ld lines long).
+;
+; **************************
+; * Strings for SaveMIDI.c *
+; **************************
+;
+MSG_CHOOSING_CHANNELS
+Choix des voix...
+;Choosing Channels...
+;
+MSG_WRITING_MIDI_TRACKS
+Ecriture de pistes MIDI...
+;Writing MIDI Tracks...
+;
+MSG_TOO_MANY_CHANNELS
+ERREUR: Cette "song" requiert trop de voix MIDI.
+;ERROR: Song requires too many MIDI channels.
+;
+;
+; *********************
+; * Strings for Gui.c *
+; *********************
+;
+MSG_REALLY_QUIT_XMODULE
+Vous voulez VRAIMENT quitter XModule ?
+;Really Quit XModule?
+;
+MSG_CLOSE_ALL_WINDOWS
+Veuillez fermer toutes les fenêtres\n\
+puis cliquer sur 'Continuer'.
+;Please close all visitor windows\n\
+;and then select `Continue'.
+;
+;
+; ***********************
+; * Strings for Instr.c *
+; ***********************
+;
+MSG_UNKNOWN_IFF
+Format IFF inconnu pour %s.
+;Unknown IFF format %s.
+;
+MSG_ILLEGAL_IFF_STRUCTURE
+Structure IFF incohérente.
+;Illegal IFF structure.
+;
+MSG_SELECT_RAW_MODE
+Format d'instrument inconnu.\n\
+Veuillez sélectionner le mode RAW.
+;Unrecognized instrument format.\n\
+;Please select RAW mode.
+;
+MSG_RAW_MODES
+8 bits signé|8 bits non signés|Abandonner
+;Signed 8bit|Unsigned 8bit|Cancel
+;
+MSG_DATATYPES_ERROR
+Erreur de DataTypes: %s.
+;DataTypes error: %s.
+; %s is a detailed description of the problem.
+;
+MSG_UNKNOWN_COMPRESSION
+Type de compression inconnu.
+;Unknown compression type.
+;
+MSG_SAMPLE_WRONG_SIZE
+Les samples de %lu bits ne sont pas acceptés.
+;%lu bit samples are not supported.
+; %lu is the number of bits per sample.
+;
+MSG_SAMPLE_NOT_MONO
+Les samples autres que MONO ne sont pas acceptés.
+;Samples other than MONO are not supported.
+;
+MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS
+Les samples de %ld voix ne sont pas acceptés.
+;Samples with %ld channels are not supported.
+;
+MSG_NO_MEMORY_TO_OPTIMIZE_INSTR
+ATTENTION: Mémoire insuffisante pour optimiser l'instrument %lx.
+;WARNING: insufficient memory to optimize instrument %lx.
+;
+MSG_INSTR_WILL_SHRINK
+L'instrument %lx sera réduit de %ld à %ld.
+;Instrument %lx will shrink from %ld to %ld.
+;
+MSG_INSTR_DUPES_REMOVED
+Instrument redondant décelé et retiré: %lx == %lx.
+;Duplicate instruments found and removed: %lx == %lx.
+;
+MSG_INSTR_UNUSED
+L'instrument %lx n'étant jamais utilisé, il est retiré.
+;Instrument %lx was never used and it's being removed.
+;
+;
+; **********************
+; * Strings for Misc.c *
+; **********************
+;
+MSG_OPENLIB_VER_FAIL
+
+;Couldn't open \"%s\" version %ld or greater.
+;
+MSG_OPENLIB_FAIL
+Impossible d'ouvrir %s version %ld ou supérieur.
+;Couldn't open \"%s\".
+;
+;
+; ***************************
+; * Strings for Operators.c *
+; ***************************
+;
+MSG_CANT_INSERT_PATT
+Impossible d'insérer une autre pattern: La limite a déjà été atteinte.
+;Unable to insert pattern: Maximum number of patterns reached.
+;
+MSG_PATT_UNUSED
+La pattern %ld n'étant jamais utilisée, elle est retirée.
+;Pattern %ld is not used and is beeing deleted.
+;
+MSG_PATT_CUT
+La pattern %ld est coupée à la ligne %ld.
+;Pattern %ld will be cut at line %ld.
+;
+MSG_PATT_DUPE
+Pattern redondante décelée et retirée: %ld == %ld.
+;Duplicate patterns found and removed: %ld == %ld.
+; The two %ld are the pattern numbers.
+;
+MSG_SONG_LEN_DIFFERENT
+ATTENTION: Longueurs des "song" différentes. La plus petite est conservée.
+;WARNING: Song lengths are different. Using shorter one.
+;
+MSG_PATT_LEN_DIFFERENT
+ATTENTION: Patterns de longueurs différentes en %ld. La plus petite est conservée.
+;WARNING: Different pattern lengths at position %ld. Using shorter one.
+;
+MSG_ERR_INSTR_OVERFLOW
+
+;ERROR: Instruments overflow. Try remapping the instruments.
+;
+;
+; ***********************
+; * Strings for Prefs.c *
+; ***********************
+;
+MSG_BAD_PREFS_VERSION
+Version incorrecte du fichier de préférences.
+;Incorrect version of preferences file
+;
+;
+; ****************************
+; * Strings for Requesters.c *
+; ****************************
+;
+MSG_XMODULE_REQUEST
+Requête de XModule
+;XModule Request
+; This is the title of requesters put out by XModule.
+;
+MSG_CLONE_WB
+Clône de l'écran Workbench
+;Clone Workbench Screen
+;
+MSG_CLOSE_FILEREQUESTER
+Veuillez fermer le Requester de fichier\n et sélectionner 'Continuer'.
+;Please close FileRequester\n and then select `Continue'.
+;
+MSG_SELECT_MODULES
+Sélectionnez le(s) Module(s)...
+;Select Module(s)...
+;
+MSG_SELECT_INSTRUMENTS
+Sélectionnez l(es) Instrument(s)...
+;Select Instrument(s)...
+;
+MSG_SELECT_PATTERN
+sélectionnez la Pattern...
+;Select Pattern...
+;
+MSG_SAVE_MODULE
+Ecriture du module...
+;Save Module...
+;
+MSG_SAVE_INSTRUMENT
+Ecriture de l'instrument...
+;Save Instrument...
+;
+MSG_SAVE_PATTERN
+Ecriture de la pattern...
+;Save Pattern...
+;
+MSG_FILE_EXISTS
+Le fichier \"%s\"\n existe déjà.
+;File \"%s\"\nalready exists.
+;
+MSG_OVERWRITE
+Réécrire|Choisir un autre|Abandonner
+;Overwrite|Choose Another|Abort
+;
+;
+; **********************
+; * Strings for Song.c *
+; **********************
+;
+MSG_CLOSE_ALL_SONGS
+
+;Please close all open songs\n\
+;and then select `Continue'.
+;
+MSG_UNESPECTED_EOF
+Mauvaise fin de fichier.
+;Unespected end of file.
+;
+MSG_MODULE_LOADED_OK
+Module chargé OK.
+;Module loaded OK.
+;
+MSG_MODULE_SAVED_OK
+Module sauvagardé OK.
+;Module saved OK.
+;
+MSG_UNKNOWN_SAVE_FORMAT
+ERREUR: Format de sauvegarde inconnu.
+;ERROR: Unrecognized save format.
+;
+MSG_INVALID_LOOP_REMOVED
+Loop invalide de l'instrument %lx retiré.
+;Removed invalid loop for instrument %lx.
+;
+MSG_INVALID_LOOP_FIXED
+Loop invalide de l'instrument %lx corrigé.
+;Fixed invalid loop for instrument %lx.
+;
+MSG_SONG_HAS_NO_PATTS
+ATTENTION: La "song" ne contient aucune pattern.
+;WARNING: Song has no patterns.
+;
+MSG_SONG_HAS_NO_SEQ
+ATTENTION: La "song" ne contient aucune séquence.
+;WARNING: Song has no sequence.
+;
+MSG_INVALID_SONG_POS
+ATTENTION: La "song" en %ld référencie la pattern %ld qui n'existe pas.
+;WARNING: Song position %ld references pattern %ld, which doesn't exist.
+;
+MSG_UNKNOWN_MOD_FORMAT
+Impossible d'identifier le format de ce module.\n\
+(Charger un ficher de datas comme un module n'est pas très judicieux)
+;Unable to identify module format.\n\
+;(Loading a data file as a module is unwise)
+;
+MSG_SOUND_PRO_CANCEL
+SoundTracker 15|ProTracker|Abandonner
+;SoundTracker 15|ProTracker|Cancel
+;
+MSG_AUTHOR_UNKNOWN
+Inconnu
+;Unknown
+;
+MSG_SONG_UNTITLED
+Pas de titre
+;Untitled
+;
+;
+; **********************
+; * Strings for Help.c *
+; **********************
+;
+MSG_AMIGAGUIDE_ERROR
+Erreur AmigaGuide:
+;AmigaGuide error:
+; Note one blank space at the end of this line!
+;
+;
+; ***************************
+; * Strings for PattPrefs.c *
+; ***************************
+;
+MSG_PATTPREFS_TITLE
+
+;Pattern Editor Settings
+;
+;
+MSG_ADVANCE_TRACKS_GAD
+
+;Advance _Tracks
+;
+MSG_ADVANCE_LINES_GAD
+
+;Advance _Lines
+;
+MSG_MAX_UNDO_LEVELS_GAD
+
+;Max _Undo Levels
+;
+MSG_MAX_UNDO_MEM_GAD
+
+;Max Undo _Memory
+;
+MSG_CLIPBOARD_UNIT_GAD
+
+;Cli_pboard Unit
+;
+MSG_SCROLLER_POS_GAD
+
+;_Scroller Position
+;
+MSG_VERT_WRAP_GAD
+
+;_Vertical Wrap
+;
+MSG_HORIZ_WRAP_GAD
+
+;_Horizontal Wrap
+;
+MSG_HEX_LINE_NUMBERS_GAD
+
+;He_x Line Numbers
+;
+MSG_BLANK_ZERO_GAD
+
+;Blank _Zero Digits
+;
+MSG_BACKDROP_GAD
+
+;Backdrop _Window
+;
+MSG_HORIZ_SCROLLER_GAD
+
+;Ho_rizontal Scroller
+;
+MSG_DO_RULER_GAD
+
+;Display Cursor Ruler
+;
+MSG_DO_TINY_LINES_GAD
+
+;Display Tiny Lines
+;
+MSG_EDITOR_FONT_GAD
+
+;_Editor Font
+;
+MSG_BACKGROUND_PEN_GAD
+
+;Background
+;
+MSG_TEXT_PEN_GAD
+
+;T_ext
+;
+MSG_LINES_PEN_GAD
+
+;Li_nes
+;
+MSG_TINY_LINES_PEN_GAD
+
+;Tin_y Lines
+;
+MSG_RIGHT_GAD
+
+;Right
+;
+MSG_LEFT_GAD
+
+;Left
+;
+MSG_OFF_GAD
+
+;Off
+;
+MSG_PATT_TOO_LONG
+ERREUR: La pattern dépasse le nombre maximum de lignes.
+;ERROR: Pattern would exceed the maximum number of lines.
+;
+;
+; *****************************
+; * Strings for SequenceWin.c *
+; *****************************
+;
+;
+MSG_SEQUENCE_TITLE
+
+;Sequence Editor
+;
+;
+MSG_SEQUENCE_GAD
+
+;Sequence
+;
+MSG_UNDERSCORE_ADD_GAD
+
+;_Add
+;
+MSG_UP_GAD
+
+;Up
+;
+MSG_DOWN_GAD
+
+;Down
+;
+MSG_UNDERSCORE_NAME_GAD
+
+;_Name
+;
+MSG_PATTERNS_GAD
+
+;Patterns
+;
+MSG_UNDERSCORE_UP_GAD
+
+;_Up
+;
+MSG_UNDERSCORE_DOWN_GAD
+
+;_Down
+;
+MSG_UNDERSCORE_INS_GAD
+
+;_Ins
+;
+;
+MSG_UNNAMED
+
+;-- unnamed --
+;
+;
+; ********************************
+; * Strings for InstrumentsWin.c *
+; ********************************
+;
+MSG_INSTRUMENTS_TITLE
+
+;Instruments
+;
+;
+MSG_VOLUME_GAD
+
+;_Volume
+;
+MSG_FINETUNE_GAD
+
+;_FineTune
+;
+MSG_LENGTH_GAD
+
+;Length
+;
+MSG_KIND_GAD
+
+;_Kind
+;
+MSG_EDIT_DOTS_GAD
+
+;_Edit...
+;
+MSG_SAMPLE_GAD
+
+;Sample
+;
+MSG_SYNTH_GAD
+
+;Synth
+;
+MSG_HYBRID_GAD
+
+;Hybrid
+;
+;
+MSG_INSTRUMENTS_MEN
+
+;Instruments
+;
+MSG_LOAD_MEN
+
+;Load...
+;
+MSG_REMAP_MEN
+
+;Remap
+;
+MSG_SAVE_COMPRESSED_MEN
+
+;Save Compressed
+;
+MSG_SAVE_RAW_MEN
+
+;Save Raw
+;
+;
+MSG_EMPTY
+
+;-- empty --
+;
+;
+; *************************
+; * Strings for PlayWin.c *
+; *************************
+;
+MSG_PLAY_TITLE
+
+;Play
+;
+;
+MSG_VOL_GAD
+
+;_Vol
+;
+MSG_POS_GAD
+
+;Pos
+;
+MSG_TIME_GAD
+
+;Time
+;
+MSG_RST_GAD
+
+;_Rst
+; Stands for Reset/Restart
+;
+MSG_PLAYER_INIT_ERR
+Erreur d'initialisation du Player: %ld.
+;Player initialization error: %ld.
+;
+;
+; *****************************
+; * Strings for ProgressWin.c *
+; *****************************
+;
+MSG_PROGRESS_TITLE
+
+;XModule is working...
+;
+MSG_LOG_TITLE
+
+;XModule Log
+;
+;
+MSG_UNDERSCORE_ABORT_GAD
+
+;_Abort
+;
+;
+MSG_PERCENT_DONE
+%ld de %ld (%ld%% réalisés).
+;%ld of %ld (%ld%% done)
+; This string appears inside the progress window gauge.
+; eg: "5 of 20 (25% done)".
+; Leave that double percent (%%) alone! :-)
+;
+;
+; *****************************
+; * Strings for SongInfoWin.c *
+; *****************************
+;
+MSG_SONGINFO_TITLE
+
+;Song Information
+;
+;
+MSG_UNDERSCORE_NEW_GAD
+
+;Ne_w
+;
+MSG_OPEN_GAD
+
+;_Open...
+;
+MSG_SAVE_GAD
+
+;_Save
+;
+MSG_DEL_GAD
+
+;Del
+;
+MSG_SONG_NAME_GAD
+
+;Song _Name
+;
+MSG_AUTHOR_NAME_GAD
+
+;_Author
+;
+MSG_DEF_TEMPO_GAD
+
+;_Tempo
+;
+MSG_DEF_SPEED_GAD
+
+;S_peed
+;
+MSG_RESTART_GAD
+
+;_Restart
+;
+MSG_LENGHT_GAD
+
+;Length
+;
+MSG_NUM_PATTS_GAD
+
+;Num Patterns
+;
+MSG_NUM_TRACKS_GAD
+
+;Tracks
+;
+MSG_TOT_MOD_SIZE_GAD
+
+;Total Module Size
+;
+MSG_TOT_INST_SIZE_GAD
+
+;Total Instruments Size
+;
+;
+MSG_SONG_MEN
+
+;Song
+;
+MSG_MERGE_SONGS_MEN
+
+;Merge Songs
+;
+MSG_JOIN_SONGS_MEN
+
+;Join Songs
+;
+;
+MSG_JOIN_REQUIRES_TWO_SONGS
+ERREUR: Un ajout nécessite deux "song".
+;ERROR: Join requires two songs.
+;
+MSG_MERGE_REQUIRES_TWO_SONGS
+ERREUR: Une fusion nécessite deux "song".
+;ERROR: Merge requires two songs.
+;
+MSG_DISCARD_CURRENT_SONG
+Abandonner le traitement de cette "song"?
+;Discard current song?
+;
+;
+; ****************************
+; * Strings for ToolBoxWin.c *
+; ****************************
+;
+MSG_TOOLBOX_TITLE
+
+;ToolBox
+;
+;
+MSG_PLAY_GAD
+
+;Play...
+;
+MSG_SONGS_GAD
+
+;_Songs...
+;
+MSG_PATTERNS_DOTS_GAD
+
+;_Patterns...
+;
+MSG_INSTRUMENTS_GAD
+
+;_Instruments...
+;
+MSG_SEQUENCE_DOTS_GAD
+
+;Se_quence...
+;
+MSG_OPTIMIZATION_GAD
+
+;_Optimization...
+;
+MSG_PROJECT_MEN
+
+;Project
+;
+MSG_NEW_MEN
+
+;New
+;
+MSG_OPEN_MEN
+
+;Open...
+;
+MSG_OPEN_NEW_MEN
+
+;Open New...
+;
+MSG_SAVE_MEN
+
+;Save
+;
+MSG_SAVE_AS_MEN
+
+;Save As...
+;
+MSG_CLEAR_MEN
+
+;Clear...
+;
+MSG_ABOUT_MEN
+
+;About...
+;
+MSG_HELP_MEN
+
+;Help...
+;
+MSG_ICONIFY_MEN
+
+;Iconify...
+;
+MSG_QUIT_MEN
+
+;Quit
+;
+MSG_SETTINGS_MEN
+
+;Settings
+;
+MSG_SAVE_FORMAT_MEN
+
+;Save Format...
+;
+MSG_USER_INTERFACE_MEN
+
+;User Interface...
+;
+MSG_SAVE_ICONS_MEN
+
+;Save Icons
+;
+MSG_CONFIRM_OVERWRITE_MEN
+
+;Confirm Overwrite
+;
+MSG_CONFIRM_EXIT_MEN
+
+;Confirm Exit
+;
+MSG_VERBOSE_MEN
+
+;Verbose Log
+;
+MSG_OPEN_SETTINGS_MEN
+
+;Open Settings...
+;
+MSG_SAVE_SETTINGS_MEN
+
+;Save Settings
+;
+MSG_SAVE_SETTINGS_AS_MEN
+
+;Save Settings As...
+;
+;
+MSG_ABOUT_TEXT
+%s %s\n\
+Programme de Traitement de Modules\n\n\
+\
+%s\n\
+Tous droits réservés.\n\n\
+\
+Internet: bernie@shock.cosmos.it\n\n\
+FidoNet:  2:332/125.1\n\
+\
+Mémoire CHIP libre: %ld Ko\n\
+Mémoire FAST libre: %ld Ko\n\n\
+Ecran Public: %s\n\
+Port ARexx: %s\n\
+Cx_HotKey: %s\n\
+Language: %s
+;%s\n\
+;A Music Module Processing Utility\n\n\
+;\
+;%s\n\
+;All rights reserved.\n\n\
+;\
+;Internet: bernie@shock.cosmos.it\n\n\
+;FidoNet:  2:332/125.1\n\
+;\
+;Free CHIP Memory: %ldKB\n\
+;Free FAST Memory: %ldKB\n\n\
+;Public Screen: %s\n\
+;ARexx Port: %s\n\
+;Cx HotKey: %s\n\
+;Language: %s
+;
+MSG_DEFAULT
+--Défaut--
+;-- Default --
+;
+MSG_DISABLED
+--Désactivé--
+;-- Disabled --
+;
+;
+; *********************************
+; * Strings for OptimizationWin.c *
+; *********************************
+;
+MSG_OPTIMIZATION_TITLE
+
+;Module Optimization
+;
+;
+MSG_OPTIMIZE_GAD
+
+;_Optimize
+;
+MSG_REM_UNUSED_PATTS_GAD
+
+;Remove Unused _Patterns
+;
+MSG_REM_DUPLICATE_PATTS_GAD
+
+;Remove _Duplicate Patterns
+;
+MSG_REM_UNUSED_INSTR_GAD
+
+;Remove Unused _Instruments
+;
+MSG_REM_DUP_INSTR_GAD
+
+;Remove Duplicate I_nstruments
+;
+MSG_CUT_AFTER_LOOP_GAD
+
+;Cut Instruments After _Loop
+;
+MSG_CUT_ZERO_TAILS_GAD
+
+;Cut Instrument _Zero Tails
+;
+MSG_CUT_PATTERNS_GAD
+
+;Cut _Breaked Patterns
+;
+MSG_REMAP_INSTRUMENTS_GAD
+
+;_Remap Instruments
+;
+;
+MSG_SAVED_X_BYTES
+%ld octets gagnés (%ld%%)
+;Saved %ld bytes (%ld%%)
+;
+;
+; **************************
+; * Strings for PrefsWin.c *
+; **************************
+;
+;
+MSG_PREFS_TITLE
+
+;User Interface Settings
+;
+;
+MSG_PUBLIC_SCREEN_GAD
+
+;_Public Screen
+;
+MSG_DISPLAY_MODE_GAD
+
+;Display _Mode
+;
+MSG_WINDOW_FONT_GAD
+
+;_Window Font
+;
+MSG_LISTVIEW_FONT_GAD
+
+;_ListView Font
+;
+MSG_REQUESTERS_GAD
+
+;_Requesters
+;
+MSG_USE_DATATYPES_GAD
+
+;Use _DataTypes
+;
+MSG_APPICON_GAD
+
+;Put App_Icon
+;
+MSG_REFRESH_GAD
+
+;Refres_h
+;
+MSG_LOG_TO_FILE_GAD
+
+;Log To File
+;
+MSG_LOG_LEVEL_GAD
+
+;_Log Level
+;
+MSG_ASK_AUTOSAVE_GAD
+
+;Confirm _Autosave
+;
+MSG_AUTOSAVE_TIME_GAD
+
+;Autosave Mi_nutes
+;
+MSG_DO_BACKUPS_GAD
+
+;Create _Backups
+;
+MSG_BACKUP_TEMPLATE_GAD
+
+;Bac_kup Template
+;
+MSG_BACKUP_VERSIONS_GAD
+
+;Backup _Versions
+;
+MSG_ASL_GAD
+
+;Asl
+;
+MSG_REQTOOLS_GAD
+
+;ReqTools
+;
+MSG_SMART_GAD
+
+;Smart
+;
+MSG_SIMPLE_GAD
+
+;Simple
+;
+MSG_CLONE_DEF_SCREEN
+
+;--Clone Default Screen--
+;
+;
+; ****************************
+; * Strings for PatternWin.c *
+; ****************************
+;
+MSG_PATTERN_TITLE
+
+;Pattern Editor
+;
+;
+MSG_PATTERNS_MEN
+
+;Patterns
+;
+MSG_SIZE_MEN
+
+;Size...
+;
+MSG_EDIT_MEN
+
+;Edit
+;
+MSG_MARK_MEN
+
+;Mark
+;
+MSG_CUT_MEN
+
+;Cut
+;
+MSG_COPY_MEN
+
+;Copy
+;
+MSG_PASTE_MEN
+
+;Paste
+;
+MSG_ERASE_MEN
+
+;Erase
+;
+MSG_UNDO_MEN
+
+;Undo
+;
+MSG_REDO_MEN
+
+;Redo
+;
+MSG_EDITOR_SETTINGS_MEN
+
+;Editor Settings...
+;
+;
+; ***************************
+; * Strings for SaversWin.c *
+; ***************************
+;
+MSG_SAVERS_TITLE
+
+;Savers
+;
+;
+MSG_SF_SEQUENCE_GAD
+
+;Se_quence
+;
+MSG_SF_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_SF_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_SF_NAMES_GAD
+
+;Names
+;
+MSG_ADD_ICON_GAD
+
+;Add I_con
+;
+MSG_MODE_GAD
+
+;_Mode
+;
+MSG_OPTIONS_DOTS_GAD
+
+;Options...
+;
+MSG_NONE_GAD
+
+;None
+;
+MSG_XPK_GAD
+
+;XPK
+;
+MSG_LHA_GAD
+
+;LhA
+;
+;
+MSG_DESCRIPTION
+
+;Description
+;
+MSG_AUTHOR
+
+;Author
+;
+MSG_MAXLENGTH
+
+;Max Length
+;
+MSG_MAXTRACKS
+
+;Max Tracks
+;
+MSG_MAXINSTRUMENTS
+
+;Max Instruments
+;
+MSG_MAXPATTERNS
+
+;Max Patterns
+;
+MSG_MAXPATTLEN
+
+;Max Pattern Length
+;
+MSG_MAXSAMPLELEN
+
+;Max Sample Length
+;
+;
+; **********************
+; * Strings ClearWin.c *
+; **********************
+;
+MSG_CLEAR_TITLE
+
+;Clear Module
+;
+;
+MSG_CLR_SEQUENCE_GAD
+
+;_Sequence
+;
+MSG_CLR_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_CLR_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_CLEARMOD_GAD
+
+;_Clear
+;
+;
+; ***************************
+; * Strings for SampleWin.c *
+; ***************************
+;
+MSG_SAMPLE_TITLE
+
+;Sample Editor
+;
+;
+MSG_RENDER_MEN
+
+;Render
+;
+MSG_POINTS_MEN
+
+;Points
+;
+MSG_LINES_MEN
+
+;Lines
+;
+MSG_FILLED_MEN
+
+;Filled
+;
+;
+; *****************************
+; * Strings for PattSizeWin.c *
+; *****************************
+;
+MSG_PATTSIZE_TITLE
+
+;Pattern Attributes
+;
+;
+MSG_LINES_GAD
+
+;_Lines
+;
+MSG_TRACKS_GAD
+
+;_Tracks
+;
+MSG_DOUBLE_GAD
+
+;_Double
+;
+MSG_HALVE_GAD
+
+;_Halve
+;
+;
+; *************************
+; * Strings for Library.c *
+; *************************
+;
+MSG_SONG_TOO_MANY_TRACKS
+
+;WARNING: The selected saver supports max %lu tracks, but the song has %lu tracks.
+;
+MSG_SONG_TOO_MANY_PATTS
+
+;WARNING: The selected saver supports max %lu patterns; the song has %lu patterns.
+;
+MSG_SONG_TOO_MANY_INSTRS
+
+;WARNING: The selected saver supports max %lu instruments, last instrument is %lu.
+;
+MSG_SONG_TOO_MANY_POS
+
+;WARNING: The selected saver supports max %lu positions, but the song has %lu positions.
+;
+MSG_PATT_LENGTH_INVALID
+
+;WARNING: The length of pattern %ld ($%lx lines) isn't allowed with the selected saver.
+;
+MSG_INSTR_TOO_LONG
+ATTENTION: L'instrument %lx est trop long.
+;WARNING: Instrument %lx is too long and will be cropped to %lu bytes.
+;
+MSG_WILL_MODIFY_SONG
+
+;Some modifications need to be performed on this song\n\
+;in order to adapt it to the limitations of the\n\
+;destination format.
+;
+MSG_TRY_REMAPPING_INSTRUMENTS
+
+;Some instruments are placed beyond the limit for\n\
+;the selected format.\n\
+;Remapping the instruments now could help saving\n\
+;all them along with the selected format.
+;
+MSG_SAVING_MODULE
+
+;Saving %s module \"%s\"...
+; the first %s is the format name, the second %s is the song name.
diff --git a/Catalogs/italiano.ct b/Catalogs/italiano.ct
new file mode 100644 (file)
index 0000000..27bc5f1
--- /dev/null
@@ -0,0 +1,1488 @@
+## version $VER: Italiano.catalog 1.1 ($TODAY)
+## language italiano
+## codeset 0
+;
+## chunk AUTH Italian catalog translation by Steven Cantini
+;**
+;** XModule.cd 3.9
+;**
+;** Copyright (C) 1995,96 Bernardo Innocenti
+;**
+;
+; **********************************
+; * Strings for all source modules *
+; **********************************
+;
+;
+MSG_NULL
+
+;
+; This string should remain blank.
+;
+MSG_OK
+Ok
+;Ok
+;
+MSG_UNDERSCORE_USE_GAD
+_Usa
+;_Use
+;
+MSG_UNDERSCORE_OK_GAD
+
+;_Ok
+;
+MSG_UNDERSCORE_CANCEL_GAD
+_Annulla
+;_Cancel
+;
+MSG_YES_OR_NO
+Sì|No
+;Yes|No
+;
+MSG_RETRY_OR_CANCEL
+Riprova|Annulla
+;Retry|Cancel
+;
+MSG_PROCEED_OR_CANCEL
+
+;Proceed|Cancel
+;
+MSG_CONTINUE
+Continua
+;Continue
+;
+MSG_NO_FREE_STORE
+Memoria insufficiente.
+;Insufficient memory.
+;
+MSG_BREAK
+Annullato.
+;Aborted.
+;
+MSG_ERR_LOAD
+Non posso caricare \"%s\": %s.
+;Unable to load \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_CANT_OPEN
+Non posso aprire il file \"%s\": %s.
+;Cannot open file \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_READING
+Errore di lettura nel file \"%s\": %s.
+;Error reading \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_WRITING
+Errore scrivendo \"%s\": %s.
+;Error writing \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+;
+; *****************************
+; * Strings for Compression.c *
+; *****************************
+;
+MSG_DECRUNCHING
+Decompressione...
+;Decrunching...
+;
+MSG_NOTHING_IN_ARC
+L'archivio \"%s\" è vuoto.
+;Nothing found in archive \"%s\".
+;
+MSG_CANT_LOAD_COMPRESSED
+Non posso caricare il file compresso.
+;Unable to load compressed file.
+;
+MSG_ERROR_DECOMPRESSING
+Errore di decompressione sul file \"%s\": %s.
+;Error decompressing file \"%s\": %s.
+;
+;
+; ********************
+; * Strings for Cx.c *
+; ********************
+;
+MSG_BAD_HOTKEY
+Descrizione della Commodity HotKey errata.
+;Bad Commodity HotKey description.
+;
+;
+; ***************************
+; * Strings for all loaders *
+; ***************************
+;
+MSG_READING_PATTS
+Lettura Patterns...
+;Reading Patterns...
+;
+MSG_READING_INSTS_INFO
+Lettura Info degli Strumenti...
+;Reading Instruments Info...
+;
+MSG_READING_INSTS
+Lettura Strumenti...
+;Reading Instruments...
+;
+MSG_ERR_CANT_LOAD_PATT
+ERRORE: Non ho potuto caricare il pattern %ld.
+;ERROR: Couldn't load pattern %ld.
+;
+MSG_ERR_CANT_LOAD_INST
+ERRORE: Non ho potuto caricare lo strumento %lx.
+;ERROR: Couldn't load instrument %lx.
+;
+MSG_ERR_NO_MEM_FOR_INST
+ERRORE: Memoria insufficiente per lo strumento %lx.
+;ERROR: Not enough memory for instrument %lx.
+;
+MSG_ERR_NOT_A_SAMPLE
+ERRORE: Lo strumento %lx non è un sample.
+;ERROR: Instrument %lx is not a sample.
+;
+MSG_SONG_TOO_LONG
+ATTENZIONE: La song è troppo grande. Verra' troncata.
+;WARNING: Song length exceeds maximum. Will be truncated.
+;
+MSG_SONG_HAS_TOO_MANY_PATT
+ATTENZIONE: La song ha troppi pattern.
+;WARNING: Song exceeds maximum number of patterns.
+;
+MSG_PATT_TOO_MANY_TRACKS
+ATTENZIONE: Il pattern %ld ha troppe tracce. Lo riduco a %ld tracce.
+;WARNING: Pattern %ld has too many tracks. Cropping to %ld tracks.
+;
+MSG_PATT_TOO_MANY_LINES
+ATTENZIONE: Il pattern %ld ha troppe linee. Lo riduco a %ld linee.
+;WARNING: Pattern %ld has too many lines. Cropping to %ld lines.
+;
+MSG_INVALID_NOTE
+ATTENZIONE: Nota %ld non valida (Patt %ld Traccia %ld Linea %ld).
+;WARNING: Invalid note %ld (Patt %ld Track %ld Line %ld).
+;
+MSG_UNKNOWN_EFF
+Effetto sconosciuto: $%lx (Patt %ld Traccia %ld Linea %ld).
+;Unknown effect: $%lx (Patt %ld Track %ld Line %ld).
+;
+MSG_EXTRA_DATA_AFTER_MOD
+ATTENZIONE: Ulteriori dati trovati dopo un modulo valido: li ignoro.
+;WARNING: Extra data found after valid module: Will be ignored.
+;
+MSG_WRITING_HEADER
+Scrittura dell'Header...
+;Writing Header...
+;
+MSG_WRITING_PATTS
+Scrittura dei Pattern...
+;Writing Patterns...
+;
+MSG_WRITING_INSTS
+Scrittura degli Strumenti...
+;Writing Instruments...
+;
+MSG_WRITING_INSTINFO
+Scrittura Info degli Strumenti...
+;Writing Instruments Info...
+;
+MSG_WRITING_INSTDATA
+Scrittura dei Dati degli Strumenti...
+;Writing Instruments Data...
+;
+MSG_NOTE_TOO_LOW
+ATTENZIONE: La nota al Patt %ld Traccia %ld Linea %ld è troppo bassa.
+;WARNING: Note at Patt %ld Track %ld Line %ld is too low.
+;
+MSG_NOTE_TOO_HIGH
+ATTENZIONE: La nota al Patt %ld Traccia %ld Linea %ld è troppo alta.
+;WARNING: Note at Patt %ld Track %ld Line %ld is too high.
+;
+MSG_NO_MEM_TO_HALVE
+ATTENZIONE: Memoria insuff. per dimezzare il volume dello strumento %lx.
+;WARNING: Not enough memory to halve volume of instrument %lx.
+;
+;
+; *****************************
+; * Strings for Get/SaveMED.c *
+; *****************************
+;
+MSG_READING_MMD
+Lettura modulo MMD%lc...
+;Loading MMD%lc module...
+; The %lc is the MED format (0, 1, 2, ...)
+;
+MSG_UNSUPPORTED_MMD_FORMAT
+ERRORE: Formato OctaMED non supportato.
+;ERROR: Unsupported OctaMED format.
+;
+MSG_WRONG_EFFECT_IN_MMD0
+ATTENZIONE: L'effetto %lx non è supportato nell'MMD0. Usa almeno l'MMD1.
+;WARNING: Effect %lx is not supported in MMD0 format. Use MMD1 or better.
+;
+;
+; ***********************************
+; * Strings for ScreamTrackerHook.c *
+; ***********************************
+;
+MSG_ADLIB_INSTR
+ERRORE: Lo strumento %lx è un %s ADLib.
+;ERROR: Instrument %lx is an ADLib %s.
+; %s is the name of the ADLib instrument (eg: Snare).
+;
+MSG_TRACK_OUT_OF_RANGE
+ATTENZIONE: La traccia %lx è fuori dal limite.
+;WARNING: Track %lx is out of range.
+;
+MSG_UNKNOWN_SAMPLE_COMPRESSION
+ATTENZIONE: Compressione del sample sconosciuta per lo strumento %lx.
+;WARNING: Unknown sample compression for instrument %lx.
+;
+MSG_INST_IS_STEREO
+ATTENZIONE: Lo strumento %lx e' stereo.
+;WARNING: Instrument %lx is a stereo sample.
+;
+MSG_INST_IS_16BIT
+ATTENZIONE: Lo strumento %lx e' a 16bit: non puo' essere caricato.
+;WARNING: Instrument %lx is 16bit: unable to load it.
+;
+;
+; *****************************
+; * Strings for TrackerHook.c *
+; *****************************
+;
+MSG_READING_TYPE_MODULE
+Lettura modulo %s...
+;Reading %s module...
+; (%s is the module format)
+;
+MSG_EXCEEDS_64_PATTS
+ATTENZIONE: Il modulo ha più di 64 pattern. Ti serve il ProTracker 2.3 per sentirlo.
+;NOTE: Module exceeds 64 patterns. You need ProTracker 2.3 to play it.
+;
+MSG_MODULE_ID
+
+;Tracker ID: \"%s\"
+;
+MSG_MODULE_HAS_N_CHN
+
+;Module has %lu tracks...
+;
+MSG_SONG_HAS_NO_INSTS
+Questo file è una song e non contiene strumenti.
+;This file is a song and doesn't contain instruments.
+;
+MSG_EXCEEDS_MAXPAATTS
+ATTENZIONE: Il modulo supera %ld pattern.
+;WARNING: Module execeeds %ld patterns.
+;
+MSG_PATT_WILL_GROW
+Il pattern %ld sarà portato a 64 linee (era lungo %ld).
+;Pattern %ld will grow to 64 lines (was %ld lines long).
+;
+MSG_SPLITTING_PATT
+Il pattern %ld verrà spezzato (era lungo %ld linee).
+;Splitting pattern %ld (was %ld lines long).
+;
+; **************************
+; * Strings for SaveMIDI.c *
+; **************************
+;
+MSG_CHOOSING_CHANNELS
+Scelta dei Canali...
+;Choosing Channels...
+;
+MSG_WRITING_MIDI_TRACKS
+Scrittura tracce MIDI...
+;Writing MIDI Tracks...
+;
+MSG_TOO_MANY_CHANNELS
+ERRORE: La song richiede troppi canali MIDI.
+;ERROR: Song requires too many MIDI channels.
+;
+;
+; *********************
+; * Strings for Gui.c *
+; *********************
+;
+MSG_REALLY_QUIT_XMODULE
+Si vuole veramente uscire da XModule?
+;Really Quit XModule?
+;
+MSG_CLOSE_ALL_WINDOWS
+Chiudi tutte le finestre in questo schermo\ne poi seleziona `Continua'.
+;Please close all visitor windows\n\
+;and then select `Continue'.
+;
+;
+; ***********************
+; * Strings for Instr.c *
+; ***********************
+;
+MSG_UNKNOWN_IFF
+Formato IFF %s sconosciuto.
+;Unknown IFF format %s.
+;
+MSG_ILLEGAL_IFF_STRUCTURE
+Struttura IFF illegale.
+;Illegal IFF structure.
+;
+MSG_SELECT_RAW_MODE
+Formato dello strumento sconosciuto.\nPer favore scegli il modo RAW.
+;Unrecognized instrument format.\n\
+;Please select RAW mode.
+;
+MSG_RAW_MODES
+8bit con segno|8bit senza segno|Annulla
+;Signed 8bit|Unsigned 8bit|Cancel
+;
+MSG_DATATYPES_ERROR
+Errore DataTypes: %s.
+;DataTypes error: %s.
+; %s is a detailed description of the problem.
+;
+MSG_UNKNOWN_COMPRESSION
+Tipo di compressione sconosciuta.
+;Unknown compression type.
+;
+MSG_SAMPLE_WRONG_SIZE
+I campioni a %lu bit non sono supportati.
+;%lu bit samples are not supported.
+; %lu is the number of bits per sample.
+;
+MSG_SAMPLE_NOT_MONO
+I campioni non MONO non sono supportati.
+;Samples other than MONO are not supported.
+;
+MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS
+I campioni con %ld canali non sono supportati.
+;Samples with %ld channels are not supported.
+;
+MSG_NO_MEMORY_TO_OPTIMIZE_INSTR
+ATTENZIONE: Memoria insufficiente per ottimizzare lo strumento %lx.
+;WARNING: insufficient memory to optimize instrument %lx.
+;
+MSG_INSTR_WILL_SHRINK
+Accorcio lo strumento %lx da %ld a %ld bytes.
+;Instrument %lx will shrink from %ld to %ld.
+;
+MSG_INSTR_DUPES_REMOVED
+Ho trovato e rimosso due strumenti uguali: %lx == %lx.
+;Duplicate instruments found and removed: %lx == %lx.
+;
+MSG_INSTR_UNUSED
+Lo strumento %lx non è mai usato. Lo rimuovo.
+;Instrument %lx was never used and it's being removed.
+;
+;
+; **********************
+; * Strings for Misc.c *
+; **********************
+;
+MSG_OPENLIB_VER_FAIL
+Non riesco ad aprire \"%s\" versione %ld o superiore.
+;Couldn't open \"%s\" version %ld or greater.
+;
+MSG_OPENLIB_FAIL
+Non riesco ad aprire \"%s\".
+;Couldn't open \"%s\".
+;
+;
+; ***************************
+; * Strings for Operators.c *
+; ***************************
+;
+MSG_CANT_INSERT_PATT
+Non posso inserire il pattern: è stato raggiunto il numero massimo di pattern.
+;Unable to insert pattern: Maximum number of patterns reached.
+;
+MSG_PATT_UNUSED
+Il pattern %ld non è usato e verrà cancellato.
+;Pattern %ld is not used and is beeing deleted.
+;
+MSG_PATT_CUT
+Il pattern %ld sarà tagliato alla linea %ld.
+;Pattern %ld will be cut at line %ld.
+;
+MSG_PATT_DUPE
+Ho trovato e rimosso due pattern uguali: %ld == %ld.
+;Duplicate patterns found and removed: %ld == %ld.
+; The two %ld are the pattern numbers.
+;
+MSG_SONG_LEN_DIFFERENT
+ATTENZIONE: Le song hanno lunghezza diversa. Uso quella piu piccola.
+;WARNING: Song lengths are different. Using shorter one.
+;
+MSG_PATT_LEN_DIFFERENT
+ATTENZIONE: Lunghezze di pattern diverse alla posizione %ld. Uso la più piccola.
+;WARNING: Different pattern lengths at position %ld. Using shorter one.
+;
+MSG_ERR_INSTR_OVERFLOW
+
+;ERROR: Instruments overflow. Try remapping the instruments.
+;
+;
+; ***********************
+; * Strings for Prefs.c *
+; ***********************
+;
+MSG_BAD_PREFS_VERSION
+Versione errata del file di preferenze
+;Incorrect version of preferences file
+;
+;
+; ****************************
+; * Strings for Requesters.c *
+; ****************************
+;
+MSG_XMODULE_REQUEST
+Richiesta di XModule
+;XModule Request
+; This is the title of requesters put out by XModule.
+;
+MSG_CLONE_WB
+Clona lo schermo Workbench
+;Clone Workbench Screen
+;
+MSG_CLOSE_FILEREQUESTER
+Per favore chiudi il FileRequester\ne poi seleziona `Continua'.
+;Please close FileRequester\n and then select `Continue'.
+;
+MSG_SELECT_MODULES
+Seleziona uno o più moduli...
+;Select Module(s)...
+;
+MSG_SELECT_INSTRUMENTS
+Seleziona uno o più strumenti...
+;Select Instrument(s)...
+;
+MSG_SELECT_PATTERN
+Seleziona un pattern...
+;Select Pattern...
+;
+MSG_SAVE_MODULE
+Salva il modulo...
+;Save Module...
+;
+MSG_SAVE_INSTRUMENT
+Salva lo strumento...
+;Save Instrument...
+;
+MSG_SAVE_PATTERN
+Salva il pattern...
+;Save Pattern...
+;
+MSG_FILE_EXISTS
+Il file \"%s\"\nesiste già.
+;File \"%s\"\nalready exists.
+;
+MSG_OVERWRITE
+Sovrascrivi|Cambia Nome|Annulla
+;Overwrite|Choose Another|Abort
+;
+;
+; **********************
+; * Strings for Song.c *
+; **********************
+;
+MSG_CLOSE_ALL_SONGS
+
+;Please close all open songs\n\
+;and then select `Continue'.
+;
+MSG_UNESPECTED_EOF
+Fine prematura del file.
+;Unespected end of file.
+;
+MSG_MODULE_LOADED_OK
+Modulo caricato correttamente.
+;Module loaded OK.
+;
+MSG_MODULE_SAVED_OK
+Modulo salvato correttamente.
+;Module saved OK.
+;
+MSG_UNKNOWN_SAVE_FORMAT
+ERRORE: Formato di save sconosciuto.
+;ERROR: Unrecognized save format.
+;
+MSG_INVALID_LOOP_REMOVED
+Ho rimosso un loop non valido per lo strumento %lx.
+;Removed invalid loop for instrument %lx.
+;
+MSG_INVALID_LOOP_FIXED
+Ho riparato un loop non valido per lo strumento %lx.
+;Fixed invalid loop for instrument %lx.
+;
+MSG_SONG_HAS_NO_PATTS
+ATTENZIONE: La song non ha pattern.
+;WARNING: Song has no patterns.
+;
+MSG_SONG_HAS_NO_SEQ
+ATTENZIONE: La song non ha una sequenza.
+;WARNING: Song has no sequence.
+;
+MSG_INVALID_SONG_POS
+ATTENZIONE: La posizione %ld rimanda al pattern %ld, che non esiste.
+;WARNING: Song position %ld references pattern %ld, which doesn't exist.
+;
+MSG_UNKNOWN_MOD_FORMAT
+Non riesco ad identificare il formato.\n(Caricare un file di dati come modulo è pericoloso)
+;Unable to identify module format.\n\
+;(Loading a data file as a module is unwise)
+;
+MSG_SOUND_PRO_CANCEL
+SoundTracker 15|ProTracker|Annulla
+;SoundTracker 15|ProTracker|Cancel
+;
+MSG_AUTHOR_UNKNOWN
+Sconosciuto
+;Unknown
+;
+MSG_SONG_UNTITLED
+Senza titolo
+;Untitled
+;
+;
+; **********************
+; * Strings for Help.c *
+; **********************
+;
+MSG_AMIGAGUIDE_ERROR
+Errore AmigaGuide:
+;AmigaGuide error:
+; Note one blank space at the end of this line!
+;
+;
+; ***************************
+; * Strings for PattPrefs.c *
+; ***************************
+;
+MSG_PATTPREFS_TITLE
+Parametri Editor Pattern
+;Pattern Editor Settings
+;
+;
+MSG_ADVANCE_TRACKS_GAD
+
+;Advance _Tracks
+;
+MSG_ADVANCE_LINES_GAD
+
+;Advance _Lines
+;
+MSG_MAX_UNDO_LEVELS_GAD
+
+;Max _Undo Levels
+;
+MSG_MAX_UNDO_MEM_GAD
+
+;Max Undo _Memory
+;
+MSG_CLIPBOARD_UNIT_GAD
+
+;Cli_pboard Unit
+;
+MSG_SCROLLER_POS_GAD
+
+;_Scroller Position
+;
+MSG_VERT_WRAP_GAD
+
+;_Vertical Wrap
+;
+MSG_HORIZ_WRAP_GAD
+
+;_Horizontal Wrap
+;
+MSG_HEX_LINE_NUMBERS_GAD
+
+;He_x Line Numbers
+;
+MSG_BLANK_ZERO_GAD
+
+;Blank _Zero Digits
+;
+MSG_BACKDROP_GAD
+
+;Backdrop _Window
+;
+MSG_HORIZ_SCROLLER_GAD
+
+;Ho_rizontal Scroller
+;
+MSG_DO_RULER_GAD
+
+;Display Cursor Ruler
+;
+MSG_DO_TINY_LINES_GAD
+
+;Display Tiny Lines
+;
+MSG_EDITOR_FONT_GAD
+
+;_Editor Font
+;
+MSG_BACKGROUND_PEN_GAD
+
+;Background
+;
+MSG_TEXT_PEN_GAD
+
+;T_ext
+;
+MSG_LINES_PEN_GAD
+
+;Li_nes
+;
+MSG_TINY_LINES_PEN_GAD
+
+;Tin_y Lines
+;
+MSG_RIGHT_GAD
+
+;Right
+;
+MSG_LEFT_GAD
+
+;Left
+;
+MSG_OFF_GAD
+
+;Off
+;
+MSG_PATT_TOO_LONG
+ERRORE: Il pattern crescerebbe oltre il massimo numero di linee.
+;ERROR: Pattern would exceed the maximum number of lines.
+;
+;
+; *****************************
+; * Strings for SequenceWin.c *
+; *****************************
+;
+;
+MSG_SEQUENCE_TITLE
+Editor Sequenza
+;Sequence Editor
+;
+;
+MSG_SEQUENCE_GAD
+Sequenza
+;Sequence
+;
+MSG_UNDERSCORE_ADD_GAD
+_Aggiungi
+;_Add
+;
+MSG_UP_GAD
+Su
+;Up
+;
+MSG_DOWN_GAD
+Giu`
+;Down
+;
+MSG_UNDERSCORE_NAME_GAD
+_Nome
+;_Name
+;
+MSG_PATTERNS_GAD
+Patterns
+;Patterns
+;
+MSG_UNDERSCORE_UP_GAD
+_Su
+;_Up
+;
+MSG_UNDERSCORE_DOWN_GAD
+_Giu`
+;_Down
+;
+MSG_UNDERSCORE_INS_GAD
+_Ins
+;_Ins
+;
+;
+MSG_UNNAMED
+--senza nome--
+;-- unnamed --
+;
+;
+; ********************************
+; * Strings for InstrumentsWin.c *
+; ********************************
+;
+MSG_INSTRUMENTS_TITLE
+Strumenti
+;Instruments
+;
+;
+MSG_VOLUME_GAD
+_Volume
+;_Volume
+;
+MSG_FINETUNE_GAD
+_FineTune
+;_FineTune
+;
+MSG_LENGTH_GAD
+Lunghezza
+;Length
+;
+MSG_KIND_GAD
+_Tipo
+;_Kind
+;
+MSG_EDIT_DOTS_GAD
+_Edita...
+;_Edit...
+;
+MSG_SAMPLE_GAD
+Campione
+;Sample
+;
+MSG_SYNTH_GAD
+Synth
+;Synth
+;
+MSG_HYBRID_GAD
+Ibrido
+;Hybrid
+;
+;
+MSG_INSTRUMENTS_MEN
+Strumenti
+;Instruments
+;
+MSG_LOAD_MEN
+Carica...
+;Load...
+;
+MSG_REMAP_MEN
+Rimappa
+;Remap
+;
+MSG_SAVE_COMPRESSED_MEN
+Salva Compresso
+;Save Compressed
+;
+MSG_SAVE_RAW_MEN
+Salva Grezzo
+;Save Raw
+;
+;
+MSG_EMPTY
+--vuoto--
+;-- empty --
+;
+;
+; *************************
+; * Strings for PlayWin.c *
+; *************************
+;
+MSG_PLAY_TITLE
+Riproduzione
+;Play
+;
+;
+MSG_VOL_GAD
+
+;_Vol
+;
+MSG_POS_GAD
+Pos
+;Pos
+;
+MSG_TIME_GAD
+Tempo
+;Time
+;
+MSG_RST_GAD
+Rst
+;_Rst
+; Stands for Reset/Restart
+;
+MSG_PLAYER_INIT_ERR
+Errore di inizializzazione del player: %ld.
+;Player initialization error: %ld.
+;
+;
+; *****************************
+; * Strings for ProgressWin.c *
+; *****************************
+;
+MSG_PROGRESS_TITLE
+
+;XModule is working...
+;
+MSG_LOG_TITLE
+
+;XModule Log
+;
+;
+MSG_UNDERSCORE_ABORT_GAD
+
+;_Abort
+;
+;
+MSG_PERCENT_DONE
+%ld di %ld (%ld%% fatto)
+;%ld of %ld (%ld%% done)
+; This string appears inside the progress window gauge.
+; eg: "5 of 20 (25% done)".
+; Leave that double percent (%%) alone! :-)
+;
+;
+; *****************************
+; * Strings for SongInfoWin.c *
+; *****************************
+;
+MSG_SONGINFO_TITLE
+Informazioni Song
+;Song Information
+;
+;
+MSG_UNDERSCORE_NEW_GAD
+
+;Ne_w
+;
+MSG_OPEN_GAD
+_Carica...
+;_Open...
+;
+MSG_SAVE_GAD
+_Salva
+;_Save
+;
+MSG_DEL_GAD
+Del
+;Del
+;
+MSG_SONG_NAME_GAD
+_Nome Song
+;Song _Name
+;
+MSG_AUTHOR_NAME_GAD
+_Autore
+;_Author
+;
+MSG_DEF_TEMPO_GAD
+_Tempo
+;_Tempo
+;
+MSG_DEF_SPEED_GAD
+_Velocita`
+;S_peed
+;
+MSG_RESTART_GAD
+_Riparti
+;_Restart
+;
+MSG_LENGHT_GAD
+Lunghezza
+;Length
+;
+MSG_NUM_PATTS_GAD
+Num Patterns
+;Num Patterns
+;
+MSG_NUM_TRACKS_GAD
+Tracce
+;Tracks
+;
+MSG_TOT_MOD_SIZE_GAD
+Lunghezza Totale Modulo
+;Total Module Size
+;
+MSG_TOT_INST_SIZE_GAD
+Lunghezza Totale Strumenti
+;Total Instruments Size
+;
+;
+MSG_SONG_MEN
+Song
+;Song
+;
+MSG_MERGE_SONGS_MEN
+Fusione Song
+;Merge Songs
+;
+MSG_JOIN_SONGS_MEN
+Unione Song
+;Join Songs
+;
+;
+MSG_JOIN_REQUIRES_TWO_SONGS
+ERRORE: L'unione richiede due song.
+;ERROR: Join requires two songs.
+;
+MSG_MERGE_REQUIRES_TWO_SONGS
+ERRORE: La fusione richiede due song.
+;ERROR: Merge requires two songs.
+;
+MSG_DISCARD_CURRENT_SONG
+Scarto la song corrente?
+;Discard current song?
+;
+;
+; ****************************
+; * Strings for ToolBoxWin.c *
+; ****************************
+;
+MSG_TOOLBOX_TITLE
+Barra Strumenti
+;ToolBox
+;
+;
+MSG_PLAY_GAD
+Play...
+;Play...
+;
+MSG_SONGS_GAD
+_Songs...
+;_Songs...
+;
+MSG_PATTERNS_DOTS_GAD
+_Patterns...
+;_Patterns...
+;
+MSG_INSTRUMENTS_GAD
+S_trumenti...
+;_Instruments...
+;
+MSG_SEQUENCE_DOTS_GAD
+Se_quenza...
+;Se_quence...
+;
+MSG_OPTIMIZATION_GAD
+_Ottimizzazione...
+;_Optimization...
+;
+MSG_PROJECT_MEN
+Progetto
+;Project
+;
+MSG_NEW_MEN
+Nuovo
+;New
+;
+MSG_OPEN_MEN
+Carica...
+;Open...
+;
+MSG_OPEN_NEW_MEN
+Carica Un Altro...
+;Open New...
+;
+MSG_SAVE_MEN
+Salva
+;Save
+;
+MSG_SAVE_AS_MEN
+Salva Con Nome...
+;Save As...
+;
+MSG_CLEAR_MEN
+Cancella...
+;Clear...
+;
+MSG_ABOUT_MEN
+Informazioni...
+;About...
+;
+MSG_HELP_MEN
+Aiuto...
+;Help...
+;
+MSG_ICONIFY_MEN
+Iconifica...
+;Iconify...
+;
+MSG_QUIT_MEN
+Fine
+;Quit
+;
+MSG_SETTINGS_MEN
+Opzioni
+;Settings
+;
+MSG_SAVE_FORMAT_MEN
+Formato Di Uscita...
+;Save Format...
+;
+MSG_USER_INTERFACE_MEN
+Interfaccia Utente...
+;User Interface...
+;
+MSG_SAVE_ICONS_MEN
+Genera Icone
+;Save Icons
+;
+MSG_CONFIRM_OVERWRITE_MEN
+Conferma Sovrascrittura
+;Confirm Overwrite
+;
+MSG_CONFIRM_EXIT_MEN
+Conferma Uscita
+;Confirm Exit
+;
+MSG_VERBOSE_MEN
+Rapporto Dettagliato
+;Verbose Log
+;
+MSG_OPEN_SETTINGS_MEN
+Carica Configurazione...
+;Open Settings...
+;
+MSG_SAVE_SETTINGS_MEN
+Salva Configurazione...
+;Save Settings
+;
+MSG_SAVE_SETTINGS_AS_MEN
+Salva Configurazione Con Nome...
+;Save Settings As...
+;
+;
+MSG_ABOUT_TEXT
+%s %s\n\
+Programma Di Elaborarazione Moduli Musicali\n\n\
+\
+%s\n\
+Tutti i diritti riservati.\n\n\
+\
+Internet: bernie@shock.cosmos.it\n\n\
+FidoNet:  2:332/125.1\n\
+\
+Memoria CHIP Libera: %ldKB\n\
+Memoria FAST Libera: %ldKB\n\n\
+Schermo Pubblico: %s\n\
+Porta ARexx: %s\n\
+Cx HotKey: %s\n\
+Lingua: %s
+;%s\n\
+;A Music Module Processing Utility\n\n\
+;\
+;%s\n\
+;All rights reserved.\n\n\
+;\
+;Internet: bernie@shock.cosmos.it\n\n\
+;FidoNet:  2:332/125.1\n\
+;\
+;Free CHIP Memory: %ldKB\n\
+;Free FAST Memory: %ldKB\n\n\
+;Public Screen: %s\n\
+;ARexx Port: %s\n\
+;Cx HotKey: %s\n\
+;Language: %s
+;
+MSG_DEFAULT
+--Default--
+;-- Default --
+;
+MSG_DISABLED
+--Disabilitato--
+;-- Disabled --
+;
+;
+; *********************************
+; * Strings for OptimizationWin.c *
+; *********************************
+;
+MSG_OPTIMIZATION_TITLE
+Ottimizzazione Modulo
+;Module Optimization
+;
+;
+MSG_OPTIMIZE_GAD
+_Ottimizzare
+;_Optimize
+;
+MSG_REM_UNUSED_PATTS_GAD
+Rimuovi _Pattern Non Usati
+;Remove Unused _Patterns
+;
+MSG_REM_DUPLICATE_PATTS_GAD
+Rimuovi Patterns _Doppi
+;Remove _Duplicate Patterns
+;
+MSG_REM_UNUSED_INSTR_GAD
+Rimuovi _Strumenti Non Usati
+;Remove Unused _Instruments
+;
+MSG_REM_DUP_INSTR_GAD
+
+;Remove Duplicate I_nstruments
+;
+MSG_CUT_AFTER_LOOP_GAD
+Taglia Strumenti Dopo Il Loop
+;Cut Instruments After _Loop
+;
+MSG_CUT_ZERO_TAILS_GAD
+Taglia Code di _Zeri Degli Strumenti
+;Cut Instrument _Zero Tails
+;
+MSG_CUT_PATTERNS_GAD
+
+;Cut _Breaked Patterns
+;
+MSG_REMAP_INSTRUMENTS_GAD
+
+;_Remap Instruments
+;
+;
+MSG_SAVED_X_BYTES
+Guadagnati %ld bytes (%ld%%)
+;Saved %ld bytes (%ld%%)
+;
+;
+; **************************
+; * Strings for PrefsWin.c *
+; **************************
+;
+;
+MSG_PREFS_TITLE
+Settaggi dell'interfaccia utente
+;User Interface Settings
+;
+;
+MSG_PUBLIC_SCREEN_GAD
+_Schermo Pubblico
+;_Public Screen
+;
+MSG_DISPLAY_MODE_GAD
+_Modo Video
+;Display _Mode
+;
+MSG_WINDOW_FONT_GAD
+Font della _Finestra
+;_Window Font
+;
+MSG_LISTVIEW_FONT_GAD
+
+;_ListView Font
+;
+MSG_REQUESTERS_GAD
+_Requesters
+;_Requesters
+;
+MSG_USE_DATATYPES_GAD
+
+;Use _DataTypes
+;
+MSG_APPICON_GAD
+Metti l'App_Icon
+;Put App_Icon
+;
+MSG_REFRESH_GAD
+
+;Refres_h
+;
+MSG_LOG_TO_FILE_GAD
+
+;Log To File
+;
+MSG_LOG_LEVEL_GAD
+
+;_Log Level
+;
+MSG_ASK_AUTOSAVE_GAD
+
+;Confirm _Autosave
+;
+MSG_AUTOSAVE_TIME_GAD
+
+;Autosave Mi_nutes
+;
+MSG_DO_BACKUPS_GAD
+
+;Create _Backups
+;
+MSG_BACKUP_TEMPLATE_GAD
+
+;Bac_kup Template
+;
+MSG_BACKUP_VERSIONS_GAD
+
+;Backup _Versions
+;
+MSG_ASL_GAD
+Asl
+;Asl
+;
+MSG_REQTOOLS_GAD
+ReqTools
+;ReqTools
+;
+MSG_SMART_GAD
+Smart
+;Smart
+;
+MSG_SIMPLE_GAD
+Simple
+;Simple
+;
+MSG_CLONE_DEF_SCREEN
+
+;--Clone Default Screen--
+;
+;
+; ****************************
+; * Strings for PatternWin.c *
+; ****************************
+;
+MSG_PATTERN_TITLE
+Editor Pattern
+;Pattern Editor
+;
+;
+MSG_PATTERNS_MEN
+Patterns
+;Patterns
+;
+MSG_SIZE_MEN
+Dimensione...
+;Size...
+;
+MSG_EDIT_MEN
+Editor
+;Edit
+;
+MSG_MARK_MEN
+Marca
+;Mark
+;
+MSG_CUT_MEN
+Taglia
+;Cut
+;
+MSG_COPY_MEN
+Copia
+;Copy
+;
+MSG_PASTE_MEN
+Incolla
+;Paste
+;
+MSG_ERASE_MEN
+Cancella
+;Erase
+;
+MSG_UNDO_MEN
+Undo
+;Undo
+;
+MSG_REDO_MEN
+Redo
+;Redo
+;
+MSG_EDITOR_SETTINGS_MEN
+Opzioni Editor...
+;Editor Settings...
+;
+;
+; ***************************
+; * Strings for SaversWin.c *
+; ***************************
+;
+MSG_SAVERS_TITLE
+
+;Savers
+;
+;
+MSG_SF_SEQUENCE_GAD
+
+;Se_quence
+;
+MSG_SF_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_SF_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_SF_NAMES_GAD
+
+;Names
+;
+MSG_ADD_ICON_GAD
+
+;Add I_con
+;
+MSG_MODE_GAD
+
+;_Mode
+;
+MSG_OPTIONS_DOTS_GAD
+
+;Options...
+;
+MSG_NONE_GAD
+
+;None
+;
+MSG_XPK_GAD
+
+;XPK
+;
+MSG_LHA_GAD
+
+;LhA
+;
+;
+MSG_DESCRIPTION
+
+;Description
+;
+MSG_AUTHOR
+
+;Author
+;
+MSG_MAXLENGTH
+
+;Max Length
+;
+MSG_MAXTRACKS
+
+;Max Tracks
+;
+MSG_MAXINSTRUMENTS
+
+;Max Instruments
+;
+MSG_MAXPATTERNS
+
+;Max Patterns
+;
+MSG_MAXPATTLEN
+
+;Max Pattern Length
+;
+MSG_MAXSAMPLELEN
+
+;Max Sample Length
+;
+;
+; **********************
+; * Strings ClearWin.c *
+; **********************
+;
+MSG_CLEAR_TITLE
+Cancellazione Modulo
+;Clear Module
+;
+;
+MSG_CLR_SEQUENCE_GAD
+
+;_Sequence
+;
+MSG_CLR_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_CLR_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_CLEARMOD_GAD
+
+;_Clear
+;
+;
+; ***************************
+; * Strings for SampleWin.c *
+; ***************************
+;
+MSG_SAMPLE_TITLE
+Editor Campionamenti
+;Sample Editor
+;
+;
+MSG_RENDER_MEN
+Grafico
+;Render
+;
+MSG_POINTS_MEN
+A Punti
+;Points
+;
+MSG_LINES_MEN
+A Linee
+;Lines
+;
+MSG_FILLED_MEN
+Riempito
+;Filled
+;
+;
+; *****************************
+; * Strings for PattSizeWin.c *
+; *****************************
+;
+MSG_PATTSIZE_TITLE
+Attributi Pattern
+;Pattern Attributes
+;
+;
+MSG_LINES_GAD
+
+;_Lines
+;
+MSG_TRACKS_GAD
+
+;_Tracks
+;
+MSG_DOUBLE_GAD
+
+;_Double
+;
+MSG_HALVE_GAD
+
+;_Halve
+;
+;
+; *************************
+; * Strings for Library.c *
+; *************************
+;
+MSG_SONG_TOO_MANY_TRACKS
+
+;WARNING: The selected saver supports max %lu tracks, but the song has %lu tracks.
+;
+MSG_SONG_TOO_MANY_PATTS
+
+;WARNING: The selected saver supports max %lu patterns; the song has %lu patterns.
+;
+MSG_SONG_TOO_MANY_INSTRS
+
+;WARNING: The selected saver supports max %lu instruments, last instrument is %lu.
+;
+MSG_SONG_TOO_MANY_POS
+
+;WARNING: The selected saver supports max %lu positions, but the song has %lu positions.
+;
+MSG_PATT_LENGTH_INVALID
+
+;WARNING: The length of pattern %ld ($%lx lines) isn't allowed with the selected saver.
+;
+MSG_INSTR_TOO_LONG
+ATTENZIONE: Lo strumento %lx è troppo lungo.
+;WARNING: Instrument %lx is too long and will be cropped to %lu bytes.
+;
+MSG_WILL_MODIFY_SONG
+
+;Some modifications need to be performed on this song\n\
+;in order to adapt it to the limitations of the\n\
+;destination format.
+;
+MSG_TRY_REMAPPING_INSTRUMENTS
+
+;Some instruments are placed beyond the limit for\n\
+;the selected format.\n\
+;Remapping the instruments now could help saving\n\
+;all them along with the selected format.
+;
+MSG_SAVING_MODULE
+
+;Saving %s module \"%s\"...
+; the first %s is the format name, the second %s is the song name.
diff --git a/Catalogs/nederlands.ct b/Catalogs/nederlands.ct
new file mode 100644 (file)
index 0000000..08f4be2
--- /dev/null
@@ -0,0 +1,1479 @@
+## version $VER: Nederlands.ct 1.0 (15.12.95)
+## language nederlands
+## codeset 0
+;
+## chunk AUTH Dutch catalog translation by Ji Yong Dijkhuis <0dijkhuis01@lelystad.flnet.nl>
+;**
+;** XModule.cd 3.9
+;**
+;** Copyright (C) 1995,96 Bernardo Innocenti
+;**
+;
+; **********************************
+; * Strings for all source modules *
+; **********************************
+;
+;
+MSG_NULL
+
+;
+; This string should remain blank.
+;
+MSG_OK
+Ok
+;Ok
+;
+MSG_UNDERSCORE_USE_GAD
+
+;_Use
+;
+MSG_UNDERSCORE_OK_GAD
+
+;_Ok
+;
+MSG_UNDERSCORE_CANCEL_GAD
+
+;_Cancel
+;
+MSG_YES_OR_NO
+Ja|Nee
+;Yes|No
+;
+MSG_RETRY_OR_CANCEL
+Nogmaals|Annuleer
+;Retry|Cancel
+;
+MSG_PROCEED_OR_CANCEL
+
+;Proceed|Cancel
+;
+MSG_CONTINUE
+Doorgaan
+;Continue
+;
+MSG_NO_FREE_STORE
+Niet genoeg geheugen
+;Insufficient memory.
+;
+MSG_BREAK
+Afgebroken
+;Aborted.
+;
+MSG_ERR_LOAD
+Kan \"%s\" niet laden: %s.
+;Unable to load \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_CANT_OPEN
+Kan \"%s\" niet openen: %s.
+;Cannot open file \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_READING
+Fout bij lezen \"%s\": %s.
+;Error reading \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_WRITING
+Fout bij bewaren \"%s\": %s.
+;Error writing \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+;
+; *****************************
+; * Strings for Compression.c *
+; *****************************
+;
+MSG_DECRUNCHING
+Uitpakken...
+;Decrunching...
+;
+MSG_NOTHING_IN_ARC
+Niets gevonden in archief \"%s\".
+;Nothing found in archive \"%s\".
+;
+MSG_CANT_LOAD_COMPRESSED
+Niet mogelijk om gecomprimeerd bestand te laden.
+;Unable to load compressed file.
+;
+MSG_ERROR_DECOMPRESSING
+Fout bij uitpakken bestand \"%s\": %s
+;Error decompressing file \"%s\": %s.
+;
+;
+; ********************
+; * Strings for Cx.c *
+; ********************
+;
+MSG_BAD_HOTKEY
+Slechte Commodity Hotkey beschrijving.
+;Bad Commodity HotKey description.
+;
+;
+; ***************************
+; * Strings for all loaders *
+; ***************************
+;
+MSG_READING_PATTS
+Lezen Patronen...
+;Reading Patterns...
+;
+MSG_READING_INSTS_INFO
+Lezen Instrumenten Info...
+;Reading Instruments Info...
+;
+MSG_READING_INSTS
+Lezen Instrumenten...
+;Reading Instruments...
+;
+MSG_ERR_CANT_LOAD_PATT
+FOUT: Kon patroon %ld niet laden.
+;ERROR: Couldn't load pattern %ld.
+;
+MSG_ERR_CANT_LOAD_INST
+FOUT: Kon instrument %lx niet laden.
+;ERROR: Couldn't load instrument %lx.
+;
+MSG_ERR_NO_MEM_FOR_INST
+FOUT: Niet genoeg geheugen voor instrument %lx.
+;ERROR: Not enough memory for instrument %lx.
+;
+MSG_ERR_NOT_A_SAMPLE
+FOUT: Instrument %lx is geen sample.
+;ERROR: Instrument %lx is not a sample.
+;
+MSG_SONG_TOO_LONG
+WAASCHUWING: Song lengte overschrijdt maximum. Zal worden ingekort.
+;WARNING: Song length exceeds maximum. Will be truncated.
+;
+MSG_SONG_HAS_TOO_MANY_PATT
+WAARSCHUWING: Song overschrijdt maximaal aantal patronen.
+;WARNING: Song exceeds maximum number of patterns.
+;
+MSG_PATT_TOO_MANY_TRACKS
+WAARSCHUWING: Patroon %ld heeft te veel sporen. Afkappen naar %ld 
+;WARNING: Pattern %ld has too many tracks. Cropping to %ld tracks.
+;
+MSG_PATT_TOO_MANY_LINES
+Patroon %ld heeft te veel lijnen. Afkappen naar %ld lijnen.
+;WARNING: Pattern %ld has too many lines. Cropping to %ld lines.
+;
+MSG_INVALID_NOTE
+WAARSCHUWING: Ongeldige noot %ld (Pat %ld Spoor %ld Lijn %ld).
+;WARNING: Invalid note %ld (Patt %ld Track %ld Line %ld).
+;
+MSG_UNKNOWN_EFF
+Onbekend effect: $%lx (Pat %ld Spoor %ld Lijn %ld).
+;Unknown effect: $%lx (Patt %ld Track %ld Line %ld).
+;
+MSG_EXTRA_DATA_AFTER_MOD
+WAARSCHUWING: Extra data gevonden achter module: Zal worden 
+;WARNING: Extra data found after valid module: Will be ignored.
+;
+MSG_WRITING_HEADER
+Bewaren Kop...
+;Writing Header...
+;
+MSG_WRITING_PATTS
+Bewaren Patroon...
+;Writing Patterns...
+;
+MSG_WRITING_INSTS
+Bewaren Instrumenten...
+;Writing Instruments...
+;
+MSG_WRITING_INSTINFO
+Bewaren Instrumenten Info...
+;Writing Instruments Info...
+;
+MSG_WRITING_INSTDATA
+Bewaren Instrumenten Data...
+;Writing Instruments Data...
+;
+MSG_NOTE_TOO_LOW
+WAARSCHUWING: Noot op Pat %ld Spoor %ld Lijn %ld is te laag.
+;WARNING: Note at Patt %ld Track %ld Line %ld is too low.
+;
+MSG_NOTE_TOO_HIGH
+WAARSCHUWING: Noot op Pat %ld Spoor %ld Lijn %ld is te hoog.
+;WARNING: Note at Patt %ld Track %ld Line %ld is too high.
+;
+MSG_NO_MEM_TO_HALVE
+WAARSCHUWING: Niet genoeg geheugen om volume van instrument %lx te 
+;WARNING: Not enough memory to halve volume of instrument %lx.
+;
+;
+; *****************************
+; * Strings for Get/SaveMED.c *
+; *****************************
+;
+MSG_READING_MMD
+Laden MMD%lc module...
+;Loading MMD%lc module...
+; The %lc is the MED format (0, 1, 2, ...)
+;
+MSG_UNSUPPORTED_MMD_FORMAT
+FOUT: Geen ondersteund OctaMED formaat.
+;ERROR: Unsupported OctaMED format.
+;
+MSG_WRONG_EFFECT_IN_MMD0
+WAARSCHUWING: Effect %lx wordt niet ondersteund in MMD0 formaat. 
+;WARNING: Effect %lx is not supported in MMD0 format. Use MMD1 or better.
+;
+;
+; ***********************************
+; * Strings for ScreamTrackerHook.c *
+; ***********************************
+;
+MSG_ADLIB_INSTR
+FOUT: Instrument %lx is een ADLib %s.
+;ERROR: Instrument %lx is an ADLib %s.
+; %s is the name of the ADLib instrument (eg: Snare).
+;
+MSG_TRACK_OUT_OF_RANGE
+WAARSCHUWING: Spoor %lx is buiten bereik.
+;WARNING: Track %lx is out of range.
+;
+MSG_UNKNOWN_SAMPLE_COMPRESSION
+WAARSCHUWING: Onbekende sample compressie voor instrument %lx.
+;WARNING: Unknown sample compression for instrument %lx.
+;
+MSG_INST_IS_STEREO
+WAARSCHUWING: Instrument %lx is een stereo sample.
+;WARNING: Instrument %lx is a stereo sample.
+;
+MSG_INST_IS_16BIT
+WAARSCHUWING: Instrument %lx is 16bit: onmogelijk om te laden.
+;WARNING: Instrument %lx is 16bit: unable to load it.
+;
+;
+; *****************************
+; * Strings for TrackerHook.c *
+; *****************************
+;
+MSG_READING_TYPE_MODULE
+Lezen %s module
+;Reading %s module...
+; (%s is the module format)
+;
+MSG_EXCEEDS_64_PATTS
+WAARSCHUWING: Module overschrijdt 64 patronen. ProTracker 2.3 benodigd om module te spelen.
+;NOTE: Module exceeds 64 patterns. You need ProTracker 2.3 to play it.
+;
+MSG_MODULE_ID
+
+;Tracker ID: \"%s\"
+;
+MSG_MODULE_HAS_N_CHN
+
+;Module has %lu tracks...
+;
+MSG_SONG_HAS_NO_INSTS
+Dit bestand is een song en heeft geen instrumenten.
+;This file is a song and doesn't contain instruments.
+;
+MSG_EXCEEDS_MAXPAATTS
+WAARSCHUWING: Module overschreidt %ld patronen.
+;WARNING: Module execeeds %ld patterns.
+;
+MSG_PATT_WILL_GROW
+Patroon %ld wordt verlengd naar 64 lijnen (was %ld lijnen lang).
+;Pattern %ld will grow to 64 lines (was %ld lines long).
+;
+MSG_SPLITTING_PATT
+Splitsen patroon %ld (was %ld lijnen lang).
+;Splitting pattern %ld (was %ld lines long).
+;
+; **************************
+; * Strings for SaveMIDI.c *
+; **************************
+;
+MSG_CHOOSING_CHANNELS
+Kiezen Kanalen...
+;Choosing Channels...
+;
+MSG_WRITING_MIDI_TRACKS
+Bewaren MIDI Sporen...
+;Writing MIDI Tracks...
+;
+MSG_TOO_MANY_CHANNELS
+FOUT: Song heeft te veel MIDI kanalen nodig.
+;ERROR: Song requires too many MIDI channels.
+;
+;
+; *********************
+; * Strings for Gui.c *
+; *********************
+;
+MSG_REALLY_QUIT_XMODULE
+Wekelijke beeindigen XModule?
+;Really Quit XModule?
+;
+MSG_CLOSE_ALL_WINDOWS
+Sluit alle vreemde vensters\n\
+a.u.b en selecteer dan `Doorgaan'.
+;Please close all visitor windows\n\
+;and then select `Continue'.
+;
+;
+; ***********************
+; * Strings for Instr.c *
+; ***********************
+;
+MSG_UNKNOWN_IFF
+Onbekend IFF formaat %s.
+;Unknown IFF format %s.
+;
+MSG_ILLEGAL_IFF_STRUCTURE
+Foutieve IFF structuur.
+;Illegal IFF structure.
+;
+MSG_SELECT_RAW_MODE
+Onbekend instrument formaat.\n\
+;Unrecognized instrument format.\n\
+;Please select RAW mode.
+;Unrecognized instrument format.\n\
+;Please select RAW mode.
+;
+MSG_RAW_MODES
+Signed 8bit|Unsigned 8bit|Annuleer
+;Signed 8bit|Unsigned 8bit|Cancel
+;
+MSG_DATATYPES_ERROR
+DataTypes fout: %s.
+;DataTypes error: %s.
+; %s is a detailed description of the problem.
+;
+MSG_UNKNOWN_COMPRESSION
+Onbekend compressie type.
+;Unknown compression type.
+;
+MSG_SAMPLE_WRONG_SIZE
+%lu bit samples worden niet ondersteund.
+;%lu bit samples are not supported.
+; %lu is the number of bits per sample.
+;
+MSG_SAMPLE_NOT_MONO
+Samples anders dan MONO worden niet ondersteund.
+;Samples other than MONO are not supported.
+;
+MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS
+Samples met %ld kanalen worden niet ondersteund.
+;Samples with %ld channels are not supported.
+;
+MSG_NO_MEMORY_TO_OPTIMIZE_INSTR
+WAARSCHUWING: onvoldoende geheugen om instrument %lx te optimalizeren.
+;WARNING: insufficient memory to optimize instrument %lx.
+;
+MSG_INSTR_WILL_SHRINK
+Instrument %lx zal krimpen van %ld naar %ld.
+;Instrument %lx will shrink from %ld to %ld.
+;
+MSG_INSTR_DUPES_REMOVED
+Dubbele instrumenten gevonden en verwijderd: %lx == %lx.
+;Duplicate instruments found and removed: %lx == %lx.
+;
+MSG_INSTR_UNUSED
+Instrument %lx is niet gebruikt en wordt verwijderd.
+;Instrument %lx was never used and it's being removed.
+;
+;
+; **********************
+; * Strings for Misc.c *
+; **********************
+;
+MSG_OPENLIB_VER_FAIL
+
+;Couldn't open \"%s\" version %ld or greater.
+;
+MSG_OPENLIB_FAIL
+Kon %s versie %ld of hoger niet openen.
+;Couldn't open \"%s\".
+;
+;
+; ***************************
+; * Strings for Operators.c *
+; ***************************
+;
+MSG_CANT_INSERT_PATT
+Invoegen patroon niet mogelijk: Maximaal aantal patronen bereikt.
+;Unable to insert pattern: Maximum number of patterns reached.
+;
+MSG_PATT_UNUSED
+Patroon %ld is niet gebruikt en wordt verwijdert.
+;Pattern %ld is not used and is beeing deleted.
+;
+MSG_PATT_CUT
+Patroon %ld wordt afgekapt op lijn %ld.
+;Pattern %ld will be cut at line %ld.
+;
+MSG_PATT_DUPE
+Dubbele patronen gevonden en verwijderd: %ld == %ld.
+;Duplicate patterns found and removed: %ld == %ld.
+; The two %ld are the pattern numbers.
+;
+MSG_SONG_LEN_DIFFERENT
+WAARSCHUWING: Song lengtes zijn verschillend. De kortere wordt gebruikt.
+;WARNING: Song lengths are different. Using shorter one.
+;
+MSG_PATT_LEN_DIFFERENT
+WAARSCHUWING: Verschillende patroon lengtes op positie %ld. De kortere wordt gebruikt.
+;WARNING: Different pattern lengths at position %ld. Using shorter one.
+;
+MSG_ERR_INSTR_OVERFLOW
+
+;ERROR: Instruments overflow. Try remapping the instruments.
+;
+;
+; ***********************
+; * Strings for Prefs.c *
+; ***********************
+;
+MSG_BAD_PREFS_VERSION
+Foutieve versie van instellingen bestand.
+;Incorrect version of preferences file
+;
+;
+; ****************************
+; * Strings for Requesters.c *
+; ****************************
+;
+MSG_XMODULE_REQUEST
+XModule Aanvraag Venster
+;XModule Request
+; This is the title of requesters put out by XModule.
+;
+MSG_CLONE_WB
+Namaken Workbench Scherm
+;Clone Workbench Screen
+;
+MSG_CLOSE_FILEREQUESTER
+Bestandsaanvraag venster\n sluiten a.u.b en selecteer dan `Doorgaan'.
+;Please close FileRequester\n and then select `Continue'.
+;
+MSG_SELECT_MODULES
+Selecteer Module(s)...
+;Select Module(s)...
+;
+MSG_SELECT_INSTRUMENTS
+Selecteer Instrument(en)...
+;Select Instrument(s)...
+;
+MSG_SELECT_PATTERN
+Selecteer Patroon...
+;Select Pattern...
+;
+MSG_SAVE_MODULE
+Bewaren Module...
+;Save Module...
+;
+MSG_SAVE_INSTRUMENT
+Bewaren Instrument...
+;Save Instrument...
+;
+MSG_SAVE_PATTERN
+Bewaren Patroon...
+;Save Pattern...
+;
+MSG_FILE_EXISTS
+Bestand \"%s\"\n bestaat reeds.
+;File \"%s\"\nalready exists.
+;
+MSG_OVERWRITE
+Overschrijven|Kies Andere|Annuleer
+;Overwrite|Choose Another|Abort
+;
+;
+; **********************
+; * Strings for Song.c *
+; **********************
+;
+MSG_CLOSE_ALL_SONGS
+
+;Please close all open songs\n\
+;and then select `Continue'.
+;
+MSG_UNESPECTED_EOF
+Onverwacht einde van bestand.
+;Unespected end of file.
+;
+MSG_MODULE_LOADED_OK
+Module goed ingeladen.
+;Module loaded OK.
+;
+MSG_MODULE_SAVED_OK
+Module goed weggeschreven.
+;Module saved OK.
+;
+MSG_UNKNOWN_SAVE_FORMAT
+FOUT: Onbekend bewaar formaat.
+;ERROR: Unrecognized save format.
+;
+MSG_INVALID_LOOP_REMOVED
+Foutieve loop voor instrument %lx verwijderd.
+;Removed invalid loop for instrument %lx.
+;
+MSG_INVALID_LOOP_FIXED
+Foutieve loop voor instrument %lx hersteld.
+;Fixed invalid loop for instrument %lx.
+;
+MSG_SONG_HAS_NO_PATTS
+WAARSCHUWING: Song heeft geen patronen.
+;WARNING: Song has no patterns.
+;
+MSG_SONG_HAS_NO_SEQ
+WAARSCHUWING: Song heeft geen volgreeks.
+;WARNING: Song has no sequence.
+;
+MSG_INVALID_SONG_POS
+WAARSCHUWING: Song positie %ld refereert aan patroon %ld, deze bestaat niet.
+;WARNING: Song position %ld references pattern %ld, which doesn't exist.
+;
+MSG_UNKNOWN_MOD_FORMAT
+Kon module formaat niet identificeren.\n\
+(Een data bestand als mudule laden is onverstandig)
+;Unable to identify module format.\n\
+;(Loading a data file as a module is unwise)
+;
+MSG_SOUND_PRO_CANCEL
+Soundtracker 15|ProTracker|Annuleer
+;SoundTracker 15|ProTracker|Cancel
+;
+MSG_AUTHOR_UNKNOWN
+Onbekend
+;Unknown
+;
+MSG_SONG_UNTITLED
+Ongetiteld
+;Untitled
+;
+;
+; **********************
+; * Strings for Help.c *
+; **********************
+;
+MSG_AMIGAGUIDE_ERROR
+AmigaGuide fout: 
+;AmigaGuide error:
+; Note one blank space at the end of this line!
+;
+;
+; ***************************
+; * Strings for PattPrefs.c *
+; ***************************
+;
+MSG_PATTPREFS_TITLE
+
+;Pattern Editor Settings
+;
+;
+MSG_ADVANCE_TRACKS_GAD
+
+;Advance _Tracks
+;
+MSG_ADVANCE_LINES_GAD
+
+;Advance _Lines
+;
+MSG_MAX_UNDO_LEVELS_GAD
+
+;Max _Undo Levels
+;
+MSG_MAX_UNDO_MEM_GAD
+
+;Max Undo _Memory
+;
+MSG_CLIPBOARD_UNIT_GAD
+
+;Cli_pboard Unit
+;
+MSG_SCROLLER_POS_GAD
+
+;_Scroller Position
+;
+MSG_VERT_WRAP_GAD
+
+;_Vertical Wrap
+;
+MSG_HORIZ_WRAP_GAD
+
+;_Horizontal Wrap
+;
+MSG_HEX_LINE_NUMBERS_GAD
+
+;He_x Line Numbers
+;
+MSG_BLANK_ZERO_GAD
+
+;Blank _Zero Digits
+;
+MSG_BACKDROP_GAD
+
+;Backdrop _Window
+;
+MSG_HORIZ_SCROLLER_GAD
+
+;Ho_rizontal Scroller
+;
+MSG_DO_RULER_GAD
+
+;Display Cursor Ruler
+;
+MSG_DO_TINY_LINES_GAD
+
+;Display Tiny Lines
+;
+MSG_EDITOR_FONT_GAD
+
+;_Editor Font
+;
+MSG_BACKGROUND_PEN_GAD
+
+;Background
+;
+MSG_TEXT_PEN_GAD
+
+;T_ext
+;
+MSG_LINES_PEN_GAD
+
+;Li_nes
+;
+MSG_TINY_LINES_PEN_GAD
+
+;Tin_y Lines
+;
+MSG_RIGHT_GAD
+
+;Right
+;
+MSG_LEFT_GAD
+
+;Left
+;
+MSG_OFF_GAD
+
+;Off
+;
+MSG_PATT_TOO_LONG
+FOUT: Patroon zou het maximum aantal lijnen overschrijden.
+;ERROR: Pattern would exceed the maximum number of lines.
+;
+;
+; *****************************
+; * Strings for SequenceWin.c *
+; *****************************
+;
+;
+MSG_SEQUENCE_TITLE
+
+;Sequence Editor
+;
+;
+MSG_SEQUENCE_GAD
+
+;Sequence
+;
+MSG_UNDERSCORE_ADD_GAD
+
+;_Add
+;
+MSG_UP_GAD
+
+;Up
+;
+MSG_DOWN_GAD
+
+;Down
+;
+MSG_UNDERSCORE_NAME_GAD
+
+;_Name
+;
+MSG_PATTERNS_GAD
+
+;Patterns
+;
+MSG_UNDERSCORE_UP_GAD
+
+;_Up
+;
+MSG_UNDERSCORE_DOWN_GAD
+
+;_Down
+;
+MSG_UNDERSCORE_INS_GAD
+
+;_Ins
+;
+;
+MSG_UNNAMED
+
+;-- unnamed --
+;
+;
+; ********************************
+; * Strings for InstrumentsWin.c *
+; ********************************
+;
+MSG_INSTRUMENTS_TITLE
+
+;Instruments
+;
+;
+MSG_VOLUME_GAD
+
+;_Volume
+;
+MSG_FINETUNE_GAD
+
+;_FineTune
+;
+MSG_LENGTH_GAD
+
+;Length
+;
+MSG_KIND_GAD
+
+;_Kind
+;
+MSG_EDIT_DOTS_GAD
+
+;_Edit...
+;
+MSG_SAMPLE_GAD
+
+;Sample
+;
+MSG_SYNTH_GAD
+
+;Synth
+;
+MSG_HYBRID_GAD
+
+;Hybrid
+;
+;
+MSG_INSTRUMENTS_MEN
+
+;Instruments
+;
+MSG_LOAD_MEN
+
+;Load...
+;
+MSG_REMAP_MEN
+
+;Remap
+;
+MSG_SAVE_COMPRESSED_MEN
+
+;Save Compressed
+;
+MSG_SAVE_RAW_MEN
+
+;Save Raw
+;
+;
+MSG_EMPTY
+
+;-- empty --
+;
+;
+; *************************
+; * Strings for PlayWin.c *
+; *************************
+;
+MSG_PLAY_TITLE
+
+;Play
+;
+;
+MSG_VOL_GAD
+
+;_Vol
+;
+MSG_POS_GAD
+
+;Pos
+;
+MSG_TIME_GAD
+
+;Time
+;
+MSG_RST_GAD
+
+;_Rst
+; Stands for Reset/Restart
+;
+MSG_PLAYER_INIT_ERR
+Afspeler initialisatie fout: %ld.
+;Player initialization error: %ld.
+;
+;
+; *****************************
+; * Strings for ProgressWin.c *
+; *****************************
+;
+MSG_PROGRESS_TITLE
+
+;XModule is working...
+;
+MSG_LOG_TITLE
+
+;XModule Log
+;
+;
+MSG_UNDERSCORE_ABORT_GAD
+
+;_Abort
+;
+;
+MSG_PERCENT_DONE
+%ld van %ld (%ld%% gedaan)
+;%ld of %ld (%ld%% done)
+; This string appears inside the progress window gauge.
+; eg: "5 of 20 (25% done)".
+; Leave that double percent (%%) alone! :-)
+;
+;
+; *****************************
+; * Strings for SongInfoWin.c *
+; *****************************
+;
+MSG_SONGINFO_TITLE
+
+;Song Information
+;
+;
+MSG_UNDERSCORE_NEW_GAD
+
+;Ne_w
+;
+MSG_OPEN_GAD
+
+;_Open...
+;
+MSG_SAVE_GAD
+
+;_Save
+;
+MSG_DEL_GAD
+
+;Del
+;
+MSG_SONG_NAME_GAD
+
+;Song _Name
+;
+MSG_AUTHOR_NAME_GAD
+
+;_Author
+;
+MSG_DEF_TEMPO_GAD
+
+;_Tempo
+;
+MSG_DEF_SPEED_GAD
+
+;S_peed
+;
+MSG_RESTART_GAD
+
+;_Restart
+;
+MSG_LENGHT_GAD
+
+;Length
+;
+MSG_NUM_PATTS_GAD
+
+;Num Patterns
+;
+MSG_NUM_TRACKS_GAD
+
+;Tracks
+;
+MSG_TOT_MOD_SIZE_GAD
+
+;Total Module Size
+;
+MSG_TOT_INST_SIZE_GAD
+
+;Total Instruments Size
+;
+;
+MSG_SONG_MEN
+
+;Song
+;
+MSG_MERGE_SONGS_MEN
+
+;Merge Songs
+;
+MSG_JOIN_SONGS_MEN
+
+;Join Songs
+;
+;
+MSG_JOIN_REQUIRES_TWO_SONGS
+FOUT: Toevoegen vereist twee songs.
+;ERROR: Join requires two songs.
+;
+MSG_MERGE_REQUIRES_TWO_SONGS
+FOUT: Samenvoegen vereist twee songs.
+;ERROR: Merge requires two songs.
+;
+MSG_DISCARD_CURRENT_SONG
+Verwijderen huidige song?
+;Discard current song?
+;
+;
+; ****************************
+; * Strings for ToolBoxWin.c *
+; ****************************
+;
+MSG_TOOLBOX_TITLE
+
+;ToolBox
+;
+;
+MSG_PLAY_GAD
+
+;Play...
+;
+MSG_SONGS_GAD
+
+;_Songs...
+;
+MSG_PATTERNS_DOTS_GAD
+
+;_Patterns...
+;
+MSG_INSTRUMENTS_GAD
+
+;_Instruments...
+;
+MSG_SEQUENCE_DOTS_GAD
+
+;Se_quence...
+;
+MSG_OPTIMIZATION_GAD
+
+;_Optimization...
+;
+MSG_PROJECT_MEN
+
+;Project
+;
+MSG_NEW_MEN
+
+;New
+;
+MSG_OPEN_MEN
+
+;Open...
+;
+MSG_OPEN_NEW_MEN
+
+;Open New...
+;
+MSG_SAVE_MEN
+
+;Save
+;
+MSG_SAVE_AS_MEN
+
+;Save As...
+;
+MSG_CLEAR_MEN
+
+;Clear...
+;
+MSG_ABOUT_MEN
+
+;About...
+;
+MSG_HELP_MEN
+
+;Help...
+;
+MSG_ICONIFY_MEN
+
+;Iconify...
+;
+MSG_QUIT_MEN
+
+;Quit
+;
+MSG_SETTINGS_MEN
+
+;Settings
+;
+MSG_SAVE_FORMAT_MEN
+
+;Save Format...
+;
+MSG_USER_INTERFACE_MEN
+
+;User Interface...
+;
+MSG_SAVE_ICONS_MEN
+
+;Save Icons
+;
+MSG_CONFIRM_OVERWRITE_MEN
+
+;Confirm Overwrite
+;
+MSG_CONFIRM_EXIT_MEN
+
+;Confirm Exit
+;
+MSG_VERBOSE_MEN
+
+;Verbose Log
+;
+MSG_OPEN_SETTINGS_MEN
+
+;Open Settings...
+;
+MSG_SAVE_SETTINGS_MEN
+
+;Save Settings
+;
+MSG_SAVE_SETTINGS_AS_MEN
+
+;Save Settings As...
+;
+;
+MSG_ABOUT_TEXT
+%s %s\n\
+Een Muziek Module Bewerkings Utility\n\n
+;%s\n\
+;A Music Module Processing Utility\n\n\
+;\
+;%s\n\
+;All rights reserved.\n\n\
+;\
+;Internet: bernie@shock.cosmos.it\n\n\
+;FidoNet:  2:332/125.1\n\
+;\
+;Free CHIP Memory: %ldKB\n\
+;Free FAST Memory: %ldKB\n\n\
+;Public Screen: %s\n\
+;ARexx Port: %s\n\
+;Cx HotKey: %s\n\
+;Language: %s
+;
+MSG_DEFAULT
+--Standaard--
+;-- Default --
+;
+MSG_DISABLED
+--Uitgeschakeld--
+;-- Disabled --
+;
+;
+; *********************************
+; * Strings for OptimizationWin.c *
+; *********************************
+;
+MSG_OPTIMIZATION_TITLE
+
+;Module Optimization
+;
+;
+MSG_OPTIMIZE_GAD
+
+;_Optimize
+;
+MSG_REM_UNUSED_PATTS_GAD
+
+;Remove Unused _Patterns
+;
+MSG_REM_DUPLICATE_PATTS_GAD
+
+;Remove _Duplicate Patterns
+;
+MSG_REM_UNUSED_INSTR_GAD
+
+;Remove Unused _Instruments
+;
+MSG_REM_DUP_INSTR_GAD
+
+;Remove Duplicate I_nstruments
+;
+MSG_CUT_AFTER_LOOP_GAD
+
+;Cut Instruments After _Loop
+;
+MSG_CUT_ZERO_TAILS_GAD
+
+;Cut Instrument _Zero Tails
+;
+MSG_CUT_PATTERNS_GAD
+
+;Cut _Breaked Patterns
+;
+MSG_REMAP_INSTRUMENTS_GAD
+
+;_Remap Instruments
+;
+;
+MSG_SAVED_X_BYTES
+Bewaard %ld bytes (%ld%%)
+;Saved %ld bytes (%ld%%)
+;
+;
+; **************************
+; * Strings for PrefsWin.c *
+; **************************
+;
+;
+MSG_PREFS_TITLE
+
+;User Interface Settings
+;
+;
+MSG_PUBLIC_SCREEN_GAD
+
+;_Public Screen
+;
+MSG_DISPLAY_MODE_GAD
+
+;Display _Mode
+;
+MSG_WINDOW_FONT_GAD
+
+;_Window Font
+;
+MSG_LISTVIEW_FONT_GAD
+
+;_ListView Font
+;
+MSG_REQUESTERS_GAD
+
+;_Requesters
+;
+MSG_USE_DATATYPES_GAD
+
+;Use _DataTypes
+;
+MSG_APPICON_GAD
+
+;Put App_Icon
+;
+MSG_REFRESH_GAD
+
+;Refres_h
+;
+MSG_LOG_TO_FILE_GAD
+
+;Log To File
+;
+MSG_LOG_LEVEL_GAD
+
+;_Log Level
+;
+MSG_ASK_AUTOSAVE_GAD
+
+;Confirm _Autosave
+;
+MSG_AUTOSAVE_TIME_GAD
+
+;Autosave Mi_nutes
+;
+MSG_DO_BACKUPS_GAD
+
+;Create _Backups
+;
+MSG_BACKUP_TEMPLATE_GAD
+
+;Bac_kup Template
+;
+MSG_BACKUP_VERSIONS_GAD
+
+;Backup _Versions
+;
+MSG_ASL_GAD
+
+;Asl
+;
+MSG_REQTOOLS_GAD
+
+;ReqTools
+;
+MSG_SMART_GAD
+
+;Smart
+;
+MSG_SIMPLE_GAD
+
+;Simple
+;
+MSG_CLONE_DEF_SCREEN
+
+;--Clone Default Screen--
+;
+;
+; ****************************
+; * Strings for PatternWin.c *
+; ****************************
+;
+MSG_PATTERN_TITLE
+
+;Pattern Editor
+;
+;
+MSG_PATTERNS_MEN
+
+;Patterns
+;
+MSG_SIZE_MEN
+
+;Size...
+;
+MSG_EDIT_MEN
+
+;Edit
+;
+MSG_MARK_MEN
+
+;Mark
+;
+MSG_CUT_MEN
+
+;Cut
+;
+MSG_COPY_MEN
+
+;Copy
+;
+MSG_PASTE_MEN
+
+;Paste
+;
+MSG_ERASE_MEN
+
+;Erase
+;
+MSG_UNDO_MEN
+
+;Undo
+;
+MSG_REDO_MEN
+
+;Redo
+;
+MSG_EDITOR_SETTINGS_MEN
+
+;Editor Settings...
+;
+;
+; ***************************
+; * Strings for SaversWin.c *
+; ***************************
+;
+MSG_SAVERS_TITLE
+
+;Savers
+;
+;
+MSG_SF_SEQUENCE_GAD
+
+;Se_quence
+;
+MSG_SF_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_SF_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_SF_NAMES_GAD
+
+;Names
+;
+MSG_ADD_ICON_GAD
+
+;Add I_con
+;
+MSG_MODE_GAD
+
+;_Mode
+;
+MSG_OPTIONS_DOTS_GAD
+
+;Options...
+;
+MSG_NONE_GAD
+
+;None
+;
+MSG_XPK_GAD
+
+;XPK
+;
+MSG_LHA_GAD
+
+;LhA
+;
+;
+MSG_DESCRIPTION
+
+;Description
+;
+MSG_AUTHOR
+
+;Author
+;
+MSG_MAXLENGTH
+
+;Max Length
+;
+MSG_MAXTRACKS
+
+;Max Tracks
+;
+MSG_MAXINSTRUMENTS
+
+;Max Instruments
+;
+MSG_MAXPATTERNS
+
+;Max Patterns
+;
+MSG_MAXPATTLEN
+
+;Max Pattern Length
+;
+MSG_MAXSAMPLELEN
+
+;Max Sample Length
+;
+;
+; **********************
+; * Strings ClearWin.c *
+; **********************
+;
+MSG_CLEAR_TITLE
+
+;Clear Module
+;
+;
+MSG_CLR_SEQUENCE_GAD
+
+;_Sequence
+;
+MSG_CLR_INSTRUMENTS_GAD
+
+;_Instruments
+;
+MSG_CLR_PATTERNS_GAD
+
+;_Patterns
+;
+MSG_CLEARMOD_GAD
+
+;_Clear
+;
+;
+; ***************************
+; * Strings for SampleWin.c *
+; ***************************
+;
+MSG_SAMPLE_TITLE
+
+;Sample Editor
+;
+;
+MSG_RENDER_MEN
+
+;Render
+;
+MSG_POINTS_MEN
+
+;Points
+;
+MSG_LINES_MEN
+
+;Lines
+;
+MSG_FILLED_MEN
+
+;Filled
+;
+;
+; *****************************
+; * Strings for PattSizeWin.c *
+; *****************************
+;
+MSG_PATTSIZE_TITLE
+
+;Pattern Attributes
+;
+;
+MSG_LINES_GAD
+
+;_Lines
+;
+MSG_TRACKS_GAD
+
+;_Tracks
+;
+MSG_DOUBLE_GAD
+
+;_Double
+;
+MSG_HALVE_GAD
+
+;_Halve
+;
+;
+; *************************
+; * Strings for Library.c *
+; *************************
+;
+MSG_SONG_TOO_MANY_TRACKS
+
+;WARNING: The selected saver supports max %lu tracks, but the song has %lu tracks.
+;
+MSG_SONG_TOO_MANY_PATTS
+
+;WARNING: The selected saver supports max %lu patterns; the song has %lu patterns.
+;
+MSG_SONG_TOO_MANY_INSTRS
+
+;WARNING: The selected saver supports max %lu instruments, last instrument is %lu.
+;
+MSG_SONG_TOO_MANY_POS
+
+;WARNING: The selected saver supports max %lu positions, but the song has %lu positions.
+;
+MSG_PATT_LENGTH_INVALID
+
+;WARNING: The length of pattern %ld ($%lx lines) isn't allowed with the selected saver.
+;
+MSG_INSTR_TOO_LONG
+WAARSCHUWING: Instrument %lx is te lang.
+;WARNING: Instrument %lx is too long and will be cropped to %lu bytes.
+;
+MSG_WILL_MODIFY_SONG
+
+;Some modifications need to be performed on this song\n\
+;in order to adapt it to the limitations of the\n\
+;destination format.
+;
+MSG_TRY_REMAPPING_INSTRUMENTS
+
+;Some instruments are placed beyond the limit for\n\
+;the selected format.\n\
+;Remapping the instruments now could help saving\n\
+;all them along with the selected format.
+;
+MSG_SAVING_MODULE
+
+;Saving %s module \"%s\"...
+; the first %s is the format name, the second %s is the song name.
diff --git a/Catalogs/xmodule.cd b/Catalogs/xmodule.cd
new file mode 100644 (file)
index 0000000..3cdbdd9
--- /dev/null
@@ -0,0 +1,1160 @@
+#language english
+#version 1
+;**
+;** XModule.cd 3.9
+;**
+;** Copyright (C) 1995,96 Bernardo Innocenti
+;**
+;
+; **********************************
+; * Strings for all source modules *
+; **********************************
+;
+;
+MSG_NULL (0/0/0)
+
+; This string should remain blank.
+;
+MSG_OK (//)
+Ok
+;
+MSG_UNDERSCORE_USE_GAD (//)
+_Use
+;
+MSG_UNDERSCORE_OK_GAD (//)
+_Ok
+;
+MSG_UNDERSCORE_CANCEL_GAD (//)
+_Cancel
+;
+MSG_YES_OR_NO (//)
+Yes|No
+;
+MSG_RETRY_OR_CANCEL (//)
+Retry|Cancel
+;
+MSG_PROCEED_OR_CANCEL (//)
+Proceed|Cancel
+;
+MSG_CONTINUE (//)
+Continue
+;
+MSG_NO_FREE_STORE (//)
+Insufficient memory.
+;
+MSG_BREAK (//)
+Aborted.
+;
+MSG_ERR_LOAD (//)
+Unable to load \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_CANT_OPEN (//)
+Cannot open file \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_READING (//)
+Error reading \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+MSG_ERROR_WRITING (//)
+Error writing \"%s\"
+; %s is the filename. A colon ':' and the explanation of failure will be postpended.
+;
+;
+; *****************************
+; * Strings for Compression.c *
+; *****************************
+;
+MSG_DECRUNCHING (//)
+Decrunching...
+;
+MSG_NOTHING_IN_ARC (//)
+Nothing found in archive \"%s\".
+;
+MSG_CANT_LOAD_COMPRESSED (//)
+Unable to load compressed file.
+;
+MSG_ERROR_DECOMPRESSING (//)
+Error decompressing file \"%s\": %s.
+;
+;
+; ********************
+; * Strings for Cx.c *
+; ********************
+;
+MSG_BAD_HOTKEY (//)
+Bad Commodity HotKey description.
+;
+;
+; ***************************
+; * Strings for all loaders *
+; ***************************
+;
+MSG_READING_PATTS (//)
+Reading Patterns...
+;
+MSG_READING_INSTS_INFO (//)
+Reading Instruments Info...
+;
+MSG_READING_INSTS (//)
+Reading Instruments...
+;
+MSG_ERR_CANT_LOAD_PATT (//)
+ERROR: Couldn't load pattern %ld.
+;
+MSG_ERR_CANT_LOAD_INST (//)
+ERROR: Couldn't load instrument %lx.
+;
+MSG_ERR_NO_MEM_FOR_INST (//)
+ERROR: Not enough memory for instrument %lx.
+;
+MSG_ERR_NOT_A_SAMPLE (//)
+ERROR: Instrument %lx is not a sample.
+;
+MSG_SONG_TOO_LONG (//)
+WARNING: Song length exceeds maximum. Will be truncated.
+;
+MSG_SONG_HAS_TOO_MANY_PATT (//)
+WARNING: Song exceeds maximum number of patterns.
+;
+MSG_PATT_TOO_MANY_TRACKS (//)
+WARNING: Pattern %ld has too many tracks. Cropping to %ld tracks.
+;
+MSG_PATT_TOO_MANY_LINES (//)
+WARNING: Pattern %ld has too many lines. Cropping to %ld lines.
+;
+MSG_INVALID_NOTE (//)
+WARNING: Invalid note %ld (Patt %ld Track %ld Line %ld).
+;
+MSG_UNKNOWN_EFF (//)
+Unknown effect: $%lx (Patt %ld Track %ld Line %ld).
+;
+MSG_EXTRA_DATA_AFTER_MOD (//)
+WARNING: Extra data found after valid module: Will be ignored.
+;
+MSG_WRITING_HEADER (//)
+Writing Header...
+;
+MSG_WRITING_PATTS (//)
+Writing Patterns...
+;
+MSG_WRITING_INSTS (//)
+Writing Instruments...
+;
+MSG_WRITING_INSTINFO (//)
+Writing Instruments Info...
+;
+MSG_WRITING_INSTDATA (//)
+Writing Instruments Data...
+;
+MSG_NOTE_TOO_LOW (//)
+WARNING: Note at Patt %ld Track %ld Line %ld is too low.
+;
+MSG_NOTE_TOO_HIGH (//)
+WARNING: Note at Patt %ld Track %ld Line %ld is too high.
+;
+MSG_NO_MEM_TO_HALVE (//)
+WARNING: Not enough memory to halve volume of instrument %lx.
+;
+;
+; *****************************
+; * Strings for Get/SaveMED.c *
+; *****************************
+;
+MSG_READING_MMD (//)
+Loading MMD%lc module...
+; The %lc is the MED format (0, 1, 2, ...)
+;
+MSG_UNSUPPORTED_MMD_FORMAT (//)
+ERROR: Unsupported OctaMED format.
+;
+MSG_WRONG_EFFECT_IN_MMD0 (//)
+WARNING: Effect %lx is not supported in MMD0 format. Use MMD1 or better.
+;
+;
+; ***********************************
+; * Strings for ScreamTrackerHook.c *
+; ***********************************
+;
+MSG_ADLIB_INSTR (//)
+ERROR: Instrument %lx is an ADLib %s.
+; %s is the name of the ADLib instrument (eg: Snare).
+;
+MSG_TRACK_OUT_OF_RANGE (//)
+WARNING: Track %lx is out of range.
+;
+MSG_UNKNOWN_SAMPLE_COMPRESSION (//)
+WARNING: Unknown sample compression for instrument %lx.
+;
+MSG_INST_IS_STEREO (//)
+WARNING: Instrument %lx is a stereo sample.
+;
+MSG_INST_IS_16BIT (//)
+WARNING: Instrument %lx is 16bit: unable to load it.
+;
+;
+; *****************************
+; * Strings for TrackerHook.c *
+; *****************************
+;
+MSG_READING_TYPE_MODULE (//)
+Reading %s module...
+; (%s is the module format)
+;
+MSG_EXCEEDS_64_PATTS (//)
+NOTE: Module exceeds 64 patterns. You need ProTracker 2.3 to play it.
+;
+MSG_MODULE_ID (//)
+Tracker ID: \"%s\"
+;
+MSG_MODULE_HAS_N_CHN (//)
+Module has %lu tracks...
+;
+MSG_SONG_HAS_NO_INSTS (//)
+This file is a song and doesn't contain instruments.
+;
+MSG_EXCEEDS_MAXPAATTS (//)
+WARNING: Module execeeds %ld patterns.
+;
+MSG_PATT_WILL_GROW (//)
+Pattern %ld will grow to 64 lines (was %ld lines long).
+;
+MSG_SPLITTING_PATT (//)
+Splitting pattern %ld (was %ld lines long).
+;
+; **************************
+; * Strings for SaveMIDI.c *
+; **************************
+;
+MSG_CHOOSING_CHANNELS (//)
+Choosing Channels...
+;
+MSG_WRITING_MIDI_TRACKS (//)
+Writing MIDI Tracks...
+;
+MSG_TOO_MANY_CHANNELS (//)
+ERROR: Song requires too many MIDI channels.
+;
+;
+; *********************
+; * Strings for Gui.c *
+; *********************
+;
+MSG_REALLY_QUIT_XMODULE (//)
+Really Quit XModule?
+;
+MSG_CLOSE_ALL_WINDOWS (//)
+Please close all visitor windows\n\
+and then select `Continue'.
+;
+;
+; ***********************
+; * Strings for Instr.c *
+; ***********************
+;
+MSG_UNKNOWN_IFF (//)
+Unknown IFF format %s.
+;
+MSG_ILLEGAL_IFF_STRUCTURE (//)
+Illegal IFF structure.
+;
+MSG_SELECT_RAW_MODE (//)
+Unrecognized instrument format.\n\
+Please select RAW mode.
+;
+MSG_RAW_MODES (//)
+Signed 8bit|Unsigned 8bit|Cancel
+;
+MSG_DATATYPES_ERROR (//)
+DataTypes error: %s.
+; %s is a detailed description of the problem.
+;
+MSG_UNKNOWN_COMPRESSION (//)
+Unknown compression type.
+;
+MSG_SAMPLE_WRONG_SIZE (//)
+%lu bit samples are not supported.
+; %lu is the number of bits per sample.
+;
+MSG_SAMPLE_NOT_MONO (//)
+Samples other than MONO are not supported.
+;
+MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS (//)
+Samples with %ld channels are not supported.
+;
+MSG_NO_MEMORY_TO_OPTIMIZE_INSTR (//)
+WARNING: insufficient memory to optimize instrument %lx.
+;
+MSG_INSTR_WILL_SHRINK (//)
+Instrument %lx will shrink from %ld to %ld.
+;
+MSG_INSTR_DUPES_REMOVED (//)
+Duplicate instruments found and removed: %lx == %lx.
+;
+MSG_INSTR_UNUSED (//)
+Instrument %lx was never used and it's being removed.
+;
+;
+; **********************
+; * Strings for Misc.c *
+; **********************
+;
+MSG_OPENLIB_VER_FAIL (//)
+Couldn't open \"%s\" version %ld or greater.
+;
+MSG_OPENLIB_FAIL (//)
+Couldn't open \"%s\".
+;
+;
+; ***************************
+; * Strings for Operators.c *
+; ***************************
+;
+MSG_CANT_INSERT_PATT (//)
+Unable to insert pattern: Maximum number of patterns reached.
+;
+MSG_PATT_UNUSED (//)
+Pattern %ld is not used and is beeing deleted.
+;
+MSG_PATT_CUT (//)
+Pattern %ld will be cut at line %ld.
+;
+MSG_PATT_DUPE (//)
+Duplicate patterns found and removed: %ld == %ld.
+; The two %ld are the pattern numbers.
+;
+MSG_SONG_LEN_DIFFERENT (//)
+WARNING: Song lengths are different. Using shorter one.
+;
+MSG_PATT_LEN_DIFFERENT (//)
+WARNING: Different pattern lengths at position %ld. Using shorter one.
+;
+MSG_ERR_INSTR_OVERFLOW (//)
+ERROR: Instruments overflow. Try remapping the instruments.
+;
+;
+; ***********************
+; * Strings for Prefs.c *
+; ***********************
+;
+MSG_BAD_PREFS_VERSION (//)
+Incorrect version of preferences file
+;
+;
+; ****************************
+; * Strings for Requesters.c *
+; ****************************
+;
+MSG_XMODULE_REQUEST (//)
+XModule Request
+; This is the title of requesters put out by XModule.
+;
+MSG_CLONE_WB (//)
+Clone Workbench Screen
+;
+MSG_CLOSE_FILEREQUESTER (//)
+Please close FileRequester\n and then select `Continue'.
+;
+MSG_SELECT_MODULES (//)
+Select Module(s)...
+;
+MSG_SELECT_INSTRUMENTS (//)
+Select Instrument(s)...
+;
+MSG_SELECT_PATTERN (//)
+Select Pattern...
+;
+MSG_SAVE_MODULE (//)
+Save Module...
+;
+MSG_SAVE_INSTRUMENT (//)
+Save Instrument...
+;
+MSG_SAVE_PATTERN (//)
+Save Pattern...
+;
+MSG_FILE_EXISTS (//)
+File \"%s\"\nalready exists.
+;
+MSG_OVERWRITE (//)
+Overwrite|Choose Another|Abort
+;
+;
+; **********************
+; * Strings for Song.c *
+; **********************
+;
+MSG_CLOSE_ALL_SONGS (//)
+Please close all open songs\n\
+and then select `Continue'.
+;
+MSG_UNESPECTED_EOF (//)
+Unespected end of file.
+;
+MSG_MODULE_LOADED_OK (//)
+Module loaded OK.
+;
+MSG_MODULE_SAVED_OK (//)
+Module saved OK.
+;
+MSG_UNKNOWN_SAVE_FORMAT (//)
+ERROR: Unrecognized save format.
+;
+MSG_INVALID_LOOP_REMOVED (//)
+Removed invalid loop for instrument %lx.
+;
+MSG_INVALID_LOOP_FIXED (//)
+Fixed invalid loop for instrument %lx.
+;
+MSG_SONG_HAS_NO_PATTS (//)
+WARNING: Song has no patterns.
+;
+MSG_SONG_HAS_NO_SEQ (//)
+WARNING: Song has no sequence.
+;
+MSG_INVALID_SONG_POS (//)
+WARNING: Song position %ld references pattern %ld, which doesn't exist.
+;
+MSG_UNKNOWN_MOD_FORMAT (//)
+Unable to identify module format.\n\
+(Loading a data file as a module is unwise)
+;
+MSG_SOUND_PRO_CANCEL (//)
+SoundTracker 15|ProTracker|Cancel
+;
+MSG_AUTHOR_UNKNOWN (//)
+Unknown
+;
+MSG_SONG_UNTITLED (//)
+Untitled
+;
+;
+; **********************
+; * Strings for Help.c *
+; **********************
+;
+MSG_AMIGAGUIDE_ERROR (//)
+AmigaGuide error:
+; Note one blank space at the end of this line!
+;
+;
+; ***************************
+; * Strings for PattPrefs.c *
+; ***************************
+;
+MSG_PATTPREFS_TITLE (//)
+Pattern Editor Settings
+;
+;
+MSG_ADVANCE_TRACKS_GAD (//)
+Advance _Tracks
+;
+MSG_ADVANCE_LINES_GAD (//)
+Advance _Lines
+;
+MSG_MAX_UNDO_LEVELS_GAD (//)
+Max _Undo Levels
+;
+MSG_MAX_UNDO_MEM_GAD (//)
+Max Undo _Memory
+;
+MSG_CLIPBOARD_UNIT_GAD (//)
+Cli_pboard Unit
+;
+MSG_SCROLLER_POS_GAD (//)
+_Scroller Position
+;
+MSG_VERT_WRAP_GAD (//)
+_Vertical Wrap
+;
+MSG_HORIZ_WRAP_GAD (//)
+_Horizontal Wrap
+;
+MSG_HEX_LINE_NUMBERS_GAD (//)
+He_x Line Numbers
+;
+MSG_BLANK_ZERO_GAD (//)
+Blank _Zero Digits
+;
+MSG_BACKDROP_GAD (//)
+Backdrop _Window
+;
+MSG_HORIZ_SCROLLER_GAD (//)
+Ho_rizontal Scroller
+;
+MSG_DO_RULER_GAD (//)
+Display Cursor Ruler
+;
+MSG_DO_TINY_LINES_GAD (//)
+Display Tiny Lines
+;
+MSG_EDITOR_FONT_GAD (//)
+_Editor Font
+;
+MSG_BACKGROUND_PEN_GAD (//)
+Background
+;
+MSG_TEXT_PEN_GAD (//)
+T_ext
+;
+MSG_LINES_PEN_GAD (//)
+Li_nes
+;
+MSG_TINY_LINES_PEN_GAD (//)
+Tin_y Lines
+;
+MSG_RIGHT_GAD (//)
+Right
+;
+MSG_LEFT_GAD (//)
+Left
+;
+MSG_OFF_GAD (//)
+Off
+;
+MSG_PATT_TOO_LONG (//)
+ERROR: Pattern would exceed the maximum number of lines.
+;
+;
+; *****************************
+; * Strings for SequenceWin.c *
+; *****************************
+;
+;
+MSG_SEQUENCE_TITLE (//)
+Sequence Editor
+;
+;
+MSG_SEQUENCE_GAD (//)
+Sequence
+;
+MSG_UNDERSCORE_ADD_GAD (//)
+_Add
+;
+MSG_UP_GAD (//)
+Up
+;
+MSG_DOWN_GAD (//)
+Down
+;
+MSG_UNDERSCORE_NAME_GAD (//)
+_Name
+;
+MSG_PATTERNS_GAD (//)
+Patterns
+;
+MSG_UNDERSCORE_UP_GAD (//)
+_Up
+;
+MSG_UNDERSCORE_DOWN_GAD (//)
+_Down
+;
+MSG_UNDERSCORE_INS_GAD (//)
+_Ins
+;
+;
+MSG_UNNAMED (//)
+-- unnamed --
+;
+;
+; ********************************
+; * Strings for InstrumentsWin.c *
+; ********************************
+;
+MSG_INSTRUMENTS_TITLE (//)
+Instruments
+;
+;
+MSG_VOLUME_GAD (//)
+_Volume
+;
+MSG_FINETUNE_GAD (//)
+_FineTune
+;
+MSG_LENGTH_GAD (//)
+Length
+;
+MSG_KIND_GAD (//)
+_Kind
+;
+MSG_EDIT_DOTS_GAD (//)
+_Edit...
+;
+MSG_SAMPLE_GAD (//)
+Sample
+;
+MSG_SYNTH_GAD (//)
+Synth
+;
+MSG_HYBRID_GAD (//)
+Hybrid
+;
+;
+MSG_INSTRUMENTS_MEN (//)
+Instruments
+;
+MSG_LOAD_MEN (//)
+Load...
+;
+MSG_REMAP_MEN (//)
+Remap
+;
+MSG_SAVE_COMPRESSED_MEN (//)
+Save Compressed
+;
+MSG_SAVE_RAW_MEN (//)
+Save Raw
+;
+;
+MSG_EMPTY (//)
+-- empty --
+;
+;
+; *************************
+; * Strings for PlayWin.c *
+; *************************
+;
+MSG_PLAY_TITLE (//)
+Play
+;
+;
+MSG_VOL_GAD (//)
+_Vol
+;
+MSG_POS_GAD (//)
+Pos
+;
+MSG_TIME_GAD (//)
+Time
+;
+MSG_RST_GAD (//)
+_Rst
+; Stands for Reset/Restart
+;
+MSG_PLAYER_INIT_ERR (//)
+Player initialization error: %ld.
+;
+;
+; *****************************
+; * Strings for ProgressWin.c *
+; *****************************
+;
+MSG_PROGRESS_TITLE (//)
+XModule is working...
+;
+MSG_LOG_TITLE (//)
+XModule Log
+;
+;
+MSG_UNDERSCORE_ABORT_GAD (//)
+_Abort
+;
+;
+MSG_PERCENT_DONE (//30)
+%ld of %ld (%ld%% done)
+; This string appears inside the progress window gauge.
+; eg: "5 of 20 (25% done)".
+; Leave that double percent (%%) alone! :-)
+;
+;
+; *****************************
+; * Strings for SongInfoWin.c *
+; *****************************
+;
+MSG_SONGINFO_TITLE (//)
+Song Information
+;
+;
+MSG_UNDERSCORE_NEW_GAD (//)
+Ne_w
+;
+MSG_OPEN_GAD (//)
+_Open...
+;
+MSG_SAVE_GAD (//)
+_Save
+;
+MSG_DEL_GAD (//)
+Del
+;
+MSG_SONG_NAME_GAD (//)
+Song _Name
+;
+MSG_AUTHOR_NAME_GAD (//)
+_Author
+;
+MSG_DEF_TEMPO_GAD (//)
+_Tempo
+;
+MSG_DEF_SPEED_GAD (//)
+S_peed
+;
+MSG_RESTART_GAD (//)
+_Restart
+;
+MSG_LENGHT_GAD (//)
+Length
+;
+MSG_NUM_PATTS_GAD (//)
+Num Patterns
+;
+MSG_NUM_TRACKS_GAD (//)
+Tracks
+;
+MSG_TOT_MOD_SIZE_GAD (//)
+Total Module Size
+;
+MSG_TOT_INST_SIZE_GAD (//)
+Total Instruments Size
+;
+;
+MSG_SONG_MEN (//)
+Song
+;
+MSG_MERGE_SONGS_MEN (//)
+Merge Songs
+;
+MSG_JOIN_SONGS_MEN (//)
+Join Songs
+;
+;
+MSG_JOIN_REQUIRES_TWO_SONGS (//)
+ERROR: Join requires two songs.
+;
+MSG_MERGE_REQUIRES_TWO_SONGS (//)
+ERROR: Merge requires two songs.
+;
+MSG_DISCARD_CURRENT_SONG (//)
+Discard current song?
+;
+;
+; ****************************
+; * Strings for ToolBoxWin.c *
+; ****************************
+;
+MSG_TOOLBOX_TITLE (//)
+ToolBox
+;
+;
+MSG_PLAY_GAD (//)
+Play...
+;
+MSG_SONGS_GAD (//)
+_Songs...
+;
+MSG_PATTERNS_DOTS_GAD (//)
+_Patterns...
+;
+MSG_INSTRUMENTS_GAD (//)
+_Instruments...
+;
+MSG_SEQUENCE_DOTS_GAD (//)
+Se_quence...
+;
+MSG_OPTIMIZATION_GAD (//)
+_Optimization...
+;
+MSG_PROJECT_MEN (//)
+Project
+;
+MSG_NEW_MEN (//)
+New
+;
+MSG_OPEN_MEN (//)
+Open...
+;
+MSG_OPEN_NEW_MEN (//)
+Open New...
+;
+MSG_SAVE_MEN (//)
+Save
+;
+MSG_SAVE_AS_MEN (//)
+Save As...
+;
+MSG_CLEAR_MEN (//)
+Clear...
+;
+MSG_ABOUT_MEN (//)
+About...
+;
+MSG_HELP_MEN (//)
+Help...
+;
+MSG_ICONIFY_MEN (//)
+Iconify...
+;
+MSG_QUIT_MEN (//)
+Quit
+;
+MSG_SETTINGS_MEN (//)
+Settings
+;
+MSG_SAVE_FORMAT_MEN (//)
+Save Format...
+;
+MSG_USER_INTERFACE_MEN (//)
+User Interface...
+;
+MSG_SAVE_ICONS_MEN (//)
+Save Icons
+;
+MSG_CONFIRM_OVERWRITE_MEN (//)
+Confirm Overwrite
+;
+MSG_CONFIRM_EXIT_MEN (//)
+Confirm Exit
+;
+MSG_VERBOSE_MEN (//)
+Verbose Log
+;
+MSG_OPEN_SETTINGS_MEN (//)
+Open Settings...
+;
+MSG_SAVE_SETTINGS_MEN (//)
+Save Settings
+;
+MSG_SAVE_SETTINGS_AS_MEN (//)
+Save Settings As...
+;
+;
+MSG_ABOUT_TEXT (//)
+%s\n\
+A Music Module Processing Utility\n\n\
+\
+%s\n\
+All rights reserved.\n\n\
+\
+Internet: bernie@shock.cosmos.it\n\n\
+FidoNet:  2:332/118.4\n\
+\
+Free CHIP Memory: %ldKB\n\
+Free FAST Memory: %ldKB\n\n\
+Public Screen: %s\n\
+ARexx Port: %s\n\
+Cx HotKey: %s\n\
+Language: %s
+;
+MSG_DEFAULT (//)
+-- Default --
+;
+MSG_DISABLED (//)
+-- Disabled --
+;
+;
+; *********************************
+; * Strings for OptimizationWin.c *
+; *********************************
+;
+MSG_OPTIMIZATION_TITLE (//)
+Module Optimization
+;
+;
+MSG_OPTIMIZE_GAD (//)
+_Optimize
+;
+MSG_REM_UNUSED_PATTS_GAD (//)
+Remove Unused _Patterns
+;
+MSG_REM_DUPLICATE_PATTS_GAD (//)
+Remove _Duplicate Patterns
+;
+MSG_REM_UNUSED_INSTR_GAD (//)
+Remove Unused _Instruments
+;
+MSG_REM_DUP_INSTR_GAD (//)
+Remove Duplicate I_nstruments
+;
+MSG_CUT_AFTER_LOOP_GAD (//)
+Cut Instruments After _Loop
+;
+MSG_CUT_ZERO_TAILS_GAD (//)
+Cut Instrument _Zero Tails
+;
+MSG_CUT_PATTERNS_GAD (//)
+Cut _Breaked Patterns
+;
+MSG_REMAP_INSTRUMENTS_GAD (//)
+_Remap Instruments
+;
+;
+MSG_SAVED_X_BYTES (//)
+Saved %ld bytes (%ld%%)
+;
+;
+; **************************
+; * Strings for PrefsWin.c *
+; **************************
+;
+;
+MSG_PREFS_TITLE (//)
+User Interface Settings
+;
+;
+MSG_PUBLIC_SCREEN_GAD (//)
+_Public Screen
+;
+MSG_DISPLAY_MODE_GAD (//)
+Display _Mode
+;
+MSG_WINDOW_FONT_GAD (//)
+_Window Font
+;
+MSG_LISTVIEW_FONT_GAD (//)
+_ListView Font
+;
+MSG_REQUESTERS_GAD (//)
+_Requesters
+;
+MSG_USE_DATATYPES_GAD (//)
+Use _DataTypes
+;
+MSG_APPICON_GAD (//)
+Put App_Icon
+;
+MSG_REFRESH_GAD (//)
+Refres_h
+;
+MSG_LOG_TO_FILE_GAD (//)
+Log To File
+;
+MSG_LOG_LEVEL_GAD (//)
+_Log Level
+;
+MSG_ASK_AUTOSAVE_GAD (//)
+Confirm _Autosave
+;
+MSG_AUTOSAVE_TIME_GAD (//)
+Autosave Mi_nutes
+;
+MSG_DO_BACKUPS_GAD (//)
+Create _Backups
+;
+MSG_BACKUP_TEMPLATE_GAD (//)
+Bac_kup Template
+;
+MSG_BACKUP_VERSIONS_GAD (//)
+Backup _Versions
+;
+MSG_ASL_GAD (//)
+Asl
+;
+MSG_REQTOOLS_GAD (//)
+ReqTools
+;
+MSG_SMART_GAD (//)
+Smart
+;
+MSG_SIMPLE_GAD (//)
+Simple
+;
+MSG_CLONE_DEF_SCREEN (//)
+--Clone Default Screen--
+;
+;
+; ****************************
+; * Strings for PatternWin.c *
+; ****************************
+;
+MSG_PATTERN_TITLE (//)
+Pattern Editor
+;
+;
+MSG_PATTERNS_MEN (//)
+Patterns
+;
+MSG_SIZE_MEN (//)
+Size...
+;
+MSG_EDIT_MEN (//)
+Edit
+;
+MSG_MARK_MEN (//)
+Mark
+;
+MSG_CUT_MEN (//)
+Cut
+;
+MSG_COPY_MEN (//)
+Copy
+;
+MSG_PASTE_MEN (//)
+Paste
+;
+MSG_ERASE_MEN (//)
+Erase
+;
+MSG_UNDO_MEN (//)
+Undo
+;
+MSG_REDO_MEN (//)
+Redo
+;
+MSG_EDITOR_SETTINGS_MEN (//)
+Editor Settings...
+;
+;
+; ***************************
+; * Strings for SaversWin.c *
+; ***************************
+;
+MSG_SAVERS_TITLE (//)
+Savers
+;
+;
+MSG_SF_SEQUENCE_GAD (//)
+Se_quence
+;
+MSG_SF_INSTRUMENTS_GAD (//)
+_Instruments
+;
+MSG_SF_PATTERNS_GAD (//)
+_Patterns
+;
+MSG_SF_NAMES_GAD (//)
+Names
+;
+MSG_ADD_ICON_GAD (//)
+Add I_con
+;
+MSG_MODE_GAD (//)
+_Mode
+;
+MSG_OPTIONS_DOTS_GAD (//)
+Options...
+;
+MSG_NONE_GAD (//)
+None
+;
+MSG_XPK_GAD (//)
+XPK
+;
+MSG_LHA_GAD (//)
+LhA
+;
+;
+MSG_DESCRIPTION (//)
+Description
+;
+MSG_AUTHOR (//)
+Author
+;
+MSG_MAXLENGTH (//)
+Max Length
+;
+MSG_MAXTRACKS (//)
+Max Tracks
+;
+MSG_MAXINSTRUMENTS (//)
+Max Instruments
+;
+MSG_MAXPATTERNS (//)
+Max Patterns
+;
+MSG_MAXPATTLEN (//)
+Max Pattern Length
+;
+MSG_MAXSAMPLELEN (//)
+Max Sample Length
+;
+;
+; **********************
+; * Strings ClearWin.c *
+; **********************
+;
+MSG_CLEAR_TITLE (//)
+Clear Module
+;
+;
+MSG_CLR_SEQUENCE_GAD (//)
+_Sequence
+;
+MSG_CLR_INSTRUMENTS_GAD (//)
+_Instruments
+;
+MSG_CLR_PATTERNS_GAD (//)
+_Patterns
+;
+MSG_CLEARMOD_GAD (//)
+_Clear
+;
+;
+; ***************************
+; * Strings for SampleWin.c *
+; ***************************
+;
+MSG_SAMPLE_TITLE (//)
+Sample Editor
+;
+;
+MSG_RENDER_MEN (//)
+Render
+;
+MSG_POINTS_MEN (//)
+Points
+;
+MSG_LINES_MEN (//)
+Lines
+;
+MSG_FILLED_MEN (//)
+Filled
+;
+;
+; *****************************
+; * Strings for PattSizeWin.c *
+; *****************************
+;
+MSG_PATTSIZE_TITLE (//)
+Pattern Attributes
+;
+;
+MSG_LINES_GAD (//)
+_Lines
+;
+MSG_TRACKS_GAD (//)
+_Tracks
+;
+MSG_DOUBLE_GAD (//)
+_Double
+;
+MSG_HALVE_GAD (//)
+_Halve
+;
+;
+; *************************
+; * Strings for Library.c *
+; *************************
+;
+MSG_SONG_TOO_MANY_TRACKS (//)
+WARNING: The selected saver supports max %lu tracks, but the song has %lu tracks.
+;
+MSG_SONG_TOO_MANY_PATTS (//)
+WARNING: The selected saver supports max %lu patterns; the song has %lu patterns.
+;
+MSG_SONG_TOO_MANY_INSTRS (//)
+WARNING: The selected saver supports max %lu instruments, last instrument is %lu.
+;
+MSG_SONG_TOO_MANY_POS (//)
+WARNING: The selected saver supports max %lu positions, but the song has %lu positions.
+;
+MSG_PATT_LENGTH_INVALID (//)
+WARNING: The length of pattern %ld ($%lx lines) isn't allowed with the selected saver.
+;
+MSG_INSTR_TOO_LONG (//)
+WARNING: Instrument %lx is too long and will be cropped to %lu bytes.
+;
+MSG_WILL_MODIFY_SONG (//)
+Some modifications need to be performed on this song\n\
+in order to adapt it to the limitations of the\n\
+destination format.
+;
+MSG_TRY_REMAPPING_INSTRUMENTS (//)
+Some instruments are placed beyond the limit for\n\
+the selected format.\n\
+Remapping the instruments now could help saving\n\
+all them along with the selected format.
+;
+MSG_SAVING_MODULE (//)
+Saving %s module \"%s\"...
+; the first %s is the format name, the second %s is the song name.
diff --git a/ClearWin.c b/ClearWin.c
new file mode 100644 (file)
index 0000000..0d217cd
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+**     ClearWin.c
+**
+**     Copyright (C) 1994,95,96 Bernardo Innocenti
+**
+**     Clear panel handling functions.
+*/
+
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Gadgets IDs */
+
+enum {
+       GD_ClearGroup0,
+               GD_ClearGroup1,
+                       GD_ClearSequence,
+                       GD_ClearInstruments,
+                       GD_ClearPatterns,
+               GD_ClearPerform,
+
+       Clear_CNT
+};
+
+
+/*****************************/
+/* Local function prototypes */
+/*****************************/
+
+static void ClearPerformClicked (struct WinUserData *wud);
+
+
+
+
+XDEF struct ClearSwitches ClearSwitches = { 1, 1, 1 };
+
+
+
+static LONG ClearArgs[] =
+{
+       VGROUP_KIND, BBFT_RIDGE,
+               CHECKBOX_KIND,  NULL,   MSG_CLR_SEQUENCE_GAD,           (LONG)&ClearSwitches.ClearSeq,          TAG_DONE,
+               CHECKBOX_KIND,  NULL,   MSG_CLR_INSTRUMENTS_GAD,        (LONG)&ClearSwitches.ClearInstr,        TAG_DONE,
+               CHECKBOX_KIND,  NULL,   MSG_CLR_PATTERNS_GAD,           (LONG)&ClearSwitches.ClearPatt, TAG_DONE,
+               ENDGROUP_KIND,
+       BUTTON_KIND,    (LONG)ClearPerformClicked,      MSG_CLEARMOD_GAD,       TAG_DONE,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG ClearWinTags[] =
+{
+       XMWIN_LayoutArgs,       (LONG)ClearArgs,
+       XMWIN_GCount,           Clear_CNT,
+       XMWIN_Title,            MSG_CLEAR_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       CHECKBOXIDCMP | BUTTONIDCMP | IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW,
+       XMWIN_HelpNode,         (LONG)"Clear",
+       TAG_DONE
+};
+
+
+
+GLOBALCALL void UpdateClearSwitches (void)
+{
+       struct WinUserData *wud = WDescr[WID_CLEAR].Wud;
+
+       if (wud && wud->Win)
+               SetGadgets (wud,
+                       GD_ClearSequence,               ClearSwitches.ClearSeq,
+                       GD_ClearInstruments,    ClearSwitches.ClearInstr,
+                       GD_ClearPatterns,               ClearSwitches.ClearPatt,
+                       -1);
+}
+
+
+/*****************/
+/* Clear Gadgets */
+/*****************/
+
+static void ClearPerformClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+       LONG i;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               if (ClearSwitches.ClearPatt)
+               {
+                       for (i = si->NumPatterns - 1 ; i >= 0 ; i--)
+                               xmRemPattern (si, i, 0);
+
+                       xmAddPatternA (si, NULL);
+               }
+
+               if (ClearSwitches.ClearInstr)
+               {
+                       for (i = 1 ; i <= si->LastInstrument ; i++)
+                               xmRemInstrument (si, i);
+               }
+
+               if (ClearSwitches.ClearSeq)
+               {
+                       xmSetSongLen (si, 1);
+                       si->Sequence[0] = 0;
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UpdateSongInfo();
+       MyCloseWindow (wud);
+}
diff --git a/CompilerSpecific.h b/CompilerSpecific.h
new file mode 100644 (file)
index 0000000..6041ece
--- /dev/null
@@ -0,0 +1,304 @@
+#ifndef COMPILERSPECIFIC_H
+#define COMPILERSPECIFIC_H
+/*
+**     $VER: CompilerSpecific.h 2.3 (26.10.97)
+**
+**     Copyright (C) 1997 Bernardo Innocenti. All rights reserved.
+**
+**     Compiler specific definitions is here. You can add support
+**     for other compilers in this header. Please return any changes
+**     you make to me, so I can add them to my personal copy of this file.
+**
+**     Here is a short description of the macros defined below:
+**
+**     LIBCALL
+**             Shared library entry point, with register args
+**
+**     HOOKCALL
+**             Hook or boopsi dispatcher entry point with arguments
+**             passed in registers
+**
+**     GLOBALCALL
+**             Attribute for functions to be exported to other modules for
+**             global access within the same executable file.
+**             Usually defined to "extern", but can be overridden for special
+**             needs, such as compiling all modules together in a single
+**             object module to optimize code better.
+**
+**     XDEF
+**             Attribute for symbols to be exported to other modules for
+**             global access within the same executable file.
+**             Usually defined to an empty value.
+**
+**     XREF
+**             Attribute for symbols to be imported from other modules
+**             within the same executable file.
+**             Usually defined to "extern".
+**
+**     INLINE
+**             Please put function body inline to the calling code
+**
+**     STDARGS
+**             Function uses standard C conventions for arguments
+**
+**     ASMCALL
+**             Function takes arguments in the specified 68K registers
+**
+**     REGCALL
+**             Function takes arguments in registers choosen by the compiler
+**
+**     CONSTCALL
+**             Function does not modify any global variable
+**
+**     FORMATCALL(archetype,string_index,first_to_check)
+**             Function uses printf or scanf-like formatting
+**
+**     SAVEDS
+**             Function needs to reload context for small data model
+**
+**     INTERRUPT
+**             Function will be called from within an interrupt
+**
+**     NORETURN
+**             Function does never return
+**
+**     ALIGNED
+**             Variable must be aligned to longword boundaries
+**
+**     CHIP
+**             Variable must be stored in CHIP RAM
+**
+**     REG(reg,arg)
+**             Put argument <arg> in 68K register <reg>
+**
+**     min(a,b)
+**             Return the minimum between <a> and <b>
+**
+**     max(a,b)
+**             Return the maximum between <a> and <b>
+**
+**     abs(a)
+**             Return the absolute value of <a>
+**
+**     _COMPILED_WITH
+**             A string containing the name of the compiler
+*/
+
+#ifdef __SASC
+       /* SAS/C 6.58 or better */
+
+       #define INLINE          static __inline
+       #define STDARGS         __stdargs
+       #define ASMCALL         __asm
+       #define REGCALL         __regcall
+       #define CONSTCALL       /* unsupported */
+       #define FORMATCALL      /* unsupported */
+       #define SAVEDS          __saveds
+       #define INTERRUPT       __interrupt
+       #define NORETURN        /* unsupported */
+       #define ALIGNED         __aligned
+       #define CHIP            __chip
+       #define REG(reg,arg) register __##reg arg
+       #define _COMPILED_WITH  "SAS/C"
+
+       /* For min(), max() and abs() */
+       #define USE_BUILTIN_MATH
+       #include <string.h>
+#else
+#ifdef __GNUC__
+       /* GeekGadgets GCC 2.7.2.1 or better */
+
+       #define INLINE          static inline
+       #define STDARGS         __attribute__((stkparm))
+       #define ASMCALL         /* nothing */
+       #define REGCALL         /* nothing */
+       #define CONSTCALL       __attribute__((const))
+       #define FORMATCALL(a,s,f)       __attribute__((format(a,s,f)))
+       #define SAVEDS          __attribute__((saveds))
+       #define INTERRUPT       __attribute__((interrupt))
+       #define NORETURN        __attribute__((noreturn))
+       #define ALIGNED         __attribute__((aligned(4)))
+       #define REG(reg,arg) arg __asm(#reg)
+       #define _COMPILED_WITH  "GCC"
+
+       #define min(a,b)        (((a)<(b))?(a):(b))
+       #define max(a,b)        (((a)>(b))?(a):(b))
+       #define abs(a)          (((a)>0)?(a):-(a))
+
+       /* Inline versions of some str*() and mem*() functions
+        */
+       #define _ANSI_SOURCE
+       #include <string.h>
+       #undef _ANSI_SOURCE
+
+       INLINE STDARGS size_t strlen (const char *src)
+               { const char *tmp = src; while (*tmp) tmp++; return tmp - src; }
+
+       INLINE STDARGS int memcmp (const void *src, const void *dest, size_t n)
+       {
+               while (n--)
+               {
+                       if (*((char *)src) != *((char *)dest))
+                               return (*((char *)src) - *((char *)dest));
+                       ((char *)src)++;
+                       ((char *)dest)++;
+               }
+               return 0;
+       }
+
+       INLINE STDARGS void * memset (void *m, int c, size_t n)
+       {
+               void *r = m;
+               n++;
+               while (--n > 0)
+                       *(((unsigned char *) m)++) = (unsigned char) c;
+               return r;
+       }
+
+       /* GCC produces code which calls these two functions
+        * to initialize and copy structures and arrays.
+        *
+        *      INLINE STDARGS void bzero (void *buf, size_t len)
+        *              { while (len--) *((char *)buf)++ = 0; }
+        *
+        *      INLINE STDARGS void bcopy (const void *src, void *dest, size_t len)
+        *              { while (len--) *((char *)dest)++ = *((const char *)src)++; }
+        */
+
+#else
+#ifdef __STORM__
+       /* StormC 2.00.23 or better */
+       #define INLINE          __inline
+       #define STDARGS         /* nothing */
+       #define ASMCALL         /* nothing */
+       #define REGCALL         register
+       #define CONSTCALL       /* unsupported */
+       #define FORMATCALL      /* unsupported */
+       #define SAVEDS          __saveds
+       #define INTERRUPT       __interrupt
+       #define NORETURN        /* unsupported */
+       #define ALIGNED         /* unsupported */
+       #define CHIP            __chip
+       #define REG(reg,arg) register __##reg arg
+       #define _COMPILED_WITH  "StormC"
+
+       #define min(a,b)        (((a)<(b))?(a):(b))
+       #define max(a,b)        (((a)>(b))?(a):(b))
+       #define abs(a)          (((a)>0)?(a):-(a))
+
+       #define _INLINE_INCLUDES
+       #include <string.h>
+#else
+#ifdef __MAXON__
+       /* Maxon C/C++ 3.0 */
+
+       #define INLINE          static inline
+       #define STDARGS         /* ? */
+       #define ASMCALL         /* ? */
+       #define REGCALL         /* ? */
+       #define CONSTCALL       /* unsupported */
+       #define FORMATCALL      /* unsupported */
+       #define SAVEDS          /* unsupported */
+       #define INTERRUPT       /* unsupported */
+       #define NORETURN        /* unsupported */
+       #define ALIGNED         /* unsupported */
+       #define REG(reg,arg)    register __##reg arg
+       #define _COMPILED_WITH  "Maxon C"
+
+       /* For min(), max() and abs() */
+       #define USE_BUILTIN_MATH
+       #include <string.h>
+
+       #error Maxon C compiler support is untested. Please check all the above definitions
+#else
+#ifdef _DCC
+       /* DICE C 3.15 */
+
+       #define INLINE          static __inline
+       #define STDARGS         __stdargs
+       #define ASMCALL         /* nothing */
+       #define REGCALL         /* ? */
+       #define CONSTCALL       /* unsupported */
+       #define FORMATCALL      /* unsupported */
+       #define SAVEDS          __geta4
+       #define INTERRUPT       /* unsupported */
+       #define NORETURN        /* unsupported */
+       #define ALIGNED         __aligned
+       #define REG(reg,arg)    __##reg arg
+       #define _COMPILED_WITH  "DICE"
+
+       #define min(a,b)        (((a)<(b))?(a):(b))
+       #define max(a,b)        (((a)>(b))?(a):(b))
+       #define abs(a)          (((a)>0)?(a):-(a))
+
+       #error DICE compiler support is untested. Please check all the above definitions
+#else
+#ifdef AZTEC_C
+       /* Aztec/Manx C */
+
+       #define INLINE          static
+       #define STDARGS         /* ? */
+       #define ASMCALL         /* ? */
+       #define REGCALL         /* ? */
+       #define CONSTCALL       /* unsupported */
+       #define FORMATCALL      /* unsupported */
+       #define SAVEDS          __geta4
+       #define INTERRUPT       /* unsupported */
+       #define NORETURN        /* unsupported */
+       #define ALIGNED         __aligned
+       #define REG(reg,arg)    __##reg arg
+       #define _COMPILED_WITH  "Manx C"
+
+       #define min(a,b)        (((a)<(b))?(a):(b))
+       #define max(a,b)        (((a)>(b))?(a):(b))
+       #define abs(a)          (((a)>0)?(a):-(a))
+
+       #error Aztec/Manx C compiler support is untested. Please check all the above definitions
+#else
+       #error Please add compiler specific definitions for your compiler
+#endif
+#endif
+#endif
+#endif
+#endif
+#endif
+
+
+/* CONST_STRPTR is a typedef made by a patched version of <exec/types.h>.
+ * Passing "const char *" parameters will only work if the OS protos are
+ * patched accordingly, otherwise you will get a lot of compiler warnings
+ * for const to volatile conversions.
+ *
+ * Using "const" where it is appropriate helps the compiler optimizing
+ * code better, so this mess is probably worth it. I wish one day the OS
+ * headers will come with support for the const keyword.
+ */
+#ifndef _CONST_STRPTR_DEFINED
+typedef char *CONST_STRPTR;
+#endif
+
+
+/* Special function attributes */
+
+#define LIBCALL                ASMCALL SAVEDS
+#define HOOKCALL       ASMCALL SAVEDS
+#ifdef __cplusplus
+       #define GLOBALCALL extern "C"
+#else
+       #define GLOBALCALL
+#endif
+
+/* special variable attributes */
+#define XDEF
+#define XREF extern
+
+
+/* AROS Compatibility: IPTR is a type which can store a pointer
+ * as well as a long integer.
+ */
+#ifndef IPTR
+#define IPTR LONG
+#endif /* IPTR */
+
+
+#endif /* !COMPILERSPECIFIC_H */
diff --git a/Compress.c b/Compress.c
new file mode 100644 (file)
index 0000000..5ba53e6
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+**     Compress.c
+**
+**     Copyright (C) 1994,96,97 Bernardo Innocenti
+**
+**     Compression/Decompression handling functions.
+*/
+
+#include <exec/memory.h>
+#include <utility/hooks.h>
+#include <xpk/xpk.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/powerpacker.h>
+#include <proto/xpkmaster.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+#define CTYPE_LHA              1
+#define CTYPE_XPK              2
+#define CTYPE_POWERPACKER      3
+
+
+/* static struct Library *PPBase = NULL; */
+static struct Library *XpkBase = NULL;
+
+
+XDEF UBYTE LhACommand[64] = "LhA >NIL: e -x0 -q \"%s\"";
+XDEF UBYTE TmpDir[PATHNAME_MAX] = "T:XModuleTmp";
+XDEF UBYTE LhAFilter[64] = "~(#?readme#?|#?txt#?|#?display#?|#?fileid#?)";
+
+
+
+static LONG HOOKCALL XPKProgressFunc (REG(a0,struct Hook *hook), REG(a1,struct XpkProgress *pr))
+{
+       return (DisplayProgress (pr->xp_UCur, pr->xp_ULen));
+}
+
+
+
+static struct Hook XPKProgressHook =
+{
+       { NULL, NULL },
+       XPKProgressFunc,
+       NULL,
+       0
+};
+
+
+
+GLOBALCALL BPTR DecompressFile (CONST_STRPTR name, UWORD type)
+
+/* This function will try to decompress the given file and store
+ * it in TmpDir.  If TmpDir does not exist, it will be created.
+ * The decompressed file is then locked and returned.
+ * A return value of NULL means failure.  Call DecompressFileDone()
+ * when you are done with the decompressed file.
+ */
+{
+       struct AnchorPath *ap;
+       BPTR ret = 0;
+       BPTR dir, olddir;
+       LONG err = 0;
+       UBYTE FullName[PATHNAME_MAX];
+
+
+       OpenProgressWindow();
+
+       DisplayAction (MSG_DECRUNCHING);
+
+       /* Find the full path name of the given file */
+       {
+               BPTR lock;
+
+               if (lock = Lock (name, ACCESS_READ))
+               {
+                       if (!NameFromLock (lock, FullName, PATHNAME_MAX))
+                               err = IoErr();
+                       UnLock (lock);
+               }
+               else err = IoErr();
+       }
+
+       if (!err)
+       {
+               /* Try to lock or create TmpDir */
+
+               if (!(dir = Lock (TmpDir, ACCESS_READ)))
+               {
+                       if (dir = CreateDir (TmpDir))
+                               if (!(ChangeMode (CHANGE_LOCK, dir, ACCESS_READ)))
+                               {
+                                       UnLock (dir);
+                                       dir = NULL;
+                               }
+               }
+
+               if (dir)
+               {
+                       olddir = CurrentDir (dir);
+
+                       switch (type)
+                       {
+                               case CTYPE_LHA:
+                               {
+                                       UBYTE buf[64+PATHNAME_MAX];
+
+                                       SPrintf (buf, LhACommand, FullName);
+                                       if (!SystemTagList (buf, NULL))
+                                       {
+                                               if (ap = AllocMem (sizeof (struct AnchorPath) + PATHNAME_MAX, MEMF_CLEAR))
+                                               {
+                                                       ap->ap_Strlen = PATHNAME_MAX;
+
+                                                       if (!(err = MatchFirst (LhAFilter, ap)))
+                                                       {
+                                                               if (!(ret = Lock (ap->ap_Buf, ACCESS_READ)))
+                                                                       err = IoErr();
+                                                       }
+
+                                                       MatchEnd (ap);
+                                                       FreeMem (ap, sizeof (struct AnchorPath) + PATHNAME_MAX);
+                                               }
+                                               else err = ERROR_NO_FREE_STORE; /* Fail AllocMem() */
+
+                                       }
+                                       else err = IoErr(); /* Fail SystemTagList() */
+
+                                       break;
+                               }
+
+                               case CTYPE_XPK:
+                               {
+                                       UBYTE dest[PATHNAME_MAX];
+                                       UBYTE errstring[XPKERRMSGSIZE];
+
+                                       if (!(XpkBase = OpenLibrary ("xpkmaster.library", 2L)))
+                                       {
+                                               CantOpenLib ("xpkmaster.library", 2L);
+                                               CloseProgressWindow();
+                                               return 0;
+                                       }
+
+                                       strcpy (dest, TmpDir);
+                                       if (AddPart (dest, "XPKTmp", PATHNAME_MAX))
+                                       {
+                                               if (XpkUnpackTags (
+                                                       XPK_InName,             FullName,
+                                                       XPK_OutName,    dest,
+                                                       XPK_GetError,   errstring,
+                                                       XPK_ChunkHook,  &XPKProgressHook,
+                                                       // XPK_TaskPri, ThisTask->pr_Task.tc_Node.ln_Pri-1,
+                                                       TAG_DONE))
+                                               {
+                                                       xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
+                                                               (APTR)MSG_ERROR_DECOMPRESSING, FilePart (FullName),
+                                                               errstring);
+                                               }
+                                               else ret = Lock (dest, ACCESS_READ);
+                                       }
+
+                                       CloseLibrary (XpkBase); XpkBase = NULL;
+                                       break;
+                               }
+
+/*                             case CTYPE_POWERPACKER:
+
+                                       if (!(PPBase = OpenLibrary ("powerpacker.library", 0L)))
+                                       {
+                                               CantOpenLib ("powerpacker.library", 0L);
+                                               CloseProgressWindow();
+                                               return 0;
+                                       }
+
+                                       xmDisplayMessageA (XMDMF_INFORMATION,
+                                               "PowerPacker compressed files are not supported yet.", NULL);
+
+                                       CloseLibrary (PPBase); PPBase = NULL;
+*/
+                               default:
+                                       break;
+                       }
+
+                       CurrentDir (olddir);
+                       UnLock (dir);
+               }
+               else err = IoErr(); /* Fail CreateDir() */
+       }
+
+       /* Report error */
+
+       if (err)
+       {
+               if (err == ERROR_NO_MORE_ENTRIES)
+                       xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
+                               (APTR)MSG_NOTHING_IN_ARC, name);
+               else
+                       xmDisplayMessageA (XMDMF_DOSFAULT | XMDMF_USECATALOG,
+                               (APTR)MSG_CANT_LOAD_COMPRESSED, NULL);
+       }
+
+       if (!ret) DecompressFileDone();
+
+       CloseProgressWindow();
+
+       return ret;
+}
+
+
+
+GLOBALCALL void DecompressFileDone (void)
+
+/* This call releases all resources got by DecompressFile(). */
+{
+       BPTR dir, olddir;
+       struct FileInfoBlock *fib;
+
+       if (dir = Lock (TmpDir, ACCESS_READ))
+       {
+               olddir = CurrentDir (dir);
+
+               if (fib = AllocDosObject (DOS_FIB, NULL))
+               {
+                       if (Examine (dir, fib))
+                       {
+                               /* Delete all files in the temp directory */
+                               while (ExNext (dir, fib))
+                                       DeleteFile (fib->fib_FileName);
+                       }
+
+                       FreeDosObject (DOS_FIB, fib);
+               }
+
+               CurrentDir (olddir);
+               UnLock (dir);
+       }
+
+       DeleteFile (TmpDir);
+}
+
+
+
+GLOBALCALL LONG CruncherType (BPTR file)
+{
+       union
+       {
+               LONG fileid;
+               struct
+               {
+                       UWORD dummy;
+                       ULONG prefix;
+               } lha;
+       } id;
+
+       if (Read (file, &id, sizeof (id)) != sizeof (id))
+               return 0;
+
+       if ((id.lha.prefix >> 8) == '-lh')
+               return CTYPE_LHA;
+
+       if (id.fileid == 'XPKF' || id.fileid == 'PP20' || id.fileid == 'PX20')
+               return CTYPE_XPK;
+
+       return 0;
+}
diff --git a/CustomClasses.c b/CustomClasses.c
new file mode 100644 (file)
index 0000000..f70a1d5
--- /dev/null
@@ -0,0 +1,626 @@
+/*
+**     CustomClasses.c
+**
+**     Copyright (C) 1995,96,97 Bernardo Innocenti
+**
+**     Special custom BOOPSI classes.
+*/
+
+#include <exec/memory.h>
+#include <utility/tagitem.h>
+#include <graphics/gfxbase.h>
+#include <devices/inputevent.h>
+
+#include <intuition/intuition.h>
+#include <intuition/classes.h>
+#include <intuition/classusr.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/icclass.h>
+#include <intuition/imageclass.h>
+
+#include <proto/exec.h>
+#include <proto/graphics.h>
+#include <proto/intuition.h>
+#include <proto/utility.h>
+
+#include "CustomClasses.h"
+#include "XModulePriv.h"
+#include "Gui.h"
+
+/* Per object instance data */
+struct ScrollButtonData
+{
+       /* The number of ticks we still have to wait
+        * before sending any notification.
+        */
+       ULONG TickCounter;
+};
+
+
+
+/* Function prototypes */
+
+static LIBCALL ULONG ScrollButtonDispatcher (
+       REG(a0, Class *cl),
+       REG(a2, struct Gadget *g),
+       REG(a1, struct gpInput *gpi));
+
+static ULONG HOOKCALL VImageDispatcher (
+       REG(a0, Class *cl),
+       REG(a2, struct Image *im),
+       REG(a1, struct opSet *ops));
+
+static void NotifyAttrChanges  (Object *o, struct GadgetInfo *gi, ULONG flags, Tag attr1, ...);
+static void DrawPlayImage              (struct RastPort *rp, UWORD width, UWORD heigth);
+static void DrawStopImage              (struct RastPort *rp, UWORD width, UWORD heigth);
+static void DrawForwardImage   (struct RastPort *rp, UWORD width, UWORD heigth);
+static void DrawRewindImage            (struct RastPort *rp, UWORD width, UWORD heigth);
+static void DrawPickImage              (struct RastPort *rp, UWORD width, UWORD height);
+static void DrawVImage                 (struct impDraw *imp, struct Image *im, struct BitMap *bm);
+
+
+
+/* tagcall stub for OM_NOTIFY */
+static void NotifyAttrChanges (Object *o, struct GadgetInfo *gi, ULONG flags, Tag attr1, ...)
+{
+       DoSuperMethod (OCLASS(o), o, OM_NOTIFY, &attr1, gi, flags);
+}
+
+
+/*
+**     ScrollButtonClass
+**
+**     Parts of the code have been inspired by ScrollerWindow 0.3 demo
+**     Copyright © 1994 Christoph Feck, TowerSystems.
+**
+**     Subclass of buttongclass.  The ROM class has two problems, which make
+**     it not quite usable for scrollarrows.  The first problem is the missing
+**     delay.  Once the next INTUITICK gets send by input.device, the ROM
+**     class already sends a notification.  The other problem is that it also
+**     notifies us, when the button finally gets released (which is necessary
+**     for command buttons).
+**
+**     We define a new class with the GM_GOACTIVE and GM_HANDLEINPUT method
+**     overloaded to work around these problems.
+*/
+
+static LIBCALL ULONG ScrollButtonDispatcher (
+       REG(a0, Class *cl),
+       REG(a2, struct Gadget *g),
+       REG(a1, struct gpInput *gpi))
+
+/* ScrollButton Class Dispatcher entrypoint.
+ * Handle BOOPSI messages.
+ */
+{
+       struct ScrollButtonData *bd = (struct ScrollButtonData *) INST_DATA(cl, g);
+
+       switch (gpi->MethodID)
+       {
+               case GM_GOACTIVE:
+                       /* May define an attribute to make delay configurable */
+                       bd->TickCounter = 3;
+
+                       /* Notify our target that we have initially hit. */
+                       NotifyAttrChanges ((Object *)g, gpi->gpi_GInfo, 0,
+                               GA_ID, g->GadgetID,
+                               TAG_DONE);
+
+                       /* Send more input */
+                       return GMR_MEACTIVE;
+
+               case GM_HANDLEINPUT:
+               {
+                       struct RastPort *rp;
+                       ULONG retval = GMR_MEACTIVE;
+                       UWORD selected = 0;
+
+                       /* This also works with classic (non-boopsi) images. */
+                       if (PointInImage ((gpi->gpi_Mouse.X << 16) + (gpi->gpi_Mouse.Y), g->GadgetRender))
+                       {
+                               /* We are hit */
+                               selected = GFLG_SELECTED;
+                       }
+
+                       if (gpi->gpi_IEvent->ie_Class == IECLASS_RAWMOUSE && gpi->gpi_IEvent->ie_Code == SELECTUP)
+                       {
+                               /* Gadgetup, time to go */
+                               retval = GMR_NOREUSE;
+                               /* Unselect the gadget on our way out... */
+                               selected = 0;
+                       }
+                       else if (gpi->gpi_IEvent->ie_Class == IECLASS_TIMER)
+                       {
+                               /* We got a tick.  Decrement counter, and if 0, send notify. */
+
+                               if (bd->TickCounter) bd->TickCounter--;
+                               else if (selected)
+                               {
+                                       NotifyAttrChanges ((Object *) g, gpi->gpi_GInfo, 0,
+                                               GA_ID, g->GadgetID,
+                                               TAG_DONE);
+                               }
+                       }
+
+                       if ((g->Flags & GFLG_SELECTED) != selected)
+                       {
+                               /* Update changes in gadget render */
+                               g->Flags ^= GFLG_SELECTED;
+                               if (rp = ObtainGIRPort (gpi->gpi_GInfo))
+                               {
+                                       DoMethod ((Object *) g, GM_RENDER, gpi->gpi_GInfo, rp, GREDRAW_UPDATE);
+                                       ReleaseGIRPort (rp);
+                               }
+                       }
+                       return retval;
+               }
+
+               default:
+                       /* Super class handles everything else */
+                       return (DoSuperMethodA (cl, (Object *)g, (Msg) gpi));
+       }
+}
+
+
+
+GLOBALCALL Class *InitScrollButtonClass (void)
+{
+       Class *class;
+
+       if (class = MakeClass (NULL, BUTTONGCLASS, NULL, sizeof(struct ScrollButtonData), 0))
+               class->cl_Dispatcher.h_Entry = (ULONG (*)()) ScrollButtonDispatcher;
+
+       return class;
+}
+
+
+
+GLOBALCALL BOOL FreeScrollButtonClass (Class *cl)
+{
+       return (FreeClass (cl));
+}
+
+
+
+static ULONG HOOKCALL VImageDispatcher (
+       REG(a0, Class *cl),
+       REG(a2, struct Image *im),
+       REG(a1, struct opSet *ops))
+
+/* VImage Class Dispatcher entrypoint.
+ * Handle BOOPSI messages.
+ */
+{
+       switch (ops->MethodID)
+       {
+               case OM_NEW:
+
+                       /* Create the image structure */
+                       if (im = (struct Image *)DoSuperMethodA (cl, (Object *)im, (Msg) ops))
+                       {
+                               ULONG                    which;
+                               struct RastPort  rp;
+                       /*      struct DrawInfo *dri = (struct DrawInfo *)GetTagData (GA_DrawInfo, NULL, ops->ops_AttrList);
+                       */
+                               which = GetTagData (SYSIA_Which, 0, ops->ops_AttrList);
+
+                               InitRastPort (&rp);
+
+#ifndef OS30_ONLY
+                               if (GfxBase->LibNode.lib_Version >= 39)
+#endif /* !OS30_ONLY */
+                                       rp.BitMap = AllocBitMap (im->Width, im->Height, 1, BMF_CLEAR, NULL);
+#ifndef OS30_ONLY
+                               else
+                               {
+                                       if (rp.BitMap = AllocMem (sizeof (struct BitMap), MEMF_PUBLIC))
+                                       {
+                                               InitBitMap (rp.BitMap, 1, im->Width, im->Height);
+                                               if (!(rp.BitMap->Planes[0] = AllocMem (RASSIZE(im->Width, im->Height), MEMF_CHIP | MEMF_CLEAR)))
+                                               {
+                                                       FreeMem (rp.BitMap, sizeof (struct BitMap));
+                                                       rp.BitMap = NULL;
+                                               }
+                                       }
+                               }
+#endif /* !OS30_ONLY */
+
+                               if (rp.BitMap)
+                               {
+                                       PLANEPTR                planeptr;
+                                       struct TmpRas   tmpras;
+                                       struct AreaInfo areainfo;
+                                       WORD                    areabuffer[(5 * 10 + 1) / 2];
+
+                                       if (planeptr = AllocRaster (im->Width, im->Height))
+                                       {
+                                               InitTmpRas (&tmpras, planeptr, RASSIZE(im->Width, im->Height));
+                                               InitArea (&areainfo, areabuffer, 10);
+                                               SetAPen (&rp, 1);
+                                               rp.TmpRas = &tmpras;
+                                               rp.AreaInfo = &areainfo;
+
+                                               switch (which)
+                                               {
+                                                       case IM_PLAY:
+                                                               DrawPlayImage (&rp, im->Width, im->Height);
+                                                               break;
+
+                                                       case IM_STOP:
+                                                               DrawStopImage (&rp, im->Width, im->Height);
+                                                               break;
+
+                                                       case IM_FWD:
+                                                               DrawForwardImage (&rp, im->Width, im->Height);
+                                                               break;
+
+                                                       case IM_REW:
+                                                               DrawRewindImage (&rp, im->Width, im->Height);
+                                                               break;
+
+                                                       case IM_PICK:
+                                                               DrawPickImage (&rp, im->Width, im->Height);
+                                                               break;
+                                               }
+
+                                               /* Just to be sure... */
+                                               rp.TmpRas = NULL;
+                                               rp.AreaInfo = NULL;
+
+                                               FreeRaster (planeptr, im->Width, im->Height);
+                                       }
+
+                                       /* This way our image will complement better */
+                                       rp.BitMap->Planes[1] = (UBYTE *)0;
+                                       rp.BitMap->Depth = 2;
+
+
+                                       /* Failing to allocate the TmpRas will cause the
+                                        * image to be blank, but no error will be
+                                        * reported.
+                                        */
+
+                                       /* Store the BitMap pointer here for later usage */
+                                       im->ImageData = (UWORD *)rp.BitMap;
+
+                                       return (ULONG)im;       /* Return new image object */
+                               }
+
+                               DisposeObject (im);
+                       }
+
+                       return NULL;
+
+               case IM_DRAW:
+               case IM_DRAWFRAME:
+                       DrawVImage ((struct impDraw *)ops, im, (struct BitMap *)im->ImageData);
+                       break;
+
+               case OM_DISPOSE:
+
+                       /* Restore original depth! */
+                       ((struct BitMap *)im->ImageData)->Depth = 1;
+
+#ifndef OS30_ONLY
+                       if (GfxBase->LibNode.lib_Version >= 39)
+#endif /* !OS30_ONLY */
+                               FreeBitMap ((struct BitMap *)im->ImageData);
+#ifndef OS30_ONLY
+                       else
+                       {
+                               FreeMem (((struct BitMap *)im->ImageData)->Planes[0], RASSIZE(im->Width, im->Height));
+                               FreeMem (((struct BitMap *)im->ImageData), sizeof (struct BitMap));
+                       }
+#endif /* !OS30_ONLY */
+
+                       /* Now let our superclass free it's istance */
+                       /* Note: I'm falling through here! */
+
+               default:
+                       /* Our Super class handles everything else */
+                       return (DoSuperMethodA (cl, (Object *)im, (Msg) ops));
+       }
+}
+
+
+
+GLOBALCALL Class *InitVImageClass (void)
+{
+       Class *class;
+
+       if (class = MakeClass (NULL, IMAGECLASS, NULL, 0, 0))
+               class->cl_Dispatcher.h_Entry = (ULONG (*)()) VImageDispatcher;
+
+       return class;
+}
+
+
+
+GLOBALCALL BOOL FreeVImageClass (Class *cl)
+{
+       return (FreeClass (cl));
+}
+
+
+
+static void DrawPlayImage (struct RastPort *rp, UWORD width, UWORD height)
+{
+       UWORD   ymin = height / 4,
+                       ymax = (height * 3) / 4,
+                       ymid;
+
+       ymin -= (ymax - ymin) & 1;      /* Force odd heigth for better arrow aspect */
+       ymid = (ymin + ymax) / 2;
+
+       RectFill (rp, 1, ymin, (width / 4) - 1, ymax);
+
+       AreaMove (rp, width / 3, ymin);
+       AreaDraw (rp, width - 2, ymid);
+       AreaDraw (rp, width / 3, ymax);
+
+       AreaEnd (rp);
+}
+
+
+
+static void DrawStopImage (struct RastPort *rp, UWORD width, UWORD height)
+{
+       RectFill (rp, width / 4, height / 4, (width * 3) / 4, (height * 3) / 4);
+}
+
+
+
+static void DrawForwardImage (struct RastPort *rp, UWORD width, UWORD height)
+{
+       UWORD   ymin = height / 4,
+                       ymax = (height * 3) / 4,
+                       ymid;
+
+       ymin -= (ymax - ymin) & 1;      /* Force odd heigth for better arrow aspect */
+       ymid = (ymin + ymax) / 2;
+
+       AreaMove (rp, 1, ymin);
+       AreaDraw (rp, width / 2, ymid);
+       AreaDraw (rp, 1, ymax);
+
+       AreaMove (rp, width / 2, ymin);
+       AreaDraw (rp, width - 2, ymid);
+       AreaDraw (rp, width / 2, ymax);
+
+       AreaEnd (rp);
+}
+
+
+
+static void DrawRewindImage (struct RastPort *rp, UWORD width, UWORD height)
+{
+       UWORD   ymin = height / 4,
+                       ymax = (height * 3) / 4,
+                       ymid;
+
+       ymin -= (ymax - ymin) & 1;      /* Force odd heigth for better arrow aspect */
+       ymid = (ymin + ymax) / 2;
+
+       AreaMove (rp, width - 2, ymin);
+       AreaDraw (rp, width / 2, ymid);
+       AreaDraw (rp, width - 2, ymax);
+
+       AreaMove (rp, width / 2 - 1, ymin);
+       AreaDraw (rp, 1, ymid);
+       AreaDraw (rp, width / 2 - 1, ymax);
+
+       AreaEnd (rp);
+}
+
+
+static void DrawPickImage (struct RastPort *rp, UWORD width, UWORD height)
+/*
+ *      arrowxmin
+ *      | tailxmin
+ *      | | tailxmax
+ *      | | |
+ *      | v v
+ *      | ###<----tailymin
+ *      v ###
+ *      #######<--arrowymin
+ *       #####
+ *        ###
+ *         #<-----arrowymax
+ *      #######<--arrowymax+1
+ */
+{
+       UWORD   tailymin        = height / 6,
+                       tailxmin        = (width * 2) / 5,
+                       tailxmax        = (width * 3) / 5,
+                       arrowymin       = (height * 2) / 5,
+                       arrowymax       = (height * 4) / 5,
+                       arrowxmin       = width / 5,
+                       arrowxmax       = (width * 4) / 5;
+
+       AreaMove (rp, tailxmin, tailymin);
+       AreaDraw (rp, tailxmax, tailymin);
+       AreaDraw (rp, tailxmax, arrowymin);
+       AreaDraw (rp, arrowxmax, arrowymin);
+       AreaDraw (rp, (arrowxmin + arrowxmax) / 2, arrowymax);
+       AreaDraw (rp, arrowxmin, arrowymin);
+       AreaDraw (rp, tailxmin, arrowymin);
+       AreaEnd (rp);
+
+       if (arrowymax < height - 1) arrowymax++;
+
+       Move (rp, arrowxmin, arrowymax);
+       Draw (rp, arrowxmax, arrowymax);
+}
+
+
+
+static void DrawVImage (struct impDraw *imp, struct Image *im, struct BitMap *bm)
+{
+       if (bm)
+               BltBitMapRastPort (bm, 0, 0, imp->imp_RPort,
+                       imp->imp_Offset.X, imp->imp_Offset.Y, im->Width, im->Height,
+                       (imp->imp_State == IDS_SELECTED) ? 0x030 : 0x0C0);
+}
+
+
+
+GLOBALCALL struct Gadget *CreateUpButton (ULONG id, struct Gadget *target, LONG *map, LONG Place)
+{
+       struct Gadget   *UpButton;
+       struct Image    *UpImage;
+
+       if (!(UpImage = NewImageObject (UPIMAGE)))
+               return NULL;
+
+       if (!(UpButton = (struct Gadget *)NewObject (ScrollButtonClass, NULL,
+               GA_ID,                          id,
+               GA_RelBottom,           - (UpImage->Height * 2) - SizeHeight + 1,
+               (Place == SCROLLERPLACE_LEFT) ? GA_Left : GA_RelRight,
+                       (Place == SCROLLERPLACE_LEFT) ? 0 : (- SizeWidth + 1),
+               /* ^--- perhaps using UpImage->Width would be better... */
+
+               (Place == SCROLLERPLACE_LEFT) ? GA_LeftBorder : GA_RightBorder, TRUE,
+
+               /* No need for GA_Width/Height.  buttongclass is smart :) */
+               GA_Image,                       UpImage,
+               ICA_TARGET,                     target,
+               ICA_MAP,                        map,
+               TAG_DONE)))
+               DisposeObject (UpImage);
+
+       return UpButton;
+}
+
+
+
+GLOBALCALL struct Gadget *CreateDnButton (ULONG id, struct Gadget *target, LONG *map, LONG Place)
+{
+       struct Gadget   *DnButton;
+       struct Image    *DnImage;
+
+
+       if (!(DnImage = NewImageObject (DOWNIMAGE)))
+               return NULL;
+
+       if (!(DnButton = (struct Gadget *)NewObject (ScrollButtonClass, NULL,
+               GA_ID,                          id,
+               GA_RelBottom,           - DnImage->Height - SizeHeight + 1,
+               (Place == SCROLLERPLACE_LEFT) ? GA_Left : GA_RelRight,
+                       (Place == SCROLLERPLACE_LEFT) ? 0 : (- SizeWidth + 1),
+               (Place == SCROLLERPLACE_LEFT) ? GA_LeftBorder : GA_RightBorder, TRUE,
+               /* No need for GA_Width/Height.  buttongclass is smart :) */
+               GA_Image,                       DnImage,
+               ICA_TARGET,                     target,
+               ICA_MAP,                        map,
+               TAG_DONE)))
+               DisposeObject (DnImage);
+
+       return DnButton;
+}
+
+
+
+GLOBALCALL struct Gadget *CreateSxButton (ULONG id, struct Gadget *target, LONG *map)
+{
+       struct Gadget   *SxButton;
+       struct Image    *SxImage;
+
+
+       if (!(SxImage = NewImageObject (LEFTIMAGE)))
+               return NULL;
+
+       if (!(SxButton = (struct Gadget *)NewObject (ScrollButtonClass, NULL,
+               GA_ID,                          id,
+               GA_RelBottom,           - SxImage->Height + 1,
+               GA_RelRight,            - SizeWidth - (SxImage->Width * 2) + 1,
+               GA_BottomBorder,        TRUE,
+               /* No need for GA_Width/Height.  buttongclass is smart :) */
+               GA_Image,                       SxImage,
+               ICA_TARGET,                     target,
+               ICA_MAP,                        map,
+               TAG_DONE)))
+               DisposeObject (SxImage);
+
+       return SxButton;
+}
+
+
+
+GLOBALCALL struct Gadget *CreateDxButton (ULONG id, struct Gadget *target, LONG *map)
+{
+       struct Gadget   *DxButton;
+       struct Image    *DxImage;
+
+
+       if (!(DxImage = NewImageObject (RIGHTIMAGE)))
+               return NULL;
+
+       if (!(DxButton = (struct Gadget *)NewObject (ScrollButtonClass, NULL,
+               GA_ID,                          id,
+               GA_RelBottom,           - DxImage->Height + 1,
+               GA_RelRight,            - SizeWidth - DxImage->Width + 1,
+               GA_BottomBorder,        TRUE,
+               /* No need for GA_Width/Height.  buttongclass is smart :) */
+               GA_Image,                       DxImage,
+               ICA_TARGET,                     target,
+               ICA_MAP,                        map,
+               TAG_DONE)))
+               DisposeObject (DxImage);
+
+       return DxButton;
+}
+
+
+
+GLOBALCALL struct Gadget *CreateVSlider (ULONG id, struct Gadget *target, LONG *map, LONG ButtonsSpacing, LONG Place)
+{
+       UWORD horizpos;
+
+       horizpos = (Place == SCROLLERPLACE_LEFT) ? Scr->WBorLeft : (- SizeWidth + 5);
+
+       return ((struct Gadget *)NewObject (NULL, PROPGCLASS,
+               GA_ID,                          id,
+               GA_Top,                         OffY + 1,
+               (Place == SCROLLERPLACE_LEFT) ? GA_Left : GA_RelRight, horizpos,
+               GA_Width,                       SizeWidth - 8,
+               GA_RelHeight,           - OffY - Scr->WBorBottom - SizeHeight
+                                                       - ButtonsSpacing,
+               (Place == SCROLLERPLACE_LEFT) ? GA_LeftBorder : GA_RightBorder, TRUE,
+
+               PGA_NewLook,            TRUE,
+
+               /* Borderless sliders do only look good with newlook screens */
+               PGA_Borderless,         ((DrawInfo->dri_Flags & DRIF_NEWLOOK) && DrawInfo->dri_Depth != 1),
+
+               ICA_TARGET,                     target,
+               ICA_MAP,                        map,
+
+               TAG_DONE));
+}
+
+
+
+GLOBALCALL struct Gadget *CreateHSlider (ULONG id, struct Gadget *target, LONG *map, LONG ButtonsSpacing)
+{
+       struct Gadget   *HSlider;
+
+       if (!(HSlider = (struct Gadget *)NewObject (NULL, PROPGCLASS,
+               GA_ID,                          id,
+               GA_Left,                        OffX - 1,
+               GA_RelBottom,           - SizeHeight + ((SizeHeight > 15) ? 4 : 3),
+               GA_RelWidth,            - OffX - SizeWidth
+                                                       - ButtonsSpacing,
+               GA_Height,                      SizeHeight - ((SizeHeight > 15)  ? 6 : 4),
+               GA_BottomBorder,        TRUE,
+
+               PGA_NewLook,            TRUE,
+               PGA_Borderless,         ((DrawInfo->dri_Flags & DRIF_NEWLOOK) && DrawInfo->dri_Depth != 1),
+               PGA_Freedom,            FREEHORIZ,
+
+               ICA_TARGET,                     target,
+               ICA_MAP,                        map,
+
+               TAG_DONE)))
+               return NULL;
+       return HSlider;
+}
diff --git a/CustomClasses.h b/CustomClasses.h
new file mode 100644 (file)
index 0000000..3dcfed8
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+**     CustomClasses.h
+**
+**     Copyright (C) 1995,96 by Bernardo Innocenti
+**
+**     Scroller button class built on top of the "buttongclass".
+**     VImage class built on top of the "imageclass".
+*/
+
+/* Function prototypes */
+
+Class  *InitScrollButtonClass  (void);
+BOOL    FreeScrollButtonClass  (Class *);
+Class  *InitVImageClass                (void);
+BOOL    FreeVImageClass                (Class *);
+
+
+/* Values for SYSIA_Which attribute of ImageButtonClass */
+#define IM_PLAY 0
+#define IM_STOP 1
+#define IM_REW 2
+#define IM_FWD 3
+#define IM_PICK 4
diff --git a/Cx.c b/Cx.c
new file mode 100644 (file)
index 0000000..4b841fd
--- /dev/null
+++ b/Cx.c
@@ -0,0 +1,171 @@
+/*
+**     Cx.c
+**
+**     Copyright (C) 1994,95,96,97,98 Bernardo Innocenti
+**
+**     Commodity support functions
+*/
+
+
+#include <exec/memory.h>
+#include <libraries/commodities.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/commodities.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+#define CX_POPKEY      'POP!'
+
+
+XDEF BYTE CxPri = 0;
+XDEF UBYTE CxPopKey[32] = "control alt x";
+XDEF BOOL CxPopup = TRUE;
+XDEF ULONG CxSig = 0;
+
+XDEF struct MsgPort *CxPort = NULL;
+XDEF CxObj *MyBroker = NULL;
+
+
+static struct NewBroker MyNewBroker =
+{
+       NB_VERSION,
+       PRGNAME,
+       Version+6,
+       "Module Processing Utility",
+       NBU_DUPLICATE,
+       COF_SHOW_HIDE,
+       0,
+       NULL,
+       0
+};
+
+
+
+GLOBALCALL void HandleCx (void)
+{
+       CxMsg   *cxm;
+       ULONG    type;
+       LONG     id;
+
+       while (cxm = (CxMsg *) GetMsg (CxPort))
+       {
+               type = CxMsgType (cxm);
+               id = CxMsgID (cxm);
+
+               switch (type)
+               {
+                       case CXM_IEVENT:
+                               if (id == CX_POPKEY) DeIconify();
+                               break;
+
+                       case CXM_COMMAND:
+                               switch (id)
+                               {
+                                       case CXCMD_DISABLE:
+                                               ActivateCxObj (MyBroker, FALSE);
+                                               break;
+
+                                       case CXCMD_ENABLE:
+                                               ActivateCxObj (MyBroker, TRUE);
+                                               break;
+
+                                       case CXCMD_APPEAR:
+                                               DeIconify();
+                                               break;
+
+                                       case CXCMD_DISAPPEAR:
+                                               CloseDownScreen();
+                                               break;
+
+                                       case CXCMD_KILL:
+                                               Quit = TRUE;
+                                               GuiSwitches.AskExit = FALSE;
+                                               break;
+
+                                       default:
+                                               break;
+
+                               }       /* End Switch (id) */
+
+                       default:
+                               break;
+
+               }       /* End Switch (type) */
+
+               ReplyMsg ((struct Message *) cxm);
+
+       }       /* End While (GetMsg()) */
+}
+
+
+
+GLOBALCALL LONG SetupCx (void)
+{
+       CxObj *filter, *sender;
+
+       if (!CxPopKey[0]) return RETURN_FAIL;
+
+       if (!(CxBase = OpenLibrary ("commodities.library", 37L)))
+               return RETURN_FAIL;
+
+       if (!(CxPort = CreateMsgPort ()))
+       {
+               CleanupCx();
+               return ERROR_NO_FREE_STORE;
+       }
+
+       CxSig = 1 << CxPort->mp_SigBit;
+       Signals |= CxSig;
+
+       MyNewBroker.nb_Pri = CxPri;
+       MyNewBroker.nb_Port = CxPort;
+
+       if (!(MyBroker = CxBroker (&MyNewBroker, NULL)))
+       {
+               CleanupCx();
+               return RETURN_FAIL;
+       }
+
+       /* Create PopKey Filter/Sender */
+
+       if (filter = CxFilter (CxPopKey))
+       {
+               if (CxObjError (filter) & COERR_BADFILTER)
+                       ShowMessage (MSG_BAD_HOTKEY);
+
+               AttachCxObj (MyBroker, filter);
+
+               if (sender = CxSender (CxPort, CX_POPKEY))
+                       AttachCxObj (filter, sender);
+       }
+
+       ActivateCxObj (MyBroker, TRUE);
+
+       return RETURN_OK;
+}
+
+
+
+GLOBALCALL void CleanupCx (void)
+{
+       if (CxBase)
+       {
+               if (MyBroker)
+                       { DeleteCxObjAll (MyBroker); MyBroker = NULL; }
+
+               if (CxPort)
+               {
+                       KillMsgPort (CxPort);
+                       CxPort = NULL;
+                       Signals &= ~CxSig;
+                       CxSig = 0;
+               }
+
+               CloseLibrary (CxBase); CxBase = NULL;
+       }
+}
diff --git a/Debug.h b/Debug.h
new file mode 100644 (file)
index 0000000..ba7b825
--- /dev/null
+++ b/Debug.h
@@ -0,0 +1,103 @@
+#ifndef DEBUG_H
+#define DEBUG_H
+/*
+**     $VER: Debug.h 2.2 (19.9.98)
+**
+**     Copyright (C) 1995,96,97 Bernardo Innocenti. All rights reserved.
+**
+**     Use 4 chars wide TABs to read this file
+**
+**     Some handy debug macros which are automatically excluded when the
+**     DEBUG preprocessor sysmbol is not defined. To make debug executables,
+**     link with debug.lib or any module containing the kprintf() function.
+**
+**     Here is a short description of the macros defined below:
+**
+**     ILLEGAL
+**             Output an inline "ILLEGAL" 68K opcode, which will
+**             be interpreted as a breakpoint by most debuggers.
+**
+**     DBPRINTF
+**             Output a formatted string to the debug console. This
+**             macro uses the debug.lib kprintf() function by default.
+**
+**     ASSERT(x)
+**             Do nothing if the expression <x> evalutates to a
+**             non-zero value, output a debug message otherwise.
+**
+**     ASSERT_VALID(x)
+**             Checks if the expression <x> points to a valid
+**             memory location, and outputs a debug message
+**             otherwise. A NULL pointer is considered VALID.
+**
+**     ASSERT_VALIDNO0(x)
+**             Checks if the expression <x> points to a valid
+**             memory location, and outputs a debug message
+**             otherwise. A NULL pointer is considered INVALID.
+**
+**     DB(x)
+**             Compile the expression <x> when making a debug
+**             executable, leave it out otherwise.
+*/
+
+#ifdef DEBUG
+
+       /* Needed for TypeOfMem() */
+       #ifndef  PROTO_EXEC_H
+       #include <proto/exec.h>
+       #endif /* PROTO_EXEC_H */
+
+       #if defined(__SASC)
+
+               extern void __builtin_emit (int);
+               // #define ILLEGAL __builtin_emit(0x4AFC)
+               #define ILLEGAL 0
+               STDARGS extern void kprintf (const char *, ...);
+
+       #elif defined(__GNUC__)
+
+               /* Currently, there is no kprintf() in libamiga.a */
+               #define kprintf printf
+
+               /* GCC doesn't accept asm statemnts in the middle of an
+                * expression such as `a ? b : asm("something")'.
+                */
+               #define ILLEGAL illegal()
+               static inline int illegal(void) { asm ("illegal"); return 0; }
+               extern void STDARGS FORMATCALL(printf,1,2) kprintf (const char *, ...);
+
+       #else
+               #error Please add compiler specific definitions for your compiler
+       #endif
+
+       #if defined(__SASC) || defined (__GNUC__)
+
+               /* common definitions for ASSERT and DB macros */
+
+               #define DBPRINTF kprintf
+
+               #define ASSERT(x) ( (x) ? 0 :                                                                   \
+                       ( DBPRINTF ("\x07%s, %ld: assertion failed: " #x "\n",          \
+                       __FILE__, __LINE__) , ILLEGAL ) );
+
+               #define ASSERT_VALID(x) ( ((((APTR)(x)) == NULL) ||                             \
+                       (((LONG)(x) > 1024) &&  TypeOfMem ((APTR)(x)))) ? 0 :           \
+                       ( DBPRINTF ("\x07%s, %ld: bad address: " #x " = $%lx\n",        \
+                       __FILE__, __LINE__, (APTR)(x)) , ILLEGAL ) );
+
+               #define ASSERT_VALIDNO0(x) ( (((LONG)(x) > 1024) &&                             \
+                       TypeOfMem ((APTR)(x))) ? 0 :                                                            \
+                       ( DBPRINTF ("\x07%s, %ld: bad address: " #x " = $%lx\n",        \
+                       __FILE__, __LINE__, (APTR)(x)) , ILLEGAL ) );
+
+               #define DB(x) x
+       #endif
+
+#else
+       #define ASSERT_VALID(x)
+       #define ASSERT_VALIDNO0(x)
+       #define ASSERT(x)
+       #define DB(x)
+#endif /* DEBUG */
+
+#endif /* !DEBUG_H */
diff --git a/FFT.c b/FFT.c
new file mode 100644 (file)
index 0000000..cf85b57
--- /dev/null
+++ b/FFT.c
@@ -0,0 +1,279 @@
+#include <math.h>
+#include <m68881.h>
+
+
+
+
+static void FFT (float data[], LONG nn, LONG isign);
+static void RealFFT (float data[], LONG n, LONG isign);
+static void MyFFT (double *real_vet, double *im_vet, long n, int m, int ind, double cf);
+
+
+
+#ifdef __SASC
+
+/* Disable SAS/C floating point error handler */
+void __stdargs _CXFERR(int code)
+{
+}
+
+#endif /* __SASC */
+
+#if 0
+GLOBALCALL void Filter (WORD *data, LONG len)
+{
+       float *fftdata;
+       LONG i, nn;
+
+
+       /* Calculate nearest power of 2 */
+       for (i = len-1, nn = 1; i != 0; i >>= 1)
+               nn <<= 1;
+
+       if (!(fftdata = AllocVec (sizeof (float) * nn * 2, MEMF_CLEAR)))
+               return;
+
+       /* Fill array of complex numbers (imaginary part is always set to 0) */
+       for (i = 0; i < len; i++)
+               fftdata[i] = (float) data[i];
+
+
+       /* Obtain frequency spectrum */
+       RealFFT (fftdata, nn>>1, 1);
+
+       /* Low-pass Filter */
+       for (i = 0; i < nn; i++)
+               fftdata[i*2] *= (i/nn) * 4;
+
+       /* Return to time domain */
+       RealFFT (fftdata, nn>>1, -1);
+       for (i = 0; i < len; i++)
+               fftdata[i] /= nn;
+
+       /* Put filtered data */
+       for (i = 0; i < len; i++)
+               data[i] = (WORD) fftdata[i];
+
+       FreeVec (fftdata);
+}
+#endif
+
+
+GLOBALCALL void Filter (WORD *data, LONG len)
+{
+       double *fftdata;
+       LONG i, nn, mm;
+
+
+       /* Calculate nearest power of 2 */
+       for (i = len-1, nn = 1, mm = 0; i != 0; i >>= 1)
+       {
+               nn <<= 1;
+               mm++;
+       }
+
+       if (!(fftdata = AllocVec (sizeof (double) * nn * 2, MEMF_CLEAR)))
+               return;
+
+       /* Fill array of complex numbers (imaginary part is always set to 0) */
+       for (i = 0; i < len; i++)
+               fftdata[i] = (float) data[i];
+
+
+       /* Obtain frequency spectrum */
+       MyFFT (fftdata, fftdata + nn, nn>>1, mm, +1, 1.0/32768.0);
+
+       /* Low-pass Filter */
+//     for (i = 0; i < nn; i++)
+//             fftdata[i] *= (i/nn) * 4;
+
+       /* Return to time domain */
+       MyFFT (fftdata, fftdata + nn, nn>>1, mm, -1, 1.0);
+
+//     for (i = 0; i < len; i++)
+//             fftdata[i] /= nn;
+
+       /* Put filtered data */
+       for (i = 0; i < len; i++)
+               data[i] = (WORD) fftdata[i];
+
+       FreeVec (fftdata);
+}
+
+
+#if 0
+#define SWAP(a,b) tempr=(a);(a)=(b);(b)=tempr
+
+static void FFT (float data[], LONG nn, LONG isign)
+{
+       int n,mmax,m,j,istep,i;
+       double wtemp,wr,wpr,wpi,wi,theta;
+       float tempr,tempi;
+
+       n=nn << 1;
+       j=1;
+       for (i=1;i<n;i+=2)
+       {
+               if (j > i)
+               {
+                       SWAP(data[j],data[i]);
+                       SWAP(data[j+1],data[i+1]);
+               }
+               m=n >> 1;
+               while (m >= 2 && j > m)
+               {
+                       j -= m;
+                       m >>= 1;
+               }
+               j += m;
+       }
+       mmax = 2;
+       while (n > mmax)
+       {
+               istep=2*mmax;
+               theta=6.28318530717959/(isign*mmax);
+               wtemp=sin(0.5*theta);
+               wpr = -2.0*wtemp*wtemp;
+               wpi=sin(theta);
+               wr=1.0;
+               wi=0.0;
+               for (m=1;m<mmax;m+=2)
+               {
+                       for (i=m;i<=n;i+=istep)
+                       {
+                               j = i+mmax;
+                               tempr=wr*data[j]-wi*data[j+1];
+                               tempi=wr*data[j+1]+wi*data[j];
+                               data[j]=data[i]-tempr;
+                               data[j+1]=data[i+1]-tempi;
+                               data[i] += tempr;
+                               data[i+1] += tempi;
+                       }
+                       wr=(wtemp=wr)*wpr-wi*wpi+wr;
+                       wi=wi*wpr+wtemp*wpi+wi;
+               }
+               mmax=istep;
+       }
+}
+
+
+static void RealFFT (float data[], LONG n, LONG isign)
+{
+       int i,i1,i2,i3,i4,n2p3;
+       float c1=0.5,c2,h1r,h1i,h2r,h2i;
+       double wr,wi,wpr,wpi,wtemp,theta;
+       void four1();
+
+       theta=3.141592653589793/(double) n;
+       if (isign == 1)
+       {
+               c2 = -0.5;
+               FFT (data,n,1);
+       }
+       else
+       {
+               c2=0.5;
+               theta = -theta;
+       }
+
+       wtemp=sin(0.5*theta);
+       wpr = -2.0*wtemp*wtemp;
+       wpi=sin(theta);
+       wr=1.0+wpr;
+       wi=wpi;
+       n2p3=2*n+3;
+       for (i=2;i<=n/2;i++)
+       {
+               i4=1+(i3=n2p3-(i2=1+(i1=i+i-1)));
+               h1r=c1*(data[i1]+data[i3]);
+               h1i=c1*(data[i2]-data[i4]);
+               h2r = -c2*(data[i2]+data[i4]);
+               h2i=c2*(data[i1]-data[i3]);
+               data[i1]=h1r+wr*h2r-wi*h2i;
+               data[i2]=h1i+wr*h2i+wi*h2r;
+               data[i3]=h1r-wr*h2r+wi*h2i;
+               data[i4] = -h1i+wr*h2i+wi*h2r;
+               wr=(wtemp=wr)*wpr-wi*wpi+wr;
+               wi=wi*wpr+wtemp*wpi+wi;
+       }
+       if (isign == 1)
+       {
+               data[1] = (h1r=data[1])+data[2];
+               data[2] = h1r-data[2];
+       }
+       else
+       {
+               data[1]=c1*((h1r=data[1])+data[2]);
+               data[2]=c1*(h1r-data[2]);
+               FFT (data,n,-1);
+       }
+}
+
+
+#undef SWAP
+#endif
+
+static void MyFFT (double *real_vet, double *im_vet, long n, int m, int ind, double cf)
+{
+       long l, i, j, nbut, ngrup, nc;
+       long but, grup;
+       long primo, secondo;
+       double w_real, w_im;
+       double t_real, t_im;
+       double arg;
+
+       // Ordinamento binario del vettore
+       j = 0;
+       nc = n >> 1;
+       for (i = 0 ; i< n-1; i++)
+       {
+               if (i < j)
+               {
+                       t_real = real_vet[j];
+                       t_im = im_vet[j];
+                       real_vet[j] = real_vet[i];
+                       im_vet[j] = im_vet[i];
+                       real_vet[i] = t_real;
+                       im_vet[i] = t_im;
+               }
+               l = nc;
+               while(l <= j)
+               {
+                       j -= l;
+                       l >>= 1;
+               }
+               j+=l;
+       }
+
+       //Calcolo FFT
+       for(l=0; l<m; l++)                                              //Ciclo esterno Log2(N)
+       {
+               nbut = (1L) << l;
+               ngrup = nc / nbut;
+               for(but = 0; but < nbut; but++)                         //Ciclo medio: numero di farfalle
+               {
+                       arg = PI * but / nbut;                  // M_PI e' la costante PI Greco
+                       w_real = cos(arg);
+                       w_im = (-1)*ind*sin(arg);
+                       for(grup = 0; grup < ngrup; grup++)             //Ciclo interno: numero di gruppi
+                       {
+                               primo = 2*nbut*grup + but;
+                               secondo = primo + nbut;
+                               t_real = w_real * real_vet[secondo] - w_im * im_vet[secondo];
+                               t_im = w_real * im_vet[secondo] + w_im * real_vet[secondo];
+                               real_vet[secondo] = real_vet[primo] - t_real;
+                               im_vet[secondo] = im_vet[primo] - t_im;
+                               real_vet[primo] += t_real;
+                               im_vet[primo] += t_im;
+                       }
+               }
+       }
+
+       //Normalizzazione
+       if (cf != 1.0)                          // Modifica di Bernardo
+               for(i = 0; i < n; i++)
+               {
+                       real_vet[i] *= cf;
+                       im_vet[i] *= cf;
+               }
+}
diff --git a/FakePlayer.asm b/FakePlayer.asm
new file mode 100644 (file)
index 0000000..792df66
--- /dev/null
@@ -0,0 +1,20 @@
+**
+**     XModule replay routine 0.1
+**
+**     Copyright (C) 1995 Bernardo Innocenti
+**
+**
+**     This code is based on:
+**
+**     PS3M 0.950 020+ 14-bit stereo surround version ;) 19.03.1995
+**     Copyright (c) Jarno Paananen a.k.a. Guru / S2 1994-95
+**
+**     Some portions based on STMIK 0.9ß by Sami Tammilehto / PSI of Future Crew
+**
+
+ Sorry, the source for the XModule player is not included because I have
+been asked not to distribute it until further notice.
+
+ Many thanks to Jarno Paanen for giving me the right to use his 32 channel
+mixing engine.  Without his kindness, developing the player would have
+required over one year of hard work.
diff --git a/Gadgets/Makefile b/Gadgets/Makefile
new file mode 100644 (file)
index 0000000..f369893
--- /dev/null
@@ -0,0 +1,62 @@
+###########################################################
+# Make the stuff in the gadgets/ subdirectory
+###########################################################
+
+include $(TOP)/config.mk
+
+GADGETS                                := pattedit.gadget
+GADGETS_CFLAGS         := CODE=NEAR DATA=FARONLY STRSECT=CODE
+
+PATTEDITNAME           := "pattedit.gadget"
+PATTEDITVERSION                := 2
+PATTEDITREVISION       := 1
+PATTEDITFLAGS          := $(GADGETS_FLAGS) \
+       DEF LIBNAME=$(PATTEDITNAME) \
+       DEF LIBVERSION=$(PATTEDITVERSION) \
+       DEF LIBREVISION=$(PATTEDITREVISION)
+
+
+all: $(GADGETS)
+
+clean:
+       -Delete *.map *.o *.gst *.xref *.gadget
+
+.PHONY: all clean
+
+
+PattEditLib.o: $(TOP)/RomTag.asm
+       $(AS) $(ASFLAGS) $(TOP)/RomTag.asm TO $@ \
+               SET "LIBVERSION=$(PATTEDITVERSION),LIBREVISION=$(PATTEDITREVISION),BOOPSICLASS"
+
+
+PattEditLib_020.o: $(TOP)/RomTag.asm
+       $(AS) $(ASFLAGS) $(TOP)/RomTag.asm TO $@ MACHINE=68020 \
+               SET "LIBVERSION=$(PATTEDITVERSION),LIBREVISION=$(PATTEDITREVISION),BOOPSICLASS"
+
+PattEditLib_020_OS30.o: $(TOP)/RomTag.asm
+       $(AS) $(ASFLAGS) $(TOP)/RomTag.asm TO $@ MACHINE=68020 \
+               SET "LIBVERSION=$(PATTEDITVERSION),LIBREVISION=$(PATTEDITREVISION),BOOPSICLASS,OS30_ONLY"
+
+PattEditClass.o: PattEditClass.c
+       $(CC) PattEditClass.c OBJNAME $@ $(CFLAGS) $(PATTEDITFLAGS) CPU=68000 DEF "BUILDMODE=*"[M68000 OS2.0]*""
+
+PattEditClass_020.o: PattEditClass.c
+       $(CC) PattEditClass.c OBJNAME $@ $(CFLAGS) $(PATTEDITFLAGS) CPU=68020 DEF "BUILDMODE=*"[M68020 OS2.0]*""
+
+PattEditClass_020_OS30.o: PattEditClass.c
+       $(CC) PattEditClass.c OBJNAME $@ $(CFLAGS) $(PATTEDITFLAGS) CPU=68020 DEF "BUILDMODE=*"[M68020 OS3.0]*"" DEF OS30_ONLY
+
+PattEditClassAsm.o: PattEditClassAsm.asm
+       $(AS) $(ASFLAGS) PattEditClassAsm.asm
+
+pattedit.gadget: PattEditClass.o PattEditLib.o PattEditClassAsm.o
+       $(LD) $(LDFLAGS) FROM PattEditLib.o PattEditClass.o PattEditClassAsm.o $(LIBS) TO $@
+       Protect $@ -e
+       FileNote $@ "MC68000 version"
+       Avail FLUSH >NIL:
+
+pattedit.gadget_020: PattEditClass_020.o PattEditLib_020.o PattEditClassAsm.o
+       $(LD) $(LDFLAGS) FROM PattEditLib_020.o PattEditClass_020.o PattEditClassAsm.o $(LIBS) TO $@
+
+pattedit.gadget_020_OS30: PattEditClass_020_OS30.o PattEditLib_020_OS30.o PattEditClassAsm.o
+       $(LD) $(LDFLAGS) FROM PattEditLib_020_OS30.o PattEditClass_020_OS30.o PattEditClassAsm.o $(LIBS) TO $@
diff --git a/Gadgets/PattEditClass.c b/Gadgets/PattEditClass.c
new file mode 100644 (file)
index 0000000..34c9ab2
--- /dev/null
@@ -0,0 +1,3049 @@
+/*
+**     PattEditClass.c
+**
+**     Copyright (C) 1995,96,97,98 Bernardo Innocenti
+**
+**     Pattern Editor gadget class
+*/
+
+#include <exec/memory.h>
+#include <exec/ports.h>
+#include <utility/tagitem.h>
+#include <utility/hooks.h>
+#include <devices/inputevent.h>
+#include <libraries/SongClass.h>
+#include <libraries/PattEditClass.h>
+#include <libraries/xmodule.h>
+
+#include <intuition/intuition.h>
+#include <intuition/intuitionbase.h>
+#include <intuition/classes.h>
+#include <intuition/classusr.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/imageclass.h>
+#include <graphics/gfxbase.h>
+#include <graphics/gfxmacros.h>
+#include <graphics/text.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/graphics.h>
+#include <proto/utility.h>
+#include <proto/keymap.h>
+
+
+#include "CompilerSpecific.h"
+#include "Debug.h"
+#include "ListMacros.h"
+
+#ifdef OS30_ONLY
+       #define WANTED_LIBVER   39
+#else
+       #define WANTED_LIBVER   37
+#endif
+
+
+/* Some handy definitions missing in <devices/inputevent.h> */
+#define IEQUALIFIER_SHIFT      (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)
+#define IEQUALIFIER_ALT                (IEQUALIFIER_LALT | IEQUALIFIER_RALT)
+#define IEQUALIFIER_COMMAND    (IEQUALIFIER_LCOMMAND | IEQUALIFIER_RCOMMAND)
+
+
+
+/* Scrolling control */
+#define SCROLLED_VERT  1
+#define SCROLLED_HORIZ 2
+
+
+
+/* Private class instance data */
+
+struct PattEditData
+{
+       struct Pattern  *Patt;
+       struct TextFont *EditorFont;
+
+       /* These are the actual Gadget position and size,
+        * regardless of any GREL_#? flag.
+        */
+       struct IBox              GBounds;
+
+       /* These are the actual editing area position and size. */
+       struct IBox              TBounds;
+
+       UWORD                    FontXSize;
+       UWORD                    FontYSize;
+
+       /* Pens used to draw various pattern editor elements */
+       ULONG                    BGPen;
+       ULONG                    TextPen;
+       ULONG                    LinesPen;
+       ULONG                    TinyLinesPen;
+
+       ULONG                    Flags;                 /* See definitions in <PattEditClass.h>                 */
+
+       /* Routine used to convert a note into its ASCII representation
+        * This routine is passed the pointer to the note and a buffer
+        * with <TrackChars> characters to fill in.
+        */
+       ASMCALL void    (*Note2ASCIIFunc)(REG(a1, UBYTE *s), REG(a2, struct Note *note));
+
+       UWORD                   TrackWidth;             /* Width of a track in pixels   */
+       UWORD                   TrackChars;             /* Width of a track in chars    */
+
+
+       /* How many tracks and lines we can fit in the gadget bounds */
+       UWORD                    DisplayTracks;
+       UWORD                    DisplayLines;
+
+       WORD                     LeftTrack;
+       WORD                     TopLine;
+
+       /* Current cursor position */
+       WORD                     Track;
+       WORD                     Line;
+       WORD                     Column;
+
+       UWORD                    CurrentInst;
+
+       UWORD                    CursState;             /* IDS_NORMAL, IDS_DISABLED or IDS_BUSY */
+       UWORD                    CursLinePos;   /* Cursor line position (0 = not drawn) */
+       struct Rectangle CursRect;              /* Cursor Position to erase it quickly. */
+
+       /* Cursor advancement */
+       WORD                     AdvanceTracks;
+       WORD                     AdvanceLines;
+
+       /* Range marking info */
+       UWORD                    RangeStartTrack;
+       UWORD                    RangeStartLine;
+       UWORD                    RangeEndTrack;
+       UWORD                    RangeEndLine;
+
+       /* Backup cursor position for mouse right button undo operation */
+       WORD                     BackupTrack;
+       WORD                     BackupLine;
+       WORD                     BackupColumn;
+
+       /* This variable holds a counter for the optimized scroller update.
+        * When the pattern is scrolling, updates are only sent after
+        * a specific amount of scrolling operations have occurred.
+        */
+       UWORD                    SliderCounter;
+
+       /* The following two are for track resizing.  ResizeXStart
+        * is the horizontal mouse position when the resize operation
+        * was started. ResizeChars is the last number of chars set.
+        */
+       UWORD                    ResizeXStart;
+       UWORD                    ResizeChars;
+
+       struct Rectangle RangeRect;             /* Backup of range rect to erase it quickly. */
+
+       /* Undo/Redo support */
+       ULONG                    Changes;
+       ULONG                    UndoCount;
+       ULONG                    UndoMem;
+       ULONG                    MaxUndoLevels;
+       ULONG                    MaxUndoMem;
+       struct MinList   UndoList;
+       struct MinList   RedoList;
+
+       /* For testing double/triple click */
+       ULONG                    DoubleClickSeconds;
+       ULONG                    DoubleClickMicros;
+       ULONG                    TripleClickSeconds;
+       ULONG                    TripleClickMicros;
+
+       /* Experimental: timer.device stuff */
+//     struct timerequest      TimerIO;
+//     struct MsgPort          TimerPort;
+//     struct Interrupt        TimerInt;
+};
+
+
+/* This structure holds an entry for the Undo/Redo buffer. */
+
+struct UndoNode
+{
+       struct MinNode          Link;
+       UWORD                           Line,
+                                               Track;
+       struct Note                     OldNote;
+};
+
+
+
+/* Function prototypes */
+
+static ULONG HOOKCALL PattEditDispatcher (
+       REG(a0, Class *cl),
+       REG(a2, struct ExtGadget *g),
+       REG(a1, Msg msg));
+static void            GetGadgetBox    (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *rect);
+static BOOL            CalcDisplaySize (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *gpi);
+static void            SaveUndo                (struct PattEditData *ped);
+static BOOL            UndoChange              (struct PattEditData *ped);
+static BOOL            RedoChange              (struct PattEditData *ped);
+static void            FreeUndoBuffers (struct PattEditData *ped, BOOL freeall);
+static BOOL            MoveCursor              (struct PattEditData *ped, WORD x, WORD y);
+static void            EraseCursor             (struct RastPort *rp, struct PattEditData *ped);
+static UWORD   DrawCursor              (struct RastPort *rp, struct PattEditData *ped, struct ExtGadget *g);
+static void            DrawRange               (struct RastPort *rp, struct PattEditData *ped);
+static void            ClearRange              (struct RastPort *rp, struct PattEditData *ped);
+static void            RedrawAll               (struct RastPort *rp, struct PattEditData *ped, struct ExtGadget *g);
+static void            RedrawPattern   (struct RastPort *rp, struct PattEditData *ped, struct ExtGadget *g);
+static void            DrawTrackSeparators (struct RastPort *rp, struct PattEditData *ped, UWORD width);
+static void            DrawTrackNumbers(struct RastPort *rp, struct PattEditData *ped);
+static void            DrawPatternLines(struct RastPort *rp, struct PattEditData *ped, UWORD min, UWORD max);
+static void            DrawNote                (struct RastPort *rp, struct PattEditData *ped);
+void ASMCALL Note2ASCII                        (REG(a1, UBYTE *s), REG(a2, struct Note *note));
+void ASMCALL Note2ASCIIBlank0  (REG(a1, UBYTE *s), REG(a2, struct Note *note));
+static UWORD   ScrollPattern   (struct RastPort *rp, struct PattEditData *ped, struct ExtGadget *g,
+                                                               UWORD lefttrack, UWORD topline);
+static void            NotifyCursor    (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags);
+static void            NotifyVSlider   (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags);
+static void            NotifyHSlider   (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags);
+static void            NotifySliders   (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags);
+
+//static void ASMCALL TimerIntServer (register REG(a1, struct ExtGadget *g));
+
+HOOKCALL struct ClassLibrary   *_UserLibInit           (REG(a6, struct ClassLibrary *mybase));
+HOOKCALL struct ClassLibrary   *_UserLibCleanup        (REG(a6, struct ClassLibrary *mybase));
+HOOKCALL Class                                 *_GetEngine                     (REG(a6, struct ClassLibrary *mybase));
+
+
+
+/* Library data */
+
+const UBYTE LibName[] = LIBNAME;
+const UBYTE LibVer[] = { '$', 'V', 'E', 'R', ':', ' ' };
+const UBYTE LibId[] = LIBNAME " 2.1 (8.4.97) " BUILDMODE " © 1995,96,97 by Bernardo Innocenti\n";
+
+
+
+/* Local data */
+
+#ifdef PORTABLE
+static const ULONG TextNotes[MAXNOTES] =
+{
+       ' -  ',
+       'C-0 ', 'C#0 ', 'D-0 ', 'D#0 ', 'E-0 ', 'F-0 ',
+       'F#0 ', 'G-0 ', 'G#0 ', 'A-0 ', 'A#0 ', 'B-0 ',
+
+       'C-1 ', 'C#1 ', 'D-1 ', 'D#1 ', 'E-1 ', 'F-1 ',
+       'F#1 ', 'G-1 ', 'G#1 ', 'A-1 ', 'A#1 ', 'B-1 ',
+
+       'C-2 ', 'C#2 ', 'D-2 ', 'D#2 ', 'E-2 ', 'F-2 ',
+       'F#2 ', 'G-2 ', 'G#2 ', 'A-2 ', 'A#2 ', 'B-2 ',
+
+       'C-3 ', 'C#3 ', 'D-3 ', 'D#3 ', 'E-3 ', 'F-3 ',
+       'F#3 ', 'G-3 ', 'G#3 ', 'A-3 ', 'A#3 ', 'B-3 ',
+
+       'C-4 ', 'C#4 ', 'D-4 ', 'D#4 ', 'E-4 ', 'F-4 ',
+       'F#4 ', 'G-4 ', 'G#4 ', 'A-4 ', 'A#4 ', 'B-4 ',
+
+       'C-5 ', 'C#5 ', 'D-5 ', 'D#5 ', 'E-5 ', 'F-5 ',
+       'F#5 ', 'G-5 ', 'G#5 ', 'A-5 ', 'A#5 ', 'B-5 '
+};
+#endif /* PORTABLE */
+
+
+/* Keyboard rawkeys -> notes conversion table */
+
+static const UBYTE KeyNotes0[10] = { 0, 26, 28,  0, 31, 33, 35,  0, 38, 40};   /* (1..0) */
+static const UBYTE KeyNotes1[10] = {25, 27, 29, 30, 32, 34, 36, 37, 39, 41};   /* (Q..]) */
+static const UBYTE KeyNotes2[10] = { 0, 14, 16,  0, 19, 21, 23,  0, 26, 28};   /* (A..') */
+static const UBYTE KeyNotes3[10] = {13, 15, 17, 18, 20, 22, 24, 25, 27, 29};   /* (Z../) */
+
+static const UBYTE HexValues[20]       = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', 'G', 'H', 'I', 'J'};
+static const UBYTE HexValuesNo0[20]    = {' ','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', 'G', 'H', 'I', 'J'};
+
+
+
+/* Cursor patterns - Used in EraseCursor() and DrawCursor() */
+
+static UWORD GhostPattern[]    = { 0xAAAA, 0x5555 };
+static UWORD MarkPattern[]     = { 0xFFFF, 0x0000 };
+
+
+
+/*****************/
+/* Library bases */
+/*****************/
+
+struct ExecBase                        *SysBase                = NULL;
+struct IntuitionBase   *IntuitionBase  = NULL;
+struct GfxBase                 *GfxBase                = NULL;
+struct UtilityBase             *UtilityBase    = NULL;
+struct Library                 *KeymapBase             = NULL;
+
+
+static ULONG HOOKCALL PattEditDispatcher (
+       REG(a0, Class *cl),
+       REG(a2, struct ExtGadget *g),
+       REG(a1, Msg msg))
+
+/* PattEdit class dispatcher entrypoint. Handle BOOPSI messages.
+ */
+{
+       struct RastPort         *rp;
+       struct PattEditData     *ped;
+       struct TagItem          *ti;
+       ULONG result = 0;
+
+
+       switch (msg->MethodID)
+       {
+               case GM_GOACTIVE:
+
+                       ped = INST_DATA (cl, g);
+
+                       if (!ped->Patt)
+                       {
+                               result = GMR_NOREUSE;
+                               break;
+                       }
+
+                       g->Flags |= GFLG_SELECTED;
+
+                       /* Render active cursor */
+                       if (rp = ObtainGIRPort (((struct gpInput *)msg)->gpi_GInfo))
+                       {
+                               DrawCursor (rp, ped, g);
+                               ReleaseGIRPort (rp);
+                       }
+
+                       /* Do not process InputEvent when the gadget has been
+                        * activated by ActivateGadget().
+                        */
+                       if (!((struct gpInput *)msg)->gpi_IEvent)
+                       {
+                               result = GMR_MEACTIVE;
+                               break;
+                       }
+
+                       /* Note: The input event that triggered the gadget
+                        * activation (usually a mouse click) should be passed
+                        * to the GM_HANDLEINPUT method, so we fall down to it.
+                        */
+
+               case GM_HANDLEINPUT:
+               {
+                       struct InputEvent *ie = ((struct gpInput *)msg)->gpi_IEvent;
+                       UWORD topline, lefttrack;
+                       WORD    MouseX, MouseY;         /* Mouse coordinates relative to editing area bounds */
+                       BOOL    moved           = FALSE,
+                                       change_note     = FALSE,
+                                       scroll_pattern = FALSE;
+                       UWORD   scrolled        = 0;
+
+                       result = GMR_MEACTIVE;
+                       ped = INST_DATA (cl, g);
+
+                       /* MouseX and MouseY represent the mouse position inside the
+                        * editing area of the gadget.
+                        */
+                       MouseX = ((struct gpInput *)msg)->gpi_Mouse.X + ped->GBounds.Left - ped->TBounds.Left;
+                       MouseY = ((struct gpInput *)msg)->gpi_Mouse.Y + ped->GBounds.Top - ped->TBounds.Top;
+
+                       switch (ie->ie_Class)
+                       {
+                               case IECLASS_TIMER:
+
+                                       /* Timer events are used to keep scrolling
+                                        * when the mouse is outside the bounds of the
+                                        * gadget.  When a timer event is recevied and
+                                        * the mouse button is pressed, we scroll the
+                                        * cursor.
+                                        */
+                                               if (ped->Flags & PEF_DRAGGING)
+                                                       moved = MoveCursor (ped, MouseX, MouseY);
+
+                                       break;
+
+                               case IECLASS_RAWMOUSE:
+                               {
+                                       BOOL double_click = FALSE, triple_click;
+                                       BOOL outside =
+                                               (MouseX < 0) || (MouseX >= ped->TBounds.Width) ||
+                                               (MouseY < 0) || (MouseY >= ped->TBounds.Height);
+
+                                       switch (ie->ie_Code)
+                                       {
+                                               case MENUDOWN:
+
+                                                       /* Abort cursor dragging operation and restore
+                                                        * old cursor position.
+                                                        */
+                                                       if (ped->Flags & PEF_DRAGGING)
+                                                       {
+                                                               ped->Line       = ped->BackupLine;
+                                                               ped->Track      = ped->BackupTrack;
+                                                               ped->Column     = ped->BackupColumn;
+                                                               moved = TRUE;
+                                                               ped->Flags &= ~(PEF_DRAGGING | PEF_SCROLLING);
+                                                       }
+                                                       else if (ped->Flags & PEF_RESIZETRACKS)
+                                                       {
+                                                               if (rp = ObtainGIRPort (((struct gpInput *)msg)->gpi_GInfo))
+                                                               {
+                                                                       SetDrMd (rp, COMPLEMENT);
+                                                                       DrawTrackSeparators (rp, ped, ped->ResizeChars * ped->FontXSize);
+                                                                       ReleaseGIRPort (rp);
+                                                               }
+                                                               ped->Flags &= ~PEF_RESIZETRACKS;
+                                                       }
+                                                       else result = GMR_REUSE;
+
+                                                       break;
+
+                                               case SELECTUP:
+                                                       /* Send final update to sliders */
+                                                       if (ped->Flags & PEF_SCROLLING)
+                                                               scrolled = SCROLLED_VERT | SCROLLED_HORIZ;
+
+                                                       if (ped->Flags & PEF_RESIZETRACKS)
+                                                       {
+                                                               ped->TrackChars = ped->ResizeChars;
+                                                               ped->TrackWidth = ped->TrackChars * ped->FontXSize;
+                                                               CalcDisplaySize(ped, g, ((struct gpInput *)msg)->gpi_GInfo);
+                                                               if (rp = ObtainGIRPort (((struct gpInput *)msg)->gpi_GInfo))
+                                                               {
+                                                                       //SetDrMd (rp, COMPLEMENT);
+                                                                       //DrawTrackSeparators (rp, ped, ped->ResizeChars * ped->FontXSize);
+
+                                                                       DoMethod ((Object *)g, GM_RENDER, ((struct gpInput *)msg)->gpi_GInfo, rp,
+                                                                               GREDRAW_REDRAW);
+
+                                                                       ReleaseGIRPort (rp);
+                                                               }
+                                                       }
+
+                                                       /* Stop scrolling, dragging, resizing tracks and
+                                                        * adjustisting the scroll box.
+                                                        */
+                                                       ped->Flags &= ~(PEF_DRAGGING | PEF_SCROLLING |
+                                                               PEF_RESIZETRACKS | PEF_ADJUSTSCROLLBOX);
+
+                                                       break;
+
+                                               case SELECTDOWN:
+
+                                                       /* Check if mouse click is still over the gadget */
+
+                                                       if (outside)
+                                                       {
+                                                               /* Click outside editing area box:
+                                                                * Check if click is really outside gadget box.
+                                                                * If it is, deactivate the gadget and reuse the event.
+                                                                * Notify application if it is interested in
+                                                                * hearing IDCMP_GADGETUP codes.
+                                                                */
+                                                               MouseX = ((struct gpInput *)msg)->gpi_Mouse.X;
+                                                               MouseY = ((struct gpInput *)msg)->gpi_Mouse.Y;
+
+                                                               if      ((MouseX < 0) || (MouseX >= ped->GBounds.Width) ||
+                                                                       (MouseY < 0) || (MouseY >= ped->GBounds.Height))
+                                                                       result = GMR_REUSE | GMR_VERIFY;
+                                                               break;
+                                                       }
+                                                       else
+                                                       {
+                                                               if (MouseX % ped->TrackWidth >= ped->TrackWidth - 2)
+                                                               {
+                                                                       /* Start track resizing mode */
+                                                                       ped->Flags |= PEF_RESIZETRACKS;
+                                                                       ped->ResizeXStart = MouseX;
+                                                                       ped->ResizeChars = 0;
+                                                               }
+                                                               else
+                                                               {
+                                                                       /* Backup cursor position for undo feature */
+                                                                       ped->BackupLine         = ped->Line;
+                                                                       ped->BackupTrack        = ped->Track;
+                                                                       ped->BackupColumn       = ped->Column;
+
+                                                                       /* Start cursor drag mode */
+                                                                       ped->Flags |= PEF_DRAGGING;
+                                                               }
+                                                       }
+
+                                                       /* Check for double click */
+
+                                                       if (DoubleClick (ped->DoubleClickSeconds, ped->DoubleClickMicros,
+                                                               ie->ie_TimeStamp.tv_secs, ie->ie_TimeStamp.tv_micro))
+                                                       {
+                                                               double_click = TRUE;
+
+                                                               if (DoubleClick (ped->TripleClickSeconds, ped->TripleClickMicros,
+                                                                       ped->DoubleClickSeconds, ped->DoubleClickMicros))
+                                                                       triple_click = TRUE;
+                                                               else
+                                                                       triple_click = FALSE;
+
+                                                               ped->TripleClickSeconds = ped->DoubleClickSeconds;
+                                                               ped->TripleClickMicros  = ped->DoubleClickMicros;
+                                                       }
+
+                                                       ped->DoubleClickSeconds = ie->ie_TimeStamp.tv_secs;
+                                                       ped->DoubleClickMicros  = ie->ie_TimeStamp.tv_micro;
+
+                                                       /* NOTE: I'm falling through here! */
+
+                                               default:
+
+                                                       if ((ped->Flags & PEF_DRAGGING))
+                                                       {
+                                                               if (outside) ped->Flags |= PEF_SCROLLING;
+                                                               else
+                                                               {
+                                                                       if (ped->Flags & PEF_SCROLLING)
+                                                                       {
+                                                                               /* Last update to sync sliders */
+                                                                               scrolled = SCROLLED_VERT | SCROLLED_HORIZ;
+                                                                               ped->Flags &= ~PEF_SCROLLING;
+                                                                       }
+                                                                       moved = MoveCursor (ped, MouseX, MouseY);
+                                                               }
+
+                                                       /*      if ((outside) && (ped->Flags & PEF_DRAGGING) && !(ped->Flags & PEF_SCROLLING))
+                                                               {
+                                                                       ped->Flags |= PEF_SCROLLING;
+
+                                                                       ped->TimerIO.tr_node.io_Command = TR_ADDREQUEST;
+                                                                       ped->TimerIO.tr_time.tv_micro = 100000;
+                                                                       BeginIO ((struct IORequest *)&ped->TimerIO);
+                                                               }       */
+                                                       }
+                                                       else if (ped->Flags & PEF_RESIZETRACKS)
+                                                       {
+                                                               UWORD chars = ped->TrackChars +
+                                                                       (((MouseX - ped->ResizeXStart) / ped->FontXSize)
+                                                                       / ((ped->ResizeXStart / ped->TrackWidth) + 1));
+
+                                                               if (chars < MINTRACKCHARS) chars = MINTRACKCHARS;
+                                                               if (chars > MAXTRACKCHARS) chars = MAXTRACKCHARS;
+
+                                                               if (chars != ped->ResizeChars)
+                                                               {
+                                                                       if (rp = ObtainGIRPort (((struct gpInput *)msg)->gpi_GInfo))
+                                                                       {
+                                                                               SetDrMd (rp, COMPLEMENT);
+
+                                                                               /* Erase old lines      */
+                                                                               DrawTrackSeparators (rp, ped, ped->ResizeChars * ped->FontXSize);
+
+                                                                               /* Redraw lines         */
+                                                                               DrawTrackSeparators (rp, ped, chars * ped->FontXSize);
+                                                                               ped->ResizeChars = chars;
+
+                                                                               ReleaseGIRPort (rp);
+                                                                       }
+                                                               }
+                                                       }
+
+                                                       if (!moved && double_click)
+                                                       {
+                                                               if (ped->Flags & PEF_MARKING)
+                                                               {
+                                                                       if (triple_click && !(ped->Flags & PEF_MARKFULLTRACKS)
+                                                                               && (ped->RangeStartLine == ped->RangeEndLine))
+                                                                       {
+                                                                               /* Start full track marking mode */
+                                                                               ped->Flags |= PEF_MARKFULLTRACKS;
+                                                                               ped->RangeStartLine = 0;
+                                                                               ped->RangeEndLine = ped->Patt->Lines - 1;
+                                                                               moved = TRUE;
+                                                                       }
+                                                                       else
+                                                                               /* Stop marking mode */
+                                                                               ped->Flags &= ~(PEF_MARKING | PEF_MARKFULLTRACKS);
+                                                               }
+                                                               else
+                                                               {
+                                                                       /* Start normal marking mode */
+
+                                                                       ped->Flags |= PEF_MARKING;
+                                                                       ped->RangeStartLine = ped->Line;
+                                                                       ped->RangeStartTrack = ped->Track;
+                                                                       moved = TRUE;
+                                                               }
+
+                                                               if (!(ped->Flags & PEF_MARKING))
+                                                               {
+                                                                       if (rp = ObtainGIRPort (((struct gpInput *)msg)->gpi_GInfo))
+                                                                       {
+                                                                               ClearRange (rp, ped);
+                                                                               ReleaseGIRPort (rp);
+                                                                       }
+                                                                       moved = TRUE;
+                                                               }
+                                                       }
+
+                                                       break;
+
+                                       }       /* End switch (ie->ie_Code) */
+
+                                       break;
+                               }
+
+                               case IECLASS_RAWKEY:
+
+                                       if (ie->ie_Code & IECODE_UP_PREFIX)
+                                       {
+                                               /* Send final update to slider */
+
+                                               if ((ie->ie_Code == (IECODE_UP_PREFIX | CURSORUP))
+                                                       || (ie->ie_Code == (IECODE_UP_PREFIX | CURSORDOWN)))
+                                                       scrolled = SCROLLED_VERT;
+
+                                               else if ((ie->ie_Code == (IECODE_UP_PREFIX | CURSORLEFT))
+                                                       || (ie->ie_Code == (IECODE_UP_PREFIX | CURSORRIGHT)))
+                                                       scrolled = SCROLLED_HORIZ;
+                                       }
+                                       else if ((ie->ie_Qualifier & IEQUALIFIER_COMMAND) && (ie->ie_Code != 0x67))
+                                               result = GMR_REUSE;
+                                       else switch (ie->ie_Code)
+                                       {
+                                               case CURSORUP:
+
+                                                       if (ped->Line)
+                                                       {
+                                                               if (ie->ie_Qualifier & IEQUALIFIER_SHIFT)
+                                                               {
+                                                                       if (ped->Line > ped->TopLine)
+                                                                               ped->Line = ped->TopLine;
+                                                                       else
+                                                                               if (ped->Line >= ped->DisplayLines - 1)
+                                                                                       ped->Line -= ped->DisplayLines - 1;
+                                                                               else ped->Line = 0;
+                                                               }
+                                                               else if (ie->ie_Qualifier & IEQUALIFIER_ALT)
+                                                                       ped->Line = 0;
+                                                               else if (ie->ie_Qualifier & IEQUALIFIER_CONTROL)
+                                                               {
+                                                                       if (ped->TopLine + ped->DisplayLines < ped->Patt->Lines)
+                                                                       {
+                                                                               topline = ped->TopLine + 1;
+                                                                               lefttrack = ped->LeftTrack;
+                                                                               scroll_pattern = TRUE;
+                                                                               if (ped->Line < topline)
+                                                                                       ped->Line++;
+                                                                       }
+                                                               }
+                                                               else ped->Line--;       /* Cursor key without qualifiers */
+
+                                                               moved = TRUE;
+                                                       }
+                                                       else if (ped->Flags & PEF_VWRAP)
+                                                       {
+                                                               ped->Line = ped->Patt->Lines - 1;
+                                                               moved = TRUE;
+                                                       }
+
+                                                       break;
+
+
+                                               case CURSORDOWN:
+
+                                                       if (ped->Line < ped->Patt->Lines - 1)
+                                                       {
+                                                               if (ie->ie_Qualifier & IEQUALIFIER_SHIFT)
+                                                               {
+                                                                       if (ped->Line < ped->TopLine + ped->DisplayLines - 1)
+                                                                               ped->Line = ped->TopLine + ped->DisplayLines - 1;
+                                                                       else
+                                                                       {
+                                                                               ped->Line += ped->DisplayLines - 1;
+                                                                               if (ped->Line > ped->Patt->Lines - 1)
+                                                                                       ped->Line = ped->Patt->Lines - 1;
+                                                                       }
+                                                               }
+                                                               else if (ie->ie_Qualifier & IEQUALIFIER_ALT)
+                                                                       ped->Line = ped->Patt->Lines - 1;
+                                                               else if (ie->ie_Qualifier & IEQUALIFIER_CONTROL)
+                                                               {
+                                                                       if (ped->TopLine > 1)
+                                                                       {
+                                                                               topline = ped->TopLine - 1;
+                                                                               lefttrack = ped->LeftTrack;
+                                                                               scroll_pattern = TRUE;
+                                                                               if (ped->Line >= (topline + ped->DisplayLines))
+                                                                                       ped->Line--;
+                                                                       }
+                                                               }
+                                                               else ped->Line++;       /* Cursor key without qualifiers */
+
+                                                               moved = TRUE;
+                                                       }
+                                                       else if (ped->Flags & PEF_VWRAP)
+                                                       {
+                                                               ped->Line = 0;
+                                                               moved = TRUE;
+                                                       }
+
+                                                       break;
+
+
+                                               case CURSORLEFT:
+
+                                                       if (ie->ie_Qualifier & IEQUALIFIER_SHIFT)
+                                                       {
+                                                               if (ped->Track)
+                                                               {
+                                                                       ped->Track--;
+                                                                       moved = TRUE;
+                                                               }
+                                                               else if (ped->Column)
+                                                               {
+                                                                       ped->Column = 0;
+                                                                       moved = TRUE;
+                                                               }
+                                                       }
+                                                       else if (ie->ie_Qualifier & IEQUALIFIER_ALT)
+                                                       {
+                                                               if (ped->Track)
+                                                               {
+                                                                       ped->Track = 0;
+                                                                       moved = TRUE;
+                                                               }
+                                                               else if (ped->Column)
+                                                               {
+                                                                       ped->Column = 0;
+                                                                       moved = TRUE;
+                                                               }
+                                                       }
+                                                       else
+                                                       {
+                                                               if (ped->Column)
+                                                               {
+                                                                       ped->Column--;
+                                                                       moved = TRUE;
+                                                               }
+                                                               else if (ped->Track)
+                                                               {
+                                                                       ped->Track--;
+                                                                       ped->Column = COL_COUNT-1;
+                                                                       moved = TRUE;
+                                                               }
+                                                       }
+
+                                                       if (!moved && (ped->Flags & PEF_HWRAP))
+                                                       {
+                                                               ped->Track = ped->Patt->Tracks - 1;
+                                                               ped->Column = COL_COUNT-1;
+                                                               moved = TRUE;
+                                                       }
+
+                                                       break;
+
+
+                                               case CURSORRIGHT:
+
+                                                       if (ie->ie_Qualifier & IEQUALIFIER_SHIFT)
+                                                       {
+                                                               if (ped->Track < ped->Patt->Tracks - 1)
+                                                               {
+                                                                       ped->Track++;
+                                                                       moved = TRUE;
+                                                               }
+                                                               else if (ped->Column != COL_COUNT-1)
+                                                               {
+                                                                       ped->Column = COL_COUNT-1;
+                                                                       moved = TRUE;
+                                                               }
+                                                       }
+                                                       else if (ie->ie_Qualifier & IEQUALIFIER_ALT)
+                                                       {
+                                                               if (ped->Track != ped->Patt->Tracks - 1)
+                                                               {
+                                                                       ped->Track = ped->Patt->Tracks - 1;
+                                                                       moved = TRUE;
+                                                               }
+                                                               else if (ped->Column != COL_COUNT-1)
+                                                               {
+                                                                       ped->Column = COL_COUNT-1;
+                                                                       moved = TRUE;
+                                                               }
+                                                       }
+                                                       else
+                                                       {
+                                                               if (ped->Column < COL_COUNT-1)
+                                                               {
+                                                                       ped->Column++;
+                                                                       moved = TRUE;
+                                                               }
+                                                               else if (ped->Track < ped->Patt->Tracks - 1)
+                                                               {
+                                                                       ped->Track++;
+                                                                       ped->Column = 0;
+                                                                       moved = TRUE;
+                                                               }
+                                                       }
+
+                                                       if (!moved && (ped->Flags & PEF_HWRAP))
+                                                       {
+                                                               ped->Track = 0;
+                                                               ped->Column = 0;
+                                                               moved = TRUE;
+                                                       }
+
+                                                       break;
+
+
+                                               case 0x00:      /* ESC  */
+                                               case 0x5F:      /* HELP */
+                                                       result = GMR_REUSE;
+                                                       break;
+
+
+                                               case 0x42:      /* TAB */
+
+                                                       if (ie->ie_Qualifier & IEQUALIFIER_ALT)
+                                                               /* Deactivate gadget on ALT+TAB to allow
+                                                                * window cycling in the application.
+                                                                */
+                                                               result = GMR_REUSE;
+                                                       else
+                                                       {
+                                                               if (ie->ie_Qualifier & IEQUALIFIER_SHIFT)
+                                                               {
+                                                                        if (ped->Track > 0)
+                                                                               ped->Track--;
+                                                                       else
+                                                                               ped->Track = ped->Patt->Tracks - 1;
+                                                               }
+                                                               else
+                                                               {
+                                                                        if (ped->Track < ped->Patt->Tracks - 1)
+                                                                               ped->Track++;
+                                                                       else
+                                                                               ped->Track = 0;
+                                                               }
+
+                                                               ped->Column = COL_NOTE;
+                                                               moved = TRUE;
+                                                       }
+
+                                                       break;
+
+
+                                               case 0x0D:      /* RETURN */
+                                                       ped->Column = COL_NOTE;
+                                                       if (ped->Line < ped->Patt->Lines - 1)
+                                                               ped->Line++;
+                                                       else if (ped->Flags & PEF_VWRAP)
+                                                               ped->Line = 0;
+                                                       moved = TRUE;
+                                                       break;
+
+
+                                               case 0x46:      /* DEL */
+                                               {
+                                                       struct Note *note = &ped->Patt->Notes[ped->Track][ped->Line];
+
+                                                       SaveUndo (ped);
+                                                       change_note = TRUE;
+
+                                                       if (ie->ie_Qualifier & IEQUALIFIER_SHIFT)
+                                                               memset (note, 0, sizeof (struct Note));
+                                                       else switch (ped->Column)
+                                                       {
+                                                               case COL_NOTE:
+                                                                       note->Note = 0;
+                                                                       note->Inst = 0;
+                                                                       break;
+
+                                                               case COL_INSTH:
+                                                                       note->Inst &= 0x0F;
+                                                                       break;
+
+                                                               case COL_INSTL:
+                                                                       note->Inst &= 0xF0;
+                                                                       break;
+
+                                                               case COL_EFF:
+                                                                       note->EffNum = EFF_NULL;
+                                                                       break;
+
+                                                               case COL_VALH:
+                                                                       note->EffVal &= 0x0F;
+                                                                       break;
+
+                                                               case COL_VALL:
+                                                                       note->EffVal &= 0xF0;
+                                                                       break;
+                                                       }
+                                                       break;
+                                               }
+
+                                               default:
+                                               {
+                                                       struct Note *note = &ped->Patt->Notes[ped->Track][ped->Line];
+                                                       UBYTE tmp = 0, keycode = 1;
+
+                                                       /* Convert to hex number */
+
+                                                       if (ped->Column != COL_NOTE)
+                                                       {
+                                                               if (MapRawKey (ie, &keycode, 1, NULL) == -1)
+                                                                       keycode = 0;
+                                                               else
+                                                               {
+                                                                       if (keycode >= '0' && keycode <= '9')
+                                                                               tmp = keycode - '0';
+                                                                       else if (keycode >= 'a' && keycode <= ((ped->Column == COL_EFF) ? 'j' : 'f'))
+                                                                               tmp = keycode - ('a' - 10);
+                                                                       else
+                                                                               keycode = 0;
+                                                               }
+                                                       }
+
+                                                       if (keycode) switch (ped->Column)
+                                                       {
+                                                               case COL_NOTE:
+
+                                                                       /* Insert note */
+
+                                                                       if (ie->ie_Code >= 0x1 && ie->ie_Code <= 0x0A)
+                                                                               tmp = KeyNotes0[ie->ie_Code - 0x1];
+                                                                       else if (ie->ie_Code >= 0x10 && ie->ie_Code <= 0x19)
+                                                                               tmp = KeyNotes1[ie->ie_Code - 0x10];
+                                                                       else if (ie->ie_Code >= 0x20 && ie->ie_Code <= 0x29)
+                                                                               tmp = KeyNotes2[ie->ie_Code - 0x20];
+                                                                       else if (ie->ie_Code >= 0x31 && ie->ie_Code <= 0x39)
+                                                                               tmp = KeyNotes3[ie->ie_Code - 0x31];
+
+                                                                       if (tmp)
+                                                                       {
+                                                                               SaveUndo (ped);
+                                                                               change_note = TRUE;
+                                                                               note->Note = tmp;
+                                                                               note->Inst = ped->CurrentInst;
+                                                                       }
+                                                                       break;
+
+                                                               case COL_INSTL:
+                                                                       SaveUndo (ped);
+                                                                       change_note = TRUE;
+                                                                       note->Inst = (note->Inst & 0xF0) | tmp;
+                                                                       break;
+
+                                                               case COL_INSTH:
+                                                                       if (tmp < MAXINSTRUMENTS>>4)
+                                                                       {
+                                                                               SaveUndo (ped);
+                                                                               change_note = TRUE;
+                                                                               note->Inst = (note->Inst & 0x0F) | (tmp<<4);
+                                                                       }
+                                                                       break;
+
+                                                               case COL_EFF:
+                                                                       SaveUndo (ped);
+                                                                       change_note = TRUE;
+                                                                       note->EffNum = tmp;
+                                                                       break;
+
+                                                               case COL_VALL:
+                                                                       SaveUndo (ped);
+                                                                       change_note = TRUE;
+                                                                       note->EffVal = (note->EffVal & 0xF0) | tmp;
+                                                                       break;
+
+                                                               case COL_VALH:
+                                                                       SaveUndo (ped);
+                                                                       change_note = TRUE;
+                                                                       note->EffVal = (note->EffVal & 0x0F) | (tmp<<4);
+                                                                       break;
+                                                       }
+                                                       break;
+                                               }
+
+                                       } /* End switch (ie->ie_Code) */
+
+                                       break;
+
+                               default:
+                                       break;
+
+                       }       /* End switch (ie->ie_Class) */
+
+                       if (moved || change_note || scroll_pattern)
+                       {
+                               if (rp = ObtainGIRPort (((struct gpInput *)msg)->gpi_GInfo))
+                               {
+                                       if (scroll_pattern)
+                                               scrolled |= ScrollPattern (rp, ped, g, lefttrack, topline);
+
+                                       if (change_note)
+                                       {
+                                               EraseCursor (rp, ped);
+                                               DrawNote (rp, ped);
+
+                                               /* Advance cursor */
+
+                                               ped->Track      += ped->AdvanceTracks;
+                                               ped->Line       += ped->AdvanceLines;
+
+                                               if (ped->Flags & PEF_HWRAP)
+                                                       ped->Track = ped->Track % ped->Patt->Tracks;
+                                               else
+                                               {
+                                                       if (ped->Track < 0)
+                                                               ped->Track = 0;
+                                                       else if (ped->Track > ped->Patt->Tracks - 1)
+                                                               ped->Track = ped->Patt->Tracks - 1;
+                                               }
+
+                                               if (ped->Flags & PEF_VWRAP)
+                                                       ped->Line = ped->Line % ped->Patt->Lines;
+                                               else
+                                               {
+                                                       if (ped->Line < 0)
+                                                               ped->Line = 0;
+                                                       else if (ped->Line > ped->Patt->Lines - 1)
+                                                               ped->Line = ped->Patt->Lines - 1;
+                                               }
+                                       }
+
+                                       scrolled |= DrawCursor (rp, ped, g);
+                                       ReleaseGIRPort (rp);
+                               }
+
+                               /* Broadcast notification to our target object. */
+                               NotifyCursor (ped, g, ((struct gpInput *)msg)->gpi_GInfo, (ie->ie_Code & IECODE_UP_PREFIX) ? 0 : OPUF_INTERIM);
+                       }
+
+                       if (scrolled & SCROLLED_VERT)
+                               NotifyVSlider (ped, g, ((struct gpInput *)msg)->gpi_GInfo, (ie->ie_Code & IECODE_UP_PREFIX) ? 0 : OPUF_INTERIM);
+
+                       if (scrolled & SCROLLED_HORIZ)
+                               NotifyHSlider (ped, g, ((struct gpInput *)msg)->gpi_GInfo, (ie->ie_Code & IECODE_UP_PREFIX) ? 0 : OPUF_INTERIM);
+
+                       break;
+               }
+
+
+               case GM_RENDER:
+
+                       ped = INST_DATA (cl, g);
+
+                       /* We do not support GREDRAW_TOGGLE */
+
+                       switch (((struct gpRender *)msg)->gpr_Redraw)
+                       {
+                               case GREDRAW_REDRAW:
+#ifndef OS30_ONLY
+                                       /* Recalculate the display size only on V37.
+                                        * As of V39, Intuition supports GM_LAYOUT, which
+                                        * allows a more optimized way to handle dynamic resizing.
+                                        */
+                                       if (IntuitionBase->LibNode.lib_Version < 39)
+                                               if (CalcDisplaySize (ped, g, ((struct gpRender *)msg)->gpr_GInfo))
+                                                       NotifySliders (ped, g, ((struct gpRender *)msg)->gpr_GInfo, 0);
+#endif /* OS30_ONLY */
+
+                                       RedrawAll (((struct gpRender *)msg)->gpr_RPort, ped, g);
+                                       break;
+
+                               case GREDRAW_UPDATE:
+                                       /* Just redraw the notes, not the lines, etc. */
+                                       RedrawPattern (((struct gpRender *)msg)->gpr_RPort, ped, g);
+                                       break;
+                       }
+
+                       break;
+
+
+               case GM_HITTEST:
+
+                       /* As we are rectangular shaped, we are always hit */
+                       result = GMR_GADGETHIT;
+                       break;
+
+               case GM_HELPTEST:
+                       result = GMR_HELPHIT;
+                       break;
+
+               case GM_GOINACTIVE:
+
+                       ped = INST_DATA (cl, g);
+
+                       g->Flags &= ~GFLG_SELECTED;
+
+                       if (ped->Patt)
+                               /* Render disabled cursor */
+                               if (rp = ObtainGIRPort (((struct gpGoInactive *)msg)->gpgi_GInfo))
+                               {
+                                       DrawCursor (rp, ped, g);
+                                       ReleaseGIRPort (rp);
+                               }
+                       break;
+
+
+               case GM_LAYOUT:
+
+                       ped = INST_DATA (cl, g);
+
+                       if (CalcDisplaySize (ped, g, ((struct gpLayout *)msg)->gpl_GInfo))
+                               NotifySliders (ped, g, ((struct gpLayout *)msg)->gpl_GInfo, 0);
+
+                       break;
+
+
+               case OM_UPDATE:
+               case OM_SET:
+               {
+                       struct TagItem *tstate = ((struct opSet *)msg)->ops_AttrList;
+                       BOOL    redraw_all              = FALSE,
+                                       redraw_pattern  = FALSE,
+                                       move_cursor             = FALSE,
+                                       scroll_pattern  = FALSE,
+                                       change_note             = FALSE,
+                                       do_super_method = FALSE;
+                       WORD    lefttrack, topline;
+                       WORD    line, track, column;
+
+                       ped = INST_DATA (cl, g);
+
+                       lefttrack       = ped->LeftTrack;
+                       topline         = ped->TopLine;
+                       line            = ped->Line;
+                       track           = ped->Track;
+                       column          = ped->Column;
+
+
+                       while (ti = NextTagItem(&tstate))
+                       {
+                               switch (ti->ti_Tag)
+                               {
+                                       case PEA_CursTrack:
+                                               track = ti->ti_Data;
+                                               break;
+
+                                       case PEA_CursColumn:
+                                               column = ti->ti_Data;
+                                               break;
+
+                                       case PEA_CursLine:
+                                               line = ti->ti_Data;
+                                               break;
+
+                                       case PEA_LeftTrack:
+                                               if (lefttrack != ti->ti_Data)
+                                               {
+                                                       lefttrack = ti->ti_Data;
+                                                       track = lefttrack + (ped->DisplayTracks / 2);
+                                                       scroll_pattern = TRUE;
+                                               }
+                                               break;
+
+                                       case PEA_TopLine:
+                                               if (topline != ti->ti_Data)
+                                               {
+                                                       topline = ti->ti_Data;
+                                                       line = topline + (ped->DisplayLines / 2);
+                                                       scroll_pattern = TRUE;
+                                               }
+                                               break;
+
+                                       case PEA_Left:
+                                               if (lefttrack)
+                                                       track = lefttrack - 1;
+                                               break;
+
+                                       case PEA_Right:
+                                               if (ped->Patt && (lefttrack + ped->DisplayTracks < ped->Patt->Tracks))
+                                                       track = lefttrack + ped->DisplayTracks;
+                                               break;
+
+                                       case PEA_Up:
+                                               if (topline)
+                                                       line = topline - 1;
+                                               break;
+
+                                       case PEA_Down:
+                                               if (ped->Patt && (topline + ped->DisplayLines < ped->Patt->Lines))
+                                                       line = topline + ped->DisplayLines;
+                                               break;
+
+                                       case PEA_CursLeft:
+                                               if (track)
+                                                       track--;
+                                               else if (ped->Flags & PEF_VWRAP)
+                                                       track = ped->Patt->Tracks - 1;
+
+                                               break;
+
+                                       case PEA_CursRight:
+                                               if (ped->Patt && (track < ped->Patt->Tracks - 1))
+                                                       track++;
+                                               else if (ped->Flags & PEF_HWRAP)
+                                                       track = 0;
+
+                                               break;
+
+                                       case PEA_CursUp:
+                                               if (line)
+                                                       line--;
+                                               else if (ped->Flags & PEF_VWRAP)
+                                                       line = ped->Patt->Lines - 1;
+
+                                               break;
+
+                                       case PEA_CursDown:
+                                               if (ped->Patt && line < ped->Patt->Lines - 1)
+                                                       line++;
+                                               else if (ped->Flags & PEF_VWRAP)
+                                                       line = 0;
+
+                                               break;
+
+                                       case PEA_UndoChange:
+                                               if (((LONG)ti->ti_Data) < 0)
+                                                       change_note |= RedoChange (ped);
+                                               else
+                                                       change_note |= UndoChange (ped);
+                                               line = ped->Line;
+                                               track = ped->Track;
+
+                                               break;
+
+                                       case PEA_Changes:
+                                               ped->Changes = ti->ti_Data;
+                                               break;
+
+                                       case PEA_MarkRegion:
+                                       {
+                                               struct Rectangle *region = (struct Rectangle *)ti->ti_Data;
+
+                                               if (!region)                            /* End mark mode */
+
+                                                       ped->Flags &= ~(PEF_MARKING | PEF_MARKFULLTRACKS);
+
+                                               else if (region == (struct Rectangle *)-1)      /* Toggle mark mode */
+                                               {
+                                                       if (ped->Flags & PEF_MARKFULLTRACKS)
+                                                               ped->Flags &= ~(PEF_MARKING | PEF_MARKFULLTRACKS);
+                                                       else if (ped->Flags & PEF_MARKING)
+                                                       {
+                                                               if (ped->RangeStartLine == ped->RangeEndLine)
+                                                               {
+                                                                       ped->Flags |= PEF_MARKFULLTRACKS;
+                                                                       ped->RangeStartLine             = 0;
+                                                                       ped->RangeEndLine               = ped->Patt->Lines - 1;
+                                                               }
+                                                               else
+                                                                       ped->Flags &= ~PEF_MARKING;
+                                                       }
+                                                       else
+                                                       {
+                                                               ped->Flags |= PEF_MARKING;
+                                                               ped->RangeStartTrack    = track;
+                                                               ped->RangeStartLine             = line;
+                                                       }
+                                               }
+                                               else                                            /* Start mark mode */
+                                               {
+                                                       memcpy (&ped->RangeStartTrack, region, sizeof (struct Rectangle));
+                                                       track   = ped->Track    = region->MaxX;
+                                                       line    = ped->Line             = region->MaxY;
+                                                       ped->Flags |= PEF_MARKING;
+                                               }
+
+                                               if (rp = ObtainGIRPort (((struct opSet *)msg)->ops_GInfo))
+                                               {
+                                                       if (!(ped->Flags & PEF_MARKING))
+                                                               ClearRange (rp, ped);
+
+                                                       DrawCursor (rp, ped, g);
+
+                                                       ReleaseGIRPort (rp);
+                                               }
+
+                                               break;
+                                       }
+
+                                       case PEA_Flags:
+                                       {
+                                               ULONG   oldflags = ped->Flags;
+                                               ped->Flags = (ped->Flags & 0xFFFF0000) | ti->ti_Data;
+
+                                               if ((oldflags & (PEF_HEXMODE | PEF_BLANKZERO | PEF_INVERSETEXT | PEF_DOTINYLINES)) !=
+                                                       (ped->Flags & (PEF_HEXMODE | PEF_BLANKZERO | PEF_INVERSETEXT | PEF_DOTINYLINES)))
+                                               {
+                                                       /* Select Note2ASCII func */
+                                                       if (ped->Flags & PEF_BLANKZERO)
+                                                               ped->Note2ASCIIFunc = Note2ASCIIBlank0;
+                                                       else
+                                                               ped->Note2ASCIIFunc = Note2ASCII;
+
+                                                       if (!(ped->Flags & PEF_DOCURSORRULER))
+                                                               ped->CursLinePos = 0;
+
+                                                       /* Cause complete radraw */
+                                                       redraw_all = TRUE;
+                                               }
+
+                                               if ((oldflags & PEF_DOCURSORRULER) !=
+                                                       (ped->Flags & PEF_DOCURSORRULER))
+                                                               move_cursor = TRUE;
+
+                                               break;
+                                       }
+
+                                       case PEA_Pattern:
+                                               ped->Patt = (struct Pattern *) ti->ti_Data;
+                                               redraw_pattern = TRUE;
+                                               FreeUndoBuffers (ped, TRUE);
+
+                                               if (ped->Patt == NULL)
+                                               {
+                                                       static LONG tags[] = { GA_Disabled, TRUE, TAG_DONE };
+
+                                                       lefttrack = topline = ped->DisplayTracks = ped->DisplayLines = 0;
+                                                       redraw_all = TRUE;
+
+                                                       DoMethod ((Object *)g, OM_UPDATE, tags, ((struct opUpdate *)msg)->opu_GInfo, 0);
+                                               }
+                                               else
+                                               {
+                                                       /* Recalculate pattern dimensions */
+                                                       if (CalcDisplaySize (ped, g, ((struct opSet *)msg)->ops_GInfo))
+                                                       {
+                                                               NotifySliders (ped, g, ((struct opSet *)msg)->ops_GInfo, 0);
+                                                               redraw_all = TRUE;
+                                                       }
+
+                                                       /* Force cursor inside pattern */
+
+                                                       if (line >= ped->Patt->Lines)
+                                                       {
+                                                               line = ped->Patt->Lines - 1;
+                                                               move_cursor = TRUE;
+                                                       }
+
+                                                       if (track >= ped->Patt->Tracks)
+                                                       {
+                                                               track = ped->Patt->Tracks - 1;
+                                                               move_cursor = TRUE;
+                                                       }
+
+                                                       if (lefttrack + ped->DisplayTracks > ped->Patt->Tracks)
+                                                       {
+                                                               lefttrack = ped->Patt->Tracks - ped->DisplayTracks;
+                                                               scroll_pattern = TRUE;
+                                                       }
+
+                                                       if (topline + ped->DisplayLines > ped->Patt->Lines)
+                                                       {
+                                                               topline = ped->Patt->Lines - ped->DisplayLines;
+                                                               scroll_pattern = TRUE;
+                                                       }
+
+                                                       if (g->Flags & GFLG_DISABLED)
+                                                       {
+                                                               static ULONG tags[] = { GA_Disabled, FALSE, TAG_DONE };
+                                                               DoMethod ((Object *)g, OM_UPDATE, tags, ((struct opUpdate *)msg)->opu_GInfo, 0);
+                                                       }
+                                               }
+                                               break;
+
+                                       case PEA_CurrentInst:
+                                               ped->CurrentInst = ti->ti_Data;
+                                               break;
+
+                                       case PEA_MaxUndoLevels:
+                                               ped->MaxUndoLevels = ti->ti_Data;
+                                               FreeUndoBuffers (ped, FALSE);
+                                               break;
+
+                                       case PEA_MaxUndoMem:
+                                               ped->MaxUndoMem = ti->ti_Data;
+
+                                               /* Unlimited undo memory */
+                                               if (ped->MaxUndoMem == 0) ped->MaxUndoMem = ~0;
+
+                                               FreeUndoBuffers (ped, FALSE);
+
+                                               break;
+
+                                       case PEA_AdvanceCurs:
+                                               ped->AdvanceLines = ti->ti_Data & 0xFFFF;
+                                               ped->AdvanceTracks = ti->ti_Data >> 16;
+                                               break;
+
+                                       case PEA_CursWrap:
+                                               ped->Flags = (ped->Flags & (PEF_HWRAP | PEF_VWRAP)) | (ti->ti_Data & (PEF_HWRAP | PEF_VWRAP));
+                                               break;
+
+                                       case PEA_BGPen:
+                                               if (ped->BGPen != ti->ti_Data)
+                                               {
+                                                       ped->BGPen = ti->ti_Data;
+                                                       redraw_all = TRUE;
+                                               }
+                                               break;
+
+                                       case PEA_TextPen:
+                                               if (ped->TextPen != ti->ti_Data)
+                                               {
+                                                       ped->TextPen = ti->ti_Data;
+                                                       redraw_all = TRUE;
+                                               }
+                                               break;
+
+                                       case PEA_LinesPen:
+                                               if (ped->LinesPen != ti->ti_Data)
+                                               {
+                                                       ped->LinesPen = ti->ti_Data;
+                                                       redraw_all = TRUE;
+                                               }
+                                               break;
+
+                                       case PEA_TinyLinesPen:
+                                               if (ped->TinyLinesPen != ti->ti_Data)
+                                               {
+                                                       ped->TinyLinesPen = ti->ti_Data;
+                                                       redraw_all = TRUE;
+                                               }
+                                               break;
+
+                                       default:
+                                               /* This little optimization avoids forwarding the
+                                                * OM_SET method to our superclass then there are
+                                                * no unknown tags.
+                                                */
+                                               do_super_method = TRUE;
+                                               break;
+                               }
+
+                       }       /* End while (NextTagItem()) */
+
+
+                       if (!move_cursor)
+                               move_cursor = ((line != ped->Line) ||
+                                       (track != ped->Track) ||
+                                       (column != ped->Column));
+
+                       if (do_super_method)
+                               result = DoSuperMethodA (cl, (Object *)g, msg);
+                       else
+                               result = TRUE;
+
+                       if (redraw_all || redraw_pattern || scroll_pattern || move_cursor || change_note)
+                       {
+                               WORD scrolled = 0;
+
+                               if (rp = ObtainGIRPort (((struct opSet *)msg)->ops_GInfo))
+                               {
+                                       if (redraw_all || redraw_pattern)
+                                       {
+                                               ped->LeftTrack  = lefttrack;
+                                               ped->TopLine    = topline;
+                                               ped->Line               = line;
+                                               ped->Track              = track;
+                                               ped->Column             = column;
+
+                                               DoMethod ((Object *)g, GM_RENDER, ((struct opSet *)msg)->ops_GInfo, rp,
+                                                       (redraw_all ? GREDRAW_REDRAW : GREDRAW_UPDATE));
+                                               scrolled = SCROLLED_VERT | SCROLLED_HORIZ;
+                                       }
+                                       else if (scroll_pattern)
+                                       {
+                                               ped->Line       = line;
+                                               ped->Track      = track;
+                                               scrolled = ScrollPattern (rp, ped, g, lefttrack, topline);
+                                       }
+                                       else if (move_cursor || change_note)
+                                       {
+                                               ped->Line       = line;
+                                               ped->Track      = track;
+                                               ped->Column     = column;
+
+                                               if (change_note)
+                                               {
+                                                       EraseCursor (rp, ped);
+                                                       DrawNote (rp, ped);
+                                               }
+
+                                               scrolled |= DrawCursor (rp, ped, g);
+                                       }
+
+                                       ReleaseGIRPort (rp);
+                               }
+
+                               /* TODO: avoid sending back updates to the sliders */
+
+                               if (scrolled & SCROLLED_VERT)
+                                       NotifyVSlider (ped, g, ((struct opSet *)msg)->ops_GInfo, msg->MethodID == OM_UPDATE ? (((struct opUpdate *)msg)->opu_Flags) : 0);
+                               if (scrolled & SCROLLED_HORIZ)
+                                       NotifyHSlider (ped, g, ((struct opSet *)msg)->ops_GInfo, msg->MethodID == OM_UPDATE ? (((struct opUpdate *)msg)->opu_Flags) : 0);
+
+                               if (scrolled || move_cursor)
+                                       NotifyCursor (ped, g, ((struct opSet *)msg)->ops_GInfo, msg->MethodID == OM_UPDATE ? (((struct opUpdate *)msg)->opu_Flags) : 0);
+                       }
+
+                       break;
+               }
+
+
+               case OM_GET:
+
+                       ped = INST_DATA (cl, g);
+                       result = TRUE;
+
+                       switch (((struct opGet *) msg)->opg_AttrID)
+                       {
+                               case PEA_CursTrack:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->Track;
+                                       break;
+
+                               case PEA_CursColumn:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->Column;
+                                       break;
+
+                               case PEA_CursLine:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->Line;
+                                       break;
+
+                               case PEA_LeftTrack:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->LeftTrack;
+                                       break;
+
+                               case PEA_TopLine:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->TopLine;
+                                       break;
+
+                               case PEA_Changes:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->Changes;
+                                       break;
+
+                               case PEA_MarkRegion:
+                               {
+                                       struct Rectangle *region = (struct Rectangle *) (((struct opGet *) msg)->opg_Storage);
+
+                                       region->MinX = min (ped->RangeStartTrack, ped->RangeEndTrack);
+                                       region->MaxX = max (ped->RangeStartTrack, ped->RangeEndTrack);
+                                       region->MinY = min (ped->RangeStartLine, ped->RangeEndLine);
+                                       region->MaxY = max (ped->RangeStartLine, ped->RangeEndLine);
+                                       break;
+                               }
+
+                               case PEA_Flags:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->Flags;
+                                       break;
+
+                               case PEA_DisplayTracks:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->DisplayTracks;
+                                       break;
+
+                               case PEA_DisplayLines:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->DisplayLines;
+                                       break;
+
+                               case PEA_Pattern:
+                                       *(((struct opGet *) msg)->opg_Storage) = (ULONG) ped->Patt;
+                                       break;
+
+                               default:
+                                       result = DoSuperMethodA (cl, (Object *)g, msg);
+                                       break;
+                       }
+
+                       break;
+
+
+               case OM_NEW:
+
+                       if (result = DoSuperMethodA (cl, (Object *)g, msg))
+                       {
+                               ULONG tmp;
+
+                               g = (struct ExtGadget *) result;
+
+                               ped = INST_DATA (cl, g);        /* Get pointer to instance data */
+                               memset (ped, 0, sizeof (struct PattEditData));
+
+                               /* We are going to use ScrollRaster() in this gadget... */
+
+                               if (g->Flags & GFLG_EXTENDED)
+                                       g->MoreFlags |= GMORE_SCROLLRASTER;
+
+                               g->Flags |= GFLG_TABCYCLE | GFLG_RELSPECIAL;
+
+                               /* Initialize our lists */
+                               NEWLIST ((struct List *)&ped->UndoList);
+                               NEWLIST ((struct List *)&ped->RedoList);
+
+
+                               /* Open the timer.device */
+/*
+                               NEWLIST (&ped->TimerPort.mp_MsgList);
+                               ped->TimerPort.mp_Flags = PA_SOFTINT;
+                               ped->TimerPort.mp_SoftInt = &ped->TimerInt;
+                               ped->TimerInt.is_Node.ln_Type = NT_INTERRUPT;
+                               ped->TimerInt.is_Data = g;
+                               ped->TimerInt.is_Code = (void (*)())TimerIntServer;
+                               ped->TimerIO.tr_node.io_Message.mn_ReplyPort = &ped->TimerPort;
+
+                               if (OpenDevice (TIMERNAME, UNIT_VBLANK, (struct IORequest *)&ped->TimerIO, 0))
+                               {
+                                       CoerceMethod (cl, g, OM_DISPOSE);
+                                       result = NULL;
+                                       break;
+                               }
+*/
+
+                               /* Initialize attributes */
+
+                               ped->Patt                       = (struct Pattern *)    GetTagData (PEA_Pattern,        NULL, ((struct opSet *)msg)->ops_AttrList);
+                               ped->CurrentInst        = GetTagData (PEA_CurrentInst,  NULL, ((struct opSet *)msg)->ops_AttrList);
+                               ped->EditorFont         = (struct TextFont *)   GetTagData (PEA_TextFont,       (ULONG)GfxBase->DefaultFont, ((struct opSet *)msg)->ops_AttrList);
+                               ped->BGPen                      = GetTagData (PEA_BGPen,                0, ((struct opSet *)msg)->ops_AttrList);
+                               ped->TextPen            = GetTagData (PEA_TextPen,              1, ((struct opSet *)msg)->ops_AttrList);
+                               ped->LinesPen           = GetTagData (PEA_LinesPen,             2, ((struct opSet *)msg)->ops_AttrList);
+                               ped->TinyLinesPen       = GetTagData (PEA_TinyLinesPen, 2, ((struct opSet *)msg)->ops_AttrList);
+                               ped->MaxUndoLevels      = GetTagData (PEA_MaxUndoLevels,        32, ((struct opSet *)msg)->ops_AttrList);
+                               ped->MaxUndoMem         = GetTagData (PEA_MaxUndoMem,           8192, ((struct opSet *)msg)->ops_AttrList);
+                               ped->Flags                      = GetTagData (PEA_Flags,                        0, ((struct opSet *)msg)->ops_AttrList);
+                               ped->Flags                      |= GetTagData (PEA_CursWrap,            ped->Flags, ((struct opSet *)msg)->ops_AttrList) & (PEF_HWRAP | PEF_VWRAP);
+
+                               tmp = GetTagData (PEA_AdvanceCurs, 1, ((struct opSet *)msg)->ops_AttrList);
+                               ped->AdvanceTracks      = tmp << 16;
+                               ped->AdvanceLines       = tmp & 0xFFFF;
+
+                               ped->Line       = GetTagData (PEA_CursLine,     0, ((struct opSet *)msg)->ops_AttrList);
+                               ped->Track      = GetTagData (PEA_CursTrack,    0, ((struct opSet *)msg)->ops_AttrList);
+                               ped->Column     = GetTagData (PEA_CursColumn,   0, ((struct opSet *)msg)->ops_AttrList);
+
+                               ped->FontXSize          = ped->EditorFont->tf_XSize;
+                               ped->FontYSize          = ped->EditorFont->tf_YSize;
+                               ped->TrackChars         = 10;
+                               ped->TrackWidth         = ped->FontXSize * ped->TrackChars;
+
+                               /* Select Note2ASCII func */
+                               if (ped->Flags & PEF_BLANKZERO)
+                                       ped->Note2ASCIIFunc = Note2ASCIIBlank0;
+                               else
+                                       ped->Note2ASCIIFunc = Note2ASCII;
+
+                               /* Unlimited undo memory */
+                               if (ped->MaxUndoMem == 0) ped->MaxUndoMem = ~0;
+
+                               if (ped->Patt == NULL)
+                               {
+                                       static LONG tags[] = { GA_Disabled, TRUE, TAG_DONE };
+                                       DoMethod ((Object *)g, OM_UPDATE, tags, NULL, 0);
+                               }
+                       }
+                       break;
+
+
+               case OM_DISPOSE:
+               {
+                       ped = INST_DATA (cl, g);
+
+                       FreeUndoBuffers (ped, TRUE);
+//                     CloseDevice ((struct IORequest *)&ped->TimerIO);
+
+                       /* NOTE: I'm falling through here! */
+               }
+
+
+               default:
+
+                       /* Unsupported method: let our superclass's
+                        * dispatcher take a look at it.
+                        */
+                       result = DoSuperMethodA (cl, (Object *)g, msg);
+                       break;
+       }
+
+       return (result);
+}
+
+
+
+static void SaveUndo (struct PattEditData *ped)
+{
+       struct UndoNode *undo;
+
+       ped->Changes++;
+
+       /* Is undo feature disabled ? */
+       if (!ped->MaxUndoLevels) return;
+
+       /* Empty redo list */
+       while (undo = (struct UndoNode *) REMHEAD((struct List *)&ped->RedoList))
+               FreeMem (undo, sizeof (struct UndoNode));
+
+       FreeUndoBuffers (ped, FALSE);
+
+       while (ped->UndoCount >= ped->MaxUndoLevels || ped->UndoMem >= ped->MaxUndoMem)
+               if (undo = (struct UndoNode *) REMTAIL ((struct List *)&ped->UndoList))
+               {
+                       FreeMem (undo, sizeof (struct UndoNode));
+                       ped->UndoCount--;
+                       ped->UndoMem -= sizeof (struct UndoNode);
+               }
+
+       /* Allocate a new undo buffer and save current note */
+       if (undo = AllocMem (sizeof (struct UndoNode), MEMF_ANY))
+       {
+               undo->Track     = ped->Track;
+               undo->Line      = ped->Line;
+
+               memcpy (&undo->OldNote, &ped->Patt->Notes[ped->Track][ped->Line],
+                       sizeof (struct Note));
+
+               ADDHEAD ((struct List *)&ped->UndoList, (struct Node *)undo);
+               ped->UndoCount++;
+               ped->UndoMem += sizeof (struct UndoNode);
+       }
+}
+
+
+
+static BOOL UndoChange (struct PattEditData *ped)
+{
+       struct UndoNode *undo;
+       struct Note             *note;
+       struct Note              tmp_note;
+
+       if (undo = (struct UndoNode *) REMHEAD ((struct List *) &ped->UndoList))
+       {
+               ped->UndoCount--;
+               ped->UndoMem -= sizeof (struct UndoNode);
+               ped->Changes--;
+
+               ped->Track      = undo->Track;
+               ped->Line       = undo->Line;
+
+               note = &ped->Patt->Notes[ped->Track][ped->Line];
+
+               /* Swap undo buffer with note */
+               memcpy (&tmp_note, &undo->OldNote, sizeof (struct Note));
+               memcpy (&undo->OldNote, note, sizeof (struct Note));
+               memcpy (note, &tmp_note, sizeof (struct Note));
+
+
+               /* Move this node to the redo buffer */
+               ADDHEAD ((struct List *)&ped->RedoList, (struct Node *)undo);
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+
+
+static BOOL RedoChange (struct PattEditData *ped)
+{
+       struct UndoNode *undo;
+       struct Note     *note;
+       struct Note      tmp_note;
+
+       if (undo = (struct UndoNode *) REMHEAD ((struct List *) &ped->RedoList))
+       {
+               ped->Track      = undo->Track;
+               ped->Line       = undo->Line;
+
+               note = &ped->Patt->Notes[ped->Track][ped->Line];
+
+               /* Swap undo buffer and note */
+               memcpy (&tmp_note, &undo->OldNote, sizeof (struct Note));
+               memcpy (&undo->OldNote, note, sizeof (struct Note));
+               memcpy (note, &tmp_note, sizeof (struct Note));
+
+               /* Move this node to the undo buffer */
+               ADDHEAD ((struct List *)&ped->UndoList, (struct Node *)undo);
+
+               ped->UndoCount++;
+               ped->Changes++;
+               ped->UndoMem += sizeof (struct UndoNode);
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+
+
+static void FreeUndoBuffers (struct PattEditData *ped, BOOL freeall)
+
+/* If <freeall> is TRUE, this routine will free all the undo buffers.
+ * Otherwhise, it will check for undo overflow and free enough nodes
+ * to keep the undo buffers inside the memory size and nodes count
+ * limits.
+ */
+{
+       struct UndoNode *undo;
+
+       if (!freeall)
+       {
+               while (ped->UndoCount >= ped->MaxUndoLevels || ped->UndoMem >= ped->MaxUndoMem)
+                       if (undo = (struct UndoNode *) RemTail ((struct List *)&ped->UndoList))
+                       {
+                               FreeMem (undo, sizeof (struct UndoNode));
+                               ped->UndoCount--;
+                               ped->UndoMem -= sizeof (struct UndoNode);
+                       }
+
+               return;
+       }
+
+       /* Free everything */
+
+       while (undo = (struct UndoNode *) REMHEAD ((struct List *)&ped->UndoList))
+               FreeMem (undo, sizeof (struct UndoNode));
+
+       while (undo = (struct UndoNode *) REMHEAD ((struct List *)&ped->RedoList))
+               FreeMem (undo, sizeof (struct UndoNode));
+
+       ped->UndoCount = 0; ped->UndoMem = 0;
+}
+
+
+
+static void NotifyCursor (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags)
+{
+       static LONG tags[] =
+       {
+               PEA_CursLine,           0,
+               PEA_CursTrack,  0,
+               PEA_CursColumn, 0,
+               GA_ID,                          0,
+               TAG_DONE
+       };
+
+       /* Always send notification if the gadget has the GACT_IMMEDIATE
+        * flag set.  If it isn't, the editor will report its cursor
+        * position only on last cursor update.
+        */
+       if ((g->Activation & GACT_IMMEDIATE) || !(flags & OPUF_INTERIM))
+       {
+               tags[1] = ped->Line;
+               tags[3] = ped->Track;
+               tags[5] = ped->Column;
+               tags[7] = g->GadgetID;
+
+               DoMethod ((Object *)g, OM_NOTIFY, tags, gi, flags);
+       }
+}
+
+
+
+static void NotifyVSlider (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags)
+{
+       static LONG tags[] =
+       {
+               PEA_TopLine,                    0,
+//             PEA_DisplayLines,               0,
+               GA_ID,                                  0,
+               TAG_DONE
+       };
+
+
+       /* Optimized slider update; only broadcast updates if one of
+        * these conditions is satisfied:
+        *
+        * - Final update (keyup or selectup),
+        * - SliderCounter reached,
+        * - Reached top/bottom of the pattern.
+        *
+        * In addition, notifications are not sent when the we receive OM_UPDATE messages
+        * from the sliders
+        */
+       if ((!(flags & OPUF_INTERIM)) || (ped->SliderCounter == 0) || (ped->TopLine == 0)
+               || (ped->TopLine + ped->DisplayLines >= ped->Patt->Lines))
+       {
+               ped->SliderCounter = 4;
+
+               tags[1] = ped->TopLine;
+//             tags[3] = ped->DisplayLines;
+               tags[3] = g->GadgetID;
+
+               DoMethod ((Object *)g, OM_NOTIFY, tags, gi, flags);
+       }
+
+       ped->SliderCounter--;
+}
+
+
+
+static void NotifyHSlider (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags)
+{
+       static LONG tags[] =
+       {
+               PEA_LeftTrack,          0,
+//             PEA_DisplayTracks,      0,
+               GA_ID,                          0,
+               TAG_DONE
+       };
+
+
+       /* Optimized slider update; only send updates if one of
+        * these conditions is satisfied:
+        *
+        * - Final update (keyup or selectup),
+        * - SliderCounter reached,
+        * - Reached left/right of pattern.
+        */
+       if ((!(flags & OPUF_INTERIM)) || (ped->SliderCounter == 0) || (ped->LeftTrack == 0)
+               || (ped->LeftTrack + ped->DisplayTracks >= ped->Patt->Tracks))
+       {
+               ped->SliderCounter = 3;
+
+               tags[1] = ped->LeftTrack;
+//             tags[3] = ped->DisplayTracks;
+               tags[3] = g->GadgetID;
+
+               DoMethod ((Object *)g, OM_NOTIFY, tags, gi, flags);
+       }
+
+       ped->SliderCounter--;
+}
+
+
+
+static void NotifySliders (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags)
+{
+       static LONG tags[] =
+       {
+               PEA_LeftTrack,          0,
+               PEA_DisplayTracks,      0,
+               PEA_TopLine,            0,
+               PEA_DisplayLines,       0,
+               GA_ID,                          0,
+               TAG_DONE
+       };
+
+       tags[1] = ped->LeftTrack;
+       tags[3] = ped->DisplayTracks;
+       tags[5] = ped->TopLine;
+       tags[7] = ped->DisplayLines;
+       tags[9] = g->GadgetID;
+
+       DoMethod ((Object *)g, OM_NOTIFY, tags, gi, flags);
+}
+
+
+
+
+
+static void GetGadgetBox (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *rect)
+
+/* This function gets the actual IBox where a gadget exists
+ * in a window.  The special cases it handles are all the REL#?
+ * (relative positioning flags).
+ *
+ * The function takes a struct GadgetInfo pointer, a struct Gadget
+ * pointer, and a struct IBox pointer.  It uses the window and
+ * gadget to fill in the IBox.
+ */
+{
+       rect->Left = g->LeftEdge;
+       if (g->Flags & GFLG_RELRIGHT) rect->Left += ginfo->gi_Domain.Width - 1;
+
+       rect->Top = g->TopEdge;
+       if (g->Flags & GFLG_RELBOTTOM) rect->Top += ginfo->gi_Domain.Height - 1;
+
+       rect->Width = g->Width;
+       if (g->Flags & GFLG_RELWIDTH) rect->Width += ginfo->gi_Domain.Width;
+
+       rect->Height = g->Height;
+       if (g->Flags & GFLG_RELHEIGHT) rect->Height += ginfo->gi_Domain.Height;
+}
+
+
+
+static BOOL CalcDisplaySize (struct PattEditData *ped, struct ExtGadget *g, struct GadgetInfo *ginfo)
+
+/* Calculate maximum number of tracks and lines that will fit in the gadget
+ * size.  Returns TRUE if something has changed.
+ *
+ * GBounds are the bounds of the whole gadget and
+ * TBounds are the bounds of the portion where the cursor lives.
+ *
+ * +--Window--------------------+
+ * |                            |
+ * | +--GBounds----------------+|
+ * | |   +--TBounds-----------+||
+ * | |001|C#2 1 000|B#2 2 C20 |||
+ * | |002|--- 0 000|A-2 2 C30 |||
+ * | |003|--- 0 000|C-2 2 C10 |||
+ * | |004|--- 0 000|C#2 3 000 |||
+ * | |   +--------------------+||
+ * | +-------------------------+|
+ * +----------------------------+
+ */
+{
+       UWORD   old_displaytracks = ped->DisplayTracks,
+                       old_displaylines = ped->DisplayLines,
+                       numcol_width = ped->FontXSize * 4;
+
+       GetGadgetBox (ginfo, g, &ped->GBounds);
+
+       /* Setup DisplayTracks and DisplayLines */
+
+       if (ped->Patt)
+       {
+               ped->DisplayTracks      = min ((ped->GBounds.Width - numcol_width) / ped->TrackWidth, ped->Patt->Tracks);
+               ped->DisplayLines       = min (ped->GBounds.Height / ped->FontYSize, ped->Patt->Lines);
+
+               if (ped->TopLine + ped->DisplayLines > ped->Patt->Lines)
+                       ped->TopLine = max (0, ped->Patt->Lines - ped->DisplayLines);
+
+               if (ped->LeftTrack + ped->DisplayTracks > ped->Patt->Tracks)
+                       ped->LeftTrack = max (0, ped->Patt->Tracks - ped->DisplayTracks);
+       }
+
+
+       /* Setup Text Bounds */
+
+       ped->TBounds.Top        = ped->GBounds.Top;
+       ped->TBounds.Left       = ped->GBounds.Left + numcol_width;
+       ped->TBounds.Width      = ped->DisplayTracks * ped->TrackWidth;
+       ped->TBounds.Height     = ped->DisplayLines * ped->FontYSize;
+
+       return ((BOOL)((old_displaytracks != ped->DisplayTracks) || (old_displaylines != ped->DisplayLines)));
+}
+
+
+
+static BOOL MoveCursor (struct PattEditData *ped, WORD x, WORD y)
+
+/* Moves the cursor to a given xy position.  Checks whether the cursor has
+ * really moved and returns FALSE if there is no need to update its imagery.
+ */
+{
+       WORD tmp;
+       BOOL moved = FALSE, maxtrack = FALSE;
+
+
+       /* X Position (Track) */
+
+       if (x < 0)
+               tmp = ped->LeftTrack - 1;
+       else
+               tmp = (x / ped->TrackWidth) + ped->LeftTrack;
+
+       if (tmp < 0) tmp = 0;
+
+       if (tmp >= ped->Patt->Tracks)
+       {
+               tmp = ped->Patt->Tracks - 1;
+               maxtrack = TRUE;
+       }
+
+       if (ped->Track != tmp)
+       {
+               moved = TRUE;
+               ped->Track = tmp;
+       }
+
+
+       /* X Position (Column) */
+
+       if (maxtrack)
+               tmp = ped->TrackChars - 1;
+       else
+               tmp = (x / ped->FontXSize) % ped->TrackChars;
+
+
+       if (tmp < 3)            tmp = COL_NOTE;
+       else if (tmp == 3)      tmp = COL_INSTH;
+       else if (tmp == 4)      tmp = COL_INSTL;
+       else if (tmp < 7)       tmp = COL_EFF;
+       else if (tmp == 7)      tmp = COL_VALH;
+       else                            tmp = COL_VALL;
+
+
+       if (ped->Column != tmp)
+       {
+               moved = TRUE;
+               ped->Column = tmp;
+       }
+
+
+       /* Y Position */
+
+       tmp = (y / ped->FontYSize) + ped->TopLine;
+
+       if (tmp < 0) tmp = 0;
+
+       if (tmp >= ped->Patt->Lines)
+               tmp = ped->Patt->Lines - 1;
+
+       if (ped->Line != tmp)
+       {
+               moved = TRUE;
+               ped->Line = tmp;
+       }
+
+       return moved;
+}
+
+
+
+static void EraseCursor (struct RastPort *rp, struct PattEditData *ped)
+{
+       SetDrMd (rp,COMPLEMENT);
+
+       switch (ped->CursState)
+       {
+               case IDS_DISABLED:
+                       SetAfPt (rp, GhostPattern, 1);
+                       RectFill (rp, ped->CursRect.MinX, ped->CursRect.MinY,
+                               ped->CursRect.MaxX, ped->CursRect.MaxY);
+                       SetAfPt (rp, NULL, 0);  /* Reset Area Pattern   */
+                       break;
+
+               case IDS_BUSY:
+                       SetAfPt (rp, MarkPattern, 1);
+                       RectFill (rp, ped->CursRect.MinX, ped->CursRect.MinY,
+                               ped->CursRect.MaxX, ped->CursRect.MaxY);
+                       SetAfPt (rp, NULL, 0);  /* Reset Area Pattern   */
+                       break;
+
+               case IDS_SELECTED:
+                       RectFill (rp, ped->CursRect.MinX, ped->CursRect.MinY,
+                               ped->CursRect.MaxX, ped->CursRect.MaxY);
+
+                       if (ped->CursLinePos)
+                       {
+                               /* Erase cursor line */
+
+                               /* Question: Is RectFill faster than Move()+Draw()? Hmmm... */
+                               RectFill (rp, ped->GBounds.Left, ped->CursRect.MaxY,
+                                       ped->TBounds.Left + ped->TBounds.Width - 1, ped->CursRect.MaxY);
+                       //      Move (rp, ped->GBounds.Left, ped->CursRect.MaxY);
+                       //      Draw (rp, ped->TBounds.Left + ped->TBounds.Width - 1, ped->CursRect.MaxY);
+                               ped->CursLinePos = 0;
+                       }
+                       break;
+
+               default:
+
+                       /* When the cursor is not rendered, its state
+                        * will be IDS_NORMAL, so we won't erase it at all.
+                        */
+                       break;
+       }
+
+       ped->CursState = IDS_NORMAL;
+}
+
+
+
+static UWORD DrawCursor (struct RastPort *rp, struct PattEditData *ped, struct ExtGadget *g)
+
+/* Draw the cursor image on the editor.  If the cursor goes outside
+ * of the scrolling region, the view is scrolled to make
+ * it visible.
+ *
+ * RESULT
+ *  0                            - no scrolling occurred,
+ *  SCROLLED_VERT                - vertical scroll occurred,
+ *  SCROLLED_HORIZ               - horizontal scroll occurred,
+ *  SCROLLED_VERT|SCROLLED_HORIZ - scrolled in both directions.
+ */
+
+{
+       /* Cursor offsets for each track column */
+       static UWORD ColumnOff[COL_COUNT] =  { 0, 3, 4, 6, 7, 8 };
+
+       struct Rectangle        NewCurs;
+       UWORD                           NewState;
+
+
+       /* Do not attemt to draw a cursor if the editor is so small that
+        * it can't display anything,
+        */
+       if (!ped->DisplayTracks || !ped->DisplayLines)
+               return 0;
+
+       /* Check whether cursor is outside the display bounds and
+        * scroll pattern to make it visible if required.
+        */
+       {
+               if (ped->Line < ped->TopLine)
+                       return ScrollPattern (rp, ped, g, ped->LeftTrack, ped->Line);
+               else if (ped->Line >= ped->TopLine + ped->DisplayLines)
+                       return ScrollPattern (rp, ped, g, ped->LeftTrack, ped->Line - ped->DisplayLines + 1);
+               if (ped->Track < ped->LeftTrack)
+                       return ScrollPattern (rp, ped, g, ped->Track, ped->TopLine);
+               else if (ped->Track >= ped->LeftTrack + ped->DisplayTracks)
+                       return ScrollPattern (rp, ped, g, ped->Track - ped->DisplayTracks + 1, ped->TopLine);
+       }
+
+
+       /* Calculate new cursor rectangle */
+
+       NewCurs.MinX = ped->TBounds.Left +
+               (((ped->Track - ped->LeftTrack) * ped->TrackChars) + ColumnOff[ped->Column]) * ped->FontXSize;
+       NewCurs.MinY = ped->TBounds.Top + (ped->Line - ped->TopLine) * ped->FontYSize;
+       NewCurs.MaxX = NewCurs.MinX + ped->FontXSize - 1;
+       NewCurs.MaxY = NewCurs.MinY + ped->FontYSize - 1;
+
+
+       /* Note field is three characters wide */
+       if (ped->Column == COL_NOTE)
+               NewCurs.MaxX += ped->FontXSize * 2;
+
+
+       /* Set AreaPattern to show current cursor state */
+
+       if (!(g->Flags & GFLG_SELECTED))
+       {
+               NewState = IDS_DISABLED;
+
+               /* Set this pattern to draw an inactive cursor */
+               SetAfPt (rp, GhostPattern, 1);
+       }
+       else if (ped->Flags & PEF_MARKING)
+       {
+               NewState = IDS_BUSY;
+
+               /* Set this pattern to draw marking cursor */
+               SetAfPt (rp, MarkPattern, 1);
+       }
+       else NewState = IDS_SELECTED;
+
+       SetDrMd (rp, COMPLEMENT);
+       /* Draw cursor */
+       RectFill (rp, NewCurs.MinX, NewCurs.MinY,
+               NewCurs.MaxX, NewCurs.MaxY);
+       SetAfPt (rp, NULL, 0);  /* Reset AreaFill Pattern */
+
+       if ((ped->Flags & PEF_DOCURSORRULER) && (NewState == IDS_SELECTED))
+       {
+               if (ped->CursLinePos != NewCurs.MaxY)
+               {
+                       /* Draw horizontal line */
+
+                       /* Question: Is RectFill faster than Move()+Draw()? Hmmm... */
+                       RectFill (rp, ped->GBounds.Left, NewCurs.MaxY,
+                               ped->TBounds.Left + ped->TBounds.Width - 1, NewCurs.MaxY);
+               //      Move (rp, ped->GBounds.Left, NewCurs.MaxY);
+               //      Draw (rp, ped->TBounds.Left + ped->TBounds.Width - 1, NewCurs.MaxY);
+               }
+               /* Cause EraseCursor() to leave the old line alone */
+               else ped->CursLinePos = 0;
+       }
+
+
+       /* Erase old cursor */
+       EraseCursor (rp, ped);
+
+       if (ped->Flags & PEF_MARKING)
+               /* Update the range */
+               DrawRange (rp, ped);
+
+
+       /* Store new position and state */
+       ped->CursRect   = NewCurs;
+       ped->CursState  = NewState;
+
+       if ((ped->Flags & PEF_DOCURSORRULER) && (NewState == IDS_SELECTED))
+               ped->CursLinePos = NewCurs.MaxY;
+
+       return FALSE;
+}
+
+
+
+static void DrawRange (struct RastPort *rp, struct PattEditData *ped)
+{
+       UWORD tmin, tmax, lmin, lmax;
+       struct Rectangle newrange;
+
+       ped->RangeEndTrack      = ped->Track;
+
+       if (!(ped->Flags & PEF_MARKFULLTRACKS))
+               ped->RangeEndLine = ped->Line;
+
+       if (ped->RangeStartTrack < ped->RangeEndTrack)
+       {
+               tmin = ped->RangeStartTrack;
+               tmax = ped->RangeEndTrack;
+       }
+       else
+       {
+               tmin = ped->RangeEndTrack;
+               tmax = ped->RangeStartTrack;
+       }
+
+       if (ped->RangeStartLine < ped->RangeEndLine)
+       {
+               lmin = ped->RangeStartLine;
+               lmax = ped->RangeEndLine;
+       }
+       else
+       {
+               lmin = ped->RangeEndLine;
+               lmax = ped->RangeStartLine;
+       }
+
+       /* Limit to visible portion of range rectangle */
+
+       if (tmin < ped->LeftTrack)
+               tmin = ped->LeftTrack;
+       if (tmin >= ped->LeftTrack + ped->DisplayTracks)
+               tmin = ped->LeftTrack + ped->DisplayTracks - 1;
+
+       if (tmax < ped->LeftTrack)
+               tmax = ped->LeftTrack;
+       if (tmax >= ped->LeftTrack + ped->DisplayTracks)
+               tmax = ped->LeftTrack + ped->DisplayTracks - 1;
+
+       if (lmin < ped->TopLine)
+               lmin = ped->TopLine;
+       if (lmin >= ped->TopLine + ped->DisplayLines)
+               lmin = ped->TopLine + ped->DisplayLines - 1;
+
+       if (lmax < ped->TopLine)
+               lmax = ped->TopLine;
+       if (lmax >= ped->TopLine + ped->DisplayLines)
+               lmax = ped->TopLine + ped->DisplayLines - 1;
+
+
+       newrange.MinX = ped->TBounds.Left + (tmin - ped->LeftTrack) * ped->TrackWidth;
+       newrange.MinY = ped->TBounds.Top + (lmin - ped->TopLine) * ped->FontYSize;
+       newrange.MaxX = ped->TBounds.Left + ((tmax - ped->LeftTrack + 1) * ped->TrackWidth) - 1;
+       newrange.MaxY = ped->TBounds.Top + ((lmax - ped->TopLine + 1) * ped->FontYSize) - 1;
+
+#ifdef OS30_ONLY
+       SetWriteMask (rp, ped->TextPen | ped->BGPen);
+#else
+       SafeSetWriteMask (rp, ped->TextPen | ped->BGPen);
+#endif /* OS30_ONLY */
+
+       SetDrMd (rp, COMPLEMENT);
+
+
+       if (ped->RangeRect.MaxX == 0)
+       {
+               RectFill (rp, newrange.MinX,
+                       newrange.MinY,
+                       newrange.MaxX,
+                       newrange.MaxY);
+       }
+       else
+       {
+               /* Incremental range box update.  We only draw changes relative to last
+                * ranged box.  As the range box is drawn complementing bitplane 1,
+                * complementing it again will erase it, so we do not care if the box
+                * has grown or shrinked; we just complemnt delta boxes.
+                */
+
+               /*       range box
+                *      +---+-----+
+                *      |###|     |
+                *      |###|     |
+                *      |###|   <-+-- unchanged
+                *      |###|     |
+                *      +---+-----+
+                *        ^---------- changed
+                */
+               if (newrange.MinX != ped->RangeRect.MinX)
+                       RectFill (rp, min (ped->RangeRect.MinX, newrange.MinX),
+                               newrange.MinY,
+                               max (ped->RangeRect.MinX, newrange.MinX) - 1,
+                               newrange.MaxY);
+
+               /*      +-----+---+
+                *      |     |###|
+                *      |     |###|
+                *      |     |###|
+                *      |     |###|
+                *      +-----+---+
+                */
+               if (newrange.MaxX != ped->RangeRect.MaxX)
+                       RectFill (rp, min (ped->RangeRect.MaxX, newrange.MaxX) + 1,
+                               newrange.MinY,
+                               max (ped->RangeRect.MaxX, newrange.MaxX),
+                               newrange.MaxY);
+
+               /*      +---------+
+                *      |#########|
+                *      +---------+
+                *      |         |
+                *      |         |
+                *      +---------+
+                */
+               if (newrange.MinY != ped->RangeRect.MinY)
+                       RectFill (rp, ped->RangeRect.MinX,
+                               min (ped->RangeRect.MinY, newrange.MinY),
+                               ped->RangeRect.MaxX,
+                               max (ped->RangeRect.MinY, newrange.MinY) - 1);
+
+               /*      +---------+
+                *      |         |
+                *      |         |
+                *      +---------+
+                *      |#########|
+                *      +---------+
+                */
+               if (newrange.MaxY != ped->RangeRect.MaxY)
+                       RectFill (rp, ped->RangeRect.MinX,
+                               min (ped->RangeRect.MaxY, newrange.MaxY) + 1,
+                               ped->RangeRect.MaxX,
+                               max (ped->RangeRect.MaxY, newrange.MaxY));
+       }
+
+       /* Copy new values */
+       ped->RangeRect = newrange;
+
+#ifdef OS30_ONLY
+       SetWriteMask (rp, ~0);
+#else
+       SafeSetWriteMask (rp, (UBYTE)~0);
+#endif /* OS30_ONLY */
+}
+
+
+
+static void ClearRange (struct RastPort *rp, struct PattEditData *ped)
+{
+#ifdef OS30_ONLY
+       SetWriteMask (rp, ped->TextPen | ped->BGPen);
+#else
+       SafeSetWriteMask (rp, ped->TextPen | ped->BGPen);
+#endif /* OS30_ONLY */
+       SetDrMd (rp, COMPLEMENT);
+
+       RectFill (rp, ped->RangeRect.MinX, ped->RangeRect.MinY,
+               ped->RangeRect.MaxX, ped->RangeRect.MaxY);
+
+       memset (&ped->RangeRect, 0, sizeof (ped->RangeRect));
+
+#ifdef OS30_ONLY
+       SetWriteMask (rp, ~0);
+#else
+       SafeSetWriteMask (rp, (UBYTE)~0);
+#endif /* OS30_ONLY */
+}
+
+
+
+static void RedrawAll (struct RastPort *rp, struct PattEditData *ped, struct ExtGadget *g)
+{
+       UWORD   HOffset,
+                       tmp;
+       ULONG   i;
+
+
+       /* Erase the whole gadget imagery */
+
+       SetAPen (rp, ped->BGPen);
+       SetDrMd (rp, JAM2);
+
+       if (!ped->Patt)
+       {
+               /* Clear everything */
+
+               RectFill (rp, ped->GBounds.Left, ped->GBounds.Top,
+                       ped->GBounds.Left + ped->GBounds.Width - 1, ped->GBounds.Top + ped->GBounds.Height - 1);
+               return;
+       }
+
+       if (rp->BitMap->Depth > 1)
+       {
+               /* Do not clear the bitplanes used by the text because they will
+                * be completely redrawn later.
+                */
+#ifdef OS30_ONLY
+               SetWriteMask (rp, ~ped->TextPen);
+#else
+               SafeSetWriteMask (rp, (UBYTE)~ped->TextPen);
+#endif /* OS30_ONLY */
+
+
+               /* +------------+
+                * |*********   |
+                * |*Cleared*   |
+                * |*********   |
+                * |            |
+                * +------------+
+                */
+               RectFill (rp, ped->GBounds.Left, ped->GBounds.Top,
+                       ped->TBounds.Left + ped->TBounds.Width - 1, ped->TBounds.Top + ped->TBounds.Height - 1);
+
+               /* Restore the Mask */
+#ifdef OS30_ONLY
+               SetWriteMask (rp, ~0);
+#else
+               SafeSetWriteMask (rp, (UBYTE)~0);
+#endif /* OS30_ONLY */
+       }
+
+       /* Now clear the area at the right and bottom side of
+        * the editing field.
+        */
+
+               /* +------------+
+                * |         ***|
+                * |         ***|
+                * |         ***|
+                * |            |
+                * +------------+
+                */
+       if (ped->TBounds.Left + ped->TBounds.Width < ped->GBounds.Left + ped->GBounds.Width)
+               RectFill (rp, ped->TBounds.Left + ped->TBounds.Width, ped->GBounds.Top,
+                       ped->GBounds.Left + ped->GBounds.Width - 1, ped->TBounds.Top + ped->TBounds.Height - 1);
+
+               /* +------------+
+                * |            |
+                * |            |
+                * |            |
+                * |************|
+                * +------------+
+                */
+       if (ped->TBounds.Top + ped->TBounds.Height < ped->GBounds.Top + ped->GBounds.Height)
+               RectFill (rp, ped->GBounds.Left, ped->TBounds.Top + ped->TBounds.Height,
+                       ped->GBounds.Left + ped->GBounds.Width - 1, ped->GBounds.Top + ped->GBounds.Height - 1);
+
+
+       /* Cursor shouldn't be deleted again since everything has been cleared */
+       ped->CursState          = IDS_NORMAL;
+       ped->CursLinePos        = 0;
+
+       if (!(ped->Patt)) return;
+
+       /* Draw track separator lines */
+       SetAPen (rp, ped->LinesPen);
+       DrawTrackSeparators (rp, ped, ped->TrackWidth);
+
+       /* Draw tiny vertical separator lines */
+       if (ped->Flags & PEF_DOTINYLINES)
+       {
+               SetAPen (rp, ped->TinyLinesPen);
+               SetDrPt (rp, 0xCCCC);
+               HOffset = ped->TBounds.Left - 1;
+
+               for (i = 0; i < ped->DisplayTracks; i++, HOffset += ped->TrackWidth)
+               {
+                       Move (rp, tmp = HOffset + ped->FontXSize * 3, ped->GBounds.Top);
+                       Draw (rp, tmp, ped->GBounds.Top + ped->GBounds.Height - 1);
+                       Move (rp, tmp = HOffset + ped->FontXSize * 5, ped->GBounds.Top);
+                       Draw (rp, tmp, ped->GBounds.Top + ped->GBounds.Height - 1);
+                       Move (rp, tmp = HOffset + ped->FontXSize * 7, ped->GBounds.Top);
+                       Draw (rp, tmp, ped->GBounds.Top + ped->GBounds.Height - 1);
+               }
+
+               SetDrPt (rp, 0xFFFF);
+       }
+
+       RedrawPattern (rp, ped, g);
+
+       DrawTrackNumbers (rp, ped);
+}
+
+
+
+static void DrawTrackSeparators (struct RastPort *rp, struct PattEditData *ped, UWORD width)
+{
+       UWORD i, HOffset = ped->TBounds.Left - 1;
+
+       for (i = 0; i <= ped->DisplayTracks; i++, HOffset += width)
+       {
+               /* Never draw outside gadget bounds */
+               if (HOffset >= ped->GBounds.Left + ped->GBounds.Width + 1) break;
+               RectFill (rp, HOffset - 1, ped->GBounds.Top, HOffset, ped->GBounds.Top + ped->GBounds.Height - 1);
+       }
+}
+
+
+
+static void DrawTrackNumbers (struct RastPort *rp, struct PattEditData *ped)
+{
+       struct TextFont *oldfont  = rp->Font;
+       UWORD   HOffset = ped->TBounds.Left + ped->FontXSize * 5;
+       UWORD   i;
+       UBYTE   ch;
+
+
+#ifndef OS30_ONLY
+       if (GfxBase->LibNode.lib_Version < 39)
+       {
+               rp->Mask = ped->LinesPen;
+               SetAPen (rp, ped->LinesPen);
+               SetBPen (rp, ped->BGPen);
+               SetDrMd (rp, JAM2);     /* JAM1 or JAM2... Which one is faster? */
+       }
+       else
+       {
+#endif /* !OS30_ONLY */
+               SetWrMsk (rp, ped->LinesPen);
+               SetABPenDrMd (rp, ped->LinesPen, ped->BGPen, JAM2);
+#ifndef OS30_ONLY
+       }
+#endif /* !OS30_ONLY */
+
+       SetFont (rp, ped->EditorFont);
+
+       for (i = 0; i < ped->DisplayTracks; i++, HOffset += ped->TrackWidth)
+       {
+               Move (rp, HOffset, ped->GBounds.Top + ped->EditorFont->tf_Baseline);
+               ch = HexValuesNo0[((i+ped->LeftTrack)>>4) & 0xF];
+               Text (rp, &ch, 1);
+               Move (rp, HOffset, ped->GBounds.Top + ped->FontXSize + ped->EditorFont->tf_Baseline);
+               ch = HexValues[(i+ped->LeftTrack) & 0xF];
+               Text (rp, &ch, 1);
+       }
+
+       SetFont (rp, oldfont);
+}
+
+
+
+static void RedrawPattern (struct RastPort *rp, struct PattEditData *ped, struct ExtGadget *g)
+{
+       EraseCursor (rp, ped);
+
+       /* Redraw all lines */
+       if (ped->DisplayLines)
+               DrawPatternLines (rp, ped, 0, ped->DisplayLines - 1);
+
+       if (ped->Flags & PEF_MARKING)
+               /* Clear the range rectangle bounds so the next call
+                * to DrawCursor() will redraw all the range box.
+                */
+               memset (&ped->RangeRect, 0, sizeof (ped->RangeRect));
+
+       DrawCursor (rp, ped, g);
+}
+
+
+
+static void DrawPatternLines (struct RastPort *rp, struct PattEditData *ped, UWORD min, UWORD max)
+
+/* Draws Pattern display lines.  Only lines from <min> to <max> are drawn.
+ * To redraw all the display, pass min = 0 and max = ped->DisplayLines - 1.
+ */
+{
+       struct Pattern  *patt;
+       struct TextFont *oldfont = rp->Font;
+       UBYTE                   *l;
+       UWORD                    i, j;
+       UWORD                    VOffset = ped->TBounds.Top + ped->EditorFont->tf_Baseline
+                                               + min * ped->FontYSize;
+
+       /* Note well: I've put 32 and not MAXTRACKS here because 255 tracks would
+        * have used too much stack. :-(
+        */
+       ALIGNED UBYTE   line[MAXTRACKCHARS * 32 + 4];
+
+
+       if (!(patt = ped->Patt)) return;
+
+       SetFont (rp, ped->EditorFont);
+
+#ifndef OS30_ONLY
+       if (GfxBase->LibNode.lib_Version >= 39)
+       {
+#endif /* !OS30_ONLY */
+               SetWriteMask (rp, ped->TextPen | ped->BGPen);
+               SetABPenDrMd (rp, ped->TextPen, ped->BGPen, JAM2);
+#ifndef OS30_ONLY
+       }
+       else
+       {
+               rp->Mask = ped->TextPen | ped->BGPen;
+               SetAPen (rp, ped->TextPen);
+               SetBPen (rp, ped->BGPen);
+               SetDrMd (rp, JAM2);     /* JAM1 or JAM2... Which one is faster? */
+       }
+#endif /* !OS30_ONLY */
+
+
+       for (i = min; i <= max; i++)
+       {
+               l = line;
+
+               /* Write Line Numbers column */
+
+               if (ped->Flags & PEF_HEXMODE)
+               {
+                       *l++ = HexValues[((i+ped->TopLine)>>8) & 0xF];
+                       *l++ = HexValues[((i+ped->TopLine)>>4) & 0xF];
+                       *l++ = HexValues[((i+ped->TopLine) & 0xF)];
+               }
+               else
+               {
+                       *l++ = HexValues[((i+ped->TopLine) / 100) % 10];
+                       *l++ = HexValues[((i+ped->TopLine) / 10) % 10];
+                       *l++ = HexValues[((i+ped->TopLine) % 10)];
+               }
+
+               *l++ = ' ';
+
+               for (j = 0; j < ped->DisplayTracks; j++, l += ped->TrackChars)
+                       ped->Note2ASCIIFunc (l, &patt->Notes[j+ped->LeftTrack][i+ped->TopLine]);
+
+               Move (rp, ped->GBounds.Left, VOffset);
+               Text (rp, line, ped->DisplayTracks * ped->TrackChars + 4);
+               VOffset += ped->FontYSize;
+       }
+
+#ifdef OS30_ONLY
+       SetWriteMask (rp, ~0);
+#else
+       SafeSetWriteMask (rp, (UBYTE)~0);
+#endif /* OS30_ONLY */
+       SetFont (rp, oldfont);
+}
+
+
+
+static void DrawNote (struct RastPort *rp, struct PattEditData *ped)
+
+/* Draws the note under the cursor. DrawNote() won't draw if the cursor
+ * is outside of the editing area. Range marking mode is also taken into
+ * account and the colors of the note will be reversed when the note must
+ * be hilighted.
+ */
+{
+       struct Pattern          *patt;
+       struct TextFont         *oldfont = rp->Font;
+
+       UBYTE buf[MAXTRACKCHARS];
+
+
+       if (!(patt = ped->Patt)) return;
+
+       /* Is the cursor outside the editing area? */
+
+       if ((ped->Line < ped->TopLine) || (ped->Line >= ped->TopLine + ped->DisplayLines) ||
+               (ped->Track < ped->LeftTrack) || (ped->Track >= ped->LeftTrack + ped->DisplayTracks))
+                       return;
+
+       SetFont (rp, ped->EditorFont);
+
+#ifndef OS30_ONLY
+       if (GfxBase->LibNode.lib_Version >= 39)
+       {
+#endif /* OS30_ONLY */
+               SetWriteMask (rp, ped->TextPen | ped->BGPen);
+               if (ped->Flags & PEF_MARKING)
+                       SetABPenDrMd (rp, ped->BGPen, ped->TextPen, JAM2);
+               else
+                       SetABPenDrMd (rp, ped->TextPen, ped->BGPen, JAM2);
+#ifndef OS30_ONLY
+       }
+       else
+       {
+               rp->Mask = ped->TextPen | ped->BGPen;
+               if (ped->Flags & PEF_MARKING)
+               {
+                       SetAPen (rp, ped->BGPen);
+                       SetBPen (rp, ped->TextPen);
+               }
+               else
+               {
+                       SetAPen (rp, ped->TextPen);
+                       SetBPen (rp, ped->BGPen);
+               }
+               SetDrMd (rp, JAM2);
+       }
+#endif /* OS30_ONLY */
+
+
+       ped->Note2ASCIIFunc (buf, &patt->Notes[ped->Track][ped->Line]);
+
+       Move (rp, ped->TBounds.Left + ((ped->Track - ped->LeftTrack) * ped->TrackWidth),
+               ped->TBounds.Top + ped->EditorFont->tf_Baseline + (ped->Line - ped->TopLine) * ped->FontYSize);
+       Text (rp, buf, ped->TrackChars);
+
+#ifdef OS30_ONLY
+       SetWriteMask (rp, (UBYTE)~0);
+#else
+       SafeSetWriteMask (rp, (UBYTE)~0);
+#endif /* OS30_ONLY */
+
+       SetFont (rp, oldfont);
+}
+
+
+
+#ifdef PORTABLE
+
+void ASMCALL Note2ASCIIBlank0 (REG(a1, UBYTE *s), REG(a2, struct Note *note))
+
+/* Fill buffer <s> with the ASCII representation of a
+ * Note structure.  <s> will be able to hold at
+ * least <ped->TrackChars> characters.
+ */
+{
+       *((ULONG *)s) = TextNotes[note->Note];
+
+       if (note->Inst)
+       {
+               s[3] = HexValuesNo0[note->Inst>>4];
+               s[4] = HexValues[note->Inst & 0x0F];
+       }
+       else
+       {
+               s[3] = ' ';
+               s[4] = '-';
+       }
+
+       s[5] = ' ';
+
+       if (note->EffNum || note->EffVal)
+       {
+               s[6] = HexValues[note->EffNum];
+               s[7] = HexValues[note->EffVal>>4];
+               s[8] = HexValues[note->EffVal & 0xF];
+               s[9] = ' ';
+       }
+       else
+               *((ULONG *)(s+6)) = ' .  ';
+}
+
+
+
+void ASMCALL Note2ASCII (REG(a1, UBYTE *s), REG(a2, struct Note *note))
+
+/* Fill buffer <s> with the ASCII representation of a
+ * Note structure.  <s> will be able to hold at
+ * least <ped->TrackChars> characters.
+ */
+{
+       *((ULONG *)s) = note->Note ? TextNotes[note->Note] : '--- ';
+
+       s[3] = HexValuesNo0[note->Inst>>4];
+       s[4] = HexValues[note->Inst & 0x0F];
+
+       s[5] = ' ';
+
+       s[6] = HexValues[note->EffNum];
+       s[7] = HexValues[note->EffVal>>4];
+       s[8] = HexValues[note->EffVal & 0xF];
+       s[9] = ' ';
+}
+
+#endif /* PORTABLE */
+
+
+
+static UWORD ScrollPattern (struct RastPort *rp, struct PattEditData *ped, struct ExtGadget *g, UWORD lefttrack, UWORD topline)
+/*
+ * Scroll the display to the new position <lefttrack>, <topline>.
+ *
+ * RESULT
+ *  0                              - no scrolling occurred,
+ *  SCROLLED_VERT                  - vertical scroll occurred,
+ *  SCROLLED_HORIZ                 - horizontal scroll occurred,
+ *  SCROLLED_VERT | SCROLLED_HORIZ - scrolled in both directions.
+ */
+{
+       if (ped->LeftTrack != lefttrack)
+       {
+               UWORD retval = SCROLLED_HORIZ | ped->TopLine != topline;
+
+               ped->LeftTrack = lefttrack;
+               ped->TopLine = topline;
+
+               EraseCursor (rp, ped);
+               DrawTrackNumbers (rp, ped);
+               RedrawPattern (rp, ped, g);
+
+               return retval;
+       }
+       else
+       {
+               UWORD scroll_lines = abs(ped->TopLine - topline);
+
+               /* Redraw everything if more than an half of the screen
+                * has scrolled.  When in mark mode, the scroll limit is set
+                * to two thirds of the display, because redrawing the mark
+                * region takes up a lot of time.
+                */
+               //if (scroll_lines > ((ped->Flags & PEF_MARKING) ? ((ped->DisplayLines * 3) >> 1) : (ped->DisplayLines >> 1)))
+               if (scroll_lines > (ped->DisplayLines * 3) >> 1)
+               {
+                       ped->LeftTrack = lefttrack;
+                       ped->TopLine = topline;
+
+                       EraseCursor (rp, ped);
+                       RedrawPattern (rp, ped, g);
+                       return SCROLLED_VERT;
+               }
+               else
+               {
+                       WORD scroll_dy = (topline - ped->TopLine) * ped->FontYSize;
+
+                       EraseCursor (rp, ped);
+
+                       /* Scroll range rectangle */
+
+                       ped->RangeRect.MinY -= scroll_dy;
+                       ped->RangeRect.MaxY -= scroll_dy;
+
+                       if (ped->RangeRect.MinY < ped->TBounds.Top)
+                               ped->RangeRect.MinY = ped->TBounds.Top;
+                       if (ped->RangeRect.MaxY >= ped->TBounds.Top + ped->TBounds.Height)
+                               ped->RangeRect.MaxY = ped->TBounds.Top + ped->TBounds.Height - 1;
+
+                       /* We use ClipBlit() to scroll the pattern because it doesn't clear
+                        * the scrolled region like ScrollRaster() would do.  Unfortunately,
+                        * ClipBlit() does not scroll along the damage regions, so we also
+                        * call ScrollRaster() with the mask set to 0, which will scroll the
+                        * layer damage regions without actually modifying the display.
+                        */
+
+#ifdef OS30_ONLY
+                       SetWriteMask (rp, ped->TextPen | ped->BGPen);
+#else
+                       SafeSetWriteMask (rp, ped->TextPen | ped->BGPen);
+#endif /* OS30_ONLY */
+
+                       if (scroll_dy > 0)
+                               /* Scroll Down */
+                               ClipBlit (rp, ped->GBounds.Left, ped->TBounds.Top + scroll_dy,
+                                       rp, ped->GBounds.Left, ped->TBounds.Top,
+                                       ped->TBounds.Width + (ped->TBounds.Left - ped->GBounds.Left), ped->TBounds.Height - scroll_dy,
+                                       0x0C0);
+                       else
+                               /* Scroll Up */
+                               ClipBlit (rp, ped->GBounds.Left, ped->TBounds.Top,
+                                       rp, ped->GBounds.Left, ped->TBounds.Top - scroll_dy,
+                                       ped->TBounds.Width + (ped->TBounds.Left - ped->GBounds.Left), ped->TBounds.Height + scroll_dy,
+                                       0x0C0);
+
+
+                       /* This will scroll the layer damage regions without actually
+                        * scrolling the display.
+                        */
+                       if (rp->Layer->Flags & LAYERSIMPLE)
+                       {
+#ifdef OS30_ONLY
+                               SetWriteMask (rp, 0);
+#else
+                               SafeSetWriteMask (rp, 0);
+#endif /* OS30_ONLY */
+
+                               ScrollRaster (rp, 0, scroll_dy,
+                                       ped->GBounds.Left, ped->TBounds.Top,
+                                       ped->TBounds.Left + ped->TBounds.Width - 1,
+                                       ped->TBounds.Top + ped->TBounds.Height - 1);
+                       }
+
+                       ped->LeftTrack = lefttrack;
+                       ped->TopLine = topline;
+
+                       if (scroll_dy > 0)
+                               /* Scroll down */
+                               DrawPatternLines (rp, ped, ped->DisplayLines - scroll_lines, ped->DisplayLines-1);
+                       else
+                               /* Scroll up */
+                               DrawPatternLines (rp, ped, 0, scroll_lines - 1);
+
+
+#ifdef OS30_ONLY
+                       SetWriteMask (rp, ~0);
+#else
+                       SafeSetWriteMask (rp, (UBYTE)~0);
+#endif /* OS30_ONLY */
+
+                       DrawCursor (rp, ped, g);
+
+                       return SCROLLED_VERT;
+               }
+       }
+
+       return 0;
+}
+
+
+
+#if 0
+INTCALL static void TimerIntServer (REG(a1) struct ExtGadget *g)
+{
+       struct PattEditData *ped = INST_DATA (OCLASS(g), g); /*!*/
+
+       /* Remove TimerIO for further usage */
+       GetMsg (&ped->TimerPort);
+
+       if ((ped->Flags & PEF_SCROLLING) && ped->TopLine)
+       {
+               SetGadgetAttrs (g, ped->MyWindow, NULL,
+                       PEA_Up, 1,
+                       TAG_DONE);
+
+               /* Send another request for next scroll */
+               ped->TimerIO.tr_node.io_Command = TR_ADDREQUEST;
+               ped->TimerIO.tr_time.tv_micro = 500000;
+               BeginIO ((struct IORequest *)&ped->TimerIO);
+       }
+}
+#endif
+
+
+
+/*
+ * Class library support functions
+ */
+
+HOOKCALL struct ClassLibrary * _UserLibInit (REG(a6, struct ClassLibrary *mybase))
+{
+       SysBase = *((struct ExecBase **)4);     /* Initialize SysBase */
+
+       IntuitionBase   = (struct IntuitionBase *) OpenLibrary ("intuition.library", WANTED_LIBVER);
+       GfxBase                 = (struct GfxBase *) OpenLibrary ("graphics.library", WANTED_LIBVER);
+       UtilityBase             = (struct UtilityBase *) OpenLibrary ("utility.library", WANTED_LIBVER);
+       KeymapBase              = OpenLibrary ("keymap.library", 36);   /* Kick V37 has keymap V36! */
+
+       if (!(IntuitionBase && GfxBase && UtilityBase && KeymapBase))
+       {
+               _UserLibCleanup (mybase);
+               return NULL;
+       }
+
+       if (mybase->cl_Class = MakeClass (PATTEDITCLASS, GADGETCLASS, NULL, sizeof (struct PattEditData), 0))
+       {
+               mybase->cl_Class->cl_Dispatcher.h_Entry = (ULONG (*)()) PattEditDispatcher;
+               AddClass (mybase->cl_Class);
+       }
+       else
+       {
+               _UserLibCleanup (mybase);
+               return NULL;
+       }
+
+       return mybase;
+}
+
+
+
+HOOKCALL struct ClassLibrary * _UserLibCleanup (REG(a6, struct ClassLibrary *mybase))
+{
+       if (mybase->cl_Class)
+               if (!FreeClass (mybase->cl_Class))
+                       return NULL;
+
+       /* The tests against NULL are needed here because CloseLibrary()
+        * in V34 crashes when passed a NULL pointer, and this
+        * function might get called when someone tries to load this
+        * library on an 1.3 system.
+        */
+       if (KeymapBase)         CloseLibrary ((struct Library *)KeymapBase);
+       if (UtilityBase)        CloseLibrary ((struct Library *)UtilityBase);
+       if (GfxBase)            CloseLibrary ((struct Library *)GfxBase);
+       if (IntuitionBase)      CloseLibrary ((struct Library *)IntuitionBase);
+
+       return mybase;
+}
+
+
+
+HOOKCALL Class * _GetEngine (REG(a6, struct ClassLibrary *mybase))
+{
+       return (mybase->cl_Class);
+}
diff --git a/Gadgets/PattEditClassAsm.asm b/Gadgets/PattEditClassAsm.asm
new file mode 100644 (file)
index 0000000..68dd865
--- /dev/null
@@ -0,0 +1,183 @@
+**
+**     PattEditClassAsm.asm
+**
+**     Copyright (C) 1995,96 Bernardo Innocenti
+**
+**     Pattern Editor gadget assembler subroutines
+**
+
+       INCDIR  Include:
+
+       SECTION __merged,code
+
+       include libraries/SongClass.i
+
+       xdef _Note2ASCIIBlank0
+       xdef _Note2ASCII
+
+
+
+* void Note2ASCIIBlank0 (UBYTE *s, struct Note *note)
+*                        A1        A2
+*
+* Fill the buffer pointed by A1 with the ASCII representation of the
+* Note structure pointed by A2.  The buffer will be able to hold at
+* least <ped->TrackChars> characters.
+
+_Note2ASCIIBlank0:
+       move.w  d2,-(sp)
+       moveq   #0,d1
+       moveq   #0,d2
+       lea     TextNotes(pc),a0
+       move.b  note_Note(a2),d1        ; note_Note
+       add.w   d1,d1
+       add.w   d1,d1
+       move.l  (a0,d1.w),d0
+
+       move.b  note_Inst(a2),d1
+       lea     HexValuesNo0(pc),a0
+       move.b  d1,d2
+       lsr.b   #4,d2
+       move.b  (a0,d2.w),d0
+       move.l  d0,(a1)+                ; Put Note and Instrument high nibble
+
+       tst.b   d1
+       beq.s   .noinst
+
+       and.w   #$0F,d1
+       lea     HexValues(pc),a0
+       move.b  (a0,d1),d1
+       lsl.w   #8,d1
+       move.b  #' ',d1
+       move.w  d1,(a1)+                ; Put Instrument low nibble and white space
+
+       bra.s   .endif
+.noinst
+       move.w  #'- ',(a1)+             ; Put Instrument low nibble and white space
+
+.endif
+
+       moveq   #0,d0
+       lea     HexValues(pc),a0
+       move.b  note_EffNum(a2),d0
+       move.b  note_EffVal(a2),d1
+       move.b  d1,d2
+       sub.b   d0,d1
+       beq.s   .noeff
+
+       moveq   #0,d1
+       move.b  (a0,d0.w),d0
+       move.b  d2,d1
+       lsl.w   #8,d0
+       lsr.b   #4,d1
+       move.b  (a0,d1.w),d0
+       move.w  d0,(a1)+                ;Put EffNum and EffVal high nibble
+
+       and.w   #$F,d2
+       move.b  (a0,d2.w),d2
+       lsl.w   #8,d2
+       move.b  #' ',d2
+       move.w  d2,(a1)+                ; Put EffVal low nibble and space
+       move.w  (sp)+,d2
+       rts
+
+       move.w  (sp)+,d2
+       rts
+
+.noeff
+       move.l  #' .  ',(a1)+           ; Put white spaces
+       move.w  (sp)+,d2
+       rts
+
+
+* void Note2ASCII (UBYTE *s, struct Note *note)
+*                  A1        A2
+*
+* Fill the buffer pointed by A1 with the ASCII representation of the
+* Note structure pointed by A2.  The buffer will be able to hold at
+* least <ped->TrackChars> characters.
+
+_Note2ASCII:
+       moveq   #0,d0
+       move.b  note_Note(a2),d0        ; note_Note
+       beq.s   .nonote
+
+       lea     TextNotes(pc),a0
+       add.w   d0,d0
+       add.w   d0,d0
+       move.l  (a0,d0.w),(a1)          ; Put Note
+       bra.s   .endif
+
+.nonote
+       move.l  #'--- ',(a1)
+
+.endif
+       lea             3(a1),a1
+
+       move.b  note_Inst(a2),d0
+       moveq   #0,d1
+       move.b  d0,d1
+
+       lea     HexValuesNo0(pc),a0
+       lsr.b   #4,d1
+       move.b  (a0,d1),(a1)+           ; Put Instrument high nibble
+
+       and.w   #$0F,d0
+       lea     HexValues(pc),a0
+       move.b  (a0,d0),(a1)+           ; Put Instrument low nibble
+
+       move.b  #' ',(a1)+              ; Put white space
+
+       move.w  d2,-(sp)
+       moveq   #0,d0
+       lea     HexValues(pc),a0
+       move.b  note_EffNum(a2),d0
+       move.b  note_EffVal(a2),d1
+       move.b  d1,d2
+
+       move.b  (a0,d0.w),d0
+       move.b  d2,d1
+       lsl.w   #8,d0
+       lsr.b   #4,d1
+       move.b  (a0,d1.w),d0
+       move.w  d0,(a1)+                ;Put EffNum and EffVal high nibble
+
+       and.w   #$F,d2
+       move.b  (a0,d2.w),d2
+       lsl.w   #8,d2
+       move.b  #' ',d2
+       move.w  d2,(a1)+                ; Put EffVal low nibble and space
+       move.w  (sp)+,d2
+       rts
+
+
+       CNOP    0,4
+TextNotes:
+
+       dc.l    ' -  '
+
+       dc.l    'C-0 ', 'C#0 ', 'D-0 ', 'D#0 ', 'E-0 ', 'F-0 '
+       dc.l    'F#0 ', 'G-0 ', 'G#0 ', 'A-0 ', 'A#0 ', 'B-0 '
+
+       dc.l    'C-1 ', 'C#1 ', 'D-1 ', 'D#1 ', 'E-1 ', 'F-1 '
+       dc.l    'F#1 ', 'G-1 ', 'G#1 ', 'A-1 ', 'A#1 ', 'B-1 '
+
+       dc.l    'C-2 ', 'C#2 ', 'D-2 ', 'D#2 ', 'E-2 ', 'F-2 '
+       dc.l    'F#2 ', 'G-2 ', 'G#2 ', 'A-2 ', 'A#2 ', 'B-2 '
+
+       dc.l    'C-3 ', 'C#3 ', 'D-3 ', 'D#3 ', 'E-3 ', 'F-3 '
+       dc.l    'F#3 ', 'G-3 ', 'G#3 ', 'A-3 ', 'A#3 ', 'B-3 '
+
+       dc.l    'C-4 ', 'C#4 ', 'D-4 ', 'D#4 ', 'E-4 ', 'F-4 '
+       dc.l    'F#4 ', 'G-4 ', 'G#4 ', 'A-4 ', 'A#4 ', 'B-4 '
+
+       dc.l    'C-5 ', 'C#5 ', 'D-5 ', 'D#5 ', 'E-5 ', 'F-5 '
+       dc.l    'F#5 ', 'G-5 ', 'G#5 ', 'A-5 ', 'A#5 ', 'B-5 '
+
+HexValues:
+       dc.b    '0','1','2','3','4','5','6','7','8','9'
+       dc.b    'A','B','C','D','E','F','G','H','I','J'
+
+HexValuesNo0:
+       dc.b    ' ','1','2','3','4','5','6','7','8','9'
+       dc.b    'A','B','C','D','E','F'
diff --git a/Gadgets/SCOPTIONS b/Gadgets/SCOPTIONS
new file mode 100644 (file)
index 0000000..b074877
--- /dev/null
@@ -0,0 +1,29 @@
+CODE=FAR
+PARAMETERS=REGISTERS
+NOSTACKCHECK
+STRINGMERGE
+NOCHECKABORT
+COMMENTNEST
+ERRORREXX
+NOMULTIPLEINCLUDES
+OPTIMIZERINLINELOCAL
+SMALLCODE
+SMALLDATA
+NOVERSION
+ABSFUNCPOINTER
+UTILITYLIBRARY
+NOICONS
+MEMORYSIZE=HUGE
+NOERRORHIGHLIGHT
+ONERROR=CONTINUE
+OPTIMIZERTIME
+MULTIPLECHARACTERCONSTANTS
+DEFINE NO_REQTOOLS_OBSOLETE
+DEFINE ASL_V38_NAMES_ONLY
+DEFINE INTUI_V36_NAMES_ONLY
+DEFINE IFFPARSE_V37_NAMES_ONLY
+DEFINE USE_BUILTIN_MATH
+DEFINE __USE_SYSBASE
+IGNORE=193
+IGNORE=306
+PUBSCREEN=Workbench
diff --git a/Gadgets/SampEditClass.c b/Gadgets/SampEditClass.c
new file mode 100644 (file)
index 0000000..7c140ff
--- /dev/null
@@ -0,0 +1,844 @@
+/*
+**     SampEditClass.c
+**
+**     Copyright (C) 1995,96 Bernardo Innocenti
+**
+**     Sample editor class built on top of the "gadgetclass".
+*/
+
+#include <string.h>
+
+#include <exec/types.h>
+#include <exec/memory.h>
+#include <exec/nodes.h>
+#include <exec/ports.h>
+#include <utility/tagitem.h>
+#include <utility/hooks.h>
+#include <devices/inputevent.h>
+#include <intuition/intuition.h>
+#include <intuition/intuitionbase.h>
+#include <intuition/classes.h>
+#include <intuition/classusr.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/imageclass.h>
+#include <graphics/gfxbase.h>
+#include <graphics/gfxmacros.h>
+#include <graphics/text.h>
+#include <libraries/songclass.h>
+#include <libraries/sampeditclass.h>
+
+#include <clib/exec_protos.h>
+#include <clib/intuition_protos.h>
+#include <clib/graphics_protos.h>
+#include <clib/utility_protos.h>
+#include <clib/alib_protos.h>
+
+#include <pragmas/exec_sysbase_pragmas.h>
+#include <pragmas/intuition_pragmas.h>
+#include <pragmas/graphics_pragmas.h>
+#include <pragmas/utility_pragmas.h>
+
+
+/* Some handy definitions missing in <devices/inputevent.h> */
+#define IEQUALIFIER_SHIFT      (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)
+#define IEQUALIFIER_ALT                (IEQUALIFIER_LALT | IEQUALIFIER_RALT)
+#define IEQUALIFIER_COMMAND    (IEQUALIFIER_LCOMMAND | IEQUALIFIER_RCOMMAND)
+
+/* Scrolling control */
+#define SCROLLED_VERT  1
+#define SCROLLED_HORIZ 2
+
+
+
+struct SampEditData
+{
+       struct Instrument       *Instr;
+
+       /* These are the actual Gadget position and size,
+        * regardless of any GREL_#? flags.
+        */
+       struct IBox              GBounds;
+
+       ULONG                    Flags;                 /* See definitions in <SampEditClass.h>                 */
+
+       UWORD                    XStart;                /* X offset from the beginning of the sample    */
+       UWORD                    XVisible;              /* Number of visible samples                                    */
+
+       UWORD                    CursState;
+       UWORD                    CursX;                 /* Cursor position (which sample)                               */
+       UWORD                    CursScrPosX;   /* Screen cursor position to erase it quickly.  */
+
+       UWORD                    Pad0;
+
+       UWORD                    BackupCursX;   /* Cursor drag undo */
+       UWORD                    BackupXStart;
+
+       ULONG                    XRatio;                        /* X Zoom ratio, 16:16 bit fixed point number   */
+
+       /* Range marking info */
+       UWORD                    RangeStartX;
+       UWORD                    RangeStartY;
+       UWORD                    RangeEndX;
+       UWORD                    RangeEndY;
+
+       struct Rectangle RangeRect;             /* Backup of range rect to erase it quickly. */
+
+       /* Rendering routine for the sample. */
+       void                    (*RenderFunc)(struct Note *note, UBYTE *s);
+
+       /* Pens used to draw various gadget elements */
+       ULONG                    BackgroundPen;
+       ULONG                    LinePen;
+       ULONG                    FillPen;
+       ULONG                    GridPen;
+
+
+       /* For double click test */
+       ULONG                    DoubleClickSeconds;
+       ULONG                    DoubleClickMicros;
+};
+
+
+
+
+/* Function prototypes */
+
+static ULONG __asm SampEditDispatcher (register __a0 Class *cl,
+                                                               register __a2 struct ExtGadget *g,
+                                                               register __a1 Msg msg);
+static ULONG   SAMP_HandleInputMethod  (Class *cl, struct ExtGadget *g, struct gpInput *msg);
+static void            SAMP_RenderMethod       (Class *cl, struct ExtGadget *g, struct gpRender *msg);
+static ULONG   SAMP_SetMethod          (Class *cl, struct ExtGadget *g, struct opSet *msg);
+static ULONG   SAMP_GetMethod          (Class *cl, struct ExtGadget *g, struct opGet *msg);
+static ULONG   SAMP_NewMethod          (Class *cl, struct ExtGadget *g, struct opSet *msg);
+
+static void            GetGadgetBox    (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *rect);
+static BOOL            CalcDisplaySize (struct SampEditData *sed, struct ExtGadget *g, struct GadgetInfo *gpi);
+static void            SaveUndo                (struct SampEditData *sed);
+static BOOL            UndoChange              (struct SampEditData *sed);
+static BOOL            RedoChange              (struct SampEditData *sed);
+static void            FreeUndoBuffers (struct SampEditData *sed, BOOL freeall);
+static BOOL            MoveCursor              (struct SampEditData *sed, WORD x /*, WORD y*/);
+static void            EraseCursor             (struct RastPort *rp, struct SampEditData *sed);
+static UWORD   DrawCursor              (struct RastPort *rp, struct SampEditData *sed, struct ExtGadget *g);
+static void            DrawRange               (struct RastPort *rp, struct SampEditData *sed);
+static void            ClearRange              (struct RastPort *rp, struct SampEditData *sed);
+static void            RedrawAll               (struct RastPort *rp, struct SampEditData *sed, struct ExtGadget *g);
+static void            RedrawSample    (struct RastPort *rp, struct SampEditData *sed, struct ExtGadget *g);
+static void            DrawGrid                (struct RastPort *rp, struct SampEditData *sed, UWORD min, UWORD max);
+static UWORD   ScrollDisplay   (struct RastPort *rp, struct SampEditData *sed, struct ExtGadget *g,
+                                                               UWORD lefttrack, UWORD topline);
+static void            NotifyCursor    (struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags);
+static void            NotifyHSlider   (struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags);
+
+//static void __asm TimerIntServer (register __a1 struct Gadget *g);
+
+struct Library * __asm _UserLibInit    (register __a6 struct Library *mybase);
+struct Library * __asm _UserLibCleanup (register __a6 struct Library *mybase);
+struct IClass * __asm  _GetClass               (register __a6 struct Library *mybase);
+
+
+
+/* Library data */
+
+#ifdef _M68020
+#define SAMPVERS "_020"
+#else
+#define SAMPVERS ""
+#endif
+
+const UBYTE LibName[] = "sampedit.gadget";
+const UBYTE LibVer[] = { '$', 'V', 'E', 'R', ':', ' ' };
+const UBYTE LibId[] = "sampedit.gadget" SAMPVERS " 1.1 (5.5.96) © 1995,96 by Bernardo Innocenti";
+
+
+
+
+/*****************/
+/* Library bases */
+/*****************/
+
+/* Get around a SAS/C bug which causes some annoying warnings
+ * with the library bases defined below.
+ */
+#ifdef __SASC
+#pragma msg 72 ignore push
+#endif /* __SASC */
+
+struct ExecBase                        *SysBase                = NULL;
+struct IntuitionBase   *IntuitionBase  = NULL;
+struct GfxBase                 *GfxBase                = NULL;
+struct Library                 *UtilityBase    = NULL;
+
+#ifdef __SASC
+#pragma msg 72 pop
+#endif /* __SASC */
+
+static struct IClass   *SampEditClass  = NULL;
+
+
+static ULONG __asm SampEditDispatcher (register __a0 Class *cl,
+                                                                               register __a2 struct ExtGadget *g,
+                                                                               register __a1 Msg msg)
+
+/* SampEdit Class Dispatcher entrypoint.
+ * Handle BOOPSI messages.
+ */
+{
+       switch (msg->MethodID)
+       {
+               case GM_GOACTIVE:
+                       return SAMP_HandleInputMethod (cl, g, (struct gpInput *)msg);
+
+
+               case GM_RENDER:
+                       SAMP_RenderMethod (cl, g, (struct gpRender *)msg);
+                       return 0;
+
+               case GM_LAYOUT:
+               {
+                       struct SampEditData *sed = INST_DATA (cl, g);
+
+                       if (CalcDisplaySize (sed, g, ((struct gpLayout *)msg)->gpl_GInfo))
+                               NotifyHSlider (g, ((struct gpLayout *)msg)->gpl_GInfo, 0);
+
+                       return 0;
+               }
+
+               case OM_SET:
+               case OM_UPDATE:
+                       return SAMP_SetMethod (cl, g, (struct opSet *)msg);
+
+               case OM_GET:
+                       return SAMP_GetMethod (cl, g, (struct opGet *)msg);
+
+               case OM_NEW:
+                       return (ULONG) SAMP_NewMethod (cl, g, (struct opSet *)msg);
+
+               case OM_DISPOSE:
+                       /* NOTE: I'm falling through here! */
+
+               default:
+
+                       /* Unsupported method: let our superclass's
+                        * dispatcher take a look at it.
+                        */
+                       return DoSuperMethodA (cl, (Object *)g, msg);
+       }
+}
+
+
+
+static ULONG SAMP_HandleInputMethod (Class *cl, struct ExtGadget *g, struct gpInput *msg)
+{
+       struct SampEditData     *sed = INST_DATA (cl, g);
+       struct InputEvent       *ie = msg->gpi_IEvent;
+       WORD    MouseX, MouseY;         /* Mouse coordinates relative to editing area bounds */
+       BOOL    moved           = FALSE,
+                       scrolled        = FALSE;
+       ULONG   result = GMR_MEACTIVE;
+
+       if (msg->MethodID == GM_GOACTIVE)
+       {
+               if (!sed->Instr)
+                       return GMR_NOREUSE;
+
+               g->Flags |= GFLG_SELECTED;
+
+               {
+                       struct RastPort *rp;
+
+                       /* Render active cursor */
+                       if (rp = ObtainGIRPort (((struct gpInput *)msg)->gpi_GInfo))
+                       {
+                               DrawCursor (rp, sed, g);
+                               ReleaseGIRPort (rp);
+                       }
+               }
+
+               /* Do not process InputEvent when the gadget has been
+                * activated by ActivateGadget().
+                */
+               if (!((struct gpInput *)msg)->gpi_IEvent)
+                       return GMR_MEACTIVE;
+
+               /* Note: The input event that triggered the gadget
+                * activation (usually a mouse click) should be passed
+                * to the GM_HANDLEINPUT method, so we fall down to it.
+                */
+       }
+
+
+       MouseX = ((struct gpInput *)msg)->gpi_Mouse.X;
+       MouseY = ((struct gpInput *)msg)->gpi_Mouse.Y;
+
+       switch (ie->ie_Class)
+       {
+               case IECLASS_TIMER:
+
+                       /* Timer events are used to keep scrolling
+                        * when the mouse is outside the bounds of the
+                        * gadget.  When a timer event is recevied and
+                        * the mouse button is still pressed, we scroll the
+                        * cursor.
+                        */
+                               if (sed->Flags & SEF_DRAGGING)
+                                       moved = MoveCursor (sed, MouseX);
+
+                       break;
+
+               case IECLASS_RAWMOUSE:
+               {
+                       BOOL double_click = FALSE;
+                       BOOL outside = (MouseX < 0) || (MouseX >= sed->GBounds.Width);
+
+                       switch (ie->ie_Code)
+                       {
+                               case MENUDOWN:
+
+                                       /* Abort cursor dragging operation and restore
+                                        * old cursor position.
+                                        */
+                                       if (sed->Flags & SEF_DRAGGING)
+                                       {
+                                               sed->CursX              = sed->BackupCursX;
+                                               sed->XStart             = sed->BackupXStart;
+                                               moved = TRUE;
+                                               sed->Flags &= ~(SEF_DRAGGING | SEF_SCROLLING);
+                                       }
+                                       else result = GMR_REUSE;
+
+                                       break;
+
+                               case SELECTUP:
+                                       /* Send final update to sliders */
+                                       scrolled = SCROLLED_HORIZ | SCROLLED_VERT;
+                                       sed->Flags &= ~(SEF_DRAGGING | SEF_SCROLLING);
+                                       break;
+
+                               case SELECTDOWN:
+
+                                       /* Check if mouse click is still over the gadget */
+
+                                       if (outside)
+                                       {
+                                               /* Click outside editing area box:
+                                                * Check if click is really outside gadget box.
+                                                * If it is, deactivate the gadget and reuse the event.
+                                                * Notify application if it is interested in
+                                                * hearing IDCMP_GADGETUP codes.
+                                                */
+
+                                               if      ((MouseX < 0) || (MouseX >= sed->GBounds.Width) ||
+                                                       (MouseY < 0) || (MouseY >= sed->GBounds.Height))
+                                                       result = GMR_REUSE | GMR_VERIFY;
+                                               break;
+                                       }
+                                       else
+                                       {
+                                               /* Backup cursor position for undo feature */
+                                               sed->BackupCursX        = sed->CursX;
+                                               sed->BackupXStart       = sed->XStart;
+
+                                               /* Start cursor drag mode */
+                                               sed->Flags |= SEF_DRAGGING;
+                                       }
+
+                                       /* Check for double clicking */
+
+                                       if (DoubleClick (sed->DoubleClickSeconds, sed->DoubleClickMicros,
+                                               ie->ie_TimeStamp.tv_secs, ie->ie_TimeStamp.tv_micro))
+                                               double_click = TRUE;
+
+                                       sed->DoubleClickSeconds = ie->ie_TimeStamp.tv_secs;
+                                       sed->DoubleClickMicros  = ie->ie_TimeStamp.tv_micro;
+
+                                       /* NOTE: I'm falling through here! */
+
+                               default:
+
+                                       if (sed->Flags & SEF_DRAGGING)
+                                       {
+                                               if (outside)
+                                                       sed->Flags |= SEF_SCROLLING;
+                                               else
+                                                       moved = MoveCursor (sed, MouseX);
+                                       }
+                                       else if (!moved && double_click)
+                                       {
+                                               if (sed->Flags & SEF_MARKING)
+                                                       sed->Flags &= ~SEF_MARKING;
+                                               else
+                                               {
+                                                       sed->Flags |= SEF_MARKING;
+                                                       sed->RangeStartX = sed->CursX;
+                                               }
+/*
+                                               if (rp = ObtainGIRPort (msg->gpi_GInfo))
+                                               {
+                                                       if (!(sed->Flags & SEF_MARKING))
+                                                               ClearRange (rp, sed);
+
+                                                       DrawCursor (rp, sed, g);
+
+                                                       ReleaseGIRPort (rp);
+                                               }
+*/
+                                       }
+
+                                       break;
+                       }
+                       break;
+               }
+
+               default:
+                       return 0;
+
+       }       /* End switch (ie->ie_Class) */
+
+       if (moved)
+       {
+               struct RastPort *rp;
+
+               if (rp = ObtainGIRPort (msg->gpi_GInfo))
+               {
+                       scrolled |= DrawCursor (rp, sed, g);
+                       ReleaseGIRPort (rp);
+               }
+
+               /* Broadcast notification to our target object. */
+               NotifyCursor (g, msg->gpi_GInfo, (ie->ie_Code & IECODE_UP_PREFIX) ? 0 : OPUF_INTERIM);
+       }
+
+       if (scrolled)
+               NotifyHSlider (g, msg->gpi_GInfo, (ie->ie_Code & IECODE_UP_PREFIX) ? 0 : OPUF_INTERIM);
+
+       return result;
+}
+
+
+
+static void SAMP_RenderMethod (Class *cl, struct ExtGadget *g, struct gpRender *msg)
+{
+       struct SampEditData     *sed = INST_DATA (cl, g);
+
+       /* We do not support GREDRAW_UPDATE and GREDRAW_TOGGLE */
+
+       if (msg->gpr_Redraw == GREDRAW_REDRAW)
+       {
+               /* Recalculate the display size only on V37.
+                * As of V39, Intuition supports GM_LAYOUT, which
+                * allows a more optimized way to handle dynamic resizing.
+                */
+               if (IntuitionBase->LibNode.lib_Version < 39)
+               {
+                       if (CalcDisplaySize (sed, g, ((struct gpRender *)msg)->gpr_GInfo))
+                               NotifyHSlider (g, msg->gpr_GInfo, 0);
+               }
+
+               RedrawAll (msg->gpr_RPort, sed, g);
+       }
+}
+
+
+
+static ULONG SAMP_SetMethod (Class *cl, struct ExtGadget *g, struct opSet *msg)
+{
+       struct SampEditData *sed = INST_DATA (cl, g);
+       struct TagItem          *ti,
+                                               *tstate = msg->ops_AttrList;
+       BOOL    zoom                    = FALSE,
+                       move_cursor             = FALSE,
+                       scroll                  = FALSE;
+
+
+       while (ti = NextTagItem(&tstate))
+       {
+               switch (ti->ti_Tag)
+               {
+                       case SEA_CursXPos:
+                               if (sed->CursXPos != ti->ti_Data)
+                               {
+                                       sed->CursXPos = ti->ti_Data;
+                                       move_cursor = TRUE;
+                               }
+                               break;
+
+                       case SEA_XStart:
+                               if (sed->XStart != ti->ti_Data)
+                               {
+                                       sed->XStart = ti->ti_Data;
+                                       scroll = TRUE;
+                               }
+                               break;
+
+                       case SEA_XRatio:
+                               if (sed->XRatio != ti->ti_Data)
+                               {
+                                       sed->XRatio = ti->ti_Data;
+                                       zoom = TRUE;
+                                       break;
+                               }
+
+                       case SEA_ScrollLeft:
+                               if (sed->XStart > 0)
+                               {
+                                       sed->XStart--;
+                                       scroll = TRUE;
+                               }
+                               break;
+
+                       case SEA_ScrollRight:
+                               if (sed->XStart + sed->XVisible < sed->Lenght)
+                               {
+                                       sed->XStart++;
+                                       scroll = TRUE;
+                               }
+                               break;
+
+                       case SEA_MarkRegion:
+                       {
+                               struct Rectangle *region = (struct Rectangle *)ti->ti_Data;
+
+                               if (!region)                            /* End mark mode */
+
+                                       sed->Flags &= ~SEF_MARKING;
+
+                               else if (region == (struct Rectangle *)-1)      /* Toggle mark mode */
+                               {
+                                       sed->Flags ^= SEF_MARKING;
+                                       sed->RangeStartX        = sed->XStart;
+                                       // sed->RangeStartY     = sed->YStart;
+                               }
+                               else                                            /* Start mark mode */
+                               {
+                                       memcpy (&sed->RangeStartX, region, sizeof (struct Rectangle));
+                                       sed->CursX      = region->MaxX;
+                                       sed->Flags |= PEF_MARKING;
+                               }
+
+                               if (rp = ObtainGIRPort (((struct opSet *)msg)->ops_GInfo))
+                               {
+                                       if (!(sed->Flags & PEF_MARKING))
+                                               ClearRange (rp, sed);
+
+                                       DrawCursor (rp, sed, g);
+
+                                       ReleaseGIRPort (rp);
+                               }
+
+                               break;
+                       }
+
+                       case SEA_Instrument:
+                               sed->Instrument = (struct Instrument *) ti->ti_Data;
+                               redraw = TRUE;
+                               FreeUndoBuffers (sed, TRUE);
+
+                               if (sed->Instrument == NULL)
+                                       sed->CursX = sed->VisibleX = 0;
+                               else
+                               {
+                                       /* Recalculate pattern dimensions */
+                                       if (CalcDisplaySize (sed, g, msg->ops_GInfo))
+                                               NotifyHSlider (g, msg->ops_GInfo, 0);
+                               }
+                               break;
+
+                       case SEA_MaxUndoLevels:
+                               sed->MaxUndoLevels = ti->ti_Data;
+                               FreeUndoBuffers (sed, FALSE);
+                               break;
+
+                       case SEA_MaxUndoMem:
+                               sed->MaxUndoMem = ti->ti_Data;
+
+                               /* Unlimited undo memory */
+                               if (sed->MaxUndoMem == 0) sed->MaxUndoMem = ~0;
+
+                               FreeUndoBuffers (sed, FALSE);
+
+                               break;
+
+                       case SEA_BackgroundPen:
+                               if (sed->BackgroundPen != ti->ti_Data)
+                               {
+                                       sed->BackgroundPen = ti->ti_Data;
+                                       redraw_all = TRUE;
+                               }
+                               break;
+
+                       case SEA_LinePen:
+                               if (sed->LinePen != ti->ti_Data)
+                               {
+                                       sed->LinePen = ti->ti_Data;
+                                       redraw_all = TRUE;
+                               }
+                               break;
+
+                       case SEA_FillPen:
+                               if (sed->FillPen != ti->ti_Data)
+                               {
+                                       sed->FillPen = ti->ti_Data;
+                                       redraw_all = TRUE;
+                               }
+                               break;
+
+                       case SEA_GridPen:
+                               if (sed->GridPen != ti->ti_Data)
+                               {
+                                       sed->GridPen = ti->ti_Data;
+                                       redraw_all = TRUE;
+                               }
+                               break;
+
+                       default:
+                               break;
+               }
+
+       }       /* End while (NextTagItem()) */
+
+
+       result = DoSuperMethodA (cl, (Object *)g, msg);
+
+       if (redraw_all || scroll || move_cursor || zoom)
+       {
+               WORD scrolled = 0;
+
+               if (rp = ObtainGIRPort (msg->ops_GInfo))
+               {
+                       if (redraw_all)
+                       {
+                               DoMethod ((Object *)g, GM_RENDER, ((struct opSet *)msg)->ops_GInfo, rp, GREDRAW_REDRAW);
+                               scrolled = 3;
+                       }
+                       else if (scroll)
+                               scrolled = ScrollPattern (rp, sed, g, lefttrack, topline);
+
+                       if (move_cursor)
+                               scrolled |= DrawCursor (rp, sed, g);
+
+                       ReleaseGIRPort (rp);
+               }
+
+               if (scrolled & 1)
+                       NotifyVSlider (g, ((struct opSet *)msg)->ops_GInfo, msg->MethodID == OM_UPDATE ? (((struct opUpdate *)msg)->opu_Flags) : 0);
+               if (scrolled & 2)
+                       NotifyHSlider (g, ((struct opSet *)msg)->ops_GInfo, msg->MethodID == OM_UPDATE ? (((struct opUpdate *)msg)->opu_Flags) : 0);
+
+               if (move_cursor || scrolled)
+                       NotifyCursor (g, ((struct opSet *)msg)->ops_GInfo, msg->MethodID == OM_UPDATE ? (((struct opUpdate *)msg)->opu_Flags) : 0);
+       }
+}
+
+
+
+static LONG SAMP_GetMethod (Class *cl, struct ExtGadget *g, struct opGet *msg)
+{
+       struct SampEditData *sed = INST_DATA (cl, g);
+
+       switch (msg->opg_AttrID)
+       {
+               case SEA_CursXPos:
+                       *msg->opg_Storage = (ULONG) sed->CursXPos;
+                       break;
+
+               case SEA_CursYPos:
+                       *msg->opg_Storage = (ULONG) sed->CursYPos;
+                       break;
+
+               case SEA_XStart:
+                       *msg->opg_Storage = (ULONG) sed->XStart;
+                       break;
+
+               case SEA_YStart:
+                       *msg->opg_Storage = (ULONG) sed->YStart;
+                       break;
+
+               case SEA_XRatio:
+                       *msg->opg_Storage = (ULONG) sed->XRatio;
+                       break;
+
+               case SEA_YRatio:
+                       *msg->opg_Storage = (ULONG) sed->YRatio;
+                       break;
+
+               case SEA_MarkRegion:
+               {
+                       struct Rectangle *region = (struct Rectangle *) *msg->opg_Storage);
+
+                       region->MinX = min (sed->RangeStartX, sed->RangeEndX);
+                       region->MaxX = max (sed->RangeStartX, sed->RangeEndX);
+                       region->MinY = min (sed->RangeStartY, sed->RangeEndY);
+                       region->MaxY = max (sed->RangeStartY, sed->RangeEndY);
+                       break;
+               }
+
+               case SEA_Flags:
+                       *msg->opg_Storage = (ULONG) sed->Flags;
+                       break;
+
+               case SEA_Instrument:
+                       *msg->opg_Storage = (ULONG) sed->Instrument;
+                       break;
+
+               default:
+                       return DoSuperMethodA (cl, (Object *)g, msg);
+       }
+
+       return TRUE;
+}
+
+
+
+static ULONG SAMP_NewMethod (Class *cl, struct ExtGadget *g, struct opSet *msg)
+{
+       struct SampEditData             *sed;
+       ULONG tmp;
+
+       if (!(g = DoSuperMethodA (cl, (Object *)g, msg)))
+               return NULL;
+
+       sed = INST_DATA (cl, g);        /* Get pointer to instance data */
+
+       memset (sed, 0, sizeof (struct SampEditData));
+
+       /* We are going to use ScrollRaster() in this gadget... */
+
+       if (g->Flags & GFLG_EXTENDED)
+               g->MoreFlags |= GMORE_SCROLLRASTER;
+
+       g->Flags |= GFLG_TABCYCLE | GFLG_RELSPECIAL;
+
+       /* Initialize our lists */
+       NEWLIST ((struct List *)&sed->UndoList);
+       NEWLIST ((struct List *)&sed->RedoList);
+
+
+       /* Initialize attributes */
+
+       sed->Instrument         = (struct Instrument *) GetTagData (SEA_Instrument, NULL, msg->ops_AttrList);
+       sed->BackgroundPen      = GetTagData (SEA_BackgroundPen,        0, msg->ops_AttrList);
+       sed->LinePen            = GetTagData (SEA_LinePen,                      2, msg->ops_AttrList);
+       sed->FillPen            = GetTagData (SEA_FillPen,                      1, msg->ops_AttrList);
+       sed->GridPen            = GetTagData (SEA_GridPen,                      3, msg->ops_AttrList);
+       sed->MaxUndoLevels      = GetTagData (SEA_MaxUndoLevels,        8, ((struct opSet *)msg)->ops_AttrList);
+       sed->MaxUndoMem         = GetTagData (SEA_MaxUndoMem,           32768, ((struct opSet *)msg)->ops_AttrList);
+       sed->Flags                      = GetTagData (SEA_Flags,                        0, ((struct opSet *)msg)->ops_AttrList);
+
+       /* Unlimited undo memory */
+       if (sed->MaxUndoMem == 0) sed->MaxUndoMem = ~0;
+
+       return (ULONG) g;
+}
+
+
+
+static void NotifyCursor (struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags)
+{
+       /* Always sends notification if the gadget has the GACT_IMMEDIATE
+        * flag set.  If it isn't, the editor will report its cursor
+        * position only the last time it updates the cursor position.
+        */
+       if ((g->Activation & GACT_IMMEDIATE) || !(flags & OPUF_INTERIM))
+       {
+               struct SampEditData *sed = INST_DATA (OCLASS(g), g);
+
+               NotifyAttrChanges ((Object *)g, gi, flags,
+                       SEA_CursXPos,   sed->CursXPos,
+                       SEA_CursYPos,   sed->CursYPos,
+                       GA_ID,                  g->GadgetID,
+                       TAG_DONE);
+       }
+}
+
+
+
+static void NotifyHSlider (struct ExtGadget *g, struct GadgetInfo *gi, ULONG flags)
+{
+       struct SampEditData *sed = INST_DATA (OCLASS(g), g);
+
+       NotifyAttrChanges ((Object *)g, gi, flags,
+               SEA_XStart,     sed->XStart;
+               GA_ID,  g->GadgetID,
+               TAG_DONE);
+}
+
+
+
+static void GetGadgetBox (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *rect)
+
+/* This function gets the actual IBox where a gadget exists
+ * in a window.  The special cases it handles are all the REL#?
+ * (relative positioning flags).
+ *
+ * The function takes a struct GadgetInfo pointer, a struct Gadget
+ * pointer, and a struct IBox pointer.  It uses the window and
+ * gadget to fill in the IBox.
+ */
+{
+       rect->Left = g->LeftEdge;
+       if (g->Flags & GFLG_RELRIGHT) rect->Left += ginfo->gi_Domain.Width - 1;
+
+       rect->Top = g->TopEdge;
+       if (g->Flags & GFLG_RELBOTTOM) rect->Top += ginfo->gi_Domain.Height - 1;
+
+       rect->Width = g->Width;
+       if (g->Flags & GFLG_RELWIDTH) rect->Width += ginfo->gi_Domain.Width;
+
+       rect->Height = g->Height;
+       if (g->Flags & GFLG_RELHEIGHT) rect->Height += ginfo->gi_Domain.Height;
+}
+
+
+
+XDEF HOOKCALL struct Library *_UserLibInit (REG(a6,struct Library *mybase))
+{
+       SysBase = *((struct ExecBase **)4);
+
+       IntuitionBase   = (struct IntuitionBase *) OpenLibrary ("intuition.library", 37);
+       GfxBase                 = (struct GfxBase *) OpenLibrary ("graphics.library", 37);
+       UtilityBase             = OpenLibrary ("utility.library", 37);
+
+       if (!(IntuitionBase && GfxBase && UtilityBase && KeymapBase))
+       {
+               _UserLibCleanup (mybase);
+               return NULL;
+       }
+
+       if (SampEditClass = MakeClass (SAMPEDITCLASS, GADGETCLASS, NULL, sizeof (struct SampEditData), 0))
+       {
+               SampEditClass->cl_Dispatcher.h_Entry = (ULONG (*)()) SampEditDispatcher;
+               AddClass (SampEditClass);
+       }
+       else
+       {
+               _UserLibCleanup (mybase);
+               return NULL;
+       }
+
+       return mybase;
+}
+
+
+
+XDEF HOOKCALL struct Library *_UserLibCleanup (REG(a6,struct Library *mybase))
+{
+       if (SampEditClass)
+               if (!FreeClass (SampEditClass))
+                       return NULL;
+
+       /* Passing NULL to CloseLibrary() is safe */
+       CloseLibrary ((struct Library *)UtilityBase);
+       CloseLibrary ((struct Library *)GfxBase);
+       CloseLibrary ((struct Library *)IntuitionBase);
+
+       return mybase;
+}
+
+
+
+XDEF HOOKCALL struct IClass *_GetEngine (REG(a6,struct Library *mybase))
+{
+       return (SampEditClass);
+}
diff --git a/Gadgets/pattedit.gadget b/Gadgets/pattedit.gadget
new file mode 100644 (file)
index 0000000..772bb12
Binary files /dev/null and b/Gadgets/pattedit.gadget differ
diff --git a/Gst.c b/Gst.c
new file mode 100644 (file)
index 0000000..369972a
--- /dev/null
+++ b/Gst.c
@@ -0,0 +1,81 @@
+/*
+**     GST.c
+**
+**     Copyright (C) 1994,95,96,97 by Bernardo Innocenti
+**
+**     This is a dummy source file used to make the Global Symbol Table
+**     for XModule.
+*/
+
+#define ASL_V38_NAMES_ONLY
+#define INTUI_V36_NAMES_ONLY
+#define IFFPARSE_V37_NAMES_ONLY
+#define USE_BUILTIN_MATH
+#define __USE_SYSBASE
+
+#include <exec/lists.h>
+#include <exec/memory.h>
+#include <exec/nodes.h>
+#include <devices/audio.h>
+#include <dos/dos.h>
+#include <dos/dostags.h>
+#include <intuition/intuition.h>
+#include <intuition/classes.h>
+#include <intuition/classusr.h>
+#include <intuition/icclass.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/imageclass.h>
+#include <libraries/asl.h>
+#include <libraries/commodities.h>
+#include <libraries/gadtools.h>
+#include <libraries/reqtools.h>
+#include <libraries/iffparse.h>
+#include <datatypes/soundclass.h>
+#include <graphics/rpattr.h>
+#include <graphics/gfxmacros.h>
+#include <graphics/gfxbase.h>
+#include <utility/tagitem.h>
+#include <rexx/storage.h>
+#include <rexx/errors.h>
+#include <workbench/workbench.h>
+#include <workbench/startup.h>
+
+#include <proto/asl.h>
+#include <proto/commodities.h>
+#include <proto/datatypes.h>
+#include <proto/dos.h>
+#include <proto/exec.h>
+#include <proto/gadtools.h>
+#include <proto/graphics.h>
+#include <proto/keymap.h>
+#include <proto/icon.h>
+#include <proto/iffparse.h>
+#include <proto/intuition.h>
+#include <proto/powerpacker.h>
+#include <proto/reqtools.h>
+#include <proto/utility.h>
+#include <proto/wb.h>
+#include <proto/xmodule.h>
+
+#ifndef LIBRARIES_PATTEDITCLASS_H
+#include <libraries/patteditclass.h>
+#endif /* !LIBRARIES_PATTEDITCLASS_H */
+
+#ifndef LIBRARIES_XMODULECLASS_H
+#include <libraries/xmoduleclass.h>
+#endif /* !LIBRARIES_XMODULECLASS_H */
+
+#ifndef LIBRARIES_SONGCLASS_H
+#include <libraries/songclass.h>
+#endif /* !LIBRARIES_SONGCLASS_H */
+
+#ifndef LIBRARIES_XMODULE_H
+#include <libraries/xmodule.h>
+#endif /* !LIBRARIES_XMODULE_H */
+
+/* Can't include this one here because of the inline functions defined in
+ *
+ * #include "XModulePriv.h"
+ */
+
+#include "Gui.h"
diff --git a/Gui.c b/Gui.c
new file mode 100644 (file)
index 0000000..36243f3
--- /dev/null
+++ b/Gui.c
@@ -0,0 +1,2934 @@
+/*
+**     Gui.c
+**
+**     Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
+**
+**     Graphic User Interface handling routines.
+*/
+
+#include <exec/nodes.h>
+#include <exec/memory.h>
+#include <intuition/intuition.h>
+#include <intuition/intuitionbase.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/imageclass.h>
+#include <utility/tagitem.h>
+#include <graphics/rpattr.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/graphics.h>
+#include <proto/layers.h>
+#include <proto/keymap.h>
+#include <proto/utility.h>
+#include <proto/commodities.h>
+#include <proto/diskfont.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+#include "CustomClasses.h"
+
+
+
+XDEF struct Screen             *Scr            = NULL;
+XDEF struct ScrInfo             ScrInfo        = {0};
+XDEF APTR                               VisualInfo     = NULL;
+XDEF struct DrawInfo   *DrawInfo       = NULL;
+
+
+XDEF struct TextAttr
+       TopazAttr       = { "topaz.font", 8, FS_NORMAL, FPF_ROMFONT },
+       ScreenAttr      = { 0 },
+       WindowAttr      = { 0 },
+       ListAttr        = { 0 },
+       EditorAttr      = { 0 };
+
+XDEF struct TextFont
+       *TopazFont      = NULL,
+       *WindowFont     = NULL,
+       *ListFont       = NULL;
+
+
+/* Window borders layout information */
+XDEF UWORD                              OffX, OffY;                            /* X and Y offsets for window rendering */
+XDEF WORD                               SizeWidth = 18,                        /* Dimensions of the window size gadget */
+                                                SizeHeight = 10;
+
+/* IDCMP windows support */
+XDEF ULONG                              IDCMPSig = 0;                          /* Signal for above mentioned port              */
+XDEF ULONG                              Signals = SIGBREAKFLAGS;       /* Signals for Wait() in main loop              */
+XDEF struct IntuiMessage IntuiMsg;                                     /* A copy of the last received IntuiMsg */
+XDEF struct List                WindowList;                            /* Linked list of all open windows              */
+
+static UBYTE                    ActiveKey      = 0;                    /* These three are used to handle               */
+static struct Gadget   *ActiveGad      = NULL;                 /*              selection of button gadgets             */
+static struct Window   *ActiveWin      = NULL;                 /*              with keyboard shortcuts.                */
+static struct Window *OldPrWindowPtr = (struct Window *)1L;
+
+XDEF LONG      LastErr                         = 0;
+XDEF ULONG     UniqueID;                                               /* An ID got from GetUniqueID()                         */
+XDEF UWORD     WinLockCount            = 0;            /* Allow nesting of window locking                      */
+XDEF BOOL      Quit                            = FALSE;
+XDEF BOOL      DoNextSelect            = TRUE;         /* Menu selection, see menu handling code       */
+XDEF BOOL      ShowRequesters          = TRUE;
+XDEF BOOL      OwnScreen                       = FALSE;        /* Are we owners or visitors?                           */
+XDEF BOOL      ScreenReopening         = FALSE;        /* Set to TRUE while reopening windows          */
+XDEF BOOL      ScreenShutdown          = FALSE;        /* Set to TRUE while shutting down screen       */
+XDEF Class *ScrollButtonClass  = NULL;
+
+/* Shared IDCMP port for all windows */
+static struct MsgPort  *WinPort                        = NULL;
+static Class                   *VImageClass            = NULL;
+static ULONG                    VImageUseCount         = 0;
+static struct Image            *ButtonFrame            = NULL;
+static WORD                             ButtonFrameWidth       = 0;
+static WORD                             ButtonFrameHeight      = 0;
+
+
+XDEF struct WDescr WDescr[WID_COUNT] =
+{
+       { NULL, 0, 0, (struct TagItem *)ToolBoxWinTags          },
+       { NULL, 0, 0, (struct TagItem *)PrefsWinTags            },
+       { NULL, 0, 0, (struct TagItem *)SongInfoWinTags         },
+       { NULL, 0, 0, (struct TagItem *)InstrumentsWinTags      },
+       { NULL, 0, 0, (struct TagItem *)SequenceWinTags         },
+       { NULL, 0, 0, (struct TagItem *)PatternWinTags          },
+       { NULL, 0, 0, (struct TagItem *)PlayWinTags                     },
+       { NULL, 0, 0, (struct TagItem *)SampleWinTags           },
+       { NULL, 0, 0, (struct TagItem *)OptimizationWinTags     },
+       { NULL, 0, 0, (struct TagItem *)SaversWinTags           },
+       { NULL, 0, 0, (struct TagItem *)PattPrefsWinTags        },
+       { NULL, 0, 0, (struct TagItem *)PattSizeWinTags         },
+       { NULL, 0, 0, (struct TagItem *)ClearWinTags            },
+       { NULL, 0, 0, (struct TagItem *)LogWinTags                      },
+       { NULL, 0, 0, (struct TagItem *)ProgressWinTags         }
+};
+
+
+
+XDEF struct GuiSwitches GuiSwitches =
+{
+       TRUE,                   /* SaveIcons            */
+       TRUE,                   /* AskOverwrite         */
+       TRUE,                   /* AskExit                      */
+       TRUE,                   /* ShowAppIcon          */
+       FALSE,                  /* UseReqTools          */
+       TRUE,                   /* SmartRefresh         */
+       TRUE,                   /* UseDataTypes         */
+       TRUE,                   /* InstrSaveIcons       */
+       TRUE,                   /* AskAutosave          */
+       FALSE,                  /* DoBackups            */
+       FALSE,                  /* LogToFile            */
+       INST_8SVX,              /* InstrSaveMode        */
+       1,                              /* SampDrawMode         */
+       XMDMF_NOTE+1,   /* LogLevel                     */
+       0,                              /* AutosaveTime         */
+       3,                              /* BackupVersions       */
+       "*,#",                  /* BackupTemplate       */
+       "CON:////XModule Log/AUTO/CLOSE/INACTIVE/SCREEN XMODULE"        /* LogFile */
+};
+
+
+
+#ifndef OS30_ONLY
+
+/* Wait pointer image data */
+
+static __chip UWORD WaitPointer[] =
+{
+       0x0000, 0x0000,
+
+       0x0400, 0x07c0,
+       0x0000, 0x07c0,
+       0x0100, 0x0380,
+       0x0000, 0x07e0,
+       0x07c0, 0x1ff8,
+       0x1ff0, 0x3fec,
+       0x3ff8, 0x7fde,
+       0x3ff8, 0x7fbe,
+       0x7ffc, 0xff7f,
+       0x7efc, 0xffff,
+       0x7ffc, 0xffff,
+       0x3ff8, 0x7ffe,
+       0x3ff8, 0x7ffe,
+       0x1ff0, 0x3ffc,
+       0x07c0, 0x1ff8,
+       0x0000, 0x07e0,
+
+       0x0000, 0x0000
+};
+
+#endif /* OS30_ONLY */
+
+
+/* Martin Taillefer's block pointer */
+/*
+static UWORD __chip BlockPointer[] =
+{
+       0x0000, 0x0000,
+
+       0x0000, 0x0100,
+       0x0100, 0x0280,
+       0x0380, 0x0440,
+       0x0100, 0x0280,
+       0x0100, 0x0ee0,
+       0x0000, 0x2828,
+       0x2008, 0x5834,
+       0x783c, 0x8002,
+       0x2008, 0x5834,
+       0x0000, 0x2828,
+       0x0100, 0x0ee0,
+       0x0100, 0x0280,
+       0x0380, 0x0440,
+       0x0100, 0x0280,
+       0x0000, 0x0100,
+       0x0000, 0x0000,
+
+       0x0000, 0x0000
+};
+*/
+
+
+/* Local function prototypes */
+
+static void                                     HandleKey                      (void);
+static void                                     SelectButton           (struct Window *win, struct Gadget *gad);
+static void                                     DeselectButton         (void);
+static void                             RenderWindowBorders(struct WinUserData *wud);
+static struct WindowBorder     *CreateWindowBorder (struct WinUserData *wud, UWORD left, UWORD top, UWORD width, UWORD height, ULONG bordertype);
+static struct Gadget           *CreateVImageButton (struct TagItem *tags, struct NewGadget *ng);
+static void                                     DeleteVImageButton     (struct Gadget *g);
+static struct Gadget           *CreateGadgets          (struct LayoutGadgetsArgs *lga, UWORD mode, UWORD left, UWORD top, UWORD width, UWORD height);
+static void                                     LayoutGadgets          (struct LayoutGadgetsArgs *lga, UWORD mode);
+static void                                     DeleteGadgets          (struct WinUserData *wud);
+
+static void                                     DeleteWUD                      (struct WinUserData *wud);
+static struct WinUserData      *CreateLayoutInfo       (struct WinUserData *wud);
+
+
+
+GLOBALCALL LONG HandleGui (void)
+
+/* Handle XModule GUI - Main event handling loop */
+{
+       ULONG recsig;   /* Received Signals     */
+       LONG rc = 0;    /* Return Code          */
+
+
+       /* This is the main event handling loop */
+
+       while (!Quit)
+       {
+               recsig = Wait (Signals);
+
+               if (recsig & IDCMPSig)
+                       HandleIDCMP();
+
+               if (recsig & AudioSig)
+                       HandleAudio();
+
+               if (recsig & PubPortSig)
+                       HandleRexxMsg();
+
+               if (recsig & AppSig)
+                       HandleAppMessage();
+
+               if (recsig & FileReqSig)
+                       HandleFileRequest();
+
+               if (recsig & CxSig)
+                       HandleCx();
+
+               if (recsig & AmigaGuideSig)
+                       HandleAmigaGuide();
+
+               /* Check break signals */
+               if (recsig & SIGBREAKFLAGS)
+               {
+                       if (recsig & SIGBREAKF_CTRL_C)
+                       {
+                               Quit = TRUE;
+                               GuiSwitches.AskExit = FALSE;
+                               rc = ERROR_BREAK;
+                       }
+
+                       if (recsig & SIGBREAKF_CTRL_D)
+                               if (MyBroker) ActivateCxObj (MyBroker, FALSE);
+
+                       if (recsig & SIGBREAKF_CTRL_E)
+                               if (MyBroker) ActivateCxObj (MyBroker, TRUE);
+
+                       if (recsig & SIGBREAKF_CTRL_F)
+                               DeIconify();
+               }
+
+
+               if (LastErr)
+               {
+                       switch (LastErr)
+                       {
+                               case ERROR_NO_FREE_STORE:
+                                       ShowMessage (MSG_NO_FREE_STORE);
+                                       break;
+
+                               case ERROR_BREAK:
+                                       ShowMessage (MSG_BREAK);
+                                       break;
+
+                               default:
+                                       break;
+                       }
+
+                       DisplayBeep (Scr);
+                       LastErr = 0;
+               }
+
+               if (Quit && GuiSwitches.AskExit)
+                       if (!ShowRequestArgs (MSG_REALLY_QUIT_XMODULE, MSG_YES_OR_NO, NULL))
+                       {
+                               Quit = FALSE;
+                               rc = 0;
+                       }
+
+       }       /* End main loop */
+
+       return rc;
+}
+
+
+
+/* Intuition Event Handler.  Based on GadToolsBox's HandleIDCMP() */
+GLOBALCALL void HandleIDCMP (void)
+{
+       struct IntuiMessage     *m;
+       struct MenuItem         *n;
+       struct Window           *win;
+       struct WinUserData      *wud;
+
+       while (m = GT_GetIMsg (WinPort))
+       {
+               IntuiMsg = *m;  /* Make a local copy and return message immediately */
+
+               GT_ReplyIMsg (m);
+
+               win = IntuiMsg.IDCMPWindow;
+               wud = (struct WinUserData *)win->UserData;
+
+               switch (IntuiMsg.Class)
+               {
+                       case IDCMP_REFRESHWINDOW:
+
+                               /* TODO: Handle multiple IDCMP_REFRESHWINDOW
+                                * messages sent at the same time.
+                                */
+
+                               /* Lock Layer in sizeable windows so its size
+                                * won't change until we are finished rendering
+                                * on it.
+                                *
+                                * Newsflash: **DON'T!**  Layers BeginUpdate() will already
+                                * lock the window layer for us, so doing it again we would
+                                * risk a complete GUI deadlock in some particular conditions...
+                                */
+                               /* if (win->Flags & WFLG_SIZEGADGET)
+                                *      LockLayer (NULL, win->WLayer);
+                                */
+
+                               if (wud->WUDFlags & WUDF_JUSTRESIZED)
+                               {
+                                       // RefreshGadgets (wud->GList, win, NULL);
+                                       GT_RefreshWindow (win, NULL);
+                                       RefreshWindowFrame (win);
+
+                                       if (wud->Borders)
+                                               RenderWindowBorders (wud);
+
+                                       wud->WUDFlags &= ~WUDF_JUSTRESIZED;
+                               }
+                               else
+                               {
+                                       GT_BeginRefresh (win);
+
+                                       if (wud->Borders)
+                                               RenderWindowBorders (wud);
+
+                                       GT_EndRefresh (win, TRUE);
+                               }
+
+                               /* if (win->Flags & WFLG_SIZEGADGET)
+                                *      UnlockLayer (win->WLayer);
+                                */
+                               break;
+
+                       case IDCMP_RAWKEY:
+                               HandleKey ();
+                               break;
+
+                       case IDCMP_NEWSIZE:
+                       {
+                               struct Gadget *g;
+                               struct LayoutGadgetsArgs lga;
+                               struct RastPort rp;
+                               ULONG SpecialTags[20];
+                               UWORD newwidth, newheight;
+
+                               /* TODO: Handle multiple IDCMP_NEWSIZE
+                                * messages sent at the same time.
+                                */
+
+//                             LockLayer (NULL, win->WLayer);
+
+                               newwidth        = win->Width - win->BorderLeft - win->BorderRight;
+                               newheight       = win->Height - win->BorderTop - win->BorderBottom;
+
+                               if ((wud->WindowSize.Width == newwidth) &&
+                                       (wud->WindowSize.Height == newheight))
+                               {
+//                                     UnlockLayer (win->WLayer);
+                                       break;
+                               }
+
+                               if (!(wud->WUDFlags & WUDF_CUSTOMLAYOUT))
+                               {
+                                       /* Detatch and free old gadgets */
+                                       RemoveGList (win, wud->GList, -1);
+                                       DeleteGadgets (wud);
+
+                                       /* Clear old window region (or what remains of it) */
+                                       SetAPen (win->RPort, 0);
+                                       SetDrMd (win->RPort, JAM1);
+                                       RectFill (win->RPort, win->BorderLeft, win->BorderTop,
+                                               min (wud->WindowSize.Width, newwidth) + win->BorderLeft - 1,
+                                               min (wud->WindowSize.Height, newheight) + win->BorderTop - 1);
+
+                                       wud->WindowSize.Width = newwidth;
+                                       wud->WindowSize.Height = newheight;
+
+
+                                       /* Layout phase 2 */
+
+                                       InitRastPort (&rp);
+                                       SetFont (&rp, wud->Font);
+
+                                       if (!CreateContext (&wud->GList))
+                                       {
+//                                             UnlockLayer (win->WLayer);
+                                               DeleteWUD (wud);
+                                               break;
+                                       }
+
+                                       lga.Args                = wud->LayoutArgs;
+                                       lga.VInfo               = VisualInfo;
+                                       lga.PrevGad             = wud->GList;
+                                       lga.GInfo               = wud->GInfo;
+                                       lga.Wud                 = wud;
+                                       lga.DummyRast   = &rp;
+                                       lga.Count               = 0;
+                                       lga.SpecialTags = SpecialTags;
+
+                                       SpecialTags[0] = GT_Underscore;
+                                       SpecialTags[1] = (ULONG) '_';
+
+                                       g = CreateGadgets (&lga, LAYOUTMODE_V, OffX + lga.GInfo[0].LabelWidth + HSPACING, OffY + VSPACING,
+                                               wud->WindowSize.Width - lga.GInfo[0].LabelWidth - HSPACING * 2,
+                                               wud->WindowSize.Height - VSPACING * 2);
+
+                                       if (!g)
+                                       {
+//                                             UnlockLayer (win->WLayer);
+                                               DeleteWUD (wud);
+                                               break;
+                                       }
+
+                                       AddGList (win, wud->GList, -1, -1, NULL);
+                                       wud->WUDFlags |= WUDF_JUSTRESIZED;
+                               }
+
+                               if (wud->IDCMPFunc) wud->IDCMPFunc (wud);
+
+//                             UnlockLayer (win->WLayer);
+                               break;
+                       }
+
+                       case IDCMP_CLOSEWINDOW:
+                               if (wud->WindowID == WID_TOOLBOX)
+                                       Quit = 1;
+                               else
+                                       MyCloseWindow (wud);
+
+                               break;
+
+                       case IDCMP_GADGETUP:
+                       case IDCMP_GADGETDOWN:
+                       {
+                               struct Gadget *gad = (struct Gadget *)IntuiMsg.IAddress;
+
+                               /* Toggle switch */
+                               if (wud->GInfo[gad->GadgetID].GKind == CHECKBOX_KIND)
+                               {
+                                       UWORD *check;
+
+                                       if (check = (WORD *)(wud->GInfo[gad->GadgetID].SpecialStorage))
+                                               *check ^= 1;
+                               }
+
+                               /* Execute function */
+                               if (((struct Gadget *)IntuiMsg.IAddress)->UserData)
+                                       ((void (*)(struct WinUserData *)) gad->UserData) (wud);
+
+                               break;
+                       }
+
+                       case IDCMP_MENUPICK:
+                               while (IntuiMsg.Code != MENUNULL)
+                               {
+                                       n = ItemAddress (win->MenuStrip, IntuiMsg.Code);
+                                       ((void (*)(struct WinUserData *))(GTMENUITEM_USERDATA(n))) (wud);
+
+                                       /* Some window operations invalidate the menu
+                                        * we are working on.  For istance, Re-opening a
+                                        * window causes the old MenuStrip to be killed.
+                                        * The DoNextSelect flag provides a way to stop
+                                        * this loop and avoid a nasty crash.
+                                        */
+                                       if (!DoNextSelect)
+                                       {
+                                               DoNextSelect = TRUE;
+                                               break;
+                                       }
+                                       IntuiMsg.Code = n->NextSelect;
+                               }
+                               break;
+
+                       case IDCMP_INACTIVEWINDOW:
+                               DeselectButton();
+                               if (wud->IDCMPFunc) wud->IDCMPFunc (wud);
+                               break;
+
+                       case IDCMP_MENUHELP:
+                       case IDCMP_GADGETHELP:
+                               HandleHelp (&IntuiMsg);
+                               break;
+
+                       default:
+                               if (wud->IDCMPFunc) wud->IDCMPFunc (wud);
+                               break;
+
+               }       /* End switch (IntuiMsg.Class) */
+
+               if (!WinPort) break;
+
+       }       /* End while (GT_GetIMsg ()) */
+}
+
+
+
+static void HandleKey (void)
+{
+       struct Window           *win = IntuiMsg.IDCMPWindow;
+       struct WinUserData      *wud = (struct WinUserData *)win->UserData;
+       UWORD i;
+       UBYTE keycode;
+
+
+       /* Handle key up for buttons */
+
+       if (IntuiMsg.Code & IECODE_UP_PREFIX)
+       {
+               struct Gadget *gad = ActiveGad;
+
+               DeselectButton();
+               if (gad && (ActiveKey == (IntuiMsg.Code & ~IECODE_UP_PREFIX)))
+                       ((void (*)(struct WinUserData *)) gad->UserData) (wud);
+
+               return;
+       }
+
+       switch (IntuiMsg.Code)
+       {
+               case 0x5F:      /* HELP */
+                       HandleHelp (&IntuiMsg);
+                       return;
+
+               case CURSORUP:
+               case CURSORDOWN:
+
+                       if (wud->GInfo)
+                               for (i = 0; i < wud->GCount; i++)
+                                       if (wud->GInfo[i].GKind == LISTVIEW_KIND)
+                                       {
+                                               struct Gadget *g = wud->Gadgets[i];
+                                               LONG selected, oldselected, top = ~0;
+
+#ifndef OS30_ONLY
+                                               if (GadToolsBase->lib_Version < 39)
+                                                       selected = (LONG)(*(UWORD *)(((char *)g)+sizeof(struct Gadget)+48));
+                                                       /* top = *(short *)(((char *)gad) + sizeof(struct Gadget) + 6); +4 ? */
+                                               else
+#endif /* !OS30_ONLY */
+                                                       GT_GetGadgetAttrs (g, win, NULL,
+                                                               GTLV_Selected,  &selected,
+                                                               GTLV_Top,               &top,
+                                                               TAG_DONE);
+
+                                               selected = (LONG)((WORD) selected);     /* Extend to long */
+                                               oldselected = selected; /* Make a backup of it */
+
+                                               if (selected == ~0)
+                                                       selected = top; /* Scroll Top */
+                                               else
+                                                       top = ~0;
+
+                                               if (IntuiMsg.Code == CURSORUP)
+                                               {
+                                                       if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
+                                                               selected -= 5;
+                                                       else if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
+                                                               selected = 0;
+                                                       else
+                                                               selected--;
+                                               }
+                                               else /* CURSORDOWN */
+                                               {
+                                                       if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
+                                                               selected += 5;
+                                                       else if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
+                                                               selected = 65535;
+                                                       else
+                                                               selected++;
+                                               }
+
+                                               if (selected < 0) selected = 0;
+
+                                               GT_SetGadgetAttrs (g, win, NULL,
+                                                       (top == ~0) ? GTLV_Selected : GTLV_Top,                 selected,
+                                                       (top == ~0) ? GTLV_MakeVisible : TAG_IGNORE,    selected,
+                                                       TAG_DONE);
+
+#ifndef OS30_ONLY
+                                               if (GadToolsBase->lib_Version < 39)
+                                                       selected = (LONG)(*(UWORD *)(((char *)g)+sizeof(struct Gadget)+48));
+                                               else
+#endif /* !OS30_ONLY */
+                                                       GT_GetGadgetAttrs (g, win, NULL,
+                                                               GTLV_Selected,  &selected,
+                                                               TAG_DONE);
+
+                                               if (selected != oldselected)
+                                               {
+                                                       IntuiMsg.Code = selected;
+                                                       if (g->UserData)
+                                                               ((void (*)(struct WinUserData *)) g->UserData) (wud);
+                                                       break; /* Stop for() loop */
+                                               }
+                                       }       /* End for */
+                       return;
+
+               case 0x42:      /* TAB */
+                       if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
+                       {
+                               struct WinUserData *nextwud;
+
+                               if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
+                               {
+                                       /* ALT+SHIFT+TAB: Cycle windows backwards */
+
+                                       nextwud = (struct WinUserData *)wud->Link.mln_Pred;
+
+                                       if (!(nextwud->Link.mln_Pred))  /* List head? */
+                                               nextwud = (struct WinUserData *)WindowList.lh_TailPred;
+                               }
+                               else
+                               {
+                                       /* ALT+TAB: Cycle windows */
+
+                                       nextwud = (struct WinUserData *)wud->Link.mln_Succ;
+
+                                       if (!(nextwud->Link.mln_Succ))  /* List tail? */
+                                               nextwud = (struct WinUserData *)WindowList.lh_Head;
+                               }
+
+                               RevealWindow (nextwud);
+                               return;
+                       }
+
+               default:
+                       break;
+
+       } /* End switch (IntuiMsg.Code) */
+
+
+       /*      Convert the IDCMP_RAWKEY IntuiMessage to the single
+        *      character representation it corresponds to. If this isn't
+        *      possible (e.g. a HELP key or cursor key) then abort.
+        */
+       {
+               static struct InputEvent ie;
+
+               ie.ie_NextEvent         = NULL;
+               ie.ie_Class                     = IECLASS_RAWKEY;
+               ie.ie_SubClass          = 0;
+               ie.ie_Code                      = IntuiMsg.Code;
+               ie.ie_Qualifier         = IntuiMsg.Qualifier & IEQUALIFIER_CONTROL;     /* Filter qualifiers. */
+               ie.ie_EventAddress      = (APTR *) *((ULONG *)IntuiMsg.IAddress);
+               if (MapRawKey (&ie, &keycode, 1, NULL) != 1)
+                       return;
+       }
+
+
+       /* Handle IDCMP_VANILLAKEY */
+
+       /* Check special keys */
+       switch (keycode)
+       {
+               case 0x03:      /* CTRL-C */
+                       Signal ((struct Task *)ThisTask, SIGBREAKF_CTRL_C);
+                       return;
+
+               case 0x09:      /* TAB */
+               case 0x0D:      /* RETURN */
+                       if (wud->GInfo)
+                               for (i = 0; i < wud->GCount; i++)
+                                       if (wud->GInfo[i].GKind == STRING_KIND || wud->GInfo[i].GKind == INTEGER_KIND)
+                                               ActivateGadget (wud->Gadgets[i],win, NULL);
+                       return;
+
+               case 0x1B:      /* ESC */
+                       if (wud->WindowID != WID_TOOLBOX)
+                               MyCloseWindow (wud);
+                       return;
+
+               default:
+                       break;
+       }
+
+
+       /* Look for gadget shortcuts */
+
+       if (wud->GInfo)
+               for (i = 0; i < wud->GCount; i++)
+               {
+                       if (wud->Keys[i] == keycode)    /* Case insensitive compare */
+                       {
+                               struct Gadget *g = wud->Gadgets[i];
+                               LONG disabled = FALSE;
+
+                               /* Check disabled */
+
+#ifndef OS30_ONLY
+                               if (GadToolsBase->lib_Version < 39)
+                                       disabled = g->Flags & GFLG_DISABLED;
+                               else
+#endif /* !OS30_ONLY */
+                                       GT_GetGadgetAttrs (g, win, NULL,
+                                               GA_Disabled, &disabled,
+                                               TAG_DONE);
+
+                               if (disabled) break;    /* Stop for() loop */
+
+                               switch (wud->GInfo[i].GKind)
+                               {
+                                       case BUTTON_KIND:
+                                               if (!(IntuiMsg.Qualifier & IEQUALIFIER_REPEAT))
+                                                       SelectButton (win, g);
+                                               break;
+
+                                       case CHECKBOX_KIND:
+
+                                               /* Toggle switch */
+                                               if (wud->GInfo[i].SpecialStorage)
+                                                       *((UWORD *)wud->GInfo[i].SpecialStorage) ^= 1;
+
+                                               GT_SetGadgetAttrs (g, win, NULL,
+                                                       GTCB_Checked, !(g->Flags & GFLG_SELECTED),
+                                                       TAG_DONE);
+
+                                               if (g->UserData)
+                                                       ((void (*)(struct WinUserData *)) g->UserData) (wud);
+                                               break;
+
+                                       case INTEGER_KIND:
+                                       case STRING_KIND:
+                                               ActivateGadget (g, win, NULL);
+                                               break;
+
+                                       case CYCLE_KIND:
+#ifndef OS30_ONLY
+                                               if (GadToolsBase->lib_Version >= 39)
+#endif /* !OS30_ONLY */
+                                               {
+                                                       LONG act, max;
+                                                       UBYTE **lab;
+
+                                                       /* ON V37: active = *(short *)(((char *)gad) + sizeof(struct Gadget) + 6); */
+
+                                                       GT_GetGadgetAttrs (g, win, NULL,
+                                                               GTCY_Active, &act,
+                                                               GTCY_Labels, &lab,
+                                                               TAG_DONE);
+
+                                                       act = (LONG)((UWORD)act);       /* Extend to LONG */
+
+                                                       if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
+                                                               act--;
+                                                       else
+                                                               act++;
+
+                                                       for (max = 0; lab[max]; max++); /* Count labels */
+
+                                                       if (act >= max) act = 0;
+                                                       else if (act < 0) act = max - 1;
+
+                                                       GT_SetGadgetAttrs (g, win, NULL,
+                                                               GTCY_Active, act,
+                                                               TAG_DONE);
+
+                                                       if (g->UserData)
+                                                       {
+                                                               IntuiMsg.Code = act;
+                                                               ((void (*)(struct WinUserData *)) g->UserData) (wud);
+                                                       }
+                                               }
+                                               break;
+
+                                       case MX_KIND:
+                                       {
+                                               LONG act;
+
+#ifndef OS30_ONLY
+                                               if (GadToolsBase->lib_Version < 39)
+                                                       act = (LONG)(*(UWORD *)(((char *)g)+sizeof(struct Gadget)+24)); /* 38? */
+                                               else
+#endif /* !OS30_ONLY */
+                                               {
+                                                       GT_GetGadgetAttrs (g, win, NULL,
+                                                               GTMX_Active, &act,
+                                                               TAG_DONE);
+                                                       act = (LONG)((UWORD)act);       /* Extend to LONG */
+                                               }
+
+                                               if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
+                                                       act--;
+                                               else
+                                                       act++;
+
+                                               GT_SetGadgetAttrs (g, win, NULL,
+                                                       GTMX_Active, act,
+                                                       TAG_DONE);
+
+#ifndef OS30_ONLY
+                                               if (GadToolsBase->lib_Version < 39)
+                                                       act = (LONG)(*(UWORD *)(((char *)g)+sizeof(struct Gadget)+24));
+                                               else
+#endif /* !OS30_ONLY */
+                                               {
+                                                       GT_GetGadgetAttrs (g, win, NULL,
+                                                               GTMX_Active, &act,
+                                                               TAG_DONE);
+                                                       act = (LONG)((UWORD)act);       /* Extend to LONG */
+                                               }
+
+                                               if (g->UserData)
+                                               {
+                                                       IntuiMsg.Code = act;
+                                                       ((void (*)(struct WinUserData *)) g->UserData) (wud);
+                                               }
+
+                                               break;
+                                       }
+
+                                       case SLIDER_KIND:
+#ifndef OS30_ONLY
+                                               if (GadToolsBase->lib_Version >= 39)
+#endif /* !OS30_ONLY */
+                                               {
+                                                       LONG min, max, level;
+
+                                                       GT_GetGadgetAttrs (g, win, NULL,
+                                                               GTSL_Min, &min,
+                                                               GTSL_Max, &max,
+                                                               GTSL_Level, &level,
+                                                               TAG_DONE);
+
+                                                       /* Extend to LONG */
+                                                       min = (LONG)((WORD)min);
+                                                       max = (LONG)((WORD)max);
+                                                       level = (LONG)((WORD)level);
+
+                                                       if (IntuiMsg.Qualifier & IEQUALIFIER_SHIFT)
+                                                       {
+                                                               if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
+                                                                       level = min;
+                                                               else level--;
+                                                       }
+                                                       else
+                                                       {
+                                                               if (IntuiMsg.Qualifier & IEQUALIFIER_ALT)
+                                                                       level = max;
+                                                               else level++;
+                                                       }
+
+                                                       if (level > max) level = max;
+                                                       if (level < min) level = min;
+
+                                                       GT_SetGadgetAttrs (g, win, NULL,
+                                                               GTSL_Level, level,
+                                                               TAG_DONE);
+
+                                                       if (g->UserData)
+                                                       {
+                                                               IntuiMsg.Code = level;
+                                                               ((void (*)(struct WinUserData *)) g->UserData) (wud);
+                                                       }
+                                               }
+                                               break;
+
+                                       default:
+                                               break;
+                               }
+
+                               return; /* Stop for() loop */
+                       }
+               }       /* End for() */
+
+
+       /* There is no apparent use for this key event,
+        * let's pass the IntuiMessage to user's IDCMPFunc()...
+        */
+       if (wud->IDCMPFunc) ((void (*)(void)) wud->IDCMPFunc) ();
+}
+
+
+
+static void SelectButton (struct Window *win, struct Gadget *gad)
+
+/* Selects the button gadget <gad>.  This operation is illegal with
+ * GadTools gadgets, but many programs do it anyway, so this trick
+ * will probably be supported in future OS releases :-).
+ */
+{
+       UWORD gadpos;
+
+       if (ActiveGad) DeselectButton();
+
+       gadpos = RemoveGadget (win, gad);
+
+       gad->Flags |= GFLG_SELECTED;
+       AddGadget (win, gad, gadpos);
+       RefreshGList (gad, win, NULL, 1);
+
+       ActiveKey = IntuiMsg.Code;
+       ActiveGad = gad;
+       ActiveWin = win;
+}
+
+
+
+static void DeselectButton (void)
+
+/* Deselects the button previously selected with SelectButton() */
+{
+       if      (ActiveGad)
+       {
+               UWORD gadpos = RemoveGadget (ActiveWin, ActiveGad);
+
+               ActiveGad->Flags &= ~GFLG_SELECTED;
+               AddGadget (ActiveWin, ActiveGad, gadpos);
+               RefreshGList (ActiveGad, ActiveWin, NULL, 1);
+
+               ActiveGad = NULL;
+       }
+}
+
+
+
+GLOBALCALL void LockWindows (void)
+
+/* Disable user input in all windows */
+{
+       struct WinUserData      *wud;
+       struct Window           *win;
+       struct WindowLock       *lock;
+
+
+       /* Are the windows already locked? */
+       WinLockCount++;
+       if (WinLockCount > 1) return;
+
+       for (wud = (struct WinUserData *) WindowList.lh_Head;
+               wud->Link.mln_Succ;
+               wud = (struct WinUserData *)wud->Link.mln_Succ)
+       {
+               if (!(win = wud->Win)) continue;
+
+               /* Set wait pointer */
+#ifndef OS30_ONLY
+               if (IntuitionBase->LibNode.lib_Version < 39)
+                       SetPointer (win, WaitPointer, 16, 16, -6, 0);
+               else
+#endif /* !OS30_ONLY */
+                       SetWindowPointer (win, WA_BusyPointer, TRUE, TAG_DONE);
+
+               /* Do not block input in Progress window */
+               if (wud->WindowID == WID_PROGRESS) continue;
+
+               /* Set an invisible Requester in window to block user input.
+                * We allocate 4 more bytes after the requester structure to store
+                * the IDCMP flags before modifying them.  MEMF_PUBLIC is used
+                * because intuition is going to process the Requester structure.
+                */
+
+               if (!(lock = AllocPooled (Pool, sizeof (struct WindowLock))))
+                       continue;
+
+               InitRequester (&lock->Req);
+               lock->Req.Flags = SIMPLEREQ | NOREQBACKFILL;
+
+               /* Disable window resizing */
+               if (win->Flags & WFLG_SIZEGADGET)
+               {
+                       lock->OldMinWidth       = win->MinWidth;
+                       lock->OldMinHeight      = win->MinHeight;
+                       lock->OldMaxWidth       = win->MaxWidth;
+                       lock->OldMaxHeight      = win->MaxHeight;
+                       WindowLimits (win, win->Width, win->Height,
+                               win->Width, win->Height);
+               }
+
+               /* Disable IDCMP messages except IDCMP_REFRESHWINDOW events.
+                * WARNING: ModifyIDCMP (win, 0) would free the shared port!!
+                */
+               lock->OldIDCMPFlags = win->IDCMPFlags;
+               ModifyIDCMP (win, IDCMP_REFRESHWINDOW);
+
+               Request (&lock->Req, win);
+       }
+}
+
+
+
+GLOBALCALL void UnlockWindows (void)
+
+/* Restore user input in all windows. */
+{
+       struct WinUserData      *wud;
+       struct Window           *win;
+       struct WindowLock       *lock;
+
+       /* Make sure windows arn't unlocked already */
+       if (WinLockCount) return;
+
+       /* Check lock nesting */
+       WinLockCount--;
+       if (WinLockCount) return;
+
+       for (wud = (struct WinUserData *) WindowList.lh_Head;
+               wud->Link.mln_Succ;
+               wud = (struct WinUserData *)wud->Link.mln_Succ)
+       {
+               if (!(win = wud->Win)) continue;
+
+               if (lock = (struct WindowLock *) win->FirstRequest)
+               {
+                       /* Restore old window IDCMP */
+                       ModifyIDCMP (win, lock->OldIDCMPFlags);
+
+                       /* Re-enable window sizing and restore old window limits */
+                       if (win->Flags & WFLG_SIZEGADGET)
+                               WindowLimits (win, lock->OldMinWidth, lock->OldMinHeight,
+                                       lock->OldMaxWidth, lock->OldMaxHeight);
+
+                       EndRequest (&lock->Req, wud->Win);
+                       FreePooled (Pool, lock, sizeof (struct WindowLock));
+               }
+
+               /* Restore standard pointer */
+#ifndef OS30_ONLY
+               if (IntuitionBase->LibNode.lib_Version < 39)
+                       ClearPointer (win);
+               else
+#endif /* !OS30_ONLY */
+                       SetWindowPointer (win, TAG_DONE);
+       }
+}
+
+
+
+GLOBALCALL void RevealWindow (struct WinUserData *wud)
+{
+       WindowToFront (wud->Win);
+       ActivateWindow (wud->Win);
+
+       /* Make the window visible on the screen */
+
+#ifndef OS30_ONLY
+       if (IntuitionBase->LibNode.lib_Version >= 39)
+#endif /* OS30_ONLY */
+               ScreenPosition (Scr, SPOS_MAKEVISIBLE,
+                       wud->Win->LeftEdge, wud->Win->TopEdge,
+                       wud->Win->LeftEdge + wud->Win->Width - 1,
+                       wud->Win->TopEdge + wud->Win->Height - 1);
+}
+
+
+
+GLOBALCALL void SetGadgets (struct WinUserData *wud, LONG arg, ...)
+
+/* Update status of gadgets in the window associated to <wud>.
+ * <arg> is the first of a -1 terminated array of commands.
+ * Each command is represented by a pair of LONGs, where the
+ * first LONG is the gadget number, and the second is the value
+ * to set for that gadget, depending on the gadget type.
+ */
+{
+       LONG *cmd = &arg;
+
+       static ULONG actions[] =
+       {
+               TAG_IGNORE,             /* GENERIC_KIND         */
+               TAG_IGNORE,             /* BUTTON_KIND          */
+               GTCB_Checked,   /* CHECKBOX_KIND        */
+               GTIN_Number,    /* INTEGER_KIND         */
+               GTLV_Selected,  /* LISTVIEW_KIND        */
+               GTMX_Active,    /* MX_KIND                      */
+               GTNM_Number,    /* NUMBER_KIND          */
+               GTCY_Active,    /* CYCLE_KIND           */
+               GTPA_Color,             /* PALETTE_KIND         */
+               TAG_IGNORE,             /* SCROLLER_KIND        */
+               TAG_IGNORE,             /* -- reserved --       */
+               GTSL_Level,             /* SLIDER_KIND          */
+               GTST_String,    /* STRING_KIND          */
+               GTTX_Text               /* TEXT_KIND            */
+       };
+
+       while (*cmd != -1)
+       {
+               GT_SetGadgetAttrs (wud->Gadgets[*cmd], wud->Win, NULL,
+                       actions[wud->GInfo[*cmd].GKind], *(cmd+1),
+                       TAG_DONE);
+
+               cmd += 2;
+       }
+}
+
+
+
+GLOBALCALL LONG AddListViewNode (struct List *lv, CONST_STRPTR label, ...)
+
+/* Var-args stub for AddListViewNodeA */
+{
+       return AddListViewNodeA (lv, label, (LONG *) (&label)+1);
+}
+
+
+
+GLOBALCALL LONG AddListViewNodeA (struct List *lv, CONST_STRPTR label, LONG *args)
+
+/* Allocate and add a new node to a ListView list.  The label
+ * is printf()-formatted and copied to a buffer just after the
+ * node structure.  Call RemListViewNode() to deallocate the node.
+ *
+ * RETURNS
+ *   0 for failure (no memory), any other value for success.
+ */
+{
+       struct Node *n;
+       UBYTE buf[256];
+
+       if (args)
+       {
+               VSPrintf (buf, label, args);
+               label = buf;
+       }
+
+       if (!(n = AllocVecPooled (Pool, sizeof (struct Node) + strlen (label) + 1)))
+               return FALSE;
+
+       n->ln_Name = ((UBYTE *)n) + sizeof (struct Node);
+
+       strcpy (n->ln_Name, label);
+       n->ln_Pri = 0;  /* Selected */
+
+       ADDTAIL (lv, n);
+
+       return TRUE;
+}
+
+
+
+GLOBALCALL void RemListViewNode (struct Node *n)
+{
+       REMOVE (n);
+       FreeVecPooled (Pool, n);
+}
+
+
+
+
+GLOBALCALL struct Image *NewImageObject (ULONG which)
+
+/* Creates a sysiclass object. */
+{
+       return ((struct Image *)NewObject (NULL, SYSICLASS,
+               SYSIA_DrawInfo, DrawInfo,
+               SYSIA_Which,    which,
+               SYSIA_Size,             Scr->Flags & SCREENHIRES ? SYSISIZE_MEDRES : SYSISIZE_LOWRES,
+               TAG_DONE));
+
+       /* NB: SYSISIZE_HIRES not yet supported. */
+}
+
+
+
+static struct Gadget *CreateVImageButton (struct TagItem *tags, struct NewGadget *ng)
+
+/* This routine is called by the layout engine to create an IMAGEBUTTON_KIND gadget. */
+{
+       struct Gadget   *VButton;
+       struct Image    *VImage;
+
+       if (!VImageClass)
+       {
+               if (VImageClass = InitVImageClass ())
+               {
+                       if (ButtonFrame = NewObject (NULL, FRAMEICLASS,
+                               IA_FrameType,   FRAME_BUTTON,
+                               IA_EdgesOnly,   TRUE,
+                               TAG_DONE))
+                       {
+                               struct IBox FrameBox, ContentsBox = { 0, 0, 0, 0 };
+
+                               DoMethod ((Object *)ButtonFrame, IM_FRAMEBOX, &ContentsBox, &FrameBox, DrawInfo, 0);
+
+                               ButtonFrameWidth = FrameBox.Width;
+                               ButtonFrameHeight = FrameBox.Height;
+                       }
+                       else
+                       {
+                               FreeVImageClass (VImageClass);
+                               VImageClass = NULL;
+                               return NULL;
+                       }
+               }
+               else return NULL;
+       }
+
+       if (!(VImage = (struct Image *)NewObject (VImageClass, NULL,
+               IA_Width,               ng->ng_Width - ButtonFrameWidth,
+               IA_Height,              ng->ng_Height - ButtonFrameHeight,
+               SYSIA_Which,    ng->ng_Flags,
+               TAG_DONE)))
+               return NULL;
+
+       if (!(VButton = (struct Gadget *)NewObject (NULL, FRBUTTONCLASS,
+               GA_ID,                  ng->ng_GadgetID,
+               GA_UserData,    ng->ng_UserData,
+               GA_Left,                ng->ng_LeftEdge,
+               GA_Top,                 ng->ng_TopEdge,
+               GA_Image,               ButtonFrame,
+               GA_LabelImage,  VImage,
+               GA_RelVerify,   TRUE,
+               TAG_DONE)))
+               DisposeObject (VImage);
+       else
+               VImageUseCount++;
+
+       return VButton;
+}
+
+
+
+static void DeleteVImageButton (struct Gadget *g)
+{
+       if (g)
+       {
+               DisposeObject (g->GadgetText);
+               DisposeObject (g);
+
+               VImageUseCount--;
+
+               if (!VImageUseCount)
+               {
+                       if (ButtonFrame)
+                       {
+                               DisposeObject (ButtonFrame);
+                               ButtonFrame = NULL;
+                       }
+
+                       if (VImageClass)
+                       {
+                               FreeVImageClass (VImageClass);
+                               VImageClass = NULL;
+                       }
+               }
+       }
+}
+
+
+
+/* Gadget definition format:
+ *
+ * VGROUP_KIND,                        BorderType,
+ * HGROUP_KIND,                        BorderType,
+ * IMAGEBUTTON_KIND,   ClickedFunction, ImageType, Tag1, ...,
+ * CHECKBOX_KIND,              ClickedFunction, Label, Storage (UWORD *), Tag1, ...,
+ * BUTTON_KIND,                        ClickedFunction, Label, Tag1, ...,
+ * INTEGER_KIND,               ClickedFunction, Label, MaxDigits, Tag1, ...,
+ * STRING_KIND,                        ClickedFunction, Label, MaxChars, Tag1, ...,
+ * TEXT_KIND,                  Label, NumChars, Tag1, ...,
+ * NUMBER_KIND,                        Label, MaxDigits, Tag1, ...,
+ * LISTVIEW_KIND,              ClickedFunction, Label, Labels (struct List *), Tag1, ...,
+ * MX_KIND,                            ClickedFunction, Label, Labels (ULONG *), Tag1, ...,
+ * CYCLE_KIND,                 ClickedFunction, Label, Labels (ULONG *), Tag1, ...,
+ * SLIDER_KIND,                        ClickedFunction, Label, Min, Max, LevelFormat (UBYTE *), MaxLevelLen (ULONG), Tag1, ...,
+ * PALETTE_KIND,               ClickedFunction, Label, Tags,
+ * ENDGROUP_KIND,
+ */
+
+
+static void LayoutGadgets (struct LayoutGadgetsArgs *lga, UWORD mode)
+{
+       struct GInfo            *ginfo,
+                                               *myginfo = &lga->GInfo[lga->Count];
+       STRPTR label;
+
+
+       /* Skip this group */
+       lga->Count++;
+
+       if (mode == LAYOUTMODE_V)
+               myginfo->Flags |= GIF_FIXEDWIDTH;
+       else
+               myginfo->Flags |= GIF_FIXEDHEIGHT;
+
+       while (*lga->Args != ENDGROUP_KIND)
+       {
+               ginfo = &lga->GInfo[lga->Count];
+               label = NULL;
+
+               switch (ginfo->GKind = *lga->Args++)
+               {
+                       case HGROUP_KIND:
+                       case VGROUP_KIND:
+                               if (*lga->Args++) /* Border? */
+                               {
+                                       LayoutGadgets (lga, (ginfo->GKind == HGROUP_KIND) ? LAYOUTMODE_H : LAYOUTMODE_V);
+
+                                       /* Add border dimensions to group size */
+                                       ginfo->MinWidth += HSPACING * 8;
+                                       ginfo->Fixed.Width += HSPACING * 8;
+                                       ginfo->MinHeight += VSPACING * 8;
+                                       ginfo->Fixed.Height += VSPACING * 8;
+                                       ginfo->Flags |= GIF_HASBORDER;
+                               }
+                               else LayoutGadgets (lga, (ginfo->GKind == HGROUP_KIND) ? LAYOUTMODE_H : LAYOUTMODE_V);
+                               break;
+
+                       case IMAGEBUTTON_KIND:
+                               lga->Args++;    /* Skip Clicked function */
+                               lga->Args++;    /* Skip Image */
+                               ginfo->MinWidth         = lga->Wud->Font->tf_XSize * 3 + 2;
+                               ginfo->MinHeight        = lga->Wud->Font->tf_YSize + 4;
+                               ginfo->Flags |= GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT;
+                               break;
+
+                       case CHECKBOX_KIND:
+                               lga->Args++;    /* Skip Clicked function */
+                               label = STR(*lga->Args++);
+                               ginfo->SpecialStorage = (void *)*lga->Args++;   /* Record storage */
+                               ginfo->MinWidth         = lga->Wud->Font->tf_XSize * 2 + 8;
+                               ginfo->MinHeight        = lga->Wud->Font->tf_YSize + 4;
+                               ginfo->LabelWidth       = TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
+                               ginfo->Flags            = GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT;
+                               break;
+
+                       case BUTTON_KIND:
+                               lga->Args++;    /* Skip Clicked function */
+                               label = STR(*lga->Args++);
+                               ginfo->MinWidth         = TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
+                               ginfo->MinHeight        = lga->Wud->Font->tf_YSize + 4;
+                               ginfo->Flags            = GIF_FIXEDHEIGHT;
+                               break;
+
+                       case INTEGER_KIND:
+                       case STRING_KIND:
+                       {
+                               UWORD maxchars;
+
+                               lga->Args++;    /* Skip Clicked function */
+                               if (label = STR (*lga->Args++))
+                                       ginfo->LabelWidth       = TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
+
+                               maxchars = *lga->Args++;
+
+                               ginfo->MinWidth         = lga->Wud->Font->tf_XSize * min (maxchars + 1, 8) + 12;
+                               ginfo->MinHeight        = lga->Wud->Font->tf_YSize + 6;
+                               ginfo->Flags            = GIF_FIXEDHEIGHT;
+
+                               break;
+                       }
+
+                       case LISTVIEW_KIND:
+                               lga->Args++;    /* Skip Clicked function */
+                               ginfo->MinWidth         = lga->Wud->Font->tf_XSize * 16 + 16;
+                               ginfo->MinHeight        = lga->Wud->Font->tf_YSize * 4 + 4;
+                               if (label = STR (*lga->Args++))
+                                       ginfo->LabelWidth       = TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
+                               ginfo->SpecialStorage = (void *)*lga->Args++;   /* Record List pointer */
+                               break;
+
+                       case MX_KIND:
+                       case CYCLE_KIND:
+                       {
+                               ULONG   *labels, cnt = 0;
+                               STRPTR  str;
+
+                               lga->Args++;    /* Skip Clicked function */
+
+                               if (label = STR(*lga->Args++))
+                                       ginfo->LabelWidth       = TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
+
+                               ginfo->MinWidth = ginfo->MinHeight = 0;
+
+
+                               /* Count labels */
+
+                               labels = (ULONG *) *lga->Args++;
+                               while (labels[cnt]) cnt++;
+
+
+                               /* Allocate and fill-in MX/Cycle labels array */
+
+                               if (!ginfo->SpecialStorage)
+                                        ginfo->SpecialStorage = AllocVecPooled (Pool, (cnt + 1) * sizeof (STRPTR));
+
+                               if (ginfo->SpecialStorage)
+                               {
+                                       ULONG i;
+
+                                       for (i = 0; i < cnt; i++)
+                                       {
+                                               /* Store localized label */
+                                               ((STRPTR *)(ginfo->SpecialStorage))[i] = str = STR (labels[i]);
+
+                                               /* Calculate maximum width */
+                                               ginfo->MinWidth = max (ginfo->MinWidth, TextLength (lga->DummyRast, str, strlen (str)) + LABELSPACING);
+
+                                               if (ginfo->GKind == MX_KIND)
+                                                       ginfo->MinHeight += lga->Wud->Font->tf_YSize + 2;
+                                       }
+
+                                       /* Terminate labels array */
+                                       ((STRPTR *)(ginfo->SpecialStorage))[i] = NULL;
+                               }
+
+                               /* add width of radio button (or cycle image) */
+                               ginfo->MinWidth += lga->Wud->Font->tf_XSize * 2 + 4;
+
+                               if (ginfo->GKind == MX_KIND)
+                                       ginfo->Flags = GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT;
+                               else
+                               {
+                                       ginfo->Flags = GIF_FIXEDHEIGHT;
+                                       ginfo->MinHeight += lga->Wud->Font->tf_YSize + 4;
+                               }
+
+                               break;
+                       }
+
+                       case NUMBER_KIND:
+                       case TEXT_KIND:
+
+                               if (label = STR (*lga->Args++))
+                                       ginfo->LabelWidth       = TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
+
+                               ginfo->MinWidth         = lga->Wud->Font->tf_XSize * (*lga->Args++) + 8;
+                               ginfo->MinHeight        = lga->Wud->Font->tf_YSize + 4;
+                               ginfo->Flags            = GIF_FIXEDHEIGHT;
+                               break;
+
+                       case SLIDER_KIND:
+                               lga->Args++;    /* Skip Clicked function */
+
+                               if (label = STR (*lga->Args++))
+                                       ginfo->LabelWidth       = TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
+
+                               lga->Args += 3; /* Skip Min, Max and LevelFormat */
+
+                               if (ginfo->SpecialStorage = (APTR)((*lga->Args++) * lga->Wud->Font->tf_XSize))
+                                       ginfo->SpecialStorage = (APTR) ((ULONG)ginfo->SpecialStorage + 4);
+
+                               ginfo->MinWidth         = lga->Wud->Font->tf_XSize * 8 + 8 + (ULONG)ginfo->SpecialStorage;
+                               ginfo->MinHeight        = lga->Wud->Font->tf_YSize;
+                               ginfo->Flags            = GIF_FIXEDHEIGHT;
+                               break;
+
+                       case PALETTE_KIND:
+                               lga->Args++;    /* Skip Clicked function */
+
+                               if (label = STR (*lga->Args++))
+                                       ginfo->LabelWidth       = TextLength (lga->DummyRast, label, strlen (label)) + LABELSPACING;
+
+                               ginfo->MinWidth         = lga->Wud->Font->tf_XSize * 6 + 8;
+                               ginfo->MinHeight        = lga->Wud->Font->tf_YSize * 3 + 4;
+                               break;
+               }
+
+               if ((ginfo->GKind != HGROUP_KIND) && (ginfo->GKind != VGROUP_KIND))
+               {
+                       UBYTE *c;
+
+                       /* Go to next gadget (ie: skip tags until TAG_END) */
+                       while (*lga->Args) lga->Args += 2;
+                       lga->Args++;
+
+                       /* Look for the key equivalent of this gadget. */
+                       if (c = label)
+                               for ( ; *c ; c++)
+                                       if (*c == '_')
+                                       {
+                                               /* Found! Now store in the key array */
+                                               lga->Wud->Keys[lga->Count] = *(++c) | (1<<5);   /* Lower case */
+                                               break;
+                                       }
+               }
+
+               if (mode == LAYOUTMODE_V)
+               {
+                       myginfo->MinHeight += ginfo->MinHeight + VSPACING;
+                       if (ginfo->Flags & GIF_HASBORDER)
+                               myginfo->MinWidth       = max (myginfo->MinWidth, ginfo->MinWidth + ginfo->LabelWidth);
+                       else
+                       {
+                               myginfo->MinWidth       = max (myginfo->MinWidth, ginfo->MinWidth);
+                               myginfo->LabelWidth     = max (myginfo->LabelWidth, ginfo->LabelWidth);
+                       }
+
+                       myginfo->Fixed.Height += VSPACING;
+                       if (ginfo->Flags & GIF_FIXEDHEIGHT)
+                               myginfo->Fixed.Height += ginfo->MinHeight;
+                       if (!(ginfo->Flags & GIF_FIXEDWIDTH))
+                               myginfo->Flags &= ~GIF_FIXEDWIDTH;
+               }
+               else    /* LAYOUTMODE_H */
+               {
+                       myginfo->MinWidth       += ginfo->MinWidth + ginfo->LabelWidth + HSPACING;
+                       myginfo->MinHeight      = max (myginfo->MinHeight, ginfo->MinHeight);
+                       myginfo->Fixed.Width += ginfo->LabelWidth + HSPACING;
+                       if (ginfo->Flags & GIF_FIXEDWIDTH)
+                               myginfo->Fixed.Width += ginfo->MinWidth;
+                       if (!(ginfo->Flags & GIF_FIXEDHEIGHT))
+                               myginfo->Flags &= ~GIF_FIXEDHEIGHT;
+               }
+
+               lga->Count++;
+
+       }       /* End while (*lga->Args != ENDGROUP_KIND) */
+
+
+       if (mode == LAYOUTMODE_H)
+       {
+               /* Remove extra HSPACING after last child */
+               myginfo->MinWidth               -= HSPACING;
+               myginfo->Fixed.Width    -= HSPACING;
+
+               if (!(myginfo->Flags & GIF_HASBORDER))
+               {
+                       /* Align first child of HGROUP_KIND with other
+                        * gadgets in our parent group
+                        */
+                       myginfo->LabelWidth             = (myginfo+1)->LabelWidth;
+                       (myginfo+1)->LabelWidth = 0;
+                       myginfo->MinWidth               -= myginfo->LabelWidth;
+                       myginfo->Fixed.Width    -= myginfo->LabelWidth;
+               }
+       }
+       else /* LAYOUTMODE_V */
+       {
+               /* Remove extra VSPACING after last child */
+               myginfo->MinHeight              -= VSPACING;
+               myginfo->Fixed.Height   -= VSPACING;
+       }
+
+       if (myginfo->MinWidth <= myginfo->Fixed.Width)
+               myginfo->Flags |= GIF_FIXEDWIDTH;
+       if (myginfo->MinHeight <= myginfo->Fixed.Height)
+               myginfo->Flags |= GIF_FIXEDHEIGHT;
+
+       lga->Args++;    /* Skip ENDGROUP_KIND           */
+       lga->Count--;   /* Take back one position       */
+}
+
+
+
+static struct Gadget *CreateGadgets (struct LayoutGadgetsArgs *lga, UWORD mode, UWORD left, UWORD top, UWORD width, UWORD height)
+{
+       struct GInfo            *ginfo,
+                                               *myginfo        = &lga->GInfo[lga->Count];
+       struct TagItem          *Tags, *tmp;
+       ULONG                            stc;
+       struct NewGadget         ng;
+       BOOL                             boopsi;
+
+
+       /* Skip this group */
+       lga->Count++;
+
+
+       while (*lga->Args != ENDGROUP_KIND)
+       {
+               ginfo = &lga->GInfo[lga->Count];
+               Tags = (struct TagItem *)lga->SpecialTags;
+               stc = 2;
+               boopsi = FALSE;
+
+               ng.ng_TextAttr          = lga->Wud->Attr;
+               ng.ng_GadgetID          = lga->Count;
+               ng.ng_VisualInfo        = lga->VInfo;
+               ng.ng_Flags                     = PLACETEXT_LEFT;
+
+               if (mode == LAYOUTMODE_V)
+               {
+                       if (ginfo->Flags & GIF_FIXEDWIDTH)
+                               ng.ng_Width = ginfo->MinWidth;
+                       else
+                               ng.ng_Width = width - ((ginfo->Flags & GIF_HASBORDER) ? ginfo->LabelWidth : 0);
+
+                       if (ginfo->Flags & GIF_FIXEDHEIGHT)
+                               ng.ng_Height = ginfo->MinHeight;
+                       else
+                               ng.ng_Height = (((ULONG)(height - myginfo->Fixed.Height)) * ((ULONG)ginfo->MinHeight)) / ((ULONG)(myginfo->MinHeight - myginfo->Fixed.Height));
+
+                       ng.ng_LeftEdge  = left + ((ginfo->Flags & GIF_HASBORDER) ? ginfo->LabelWidth : 0);
+                       ng.ng_TopEdge   = top;
+                       top = ng.ng_TopEdge + ng.ng_Height + VSPACING;
+               }
+               else    /* mode == LAYOUTMODE_H */
+               {
+                       if (ginfo->Flags & GIF_FIXEDWIDTH)
+                               ng.ng_Width = ginfo->MinWidth;
+                       else
+                               ng.ng_Width = (((ULONG)(width - myginfo->Fixed.Width)) * ((ULONG)ginfo->MinWidth)) / ((ULONG)(myginfo->MinWidth - myginfo->Fixed.Width));
+
+                       if (ginfo->Flags & GIF_FIXEDHEIGHT)
+                       {
+                               /* Vertical center */
+                               ng.ng_Height    = ginfo->MinHeight;
+                               ng.ng_TopEdge   = top + (height - ng.ng_Height) / 2;
+                       }
+                       else
+                       {
+                               /* Vertical stretch */
+                               ng.ng_Height    = height;
+                               ng.ng_TopEdge   = top;
+                       }
+
+                       ng.ng_LeftEdge  = left + ginfo->LabelWidth;
+
+                       left = ng.ng_LeftEdge + ng.ng_Width + HSPACING;
+               }
+
+               switch (ginfo->GKind = *lga->Args++)
+               {
+                       case BOOPSI_KIND:
+                               ng.ng_UserData = (void *)(*lga->Args++);
+                               lga->SpecialTags[stc++] = XMGAD_SetupFunc;
+                               lga->SpecialTags[stc++] = (*lga->Args++);
+                               boopsi = TRUE;
+                               break;
+
+                       case IMAGEBUTTON_KIND:
+                               ng.ng_UserData          = (void *)(*lga->Args++);
+                               ng.ng_Flags                     = (*lga->Args++);
+                               ng.ng_GadgetText        = NULL;
+                               lga->SpecialTags[stc++] = XMGAD_SetupFunc;
+                               lga->SpecialTags[stc++] = (ULONG)CreateVImageButton;
+                               boopsi = TRUE;
+                               break;
+
+                       case VGROUP_KIND:
+                       case HGROUP_KIND:
+                       {
+                               ULONG bordertype;
+
+                               if (bordertype = *lga->Args++)
+                               {
+                                       if (!CreateWindowBorder (lga->Wud, ng.ng_LeftEdge - ginfo->LabelWidth, ng.ng_TopEdge,
+                                               ng.ng_Width + ginfo->LabelWidth, ng.ng_Height, bordertype))
+                                               return NULL;
+                                       if (!CreateGadgets (lga, (ginfo->GKind == VGROUP_KIND) ? LAYOUTMODE_V : LAYOUTMODE_H,
+                                               ng.ng_LeftEdge + HSPACING*4, ng.ng_TopEdge + VSPACING*4,
+                                               ng.ng_Width - HSPACING*8, ng.ng_Height - VSPACING*8))
+                                               return NULL;
+                               }
+                               else if (!CreateGadgets (lga, (ginfo->GKind == VGROUP_KIND) ? LAYOUTMODE_V : LAYOUTMODE_H,
+                                       ng.ng_LeftEdge, ng.ng_TopEdge, ng.ng_Width, ng.ng_Height))
+                                       return NULL;
+
+
+                               break;
+                       }
+
+                       case BUTTON_KIND:
+                               ng.ng_UserData          = (void *)(*lga->Args++);
+                               ng.ng_GadgetText        = STR(*lga->Args++);
+                               ng.ng_Flags                     = PLACETEXT_IN;
+                               break;
+
+                       case CHECKBOX_KIND:
+                               ng.ng_UserData = (void *)(*lga->Args++);
+                               ng.ng_GadgetText = STR(*lga->Args++);
+                               lga->Args++;    /* Skip storage */
+
+                               lga->SpecialTags[stc++] = GTCB_Checked;
+                               lga->SpecialTags[stc++] = (ginfo->SpecialStorage ? (*((UWORD *)ginfo->SpecialStorage)) : NULL);
+                               lga->SpecialTags[stc++] = GTCB_Scaled;
+                               lga->SpecialTags[stc++] = TRUE;
+                               break;
+
+                       case INTEGER_KIND:
+                               ng.ng_UserData = (void *)(*lga->Args++);
+                               ng.ng_GadgetText = STR(*lga->Args++);
+
+                               /* Editing in right-justified integer gadgets
+                                * is very uncomfortable!!
+                                *
+                                * lga->SpecialTags[stc++] = STRINGA_Justification;
+                                * lga->SpecialTags[stc++] = GACT_STRINGRIGHT;
+                                */
+                               lga->SpecialTags[stc++] = GTIN_MaxChars;
+                               lga->SpecialTags[stc++] = *lga->Args++;
+                               lga->SpecialTags[stc++] = STRINGA_ExitHelp;
+                               lga->SpecialTags[stc++] = TRUE;
+                               break;
+
+                       case LISTVIEW_KIND:
+                       {
+                               struct List *l;
+
+                               ng.ng_UserData          = (void *)*lga->Args++;
+                               ng.ng_GadgetText        = STR(*lga->Args++);
+
+                               lga->SpecialTags[stc++] = GTLV_ShowSelected;
+                               lga->SpecialTags[stc++] = NULL;
+
+                               if (l = (struct List *)*lga->Args++)
+                               {
+                                       lga->SpecialTags[stc++] = GTLV_Labels;
+                                       lga->SpecialTags[stc++] = (ULONG)l;
+
+                                       // l->lh_Type = 0;      /* Selected Item        */
+                                       // l->l_pad = 0;        /* Item Count           */
+                               }
+
+#ifndef OS30_ONLY
+                               if (GadToolsBase->lib_Version < 39)
+                                       ng.ng_Height -= 4;
+#endif /* !OS30_ONLY */
+
+                               break;
+                       }
+
+                       case MX_KIND:
+                       case CYCLE_KIND:
+                       {
+                               ng.ng_UserData          = (void *)(*lga->Args++);
+                               ng.ng_GadgetText        = STR(*lga->Args++);
+
+                               /* Skip labels */
+                               lga->Args++;
+
+                               if (ginfo->GKind == MX_KIND)
+                               {
+                                       /* Special kludge: MX gadgets width and height refer to
+                                        * one button instead of the whole gadget.
+                                        */
+                                       ng.ng_Width = lga->Wud->Font->tf_XSize * 2;
+                                       ng.ng_Height = lga->Wud->Font->tf_YSize;
+
+                                       ng.ng_Flags = PLACETEXT_RIGHT;
+
+                                       lga->SpecialTags[stc++] = GTMX_Labels;
+                                       lga->SpecialTags[stc++] = (ULONG)ginfo->SpecialStorage;
+                                       lga->SpecialTags[stc++] = GTMX_Scaled;
+                                       lga->SpecialTags[stc++] = TRUE;
+                                       lga->SpecialTags[stc++] = GTMX_Spacing;
+                                       lga->SpecialTags[stc++] = 2;
+                               }
+                               else
+                               {
+                                       lga->SpecialTags[stc++] = GTCY_Labels;
+                                       lga->SpecialTags[stc++] = (ULONG)ginfo->SpecialStorage;
+                               }
+
+                               break;
+                       }
+
+                       case NUMBER_KIND:
+                               ng.ng_UserData = NULL;
+                               ng.ng_GadgetText = STR(*lga->Args++);
+
+                               lga->Args++;    /* Skip NumDigits */
+
+                               lga->SpecialTags[stc++] = GTNM_Border;
+                               lga->SpecialTags[stc++] = TRUE;
+
+                               /* Under V39 and below, GTJ_RIGHT does not work properly. */
+                               if (GadToolsBase->lib_Version > 39)
+                               {
+                                       lga->SpecialTags[stc++] = GTNM_Justification;
+                                       lga->SpecialTags[stc++] = GTJ_RIGHT;
+                               }
+                               break;
+
+                       case TEXT_KIND:
+                               ng.ng_UserData = NULL;
+                               ng.ng_GadgetText = STR(*lga->Args++);
+
+                               lga->Args++;    /* Skip NumDigits */
+                               break;
+
+                       case PALETTE_KIND:
+                               ng.ng_UserData = (void *)(*lga->Args++);
+                               ng.ng_GadgetText = STR(*lga->Args++);
+
+                               lga->SpecialTags[stc++] = GTPA_IndicatorWidth;
+                               lga->SpecialTags[stc++] = ng.ng_Width / 8;
+                               break;
+
+                       case SCROLLER_KIND:
+                               ng.ng_UserData = (void *)(*lga->Args++);
+                               ng.ng_GadgetText = STR(*lga->Args++);
+
+                               lga->SpecialTags[stc++] = GTSC_Arrows;
+                               lga->SpecialTags[stc++] = lga->Wud->Font->tf_XSize + 4;
+                               break;
+
+                       case STRING_KIND:
+/*                             ClassId = STRGCLASS;
+                               lga->SpecialTags[stc++] = GA_UserData;
+                               lga->SpecialTags[stc++] = *lga->Args++;
+                               lga->SpecialTags[stc++] = GA_LabelImage;
+                               lga->SpecialTags[stc++] = *lga->Args++;
+                               lga->SpecialTags[stc++] = STRINGA_MaxChars;
+                               lga->SpecialTags[stc++] = *lga->Args++;
+                               lga->SpecialTags[stc++] = GA_Border;
+                               lga->SpecialTags[stc++] = StringFrame;
+                               lga->SpecialTags[stc++] = STRINGA_ExitHelp;
+                               lga->SpecialTags[stc++] = TRUE;
+*/
+                               ng.ng_UserData = (void *)(*lga->Args++);
+                               ng.ng_GadgetText = STR(*lga->Args++);
+
+                               lga->SpecialTags[stc++] = GTST_MaxChars;
+                               lga->SpecialTags[stc++] = *lga->Args++;
+                               lga->SpecialTags[stc++] = STRINGA_ExitHelp;
+                               lga->SpecialTags[stc++] = TRUE;
+                               break;
+
+                       case SLIDER_KIND:
+                               ng.ng_UserData = (void *)(*lga->Args++);
+                               ng.ng_GadgetText = STR(*lga->Args++);
+
+                               lga->SpecialTags[stc++] = GTSL_Min;
+                               lga->SpecialTags[stc++] = *lga->Args++;
+                               lga->SpecialTags[stc++] = GTSL_Max;
+                               lga->SpecialTags[stc++] = *lga->Args++;
+                               lga->SpecialTags[stc++] = GTSL_LevelFormat;
+                               lga->SpecialTags[stc++] = *lga->Args++;
+                               lga->SpecialTags[stc++] = GTSL_MaxLevelLen;
+                               lga->SpecialTags[stc++] = *lga->Args++;
+                               lga->SpecialTags[stc++] = GTSL_LevelPlace;
+                               lga->SpecialTags[stc++] = PLACETEXT_RIGHT;
+                               lga->SpecialTags[stc++] = GTSL_Justification;
+                               lga->SpecialTags[stc++] = GTJ_RIGHT;
+                               lga->SpecialTags[stc++] = GA_RelVerify;
+                               lga->SpecialTags[stc++] = TRUE;
+
+                               ng.ng_Width -= (ULONG)ginfo->SpecialStorage;
+                               break;
+               }
+
+               if ((ginfo->GKind != HGROUP_KIND) && (ginfo->GKind != VGROUP_KIND))
+               {
+                       if (*lga->Args != TAG_DONE)
+                       {
+                               /* Add user Tags */
+                               lga->SpecialTags[stc++] = TAG_MORE;
+                               lga->SpecialTags[stc] = (ULONG) lga->Args;
+                       }
+                       else lga->SpecialTags[stc] = TAG_DONE;
+
+
+                       /* Go to next gadget (ie: skip tags until TAG_END) */
+                       while (*lga->Args) lga->Args += 2;
+                       lga->Args++;
+
+                       if (boopsi)
+                       {
+                               /* BOOPSI Gadget. Let SetupFunc() allocate the gadget for us */
+
+                               if (tmp = FindTagItem (XMGAD_SetupFunc, Tags))
+                               {
+                                       if (lga->PrevGad->NextGadget = ((struct Gadget * (*)(struct TagItem *, struct NewGadget *)) (tmp->ti_Data)) (Tags, &ng))
+                                               /* Record it into the list */
+                                               lga->Wud->Gadgets[lga->Count] = lga->PrevGad->NextGadget;
+                                       else
+                                               return NULL;
+                               }
+                               else
+                                       return NULL;
+                       }
+                       else
+                       {
+                               /* Normal GadTools gadget */
+
+                               if (!(lga->Wud->Gadgets[lga->Count] = CreateGadgetA (ginfo->GKind, lga->PrevGad, &ng, Tags)))
+                                       return NULL;
+                       }
+
+                       lga->PrevGad = lga->Wud->Gadgets[lga->Count];
+
+               } /* End if (GKind != ?GROUP_KIND */
+
+               lga->Count++;   /* Go to next gadget    */
+       }
+
+       lga->Args++;    /* Skip ENDGROUP_KIND           */
+       lga->Count--;   /* Take back one position       */
+
+       return (lga->Wud->GList);
+}
+
+
+
+static void RenderWindowBorders (struct WinUserData *wud)
+{
+       struct WindowBorder *border = wud->Borders;
+
+       while (border)
+       {
+               DrawBevelBox (wud->Win->RPort,
+                       border->Size.Left, border->Size.Top,
+                       border->Size.Width, border->Size.Height,
+                       GT_VisualInfo,  VisualInfo,
+                       GTBB_Recessed,  TRUE,
+                       GTBB_FrameType, border->Type,
+                       TAG_DONE);
+
+               border = border->NextBorder;
+       }
+}
+
+
+
+static struct WindowBorder *CreateWindowBorder (struct WinUserData *wud,
+       UWORD left, UWORD top, UWORD width, UWORD height, ULONG bordertype)
+
+/* Creates new WindowBorder structure and links it to the WUD passed.
+ * Will return NULL in case of failure.
+ */
+{
+       struct WindowBorder *border;
+
+       if (!(border = AllocPooled (Pool, sizeof (struct WindowBorder))))
+               return NULL;
+
+       /* Link this border struct in WUD */
+       border->NextBorder = wud->Borders;
+       wud->Borders = border;
+
+       /* Fill in WindowBorder structure */
+       border->Type            = bordertype;
+       border->Size.Left       = left;
+       border->Size.Top        = top;
+       border->Size.Width      = width;
+       border->Size.Height     = height;
+
+       return border;
+}
+
+
+
+static void DeleteGadgets (struct WinUserData *wud)
+{
+       FreeGadgets (wud->GList);       wud->GList = NULL;
+
+       if (wud->GInfo)
+       {
+               ULONG i;
+
+               for (i = 0; i < wud->GCount; i++)
+               {
+                       if (wud->Gadgets[i])
+                       {
+                               switch (wud->GInfo[i].GKind)
+                               {
+                                       case IMAGEBUTTON_KIND:
+                                               DeleteVImageButton (wud->Gadgets[i]);
+                                               break;
+
+                                       default:
+                                               break;
+                               }
+                       }
+               }
+       }
+
+       if (wud->Borders)
+       {
+               struct WindowBorder *border = wud->Borders, *nextborder;
+
+               do
+               {
+                       nextborder = border->NextBorder;
+                       FreePooled (Pool, border, sizeof (struct WindowBorder));
+               }
+               while (border = nextborder);
+
+               wud->Borders = NULL;
+       }
+}
+
+
+
+struct WinUserData *CreateWUD (ULONG id)
+{
+       struct WinUserData *wud;
+       struct WDescr *wdescr = &WDescr[id];
+
+
+       /* Create a new WUD structure for this window */
+       if (wud = CAllocPooled (Pool, sizeof (struct WinUserData)))
+       {
+               wdescr->Wud = wud;
+               wdescr->UseCnt++;
+               wud->WindowID = id;
+               wud->IDCMPFlags =       IDCMP_RAWKEY | IDCMP_MENUHELP |
+                                                       IDCMP_GADGETHELP | IDCMP_INACTIVEWINDOW;
+               wud->Flags = WFLG_DRAGBAR | WFLG_DEPTHGADGET;
+               wud->Title = PrgName;
+
+               /* Link WUD to windows list */
+               ADDHEAD (&WindowList, (struct Node *)wud);
+
+               if (wdescr->CreationTags)
+               {
+                       struct TagItem  *tstate = wdescr->CreationTags,
+                                                       *tag;
+
+                       /* Read taglist arguments */
+
+                       while (tag = NextTagItem (&tstate))
+                               switch (tag->ti_Tag)
+                               {
+                                       case XMWIN_NewMenu:
+                                               wud->NewMenu = (struct NewMenu *)tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_LayoutArgs:
+                                               wud->LayoutArgs = (ULONG *)tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_GCount:
+                                               wud->GCount = tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_Title:
+                                               wud->Title = STR(tag->ti_Data);
+                                               break;
+
+                                       case XMWIN_WindowFlags:
+                                               wud->Flags |= tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_IDCMPFlags:
+                                               wud->IDCMPFlags |= tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_IDCMPFunc:
+                                               wud->IDCMPFunc = (void (*)()) tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_DropIconFunc:
+                                               wud->DropIcon = (void (*)()) tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_LayoutFunc:
+                                               wud->LayoutArgs = (ULONG *)tag->ti_Data;
+                                               wud->WUDFlags |= WUDF_CUSTOMLAYOUT;
+                                               break;
+
+                                       case XMWIN_PreOpenFunc:
+                                               wud->PreOpenFunc = (LONG (*)()) tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_PostOpenFunc:
+                                               wud->PostOpenFunc = (void (*)()) tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_PreCloseFunc:
+                                               wud->PreCloseFunc = (LONG (*)()) tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_PostCloseFunc:
+                                               wud->PostCloseFunc = (void (*)()) tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_HelpNode:
+                                               wud->HelpNode = (STRPTR) tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_UserData:
+                                               wud->UserData = (APTR) tag->ti_Data;
+                                               break;
+
+                                       case XMWIN_LayoutCleanup:
+                                               wud->LayoutCleanupFunc = (void (*)()) tag->ti_Data;
+                                               break;
+
+                                       default:
+                                               break;
+                               }
+               }
+
+               if (!CreateLayoutInfo (wud))
+               {
+                       DeleteWUD (wud);
+                       wud = NULL;
+               }
+       }
+
+       return wud;
+}
+
+
+
+static void DeleteWUD (struct WinUserData *wud)
+{
+       if (wud)
+       {
+               MyCloseWindow (wud);
+
+               REMOVE ((struct Node *) wud);
+
+               {
+                       struct WDescr *wdescr = &WDescr[wud->WindowID];
+
+                       wdescr->Wud = NULL;
+
+                       if (--wdescr->UseCnt)
+                       {
+                               struct WinUserData *otherwud;
+
+                               /* Search for another wud with same ID and set it in WDescr... */
+
+                               for (otherwud = (struct WinUserData *)WindowList.lh_Head;
+                                               otherwud->Link.mln_Succ;
+                                               otherwud = (struct WinUserData *)otherwud->Link.mln_Succ)
+                                       if (otherwud->WindowID == wud->WindowID)
+                                       {
+                                               wdescr->Wud = otherwud;
+                                               break;
+                                       }
+                       }
+               }
+
+               DeleteLayoutInfo (wud);
+
+               FreePooled (Pool, wud, sizeof (struct WinUserData));
+       }
+}
+
+
+
+static struct WinUserData *CreateLayoutInfo (struct WinUserData *wud)
+{
+       struct WDescr *wdescr = &WDescr[wud->WindowID];
+
+       /* Compute font */
+
+       if (wud->Font = WindowFont)
+               wud->Attr = &WindowAttr;
+       else
+       {
+               wud->Font = TopazFont;
+               wud->Attr = &TopazAttr;
+       }
+
+
+       if (wud->LayoutArgs)
+       {
+               if (!wud->Gadgets)
+               {
+                       /* Allocate Gadgets array */
+                       if (!(wud->Gadgets = CAllocPooled (Pool,
+                               wud->GCount * sizeof (struct Gadget *))))
+                               return NULL;
+               }
+
+               if (wud->WUDFlags & WUDF_CUSTOMLAYOUT)
+               {
+                       if (!(wud->GList = ((struct Gadget * (*)(struct WinUserData *))
+                               wud->LayoutArgs) (wud) ))
+                                       return NULL;
+               }
+               else    /* Use standard layout engine */
+               {
+                       struct LayoutGadgetsArgs lga;
+
+                       lga.Wud = wud;
+
+                       /* Layout Phase 1 */
+
+                       if (!wud->GInfo)
+                       {
+                               struct RastPort rp;
+                               InitRastPort (&rp);
+                               SetFont (&rp, wud->Font);
+
+                               lga.DummyRast   = &rp;
+                               lga.Args                = wud->LayoutArgs;
+                               lga.Count               = 0;
+
+
+                               /* Allocate Key shortcuts array */
+                               if (!(wud->Keys = CAllocPooled (Pool, wud->GCount)))
+                                       return NULL;
+
+                               /* Allocate GInfo array */
+                               if (!(wud->GInfo = lga.GInfo = CAllocPooled (Pool, sizeof (struct GInfo) * wud->GCount)))
+                                       return NULL;
+
+                               LayoutGadgets (&lga, LAYOUTMODE_V);
+
+                               if ((lga.GInfo[0].Flags & (GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT)) != (GIF_FIXEDWIDTH | GIF_FIXEDHEIGHT))
+                               {
+                                       wud->Flags |= (WFLG_SIZEGADGET | WFLG_SIZEBBOTTOM);
+                                       wud->IDCMPFlags |= IDCMP_NEWSIZE;
+                               }
+
+                               /* Force minimum size */
+                               wud->WindowSize.Width = max (wud->WindowSize.Width, lga.GInfo[0].MinWidth + lga.GInfo[0].LabelWidth + HSPACING * 2);
+                               wud->WindowSize.Height = max (wud->WindowSize.Height, lga.GInfo[0].MinHeight + VSPACING * 2);
+                       }
+                       else
+                               /* Reuse previous GInfo array */
+                               lga.GInfo = wud->GInfo;
+
+
+                       /* Layout Phase 2 */
+                       {
+                               ULONG SpecialTags[20];
+
+                               SpecialTags[0] = GT_Underscore;
+                               SpecialTags[1] = (ULONG) '_';
+
+                               if (!CreateContext (&wud->GList))
+                                       return NULL;
+
+                               lga.Args                = wud->LayoutArgs;
+                               lga.VInfo               = VisualInfo;
+                               lga.PrevGad             = wud->GList;
+                               lga.Count               = 0;
+                               lga.SpecialTags = SpecialTags;
+
+                               if (!CreateGadgets (&lga, LAYOUTMODE_V, OffX + lga.GInfo[0].LabelWidth + HSPACING, OffY + VSPACING,
+                                       wud->WindowSize.Width - lga.GInfo[0].LabelWidth - HSPACING * 2,
+                                       wud->WindowSize.Height - VSPACING * 2))
+                                       return NULL;
+                       }
+               }
+       }
+
+       if (!(wdescr->Flags & XMWINF_LOCALEDONE))
+       {
+               if (wud->NewMenu)
+               {
+                       /* Localize the MenuStrip */
+                       struct NewMenu *nm = wud->NewMenu;
+
+                       while (nm->nm_Type != NM_END)
+                       {
+                               if (nm->nm_Label != (STRPTR)NM_BARLABEL)
+                                       nm->nm_Label = STR ((ULONG)nm->nm_Label);
+
+                               nm++;
+                       }
+               }
+
+               wdescr->Flags |= XMWINF_LOCALEDONE;
+       }
+
+
+       if (wud->NewMenu && (!wud->MenuStrip))
+       {
+               if (!(wud->MenuStrip = CreateMenusA (wud->NewMenu, NULL)))
+                       return NULL;
+
+               if (!(LayoutMenus (wud->MenuStrip, VisualInfo,
+                       GTMN_NewLookMenus, TRUE,
+                       TAG_DONE)))
+                       return NULL;
+       }
+
+
+       /* Setup zoom size */
+
+       if (wud->Flags & WFLG_SIZEGADGET)
+       {
+               wud->WindowZoom.Left = 0;
+               wud->WindowZoom.Top = 1;
+               wud->WindowZoom.Width = Scr->Width;
+               wud->WindowZoom.Height = Scr->Height;
+       }
+       else
+       {
+               wud->WindowZoom.Left = wud->WindowSize.Left;
+               wud->WindowZoom.Top = wud->WindowSize.Top;
+               if (wud->Title)
+                       wud->WindowZoom.Width = TextLength (&Scr->RastPort, wud->Title,
+                               strlen (wud->Title)) + 80;
+               else
+                       wud->WindowZoom.Width = 80;
+               wud->WindowZoom.Height = Scr->WBorTop + Scr->RastPort.TxHeight + 1;
+
+               /* TODO: Under V39 specifying ~0,~0 as Zoom X,Y, intuition
+                * will only size-zoom the window.  Consider implementing it...
+                */
+       }
+
+       return wud;
+}
+
+
+
+GLOBALCALL void DeleteLayoutInfo (struct WinUserData *wud)
+{
+       if (!wud) return;
+
+       DeleteGadgets (wud);
+
+       if (wud->LayoutCleanupFunc)
+               wud->LayoutCleanupFunc (wud);
+
+       if (wud->Keys)
+       {
+               FreePooled (Pool, wud->Keys, wud->GCount);
+               wud->Keys = NULL;
+       }
+
+       if (wud->Gadgets)
+       {
+               if (wud->GInfo)
+               {
+                       ULONG i;
+
+                       /* Free gadget associated resources */
+
+                       for (i = 0; i < wud->GCount; i++)
+                       {
+                               if (wud->Gadgets[i])
+                               {
+                                       switch (wud->GInfo[i].GKind)
+                                       {
+                                               case MX_KIND:
+                                               case CYCLE_KIND:
+                                                       FreeVecPooled (Pool, wud->GInfo[i].SpecialStorage);
+
+                                               default:
+                                                       break;
+                                       }
+                               }
+                       }
+               }
+
+               FreePooled (Pool, wud->Gadgets, wud->GCount * sizeof (struct Gadget *));
+               wud->Gadgets = NULL;
+       }
+
+       if (wud->GInfo)
+       {
+               FreePooled (Pool, wud->GInfo, wud->GCount * sizeof (struct GInfo));
+               wud->GInfo = NULL;
+       }
+
+       if (wud->MenuStrip)
+       {
+               FreeMenus (wud->MenuStrip);
+               wud->MenuStrip = NULL;
+       }
+}
+
+
+
+GLOBALCALL struct Window *NewWindow (ULONG id)
+
+/* Create a new window using the given Window ID. */
+{
+       struct WDescr           *wdescr         = &WDescr[id];
+       struct WinUserData      *wud;
+
+       if (!Scr) return NULL;
+
+       /* Some windows can not be opened multiple times */
+
+       if (!(wud = wdescr->Wud) || (wdescr->Flags & XMWINF_OPENMULTI))
+       {
+               if (!(wud = CreateWUD (id)))
+                       return NULL;
+       }
+
+       return MyOpenWindow (wud);
+}
+
+
+
+GLOBALCALL struct Window *MyOpenWindow (struct WinUserData *wud)
+
+/* Open & setup a window using the given WinUserData structure. */
+/* This function is now OBSOLETE and should be merged with NewWindow() */
+{
+       if (!Scr) return NULL;
+
+       if (wud->PreOpenFunc)
+               if (wud->PreOpenFunc (wud))
+               {
+                       if (wud->PreCloseFunc) wud->PreCloseFunc (wud);
+                       return NULL;
+               }
+
+       if (wud->Win)
+       {
+               RevealWindow (wud);
+               return wud->Win;
+       }
+
+       if (!wud->Gadgets && wud->LayoutArgs)
+               if (!CreateLayoutInfo (wud))
+                       return NULL;
+
+
+       /* Open the Window */
+
+       if (wud->Win = OpenWindowTags (NULL,
+               WA_Left,                        wud->WindowSize.Left,
+               WA_Top,                         wud->WindowSize.Top,
+               WA_InnerWidth,          wud->WindowSize.Width,
+               WA_InnerHeight,         wud->WindowSize.Height,
+               WA_Gadgets,                     wud->GList,
+               WA_Title,                       wud->Title,
+               WA_Flags,                       wud->Flags,
+               WA_Zoom,                        &(wud->WindowZoom),
+               WA_PubScreen,           Scr,
+               WA_HelpGroup,           UniqueID,
+               /* Set user preferred refresh method unless this window
+                * requests simple refresh explicitly.
+                */
+               GuiSwitches.SmartRefresh ? WA_SmartRefresh : WA_SimpleRefresh, TRUE,
+               WA_Activate,            !ScreenReopening,
+
+               /* Set minimum and maximum size according to the main gadget group
+                * properties.
+                */
+               (wud->Flags & WFLG_SIZEGADGET) ? WA_MinWidth : TAG_IGNORE,
+                       wud->GInfo ? (wud->GInfo[0].MinWidth + Scr->WBorLeft + Scr->WBorRight) : wud->WindowSize.Width,
+               (wud->Flags & WFLG_SIZEGADGET) ? WA_MinHeight : TAG_IGNORE,
+                       wud->GInfo ? (wud->GInfo[0].MinHeight + Scr->WBorTop + Scr->WBorBottom + Scr->Font->ta_YSize + SizeHeight + 1) : wud->WindowSize.Height,
+/**/   (wud->Flags & WFLG_SIZEGADGET) ? WA_MaxWidth : TAG_IGNORE,
+                       wud->GInfo ? (wud->GInfo[0].Flags & GIF_FIXEDWIDTH ? 0 : -1) : -1,
+               (wud->Flags & WFLG_SIZEGADGET) ? WA_MaxHeight : TAG_IGNORE,
+                       wud->GInfo ? (wud->GInfo[0].Flags & GIF_FIXEDHEIGHT ? wud->GInfo[0].MinHeight : -1) : -1,
+
+               /* Constant tags */
+               WA_ScreenTitle,         (ULONG)Version+6,
+               WA_AutoAdjust,          TRUE,
+               WA_MenuHelp,            TRUE,
+       //      WA_MouseQueue,          2,
+       //      WA_RptQueue,            2,
+               WA_NewLookMenus,        TRUE,
+
+               /* Add user tags */
+               WDescr[wud->WindowID].CreationTags ? TAG_MORE : TAG_DONE, WDescr[wud->WindowID].CreationTags
+               ))
+       {
+               wud->Win->UserData = (BYTE *) wud;
+               wud->Win->UserPort = WinPort;
+
+               if (ModifyIDCMP (wud->Win, wud->IDCMPFlags))
+               {
+                       /* Set default font for this window */
+                       if (wud->Font) SetFont (wud->Win->RPort, wud->Font);
+
+                       if (wud->MenuStrip) SetMenuStrip (wud->Win, wud->MenuStrip);
+
+                       /* Do initial refresh */
+                       GT_RefreshWindow (wud->Win, NULL);
+                       RenderWindowBorders (wud);
+
+                       /* Make the window visible on the screen */
+                       if (!ScreenReopening
+#ifndef OS30_ONLY
+                               && (IntuitionBase->LibNode.lib_Version >= 39)
+#endif /* !OS30_ONLY */
+                               )
+                           ScreenPosition (Scr, SPOS_MAKEVISIBLE,
+                                       wud->Win->LeftEdge, wud->Win->TopEdge,
+                                       wud->Win->LeftEdge + wud->Win->Width - 1,
+                                       wud->Win->TopEdge + wud->Win->Height - 1);
+
+                       /* Make it an AppWindow if it is requested */
+                       if (wud->DropIcon) AddAppWin (wud);
+
+                       if (wud->PostOpenFunc)
+                               wud->PostOpenFunc (wud);
+
+                       wud->WUDFlags &= ~WUDF_REOPENME;
+
+                       return wud->Win;
+               }
+
+               /* ClearMenuStrip() -- no need to call it... */
+               CloseWindow (wud->Win); wud->Win = NULL;
+       }
+
+       return NULL;
+}
+
+
+
+/* Close a window and all related resources */
+GLOBALCALL void MyCloseWindow (struct WinUserData *wud)
+{
+       struct Window *win = wud->Win;
+       struct Requester *req;
+
+       if (win)
+       {
+               DeselectButton();
+
+               if (wud->PreCloseFunc)
+                       if (wud->PreCloseFunc (wud))
+                               return;
+
+               /* Cleanup locked window */
+               if (req = win->FirstRequest)
+               {
+                       EndRequest (req, win);
+                       FreePooled (Pool, req, sizeof (struct WindowLock));
+               }
+
+               /* Remove AppWindow */
+               if (wud->AppWin) RemAppWin (wud);
+
+
+               /* Free MenuStrip */
+               if (win->MenuStrip)
+               {
+                       ClearMenuStrip (win);
+                       DoNextSelect = 0;       /* Do not loop any more on this window's MenuStrip */
+               }
+
+               /* Now remove any pending message from the shared IDCMP port */
+
+               Forbid();
+               {
+                       struct Node *succ;
+                       struct Message *msg = (struct Message *) win->UserPort->mp_MsgList.lh_Head;
+
+                       while (succ = msg->mn_Node.ln_Succ)
+                       {
+                               if (((struct IntuiMessage *)msg)->IDCMPWindow ==  win)
+                               {
+                                       REMOVE ((struct Node *)msg);
+                                       ReplyMsg (msg);
+                               }
+                               msg = (struct Message *) succ;
+                       }
+
+                       win->UserPort = NULL;   /* Keep intuition from freeing our port... */
+                       ModifyIDCMP (win, 0L);  /* ...and from sending us any more messages. */
+               }
+               Permit();
+
+
+               /* Save Window position and clear window pointer */
+
+               wud->WindowSize.Left    = win->LeftEdge;
+               wud->WindowSize.Top             = win->TopEdge;
+
+               if (win->Flags & WFLG_SIZEGADGET)
+               {
+                       wud->WindowSize.Width   = win->Width - win->BorderLeft - win->BorderRight;
+                       wud->WindowSize.Height  = win->Height - win->BorderTop - win->BorderBottom;
+               }
+
+               CloseWindow (win);      wud->Win = NULL;
+
+               if (wud->PostCloseFunc)
+                       wud->PostCloseFunc (wud);
+       }
+
+       if (wud->Font)
+               { CloseFont (wud->Font); wud->Font = NULL; }
+
+       if ((WDescr[wud->WindowID].Flags & XMWINF_OPENMULTI)
+               && (WDescr[wud->WindowID].UseCnt > 1))
+               DeleteWUD (wud);
+}
+
+
+
+GLOBALCALL void ReopenWindows (void)
+
+/* Reopen windows that were previously open */
+{
+       struct WinUserData *wud;
+
+       ScreenReopening = TRUE;
+
+       /* Find window */
+       for (wud = (struct WinUserData *)WindowList.lh_Head; wud->Link.mln_Succ;
+               wud = (struct WinUserData *)wud->Link.mln_Succ)
+       {
+               if (wud->WUDFlags & WUDF_REOPENME)
+                       MyOpenWindow (wud);
+       }
+
+       ScreenReopening = FALSE;
+}
+
+
+
+GLOBALCALL LONG SetupScreen (void)
+{
+       struct Screen *DefScr;
+       struct DrawInfo *DefDri;
+
+       static LONG ExtraScreenTags[] =
+       {
+               SA_SysFont,                     1,
+               SA_FullPalette,         TRUE,
+               SA_SharePens,           TRUE,   /* These three Tags are valid only under
+               SA_LikeWorkbench,       TRUE,    * V39 and are properly ignored by V37.
+               SA_MinimizeISG,         TRUE,    */
+               SA_Interleaved,         TRUE,
+               TAG_DONE
+       };
+
+       if (Scr)
+       {
+               /* If screen is already open, pop it to front and activate
+                * the main window.
+                */
+               ScreenToFront (Scr);
+               if (ThisTask->pr_WindowPtr)
+                       RevealWindow ((struct WinUserData *)(((struct Window *)(ThisTask->pr_WindowPtr))->UserData));
+
+               return RETURN_OK;
+       }
+
+
+       if (!(ScrollButtonClass = InitScrollButtonClass()))
+               return ERROR_NO_FREE_STORE;
+
+
+       /* Try the user selected Public Screen */
+
+       if (!(Scr = LockPubScreen (ScrInfo.PubScreenName[0] ? ScrInfo.PubScreenName : NULL)))
+       {
+               /* Try to open own screen */
+
+               if (ScrInfo.DisplayID)
+               {
+                       static UWORD             PensArray[1] = {(UWORD)~0};
+                       ULONG                           *ColorTable     = NULL;
+#ifndef OS30_ONLY
+                       struct ColorSpec        *ColorSpec      = NULL;
+#endif /* !OS30_ONLY */
+                       ULONG                            i;
+
+                       /* Color map translation */
+
+                       if (ScrInfo.OwnPalette)
+                       {
+#ifndef OS30_ONLY
+                               if (IntuitionBase->LibNode.lib_Version >= 39)
+                               {
+#endif /* !OS30_ONLY */
+                                       if (ColorTable = AllocPooled (Pool, ((32 * 3) + 2) * sizeof (LONG)))
+                                       {
+                                               ULONG   *col = ColorTable,
+                                                                tmp;
+
+                                               *col++ = 32 << 16;
+
+                                               for (i = 0; i < 32; i++)
+                                               {
+                                                       tmp = (ScrInfo.Colors[i] >> 16);                        /* Red */
+                                                       tmp |= tmp << 8 | tmp << 16 | tmp << 24;
+                                                       *col++ = tmp;
+
+                                                       tmp = (ScrInfo.Colors[i] >> 8) & 0xFF;          /* Green */
+                                                       tmp |= tmp << 8 | tmp << 16 | tmp << 24;
+                                                       *col++ = tmp;
+
+                                                       tmp = ScrInfo.Colors[i] & 0xFF;                         /* Blue */
+                                                       tmp |= tmp << 8 | tmp << 16 | tmp << 24;
+                                                       *col++ = tmp;
+                                               }
+
+                                               *col = 0;
+                                       }
+                               }
+#ifndef OS30_ONLY
+                               else    /* V37 */
+                               {
+                                       if (ColorSpec = AllocPooled (Pool, 33 * sizeof (struct ColorSpec)))
+                                       {
+                                               for (i = 0; i < 32; i++)
+                                               {
+                                                       ColorSpec[i].ColorIndex = i;
+                                                       ColorSpec[i].Red        = ScrInfo.Colors[i] >> 20;
+                                                       ColorSpec[i].Green      = ScrInfo.Colors[i] >> 12 & 0xF;
+                                                       ColorSpec[i].Blue       = ScrInfo.Colors[i] >>  4 & 0xF;
+                                               }
+
+                                               ColorSpec[i].ColorIndex = -1;
+                                       }
+                               }
+                       }
+#endif /* !OS30_ONLY */
+
+
+                       /* Use user requested attributes for screen */
+
+                       Scr = OpenScreenTags (NULL,
+                               SA_Width,                       ScrInfo.Width,
+                               SA_Height,                      ScrInfo.Height,
+                               SA_Depth,                       ScrInfo.Depth,
+                               SA_DisplayID,           ScrInfo.DisplayID,
+                               SA_Overscan,            ScrInfo.OverscanType,
+                               SA_AutoScroll,          ScrInfo.AutoScroll,
+                               SA_Title,                       ScrInfo.PubScreenName,
+                               SA_PubName,                     ScrInfo.PubScreenName,
+                               SA_Font,                        ScreenAttr.ta_Name ? &ScreenAttr : NULL,
+                               SA_Pens,                        PensArray,
+                               ScrInfo.OwnPalette ?
+#ifndef OS30_ONLY
+                                       ((IntuitionBase->LibNode.lib_Version >= 39) ?
+                                               SA_Colors32 : SA_Colors) : TAG_IGNORE,
+                                       (IntuitionBase->LibNode.lib_Version >= 39) ?
+                                               (ULONG)ColorTable : (ULONG)ColorSpec,
+#else
+                                       SA_Colors32 : TAG_IGNORE,       ColorTable,
+#endif /* !OS30_ONLY */
+
+                               TAG_MORE,                       ExtraScreenTags);
+
+                       if (ColorTable) FreePooled (Pool, ColorTable, ((32 * 3) + 2) * sizeof (LONG));
+#ifndef OS30_ONLY
+                       if (ColorSpec)  FreePooled (Pool, ColorSpec, 33 * sizeof (struct ColorSpec));
+#endif /* !OS30_ONLY */
+                       if (Scr) OwnScreen = TRUE;
+               }
+
+               if (!Scr)
+               {
+                       /* Try to clone the Default (Workbench) Screen */
+
+                       if (!(DefScr = LockPubScreen (NULL)))
+                       {
+                               CloseDownScreen();
+                               return ERROR_OBJECT_NOT_FOUND;
+                       }
+
+                       DefDri = GetScreenDrawInfo (DefScr);
+
+                       if (Scr = OpenScreenTags (NULL,
+                               SA_Depth,                       DefDri->dri_Depth,
+                               SA_DisplayID,           GetVPModeID (&DefScr->ViewPort),
+                               SA_Overscan,            OSCAN_TEXT,
+                               SA_AutoScroll,          TRUE,
+                               SA_Title,                       ScrInfo.PubScreenName,
+                               SA_PubName,                     ScrInfo.PubScreenName,
+                               SA_Pens,                        DefDri->dri_Pens,
+                               TAG_MORE,                       ExtraScreenTags))
+                       {
+                               UnlockPubScreen (NULL, DefScr);
+                               OwnScreen = TRUE;
+                       }
+                       else
+                               Scr = DefScr;
+
+                       FreeScreenDrawInfo (DefScr, DefDri);
+               }
+
+               /* Make our screen really public */
+               if (OwnScreen) PubScreenStatus (Scr, 0);
+       }
+
+       if (!(VisualInfo = GetVisualInfoA (Scr, NULL)))
+       {
+               CloseDownScreen();
+               return ERROR_NO_FREE_STORE;
+       }
+
+       DrawInfo = GetScreenDrawInfo (Scr);
+
+       OffX = Scr->WBorLeft;
+       OffY = Scr->RastPort.TxHeight + Scr->WBorTop + 1;
+
+       /* Setup fonts */
+       if (!WindowAttr.ta_Name)        CopyTextAttrPooled (Pool, Scr->Font, &WindowAttr);
+       if (!ListAttr.ta_Name)          CopyTextAttrPooled (Pool, Scr->Font, &ListAttr);
+       if (!EditorAttr.ta_Name)        CopyTextAttrPooled (Pool, &TopazAttr, &EditorAttr);
+
+       if (!(TopazFont = OpenFont (&TopazAttr)))
+       {
+               CantOpenLib (TopazAttr.ta_Name, 0);
+               CloseDownScreen();
+               return ERROR_NO_FREE_STORE;
+       }
+
+       if (DiskfontBase)
+       {
+               if (!(WindowFont = OpenDiskFont (&WindowAttr)))
+                       CantOpenLib (WindowAttr.ta_Name, 0);
+               if (!(ListFont = OpenDiskFont (&WindowAttr)))
+                       CantOpenLib (ListAttr.ta_Name, 0);
+       }
+
+       /* Setup windows shared Message Port */
+       if (!(WinPort = CreateMsgPort()))
+       {
+               CloseDownScreen();
+               return ERROR_NO_FREE_STORE;
+       }
+       IDCMPSig = 1 << WinPort->mp_SigBit;
+       Signals |= IDCMPSig;
+
+
+       /* Create a SIZEIMAGE to get the correct size
+        * and position for the slider and arrow buttons.
+        */
+       {
+               struct Image *sizeimage;
+
+               if (sizeimage = NewImageObject (SIZEIMAGE))
+               {
+                       SizeWidth       = sizeimage->Width;
+                       SizeHeight      = sizeimage->Height;
+                       DisposeObject (sizeimage);
+               }
+       }
+
+
+       if (!NewWindow (WID_TOOLBOX))
+       {
+               CloseDownScreen();
+               return ERROR_NO_FREE_STORE;
+       }
+
+       /* Set Process Window pointer for DOS requesters */
+       OldPrWindowPtr = ThisTask->pr_WindowPtr;
+       ThisTask->pr_WindowPtr = WDescr[WID_TOOLBOX].Wud->Win;
+
+       ReopenWindows();
+
+       /* Bring screen to front in case it was hidden */
+       ScreenToFront (Scr);
+
+       return RETURN_OK;
+}
+
+
+
+GLOBALCALL void CloseDownScreen (void)
+
+/* Free screen and all associated resources */
+{
+       struct WinUserData *wud;
+
+       if (!Scr) return;
+
+       ScreenShutdown = TRUE;
+
+       /* Close AmigaGuide help window */
+       CleanupHelp();
+
+
+       if (Quit)
+       {
+               /* Destroy all windows */
+               while (!IsListEmpty (&WindowList))
+                       DeleteWUD ((struct WinUserData *)WindowList.lh_Head);
+       }
+       else
+       {
+               /* Close all windows */
+               for (wud = (struct WinUserData *)WindowList.lh_Head; wud->Link.mln_Succ;
+                               wud = (struct WinUserData *)wud->Link.mln_Succ)
+               {
+                       MyCloseWindow (wud);
+                       DeleteLayoutInfo (wud);
+                       wud->WUDFlags |= WUDF_REOPENME;
+               }
+       }
+
+
+       if (WinPort)
+       {
+               Signals &= ~IDCMPSig;
+               IDCMPSig = 0;
+               DeleteMsgPort (WinPort); WinPort = NULL;
+       }
+
+       FreeScreenDrawInfo (Scr, DrawInfo);     DrawInfo = NULL;
+       FreeVisualInfo (VisualInfo);            VisualInfo = NULL;
+
+       if (OwnScreen)
+       {
+               /* TODO: Use BuildEasyRequest()/SysReqHandler() with SA_PubSig like IPrefs */
+
+               while (!CloseScreen (Scr))
+                       ShowRequestArgs (MSG_CLOSE_ALL_WINDOWS, MSG_CONTINUE, NULL);
+       }
+       else UnlockPubScreen (NULL, Scr);
+
+       if (OldPrWindowPtr != (struct Window *)1L)
+       {
+               ThisTask->pr_WindowPtr = OldPrWindowPtr;
+               OldPrWindowPtr = (struct Window *)1L;
+       }
+
+       if (WindowFont)
+       {
+               CloseFont (WindowFont);
+               WindowFont = NULL;
+       }
+
+       if (ListFont)
+       {
+               CloseFont (ListFont);
+               ListFont = NULL;
+       }
+
+       if (TopazFont)
+       {
+               CloseFont (TopazFont);
+               TopazFont = NULL;
+       }
+
+       if (ScrollButtonClass)
+       {
+               FreeScrollButtonClass (ScrollButtonClass);
+               ScrollButtonClass = NULL;
+       }
+
+       Scr = NULL;
+       OwnScreen = FALSE;
+       ScreenShutdown = FALSE;
+}
diff --git a/Gui.h b/Gui.h
new file mode 100644 (file)
index 0000000..682e599
--- /dev/null
+++ b/Gui.h
@@ -0,0 +1,562 @@
+/*
+**     Gui.h
+**
+**     Copyright (C) 1993,94,95,96,98 by Bernardo Innocenti
+**
+**     Various definitions for the user interface.
+*/
+
+#ifndef INTUITION_INTUITION_H
+#include <intuition/intuition.h>
+#endif
+
+#ifndef XMODULE_PRIV_H
+#include <XModulePriv.h>
+#endif
+
+
+/***************/
+/* WinUserData */
+/***************/
+
+/* This structure holds data needed to handle windows
+ * in a nice Object oriented way (no more endless switches :-)
+ */
+struct WinUserData
+{
+       struct MinNode           Link;  /* Used to mantain a list of all open windows. */
+
+       struct Window           *Win;
+       ULONG                            GCount;
+       struct Gadget           **Gadgets;
+       struct GInfo            *GInfo;
+       UBYTE                           *Keys;
+       struct TextAttr         *Attr;
+       struct TextFont         *Font;
+       struct NewMenu          *NewMenu;
+       struct Menu                     *MenuStrip;
+       struct Gadget           *GList;
+       ULONG                           *LayoutArgs;
+       struct WindowBorder     *Borders;
+       struct AppWindow        *AppWin;
+       ULONG                            Flags;
+       ULONG                            IDCMPFlags;
+       STRPTR                           Title;
+
+       ULONG                            WindowID;
+       struct IBox                      WindowSize;
+       struct IBox                      WindowZoom;
+       LONG                             WUDFlags;
+       APTR                             UserData;
+       STRPTR                           HelpNode;
+
+       /* Window specific user functions */
+       LONG (*PreOpenFunc)(struct WinUserData *);
+       void (*PostOpenFunc)(struct WinUserData *);
+       LONG (*PreCloseFunc)(struct WinUserData *);
+       void (*PostCloseFunc)(struct WinUserData *);
+       void (*IDCMPFunc)(struct WinUserData *);
+       void (*DropIcon)(struct AppMessage *);
+       void (*LayoutCleanupFunc)(struct WinUserData *);
+};
+
+
+/* Flags for WinUserData->WUDFlags */
+
+#define WUDF_REOPENME          (1<<0)  /* Open this window again later                         */
+#define WUDF_LOCALEDONE                (1<<1)  /* Set when menustrip has been localized        */
+#define WUDF_JUSTRESIZED       (1<<3)  /* Set just after an IDCMP_NEWSIZE                      */
+
+#define WUDF_CUSTOMLAYOUT      (1<<2)
+/* Do not use the standard layout routines for
+ * this window.  If not NULL, wud->LayoutArgs must point to
+ * a user function which should create and position the gadgets.
+ *
+ *     struct Gadget *CustomLayoutFunc (struct WinUserData *wud);
+ */
+
+
+
+struct WDescr
+{
+       struct WinUserData      *Wud;                   /* Pointer to WinUserData                               */
+       UWORD                            UseCnt;                /* Number of WUDs of this kind                  */
+       UWORD                            Flags;                 /* Some flags, see below                                */
+       struct TagItem          *CreationTags;  /* See <gui.h> for possible tags                */
+};
+
+
+
+/* Flags for WDescr->Flags */
+#define XMWINF_OPENMULTI       (1<<0)  /* Allow opening multiple times                         */
+#define XMWINF_LOCALEDONE      (1<<1)  /* Set when menustrip has been localized        */
+
+
+/* Window IDs */
+
+enum
+{
+       WID_TOOLBOX,
+       WID_PREFS,
+       WID_SONGINFO,
+       WID_INSTRUMENTS,
+       WID_SEQUENCE,
+       WID_PATTERN,
+       WID_PLAY,
+       WID_SAMPLE,
+       WID_OPTIMIZATION,
+       WID_SAVERS,
+       WID_PATTPREFS,
+       WID_PATTSIZE,
+       WID_CLEAR,
+       WID_LOG,
+       WID_PROGRESS,
+
+       WID_COUNT
+};
+
+
+
+/* Tags for MyOpenWindow() */
+#define XMWIN_Dummy                    (TAG_USER + 'WN' << 8)
+#define XMWIN_NewMenu          (XMWIN_Dummy + 1)       /* Pointer to NewMenu array                     */
+#define XMWIN_LayoutArgs       (XMWIN_Dummy + 2)       /* Pointer to layout args array         */
+#define XMWIN_GCount           (XMWIN_Dummy + 3)       /* Number of gadgets in window          */
+#define XMWIN_Title                    (XMWIN_Dummy + 4)       /* Window title                                         */
+#define XMWIN_WindowFlags      (XMWIN_Dummy + 5)       /* Window flags for WA_Flags            */
+#define XMWIN_IDCMPFlags       (XMWIN_Dummy + 6)       /* Flags for WA_IDCMPFlags                      */
+#define XMWIN_IDCMPFunc                (XMWIN_Dummy + 7)       /* Custom IDCMP handler                         */
+#define XMWIN_DropIconFunc     (XMWIN_Dummy + 8)       /* AppWindow handler                            */
+#define XMWIN_LayoutFunc       (XMWIN_Dummy + 9)       /* Your custom layout function          */
+#define XMWIN_PreOpenFunc      (XMWIN_Dummy + 10)      /* Called just before opening win       */
+#define XMWIN_PostOpenFunc     (XMWIN_Dummy + 11)      /* Called just after opening win        */
+#define XMWIN_PreCloseFunc     (XMWIN_Dummy + 12)      /* Called just before closing win       */
+#define XMWIN_PostCloseFunc    (XMWIN_Dummy + 13)      /* Called just after closing win        */
+#define XMWIN_HelpNode         (XMWIN_Dummy + 14)      /* Name of AmigaGuide node for help     */
+#define XMWIN_UserData         (XMWIN_Dummy + 15)      /* Whatever you want...                         */
+#define XMWIN_LayoutCleanup    (XMWIN_Dummy + 16)      /* Cleanup for custom layout func       */
+
+/* You can also use standard OpenWindow() tags */
+
+
+
+/* This structure is used by LockWindows() to
+ * track modifications made to the windows.
+ */
+struct WindowLock
+{
+       struct Requester        Req;
+       ULONG                           OldIDCMPFlags;
+       UWORD                           OldMinWidth,
+                                               OldMinHeight,
+                                               OldMaxWidth,
+                                               OldMaxHeight;
+};
+
+
+
+/****************/
+/* WindowBorder */
+/****************/
+
+/* A linked list of these structures is attached to all
+ * WUDs to keep track of the bevel boxes inside the window.
+ */
+struct WindowBorder
+{
+       struct WindowBorder     *NextBorder;
+       ULONG                            Type;
+       struct IBox                      Size;
+};
+
+
+
+/*********/
+/* GInfo */
+/*********/
+
+/* Each WUD gets an array of GInfo structures to store
+ * some precalculated layout data. Each gadget and group
+ * has a GInfo structure in the array.
+ */
+struct GInfo
+{
+       WORD    GKind;                                  /* Object kind (#?_KIND)                */
+       UWORD   MinWidth, MinHeight;    /* Minimum size                                 */
+       UWORD   LabelWidth;                             /* Label width for this object  */
+       UWORD   Flags;                                  /* See below                                    */
+       UWORD   Dummy;                                  /* Keep structure long aligned  */
+       union {
+               APTR    SpecialStorage;         /* For gadget objects                   */
+               struct {
+                       UWORD Width, Height;    /* For group objects                    */
+               } Fixed;
+       };
+};
+
+/* Flags for GInfo->Flags */
+#define GIF_FIXEDWIDTH (1<<0)
+#define GIF_FIXEDHEIGHT        (1<<1)
+#define GIF_HASBORDER  (1<<4)
+
+
+
+/*********************/
+/* LayoutGadgetsArgs */
+/*********************/
+
+/* LayoutGadgetsArgs is used to pass arguments to LayoutGadgets()
+ * and CreateGadgets().
+ */
+struct LayoutGadgetsArgs
+{
+       ULONG                           *Args;                  /* Arguments array                                                      */
+       ULONG                            Count;                 /* GInfo array counter                                          */
+       struct GInfo            *GInfo;                 /* GInfo array                                                          */
+       struct WinUserData      *Wud;                   /* The window we are working for                        */
+       struct RastPort         *DummyRast;             /* RastPort initialzied for TextLength()        */
+       void                            *VInfo;                 /* VisualInfo for GadTools gadgets                      */
+       struct Gadget           *PrevGad;               /* Used to link gadgets together                        */
+       ULONG                           *SpecialTags;   /* Storage for additional tags for gadgets      */
+};
+
+
+/* Layout modes for LayoutGadgets and CreateGadgets() */
+#define LAYOUTMODE_V   0
+#define LAYOUTMODE_H   1
+
+#define VSPACING               1               /* Vertical spacing between GUI elements        */
+#define HSPACING               2               /* Horizontal spacing between GUI elements      */
+#define LABELSPACING   8               /* Extra horizontal spacing for labels          */
+
+
+
+/***********/
+/* ScrInfo */
+/***********/
+
+struct ScrInfo
+{
+    ULONG      DisplayID;              /* Display mode ID                              */
+    ULONG      Width;                  /* Width of display in pixels   */
+    ULONG      Height;                 /* Height of display in pixels  */
+    UWORD      Depth;                  /* Number of bit-planes                 */
+    UWORD      OverscanType;   /* Type of overscan of display  */
+    BOOL       AutoScroll;             /* Display should auto-scroll?  */
+       BOOL    OwnPalette;
+       UWORD   Colors[32];
+       UBYTE   PubScreenName[32];
+};
+
+
+
+/*******************/
+/* File requesters */
+/*******************/
+
+/* This structure is used to reference all the file requesters.
+ */
+struct XMFileReq
+{
+       APTR    FReq;           /* Real file requester (ASL or ReqTools)                                */
+       ULONG   Title;          /* Message number for title                                                             */
+       ULONG   Flags;          /* FRF_DOSAVEMODE, FRF_DOPATTERNS, FRF_DOMULTISELECT... */
+};
+
+enum
+{
+       FREQ_LOADMOD,
+       FREQ_SAVEMOD,
+       FREQ_LOADINST,
+       FREQ_SAVEINST,
+       FREQ_LOADPATT,
+       FREQ_SAVEPATT,
+       FREQ_LOADMISC,
+       FREQ_SAVEMISC,
+
+       FREQ_COUNT
+};
+
+
+
+/************/
+/* Switches */
+/************/
+
+
+struct ClearSwitches
+{
+       BOOL    ClearPatt,
+                       ClearSeq,
+                       ClearInstr;
+};
+
+struct SaveSwitches
+{
+       BOOL    SavePatt,
+                       SaveSeq,
+                       SaveInstr,
+                       SaveNames,
+                       SaveIcons;
+};
+
+struct OptSwitches
+{
+       BOOL    RemPatts,
+                       RemDupPatts,
+                       RemInstr,
+                       RemDupInstr,
+                       CutAfterLoop,
+                       CutZeroTail,
+                       CutPatts,
+                       RemapInstr;
+};
+
+struct GuiSwitches
+{
+       BOOL    SaveIcons,
+                       AskOverwrite,
+                       AskExit,
+                       ShowAppIcon,
+                       UseReqTools,
+                       SmartRefresh,
+                       UseDataTypes,
+                       InstrSaveIcons,
+                       AskAutosave,
+                       DoBackups,
+                       LogToFile;
+       UWORD   InstrSaveMode,
+                       SampDrawMode,
+                       LogLevel,
+                       AutosaveTime,
+                       BackupVersions;
+       UBYTE   BackupTemplate[64];
+       UBYTE   LogFile[128];
+};
+
+struct PattSwitches
+{
+       WORD    AdvanceTracks,  AdvanceLines;
+       ULONG   MaxUndoLevels,  MaxUndoMem;
+       ULONG   Flags;                                                  /* See <patteditclass.h> for possible flags */
+       WORD    VScrollerPlace, HScrollerPlace;
+       UBYTE   ClipboardUnit,  Pad0;
+       UWORD   TrackChars, Backdrop;
+       ULONG   BGPen, TextPen, LinesPen, TinyLinesPen;
+};
+
+
+
+/* Possible values for (H|V)ScrollerPlace */
+#define SCROLLERPLACE_NONE             0
+#define SCROLLERPLACE_RIGHT            1
+#define SCROLLERPLACE_BOTTOM   1
+#define SCROLLERPLACE_LEFT             2
+
+
+
+/* Instrument save modes */
+enum {
+       INST_8SVX,
+       INST_8SVX_FIB,
+       INST_RAW,
+       INST_XPK
+};
+
+
+/* Handy macros to get a gadget string/number */
+
+#define GetString(g)      (((struct StringInfo *)g->SpecialInfo)->Buffer)
+#define GetNumber(g)      (((struct StringInfo *)g->SpecialInfo)->LongInt)
+
+
+/* Some handy definitions missing in <devices/inputevent.h> */
+
+#define IEQUALIFIER_SHIFT      (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)
+#define IEQUALIFIER_ALT                (IEQUALIFIER_LALT | IEQUALIFIER_RALT)
+#define IEQUALIFIER_COMMAND    (IEQUALIFIER_LCOMMAND | IEQUALIFIER_RCOMMAND)
+
+
+/* Any break flag */
+
+#define SIGBREAKFLAGS (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D | SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F)
+
+
+/**************************/
+/* XModule custom gadgets */
+/**************************/
+
+
+#define ENDGROUP_KIND  -1      /* End of group                                         */
+#define VGROUP_KIND            -2      /* Vertical group                                       */
+#define HGROUP_KIND            -3      /* Horizontal group                                     */
+#define IMAGEBUTTON_KIND 20 /* button with vector image label  */
+#define BOOPSI_KIND             21     /* BOOPSI gadget                                        */
+
+
+/* ti_Data is a pointer to a setup function for custom gadgets.
+ * It receives the newly allocated Gadget structure and should customize
+ * all fields it is interested in, as well as doing all the allocations
+ * and precalculations which are needed for the custom gadget.
+ *
+ * When the XMGAD_BOOPSI tag is specified, SetupFunc() receves the
+ * tag array pointer and the NewGadget structure associated with the
+ * gadget.  SetupFunc() should then return a pointer to the BOOPSI
+ * Gadget it has allocated.
+ */
+#define XMGAD_SetupFunc        (TAG_USER+1)
+
+
+/* If ti_Data is TRUE, the layout routines will let the SetupFunc()
+ * allocate a BOOPSI gadget itself, So the gadget is _not_ allocated with
+ * the GadTools function CreateGadget(). When the window is closed with
+ * MyCloseWindow(), or when MyOpenWindow() fails after creating the object,
+ * it is your duty to DisposeObject() on these gadgets.
+ */
+//#define XMGAD_BOOPSI (TAG_USER+2)
+
+
+
+/********************/
+/* External symbols */
+/********************/
+
+XREF struct Screen             *Scr;
+XREF struct DrawInfo   *DrawInfo;
+XREF APTR                               VisualInfo;
+XREF struct ScrInfo             ScrInfo;
+XREF struct WDescr              WDescr[];
+XREF CxObj                             *MyBroker;
+XREF UWORD __chip              *BlockPointer;  /* for OS2.0 only */
+XREF Class                             *ScrollButtonClass;
+XREF UWORD                              OffX, OffY;
+XREF WORD                               SizeWidth, SizeHeight;
+XREF ULONG                              UniqueID;
+
+XREF struct IntuiMessage IntuiMsg;
+
+XREF struct MsgPort
+       *PubPort,
+       *CxPort;
+
+XREF struct TextAttr
+       TopazAttr,
+       ScreenAttr,
+       WindowAttr,
+       ListAttr,
+       EditorAttr;
+
+XREF struct TextFont *TopazFont;
+
+XREF ULONG
+       FileReqSig,
+       AppSig,
+       AudioSig,
+       CxSig,
+       AmigaGuideSig,
+       PubPortSig,
+       Signals;                // Global Wait() signals
+
+
+XREF struct XMFileReq FileReqs[FREQ_COUNT];
+
+
+XREF struct List
+       WindowList,
+       InstrList,
+       PatternsList,
+       SequenceList,
+       LogList;
+
+
+XREF struct SaveSwitches       SaveSwitches;
+XREF struct ClearSwitches      ClearSwitches;
+XREF struct OptSwitches                OptSwitches;
+XREF struct GuiSwitches                GuiSwitches;
+XREF struct PattSwitches       PattSwitches;
+
+XREF BOOL DoNextSelect;
+XREF BOOL ShowRequesters;
+XREF BOOL Iconified;
+XREF BOOL Quit;
+
+XREF LONG
+       ToolBoxWinTags[],
+       PrefsWinTags[],
+       SongInfoWinTags[],
+       InstrumentsWinTags[],
+       SequenceWinTags[],
+       PatternWinTags[],
+       PlayWinTags[],
+       SampleWinTags[],
+       OptimizationWinTags[],
+       SaversWinTags[],
+       PattPrefsWinTags[],
+       PattSizeWinTags[],
+       ClearWinTags[],
+       LogWinTags[],
+       ProgressWinTags[];
+
+
+
+/***********************/
+/* Function prototypes */
+/***********************/
+
+GLOBALCALL void        OpenProgressWindow      (void);
+GLOBALCALL void        CloseProgressWindow     (void);
+
+GLOBALCALL void        UpdateInstrList         (void);
+GLOBALCALL void        UpdateInstrInfo         (void);
+GLOBALCALL void        DetatchSongInfoList (void);
+GLOBALCALL void        UpdateSongInfoList      (void);
+GLOBALCALL void        UpdateSongInfo          (void);
+GLOBALCALL void        UpdatePatternList       (void);
+GLOBALCALL void        UpdateSequenceList      (void);
+/*
+GLOBALCALL void        UpdateSample            (void);
+GLOBALCALL void        UpdateSampInfo          (void);
+GLOBALCALL void        UpdateSampGraph         (void);
+*/
+GLOBALCALL void        UpdateSampleMenu        (void);
+GLOBALCALL void        UpdateGuiSwitches       (void);
+GLOBALCALL void        UpdateInstrSwitches (void);
+GLOBALCALL void        UpdateClearSwitches     (void);
+GLOBALCALL void        UpdateSaveSwitches      (void);
+GLOBALCALL void        UpdateOptSwitches       (void);
+GLOBALCALL void        UpdatePrefsWindow       (void);
+GLOBALCALL void        UpdatePattern           (void);
+GLOBALCALL void        UpdateEditorInst        (void);
+//GLOBALCALL void      UpdatePlay                      (void);
+
+GLOBALCALL void        UpdatePattSize          (void);
+GLOBALCALL void        UpdatePattPrefs         (void);
+
+GLOBALCALL void        ToolBoxDropIcon         (struct AppMessage *msg);
+
+GLOBALCALL void        HandleIDCMP                     (void);
+GLOBALCALL void        LockWindows                     (void);
+GLOBALCALL void        UnlockWindows           (void);
+GLOBALCALL void        RevealWindow            (struct WinUserData *wud);
+GLOBALCALL void        SetGadgets                      (struct WinUserData *wud, LONG arg, ...);
+
+GLOBALCALL LONG        AddListViewNodeA        (struct List *lv, CONST_STRPTR label, LONG *args);
+GLOBALCALL LONG        AddListViewNode         (struct List *lv, CONST_STRPTR label, ...);
+GLOBALCALL void        RemListViewNode         (struct Node *n);
+GLOBALCALL struct Image *NewImageObject (ULONG which);
+GLOBALCALL struct Window *NewWindow    (ULONG id);
+GLOBALCALL struct Window *MyOpenWindow (struct WinUserData *wud);
+GLOBALCALL void        MyCloseWindow           (struct WinUserData *wud);
+GLOBALCALL struct WinUserData *CreateWUD (ULONG id);
+GLOBALCALL void        DeleteLayoutInfo        (struct WinUserData     *wud);
+
+GLOBALCALL void        ReopenWindows           (void);
+GLOBALCALL LONG        SetupScreen                     (void);
+GLOBALCALL void        CloseDownScreen         (void);
+
+GLOBALCALL struct Gadget *CreateUpButton (ULONG id, struct Gadget *target, LONG *map, LONG Place);
+GLOBALCALL struct Gadget *CreateDnButton (ULONG id, struct Gadget *target, LONG *map, LONG Place);
+GLOBALCALL struct Gadget *CreateSxButton (ULONG id, struct Gadget *target, LONG *map);
+GLOBALCALL struct Gadget *CreateDxButton (ULONG id, struct Gadget *target, LONG *map);
+GLOBALCALL struct Gadget *CreateVSlider (ULONG id, struct Gadget *target, LONG *map, LONG ButtonsSpacing, LONG Place);
+GLOBALCALL struct Gadget *CreateHSlider (ULONG id, struct Gadget *target, LONG *map, LONG ButtonsSpacing);
diff --git a/Guru.i b/Guru.i
new file mode 100644 (file)
index 0000000..051443a
--- /dev/null
+++ b/Guru.i
@@ -0,0 +1,91 @@
+Push   macro
+       ifc     "\1","All"
+       movem.l d0-a6,-(sp)
+       else
+       movem.l \1,-(sp)
+       endc
+       endm
+
+Pull   macro
+       ifc     "\1","All"
+       movem.l (sp)+,d0-a6
+       else
+       movem.l (sp)+,\1
+       endc
+       endm
+
+CALL   macro
+       ifnd    _LVO\1
+       xref    _LVO\1
+       endc
+       jsr     _LVO\1(a6)
+       endm
+
+CLIB   macro
+       xref    _LVO\2
+       ifc     "\1","Exec"
+       move.l  4.w,a6
+       else
+       move.l  _\1Base,a6
+       endc
+       jsr     _LVO\2(a6)
+       endm
+
+CPLIB  macro
+       xref    _LVO\2
+       ifc     "\1","Exec"
+       move.l  4.w,a6
+       else
+       move.l  _\1Base(pc),a6
+       endc
+       jsr     _LVO\2(a6)
+       endm
+
+C5LIB  macro
+       ifnd    _LVO\2
+       xref    _LVO\2
+       endc
+       ifc     "\1","Exec"
+       move.l  4.w,a6
+       else
+       move.l  _\1Base(a5),a6
+       endc
+       jsr     _LVO\2(a6)
+       endm
+
+OLIB   macro   *LIB_ID,CLEANUP openlib Dos, cleanup
+D\1    set     1
+       move.l  4.w,a6
+       lea     _\1Lib(pc),a1
+       moveq   #0,d0
+       jsr     _LVOOpenLibrary(a6)
+       ifd     RELATIVE
+       move.l  d0,_\1Base(a5)
+       endc
+       ifnd    RELATIVE
+       move.l  d0,_\1Base
+       endc
+       ifnc    '\2',''
+       beq     \2
+       endc
+       endm
+
+CLLIB  macro   *LIB_ID         closlib Dos
+       ifd     RELATIVE
+       move.l  _\1Base(a5),a1
+       endc
+       ifnd    RELATIVE
+       move.l  _\1Base(pc),a1
+       endc
+       move.l  a1,d0
+       beq     cLIB\@
+       move.l  4,a6
+       jsr     _LVOCloseLibrary(a6)
+cLIB\@
+       ifd     RELATIVE
+       clr.l   _\1Base(a5)
+       endc
+       ifnd    RELATIVE
+       clr.l   _\1Base
+       endc
+       endm
diff --git a/Help.c b/Help.c
new file mode 100644 (file)
index 0000000..362018f
--- /dev/null
+++ b/Help.c
@@ -0,0 +1,124 @@
+/*
+**     Help.c
+**
+**     Copyright (C) 1994,95,96 by Bernardo Innocenti
+**
+**     Handle on-line, context sensitive, AmigaGuide help.
+*/
+
+#include <libraries/amigaguide.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/amigaguide.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+XDEF ULONG     AmigaGuideSig = 0L;
+
+static struct Library *AmigaGuideBase = NULL;
+static struct NewAmigaGuide NewGuide = { 0 };
+static AMIGAGUIDECONTEXT Context = NULL;
+
+static STRPTR ContextList[] =
+{
+       "Main",
+       NULL
+};
+
+
+
+GLOBALCALL void HandleHelp (struct IntuiMessage *msg)
+{
+
+       if (!AmigaGuideBase)
+       {
+               if (!(AmigaGuideBase = OpenLibrary ("amigaguide.library", 33)))
+               {
+                       CantOpenLib ("amigaguide.library", 33);
+                       return;
+               }
+
+               NewGuide.nag_Name               = (STRPTR) PRGNAME ".guide";
+               NewGuide.nag_Node               = (STRPTR) "MAIN";
+               NewGuide.nag_BaseName   = (STRPTR) PRGNAME;
+               NewGuide.nag_ClientPort = NULL; // (STRPTR) "XMODULE_HELP";
+               NewGuide.nag_Context    = ContextList;
+               NewGuide.nag_Screen             = Scr;
+
+               if(Context = OpenAmigaGuideAsync (&NewGuide,
+                       AGA_HelpGroup,  UniqueID,
+                       TAG_DONE))
+               {
+                       AmigaGuideSig = AmigaGuideSignal (Context);
+                       Signals |= AmigaGuideSig;
+
+                       /* Get startup message */
+                       Wait (AmigaGuideSig);
+                       HandleAmigaGuide();
+               }
+               else
+               {
+                       LastErr = IoErr();
+                       CloseLibrary (AmigaGuideBase); AmigaGuideBase = NULL;
+                       return;
+               }
+       }
+
+
+       /* Link with node */
+       {
+               UBYTE cmd[48];
+
+               if (IntuiMsg.Class == IDCMP_RAWKEY || IntuiMsg.Class == IDCMP_MENUHELP)
+                       SPrintf (cmd, "LINK \"%s\"", ((struct WinUserData *)IntuiMsg.IDCMPWindow->UserData)->HelpNode);
+               else
+                       strcpy (cmd, "LINK Main");
+
+               SendAmigaGuideCmdA (Context, cmd, NULL);
+       }
+}
+
+
+
+GLOBALCALL void HandleAmigaGuide (void)
+{
+       struct AmigaGuideMsg *agm;
+
+       while (agm = GetAmigaGuideMsg (Context))
+       {
+               if (agm->agm_Pri_Ret) /* Error? */
+               {
+                       STRPTR reason;
+
+                       if (reason = GetAmigaGuideString (agm->agm_Sec_Ret))
+                               ShowRequest (MSG_AMIGAGUIDE_ERROR, 0, reason);
+               }
+
+//             switch (agm->agm_Type)
+//             {
+//                     case ToolCmdReplyID:            /* A command has completed */
+//                     case ToolStatusID:
+
+//                     default:
+//                             break;
+//             }
+
+               ReplyAmigaGuideMsg (agm);
+       }
+}
+
+
+
+GLOBALCALL void CleanupHelp (void)
+{
+       if (AmigaGuideBase)
+       {
+               Signals &= ~AmigaGuideSig;
+               AmigaGuideSig = 0;
+               CloseAmigaGuide (Context);              Context = NULL;
+               CloseLibrary (AmigaGuideBase);  AmigaGuideBase = NULL;
+       }
+}
diff --git a/Hooks/FastTracker2.h b/Hooks/FastTracker2.h
new file mode 100644 (file)
index 0000000..c778445
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+**     FastTracker2.h
+**
+**     Copyright (C) 1994,95 Bernardo Innocenti
+**
+**     Definitions for the FastTracker ][ XM format.
+*/
+
+
+
+struct FT2Header
+{
+       UBYTE   ID[17];                 /* ID text: "Extended module: "                         */
+       UBYTE   Name[20];               /* Module name, padded with zeroes                      */
+       UBYTE   Dummy;                  /* Constant: $1a                                                        */
+       UBYTE   Tracker[20];    /* Tracker name                                                         */
+       UBYTE   Revision;               /* minor version number ($03)                           */
+       UBYTE   Version;                /* major version number ($01)                           */
+       ULONG   HeaderSize;             /* sizeof (struct FT2Header)                            */
+       UWORD   Length;                 /* Song length (in patten order table)          */
+       UWORD   Restart;                /* Restart position                                                     */
+       UWORD   Channels;               /* Number of channels (2,4,6,8,10,...,32)       */
+       UWORD   NumPatt;                /* Number of patterns (max 256)                         */
+       UWORD   NumInstr;               /* Number of instruments (max 128)                      */
+       UWORD   Flags;                  /* See below...                                                         */
+       UWORD   DefTempo;               /* Default tempo                                                        */
+       UWORD   DefBPM;                 /* Default BeatsPerMinute                                       */
+       /* Followed by patterns order table */
+};
+
+
+
+struct FT2Pattern
+{
+       ULONG   Size;                                   /* Header size, sizeof (FT2Pattern)     */
+       UBYTE   Packing;                                /* Packing type (always 0)                      */
+
+       /* Two words at odd offsetts! Argh!!! */
+       UBYTE   RowsL, RowsH;                   /* Number of rows in pattern            */
+       UBYTE   PackSizeL, PackSizeH;   /* Packed patterndata size                      */
+
+       /* Packed pattern data follows, but this structure's size is
+        * variable!  Check FT2Pattern->Size to find out...
+        */
+};
+
+
+
+struct FT2Instrument
+{
+       ULONG   Size;                   /* Instrument size                                                      */
+       UBYTE   Name[22];               /* Instrument name                                                      */
+       UBYTE   Type;                   /* Instrument type (always 0)                           */
+
+       /* Yet another time: words at odd offsetts! */
+       UBYTE   SamplesL;               /* If the number of samples > 0,                        */
+       UBYTE   SamplesH;               /*  then an FT2Sample structure will follow     */
+};
+
+
+
+struct FT2InstExt
+{
+       ULONG   Size;                   /* Sample size                                                          */
+       UBYTE   Number[96];             /* Sample number for all notes                          */
+       UBYTE   VolEnv[48];             /* Points for volume envelope                           */
+       UBYTE   PanEnv[48];             /* Points for panning envelope                          */
+       UBYTE   VolCount;               /* Number of volume points                                      */
+       UBYTE   PanCount;               /* Number of panning points                                     */
+       UBYTE   VolSustain;             /* Volume sustain point                                         */
+       UBYTE   VolLoopStart;   /* Volume loop start point                                      */
+       UBYTE   VolLoopEnd;             /* Volume loop end point                                        */
+       UBYTE   PanSustain;             /* Panning sustain point                                        */
+       UBYTE   PanLoopStart;   /* Panning loop start point                                     */
+       UBYTE   PanLoopEnd;             /* Panning loop end point                                       */
+       UBYTE   VolType;                /* Volume type: bit 0:On; 1:Sustain; 2:Loop     */
+       UBYTE   PanType;                /* Panning type: bit 0:On; 1:Sustain; 2:Loop*/
+       UBYTE   VibType;                /* Vibrato type                                                         */
+       UBYTE   VibSweep;               /* Vibrato sweep                                                        */
+       UBYTE   VibDepth;               /* Vibrato depth                                                        */
+       UBYTE   VibRate;                /* Vibrato rate                                                         */
+       UBYTE   VolFadeoutL;
+       UBYTE   VolFadeoutH;
+       UBYTE   ReservedL;
+       UBYTE   ReservedH;
+};
+
+
+
+struct FT2Sample
+{
+       ULONG   Length;                 /* Sample length                        */
+       ULONG   Repeat;                 /* Sample loop start            */
+       ULONG   Replen;                 /* Sample loop length           */
+       UBYTE   Volume;
+       BYTE    Finetune;               /* (signed byte -16..+15)       */
+       UBYTE   Type;                   /* See below...                         */
+       UBYTE   Panning;                /* Panning (0-255)                      */
+       BYTE    RelNote;                /* Relative note number         */
+       UBYTE   Reserved;
+       UBYTE   Name[22];
+};
+
+
+
+/* Flags for FT2Sample->Type */
+
+#define STYP_LOOPMASK  3       /* bits 0-1                             */
+#define STYPF_FWDLOOP  1       /* Forward loop                 */
+#define STYPF_PINGLOOP 2       /* Ping-Pong loop               */
+#define STYPF_16BIT            16      /* 16-bit sampledata    */
+
diff --git a/Hooks/GetFT2.c b/Hooks/GetFT2.c
new file mode 100644 (file)
index 0000000..6acf333
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+**     GetFT2.c
+**
+**     Copyright (C) 1995 Bernardo Innocenti
+**
+**     Load a FastTracker ][ module with any number of tracks.
+*/
+
+#include <exec/memory.h>
+#include <libraries/XModule.h>
+
+#include <clib/exec_protos.h>
+#include <clib/dos_protos.h>
+#include <clib/xmodule_protos.h>
+
+#include <pragmas/exec_sysbase_pragmas.h>
+#include <pragmas/dos_pragmas.h>
+#include <pragmas/xmodule_pragmas.h>
+
+#include "Gui.h"
+#include "FastTracker2.h"
+
+
+/* Convert an Intel style WORD to Motorola format */
+#define I2M(x) ( (UWORD) ( (((UWORD)(x)) >> 8) | (((UWORD)(x)) << 8) ) )
+
+/* Convert an Intel style LONG to Motorola format */
+#define I2ML(x) ( I2M((x)>>16) | (I2M((x))<<16) )
+
+
+
+LONG GetFT2 (struct SongInfo *si, BPTR fh)
+{
+       ULONG i, j;             /* Loop counters */
+       struct FT2Header ft2hd;
+
+
+       /* Read module header */
+       if (Read (fh, &ft2hd, sizeof (ft2hd)) != sizeof (ft2hd))
+               return ERR_READWRITE;
+
+       /* Convert from Intel's shitty endian */
+       ft2hd.HeaderSize        = I2ML(ft2hd.HeaderSize);
+       ft2hd.Length            = I2M(ft2hd.Length);
+       ft2hd.Restart           = I2M(ft2hd.Restart);
+       ft2hd.Channels          = I2M(ft2hd.Channels);
+       ft2hd.NumPatt           = I2M(ft2hd.NumPatt);
+       ft2hd.NumInstr          = I2M(ft2hd.NumInstr);
+       ft2hd.Flags                     = I2M(ft2hd.Flags);
+       ft2hd.DefTempo          = I2M(ft2hd.DefTempo);
+       ft2hd.DefBPM            = I2M(ft2hd.DefBPM);
+
+       if (!xmSetSongLen (si, ft2hd.Length))
+               return ERROR_NO_FREE_STORE;
+
+       if (Read (fh, si->Sequence, si->Length * sizeof (UWORD)) !=
+               si->Length * sizeof (UWORD))
+               return ERR_READWRITE;
+
+       for (i = 0; i < si->Length; i++)
+               si->Sequence[i] = I2M(si->Sequence[i]);
+
+       si->Restart = ft2hd.Restart;
+       si->GlobalSpeed = ft2hd.DefTempo;
+       si->GlobalTempo = ft2hd.DefBPM;
+
+       return RETURN_OK;
+}
diff --git a/Hooks/MMDHook.c b/Hooks/MMDHook.c
new file mode 100644 (file)
index 0000000..c493f71
--- /dev/null
@@ -0,0 +1,1025 @@
+/*
+**     MMDHook.c
+**
+**     Copyright (C) 1994,95 by Bernardo Innocenti
+**
+**     External loader and saver hook for MMD0 or MMD1 modules.
+**
+**     Note: Sorry, this source is a bit of a mess, but the MMD
+**           format is awfully complex <grin>.
+**
+**
+**     Structure of an MMD0/1/2 module as saved by SaveMED():
+**
+**     MMD0
+**     MMD0song
+**     MMD0exp
+**     InstrExt[]
+**     InstrInfo[]
+**     <AnnoTxt>
+**     <SongName>
+**     NotationInfo
+**     MMD0blockarr
+**     MMD0samparr
+**             <blocks>
+**             <BlockInfo>             /* Only for MMD1/MMD2 */
+**             <BlockName>             /* Only for MMD1/MMD2 */
+**     <samples>
+*/
+
+#include <exec/types.h>
+
+#include <clib/exec_protos.h>
+#include <clib/dos_protos.h>
+#include <clib/xmodule_protos.h>
+
+#include <pragmas/exec_sysbase_pragmas.h>
+#include <pragmas/dos_pragmas.h>
+#include <pragmas/xmodule_pragmas.h>
+
+#include "XModule.h"
+#include "Gui.h"
+#include "OctaMed.h"
+
+
+#define OFFSET_MMD0song                sizeof (struct MMD0)
+#define OFFSET_MMD0exp         (sizeof (struct MMD0) + sizeof (struct MMD0song))
+#define OFFSET_InstrExt                (sizeof (struct MMD0) + sizeof (struct MMD0song) + sizeof (struct MMD0exp))
+
+
+
+static INLINE UBYTE DecodeEff (UBYTE eff, UBYTE *effval, UBYTE flags, UBYTE flags2);
+
+
+
+/* Effects conversion table */
+static const UBYTE Effects[MAXTABLEEFFECTS] =
+{
+/*   MED               XModule                 Val */
+
+       0x00,   //      Null effect             $00
+
+       0x01,   //      Portamento Up   $01
+       0x02,   //      Portamento Down $02
+       0x03,   //      Tone Portamento $03
+       0x14,   //      Vibrato                 $04
+       0x05,   //      ToneP + VolSl   $05
+       0x06,   //      Vibra + VolSl   $06
+       0x07,   //      Tremolo                 $07
+       0x08,   //      Set Hold/Decay  $08
+       0x19,   //      Sample Offset   $09
+       0x0A,   //      Volume Slide    $0A
+       0x0B,   //      Position Jump   $0B
+       0x0C,   //      Set Volume              $0C
+       0x1D,   //      Pattern break   $0D
+       0x00,   //      Misc                    $0E
+       0x09,   //      Set Speed               $0F
+       0x0F,   //      Set Tempo               $10
+       0x00,   //      Arpeggio                $11
+
+       0x00,   //      Oktalyzer H
+       0x00,   //      Oktalyzer L
+};
+
+
+
+LONG GetMED (struct SongInfo *si, BPTR fp)
+{
+       struct Instrument *inst = si->Inst;
+       struct MMD0 mmd0;
+       struct MMD0song song;
+       ULONG i, j, k, mmdtype, len;
+
+       /******************************/
+       /*      Read MMD0 header      */
+       /******************************/
+
+       if (Read (fp, &mmd0, sizeof (mmd0)) != sizeof (mmd0))
+               return ERR_READWRITE;
+
+       mmdtype = mmd0.id & 0xFF;
+
+       if (GuiSwitches.Verbose)
+               ShowMessage (MSG_READING_MMD, mmdtype);
+
+       mmdtype -= '0';
+
+       if (mmdtype > 1)
+       {
+               ShowMessage (MSG_UNSUPPORTED_MMD_FORMAT);
+               return ERR_NOTMODULE;
+       }
+
+
+       /******************************/
+       /*  Read MMD0song structure   */
+       /******************************/
+
+       if (Seek (fp, (LONG)mmd0.song, OFFSET_BEGINNING) == -1)
+               return ERR_READWRITE;
+
+       if (Read (fp, &song, sizeof (song)) != sizeof (song))
+               return ERR_READWRITE;
+
+
+       /* Set instruments parameters */
+
+       for (i = 0; i <= song.numsamples; i++)
+       {
+               inst[i+1].Volume = song.sample[i].svol;
+
+               if (song.sample[i].rep || song.sample[i].replen != 1)
+               {
+                       inst[i+1].Repeat = song.sample[i].rep << 1;
+                       inst[i+1].Replen = song.sample[i].replen << 1;
+               }
+       }
+
+       /* Set position table */
+       if (xmSetSongLen (si, song.songlen))
+               for (i = 0; i < si->Length; i++)
+                       si->Sequence[i] = song.playseq[i];
+
+
+       if (song.flags2 & FLAG2_BPM)
+               si->GlobalTempo = (song.deftempo * 4) / ((song.flags2 & FLAG2_BMASK) + 1);
+       else
+               si->GlobalTempo = song.deftempo;
+
+       si->GlobalSpeed = song.tempo2;
+
+
+
+       /******************************/
+       /*   Read MMD0exp structure   */
+       /******************************/
+
+       if (mmd0.expdata)
+       {
+               struct MMD0exp exp;
+
+               if (Seek (fp, (LONG)mmd0.expdata, OFFSET_BEGINNING) == -1)
+                       return ERR_READWRITE;
+
+               if (Read (fp, &exp, sizeof (exp)) != sizeof (exp))
+                       return ERR_READWRITE;
+
+
+               /******************************/
+               /*  Read InstrExt structures  */
+               /******************************/
+
+               if (exp.exp_smp && (exp.s_ext_entrsz >= 4))
+               {
+                       struct InstrExt instrext;
+                       ULONG                   size = min (exp.s_ext_entrsz, sizeof (instrext));
+
+                       if (Seek (fp, (LONG)exp.exp_smp, OFFSET_BEGINNING) == -1)
+                               return ERR_READWRITE;
+
+                       for (i = 1; i <= exp.s_ext_entries; i++)
+                       {
+                               if (Read (fp, &instrext, size) != size)
+                                       return ERR_READWRITE;
+
+                               inst[i].FineTune = instrext.finetune;
+                               if (exp.s_ext_entrsz > size)
+                                       if (Seek (fp, exp.s_ext_entrsz - size, OFFSET_CURRENT) == -1)
+                                               return ERR_READWRITE;
+                       }
+               }
+
+               /******************************/
+               /*      Read Annotation       */
+               /******************************/
+
+               if (exp.annotxt && exp.annolen > 1)
+               {
+                       ULONG len = min (exp.annolen, MAXAUTHNAME-1);
+
+                       if (Seek (fp, (LONG)exp.annotxt, OFFSET_BEGINNING) == -1)
+                               return ERR_READWRITE;
+
+                       if (Read (fp, si->Author, len) != len)
+                               return ERR_READWRITE;
+               }
+
+
+               /******************************/
+               /*  Read InstrInfo structures */
+               /******************************/
+
+               if (exp.iinfo && (exp.i_ext_entrsz >= sizeof (struct MMDInstrInfo)))
+               {
+                       struct MMDInstrInfo instrinfo;
+
+                       if (Seek (fp, (LONG)exp.iinfo, OFFSET_BEGINNING) == -1)
+                               return ERR_READWRITE;
+
+                       for (i = 1; i <= exp.i_ext_entries; i++)
+                       {
+                               if (Read (fp, &instrinfo, sizeof (instrinfo)) != sizeof (instrinfo))
+                                       return ERR_READWRITE;
+
+                               strncpy (inst[i].Name, instrinfo.name, MAXINSTNAME - 1);
+
+                               if (exp.i_ext_entrsz > sizeof (struct MMDInstrInfo))
+                                       if (Seek (fp, exp.i_ext_entrsz - sizeof (struct MMDInstrInfo), OFFSET_CURRENT) == -1)
+                                               return ERR_READWRITE;
+                       }
+               }
+
+               /******************************/
+               /*       Read SongName        */
+               /******************************/
+
+               if (exp.songname && exp.songnamelen > 1)
+               {
+                       ULONG size = min (exp.songnamelen, MAXSONGNAME-1);
+
+                       if (Seek (fp, (LONG)exp.songname, OFFSET_BEGINNING) == -1)
+                               return ERR_READWRITE;
+
+                       if (Read (fp, si->SongName, size) != size)
+                               return ERR_READWRITE;
+               }
+       }
+
+
+       /******************************/
+       /*        Read blocarr        */
+       /******************************/
+
+       if (mmd0.blockarr)
+       {
+               LONG *blockarr;
+
+               DisplayAction (MSG_READING_PATTS);
+
+               if (Seek (fp, (LONG)mmd0.blockarr, OFFSET_BEGINNING) == -1)
+                       return ERR_READWRITE;
+
+               if (!(blockarr = AllocMem (song.numblocks * 4, MEMF_ANY)))
+                       return ERROR_NO_FREE_STORE;
+
+               if (Read (fp, blockarr, song.numblocks * 4) != song.numblocks * 4)
+               {
+                       FreeMem (blockarr, song.numblocks * 4);
+                       return ERR_READWRITE;
+               }
+
+
+
+               /***************/
+               /* Read blocks */
+               /***************/
+
+               for (i = 0; i < song.numblocks; i++)
+               {
+                       struct Pattern          *patt;
+                       struct Note                     *note;
+                       struct MMD1block         block;
+                       struct MMD0block         mmd0block;
+                       struct BlockInfo         blockinfo;
+                       ULONG mednote;
+
+                       if (DisplayProgress (i, song.numblocks))
+                       {
+                               FreeMem (blockarr, song.numblocks * 4);
+                               return ERROR_BREAK;
+                       }
+
+                       if (Seek (fp, (LONG)blockarr[i], OFFSET_BEGINNING) == -1)
+                               ShowMessage (MSG_ERR_CANT_LOAD_PATT, i);
+                       else
+                       {
+                               if (mmdtype == 0)
+                                       len = Read (fp, &mmd0block, sizeof (struct MMD0block));
+                               else
+                                       len = Read (fp, &block, sizeof (struct MMD1block));
+
+                               if (len != (mmdtype ? sizeof (struct MMD1block) : sizeof (struct MMD0block)))
+                                       ShowMessage (MSG_ERR_CANT_LOAD_PATT, i);
+                               else
+                               {
+                                       if (mmdtype == 0)       /* Convert MMD0 block */
+                                       {
+                                               block.numtracks = mmd0block.numtracks;
+                                               block.lines     = mmd0block.lines;
+                                               block.info      = 0;
+                                       }
+
+                                       if (block.numtracks >= MAXTRACKS)
+                                       {
+                                               ShowMessage (MSG_PATT_TOO_MANY_TRACKS, i, MAXTRACKS);
+                                               continue;
+                                       }
+
+                                       if (si->MaxTracks < block.numtracks)
+                                               si->MaxTracks = block.numtracks;
+
+                                       if (block.lines > MAXPATTLINES - 1)
+                                       {
+                                               ShowMessage (MSG_PATT_TOO_MANY_LINES, i, MAXPATTLINES);
+                                               block.lines = MAXPATTLINES - 1;
+                                       }
+
+                                       if (!(patt = AddPattern (si, block.numtracks, block.lines + 1)))
+                                       {
+                                               FreeMem (blockarr, song.numblocks * 4);
+                                               return ERROR_NO_FREE_STORE;
+                                       }
+
+                                       /***************/
+                                       /* Read Tracks */
+                                       /***************/
+
+                                       for (k = 0; k < patt->Lines; k++)
+                                       {
+                                               for (j = 0; j < patt->Tracks; j++)
+                                               {
+                                                       note = &patt->Notes[j][k];
+
+                                                       if (mmdtype == 0)
+                                                       {
+                                                               if (Read (fp, &mednote, MMD0ROW) != MMD0ROW)
+                                                                       return ERR_READWRITE;
+
+                                                               mednote >>= 8;
+
+                                                               note->EffVal = mednote & 0xFF;
+                                                               note->EffNum = DecodeEff ((mednote >> 8) & 0x0F, &note->EffVal, song.flags, song.flags2);
+                                                               note->Note = ((mednote >> 16) & 0x3F);
+                                                               if (note->Note) note->Note += 12;
+                                                               note->Inst = ((mednote >> 12) & 0xF) |
+                                                                       ((mednote & (1<<22)) ? 0x20 : 0) | ((mednote & (1<<23)) ? 0x10 : 0);
+                                                       }
+                                                       else
+                                                       {
+                                                               if (Read (fp, &mednote, MMD1ROW) != MMD1ROW)
+                                                                       return ERR_READWRITE;
+
+                                                               note->EffVal = mednote & 0xFF;
+                                                               note->EffNum = DecodeEff ((mednote >> 8) & 0xFF, &note->EffVal, song.flags, song.flags2);
+                                                               note->Inst = ((mednote >> 16) & 0x3F);
+                                                               note->Note = ((mednote >> 24) & 0x7F);
+
+                                                               if (note->Note) note->Note += 12;
+                                                       }
+                                               }
+                                       }       /* End for (patt->Tracks) */
+
+                                       /******************/
+                                       /* Read BlockInfo */
+                                       /******************/
+
+                                       if (block.info)
+                                               if (Seek (fp, (LONG)block.info, OFFSET_BEGINNING) != -1);
+                                               {
+                                                       if (Read (fp, &blockinfo, sizeof (blockinfo)) == sizeof(blockinfo))
+                                                       {
+                                                               if (blockinfo.blockname)
+                                                                       if (Seek (fp, (LONG)blockinfo.blockname, OFFSET_BEGINNING) != -1);
+                                                                       {
+                                                                               Read (fp, patt->PattName, min (blockinfo.blocknamelen, MAXPATTNAME));
+                                                                               patt->PattName[MAXPATTNAME-1] = '\0';   /* Ensure NULL-termination */
+                                                                       }
+                                                       }
+                                               }
+                               }
+                       }
+               }       /* End for (song->numblocks) */
+
+               FreeMem (blockarr, song.numblocks * 4);
+       }
+
+
+
+       /******************************/
+       /*        Read smplarr        */
+       /******************************/
+
+       if (mmd0.smplarr)
+       {
+               LONG *smplarr;
+               struct InstrHdr instrhdr;
+
+               DisplayAction (MSG_READING_INSTS);
+
+               if (Seek (fp, (LONG)mmd0.smplarr, OFFSET_BEGINNING) == -1)
+                       return ERR_READWRITE;
+
+               if (!(smplarr = AllocMem (song.numsamples * 4, MEMF_ANY)))
+                       return ERROR_NO_FREE_STORE;
+
+               if (Read (fp, smplarr, song.numsamples * 4) != song.numsamples * 4)
+               {
+                       FreeMem (smplarr, song.numsamples * 4);
+                       return ERR_READWRITE;
+               }
+
+
+               /******************************/
+               /*        Read samples        */
+               /******************************/
+
+               for (i = 1; i <= song.numsamples ; i++)
+               {
+                       if (!smplarr[i-1]) continue;
+
+                       if (DisplayProgress (i, song.numsamples))
+                       {
+                               FreeMem (smplarr, song.numsamples * 4);
+                               return ERROR_BREAK;
+                       }
+
+                       if (Seek (fp, smplarr[i-1], OFFSET_BEGINNING) == -1)
+                               ShowMessage (MSG_ERR_CANT_LOAD_INST, i);
+                       else
+                       {
+                               if (Read (fp, &instrhdr, sizeof (instrhdr)) != sizeof (instrhdr))
+                                       ShowMessage (MSG_ERR_CANT_LOAD_INST, i);
+                               else
+                               {
+                                       switch (instrhdr.type)
+                                       {
+                                               case SAMPLE:
+                                                       inst[i].Length = instrhdr.length;
+
+                                                       if (!(inst[i].SampleData = (UBYTE *) AllocVec (inst[i].Length, MEMF_SAMPLE)))
+                                                       {
+                                                               ShowMessage (MSG_ERR_NO_MEM_FOR_INST, i);
+                                                               break;
+                                                       }
+
+                                                       if (Read (fp, inst[i].SampleData, inst[i].Length) != inst[i].Length)
+                                                               ShowMessage (MSG_ERR_CANT_LOAD_INST, i);
+
+                                                       /* Double sample data */
+                                                       if (song.flags & FLAG_8CHANNEL)
+                                                       {
+                                                               BYTE *data = inst[i].SampleData;
+
+                                                               for (j = 0; j < inst[j].Length; j++)
+                                                                       *data++ <<= 1;
+                                                       }
+                                                       break;
+
+                                               default:
+                                                       ShowMessage (MSG_ERR_NOT_A_SAMPLE, i);
+                                                       break;
+                                       }
+                               }
+                       }
+               }
+
+               FreeMem (smplarr, song.numsamples * 4);
+       }
+
+       return RETURN_OK;
+}
+
+
+
+static __inline UBYTE DecodeEff (UBYTE eff, UBYTE *effval, UBYTE flags, UBYTE flags2)
+{
+       UBYTE i;
+
+       switch (eff)
+       {
+               case 0x00:
+                       if (*effval) return EFF_ARPEGGIO;
+                       else return EFF_NULL;
+
+               case 0x0C:      /* Volume */
+                       if (!(flags & FLAG_VOLHEX))
+                               /* Convert decimal volumes */
+                               *effval = (*effval & 0x0F) + (*effval >> 4) * 10;
+                       return EFF_SETVOLUME;
+
+               case 0x0F:      /* Tempo */
+                       if (*effval == 0) return EFF_PATTERNBREAK;
+                       if (*effval <= 0x0A) return EFF_SETSPEED;
+                       if (*effval <= 0xF0)
+                       {
+                               *effval = (*effval * 4) / ((flags2 & FLAG2_BMASK) + 1);
+                               return EFF_SETTEMPO;
+                       }
+                       if (*effval == 0xF8)
+                       {
+                               *effval = 1;
+                               return EFF_MISC;        /* Filter off */
+                       }
+                       if (*effval == 0xF9)
+                       {
+                               *effval = 0;
+                               return EFF_MISC;        /* Filter on */
+                       }
+                       return EFF_NULL;
+
+               default:
+                       for ( i = 0 ; i < MAXTABLEEFFECTS ; i++ )
+                               if (eff == Effects[i])
+                                       return i;
+       }
+
+       return 0;
+}
+
+
+
+LONG SaveMED (struct SongInfo *si, BPTR fp, UWORD mmd_type)
+{
+       struct Instrument       *inst = si->Inst;
+       struct Pattern          *patt = si->PattData;
+       ULONG   modlen, blockslen, instlen, blockptr, offset_blockarr,
+                       i, j, k, lastinstr;
+       BOOL    halve_instr;
+
+       if (GuiSwitches.Verbose)
+               ShowMessage (MSG_WRITING_MMD, mmd_type + '0');
+
+       DisplayAction (MSG_WRITING_HEADER);
+
+
+       /*********************************/
+       /* Calculate total patterns size */
+       /*********************************/
+       {
+               if (mmd_type > 0)       /* MMD1 */
+               {
+                       for (i = 0, blockslen = 0; i < si->NumPatterns ; i++)
+                       {
+                               blockslen += sizeof (struct MMD1block) +
+                                       (MMD1ROW * patt[i].Lines * patt[i].Tracks) +
+                                       (patt[i].PattName[0] ?
+                                                (sizeof (struct BlockInfo) + strlen (patt[i].PattName) + 1)
+                                                : 0);
+
+                               if (blockslen & 1) blockslen++; /* Pad to words */
+                       }
+               }
+               else    /* MMD0 */
+               {
+                       for (i = 0, blockslen = 0; i < si->NumPatterns ; i++)
+                       {
+                               blockslen += sizeof (struct MMD0block) +
+                                       (MMD0ROW * patt[i].Lines * patt[i].Tracks);
+
+                               if (blockslen & 1) blockslen++; /* Pad to words */
+                       }
+               }
+       }
+
+       /* Find last used instrument */
+       for (lastinstr = 63; lastinstr > 0 ; lastinstr--)
+               if (inst[lastinstr].Length || inst[lastinstr].Name[0]) break;
+
+       /* Calculate Total instruments size */
+       for (i = 1, instlen = 0; i <= lastinstr; i++)
+               instlen += (inst[i].Length) ? (si->Inst[i].Length + sizeof (struct InstrHdr)) : 0;
+
+       /* Calculate blockarr offset */
+       offset_blockarr = sizeof (struct MMD0) + sizeof (struct MMD0song) + sizeof (struct MMD0exp) +
+               lastinstr * (sizeof (struct InstrExt) + sizeof (struct MMDInstrInfo));
+
+       offset_blockarr += strlen (si->Author) + 1;
+       if (offset_blockarr & 1) offset_blockarr++;             /* Pad to word */
+       offset_blockarr += strlen (si->SongName) + 1;
+       if (offset_blockarr & 1) offset_blockarr++;             /* Pad to word */
+
+       /* Calculate Total module size */
+       modlen =        offset_blockarr +
+                               si->NumPatterns * 4 +           /* blockarr             */
+                               lastinstr * 4 +                         /* samplearr    */
+                               blockslen +
+                               instlen;
+
+       /**********************************/
+       /* Fill-in & write module header */
+       /*********************************/
+       {
+               struct MMD0 mmd0;
+
+               memset (&mmd0, 0, sizeof (mmd0));
+
+               mmd0.id                 = mmd_type ? ID_MMD1 : ID_MMD0;
+               mmd0.modlen     = modlen;
+               mmd0.song               = (struct MMD0song *) OFFSET_MMD0song;
+//             mmd0.reserved0  = 0;
+               mmd0.blockarr   = (struct MMD0Block **) offset_blockarr;
+//             mmd0.reserved1  = si->NumPatterns;
+               mmd0.smplarr    = (struct InstrHdr **) (offset_blockarr + (si->NumPatterns * 4));
+//             mmd0.reserved2  = lastinst * sizeof (LONG);
+               mmd0.expdata    = (struct MMD0exp *) OFFSET_MMD0exp;
+//             mmd0.reserved3  = 0;                                    /* expsize */
+//             mmd0.pstate             = 0;                                    /* the state of the player */
+               mmd0.pblock             = si->CurrentPatt;              /* current block */
+//             mmd0.pline              = 0;                                    /* current line */
+               mmd0.pseqnum    = si->CurrentPos;               /* current # of playseqlist */
+               mmd0.actplayline= -1;                                   /* OBSOLETE!! DON'T TOUCH! */
+//             mmd0.counter    = 0;                                    /* delay between notes */
+//             mmd0.extra_songs= 0;
+
+               /* Write file header */
+               if (Write (fp, &mmd0, sizeof (mmd0)) != sizeof (mmd0))
+                       return ERR_READWRITE;
+
+       }
+
+       /************************************/
+       /* Fill-in and write Song structure */
+       /************************************/
+       {
+               struct MMD0song song;
+
+               for (i = 0; i < 63 ; i++)
+               {
+                       song.sample[i].rep                      = inst[i+1].Repeat >> 1;
+                       song.sample[i].replen           = inst[i+1].Replen >> 1;
+                       song.sample[i].midich           = 0;
+                       song.sample[i].midipreset       = 0;
+                       song.sample[i].svol                     = inst[i+1].Volume;
+                       song.sample[i].strans           = 0;
+               }
+
+               song.numblocks  = si->NumPatterns;
+               song.songlen    = min (si->Length, 256);
+
+               for (i = 0; i < song.songlen; i++)
+                       song.playseq[i] = si->Sequence[i];
+
+               song.deftempo   = si->GlobalTempo;
+               song.playtransp = 0;
+               halve_instr             = si->MaxTracks > 4;
+               song.flags              = (halve_instr ? FLAG_8CHANNEL : 0) | FLAG_STSLIDE | FLAG_VOLHEX;
+               song.flags2             = 3 | FLAG2_BPM;
+               song.tempo2             = si->GlobalSpeed;
+
+               for (i = 0; i <16; i++)
+                       song.trkvol[i] = 64;
+
+               song.mastervol  = 64;
+               song.numsamples = lastinstr;
+
+               if (Write (fp, &song, sizeof (song)) != sizeof (song))
+                       return ERR_READWRITE;
+       }
+
+       /*****************************/
+       /* Write extension (MMD0exp) */
+       /*****************************/
+       {
+               struct MMD0exp mmd0exp;
+               LONG len;
+
+               memset (&mmd0exp, 0, sizeof (mmd0exp));
+
+               mmd0exp.exp_smp                 = (struct InstrExt *) OFFSET_InstrExt;
+               mmd0exp.s_ext_entries   = lastinstr;
+               mmd0exp.s_ext_entrsz    = sizeof (struct InstrExt);
+               mmd0exp.annotxt                 = (STRPTR) (OFFSET_InstrExt + lastinstr * (sizeof (struct InstrExt) + sizeof (struct MMDInstrInfo)));
+               mmd0exp.annolen                 = strlen (si->Author) + 1;
+               mmd0exp.iinfo                   = (struct MMDInstrInfo *) (OFFSET_InstrExt + lastinstr * sizeof (struct InstrExt));
+               mmd0exp.i_ext_entries   = lastinstr;
+               mmd0exp.i_ext_entrsz    = sizeof (struct MMDInstrInfo);
+               mmd0exp.songname                = mmd0exp.annotxt + mmd0exp.annolen + ((mmd0exp.annolen & 1) ? 1 : 0);
+               mmd0exp.songnamelen             = strlen (si->SongName) + 1;
+
+               if (Write (fp, &mmd0exp, sizeof (mmd0exp)) != sizeof (mmd0exp))
+                       return ERR_READWRITE;
+
+
+               /************************/
+               /* Write InstrExt array */
+               /************************/
+               {
+                       struct InstrExt instrext = { 0 };
+
+                       for (i = 1; i <= lastinstr; i++)
+                       {
+                               // instrext.hold                                = 0;
+                               // instrext.decay                               = 0;
+                               // instrext.suppress_midi_off   = 0;
+                               instrext.finetune                               = si->Inst[i].FineTune;
+                               // instrext.default_pitch               = 0;
+                               // instrext.instr_flags                 = 0;
+                               // instrext.long_midi_preset    = 0;
+                               // instrext.output_device               = 0;
+                               // instrext.reserved                    = 0;
+
+                               if (Write (fp, &instrext, sizeof (instrext)) != sizeof (instrext))
+                                       return ERR_READWRITE;
+                       }
+               }
+
+               /*************************/
+               /* Write InstrInfo array */
+               /*************************/
+               {
+                       struct MMDInstrInfo instrinfo = { 0 };
+
+                       for (i = 1; i <= lastinstr; i++)
+                       {
+                               strcpy (instrinfo.name, si->Inst[i].Name);
+
+                               if (Write (fp, &instrinfo, sizeof (instrinfo)) != sizeof (instrinfo))
+                                       return ERR_READWRITE;
+                       }
+               }
+
+               /* Write AnnoTxt */
+
+               len = strlen (si->Author) + 1;
+               if (len & 1) len++;     /* Pad to WORD */
+               if (Write (fp, si->Author, len) != len)
+                       return ERR_READWRITE;
+
+               /* Write SongName */
+
+               len = strlen (si->SongName) + 1;
+               if (len & 1) len++;     /* Pad to WORD */
+               if (Write (fp, si->SongName, len) != len)
+                       return ERR_READWRITE;
+       }
+
+       /*************************************/
+       /* Write pattern pointers (blockarr) */
+       /*************************************/
+       {
+               blockptr = offset_blockarr + (si->NumPatterns * 4) + (lastinstr * 4);
+
+               for (i = 0; i < si->NumPatterns; i++)
+               {
+                       if (Write (fp, &blockptr, 4) != 4)
+                               return ERR_READWRITE;
+
+                       if (mmd_type)   /* MMD1 and up */
+                       {
+                               blockptr += sizeof (struct MMD1block) +
+                                       (MMD1ROW * patt[i].Tracks * patt[i].Lines) +
+                                       ((patt[i].PattName[0]) ?
+                                       (sizeof (struct BlockInfo) + strlen (patt[i].PattName) + 1)
+                                       : 0);
+                       }
+                       else    /* MMD0 */
+                       {
+                               blockptr += sizeof (struct MMD0block) +
+                                       (MMD0ROW * patt[i].Tracks * patt[i].Lines);
+                       }
+
+                       /* Pad to words */
+                       if (blockptr & 1)       blockptr++;
+               }
+       }
+
+       /*************************************/
+       /* Write sample pointers (samplearr) */
+       /*************************************/
+       {
+               ULONG sampleptr = blockptr, x;
+
+               for (i = 1; i <= lastinstr; i++)
+               {
+                       x = ((inst[i].Length && inst[i].SampleData) ? sampleptr : 0);
+
+                       if (Write (fp, &x, 4) != 4)
+                               return ERR_READWRITE;
+
+                       if (x) sampleptr += inst[i].Length + sizeof (struct InstrHdr);
+               }
+       }
+
+       /********************************/
+       /* Write patterns (blocks) data */
+       /********************************/
+       {
+               struct Note     *note;
+               ULONG            mednote;
+               LONG             blockinfoptr = offset_blockarr + (si->NumPatterns * 4) + (lastinstr * 4);
+               UBYTE            eff, effval;
+               BOOL             quiet = FALSE;
+
+
+               DisplayAction (MSG_WRITING_PATTS);
+
+
+               for (i = 0; i < si->NumPatterns; i++)
+               {
+                       if (DisplayProgress (i, si->NumPatterns))
+                               return ERROR_BREAK;
+
+                       /* Write Header */
+
+                       if (mmd_type)   /* MMD1 and higher */
+                       {
+                               struct MMD1block         block;
+
+                               blockinfoptr +=  sizeof (struct MMD1block) +
+                                       (MMD1ROW * patt[i].Tracks * patt[i].Lines);
+
+                               block.numtracks = patt[i].Tracks;
+                               block.lines = patt[i].Lines - 1;
+
+                               /* Write BlockInfo only if this pattern has a name */
+                               block.info = (struct BlockInfo *)(patt[i].PattName[0] ? blockinfoptr : 0);
+
+                               if (Write (fp, &block, sizeof (block)) != sizeof (block))
+                                       return ERR_READWRITE;
+                       }
+                       else    /* MMD0 */
+                       {
+                               struct MMD0block         block;
+
+                               block.numtracks = patt[i].Tracks;
+                               block.lines = patt[i].Lines - 1;
+
+                               if (Write (fp, &block, sizeof (block)) != sizeof (block))
+                                       return ERR_READWRITE;
+                       }
+
+
+                       /* Write Tracks */
+                       for (k = 0; k < patt[i].Lines; k++)
+                       {
+                               for (j = 0; j < patt[i].Tracks; j++)
+                               {
+                                       note = &patt[i].Notes[j][k];
+                                       eff = Effects[note->EffNum];
+
+                                       if (note->EffNum == EFF_MISC)
+                                       {
+/*
+               pt module       xmod bad conversion     octamed right conversion
+
+               E01                     006                                     0FF8 (Led OFF)
+               E11                     not converted           1101 (Slide pitch up once)
+               EC3                     not converted           1803 (Cut note)
+               E62                     not converted           1602 (Loop)
+               601                     1601                            0601 (Vibrato and fade)
+               E93                     not converted           1F03 (Note delay and retrigger)
+               ED2                     ????                            1F20 (Note delay and retrigger)
+               EB1                     not converted           1B01 (Slide volume down once)
+*/
+
+                                               switch (note->EffNum >> 4)
+                                               {
+                                                       case 0x6:       /* Loop */
+                                                               eff = 0x16;
+                                                               effval = note->EffNum & 0x0F;
+                                                               break;
+                                                       case 0xC:       /* Note Cut */
+                                                               eff = 0x18;
+                                                               break;
+                                                       case 0xE:       /* Replay line */
+                                                               eff = 0x1E;
+                                                               effval = note->EffNum & 0x0F;
+                                                               break;
+                                               }
+                                       }
+                                       else if (note->EffNum == EFF_PATTERNBREAK)
+                                               effval = 0;
+                                       else
+                                               effval = note->EffVal;
+
+                               //      if (note->EffNum == EFF_SETTEMPO)
+                               //              effval /= 4;
+
+                                       if (mmd_type)   /* MMD1 and up */
+                                       {
+                                               mednote = effval |
+                                                       ((ULONG)eff << 8) |
+                                                       ((ULONG)(note->Inst & 0x3F) << 16) |
+                                                       ((ULONG)((note->Note ? (note->Note - 12) : 0) & 0x7F) << 24);
+
+                                               if (Write (fp, &mednote, MMD1ROW) != MMD1ROW)
+                                                       return ERR_READWRITE;
+                                       }
+                                       else
+                                       {
+                                               if ((eff > 0x0F) && !quiet)
+                                               {
+                                                       ShowMessage (MSG_WRONG_EFFECT_IN_MMD0, eff);
+                                                       quiet = TRUE;
+                                               }
+
+                                               mednote = (effval |
+                                                       ((ULONG)(eff & 0x0F) << 8) |
+                                                       ((ULONG)(note->Inst & 0x0F) << 12) |
+                                                       ((ULONG)((note->Note ? (note->Note - 12) : 0) & 0x3F) << 16) |
+                                                       ((ULONG)(note->Inst & 0x20) << 17) |
+                                                       ((ULONG)(note->Inst & 0x10) << 19)) << 8;
+
+                                               if (Write (fp, &mednote, MMD0ROW) != MMD0ROW)
+                                                       return ERR_READWRITE;
+                                       }
+                               }
+                       }
+
+                       if (mmd_type)
+                       {
+                               if (patt[i].PattName[0])
+                               {
+                                       /*****************************/
+                                       /* Write BlockInfo structure */
+                                       /*****************************/
+
+                                       struct BlockInfo blockinfo;
+
+                                       blockinfoptr += sizeof (blockinfo);
+
+                                       memset (&blockinfo, 0, sizeof (blockinfo));
+                                       blockinfo.blockname = (UBYTE *)blockinfoptr;
+                                       blockinfo.blocknamelen = strlen (patt[i].PattName) + 1;
+
+                                       if (Write (fp, &blockinfo, sizeof (blockinfo)) != sizeof (blockinfo))
+                                               return ERR_READWRITE;
+
+                                       if (Write (fp, patt[i].PattName, blockinfo.blocknamelen) != blockinfo.blocknamelen)
+                                               return ERR_READWRITE;
+
+                                       blockinfoptr += sizeof (struct BlockInfo) + blockinfo.blocknamelen;
+
+                                       /* Pad to words */
+                                       if (blockinfoptr & 1)
+                                       {
+                                               UBYTE dummy;
+
+                                               if (Write (fp, &dummy, 1) != 1)
+                                                       return ERR_READWRITE;
+
+                                               blockinfoptr++;
+                                       }
+                               }
+                       }
+                       else    /* MMD0 */
+                       {
+                               /* Add a pad byte when length is not even */
+                               if ((patt[i].Lines * patt[i].Tracks) & 1)
+                               {
+                                       UBYTE dummy = 0;
+
+                                       if (Write (fp, &dummy, 1) != 1)
+                                               return ERR_READWRITE;
+                               }
+                       }
+               }       /* End for (si->NumPatterns) */
+       }
+
+
+       /**********************/
+       /* Write samples data */
+       /**********************/
+       {
+               struct InstrHdr instrhdr;
+               UBYTE *samp;
+               LONG l;
+               BOOL free_samp;
+
+               DisplayAction (MSG_WRITING_INSTDATA);
+
+               for (i = 1; i <= lastinstr; i++)
+               {
+                       free_samp = FALSE;
+
+                       l = inst[i].Length;
+                       if (!l || (!inst[i].SampleData)) continue;
+
+                       if (DisplayProgress (i, lastinstr))
+                               return ERROR_BREAK;
+
+                       if (halve_instr)
+                       {
+                               if (samp = AllocVec (l, MEMF_ANY))
+                               {
+                                       /* Halve Volume */
+                                       for (k = 0; k < l; k++)
+                                               samp[k] = inst[i].SampleData[k] >> 1;
+
+                                       free_samp = TRUE;       /* Free this buffer when you are done */
+                               }
+                               else
+                               {
+                                       ShowMessage (MSG_NO_MEM_TO_HALVE, i);
+                                       samp = inst[i].SampleData;
+                               }
+                       }
+                       else samp = inst[i].SampleData;
+
+                       instrhdr.length = l;
+                       instrhdr.type = SAMPLE;
+
+                       if (Write (fp, &instrhdr, sizeof (instrhdr)) != sizeof (instrhdr))
+                               return ERR_READWRITE;
+
+                       if (l)
+                               if (Write (fp, samp, l) != l)
+                                       return ERR_READWRITE;
+
+                       if (free_samp)
+                               FreeVec (samp);
+               }
+       }
+
+       return RETURN_OK;
+}
diff --git a/Hooks/Makefile b/Hooks/Makefile
new file mode 100644 (file)
index 0000000..52f10f9
--- /dev/null
@@ -0,0 +1,47 @@
+##
+## Make the stuff in the Hooks/ subdirectory
+##
+
+include /config.mk
+
+ALL_HOOKS              := oktalyzer.xmhook screamtracker.xmhook
+
+HOOK_CFLAGS            := CODE=NEAR DATA=FARONLY STRSECT=CODE
+
+OKTA_VERSION   := 1
+OKTA_REVISION  := 0
+
+S3M_VERSION            := 1
+S3M_REVISION   := 0
+
+
+all: $(ALL_HOOKS)
+
+clean:
+       -Delete *.map *.o *.gst *.xref *.xmhook
+
+.PHONY: all clean
+
+
+OktalyzerHookInit.o: $(TOP)/RomTag.asm
+       $(AS) $(ASFLAGS) $(TOP)/RomTag.asm TO $@ \
+               SET "LIBVERSION=$(OKTA_VERSION),LIBREVISION=$(OKTA_REVISION),XMHOOK"
+
+OktalyzerHook.o: OktalyzerHook.c
+       $(CC) OktalyzerHook.c $(CFLAGS) $(HOOK_CFLAGS)
+
+oktalyzer.xmhook: OktalyzerHook.o OktalyzerHookInit.o
+       $(LD) $(LDFLAGS) FROM OktalyzerHookInit.o OktalyzerHook.o $(LIBS) TO $@
+       @Protect $@ -e
+
+
+ScreamTrackerHookInit.o: $(TOP)/RomTag.asm
+       $(AS) $(ASFLAGS) $(TOP)/RomTag.asm TO $@ \
+               SET "LIBVERSION=$(S3M_VERSION),LIBREVISION=$(S3M_REVISION),XMHOOK"
+
+ScreamTrackerHook.o: ScreamTrackerHook.c
+       $(CC) ScreamTrackerHook.c $(CFLAGS) $(HOOK_CFLAGS)
+
+screamtracker.xmhook: ScreamTrackerHook.o ScreamTrackerHookInit.o
+       $(LD) $(LDFLAGS) FROM ScreamTrackerHookInit.o ScreamTrackerHook.o $(LIBS) TO $@
+       @Protect $@ -e
diff --git a/Hooks/OctaMed.h b/Hooks/OctaMed.h
new file mode 100644 (file)
index 0000000..2073f61
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+**     OctaMed.h
+**
+**     Copyright (C) 1994,95 Bernardo Innocenti
+**
+**     Use 4 chars wide TABs to read this source
+**
+**     Structure definition for MED/OctaMED file formats.
+**  Based on Teijo Kinnunen's format description rev 2 (30.5.93)
+*/
+
+
+struct MMD0sample
+{
+       UWORD rep, replen;      /* offs: 0(s), 2(s) */
+       UBYTE midich;           /* offs: 4(s) */
+       UBYTE midipreset;       /* offs: 5(s) */
+       UBYTE svol;                     /* offs: 6(s) */
+       BYTE strans;            /* offs: 7(s) */
+};
+
+
+struct MMD0
+{
+       ULONG   id;
+       ULONG   modlen;
+       struct MMD0song *song;
+       ULONG   reserved0;
+       struct MMD0Block **blockarr;
+       ULONG   reserved1;
+       struct InstrHdr **smplarr;
+       ULONG   reserved2;
+       struct MMD0exp *expdata;
+       ULONG   reserved3;
+       UWORD   pstate;                 /* some data for the player routine */
+       UWORD   pblock;
+       UWORD   pline;
+       UWORD   pseqnum;
+       WORD    actplayline;
+       UBYTE   counter;
+       UBYTE   extra_songs;    /* number of songs - 1 */
+}; /* length = 52 bytes */
+
+
+
+struct MMD0song
+{
+       struct MMD0sample sample[63];
+       UWORD   numblocks;
+       UWORD   songlen;                /* NOTE: number of sections in MMD2 */
+       union {
+               UBYTE   playseq[256];   /* MMD0/MMD1 only       */
+               struct {                                /* MMD2 only            */
+                       struct PlaySeq  **playseqtable;
+                       UWORD   *sectiontable;  /* UWORD section numbers */
+                       UBYTE   *trackvols; /* UBYTE track volumes */
+                       UWORD   numtracks;  /* max. number of tracks in the song (also
+                                                                * the number of entries in 'trackvols' table)
+                                                                */
+                       UWORD   numpseqs;       /* number of PlaySeqs in 'playseqtable' */
+                       UBYTE   pad0[240];      /* reserved for future expansion                */
+               };
+       };
+       UWORD   deftempo;
+       BYTE    playtransp;
+       UBYTE   flags;
+       UBYTE   flags2;
+       UBYTE   tempo2;
+       UBYTE   trkvol[16];             /* Unused in MMD2 */
+       UBYTE   mastervol;
+       UBYTE   numsamples;
+}; /* length = 788 bytes */
+
+
+struct PlaySeq {
+       char    name[32];               /* 31 chars + \0 */
+       ULONG   reserved[2];    /* for possible extensions */
+       UWORD   length;                 /* # of entries */
+       UWORD   seq[0];                 /* block numbers.. */
+};
+
+
+/* Flags for MMD0song->Flags */
+
+#define        FLAG_FILTERON   0x1             /* hardware low-pass filter */
+#define        FLAG_JUMPINGON  0x2             /* jumping.. */
+#define        FLAG_JUMP8TH    0x4             /* jump 8th.. */
+#define        FLAG_INSTRSATT  0x8             /* instruments are attached (sng+samples)
+                                                                  used only in saved MED-songs */
+#define        FLAG_VOLHEX             0x10    /* volumes are represented as hex */
+#define FLAG_STSLIDE   0x20    /* no effects on 1st timing pulse (STS) */
+#define FLAG_8CHANNEL  0x40    /* OctaMED 8 channel song, examine this bit
+                                                                  to find out which routine to use */
+
+/* Flags for MMD0song->Flags2 */
+
+#define FLAG2_BMASK            0x1F    /* (bits 0-4) BPM beat length (in lines)
+                                                                       0 = 1 line, $1F = 32 lines.0
+                                                                       (The rightmost slider in OctaMED Pro
+                                                                       BPM mode.) */
+#define FLAG2_BPM              0x20    /* BPM mode on */
+
+
+
+struct MMD0block
+{
+       UBYTE   numtracks,
+                       lines;
+};
+
+
+
+struct MMD1block
+{
+       UWORD   numtracks;
+       UWORD   lines;
+       struct BlockInfo *info;
+};
+
+
+
+struct BlockInfo
+{
+       ULONG   *hlmask;
+       UBYTE   *blockname;
+       ULONG   blocknamelen;
+       ULONG   reserved[6];
+};
+
+
+
+struct InstrHdr {
+       ULONG   length;
+       WORD    type;
+       /* Followed by actual data */
+};
+
+
+/* Values for InstrHdr.type */
+
+#define        HYBRID          -2
+#define        SYNTHETIC       -1
+#define        SAMPLE          0       /* ordinary 1 octave sample */
+#define        IFF5OCT         1       /* 5 octaves    */
+#define        IFF3OCT         2       /* 3 octaves    */
+
+/* The following ones are recognized by OctaMED Pro only */
+#define        IFF2OCT         3       /* 2 octaves    */
+#define        IFF4OCT         4       /* 4 octaves    */
+#define        IFF6OCT         5       /* 6 octaves    */
+#define        IFF7OCT         6       /* 7 octaves    */
+
+
+
+struct SynthInstr {
+       ULONG   length;         /* length of this struct        */
+       WORD    type;           /* -1 or -2                                     */
+       UBYTE   defaultdecay;
+       UBYTE   reserved[3];
+       UWORD   rep;
+       UWORD   replen;
+       UWORD   voltbllen;
+       UWORD   wftbllen;
+       UBYTE   volspeed;
+       UBYTE   wfspeed;
+       UWORD   wforms;
+       UBYTE   voltbl[128];
+       UBYTE   wftbl[128];
+       struct SynthWF *wf[64];
+};
+
+
+
+struct InstrExt {
+       UBYTE   hold;                           /* hold/decay values of the instrument  */
+       UBYTE   decay;
+       UBYTE   suppress_midi_off;      /* 0 (FALSE) or not (TRUE)                              */
+       BYTE    finetune;                       /* instrument finetune (-8-+7)                  */
+       /* length = 4 bytes */
+
+       /* Below fields saved by >= V5 */
+       UBYTE default_pitch;
+       UBYTE instr_flags;
+       UWORD long_midi_preset;
+       /* length = 8 bytes */
+
+       /* Below fields saved by >= V5.02 */
+       UBYTE output_device;
+       UBYTE reserved;
+       /* length = 10 bytes */
+};
+
+
+
+struct MMDInstrInfo {
+       UBYTE   name[40];                       /* null-terminated instrument name.     */
+};     /* length = 40 bytes */
+
+
+
+struct MMD0exp {
+       struct MMD0 *nextmod;           /* Pointer to the next module (or zero).        */
+       struct InstrExt *exp_smp;
+       UWORD    s_ext_entries;         /* The size of InstrExt structure array
+                                                                * (i.e. the number of InstrExt structures).
+                                                                */
+       UWORD    s_ext_entrsz;          /* Size of each InstrExt structure.                     */
+       UBYTE   *annotxt;                       /* Pointer to the null-terminated annotation text.              */
+       ULONG    annolen;                       /* Length of 'annotxt', including the terminating \0.   */
+       struct MMDInstrInfo *iinfo;
+       UWORD    i_ext_entries;         /* Size of the MMDInstrInfo struct array
+                                                                * (i.e. the number of MMDInstrInfo structures).
+                                                                */
+       UWORD    i_ext_entrsz;          /* Size of each MMDInstrInfo struct                     */
+       ULONG    jumpmask;                      /* OBSOLETE                                                                     */
+       UWORD   *rgbtable;                      /* Pointer to eight UWORDs (screen colors)
+                                                                * to be passed to LoadRGB4() routine.
+                                                                */
+       UBYTE    channelsplit[4];       /* this longword is divided to four boolean bytes,
+                                                                * controlling channel splitting in OctaMED 5 - 8 chnl
+                                                                * modes. (A non-zero byte means that the channel is
+                                                                * NOT splitted.) Currently only the following
+                                                                * combinations should be used:
+                                                                *
+                                                                * 0x00000000 (8 channels (or normal 4 channel mode))
+                                                                * 0x000000FF (7 channels)
+                                                                * 0x0000FFFF (6 channels)
+                                                                * 0x00FFFFFF (5 channels)
+                                                                */
+       struct NotationInfo *n_info;/* pointer to NotationInfo structure (used only in
+                                                                * OctaMED V2.0 and later). It contains some info for
+                                                                * the notation editor.
+                                                                */
+       UBYTE   *songname;                      /* song name of the current song (0-terminated).
+                                                                * Each song of a multi-module can have a different
+                                                                * name.
+                                                                */
+
+       ULONG    songnamelen;           /* song name length (including the \0).         */
+       struct MMDDumpData *dumps;
+       ULONG    reserved2[7];          /* future expansion fields, that MUST be zero now.      */
+};
+
+
+
+struct NotationInfo
+{
+       UBYTE   n_of_sharps;    /* number of sharps or flats (0 - 6).   */
+       UBYTE   flags;                  /* See below.                                                   */
+       WORD    trksel[5];              /* The number of the selected track,
+                                                        * for each display preset
+                                                        * (-1 = no track selected)
+                                                        */
+       UBYTE   trkshow[16];    /* tracks shown (five bits used in
+                                                        * each byte, bit #0 = preset 1, etc.)
+                                                        */
+       UBYTE   trkghost[16];   /* tracks ghosted (as in 'trkshow')             */
+       BYTE    notetr[63];             /* note transpose value for each
+                                                        * instrument (-24 - +24). If bit 6 is
+                                                        * negated, the instrument is hidden.
+                                                        */
+       UBYTE   pad;
+};
+
+/* flags for NotationInfo->flags */
+#define NFLG_FLAT 1
+#define NFLG_3_4  2
+
+
+/* MIDI dump data (created using OctaMED Pro MIDI
+ * message editor).
+ */
+
+struct MMDDumpData {
+       UWORD   numdumps;
+       UWORD   reserved[3];
+       /* Immediately after this struct, there are 'numdumps'
+        * pointers to MMDDump structures.
+        */
+};
+
+struct MMDDump {
+       ULONG   length;         /* length of the MIDI message dump.                     */
+       UBYTE   *data;          /* pointer to the actual MIDI dump data.        */
+       UWORD   ext_len;        /* MMDDump struct extension length.                     */
+
+       /*      (if ext_len >= 20, the following fields exist)  */
+       UBYTE   name[20];       /* name of the dump.                                            */
+};
+
+
+
+#define MMD0ROW        3                       /* Size of an MMD0 pattern row */
+#define MMD1ROW        4                       /* Size of an MMD1/MMD2 pattern row */
+
+#define ID_MMD0        0x4D4D4430
+#define ID_MMD1 0x4D4D4431
+#define ID_MMD2 0x4D4D4432
diff --git a/Hooks/OktalyzerHook.c b/Hooks/OktalyzerHook.c
new file mode 100644 (file)
index 0000000..85e4f60
--- /dev/null
@@ -0,0 +1,841 @@
+/*
+**     OktalyzerHook.c
+**
+**     Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
+**
+**     Originally based on Gerardo Iula's Tracker sources.
+**
+**     External hook for Oktalyzer 1.1-1.57 module format.
+*/
+
+
+#include <exec/memory.h>
+#include <dos/stdio.h>
+#include <libraries/xmodule.h>
+#include <libraries/songclass.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Local functions prototypes */
+
+HOOKCALL APTR _GetEngine (
+       REG(a6, struct Library *MyBase));
+HOOKCALL APTR _SetupXMHook (
+       REG(a0, struct XModuleBase *_XModuleBase),
+       REG(a6, struct Library *mybase));
+static HOOKCALL struct XMHook *IdentifyOktalyzer (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags));
+static HOOKCALL LONG LoadOktalyzer (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *loader),
+       REG(a2, ULONG *tags));
+static HOOKCALL LONG SaveOktalyzer (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *saver),
+       REG(a2, ULONG *tags));
+
+INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval, UWORD patt, UWORD line, UWORD track);
+
+
+
+#define OKT_MODE4      1       /* Mode 4: play 8 bit instruments */
+#define OKT_MODE8      0       /* Mode 8: play 7 bit instruments */
+#define OKT_MODEB      2       /* Mode B: play both 8 & 7 bit instruments */
+
+
+/* Oktalyzer chunk IDs */
+
+#define ID_OKTA 'OKTA'
+#define ID_CMOD 'CMOD'
+#define ID_SAMP        'SAMP'
+#define ID_SPEE        'SPEE'
+#define ID_SLEN        'SLEN'
+#define ID_PLEN        'PLEN'
+#define ID_PBOD 'PBOD'
+#define ID_SBOD 'SBOD'
+
+#ifndef ID_SONG
+#define ID_SONG 'SONG'
+#endif /* !ID_SONG */
+
+#ifndef ID_PATT
+#define ID_PATT 'PATT'
+#endif /* !ID_PATT */
+
+
+
+/* Library data */
+
+const UBYTE LibName[] = "oktalyzer.xmhook";
+const UBYTE LibVer[] = { '$', 'V', 'E', 'R', ':', ' ' };
+const UBYTE LibId[] = "oktalyzer.xmhook 1.0 (28.1.96) © 1993-96 by Bernardo Innocenti";
+
+
+
+/* Effects conversion table
+ * Originally based on Gerardo Iula's "Tracker" source.
+ */
+static const UBYTE Effects[MAXTABLEEFFECTS] =
+{
+/*  OKTA               XModule                 Val */
+
+       0x00,   /*      Null effect             $00     */
+
+       0x01,   /*      Portamento Up   $01     */
+       0x02,   /*      Portamento Down $02     */
+       0x00,   /*      Tone Portamento $03     */
+       0x00,   /*      Vibrato                 $04     */
+       0x00,   /*      ToneP + VolSl   $05     */
+       0x00,   /*      Vibra + VolSl   $06     */
+       0x00,   /*      Tremolo                 $07     */
+       0x00,   /*      Set Hold/Decay  $08     */
+       0x00,   /*      Sample Offset   $09     */
+       0x1E,   /*      Volume Slide    $0A     */
+       0x19,   /*      Position Jump   $0B     */
+       0x1F,   /*      Set Volume              $0C     */
+       0x00,   /*      Pattern break   $0D     */
+       0x00,   /*      Misc                    $0E     */
+       0x1C,   /*      Set Speed               $0F     */
+       0x00,   /*      Set Tempo               $10     */
+       0x1A,   /*      Arpeggio                $11     */
+
+       0x11,   /*      Oktalyzer H             */
+       0x15    /*      Oktalyzer L             */
+};
+
+
+
+/* Get around a SAS/C bug which causes some annoying warnings
+ * with the library bases defined below.  The problem happens
+ * when the GST has been compiled with the CODE=FAR switch and
+ * the source is being compiled with CODE=NEAR.
+ */
+#ifdef __SASC
+       #pragma msg 72 ignore push
+#endif /* __SASC */
+
+struct ExecBase                        *SysBase                = NULL;
+struct XModuleBase             *XModuleBase    = NULL;
+struct DosLibrary              *DOSBase                = NULL;
+struct IntuitionBase   *IntuitionBase  = NULL;
+
+#ifdef __SASC
+#pragma msg 72 pop
+#endif /* __SASC */
+
+
+
+static HOOKCALL struct XMHook *IdentifyOktalyzer (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags))
+
+/* Determine if the given file is an Oktalyzer module.
+ * Note: the file position will be changed on exit.
+ */
+{
+       ULONG id[3];
+
+       Seek (fh, 0, OFFSET_BEGINNING);
+       if (FRead (fh, &id, 12, 1) != 1)
+               return NULL;
+
+       if ((id[0] == ID_OKTA) && (id[1] == ID_SONG) && (id[2] == ID_CMOD))
+               return loader;
+
+       return NULL;
+}
+
+
+
+INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval, UWORD patt, UWORD line, UWORD track)
+
+/* Inputs: old effect & old type.
+ * Output: new effect in requested newtype.
+ */
+{
+       UBYTE i;
+
+       for ( i = 0 ; i < MAXTABLEEFFECTS ; i++ )
+               if (eff == Effects[i])
+                       return i;
+
+       xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
+               (APTR)MSG_UNKNOWN_EFF, 5, eff, patt, track, line);
+       return 0;
+}
+
+
+
+static HOOKCALL LONG LoadOktalyzer (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *loader),
+       REG(a2, ULONG *tags))
+{
+       struct Instrument       *instr;
+       struct Pattern          *patt;
+       ULONG i, j, k;  /* Loop counters        */
+       ULONG size;             /* Read buffer          */
+       ULONG l;                /* Read buffer          */
+       UWORD voices, numpatts, songlen, instr_mode[37];
+       UWORD w;
+       UBYTE oktanote[4];
+
+
+       /* Check file header OKTASONGCMOD */
+       {
+               LONG id[3];
+
+               if (FRead (fh, id, 12, 1) != 1) return ERROR_IOERR;
+               if ((id[0] != ID_OKTA) || (id[1] != ID_SONG) || (id[2] != ID_CMOD))
+                       return ERROR_NOTMODULE;
+       }
+
+
+       /* TODO: set si->SongName */
+
+
+       /* CMOD Chunk size ($0000 0008) */
+       Seek (fh, 4, OFFSET_CURRENT); /* Skip 4 bytes */
+
+       voices = 4;             /* Set minimum voices and check others  */
+       for (i = 0 ; i < 4 ; i++)
+       {
+               if (FRead (fh, &w, 2, 1) != 1) return ERROR_IOERR;
+               if (w) voices++;
+       }
+
+       /* Get Sample Name, Length, Repeat Replen, Volume for each instr. */
+
+       /* Check header SAMP */
+       if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+       if (l != ID_SAMP) return ERROR_NOTMODULE;
+
+       /* SAMP Chunk Size ($0000 0480) */
+       Seek (fh, 4, OFFSET_CURRENT); /* Skip 4 bytes */
+
+       xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
+               (APTR)MSG_READING_INSTS_INFO, NULL);
+
+       /* Read in 36 instruments */
+       for ( j = 1 ; j <= 36 ; j++ )
+       {
+               struct
+               {
+                       UBYTE   name[20];
+                       ULONG   size;
+                       UWORD   repeat,
+                                       replen,
+                                       volume,
+                                       mode;
+               } oktainstr;
+
+               /* Get instrument data */
+               if (FRead (fh, &oktainstr, sizeof (oktainstr), 1) != 1)
+                       return ERROR_IOERR;
+
+               oktainstr.name[19] = '\0';
+
+               /* Oktalyzer sometimes saves odd lengths for instruments,
+                * but the data saved in SBOD is always rounded _down_ to an even
+                * number!  It took me two weeks to find and kill this bug :-(
+                */
+               oktainstr.size &= (~1);
+
+               /* Oktalyzer instrument modes:
+                *      mode 8 $00 7 bit instruments
+                *      mode 4 $01 normal instruments
+                *      mode B $02 both 8bit & 7bit
+                */
+               instr_mode[j] = oktainstr.mode;
+
+               if (oktainstr.size)
+                       if (!(xmAddInstrument (si, j,
+                               INSTRA_Name,    oktainstr.name,
+                               INSTRA_Length,  oktainstr.size,
+                               INSTRA_Repeat,  oktainstr.repeat << 1,
+                               INSTRA_Replen,  oktainstr.replen << 1,
+                               INSTRA_Volume,  oktainstr.volume,
+                               TAG_DONE)))
+                               return ERROR_NO_FREE_STORE;
+       }
+
+
+       /* Get global song speed */
+
+       /* Check speed header "SPEE" */
+       if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+       if (l != ID_SPEE) return ERROR_NOTMODULE;
+
+       /* SPEE Chunk size ($0000 0002) */
+       if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+       /* Get Song Global Speed */
+       if (FRead (fh, &w, 2, 1) != 1) return ERROR_IOERR;
+
+       SetAttrs (si,
+               SNGA_GlobalSpeed, w,
+               TAG_DONE);
+
+
+       /* Get number of patterns */
+
+       /* Check header SLEN */
+       if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+       if (l != ID_SLEN) return ERROR_NOTMODULE;
+
+       /* SLEN Chunk size ($0000 0002) */
+       if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+       /* Get number of patterns */
+       if (FRead (fh, &numpatts, 2, 1) != 1) return ERROR_IOERR;
+
+       /* Check value */
+       if (numpatts > MAXPATTERNS)
+       {
+               xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
+                       (APTR)MSG_SONG_HAS_TOO_MANY_PATT, NULL);
+               numpatts = MAXPATTERNS - 1;
+       }
+
+
+       /* Get number of positions in song (Length) */
+
+       /* Check header PLEN */
+       if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+       if (l != ID_PLEN) return ERROR_NOTMODULE;
+
+       /* Chunk size ($0000 0002) */
+       if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+       /* Get number of patterns */
+       if (FRead (fh, &songlen, 2, 1) != 1) return ERROR_IOERR;
+
+       /* Check value */
+       if (songlen > MAXPOSITIONS)
+       {
+               xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
+                       (APTR)MSG_SONG_TOO_LONG, NULL);
+               songlen = MAXPOSITIONS;
+       }
+
+
+       /* Get position table */
+
+       /* Check header PATT */
+       if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+       if (l != ID_PATT) return ERROR_NOTMODULE;
+
+       /* PATT Chunk size ($0000 0080) */
+       if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+
+       /* Read song sequence */
+       {
+               UBYTE postable[128];
+
+               if (FRead (fh, postable, 1, 128) != 128) return ERROR_IOERR;
+
+               if (!(xmSetSongLen (si, songlen)))
+                       return ERROR_NO_FREE_STORE;
+
+               for (i = 0; i < si->Length; i++)
+                       si->Sequence[i] = postable[i];
+       }
+
+
+       /* Get pattern bodies and convert them */
+
+       xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
+               (APTR)MSG_READING_PATTS, NULL);
+
+       for (j = 0; j < numpatts; j++)
+       {
+               if (xmDisplayProgress (j + 1, numpatts))
+                       return ERROR_BREAK;
+
+               /* Check header "PBOD" */
+               if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+               if (l != ID_PBOD) return ERROR_NOTMODULE;
+
+               /* Skip Chunk Length (Lines * Tracks * 4 + 2) */
+               if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+               /* Get pattern length */
+               if (FRead (fh, &w, 2, 1) != 1) return ERROR_IOERR;
+
+               /* Allocate memory for all tracks */
+               if (!(patt = xmAddPattern (si,
+                       PATTA_Tracks,   voices,
+                       PATTA_Lines,    w,
+                       TAG_DONE)))
+                       return ERROR_NO_FREE_STORE;
+
+               for ( k = 0 ; k < patt->Lines ; k++)
+               {
+                       for (i = 0; i < voices; i++)
+                       {
+                               struct Note *n = &patt->Notes[i][k];
+
+                               /* Read a whole track row */
+                               if (FRead (fh, oktanote, 4, 1) != 1) return ERROR_IOERR;
+
+                               /* Convert Note:
+                                *
+                                * Oktalyzer supports 3 octaves (1 to 3).
+                                * Notes are numbered from 1 (C-1) to 36 (B-3).
+                                * 0 means no note.
+                                */
+                               if (oktanote[0] <= 36)
+                                       n->Note = (oktanote[0] ? (oktanote[0] + 12) : 0);       /* Add one octave */
+                               else
+                                       xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
+                                               (APTR)MSG_INVALID_NOTE, oktanote[0], j, i, k);
+
+                               /* Store Instrument Number */
+                               n->Inst = (n->Note ? (oktanote[1] + 1) : 0);
+
+                               /* Convert Effect */
+                               n->EffNum = DecodeEff (oktanote[2], oktanote[3], j, k, i);
+
+                               /* Store Effect Value */
+                               n->EffVal = oktanote[3];
+
+                               /* Effect Exceptions */
+                               if (n->EffNum == EFF_MISC)
+                               {
+                                       /* Oktalyzer has SetFilter values inverted! */
+                                       if ((n->EffVal >> 4) == 1)
+                                               n->EffVal = 0x10 | (n->EffVal & 0x0F ? 0 : 1);
+                               }
+                       }       /* end for (i) */
+               }       /* End for (k) */
+       }       /* End for (j) */
+
+
+       /* Load instruments */
+
+       xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
+               (APTR)MSG_READING_INSTS, NULL);
+
+       for (j = 1 ; j <= 36 ; j++)
+       {
+               BYTE *sample;
+
+               if (!(instr = si->Instr[j])) continue;
+
+               if (xmDisplayProgress(j, 36))
+                       return ERROR_BREAK;
+
+               /* Check header SBOD */
+               if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+               if (l != ID_SBOD) return ERROR_NOTMODULE;
+
+               /* Read chunk size (length of instrument) & check it */
+               if (FRead (fh, &size, 4, 1) != 1) return ERROR_IOERR;
+               if (size != instr->Length) return ERROR_NOTMODULE;
+
+               if (!(sample = AllocVec (instr->Length, MEMF_SAMPLE)))
+                       return ERROR_NO_FREE_STORE;
+
+               /* Read instrument body */
+               if (FRead (fh, sample, 1, instr->Length) != instr->Length)
+               {
+                       FreeVec (sample);
+                       return ERROR_IOERR;
+               }
+
+               /* Double sample data */
+               if (instr_mode[j] == OKT_MODE8 || instr_mode[j] == OKT_MODEB)
+               {
+                       for (i = 0; i < instr->Length; i++)
+                               *sample++ <<= 1;
+               }
+
+               xmSetInstrument (si, j,
+                       INSTRA_Sample,  sample,
+                       TAG_DONE);
+       }
+
+       /* Check for extra data following the module */
+       if (FGetC (fh) != ENDSTREAMCH)
+               xmDisplayMessageA (XMDMF_NOTE | XMDMF_USECATALOG,
+                       (APTR)MSG_EXTRA_DATA_AFTER_MOD, NULL);
+
+       return 0;
+}
+
+
+
+static HOOKCALL LONG SaveOktalyzer (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *saver),
+       REG(a2, ULONG *tags))
+{
+       struct Instrument *instr;
+       ULONG   i, j, k;
+       UWORD   voices, instr_mode, songlen;
+       ULONG   l;              /* Write buffers */
+       UWORD   w;
+       UBYTE   oktanote[4];
+
+       /* Check number of tracks and fix data */
+       voices = si->MaxTracks;
+       if (voices < 4) voices = 4;
+       if (voices > 8) voices = 8;
+
+
+       /* Oktalyzer does not support pattern break (D) command */
+
+       xmProcessSong (si, NULL,
+               XMSNG_Optimize, XMOF_CUT_PATTERNS,
+               TAG_DONE);
+
+
+       /* Write file header */
+       if (FWrite (fh, "OKTASONGCMOD", 12, 1) != 1) return ERROR_IOERR;
+
+
+       /* Write maximum number of tracks */
+       l = 8;
+       if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+       /* Write active tracks (only 5 to 8: tracks 1..4 are always on)
+        * TODO: Ask user what tracks should be made active, or loadnig and
+        * saving back the same module will result in the lost of the original
+        * track order.
+        */
+       for (i = 5 ; i <= 8 ; i++)
+       {
+               if (voices >= i) w = 1;
+                       else w = 0;
+               if (FWrite (fh, &w, 2, 1) != 1) return ERROR_IOERR;
+       }
+
+       /* Choose mode for instruments.
+        * When the module is 4 channels, we can always use mode 4.
+        * On modules with 5-7 channels, it is hard to guess which instruments
+        * could be made mode 4, 8 or B, so we always choose mode B.
+        * for 8 channels modules, we just use mode 8 for all instruments.
+        */
+       switch (voices)
+       {
+               case 4:
+                       instr_mode = OKT_MODE4;
+                       break;
+
+               case 8:
+                       instr_mode = OKT_MODE8;
+                       break;
+
+               default:
+                       instr_mode = OKT_MODEB;
+       }
+
+
+
+       /* Write sample names, length, effects, volume */
+       xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
+               (APTR)MSG_WRITING_INSTINFO, NULL);
+
+       if (FWrite (fh, "SAMP", 4, 1) != 1) return ERROR_IOERR;
+
+       /* Write chunk length */
+       l = 0x480;
+       if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+       for ( j = 1 ; j <= 36 ; j++ )
+       {
+               struct
+               {
+                       UBYTE   name[20];
+                       ULONG   size;
+                       UWORD   repeat,
+                                       replen,
+                                       volume,
+                                       mode;
+               } oktainstr = { 0 };
+
+
+               if (instr = si->Instr[j])
+               {
+                       strncpy (oktainstr.name, instr->Name, 19);
+                       oktainstr.size          = instr->Length;
+                       oktainstr.repeat        = instr->Repeat >> 1;
+                       oktainstr.replen        = instr->Replen >> 1;
+                       oktainstr.volume        = instr->Volume;
+                       oktainstr.mode          = instr->Length ? instr_mode : 0;
+               }
+
+               /* Write instrument data */
+               if (FWrite (fh, &oktainstr, sizeof (oktainstr), 1) != 1)
+                       return ERROR_IOERR;
+       }
+
+
+       /* Write global song speed */
+
+       if (FWrite (fh, "SPEE", 4, 1) != 1) return ERROR_IOERR;
+       l = 2;
+       if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+       w = si->GlobalSpeed;
+       if (FWrite (fh, &w, 2, 1) != 1) return ERROR_IOERR;
+
+
+       if (FWrite (fh, "SLEN", 4, 1) != 1) return ERROR_IOERR;
+       l = 2;
+       if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+       w = si->NumPatterns;
+       if (FWrite (fh, &w, 2, 1) != 1) return ERROR_IOERR;
+
+
+       /* Write patterns number */
+
+       if (FWrite (fh, "PLEN", 4, 1) != 1) return ERROR_IOERR;
+       l = 2;
+       if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+       songlen = min (si->Length, 128);
+       if (FWrite (fh, &songlen, 2, 1) != 1) return ERROR_IOERR;
+
+
+       /* Write patterns sequence */
+
+       if (FWrite (fh, "PATT", 4, 1) != 1) return ERROR_IOERR;
+       l = 128;
+       if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+       {
+               UBYTE postable[128];
+
+               memset (postable, 0, 128);
+
+               for (i = 0; i < songlen; i++)
+                       postable[i] = si->Sequence[i];
+
+               if (FWrite (fh, postable, 1, 128) != 128) return ERROR_IOERR;
+       }
+
+
+       /* Write patterns */
+       xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
+               (APTR)MSG_WRITING_PATTS, NULL);
+
+       for ( j = 0 ; j < si->NumPatterns ; j++)
+       {
+               if (xmDisplayProgress (j + 1, si->NumPatterns))
+                       return ERROR_BREAK;
+
+               if (FWrite (fh, "PBOD", 4, 1) != 1) return ERROR_IOERR;
+               l = si->Patt[j]->Lines * si->MaxTracks * 4 + 2;
+               if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+               /* Write pattern length (WORD) */
+               w = si->Patt[j]->Lines;
+               if (FWrite (fh, &w, 2, 1) != 1) return ERROR_IOERR;
+
+               for (k = 0 ; k < si->Patt[j]->Lines ; k++)
+               {
+                       for ( i = 0 ; i < voices ; i++)
+                       {
+                               struct Note *n = &(si->Patt[j]->Notes[i][k]);
+
+                               if (n->Note)
+                               {
+                                       if (n->Note < 13)
+                                       {
+                                               xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
+                                                       (APTR)MSG_NOTE_TOO_LOW, j, i, k);
+                                               oktanote[0] = n->Note;
+                                       }
+                                       else
+                                       {
+                                               if (n->Note > 48)
+                                               {
+                                                       xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
+                                                               (APTR)MSG_NOTE_TOO_HIGH, j, i, k);
+                                                       oktanote[0] = n->Note - 24;
+                                               }
+                                               else
+                                                       oktanote[0] = n->Note - 12;
+                                       }
+                               }
+                               else oktanote[0] = 0;
+
+                               oktanote[1] = (n->Inst ? (n->Inst-1) : 0);
+                               oktanote[2] = Effects[n->EffNum];
+
+                               /* Effect Exceptions */
+                               switch (n->EffNum)
+                               {
+                                       case EFF_MISC:
+                                               if ((n->EffVal >> 4) == 1)      /* Filter On/Off */
+                                                       oktanote[3] = (n->EffVal ? 0 : 1);
+                                               break;
+
+                                       case EFF_SETSPEED:
+                                               if (n->EffVal > 0x0F)
+                                                       oktanote[3] = 0x0F;
+                                               else
+                                                       oktanote[3] = n->EffVal;
+                                               break;
+
+                                       default:
+                                               oktanote[3] = n->EffVal;
+                                               break;
+                               }
+
+                               if (FWrite (fh, oktanote, 4, 1) != 1) return ERROR_IOERR;
+
+                       }       /* end for(i) */
+               }       /* End for(k) */
+       }       /* End for(j) */
+
+
+       /* Write Instruments Data */
+
+       xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
+               (APTR)MSG_WRITING_INSTDATA, NULL);
+
+       for (j = 1 ; j <= 36 ; j++)
+       {
+               BYTE *samp;
+               BOOL free_samp = FALSE;
+
+               /* Skip empty instruments slots */
+
+               if (!(instr = si->Instr[j]))
+                       continue;
+               if (!(l = instr->Length))
+                       continue;
+
+               if (xmDisplayProgress(j, 36))
+                       return ERROR_BREAK;
+
+
+               if (instr_mode == OKT_MODE8 || instr_mode == OKT_MODEB)
+               {
+                       if (samp = AllocVec (l, MEMF_ANY))
+                       {
+                               /* Halve volume */
+                               for (i = 0; i < l; i++)
+                                       samp[i] = instr->Sample[i] >> 1;
+
+                               free_samp = TRUE;       /* Free this when done */
+                       }
+                       else
+                       {
+                               xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                                       (APTR)MSG_NO_MEM_TO_HALVE, j);
+                               samp = instr->Sample;
+                       }
+               }
+               else samp = instr->Sample;
+
+
+               if (!samp) l = 0;
+
+               if (FWrite (fh, "SBOD", 4, 1) != 1) return ERROR_IOERR;
+               if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+               if (l)
+               {
+                       Flush (fh);
+                       if (Write (fh, samp, l) != l)
+                               return ERROR_IOERR;
+               }
+
+               if (free_samp) FreeVec (samp);
+       }
+
+       return 0;
+}
+
+
+
+HOOKCALL struct Library * _UserLibInit (REG(a6, struct Library *mybase))
+{
+       return mybase;
+}
+
+
+
+HOOKCALL struct Library * _UserLibCleanup (REG(a6, struct Library *mybase))
+{
+       return mybase;
+}
+
+
+
+HOOKCALL APTR _GetEngine (REG(a6, struct Library *mybase))
+{
+       return NULL;
+}
+
+
+
+HOOKCALL APTR _SetupXMHook (
+       REG(a0, struct XModuleBase *_XModuleBase),
+       REG(a6, struct Library *mybase))
+{
+       if (!(strcmp (_XModuleBase->xm_Library.lib_Node.ln_Name, "xmodule.library")))
+       {
+               SysBase                 = *((struct ExecBase **)4);
+               XModuleBase             = _XModuleBase;
+               IntuitionBase   = XModuleBase->xm_IntuitionBase;
+               DOSBase                 = XModuleBase->xm_DOSBase;
+
+               xmAddHook (
+                       XMHOOK_Type,                    NT_XMLOADER,
+                       XMHOOK_Name,                    (LONG)"Oktalyzer",
+                       XMHOOK_Priority,                0,
+                       XMHOOK_Descr,                   (LONG)"Oktalyzer 1.57",
+                       XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+                       XMHOOK_ID,                              ID_OKTA,
+                       XMHOOK_LibraryBase,             mybase,
+                       XMHOOK_LoadModFunc,             LoadOktalyzer,
+                       XMHOOK_IdentifyModFunc, IdentifyOktalyzer,
+                       XMHOOK_MaxTracks,               8,
+                       XMHOOK_MaxPatterns,             64,
+                       XMHOOK_MaxInstruments,  36,
+                       XMHOOK_MaxLength,               128,
+                       XMHOOK_MaxSampleLen,    131070,
+                       XMHOOK_MaxPattLen,              128,
+                       TAG_DONE);
+
+               xmAddHook (
+                       XMHOOK_Type,                    NT_XMSAVER,
+                       XMHOOK_Name,                    (LONG)"Oktalyzer",
+                       XMHOOK_Priority,                0,
+                       XMHOOK_Descr,                   (LONG)"Oktalyzer 1.57",
+                       XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+                       XMHOOK_ID,                              ID_OKTA,
+                       XMHOOK_LibraryBase,             mybase,
+                       XMHOOK_SaveModFunc,             SaveOktalyzer,
+                       XMHOOK_MaxTracks,               8,
+                       XMHOOK_MaxPatterns,             64,
+                       XMHOOK_MaxInstruments,  36,
+                       XMHOOK_MaxLength,               128,
+                       XMHOOK_MaxSampleLen,    131070,
+                       XMHOOK_MaxPattLen,              128,
+                       TAG_DONE);
+       }
+
+       return NULL;
+}
diff --git a/Hooks/SCOPTIONS b/Hooks/SCOPTIONS
new file mode 100644 (file)
index 0000000..b074877
--- /dev/null
@@ -0,0 +1,29 @@
+CODE=FAR
+PARAMETERS=REGISTERS
+NOSTACKCHECK
+STRINGMERGE
+NOCHECKABORT
+COMMENTNEST
+ERRORREXX
+NOMULTIPLEINCLUDES
+OPTIMIZERINLINELOCAL
+SMALLCODE
+SMALLDATA
+NOVERSION
+ABSFUNCPOINTER
+UTILITYLIBRARY
+NOICONS
+MEMORYSIZE=HUGE
+NOERRORHIGHLIGHT
+ONERROR=CONTINUE
+OPTIMIZERTIME
+MULTIPLECHARACTERCONSTANTS
+DEFINE NO_REQTOOLS_OBSOLETE
+DEFINE ASL_V38_NAMES_ONLY
+DEFINE INTUI_V36_NAMES_ONLY
+DEFINE IFFPARSE_V37_NAMES_ONLY
+DEFINE USE_BUILTIN_MATH
+DEFINE __USE_SYSBASE
+IGNORE=193
+IGNORE=306
+PUBSCREEN=Workbench
diff --git a/Hooks/SaveMIDI.c b/Hooks/SaveMIDI.c
new file mode 100644 (file)
index 0000000..514accb
--- /dev/null
@@ -0,0 +1,947 @@
+/*
+**     SaveMIDI.c
+**
+**     Copyright (C) 1994 Bernardo Innocenti
+**
+**     Original MOD2MIDI PC code (C) 1993 Andrew Scott
+**     Amiga Mod2Midi port (C) 1994 Raul Sobon
+**
+**     Save internal data to a MIDI type 1 file.
+*/
+
+#include <exec/types.h>
+
+#include <clib/dos_protos.h>
+
+#include <pragmas/dos_pragmas.h>
+
+#include "XModule.h"
+#include "Gui.h"
+
+
+#define ID_MThd                0x4D546864      /* "MThd", Midi Track HeaDer    */
+#define ID_MTrk                0x4D54726B      /* "MTrk", Midi TRacK                   */
+
+
+
+#define DRUMCHANNEL 9
+
+/* Returns a volume in the range 0..127 */
+#define RESTRICTVOL(v) (((v) < 0) ? 0 : (((v) > 127) ? 127 : (v)))
+
+/* Convert XModule note -> MIDI note */
+#define NOTEVALUE(n) ((n)+36)
+
+#define EVOL(ie,x) (((x) + (ie)->VolShift[0]) * (ie)->VolShift[1] / (ie)->VolShift[2])
+
+#define ANOTE(x) (((x) < 0) ? (-x) : NOTEVALUE(x))
+
+#define ENOTE(ie,x,y) (((ie)->MidiCh > 127) ? ((ie)->MidiCh - 128) : (ANOTE(x) + (ie)->Transpose[y]))
+
+
+
+struct MThd
+{
+       UWORD unknown1;         /* Set to 1             */
+       UWORD Tracks;
+       UWORD unknown2;         /* Set to 192   */
+};
+
+struct TRKInfo
+{
+       UBYTE unknown1[63];
+       UBYTE NameLen;
+       /* SongName follows */
+};
+
+struct InstrExtra
+{
+       UBYTE MidiCh;
+       UBYTE MidiPr;
+       UBYTE VolShift[3];
+       UBYTE Transpose[3];
+};
+
+
+/* Local functions prototypes */
+
+static UWORD ChooseChannels            (struct Instrument *instr, struct InstrExtra *ie, UBYTE DrumChann);
+static ULONG WriteTrack0Info   (BPTR fp, struct SongInfo *si);
+static UWORD WriteMIDIHeader   (BPTR fp, UWORD numofchannels);
+static LONG WriteVLQ                   (BPTR fp, ULONG i);
+static ULONG StopNote                  (BPTR fp, struct InstrExtra *ie, UWORD channel, UWORD note, UWORD vol, ULONG timer);
+static ULONG NoteLength (UBYTE note, ULONG lenght, UWORD beats);
+
+
+
+UWORD SaveMIDI (struct SongInfo *si, BPTR fp)
+{
+       UWORD numofchannels, err,
+               i, j, k;
+       struct Instrument *instr = &si->Inst[0], *inst1;
+       ULONG l;
+       BOOL tempodone = FALSE;
+
+       struct InstrExtra iext[MAXINSTRUMENTS], *ie1;
+
+       DisplayAction (MSG_CHOOSING_CHANNELS);
+
+       /* Setup InstrExtra structures */
+       for (i = 0; i < MAXINSTRUMENTS; i++)
+       {
+               iext[i].VolShift[0] = 0;
+               iext[i].VolShift[1] = 1;
+               iext[i].VolShift[2] = 1;
+               iext[i].Transpose[0] = 0;
+               iext[i].Transpose[1] = 0;
+               iext[i].Transpose[2] = 0;
+       }
+
+
+       /* Get required number of channels */
+       if ((numofchannels = ChooseChannels (instr, iext, DRUMCHANNEL)) > 16)
+       {
+               ShowMessage (MSG_TOO_MANY_CHANNELS);
+               return RETURN_FAIL;
+       }
+
+       /* Write Header */
+       if (err = WriteMIDIHeader (fp, numofchannels))
+               return err;
+
+       DisplayAction (MSG_WRITING_MIDI_TRACKS);
+
+       for (i = 0, inst1 = instr, ie1 = iext ; i < MAXINSTRUMENTS; inst1++, ie1++, i++)
+       {
+               ULONG count, mktrlenpos, timer, delay[MAXTRACKS];
+               UBYTE c;
+
+               if (!i || inst1->Length)
+               {
+                       if (DisplayProgress (i, MAXINSTRUMENTS))
+                               return ERROR_BREAK;
+
+                       /* Write MIDI Track */
+                       l = ID_MTrk;
+                       if (!FWrite (fp, &l, 4, 1)) return ERR_READWRITE;
+
+                       /* Write chunk length   (set to 0 now...) */
+
+                       mktrlenpos = Seek (fp, 0, OFFSET_CURRENT);      /* Write it later */
+                       l = 0;
+                       if (!FWrite (fp, &l, 4, 1)) return ERR_READWRITE;
+
+                       if (!i)
+                       {
+                               if (!(count = WriteTrack0Info (fp, si)))
+                                       return ERR_READWRITE;
+                       }
+                       else
+                       {
+                               static UBYTE unknown[4] = {0, 255, 3, 0};
+                               static struct
+                               {
+                                       UBYTE unknown_zero;
+                                       UBYTE channel;
+                                       UBYTE preset;
+                               } instrinfo;
+
+                               /* Write some unknown header */
+                               unknown[3] = strlen (inst1->Name);
+                               if (!FWrite (fp, unknown, 4, 1)) return ERR_READWRITE;
+
+                               /* Write instrument name */
+                               if (!FWrite (fp, inst1->Name, unknown[3], 1)) return ERR_READWRITE;
+
+                               instrinfo.unknown_zero = 0;
+                               instrinfo.channel = c = 0xC0 + ie1->MidiCh;
+                               instrinfo.unknown_zero = (ie1->MidiPr > 127) ? 126 : ie1->MidiPr;
+                               if (!FWrite (fp, &instrinfo, sizeof (instrinfo), 1))
+                                       return ERR_READWRITE;
+
+                               count = sizeof (unknown) + sizeof (instrinfo) + unknown[3];
+                       }
+               }
+
+               timer = 0;
+               if (!i || inst1->Length)
+               {
+                       UWORD bpm, ticks, l, h;
+                       UBYTE sampnum, effnum, effval, lastsam[MAXTRACKS] = {0}, vol[MAXTRACKS];
+                       BYTE patbreak;
+                       ULONG x, pause;
+                       UWORD note, lastslide, slideto;
+                       UWORD n[MAXTRACKS][48][2];              /* Note data for a song position                */
+                       UWORD lastn[MAXTRACKS];                 /* Last note on a particular channel    */
+
+                       memset (lastn, 0, MAXTRACKS * sizeof (UWORD));
+                       memset (vol, 0, MAXTRACKS);
+                       memset (delay, 0, MAXTRACKS * sizeof (ULONG));
+
+                       bpm = si->GlobalTempo;
+                       ticks = si->GlobalSpeed;
+                       lastslide = slideto = 0;
+                       patbreak = 0;
+
+                       for (h = 48; h--; )
+                               for (k = MAXTRACKS; k--; )
+                                       n[k][h][0] = 0;
+
+                       for (l = 0; l < si->Length; l++)
+                       {
+                               struct Pattern *patt = &si->PattData[si->Sequence[l]];
+
+                               /* ??? */
+                               if (patbreak > 0)
+                                       patbreak = 1 - patbreak;
+
+                               for (j = 0; j < patt->Lines; j++)
+                               {
+                                       pause = 0;
+                                       if (!patbreak)
+                                       {
+                                               for (k = 0; k < patt->Tracks; k++)
+                                               {
+                                                       n[k][0][1] = inst1->Volume;
+
+                                                       sampnum = patt->Notes[k]->Inst;
+                                                       note    = patt->Notes[k]->Note;
+                                                       effnum  = patt->Notes[k]->EffNum;
+                                                       effval  = patt->Notes[k]->EffVal;
+
+                                                       if (!i) note = 0;
+
+                                                       if ((note || sampnum) && delay[k])
+                                                       {
+                                                               count += StopNote (fp, ie1, c, lastn[k], vol[k], timer);
+                                                               timer = 0;
+                                                               delay[k] = 0;
+                                                       }
+
+                                                       if (!note && sampnum == i) /* check "defaults" */
+                                                               note = lastn[k];
+                                                       else
+                                                       {
+                                                               if (!sampnum)
+                                                               {
+                                                                       if (lastsam[k] == i)
+                                                                               sampnum = i;
+                                                                       else
+                                                                               note = 0;
+                                                               }
+                                                               else
+                                                               {
+                                                                       if (sampnum != i)
+                                                                               note = 0;
+                                                                       lastsam[k] = sampnum;
+                                                               }
+                                                               n[k][0][0] = note;
+                                                       }
+
+                                                       /* Do Effects */
+                                                       switch (effnum)
+                                                       {
+                                                               case 0x0:                               /* Arpeggio */
+                                                               {
+                                                                       UWORD nv;
+
+                                                                       if (!i || !effval || ie1->MidiCh > 127)
+                                                                               break;
+                                                                       if (!note)
+                                                                       {
+                                                                               if (!delay[k])
+                                                                                       break;
+                                                                               else
+                                                                               {
+                                                                                       nv = NOTEVALUE(lastn[k]);
+                                                                                       n[k][47][0] = lastn[k];
+                                                                                       n[k][47][1] = vol[k];
+                                                                                       if (effval & 0xF0)
+                                                                                               n[k][16][0] = -(nv + ((effval & 0xF0) >> 4));
+                                                                                       n[k][16][1] = vol[k];
+                                                                                       if (effval & 0x0F)
+                                                                                               n[k][32][0] = -(nv + (effval & 0x0F));
+                                                                                       n[k][32][1] = vol[k];
+                                                                               }
+                                                                       }
+                                                                       else
+                                                                       {
+                                                                               nv = NOTEVALUE(note);
+                                                                               n[k][47][0] = note;
+                                                                               n[k][47][1] = inst1->Volume;
+                                                                               if (effval & 0xF0)
+                                                                                       n[k][16][0] = -(nv + ((effval & 0xF0) >> 4));
+                                                                               n[k][16][1] = inst1->Volume;
+                                                                               if (effval & 0x0F)
+                                                                                       n[k][32][0] = -(nv + (effval & 0x0F));
+                                                                               n[k][32][1] = inst1->Volume;
+                                                                       }
+                                                                       break;
+                                                               }
+
+                                                               case 0x1:                               /* Slide Up */
+                                                               case 0x2:                               /* Slide Down */
+                                                                       if (!(effval & 0xFF) || ie1->MidiCh > 127)
+                                                                               break;
+                                                                       if (effnum == 0x2)
+                                                                               lastslide = effval;
+                                                                       else
+                                                                               lastslide = -effval;
+                                                                       if (!note)
+                                                                               if (!delay[k])
+                                                                                       break;
+                                                                               else
+                                                                               {
+                                                                                       n[k][0][0] = lastn[k] + lastslide;
+                                                                                       n[k][0][1] = vol[k];
+                                                                               }
+                                                                       else
+                                                                               n[k][0][0] += lastslide;
+                                                                       if (n[k][0][0] < 13)
+                                                                               n[k][0][0] = 13;        /* C-1 */
+                                                                       else if (n[k][0][0] > 48)
+                                                                               n[k][0][0] = 48;        /* B#3 */
+                                                                       break;
+
+                                                               case 0x3:                               /* Slide To */
+                                                                       if (!note && !slideto || note == lastn[k] || ie1->MidiCh > 127)
+                                                                               break;
+                                                                       if (effval & 0xFF)
+                                                                               lastslide = effval;
+                                                                       else
+                                                                               lastslide = abs (lastslide);
+                                                                       if (note)
+                                                                               slideto = note;
+                                                                       if (slideto > lastn[k])
+                                                                       {
+                                                                               n[k][0][0] = lastn[k] + lastslide * (ticks-1);
+                                                                               if (n[k][0][0] < 13)
+                                                                                       n[k][0][0] = 13;        /* C-1 */
+                                                                               if (n[k][0][0] > slideto)
+                                                                                       n[k][0][0] = slideto;
+                                                                       }
+                                                                       else
+                                                                       {
+                                                                               n[k][0][0] = lastn[k] - lastslide*(ticks-1);
+                                                                               if (n[k][0][0] > 48)
+                                                                                       n[k][0][0] = 48;        /* B#3 */
+                                                                               if (n[k][0][0] < slideto)
+                                                                                       n[k][0][0] = slideto;
+                                                                       }
+                                                                       n[k][0][1] = vol[k];
+                                                                       break;
+
+                                                               case 0x4:                       /* Vibrato */
+                                                               case 0x7:                       /* Tremolo */
+                                                                       /* ignore these effects.. not convertable */
+                                                                       break;
+
+                                                               case 0x5:                       /* Slide To + Volume Slide */
+                                                                       if ((note || slideto) && note!=lastn[k] && ie1->MidiCh < 128)
+                                                                       {
+                                                                               if (note)
+                                                                                       slideto = note;
+                                                                               if (slideto > lastn[k])
+                                                                               {
+                                                                                       n[k][0][0] = lastn[k] + lastslide*(ticks-1);
+                                                                                       if (n[k][0][0] < 13)
+                                                                                               n[k][0][0] = 13;        /* C-1 */
+                                                                                       if (n[k][0][0] > slideto)
+                                                                                               n[k][0][0] = slideto;
+                                                                               }
+                                                                               else
+                                                                               {
+                                                                                       n[k][0][0] = lastn[k] - lastslide*(ticks-1);
+                                                                                       if (n[k][0][0] > 48)
+                                                                                               n[k][0][0] = 48;        /* B#3 */
+                                                                                       if (n[k][0][0] < slideto)
+                                                                                               n[k][0][0] = slideto;
+                                                                               }
+                                                                       }
+                                                                       else
+                                                                               n[k][0][0] = 0;
+                                                                       note = 0;
+
+                                                                       /* We do not break here: the next case block (0xA)
+                                                                        * will slide volume for us
+                                                                        */
+
+                                                               case 0x6:                       /* Vibrato & Volume Slide */
+                                                                       /* Ignore Vibrato; do Volume Slide only */
+
+                                                               case 0xA:                       /* Volume Slide */
+                                                               {
+                                                                       UWORD v;
+
+                                                                       if (!note)
+                                                                               v = vol[k];
+                                                                       else
+                                                                               v = inst1->Volume;
+                                                                       v += (ticks-1)*(effval & 0xF0); /* Can't really slide */
+                                                                       v -= (ticks-1)*(effval & 0x0F);
+                                                                       if (v > 127)
+                                                                               v = 127;
+                                                                       else if (v < 0)
+                                                                               v = 0;
+                                                                       n[k][0][1] = v;
+                                                                       break;
+                                                               }
+
+                                                               case 0x9:               /* Set offset: pretend it's retrigger */
+                                                                       if ((!n[k][0][0] || !sampnum) && delay[k])
+                                                                       {
+                                                                               n[k][0][0] = lastn[k];
+                                                                               n[k][0][1] = vol[k];
+                                                                       }
+                                                                       break;
+
+                                                               case 0xB:                       /* Position Jump */
+                                                                       patbreak = 1;   /* Ignore, but break anyway */
+                                                                       break;
+
+                                                               case 0xD:                       /* Pattern Break */
+                                                                       patbreak = 1 + 10 * (effval & 0xF0) + (effval & 0x0F);
+                                                                       break;
+
+                                                               case 0xC:                       /* Set Volume */
+                                                                       {
+                                                                               UWORD vol = effval;
+
+                                                                               if (vol == 0x40) vol=0x3F;
+                                                                               vol = vol & 0x3F;
+                                                                               n[k][0][1] = vol << 1;
+                                                                       }
+                                                                       break;
+
+                                                               case 0xF:                       /* Set Speed/Tempo */
+                                                               {
+                                                                       UWORD temp;
+
+                                                                       temp = effval;
+
+                                                                       if (!temp)
+                                                                               temp = 1;
+                                                                       if (temp < 32)
+                                                                       {
+                                                                               ticks = temp;
+                                                                       //      if (TempoType)  /* Tempos act strangely so .. */
+                                                                               {
+                                                                                       bpm = 750 / temp;
+                                                                                       x = 80000 * temp;
+                                                                               }
+                                                                       }
+                                                                       else
+                                                                       {
+                                                                               bpm = temp;
+                                                                               x = 60000000 / temp;
+                                                                       }
+
+                                                                       if (i)          /* Only write tempo on track 0 */
+                                                                               break;
+
+                                                                       count += 6 + WriteVLQ (fp, timer);
+                                                                       timer = 0;
+                                                                       FPutC (fp, 255);        /* Meta-Event   */
+                                                                       FPutC (fp, 81);         /* Set Tempo    */
+                                                                       FPutC (fp, 3);
+                                                                       FPutC (fp, x >> 16);
+                                                                       FPutC (fp, (x >> 8) & 0xFF);
+                                                                       FPutC (fp, x & 0xFF);
+                                                                       tempodone = TRUE;
+
+                                                                       break;
+                                                               }
+
+                                                               case 0xE:               /* Extended Effects */
+                                                                       switch (effval & 0xF0)
+                                                                       {
+                                                                               case 0x10:              /* Fine Slide Up */
+                                                                                       if (!(effval & 0x0F) || ie1->MidiCh > 127)
+                                                                                               break;
+                                                                                       if (!note)
+                                                                                       {
+                                                                                               if (!delay[k])
+                                                                                                       break;
+                                                                                               else
+                                                                                               {
+                                                                                                       n[k][h][0] = lastn[k] + (effval & 0x0F);
+                                                                                                       n[k][h][1] = vol[k];
+                                                                                               }
+                                                                                       }
+                                                                                       else
+                                                                                               n[k][h][0] += effval & 0x0F;
+                                                                                       break;
+
+                                                                               case 0x020:             /* Fine Slide Down */
+                                                                                       if (!(effval & 0x0F) || ie1->MidiCh > 127)
+                                                                                               break;
+                                                                                       if (!note)
+                                                                                               if (!delay[k])
+                                                                                                       break;
+                                                                                               else {
+                                                                                                       n[k][h][0] = lastn[k] - (effval & 0x0F);
+                                                                                                       n[k][h][1] = vol[k];
+                                                                                               }
+                                                                                       else
+                                                                                               n[k][h][0] -= effval & 0x0F;
+                                                                                       break;
+                                                                               case 0x00: /* set filter on/off */
+                                                                               case 0x30: /* glissando on/off */
+                                                                               case 0x40: /* set vibrato wave */
+                                                                               case 0x50: /* set finetune */
+                                                                               case 0x60: /* pattern loop */
+                                                                               case 0x70: /* set tremolo wave */
+                                                                               case 0x80: /* un-used */
+                                                                               case 0xF0: /* invert loop */
+                                                                                       /* Can't do these in MIDI.. ignore */
+                                                                                       break;
+
+                                                                               case 0x0A0:             /* Fine volume slide up         */
+                                                                               case 0x0B0:             /* Fine volume slide down       */
+                                                                               {
+                                                                                       UWORD v;
+
+                                                                                       v = inst1->Volume;
+                                                                                       if (effval & 0xA0)
+                                                                                               v += effval & 0x0F;
+                                                                                       else
+                                                                                               v -= effval & 0x0F;
+                                                                                       if (v < 0)
+                                                                                               v = 0;
+                                                                                       else if (v>127)
+                                                                                               v = 127;
+                                                                                       n[k][0][1] = v;
+                                                                                       break;
+                                                                               }
+
+                                                                               case 0x90:              /* Retrigger sample */
+                                                                               {
+                                                                                       UWORD a, b, c;
+
+                                                                                       if (!note && !delay[k] || !(effval & 0x0F))
+                                                                                               break;
+                                                                                       a = effval & 0x0F;
+                                                                                       if (!(ticks / a))
+                                                                                               break;
+                                                                                       if (!note)
+                                                                                       {
+                                                                                               n[k][0][0] = lastn[k];
+                                                                                               n[k][0][1] = vol[k];
+                                                                                       }
+                                                                                       c = 0;
+                                                                                       b = 1;
+                                                                                       a *= 48;
+                                                                                       while (c < 48)
+                                                                                       {
+                                                                                               n[k][c][0] = note;
+                                                                                               n[k][c][1] = n[k][0][1];
+                                                                                               c = b * a / ticks;
+                                                                                               b++;
+                                                                                       }
+
+                                                                                       break;
+                                                                               }
+
+                                                                               case 0xC0:              /* Cut sample */
+                                                                               {
+                                                                                       UWORD a;
+
+                                                                                       if (!note && !delay[k])
+                                                                                               break;
+                                                                                       a = 48 * (effval & 0x0F) / ticks;
+                                                                                       if (a > 47)
+                                                                                               break;
+                                                                                       if (note)
+                                                                                               n[k][a][0] = note;
+                                                                                       else
+                                                                                               n[k][a][0] = lastn[k];
+                                                                                       n[k][a][1] = 0;
+                                                                                       break;
+                                                                               }
+
+                                                                               case 0xD0:              /* Delay Sample */
+                                                                               {
+                                                                                       UWORD a;
+
+                                                                                       if (!note || !(effval & 0x0F))
+                                                                                               break;
+                                                                                       a = 48 * (effval & 0x0F) / ticks;
+                                                                                       n[k][0][0] = 0;
+                                                                                       if (a > 47)
+                                                                                               break;
+                                                                                       n[k][a][0] = note;
+                                                                                       n[k][a][1] = n[k][a][0];
+                                                                                       break;
+                                                                               }
+
+                                                                               case 0xE0:              /* Pattern Pause */
+                                                                                       pause = 48 * (effval & 0x0F);
+                                                                                       break;
+
+                                                                       }       /* End Switch (E effects) */
+
+                                                                       break;
+                                                                       /* else dunno what it does.. disbelieve it ;) */
+
+                                                       }       /* End switch (effnum) */
+
+                                               }       /* End for (Tracks) */
+                                       }       /* End if (!pattnreak) */
+
+                                       for (h = 0; h<48; h++)
+                                       {
+                                               for (k = 0; k < patt->Tracks; k++)
+                                               {
+                                                       if (n[k][h][0])
+                                                       {
+                                                               if (delay[k])  /* Turn off old note on same channel */
+                                                               {
+                                                                       count += StopNote (fp, ie1, c, lastn[k], vol[k], timer);
+                                                                       timer = 0;
+                                                                       delay[k] = 0;
+                                                               }
+                                                               lastn[k] = n[k][h][0];
+                                                               n[k][h][0] = 0;
+                                                               vol[k] = n[k][h][1];
+                                                               count += 3 + WriteVLQ(fp, timer);
+                                                               timer = 0;
+                                                               FPutC (fp, 0x90 + c);   /* Note On */
+                                                               FPutC (fp, ENOTE(ie1, lastn[k], 0));
+                                                               FPutC (fp, RESTRICTVOL(EVOL(ie1,vol[k])));
+                                                               if (ie1->Transpose[1])
+                                                               {
+                                                                       count += 4;
+                                                                       FPutC (fp, 0);
+                                                                       FPutC (fp, 0x90 + c);
+                                                                       FPutC (fp, ENOTE(ie1, lastn[k], 1));
+                                                                       FPutC (fp, RESTRICTVOL(EVOL(ie1, vol[k])));
+                                                                       if (ie1->Transpose[2])
+                                                                       {
+                                                                               count += 4;
+                                                                               FPutC (fp, 0);
+                                                                               FPutC (fp, 0x90 + c);
+                                                                               FPutC (fp, ENOTE(ie1, lastn[k], 2));
+                                                                               FPutC (fp, RESTRICTVOL(EVOL(ie1, vol[k])));
+                                                                       }
+                                                               }
+                                                               delay[k] = NoteLength (ANOTE(lastn[k]), inst1->Length, bpm);
+                                                       }
+                                                       else if (delay[k]==1)
+                                                       {
+                                                               delay[k] = 0;
+                                                               count += StopNote (fp, ie1, c, lastn[k], vol[k], timer);
+                                                               timer = 0;
+                                                       }
+                                                       else if (delay[k]>0)
+                                                               delay[k]--;
+                                               }
+                                               timer++;
+                                       }
+                                       timer += pause;
+                                       if (patbreak<0)
+                                               patbreak++;
+                                       else if (patbreak>0)
+                                       {
+                                               patbreak = 1 - patbreak;
+                                               j = 0;
+                                       }
+                               }       /* End for (Lines) */
+                       }       /* End for (si->Length) */
+
+                       for (k = 0; k < si->MaxTracks; k++)
+                               if (delay[k])
+                               {
+                                       count += StopNote (fp, ie1, c, lastn[k], vol[k], timer);
+                                       timer = 0;
+                               }
+               }
+
+               if(!i && !tempodone)
+               {
+                       count += 7;
+                       FPutC (fp, 0);          /* Give the default 128 bpm if none done yet */
+                       FPutC (fp, 255);
+                       FPutC (fp, 81);
+                       FPutC (fp, 3);
+                       FPutC (fp, 7);
+                       FPutC (fp, 39);
+                       FPutC (fp, 14);
+               }
+
+               if(inst1->Length || !i)         // RAUL addition
+               {
+                       count += 3 + WriteVLQ (fp, timer);
+                       FPutC (fp, 255);
+                       FPutC (fp, 47);
+                       FPutC (fp, 0);
+
+                       /* Write total chunk length */
+
+                       if (Seek (fp, mktrlenpos, OFFSET_BEGINNING) == -1)
+                               return ERR_READWRITE;
+
+                       if (!(FWrite (fp, &count, 4, 1)))
+                               return ERR_READWRITE;
+
+                       if (Seek (fp, 0, OFFSET_END) == -1)
+                               return ERR_READWRITE;
+               }
+
+       }       /* End for (instruments) */
+
+       return RETURN_OK;
+}
+
+
+
+static UWORD ChooseChannels (struct Instrument *instr, struct InstrExtra *ie, UBYTE DrumChann)
+
+/*     Returns: The number of different channels needed to play instruments.
+ *     If that number is not greater than 16, upto 16 channels will
+ *     be allocated to the samples.
+ */
+{
+       UBYTE c, Presets[128], m, n, numchan;
+       UBYTE DrumUsed = 0;
+       struct Instrument *instr1, *instr2;
+       struct InstrExtra *ie1, *ie2;
+
+       /* Preset all presets !!! */
+       for (n = 0; n < MAXINSTRUMENTS; n++)
+               ie[n].MidiPr = n;
+
+       memset (Presets, 0, 128);
+
+       for (n = MAXINSTRUMENTS, instr1 = &instr[1], ie1 = &ie[1]; n--; instr1++, ie1++)
+       {
+               ie->MidiCh = 255;
+               if (instr1->Length)
+               {
+                       if (ie1->MidiPr > 127)
+                       {
+                               DrumUsed = 1;
+                               ie1->MidiCh = DrumChann;
+                       }
+                       else
+                               Presets[ie1->MidiPr] = 1;
+               }
+               else
+                       ie1->MidiPr = 0;
+       }
+
+       for (numchan = DrumUsed, n = 128; n--; numchan += Presets[n]);
+
+       if (numchan > 16)
+               return numchan;
+
+       /* Go through and set channels appropriately */
+       m = MAXINSTRUMENTS;
+       instr1 = &instr[1];
+       ie1 = &ie[1];
+       c = 0;
+       while (--m)
+       {
+               if (ie1->MidiCh < 0)
+               {
+                       ie1->MidiCh = c;
+                       n = m;
+                       ie2 = ie1 + 1;
+                       instr2 = instr1 + 1;
+
+                       /* Search for other instruments with the same preset and set
+                        * all them to the same MIDI channel.
+                        */
+                       while (n--)
+                       {
+                               if (ie2->MidiCh < 0)
+                                       if (ie2->MidiPr == ie1->MidiPr || !instr2->Length)
+                                               ie2->MidiCh = c;
+                               instr2++;
+                               ie2++;
+                       }
+                       if (++c == DrumChann && DrumUsed)
+                               c++;
+               }
+               ie1++;
+               instr1++;
+       }
+       return numchan;
+}
+
+
+
+static UWORD WriteMIDIHeader (BPTR fp, UWORD numofchannels)
+{
+       ULONG l;
+
+       /* Write MIDI header    */
+       l = ID_MThd;
+       if (!FWrite (fp, &l, 4, 1)) return ERR_READWRITE;
+
+       /* Write chunk length   */
+       l = sizeof (struct MThd);
+       if (!FWrite (fp, &l, 4, 1)) return ERR_READWRITE;
+
+       /* Write header chunk   */
+       {
+               struct MThd mthd;
+
+               mthd.unknown1 = 1;
+               mthd.Tracks = numofchannels;
+               mthd.unknown2 = 192;
+
+               if (!FWrite (fp, &mthd, sizeof (mthd), 1)) return ERR_READWRITE;
+       }
+
+       return RETURN_OK;
+}
+
+
+
+static ULONG WriteTrack0Info (BPTR fp, struct SongInfo *si)
+
+/* Write info for track 0.
+ * Return actual number of bytes written, or 0 for failure.
+ */
+{
+       static UBYTE TRK0I[63] =
+       {
+               0, 255, 2, 42, 70, 105, 108, 101, 32, 67, 111, 112, 121, 114, 105, 103,
+               104, 116, 32, 40, 99, 41, 32, 49 ,57, 57, 51, 32, 65, 100, 114, 101, 110,
+               97, 108, 105, 110, 32, 83, 111, 102, 116, 119, 97, 114, 101,
+               0, 255, 88, 4, 3, 2, 24, 8,
+               0, 255, 89, 2, 0, 0,
+               0, 255, 3
+       }; /* standard header + copyright message */
+
+       struct TRKInfo trkinfo;
+
+       memcpy (trkinfo.unknown1, TRK0I, 63);
+       trkinfo.NameLen = strlen (si->SongName);
+
+       if (!FWrite (fp, &trkinfo, sizeof (trkinfo), 1))
+               return 0;
+
+       if (!FWrite (fp, si->SongName, trkinfo.NameLen, 1))
+               return 0;
+
+       return (sizeof (trkinfo) + trkinfo.NameLen);
+}
+
+
+
+static LONG WriteVLQ (BPTR fp, ULONG i)
+
+/*     Writes a stream of bytes, each with the msb (bit 7) set to 1 to mean
+ *     continuation and 0 to end the byte sequnce.  Note that the data is
+ *     put in reverse order.
+ *
+ *     Returns: # of bytes written after a variable-length-quantity equivalent
+ *     of i has been written to the file f, or 0 for failure.
+ */
+{
+       LONG x = 0;
+       ULONG buffer;
+
+       buffer = i & 0x7F;
+       while ((i >>= 7) > 0)
+               buffer = ((buffer << 8) | 0x80) + (i & 0x7F);
+       while (1)
+       {
+               FPutC (fp, buffer & 0xFF);
+
+               x++;
+
+               if (buffer & 0x80)
+                       buffer >>= 8;
+               else
+                       return x;
+       }
+}
+
+
+
+static ULONG StopNote (BPTR fp, struct InstrExtra *ie, UWORD channel, UWORD note, UWORD vol, ULONG timer)
+
+/* stop old note */
+{
+       UWORD count = 3 + WriteVLQ (fp, timer);
+
+       FPutC (fp, 0x80 + channel);     /* note off */
+       FPutC (fp, ENOTE(ie, note, 0));
+       FPutC (fp, RESTRICTVOL(EVOL(ie, vol)));
+       if (ie->Transpose[1])
+       {
+               count += 4;
+               FPutC (fp, 0);
+               FPutC (fp, 0x80 + channel);
+               FPutC (fp, ENOTE(ie, note, 1));
+               FPutC (fp, RESTRICTVOL(EVOL(ie, vol)));
+               if (ie->Transpose[2])
+               {
+                       count += 4;
+                       FPutC (fp, 0);
+                       FPutC (fp, 0x80 + channel);
+                       FPutC (fp, ENOTE(ie, note, 2));
+                       FPutC (fp, RESTRICTVOL(EVOL(ie, vol)));
+               }
+       }
+
+       return count;
+}
+
+
+
+static ULONG NoteLength (UBYTE note, ULONG lenght, UWORD beats)
+
+{
+       ULONG idx;
+
+       /* Note conversion table for Sound/Noise/ProTracker */
+       static const UWORD TrackerNotes[] =
+       {
+               0x000,                                                                          /* Null note */
+
+               0x6B0, 0x650, 0x5F5, 0x5A0, 0x54D, 0x501,       /* Octave 0 */
+               0x4B9, 0x475, 0x435, 0x3F9, 0x3C1, 0x38B,
+
+               0x358, 0x328, 0x2FA, 0x2D0, 0x2A6, 0x280,       /* Octave 1 */
+               0x25C, 0x23A, 0x21A, 0x1FC, 0x1E0, 0x1C5,
+
+               0x1AC, 0x194, 0x17D, 0x168, 0x153, 0x140,       /* Octave 2 */
+               0x12E, 0x11d, 0x10D, 0x0FE, 0x0F0, 0x0E2,
+
+               0x0D6, 0x0CA, 0x0BE, 0x0B4, 0x0AA, 0x0A0,       /* Octave 3 */
+               0x097, 0x08F, 0x087, 0x07F, 0x078, 0x071,
+
+               0x06B, 0x065, 0x05F, 0x05A, 0x055, 0x050,       /* Octave 4 */
+               0x04C, 0x047, 0x043, 0x040, 0x03C, 0x039,
+
+               0x035, 0x032, 0x030, 0x02D, 0x02A, 0x028,       /* Octave 5 */
+               0x026, 0x024, 0x022, 0x020, 0x01E, 0x01C
+       };
+
+/*     static float t[84] =
+       {
+               3.200e-3, 3.020e-3, 2.851e-3, 2.691e-3, 2.540e-3, 2.397e-3,
+               2.263e-3, 2.136e-3, 2.016e-3, 1.903e-3, 1.796e-3, 1.695e-3,
+               1.600e-3, 1.510e-3, 1.425e-3, 1.345e-3, 1.270e-3, 1.197e-3,
+               1.131e-3, 1.068e-3, 1.008e-3, 9.514e-4, 8.980e-4, 8.476e-4,
+               8.000e-4, 7.551e-4, 7.127e-4, 6.727e-4, 6.350e-4, 5.993e-4,
+               5.657e-4, 5.339e-4, 5.040e-4, 4.757e-4, 4.490e-4, 4.238e-4,
+               4.000e-4, 3.775e-4, 3.564e-4, 3.364e-4, 3.175e-4, 2.997e-4,
+               2.828e-4, 2.670e-4, 2.520e-4, 2.378e-4, 2.245e-4, 2.119e-4,
+               2.000e-4, 1.888e-4, 1.782e-4, 1.682e-4, 1.587e-4, 1.498e-4,
+               1.414e-4, 1.335e-4, 1.260e-4, 1.189e-4, 1.122e-4, 1.059e-4,
+               1.000e-4, 9.439e-5, 8.909e-5, 8.409e-5, 7.937e-5, 7.492e-5,
+               7.071e-5, 6.674e-5, 6.300e-5, 5.946e-5, 5.612e-5, 5.297e-5,
+               5.000e-5, 4.719e-5, 4.454e-5, 4.204e-5, 3.969e-5, 3.746e-5,
+               3.536e-5, 3.337e-5, 3.150e-5, 2.973e-5, 2.806e-5, 2.649e-5
+       }; */ /* multipliers for each pitch: 12th roots of 2 apart */
+
+       idx = note - 36;
+       if (idx < 0) idx = 0;
+       if (idx > 84) idx = 84;
+
+       return (TrackerNotes[idx] * beats * lenght); /* better not slide out of this range :( */
+}
diff --git a/Hooks/ScreamTrackerHook.c b/Hooks/ScreamTrackerHook.c
new file mode 100644 (file)
index 0000000..587445d
--- /dev/null
@@ -0,0 +1,606 @@
+/*
+**     ScreamTrackerHook.c
+**
+**     Copyright (C) 1995,96,97 Bernardo Innocenti
+**
+**     Load a ScreamTracker 3.01 module with any number of tracks.
+**     Only sample instruments are supported.
+*/
+
+
+#include <exec/memory.h>
+#include <libraries/xmodule.h>
+#include <libraries/songclass.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+
+
+/* Local function prototypes */
+
+APTR HOOKCALL _GetEngine (
+       REG(a6, struct Library *MyBase));
+APTR HOOKCALL _SetupXMHook (
+       REG(a0, struct XModuleBase *_XModuleBase),
+       REG(a6, struct Library *mybase));
+static HOOKCALL struct XMHook *IdentifyScreamTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags));
+static HOOKCALL LONG LoadScreamTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *loader),
+       REG(a2, ULONG *tags));
+INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval);
+
+
+#ifndef MAKE_ID
+#define MAKE_ID(a,b,c,d) \
+       ((ULONG) (a)<<24 | (ULONG) (b)<<16 | (ULONG) (c)<<8 | (ULONG) (d))
+#endif
+
+/* Convert an Intel style WORD to Motorola format */
+#define I2M(x) ( (UWORD) ( (((UWORD)(x)) >> 8) | (((UWORD)(x)) << 8) ) )
+
+/* Convert an Intel style LONG to Motorola format */
+#define I2ML(x) ( I2M((x)>>16) | (I2M((x))<<16) )
+
+/* Convert a ParaPointer to a normal file offset */
+#define PARA(x) (((ULONG)I2M(x)) << 4)
+
+/* Convert a 3-byte ParaPointer to a normal file offset */
+#define PARAL(x) (( (ULONG)x[0]<<16 | (ULONG)x[2]<<8 | (ULONG)x[1] ) << 4)
+
+
+struct S3MHeader
+{
+       UBYTE SongName[28];
+       UBYTE Constant;
+       UBYTE Type;
+       UWORD Pad0;
+       UWORD OrdNum;           /* Number of positions */
+       UWORD InsNum;
+       UWORD PatNum;
+       UWORD Flags;
+       UWORD CreatedWith;
+       UWORD FileFormat;       /* See below */
+       ULONG ID;                       /* Should be ID_SCRM */
+       UBYTE GlobalVolume;
+       UBYTE InitialSpeed;
+       UBYTE InitialTempo;
+       UBYTE MasterVolume;
+       UBYTE Pad1[10];
+       UWORD Special;
+       UBYTE Channels[32];
+};
+
+
+
+/* Values for S3MHeader->FileFormat */
+
+#define S3MFF_SIGNEDSAMPLES            1
+#define S3MFF_UNSIGNEDSAMPLES  2
+
+
+
+struct S3MSamp
+{
+       UBYTE Type;                     /* See ITYPE_#? definitions below.      */
+       UBYTE DosName[12];
+       UBYTE MemSeg[3];        /* :-))) Parapointer to sample data     */
+       ULONG Length;
+       ULONG LoopBeg;
+       ULONG LoopEnd;
+       UBYTE Volume;
+       UBYTE Pad0;
+       UBYTE Pack;                     /* See SPACK_#? definitions below. */
+       UBYTE Flags;            /* See SFLAG_#? definitions below. */
+       ULONG C2Spd;
+       ULONG Pad1;
+       UWORD Reserved0[2];
+       ULONG Reserved1[1];
+       UBYTE SampleName[28];
+       ULONG SampID;           /* Should be ID_SCRS */
+};
+
+
+/* S3M Instrument types */
+
+#define ITYPE_NONE                     0
+#define ITYPE_SAMPLE           1
+#define ITYPE_ADLIB_MELODY     2
+#define ITYPE_ADLIB_SNARE      3
+#define ITYPE_ADLIB_TOM                4
+#define ITYPE_ADLIB_CYMBAL     5
+#define ITYPE_ADLIB_HIHAT      6
+
+
+/* S3M Sample packing */
+
+#define SPACK_NONE                     0
+#define SPACK_ADPCM                    1       /* Unsupported by ST3.01 */
+
+
+/* S3M Sample Flags */
+#define SFLAG_LOOP                     1
+#define SFLAG_STEREO           2       /* Unsupported by ST3.01 */
+#define SFLAG_16BIT                    4       /* Unsupported by ST3.01 */
+
+
+/* S3M IDs */
+
+#define ID_S3M MAKE_ID('S','3','M','\0')       /* ScreamTracker ID             */
+#define ID_SCRM        MAKE_ID('S','C','R','M')        /* ScreamTracker Module */
+#define ID_SCRS        MAKE_ID('S','C','R','S')        /* ScreamTracker Sample */
+#define ID_SCRI        MAKE_ID('S','C','R','I')        /* ScreamTracker Instr  */
+
+
+
+/* Effects conversion table */
+static const UBYTE Effects[MAXTABLEEFFECTS] =
+{
+/*   S3M               XModule                 Val */
+
+       0x00,   /*      Null effect             $00     */
+
+       0x05,   /*      Portamento Up   $01     */
+       0x04,   /*      Portamento Down $02     */
+       0x06,   /*      Tone Portamento $03     */
+       0x07,   /*      Vibrato                 $04     */
+       0x0B,   /*      ToneP + VolSl   $05     */
+       0x0A,   /*      Vibra + VolSl   $06     */
+       0x08,   /*      Tremolo                 $07     */
+       0x00,   /*      Set Hold/Decay  $08     */
+       0x0E,   /*      Sample Offset   $09     */
+       0x03,   /*      Volume Slide    $0A     */
+       0x01,   /*      Position Jump   $0B     */
+       0x12,   /*      Set Volume              $0C     */
+       0x02,   /*      Pattern break   $0D     */
+       0x10,   /*      Misc                    $0E     */
+       0x00,   /*      Set Speed               $0F     */
+       0x11,   /*      Set Tempo               $10     */
+       0x09,   /*      Arpeggio                $11     */
+
+       0x03,   /*      Oktalyzer H             */
+       0x03    /*      Oktalyzer L             */
+};
+
+
+
+/* Library data */
+
+const UBYTE LibName[] = "screamtracker.xmhook";
+const UBYTE LibVer[] = { '$', 'V', 'E', 'R', ':', ' ' };
+const UBYTE LibId[] = "screamtracker.xmhook 1.0 (17.3.96) © 1995-96 by Bernardo Innocenti";
+
+
+/* Get around a SAS/C bug which causes some annoying warnings
+ * with the library bases defined below.  The problem happens
+ * when the GST has been compiled with the CODE=FAR switch and
+ * the source is being compiled with CODE=NEAR.
+ */
+#ifdef __SASC
+#pragma msg 72 ignore push
+#endif /* __SASC */
+
+struct ExecBase                        *SysBase                = NULL;
+struct XModuleBase             *XModuleBase    = NULL;
+struct DosLibrary              *DOSBase                = NULL;
+struct IntuitionBase   *IntuitionBase  = NULL;
+
+#ifdef __SASC
+#pragma msg 72 ignore pop
+#endif /* __SASC */
+
+
+static HOOKCALL struct XMHook *IdentifyScreamTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags))
+
+/* Determine if the given file is a ScreamTracker 3.0 module.
+ * Note: the file position will be changed on exit.
+ */
+{
+       ULONG id;
+
+       Seek (fh, 0x2C, OFFSET_BEGINNING);
+       if (FRead (fh, &id, 4, 1) != 1)
+               return NULL;
+
+       return ((id == ID_SCRM) ? loader : NULL);
+}
+
+
+
+static HOOKCALL LONG LoadScreamTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *loader),
+       REG(a2, ULONG *tags))
+{
+       UWORD                           *ParaInst,
+                                               *ParaPatt;
+       struct Note                     *note;
+       struct Pattern          *patt;
+       struct S3MHeader         s3mhd;
+
+       ULONG           i, j;                   /* Loop counters */
+       LONG            err = 0;
+       UWORD           numchannels = 0, w;
+       LONG            l;                              /* Dummy read buffer */
+
+
+       /* Read module header */
+       if (FRead (fh, &s3mhd, sizeof (s3mhd), 1) != 1)
+               return ERROR_IOERR;
+
+       /* Fix Intel WORD format */
+       s3mhd.OrdNum            = I2M (s3mhd.OrdNum);
+       s3mhd.InsNum            = I2M (s3mhd.InsNum);
+       s3mhd.PatNum            = I2M (s3mhd.PatNum);
+       s3mhd.Flags                     = I2M (s3mhd.Flags);
+       s3mhd.CreatedWith       = I2M (s3mhd.CreatedWith);
+       s3mhd.FileFormat        = I2M (s3mhd.FileFormat);
+       s3mhd.Special           = I2M (s3mhd.Special);
+
+
+       for (i = 0; i < 32; i++)
+               if (s3mhd.Channels[i] != 255) numchannels++;
+
+       s3mhd.SongName[27] = '\0';      /* Ensure Null-termination */
+
+
+       /* Read the pattern sequence */
+
+       if (!xmSetSongLen (si, s3mhd.OrdNum))
+               return ERROR_NO_FREE_STORE;
+
+       for (i = 0; i < s3mhd.OrdNum; i++)
+       {
+               if ((l = FGetC (fh)) != -1L)
+                       si->Sequence[i] = l;
+               else
+                       return ERROR_IOERR;
+       }
+
+       if (s3mhd.OrdNum & 1) FGetC (fh);       /* Keep WORD alignament */
+
+
+
+       /*******************/
+       /* Get Instruments */
+       /*******************/
+
+       xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
+               (APTR)MSG_READING_INSTS, NULL);
+
+       /* Get parapointers to instruments */
+
+       if (!(ParaInst = AllocVec (s3mhd.InsNum * sizeof (UWORD), MEMF_PUBLIC)))
+               return ERROR_NO_FREE_STORE;
+
+       if (!(ParaPatt = AllocVec (s3mhd.PatNum * sizeof (UWORD), MEMF_PUBLIC)))
+       {
+               FreeVec (ParaInst);
+               return ERROR_NO_FREE_STORE;
+       }
+
+       if (FRead (fh, ParaInst, sizeof (UWORD), s3mhd.InsNum) == s3mhd.InsNum)
+       {
+               if (FRead (fh, ParaPatt, sizeof (UWORD), s3mhd.PatNum) == s3mhd.PatNum)
+               {
+                       struct S3MSamp samp;
+
+                       /* Note: Need to Flush() here?  Hmm... I think not. */
+
+                       for (i = 0; i < s3mhd.InsNum; i++)
+                       {
+                               if (xmDisplayProgress (i + 1, s3mhd.InsNum))
+                               {
+                                       err = ERROR_BREAK;
+                                       break;
+                               }
+
+                               if (Seek (fh, PARA(ParaInst[i]), OFFSET_BEGINNING) == -1)
+                               {
+                                       err = ERROR_IOERR;
+                                       break;
+                               }
+                               else
+                               {
+                                       struct Instrument *inst;
+                                       BYTE *sample;
+
+                                       if (Read (fh, &samp, sizeof (samp)) != sizeof (samp))
+                                       {
+                                               err = ERROR_IOERR;
+                                               break;
+                                       }
+
+                                       samp.SampleName[27] = '\0';     /* Ensure NULL termination */
+
+                                       if (samp.Type == 0)
+                                               ;       /* Do nothing */
+                                       else if (samp.Type == ITYPE_SAMPLE)
+                                       {
+                                               if (samp.Pack != SPACK_NONE)
+                                                       xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
+                                                               (APTR)MSG_UNKNOWN_SAMPLE_COMPRESSION, i + 1);
+
+                                               if (samp.Flags & SFLAG_STEREO)
+                                                       xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
+                                                       (APTR)MSG_INST_IS_STEREO, i + 1);
+
+                                               if (samp.Flags & SFLAG_16BIT)
+                                               {
+                                                       xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
+                                                               (APTR)MSG_INST_IS_16BIT, i + 1);
+                                                       continue;
+                                               }
+
+                                               if (!(inst = xmAddInstrument (si, i + 1,
+                                                       INSTRA_Name,    samp.SampleName,
+                                                       INSTRA_Length,  I2ML(samp.Length),
+                                                       INSTRA_Volume,  (samp.Volume > 64) ? 64 : samp.Volume,
+                                                       (samp.Flags & SFLAG_LOOP) ? INSTRA_LoopStart : TAG_IGNORE, I2ML(samp.LoopBeg),
+                                                       (samp.Flags & SFLAG_LOOP) ? INSTRA_LoopEnd : TAG_IGNORE,        I2ML(samp.LoopEnd),
+                                                       TAG_DONE)))
+                                                       return ERROR_NO_FREE_STORE;
+
+
+                                               /* Seek where the sample is stored */
+                                               if (Seek (fh, PARAL(samp.MemSeg), OFFSET_BEGINNING) == -1)
+                                               {
+                                                       err = ERROR_IOERR;
+                                                       break;
+                                               }
+
+                                               /* Allocate memory for sample */
+
+                                               if (!(sample = AllocVec (inst->Length, MEMF_SAMPLE)))
+                                               {
+                                                       err = ERROR_NO_FREE_STORE;
+                                                       break;
+                                               }
+
+                                               /* Load sample data */
+
+                                               if (Read (fh, sample, inst->Length) != inst->Length)
+                                               {
+                                                       FreeVec (sample);
+                                                       err = ERROR_IOERR;
+                                                       break;
+                                               }
+
+                                               if (s3mhd.FileFormat >= S3MFF_UNSIGNEDSAMPLES)
+                                               {
+                                                       /* unsigned -> signed  conversion */
+                                                       for (j = 0; j < inst->Length; j++)
+                                                               sample[j] ^= 0x80;
+                                               }
+
+                                               xmSetInstrument (si, i,
+                                                       INSTRA_Sample,  sample,
+                                                       TAG_DONE);
+                                       }
+                                       else if (samp.Type <= ITYPE_ADLIB_HIHAT)
+                                       {
+                                               static UBYTE *ADLibNames[] =
+                                               {
+                                                       "Melody",
+                                                       "Snare Drum",
+                                                       "Tom",
+                                                       "Cymbal",
+                                                       "Hihat"
+                                               };
+
+                                               xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
+                                                       (APTR)MSG_ADLIB_INSTR, i + 1, ADLibNames[samp.Type - 2]);
+                                       }
+                                       else
+                                               xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
+                                                       (APTR)MSG_ERR_NOT_A_SAMPLE, i + 1);
+                               }
+                       }
+               }
+               else err = ERROR_IOERR;
+       }
+       else err = ERROR_IOERR;
+
+       FreeVec (ParaInst);
+
+       SetAttrs (si,
+               SNGA_Title,                     s3mhd.SongName,
+               SNGA_Author,            -1,
+               SNGA_GlobalSpeed,       s3mhd.InitialSpeed,
+               SNGA_GlobalTempo,       s3mhd.InitialTempo,
+               TAG_DONE);
+
+       if (err)
+       {
+               FreeVec (ParaPatt);
+               return err;
+       }
+
+
+       /****************/
+       /* Get Patterns */
+       /****************/
+
+       xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
+               (APTR)MSG_READING_PATTS, NULL);
+
+       for (i = 0; i < s3mhd.PatNum; i++)
+       {
+               if (xmDisplayProgress (i + 1, s3mhd.PatNum))
+               {
+                       err = ERROR_BREAK;
+                       break;
+               }
+
+               if (Seek (fh, PARA(ParaPatt[i]), OFFSET_BEGINNING) == -1)
+               {
+                       err = ERROR_IOERR;
+                       break;
+               }
+               else
+               {
+                       LONG ch = 0;    /* Channel number */
+
+                       /* Read size */
+
+                       if (Read (fh, &w, sizeof (UWORD)) != sizeof (UWORD))
+                       {
+                               err = ERROR_IOERR;
+                               break;
+                       }
+
+                       if (patt = xmAddPattern (si,
+                               PATTA_Tracks,   numchannels,
+                               PATTA_Lines,    64,
+                               TAG_DONE))
+                       {
+                               /* Loop on rows */
+                               for (j = 0; (j < 64) && (ch != -1L); j++)
+                               {
+                                       while ((ch = FGetC (fh)) != -1L)
+                                       {
+                                               if (ch == 0)    /* End of row */
+                                                       break;
+
+                                               if ((ch & 31) >= numchannels)
+                                               {
+                                                       xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                                                               (APTR)MSG_TRACK_OUT_OF_RANGE, ch & 31);
+                                                       note = &patt->Notes[0][j];
+                                               }
+                                               else
+                                                       note = &patt->Notes[ch & 31][j];
+
+                                               if (ch & 32)    /* Note and instrument follows */
+                                               {
+                                                       l = FGetC (fh);
+                                                       if (l != 255)
+                                                               note->Note = ((((l >> 4) - 2) * 12) | (l & 0x0F)) + 1;
+                                                       note->Inst = FGetC (fh);
+                                               }
+                                               if (ch & 64)    /* Volume */
+                                               {
+                                                       l = FGetC (fh);
+                                                       note->EffNum = EFF_SETVOLUME;
+                                                       note->EffVal = l;
+                                               }
+
+                                               if (ch & 128)   /* Command and Info */
+                                               {
+                                                       note->EffNum = FGetC (fh);
+                                                       note->EffVal = FGetC (fh);
+                                                       note->EffNum = DecodeEff (note->EffNum, note->EffVal);
+                                               }
+                                       }
+                               }
+
+                               if (ch == -1)
+                               {
+                                       err = ERROR_IOERR;
+                                       break;
+                               }
+                       }
+                       else
+                       {
+                               err = ERROR_NO_FREE_STORE;
+                               break;
+                       }
+
+                       /* Flush (fh); */
+               }
+       }
+
+       FreeVec (ParaPatt);
+
+       return err;
+}
+
+
+
+INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval)
+{
+       UBYTE i;
+
+       if ((eff == 0) && effval) /* Speed/Tempo */
+       {
+               if (effval < 0x20)
+                       return EFF_SETSPEED;
+               else
+                       return EFF_SETTEMPO;
+       }
+
+       for (i = 0 ; i < MAXTABLEEFFECTS; i++)
+               if (eff == Effects[i])
+                       return i;
+
+       return 0;
+}
+
+
+
+HOOKCALL struct Library * _UserLibInit (REG(a6, struct Library *mybase))
+{
+       return mybase;
+}
+
+
+
+HOOKCALL struct Library * _UserLibCleanup (REG(a6, struct Library *mybase))
+{
+       return mybase;
+}
+
+
+
+HOOKCALL APTR _GetEngine (REG(a6, struct Library *mybase))
+{
+       return NULL;
+}
+
+
+
+HOOKCALL APTR _SetupXMHook (
+       REG(a0, struct XModuleBase *_XModuleBase),
+       REG(a6, struct Library *mybase))
+{
+       if (!(strcmp (_XModuleBase->xm_Library.lib_Node.ln_Name, "xmodule.library")))
+       {
+               SysBase = *((struct ExecBase **)4);
+               XModuleBase             = _XModuleBase;
+               IntuitionBase   = XModuleBase->xm_IntuitionBase;
+               DOSBase                 = XModuleBase->xm_DOSBase;
+
+               xmAddHook (
+                       XMHOOK_Type,                    NT_XMLOADER,
+                       XMHOOK_Name,                    (LONG)"ScreamTracker",
+                       XMHOOK_Priority,                0,
+                       XMHOOK_Descr,                   (LONG)"ScreamTracker 3.01",
+                       XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+                       XMHOOK_ID,                              ID_S3M,
+                       XMHOOK_Flags,                   0,
+                       XMHOOK_LibraryBase,             mybase,
+                       XMHOOK_LoadModFunc,             LoadScreamTracker,
+                       XMHOOK_IdentifyModFunc, IdentifyScreamTracker,
+                       XMHOOK_MaxTracks,               16,
+                       XMHOOK_MaxPatterns,             100,
+                       XMHOOK_MaxInstruments,  99,
+                       XMHOOK_MaxLength,               255,
+                       XMHOOK_MaxSampleLen,    64000,
+                       XMHOOK_MaxPattLen,              64,
+                       TAG_DONE);
+       }
+
+       return NULL;
+}
diff --git a/Hooks/oktalyzer.xmhook b/Hooks/oktalyzer.xmhook
new file mode 100644 (file)
index 0000000..d6af097
Binary files /dev/null and b/Hooks/oktalyzer.xmhook differ
diff --git a/Hooks/screamtracker.xmhook b/Hooks/screamtracker.xmhook
new file mode 100644 (file)
index 0000000..10a6a3b
Binary files /dev/null and b/Hooks/screamtracker.xmhook differ
diff --git a/Instr.c b/Instr.c
new file mode 100644 (file)
index 0000000..5f07735
--- /dev/null
+++ b/Instr.c
@@ -0,0 +1,1057 @@
+/*
+**     Instr.c
+**
+**     Copyright (C) 1994,95,96,97 Bernardo Innocenti
+**
+**     Instrument loading/saving/handling routines.
+*/
+
+#define IFFPARSE_V37_NAMES_ONLY
+
+#include <exec/libraries.h>
+#include <exec/memory.h>
+#include <datatypes/soundclass.h>
+#include <libraries/iffparse.h>
+#include <libraries/maud.h>
+#include <libraries/toccata.h>
+#include <libraries/xmoduleclass.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/iffparse.h>
+#include <proto/datatypes.h>
+#include <proto/toccata.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+#define UNITY 0x10000L
+
+
+/* Local function prototypes */
+static LONG DTLoadInstrument   (struct SongInfo *si, ULONG num, CONST_STRPTR filename);
+static LONG LoadMAUDInstrument (struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename);
+static LONG RawLoadInstrument  (struct SongInfo *si, ULONG num, CONST_STRPTR filename, UWORD mode);
+static BYTE D1Unpack                   (UBYTE source[], LONG n, BYTE dest[], BYTE x);
+static void DUnpack                            (UBYTE source[], LONG n, BYTE dest[]);
+
+
+
+struct Library *DataTypesBase  = NULL;
+struct Library *ToccataBase    = NULL;
+
+
+
+GLOBALCALL LONG LoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename)
+
+/* Load an instrument file to the instrument slot <num> in the passed song.
+ * Will use DataTypes if available, otherwise it will use the built-in loaders.
+ */
+{
+       LONG err = IFFERR_NOTIFF;
+
+       /* Try loading with DataTypes */
+
+       if (GuiSwitches.UseDataTypes)
+               if (DataTypesBase = OpenLibrary ("datatypes.library", 39L))
+               {
+                       err = DTLoadInstrument (si, num, filename);
+                       CloseLibrary (DataTypesBase);   DataTypesBase = NULL;
+               }
+
+
+       /* Try again using built-in loaders */
+
+       if (err)
+       {
+               struct IFFHandle *iff;
+
+               if (iff = AllocIFF())
+               {
+                       if (iff->iff_Stream = (ULONG) Open (filename, MODE_OLDFILE))
+                       {
+                               InitIFFasDOS (iff);
+
+                               if (!(err = OpenIFF (iff, IFFF_READ)))
+                               {
+                                       if (!(err = ParseIFF (iff, IFFPARSE_RAWSTEP)))
+                                       {
+                                               LONG type = CurrentChunk (iff)->cn_Type;
+                                               switch (type)
+                                               {
+                                                       case ID_8SVX:
+                                                                       err = Load8SVXInstrument (si, num, iff, filename);
+                                                               break;
+
+                                                       case ID_MAUD:
+                                                                       err = LoadMAUDInstrument (si, num, iff, filename);
+                                                               break;
+
+                                                       default:
+                                                       {
+                                                               UBYTE buf[5];
+
+                                                               IDtoStr (type, buf);
+                                                               ShowMessage (MSG_UNKNOWN_IFF, buf);
+                                                               err = ERROR_OBJECT_WRONG_TYPE;
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               Close (iff->iff_Stream);
+                       }
+                       else err = IoErr();
+
+                       FreeIFF (iff);
+               }
+               else err = ERROR_NO_FREE_STORE;
+       }
+
+       if (err == IFFERR_MANGLED || err == IFFERR_SYNTAX)
+               ShowMessage (MSG_ILLEGAL_IFF_STRUCTURE);
+
+       if (err == IFFERR_NOTIFF)
+       {
+               LONG mode;
+
+               if (mode = ShowRequestArgs (MSG_SELECT_RAW_MODE,
+               MSG_RAW_MODES, NULL))
+                       err = RawLoadInstrument (si, num, filename, mode);
+               else err = 0;
+       }
+
+       UpdateInstrList();
+
+       if (err) LastErr = err;
+       return err;
+}
+
+
+
+static LONG DTLoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename)
+{
+       Object  *dto;
+       UBYTE   *instname;
+       LONG     err = 0;
+
+       if (dto = NewDTObject (filename,
+               DTA_GroupID, GID_SOUND,
+               TAG_DONE))
+       {
+               struct VoiceHeader *vhdr;
+               LONG Len, Vol;
+               UBYTE *Sample;
+               UBYTE *errorstring;
+
+               if (GetDTAttrs (dto,
+                       SDTA_VoiceHeader,       &vhdr,
+                       SDTA_SampleLength,      &Len,
+                       SDTA_Volume,            &Vol,
+                       SDTA_Sample,            &Sample,
+                       DTA_Title,                      &instname,
+                       TAG_DONE) == 5)
+               {
+                       /* Detach sample from DataType Object, so it won't
+                        * be freed when we dispose the object.
+                        */
+                       SetDTAttrs (dto, NULL, NULL,
+                               SDTA_Sample, NULL,
+                               TAG_DONE);
+
+                       /* Create new instrument */
+
+                       if (!xmAddInstrument (si, num,
+                               INSTRA_Name,    instname,
+                               INSTRA_Sample,  Sample,
+                               INSTRA_Length,  Len,
+                               INSTRA_Repeat,  vhdr->vh_OneShotHiSamples,
+                               INSTRA_Replen,  vhdr->vh_RepeatHiSamples,
+
+                               /* The sound.datatype _should_ return
+                                * volumes in the normal Amiga range 0-64.
+                                * However, the 8svx.datatype behaves
+                                * differently: it returns the volume in the
+                                * standard 8SVX format, where the maximum
+                                * volume is $10000.  Here is a good
+                                * workaround to this bug.
+                                */
+                               INSTRA_Volume,  (vhdr->vh_Volume > 64) ?
+                                       ((vhdr->vh_Volume * 64) / UNITY) : vhdr->vh_Volume,
+                               TAG_DONE))
+                               err = ERROR_NO_FREE_STORE;
+
+                               DB(kprintf ("Vol: %ld InstVol: %ld\n", Vol, si->Instr[num]->Volume));
+               }
+               else err = RETURN_FAIL;
+
+
+               if (GetDTAttrs (dto,
+                       DTA_ErrorString, &errorstring,
+                       TAG_DONE) == 1)
+                       ShowMessage (MSG_DATATYPES_ERROR, errorstring);
+
+               DisposeDTObject (dto);
+       }
+       else err = IoErr();
+
+       return err;
+}
+
+
+
+GLOBALCALL LONG Load8SVXInstrument (struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename)
+
+/* Load an IFF 8SVX file to the instrument slot <num>.  If <num> is 0, it requires
+ * the INST chunk and it uses the num stored there.
+ * Can decode Fibonacci Delta encoded samples.
+ */
+{
+       struct ContextNode              *cn;
+       struct VoiceHeader               vhdr;
+       struct InstrumentHeader  insthdr;
+       LONG    err;
+       UBYTE   name[64];
+       BOOL    is_valid_8svx = FALSE,
+                       insthdr_loaded = FALSE;
+
+       static LONG stopchunks[] =
+       {
+               ID_8SVX, ID_INST,
+               ID_8SVX, ID_VHDR,
+               ID_8SVX, ID_BODY,
+               ID_8SVX, ID_NAME
+       };
+
+       /* Put the file name if the optional NAME propriety is missing */
+       if (filename)
+       {
+               strncpy (name, FilePart (filename), 63);
+               name[63] = '\0';
+       }
+       else name[0] = '\0';
+
+       if (err = StopChunks (iff, stopchunks, 4))
+               return err;
+
+       if (err = StopOnExit (iff, ID_8SVX, ID_FORM))
+               return err;
+
+       while (1)
+       {
+               if (err = ParseIFF (iff, IFFPARSE_SCAN))
+               {
+                       if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
+                       break; /* Free resources & exit */
+               }
+
+               if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_8SVX))
+               {
+                       switch (cn->cn_ID)
+                       {
+                               case ID_INST:
+                               {
+                                       if ((err = ReadChunkBytes (iff, &insthdr, sizeof (insthdr))) !=
+                                               sizeof (insthdr)) return err;
+
+                                       if (!num) num = insthdr.Num;
+                               }
+
+                               case ID_VHDR:
+                               {
+                                       if ((err = ReadChunkBytes (iff, &vhdr, sizeof (vhdr))) !=
+                                               sizeof (vhdr)) return err;
+
+                                       if (!is_valid_8svx)
+                                       {
+                                               if (!xmAddInstrumentA (si, num, NULL))
+                                                       return ERROR_NO_FREE_STORE;
+
+                                               if (!num) num = si->LastInstrument;
+                                       }
+
+                                       xmSetInstrument (si, num,
+                                               INSTRA_Repeat,          (vhdr.vh_RepeatHiSamples ? vhdr.vh_OneShotHiSamples : 0),
+                                               INSTRA_Replen,          vhdr.vh_RepeatHiSamples,
+                                               INSTRA_Volume,          (vhdr.vh_Volume * 64) / UNITY,
+                                               INSTRA_FineTune,        insthdr_loaded ? insthdr.FineTune : 0,
+                                               TAG_DONE);
+
+                                       is_valid_8svx = TRUE;
+
+                                       break;
+                               }
+
+                               case ID_BODY:
+                               {
+                                       struct Instrument *instr;
+                                       BYTE *sample;
+
+                                       if (!is_valid_8svx)
+                                       {
+                                               xmAddInstrumentA (si, num, NULL);
+                                               if (!num) num = si->LastInstrument;
+                                       }
+
+                                       if (!(instr = si->Instr[num]))
+                                               return ERROR_NO_FREE_STORE;
+
+                                       if (!(sample = AllocVec (cn->cn_Size, MEMF_SAMPLE)))
+                                               return ERROR_NO_FREE_STORE;
+
+                                       xmSetInstrument (si, num,
+                                               INSTRA_Sample,  sample,
+                                               INSTRA_Length,  cn->cn_Size,
+                                               TAG_DONE);
+
+                                       /* We only require that at least some data is
+                                        * read.  This way if, say, you have a corrupted
+                                        * 8SVX file, you can still load part of the
+                                        * instrument data.
+                                        */
+                                       if ((err = ReadChunkBytes (iff, instr->Sample,
+                                               cn->cn_Size)) < 0)
+                                               return err;
+
+                                       is_valid_8svx = TRUE;
+                                       break;
+                               }
+
+                               case ID_NAME:
+                                       ReadChunkBytes (iff, name, min(cn->cn_Size, 63));
+                                       name[63] = '\0'; /* Ensure string termination */
+                                       break;
+
+                               default:
+                                       break;
+                       }
+               }
+       }
+
+       if (is_valid_8svx)
+       {
+               xmSetInstrument (si, num,
+                       INSTRA_Name,    name,
+                       TAG_DONE);
+
+               if (!err)
+               {
+                       if (vhdr.vh_Compression == CMP_FIBDELTA)
+                       {
+                               BYTE *buf;
+                               struct Instrument *instr = si->Instr[num];
+
+                               if (buf = AllocVec (instr->Length * 2, MEMF_SAMPLE))
+                               {
+                                       DUnpack (instr->Sample, instr->Length + 2, buf);
+                                       FreeVec (instr->Sample);
+
+                                       xmSetInstrument (si, num,
+                                               INSTRA_Sample,  buf,
+                                               INSTRA_Length,  instr->Length * 2,
+                                               TAG_DONE);
+                               }
+                       }
+                       else if (vhdr.vh_Compression != CMP_NONE)
+                               ShowMessage (MSG_UNKNOWN_COMPRESSION);
+               }
+       }
+       else err = IFFERR_MANGLED;
+
+       return err;
+}
+
+
+
+/* Number of samples loaded & processed at one time */
+#define MAUDBLOCKSIZE 32768
+
+static LONG LoadMAUDInstrument (struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename)
+
+/* Load an IFF MAUD file to the instrument slot <num> of the passed song.
+ * MAUD is the standard file format for Macrosystem's audio boards
+ * Toccata and Maestro.
+ */
+{
+       struct ContextNode      *cn;
+       struct MaudHeader mhdr;
+       LONG err;
+       BOOL    is_valid_maud = FALSE;
+       UBYTE   name[64];
+
+       static LONG stopchunks[] =
+       {
+               ID_MAUD, ID_MHDR,
+               ID_MAUD, ID_MDAT,
+               ID_MAUD, ID_NAME
+       };
+
+
+       /* Put the file name if the optional NAME propriety is missing */
+
+       if (filename)
+       {
+               strncpy (name, FilePart (filename), 63);
+               name[63] = '\0';
+       }
+       else name[0] = '\0';
+
+       if (err = StopChunks (iff, stopchunks, 3))
+               return err;
+
+       if (err = StopOnExit (iff, ID_MAUD, ID_FORM))
+               return err;
+
+
+       while (1)
+       {
+               if (err = ParseIFF (iff, IFFPARSE_SCAN))
+               {
+                       if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
+                       break; /* Free resources & exit */
+               }
+
+               if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_MAUD))
+               {
+                       switch (cn->cn_ID)
+                       {
+                               case ID_MHDR:
+                                       if ((err = ReadChunkBytes (iff, &mhdr, sizeof (mhdr))) !=
+                                               sizeof (mhdr)) return err;
+
+                                       if ((mhdr.mhdr_SampleSizeU != 8) && (mhdr.mhdr_SampleSizeU != 16))
+                                       {
+                                               ShowMessage (MSG_SAMPLE_WRONG_SIZE, mhdr.mhdr_SampleSizeU);
+                                               return IFFERR_SYNTAX;
+                                       }
+
+                                       if (mhdr.mhdr_ChannelInfo != MCI_MONO)
+                                       {
+                                               ShowMessage (MSG_SAMPLE_NOT_MONO, mhdr.mhdr_ChannelInfo);
+                                               return IFFERR_SYNTAX;
+                                       }
+
+                                       if (mhdr.mhdr_Channels != 1)
+                                       {
+                                               ShowMessage (MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS, mhdr.mhdr_Channels);
+                                               return IFFERR_SYNTAX;
+                                       }
+
+                                       is_valid_maud = TRUE;
+                                       break;
+
+                               case ID_MDAT:
+                               {
+                                       ULONG i;
+                                       struct Instrument *instr;
+                                       BYTE *sample;
+
+                                       if (!is_valid_maud)
+                                               return IFFERR_SYNTAX;
+
+                                       if (!(sample = AllocVec ((mhdr.mhdr_Samples + 1) & (~1), MEMF_SAMPLE)))
+                                               return ERROR_NO_FREE_STORE;
+
+                                       if (!(instr = xmAddInstrument (si, num,
+                                               INSTRA_Sample,  sample,
+                                               INSTRA_Length,  (mhdr.mhdr_Samples + 1) & (~1),
+                                               TAG_DONE)))
+                                       {
+                                               FreeVec (sample);
+                                               return ERROR_NO_FREE_STORE;
+                                       }
+
+                                       if (mhdr.mhdr_SampleSizeU == 8)                         /* 8 bit */
+                                       {
+                                               /* We only require that at least some data is
+                                                * read.  This way if, say, you have a corrupted
+                                                * MAUD file, you can still load part of the
+                                                * sample data.
+                                                */
+                                               if ((err = ReadChunkBytes (iff, instr->Sample,
+                                                       instr->Length)) == 0) return err;
+
+                                               SampChangeSign8 (instr->Sample, instr->Length);
+
+                                       }
+                                       else if (mhdr.mhdr_SampleSizeU == 16)           /* 16 bit */
+                                       {
+                                               WORD *tmp;
+                                               ULONG actual, current = 0;
+
+                                               if (!(tmp = AllocPooled (Pool, MAUDBLOCKSIZE * ((mhdr.mhdr_SampleSizeC + 7) / 8))))
+                                                       return ERROR_NO_FREE_STORE;
+
+                                               if (mhdr.mhdr_Compression != MCOMP_NONE)
+                                                       if (!(ToccataBase = MyOpenLibrary ("toccata.library", 0L)))
+                                                       {
+                                                               FreePooled (Pool, tmp, MAUDBLOCKSIZE * sizeof (WORD));
+                                                               CantOpenLib ("toccata.library", 0L);
+                                                               return ERROR_INVALID_RESIDENT_LIBRARY;
+                                                       }
+
+                                               for (;;)
+                                               {
+                                                       actual = ReadChunkBytes (iff, tmp, MAUDBLOCKSIZE * ((mhdr.mhdr_SampleSizeC + 7) / 8));
+
+                                                       if (actual == 0) break;
+
+                                                       /* Filter (tmp, actual); */
+
+                                                       switch (mhdr.mhdr_Compression)
+                                                       {
+                                                               case MCOMP_ALAW:
+                                                                       /* Convert 8bit A-Law data to 8bit signed data */
+                                                                       T_Convert (tmp, instr->Sample + current, actual, TMODE_ALAW, TMODE_LINEAR_8);
+                                                                       current += actual;
+                                                                       break;
+
+                                                               case MCOMP_ULAW:
+                                                                       /* Convert 8bit µ-Law data to 8bit signed data */
+                                                                       T_Convert (tmp, instr->Sample + current, actual, TMODE_ULAW, TMODE_LINEAR_8);
+                                                                       current += actual;
+                                                                       break;
+
+                                                               default:
+                                                                       /* Convert 16bit signed data to 8bit signed data */
+                                                                       actual >>= 1;
+                                                                       for (i = 0; (i < actual) && (current < mhdr.mhdr_Samples); i++, current++)
+                                                                               instr->Sample[current] = tmp[i] >> 8;
+                                                                       break;
+                                                       }
+                                               }
+
+                                               SampChangeSign8 (instr->Sample, instr->Length);
+                                               FreePooled (Pool, tmp, MAUDBLOCKSIZE * sizeof (WORD));
+                                               CloseLibrary (ToccataBase);
+                                               ToccataBase = NULL;
+                                       }
+
+                                       is_valid_maud = TRUE;
+                                       break;
+                               }
+
+                               case ID_NAME:
+                                       ReadChunkBytes (iff, name, min(cn->cn_Size, 63));
+                                       break;
+
+                               default:
+                                       break;
+                       }
+               }
+       }
+
+       if (is_valid_maud)
+       {
+               xmSetInstrument (si, num,
+               INSTRA_Name,    name,
+               TAG_DONE);
+
+               if (!err)
+               {
+                       if (mhdr.mhdr_Compression == CMP_FIBDELTA)
+                       {
+                               BYTE *buf;
+                               struct Instrument *instr = si->Instr[num];
+
+                               if (buf = AllocVec (instr->Length * 2, MEMF_SAMPLE))
+                               {
+                                       DUnpack (instr->Sample, instr->Length + 2, buf);
+                                       FreeVec (instr->Sample);
+
+                                       xmSetInstrument (si, num,
+                                               INSTRA_Sample,  buf,
+                                               INSTRA_Length,  instr->Length * 2,
+                                               TAG_DONE);
+                               }
+                       }
+                       else if (mhdr.mhdr_Compression != CMP_NONE)
+                               ShowMessage (MSG_UNKNOWN_COMPRESSION);
+               }
+       }
+       else err = IFFERR_NOTIFF;
+
+       return err;
+}
+
+
+
+static LONG RawLoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename, UWORD mode)
+
+/* Load a raw file to the instrument slot pointed by inst.
+ * mode   1 - signed 8bit
+ *        2 - unsigned 8bit
+ */
+{
+       BPTR lock, fh;
+       struct FileInfoBlock *fib;
+       struct Instrument *instr;
+       LONG err = 0;
+       ULONG len;
+
+       if (lock = Lock (filename, ACCESS_READ))
+       {
+               /* Get file size */
+               if (fib = AllocDosObject (DOS_FIB, NULL))
+               {
+                       if (Examine (lock, fib))
+                               len = fib->fib_Size;
+                       else
+                               err = IoErr();
+                       FreeDosObject (DOS_FIB, fib);
+               }
+               else err = ERROR_NO_FREE_STORE;
+
+               if (!err)
+               {
+                       if (fh = OpenFromLock (lock))
+                       {
+                               BYTE *sample;
+
+                               lock = NULL;    /* OpenFromLock() relinquished our lock! */
+
+                               if (sample = AllocVec (len, MEMF_SAMPLE))
+                               {
+                                       if (instr = xmAddInstrument (si, num,
+                                               INSTRA_Sample,  sample,
+                                               INSTRA_Length,  len,
+                                               INSTRA_Volume,  64,
+                                               INSTRA_Name,    FilePart (filename),
+                                               TAG_DONE))
+                                       {
+                                               /* We do not check for failure here to
+                                                * be more error tolerant.  This way you
+                                                * can load at least part of an instrument
+                                                * from a corrupted file ;-)
+                                                */
+                                               Read (fh, sample, len);
+
+                                               if (mode == 2)
+                                                       SampChangeSign8 (sample, instr->Length);
+                                       }
+                                       else
+                                       {
+                                               FreeVec (sample);
+                                               err = ERROR_NO_FREE_STORE;
+                                       }
+                               }
+                               else err = ERROR_NO_FREE_STORE;
+
+                               Close (fh);
+                       }
+                       else err = IoErr();
+               }
+
+               UnLock (lock);  /* Will be NULL if OpenFromLock() was successful */
+       }
+       else err = IoErr();
+
+       return err;
+}
+
+
+
+GLOBALCALL LONG SaveInstrument (struct Instrument *inst, CONST_STRPTR filename)
+{
+       LONG err;
+       struct IFFHandle *iff;
+
+
+       if (iff = AllocIFF())
+       {
+               if (iff->iff_Stream = (ULONG) Open (filename, MODE_NEWFILE))
+               {
+                       InitIFFasDOS (iff);
+
+                       if (!(err = OpenIFF (iff, IFFF_WRITE)))
+                       {
+                               err = Save8SVXInstrument (inst, 0, iff);
+                               CloseIFF (iff);
+                       }
+
+                       Close (iff->iff_Stream);
+               }
+               else err = IoErr();
+
+               FreeIFF (iff);
+       }
+       else return ERROR_NO_FREE_STORE;
+
+       if (!err)
+       {
+               if (GuiSwitches.InstrSaveIcons)
+                       /* Write icon */
+                       PutIcon ("def_Instrument", filename);
+       }
+       else
+       {
+               /* Remove incomplete file */
+               LastErr = err;
+               DeleteFile (filename);
+       }
+
+       return (err);
+}
+
+
+
+GLOBALCALL LONG Save8SVXInstrument (struct Instrument *instr, ULONG num, struct IFFHandle *iff)
+
+/* Save the instrument pointed by inst to a standard IFF 8SVX file.
+ */
+{
+       struct VoiceHeader vhdr;
+       LONG err;
+
+
+       /* Write 8SVX */
+
+       if (err = PushChunk (iff, ID_8SVX, ID_FORM, IFFSIZE_UNKNOWN))
+               return err;
+
+
+       /* Write INST */
+       if (num)
+       {
+               struct InstrumentHeader insthdr;
+
+               insthdr.Num = num;
+               insthdr.Type = ITYPE_SAMPLE8;
+               insthdr.FineTune = instr->FineTune;
+
+               if (err = PushChunk (iff, ID_8SVX, ID_INST, sizeof (insthdr)))
+                       return err;
+               if ((err = WriteChunkBytes (iff, &insthdr, sizeof (insthdr))) !=
+                       sizeof (insthdr))
+                       return err;
+               if (err = PopChunk (iff)) return err;   /* Pop INST */
+       }
+
+       /* Write VHDR */
+
+       if (vhdr.vh_RepeatHiSamples = instr->Replen)
+               /* Loop */
+               vhdr.vh_OneShotHiSamples = instr->Repeat;
+       else
+               /* No Loop */
+               vhdr.vh_OneShotHiSamples = instr->Length;
+
+       vhdr.vh_SamplesPerHiCycle = 0;
+       vhdr.vh_SamplesPerSec = 8363;
+       vhdr.vh_Octaves = 1;
+       vhdr.vh_Compression = CMP_NONE;
+       vhdr.vh_Volume = (instr->Volume * UNITY) / 64;
+
+       if (err = PushChunk (iff, ID_8SVX, ID_VHDR, sizeof (vhdr)))
+               return err;
+       if ((err = WriteChunkBytes (iff, &vhdr, sizeof (vhdr))) !=
+               sizeof (vhdr))
+               return err;
+       if (err = PopChunk (iff)) return err;   /* Pop VHDR */
+
+
+       /* Write NAME */
+       {
+               ULONG l = strlen (instr->Name) + 1;
+
+               if (err = PushChunk (iff, ID_8SVX, ID_NAME, l))
+                       return err;
+               if ((err = WriteChunkBytes (iff, instr->Name, l)) != l)
+                       return err;
+               if (err = PopChunk (iff)) return err;   /* Pop NAME */
+       }
+
+       /* Write BODY */
+
+       if (instr->Sample)
+       {
+               if (PushChunk (iff, ID_8SVX, ID_BODY, instr->Length))
+                       return err;
+               if ((err = WriteChunkBytes (iff, instr->Sample, instr->Length)) !=
+                       instr->Length) return err;
+               if (err = PopChunk (iff)) return err;   /* Pop BODY */
+       }
+
+       PopChunk (iff); /* Pop 8SVX */
+
+       return err;
+}
+
+
+
+GLOBALCALL void OptimizeInstruments (struct SongInfo *si)
+
+/* Remove useless sample data (cut beyond loops and zero-tails) */
+{
+       UWORD   i;
+       ULONG   newlen;
+       struct Instrument *instr;
+
+       for (i = 1 ; i <= si->LastInstrument ; i++)
+       {
+               if (!(instr = si->Instr[i])) continue;
+               if (!instr->Sample) continue;
+
+               newlen = instr->Length;
+
+               if (instr->Replen)
+               {
+                       if (instr->Length > instr->Repeat + instr->Replen)
+                               newlen = instr->Repeat + instr->Replen; /* Cut instrument after loop */
+               }
+               else
+               {
+                       BYTE *tail;
+
+                       instr->Repeat = 0;      /* Kill null loops */
+
+                       /* Kill instrument zero-tail.
+                        * In order to reduce the instrument even more,
+                        * 1 & -1 are treated the same as zero.
+                        */
+
+                       tail = instr->Sample + instr->Length - 1;
+                       while ((*tail < 1) && (*tail > -1) && (tail > instr->Sample))
+                               tail--;
+
+                       newlen = tail - instr->Sample;
+                       if (newlen & 1) newlen++;       /* Pad instrument size to words */
+
+                       /* leave 2 end zeroes to prevent an audible end-of-instrument click. */
+                       if (newlen) newlen += 2;
+               }
+
+               if (newlen == 0)
+                       xmRemInstrument (si, i);        /* This instrument is mute!  Free it... */
+               else if (newlen < instr->Length)
+               {
+                       /* Resize the instrument if necessary */
+                       BYTE *newinstr;
+
+                       /* Allocate memory for optimized instrument */
+                       if (!(newinstr = AllocVec (newlen, MEMF_SAMPLE)))
+                       {
+                               ShowMessage (MSG_NO_MEMORY_TO_OPTIMIZE_INSTR, i);
+                               continue;       /* Better luck with next instrument :) */
+                       }
+
+                       ShowMessage (MSG_INSTR_WILL_SHRINK, i, instr->Length, newlen);
+
+                       /* Copy first part of instrument */
+                       CopyMem (instr->Sample, newinstr, newlen);
+
+                       /* Free old instrument */
+                       FreeVec (instr->Sample);
+
+                       /* Replace with new instrument */
+                       xmSetInstrument (si, i,
+                               INSTRA_Sample,  newinstr,
+                               INSTRA_Length,  newlen,
+                               TAG_DONE);
+
+               }
+       }
+}
+
+
+
+GLOBALCALL void RemDupInstruments (struct SongInfo *si)
+/* Find out identical patterns and cut them out */
+{
+       ULONG i, j, k, w, v;
+       struct Instrument *insta, *instb;
+       struct Pattern *patt;
+       struct Note *note;
+
+
+       for (i = 1; i < si->LastInstrument; i++)        /* '<' instead of '<=' is ok here... */
+       {
+               if (!(insta = si->Instr[i])) continue;
+               if (!insta->Length) continue;
+
+               for (j = i + 1; j <= si->LastInstrument ; j++)
+               {
+                       if (!(instb = si->Instr[j])) continue;
+
+                       if (insta->Length == instb->Length &&
+                               insta->Repeat == instb->Repeat &&
+                               insta->Replen == instb->Replen &&
+                               insta->Volume == instb->Volume &&
+                               insta->Type == instb->Type &&
+                               insta->FineTune == instb->FineTune)
+                       {
+                               if (!memcmp (insta->Sample, instb->Sample, insta->Length))
+                               {
+                                       xmRemInstrument (si, j);
+
+                                       for (k = 0; k < si->NumPatterns; k++)
+                                       {
+                                               if (!(patt = si->Patt[k])) continue;
+
+                                               for (w = 0; w < patt->Tracks; w++)
+                                               {
+                                                       note = patt->Notes[w];
+                                                       for (v = 0; v < patt->Lines; v++, note++)
+                                                               if (note->Inst == j) note->Inst = i;
+                                               }
+                                       }
+
+                                       ShowMessage (MSG_INSTR_DUPES_REMOVED, i, j);
+                               }
+                       }
+               }
+       }
+
+}
+
+
+
+GLOBALCALL void RemapInstruments (struct SongInfo *si)
+
+/* Remove empty slots between instruments, to allow those module formats
+ * that support less instruments to use even the last instruments.
+ */
+{
+       UWORD i, j, k;
+       UBYTE newpos[MAXINSTRUMENTS] = { 0 };
+       struct Instrument *instr;
+       struct Pattern *patt;
+       struct Note *note;
+
+       /* newpos[0] = 0; */
+
+       DB (kprintf ("Before - LastInstrument = %ld", si->LastInstrument));
+
+       /* Build instrument remap table &  compress instrument slots */
+       for (i = 1, j = 0; i <= si->LastInstrument; i++)
+       {
+               if (!(instr = si->Instr[i])) continue;
+
+               if (instr->Length)
+               {
+                       j++;
+                       newpos[i] = j;
+
+                       if (j != i) DoMethod ((Object *)si, SNGM_SWAPINSTRUMENTS, i, j);
+               }
+               else newpos[i] = 0;
+       }
+
+
+       /* Update score */
+       for (i = 0 ; i < si->NumPatterns ; i++)
+       {
+               patt = si->Patt[i];
+
+               for (j = 0 ; j < patt->Tracks ; j++)
+               {
+                       note = patt->Notes[j];
+
+                       for (k = 0; k < patt->Lines ; k++, note++)
+                               if (note->Note)
+                               {
+                                       note->Inst = newpos[note->Inst];
+                                       if (!note->Inst)
+                                               note->Note = 0;
+                               }
+               }
+       }
+
+       DB (kprintf ("After - LastInstrument = %ld", si->LastInstrument));
+}
+
+
+
+GLOBALCALL void RemUnusedInstruments (struct SongInfo *si)
+{
+       ULONG usecount[MAXINSTRUMENTS] = { 0 };
+       struct Pattern *patt;
+       struct Note *note;
+       UWORD i, j, k;
+
+       for (i = 0; i < si->NumPatterns; i++)
+       {
+               if (!(patt = si->Patt[i])) continue;
+
+               for (j = 0; j < patt->Tracks; j++)
+               {
+                       note = patt->Notes[j];
+
+                       for (k = 0; k < patt->Lines; k++, note++)
+                               usecount[note->Inst]++;
+               }
+       }
+
+       for (i = 1; i <= si->LastInstrument; i++)
+       {
+               if ((usecount[i] == 0) && si->Instr[i])
+               {
+                       ShowMessage (MSG_INSTR_UNUSED, i);
+                       xmRemInstrument (si, i);
+               }
+       }
+}
+
+
+
+/* DUnpack.c --- Fibonacci Delta decompression by Steve Hayes */
+
+/* Fibonacci delta encoding for sound data */
+static const BYTE codeToDelta[16] = {-34,-21,-13,-8,-5,-3,-2,-1,0,1,2,3,5,8,13,21};
+
+
+static BYTE D1Unpack (UBYTE source[], LONG n, BYTE dest[], BYTE x)
+
+/* Unpack Fibonacci-delta encoded data from n byte source
+ * buffer into 2*n byte dest buffer, given initial data
+ * value x.  It returns the last data value x so you can
+ * call it several times to incrementally decompress the data.
+ */
+{
+       UBYTE d;
+       LONG i, lim;
+
+       lim = n << 1;
+       for (i = 0; i < lim; ++i)
+       {
+               /* Decode a data nibble, high nibble then low nibble */
+               d = source[i >> 1];             /* get a pair of nibbles                */
+               if (i & 1)                              /* select low or high nibble    */
+                       d &= 0xf;                       /* mask to get the low nibble   */
+               else
+                       d >>= 4;                        /* shift to get the high nibble */
+               x += codeToDelta[d];    /* add in the decoded delta             */
+               dest[i] = x;                    /* store a 1 byte sample                */
+       }
+       return x;
+}
+
+
+static void DUnpack (UBYTE source[], LONG n, BYTE dest[])
+
+/* Unpack Fibonacci-delta encoded data from n byte
+ * source buffer into 2*(n-2) byte dest buffer.
+ * Source buffer has a pad byte, an 8-bit initial
+ * value, followed by n-2 bytes comprising 2*(n-2)
+ * 4-bit encoded samples.
+ */
+{
+       D1Unpack (source+2, n-2, dest, (BYTE)source[1]);
+}
+
+
+
+GLOBALCALL void SampChangeSign8 (UBYTE *samp, ULONG len)
+
+/* Performs a sign conversion on a 8bit sample.  The same function can be
+ * used to convert a signed sample into an unsigned one and vice versa.
+ *
+ * TODO: optimize with a LONG oriented loop
+ */
+{
+       while (len)
+               samp[--len] ^= 0x80;
+}
diff --git a/InstrumentsWin.c b/InstrumentsWin.c
new file mode 100644 (file)
index 0000000..479d6e2
--- /dev/null
@@ -0,0 +1,657 @@
+/*
+**     InstrumentsWin.c
+**
+**     Copyright (C) 1994,95,96 Bernardo Innocenti
+**
+**     Instruments editor handling functions.
+*/
+
+#include <exec/nodes.h>
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+#include <libraries/asl.h>
+#include <workbench/workbench.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Gadgets IDs */
+
+enum
+{
+       GD_InstrGroup0,
+               GD_InstrGroup1,
+                       GD_InstrGroup2,
+                               GD_InstrList,
+                               GD_InstrName,
+                       GD_InstrGroup3,
+                               GD_InstrNew,
+                               GD_InstrOpen,
+                               GD_InstrDel,
+                               GD_InstrUp,
+                               GD_InstrDown,
+               GD_InstrGroup4,
+                       GD_InstrVolume,
+                       GD_InstrFineTune,
+                       GD_InstrLen,
+                       GD_InstrKind,
+                       GD_InstrEdit,
+
+       Instruments_CNT
+};
+
+
+
+/*****************************/
+/* Local function prototypes */
+/*****************************/
+
+static void InstrumentsDropIcon                (struct AppMessage *msg);
+static void InstrumentsPostOpen                (void);
+static void InstrumentsPostClose       (void);
+void UpdateInstrInfo   (void);
+void UpdateInstrList   (void);
+
+static void InstrNewClicked                    (void);
+static void InstrOpenClicked           (void);
+static void InstrDelClicked                    (void);
+static void InstrUpClicked                     (void);
+// TODO static void InstrSwapClicked           (void);
+static void InstrDownClicked           (void);
+static void InstrNameClicked           (struct WinUserData *wud);
+static void InstrListClicked           (void);
+static void InstrVolumeClicked         (void);
+static void InstrFineTuneClicked       (void);
+static void InstrEditClicked           (void);
+static void InstrKindClicked           (void);
+
+static void InstrumentsMiLoad          (void);
+static void InstrumentsMiSave          (void);
+static void InstrumentsMiSaveAs                (void);
+static void InstrumentsMiRemap         (void);
+static void InstrumentsMiSaveIcons     (void);
+static void InstrumentsMiSaveCompressed        (void);
+static void InstrumentsMiSaveRaw       (void);
+
+
+XDEF struct List       InstrList;
+static ULONG           InstrSecs = 0, InstrMicros = 0;
+
+
+static struct NewMenu InstrumentsNewMenu[] = {
+       NM_TITLE, (STRPTR)MSG_INSTRUMENTS_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_LOAD_MEN, (STRPTR)"L", 0, 0L, (APTR)InstrumentsMiLoad,
+       NM_ITEM, (STRPTR)MSG_SAVE_MEN, (STRPTR)"S", 0, 0L, (APTR)InstrumentsMiSave,
+       NM_ITEM, (STRPTR)MSG_SAVE_AS_MEN, (STRPTR)"A", 0, 0L, (APTR)InstrumentsMiSaveAs,
+       NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
+       NM_ITEM, (STRPTR)MSG_REMAP_MEN, (STRPTR)"R", 0, 0L, (APTR)InstrumentsMiRemap,
+       NM_TITLE, (STRPTR)MSG_SETTINGS_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_SAVE_ICONS_MEN, NULL, CHECKIT|MENUTOGGLE, 0L, (APTR)InstrumentsMiSaveIcons,
+       NM_ITEM, (STRPTR)MSG_SAVE_COMPRESSED_MEN, NULL, CHECKIT|MENUTOGGLE, 4L, (APTR)InstrumentsMiSaveCompressed,
+       NM_ITEM, (STRPTR)MSG_SAVE_RAW_MEN, NULL, CHECKIT|MENUTOGGLE, 2L, (APTR)InstrumentsMiSaveRaw,
+       NM_END, NULL, NULL, 0, 0L, NULL };
+
+
+
+static STRPTR InstrKindLabels[] = {
+       (STRPTR)MSG_SAMPLE_GAD,
+       (STRPTR)MSG_SYNTH_GAD,
+       (STRPTR)MSG_HYBRID_GAD,
+       (STRPTR)0
+};
+
+
+
+static ULONG InstrumentsArgs[] =
+{
+       HGROUP_KIND, BBFT_RIDGE,
+               VGROUP_KIND, 0,
+                       LISTVIEW_KIND,  (ULONG)InstrListClicked,                0,      (ULONG)&InstrList,      TAG_DONE,
+                       STRING_KIND,    (ULONG)InstrNameClicked,                0,      64,                                     TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       BUTTON_KIND,    (ULONG)InstrNewClicked,         MSG_UNDERSCORE_NEW_GAD,         TAG_DONE,
+                       BUTTON_KIND,    (ULONG)InstrOpenClicked,        MSG_OPEN_GAD,                           TAG_DONE,
+                       BUTTON_KIND,    (ULONG)InstrDelClicked,         MSG_DEL_GAD,                            TAG_DONE,
+                       BUTTON_KIND,    (ULONG)InstrUpClicked,          MSG_UNDERSCORE_UP_GAD,          TAG_DONE,
+                       BUTTON_KIND,    (ULONG)InstrDownClicked,        MSG_UNDERSCORE_DOWN_GAD,        TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       VGROUP_KIND,    BBFT_RIDGE,
+               SLIDER_KIND,    (ULONG)InstrVolumeClicked,      MSG_VOLUME_GAD,         0,      64,     (ULONG)"%lu",   3,      TAG_DONE,
+               SLIDER_KIND,    (ULONG)InstrFineTuneClicked,MSG_FINETUNE_GAD,   -8,     +7,     (ULONG)"%ld",   3,      TAG_DONE,
+               NUMBER_KIND,    MSG_LENGHT_GAD,                         7,                                                                                              TAG_DONE,
+               CYCLE_KIND,             (ULONG)InstrKindClicked,        MSG_KIND_GAD,   (ULONG)InstrKindLabels,                 TAG_DONE,
+               BUTTON_KIND,    (ULONG)InstrEditClicked,        MSG_EDIT_DOTS_GAD,                                                              TAG_DONE,
+               ENDGROUP_KIND,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG InstrumentsWinTags[] =
+{
+       XMWIN_NewMenu,          (LONG)InstrumentsNewMenu,
+       XMWIN_LayoutArgs,       (LONG)InstrumentsArgs,
+       XMWIN_GCount,           Instruments_CNT,
+       XMWIN_Title,            MSG_INSTRUMENTS_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       BUTTONIDCMP|STRINGIDCMP|LISTVIEWIDCMP|INTEGERIDCMP|NUMBERIDCMP|IDCMP_MENUPICK|IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW,
+       XMWIN_PostOpenFunc,     (LONG)InstrumentsPostOpen,
+       XMWIN_PostCloseFunc,(LONG)InstrumentsPostClose,
+       XMWIN_DropIconFunc,     (LONG)InstrumentsDropIcon,
+       XMWIN_HelpNode,         (LONG)"Instruments",
+       TAG_DONE
+};
+
+
+
+static void InstrumentsPostOpen (void)
+{
+       UpdateInstrSwitches();
+       UpdateInstrList();
+}
+
+static void InstrumentsPostClose (void)
+{
+       while (!IsListEmpty(&InstrList))
+               RemListViewNode (InstrList.lh_Head);
+}
+
+
+
+static void InstrumentsDropIcon (struct AppMessage *msg)
+{
+       struct SongInfo *si;
+
+       LockWindows();
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               struct WBArg    *wba = msg->am_ArgList;
+               BPTR olddir;
+               UWORD i, j;
+
+               olddir = CurrentDir (wba->wa_Lock);
+
+               for (i = 0, j = si->CurrentInst;
+                       (i < msg->am_NumArgs) && (j < MAXINSTRUMENTS);
+                       i++, j++)
+               {
+                       CurrentDir (msg->am_ArgList->wa_Lock);
+
+                       if (j == 0) j++;
+
+                       if (LastErr = LoadInstrument (si, j, wba->wa_Name))
+                               break;
+
+                       wba++;
+               }
+
+               ReleaseSemaphore (&si->Lock);
+
+               if (si->CurrentInst == 0)
+                       SetAttrs (si,
+                               SNGA_CurrentInst, 1,
+                               TAG_DONE);
+
+               CurrentDir (olddir);
+       }
+
+       UnlockWindows();
+}
+
+
+
+/*************************/
+/* Instruments Functions */
+/*************************/
+
+GLOBALCALL void UpdateInstrList (void)
+{
+       struct SongInfo *si;
+       struct WinUserData *wud = WDescr[WID_INSTRUMENTS].Wud;
+       ULONG i, curr = 0;
+
+       if (wud && wud->Win)
+       {
+               GT_SetGadgetAttrs (wud->Gadgets[GD_InstrList], wud->Win, NULL,
+                       GTLV_Labels, ~0,
+                       TAG_DONE);
+
+               /* Empty previous list */
+               while (!IsListEmpty (&InstrList))
+                       RemListViewNode (InstrList.lh_Head);
+
+               if (si = xmLockActiveSong (SM_SHARED))
+               {
+                       for (i = 1 ; i <= si->LastInstrument; i++)
+                       {
+                               AddListViewNode (&InstrList, "%02lx %s", i,
+                                       si->Instr[i] ?
+                                               (si->Instr[i]->Name ? si->Instr[i]->Name : STR(MSG_UNNAMED))
+                                               : STR(MSG_EMPTY));
+                       }
+
+                       curr = si->CurrentInst - 1;
+
+                       ReleaseSemaphore (&si->Lock);
+               }
+
+               GT_SetGadgetAttrs (wud->Gadgets[GD_InstrList], wud->Win, NULL,
+                       GTLV_Labels, &InstrList,
+                       GTLV_MakeVisible, curr,
+                       TAG_DONE);
+
+               UpdateInstrInfo();
+       }
+}
+
+
+
+GLOBALCALL void UpdateInstrInfo (void)
+{
+       struct SongInfo *si;
+       struct WinUserData *wud;
+
+       if (!(wud = WDescr[WID_INSTRUMENTS].Wud)) return;
+       if (!(wud->Win)) return;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               struct Instrument *instr;
+
+               if (si->CurrentInst && (instr = si->Instr[si->CurrentInst]))
+                       SetGadgets (wud,
+                               GD_InstrName,           instr->Name,
+                               GD_InstrVolume,         instr->Volume,
+                               GD_InstrFineTune,       instr->FineTune,
+                               GD_InstrLen,            instr->Length,
+                               GD_InstrList,           si->CurrentInst - 1,
+                               -1);
+               else
+                       SetGadgets (wud,
+                               GD_InstrName,           NULL,
+                               GD_InstrVolume,         0,
+                               GD_InstrFineTune,       0,
+                               GD_InstrLen,            0,
+                               GD_InstrList,           si->CurrentInst - 1,
+                               -1);
+
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       DB(kprintf("remember to uncomment updatesample!\n"));
+       //UpdateSample();
+       UpdateEditorInst();
+}
+
+
+
+GLOBALCALL void UpdateInstrSwitches (void)
+{
+       struct MenuItem *item;
+       struct WinUserData *wud = WDescr[WID_INSTRUMENTS].Wud;
+
+       if (wud && wud->MenuStrip)
+       {
+               if (wud->Win) ClearMenuStrip (wud->Win);
+
+               item = ItemAddress (wud->MenuStrip, SHIFTMENU(1) | SHIFTITEM(0) );
+
+               /* Save Icons? */
+               if (GuiSwitches.InstrSaveIcons)
+                       item->Flags |= CHECKED;
+               else
+                       item->Flags &= ~CHECKED;
+
+               if (wud->Win) ResetMenuStrip (wud->Win, wud->MenuStrip);
+       }
+}
+
+
+
+static void InstrumentsLoad (STRPTR name, ULONG num, ULONG count)
+{
+       struct SongInfo *si;
+
+       LockWindows();
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               UWORD instnum;
+
+               if (si->CurrentInst == 0)
+                       instnum = 1;
+               else
+                       instnum = si->CurrentInst + num;
+
+               if (instnum < MAXINSTRUMENTS)
+                       LoadInstrument (si, instnum, name);
+
+               if (si->CurrentInst == 0)
+                       SetAttrs (si,
+                               SNGA_CurrentInst, 1,
+                               TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UnlockWindows();
+}
+
+
+
+/***********************/
+/* Instruments Gadgets */
+/***********************/
+
+static void InstrNewClicked (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               xmAddInstrumentA (si, -1, NULL);
+
+               SetAttrs (si,
+                       SNGA_CurrentInst,       si->LastInstrument,
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UpdateSongInfo();
+}
+
+
+
+static void InstrOpenClicked (void)
+{
+       StartFileRequest (FREQ_LOADINST, InstrumentsLoad);
+}
+
+
+
+static void InstrDelClicked (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               xmRemInstrument (si, si->CurrentInst);
+
+               SetAttrs (si,
+                       SNGA_CurrentInst,       si->CurrentInst + 1,
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UpdateSongInfo();
+}
+
+
+
+static void InstrUpClicked (void)
+{
+       struct Instrument *tmp;
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               /* Swap actual instrument with the previous one */
+               if (si->CurrentInst <= 1) return;
+
+               tmp = si->Instr[si->CurrentInst];
+               si->Instr[si->CurrentInst] = si->Instr[si->CurrentInst-1];
+               si->Instr[si->CurrentInst-1] = tmp;
+
+               SetAttrs (si,
+                       SNGA_CurrentInst,       si->CurrentInst-1,
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+
+               UpdateInstrList();
+       }
+}
+
+
+
+static void InstrDownClicked (void)
+{
+       struct SongInfo *si;
+       struct Instrument *tmp;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               /* Swap actual instrument with the next one */
+               if (si->CurrentInst >= MAXINSTRUMENTS-1) DisplayBeep (Scr);
+               else
+               {
+                       /**/ /* Use some OO-legal way to do this!! :) */
+                       tmp = si->Instr[si->CurrentInst];
+                       si->Instr[si->CurrentInst] = si->Instr[si->CurrentInst+1];
+                       si->Instr[si->CurrentInst+1] = tmp;
+                       if (si->CurrentInst + 1 >= si->LastInstrument)
+                               si->LastInstrument = si->CurrentInst + 1;
+
+                       SetAttrs (si,
+                               SNGA_CurrentInst,       si->CurrentInst + 1,
+                               TAG_DONE);
+               }
+
+               ReleaseSemaphore (&si->Lock);
+
+               UpdateInstrList();
+       }
+}
+
+
+
+/*
+static void InstrSwapClicked (void)
+{
+       TODO
+}
+*/
+
+
+static void InstrNameClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               xmSetInstrument (si, si->CurrentInst,
+                       INSTRA_Name, GetString (wud->Gadgets[GD_InstrName]),
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+
+               UpdateInstrList();
+       }
+}
+
+
+
+static void InstrListClicked (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               if (si->CurrentInst != IntuiMsg.Code + 1)
+               {
+                       si->CurrentInst = IntuiMsg.Code + 1;
+                       UpdateInstrInfo();
+               }
+               else
+               {
+                       /* Check Double Click */
+                       if (DoubleClick (InstrSecs, InstrMicros, IntuiMsg.Seconds, IntuiMsg.Micros))
+                               NewWindow (WID_SAMPLE);
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       InstrSecs = IntuiMsg.Seconds;
+       InstrMicros = IntuiMsg.Micros;
+}
+
+
+
+static void InstrVolumeClicked (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               xmSetInstrument (si, si->CurrentInst,
+                       INSTRA_Volume, IntuiMsg.Code,
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void InstrFineTuneClicked (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               xmSetInstrument (si, si->CurrentInst,
+                       INSTRA_FineTune, IntuiMsg.Code,
+                       TAG_DONE);
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void InstrKindClicked (void)
+{
+       /**/
+}
+
+
+
+static void InstrEditClicked (void)
+{
+       NewWindow (WID_SAMPLE);
+}
+
+
+
+/**************/
+/* Menu Items */
+/**************/
+
+static void InstrumentsMiLoad (void)
+{
+       StartFileRequest (FREQ_LOADINST, InstrumentsLoad);
+}
+
+
+
+static void InstrumentsMiSave (void)
+{
+       struct SongInfo *si;
+
+       LockWindows();
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               if (si->Instr[si->CurrentInst])
+                       LastErr = SaveInstrument (si->Instr[si->CurrentInst],
+                               si->Instr[si->CurrentInst]->Name);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UnlockWindows();
+}
+
+
+
+static void InstrumentsMiSaveAs (void)
+{
+       struct SongInfo *si;
+
+       LockWindows();
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               struct Instrument *instr;
+               UBYTE name[PATHNAME_MAX];
+
+               if (instr = si->Instr[si->CurrentInst])
+               {
+                       strncpy (name, instr->Name, PATHNAME_MAX-1);
+                       name[PATHNAME_MAX-1] = '\0';
+
+                       if (FileRequest (FREQ_SAVEINST, name))
+                       {
+                               xmSetInstrument (si, si->CurrentInst,
+                                       INSTRA_Name,    FilePart (name),
+                                       TAG_DONE);
+
+                               LastErr = SaveInstrument (instr, name);
+                       }
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UnlockWindows();
+}
+
+
+
+static void InstrumentsMiRemap (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               RemapInstruments (si);
+               UpdateSongInfo();
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void InstrumentsMiSaveIcons (void)
+{
+       GuiSwitches.InstrSaveIcons ^= 1;
+}
+
+
+
+static void InstrumentsMiSaveCompressed (void)
+{
+       /**/ /* TODO */
+}
+
+
+
+static void InstrumentsMiSaveRaw (void)
+{
+       /**/ /* TODO */
+}
diff --git a/Library.c b/Library.c
new file mode 100644 (file)
index 0000000..16c1918
--- /dev/null
+++ b/Library.c
@@ -0,0 +1,2161 @@
+/*
+**     Library.c
+**
+**     Copyright (C) 1995,96,97 Bernardo Innocenti
+**
+**     xmodule.library functions
+*/
+
+#include <exec/libraries.h>
+#include <exec/memory.h>
+#include <dos/dos.h>
+#include <dos/stdio.h>
+#include <libraries/xmodule.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/utility.h>
+#include <proto/intuition.h>
+#include <proto/utility.h>
+#include <proto/iffparse.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/****** xmodule/--background-- *********************************************
+*
+*      INTRODUCTION
+*              The xmodule.library is an API that provides access to the XModule
+*              song management engine, as well as other general pourpose
+*              services.  Hooks and external applications can use this library
+*              to create, load, save and modify songs.
+*
+*              The xmodule.library does not exist as a stand alone shared libray.
+*              Instead, it's code is contained inside the main XModule executable
+*              (altrough it's logically independant from the rest of the XModule
+*              code).
+*
+*              XModule adds the xmodule.library to the exec libray list at
+*              startup time, unless it finds one already in the list (this might
+*              happen when multiple copies of XModule are running at the same
+*              time).
+*
+*      PUBLIC SONGS
+*              The xmodule.library maintains a list of public songs which can be
+*              used by all applications which opened the library.  Each song in
+*              the list is protected from multiple task access by a
+*              SignalSemaphore, and whole list is also protected by another
+*              semaphore.  The locking mechanism is very simple: before you can
+*              access a song you must obtain its semaphore, and you must release
+*              it as soon as you are done with it.  If you are locking a song to
+*              just to read some information (i.e.: you don't want to modify
+*              anything), you should obtain a shared lock instead of an exclusive
+*              one.  The list must be locked whenever you want to add or remove
+*              a song, or when you scan it in any way.
+*
+*              Since public songs could be modified or even deleted by other
+*              tasks, do not make your songs public unless your code is smart
+*              enough to handle all possible situations.
+*
+*
+*      HOOKS
+*              Actually, most modular programs call them `modules', but it would
+*              have created a lot of confusion with a program like XModule :-).
+*
+*              Hooks are programs that add some kind of functionality
+*              to XModule.  External hook files are standard shared libraries
+*              which will open the xmodule.library when they are loaded and
+*              call xmAddHookA() to add themselves to a list of hooks maintained
+*              by the library.
+*
+*              Currently, XModule supports two kinds of hooks: loaders and
+*              savers.  Loader hooks can also provide a function which
+*              identifies a particular module format.
+*
+*              An external hook libray may also contain several hooks.
+*              Putting a loader and a saver for one particolar format together
+*              in one libray is generally a good idea, while making a hook
+*              with hundereds of loaders and savers isn't a good move because
+*              it makes the whole concept of external hooks quite useless.
+*              Grouping different versions or variants of one format together
+*              in one external hook is acceptable.
+*
+*      SEE ALSO
+*              songclass/--background--, exec/ObtainSemaphore()
+*
+****************************************************************************
+*/
+
+
+
+/* Library function prototypes */
+
+static LIBCALL struct SongInfo * XModuleCreateSong (
+       REG(a0, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL void XModuleDeleteSong (
+       REG(a0, struct SongInfo *si),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL ULONG XModuleAddSong (
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct SongInfo *position),
+       REG(a2, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL ULONG XModuleRemSong (
+       REG(a0, struct SongInfo *si),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL ULONG XModuleActivateSong (
+       REG(a0, struct SongInfo *si),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL struct SongInfo *XModuleLockActiveSong (
+       REG(d0, UWORD mode),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL struct XMHook *XModuleAddHook (
+       REG(a0, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL void XModuleRemHook (
+       REG(a0, struct XMHook *hook),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL struct XMHook *XModuleIdentifyModule(
+       REG(d0, BPTR fh),
+       REG(a0, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL struct SongInfo * XModuleLoadModule (
+       REG(a0, CONST_STRPTR filename),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL LONG XModuleSaveModule (
+       REG(a0, struct SongInfo *si),
+       REG(a1, CONST_STRPTR filename),
+       REG(a2, struct XMHook *saver),
+       REG(a3, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL UWORD *XModuleSetSongLen(
+       REG(a0, struct SongInfo *si),
+       REG(d0, UWORD length),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL void XModuleAddPattern (
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL void XModuleSetPattern (
+       REG(a0, struct SongInfo *si),
+       REG(d0, ULONG pattNum),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL void XModuleRemPattern (
+       REG(a0, struct SongInfo *si),
+       REG(d0, ULONG pattNum),
+       REG(d1, ULONG replaceWith));
+static LIBCALL void XModuleAddInstrument (
+       REG(a0, struct SongInfo *si),
+       REG(d0, LONG num),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL void XModuleSetInstrument (
+       REG(a0, struct SongInfo *si),
+       REG(d0, LONG num),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL void XModuleRemInstrument (
+       REG(a0, struct SongInfo *si),
+       REG(d0, ULONG num),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL LONG XModuleProcessSong (
+       REG(a0, struct SongInfo *si),
+       REG(a1, void *reserved),
+       REG(a2, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL void XModuleDisplayMessage (
+       REG(d0, ULONG level),
+       REG(a0, CONST_STRPTR msg),
+       REG(a1, LONG *args),
+       REG(a6, struct XModuleBase *XModuleBase));
+static LIBCALL void XModuleDisplayProgress (
+       REG(d0, ULONG actual),
+       REG(d1, ULONG max),
+       REG(a6, struct XModuleBase *XModuleBase));
+
+
+
+/* Local function prototypes */
+
+static LIBCALL void XModuleLibOpen             (void);
+static LIBCALL void XModuleLibClose            (void);
+static LIBCALL void XModuleLibExpunge  (void);
+static LIBCALL void XModuleLibExtFunc  (void);
+static LIBCALL LONG XModuleRexxServer  (void);
+
+
+
+/* Library vector table */
+
+static APTR XModuleVectors[] =
+{
+       XModuleLibOpen,
+       XModuleLibClose,
+       XModuleLibExpunge,
+       XModuleLibExtFunc,
+       XModuleRexxServer,
+       XModuleRexxServer,
+       XModuleRexxServer,
+       XModuleRexxServer,
+
+       XModuleCreateSong,
+       XModuleDeleteSong,
+       XModuleAddSong,
+       XModuleRemSong,
+       XModuleActivateSong,
+       XModuleLockActiveSong,
+       XModuleAddHook,
+       XModuleRemHook,
+       XModuleIdentifyModule,
+       XModuleLoadModule,
+       XModuleSaveModule,
+       XModuleSetSongLen,
+       XModuleAddPattern,
+       XModuleSetPattern,
+       XModuleRemPattern,
+       XModuleAddInstrument,
+       XModuleSetInstrument,
+       XModuleRemInstrument,
+       XModuleProcessSong,
+       XModuleDisplayMessage,
+       XModuleDisplayProgress,
+
+       (APTR)-1
+};
+
+
+
+GLOBALCALL ULONG MakeXModuleLibrary (void)
+{
+       if (XModuleBase = (struct XModuleBase *)MakeLibrary (XModuleVectors,
+               NULL, NULL, sizeof (struct XModuleBase), NULL))
+       {
+               /* Clear base variables following the library node
+                * (InitSemaphore reqires a clean memory block!)
+                *
+                * GOOD NEWS: Clearing the library base is not required because
+                * MakeLibrary() allocates a MEMF_CLEAR memory block.
+                *
+                * memset (((UBYTE *)XModuleBase) + sizeof (struct Library), 0,
+                *      sizeof (struct XModuleBase) - sizeof (struct Library));
+                */
+
+               XModuleBase->xm_Library.lib_Node.ln_Name = (STRPTR)"xmodule.library";
+               XModuleBase->xm_Library.lib_Version = VERSION;
+               XModuleBase->xm_Library.lib_Revision = REVISION;
+               XModuleBase->xm_Library.lib_IdString = (STRPTR)Version + 6;
+
+               XModuleBase->xm_DOSBase = DOSBase;
+               XModuleBase->xm_UtilityBase = UtilityBase;
+               XModuleBase->xm_IFFParseBase = IFFParseBase;
+               XModuleBase->xm_IntuitionBase = IntuitionBase;
+
+               NEWLIST ((struct List *)&XModuleBase->xm_Songs);
+               NEWLIST ((struct List *)&XModuleBase->xm_Loaders);
+               NEWLIST ((struct List *)&XModuleBase->xm_Savers);
+               InitSemaphore (&XModuleBase->xm_BaseLock);
+
+               /* Create global memory pool */
+               if (!(XModuleBase->xm_Pool = CreatePool (MEMF_ANY, 16*1024, 4*1024)))
+                       return ERROR_NO_FREE_STORE;
+
+               if (!(XModuleBase->xm_SongClass = InitSongClass()))
+                       return ERROR_NO_FREE_STORE;
+
+               return RETURN_OK;
+       }
+
+       return ERROR_NO_FREE_STORE;
+}
+
+
+
+GLOBALCALL void DisposeXModuleLibrary (void)
+{
+       if (XModuleBase)
+       {
+               ObtainSemaphore (&XModuleBase->xm_BaseLock);
+
+               /* Free all songs in SongList */
+               while (!IsListEmpty ((struct List *)&XModuleBase->xm_Songs))
+                       xmDeleteSong ((struct SongInfo *)XModuleBase->xm_Songs.mlh_Head);
+
+               /* Remove all loaders */
+               while (!IsListEmpty ((struct List *)&XModuleBase->xm_Loaders))
+                       xmRemHook ((struct XMHook *)XModuleBase->xm_Loaders.mlh_Head);
+
+               /* Remove all savers */
+               while (!IsListEmpty ((struct List *)&XModuleBase->xm_Savers))
+                       xmRemHook ((struct XMHook *)XModuleBase->xm_Savers.mlh_Head);
+
+               /* Free the songclass */
+               FreeSongClass (XModuleBase->xm_SongClass);
+
+               /* Dispose the global memory pool */
+               if (XModuleBase->xm_Pool) DeletePool (XModuleBase->xm_Pool);
+
+               FreeMem (((UBYTE *)XModuleBase) - XModuleBase->xm_Library.lib_NegSize,
+                       XModuleBase->xm_Library.lib_NegSize + XModuleBase->xm_Library.lib_PosSize);
+               XModuleBase = NULL;
+       }
+}
+
+
+
+static LIBCALL void XModuleLibOpen (void)
+{
+}
+
+
+
+static LIBCALL void XModuleLibClose (void)
+{
+}
+
+
+
+static LIBCALL void XModuleLibExpunge (void)
+{
+}
+
+
+
+static LIBCALL void XModuleLibExtFunc (void)
+{
+}
+
+
+
+static LIBCALL LONG XModuleRexxServer (void)
+{
+       return 0;
+}
+
+
+
+/****** xmodule/xmAddHook **************************************************
+*
+*      NAME
+*              xmAddHookA -- Creates a new XModule Hook
+*              xmAddHook -- Varargs stub for xmAddHookA
+*
+*      SYNOPSIS
+*              hook = xmAddHookA(tagList)
+*              D0                A0
+*
+*              struct XMHook *xmAddHookA(struct TagItem *);
+*
+*
+*              hook = xmAddHook(Tag1,...)
+*
+*              struct XMHook *xmAddHook(ULONG,...);
+*
+*      FUNCTION
+*              Creates a new XMHook structure and fills it in with data supplied
+*              with the TagList. Adds the newly created Hook to the appropriate
+*              list.
+*
+*      INPUTS
+*              tagList - pointer to a tag list specifying how to initialize the
+*                      XMHook structure.
+*
+*      TAGS
+*              XMHOOK_Type - (ULONG) Defines the pourpose of this hook. Possible
+*                      values are currently NT_XMLOADER and NT_XMSAVER. (This
+*                      tag is REQUIRED).
+*
+*              XMHOOK_Name - (STRPTR) ti_Data contains a short name for the
+*                      hook (e.g: "SoundTracker").  This name will appear in the
+*                      Savers list if this hook is a saver and will be passed as an
+*                      argument for some ARexx commands, so please use a single
+*                      word name.  (This tag is REQUIRED).
+*
+*              XMHOOK_Priority - (BYTE) Priority to give to this hook.  Hooks
+*                      with higher priorities will be used before lower priority
+*                      ones and will come first in lists shown to the user. Valid
+*                      range is -128..+127, but please restrict to a -20..+20
+*                      interval for normal cases. (Defaults to 0).
+*
+*              XMHOOK_Descr - (STRPTR) Verbose description of the hook
+*                      (without newlines). (Defaults to NULL).
+*
+*              XMHOOK_Author - (STRPTR) Author's name.  Please, just put
+*                      your full name here; no greetings, copyright notices,
+*                      etc. (Defaults to NULL).
+*
+*              XMHOOK_ID - (ULONG) This is a unique, IFF-style identifier for
+*                      the format.  If the format is an IFF format, it must be the
+*                      same of the FORM ID. (Defaults to 0, this tag is required
+*                      for IFF loaders and savers).
+*
+*              XMHOOK_Flags - (ULONG) Sets miscellaneous flags for this hook.
+*                      See xmodule.h for possible flags.
+*
+*              XMHOOK_LibraryBase - (struct Library *) Pointer to the library
+*                      base for external hooks.  This pointer will be used to open
+*                      the library when the hook is created and to close it when
+*                      the hook is deleted.  If you do not pass this tag, you must
+*                      find some other way to keep your code in memory until one
+*                      or more of your hooks are active.  XModule will close your
+*                      library just after calling the SetupXMHook() function.
+*                      (Defaults to NULL).
+*
+*              XMHOOK_UserData - (APTR) ti_Data will be stored in the
+*                      xmh_UserData field of the XMHook structure. This field can
+*                      come andy to store private data. (Defaults to NULL).
+*
+*              XMHOOK_RemoveHookFunc - (APTR) Pointer to a function which will be
+*                      called upon removing the hook.  This function can be used to
+*                      free any private resources allocated by the hook when it was
+*                      created. The template for the function is:
+*
+*                              void RemoveHookFunc (struct XMHook *hook);
+*                                                                       A0
+*
+*              XMHOOK_LoadModFunc - (APTR) Pointer to the hook function which
+*                      loads a module. The template for the function is:
+*
+*                              LONG LoadModFunc (BPTR fileHandle, struct SongInfo *song,
+*                                                                D0               A0
+*                                      struct XMHook *loader);
+*                                      A1
+*
+*                      `fileHandle' is an open file to load the module from. The
+*                      caller will take care to close it for you. The loader should
+*                      return RETURN_OK for success, or any other AmigaDOS error
+*                      code to mean failure.  In particular, RETURN_WARN indicates
+*                      that something went wrong, but the song is ok.  The special
+*                      error code ERROR_IOERR can be used when some dos.library
+*                      call (e.g.: Read()) failed.  In the latter case, the
+*                      AmigaDOS IoErr value should be set to explain the specific
+*                      cause of the problem.
+*                      (This tag is required by all NT_XMLOADER type hooks).
+*
+*              XMHOOK_SaveModFunc - (APTR) Pointer to the hook function which
+*                      saves a module. The template for the function is:
+*
+*                              LONG SaveModFunc (BPTR fileHandle, struct SongInfo *song,
+*                                                                D0               A0
+*                                      struct XMHook *saver);
+*                                      A1
+*
+*                      fileHandle is an open file to save the module to. The caller
+*                      will take care to close it for you. The saver should return
+*                      RETURN_OK for success, or any other AmigaDOS error code to
+*                      mean failure.  In particular, RETURN_WARN indicates that
+*                      something went wrong, but the song is ok.  The special
+*                      error code ERROR_IOERR can be used when some dos.library
+*                      call (e.g.: Read()) failed.  In the latter case, the
+*                      AmigaDOS IoErr value should be set to explain the specific
+*                      cause of the problem.
+*                      (This tag is required by all NT_XMSAVER type hooks).
+*
+*
+*              XMHOOK_IdentifyModFunc - (APTR) Pointer to the hook function
+*                      which identifies a module format. The template for the
+*                      function is:
+*
+*                              struct XMHook *IdentifyModFunc (BPTR fileHandle,
+*                                                                                  D0
+*                                      struct XMHook *hook,struct TagItem *tagList);
+*                                      A0                  A1
+*
+*                      fileHandle is an open file to try the identification on. The
+*                      caller will take care to close it. NOTE: Do not make assumptions
+*                      on the initial file position (e.g: seek yourself to position 0
+*                      if you need to). This funtion should return a pointer to a valid
+*                      loader (usually the one passed in) if the format is recognized,
+*                      NULL otherwhise.  (This tag is required by all NT_XMLOADER type
+*                      hooks).
+*
+*      RESULT
+*              hook - Pointer to the newly allocated XMHook structure, or
+*                      NULL for failure.
+*
+*      SEE ALSO
+*              xmRemHook(), xmIdentifyModule(), xmLoadSong(), xmSaveSong()
+*
+****************************************************************************
+*/
+static LIBCALL struct XMHook *XModuleAddHook (
+       REG(a0, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       struct XMHook *hook;
+
+       if (hook = AllocPooled (XModuleBase->xm_Pool, sizeof (struct XMHook)))
+       {
+               hook->xmh_Link.ln_Name  = (UBYTE *)     GetTagData (XMHOOK_Name,                NULL,   tags);
+               hook->xmh_Link.ln_Type  = (UBYTE)       GetTagData (XMHOOK_Type,                0,              tags);
+               hook->xmh_Link.ln_Pri   = (BYTE)        GetTagData (XMHOOK_Priority,    0,              tags);
+               hook->xmh_Descr                 = (STRPTR)      GetTagData (XMHOOK_Descr,               0,              tags);
+               hook->xmh_Author                = (STRPTR)      GetTagData (XMHOOK_Author,              0,              tags);
+               hook->xmh_ID                    = (ULONG)       GetTagData (XMHOOK_ID,                  0,              tags);
+               hook->xmh_Flags                 = (ULONG)       GetTagData (XMHOOK_Flags,               0,              tags);
+               hook->xmh_UserData              = (APTR)        GetTagData (XMHOOK_UserData,    NULL,   tags);
+               hook->xmh_LibraryBase   = (APTR)        GetTagData (XMHOOK_LibraryBase, NULL,   tags);
+               hook->xmh_RemoveHook    = (void (* ASMCALL)()) GetTagData (XMHOOK_RemoveFunc,   NULL, tags);
+               hook->xmh_LoadMod               = (LONG (* ASMCALL)()) GetTagData (XMHOOK_LoadModFunc,  NULL, tags);
+               hook->xmh_SaveMod               = (LONG (* ASMCALL)()) GetTagData (XMHOOK_SaveModFunc,  NULL, tags);
+               hook->xmh_IdentifyMod   = (struct XMHook * (* ASMCALL)()) GetTagData (XMHOOK_IdentifyModFunc, NULL, tags);
+               hook->xmh_MaxTracks             = GetTagData (XMHOOK_MaxTracks,                 MAXTRACKS,              tags);
+               hook->xmh_MaxPatterns   = GetTagData (XMHOOK_MaxPatterns,               MAXPATTERNS,    tags);
+               hook->xmh_MaxInstruments= GetTagData (XMHOOK_MaxInstruments,    MAXINSTRUMENTS, tags);
+               hook->xmh_MaxLength             = GetTagData (XMHOOK_MaxLength,                 MAXPOSITIONS,   tags);
+               hook->xmh_MaxSampleLen  = GetTagData (XMHOOK_MaxSampleLen,              ((2<<31)-1),    tags);
+               hook->xmh_MaxPattLen    = GetTagData (XMHOOK_MaxPattLen,                MAXPATTLINES,   tags);
+               hook->xmh_Version               = XMHOOK_VERSION;
+
+
+               if (hook->xmh_LibraryBase)
+                       if (!(OpenLibrary (hook->xmh_LibraryBase->lib_Node.ln_Name, 0)))
+                               return NULL;
+
+               ObtainSemaphore (&XModuleBase->xm_BaseLock);
+               Enqueue ((struct List *)((hook->xmh_Link.ln_Type == NT_XMSAVER) ?
+                       &XModuleBase->xm_Savers : &XModuleBase->xm_Loaders),
+                       (struct Node *)hook);
+               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+       }
+
+       return hook;
+}
+
+
+
+/****** xmodule/xmRemHook **************************************************
+*
+*      NAME
+*              xmRemHook -- Removes an XModule Hook
+*
+*      SYNOPSIS
+*              xmRemHookA(xmHook)
+*                         A0
+*
+*              void xmRemHook(struct XMHook *);
+*
+*      FUNCTION
+*              Removes an XModule Hook, calls its RemoveHookFunc() and then
+*              frees its memory.
+*
+*      INPUTS
+*              xmHook - pointer to the hook to be removed.
+*
+*      SEE ALSO
+*              xmAddHook()
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleRemHook (
+       REG(a0, struct XMHook *hook),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       ObtainSemaphore (&XModuleBase->xm_BaseLock);
+       REMOVE ((struct Node *)hook);
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+       if (hook->xmh_RemoveHook)
+               hook->xmh_RemoveHook (hook);
+
+       if (hook->xmh_LibraryBase) CloseLibrary (hook->xmh_LibraryBase);
+
+       FreePooled (XModuleBase->xm_Pool, hook, sizeof (struct XMHook));
+}
+
+
+
+
+/****** xmodule/xmCreateSong ***********************************************
+*
+*      NAME
+*              xmCreateSongA -- Create and initialize a new SongInfo structure
+*              xmCreateSong -- Varargs stub for xmCreateSongA
+*
+*      SYNOPSIS
+*              songInfo = xmCreateSongA(tagList);
+*              D0                      A0
+*
+*              struct SongInfo *xmCreateSongA(struct TagItem *);
+*
+*
+*              songInfo = xmCreateSong(Tag1,...);
+*
+*              struct SongInfo *xmCreateSong(ULONG,...);
+*
+*      FUNCTION
+*              Allocates and initializes a new SongInfo structure.  The song
+*              can then be added to the song list via xmAddSongA(), in which
+*              case, it is no longer required to free it with xmDeleteSong().
+*
+*      INPUTS
+*              tagList - pointer to an optional tag list specifying how to
+*                      initialize the SongInfo structure.
+*
+*      TAGS
+*              SNGA_DefaultTracks - Sets the default number of pattern tracks for
+*                      the song. Defaults to 4.
+*
+*              SNGA_DefaultPattLen - Sets the default number of pattern lines for
+*                      the song. Defaults to 64.
+*
+*              SNGA_ReadyToUse - Adds one pattern and one position to the song.
+*                      Defaults to FALSE.
+*
+*              XMSNG_AddToList - (struct SongInfo *) Add the song to the song list.
+*                      When a song is being added to the list, a shared lock is
+*                      obtained on it to prevent other tasks from modifying (or even
+*                      remove) the song before you can do your job on it. IT IS YOUR
+*                      DUTY TO UNLOCK THE SONG as soon as you are done modifying it.
+*                      Passing NULL adds the song on the head of the list, while -1
+*                      (~0) will add it to the end of the SongList. Passing in a
+*                      pointer to another song which is already in the list, will
+*                      add the new song _after_ the latter.
+*                      (Default is not adding the song).
+*
+*              XMSNG_Active - (BOOL) Makes this song the active one. This tag is
+*                      considered only if the XMSNG_AddToList tag is also passed.
+*
+*      RESULT
+*              songInfo - pointer to the newly allocated SongInfo structure, or
+*                      NULL for failure.
+*
+*      SEE ALSO
+*              xmDeleteSong(), xmAddSongA()
+*
+****************************************************************************
+*/
+static LIBCALL struct SongInfo * XModuleCreateSong (
+       REG(a0, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       struct SongInfo *si;
+       struct TagItem *tag;
+
+       if (si = NewObjectA (XModuleBase->xm_SongClass, NULL, tags))
+       {
+               if (tag = FindTagItem (XMSNG_AddToList, tags))
+               {
+                       ObtainSemaphore (&si->Lock);
+                       xmAddSongA (si, (struct SongInfo *)tag->ti_Data, tags);
+               }
+       }
+
+       return si;
+}
+
+
+
+/****** xmodule/xmDeleteSong ***********************************************
+*
+*      NAME
+*              xmDeleteSong -- Deletes a song
+*
+*      SYNOPSIS
+*              xmDeleteSong(songInfo)
+*                           A0
+*
+*              void xmDeleteSong(struct SongInfo *);
+*
+*      FUNCTION
+*              Deletes a song and all the items attached to it (patterns,
+*              instruments, etc.).  This call will also remove the song from
+*              the song list if it had been added to it.
+*
+*      INPUTS
+*              songInfo - pointer to the SongInfo structure to be deleted.
+*                      Passing a NULL pointer is harmless.
+*
+*      NOTE
+*              In order to delete a song which has been added to the public
+*              song list, you must first obtain an exclusive lock on it to
+*              prevent other tasks from walking on it while you are freeing
+*              it's data structures.  The semaphore does NOT need to be
+*              relinquished, because the SongInfo structure won't exist any
+*              more when this call returns.
+*
+*      SEE ALSO
+*              xmCreateSong(), xmRemSong()
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleDeleteSong (
+       REG(a0, struct SongInfo *si),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       xmRemSong (si);
+       DisposeObject (si);
+}
+
+
+
+/****** xmodule/xmAddSongA *************************************************
+*
+*      NAME
+*              xmAddSongA -- Add a song to the song list
+*              xmAddSong -- Varargs stub for xmAddSongA
+*
+*      SYNOPSIS
+*              success = xmAddSongA(songInfo,position,tagList);
+*              D0                   A0       A1       A2
+*
+*              ULONG xmAddSongA(struct SongInfo *,struct SongInfo *,
+*                      struct TagItem *);
+*
+*
+*              success = xmAddSong(songInfo,position,tag1,...);
+*
+*              ULONG xmAddSong(struct SongInfo *,struct SongInfo *,
+*                      LONG,...);
+*
+*      FUNCTION
+*              Adds a song to the public song list. Trying to add a song
+*              twice is harmless.
+*
+*      INPUTS
+*              songInfo - song to be added.  Passing a NULL pointer is safe.
+*              position - Position to add the song in.  Passing NULL adds the
+*                      song in the head of the list, while ~0 adds it in the tail.
+*                      passing a pointer to a SongInfo already in the list inserts
+*                      the first song _after_ the other one.
+*              tagList - pointer to a TagList for more arguments.
+*
+*      TAGS
+*              XMSNG_Active - (BOOL) Makes this song the active one. The
+*                      default is to just add the song without activating it.
+*
+*      RESULT
+*              success - Will be 0 for failure, in which case the song will
+*                      not be added to the songs list. Currently, xmAddSongA()
+*                      will never fail.
+*
+*      NOTE
+*              The song list is protected from multiple tasks access by a
+*              SignalSemaphore in XModuleBase. This call will wait if the
+*              song list has been locked by another task. Beware of deadlock
+*              conditions!
+*
+*      SEE ALSO
+*              xmRemSong()
+*
+****************************************************************************
+*/
+static LIBCALL ULONG XModuleAddSong (
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct SongInfo *position),
+       REG(a2, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       if (!si) return FALSE;
+
+       if (!si->Link.ln_Type)  /* Is it already in the list? */
+       {
+               ObtainSemaphore (&XModuleBase->xm_BaseLock);
+               DetatchSongInfoList();
+
+               if ((ULONG)position == ~0)
+                       ADDTAIL ((struct List *)&XModuleBase->xm_Songs, (struct Node *)si);
+               else
+                       Insert ((struct List *)&XModuleBase->xm_Songs, (struct Node *)si, (struct Node *)position);
+
+               si->Link.ln_Type = NT_XMSONG;           /* Mark this song */
+
+               if (GetTagData (XMSNG_Active, FALSE, tags))
+                       xmActivateSong (si);
+
+               UpdateSongInfoList();
+
+               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+       }
+
+       return TRUE;
+}
+
+
+
+/****** xmodule/xmRemSong **************************************************
+*
+*      NAME
+*              xmRemSong -- Remove a song from the song list
+*
+*      SYNOPSIS
+*              success = xmRemSong(songInfo);
+*              D0                  A0
+*
+*              ULONG xmRemSong(struct SongInfo *);
+*
+*      FUNCTION
+*              Removes a song from the public song list. It is safe to call this
+*              function even if the song has not been added to the song list. If
+*              the passed SongInfo is the active one, another song will be
+*              selected automatically.
+*
+*      INPUTS
+*              songInfo - song to be removed. If NULL, this function will take
+*                      no action.
+*
+*      RESULT
+*              success - Will be 0 for failure, in which case the song will
+*                      not be removed from the song list. Currently,
+*                      xmRemSong() will never fail.
+*
+*      NOTE
+*              In order to remove a song, you must first obtain an exclusive
+*              lock on it.
+*
+*              The song list is also protected from multiple task access by
+*              a SignalSemaphore. This call will wait if the song list has
+*              been locked by another task. Beware of deadlock conditions!
+*
+*      SEE ALSO
+*              xmAddSongA()
+*
+****************************************************************************
+*/
+static LIBCALL ULONG XModuleRemSong (
+       REG(a0, struct SongInfo *si),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       if (!si) return FALSE;
+
+       if (!si->Link.ln_Type) return TRUE;     /* Not in the list */
+
+
+       /* Brutal but effective way to obtain two locks at the same time,
+        * avoiding the risk of deadlock conditions.  We can't just use
+        * ObtainSemaphoreList() here because the two semaphores can
+        * not be linked into a list.
+        */
+/*     while (1)
+       {
+               ObtainSemaphore (&XModuleBase->xm_BaseLock);
+
+               if (AttemptSemaphore (&si->Lock))
+                       break;
+
+               ReleaseSemaphore (&XModuleBase->xm_SongsLock);
+               ObtainSemaphore (&si->Lock);
+
+               if (AttemptSemaphore (&XModuleBase->xm_SongsLock))
+                       break;
+
+               ReleaseSemaphore (&si->Lock);
+       }
+*/
+
+       ObtainSemaphore (&XModuleBase->xm_BaseLock);
+       DetatchSongInfoList();
+
+       REMOVE ((struct Node *)si);
+       si->Link.ln_Type = 0;   /* Mark song outside list */
+
+       if (XModuleBase->xm_CurrentSong == si)
+       {
+               if (IsListEmpty ((struct List *)&XModuleBase->xm_Songs))
+                       XModuleBase->xm_CurrentSong = NULL;
+               else
+                       XModuleBase->xm_CurrentSong = (struct SongInfo *)
+                               XModuleBase->xm_Songs.mlh_TailPred;
+
+               UpdateSongInfoList();
+               UpdateSongInfo();
+       }
+       else UpdateSongInfoList();
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+       return TRUE;
+}
+
+
+
+/****** xmodule/xmActivateSong *********************************************
+*
+*      NAME
+*              xmActivateSong -- Makes a song the active one
+*
+*      SYNOPSIS
+*              success = xmActivateSong(songInfo);
+*              D0                       A0
+*
+*              ULONG xmActivateSong(struct SongInfo *);
+*
+*      FUNCTION
+*              Makes the passed song the currently active one.  It's pointer
+*              will be stored in the public song list and most actions
+*              will happen on this song by default.
+*
+*      INPUTS
+*              songInfo - song to be activated. If NULL, this function will
+*                      take no action.
+*
+*      RESULT
+*              success - Will be 0 for failure, in which case the song will
+*                      not be removed from the song list. Currently,
+*                      xmActivateSong() will never fail.
+*
+*      NOTE
+*              In order to activate a song, you must own a shared lock
+*              on it.  Please do not hog this lock for a long time when
+*              xmActivateSong() returns, because most internal routines
+*              try to lock the current song before taking any action.
+*
+*      SEE ALSO
+*
+****************************************************************************
+*/
+static LIBCALL ULONG XModuleActivateSong (
+       REG(a0, struct SongInfo *si),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       if (!si) return FALSE;
+       if (!si->Link.ln_Type) return FALSE;    /* Not in the list */
+
+       ObtainSemaphore (&XModuleBase->xm_BaseLock);
+
+       if (XModuleBase->xm_CurrentSong != si)
+       {
+               XModuleBase->xm_CurrentSong = si;
+               UpdateSongInfo();       /**/
+       }
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+       return TRUE;
+}
+
+
+
+/****** xmodule/xmLockActiveSong *******************************************
+*
+*      NAME
+*              xmLockActiveSong -- Obtains an lock on the active song
+*
+*      SYNOPSIS
+*              song = xmLockActiveSong(mode);
+*              D0                                              D0:16
+*
+*              struct SongInfo *xmActivateSong(UWORD);
+*
+*      FUNCTION
+*              Obtains an exclusive or shared lock on the active song,
+*              waiting if needed.  This call is a shortcut to:
+*
+*                      ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+*                      ObtainSemaphore[Shared] (&XModuleBase->xm_CurrentSong.Lock);
+*                      ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+*
+*              To unlock a song obtained in this way, you just need to
+*              ReleaseSemaphore() it.
+*
+*              You MUST always lock a song before you even think to
+*              read from -or write to- its data!
+*
+*      INPUTS
+*              mode - one of SM_SHARED or SM_EXCLUSIVE.
+*
+*      RESULT
+*              song - Pointer to the song which *was* active at the time
+*                      you called xmLockActiveSong().  The song will be
+*                      locked for you.  The result will be NULL if no song
+*                      is active.
+*
+*      NOTE
+*              Please be careful if you call this function while holding
+*              locks on other songs.  Doing so, you run the risk of causing
+*              deadlock condition.
+*              This function does only lock the song; it is NOT guaranteed
+*              that it will remain the active one.
+*
+*      SEE ALSO
+*
+****************************************************************************
+*/
+static LIBCALL struct SongInfo *XModuleLockActiveSong (
+       REG(d0, UWORD mode),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       struct SongInfo *si;
+
+
+#ifdef OS30_ONLY
+       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#else
+       /* Workaround for Pre-V39 ObtainSemaphoreShared() bug (see autodoc) */
+
+       /* Try to get the shared semaphore */
+       if (!AttemptSemaphoreShared (&XModuleBase->xm_BaseLock))
+               /* Check if we can get the exclusive version */
+               if (!AttemptSemaphore (&XModuleBase->xm_BaseLock))
+                       /* Oh well, wait for the shared lock */
+                       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#endif /* OS30_ONLY */
+
+       if (si = XModuleBase->xm_CurrentSong)
+       {
+               if (mode == SM_SHARED)
+               {
+#ifdef OS30_ONLY
+                       ObtainSemaphoreShared (&si->Lock);
+#else
+                       /* Try to get the shared semaphore */
+                       if (!AttemptSemaphoreShared (&si->Lock))
+                               /* Check if we can get the exclusive version */
+                               if (!AttemptSemaphore (&si->Lock))
+                                       /* Oh well, wait for the shared lock */
+                                       ObtainSemaphoreShared (&si->Lock);
+#endif /* OS30_ONLY */
+               }
+               else ObtainSemaphore (&si->Lock);
+       }
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+       return si;
+}
+
+
+
+/****** xmodule/xmLoadModuleA **********************************************
+*
+*      NAME
+*              xmLoadModuleA -- Loads a module and converts it in XModule format
+*              xmLoadModule -- Varargs stub for xmLoadModuleA
+*
+*      SYNOPSIS
+*              songInfo = xmLoadModuleA(fileName,tagList)
+*              D0                       A0       A1
+*
+*              struct SongInfo *xmLoadModuleA(CONST_STRPTR,struct TagItem *);
+*
+*
+*              songInfo = xmLoadModule(fileName,tag,...)
+*
+*              struct SongInfo *xmLoadModule(CONST_STRPTR,LONG,...);
+*
+*      FUNCTION
+*              Loads fileName using the correct module loader.
+*
+*      INPUTS
+*              fileName - File to read. Can be NULL if the XMSNG_FileHandle
+*                      tag is passed.
+*              tagList - Additional parameters (see below).  Can be NULL.
+*
+*      TAGS
+*              XMSNG_OldSong - ti_Data is the pointer to a SongInfo which will be
+*                      freed as soon as the module format has been determined.  This
+*                      is useful when the user wants to replace a song with another
+*                      one.  Passing NULL uses the currently active song.
+*
+*              XMSNG_AddToList - (struct SongInfo *) Add the song to the song list.
+*                      When a song is being added to the list, a shared lock is
+*                      obtained on it to prevent other tasks from modifying (or even
+*                      remove) the song before you can do your job on it. IT IS YOUR
+*                      DUTY TO UNLOCK THE SONG as soon as you are done modifying it.
+*                      Passing NULL adds the song on the head of the list, while -1
+*                      (~0) will add it to the end of the SongList. Passing in a
+*                      pointer to another song which is already in the list, will
+*                      add the new song _after_ the latter.
+*                      (Default is not adding the song).
+*
+*              XMSNG_Loader - (struct XMHook *) Disables automatic format
+*                      checking and forces loading the module with the given
+*                      loader. (Defaults to NULL).
+*
+*              XMSNG_FileHandle - (BPTR) pointer to an open AmigaDOS
+*                      FileHandle to read the module from.  The file must
+*                      already be positioned over the beginning of the module data.
+*                      NOTE: Even if this tag is passed, the fileName parameter is
+*                              still used to fill in the SongName field in case it is
+*                              missing inside the module AND the filesystem does not
+*                              support the ACTION_EXAMINE_FH packet.
+*                      NOTE: Some loaders may require a fully seekable file, so be
+*                              careful when using pipes.
+*                      NOTE: automatic data decompression is not possible when
+*                              XMSNG_FileHandle is passed.
+*                      (Defaults to NULL).
+*
+*              XMSNG_IFFHandle - (BPTR) pointer to an already initialized
+*                      IFFHandle to read the module from.  The IFF must
+*                      already be positioned over the beginning of the module FORM.
+*                      Even if this tag is passed, the fileName parameter is still
+*                      used to fill in the SongName field in case it is missing
+*                      inside the module.
+*                      NOTE: The iffparse.library can deal with non-seekable files,
+*                              but some loaders may require a fully seekable file, so be
+*                              careful when using pipes.
+*                      NOTE: automatic file decompression is not possible when
+*                              XMSNG_IFFHandle is passed.
+*                      (Defaults to NULL).
+*
+*              XMSNG_Active - (BOOL) Makes this song the active one. This tag is
+*                      considered only if the XMSNG_AddToList tag is also passed.
+*
+*      RESULT
+*              songInfo - Pointer to the newly allocated SongInfo structure, or
+*                      NULL for failure.  This function will not fail if the module is
+*                      partially corrupted. Anyway, the returned SongInfo will always
+*                      be valid. You can check IoErr() to know if there were problems.
+*
+*      EXAMPLE
+*              \* Load a song and add it to the SongList *\
+*              si = xmLoadSongTags (file, NULL,
+*                      XMSNG_AddToList, (struct SongInfo *)~0,
+*                      TAG_DONE);
+*
+*              \* Check for errors even if si is not NULL *\
+*              error = IoErr();
+*
+*              \* Release Semaphore got by xmLoadSong() *\
+*              if (si) ReleaseSemaphore (&si->Lock);
+*
+*      SEE ALSO
+*              xmAddSongA(), xmIdentifyModule()
+*
+****************************************************************************
+*/
+static LIBCALL struct SongInfo * XModuleLoadModule (
+       REG(a0, CONST_STRPTR filename),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       struct XMHook   *loader;
+       struct SongInfo *si = NULL;
+       UBYTE                    fullpath[PATHNAME_MAX];
+       ULONG                    err;
+       BPTR                     fh, compressed = 0;
+       UWORD                    type;
+
+
+       loader = (struct XMHook *)GetTagData (XMSNG_Loader, NULL, tags);
+
+
+       /* Get source file or open it */
+
+       if (fh = (BPTR) GetTagData (XMSNG_FileHandle, NULL, tags))
+       {
+               /* Get the full pathname */
+
+               if (!NameFromFH (fh, fullpath, PATHNAME_MAX))
+                       fullpath[0] = '\0';
+       }
+       else
+       {
+               if (!(fh = Open (filename, MODE_OLDFILE)))
+               {
+                       UBYTE buf[FAULT_MAX];
+
+                       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_DOSFAULT,
+                               (APTR)MSG_ERR_LOAD, filename, buf);
+                       return NULL;
+               }
+
+               /* Optimize file buffering for faster I/O.
+                *
+                * NOTE: SetVBuf() was introduced in V39, but V37 has a
+                * no-op vector for it, so it is safe to call SetVBuf()
+                * without checking the OS version.
+                * NOTE: Due to a bug in dos.library V40, SetVBuf() works
+                * only when called right after Open() (before doing any I/O).
+                */
+               SetVBuf (fh, NULL, BUF_FULL, 16*1024);
+
+
+               /* Get the full pathname */
+
+               if (!NameFromFH (fh, fullpath, PATHNAME_MAX))
+                       fullpath[0] = '\0';
+
+
+               /* Check wether the file is compressed */
+
+               if (type = CruncherType (fh))
+               {
+                       Close (fh);
+
+                       if (compressed = DecompressFile (filename, type))
+                       {
+                               if (!(fh = OpenFromLock (compressed)))
+                               {
+                                       err = IoErr();
+                                       UnLock (compressed);
+                                       DecompressFileDone();
+                                       SetIoErr (LastErr = err);
+                                       return NULL;
+                               }
+                       }
+                       else return NULL;
+               }
+       }
+
+
+
+       /* Lock loaders list */
+
+#ifdef OS30_ONLY
+       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#else
+       /* Workaround for Pre-V39 ObtainSemaphoreShared() bug (see autodoc) */
+
+       /* Try to get the shared semaphore */
+       if (!AttemptSemaphoreShared (&XModuleBase->xm_BaseLock))
+               /* Check if we can get the exclusive version */
+               if (!AttemptSemaphore (&XModuleBase->xm_BaseLock))
+                       /* Oh well, wait for the shared lock */
+                       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#endif /* OS30_ONLY */
+
+
+       /* Find out what the heck this file format is */
+
+       if (!loader)
+               loader = xmIdentifyModule (fh, tags);
+
+       if (loader)
+       {
+               if (loader->xmh_LoadMod)
+               {
+                       Seek (fh, 0, OFFSET_BEGINNING);         /* Reset file */
+
+
+                       /* Free old song */
+                       {
+                               struct TagItem *tag;
+
+                               if (tag = FindTagItem (XMSNG_OldSong, tags))
+                                       xmDeleteSong (tag->ti_Data ?
+                                               (struct SongInfo *)tag->ti_Data : XModuleBase->xm_CurrentSong);
+                       }
+
+
+                       /* Create a new song and set its title and path.
+                        * File name will be replaced by the real song name if the
+                        * load format supports embedded song names (e.g.: SoundTracker).
+                        */
+                       if (si = xmCreateSong (
+                               SNGA_Path,      fullpath,
+                               SNGA_Title,     FilePart (filename),
+                               TAG_DONE))
+                       {
+                               OpenProgressWindow();
+
+                               xmDisplayMessage (XMDMF_USECATALOG | XMDMF_INFORMATION,
+                                       (APTR)MSG_READING_TYPE_MODULE, loader->xmh_Link.ln_Name);
+
+
+                               /* Call loader hook */
+                               err = loader->xmh_LoadMod (fh, si, loader, tags);
+
+                               if (err == ERROR_IOERR)
+                               {
+                                       if (err = IoErr())
+                                               xmDisplayMessage (XMDMF_USECATALOG | XMDMF_DOSFAULT | err,
+                                                       (APTR)MSG_ERROR_READING, FilePart (filename));
+                                       else
+                                               xmDisplayMessage (XMDMF_USECATALOG | XMDMF_ERROR,
+                                                       (APTR)MSG_UNESPECTED_EOF);
+                               }
+
+                               FixSong (si);
+
+                               CloseProgressWindow();
+                       }
+                       else err = ERROR_NO_FREE_STORE;
+               }
+               else err = ERROR_ACTION_NOT_KNOWN;
+       }
+       else err = ERROR_OBJECT_WRONG_TYPE;
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+       Close (fh);
+
+       if (compressed) DecompressFileDone ();
+
+       if (!err)
+               xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_USECATALOG,
+                       (APTR)MSG_MODULE_LOADED_OK, NULL);
+       else
+               SetIoErr (LastErr = err);
+
+       if (si)
+       {
+               struct TagItem *tag;
+
+               if (tag = FindTagItem (XMSNG_AddToList, tags))
+               {
+                       ObtainSemaphore (&si->Lock);
+                       xmAddSongA (si, (struct SongInfo *)tag->ti_Data, NULL);
+                       if (GetTagData (XMSNG_Active, FALSE, tags))
+                               xmActivateSong (si);
+               }
+       }
+
+       return si;
+}
+
+
+
+/****** xmodule/xmSaveModuleA **********************************************
+*
+*      NAME
+*              xmSaveModuleA -- Saves a module to the specified file format
+*              xmSaveModule -- Varargs stub for xmSaveModuleA
+*
+*      SYNOPSIS
+*              error = xmSaveModuleA(songInfo, fileName, saver, tagList)
+*              D0                    A0        A1        A2     A3
+*
+*              LONG xmSaveModuleA(struct SongInfo *,CONST_STRPTR,struct XMHook *,
+*                      struct TagItem *);
+*
+*
+*              error = xmSaveModule(songInfo, fileName, saver, tag1,...)
+*
+*              LONG xmSaveModule(struct SongInfo *,CONST_STRPTR,struct XMHook *,
+*                      LONG,...);
+*
+*      FUNCTION
+*              Saves songInfo to fileName using the specified saver.
+*
+*      INPUTS
+*              songInfo - Song to save.
+*              fileName - AmigaDOS filename to write the song to.
+*              saver - Pointer to the saver to use.  Pass NULL to use
+*                      the default saver.
+*
+*      TAGS
+*              No tags are defined for this call.
+*
+*      RESULT
+*              error - RETURN_OK for success, or any other AmigaDOS error code
+*                      to mean failure.  In particular, RETURN_WARN indicates that
+*                      something went wrong, but the song is still ok. The special
+*                      error code ERROR_IOERR means that some dos.library
+*                      call (e.g.: Read()) failed.  In the latter case, the
+*                      AmigaDOS IoErr() value will be set to explain the specific
+*                      cause of the problem.
+*
+*      SEE ALSO
+*
+****************************************************************************
+*/
+static LIBCALL LONG XModuleSaveModule (
+       REG(a0, struct SongInfo *si),
+       REG(a1, CONST_STRPTR filename),
+       REG(a2, struct XMHook *saver),
+       REG(a3, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       LONG err;
+       BPTR fh;
+       BOOL releasebase = FALSE;
+
+
+       /* Use the default saver if not specified */
+       if (!saver)
+       {
+#ifdef OS30_ONLY
+               ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#else
+               /* Workaround for Pre-V39 ObtainSemaphoreShared() bug (see autodoc) */
+
+               /* Try to get the shared semaphore */
+               if (!AttemptSemaphoreShared (&XModuleBase->xm_BaseLock))
+                       /* Check if we can get the exclusive version */
+                       if (!AttemptSemaphore (&XModuleBase->xm_BaseLock))
+                               /* Oh well, wait for the shared lock */
+                               ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#endif /* OS30_ONLY */
+
+               if (!(saver = XModuleBase->xm_DefaultSaver))
+               {
+                       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+                       return ERROR_ACTION_NOT_KNOWN;
+               }
+
+               releasebase = TRUE;
+       }
+
+
+       if (saver->xmh_SaveMod)
+       {
+               ULONG i;
+
+               /* Make some checks before actually saving */
+
+
+               if (si->LastInstrument > saver->xmh_MaxInstruments)
+               {
+                       xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                               (APTR)MSG_SONG_TOO_MANY_INSTRS, si->LastInstrument, saver->xmh_MaxInstruments);
+
+                       if (ShowRequest (MSG_TRY_REMAPPING_INSTRUMENTS, MSG_PROCEED_OR_CANCEL))
+                               xmProcessSong (si, NULL,
+                                       XMSNG_RemapInstruments, TRUE,
+                                       TAG_DONE);
+               }
+
+               for (i = 1; i <= si->LastInstrument; i++)
+                       if (si->Instr[i] && (si->Instr[i]->Length > saver->xmh_MaxSampleLen))
+                       {
+                               xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                                       (APTR)MSG_INSTR_TOO_LONG, i, saver->xmh_MaxSampleLen);
+                       }
+
+               for (i = 0; i < si->NumPatterns; i++)
+               {
+                       if (si->Patt[i])
+                               if ((si->Patt[i]->Lines > saver->xmh_MaxPattLen) ||
+                                       ( (saver->xmh_Flags & XMHF_FIXED_PATT_LEN) &&
+                                       (si->Patt[i]->Lines != saver->xmh_MaxPattLen) ) )
+                       {
+                               xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                                       (APTR)MSG_PATT_LENGTH_INVALID, i, si->Patt[i]->Lines);
+
+                               if (ShowRequest (MSG_WILL_MODIFY_SONG, MSG_PROCEED_OR_CANCEL))
+                               {
+                                       xmProcessSong (si, NULL,
+                                               XMSNG_LimitPatterns, saver->xmh_MaxPattLen |
+                                               ((saver->xmh_Flags & XMHF_FIXED_PATT_LEN) ? (saver->xmh_MaxPattLen << 16) : 0),
+                                               TAG_DONE);
+                                       break;
+                               }
+                               else
+                               {
+                                       if (releasebase)
+                                               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+                                       return ERROR_BREAK;
+                               }
+                       }
+               }
+
+               if (si->MaxTracks > saver->xmh_MaxTracks)
+                       xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                               (APTR)MSG_SONG_TOO_MANY_TRACKS, saver->xmh_MaxTracks, si->MaxTracks);
+
+               if (si->NumPatterns > saver->xmh_MaxPatterns)
+                       xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                               (APTR)MSG_SONG_TOO_MANY_PATTS, saver->xmh_MaxPatterns, si->NumPatterns);
+
+               if (si->Length > saver->xmh_MaxLength)
+                       xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                               (APTR)MSG_SONG_TOO_MANY_POS, saver->xmh_MaxLength, si->Length);
+
+
+               /* Now make a backup of the old file before overwriting this */
+
+               if (GuiSwitches.DoBackups)
+                       BackupFile (filename, GuiSwitches.BackupTemplate, GuiSwitches.BackupVersions);
+
+
+               if (fh = Open (filename, MODE_NEWFILE))
+               {
+                       OpenProgressWindow();
+
+                       /* Optimize file buffering for faster I/O.
+                        *
+                        * NOTE: SetVBuf() was introduced in V39, but V37 has a
+                        * no-op vector for it, so it is safe to call SetVBuf()
+                        * without checking the OS version.
+                        * NOTE: Due to a bug in dos.library V40, SetVBuf() works
+                        * only when called right after Open() (before doing any I/O).
+                        */
+                       SetVBuf (fh, NULL, BUF_FULL, 16*1024);
+
+
+                       xmDisplayMessage (XMDMF_INFORMATION | XMDMF_USECATALOG,
+                               (APTR)MSG_SAVING_MODULE, saver->xmh_Link.ln_Name,
+                               si->Title ? si->Title : STR(MSG_SONG_UNTITLED));
+
+
+                       err = saver->xmh_SaveMod (fh, si, saver, tags);
+
+                       Close (fh);
+                       SetComment (filename, VERS " by Bernardo Innocenti");
+
+                       if (err)
+                       {
+                               /* Delete incomplete file */
+                               DeleteFile (filename);
+
+                               if (err == ERROR_IOERR)
+                                       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_DOSFAULT,
+                                               (APTR)MSG_ERROR_WRITING, filename);
+                       }
+                       else
+                       {
+                               if (SaveSwitches.SaveIcons)
+                                       PutIcon ("def_Module", filename);
+                               xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_USECATALOG,
+                                       (APTR)MSG_MODULE_SAVED_OK, NULL);
+
+                       }
+
+                       CloseProgressWindow();
+               }
+               else    /* Open failed */
+               {
+                       err = IoErr();
+                       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_DOSFAULT,
+                               (APTR)MSG_CANT_OPEN, filename);
+               }
+       }
+       else err = ERROR_ACTION_NOT_KNOWN;
+
+       if (releasebase)
+               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+       return err;
+}
+
+
+
+/****** xmodule/xmIdentifyModule *******************************************
+*
+*      NAME
+*              xmIdentifyModule -- Returns the type of a module
+*
+*      SYNOPSIS
+*              loader = xmIdentifyModule(fh, tagList)
+*              D0                        D0  A0
+*
+*              struct XMHook *xmIdentifyModule(BPTR,struct TagItem *);
+*
+*      FUNCTION
+*              Finds out a loader which is able to read the given module.
+*
+*      INPUTS
+*              fh - AmigaDOS FileHandle to examine.
+*              tagList - Additional parameters. Leave it NULL for now.
+*
+*      RESULT
+*              loader - Pointer to the first loader which knows how to load this
+*                      module, or NULL otherwise.
+*
+*      NOTE
+*              Before you call this function, you must first obtain a lock on the
+*              loaders list to protect yourself from other tasks which might remove
+*              the returned loader before you can actually use it.
+*
+*      SEE ALSO
+*
+****************************************************************************
+*/
+static LIBCALL struct XMHook *XModuleIdentifyModule(
+       REG(d0, BPTR fh),
+       REG(a0, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       struct XMHook *loader, *ret = NULL;
+
+       loader = (struct XMHook *) XModuleBase->xm_Loaders.mlh_Head;
+
+       while (loader->xmh_Link.ln_Succ)
+       {
+               if (ret = loader->xmh_IdentifyMod (fh, loader, tags))
+                       break;
+
+               loader = (struct XMHook *)loader->xmh_Link.ln_Succ;
+       }
+
+       Seek (fh, 0, OFFSET_BEGINNING);
+
+       return ret;
+}
+
+
+
+/****** xmodule/xmSetSongLen ***********************************************
+*
+*      NAME
+*              xmSetSongLen -- Set the number of song positions
+*
+*      SYNOPSIS
+*              sequence = xmSetSongLen(songInfo, length)
+*              D0                      A0        D0:16
+*
+*              UWORD *xmSetSongLen(struct SongInfo *, UWORD);
+*
+*      FUNCTION
+*              Allocates space in the song for a sequence table of the given
+*              length. If no sequence exists yet, a new one is allocated.
+*              Increasing the song length may require the sequence table to be
+*              expanded, in which case it will be re-allocated and the old sequence
+*              entries will be copied. Decreasing the song length could also cause
+*              a reallocation of the sequence table if the size drops down enough.
+*              Setting the song length to 0 will free the sequence table and return
+*              NULL.
+*
+*      INPUTS
+*              songInfo - pointer to a SongInfo structure.
+*              length - new length of song sequence table.
+*
+*      NOTE
+*              This function will also adjust the CurrentPos field if it is found
+*              beyond the song end
+*
+*      BUGS
+*              This funtion is quite useless because all it does is setting the
+*              SNGA_Length attribute in the song object.  Setting the SNGA_Length
+*              attribute yourself is easier and faster if you are already setting
+*              other attributes in the same song.
+*
+*   RESULT
+*              Pointer to the newly allocated sequence table or NULL for failure,
+*              in which case the previous sequence table is left untouched.
+*
+****************************************************************************
+*/
+static LIBCALL UWORD *XModuleSetSongLen (
+       REG(a0, struct SongInfo *si),
+       REG(d0, UWORD len),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       SetAttrs (si, SNGA_Length, len, TAG_DONE);
+       return si->Sequence;
+}
+
+
+
+/****** xmodule/xmAddPatternA **********************************************
+*
+*      NAME
+*              xmAddPatternA -- Adds a pattern to a song
+*              xmAddPattern -- Varargs stub for xmAddPatternA
+*
+*      SYNOPSIS
+*              pattern = xmAddPatternA(si, tagList)
+*              D0                      A0  A1
+*
+*              struct Pattern *xmAddPatternA(struct SongInfo *,struct TagItem *);
+*
+*
+*              pattern = xmAddPattern (si,tag1,...)
+*
+*              struct Pattern *xmAddPattern(struct SongInfo *,LONG Tag1,...);
+*
+*      FUNCTION
+*              Adds a pattern to a song by calling the SNGM_ADDPATTERN method.
+*
+*      INPUTS
+*              si - pointer to the song to which the pattern should be added.
+*              tagList - optional TagList.  See SNGM_ADDPATTERN for possible tags.
+*
+*      RESULT
+*              Pointer to the new pattern or NULL for failure.
+*
+*      NOTE
+*              In order to add patterns, you should have exclusive access to
+*              the song.  Always obtain a lock before you call this function on
+*              public songs.
+*
+*      SEE ALSO
+*              xmRemPattern(), songclass/SNGM_ADDPATTERN
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleAddPattern (
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       DoMethod ((Object *)si, SNGM_ADDPATTERN, tags);
+}
+
+
+
+/****** xmodule/xmSetPatternA **********************************************
+*
+*      NAME
+*              xmSetPatternA -- Sets pattern attributes
+*              xmSetPattern -- Varargs stub for xmSetPatternA
+*
+*      SYNOPSIS
+*              success = xmSetPatternA(si, pattNum, tagList)
+*              D0                      A0  D0       A1
+*
+*              ULONG xmSetPatternA(struct SongInfo *,ULONG,struct TagItem *);
+*
+*
+*              success = xmSetPattern (si,pattNum,tag1,...)
+*
+*              ULONG xmSetPattern(struct SongInfo *,ULONG,LONG Tag1,...);
+*
+*      FUNCTION
+*              Sets attributes of a pattern by calling the SNGM_SETPATTERN method.
+*
+*      INPUTS
+*              si - pointer to the song which contains the pattern to be set.
+*              tagList - list of attributes to set.  See SNGM_SETPATTERN for
+*                      possible tags.
+*
+*      RESULT
+*              Non zero for success
+*
+*      NOTE
+*              In order to set patterns attributes, you should have exclusive
+*              access to the song.  Always obtain a lock before you call this
+*              function on public songs.
+*
+*      SEE ALSO
+*              xmAddPattern(), songclass/SNGM_SETPATTERN
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleSetPattern (
+       REG(a0, struct SongInfo *si),
+       REG(d0, ULONG pattNum),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       DoMethod ((Object *)si, SNGM_SETPATTERN, pattNum, tags);
+}
+
+
+
+/****** xmodule/xmRemPattern ***********************************************
+*
+*      NAME
+*              xmRemPattern -- Removes a pattern from a song
+*
+*      SYNOPSIS
+*              xmRemPattern(si, pattNum, replaceWith)
+*                           A0  D0,      D1
+*
+*              void xmRemPattern(struct SongInfo *,LONG,LONG);
+*
+*      FUNCTION
+*              Removes a pattern from a song by calling the SNGM_REMPATTERN method.
+*
+*      INPUTS
+*              si - pointer to the song to which the pattern should be removed.
+*              mum - Number of the pattern to be removed.
+*              replaceWith - What to put in the song sequence in place of the
+*                      deleted pattern.
+*
+*      NOTE
+*              In order to remove patterns, you should have exclusive access to
+*              the song.  Always obtain a lock before you call this function on
+*              public songs.
+*
+*      SEE ALSO
+*              xmAddPatternA(), songclass/SNGM_REMPATTERN
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleRemPattern (
+       REG(a0, struct SongInfo *si),
+       REG(d0, ULONG pattNum),
+       REG(d1, ULONG replaceWith))
+{
+       DoMethod ((Object *)si, SNGM_REMPATTERN, pattNum, replaceWith);
+}
+
+
+
+/****** xmodule/xmAddInstrumentA *******************************************
+*
+*      NAME
+*              xmAddInstrumentA -- Adds a instrument to a song
+*              xmAddInstrument -- Varargs stub for xmAddInstrumentA
+*
+*      SYNOPSIS
+*              instrument = xmAddInstrumentA(si, instrNum, tagList)
+*              D0                            A0  D0        A1
+*
+*              struct Instrument *xmAddInstrumentA(struct SongInfo *,LONG,
+*                      struct TagItem *);
+*
+*
+*              instrument = xmAddInstrument(si,instrNum,tag1,...)
+*
+*              struct Instrument *xmAddInstrument(struct SongInfo *,LONG,LONG,...);
+*
+*      FUNCTION
+*              Adds an instrument to a song by calling the SNGM_ADDINSTRUMENT method.
+*
+*      INPUTS
+*              si - pointer to the song to which the instrument should be added.
+*              tagList - optional TagList.  See SNGM_ADDINSTRUMENT for possible tags.
+*
+*      RESULT
+*              Pointer to the new instrument or NULL for failure.
+*
+*      NOTE
+*              In order to add instruments, you should have exclusive access to
+*              the song.  Always obtain a lock before you call this function on
+*              public songs.
+*
+*      SEE ALSO
+*              xmRemInstrument(), songclass/SNGM_REMINSTRUMENT
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleAddInstrument (
+       REG(a0, struct SongInfo *si),
+       REG(d0, LONG num),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       DoMethod ((Object *)si, SNGM_ADDINSTRUMENT, num, tags);
+}
+
+
+
+/****** xmodule/xmSetInstrumentA *******************************************
+*
+*      NAME
+*              xmSetInstrumentA -- Sets an instrument's attributes
+*              xmSetInstrument -- Varargs stub for xmSetInstrumentA
+*
+*      SYNOPSIS
+*              success = xmSetInstrumentA(si, instrNum, tagList)
+*              D0                         A0  D0        A1
+*
+*              ULONG xmSetInstrumentA(struct SongInfo *,LONG,
+*                      struct TagItem *);
+*
+*
+*              success = xmSetInstrument(si,instrNum,tag1,...)
+*
+*              ULONG xmSetInstrument(struct SongInfo *,LONG,LONG,...);
+*
+*      FUNCTION
+*              Sets an instrument's attributes by calling the SNGM_SETINSTRUMENT
+*              method.
+*
+*      INPUTS
+*              si - pointer to the song which contains the instrument to be set.
+*              tagList - instrument attributes to set.  See SNGM_SETINSTRUMENT
+*                      for possible tags.
+*
+*      RESULT
+*              non zero for success.
+*
+*      NOTE
+*              In order to set instruments' attributes, you should have
+*              exclusive access to the song.  Always obtain a lock before
+*              you call this function on public songs.
+*
+*      SEE ALSO
+*              xmAddInstrument(), songclass/SNGM_SETINSTRUMENT
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleSetInstrument (
+       REG(a0, struct SongInfo *si),
+       REG(d0, LONG num),
+       REG(a1, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       DoMethod ((Object *)si, SNGM_SETINSTRUMENT, num, tags);
+}
+
+
+
+/****** xmodule/xmRemInstrument ***********************************************
+*
+*      NAME
+*              xmRemInstrument -- Removes an instrument from a song
+*
+*      SYNOPSIS
+*              xmRemInstrument(si, instrNum)
+*                              A0  D0
+*
+*              void xmRemInstrument(struct SongInfo *,LONG);
+*
+*      FUNCTION
+*              Removes an instrument from a song by calling the SNGM_REMINSTRUMENT
+*              method.
+*
+*      INPUTS
+*              si - pointer to the song to which the instrument should be removed.
+*              mum - Number of the instrument to be removed.
+*
+*      NOTE
+*              In order to remove instruments, you should have exclusive access to
+*              the song.  Always obtain a lock before you call this function on
+*              public songs.
+*
+*      SEE ALSO
+*              xmAddInstrumentA(), songclass/SNGM_REMINSTRUMENT
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleRemInstrument (
+       REG(a0, struct SongInfo *si),
+       REG(d0, ULONG num),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       DoMethod ((Object *)si, SNGM_REMINSTRUMENT, num);
+}
+
+
+
+/****** xmodule/xmProcessSongA *********************************************
+*
+*      NAME
+*              xmProcessSongA -- Performs complex processing on a song
+*              xmProcessSong -- Varargs stub for xmProcessSongA()
+*
+*      SYNOPSIS
+*              result = xmProcessSongA(si, reserved, tagList)
+*                                      A0  A1,       A2
+*
+*              LONG xmProcessSongA(struct SongInfo *,void *,struct TagItem *);
+*
+*
+*              result = xmProcessSong(si, reserved, tag1, ...)
+*
+*              LONG xmProcessSongA(struct SongInfo *,void *,LONG,...);
+*
+*      FUNCTION
+*              Performs complex processing operations on a song.
+*
+*      INPUTS
+*              si - pointer to the song to be processed.
+*              reserved - Reserved for future use.
+*              tagList - List of arguments.  See below for possible tags.
+*
+*      RESULT
+*              The result depends on the kind of operation done.  For most
+*              operations, it's value will be 0 to indicate success and
+*              an AmigaDOS error code which indicates the cause for failure.
+*
+*      TAGS
+*              XMSNG_Optimize - (LONG).  Tries to reduce the song size by removing
+*                      all unused data.  Possible optimizations are:
+*                              - XMOF_REM_UNUSED_PATTERNS
+*                              - XMOF_REM_DUP_PATTERNS
+*                              - XMOF_CUT_PATTERNS
+*                              - XMOF_REM_UNUSED_INSTRS
+*                              - XMOF_CUT_INSTR_LOOPS
+*                              - XMOF_CUT_INSTR_TAILS
+*                              - XMOF_DEFAULT
+*                      XMOF_DEFAULT will select all the optimizations choosen by
+*                      the user in addition to the ones specified with the other flags.
+*
+*              XMSNG_RemapInstruments - (BOOL) Performs instruments remapping.
+*
+*              XMSNG_LimitPatterns - (UWORD,UWORD) Limits the length all the
+*                      patterns inside a minimum and maximum value.  The upper 16bits
+*                      of the argument are the minimum value, the lower ones are
+*                      the maximum value.  Patterns outside this limits will be grown
+*                      or split as appropriate.
+*
+*              XMSNG_Join - (struct SongInfo *) - Joins the song with another one.
+*
+*              XMSNG_Merge - (struct SongInfo *) - Merges the song with another one.
+*
+*      NOTE
+*              In order to process a song, you should have exclusive access to
+*              it.  Always obtain a lock before you call this function on
+*              public songs.
+*
+****************************************************************************
+*/
+static LIBCALL LONG XModuleProcessSong (
+       REG(a0, struct SongInfo *si),
+       REG(a1, void *reserved),
+       REG(a2, struct TagItem *tags),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       struct TagItem  *ti, *tstate = tags;
+       LONG result = 0;
+
+       while (ti = NextTagItem(&tstate))
+       {
+               switch (ti->ti_Tag)
+               {
+                       case XMSNG_Optimize:
+                       {
+                               ULONG flags = ti->ti_Data;
+
+                               if (flags & XMOF_DEFAULT)
+                               {
+                                       if (OptSwitches.RemPatts)               flags |= XMOF_REM_UNUSED_PATTERNS;
+                                       if (OptSwitches.RemDupPatts)    flags |= XMOF_REM_DUP_PATTERNS;
+                                       if (OptSwitches.RemInstr)               flags |= XMOF_REM_UNUSED_INSTRS;
+                                       if (OptSwitches.RemDupInstr)    flags |= XMOF_REM_DUP_INSTRS;
+                                       if (OptSwitches.CutAfterLoop)   flags |= XMOF_CUT_INSTR_LOOPS;
+                                       if (OptSwitches.CutZeroTail)    flags |= XMOF_CUT_INSTR_TAILS;
+                                       if (OptSwitches.CutPatts)               flags |= XMOF_CUT_PATTERNS;
+                                       if (OptSwitches.RemapInstr)             flags |= XMOF_REMAP_INSTRS;
+                               }
+
+                               if (flags & XMOF_REM_UNUSED_PATTERNS)           DiscardPatterns (si);
+                               if (flags & XMOF_REM_DUP_PATTERNS)                      RemDupPatterns (si);
+                               if (flags & XMOF_CUT_PATTERNS)                          CutPatterns (si);
+                               if (flags & XMOF_REM_UNUSED_INSTRS)                     RemUnusedInstruments (si);
+                               if (flags & XMOF_REM_DUP_INSTRS)                        RemDupInstruments (si);
+                               if (flags & XMOF_CUT_INSTR_LOOPS)                       OptimizeInstruments (si);
+                               if (flags & XMOF_CUT_INSTR_TAILS)                       OptimizeInstruments (si);
+                               if (flags & XMOF_REMAP_INSTRS)                          RemapInstruments (si);
+
+                               break;
+                       }
+
+                       case XMSNG_RemapInstruments:
+                               RemapInstruments (si);
+                               result = 0;
+                               break;
+
+                       case XMSNG_LimitPatterns:
+                               result = ResizePatterns (si, ti->ti_Data >> 16, ti->ti_Data & 0xFFFF);
+                               break;
+
+                       case XMSNG_Join:
+                               result = NULL;
+                               break;
+
+                       case XMSNG_Merge:
+                               result = NULL;
+                               break;
+
+                       default:
+                               break;
+               }
+       }
+
+       return result;
+}
+
+
+
+/****** xmodule/xmDisplayMessage *******************************************
+*
+*      NAME
+*              xmDisplayMessageA -- Displays a message to the user
+*              xmDisplayMessage -- Varargs stub for xmDisplayMessageA()
+*
+*      SYNOPSIS
+*              xmDisplayMessageA(level, message, args)
+*                                DO     A0       A1
+*
+*              void xmDisplayMessageA(ULONG,APTR,LONG *);
+*
+*
+*              xmDisplayMessage(level, message, ...)
+*
+*              void xmDisplayMessage(ULONG,APTR,...);
+*
+*      FUNCTION
+*              Outputs a string in the XModule log window or in the 'action' field
+*              of the progress indicator.  The string is printf-formatted before
+*              being output.
+*
+*      INPUTS
+*              level - a number from 0 to 7 which indicates the importance of the
+*                      message.  7 is used for very annoying messages (eg: debug output),
+*                      0 for very important things (eg: disaster).
+*                      If you set the XMDMF_USECATALOG bit in the level parameter, you
+*                      can pass a catalog string number instead of a pointer to a string.
+*                      Specifying XMDMF_ACTION, the message will be put in the 'action'
+*                      field of the progress indicator.
+*                      If the flag XMDMF_DOSFAULT is specified, a Fault() output is
+*                      formatted using the message as an header.  In this case, the
+*                      level parameter takes another meaing: The lower word can contain
+*                      an AmigaDOS error code. If it is 0, IoErr() will be used to get
+*                      the error code.
+*              message - pointer to a string or catalog message number,
+*                      when the XMDMF_USECATALOG flag is set.
+*              args - arguments for formatting.
+*
+*      EXAMPLES
+*              xmDisplayMessage (XMDMF_ALERT,
+*                      "The application `%s' fucked up Windoze95 because %s.",
+*                      "(unknown name)", "an error occurred");
+*
+*              xmDisplayMessage (XMDMF_USE_CATALOG | XMDMF_COMMENT,
+*                      MSG_LIFE_UNIVERSE_AND_ALLTHAT, 42, "Fortytwo", "For tea too");
+*
+*              xmDisplayMessageA (XMDMF_ACTION | XMDMF_USECATALOG,
+*                      MSG_READING_COMICS, NULL);
+*
+*              xmDisplayMessage (XMDMF_DOSFAULT | XMDMF_USECATALOG,
+*                      MSG_CANT_LOAD_MODULE, ModuleName);
+*
+*      SEE ALSO
+*              xmDisplayProgress()
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleDisplayMessage (
+       REG(d0, ULONG level),
+       REG(a0, CONST_STRPTR msg),
+       REG(a1, LONG *args),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       if (level & XMDMF_USECATALOG)
+               msg = STR((ULONG)msg);
+
+       if (level & XMDMF_DOSFAULT)
+       {
+               UBYTE buf[FAULT_MAX + 100];
+               UBYTE buf2[100];
+
+               if (GuiSwitches.LogLevel >= XMDMF_ERROR)
+               {
+                       if (args) VSPrintf (buf2, msg, args);
+
+                       if (Fault ((level & XMDMF_DOSERRORMASK) ? (level & XMDMF_DOSERRORMASK) : IoErr(),
+                               args ? buf2 : msg, buf, FAULT_MAX + 100))
+                       {
+                               if (level & XMDMF_USEREQUESTER) ShowRequestStr (buf, NULL, args);
+                               else ShowString (buf, args);
+                       }
+               }
+       }
+       else if (level & XMDMF_ACTION)
+       {
+               DisplayActionStr (msg);
+               if (GuiSwitches.LogLevel > XMDMF_INFORMATION)
+                       ShowString (msg, args);
+       }
+       else if (level & XMDMF_USEREQUESTER)
+               ShowRequestStr (msg, NULL, args);
+       else if (GuiSwitches.LogLevel > (level & XMDMF_LEVELMASK))
+               ShowString (msg, args);
+}
+
+
+
+/****** xmodule/xmDisplayProgress ******************************************
+*
+*      NAME
+*              xmDisplayProgress -- Uptdates the progress bar indicator
+*
+*      SYNOPSIS
+*              abort = xmDisplayProgress(done, total)
+*                                        D0    D1
+*
+*              LONG xmDisplayMessageA(ULONG,ULONG);
+*
+*      FUNCTION
+*              Updates the position of the fuel gauge in the progress window to
+*              show the progress of an operation.  Additionally, it checks for
+*              user abort.
+*
+*      INPUTS
+*              done - a number which indicates how much of the work has
+*                      been already completed
+*              total - Total number of operations to do.
+*
+*      RESULT
+*              0 for success, ERROR_BREAK if user abort was detected.  You should
+*              always check this return code to abort what you were doing.
+*
+****************************************************************************
+*/
+static LIBCALL void XModuleDisplayProgress (
+       REG(d0, ULONG done),
+       REG(d1, ULONG total),
+       REG(a6, struct XModuleBase *XModuleBase))
+{
+       DisplayProgress (done, total);
+}
diff --git a/ListMacros.h b/ListMacros.h
new file mode 100644 (file)
index 0000000..4b7d808
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef LISTMACROS_H
+#define LISTMACROS_H
+/*
+**     $VER: ListMacros.h 2.1 (1.9.97)
+**
+**     Copyright (C) 1996,97 Bernardo Innocenti. All rights reserved.
+**
+**     Use 4 chars wide TABs to read this source
+**
+**     Some handy macros for list operations.  Using these macros is faster
+**     than calling their exec.library equivalents, but they will eventually
+**     make your code a little bigger and are also subject to common macro
+**     side effects.
+*/
+
+
+#define NEWLIST(l)     ( (l)->lh_TailPred = (struct Node *)(l),                        \
+                                       (l)->lh_Tail = 0,                                                                       \
+                                       (l)->lh_Head = (struct Node *)(&((l)->lh_Tail)) )
+
+#define ADDHEAD(l,n) ( (n)->ln_Pred = (struct Node *)(l),                              \
+                                       (n)->ln_Succ = (l)->lh_Head,                                            \
+                                       (l)->lh_Head->ln_Pred = (n),                                            \
+                                       (l)->lh_Head = (n) )
+
+#define ADDTAIL(l,n) ( (n)->ln_Succ = (struct Node *)(&((l)->lh_Tail)),        \
+                                       (n)->ln_Pred = (l)->lh_TailPred,                                        \
+                                       (l)->lh_TailPred->ln_Succ = (n),                                        \
+                                       (l)->lh_TailPred = (n) )
+
+#define REMOVE(n)      ( (n)->ln_Succ->ln_Pred = (n)->ln_Pred,                         \
+                                       (n)->ln_Pred->ln_Succ = (n)->ln_Succ )
+
+#define GETHEAD(l)     ( (l)->lh_Head->ln_Succ ? (l)->lh_Head : (struct Node *)NULL )
+
+#define GETTAIL(l)  ( (l)->lh_TailPred->ln_Succ ? (l)->lh_TailPred : (struct Node *)NULL )
+
+#define GETSUCC(n)  ( (n)->ln_Succ->ln_Succ ? (n)->ln_Succ : (struct Node *)NULL )
+
+#define GETPRED(n)  ( (n)->ln_Pred->ln_Pred ? (n)->ln_Pred : (struct Node *)NULL )
+
+
+#ifdef __GNUC__
+
+#define REMHEAD(l)                                                                                                                     \
+({                                                                                                                                                     \
+       struct Node *n = (l)->lh_Head;                                                                                  \
+       n->ln_Succ ?                                                                                                                    \
+               (l)->lh_Head = n->ln_Succ,                                                                                      \
+                       (l)->lh_Head->ln_Pred = (struct Node *)(l),                                             \
+                       n :                                                                                                                             \
+               NULL;                                                                                                                           \
+})
+
+#define REMTAIL(l)                                                                                                                     \
+({                                                                                                                                                     \
+       struct Node *n = (l)->lh_TailPred;                                                                              \
+       n->ln_Pred ?                                                                                                                    \
+               (l)->lh_TailPred = n->ln_Pred,                                                                          \
+                       (l)->lh_TailPred->ln_Succ = (struct Node *)(&((l)->lh_Tail)),   \
+                       n :                                                                                                                             \
+               NULL;                                                                                                                           \
+})
+
+
+#else
+
+/* These two can't be implemented as macros without the GCC ({...}) language extension */
+
+INLINE struct Node *REMHEAD(struct List *l)
+{
+       struct Node *n = l->lh_Head;
+
+       if (n->ln_Succ)
+       {
+               l->lh_Head = n->ln_Succ;
+               l->lh_Head->ln_Pred = (struct Node *)l;
+               return n;
+       }
+       return NULL;
+}
+
+INLINE struct Node *REMTAIL(struct List *l)
+{
+       struct Node *n = l->lh_TailPred;
+
+       if (n->ln_Pred)
+       {
+               l->lh_TailPred = n->ln_Pred;
+               l->lh_TailPred->ln_Succ = (struct Node *)(&(l->lh_Tail));
+               return n;
+       }
+       return NULL;
+}
+
+#endif
+
+#endif /* !LISTMACROS_H */
diff --git a/Locale.c b/Locale.c
new file mode 100644 (file)
index 0000000..1b48a82
--- /dev/null
+++ b/Locale.c
@@ -0,0 +1,402 @@
+/*
+**     Locale.c
+**
+**     Copyright (C) 1994,95,96,97 Bernardo Innocenti
+**
+**     Routines to handle localization.
+**
+**     NOTE: The Locale.c file is generated automatically by `FlexCat 2.1'
+**           using "Catalogs/XModule.cd". Do NOT edit by hand!
+*/
+
+#include <libraries/locale.h>
+
+#include <proto/exec.h>
+#include <proto/locale.h>
+
+#include "XModulePriv.h"
+
+
+#define CATALOGVERSION 1
+#define CATALOGNAME            "XModule.catalog"
+#define MSG_COUNT              311
+
+
+STRPTR AppStrings[] =
+{
+       "",
+       "Ok",
+       "_Use",
+       "_Ok",
+       "_Cancel",
+       "Yes|No",
+       "Retry|Cancel",
+       "Proceed|Cancel",
+       "Continue",
+       "Insufficient memory.",
+       "Aborted.",
+       "Unable to load \"%s\"",
+       "Cannot open file \"%s\"",
+       "Error reading \"%s\"",
+       "Error writing \"%s\"",
+       "Decrunching...",
+       "Nothing found in archive \"%s\".",
+       "Unable to load compressed file.",
+       "Error decompressing file \"%s\": %s.",
+       "Bad Commodity HotKey description.",
+       "Reading Patterns...",
+       "Reading Instruments Info...",
+       "Reading Instruments...",
+       "ERROR: Couldn't load pattern %ld.",
+       "ERROR: Couldn't load instrument %lx.",
+       "ERROR: Not enough memory for instrument %lx.",
+       "ERROR: Instrument %lx is not a sample.",
+       "WARNING: Song length exceeds maximum. Will be truncated.",
+       "WARNING: Song exceeds maximum number of patterns.",
+       "WARNING: Pattern %ld has too many tracks. Cropping to %ld tracks.",
+       "WARNING: Pattern %ld has too many lines. Cropping to %ld lines.",
+       "WARNING: Invalid note %ld (Patt %ld Track %ld Line %ld).",
+       "Unknown effect: $%lx (Patt %ld Track %ld Line %ld).",
+       "WARNING: Extra data found after valid module: Will be ignored.",
+       "Writing Header...",
+       "Writing Patterns...",
+       "Writing Instruments...",
+       "Writing Instruments Info...",
+       "Writing Instruments Data...",
+       "WARNING: Note at Patt %ld Track %ld Line %ld is too low.",
+       "WARNING: Note at Patt %ld Track %ld Line %ld is too high.",
+       "WARNING: Not enough memory to halve volume of instrument %lx.",
+       "Loading MMD%lc module...",
+       "ERROR: Unsupported OctaMED format.",
+       "WARNING: Effect %lx is not supported in MMD0 format. Use MMD1 or better.",
+       "ERROR: Instrument %lx is an ADLib %s.",
+       "WARNING: Track %lx is out of range.",
+       "WARNING: Unknown sample compression for instrument %lx.",
+       "WARNING: Instrument %lx is a stereo sample.",
+       "WARNING: Instrument %lx is 16bit: unable to load it.",
+       "Reading %s module...",
+       "NOTE: Module exceeds 64 patterns. You need ProTracker 2.3 to play it.",
+       "Tracker ID: \"%s\"",
+       "Module has %lu tracks...",
+       "This file is a song and doesn't contain instruments.",
+       "WARNING: Module execeeds %ld patterns.",
+       "Pattern %ld will grow to 64 lines (was %ld lines long).",
+       "Splitting pattern %ld (was %ld lines long).",
+       "Choosing Channels...",
+       "Writing MIDI Tracks...",
+       "ERROR: Song requires too many MIDI channels.",
+       "Really Quit XModule?",
+       "Please close all visitor windows\n"\
+       "and then select `Continue'.",
+       "Unknown IFF format %s.",
+       "Illegal IFF structure.",
+       "Unrecognized instrument format.\n"\
+       "Please select RAW mode.",
+       "Signed 8bit|Unsigned 8bit|Cancel",
+       "DataTypes error: %s.",
+       "Unknown compression type.",
+       "%lu bit samples are not supported.",
+       "Samples other than MONO are not supported.",
+       "Samples with %ld channels are not supported.",
+       "WARNING: insufficient memory to optimize instrument %lx.",
+       "Instrument %lx will shrink from %ld to %ld.",
+       "Duplicate instruments found and removed: %lx == %lx.",
+       "Instrument %lx was never used and it's being removed.",
+       "Couldn't open \"%s\" version %ld or greater.",
+       "Couldn't open \"%s\".",
+       "Unable to insert pattern: Maximum number of patterns reached.",
+       "Pattern %ld is not used and is beeing deleted.",
+       "Pattern %ld will be cut at line %ld.",
+       "Duplicate patterns found and removed: %ld == %ld.",
+       "WARNING: Song lengths are different. Using shorter one.",
+       "WARNING: Different pattern lengths at position %ld. Using shorter one.",
+       "ERROR: Instruments overflow. Try remapping the instruments.",
+       "Incorrect version of preferences file",
+       "XModule Request",
+       "Clone Workbench Screen",
+       "Please close FileRequester\n and then select `Continue'.",
+       "Select Module(s)...",
+       "Select Instrument(s)...",
+       "Select Pattern...",
+       "Save Module...",
+       "Save Instrument...",
+       "Save Pattern...",
+       "File \"%s\"\nalready exists.",
+       "Overwrite|Choose Another|Abort",
+       "Please close all open songs\n"\
+       "and then select `Continue'.",
+       "Unespected end of file.",
+       "Module loaded OK.",
+       "Module saved OK.",
+       "ERROR: Unrecognized save format.",
+       "Removed invalid loop for instrument %lx.",
+       "Fixed invalid loop for instrument %lx.",
+       "WARNING: Song has no patterns.",
+       "WARNING: Song has no sequence.",
+       "WARNING: Song position %ld references pattern %ld, which doesn't exist.",
+       "Unable to identify module format.\n"\
+       "(Loading a data file as a module is unwise)",
+       "SoundTracker 15|ProTracker|Cancel",
+       "Unknown",
+       "Untitled",
+       "AmigaGuide error:",
+       "Pattern Editor Settings",
+       "Advance _Tracks",
+       "Advance _Lines",
+       "Max _Undo Levels",
+       "Max Undo _Memory",
+       "Cli_pboard Unit",
+       "_Scroller Position",
+       "_Vertical Wrap",
+       "_Horizontal Wrap",
+       "He_x Line Numbers",
+       "Blank _Zero Digits",
+       "Backdrop _Window",
+       "Ho_rizontal Scroller",
+       "Display Cursor Ruler",
+       "Display Tiny Lines",
+       "_Editor Font",
+       "Background",
+       "T_ext",
+       "Li_nes",
+       "Tin_y Lines",
+       "Right",
+       "Left",
+       "Off",
+       "ERROR: Pattern would exceed the maximum number of lines.",
+       "Sequence Editor",
+       "Sequence",
+       "_Add",
+       "Up",
+       "Down",
+       "_Name",
+       "Patterns",
+       "_Up",
+       "_Down",
+       "_Ins",
+       "-- unnamed --",
+       "Instruments",
+       "_Volume",
+       "_FineTune",
+       "Length",
+       "_Kind",
+       "_Edit...",
+       "Sample",
+       "Synth",
+       "Hybrid",
+       "Instruments",
+       "Load...",
+       "Remap",
+       "Save Compressed",
+       "Save Raw",
+       "-- empty --",
+       "Play",
+       "_Vol",
+       "Pos",
+       "Time",
+       "_Rst",
+       "Player initialization error: %ld.",
+       "XModule is working...",
+       "XModule Log",
+       "_Abort",
+       "%ld of %ld (%ld%% done)",
+       "Song Information",
+       "Ne_w",
+       "_Open...",
+       "_Save",
+       "Del",
+       "Song _Name",
+       "_Author",
+       "_Tempo",
+       "S_peed",
+       "_Restart",
+       "Length",
+       "Num Patterns",
+       "Tracks",
+       "Total Module Size",
+       "Total Instruments Size",
+       "Song",
+       "Merge Songs",
+       "Join Songs",
+       "ERROR: Join requires two songs.",
+       "ERROR: Merge requires two songs.",
+       "Discard current song?",
+       "ToolBox",
+       "Play...",
+       "_Songs...",
+       "_Patterns...",
+       "_Instruments...",
+       "Se_quence...",
+       "_Optimization...",
+       "Project",
+       "New",
+       "Open...",
+       "Open New...",
+       "Save",
+       "Save As...",
+       "Clear...",
+       "About...",
+       "Help...",
+       "Iconify...",
+       "Quit",
+       "Settings",
+       "Save Format...",
+       "User Interface...",
+       "Save Icons",
+       "Confirm Overwrite",
+       "Confirm Exit",
+       "Verbose Log",
+       "Open Settings...",
+       "Save Settings",
+       "Save Settings As...",
+       "%s\n"\
+       "A Music Module Processing Utility\n\n"\
+       "%s\n"\
+       "All rights reserved.\n\n"\
+       "Internet: bernie@shock.cosmos.it\n\n"\
+       "FidoNet:  2:332/118.4\n"\
+       "Free CHIP Memory: %ldKB\n"\
+       "Free FAST Memory: %ldKB\n\n"\
+       "Public Screen: %s\n"\
+       "ARexx Port: %s\n"\
+       "Cx HotKey: %s\n"\
+       "Language: %s",
+       "-- Default --",
+       "-- Disabled --",
+       "Module Optimization",
+       "_Optimize",
+       "Remove Unused _Patterns",
+       "Remove _Duplicate Patterns",
+       "Remove Unused _Instruments",
+       "Remove Duplicate I_nstruments",
+       "Cut Instruments After _Loop",
+       "Cut Instrument _Zero Tails",
+       "Cut _Breaked Patterns",
+       "_Remap Instruments",
+       "Saved %ld bytes (%ld%%)",
+       "User Interface Settings",
+       "_Public Screen",
+       "Display _Mode",
+       "_Window Font",
+       "_ListView Font",
+       "_Requesters",
+       "Use _DataTypes",
+       "Put App_Icon",
+       "Refres_h",
+       "Log To File",
+       "_Log Level",
+       "Confirm _Autosave",
+       "Autosave Mi_nutes",
+       "Create _Backups",
+       "Bac_kup Template",
+       "Backup _Versions",
+       "Asl",
+       "ReqTools",
+       "Smart",
+       "Simple",
+       "--Clone Default Screen--",
+       "Pattern Editor",
+       "Patterns",
+       "Size...",
+       "Edit",
+       "Mark",
+       "Cut",
+       "Copy",
+       "Paste",
+       "Erase",
+       "Undo",
+       "Redo",
+       "Editor Settings...",
+       "Savers",
+       "Se_quence",
+       "_Instruments",
+       "_Patterns",
+       "Names",
+       "Add I_con",
+       "_Mode",
+       "Options...",
+       "None",
+       "XPK",
+       "LhA",
+       "Description",
+       "Author",
+       "Max Length",
+       "Max Tracks",
+       "Max Instruments",
+       "Max Patterns",
+       "Max Pattern Length",
+       "Max Sample Length",
+       "Clear Module",
+       "_Sequence",
+       "_Instruments",
+       "_Patterns",
+       "_Clear",
+       "Sample Editor",
+       "Render",
+       "Points",
+       "Lines",
+       "Filled",
+       "Pattern Attributes",
+       "_Lines",
+       "_Tracks",
+       "_Double",
+       "_Halve",
+       "WARNING: The selected saver supports max %lu tracks, but the song has %lu tracks.",
+       "WARNING: The selected saver supports max %lu patterns; the song has %lu patterns.",
+       "WARNING: The selected saver supports max %lu instruments, last instrument is %lu.",
+       "WARNING: The selected saver supports max %lu positions, but the song has %lu positions.",
+       "WARNING: The length of pattern %ld ($%lx lines) isn't allowed with the selected saver.",
+       "WARNING: Instrument %lx is too long and will be cropped to %lu bytes.",
+       "Some modifications need to be performed on this song\n"\
+       "in order to adapt it to the limitations of the\n"\
+       "destination format.",
+       "Some instruments are placed beyond the limit for\n"\
+       "the selected format.\n"\
+       "Remapping the instruments now could help saving\n"\
+       "all them along with the selected format.",
+       "Saving %s module \"%s\"..."
+};
+
+static struct Library          *LocaleBase = NULL;
+XDEF struct Catalog                    *Catalog = NULL;
+
+
+
+GLOBALCALL void SetupLocale (void)
+{
+       /* Try to open locale.library */
+       if (LocaleBase = OpenLibrary ("locale.library", 38L))
+       {
+               /* Try to get catalog for current language */
+               if (Catalog = OpenCatalog (NULL, CATALOGNAME,
+                       OC_BuiltInLanguage,     "english",
+                       OC_Version,                     CATALOGVERSION,
+                       TAG_DONE))
+               {
+                       /* Read in locale language strings */
+                       UBYTE **as = AppStrings;
+                       ULONG i;
+
+                       /* Get translation strings */
+                       for (i = 1; i < MSG_COUNT; i++, as++)
+                               *as = GetCatalogStr (Catalog, i, *as);
+               }
+       }
+
+       /* Fix for a limitation of FlexCat: MSG_NULL should translate
+        * to a NULL pointer, not a pointer to an empty string :)
+        */
+       AppStrings[0] = NULL;
+}
+
+
+
+GLOBALCALL void CleanupLocale (void)
+{
+       if (LocaleBase)
+       {
+               /* No need to check for NULL */
+               CloseCatalog (Catalog);         Catalog = NULL;
+               CloseLibrary (LocaleBase);      LocaleBase = NULL;
+       }
+}
diff --git a/LocaleStrings.h b/LocaleStrings.h
new file mode 100644 (file)
index 0000000..6f78d35
--- /dev/null
@@ -0,0 +1,327 @@
+/*
+**     NOTE: The LocaleStrings.h file is generated automatically by `FlexCat 2.1'
+**           using "Catalogs/XModule.cd". Do NOT edit by hand!
+*/
+
+#ifndef XModule_CAT_H
+#define XModule_CAT_H
+
+
+/* Message Definitions */
+#define MSG_NULL 0
+#define MSG_OK 1
+#define MSG_UNDERSCORE_USE_GAD 2
+#define MSG_UNDERSCORE_OK_GAD 3
+#define MSG_UNDERSCORE_CANCEL_GAD 4
+#define MSG_YES_OR_NO 5
+#define MSG_RETRY_OR_CANCEL 6
+#define MSG_PROCEED_OR_CANCEL 7
+#define MSG_CONTINUE 8
+#define MSG_NO_FREE_STORE 9
+#define MSG_BREAK 10
+#define MSG_ERR_LOAD 11
+#define MSG_CANT_OPEN 12
+#define MSG_ERROR_READING 13
+#define MSG_ERROR_WRITING 14
+#define MSG_DECRUNCHING 15
+#define MSG_NOTHING_IN_ARC 16
+#define MSG_CANT_LOAD_COMPRESSED 17
+#define MSG_ERROR_DECOMPRESSING 18
+#define MSG_BAD_HOTKEY 19
+#define MSG_READING_PATTS 20
+#define MSG_READING_INSTS_INFO 21
+#define MSG_READING_INSTS 22
+#define MSG_ERR_CANT_LOAD_PATT 23
+#define MSG_ERR_CANT_LOAD_INST 24
+#define MSG_ERR_NO_MEM_FOR_INST 25
+#define MSG_ERR_NOT_A_SAMPLE 26
+#define MSG_SONG_TOO_LONG 27
+#define MSG_SONG_HAS_TOO_MANY_PATT 28
+#define MSG_PATT_TOO_MANY_TRACKS 29
+#define MSG_PATT_TOO_MANY_LINES 30
+#define MSG_INVALID_NOTE 31
+#define MSG_UNKNOWN_EFF 32
+#define MSG_EXTRA_DATA_AFTER_MOD 33
+#define MSG_WRITING_HEADER 34
+#define MSG_WRITING_PATTS 35
+#define MSG_WRITING_INSTS 36
+#define MSG_WRITING_INSTINFO 37
+#define MSG_WRITING_INSTDATA 38
+#define MSG_NOTE_TOO_LOW 39
+#define MSG_NOTE_TOO_HIGH 40
+#define MSG_NO_MEM_TO_HALVE 41
+#define MSG_READING_MMD 42
+#define MSG_UNSUPPORTED_MMD_FORMAT 43
+#define MSG_WRONG_EFFECT_IN_MMD0 44
+#define MSG_ADLIB_INSTR 45
+#define MSG_TRACK_OUT_OF_RANGE 46
+#define MSG_UNKNOWN_SAMPLE_COMPRESSION 47
+#define MSG_INST_IS_STEREO 48
+#define MSG_INST_IS_16BIT 49
+#define MSG_READING_TYPE_MODULE 50
+#define MSG_EXCEEDS_64_PATTS 51
+#define MSG_MODULE_ID 52
+#define MSG_MODULE_HAS_N_CHN 53
+#define MSG_SONG_HAS_NO_INSTS 54
+#define MSG_EXCEEDS_MAXPAATTS 55
+#define MSG_PATT_WILL_GROW 56
+#define MSG_SPLITTING_PATT 57
+#define MSG_CHOOSING_CHANNELS 58
+#define MSG_WRITING_MIDI_TRACKS 59
+#define MSG_TOO_MANY_CHANNELS 60
+#define MSG_REALLY_QUIT_XMODULE 61
+#define MSG_CLOSE_ALL_WINDOWS 62
+#define MSG_UNKNOWN_IFF 63
+#define MSG_ILLEGAL_IFF_STRUCTURE 64
+#define MSG_SELECT_RAW_MODE 65
+#define MSG_RAW_MODES 66
+#define MSG_DATATYPES_ERROR 67
+#define MSG_UNKNOWN_COMPRESSION 68
+#define MSG_SAMPLE_WRONG_SIZE 69
+#define MSG_SAMPLE_NOT_MONO 70
+#define MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS 71
+#define MSG_NO_MEMORY_TO_OPTIMIZE_INSTR 72
+#define MSG_INSTR_WILL_SHRINK 73
+#define MSG_INSTR_DUPES_REMOVED 74
+#define MSG_INSTR_UNUSED 75
+#define MSG_OPENLIB_VER_FAIL 76
+#define MSG_OPENLIB_FAIL 77
+#define MSG_CANT_INSERT_PATT 78
+#define MSG_PATT_UNUSED 79
+#define MSG_PATT_CUT 80
+#define MSG_PATT_DUPE 81
+#define MSG_SONG_LEN_DIFFERENT 82
+#define MSG_PATT_LEN_DIFFERENT 83
+#define MSG_ERR_INSTR_OVERFLOW 84
+#define MSG_BAD_PREFS_VERSION 85
+#define MSG_XMODULE_REQUEST 86
+#define MSG_CLONE_WB 87
+#define MSG_CLOSE_FILEREQUESTER 88
+#define MSG_SELECT_MODULES 89
+#define MSG_SELECT_INSTRUMENTS 90
+#define MSG_SELECT_PATTERN 91
+#define MSG_SAVE_MODULE 92
+#define MSG_SAVE_INSTRUMENT 93
+#define MSG_SAVE_PATTERN 94
+#define MSG_FILE_EXISTS 95
+#define MSG_OVERWRITE 96
+#define MSG_CLOSE_ALL_SONGS 97
+#define MSG_UNESPECTED_EOF 98
+#define MSG_MODULE_LOADED_OK 99
+#define MSG_MODULE_SAVED_OK 100
+#define MSG_UNKNOWN_SAVE_FORMAT 101
+#define MSG_INVALID_LOOP_REMOVED 102
+#define MSG_INVALID_LOOP_FIXED 103
+#define MSG_SONG_HAS_NO_PATTS 104
+#define MSG_SONG_HAS_NO_SEQ 105
+#define MSG_INVALID_SONG_POS 106
+#define MSG_UNKNOWN_MOD_FORMAT 107
+#define MSG_SOUND_PRO_CANCEL 108
+#define MSG_AUTHOR_UNKNOWN 109
+#define MSG_SONG_UNTITLED 110
+#define MSG_AMIGAGUIDE_ERROR 111
+#define MSG_PATTPREFS_TITLE 112
+#define MSG_ADVANCE_TRACKS_GAD 113
+#define MSG_ADVANCE_LINES_GAD 114
+#define MSG_MAX_UNDO_LEVELS_GAD 115
+#define MSG_MAX_UNDO_MEM_GAD 116
+#define MSG_CLIPBOARD_UNIT_GAD 117
+#define MSG_SCROLLER_POS_GAD 118
+#define MSG_VERT_WRAP_GAD 119
+#define MSG_HORIZ_WRAP_GAD 120
+#define MSG_HEX_LINE_NUMBERS_GAD 121
+#define MSG_BLANK_ZERO_GAD 122
+#define MSG_BACKDROP_GAD 123
+#define MSG_HORIZ_SCROLLER_GAD 124
+#define MSG_DO_RULER_GAD 125
+#define MSG_DO_TINY_LINES_GAD 126
+#define MSG_EDITOR_FONT_GAD 127
+#define MSG_BACKGROUND_PEN_GAD 128
+#define MSG_TEXT_PEN_GAD 129
+#define MSG_LINES_PEN_GAD 130
+#define MSG_TINY_LINES_PEN_GAD 131
+#define MSG_RIGHT_GAD 132
+#define MSG_LEFT_GAD 133
+#define MSG_OFF_GAD 134
+#define MSG_PATT_TOO_LONG 135
+#define MSG_SEQUENCE_TITLE 136
+#define MSG_SEQUENCE_GAD 137
+#define MSG_UNDERSCORE_ADD_GAD 138
+#define MSG_UP_GAD 139
+#define MSG_DOWN_GAD 140
+#define MSG_UNDERSCORE_NAME_GAD 141
+#define MSG_PATTERNS_GAD 142
+#define MSG_UNDERSCORE_UP_GAD 143
+#define MSG_UNDERSCORE_DOWN_GAD 144
+#define MSG_UNDERSCORE_INS_GAD 145
+#define MSG_UNNAMED 146
+#define MSG_INSTRUMENTS_TITLE 147
+#define MSG_VOLUME_GAD 148
+#define MSG_FINETUNE_GAD 149
+#define MSG_LENGTH_GAD 150
+#define MSG_KIND_GAD 151
+#define MSG_EDIT_DOTS_GAD 152
+#define MSG_SAMPLE_GAD 153
+#define MSG_SYNTH_GAD 154
+#define MSG_HYBRID_GAD 155
+#define MSG_INSTRUMENTS_MEN 156
+#define MSG_LOAD_MEN 157
+#define MSG_REMAP_MEN 158
+#define MSG_SAVE_COMPRESSED_MEN 159
+#define MSG_SAVE_RAW_MEN 160
+#define MSG_EMPTY 161
+#define MSG_PLAY_TITLE 162
+#define MSG_VOL_GAD 163
+#define MSG_POS_GAD 164
+#define MSG_TIME_GAD 165
+#define MSG_RST_GAD 166
+#define MSG_PLAYER_INIT_ERR 167
+#define MSG_PROGRESS_TITLE 168
+#define MSG_LOG_TITLE 169
+#define MSG_UNDERSCORE_ABORT_GAD 170
+#define MSG_PERCENT_DONE 171
+#define MSG_SONGINFO_TITLE 172
+#define MSG_UNDERSCORE_NEW_GAD 173
+#define MSG_OPEN_GAD 174
+#define MSG_SAVE_GAD 175
+#define MSG_DEL_GAD 176
+#define MSG_SONG_NAME_GAD 177
+#define MSG_AUTHOR_NAME_GAD 178
+#define MSG_DEF_TEMPO_GAD 179
+#define MSG_DEF_SPEED_GAD 180
+#define MSG_RESTART_GAD 181
+#define MSG_LENGHT_GAD 182
+#define MSG_NUM_PATTS_GAD 183
+#define MSG_NUM_TRACKS_GAD 184
+#define MSG_TOT_MOD_SIZE_GAD 185
+#define MSG_TOT_INST_SIZE_GAD 186
+#define MSG_SONG_MEN 187
+#define MSG_MERGE_SONGS_MEN 188
+#define MSG_JOIN_SONGS_MEN 189
+#define MSG_JOIN_REQUIRES_TWO_SONGS 190
+#define MSG_MERGE_REQUIRES_TWO_SONGS 191
+#define MSG_DISCARD_CURRENT_SONG 192
+#define MSG_TOOLBOX_TITLE 193
+#define MSG_PLAY_GAD 194
+#define MSG_SONGS_GAD 195
+#define MSG_PATTERNS_DOTS_GAD 196
+#define MSG_INSTRUMENTS_GAD 197
+#define MSG_SEQUENCE_DOTS_GAD 198
+#define MSG_OPTIMIZATION_GAD 199
+#define MSG_PROJECT_MEN 200
+#define MSG_NEW_MEN 201
+#define MSG_OPEN_MEN 202
+#define MSG_OPEN_NEW_MEN 203
+#define MSG_SAVE_MEN 204
+#define MSG_SAVE_AS_MEN 205
+#define MSG_CLEAR_MEN 206
+#define MSG_ABOUT_MEN 207
+#define MSG_HELP_MEN 208
+#define MSG_ICONIFY_MEN 209
+#define MSG_QUIT_MEN 210
+#define MSG_SETTINGS_MEN 211
+#define MSG_SAVE_FORMAT_MEN 212
+#define MSG_USER_INTERFACE_MEN 213
+#define MSG_SAVE_ICONS_MEN 214
+#define MSG_CONFIRM_OVERWRITE_MEN 215
+#define MSG_CONFIRM_EXIT_MEN 216
+#define MSG_VERBOSE_MEN 217
+#define MSG_OPEN_SETTINGS_MEN 218
+#define MSG_SAVE_SETTINGS_MEN 219
+#define MSG_SAVE_SETTINGS_AS_MEN 220
+#define MSG_ABOUT_TEXT 221
+#define MSG_DEFAULT 222
+#define MSG_DISABLED 223
+#define MSG_OPTIMIZATION_TITLE 224
+#define MSG_OPTIMIZE_GAD 225
+#define MSG_REM_UNUSED_PATTS_GAD 226
+#define MSG_REM_DUPLICATE_PATTS_GAD 227
+#define MSG_REM_UNUSED_INSTR_GAD 228
+#define MSG_REM_DUP_INSTR_GAD 229
+#define MSG_CUT_AFTER_LOOP_GAD 230
+#define MSG_CUT_ZERO_TAILS_GAD 231
+#define MSG_CUT_PATTERNS_GAD 232
+#define MSG_REMAP_INSTRUMENTS_GAD 233
+#define MSG_SAVED_X_BYTES 234
+#define MSG_PREFS_TITLE 235
+#define MSG_PUBLIC_SCREEN_GAD 236
+#define MSG_DISPLAY_MODE_GAD 237
+#define MSG_WINDOW_FONT_GAD 238
+#define MSG_LISTVIEW_FONT_GAD 239
+#define MSG_REQUESTERS_GAD 240
+#define MSG_USE_DATATYPES_GAD 241
+#define MSG_APPICON_GAD 242
+#define MSG_REFRESH_GAD 243
+#define MSG_LOG_TO_FILE_GAD 244
+#define MSG_LOG_LEVEL_GAD 245
+#define MSG_ASK_AUTOSAVE_GAD 246
+#define MSG_AUTOSAVE_TIME_GAD 247
+#define MSG_DO_BACKUPS_GAD 248
+#define MSG_BACKUP_TEMPLATE_GAD 249
+#define MSG_BACKUP_VERSIONS_GAD 250
+#define MSG_ASL_GAD 251
+#define MSG_REQTOOLS_GAD 252
+#define MSG_SMART_GAD 253
+#define MSG_SIMPLE_GAD 254
+#define MSG_CLONE_DEF_SCREEN 255
+#define MSG_PATTERN_TITLE 256
+#define MSG_PATTERNS_MEN 257
+#define MSG_SIZE_MEN 258
+#define MSG_EDIT_MEN 259
+#define MSG_MARK_MEN 260
+#define MSG_CUT_MEN 261
+#define MSG_COPY_MEN 262
+#define MSG_PASTE_MEN 263
+#define MSG_ERASE_MEN 264
+#define MSG_UNDO_MEN 265
+#define MSG_REDO_MEN 266
+#define MSG_EDITOR_SETTINGS_MEN 267
+#define MSG_SAVERS_TITLE 268
+#define MSG_SF_SEQUENCE_GAD 269
+#define MSG_SF_INSTRUMENTS_GAD 270
+#define MSG_SF_PATTERNS_GAD 271
+#define MSG_SF_NAMES_GAD 272
+#define MSG_ADD_ICON_GAD 273
+#define MSG_MODE_GAD 274
+#define MSG_OPTIONS_DOTS_GAD 275
+#define MSG_NONE_GAD 276
+#define MSG_XPK_GAD 277
+#define MSG_LHA_GAD 278
+#define MSG_DESCRIPTION 279
+#define MSG_AUTHOR 280
+#define MSG_MAXLENGTH 281
+#define MSG_MAXTRACKS 282
+#define MSG_MAXINSTRUMENTS 283
+#define MSG_MAXPATTERNS 284
+#define MSG_MAXPATTLEN 285
+#define MSG_MAXSAMPLELEN 286
+#define MSG_CLEAR_TITLE 287
+#define MSG_CLR_SEQUENCE_GAD 288
+#define MSG_CLR_INSTRUMENTS_GAD 289
+#define MSG_CLR_PATTERNS_GAD 290
+#define MSG_CLEARMOD_GAD 291
+#define MSG_SAMPLE_TITLE 292
+#define MSG_RENDER_MEN 293
+#define MSG_POINTS_MEN 294
+#define MSG_LINES_MEN 295
+#define MSG_FILLED_MEN 296
+#define MSG_PATTSIZE_TITLE 297
+#define MSG_LINES_GAD 298
+#define MSG_TRACKS_GAD 299
+#define MSG_DOUBLE_GAD 300
+#define MSG_HALVE_GAD 301
+#define MSG_SONG_TOO_MANY_TRACKS 302
+#define MSG_SONG_TOO_MANY_PATTS 303
+#define MSG_SONG_TOO_MANY_INSTRS 304
+#define MSG_SONG_TOO_MANY_POS 305
+#define MSG_PATT_LENGTH_INVALID 306
+#define MSG_INSTR_TOO_LONG 307
+#define MSG_WILL_MODIFY_SONG 308
+#define MSG_TRY_REMAPPING_INSTRUMENTS 309
+#define MSG_SAVING_MODULE 310
+
+/* Strings Definitions */
+#define STR(x) (AppStrings[x])
+
+
+#endif /*   !XModule_CAT_H  */
diff --git a/LocaleStrings_h.sd b/LocaleStrings_h.sd
new file mode 100644 (file)
index 0000000..902dcee
--- /dev/null
@@ -0,0 +1,19 @@
+##stringtype C
+##shortstrings
+/*
+**     NOTE: The LocaleStrings.h file is generated automatically by `%fv'
+**           using "%f0". Do NOT edit by hand!
+*/
+
+#ifndef %b_CAT_H
+#define %b_CAT_H
+
+
+/* Message Definitions */
+#define %i %d
+
+/* Strings Definitions */
+#define STR(x) (AppStrings[x])
+
+
+#endif /*   !%b_CAT_H  */
diff --git a/Locale_c.sd b/Locale_c.sd
new file mode 100644 (file)
index 0000000..f391506
--- /dev/null
@@ -0,0 +1,74 @@
+##stringtype C
+##shortstrings
+/*
+**     Locale.c
+**
+**     Copyright (C) 1994,95,96,97 Bernardo Innocenti
+**
+**     Routines to handle localization.
+**
+**     NOTE: The Locale.c file is generated automatically by `%fv'
+**           using "%f0". Do NOT edit by hand!
+*/
+
+#include <libraries/locale.h>
+
+#include <proto/exec.h>
+#include <proto/locale.h>
+
+#include "XModulePriv.h"
+
+
+#define CATALOGVERSION %v
+#define CATALOGNAME            "%b.catalog"
+#define MSG_COUNT              %n
+
+
+STRPTR AppStrings[] =
+{
+       %s%(,)
+};
+
+static struct Library          *LocaleBase = NULL;
+XDEF struct Catalog                    *Catalog = NULL;
+
+
+
+GLOBALCALL void SetupLocale (void)
+{
+       /* Try to open locale.library */
+       if (LocaleBase = OpenLibrary ("locale.library", 38L))
+       {
+               /* Try to get catalog for current language */
+               if (Catalog = OpenCatalog (NULL, CATALOGNAME,
+                       OC_BuiltInLanguage,     "english",
+                       OC_Version,                     CATALOGVERSION,
+                       TAG_DONE))
+               {
+                       /* Read in locale language strings */
+                       UBYTE **as = AppStrings;
+                       ULONG i;
+
+                       /* Get translation strings */
+                       for (i = 1; i < MSG_COUNT; i++, as++)
+                               *as = GetCatalogStr (Catalog, i, *as);
+               }
+       }
+
+       /* Fix for a limitation of FlexCat: MSG_NULL should translate
+        * to a NULL pointer, not a pointer to an empty string :)
+        */
+       AppStrings[0] = NULL;
+}
+
+
+
+GLOBALCALL void CleanupLocale (void)
+{
+       if (LocaleBase)
+       {
+               /* No need to check for NULL */
+               CloseCatalog (Catalog);         Catalog = NULL;
+               CloseLibrary (LocaleBase);      LocaleBase = NULL;
+       }
+}
diff --git a/Main.c b/Main.c
new file mode 100644 (file)
index 0000000..bb71e6f
--- /dev/null
+++ b/Main.c
@@ -0,0 +1,587 @@
+/*
+**     $VER: XModule 3.9 (16.8.97) Copyright (C) 1993,94,95,96 Bernardo Innocenti
+**
+**     This source code is provided "AS-IS", without warranties of any kind and
+**     it is subject to change without notice.  All usage is at your own risk.
+**     No liability or responsibility is assumed by the author.
+**
+**     Use 4 chars wide TABs to read this file
+*/
+
+#include <exec/execbase.h>
+#include <exec/lists.h>
+#include <exec/memory.h>
+#include <dos/dos.h>
+#include <dos/dostags.h>
+#include <libraries/gadtools.h>
+#include <utility/utility.h>
+#include <workbench/startup.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/icon.h>
+#include <proto/utility.h>
+#include <proto/xmodule.h>
+
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Local functions prototypes */
+
+static LONG GetShellArgs (void);
+static LONG GetWBArgs (void);
+static void DisposeArgs (void);
+static void HandleFrom (void);
+static void SetupHooks (void);
+static void Cleanup (LONG err);
+static LONG Setup (void);
+
+
+
+/* Memory pool for use by main program only */
+XDEF void *Pool = NULL;
+
+
+
+/* Version tag */
+XDEF const UBYTE Version[] = "$VER: " VSTR " " BUILDMODE " " XMODULECOPY;
+XDEF const UBYTE BaseName[] = BASENAME;
+XDEF const UBYTE PrgName[] = PRGNAME;
+
+
+
+/* Library bases */
+XDEF struct IntuitionBase      *IntuitionBase  = NULL;
+XDEF struct GfxBase                    *GfxBase                = NULL;
+XDEF struct Library                    *LayersBase             = NULL;
+XDEF struct UtilityBase                *UtilityBase    = NULL;
+XDEF struct Library                    *GadToolsBase   = NULL;
+XDEF struct Library                    *DiskfontBase   = NULL;
+XDEF struct Library                    *AslBase                = NULL;
+XDEF struct Library                    *IFFParseBase   = NULL;
+XDEF struct Library                    *WorkbenchBase  = NULL;
+XDEF struct Library                    *IconBase               = NULL;
+XDEF struct ReqToolsBase       *ReqToolsBase   = NULL;
+XDEF struct Library                    *CxBase                 = NULL;
+XDEF struct Library                    *KeymapBase             = NULL;
+XDEF struct XModuleBase                *XModuleBase    = NULL;
+
+
+
+/* This structure holds data to open required libraries automatically. */
+
+struct OpenLibs
+{
+       struct Library **Base;
+       UBYTE *Name;
+       LONG Version;
+};
+
+
+#ifdef OS30_ONLY
+ #define OSLIBVER 39
+#else
+ #define OSLIBVER 37
+#endif /* !OS30_ONLY */
+
+static const struct OpenLibs openlibs[] =
+{
+       { (struct Library **)&IntuitionBase,    "intuition.library",    OSLIBVER },
+       { (struct Library **)&GfxBase,                  "graphics.library",             OSLIBVER },
+       { (struct Library **)&LayersBase,               "layers.library",               OSLIBVER },
+       { (struct Library **)&UtilityBase,              "utility.library",              OSLIBVER },
+       { (struct Library **)&GadToolsBase,             "gadtools.library",             OSLIBVER },
+       { (struct Library **)&KeymapBase,               "keymap.library",               37 },
+       { (struct Library **)&IFFParseBase,             "iffparse.library",             37 },
+       { (struct Library **)&WorkbenchBase,    "workbench.library",    37 },
+       { (struct Library **)&IconBase,                 "icon.library",                 37 },
+       { (struct Library **)&DiskfontBase,             "diskfont.library",             0 },
+
+       { NULL }
+};
+
+
+
+/* Used for argument parsing.
+ * NOTE: All fields must be 4 bytes long.
+ */
+static struct
+{
+       STRPTR  *From,
+                        PubScreen,
+                        PortName,
+                        Settings;
+       LONG     CxPopup;               /* Set to TRUE by default. */
+       STRPTR   CxPopKey;
+       LONG    *CxPriority,
+                       *IconX,
+                       *IconY;
+       STRPTR   IconName;
+} XMArgs; /* egcs doesn't like this: = { 0 }; */
+
+static struct RDArgs *RDArgs = NULL;
+
+#define ARGS_TEMPLATE  "FROM/M,PUBSCREEN/K,PORTNAME/K,SETTINGS/K,CX_POPUP/T,CX_POPKEY/K,CX_PRIORITY/K/N,ICONXPOS/K/N,ICONYPOS/K/N,ICONNAME/K"
+
+
+
+extern LONG STDARGS __main (void)
+
+/* XModule main entry point.  Get arguments from CLI/Workbench,
+ * setup environment, open required files and call main event
+ * handling loop.
+ */
+{
+       LONG err;
+
+       DB(kprintf ("\n"));
+       DB(kprintf ((STRPTR)(Version + 6)));
+       DB(kprintf ("\n*** Starting debug session -- Good luck!\n\n"));
+
+
+       if (!(err = Setup())) /* Setup environment */
+               err = HandleGui();
+
+       Cleanup (err);
+
+       return err;
+}      /* End main() */
+
+
+
+static LONG GetShellArgs (void)
+
+/* Parse command line arguments */
+{
+       if (!(RDArgs = ReadArgs (ARGS_TEMPLATE, (LONG *)&XMArgs, NULL)))
+               return IoErr();
+
+       return RETURN_OK;
+
+} /* End GetShellArgs() */
+
+
+
+static LONG GetWBArgs (void)
+
+/* Parse Workbench arguments */
+{
+       struct DiskObject *dobj;
+       STRPTR val;
+       UWORD i;
+
+
+       /* Get Multiselect args.
+        * Create a NULL-terminated array of STRPTRs
+        * in the same way ReadArgs() would have done.
+        */
+       if (WBenchMsg->sm_NumArgs > 1)
+               if (!(XMArgs.From = AllocVec (WBenchMsg->sm_NumArgs * sizeof (STRPTR), MEMF_CLEAR)))
+                       return RETURN_FAIL;
+
+       for (i = 1; i < WBenchMsg->sm_NumArgs; i++)
+       {
+               UBYTE buf[PATHNAME_MAX];
+
+               if (NameFromLock (WBenchMsg->sm_ArgList[i].wa_Lock, buf, PATHNAME_MAX))
+                       if (AddPart (buf, WBenchMsg->sm_ArgList[i].wa_Name, PATHNAME_MAX))
+                       {
+                               if (XMArgs.From[i-1] = AllocVec (strlen (buf), MEMF_ANY))
+                                       strcpy (XMArgs.From[i-1], buf);
+                               else
+                                       break;
+                       }
+       }
+
+
+       /* Get ToolTypes */
+
+       if (!(dobj = GetProgramIcon()))
+               return RETURN_FAIL;
+
+       if (val = FindToolType (dobj->do_ToolTypes, "PUBSCREEN"))
+               if (XMArgs.PubScreen = AllocVec (strlen (val), MEMF_ANY))
+                       strcpy (XMArgs.PubScreen, val);
+
+       if (val = FindToolType (dobj->do_ToolTypes, "PORTNAME"))
+               if (XMArgs.PortName = AllocVec (strlen (val), MEMF_ANY))
+                       strcpy (XMArgs.PortName, val);
+
+       if (val = FindToolType (dobj->do_ToolTypes, "SETTINGS"))
+               if (XMArgs.Settings = AllocVec (strlen (val), MEMF_ANY))
+                       strcpy (XMArgs.Settings, val);
+
+       if (val = FindToolType (dobj->do_ToolTypes, "CX_POPUP"))
+               XMArgs.CxPopup = MatchToolValue (val, "YES");
+
+       if (val = FindToolType (dobj->do_ToolTypes, "CX_POPKEY"))
+               if (XMArgs.CxPopKey = AllocVec (strlen (val), MEMF_ANY))
+                       strcpy (XMArgs.CxPopKey, val);
+
+       if (val = FindToolType (dobj->do_ToolTypes, "CX_PRIORITY"))
+               if (XMArgs.CxPriority = AllocVec (sizeof (LONG), MEMF_ANY))
+                       StrToLong (val, XMArgs.CxPriority);
+
+       if (val = FindToolType (dobj->do_ToolTypes, "ICONXPOS"))
+               if (XMArgs.IconX = AllocVec (sizeof (LONG), MEMF_ANY))
+                       StrToLong (val, XMArgs.IconX);
+
+       if (val = FindToolType (dobj->do_ToolTypes, "ICONYPOS"))
+               if (XMArgs.IconY = AllocVec (sizeof (LONG), MEMF_ANY))
+                       StrToLong (val, XMArgs.IconY);
+
+       if (val = FindToolType (dobj->do_ToolTypes, "ICONNAME"))
+               if (XMArgs.IconName = AllocVec (strlen (val), MEMF_ANY))
+                       strcpy (XMArgs.IconName, val);
+
+       FreeDiskObject (dobj);
+
+       return RETURN_OK;
+} /* End GetWBArgs() */
+
+
+
+static void DisposeArgs (void)
+{
+       if (RDArgs)
+       {
+               FreeArgs (RDArgs);
+               RDArgs = NULL;
+       }
+       else    /* Workbench */
+       {
+               /* NULL is a valid parameter for FreeVec() */
+               FreeVec (XMArgs.IconName);
+               FreeVec (XMArgs.IconY);
+               FreeVec (XMArgs.IconX);
+               FreeVec (XMArgs.CxPriority);
+               FreeVec (XMArgs.CxPopKey);
+               FreeVec (XMArgs.Settings);
+               FreeVec (XMArgs.PortName);
+               FreeVec (XMArgs.PubScreen);
+
+               if (XMArgs.From)
+               {
+                       STRPTR *tmp = XMArgs.From;
+
+                       while (*tmp)
+                       {
+                               FreeVec (*tmp);
+                               tmp++;
+                       }
+
+                       FreeVec (XMArgs.From);
+               }
+       }
+
+       memset (&XMArgs, 0, sizeof (XMArgs));
+}
+
+
+
+static void HandleFrom (void)
+{
+       if (XMArgs.From)
+       {
+               STRPTR                          *name = XMArgs.From;
+               struct AnchorPath       *ap;
+               LONG err;
+
+               if (ap = AllocMem (sizeof (struct AnchorPath) + PATHNAME_MAX, MEMF_CLEAR))
+               {
+                       OpenProgressWindow ();
+
+                       ap->ap_BreakBits = SIGBREAKF_CTRL_C;
+                       ap->ap_Strlen = PATHNAME_MAX;
+
+                       while (*name)
+                       {
+                               err = MatchFirst (*name, ap);
+
+                               while (!err)
+                               {
+                                       xmLoadModule (ap->ap_Buf,
+                                               XMSNG_AddToList, TRUE,
+                                               TAG_DONE);
+
+                                       err = MatchNext (ap);
+                               }
+
+                               if (err != ERROR_NO_MORE_ENTRIES)
+                               {
+                                       UBYTE buf[FAULT_MAX];
+
+                                       Fault (err, NULL, buf, FAULT_MAX);
+                                       ShowMessage (MSG_ERR_LOAD, *name, buf);
+                               }
+
+                               MatchEnd (ap);
+                               name++;
+                       }
+
+                       CloseProgressWindow();
+
+                       FreeMem (ap, sizeof (struct AnchorPath) + PATHNAME_MAX);
+               }
+               else LastErr = ERROR_NO_FREE_STORE;
+       }
+}
+
+
+
+static void SetupHooks (void)
+{
+       struct Library                  *XMHookBase;
+       struct FileInfoBlock    *fib;
+       BPTR                                     lock;
+       UBYTE                                    libpath[PATHNAME_MAX];
+
+       /* Built-in hooks */
+       AddXModuleHooks ();
+       AddTrackerHooks ();
+
+
+       if (fib = (struct FileInfoBlock *)AllocDosObject (DOS_FIB, NULL))
+       {
+               if (lock = Lock (DEF_HOOKSDIR, ACCESS_READ))
+                       if (Examine (lock, fib))
+                               while (ExNext (lock, fib))
+                               {
+                                       strcpy (libpath, DEF_HOOKSDIR);
+                                       AddPart (libpath, fib->fib_FileName, PATHNAME_MAX);
+
+                                       if (XMHookBase = OpenLibrary (libpath, 0))
+                                       {
+                                               #if defined(__SASC)
+                                                       #pragma libcall XMHookBase SetupXMHook 24 801
+                                                       void SetupXMHook (struct XModuleBase *);
+                                               #elif defined(__GNUC__)
+                                                       #define SetupXMHook(xmbase) \
+                                                               LP1NR(0x24, SetupXMHook, struct XModuleBase *, xmbase, a0, \
+                                                               , XMHookBase)
+                                               #else
+                                                       #error Define SetupXMHook() library call for your compiler
+                                               #endif
+
+                                               SetupXMHook (XModuleBase);
+                                               CloseLibrary (XMHookBase);
+                                       }
+                               }
+
+               FreeDosObject (DOS_FIB, fib);
+       }
+
+
+       /* Set default saver */
+
+       if (!IsListEmpty ((struct List *)&XModuleBase->xm_Savers))
+               XModuleBase->xm_DefaultSaver = (struct XMHook *)XModuleBase->xm_Savers.mlh_Head;
+}
+
+
+
+static LONG Setup (void)
+{
+       LONG err;
+       ULONG i;
+
+       SetProgramName (PrgName);
+
+       /* Initialize view lists */
+       NEWLIST (&WindowList);
+       NEWLIST (&LogList);
+       NEWLIST (&PatternsList);
+       NEWLIST (&SequenceList);
+       NEWLIST (&InstrList);
+
+       /* Install graphics function replacements */
+       /*      InstallGfxFunctions();  */      /* These are currently never used */
+
+       /* Initialize ScrInfo structure */
+       strcpy (ScrInfo.PubScreenName, BaseName);
+
+       /* Initialize PubPort name */
+       strcpy (PubPortName, BaseName);
+
+       /* Initialize PubPort name */
+       strcpy (IconName, PrgName);
+
+
+       /* Open required libraries */
+       for (i = 0 ; openlibs[i].Base ; i++)
+               if (!(*(openlibs[i].Base) = MyOpenLibrary (openlibs[i].Name, openlibs[i].Version)))
+                       if (openlibs[i].Version) return RETURN_FAIL;
+
+#ifndef OS30_ONLY
+       if (UtilityBase->lib_Version >= 39)
+#endif /* !OS30_ONLY */
+               UniqueID = GetUniqueID();       /* Get ID for HelpGroup and other jobs */
+
+       /* Get startup arguments */
+       XMArgs.CxPopup = TRUE;
+
+       if (WBenchMsg)
+               err = GetWBArgs();
+       else
+               err = GetShellArgs();
+
+       if (err) return err;
+
+       SetupLocale();
+
+       /* Create XModule library */
+       if (err = MakeXModuleLibrary ())
+               return err;
+
+       Pool = XModuleBase->xm_Pool;
+
+
+       /* Try to load XModule preferences */
+       if (XMArgs.Settings)
+       {
+               if (LoadPrefs (XMArgs.Settings))
+               {
+                       UBYTE buf[FAULT_MAX];
+
+                       Fault (IoErr(), NULL, buf, FAULT_MAX);
+                       ShowMessage (MSG_ERR_LOAD, XMArgs.Settings, buf);
+               }
+       }
+       else
+       {
+               if (LoadPrefs ("PROGDIR:" PRGNAME ".prefs"))
+                       LoadPrefs ("ENV:" PRGNAME ".prefs");
+       }
+
+
+       /* Use startup Arguments */
+
+       if (XMArgs.PubScreen)
+               strncpy (ScrInfo.PubScreenName, XMArgs.PubScreen, 31);
+
+       if (XMArgs.PortName)
+               strncpy (PubPortName, XMArgs.PortName, 15);
+
+       CxPopup = XMArgs.CxPopup;
+
+       if (XMArgs.CxPopKey)
+               strncpy (CxPopKey, XMArgs.CxPopKey, 31);
+
+       if (XMArgs.CxPriority)
+               CxPri = *XMArgs.CxPriority;
+
+       if (XMArgs.IconX)
+               IconX = *XMArgs.IconX;
+
+       if (XMArgs.IconY)
+               IconY = *XMArgs.IconY;
+
+       if (XMArgs.IconName)
+               strncpy (IconName, XMArgs.IconName, 15);
+
+       /* Setup FileRequesters if LoadPrefs() hasn't already done it */
+       if (!AslBase && !ReqToolsBase)
+               if (err = SetupRequesters())
+                       return err;
+
+       /* Setup App Message Port */
+       SetupApp();
+
+       /* Setup Rexx Host */
+       CreateRexxPort();
+
+       /* Setup Commodity object */
+       SetupCx();
+
+       /* Add internal hooks and load external ones */
+       SetupHooks();
+
+       /* Allocate a new SongInfo structure */
+       if (!XModuleBase->xm_CurrentSong)
+       {
+               struct SongInfo *si;
+
+               if (si = xmCreateSong (
+                       SNGA_ReadyToUse,        TRUE,
+                       XMSNG_AddToList,        -1,
+                       XMSNG_Active,           TRUE,
+                       TAG_DONE))
+                       ReleaseSemaphore (&si->Lock);
+               else
+                       return ERROR_NO_FREE_STORE;
+       }
+
+       /* Open screen and ToolBox window */
+       if (CxPopup)
+               if (err = SetupScreen())
+                       return err;
+
+
+       /* Load modules requested with Shell/Workbench startup arguments */
+
+       HandleFrom();
+
+       DisposeArgs();
+
+       return 0;
+}
+
+
+
+static void Cleanup (LONG err)
+
+/* Cleanup routine.  Display error message, free all resources & exit */
+{
+       ULONG i;
+
+       if (err > 100)
+               PrintFault (err, PrgName);
+
+       /* Free all allocated resources */
+
+       CleanupAudio();
+
+       DisposeArgs();  /* Just to be sure */
+
+       FreeFReq();
+       CloseDownScreen();
+
+       if (Pool)
+       {
+               FreeVecPooled (Pool, ScreenAttr.ta_Name);
+               FreeVecPooled (Pool, WindowAttr.ta_Name);
+               FreeVecPooled (Pool, ListAttr.ta_Name);
+               FreeVecPooled (Pool, EditorAttr.ta_Name);
+       }
+
+       /* Dispose XModule library */
+       DisposeXModuleLibrary ();
+
+       /* Remove AppIcons/AppWindows Port */
+       CleanupApp();
+
+       /* Remove Commodity Broker */
+       CleanupCx();
+
+       /* Remove ARexx port */
+       DeleteRexxPort();
+
+       CleanupLocale();
+
+       /* Close all libraries */
+       for (i = 0 ; openlibs[i].Base ; i++)
+               /* starting with V36, NULL is a valid parameter for CloseLibrary(). */
+               CloseLibrary (*(openlibs[i].Base));
+}
+
+
+
+/*!*/
+/* Emergency patch: */
+
+LONG SampleWinTags[1] = { 0 };
+void UpdateSampleMenu  (void) {}
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..7ccc92e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,394 @@
+##
+##     $VER: XModule_Makefile 4.1 (15.8.98)
+##
+##     Copyright (C) 1993,94,95,96,97,98 by Bernardo Innocenti
+##
+##     This Makefile must be processed with GNU make 3.76.1
+##     Use 4 chars wide TABs to read this file
+##
+##     Please see below to know what developement tools you need to
+##     install in order to build XModule and all related files.
+##
+##
+##     Relevant build targets:
+##
+##             make all                - Make all binaries. This is the default
+##             make release    - Make distribution archives
+##             make bumprev    - Bump the revision number of XModule
+##             make newctfiles - Make new .ct files for catalog translators
+##             make clean              - Removes all objects and temporary files
+
+include config.mk
+
+###########################################################
+# Tools used to make this project:
+###########################################################
+#
+# SAS/C 6.58        is a commercial product. Updates are available on Aminet
+# PhxAss 4.38       is on Aminet in the archive "dev/asm/PhxAss438.lha"
+# PhxLnk 4.31       is on Aminet in the archive "dev/asm/PhxLnk431.lha"
+# FlexCat 2.1       is on Aminet in the archive "dev/misc/FlexCat.lha"
+# FD2Pragma 2.52    is on Aminet in the archive "dev/misc/FD2Pragma.lha"
+# ADtoHT 2.1        is on Aminet in the archive "text/hyper/ADtoHT2_1.lha"
+# RevUp 1.4         is on Aminet in the archive "dev/misc/RevUp1_4.lha"
+# GNU Make 3.76.1   is on Aminet in the archive "dev/c/make_bin.lha"
+# GNU MakeInfo 1.64 is part of the GeekGadgets project
+# scompare          is part of the SAS/C Developement System and is not freely distributable
+# autodoc           is in the 3.1 NDTK or in the Amiga Developer CD 1.2
+#
+# 3.1 Includes      is in the 3.1 NDTK or in the Amiga Developer CD 1.2
+# XPK 2.5 DTK       is on Aminet in the archive "util/pack/×pk25dev.lha"
+# ReqTools DTK      is on Aminet in the archive "util/libs/ReqToolDev.lha"
+#
+#
+# My ethernal gratitude to the clever authors of these useful tools!
+#
+
+###########################################################
+# Additional configuration
+###########################################################
+#
+# Subdirectories to work in
+#
+SUBDIRS := Hooks gadgets manuals
+
+
+###########################################################
+# Source files to compile for the XModule executible
+###########################################################
+#
+#
+
+SRCS := Main.c Misc.c Rexx.c Gui.c Requesters.c Prefs.c Locale.c \
+ Audio.c Compress.c App.c Cx.c Help.c Operators.c Instr.c \
+ SongClass.c Library.c XModuleHook.c TrackerHook.c CustomClasses.c \
+ ToolBoxWin.c OptimizationWin.c ClearWin.c InstrumentsWin.c \
+ SongInfoWin.c SequenceWin.c ProgressWin.c SaversWin.c \
+ PlayWin.c PrefsWin.c PatternWin.c PattPrefsWin.c
+
+# List the objects from the source files
+OBJS := $(SRCS:%.c=$(OBJDIR)/%.o)
+
+
+# NOTE: SampleWin.o removed because still not updated to new GUI system
+
+
+
+###########################################################
+# Targets to make for normal builds. This is the default
+###########################################################
+#
+# NOTE: the GST file is put here to have it remade when needed.
+#       the source files do not depend on $(GST) because otherwise
+#       make would rebuild all them when the GST is rebuild.
+
+all: $(GST) $(PROJNAME) Players/32Channels.player subdirs
+
+final: $(GST) $(PROJNAME)_final Players/32Channels.player subdirs
+
+subdirs:
+       @$(foreach subdir, $(SUBDIRS), $(MAKE) -w $(MAKEFLAGS) -C $(subdir) @@)
+
+
+
+###########################################################
+# Patch files for special binary versions
+###########################################################
+
+patches:                                                               \
+ Patches/XModule_020.pch                               \
+ Patches/XModule_020_OS30.pch                  \
+ Patches/XModule.guide_39.pch                  \
+ Patches/XModule.guide_40.pch                  \
+ Patches/XModule.doc.pch                               \
+ Patches/pattedit.gadget_020.pch               \
+ Patches/pattedit.gadget_020_OS30.pch
+
+
+###########################################################
+# Files to make for the distribution
+###########################################################
+
+release: all patches catalogs autodocs
+
+catalogs:
+       @$(MAKE) -w $(MAKEFLAGS) -C catalogs
+
+
+###########################################################
+# Make the Global Symbol Table used to speed up SAS/C
+###########################################################
+
+ifneq ($(strip $(GST)),)
+
+$(GST): XModulePriv.h Gui.h LocaleStrings.h Gst.c \
+ include/libraries/xmodule.h include/libraries/xmoduleclass.h \
+ include/libraries/songclass.h include/libraries/patteditclass.h \
+ include/pragmas/xmodule_pragmas.h include/clib/xmodule_protos.h
+       $(CC) FROM Gst.c MakeGst=$(GST) NOOBJNAME $(CFLAGS)
+
+endif
+
+###########################################################
+# Make the main XModule executable
+###########################################################
+#
+# Generic rule for making all the objects
+# NOTE: the `:' path separator must be escaped to avoid confusing GNU make
+#
+$(OBJS): $(subst :,\:,$(OBJDIR))/%.o: %.c LocaleStrings.h
+       $(CC) $< $(CC_OUT) $@ $(CFLAGS) $(MORE_CFLAGS)
+
+$(OBJDIR)/Startup.o: Startup.asm
+       $(AS) Startup.asm TO $@ $(ASFLAGS) $(MORE_ASFLAGS) SET "STKSIZE=$(STACKSIZE)"
+
+# NOTE: the following rule works around the command line length limit of the
+#       Amiga native shell by striping away the path component from $(OBJECTS)
+$(PROJNAME): $(OBJDIR)/Startup.o $(OBJS)
+       cd $(OBJDIR) @@\
+       $(LD) FROM $(OBJDIR)/Startup.o $(OBJS:$(OBJDIR)/%=%) TO $@ $(LDFLAGS) $(LIBS) @@\
+       move $@ $(TOP)/$@
+
+# EXPERIMENTAL: to get better code optimization, we compile all the source
+# code modules at once, by including all them in a single dummy source file.
+#
+$(PROJNAME)_final: $(OBJDIR)/Startup.o $(PROJNAME)_final.o
+       $(LD) FROM $(OBJDIR)/Startup.o $(OBJDIR)/$(PROJNAME)_final.o TO $@ $(LDFLAGS) $(LIBS)
+
+$(PROJNAME)_final.o: $(PROJNAME)_final.c
+       $(CC) $(PROJNAME)_final.c $(CC_OUT) $(OBJDIR)/$(PROJNAME)_final.o $(CFLAGS) $(MORE_CFLAGS) \
+               DEF SINGLE_OBJECT
+
+$(PROJNAME)_final.c: $(SRCS)
+       echo "/** This source file is automatically generated - do not edit **/" >$(PROJNAME)_final.c
+       list $(SRCS) LFORMAT "#include *"%p%n*"" >>$(PROJNAME)_final.c
+
+
+###########################################################
+# Make other executable flavors
+###########################################################
+
+XModule_020: XModule
+       $(MAKE) -u "MORE_CFLAGS=CPU 68020" "MORE_ASFLAGS=MACHINE 68020" XModule
+       Move XModule XModule_020
+       $(MAKE) clean
+
+XModule_OS30: XModule
+       $(MAKE) -u "MORE_CFLAGS=DEF OS30_ONLY" "MORE_ASFLAGS=SET OS30_ONLY" $(GST) XModule
+       Move XModule XModule_OS30
+       $(MAKE) clean
+
+XModule_020_OS30: XModule
+       $(MAKE) -u "MORE_CFLAGS=CPU 68020 DEF OS30_ONLY" \
+        "MORE_ASFLAGS=SET OS30_ONLY MACHINE 68020" $(GST) XModule
+       Move XModule XModule_020_OS30
+       $(MAKE) clean
+
+
+
+
+###########################################################
+# Make 32Channels.player
+###########################################################
+
+Player.o: Player.asm
+       $(AS) Player.asm $(ASFLAGS) $(MORE_ASFLAGS)
+
+Players/32Channels.player: Player.o
+       $(LD) Player.o TO Players/32Channels.player LIB:Small.lib NODEBUG SMALLCODE SMALLDATA
+
+
+
+###########################################################
+# Locale related stuff
+###########################################################
+#
+
+LocaleStrings.h Locale.c: LocaleStrings_h.sd Locale_c.sd Catalogs/$(PROJNAME).cd
+       $(FLEXCAT) Catalogs/$(PROJNAME).cd LocaleStrings.h=LocaleStrings_h.sd Locale.c=Locale_c.sd
+
+# Make NewCTFiles to create updated CT files for all supported languages
+
+NewCTFiles:
+       @$(MAKE) -w $(MAKEFLAGS) -C catalogs NewCTFiles
+
+
+
+
+###########################################################
+# Make xmodule_pragmas.h
+###########################################################
+#
+# Do not use SAS/C's fd2pragma! What you need here is FD2Pragma 2.52 or newer.
+# It can be found on Aminet in the archive "dev/misc/FD2Pragma.lha".
+#
+
+include/pragmas/xmodule_pragmas.h: xmodule_lib.fd
+       $(FD2PRAGMA) FDFILE xmodule_lib.fd SPECIAL 6 TO include/pragmas/xmodule_pragmas.h
+       $(FD2PRAGMA) FDFILE xmodule_lib.fd SPECIAL 10 TO include/libraries/xmodule_lib.i
+       $(FD2PRAGMA) FDFILE xmodule_lib.fd SPECIAL 18 TO include/proto/xmodule.h
+
+
+
+###########################################################
+# Make AutoDocs
+###########################################################
+#
+# autodoc is a standard Commodore developer tool.  You can
+# find it in the 3.1 NDTK.
+#
+# ADtoHT 2.1 is an AutoDoc to AmigaGuide file conversion
+# utility made by Christian Stieber.  It can be found on
+# Aminet in the directory "text/hyper/".
+#
+
+autodocs: Autodocs/xmodule.doc Autodocs/songclass.doc
+
+Autodocs/xmodule.doc Autodocs/songclass.doc: Library.c SongClass.c
+       $(CP) include/libraries/xmodule.h include/libraries/songclass.h Autodocs/
+       autodoc -t4 -C -I -c Library.c >Autodocs/xmodule.doc
+       autodoc -t4 -C -I -c SongClass.c >Autodocs/songclass.doc
+       SC:Doc/ADtoHT Autodocs/ RAM: Autodocs/ Autodocs/ VERSION 39
+       $(RM) Autodocs/xmodule.h
+       $(RM) Autodocs/songclass.h
+
+
+###########################################################
+# Bump the revision
+###########################################################
+#
+# RevUp is a developer tool by Boris Folgmann which is similar to BumpRev.
+# You can find it on Aminet in the archive "dev/misc/RevUp1_4.lha".
+#
+
+bumprev:
+       RevUp 3 XModule NOASM EXTRA TINY
+
+
+###########################################################
+# Update dependencies
+###########################################################
+#
+# EXPERIMENTAL - Do not use
+#
+
+depend:
+       gcc -Iinclude: -Iinclude -MM *.c >depend
+
+# other handy gcc options:
+# -O2 -fomit-frame-pointer -fstrength-reduce -finline-functions -funroll-loops
+# -W -Wunused -Wuninitialized -Wparentheses Wreturn-type -Wshadow -Wcast-align
+# -Wstrict-prototypes
+
+###########################################################
+# Cleanup all mess
+###########################################################
+
+clean:
+       -$(RM) *.pch *.map *.gst *.xref XModule XModule.doc \
+       Locale.c LocaleStrings.h Players/*.player Patches/*.pch $(PROJNAME)_final.c depend
+       -$(RM) $(OBJDIR)/*.o
+       @$(MAKE) -w $(MAKEFLAGS) -C Hooks clean
+       @$(MAKE) -w $(MAKEFLAGS) -C gadgets clean
+       @$(MAKE) -w $(MAKEFLAGS) -C catalogs clean
+
+
+
+##################################################################################
+# Make patches for special executables optimized for different CPU and OS version
+##################################################################################
+
+Patches/XModule_020.pch: XModule_020
+       scompare -o$@ XModule XModule_020
+
+Patches/XModule_020_OS30.pch: XModule_020_OS30
+       scompare -o$@ XModule XModule_020_OS30
+
+Patches/pattedit.gadget_020.pch: Gadgets/pattedit.gadget_020
+       scompare -o$@ Gadgets/pattedit.gadget Gadgets/pattedit.gadget_020
+
+Patches/pattedit.gadget_020_OS30.pch: Gadgets/pattedit.gadget_020_OS30
+       scompare -o$@ Gadgets/pattedit.gadget Gadgets/pattedit.gadget_020_OS30
+
+Patches/XModule.guide_39.pch: manuals/XModule.guide_39
+       scompare -o$@ manuals/XModule.guide_34 manuals/XModule.guide_39
+
+Patches/XModule.guide_40.pch: manuals/XModule.guide_40
+       scompare -o$@ manuals/XModule.guide_34 manuals/XModule.guide_40
+
+Patches/XModule.doc.pch: manuals/XModule.txt
+       scompare -o$@ manuals/XModule.doc manuals/XModule.txt
+
+
+
+###########################################################
+# Make the distribution archives
+###########################################################
+#
+# Note: We can't use the `#?' wildcard because the `#' character
+#       is also used to start a comment in a Makefile. This script
+#       will work only when the dos wildstar flag is set.
+#
+
+release: distribution_archive source_archive
+
+distribution_archive:
+       Execute <<
+               If NOT EXISTS $(DISTPREFIX)/XModule
+                       MakeDir $(DISTPREFIX)/XModule
+               EndIf
+
+               $(CP) XModule $(DISTPREFIX)/XModule/
+               $(CP) manuals/XModule.doc manuals/HISTORY manuals/README $(DISTPREFIX)/XModule/
+               $(CP) manuals/XModule.guide_34 $(DISTPREFIX)/XModule/XModule.guide
+               $(CP) Players/* $(DISTPREFIX)/XModule/Players/
+               $(CP) Hooks/*.xmhook $(DISTPREFIX)/XModule/Hooks/
+               $(CP) Gadgets/pattedit.gadget $(DISTPREFIX)/XModule/Gadgets/
+               $(CP) Patches/*.pch $(DISTPREFIX)/XModule/Patches/
+
+               $(RM) ALL $(DISTPREFIX)/XModule/Catalogs/**
+               $(CP) ALL Catalogs $(DISTPREFIX)/XModule/Catalogs/
+
+
+               CD $(DISTPREFIX)
+               If EXISTS $(ARCNAME)
+                       $(RM) $(ARCNAME)
+               EndIf
+               $(ARCHIVER) $(ARCNAME) XModule/** XModule.info
+       <
+
+source_archive:
+       Execute <<
+               If EXISTS $(SRCARCNAME)
+                       $(RM) $(SRCARCNAME)
+               EndIf
+
+               $(RM) ALL $(SRCDISTDIR)/XModuleSource/**
+
+               $(CP) *.c $(DISTPREFIX)/XModuleSource/
+               $(CP) *.h $(DISTPREFIX)/XModuleSource/
+               $(CP) *.asm TO $(DISTPREFIX)/XModuleSource/
+               $(CP) ALL Include $(DISTPREFIX)/XModuleSource/Include
+               $(CP) XModule.texi MakeFile XModule_rev.rev LocaleStrings_h.sd Locale_c.sd TODO SCOPTIONS $(DISTPREFIX)/XModuleSource/
+
+               $(CP) FakePlayer.asm $(DISTPREFIX)/XModuleSource/Player.asm
+               $(RM) $(DISTPREFIX)XModuleSource/FakePlayer.asm
+
+               If EXISTS $(SRCARCNAME)
+                       $(RM) $(SRCARCNAME)
+               EndIf
+               $(ARCHIVER) $(SRCARCNAME) include
+               CD $(DISTPREFIX)
+               $(ARCHIVER) $(SRCARCNAME) XModuleSource XModuleSource.info
+       <
+
+
+
+###########################################################
+# Phony targets (targets which arn't really files to make)
+###########################################################
+
+.PHONY: all patches release autodocs distribution_archive source_archive \
+       NewCTFiles bumprev depend clean subdirs $(SUBDIRS)
+
+#end
diff --git a/Misc.c b/Misc.c
new file mode 100644 (file)
index 0000000..42f929d
--- /dev/null
+++ b/Misc.c
@@ -0,0 +1,582 @@
+/*
+**     Misc.c
+**
+**     Copyright (C) 1993,94,95,96 Bernardo Innocenti
+**
+**     Parts of this file are:
+**
+**     Copyright © 1990-1993 by Olaf `Olsen' Barthel & MXM
+**             All Rights Reserved
+**
+**     Miscellaneus useful functions
+*/
+
+#include <exec/ports.h>
+#include <workbench/startup.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/icon.h>
+#include <proto/graphics.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+#if 0 /* --- This part has been removed --- */
+
+/* Function pointers to Kickstart-sensitive RastPort query code */
+ULONG (*ReadAPen)(struct RastPort *RPort);
+ULONG (*ReadBPen)(struct RastPort *RPort);
+ULONG (*ReadDrMd)(struct RastPort *RPort);
+
+
+
+/* Local function prototypes */
+static ULONG OldGetAPen (struct RastPort *RPort);
+static ULONG OldGetBPen (struct RastPort *RPort);
+static ULONG OldGetDrMd (struct RastPort *RPort);
+static ULONG NewGetAPen (struct RastPort *RPort);
+static ULONG NewGetBPen (struct RastPort *RPort);
+static ULONG NewGetDrMd (struct RastPort *RPort);
+
+#endif /* --- This part has been removed --- */
+
+
+
+GLOBALCALL struct DiskObject *GetProgramIcon (void)
+
+/* Get program associated icon.
+ * This function will fail if we are not a son of Workbench.
+ * The returned DiskObject must be freed by FreeDiskObject().
+ */
+{
+       struct DiskObject *dobj;
+       BPTR olddir;
+
+       if (!WBenchMsg) return NULL;
+
+       olddir = CurrentDir (WBenchMsg->sm_ArgList->wa_Lock);
+       dobj = GetDiskObject (WBenchMsg->sm_ArgList->wa_Name);
+       CurrentDir (olddir);
+
+       return dobj;
+}
+
+
+
+GLOBALCALL struct Library *MyOpenLibrary (CONST_STRPTR name, ULONG ver)
+{
+       struct Library *lib;
+       BPTR progdir = NULL;
+
+       while (!(lib = OpenLibrary (name, ver)))
+       {
+               UBYTE path[PATHNAME_MAX];
+
+
+               /* Search the library file in "PROGDIR:"... */
+
+               strcpy (path, "PROGDIR:");
+               AddPart (path, name, PATHNAME_MAX);
+               if (lib = OpenLibrary (path, ver))
+                       break;
+
+               strcpy (path, "PROGDIR:Libs");
+               AddPart (path, name, PATHNAME_MAX);
+               if (lib = OpenLibrary (path, ver))
+                       break;
+
+               /* Tell the user we can't find this library anywhere... */
+               if (!ShowRequest (MSG_OPENLIB_FAIL, MSG_RETRY_OR_CANCEL, name, ver))
+                       break;
+       }
+
+       if (progdir) UnLock (progdir);
+
+       return lib;
+}
+
+
+
+GLOBALCALL void CantOpenLib (CONST_STRPTR name, LONG ver)
+
+/* Notify the user that a library didn't open */
+{
+       if (ver)
+               ShowRequest (MSG_OPENLIB_VER_FAIL, MSG_CONTINUE, name, ver);
+       else
+               ShowRequest (MSG_OPENLIB_FAIL, MSG_CONTINUE, name);
+}
+
+
+
+GLOBALCALL void KillMsgPort (struct MsgPort *mp)
+
+/* Reply all pending messages and call DeletePort() */
+{
+       struct Message *msg;
+
+       Forbid();       /* is this really useful? */
+
+       /* Reply all pending Messages */
+       while (msg = GetMsg (mp))
+               ReplyMsg (msg);
+
+       DeleteMsgPort (mp);
+
+       Permit();
+}
+
+
+
+GLOBALCALL struct TextAttr *CopyTextAttrPooled (void *pool, const struct TextAttr *source, struct TextAttr *dest)
+
+/* Copy <source> textattr structure over <dest>, allocating and copying
+ * the ta_Name field.  <dest>->ta_Name if FreeVec()ed before
+ * allocating the new one.
+ *
+ * Returns: <dest> if everything was ok, NULL for failure.
+ */
+
+{
+       FreeVecPooled (pool, dest->ta_Name);
+
+       memcpy (dest, source, sizeof (struct TextAttr));
+
+       if (dest->ta_Name = AllocVecPooled (pool, strlen (source->ta_Name) + 1))
+       {
+               strcpy (dest->ta_Name, source->ta_Name);
+               return dest;
+       }
+       return NULL;
+}
+
+
+
+GLOBALCALL UWORD CmpTextAttr (const struct TextAttr *ta1, const struct TextAttr *ta2)
+
+/* Compares two TextAttr structures and returns 0 if they refer to
+ * the same font, a non-zero value otherwise.
+ */
+{
+       if (ta1->ta_YSize == ta2->ta_YSize && ta1->ta_Style == ta2->ta_Style)
+       {
+               if (!ta1->ta_Name && !ta2->ta_Name)
+                               return 0;
+
+               if (!ta1->ta_Name || !ta2->ta_Name)
+                               return 1;
+
+               if (!strcmp (ta1->ta_Name, ta2->ta_Name))
+                       return 0;
+       }
+
+       return 1;
+}
+
+
+
+/* More memory pools support */
+
+#ifdef PORTABLE
+
+GLOBCALL void *AllocVecPooled (void *pool, ULONG size)
+{
+    void *mem;
+
+       size += 4;
+
+#ifdef OS30_ONLY
+       if (mem = AllocPooled (pool, size))
+#else
+       if (mem = AsmAllocPooled (pool, size))
+#endif
+               *((ULONG *)mem)++ = size;
+
+    return mem;
+}
+
+
+
+GLOBCALL void FreeVecPooled (void *pool, void *memory)
+{
+    if (mem)
+    {
+               --(((ULONG *)mem);
+#ifdef OS30_ONLY
+               FreePooled (pool, mem, *((ULONG *)mem));
+#else
+               AsmFreePooled (pool, mem, *((ULONG *)mem));
+#endif
+    }
+}
+
+
+
+GLOBCALL void *CAllocVecPooled (void *pool, ULONG size)
+
+{
+    void *mem;
+
+       size += 4;
+
+#ifdef OS30_ONLY
+       if (mem = AllocPooled (pool, size))
+#else
+       if (mem = AsmAllocPooled (pool, size))
+#endif
+       {
+               memset (mem, size, 0);
+               *((ULONG *)mem)++ = size;
+       }
+
+    return mem;
+}
+
+#endif /* PORTABLE */
+
+
+
+GLOBALCALL STRPTR DupStringPooled (void *pool, CONST_STRPTR source, STRPTR *dest)
+
+/* Allocates a buffer big enough to fit the <source> string
+ * using AllocVecPooled() and then copies the contents of <source>
+ * there.  If the buffer pointed by <dest> is not NULL,
+ * DupStringPooled() will first check to see if <source> and
+ * <dest> are identical, in which case no new buffer will be
+ * allocated and the result will be FALSE.  Otherwise, <dest> will
+ * be deallocated and a new buffer will be allocated. Both <source> and <*dest>
+ * can be NULL.
+ *
+ * RESULT
+ *     Non-zero if <dest> has been reallocated, FALSE otherwise.
+ */
+{
+       if (*dest)
+       {
+               if (source)
+               {
+                       if (!(strcmp (*dest, source)))
+                               return NULL;
+               }
+
+               FreeVecPooled (pool, *dest);
+       }
+
+       if (source)
+       {
+               if (*dest = AllocVecPooled (pool, strlen (source) + 1))
+                       strcpy (*dest, source);
+       }
+
+       return *dest;
+}
+
+
+
+GLOBALCALL void FilterName (STRPTR name)
+
+/* Finds and blanks out invalid characters in a string.
+ * Will also strip blanks at the end.  Passing NULL is safe.
+ */
+{
+       UWORD i = 0;
+
+       if (!name) return;
+
+       while (name[i])
+       {
+               if (name[i] < ' ' ||
+                       (name[i] >'~' && name[i] < '¡'))
+                       name[i] = ' ';
+
+               i++;
+       }
+
+       /* Kill blanks at the end of the string */
+       for (--i; i > 0 ; i--)
+               if (name[i] == ' ') name[i] = '\0';
+               else break;
+}
+
+
+
+GLOBALCALL LONG PutIcon (CONST_STRPTR source, CONST_STRPTR dest)
+
+/* Add the <source> icon to <dest> file */
+{
+       struct DiskObject *dobj;
+       UBYTE buf[PATHNAME_MAX];
+
+       /* We do not alter existing icons */
+       if (dobj = GetDiskObject (dest))
+       {
+               FreeDiskObject (dobj);
+               return RETURN_WARN;
+       }
+
+       /* Get source icon */
+
+       strcpy (buf, "PROGDIR:Icons");
+       AddPart (buf, source, PATHNAME_MAX);
+       if (!(dobj = GetDiskObject (buf)))
+       {
+               strcpy (buf, "ENV:Sys");
+               AddPart (buf, source, PATHNAME_MAX);
+               if (!(dobj = GetDiskObject (buf)))
+               {
+                       /* Get default project icon */
+                       dobj = GetDefDiskObject (WBPROJECT);
+               }
+       }
+
+       if (dobj)
+       {
+               dobj->do_CurrentX = dobj->do_CurrentY = NO_ICON_POSITION;
+
+               if (!(dobj->do_DefaultTool[0]))
+               {
+                       /* Get program path and store in icon's Default Tool */
+
+                       BPTR progdir;
+
+                       dobj->do_DefaultTool = NULL;
+
+                       if (WBenchMsg)  /* WB */
+                               progdir = WBenchMsg->sm_ArgList->wa_Lock;
+                       else                    /* CLI */
+                               progdir = GetProgramDir();
+
+                       if (progdir)
+                       {
+                               if (NameFromLock (progdir, buf, PATHNAME_MAX))
+                               {
+                                       UBYTE progname[32];
+
+                                       if (WBenchMsg)  /* WB */
+                                               strncpy (progname, WBenchMsg->sm_ArgList->wa_Name, 32);
+                                       else                    /* CLI*/
+                                               GetProgramName (progname, 32);
+
+                                       if(AddPart (buf, progname, PATHNAME_MAX))
+                                               dobj->do_DefaultTool = buf;
+                               }
+                       }
+               }
+
+               if (!dobj->do_DefaultTool) dobj->do_DefaultTool = BaseName;
+               PutDiskObject (dest, dobj);
+               FreeDiskObject (dobj);
+               return RETURN_OK;
+       }
+
+       return RETURN_FAIL;
+}
+
+
+
+static ULONG MakeBackupName (STRPTR buf, CONST_STRPTR s, CONST_STRPTR tmpl, ULONG n)
+
+/* DESCRIPTION
+ *     Subfunction for BackupFile() - Constructs a file name for the backup.
+ *     <tmpl> is the template to build the backup name.
+ *     Each occurence of the '#' character in the template is replaced with
+ *     an ASCII decimal representation of <n>. The '*' character is replaced
+ *     with the original filename <s>.
+ *
+ * RESULT
+ *     The result is stored in <buf>. Returns TRUE if at least one occurrence
+ *     of the '#' wildcard has been replaced, otherwise returns FALSE.
+ *
+ * BUGS
+ *     Does not check for buffer overflow.
+ *
+ */
+{
+       BOOL wild_done = FALSE;
+       BOOL num_done = FALSE;
+
+       do
+       {
+               if ((*tmpl == '*') && (!wild_done))
+               {
+                       /* Copy file name */
+
+                       s = FilePart (s);
+                       while (*buf++ = *s++);
+                       --buf;
+
+                       wild_done = TRUE;
+               }
+               else if (*tmpl == '#')
+               {
+                       /* Insert backup number */
+
+                       if (n > 9)
+                               *buf++ = (n / 10) + '0';
+                       *buf++ = (n % 10) + '0';
+
+                       num_done = TRUE;
+               }
+               else if (*tmpl == '\\')
+                       /* Handle escape character */
+                       *buf++ = *++tmpl;
+               else
+                       *buf++ = *tmpl;
+       }
+       while (*tmpl++);
+
+       return num_done;
+}
+
+
+
+GLOBALCALL LONG BackupFile (CONST_STRPTR src, CONST_STRPTR template, ULONG versions)
+
+/* Creates backups of the given file */
+{
+       UBYTE buf[PATHNAME_MAX], oldbuf[PATHNAME_MAX];
+       ULONG i;
+       BPTR lock, dir, olddir;
+
+       if (!(lock = Lock (src, ACCESS_READ)))
+               return IoErr();
+
+       /* CD to source directory: needed for relative paths */
+       dir = ParentDir (lock);
+       olddir = CurrentDir (dir);
+
+
+       if (!MakeBackupName (buf, src, template, 1))
+       {
+               /* Simple backup */
+               DeleteFile (buf);
+       }
+       else
+       {
+               /* Delete oldest backup if exists */
+
+               MakeBackupName (buf, src, template, versions);
+               DeleteFile (buf);
+
+               /* Shift all others ahead... */
+
+               for (i = versions; i > 0; --i)
+               {
+                       strcpy (oldbuf, buf);
+                       MakeBackupName (buf, src, template, i);
+                       Rename (buf, oldbuf);
+
+                       DB (kprintf ("Backup name is: %s\n", buf));
+               }
+       }
+
+       /* And finally move file to its backup location */
+       Rename (src, buf);
+
+       /* Restore old current directory */
+       CurrentDir (olddir);
+       UnLock (dir);
+       UnLock (lock);
+
+       return RETURN_OK;
+}
+
+
+
+#if 0 /* --- This part has been removed --- */
+
+GLOBALCALL void InstallGfxFunctions (void)
+
+/* Install the correct routines to query
+ * the rendering colours and drawing mode.
+ */
+{
+       if(GfxBase->lib_Version >= 39)
+       {
+               ReadAPen = NewGetAPen;
+               ReadBPen = NewGetBPen;
+               ReadDrMd = NewGetDrMd;
+       }
+       else
+       {
+               ReadAPen = OldGetAPen;
+               ReadBPen = OldGetBPen;
+               ReadDrMd = OldGetDrMd;
+       }
+}
+
+
+
+/* OldGetAPen(struct RastPort *RPort):
+ *
+ *     Query the current primary rendering colour (old style).
+ */
+
+static ULONG OldGetAPen (struct RastPort *RPort)
+{
+       return((ULONG)RPort->FgPen);
+}
+
+
+
+/* OldGetBPen(struct RastPort *RPort):
+ *
+ *     Query the current seconary rendering colour (old style).
+ */
+
+static ULONG OldGetBPen (struct RastPort *RPort)
+{
+       return((ULONG)RPort->BgPen);
+}
+
+
+
+/* OldGetDrMd(struct RastPort *RPort):
+ *
+ *     Query the current drawing mode (old style).
+ */
+
+static ULONG OldGetDrMd (struct RastPort *RPort)
+{
+       return((ULONG)RPort->DrawMode);
+}
+
+
+
+/* NewGetAPen(struct RastPort *RPort):
+ *
+ *     Query the current primary rendering colour (new style).
+ */
+
+static ULONG NewGetAPen (struct RastPort *RPort)
+{
+       return(GetAPen (RPort));
+}
+
+
+
+/* NewGetBPen(struct RastPort *RPort):
+ *
+ *     Query the current seconary rendering colour (new style).
+ */
+
+static ULONG NewGetBPen (struct RastPort *RPort)
+{
+       return (GetBPen (RPort));
+}
+
+
+
+/* NewGetDrMd(struct RastPort *RPort):
+ *
+ *     Query the current drawing mode (new style).
+ */
+
+static ULONG NewGetDrMd (struct RastPort *RPort)
+{
+       return(GetDrMd (RPort));
+}
+
+#endif /* --- This part has been removed --- */
diff --git a/Operators.c b/Operators.c
new file mode 100644 (file)
index 0000000..0129a06
--- /dev/null
@@ -0,0 +1,838 @@
+/*
+**     Operators.c
+**
+**     Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
+**
+**     General pourpose module handling/processing functions.
+*/
+
+#include <exec/memory.h>
+
+#include <proto/exec.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+/* Local function prototypes */
+
+static void    BreakPattern    (struct Note **arr, UWORD rows, UWORD tracks);
+
+
+
+GLOBALCALL ULONG InsertPattern (struct SongInfo *si, struct Pattern *patt, UWORD patnum)
+
+/* Inserts a pattern at any song position.  Patterns >= patnum will be moved
+ * ahead one slot.  The position table is updated inserting references to
+ * the new pattern immediately before each occurence of patnum, so that the
+ * two patterns are always played together.
+ *
+ * RESULT
+ *     0 to mean succes,
+ *     any other value means failure.
+ */
+{
+       ULONG i, k;
+
+
+       if (xmAddPattern (si,
+               PATTA_Num,              patnum,
+               PATTA_Pattern,  patt,
+               TAG_DONE))
+       {
+               xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
+                       (APTR)MSG_CANT_INSERT_PATT, NULL);
+               return 1;
+       }
+
+       /* Adjust position table */
+       for (i = 0 ; i < si->Length ; i++)
+       {
+               /* Song can't grow bigger than MAXPOSITIONS */
+               if (si->Length >= MAXPOSITIONS)
+                       return 2;       /* TODO: better error handling */
+
+               /* Fix pattern numbers */
+               if (si->Sequence[i] > patnum) si->Sequence[i]++;
+
+               /* Insert references to the new pattern in the position table */
+               if (si->Sequence[i] == patnum)
+               {
+                       /* Grow song */
+                       if (xmSetSongLen (si, si->Length + 1))
+                       {
+                               /* Shift subsequent positions ahead 1 slot */
+                               for (k = si->Length - 1; k > i ; k--)
+                                       si->Sequence[k] = si->Sequence[k-1];
+
+                               si->Sequence[i+1] = patnum+1;   /* Restore old pattern */
+
+                               i++;                    /* Avoid processing this pattern again */
+
+                               /* TODO: It would be nice to fix Pattern Jump commands too... */
+                       }
+                       else return 1;
+               }
+       }
+
+       return 0;
+}
+
+
+
+GLOBALCALL void DiscardPatterns (struct SongInfo *si)
+
+/* Discard patterns beyond the last pattern referenced
+ * in the song sequence.
+ */
+{
+       ULONG i, j;
+       UBYTE *used;
+       struct Pattern *patt;
+
+       if (!(used = AllocVecPooled (Pool, si->NumPatterns)))
+               return;
+
+       /* Flag patterns REALLY used in the song */
+       for (i = 0; i < si->Length ; i++)
+               used[si->Sequence[i]]++;
+
+
+       for (i = 0; i < si->NumPatterns; i++)
+       {
+               if (!(patt = si->Patt[i])) continue;
+
+               if (!used[i] && patt)
+               {
+                       xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
+                               (APTR)MSG_PATT_UNUSED, i);
+
+                       xmRemPattern (si, i, 0);
+
+                       /* Shift all subsequent patterns one position back */
+                       for (j = i; j < si->NumPatterns; j++)
+                               used[j] = used[j+1];
+
+                       /* Rearrange Position Table */
+                       for (j = 0; j < si->Length; j++)
+                               if (si->Sequence[j] > i) si->Sequence[j]--;
+
+                       /* TODO: It would be nice to fix Pattern Jump commands too... */
+
+                       i--; /* Process this pattern # again, since it's now another pattern */
+               }
+       }
+
+       FreeVecPooled (Pool, used);
+}
+
+
+
+GLOBALCALL void CutPatterns (struct SongInfo *si)
+
+/* Find out what patterns are breaked (effect D) and resize them */
+{
+       ULONG i, j, k, l;
+       struct Pattern *patt, *newpatt;
+
+       for (i = 0; i < si->NumPatterns; i++)
+       {
+               if (!(patt = si->Patt[i])) continue;
+
+               for (j = 0; j < patt->Lines; j++)
+                       for (k = 0; k < patt->Tracks; k++)
+                               if (patt->Notes[k][j].EffNum == EFF_PATTERNBREAK)
+                               {
+                                       xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
+                                               (APTR)MSG_PATT_CUT, i, j+1);
+
+                                       if (newpatt = xmAddPattern (si,
+                                               PATTA_Name,             patt->Name,
+                                               PATTA_Tracks,   patt->Tracks,
+                                               PATTA_Lines,    j + 1,
+                                               PATTA_Num,              -1,                     /* Do not add it */
+                                               TAG_DONE))
+                                       {
+                                               /* Remove break command */
+                                               patt->Notes[k][j].EffNum = 0;
+                                               patt->Notes[k][j].EffVal = 0;
+
+                                               /* Copy notes */
+                                               for (l = 0; l < patt->Tracks; l++)
+                                                       CopyMem (patt->Notes[l], newpatt->Notes[l], sizeof (struct Note) * (j+1));
+
+                                               /* Replace with new pattern */
+                                               xmAddPattern (si,
+                                                       PATTA_Pattern,  patt,
+                                                       PATTA_Num,              i,
+                                                       PATTA_Replace,  TRUE,
+                                                       TAG_DONE);
+
+                                               /* stop the loop on this pattern */
+                                               patt = newpatt;
+                                               j = patt->Lines;
+                                               k = patt->Tracks;
+                                       }
+                               }
+       }
+}
+
+
+
+GLOBALCALL void RemDupPatterns (struct SongInfo *si)
+
+/* Find out identical patterns and cut them out */
+{
+       ULONG i, j, k;
+       struct Pattern *patta, *pattb;
+
+       if (si->NumPatterns < 2) return;
+
+       for (i = 0; i < si->NumPatterns-1; i++)
+       {
+               if (!(patta = si->Patt[i])) continue;
+
+               for (j = i+1; j < si->NumPatterns; j++)
+               {
+                       if (!(pattb = si->Patt[j])) continue;
+
+                       if ((patta->Lines == pattb->Lines) && (patta->Tracks == pattb->Tracks))
+                       {
+                               for (k = 0; k < patta->Tracks; k++)
+                                       if (memcmp (patta->Notes[k], pattb->Notes[k], sizeof (struct Note) * patta->Lines))
+                                               break;
+
+                               if (k == patta->Tracks)
+                               {
+                                       xmRemPattern (si, j, i);
+                                       xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
+                                               (APTR)MSG_PATT_DUPE, i, j);
+                                       j--;
+                               }
+                       }
+               }
+       }
+}
+
+
+
+static void BreakPattern (struct Note **arr, UWORD row, UWORD tracks)
+
+/* Put a break command at the end of a pattern */
+{
+       ULONG i;
+
+       /* Try to find a free effect slot in the row... */
+       for (i = 0 ; i < tracks ; i++)
+               if (arr[i][row].EffNum == 0 && arr[i][row].EffVal == 0)
+                       break;
+
+       if (i == tracks) i = 0;
+
+       arr[i][row].EffNum = EFF_PATTERNBREAK; /* ...and break the pattern */
+       arr[i][row].EffVal = 0;
+}
+
+
+
+GLOBALCALL LONG ResizePatterns (struct SongInfo *si, ULONG min, ULONG max)
+
+/* Find out what patterns are less than <min> and more than <max>
+ * lines long and either grow them or split them in shorter
+ * patterns.  Pattern splitting will automatically recurse when
+ * more than one split is required.
+ *
+ * RESULT
+ *     0 to mean succes, any other value means failure.
+ */
+{
+       struct Pattern  *patt, *newpatt, *newpatt2;
+       ULONG i, j;
+       UWORD len;
+       BOOL do_update = FALSE;
+
+       for (i = 0 ; i < si->NumPatterns ; i++)
+       {
+               if (!(patt = si->Patt[i])) continue;
+
+               if ((len = patt->Lines) < min)
+               {
+                       /* BREAK PATTERN */
+
+                       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_COMMENT,
+                               (APTR)MSG_PATT_WILL_GROW, i, len);
+
+                       if (!(newpatt = xmAddPattern (si,
+                               PATTA_Num,              -1,
+                               PATTA_Lines,    min,
+                               PATTA_Tracks,   patt->Tracks,
+                               TAG_DONE)))
+                               return ERROR_NO_FREE_STORE;
+
+
+                       /* Copy the old notes in the new (longer) pattern */
+                       for (j = 0 ; j < patt->Tracks ; j++)
+                               memcpy (newpatt->Notes[j], patt->Notes[j],
+                                               (sizeof (struct Note)) * len);
+
+                       /* Break the new pattern */
+                       BreakPattern (newpatt->Notes, len-1, newpatt->Tracks);
+
+                       xmAddPattern (si,
+                               PATTA_Num, i,
+                               PATTA_Pattern, newpatt,
+                               PATTA_Replace, TRUE,
+                               TAG_END);
+               }
+               else if (len > max)
+               {
+                       /* SPLIT PATTERN */
+
+                       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_COMMENT,
+                               (APTR)MSG_SPLITTING_PATT, i, len);
+
+                       if (!(newpatt = xmAddPattern (si,
+                               PATTA_Num,              -1,
+                               PATTA_Lines,    max,
+                               PATTA_Tracks,   patt->Tracks,
+                               TAG_DONE)))
+                               return ERROR_NO_FREE_STORE;
+
+
+                       /* If len - max is still above max, this pattern
+                        * will be splitted or breaked once again in the
+                        * next loop.
+                        */
+
+                       if (!(newpatt2 = xmAddPattern (si,
+                               PATTA_Num,              -1,
+                               PATTA_Lines,    len - max,
+                               PATTA_Tracks,   patt->Tracks,
+                               TAG_DONE)))
+                       {
+                               xmRemPattern (si, -1, (ULONG)newpatt);
+                               return ERROR_NO_FREE_STORE;
+                       }
+
+                       /* Copy first <max> rows of the pattern */
+                       for (j = 0 ; j < si->MaxTracks ; j++)
+                               memcpy (newpatt->Notes[j], patt->Notes[j],
+                                               (sizeof (struct Note)) * max);
+
+                       /* Copy the rest of the pattern */
+                       for (j = 0 ; j < si->MaxTracks ; j++)
+                               memcpy (newpatt2->Notes[j], patt->Notes[j] + max,
+                                               (sizeof (struct Note)) * (len - max));
+
+
+                       /* Make room for the new pattern */
+                       if (InsertPattern (si, newpatt, i))
+                       {
+                               xmRemPattern (si, -1, (ULONG)newpatt);
+                               xmRemPattern (si, -1, (ULONG)newpatt2);
+                               continue;
+                       }
+
+                       /* Substitute old pattern */
+                       if (!(xmAddPattern (si,
+                               PATTA_Num,              i + 1,
+                               PATTA_Pattern,  newpatt2,
+                               TAG_DONE)))
+                       {
+                               xmRemPattern (si, -1, (ULONG)newpatt2);
+                               continue;
+                       }
+               }
+       }       /* End for(i) */
+
+       if (do_update)
+               UpdateSongInfo();
+
+       return 0;
+
+}      /* End ResizePatterns */
+
+
+
+GLOBALCALL struct Pattern *CopyPattern (struct SongInfo *si, struct Pattern *src, ULONG destNum)
+
+/* Makes a copy of the notes and attributes of the <src>
+ * pattern to the <dest> pattern.  The destination pattern is
+ * created with xmAddPattern().
+ */
+{
+       struct Pattern *dest;
+       ULONG i;
+
+       if (!src) return NULL;
+
+       if (dest = xmAddPattern (si, destNum,
+               PATTA_Lines, src->Lines,
+               PATTA_Tracks, src->Tracks,
+               PATTA_Name, src->Name))
+       {
+               for (i = 0; i < src->Tracks; i++)
+                       CopyMem (src->Notes[i], dest->Notes[i],
+                               sizeof (struct Note) * src->Lines);
+       }
+
+       return dest;
+}
+
+
+
+GLOBALCALL struct SongInfo *MergeSongs (struct SongInfo *songa, struct SongInfo *songb)
+
+/* Merges <songa> with <songb> in a new song where the notes of the
+ * two sources are played at the same time.
+ */
+{
+       struct SongInfo         *songc;
+       struct Pattern          *patta, *pattb, *pattc;
+       struct Note             *note;
+       struct Instrument       *source;
+       BYTE                            *newsample;
+       LONG                             slen, plen, ntrk, i, j, k;
+
+
+       if (songa->Length != songb->Length)
+               xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
+                       (APTR)MSG_SONG_LEN_DIFFERENT, NULL);
+
+       /* Create new song */
+       {
+               UBYTE newtitle[64], newauthor[64];
+
+               /* Make new title */
+               newtitle[63] = '\0';
+               strncpy (newtitle, songa->Title, 63);
+               strncat (newtitle, " + ", 63);
+               strncat (newtitle, songb->Title, 63);
+
+               /* Make new author */
+
+               newauthor[63] = '\0';
+               if (songa->Author && songa->Author[0])
+                       strncpy (newauthor, songa->Author, 63);
+               if (songb->Author && songb->Author[0])
+               {
+                       if (songa->Author && songa->Author[0])
+                       {
+                               strncat (newauthor, " & ", 63);
+                               strncat (newauthor, songb->Author, 63);
+                       }
+                       else strncpy (newauthor, songb->Author, 63);
+               }
+
+               if (!(songc = xmCreateSong (
+                       SNGA_GlobalSpeed,       songa->GlobalSpeed,
+                       SNGA_GlobalTempo,       songa->GlobalTempo,
+                       SNGA_RestartPos,        songa->RestartPos,
+                       SNGA_Title,                     newtitle,
+                       SNGA_Author,            newauthor,
+                       TAG_DONE)))
+                       return NULL;
+       }
+
+       slen = min (songa->Length, songb->Length);
+       xmSetSongLen (songc, slen);
+
+
+       for (i = 0; i < slen; i++)
+       {
+               UBYTE pattname[64];
+
+               patta = songa->Patt[songa->Sequence[i]];
+               pattb = songb->Patt[songb->Sequence[i]];
+
+               if (patta->Lines != pattb->Lines)
+                       xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                               (APTR)MSG_PATT_LEN_DIFFERENT, i);
+
+               plen = min (patta->Lines, pattb->Lines);
+               ntrk = patta->Tracks + pattb->Tracks;
+
+               if (ntrk > MAXTRACKS)
+               {
+                       xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                               (APTR)MSG_PATT_TOO_MANY_TRACKS, MAXTRACKS);
+
+                       ntrk = MAXTRACKS;
+               }
+
+               /* Set pattern name */
+               if (patta->Name && pattb->Name)
+               {
+                       pattname[63] = '\0';
+                       strncpy (pattname, patta->Name, 63);
+                       strncat (pattname, " + ", 63);
+                       strncat (pattname, pattb->Name, 63);
+               }
+               else
+                       SPrintf (pattname, "%ld + %ld", songa->Sequence[i], songb->Sequence[i]);
+
+               if (!(pattc = xmAddPattern (songc,
+                       PATTA_Lines,    plen,
+                       PATTA_Tracks,   ntrk,
+                       PATTA_Name,             pattname,
+                       TAG_DONE)))
+               {
+                       xmDeleteSong (songc);
+                       LastErr = ERROR_NO_FREE_STORE;
+                       return NULL;
+               }
+
+               songc->Sequence[i] = i;
+
+               /* Copy tracks from source A */
+               for (j = 0; j < patta->Tracks; j++)
+                       CopyMem (patta->Notes[j], pattc->Notes[j], sizeof (struct Note) * plen);
+
+               /* Copy tracks from source B */
+               for (j = 0; j < pattb->Tracks; j++)
+               {
+                       if (j + patta->Tracks >= MAXTRACKS) break;
+
+                       note = pattc->Notes[j + patta->Tracks];
+                       CopyMem (pattb->Notes[j], note, sizeof (struct Note) * plen);
+
+                       for (k = 0; k < plen; k++)
+                               if (note[k].Note)
+                               {
+                                       if ((note[k].Inst + songa->LastInstrument) >= MAXINSTRUMENTS)
+                                       {
+                                               xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
+                                                       (APTR)MSG_ERR_INSTR_OVERFLOW, NULL);
+                                               xmDeleteSong (songc);
+                                               return NULL;
+                                       }
+
+                                       note[k].Inst += songa->LastInstrument - 1;
+                               }
+               }
+       }
+
+
+       /* Copy instruments */
+
+       for (i = 1; i < songa->LastInstrument + songb->LastInstrument; i++)
+       {
+               if (i <= songa->LastInstrument)
+                       source = songa->Instr[i];
+               else
+                       source = songb->Instr[i - songa->LastInstrument + 1];
+
+               if (source)
+               {
+                       if (source->Sample)
+                       {
+                               if (!(newsample = AllocVec (source->Length, MEMF_SAMPLE)))
+                               {
+                                       xmDeleteSong (songc);
+                                       LastErr = ERROR_NO_FREE_STORE;
+                                       return NULL;
+                               }
+                               CopyMem (source->Sample, newsample, source->Length);
+                       }
+                       else
+                               newsample = NULL;
+
+                       if (!xmAddInstrument (songc, i,
+                               INSTRA_Type,            source->Type,
+                               INSTRA_Name,            source->Name,
+                               INSTRA_Volume,          source->Volume,
+                               INSTRA_Sample,          newsample,
+                               INSTRA_Length,          source->Length,
+                               INSTRA_LoopStart,       source->Repeat,
+                               INSTRA_LoopLen,         source->Replen,
+                               INSTRA_FineTune,        source->FineTune,
+                               TAG_DONE))
+                       {
+                               FreeVec (newsample);
+                               xmDeleteSong (songc);
+                               LastErr = ERROR_NO_FREE_STORE;
+                               return NULL;
+                       }
+               }
+       }
+
+       return songc;
+}
+
+
+
+GLOBALCALL struct SongInfo *JoinSongs (struct SongInfo *songa, struct SongInfo *songb)
+
+/* Joins <songa> with <songb> in a new song where the notes of the
+ * two sources are played one after the oder.
+ */
+{
+       struct SongInfo         *songc;
+       struct Pattern          *patta, *pattb, *pattc;
+       struct Note                     *note;
+       struct Instrument       *inst, *source;
+       BYTE                            *newsample;
+       ULONG                            plen, ntrk, i, j, k;
+       UWORD                            songclen;
+
+
+       /* Check maximum values */
+
+       if ((songa->Length + songb->Length) > MAXPOSITIONS)
+       {
+               xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
+                       (APTR)MSG_SONG_TOO_LONG, NULL);
+               songclen = MAXPOSITIONS;
+       }
+       else songclen = songa->Length + songb->Length;
+
+       if ((songa->NumPatterns + songb->NumPatterns) > MAXPATTERNS)
+               xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
+                       (APTR)MSG_SONG_HAS_TOO_MANY_PATT, NULL);
+
+
+       /* Create new song */
+       {
+               UBYTE newtitle[64], newauthor[64];
+
+               /* Make new title */
+               newtitle[63] = '\0';
+               strncpy (newtitle, songa->Title, 63);
+               strncat (newtitle, " & ", 63);
+               strncat (newtitle, songb->Title, 63);
+
+               /* Make new author */
+
+               newauthor[63] = '\0';
+               if (songa->Author && songa->Author[0])
+                       strncpy (newauthor, songa->Author, 63);
+               if (songb->Author && songb->Author[0])
+               {
+                       if (songa->Author && songa->Author[0])
+                       {
+                               strncat (newauthor, " & ", 63);
+                               strncat (newauthor, songb->Author, 63);
+                       }
+                       else strncpy (newauthor, songb->Author, 63);
+               }
+
+               if (!(songc = xmCreateSong (
+                       SNGA_GlobalSpeed,       songa->GlobalSpeed,
+                       SNGA_GlobalTempo,       songa->GlobalTempo,
+                       SNGA_RestartPos,        songa->RestartPos,
+                       SNGA_Title,                     newtitle,
+                       SNGA_Author,            newauthor,
+                       TAG_DONE)))
+                       return NULL;
+       }
+
+       if (xmSetSongLen (songc, songclen))
+       {
+               /* Copy position table of song A */
+               memcpy (songc->Sequence, songa->Sequence, songa->Length * sizeof (UWORD));
+
+               /* Append position table of song B */
+               for (i = 0; i < songb->Length; i++)
+               {
+                       if (i + songa->Length >= songc->Length) break;
+                       songc->Sequence[i + songa->Length] = (songb->Sequence[i] + songa->NumPatterns > MAXPATTERNS) ?
+                               songb->Sequence[i] : (songb->Sequence[i] + songa->NumPatterns);
+               }
+       }
+       else
+       {
+               xmDeleteSong (songc);
+               LastErr = ERROR_NO_FREE_STORE;
+               return NULL;
+       }
+
+
+       /* Copy song A patterns */
+
+       for (i = 0; i < songa->NumPatterns; i++)
+       {
+               patta = songa->Patt[i];
+               plen = patta->Lines;
+               ntrk = patta->Tracks;
+
+               if (!(pattc = xmAddPattern (songc,
+                       PATTA_Lines,    plen,
+                       PATTA_Tracks,   ntrk,
+                       PATTA_Name,             patta->Name,
+                       TAG_DONE)))
+               {
+                       xmDeleteSong (songc);
+                       LastErr = ERROR_NO_FREE_STORE;
+                       return NULL;
+               }
+
+               /* Copy tracks from source A */
+
+               for (j = 0; j < ntrk; j++)
+                       CopyMem (patta->Notes[j], pattc->Notes[j], sizeof (struct Note) * plen);
+       }
+
+
+       /* Append song B patterns */
+
+       for (i = 0; i < songb->NumPatterns; i++)
+       {
+               pattb = songb->Patt[i];
+               plen = pattb->Lines;
+               ntrk = pattb->Tracks;
+
+               if (!(pattc = xmAddPattern (songc,
+                       PATTA_Lines,    plen,
+                       PATTA_Tracks,   ntrk,
+                       PATTA_Name,             pattb->Name,
+                       TAG_DONE)))
+               {
+                       xmDeleteSong (songc);
+                       LastErr = ERROR_NO_FREE_STORE;
+                       return NULL;
+               }
+
+               /* Copy tracks from source B */
+
+               for (j = 0; j < ntrk; j++)
+                       CopyMem (pattb->Notes[j], pattc->Notes[j], sizeof (struct Note) * plen);
+
+               /* Adjust instruments references */
+
+               for (j = 0; j < ntrk; j++)
+                       for (k = 0; k < plen; k++)
+                       {
+                               note = &pattc->Notes[j][k];
+
+                               if (note->Inst)
+                               {
+                                       if ((note->Inst + songa->LastInstrument) >= MAXINSTRUMENTS)
+                                       {
+                                               xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
+                                                       (APTR)MSG_ERR_INSTR_OVERFLOW, NULL);
+                                               xmDeleteSong (songc);
+                                               return NULL;
+                                       }
+
+                                       note->Inst += songa->LastInstrument - 1;
+                               }
+                       }
+       }
+
+
+
+       /* Copy instruments */
+
+       for (i = 1; i < songa->LastInstrument + songb->LastInstrument; i++)
+       {
+               if (i <= songa->LastInstrument)
+                       source = songa->Instr[i];
+               else
+                       source = songb->Instr[i - songa->LastInstrument + 1];
+
+               if (source)
+               {
+                       if (source->Sample)
+                       {
+                               if (!(newsample = AllocVec (source->Length, MEMF_ANY)))
+                               {
+                                       xmDeleteSong (songc);
+                                       LastErr = ERROR_NO_FREE_STORE;
+                                       return NULL;
+                               }
+                               CopyMem (source->Sample, newsample, source->Length);
+                       }
+                       else
+                               newsample = NULL;
+
+                       if (!(inst = xmAddInstrument (songc, i,
+                               INSTRA_Type,            source->Type,
+                               INSTRA_Name,            source->Name,
+                               INSTRA_Volume,          source->Volume,
+                               INSTRA_Sample,          newsample,
+                               INSTRA_Length,          source->Length,
+                               INSTRA_Repeat,          source->Repeat,
+                               INSTRA_Replen,          source->Replen,
+                               INSTRA_FineTune,        source->FineTune,
+                               TAG_DONE)))
+                       {
+                               FreeVec (newsample);
+                               xmDeleteSong (songc);
+                               LastErr = ERROR_NO_FREE_STORE;
+                               return NULL;
+                       }
+               }
+       }
+
+       return songc;
+}
+
+
+
+GLOBALCALL void FixSong (struct SongInfo *si)
+
+/* Fixes dangerous errors in song structure */
+{
+       struct Instrument *instr;
+       ULONG i;
+
+       /* Check instruments */
+
+       for (i = 1 ; i < si->LastInstrument ; i++)
+       {
+               if (!(instr = si->Instr[i])) continue;
+
+               /* Filter instrument names */
+               FilterName (instr->Name);
+
+               /* Some sensible sanity checks on loops */
+
+               if (instr->Repeat && (instr->Repeat >= instr->Length))
+               {
+                       instr->Repeat = instr->Replen = 0;
+                       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
+                               (APTR)MSG_INVALID_LOOP_REMOVED, i);
+               }
+               else if (instr->Repeat + instr->Replen > instr->Length)
+               {
+                       instr->Replen = instr->Length - instr->Repeat;
+                       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
+                               (APTR)MSG_INVALID_LOOP_FIXED, i);
+               }
+       }
+
+       /* Check patterns */
+       for (i = 0; i < si->NumPatterns; i++)
+               if (si->Patt[i]) FilterName (si->Patt[i]->Name);
+
+       /* Can't have a song with no patterns!!! */
+       if (si->NumPatterns == 0)
+       {
+               xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_NOTE,
+                       (APTR)MSG_SONG_HAS_NO_PATTS, NULL);
+               xmAddPattern (si, NULL);
+       }
+
+       /* Better forcing the song to a minimum length of 1. */
+       if (si->Length == 0)
+       {
+               xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_NOTE,
+                       (APTR)MSG_SONG_HAS_NO_SEQ, NULL);
+               xmSetSongLen (si, 1);
+       }
+
+       /* Check sequence */
+       for (i = 0; i < si->Length; i++)
+       {
+               if (si->Sequence[i] >= si->NumPatterns)
+               {
+                       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
+                               (APTR)MSG_INVALID_SONG_POS, i, si->Sequence[i]);
+                       si->Sequence[i] = si->NumPatterns - 1;
+               }
+       }
+
+       FilterName (si->Title);
+       FilterName (si->Author);
+}
diff --git a/OptimizationWin.c b/OptimizationWin.c
new file mode 100644 (file)
index 0000000..ea4cd05
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+**     OptimizationWin.c
+**
+**     Copyright (C) 1994,95,96 Bernardo Innocenti
+**
+**     Song optimization panel.
+*/
+
+
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Local functions prototypes */
+
+static void OptPerformClicked (struct WinUserData *wud);
+
+
+
+/* Gadgets IDs */
+
+enum
+{
+       GD_OptimizationGroup0,
+               GD_OptimizationGroup1,
+                       GD_RemPatts,
+                       GD_RemDupPatts,
+                       GD_RemInstr,
+                       GD_RemDupInstr,
+                       GD_CutAfterLoop,
+                       GD_CutZeroTail,
+                       GD_CutPatterns,
+                       GD_RemapInstr,
+               GD_OptPerform,
+
+       Optimization_CNT
+};
+
+
+
+
+XDEF struct OptSwitches OptSwitches = { 1, 1, 1, 1, 1, 1, 0, 0 };
+
+
+
+
+static ULONG OptimizationArgs[] =
+{
+       VGROUP_KIND, BBFT_RIDGE,
+               CHECKBOX_KIND,  NULL,   MSG_REM_UNUSED_PATTS_GAD,       (ULONG)&OptSwitches.RemPatts,           TAG_DONE,
+               CHECKBOX_KIND,  NULL,   MSG_REM_DUPLICATE_PATTS_GAD,(ULONG)&OptSwitches.RemDupPatts,    TAG_DONE,
+               CHECKBOX_KIND,  NULL,   MSG_REM_UNUSED_INSTR_GAD,       (ULONG)&OptSwitches.RemInstr,           TAG_DONE,
+               CHECKBOX_KIND,  NULL,   MSG_REM_DUP_INSTR_GAD,          (ULONG)&OptSwitches.RemDupInstr,        TAG_DONE,
+               CHECKBOX_KIND,  NULL,   MSG_CUT_AFTER_LOOP_GAD,         (ULONG)&OptSwitches.CutAfterLoop,       TAG_DONE,
+               CHECKBOX_KIND,  NULL,   MSG_CUT_ZERO_TAILS_GAD,         (ULONG)&OptSwitches.CutZeroTail,        TAG_DONE,
+               CHECKBOX_KIND,  NULL,   MSG_CUT_PATTERNS_GAD,           (ULONG)&OptSwitches.CutPatts,           TAG_DONE,
+               CHECKBOX_KIND,  NULL,   MSG_REMAP_INSTRUMENTS_GAD,      (ULONG)&OptSwitches.RemapInstr,         TAG_DONE,
+               ENDGROUP_KIND,
+       BUTTON_KIND,    (ULONG)OptPerformClicked,       MSG_OPTIMIZE_GAD,       TAG_DONE,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG OptimizationWinTags[] =
+{
+       XMWIN_LayoutArgs,       (LONG)OptimizationArgs,
+       XMWIN_GCount,           Optimization_CNT,
+       XMWIN_Title,            MSG_OPTIMIZATION_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       CHECKBOXIDCMP | BUTTONIDCMP | IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW,
+       XMWIN_HelpNode,         (LONG)"Optimization",
+       TAG_DONE
+};
+
+
+
+GLOBALCALL void UpdateOptSwitches (void)
+{
+       struct WinUserData *wud = WDescr[WID_OPTIMIZATION].Wud;
+
+       if (wud && wud->Win)
+               SetGadgets (wud,
+                       GD_RemPatts,            OptSwitches.RemPatts,
+                       GD_RemDupPatts,         OptSwitches.RemDupPatts,
+                       GD_RemInstr,            OptSwitches.RemInstr,
+                       GD_RemDupInstr,         OptSwitches.RemDupInstr,
+                       GD_CutAfterLoop,        OptSwitches.CutAfterLoop,
+                       GD_CutZeroTail,         OptSwitches.CutZeroTail,
+                       GD_CutPatterns,         OptSwitches.CutPatts,
+                       GD_RemapInstr,          OptSwitches.RemapInstr,
+                       -1);
+}
+
+
+
+/************************/
+/* Optimization Gadgets */
+/************************/
+
+static void OptPerformClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+       ULONG oldsize, newsize;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               oldsize = CalcSongSize (si);
+
+               xmProcessSong (si, NULL,
+                       XMSNG_Optimize, XMOF_DEFAULT,
+                       TAG_DONE);
+
+               UpdateSongInfo();
+
+               newsize = CalcSongSize (si);
+
+               ReleaseSemaphore (&si->Lock);
+
+               xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG, (STRPTR)MSG_SAVED_X_BYTES,
+                       oldsize - newsize, ((oldsize - newsize) * 100) / oldsize );
+       }
+
+       MyCloseWindow (wud);
+}
diff --git a/OptimizationWin.s b/OptimizationWin.s
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/PattPrefsWin.c b/PattPrefsWin.c
new file mode 100644 (file)
index 0000000..20c3e25
--- /dev/null
@@ -0,0 +1,790 @@
+/*
+**     PattPrefsWin.c
+**
+**     Copyright (C) 1995,96 Bernardo Innocenti
+**
+**     Pattern preferences panel and pattern size panel handling functions.
+*/
+
+#include <intuition/intuition.h>
+#include <libraries/gadtools.h>
+#include <libraries/patteditclass.h>
+#include <libraries/asl.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/utility.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "CustomClasses.h"
+#include "Gui.h"
+
+
+static LONG PattPrefsPreOpen           (struct WinUserData *wud);
+static void PattPrefsPostClose         (void);
+static void ReopenPatternWindow                (void);
+
+static void AdvanceTracksClicked       (struct WinUserData *wud);
+static void AdvanceLinesClicked                (struct WinUserData *wud);
+static void MaxUndoLevelsClicked       (struct WinUserData *wud);
+static void MaxUndoMemClicked          (struct WinUserData *wud);
+static void ClipUnitClicked                    (struct WinUserData *wud);
+static void VScrollerPlaceClicked      (struct WinUserData *wud);
+static void HScrollerPlaceClicked      (struct WinUserData *wud);
+static void WrapVertClicked                    (struct WinUserData *wud);
+static void WrapHorizClicked           (struct WinUserData *wud);
+static void HexLineNumbersClicked      (struct WinUserData *wud);
+static void BlankZeroClicked           (struct WinUserData *wud);
+static void DoRulerClicked                     (struct WinUserData *wud);
+static void DoTinyLinesClicked         (struct WinUserData *wud);
+static void GetEditorFontClicked       (struct WinUserData *wud);
+static void BGPenClicked                       (struct WinUserData *wud);
+static void TextPenClicked                     (struct WinUserData *wud);
+static void LinesPenClicked                    (struct WinUserData *wud);
+static void TinyLinesPenClicked                (struct WinUserData *wud);
+static void PattPrefsOkClicked         (struct WinUserData *wud);
+static void PattPrefsCancelClicked     (struct WinUserData *wud);
+
+static void PattSizePostClose          (void);
+
+static void PattSizeLinesClicked       (struct WinUserData *wud);
+static void PattSizeTracksClicked      (struct WinUserData *wud);
+static void PattSizeDoubleClicked      (struct WinUserData *wud);
+static void PattSizeHalveClicked       (struct WinUserData *wud);
+static void PattSizeOkClicked          (struct WinUserData *wud);
+static void PattSizeCancelClicked      (struct WinUserData *wud);
+
+
+
+enum {
+       GD_PattPrefsGroup0,
+               GD_PattPrefsGroup1,
+                       GD_PattPrefsGroup2,
+                               GD_AdvanceTracks,
+                               GD_AdvanceLines,
+                               GD_MaxUndoLevels,
+                               GD_MaxUndoMem,
+                               GD_ClipUnit,
+                               GD_VertScroller,
+                               GD_HorizScroller,
+                       GD_PattPrefsGroup3,
+                               GD_WrapVert,
+                               GD_WrapHoriz,
+                               GD_HexLineNumbers,
+                               GD_BlankZero,
+                               GD_DoRuler,
+                               GD_DoTinyLines,
+                               GD_Backdrop,
+               GD_PattPrefsGroup4,
+                       GD_PattPrefsGroup5,
+                               GD_EditorFont,
+                               GD_GetEditorFont,
+                       GD_PattPrefsGroup6,
+                               GD_PattPrefsGroup7,
+                                       GD_BGPen,
+                                       GD_TextPen,
+                               GD_PattPrefsGroup8,
+                                       GD_LinesPen,
+                                       GD_TinyLinesPen,
+               GD_PattPrefsGroup9,
+                       GD_PattPrefsOk,
+                       GD_PattPrefsCancel,
+
+       PattPrefs_CNT
+};
+
+
+
+enum {
+       GD_PattSizeGroup0,
+               GD_PattSizeGroup1,
+                       GD_PattSizeGroup2,
+                               GD_PattSizeLines,
+                               GD_PattSizeTracks,
+                       GD_PattSizeGroup3,
+                               GD_PattSizeDouble,
+                       GD_PattSizeGroup4,
+                               GD_PattSizeHalve,
+               GD_PattSizeGroup5,
+                       GD_PattSizeOk,
+                       GD_PattSizeCancel,
+
+       PattSize_CNT
+};
+
+
+
+static struct PattSwitches OldPattSwitches;
+static struct Pattern  *OldPatt = NULL;
+static struct SongInfo *OldSong = NULL;
+static ULONG OldPattNum = 0;
+static UBYTE *ColorTable;
+static LONG BGPenTags[] = {
+       GTPA_Color, 0,
+       GTPA_ColorTable, NULL,
+       GTPA_NumColors, 2,
+       TAG_DONE
+};
+static LONG TextPenTags[] = {
+       GTPA_Color, 0,
+       GTPA_NumColors, 2,
+       GTPA_ColorOffset, 1,
+       TAG_DONE };
+
+
+
+static STRPTR ScrollerPosLabels[] =
+{
+       (STRPTR)MSG_OFF_GAD,
+       (STRPTR)MSG_RIGHT_GAD,
+       (STRPTR)MSG_LEFT_GAD,
+       (STRPTR)NULL
+};
+
+
+
+static LONG PattPrefsArgs[] =
+{
+       HGROUP_KIND, BBFT_RIDGE,
+               VGROUP_KIND, 0,
+                       INTEGER_KIND,   (LONG)AdvanceTracksClicked, MSG_ADVANCE_TRACKS_GAD,             3,      TAG_DONE,
+                       INTEGER_KIND,   (LONG)AdvanceLinesClicked,      MSG_ADVANCE_LINES_GAD,          5,      TAG_DONE,
+                       INTEGER_KIND,   (LONG)MaxUndoLevelsClicked,     MSG_MAX_UNDO_LEVELS_GAD,        6,      TAG_DONE,
+                       INTEGER_KIND,   (LONG)MaxUndoMemClicked,        MSG_MAX_UNDO_MEM_GAD,           10,     TAG_DONE,
+                       INTEGER_KIND,   (LONG)ClipUnitClicked,          MSG_CLIPBOARD_UNIT_GAD,         3,      TAG_DONE,
+                       CYCLE_KIND,             (LONG)VScrollerPlaceClicked,MSG_SCROLLER_POS_GAD,       (LONG)ScrollerPosLabels,        TAG_DONE,
+                       CHECKBOX_KIND,  (LONG)HScrollerPlaceClicked,MSG_HORIZ_SCROLLER_GAD,             NULL,   TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       CHECKBOX_KIND,  (LONG)WrapVertClicked,                  MSG_VERT_WRAP_GAD,                      NULL,   TAG_DONE,
+                       CHECKBOX_KIND,  (LONG)WrapHorizClicked,                 MSG_HORIZ_WRAP_GAD,                     NULL,   TAG_DONE,
+                       CHECKBOX_KIND,  (LONG)HexLineNumbersClicked,    MSG_HEX_LINE_NUMBERS_GAD,       NULL,   TAG_DONE,
+                       CHECKBOX_KIND,  (LONG)BlankZeroClicked,                 MSG_BLANK_ZERO_GAD,                     NULL,   TAG_DONE,
+                       CHECKBOX_KIND,  (LONG)DoRulerClicked,                   MSG_DO_RULER_GAD,                       NULL,   TAG_DONE,
+                       CHECKBOX_KIND,  (LONG)DoTinyLinesClicked,               MSG_DO_TINY_LINES_GAD,          NULL,   TAG_DONE,
+                       CHECKBOX_KIND,  NULL,                                                   MSG_BACKDROP_GAD,                       NULL,   TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       VGROUP_KIND, BBFT_RIDGE,
+               HGROUP_KIND, 0,
+                       TEXT_KIND,                      MSG_EDITOR_FONT_GAD,                    0,              GTTX_Border,    TRUE,   TAG_DONE,
+                       IMAGEBUTTON_KIND,       (ULONG)GetEditorFontClicked,    IM_PICK,        TAG_DONE,
+                       ENDGROUP_KIND,
+               HGROUP_KIND, 0,
+                       VGROUP_KIND, 0,
+                               PALETTE_KIND,   (LONG)BGPenClicked,                     MSG_BACKGROUND_PEN_GAD, TAG_MORE,       (LONG)BGPenTags, TAG_DONE,
+                               PALETTE_KIND,   (LONG)TextPenClicked,           MSG_TEXT_PEN_GAD,               TAG_MORE,       (LONG)TextPenTags, TAG_DONE,
+                               ENDGROUP_KIND,
+                       VGROUP_KIND, 0,
+                               PALETTE_KIND,   (LONG)LinesPenClicked,          MSG_LINES_PEN_GAD,              TAG_MORE,       (LONG)TextPenTags, TAG_DONE,
+                               PALETTE_KIND,   (LONG)TinyLinesPenClicked,      MSG_TINY_LINES_PEN_GAD, TAG_MORE,       (LONG)TextPenTags, TAG_DONE,
+                               ENDGROUP_KIND,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       HGROUP_KIND,    0,
+               BUTTON_KIND,    (LONG)PattPrefsOkClicked,               MSG_UNDERSCORE_OK_GAD,          TAG_DONE,
+               BUTTON_KIND,    (LONG)PattPrefsCancelClicked,   MSG_UNDERSCORE_CANCEL_GAD,      TAG_DONE,
+               ENDGROUP_KIND,
+       ENDGROUP_KIND
+};
+
+
+
+static LONG PattSizeArgs[] =
+{
+       HGROUP_KIND, BBFT_RIDGE,
+               VGROUP_KIND, 0,
+                       INTEGER_KIND,   (LONG)PattSizeLinesClicked,             MSG_LINES_GAD,  5, TAG_DONE,
+                       INTEGER_KIND,   (LONG)PattSizeTracksClicked,    MSG_TRACKS_GAD, 3, TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       BUTTON_KIND,    (LONG)PattSizeDoubleClicked,    MSG_DOUBLE_GAD, TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       BUTTON_KIND,    (LONG)PattSizeHalveClicked,             MSG_HALVE_GAD,  TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       HGROUP_KIND, 0,
+               BUTTON_KIND,    (LONG)PattSizeOkClicked,                MSG_UNDERSCORE_OK_GAD,          TAG_DONE,
+               BUTTON_KIND,    (LONG)PattSizeCancelClicked,    MSG_UNDERSCORE_CANCEL_GAD,      TAG_DONE,
+               ENDGROUP_KIND,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG PattPrefsWinTags[] =
+{
+       XMWIN_LayoutArgs,       (LONG)PattPrefsArgs,
+       XMWIN_GCount,           PattPrefs_CNT,
+       XMWIN_Title,            MSG_PATTPREFS_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       BUTTONIDCMP|INTEGERIDCMP|PALETTEIDCMP|CHECKBOXIDCMP|IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW,
+       XMWIN_PreOpenFunc,      (LONG)PattPrefsPreOpen,
+       XMWIN_PostOpenFunc,     (LONG)UpdatePattPrefs,
+       XMWIN_PostCloseFunc,(LONG)PattPrefsPostClose,
+       XMWIN_HelpNode,         (LONG)"PattPrefs",
+       TAG_DONE
+};
+
+
+
+XDEF LONG PattSizeWinTags[] =
+{
+       XMWIN_LayoutArgs,       (LONG)PattSizeArgs,
+       XMWIN_GCount,           PattSize_CNT,
+       XMWIN_Title,            MSG_PATTSIZE_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       BUTTONIDCMP|INTEGERIDCMP|IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW,
+       XMWIN_PostOpenFunc,     (LONG)UpdatePattSize,
+       XMWIN_PostCloseFunc,(LONG)PattSizePostClose,
+       XMWIN_HelpNode,         (LONG)"PattSize",
+       TAG_DONE
+};
+
+
+
+static LONG PattPrefsPreOpen (struct WinUserData *wud)
+{
+       ULONG i, j, numcols;
+
+       numcols = 1 << Scr->RastPort.BitMap->Depth;
+
+       if (!ColorTable)
+       {
+               if (!(ColorTable = AllocVecPooled (Pool, numcols)))
+                       return 1;
+
+               /* Fill ColorTable */
+               for (i = 0, j = 0; i < numcols; i++)
+                       if (!(i & PattSwitches.TextPen))
+                               ColorTable[j++] = i;
+
+               /* Clear remaining colors */
+               for (i = j ; i < numcols; i++)
+                       ColorTable[i] = 0;
+
+               BGPenTags[1]    = PattSwitches.LinesPen;
+               BGPenTags[3]    = (ULONG) ColorTable;
+               BGPenTags[5]    = j;
+               TextPenTags[1]  = PattSwitches.TextPen;
+               TextPenTags[3]  = numcols - 1;
+
+               memcpy (&OldPattSwitches, &PattSwitches, sizeof (PattSwitches));
+       }
+
+       return RETURN_OK;
+}
+
+
+
+static void PattPrefsPostClose (void)
+{
+       FreeVecPooled (Pool, ColorTable); ColorTable = NULL;
+}
+
+
+static void ReopenPatternWindow (void)
+
+/* Closes all editor windows and reopens one */
+{
+       if (WDescr[WID_PATTERN].Wud)
+       {
+               if (WDescr[WID_PATTERN].Wud->Win)
+               {
+                       /* Close all pattern editors... */
+                       while (WDescr[WID_PATTERN].UseCnt > 1)
+                               MyCloseWindow (WDescr[WID_PATTERN].Wud);
+
+                       /* And close last window too... */
+                       MyCloseWindow (WDescr[WID_PATTERN].Wud);
+
+                       /* Now ensure the LayoutInfo for this window gets flushed... */
+                       DeleteLayoutInfo (WDescr[WID_PATTERN].Wud);
+
+                       /* And reopen one pattern editor to show changes! */
+                       NewWindow (WID_PATTERN);
+               }
+               else DeleteLayoutInfo (WDescr[WID_PATTERN].Wud);
+       }
+}
+
+
+
+void UpdatePattPrefs (void)
+{
+       /* This variable is declared static because gadtools text
+        * gadgets do not buffer their texts and require them to be
+        * accessible whenever a refresh is needed :-(.
+        */
+       static UBYTE editorfont[40];
+
+       struct WinUserData *wud = WDescr[WID_PATTPREFS].Wud;
+
+       if (wud && wud->Win)
+       {
+               SPrintf (editorfont, "%s/%ld", EditorAttr.ta_Name, EditorAttr.ta_YSize);
+
+               SetGadgets (wud,
+                       GD_AdvanceTracks,       PattSwitches.AdvanceTracks,
+                       GD_AdvanceLines,        PattSwitches.AdvanceLines,
+                       GD_MaxUndoLevels,       PattSwitches.MaxUndoLevels,
+                       GD_MaxUndoMem,          PattSwitches.MaxUndoMem,
+                       GD_VertScroller,        PattSwitches.VScrollerPlace,
+                       GD_HorizScroller,       PattSwitches.HScrollerPlace,
+                       GD_WrapHoriz,           PattSwitches.Flags & PEF_HWRAP,
+                       GD_WrapVert,            PattSwitches.Flags & PEF_VWRAP,
+                       GD_HexLineNumbers,      PattSwitches.Flags & PEF_HEXMODE,
+                       GD_BlankZero,           PattSwitches.Flags & PEF_BLANKZERO,
+                       GD_DoRuler,                     PattSwitches.Flags & PEF_DOCURSORRULER,
+                       GD_DoTinyLines,         PattSwitches.Flags & PEF_DOTINYLINES,
+                       GD_EditorFont,          editorfont,
+                       GD_BGPen,                       PattSwitches.BGPen,
+                       GD_TextPen,                     PattSwitches.TextPen,
+                       GD_LinesPen,            PattSwitches.LinesPen,
+                       GD_TinyLinesPen,        PattSwitches.TinyLinesPen,
+                       -1);
+       }
+}
+
+
+
+static void PattSizePostClose (void)
+{
+       xmRemPattern (OldSong, -1, (ULONG)OldPatt);
+       OldPatt = NULL;
+}
+
+
+
+GLOBALCALL void UpdatePattSize (void)
+{
+       struct WinUserData *wud = WDescr[WID_PATTSIZE].Wud;
+
+       if (wud && wud->Win)
+       {
+               struct SongInfo *si;
+               struct Pattern *patt;
+
+               if (si = xmLockActiveSong (SM_SHARED))
+               {
+                       patt = si->Patt[si->CurrentPatt];
+
+                       /* Free previous backup copy, if any */
+
+                       if (OldPatt) xmRemPattern (OldSong, -1, (ULONG)OldPatt);
+
+                       /* Make a local backup copy of the current pattern (it may fail) */
+
+                       OldSong         = si;
+                       OldPattNum      = si->CurrentPatt;
+                       OldPatt         = CopyPattern (si, si->Patt[si->CurrentPatt], -1);
+
+                       /* Update gadgets */
+                       SetGadgets (wud,
+                               GD_PattSizeLines, patt->Lines,
+                               GD_PattSizeTracks, patt->Tracks,
+                               -1);
+
+                       ReleaseSemaphore (&si->Lock);
+               }
+       }
+}
+
+
+
+static void AdvanceTracksClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+       PattSwitches.AdvanceTracks = (WORD) GetNumber (wud->Gadgets[GD_AdvanceTracks]);
+
+       if (patternwud  && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_AdvanceCurs,        (PattSwitches.AdvanceTracks << 16) |
+                                                               ((UWORD)PattSwitches.AdvanceLines),
+                       TAG_DONE);
+}
+
+
+
+static void AdvanceLinesClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.AdvanceLines = (WORD) GetNumber(wud->Gadgets[GD_AdvanceLines]);
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_AdvanceCurs,        (PattSwitches.AdvanceTracks << 16) |
+                                                               ((UWORD)PattSwitches.AdvanceLines),
+                       TAG_DONE);
+}
+
+
+
+static void MaxUndoLevelsClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.MaxUndoLevels = (WORD) GetNumber (wud->Gadgets[GD_MaxUndoLevels]);
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_MaxUndoLevels,      PattSwitches.MaxUndoLevels,
+                       TAG_DONE);
+}
+
+
+
+static void MaxUndoMemClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.MaxUndoMem = (WORD) GetNumber (wud->Gadgets[GD_MaxUndoMem]);
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_MaxUndoMem, PattSwitches.MaxUndoMem,
+                       TAG_DONE);
+}
+
+
+
+static void VScrollerPlaceClicked (struct WinUserData *wud)
+{
+       PattSwitches.VScrollerPlace = IntuiMsg.Code;
+       ReopenPatternWindow ();
+}
+
+
+static void HScrollerPlaceClicked (struct WinUserData *wud)
+{
+       PattSwitches.HScrollerPlace ^= SCROLLERPLACE_BOTTOM;
+       ReopenPatternWindow ();
+}
+
+
+static void WrapVertClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.Flags ^= PEF_VWRAP;
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_Flags,      PattSwitches.Flags,
+                       TAG_DONE);
+}
+
+
+
+static void WrapHorizClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.Flags ^= PEF_HWRAP;
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_Flags,      PattSwitches.Flags,
+                       TAG_DONE);
+}
+
+
+
+static void HexLineNumbersClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.Flags ^= PEF_HEXMODE;
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_Flags,      PattSwitches.Flags,
+                       TAG_DONE);
+}
+
+
+
+static void BlankZeroClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.Flags ^= PEF_BLANKZERO;
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_Flags,      PattSwitches.Flags,
+                       TAG_DONE);
+}
+
+
+static void DoRulerClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.Flags ^= PEF_DOCURSORRULER;
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_Flags,      PattSwitches.Flags,
+                       TAG_DONE);
+}
+
+
+
+static void DoTinyLinesClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.Flags ^= PEF_DOTINYLINES;
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_Flags,      PattSwitches.Flags,
+                       TAG_DONE);
+}
+
+
+
+static void ClipUnitClicked (struct WinUserData *wud)
+{
+       PattSwitches.ClipboardUnit = (UBYTE) GetNumber (wud->Gadgets[GD_ClipUnit]);
+}
+
+
+
+static void GetEditorFontClicked (struct WinUserData *wud)
+{
+       FontRequest (&EditorAttr, FOF_FIXEDWIDTHONLY);
+       ReopenPatternWindow ();
+       UpdatePattPrefs();
+}
+
+
+
+static void BGPenClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.BGPen = (UWORD) IntuiMsg.Code;
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_BGPen,      PattSwitches.BGPen,
+                       TAG_DONE);
+}
+
+
+
+static void TextPenClicked (struct WinUserData *wud)
+{
+       ULONG i, j, numcols = 1 << Scr->RastPort.BitMap->Depth;
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.TextPen = (UWORD) IntuiMsg.Code;
+
+       /* Recalculate Lines colors */
+       for (i = 0, j = 0; i < numcols; i++)
+               if (!(i & PattSwitches.TextPen))
+                       ColorTable[j++] = i;
+
+       /* Clear remaining colors */
+       for (i = j ; i < numcols; i++)
+               ColorTable[i] = 0;
+
+       if (PattSwitches.LinesPen & PattSwitches.TextPen)
+               PattSwitches.LinesPen = ColorTable[(j > 1) ? 1 : 0];
+
+       if (PattSwitches.TinyLinesPen & PattSwitches.TextPen)
+               PattSwitches.TinyLinesPen = ColorTable[(j > 1) ? 1 : 0];
+
+       GT_SetGadgetAttrs (wud->Gadgets[GD_LinesPen], wud->Win, NULL,
+               GTPA_Color,                     PattSwitches.LinesPen,
+               GTPA_ColorTable,        ColorTable,
+               GTPA_NumColors,         j,
+               TAG_DONE);
+
+       GT_SetGadgetAttrs (wud->Gadgets[GD_TinyLinesPen], wud->Win, NULL,
+               GTPA_Color,                     PattSwitches.TinyLinesPen,
+               GTPA_ColorTable,        ColorTable,
+               GTPA_NumColors,         j,
+               TAG_DONE);
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_TextPen,            PattSwitches.TextPen,
+                       PEA_LinesPen,           PattSwitches.LinesPen,
+                       PEA_TinyLinesPen,       PattSwitches.TinyLinesPen,
+                       TAG_DONE);
+}
+
+
+
+static void LinesPenClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.LinesPen = (UWORD) IntuiMsg.Code;
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_LinesPen,   PattSwitches.LinesPen,
+                       TAG_DONE);
+}
+
+
+
+static void TinyLinesPenClicked (struct WinUserData *wud)
+{
+       struct WinUserData *patternwud = WDescr[WID_PATTERN].Wud;
+
+       PattSwitches.TinyLinesPen = (UWORD) IntuiMsg.Code;
+
+       if (patternwud && patternwud->Win)
+               SetGadgetAttrs (patternwud->Gadgets[0], patternwud->Win, NULL,
+                       PEA_TinyLinesPen,       PattSwitches.TinyLinesPen,
+                       TAG_DONE);
+}
+
+
+
+static void PattPrefsOkClicked (struct WinUserData *wud)
+{
+       MyCloseWindow (wud);
+}
+
+
+
+static void PattPrefsCancelClicked (struct WinUserData *wud)
+{
+       MyCloseWindow (wud);
+}
+
+
+
+static void PattSizeLinesClicked (struct WinUserData *wud)
+{
+}
+
+
+
+static void PattSizeTracksClicked (struct WinUserData *wud)
+{
+}
+
+
+
+static void PattSizeDoubleClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               struct Pattern *patt = si->Patt[si->CurrentPatt];
+               struct Pattern *newpatt;
+               UWORD i, j;
+
+               if (patt->Lines > MAXPATTLINES/2)
+                       ShowMessage (MSG_PATT_TOO_LONG);
+
+               if (newpatt = xmAddPattern (si,
+                       PATTA_Lines,    patt->Lines << 1,
+                       PATTA_Tracks,   patt->Tracks,
+                       PATTA_Name,             patt->Name,
+                       PATTA_Num,              -1,
+                       TAG_DONE))
+               {
+                       for (i = 0; i < patt->Tracks; i++)
+                               for (j = 0; j < patt->Lines; j++)
+                                       memcpy (&newpatt->Notes[i][j*2], &patt->Notes[i][j], sizeof (struct Note));
+
+                       xmAddPattern (si,
+                               PATTA_Num,              si->CurrentPatt,
+                               PATTA_Pattern,  newpatt,
+                               PATTA_Replace,  TRUE,
+                               TAG_DONE);
+
+                       UpdatePattern();
+               }
+               else LastErr = ERROR_NO_FREE_STORE;
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PattSizeHalveClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               struct Pattern *patt = si->Patt[si->CurrentPatt];
+               struct Pattern *newpatt;
+               UWORD i, j;
+
+
+               if (newpatt = xmAddPattern (si,
+                       PATTA_Lines,    patt->Lines >> 1,
+                       PATTA_Tracks,   patt->Tracks,
+                       PATTA_Name,             patt->Name,
+                       PATTA_Num,              -1,
+                       TAG_DONE))
+               {
+                       for (i = 0; i < newpatt->Tracks; i++)
+                               for (j = 0; j < newpatt->Lines; j++)
+                                       memcpy (&newpatt->Notes[i][j], &patt->Notes[i][j*2], sizeof (struct Note));
+
+                       xmAddPattern (si,
+                               PATTA_Num,              si->CurrentPatt,
+                               PATTA_Pattern,  newpatt,
+                               PATTA_Replace,  TRUE,
+                               TAG_DONE);
+
+                       UpdatePattern();
+               }
+               else LastErr = ERROR_NO_FREE_STORE;
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PattSizeOkClicked (struct WinUserData *wud)
+{
+       MyCloseWindow (wud);
+}
+
+
+
+static void PattSizeCancelClicked (struct WinUserData *wud)
+{
+       if (OldPatt)
+       {
+               struct SongInfo *si;
+
+               if (si = xmLockActiveSong (SM_EXCLUSIVE))
+               {
+                       if (OldSong == si)
+                       {
+                               /* Restore original pattern */
+                               CopyPattern (si, OldPatt, OldPattNum);
+
+                               /* Free backup copy */
+                               xmRemPattern (OldSong, -1, (ULONG)OldPatt);
+                               OldPatt = NULL;
+                       }
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UpdatePattern();
+
+       MyCloseWindow (wud);
+}
diff --git a/PatternWin.c b/PatternWin.c
new file mode 100644 (file)
index 0000000..4a4d5e1
--- /dev/null
@@ -0,0 +1,1075 @@
+/*
+**     PatternWin.c
+**
+**     Copyright (C) 1994,95,96 Bernardo Innocenti
+**
+**     Parts of the code have been inspired by ScrollerWindow 0.3 demo
+**     Copyright © 1994 Christoph Feck, TowerSystems.
+**
+**     Pattern editor handling functions.
+**
+**
+**     Diagram of object interconnections:
+**
+**
+**                ScrollButtonClass objects
+**     +----------+ +---------+ +----------+ +----------+
+**     | UpButton | | DnButton| | SxButton | | DxButton |
+**     +----------+ +---------+ +----------+ +----------+
+**      | GA_ID =   | GA_ID =    | GA_ID =    | GA_ID =
+**      | PATTA_Up  | PATTA_Down | PATTA_Left | PATTA_Right
+**      |           |            |            |
+**      |  +--------+            |            |
+**      |  |  +------------------+            |
+**      |  |  |  +----------------------------+
+**      |  |  |  |      propgclass  object                       icclass object
+**      |  |  |  |      +----------------+                     +-----------------+
+**      |  |  |  |   +--|     HSlider    |<--------------------| EditorToHSlider |
+**      |  |  |  |   |  +----------------+  PATTA_LeftTrack =  +-----------------+
+**      |  |  |  |   | PGA_Top =            PGA_Top                    ^
+**      |  |  |  |   | PATTA_TopLine        PATTA_DisplayTracks =      |
+**      |  |  |  |   |                      PGA_Visible                |
+**      |  |  |  |   |                   +-----------------------------+
+**      V  V  V  V   V                   |
+**     +---------------+            ************
+**     |  PattEditGad  |----------->*   Model  *----------------> IDCMP_IDCMPUPDATE
+**     +---------------+            ************ PATTA_CursLine   to PatternWin
+**       ^                               |       PATTA_CursTrack
+**       | propgclass object             |
+**       | PGA_Top = PATTA_TopLine       V icclass object
+**     +----------------+       +-----------------+
+**     |    VSlider     |<------| EditorToVSlider | PATTA_TopLine      = PGA_Top
+**     +----------------+       +-----------------+ PATTA_DisplayLines = PGA_Visible
+*/
+
+
+#include <intuition/intuition.h>
+#include <intuition/icclass.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/imageclass.h>
+#include <graphics/gfxbase.h>
+#include <libraries/gadtools.h>
+#include <libraries/iffparse.h>
+#include <libraries/patteditclass.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/graphics.h>
+#include <proto/layers.h>
+#include <proto/utility.h>
+#include <proto/dos.h>
+#include <proto/iffparse.h>
+#include <proto/diskfont.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+#include "CustomClasses.h"
+
+
+/* Gadget IDs */
+enum {
+       GD_PattEdit,
+       GD_UpButton,
+       GD_DnButton,
+       GD_VSlider,
+       GD_SxButton,
+       GD_DxButton,
+       GD_HSlider,
+
+       Pattern_CNT
+};
+
+
+/* Local function prototypes */
+
+static struct Gadget   *CreatePattEdit (void);
+static struct Gadget   *LayoutPatternWin (struct WinUserData *wud);
+
+static void PatternPostOpen            (struct WinUserData *wud);
+static void PatternWindowCleanup(struct WinUserData *wud);
+
+static void RenderPatternWindow        (struct WinUserData *wud);
+static void HandlePatternIDCMP (struct WinUserData *wud);
+static void PatternLoad                        (STRPTR name, ULONG num, ULONG count);
+static LONG PatternSave                        (STRPTR name, struct Pattern *patt);
+
+static void PatternMiOpen              (void);
+static void PatternMiSave              (void);
+static void PatternMiSaveAs            (void);
+static void PatternMiSize              (void);
+static void PatternMiMark              (struct WinUserData *wud);
+static void PatternMiCut               (struct WinUserData *wud);
+static void PatternMiCopy              (struct WinUserData *wud);
+static void PatternMiPaste             (struct WinUserData *wud);
+static void PatternMiErase             (struct WinUserData *wud);
+static void PatternMiUndo              (struct WinUserData *wud);
+static void PatternMiRedo              (struct WinUserData *wud);
+static void PatternMiSettings  (void);
+
+
+
+/* Local data */
+
+static struct Library  *PattEditBase           = NULL;
+static struct TextFont *EditorFont                     = NULL;
+static Object                  *Model                          = NULL,
+                                               *EditorToVSlider        = NULL,
+                                               *EditorToHSlider        = NULL;
+
+static UBYTE WindowTitle[128];
+static UBYTE TitleInfo[16];
+
+
+
+XDEF struct PattSwitches PattSwitches =
+{
+       0,      1,                                      /* AdvanceTracks,       AdvanceLines    */
+       32,     8192,                           /* MaxUndoLevels,       MaxUndoMem              */
+       0,                                              /* Flags                                                        */
+       SCROLLERPLACE_RIGHT,    /* VScrollerPlace                                       */
+       SCROLLERPLACE_BOTTOM,   /* HScrollerPlace                                       */
+       0,      0,                                      /* ClipboardUnit,       Pad0                    */
+       10, 0,                                  /* TrackChars, Backdrop                         */
+       0,      1,      2,      2                       /* BGPen, TextPen, LinesPen, TinyLinesPen */
+};
+
+
+static LONG MapVSlider2Editor[] =
+{
+       PGA_Top,                                PEA_TopLine,
+       TAG_DONE
+};
+
+static LONG MapHSlider2Editor[] =
+{
+       PGA_Top,                                PEA_LeftTrack,
+       TAG_DONE
+};
+
+static LONG MapEditorToVSlider[] =
+{
+       PEA_TopLine,                    PGA_Top,
+       PEA_DisplayLines,               PGA_Visible,
+       TAG_DONE
+};
+
+static LONG MapEditorToHSlider[] =
+{
+       PEA_LeftTrack,          PGA_Top,
+       PEA_DisplayTracks,      PGA_Visible,
+       TAG_DONE
+};
+
+static LONG MapUp2Editor[] =
+{
+       GA_ID,          PEA_Up,
+       TAG_DONE
+};
+
+static LONG MapDn2Editor[] =
+{
+       GA_ID,          PEA_Down,
+       TAG_DONE
+};
+
+static LONG MapSx2Editor[] =
+{
+       GA_ID,          PEA_Left,
+       TAG_DONE
+};
+
+static LONG MapDx2Editor[] =
+{
+       GA_ID,          PEA_Right,
+       TAG_DONE
+};
+
+
+
+static struct NewMenu PatternNewMenu[] = {
+       NM_TITLE, (STRPTR)MSG_PATTERNS_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_OPEN_MEN, (STRPTR)"O", 0, 0L, (APTR)PatternMiOpen,
+       NM_ITEM, (STRPTR)MSG_SAVE_MEN, (STRPTR)"S", 0, 0L, (APTR)PatternMiSave,
+       NM_ITEM, (STRPTR)MSG_SAVE_AS_MEN, (STRPTR)"A", 0, 0L, (APTR)PatternMiSaveAs,
+       NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
+       NM_ITEM, (STRPTR)MSG_SIZE_MEN, NULL, 0, 0L, (APTR)PatternMiSize,
+       NM_TITLE, (STRPTR)MSG_EDIT_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_MARK_MEN, (STRPTR)"B", 0, 0L, (APTR)PatternMiMark,
+       NM_ITEM, (STRPTR)MSG_CUT_MEN, (STRPTR)"X", 0, 0L, (APTR)PatternMiCut,
+       NM_ITEM, (STRPTR)MSG_COPY_MEN, (STRPTR)"C", 0, 0L, (APTR)PatternMiCopy,
+       NM_ITEM, (STRPTR)MSG_PASTE_MEN, (STRPTR)"V", 0, 0L, (APTR)PatternMiPaste,
+       NM_ITEM, (STRPTR)MSG_ERASE_MEN, (STRPTR)"E", 0, 0L, (APTR)PatternMiErase,
+       NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
+       NM_ITEM, (STRPTR)MSG_UNDO_MEN, (STRPTR)"U", 0, 0L, (APTR)PatternMiUndo,
+       NM_ITEM, (STRPTR)MSG_REDO_MEN, (STRPTR)"R", 0, 0L, (APTR)PatternMiRedo,
+       NM_TITLE, (STRPTR)MSG_SETTINGS_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_EDITOR_SETTINGS_MEN, (STRPTR)"P", 0, 0L, (APTR)PatternMiSettings,
+       NM_END, NULL, NULL, 0, 0L, NULL };
+
+
+
+XDEF LONG PatternWinTags[] =
+{
+       TAG_IGNORE,                     TRUE,   /* WA_SizeB(Left|Right) */
+#ifdef OS30_ONLY
+       WA_BackFill,            LAYERS_NOBACKFILL,
+#endif /* OS30_ONLY */
+       XMWIN_NewMenu,          (LONG)PatternNewMenu,
+       XMWIN_LayoutFunc,       (LONG)LayoutPatternWin,
+       XMWIN_GCount,           Pattern_CNT,
+       XMWIN_Title,            MSG_PATTERN_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET | WFLG_ACTIVATE|
+               WFLG_SIZEGADGET | WFLG_SIMPLE_REFRESH | WFLG_NOCAREREFRESH,
+       XMWIN_IDCMPFlags,       IDCMP_MENUPICK|IDCMP_CLOSEWINDOW|IDCMP_ACTIVEWINDOW|IDCMP_INACTIVEWINDOW|IDCMP_NEWSIZE|IDCMP_IDCMPUPDATE|IDCMP_INTUITICKS,
+       XMWIN_IDCMPFunc,        (LONG)HandlePatternIDCMP,
+       XMWIN_PostOpenFunc,     (LONG)PatternPostOpen,
+       XMWIN_LayoutCleanup,(LONG)PatternWindowCleanup,
+       XMWIN_HelpNode,         (LONG)"Pattern",
+       TAG_DONE
+};
+
+
+
+static struct Gadget *CreatePattEdit (void)
+{
+       /* We do not initialize PEA_Pattern right now because
+        * it is done later by UpdatePattern().
+        */
+       return ((struct Gadget *)NewObject (NULL, PATTEDITCLASS,
+               GA_ID,                          GD_PattEdit,
+               GA_Left,                        ((PattSwitches.VScrollerPlace == SCROLLERPLACE_LEFT) ? SizeWidth : OffX),
+               GA_Top,                         OffY,
+               GA_RelWidth,            - SizeWidth - ((PattSwitches.VScrollerPlace == SCROLLERPLACE_LEFT) ? Scr->WBorRight : OffX),
+               GA_RelHeight,           - OffY - (((PattSwitches.HScrollerPlace == SCROLLERPLACE_BOTTOM) ||
+                       (PattSwitches.VScrollerPlace != SCROLLERPLACE_RIGHT)) ? SizeHeight : Scr->WBorBottom),
+               PEA_TextFont,           EditorFont ? EditorFont : TopazFont,
+               PEA_AdvanceCurs,        (PattSwitches.AdvanceTracks << 16) | PattSwitches.AdvanceLines,
+               PEA_MaxUndoLevels,      PattSwitches.MaxUndoLevels,
+               PEA_MaxUndoMem,         PattSwitches.MaxUndoMem,
+               PEA_Flags,                      PattSwitches.Flags,
+               PEA_TrackChars,         PattSwitches.TrackChars,
+               PEA_BGPen,                      PattSwitches.BGPen,
+               PEA_TextPen,            PattSwitches.TextPen,
+               PEA_LinesPen,           PattSwitches.LinesPen,
+               PEA_TinyLinesPen,       PattSwitches.TinyLinesPen,
+               TAG_DONE));
+}
+
+
+
+static struct Gadget *LayoutPatternWin (struct WinUserData *wud)
+{
+       struct Gadget *pattedit;
+
+       if (!PattEditBase)
+               if (!(PattEditBase = MyOpenLibrary (PATTEDITNAME, PATTEDITVERS)))
+                       return NULL;
+
+       {
+               ULONG numcols = 1 << Scr->RastPort.BitMap->Depth; /**/
+
+               if (PattSwitches.TextPen >= numcols)
+                       PattSwitches.TextPen = 1;
+
+               if (PattSwitches.LinesPen >= numcols)
+                       PattSwitches.LinesPen = 2;
+
+               if (PattSwitches.TinyLinesPen >= numcols)
+                       PattSwitches.TinyLinesPen = 2;
+       }
+
+       if (!(EditorFont = OpenFont (&EditorAttr)))
+               if (DiskfontBase)
+                       EditorFont = OpenDiskFont (&EditorAttr);
+
+       if (!EditorFont)
+       {
+               CantOpenLib (EditorAttr.ta_Name, 0);
+               EditorFont = OpenFont (&TopazAttr);
+       }
+
+
+       PatternWinTags[0] = (PattSwitches.VScrollerPlace == SCROLLERPLACE_RIGHT) ?
+               WA_SizeBRight : WA_SizeBBottom;
+
+       if (PattSwitches.Backdrop)
+       {
+               wud->Flags = WFLG_BORDERLESS | WFLG_BACKDROP | WFLG_SMART_REFRESH | WFLG_NOCAREREFRESH;
+               wud->WindowSize.Left = wud->WindowSize.Top = 0;
+               wud->WindowSize.Width = Scr->Width;
+               wud->WindowSize.Height = Scr->Height;
+       }
+       else
+       {
+               wud->WindowSize.Width = max (wud->WindowSize.Width,
+                       EditorFont->tf_XSize * (MINTRACKCHARS + 4));
+               wud->WindowSize.Height = max (wud->WindowSize.Height,
+                       EditorFont->tf_YSize * 2);
+       }
+
+       if (!(pattedit = wud->Gadgets[GD_PattEdit] = CreatePattEdit()))
+               return NULL;
+
+       if (PattSwitches.VScrollerPlace)
+       {
+               if (!(wud->Gadgets[GD_UpButton] = CreateUpButton (GD_UpButton, pattedit, MapUp2Editor, PattSwitches.VScrollerPlace)))
+                       return NULL;
+               if (!(wud->Gadgets[GD_DnButton] = CreateDnButton (GD_DnButton, pattedit, MapDn2Editor, PattSwitches.VScrollerPlace)))
+                       return NULL;
+               if (!(wud->Gadgets[GD_VSlider] = CreateVSlider (GD_VSlider, pattedit, MapVSlider2Editor,
+                       wud->Gadgets[GD_UpButton]->Height + wud->Gadgets[GD_DnButton]->Height,
+                       PattSwitches.VScrollerPlace)))
+                       return NULL;
+       }
+
+       if (PattSwitches.HScrollerPlace)
+       {
+               if (!(wud->Gadgets[GD_SxButton] = CreateSxButton (GD_SxButton, pattedit, MapSx2Editor)))
+                       return NULL;
+               if (!(wud->Gadgets[GD_DxButton] = CreateDxButton (GD_DxButton, pattedit, MapDx2Editor)))
+                       return NULL;
+               if (!(wud->Gadgets[GD_HSlider] = CreateHSlider (GD_HSlider, pattedit, MapHSlider2Editor,
+               wud->Gadgets[GD_DxButton]->Width + wud->Gadgets[GD_SxButton]->Width)))
+                       return NULL;
+       }
+
+
+       /* Link gadgets together */
+
+       if (PattSwitches.VScrollerPlace)
+       {
+               wud->Gadgets[GD_PattEdit]->NextGadget   = wud->Gadgets[GD_UpButton];
+               wud->Gadgets[GD_UpButton]->NextGadget   = wud->Gadgets[GD_DnButton];
+               wud->Gadgets[GD_DnButton]->NextGadget   = wud->Gadgets[GD_VSlider];
+               if (PattSwitches.HScrollerPlace)
+                       wud->Gadgets[GD_VSlider]->NextGadget = wud->Gadgets[GD_SxButton];
+       }
+       if (PattSwitches.HScrollerPlace)
+       {
+               if (!PattSwitches.VScrollerPlace)
+                       wud->Gadgets[GD_PattEdit]->NextGadget   = wud->Gadgets[GD_SxButton];
+
+               wud->Gadgets[GD_SxButton]->NextGadget   = wud->Gadgets[GD_DxButton];
+               wud->Gadgets[GD_DxButton]->NextGadget   = wud->Gadgets[GD_HSlider];
+       }
+
+
+       if (PattSwitches.VScrollerPlace || PattSwitches.HScrollerPlace)
+       {
+               /* Create the Model */
+               if (Model = NewObject (NULL, MODELCLASS,
+                       ICA_TARGET,             ICTARGET_IDCMP,
+                       TAG_DONE))
+               {
+                       /* Connect Editor to Model */
+                       SetAttrs (wud->Gadgets[GD_PattEdit],
+                               ICA_TARGET, Model,
+                               TAG_DONE);
+
+                       if (PattSwitches.VScrollerPlace)
+                       {
+                               if (EditorToVSlider = NewObject (NULL, ICCLASS,
+                                       ICA_TARGET,     wud->Gadgets[GD_VSlider],
+                                       ICA_MAP,        MapEditorToVSlider,
+                                       TAG_DONE))
+                                       /* Connect Model to VSlider */
+                                       if (!DoMethod (Model, OM_ADDMEMBER, EditorToVSlider))
+                                               DisposeObject (EditorToVSlider); EditorToVSlider = NULL;
+                       }
+
+                       if (PattSwitches.HScrollerPlace)
+                       {
+                               if (EditorToHSlider = NewObject (NULL, ICCLASS,
+                                       ICA_TARGET,     wud->Gadgets[GD_HSlider],
+                                       ICA_MAP,        MapEditorToHSlider,
+                                       TAG_DONE))
+                                               /* Connect Model to HSlider */
+                                               if (!DoMethod (Model, OM_ADDMEMBER, EditorToHSlider))
+                                                       DisposeObject (EditorToHSlider); EditorToHSlider = NULL;
+                       }
+               }
+       }
+       else
+               /* Connect Editor to application */
+               SetAttrs (wud->Gadgets[GD_PattEdit],
+                       ICA_TARGET, ICTARGET_IDCMP,
+                       TAG_DONE);
+
+       return wud->Gadgets[GD_PattEdit];
+
+       /* NOTE: The model will also dispose its members... */
+/*
+       DisposeObject (Model); Model = NULL;
+       EditorToVSlider = NULL; EditorToHSlider = NULL;
+       return NULL;
+*/
+}
+
+
+
+
+#ifndef OS30_ONLY
+
+static ULONG __asm BFHookFunc (void)
+
+/* No-op backfilling hook.  Since we are going to redraw the whole window
+ * anyway, we can disable backfilling.  This avoids ugly flashing while
+ * resizing or revealing the window.
+ *
+ * This function hasn't the __saveds attribute because it makes no
+ * references to external data.
+ */
+{
+       return 1;       /* Do nothing */
+}
+
+static struct Hook BFHook =
+{
+       NULL, NULL,
+       BFHookFunc,
+};
+#endif /* !OS30_ONLY */
+
+
+
+static void PatternPostOpen (struct WinUserData *wud)
+{
+       /* Limit window flashing by providing a no-op hook for window
+        * backfilling.
+        */
+#ifndef OS30_ONLY
+       InstallLayerHook (wud->Win->WLayer, (LayersBase->lib_Version >= 39) ?
+               ((struct Hook *)LAYERS_NOBACKFILL) : &BFHook);
+#endif /* !OS30_ONLY */
+
+       UpdatePattern();
+
+       /* Allow window resizing.  Minimum size is chosen so that at least
+        * two lines are visible.
+        */
+       WindowLimits (wud->Win,
+               wud->Win->BorderLeft + wud->Win->BorderRight + EditorFont->tf_XSize * (10 + 4),
+               wud->Win->BorderTop + wud->Win->BorderBottom + EditorFont->tf_YSize * 2,
+               -1, -1);
+}
+
+
+
+static void PatternWindowCleanup (struct WinUserData *wud)
+{
+       /* NOTE: The model will also dispose its members. */
+       DisposeObject (Model);  Model = NULL;
+       EditorToVSlider = NULL;
+       EditorToHSlider = NULL;
+
+       if (wud->Gadgets)
+       {
+               DisposeObject (wud->Gadgets[GD_VSlider]);
+               wud->Gadgets[GD_VSlider] = NULL;
+               DisposeObject (wud->Gadgets[GD_HSlider]);
+               wud->Gadgets[GD_HSlider] = NULL;
+
+               if (wud->Gadgets[GD_UpButton])
+               {
+                       DisposeObject (wud->Gadgets[GD_UpButton]->GadgetRender);
+                       DisposeObject (wud->Gadgets[GD_UpButton]);
+                       wud->Gadgets[GD_UpButton] = NULL;
+               }
+
+               if (wud->Gadgets[GD_DnButton])
+               {
+                       DisposeObject (wud->Gadgets[GD_DnButton]->GadgetRender);
+                       DisposeObject (wud->Gadgets[GD_DnButton]);
+                       wud->Gadgets[GD_DnButton] = NULL;
+               }
+
+               if (wud->Gadgets[GD_SxButton])
+               {
+                       DisposeObject (wud->Gadgets[GD_SxButton]->GadgetRender);
+                       DisposeObject (wud->Gadgets[GD_SxButton]);
+                       wud->Gadgets[GD_SxButton] = NULL;
+               }
+
+               if (wud->Gadgets[GD_DxButton])
+               {
+                       DisposeObject (wud->Gadgets[GD_DxButton]->GadgetRender);
+                       DisposeObject (wud->Gadgets[GD_DxButton]);
+                       wud->Gadgets[GD_DxButton] = NULL;
+               }
+
+               DisposeObject (wud->Gadgets[GD_PattEdit]);
+               wud->Gadgets[GD_PattEdit] = NULL;
+       }
+
+       if (EditorFont)
+       {
+               CloseFont (EditorFont);
+               EditorFont = NULL;
+       }
+
+       if (PattEditBase)
+       {
+               CloseLibrary (PattEditBase);
+               PattEditBase = NULL;
+       }
+}
+
+
+
+static void HandlePatternIDCMP (struct WinUserData *wud)
+{
+       switch (IntuiMsg.Class)
+       {
+               case IDCMP_IDCMPUPDATE:
+               {
+                       struct TagItem *ti, *tstate = IntuiMsg.IAddress;
+                       ULONG line = ((UWORD)~0), track = ((UWORD)~0);
+
+                       while (ti = NextTagItem (&tstate))
+                               switch (ti->ti_Tag)
+                               {
+                                       case PEA_CursLine:
+                                               line = ti->ti_Data;
+                                               break;
+
+                                       case PEA_CursTrack:
+                                               track = ti->ti_Data;
+                                               break;
+
+                                       case PEA_ChangeNote:
+                                               /* TODO: Increment changes counter of song */
+                                               break;
+
+                                       default:
+                                               break;
+                               }
+
+                       if (track != ((UWORD)~0))
+                       {
+                               SPrintf (TitleInfo, "   %lu, %lu", track, line);
+                               RenderPatternWindow (wud);
+                       }
+
+                       break;
+               }
+
+               case IDCMP_ACTIVEWINDOW:
+               case IDCMP_INACTIVEWINDOW:
+               case IDCMP_NEWSIZE:
+                       RenderPatternWindow (wud);
+                       break;
+
+               case IDCMP_INTUITICKS:
+                       if (!(wud->Gadgets[GD_PattEdit]->Flags & GFLG_SELECTED))
+                               ActivateGadget (wud->Gadgets[GD_PattEdit], wud->Win, NULL);
+                       break;
+
+               default:
+                       break;
+       }
+}
+
+
+
+static void RenderPatternWindow (struct WinUserData *wud)
+
+/* Prints <TitleInfo> on the right hand of the window title bar */
+{
+       UWORD tlen, twidth;
+       struct RastPort *rp;
+       struct TextFont *oldfont;
+
+       tlen = strlen (TitleInfo);      /* Couldn't get it from SPrintf()!! */
+
+       rp = wud->Win->RPort;
+       oldfont = rp->Font;
+       SetFont (rp, DrawInfo->dri_Font);
+
+       twidth = TextLength (rp, TitleInfo, tlen);
+
+       if (wud->Win->Flags & WFLG_WINDOWACTIVE)
+       {
+#ifndef OS30_ONLY
+               if (GfxBase->LibNode.lib_Version >= 39)
+#endif /* !OS30_ONLY */
+                       SetABPenDrMd (rp, DrawInfo->dri_Pens[FILLTEXTPEN],
+                               DrawInfo->dri_Pens[FILLPEN], JAM2);
+#ifndef OS30_ONLY
+               else
+               {
+                       SetAPen (rp, DrawInfo->dri_Pens[FILLTEXTPEN]);
+                       SetBPen (rp, DrawInfo->dri_Pens[FILLPEN]);
+                       SetDrMd (rp, JAM2);
+               }
+#endif /* !OS30_ONLY */
+       }
+       else
+       {
+#ifndef OS30_ONLY
+               if (GfxBase->LibNode.lib_Version >= 39)
+#endif /* !OS30_ONLY */
+                       SetABPenDrMd (rp, DrawInfo->dri_Pens[TEXTPEN],
+                               DrawInfo->dri_Pens[BACKGROUNDPEN], JAM2);
+#ifndef OS30_ONLY
+               else
+               {
+                       SetAPen (rp, DrawInfo->dri_Pens[TEXTPEN]);
+                       SetBPen (rp, DrawInfo->dri_Pens[BACKGROUNDPEN]);
+                       SetDrMd (rp, JAM2);
+               }
+#endif /* !OS30_ONLY */
+       }
+
+       Move (rp, wud->Win->Width - twidth - 60, rp->TxBaseline + 1);
+       Text (rp, TitleInfo, tlen);
+
+       SetFont (rp, oldfont);
+}
+
+
+
+GLOBALCALL void UpdatePattern (void)
+{
+       struct WinUserData *wud = WDescr[WID_PATTERN].Wud;
+
+       if (wud && wud->Win)
+       {
+               struct SongInfo *si;
+               struct Pattern *patt;
+               ULONG currentinst;
+
+               if (si = xmLockActiveSong (SM_SHARED))
+               {
+                       patt = si->Patt[si->CurrentPatt];
+                       currentinst = si->CurrentInst;
+
+                       SPrintf (WindowTitle, "%s/%03ld: %s  (%ld/%ld)",
+                               si->Title,
+                               si->CurrentPatt,
+                               patt->Name ? patt->Name : (UBYTE *)"", patt->Tracks, patt->Lines);
+
+                       ReleaseSemaphore (&si->Lock);
+               }
+               else
+               {
+                       patt = NULL;
+                       currentinst = 0;
+                       strcpy (WindowTitle, "Pattern Editor");
+               }
+
+               SetWindowTitles (wud->Win, WindowTitle, NULL);
+
+               SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
+                       PEA_Pattern,            patt,
+                       PEA_CurrentInst,        currentinst,
+                       TAG_DONE);
+
+               SetGadgetAttrs (wud->Gadgets[GD_VSlider], wud->Win, NULL,
+                       PGA_Total,                      patt ? patt->Lines : 0,
+                       TAG_DONE);
+
+               SetGadgetAttrs (wud->Gadgets[GD_HSlider], wud->Win, NULL,
+                       PGA_Total,                      patt ? patt->Tracks : 0,
+                       TAG_DONE);
+       }
+
+       UpdatePattSize();
+}
+
+
+
+GLOBALCALL void UpdateEditorInst (void)
+{
+       struct WinUserData *wud = WDescr[WID_PATTERN].Wud;
+
+       if (wud && wud->Win)
+       {
+               struct SongInfo *si;
+
+               if (si = xmLockActiveSong (SM_SHARED))
+               {
+                       SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
+                               PEA_CurrentInst,        si->CurrentInst,
+                               TAG_DONE);
+                       ReleaseSemaphore (&si->Lock);
+               }
+       }
+}
+
+
+
+
+static void PatternLoad (STRPTR name, ULONG num, ULONG count)
+
+/* Load a pattern from an IFF PATT file.  This function is called only
+ * by the file requester handler.
+ */
+{
+       struct SongInfo *si;
+       struct IFFHandle *iff;
+       LONG err;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               LockWindows();
+
+               if (iff = AllocIFF())
+               {
+                       InitIFFasDOS (iff);
+                       if (iff->iff_Stream = (ULONG) Open (name, MODE_OLDFILE))
+                       {
+                               if (!(err = OpenIFF (iff, IFFF_READ)))
+                               {
+                                       LoadPattern (si, si->CurrentPatt + num, iff);
+                                       CloseIFF (iff);
+                               }
+
+                               Close (iff->iff_Stream);
+                       }
+                       else err = IoErr();
+
+                       FreeIFF (iff);
+               }
+               else err = ERROR_NO_FREE_STORE;
+
+               ReleaseSemaphore (&si->Lock);
+
+               UpdatePatternList(); // This will also update the Pattern Editor.
+
+               UnlockWindows();
+
+               LastErr = err;
+       }
+}
+
+
+
+static LONG PatternSave (STRPTR name, struct Pattern *patt)
+
+/* Store a pattern to an IFF PATT file. */
+{
+       struct IFFHandle        *iff;
+       LONG err;
+
+
+       if (iff = AllocIFF())
+       {
+               if (name)
+               {
+                       InitIFFasDOS (iff);
+                       iff->iff_Stream = (ULONG) Open (name, MODE_NEWFILE);
+               }
+               else /* Clipboard */
+               {
+                       InitIFFasClip (iff);
+                       iff->iff_Stream = (ULONG) OpenClipboard (PattSwitches.ClipboardUnit);
+               }
+
+               if (iff->iff_Stream)
+               {
+
+                       if (!(err = OpenIFF (iff, IFFF_WRITE)))
+                       {
+                               SavePattern (iff, patt);
+                               CloseIFF (iff);
+                       }
+
+                       if (name)
+                               Close (iff->iff_Stream);
+                       else
+                               CloseClipboard ((struct ClipboardHandle *)iff->iff_Stream);
+               }
+               else err = IoErr();
+
+               FreeIFF (iff);
+       }
+       else err = ERROR_NO_FREE_STORE;
+
+       return err;
+}
+
+
+
+/*****************/
+/* Pattern Menus */
+/*****************/
+
+static void PatternMiOpen (void)
+{
+       StartFileRequest (FREQ_LOADPATT, PatternLoad);
+}
+
+
+
+static void PatternMiSave (void)
+{
+       struct SongInfo *si;
+       struct Pattern *patt;
+
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               LockWindows();
+               patt = si->Patt[si->CurrentPatt];
+               LastErr = PatternSave (patt->Name, patt);
+               UnlockWindows();
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PatternMiSaveAs (void)
+{
+       struct SongInfo *si;
+       struct Pattern *patt;
+       UBYTE name[PATHNAME_MAX];
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               LockWindows();
+
+               patt = si->Patt[si->CurrentPatt];
+
+               strncpy (name, patt->Name, PATHNAME_MAX-1);
+               name[PATHNAME_MAX-1] = '\0';
+
+               if (FileRequest (FREQ_SAVEINST, name))
+                       LastErr = PatternSave (name, patt);
+
+               UnlockWindows();
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PatternMiSize (void)
+{
+       NewWindow (WID_PATTSIZE);
+}
+
+
+
+static void PatternMiMark (struct WinUserData *wud)
+{
+       SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
+               PEA_MarkRegion, -1,
+               TAG_DONE);
+}
+
+
+
+static void PatternMiCut (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+       struct Pattern *patt, *cpatt;
+       struct Rectangle markregion;
+       UWORD i;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               patt = si->Patt[si->CurrentPatt];
+
+               if (GetAttr (PEA_MarkRegion, wud->Gadgets[GD_PattEdit], (ULONG *)&markregion))
+               {
+                       if ((markregion.MaxX == 0) && (markregion.MaxY == 0))
+                               return; /* Not in mark mode */
+
+                       if (cpatt = xmAddPattern (si, -1,
+                               PATTA_Tracks,   markregion.MaxX - markregion.MinX + 1,
+                               PATTA_Lines,    0, /* Do not allocate anything */
+                               PATTA_Name,             patt->Name,
+                               TAG_DONE))
+                       {
+                               cpatt->Lines = markregion.MaxY - markregion.MinY + 1;
+
+                               for (i = 0; i < cpatt->Tracks; i++)
+                                       cpatt->Notes[i] = patt->Notes[i + markregion.MinX] + markregion.MinY;
+
+                               /* Snap marked region to Clipboard */
+                               PatternSave (NULL, cpatt);
+
+                               /* And then clear it */
+                               for (i = 0; i < cpatt->Tracks; i++)
+                                       memset (cpatt->Notes[i], 0, sizeof (struct Note) * cpatt->Lines);
+
+                               /* Free dummy pattern */
+                               cpatt->Lines = 0;
+                               for (i = 0; i < cpatt->Tracks; i++)
+                                       cpatt->Notes[i] = NULL;
+                               xmRemPattern (si, -1, (ULONG)cpatt);
+
+                               SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
+                                       PEA_Pattern,            patt,           /* Refresh display      */
+                                       PEA_MarkRegion,         NULL,           /* Stop marking         */
+                                       TAG_DONE);
+                       }
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PatternMiCopy (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+       struct Pattern *patt, *cpatt;
+       struct Rectangle markregion;
+       UWORD i;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               patt = si->Patt[si->CurrentPatt];
+
+               if (GetAttr (PEA_MarkRegion, wud->Gadgets[GD_PattEdit], (ULONG *)&markregion))
+               {
+                       if ((markregion.MaxX == 0) && (markregion.MaxY == 0))
+                               return; /* Not in mark mode */
+
+                       if (cpatt = xmAddPattern (si, -1,
+                               PATTA_Tracks,   markregion.MaxX - markregion.MinX + 1,
+                               PATTA_Lines,    0, /* Do not allocate anything */
+                               PATTA_Name,             patt->Name,
+                               TAG_DONE))
+                       {
+                               cpatt->Lines = markregion.MaxY - markregion.MinY + 1;
+
+                               for (i = 0; i < cpatt->Tracks; i++)
+                                       cpatt->Notes[i] = patt->Notes[i + markregion.MinX] + markregion.MinY;
+
+                               PatternSave (NULL, cpatt);
+
+                               /* Free dummy pattern */
+                               cpatt->Lines = 0;
+                               for (i = 0; i < cpatt->Tracks; i++)
+                                       cpatt->Notes[i] = NULL;
+                               xmRemPattern (si, -1, (ULONG)cpatt);
+
+                               SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
+                                       PEA_MarkRegion, NULL,   /* Stop marking */
+                                       TAG_DONE);
+                       }
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PatternMiPaste (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+       struct IFFHandle *iff;
+       struct Pattern *patt, *cpatt;
+       ULONG track, line;
+       UWORD i;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               patt = si->Patt[si->CurrentPatt];
+
+
+               /* Get pattern in Clip */
+
+               if (iff = AllocIFF())
+               {
+                       InitIFFasClip (iff);
+                       if (iff->iff_Stream = (ULONG) OpenClipboard (PattSwitches.ClipboardUnit))
+                       {
+                               if (!OpenIFF (iff, IFFF_READ))
+                               {
+                                       if (cpatt = LoadPattern (si, -1, iff))
+                                       {
+                                               if (GetAttr (PEA_CursTrack, wud->Gadgets[GD_PattEdit], &track) &&
+                                                       GetAttr (PEA_CursLine, wud->Gadgets[GD_PattEdit], &line))
+                                               {
+                                                       /* Paste it into the pattern */
+
+                                                       for (i = 0; i < min (cpatt->Tracks, patt->Tracks - track); i++)
+                                                               CopyMem (cpatt->Notes[i], patt->Notes[i + track] + line,
+                                                                       sizeof (struct Note) * min (patt->Lines - line, cpatt->Lines));
+
+                                                       SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
+                                                               PEA_Pattern,    patt,   /* Refresh display      */
+                                                               PEA_CursTrack,  min (track + cpatt->Tracks, patt->Tracks) - 1,
+                                                               PEA_CursLine,   min (line + cpatt->Lines, patt->Lines)  - 1,
+                                                               TAG_DONE);
+                                               }
+
+                                               xmRemPattern (si, -1, (ULONG)cpatt);
+                                       }
+                                       CloseIFF (iff);
+                               }
+
+                               CloseClipboard ((struct ClipboardHandle *)iff->iff_Stream);
+                       }
+
+                       FreeIFF (iff);
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PatternMiErase (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+       struct Pattern *patt;
+       struct Rectangle markregion;
+       UWORD i;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               patt = si->Patt[si->CurrentPatt];
+
+               if (GetAttr (PEA_MarkRegion, wud->Gadgets[GD_PattEdit], (ULONG *)&markregion))
+               {
+                       if ((markregion.MaxX == 0) && (markregion.MaxY == 0))
+                               return; /* Not in mark mode */
+
+                       /* Clear marked region */
+                       for (i = markregion.MinX; i <= markregion.MaxX; i++)
+                               memset (patt->Notes[i] + markregion.MinY, 0, sizeof (struct Note) *
+                                       (markregion.MaxY - markregion.MinY + 1));
+
+                       SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
+                               PEA_Pattern,    patt,   /* Refresh display      */
+                               PEA_MarkRegion, NULL,   /* Stop marking         */
+                               TAG_DONE);
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PatternMiUndo (struct WinUserData *wud)
+{
+       SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
+               PEA_UndoChange, 1,
+               TAG_DONE);
+}
+
+
+
+static void PatternMiRedo (struct WinUserData *wud)
+{
+       SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
+               PEA_UndoChange, -1,
+               TAG_DONE);
+}
+
+
+
+static void PatternMiSettings (void)
+{
+       NewWindow (WID_PATTPREFS);
+}
diff --git a/PlayWin.c b/PlayWin.c
new file mode 100644 (file)
index 0000000..78991c7
--- /dev/null
+++ b/PlayWin.c
@@ -0,0 +1,262 @@
+/*
+**     PlayWin.c
+**
+**     Copyright (C) 1995,96 Bernardo Innocenti
+**
+**     Play window handling functions.
+*/
+
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/imageclass.h>
+#include <libraries/gadtools.h>
+#include <dos/dostags.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/utility.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+#include "Player.h"
+#include "CustomClasses.h"
+
+
+
+/*****************************/
+/* Local function prototypes */
+/*****************************/
+
+static void PlayRewClicked (void);
+static void PlayPlayClicked (void);
+static void PlayFwdClicked (void);
+static void PlayStopClicked (void);
+static void PlayVolClicked (void);
+static void PlayRstClicked (void);
+
+
+
+enum {
+       GD_PlayGroup0,
+               GD_PlayGroup1,
+                       GD_PlayGroup2,
+                               GD_PlayGroup3,
+                                       GD_PlayRew,
+                                       GD_PlayPlay,
+                                       GD_PlayFwd,
+                                       GD_PlayStop,
+                               GD_PlayVol,
+                       GD_PlayGroup4,
+                               GD_PlayPos,
+                               GD_PlayGroup5,
+                                       GD_PlayTime,
+                                       GD_PlayTimeReset,
+
+       Play_CNT
+};
+
+
+
+static struct Process *PlayerProc = NULL;
+static struct MsgPort *PlayerPort = NULL;
+
+
+
+static LONG PlayArgs[] =
+{
+       HGROUP_KIND, 0,
+               VGROUP_KIND, 0,
+                       HGROUP_KIND, 0,
+                               IMAGEBUTTON_KIND,       (LONG)PlayRewClicked,   IM_REW,         TAG_DONE,
+                               IMAGEBUTTON_KIND,       (LONG)PlayPlayClicked,  IM_PLAY,        TAG_DONE,
+                               IMAGEBUTTON_KIND,       (LONG)PlayFwdClicked,   IM_FWD,         TAG_DONE,
+                               IMAGEBUTTON_KIND,       (LONG)PlayStopClicked,  IM_STOP,        TAG_DONE,
+                               ENDGROUP_KIND,
+                       SLIDER_KIND,    (LONG)PlayVolClicked, MSG_VOL_GAD, 0, 64, (LONG)"%lu", 3, TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       TEXT_KIND,      MSG_POS_GAD,    7,      GTTX_Border,    TRUE,   TAG_DONE,
+                       HGROUP_KIND, 0,
+                               TEXT_KIND,              MSG_TIME_GAD,   7,      GTTX_Border,    TRUE,   TAG_DONE,
+                               BUTTON_KIND,    (LONG)PlayRstClicked,   MSG_RST_GAD,            TAG_DONE,
+                               ENDGROUP_KIND,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG PlayWinTags[] =
+{
+       XMWIN_LayoutArgs,       (LONG)PlayArgs,
+       XMWIN_GCount,           Play_CNT,
+       XMWIN_Title,            MSG_PLAY_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW|BUTTONIDCMP|SLIDERIDCMP,
+       XMWIN_PostOpenFunc,     (LONG)SetupPlayer,
+//     XMWIN_PostOpenFunc,     (LONG)UpdatePlay,
+       XMWIN_PostCloseFunc,(LONG)CleanupPlayer,
+       XMWIN_HelpNode,         (LONG)"Play",
+       TAG_DONE
+};
+
+
+
+/*************************/
+/* Play window functions */
+/*************************/
+
+
+/*
+static void UpdatePlay (void)
+{
+
+}
+*/
+
+
+GLOBALCALL LONG SetupPlayer (void)
+{
+       LONG err;
+       BPTR PlayerSegList;
+
+       if (PlayerProc) return RETURN_OK;
+
+       if (PlayerPort = CreateMsgPort())
+       {
+               if (PlayerSegList = NewLoadSeg ("PROGDIR:Players/32Channels.player", NULL))
+               {
+                       if (PlayerProc = CreateNewProcTags (
+                               NP_Seglist,             PlayerSegList,
+                               NP_FreeSeglist, TRUE,
+                               NP_Name,                "XModule Player",
+                               NP_Priority,    25,
+                               NP_WindowPtr,   NULL,
+                               NP_Input,               NULL,
+                               NP_Output,              NULL,
+                               NP_Error,               NULL,
+                               NP_CopyVars,    FALSE,
+                               TAG_DONE))
+                       {
+                               struct PlayerCmd dummy;
+
+                               dummy.pcmd_Message.mn_ReplyPort = PlayerPort;
+                               dummy.pcmd_ID = PCMD_SETUP;
+
+                               PutMsg (&PlayerProc->pr_MsgPort, (struct Message *)&dummy);
+
+                               WaitPort (PlayerPort);
+                               GetMsg (PlayerPort);
+
+                               if (!dummy.pcmd_Err)
+                                       return RETURN_OK;
+
+                               /* Error cleanup */
+
+                               ShowRequest (MSG_PLAYER_INIT_ERR, 0, dummy.pcmd_Err);
+                               CleanupPlayer();
+                               return dummy.pcmd_Err;
+                       }
+                       else
+                               err = ERROR_NO_FREE_STORE;
+
+                       UnLoadSeg (PlayerSegList);
+               }
+               else
+                       CantOpenLib ("PROGDIR:Players/32Channels.player", 0);
+
+               DeleteMsgPort (PlayerPort);     PlayerPort = NULL;
+       }
+       else
+               err = ERROR_NO_FREE_STORE;
+
+       return err;
+}
+
+
+
+void CleanupPlayer (void)
+{
+       if (PlayerProc)
+       {
+               /* Signal player process to give up.  dos.library will
+                * automatically UnLoadSeg() its code.
+                */
+               Signal ((struct Task *)PlayerProc, SIGBREAKF_CTRL_C);
+               PlayerProc = NULL;
+
+               DeleteMsgPort (PlayerPort);     PlayerPort = NULL;
+       }
+}
+
+
+
+/****************/
+/* Play Gadgets */
+/****************/
+
+static void PlayPlayClicked (void)
+{
+       ShowRequestStr ("Enjoy the silence...\n\n"
+               "Sorry, XModule replay code is still under developement.\n"
+               "I'm adapting the DeliTracker 14bit-NotePlayer mixing engine\n"
+               "to XModule's internal module format, which will allow\n"
+               "upto 32 channels with 14bit stereo output at 56Khz max,\n"
+               "provided your machine is fast enough.",
+               "Incredible!", NULL);
+}
+
+
+
+static void PlayRewClicked (void)
+{
+/*
+       struct PlayerCmd cmd;
+
+       cmd.pcmd_Message.mn_ReplyPort = PlayerPort;
+       cmd.pcmd_ID = PCMD_INIT;
+       cmd.pcmd_Data = songinfo;
+
+       PutMsg (&PlayerProc->pr_MsgPort, (struct Message *)&cmd);
+
+       WaitPort (PlayerPort);
+       GetMsg (PlayerPort);
+
+       ShowString ("%ld", &cmd.pcmd_Err);
+*/
+}
+
+
+static void PlayFwdClicked (void)
+{
+/*
+       struct PlayerCmd cmd;
+
+       cmd.pcmd_Message.mn_ReplyPort = PlayerPort;
+       cmd.pcmd_ID = PCMD_PLAY;
+       cmd.pcmd_Data = songinfo;
+
+       PutMsg (&PlayerProc->pr_MsgPort, (struct Message *)&cmd);
+
+       WaitPort (PlayerPort);
+       GetMsg (PlayerPort);
+*/
+}
+
+
+static void PlayStopClicked (void)
+{
+}
+
+
+static void PlayVolClicked (void)
+{
+}
+
+
+static void PlayRstClicked (void)
+{
+}
diff --git a/Player.asm b/Player.asm
new file mode 100644 (file)
index 0000000..b8e78e4
--- /dev/null
@@ -0,0 +1,3251 @@
+**
+**     XModule replay routine 0.1
+**
+**     Copyright (C) 1995,96 Bernardo Innocenti
+**
+**
+
+DEBUG_VERSION  EQU     0       ; Debug Code On/Off
+USE_CALIBRATION        EQU     0       ; Calibration On/Off
+STAND_ALONE    EQU     0       ; Start without XModule
+
+MAXCHANNELS    EQU     32
+
+       MACHINE 68020
+
+VersionTag MACRO
+       dc.b    '$VER: XModule_Replay 0.1 (6.1.96) © 1995,96 by Bernardo Innocenti.',$a
+       endm
+
+
+       incdir  Include:
+
+       include exec/types.i
+
+       include exec/io.i
+       include exec/macros.i
+       include exec/memory.i
+       include exec/tasks.i
+       include exec/execbase.i
+       include exec/funcdef.i
+       include hardware/intbits.i
+       include hardware/custom.i
+       include devices/audio.i
+       include dos/dos.i
+       include dos/dosextens.i
+       include graphics/gfxbase.i
+
+       include libraries/songclass.i
+
+       include Guru.i
+       include Player.i
+
+
+       xref    _DATA_BAS_
+
+custom EQU     $dff000
+
+
+ShowDebug MACRO
+       IFNE    '\2',''
+       move.l  \2,-(sp)
+       ENDC
+       lea             .msg\@(pc),a0
+       move.l  sp,a1                   ; Arguments array
+       xref    KPrintF
+       jsr             KPrintF
+       IFNE    '\2',''
+       lea             4(sp),sp        ; Fix stack
+       ENDC
+       bra.s   .cont
+.msg\@
+       dc.b    \1,$a,0
+       even
+.cont
+       ENDM
+
+* An harmless Enforcer hit to help debugging operations :-)
+Enforcer MACRO
+       suba.l  \1,\1
+       clr.l   (\1)
+       ENDM
+
+
+
+***************************************************************************
+* Start
+***************************************************************************
+
+       SECTION XMPlay,code
+
+
+Start:
+       lea     _DATA_BAS_+32766,a4
+       NEAR    a4
+
+* Initialize SysBase
+       move.l  4.w,a6
+       move    a6,_SysBase(a4)
+
+* D3 is the return code for the startup message
+       moveq   #RETURN_OK,d3
+
+* Find this task's process structure
+       suba.l  a1,a1
+       JSRLIB  FindTask
+       move.l  d0,MyTask(a4)
+       move.l  d0,a2
+
+* Calculate ClockBase
+       move.l  ex_EClockFrequency(a6),d0
+       move.l  d0,d1
+       lsl.l   #2,d1                           ; #3 for panning support
+       add.l   d1,d0
+       move.l  d0,ClockBase                    ; Clock constant
+
+* Open graphics.library
+       lea     GFXName(pc),a1
+       moveq.l #0,d0                           ; Library version
+       JSRLIB  OpenLibrary
+       move.l  d0,_GFXBase(a4)
+       bne.s   .gfx_ok
+
+       move.l  #ERROR_INVALID_RESIDENT_LIBRARY,d3
+.gfx_ok
+
+       IFEQ    STAND_ALONE
+
+* Wait for startup message
+       lea     pr_MsgPort(a2),a2       ; a2 still contains our Process structure.
+       move.l  a2,a0
+       JSRLIB  WaitPort
+
+* Get startup message
+       move.l  a2,a0
+       JSRLIB  GetMsg
+       move.l  d0,a1
+
+* Get parent task address
+       move.l  MN_REPLYPORT(a1),a0
+       move.l  MP_SIGTASK(a0),ParentTask(a4)
+
+* Reply startup message
+       move.l  d3,pcmd_Err(a1)         ; Put returncode
+       JSRLIB  ReplyMsg
+
+       ENDC    STAND_ALONE
+
+       tst.l   d3                              ; Error?
+       bne.s   Exit
+
+* Go ahead to MainLoop
+
+
+***************************************************************************
+* Main loop
+***************************************************************************
+
+MainLoop:
+
+       IFEQ    STAND_ALONE
+
+       moveq   #0,d1
+       moveq   #0,d0
+       move.b  MP_SIGBIT(a2),d1
+       bset.l  d1,d0
+       bset.l  #SIGBREAKB_CTRL_C,d0
+       JSRLIB  Wait
+       move.l  d0,d2                           ; Save signal bits
+
+* Check message port
+.checkmsg
+       move.l  a2,a0
+       JSRLIB  GetMsg
+       tst.l   d0
+       beq.s   .nomsg
+       move.l  d0,a3
+
+       ELSE
+
+       moveq   #0,d0
+
+       ENDC    STAND_ALONE
+
+* Command Dispatcher
+       move.l  pcmd_ID(a3),d0
+       cmp.l   #PCMD_COUNT,d0                  ; Check if command is supported
+       bge.s   .unknown_msg
+
+* Switch (pcmd_ID)
+       lsl.l   #2,d0
+       move.l  a3,a0
+       move.l  .jumptable(pc,d0),a1
+       jsr             (a1)
+
+.reply_msg
+       IFEQ    STAND_ALONE
+       move.l  d0,pcmd_Err(a3)                 ; Set returncode
+       move.l  a3,a1
+       JSRLIB  ReplyMsg                        ; Return message to parent...
+
+       bra.s   .checkmsg                       ; ...and check for more messages
+       ELSE
+       bra.s   Exit
+       ENDC    STAND_ALONE
+
+.unknown_msg
+       moveq   #PERR_UNKNOWN_COMMAND,d0
+       bra.s   .reply_msg
+
+.jumptable
+       dc.l    CmdInit
+       dc.l    CmdPlay
+
+.nomsg
+
+* Check for abort signal
+       btst.l  #SIGBREAKB_CTRL_C,d2
+       beq.s   MainLoop
+
+* Note: I'm falling down to Exit.
+
+
+
+***************************************************************************
+* Cleanup and Exit
+***************************************************************************
+
+Exit:
+
+* Close graphics.library
+
+       move.l  _GFXBase(a4),d0
+       beq.s   .noclose
+       move.l  d0,a1
+       JSRLIB  CloseLibrary
+.noclose
+
+
+* Exit point.
+* This rts will remove our process and call UnLoadSeg() on us.
+
+       rts
+
+       VersionTag
+
+GFXName                dc.b    'graphics.library',0
+AudioDevName   dc.b    'audio.device',0
+
+       EVEN
+
+
+
+***************************************************************************
+* LONG error = CmdInit (struct PlayerCmd *pcmd, struct ExecBase *SysBase)
+* D0                    A0                      A6
+***************************************************************************
+
+CmdInit
+       move.l  a3,-(a7)                                ; Save a3
+       move.l  pcmd_Data(a0),a3                ; get SongInfo
+
+* Allocate audio channels
+       moveq   #0,d0
+       moveq   #0,d1
+       lea     AudioDevName(pc),a0
+       lea     AllocIORequest(a4),a1
+       JSRLIB  OpenDevice
+       tst.l   d0
+       bne     .AllocFail                      ; Error
+
+* Set audio interrupts
+       move.w  AttnFlags(a6),d0
+       btst.l  #AFB_68020,d0                   ; >= 68020 CPU ?
+       bne.s   .CPU68020                       ; yes !
+
+.CPU68000
+       moveq   #INTB_AUD0,d0
+       lea     Audio0IRQ_000(a4),a1
+       JSRLIB  SetIntVector                    ; set new Audio0-IRQ
+       move.l  d0,_OldAudio0(a4)
+
+       moveq   #INTB_AUD1,d0
+       lea     Audio1IRQ_000(a4),a1
+       JSRLIB  SetIntVector                    ; set new Audio1-IRQ
+       move.l  d0,_OldAudio1(a4)
+       bra.s   .CalcScale
+
+.CPU68020
+       moveq   #INTB_AUD0,d0
+       lea     Audio0IRQ_020(a4),a1
+       JSRLIB  SetIntVector                    ; set new Audio0-IRQ
+       move.l  d0,_OldAudio0(a4)
+
+       moveq   #INTB_AUD1,d0
+       lea     Audio1IRQ_020(a4),a1
+       JSRLIB  SetIntVector                    ; set new Audio1-IRQ
+       move.l  d0,_OldAudio1(a4)
+
+.CalcScale
+
+* Initialize ScaleFactor
+       move.w  si_MaxTracks(a3),d1
+       move.w  d1,ChannelNum(a4)
+       lsr.w   #1,d1                           ; !!!
+       moveq   #32,d0                          ; max. channels (16 for no panning!)
+       divu    d1,d0
+       move.w  d0,ScaleFactor                  ; set ScaleFactor
+
+* Initialize player.  Warning: these calls will trash all registrers!
+       movem.l d2-d7/a2-a6,-(a7)
+       jsr     InitSVolTable(pc)
+       jsr     InitBoostTable(pc)
+       jsr     InitTracksNChannels(pc)
+       bsr     NewMixFreq                      ; MixPeriod berechnen
+       movem.l (sp)+,d2-d7/a2-a6
+
+* Audio hardware twiddling
+       lea     custom,a0
+       move.w  #$000f,dmacon(a0)               ; Audio-DMA aus
+       move.w  #$00ff,adkcon(a0)
+       move.w  #$0780,intreq(a0)               ; Audio-Req. löschen
+       move.w  #$0780,intena(a0)               ; Audio-Int. aus
+
+       moveq   #RETURN_OK,d0
+       move.l  (a7)+,a3
+       rts
+
+.AllocFail
+       moveq   #PERR_NO_AUDIO,d0
+       move.l  (a7)+,a3
+       rts
+
+
+
+***************************************************************************
+* LONG error = CmdPlay (struct PlayerCmd *pcmd, struct ExecBase *SysBase)
+* D0                    A0                      A6
+***************************************************************************
+
+CmdPlay
+       move.l  pcmd_Data(a0),a0                                ; get SongInfo
+       move.l  a0,SongInfo
+       move.w  si_MaxTracks(a0),mt_NumCh(a4)   ; set channels
+
+       lea     mt_ChanTemp,a0
+       moveq   #MAXCHANNELS-1,d0
+       moveq   #0,d1
+mt_clear
+       move.l  d1,(a0)+
+       move.l  d1,(a0)+
+       move.l  d1,(a0)+
+       move.l  d1,(a0)+
+       move.l  d1,(a0)+
+       move.l  d1,(a0)+
+       move.l  d1,(a0)+
+       move.l  d1,(a0)+
+       move.l  d1,(a0)+
+       move.l  d1,(a0)+
+       dbra    d0,mt_clear
+
+       bsr     mt_init2
+
+       moveq   #RETURN_OK,d0
+       rts
+
+
+       FAR
+
+
+
+***************************************************************************
+* void  SetTimer (timeval)
+*                 D0
+***************************************************************************
+
+SetTimer
+       RTS                     ; !!!
+
+
+***************************************************************************
+* void  SetTimer (void)
+*
+***************************************************************************
+
+SongEnd
+       RTS                     ; !!!
+
+
+*-----------------------------------------------------------------------*
+;
+; Next Pattern
+
+NPatt
+;      move.l  dtg_StopInt(a5),a0
+;      jsr     (a0)                    !!!!!!!!!!!!!!!!!!!
+       clr.w   mt_counter
+       clr.w   mt_PBreakPos
+       clr.b   mt_PosJumpFlag
+       clr.w   mt_PatternPos
+       addq.w  #1,mt_SongPos
+       move.w  mt_MaxPos,d0
+       cmp.w   mt_SongPos,d0
+       bne.s   NEnd
+       clr.w   mt_SongPos
+NEnd
+;      move.l  dtg_StartInt(a5),a0
+;      jsr     (a0)                    !!!!!!!!!!!!!!!!!!!
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Previous Pattern
+
+PPatt
+;      move.l  dtg_StopInt(a5),a0      !!!!!!!!!!!!!!!!!!!
+;      jsr     (a0)
+       clr.w   mt_counter
+       clr.w   mt_PBreakPos
+       clr.b   mt_PosJumpFlag
+       clr.w   mt_PatternPos
+       subq.w  #1,mt_SongPos
+       bcc.s   PEnd
+       move.w  mt_MaxPos,d0
+       subq.w  #1,d0
+       move.w  d0,mt_SongPos
+PEnd
+;      move.l  dtg_StartInt(a5),a0     !!!!!!!!!!!!!!!!!!
+;      jsr     (a0)
+       rts
+
+
+
+*-----------------------------------------------------------------------*
+;
+; TakeTracker - Replay
+
+;********************************************
+;* ----- Protracker V1.1B Playroutine ----- *
+;* Lars "Zap" Hamre/Amiga Freelancers 1991  *
+;* Bekkeliveien 10, N-2010 STRØMMEN, Norway *
+;********************************************
+
+;---- Tempo ----
+
+SetTempo
+       move.l  _SysBase,a4
+       move.l  ex_EClockFrequency(a4),d1
+       add.l   d1,d1
+       add.l   d1,d1
+       add.l   ex_EClockFrequency(a4),d1
+       lsr.l   #1,d1
+       cmp.w   #32,d0
+       bhs.s   setemsk
+       moveq   #32,d0
+setemsk        divu    d0,d1
+       move.l  d1,d0
+       jsr             SetTimer
+       rts
+
+;---- Playroutine ----
+
+       STRUCTURE PlayNote,0
+       UBYTE   pn_Note
+       UBYTE   pn_Inst
+       UBYTE   pn_Cmd
+       UBYTE   pn_Val
+       APTR    pn_Start
+       ULONG   pn_Length
+       LONG    pn_LoopStart
+       LONG    pn_Replen
+       ULONG   pn_WaveStart
+       ULONG   pn_RealLength
+       UWORD   pn_Period
+       UWORD   pn_WantedPeriod
+       UBYTE   pn_FineTune
+       UBYTE   pn_Volume
+       UBYTE   pn_TonePortDirec
+       UBYTE   pn_TonePortSpeed
+       UBYTE   pn_VibratoCmd
+       UBYTE   pn_VibratoPos
+       UBYTE   pn_TremoloCmd
+       UBYTE   pn_TremoloPos
+       UBYTE   pn_WaveControl
+       UBYTE   pn_GlissFunk
+       UBYTE   pn_SampleOffset
+       UBYTE   pn_PattPos
+       UBYTE   pn_LoopCount
+       UBYTE   pn_FunkOffset
+       LABEL   pn_SIZEOF
+
+
+mt_init2
+
+       MOVE.L  songinfo,a0
+       move.w  si_Length(a0),mt_MaxPos
+
+       moveq   #0,d0
+       MOVE.W  si_GlobalTempo(a0),d0
+       BSR     SetTempo
+
+       MOVE.W  si_GlobalSpeed(a0),mt_speed
+       CLR.W   mt_counter
+       CLR.W   mt_SongPos
+       CLR.W   mt_PBreakPos
+       CLR.B   mt_PosJumpFlag
+       CLR.B   mt_PBreakFlag
+       CLR.B   mt_LowMask
+       CLR.B   mt_PattDelTime
+       CLR.B   mt_PattDelTime2
+       CLR.B   mt_Reset
+       CLR.W   mt_PatternPos
+
+       RTS
+
+mt_music
+       MOVEM.L D2-D7/A2-A6,-(SP)
+       TST.B   mt_Reset
+       BEQ.S   mt_mus
+       JSR     SongEnd
+       SUBQ.B  #1,mt_Reset
+       BEQ     mt_exit
+       BSR     mt_init2
+       CLR.B   mt_Reset
+       BRA     mt_exit
+mt_mus
+       ADDQ.W  #1,mt_counter
+       MOVE.W  mt_counter,d0
+       CMP.W   mt_speed,d0
+       BLO.S   mt_NoNewNote
+       CLR.W   mt_counter
+       TST.B   mt_PattDelTime2
+       BEQ     mt_GetNewNote
+       BSR.S   mt_NoNewAllChannels
+       BRA     mt_dskip
+
+mt_NoNewNote
+       BSR.S   mt_NoNewAllChannels
+       BRA     mt_NoNewPosYet
+
+mt_NoNewAllChannels
+       move.l  NoteStructure,a5
+       lea     mt_ChanTemp,a6
+       move.w  mt_NumCh,d5
+       subq.w  #1,d5
+mt_NoNewLoop
+       bsr     mt_CheckEfx
+       lea     NoteChannel_SIZE(a5),a5
+       lea     pn_SIZEOF(a6),a6
+       dbra    d5,mt_NoNewLoop
+       RTS
+
+mt_GetNewNote
+       move.l  songinfo,a0
+       move.l  si_Patt(a0),a1
+       move.l  si_Sequence(a0),a0
+       moveq   #0,d0
+       move.w  mt_SongPos,d0
+       add.w   d0,d0
+       move.w  (a0,d0.w),d0                    ; Put current patt number in d0
+       mulu    #pa_SIZEOF,d0
+       move.l  pa_Notes(a0,d0),a0                      ; Get pointer to tracks array
+
+       move.l  NoteStructure,a5
+       lea     mt_ChanTemp,a6
+       move.w  mt_NumCh,d5
+       subq.w  #1,d5
+
+mt_GetNewLoop
+       bsr     mt_PlayVoice
+       lea     NoteChannel_SIZE(a5),a5
+       lea     pn_SIZEOF(a6),a6
+       dbra    d5,mt_GetNewLoop
+       BRA     mt_dskip
+
+mt_PlayVoice
+       CMP.W   #NCHD_Ignore,nch_Stereo(a5)
+       BEQ     mt_PerNop
+       TST.L   (A6)
+       BNE.S   mt_plvskip
+       BSR     mt_PerNop
+mt_plvskip
+       move.l  (a0)+,a4
+       moveq   #0,d0
+       move.w  d5,d0
+       add.l   d0,d0
+       add.l   d0,d0
+       move.l  (a4,d0),a4              ; Pointer to channel
+       moveq   #0,d0
+       move.w  mt_PatternPos,d0
+       add.l   d0,d0
+       add.l   d0,d0
+       move.l  (a4,d0),(a6)            ; Current note in (a6)
+
+       moveq   #0,d2
+       move.b  note_Inst(a6),d2        ; Instrument number in d2
+       TST.W   d2
+       BEQ     mt_SetRegs
+       MOVEQ   #0,d3
+
+* Retrieve Instrument structure
+       move.l  songinfo,a1
+       lea     si_Instr(a1),a1
+       mulu    #in_SIZEOF,d2
+       lea     (a1,d2),a1
+
+       move.l  in_SampleData(a1),pn_Start(a6)
+       move.l  in_Length(a1),pn_Length(a6)
+       move.l  in_Length(a1),pn_RealLength(a6)
+       move.b  in_FineTune+1(a1),pn_FineTune(a6)
+       move.b  in_Volume(a1),pn_Volume(a6)
+       move.l  in_Repeat(a1),d3                ; Get repeat
+       tst.l   D3
+       beq.s   mt_NoLoop
+       move.l  pn_Start(a6),d2                 ; Get start
+       add.l   D3,D2                           ; Add repeat
+       move.l  D2,pn_LoopStart(a6)
+       move.l  D2,pn_WaveStart(a6)
+       move.l  in_Repeat(a1),d0                ; Get repeat
+       add.l   in_Replen(a1),d0                ; Add replen
+       move.l  d0,pn_Length(A6)
+       move.l  in_Replen(a1),pn_Replen(a6)     ; Save replen
+       MOVEQ   #0,D0
+       MOVE.B  pn_Volume(A6),D0
+       move.w  d0,nch_Volume(a5)
+       bset.b  #NCHB_Volume,nch_Changed(a5)
+       BRA.S   mt_SetRegs
+
+mt_NoLoop
+       MOVE.L  pn_Start(a6),d2
+       ADD.L   D3,D2
+       MOVE.L  D2,pn_LoopStart(A6)
+       MOVE.L  D2,pn_WaveStart(A6)
+       MOVE.L  in_Replen(a1),pn_Replen(a6)     ; Save replen
+       MOVEQ   #0,D0
+       MOVE.B  pn_Volume(A6),D0
+       move.w  d0,nch_Volume(a5)
+       bset.b  #NCHB_Volume,nch_Changed(a5)
+
+mt_SetRegs
+       move.b  pn_Note(a6),d0
+       beq     mt_CheckMoreEfx2        ; If no note
+       move.w  pn_Cmd(a6),D0
+       AND.W   #$FFF0,D0
+       CMP.W   #$0E50,D0               ; Check for E5
+       beq.s   mt_DoSetFineTune
+       move.b  pn_Cmd(A6),D0
+       cmp.b   #EFF_TONEPORTAMENTO,d0  ; TonePortamento
+       beq.s   mt_ChkTonePorta
+       cmp.b   #EFF_TONEPVOLSLIDE,d0
+       beq.s   mt_ChkTonePorta
+       cmp.b   #EFF_SAMPLEOFFSET,d0    ; Sample Offset
+       BNE.S   mt_SetPeriod
+       BSR     mt_CheckMoreEfx
+       BRA.S   mt_SetPeriod
+
+mt_DoSetFineTune
+       bsr     mt_SetFineTune
+       bra.s   mt_SetPeriod
+
+mt_ChkTonePorta
+       bsr     mt_SetTonePorta
+       bra     mt_CheckMoreEfx2
+
+mt_SetPeriod
+       MOVEM.L D0-D1/A0-A1,-(SP)
+       moveq   #0,d1
+       move.b  pn_Note(a6),d1                  ; Find note period
+       sub.b   #24,d1                          ; Less octaves!!!
+       add.l   d1,d1                           ; Look up period table
+       move.l  d1,d0
+       lea     mt_PeriodTable,a1
+
+       MOVEQ   #0,D1
+       move.b  pn_FineTune(a6),d1
+       MULU    #MAXNOTES*2,D1
+       ADD.L   D1,A1
+       move.w  (a1,d0.w),pn_Period(a6)
+       movem.l (sp)+,d0-d1/a0-a1
+
+       MOVE.W  2(A6),D0
+       AND.W   #$0FF0,D0
+       CMP.W   #$0ED0,D0 ; Notedelay
+       BEQ     mt_CheckMoreEfx2
+
+       BTST    #2,pn_WaveControl(A6)
+       BNE.S   mt_vibnoc
+       CLR.B   pn_VibratoPos(A6)
+mt_vibnoc
+       BTST    #6,pn_WaveControl(A6)
+       BNE.S   mt_trenoc
+       CLR.B   pn_TremoloPos(A6)
+mt_trenoc
+       move.l  pn_Start(a6),nch_SampleStart(a5)
+       move.l  pn_Length(a6),d0
+       move.l  d0,nch_SampleLength(a5)
+       bset.b  #NCHB_Sample,nch_Changed(a5)
+       move.w  pn_Period(A6),nch_Frequency(a5)
+       bset.b  #NCHB_Frequency,nch_Changed(a5)
+mt_CheckMoreEfx2
+       move.l  pn_LoopStart(A6),nch_RepeatStart(a5)
+       move.l  pn_Replen(A6),d0
+       move.l  d0,nch_RepeatLength(a5)
+       bset.b  #NCHB_Repeat,nch_Changed(a5)
+
+       bra     mt_CheckMoreEfx
+mt_dskip
+       MOVE.W  mt_rsize,d0                     ; !!!
+       ADD.W   d0,mt_PatternPos
+       MOVE.B  mt_PattDelTime,d0
+       BEQ.S   mt_dskc
+       MOVE.B  d0,mt_PattDelTime2
+       CLR.B   mt_PattDelTime
+mt_dskc        TST.B   mt_PattDelTime2
+       BEQ.S   mt_dska
+       SUBQ.B  #1,mt_PattDelTime2
+       BEQ.S   mt_dska
+       MOVE.W  mt_rsize,D0
+       SUB.W   D0,mt_PatternPos
+mt_dska        TST.B   mt_PBreakFlag
+       BEQ.S   mt_nnpysk
+       SF      mt_PBreakFlag
+       MOVEQ   #0,D0
+       MOVE.W  mt_PBreakPos,D0
+       CLR.W   mt_PBreakPos
+       MULU    mt_rsize,D0
+       MOVE.W  D0,mt_PatternPos
+mt_nnpysk
+       MOVE.W  mt_PatternPos,d0
+       CMP.W   mt_psize,d0
+       BLO.S   mt_NoNewPosYet
+mt_NextPosition
+       MOVE.W  mt_PBreakPos,d0
+       MULU    mt_rsize,d0
+       MOVE.W  D0,mt_PatternPos
+       CLR.W   mt_PBreakPos
+       CLR.B   mt_PosJumpFlag
+       ADDQ.W  #1,mt_SongPos
+       MOVE.W  mt_SongPos,d1
+       CMP.W   mt_MaxPos,d1
+       BLO.S   mt_NoNewPosYet
+       MOVE.W  mt_StartPos,mt_SongPos
+       MOVE.B  #2,mt_Reset
+mt_NoNewPosYet
+       TST.B   mt_PosJumpFlag
+       BNE.S   mt_NextPosition
+       jsr     NoteSignal
+mt_exit        MOVEM.L (SP)+,D2-D7/A2-A6
+       RTS
+
+mt_CheckEfx
+       CMP.W   #NCHD_Ignore,nch_Stereo(a5)
+       BEQ     mt_Return
+       BSR     mt_UpdateFunk
+       moveq   #0,d0
+       move.b  pn_Cmd(a6),d0
+       beq.s   mt_PerNop
+       cmpi.b  #EFF_COUNT,d0
+       bge.s   mt_PerNop
+       move.l  efftable(pc),a0
+       add.l   d0,d0
+       add.l   d0,d0
+       move.l  (a0,d0),a0
+       jmp     (a0)
+
+SetBack        move.w  pn_Period(a6),nch_Frequency(a5)
+       bset.b  #NCHB_Frequency,nch_Changed(a5)
+mt_Return
+       RTS
+
+efftable
+       dc.l mt_PerNop                  ; EFF_NULL
+       dc.l mt_PortaUp                 ; EFF_PORTAMENTOUP
+       dc.l mt_PortaDown               ; EFF_PORTAMENTODOWN
+       dc.l mt_TonePortamento          ; EFF_TONEPORTAMENTO
+       dc.l mt_Vibrato                 ; EFF_VIBRATO
+       dc.l mt_TonePlusVolSlide        ; EFF_TONEPVOLSLIDE
+       dc.l mt_VibratoPlusVolSlide     ; EFF_VIBRATOVOLSLIDE
+       dc.l mt_Tremolo                 ; EFF_TREMOLO
+       dc.l mt_PerNop                  ; EFF_UNUSED
+       dc.l mt_PerNop                  ; EFF_SAMPLEOFFSET
+       dc.l mt_VolumeSlide             ; EFF_VOLSLIDE
+       dc.l mt_PerNop                  ; EFF_POSJUMP
+       dc.l mt_PerNop                  ; EFF_SETVOLUME
+       dc.l mt_PerNop                  ; EFF_PATTERNBREAK
+       dc.l mt_E_Commands              ; EFF_MISC
+       dc.l mt_PerNop                  ; EFF_SETSPEED
+       dc.l mt_PerNop                  ; EFF_SETTEMPO
+       dc.l mt_Arpeggio                ; EFF_ARPEGGIO
+
+
+mt_PerNop
+       move.w  pn_Period(A6),nch_Frequency(a5)
+       bset.b  #NCHB_Frequency,nch_Changed(a5)
+       RTS
+
+mt_Arpeggio
+       MOVEQ   #0,D0
+       MOVE.W  mt_counter,D0
+       DIVS    #3,D0
+       SWAP    D0
+       CMP.W   #0,D0
+       BEQ.S   mt_Arpeggio2
+       CMP.W   #2,D0
+       BEQ.S   mt_Arpeggio1
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       LSR.B   #4,D0
+       BRA.S   mt_Arpeggio3
+
+mt_Arpeggio1
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #15,D0
+       BRA.S   mt_Arpeggio3
+
+mt_Arpeggio2
+       MOVE.W  pn_Period(A6),D2
+       BRA.S   mt_Arpeggio4
+
+mt_Arpeggio3
+       ASL.W   #1,D0
+       MOVEQ   #0,D1
+       MOVE.B  pn_FineTune(A6),D1
+       MULU    #36*2,D1
+       LEA     mt_PeriodTable,A0
+       ADD.L   D1,A0
+       MOVEQ   #0,D1
+       MOVE.W  pn_Period(A6),D1
+       MOVEQ   #35,D3                          ; bugfix !!!
+mt_arploop
+       MOVE.W  (A0,D0.W),D2
+       CMP.W   (A0),D1
+       BHS.S   mt_Arpeggio4
+       ADDQ.L  #2,A0
+       DBRA    D3,mt_arploop
+       RTS
+
+mt_Arpeggio4
+       move.w  d2,nch_Frequency(a5)
+       bset.b  #NCHB_Frequency,nch_Changed(a5)
+;      MOVE.W  D2,6(A5)
+       RTS
+
+mt_FinePortaUp
+       TST.W   mt_counter
+       BNE     mt_Return
+       MOVE.B  #$0F,mt_LowMask
+mt_PortaUp
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   mt_LowMask,D0
+       MOVE.B  #$FF,mt_LowMask
+       SUB.W   D0,pn_Period(A6)
+       MOVE.W  pn_Period(A6),D0
+       AND.W   #$0FFF,D0
+       CMP.W   #113,D0
+       BPL.S   mt_PortaUskip
+       AND.W   #$F000,pn_Period(A6)
+       OR.W    #113,pn_Period(A6)
+mt_PortaUskip
+       MOVE.W  pn_Period(A6),D0
+       AND.W   #$0FFF,D0
+       move.w  d0,nch_Frequency(a5)
+       bset.b  #NCHB_Frequency,nch_Changed(a5)
+       RTS
+
+mt_FinePortaDown
+       TST.W   mt_counter
+       BNE     mt_Return
+       MOVE.B  #$0F,mt_LowMask
+mt_PortaDown
+       CLR.W   D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   mt_LowMask,D0
+       MOVE.B  #$FF,mt_LowMask
+       ADD.W   D0,pn_Period(A6)
+       MOVE.W  pn_Period(A6),D0
+       AND.W   #$0FFF,D0
+       CMP.W   #856,D0
+       BMI.S   mt_PortaDskip
+       AND.W   #$F000,pn_Period(A6)
+       OR.W    #856,pn_Period(A6)
+mt_PortaDskip
+       MOVE.W  pn_Period(A6),D0
+       AND.W   #$0FFF,D0
+       move.w  d0,nch_Frequency(a5)
+       bset.b  #NCHB_Frequency,nch_Changed(a5)
+       RTS
+
+mt_SetTonePorta
+       MOVE.L  a0,-(sp)
+       MOVE.W  (A6),D2
+       AND.W   #$0FFF,D2
+       MOVEQ   #0,D0
+       MOVE.B  pn_FineTune(A6),D0
+       MULU    #36*2,D0                        ; bugfix !!!
+       LEA     mt_PeriodTable,A0
+       ADD.L   D0,A0
+       MOVEQ   #0,D0
+mt_StpLoop
+       CMP.W   (A0,D0.W),D2
+       BHS.S   mt_StpFound
+       ADDQ.W  #2,D0
+       CMP.W   #36*2,D0                        ; bugfix !!!
+       BLO.S   mt_StpLoop
+       MOVEQ   #35*2,D0
+mt_StpFound
+       MOVE.B  pn_FineTune(A6),D2
+       AND.B   #8,D2
+       BEQ.S   mt_StpGoss
+       TST.W   D0
+       BEQ.S   mt_StpGoss
+       SUBQ.W  #2,D0
+mt_StpGoss
+       MOVE.W  (A0,D0.W),D2
+       MOVE.L  (SP)+,A0
+       MOVE.W  D2,pn_WantedPeriod(A6)
+       MOVE.W  pn_Period(A6),D0
+       CLR.B   pn_TonePortDirec(A6)
+       CMP.W   D0,D2
+       BEQ.S   mt_ClearTonePorta
+       BGE     mt_Return
+       MOVE.B  #1,pn_TonePortDirec(A6)
+       RTS
+
+mt_ClearTonePorta
+       CLR.W   pn_WantedPeriod(A6)
+       RTS
+
+mt_TonePortamento
+       MOVE.B  pn_Val(A6),D0
+       BEQ.S   mt_TonePortNoChange
+       MOVE.B  D0,pn_TonePortSpeed(A6)
+       CLR.B   pn_Val(A6)
+mt_TonePortNoChange
+       TST.W   pn_WantedPeriod(A6)
+       BEQ     mt_Return
+       MOVEQ   #0,D0
+       MOVE.B  pn_TonePortSpeed(A6),D0
+       TST.B   pn_TonePortDirec(A6)
+       BNE.S   mt_TonePortaUp
+mt_TonePortaDown
+       ADD.W   D0,pn_Period(A6)
+       MOVE.W  pn_WantedPeriod(A6),D0
+       CMP.W   pn_Period(A6),D0
+       BGT.S   mt_TonePortaSetPer
+       MOVE.W  pn_WantedPeriod(A6),pn_Period(A6)
+       CLR.W   pn_WantedPeriod(A6)
+       BRA.S   mt_TonePortaSetPer
+
+mt_TonePortaUp
+       SUB.W   D0,pn_Period(A6)
+       MOVE.W  pn_WantedPeriod(A6),D0
+       CMP.W   pn_Period(A6),D0
+       BLT.S   mt_TonePortaSetPer
+       MOVE.W  pn_WantedPeriod(A6),pn_Period(A6)
+       CLR.W   pn_WantedPeriod(A6)
+
+mt_TonePortaSetPer
+       MOVE.W  pn_Period(A6),D2
+       MOVE.B  pn_GlissFunk(A6),D0
+       AND.B   #$0F,D0
+       BEQ.S   mt_GlissSkip
+       MOVEQ   #0,D0
+       MOVE.B  pn_FineTune(A6),D0
+       MULU    #36*2,D0
+       LEA     mt_PeriodTable,A0
+       ADD.L   D0,A0
+       MOVEQ   #0,D0
+mt_GlissLoop
+       CMP.W   (A0,D0.W),D2
+       BHS.S   mt_GlissFound
+       ADDQ.W  #2,D0
+       CMP.W   #36*2,D0
+       BLO.S   mt_GlissLoop
+       MOVEQ   #35*2,D0
+mt_GlissFound
+       MOVE.W  (A0,D0.W),D2
+mt_GlissSkip
+       move.w  d2,nch_Frequency(a5)
+       bset.b  #NCHB_Frequency,nch_Changed(a5)
+       RTS
+
+mt_Vibrato
+       MOVE.B  pn_Val(A6),D0
+       BEQ.S   mt_Vibrato2
+       MOVE.B  pn_VibratoCmd(A6),D2
+       AND.B   #$0F,D0
+       BEQ.S   mt_vibskip
+       AND.B   #$F0,D2
+       OR.B    D0,D2
+mt_vibskip
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$F0,D0
+       BEQ.S   mt_vibskip2
+       AND.B   #$0F,D2
+       OR.B    D0,D2
+mt_vibskip2
+       MOVE.B  D2,pn_VibratoCmd(A6)
+mt_Vibrato2
+       MOVE.B  pn_VibratoPos(A6),D0
+       LEA     mt_VibratoTable,A4
+       LSR.W   #2,D0
+       AND.W   #$001F,D0
+       MOVEQ   #0,D2
+       MOVE.B  pn_WaveControl(A6),D2
+       AND.B   #$03,D2
+       BEQ.S   mt_vib_sine
+       LSL.B   #3,D0
+       CMP.B   #1,D2
+       BEQ.S   mt_vib_rampdown
+       MOVE.B  #255,D2
+       BRA.S   mt_vib_set
+mt_vib_rampdown
+       TST.B   pn_VibratoPos(A6)
+       BPL.S   mt_vib_rampdown2
+       MOVE.B  #255,D2
+       SUB.B   D0,D2
+       BRA.S   mt_vib_set
+mt_vib_rampdown2
+       MOVE.B  D0,D2
+       BRA.S   mt_vib_set
+mt_vib_sine
+       MOVE.B  (A4,D0.W),D2
+mt_vib_set
+       MOVE.B  pn_VibratoCmd(A6),D0
+       AND.W   #15,D0
+       MULU    D0,D2
+       LSR.W   #7,D2
+       MOVE.W  pn_Period(A6),D0
+       TST.B   pn_VibratoPos(A6)
+       BMI.S   mt_VibratoNeg
+       ADD.W   D2,D0
+       BRA.S   mt_Vibrato3
+mt_VibratoNeg
+       SUB.W   D2,D0
+mt_Vibrato3
+       move.w  d0,nch_Frequency(a5)
+       bset.b  #NCHB_Frequency,nch_Changed(a5)
+       MOVE.B  pn_VibratoCmd(A6),D0
+       LSR.W   #2,D0
+       AND.W   #$003C,D0
+       ADD.B   D0,pn_VibratoPos(A6)
+       RTS
+
+mt_TonePlusVolSlide
+       BSR     mt_TonePortNoChange
+       BRA     mt_VolumeSlide
+
+mt_VibratoPlusVolSlide
+       BSR.S   mt_Vibrato2
+       BRA     mt_VolumeSlide
+
+mt_Tremolo
+       MOVE.B  pn_Val(A6),D0
+       BEQ.S   mt_Tremolo2
+       MOVE.B  pn_TremoloCmd(A6),D2
+       AND.B   #$0F,D0
+       BEQ.S   mt_treskip
+       AND.B   #$F0,D2
+       OR.B    D0,D2
+mt_treskip
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$F0,D0
+       BEQ.S   mt_treskip2
+       AND.B   #$0F,D2
+       OR.B    D0,D2
+mt_treskip2
+       MOVE.B  D2,pn_TremoloCmd(A6)
+mt_Tremolo2
+       MOVE.B  pn_TremoloPos(A6),D0
+       LEA     mt_VibratoTable,A4
+       LSR.W   #2,D0
+       AND.W   #$001F,D0
+       MOVEQ   #0,D2
+       MOVE.B  pn_WaveControl(A6),D2
+       LSR.B   #4,D2
+       AND.B   #$03,D2
+       BEQ.S   mt_tre_sine
+       LSL.B   #3,D0
+       CMP.B   #1,D2
+       BEQ.S   mt_tre_rampdown
+       MOVE.B  #255,D2
+       BRA.S   mt_tre_set
+mt_tre_rampdown
+       TST.B   pn_VibratoPos(A6)
+       BPL.S   mt_tre_rampdown2
+       MOVE.B  #255,D2
+       SUB.B   D0,D2
+       BRA.S   mt_tre_set
+mt_tre_rampdown2
+       MOVE.B  D0,D2
+       BRA.S   mt_tre_set
+mt_tre_sine
+       MOVE.B  (A4,D0.W),D2
+mt_tre_set
+       MOVE.B  pn_TremoloCmd(A6),D0
+       AND.W   #15,D0
+       MULU    D0,D2
+       LSR.W   #6,D2
+       MOVEQ   #0,D0
+       MOVE.B  pn_Volume(A6),D0
+       TST.B   pn_TremoloPos(A6)
+       BMI.S   mt_TremoloNeg
+       ADD.W   D2,D0
+       BRA.S   mt_Tremolo3
+mt_TremoloNeg
+       SUB.W   D2,D0
+mt_Tremolo3
+       BPL.S   mt_TremoloSkip
+       CLR.W   D0
+mt_TremoloSkip
+       CMP.W   #$40,D0
+       BLS.S   mt_TremoloOk
+       MOVE.W  #$40,D0
+mt_TremoloOk
+       move.w  d0,nch_Volume(a5)
+       bset.b  #NCHB_Volume,nch_Changed(a5)
+       MOVE.B  pn_TremoloCmd(A6),D0
+       LSR.W   #2,D0
+       AND.W   #$003C,D0
+       ADD.B   D0,pn_TremoloPos(A6)
+       RTS
+
+mt_SampleOffset
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       BEQ.S   mt_sononew
+       MOVE.B  D0,pn_SampleOffset(A6)
+mt_sononew
+       MOVE.B  pn_SampleOffset(A6),D0
+       LSL.L   #7,D0
+       CMP.L   pn_Length(A6),D0
+       BGE.S   mt_sofskip
+       SUB.L   D0,pn_Length(A6)
+       LSL.L   #1,D0
+       ADD.L   D0,pn_Start(A6)
+       RTS
+mt_sofskip
+       MOVE.L  #$0001,pn_Length(A6)
+       RTS
+
+mt_VolumeSlide
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       LSR.B   #4,D0
+       TST.B   D0
+       BEQ.S   mt_VolSlideDown
+mt_VolSlideUp
+       ADD.B   D0,pn_Volume(A6)
+       CMP.B   #$40,pn_Volume(A6)
+       BMI.S   mt_vsuskip
+       MOVE.B  #$40,pn_Volume(A6)
+mt_vsuskip
+       MOVE.B  pn_Volume(A6),D0
+       move.w  d0,nch_Volume(a5)
+       bset.b  #NCHB_Volume,nch_Changed(a5)
+       RTS
+
+mt_VolSlideDown
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+mt_VolSlideDown2
+       SUB.B   D0,pn_Volume(A6)
+       BPL.S   mt_vsdskip
+       CLR.B   pn_Volume(A6)
+mt_vsdskip
+       MOVE.B  pn_Volume(A6),D0
+       move.w  d0,nch_Volume(a5)
+       bset.b  #NCHB_Volume,nch_Changed(a5)
+       RTS
+
+mt_PositionJump
+       MOVE.W  mt_SongPos,D0
+       ADDQ.W  #1,D0
+       CMP.W   mt_MaxPos,D0
+       BNE.S   mt_pj1
+       MOVE.B  #1,mt_Reset
+mt_pj1 MOVE.B  pn_Val(A6),D0
+       CMP.W   mt_SongPos,D0
+       BNE.S   mt_pj0
+       MOVE.B  #1,mt_Reset
+       TST.W   mt_PatternPos
+       BNE.S   mt_pj0
+       MOVE.B  #2,mt_Reset
+mt_pj0 SUBQ.B  #1,D0
+       MOVE.W  D0,mt_SongPos
+       BPL.S   mt_pj2
+       MOVE.B  #2,mt_Reset
+mt_pj2 CLR.W   mt_PBreakPos
+       ST      mt_PosJumpFlag
+       RTS
+
+mt_VolumeChange
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       CMP.B   #$40,D0
+       BLS.S   mt_VolumeOk
+       MOVEQ   #$40,D0
+mt_VolumeOk
+       MOVE.B  D0,pn_Volume(A6)
+       move.w  d0,nch_Volume(a5)
+       bset.b  #NCHB_Volume,nch_Changed(a5)
+       RTS
+
+mt_PatternBreak
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       MOVE.L  D0,D2
+       LSR.W   #4,D0
+       MULU    #10,D0
+       AND.W   #$0F,D2
+       ADD.W   D2,D0
+       CMP.W   #63,D0
+       BHI.S   mt_pj2
+       MOVE.W  D0,mt_PBreakPos
+       ST.B    mt_PosJumpFlag
+       RTS
+
+mt_SetSpeed
+       MOVEQ   #0,D0
+       MOVE.B  3(A6),D0
+       BNE.S   mt_SetSpd
+       CMP.W   #32,D0
+       BHS     SetTempo
+mt_SetSpd
+       TST.W   D0
+       BEQ.S   mt_SetBack
+       CLR.W   mt_counter
+       MOVE.W  D0,mt_speed
+mt_SetBack
+       RTS
+
+mt_CheckMoreEfx
+       BSR     mt_UpdateFunk
+       MOVE.B  2(A6),D0
+       AND.B   #$0F,D0
+       CMP.B   #$9,D0
+       BEQ     mt_SampleOffset
+       CMP.B   #$B,D0
+       BEQ     mt_PositionJump
+       CMP.B   #$D,D0
+       BEQ     mt_PatternBreak
+       CMP.B   #$E,D0
+       BEQ.S   mt_E_Commands
+       CMP.B   #$F,D0
+       BEQ.S   mt_SetSpeed
+       CMP.B   #$C,D0
+       BEQ     mt_VolumeChange
+       BRA     mt_PerNop
+
+mt_E_Commands
+       MOVE.B  pn_Val(A6),D0           ; !!!
+       AND.B   #$F0,D0
+       LSR.B   #4,D0
+;      BEQ.S   mt_FilterOnOff
+       CMP.B   #1,D0
+       BEQ     mt_FinePortaUp
+       CMP.B   #2,D0
+       BEQ     mt_FinePortaDown
+       CMP.B   #3,D0
+       BEQ.S   mt_SetGlissControl
+       CMP.B   #4,D0
+       BEQ.S   mt_SetVibratoControl
+       CMP.B   #5,D0
+       BEQ.S   mt_SetFineTune
+       CMP.B   #6,D0
+       BEQ.S   mt_JumpLoop
+       CMP.B   #7,D0
+       BEQ     mt_SetTremoloControl
+       CMP.B   #9,D0
+       BEQ     mt_RetrigNote
+       CMP.B   #$A,D0
+       BEQ     mt_VolumeFineUp
+       CMP.B   #$B,D0
+       BEQ     mt_VolumeFineDown
+       CMP.B   #$C,D0
+       BEQ     mt_NoteCut
+       CMP.B   #$D,D0
+       BEQ     mt_NoteDelay
+       CMP.B   #$E,D0
+       BEQ     mt_PatternDelay
+       CMP.B   #$F,D0
+       BEQ     mt_FunkIt
+       RTS
+
+mt_FilterOnOff
+;      MOVE.B  pn_Val(A6),D0
+;      AND.B   #1,D0
+;      ASL.B   #1,D0
+;      AND.B   #$FD,$BFE001
+;      OR.B    D0,$BFE001
+;      RTS
+
+mt_SetGlissControl
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       AND.B   #$F0,pn_GlissFunk(A6)
+       OR.B    D0,pn_GlissFunk(A6)
+       RTS
+
+mt_SetVibratoControl
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       AND.B   #$F0,pn_WaveControl(A6)
+       OR.B    D0,pn_WaveControl(A6)
+       RTS
+
+mt_SetFineTune
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       MOVE.B  D0,pn_FineTune(A6)
+       RTS
+
+mt_JumpLoop
+       TST.W   mt_counter
+       BNE     mt_Return
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       BEQ.S   mt_SetLoop
+       TST.B   pn_LoopCount(A6)
+       BEQ.S   mt_jumpcnt
+       SUBQ.B  #1,pn_LoopCount(A6)
+       BEQ     mt_Return
+mt_jmploop     MOVE.W  pn_PattPos(A6),mt_PBreakPos
+       ST      mt_PBreakFlag
+       RTS
+
+mt_jumpcnt
+       MOVE.B  D0,pn_LoopCount(A6)
+       BRA.S   mt_jmploop
+
+mt_SetLoop
+       MOVE.W  mt_PatternPos,D0
+       LSR.W   #4,D0
+       MOVE.B  D0,pn_PattPos(A6)
+       RTS
+
+mt_SetTremoloControl
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       LSL.B   #4,D0
+       AND.B   #$0F,pn_WaveControl(A6)
+       OR.B    D0,pn_WaveControl(A6)
+       RTS
+
+mt_RetrigNote
+       MOVE.L  D1,-(SP)
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       BEQ.S   mt_rtnend
+       MOVEQ   #0,D1
+       MOVE.W  mt_counter,D1
+       BNE.S   mt_rtnskp
+       MOVE.W  (A6),D1
+       AND.W   #$0FFF,D1
+       BNE.S   mt_rtnend
+       MOVEQ   #0,D1
+       MOVE.W  mt_counter,D1
+mt_rtnskp
+       DIVU    D0,D1
+       SWAP    D1
+       TST.W   D1
+       BNE.S   mt_rtnend
+mt_DoRetrig
+       move.l  pn_Start(a6),nch_SampleStart(a5)
+       move.l  pn_Length(a6),d0
+       move.l  d0,nch_SampleLength(a5)
+       bset.b  #NCHB_Sample,nch_Changed(a5)
+       move.l  pn_LoopStart(A6),nch_RepeatStart(a5)
+       move.l  pn_Replen(A6),d0
+       move.l  d0,nch_RepeatLength(a5)
+       bset.b  #NCHB_Repeat,nch_Changed(a5)
+       move.w  pn_Period(A6),nch_Frequency(a5)
+       bset.b  #NCHB_Frequency,nch_Changed(a5)
+mt_rtnend
+       move.l  (sp)+,d1
+       rts
+
+mt_VolumeFineUp
+       TST.W   mt_counter
+       BNE     mt_Return
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$F,D0
+       BRA     mt_VolSlideUp
+
+mt_VolumeFineDown
+       TST.W   mt_counter
+       BNE     mt_Return
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       BRA     mt_VolSlideDown2
+
+mt_NoteCut
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       CMP.W   mt_counter,D0
+       BNE     mt_Return
+       CLR.B   pn_Volume(A6)
+       MOVEQ   #0,D0
+       move.w  d0,nch_Volume(a5)
+       bset.b  #NCHB_Volume,nch_Changed(a5)
+       RTS
+
+mt_NoteDelay
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       CMP.W   mt_counter,D0
+       BNE     mt_Return
+       MOVE.W  (A6),D0
+       BEQ     mt_Return
+       MOVE.L  D1,-(SP)
+       BRA     mt_DoRetrig
+
+mt_PatternDelay
+       TST.W   mt_counter
+       BNE     mt_Return
+       MOVEQ   #0,D0
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       TST.B   mt_PattDelTime2
+       BNE     mt_Return
+       ADDQ.B  #1,D0
+       MOVE.B  D0,mt_PattDelTime
+       RTS
+
+mt_FunkIt
+       TST.W   mt_counter
+       BNE     mt_Return
+       MOVE.B  pn_Val(A6),D0
+       AND.B   #$0F,D0
+       LSL.B   #4,D0
+       AND.B   #$0F,pn_GlissFunk(A6)
+       OR.B    D0,pn_GlissFunk(A6)
+       TST.B   D0
+       BEQ     mt_Return
+mt_UpdateFunk
+       MOVEM.L A0/D1,-(SP)
+       MOVEQ   #0,D0
+       MOVE.B  pn_GlissFunk(A6),D0
+       LSR.B   #4,D0
+       BEQ.S   mt_funkend
+       LEA     mt_FunkTable,A0
+       MOVE.B  (A0,D0.W),D0
+       ADD.B   D0,pn_FunkOffset(A6)
+       BTST    #7,pn_FunkOffset(A6)
+       BEQ.S   mt_funkend
+       CLR.B   pn_FunkOffset(A6)
+
+       MOVE.L  pn_LoopStart(A6),D0
+       move.l  pn_Replen(a6),d1
+       ADD.L   D1,D0
+       ADD.L   D1,D0
+       MOVE.L  pn_WaveStart(A6),A0
+       ADDQ.L  #1,A0
+       CMP.L   D0,A0
+       BLO.S   mt_funkok
+       MOVE.L  pn_LoopStart(A6),A0
+mt_funkok
+       MOVE.L  A0,pn_WaveStart(A6)
+       MOVEQ   #-1,D0
+       SUB.B   (A0),D0
+       MOVE.B  D0,(A0)
+mt_funkend
+       MOVEM.L (SP)+,A0/D1
+       RTS
+
+
+mt_FunkTable dc.b 0,5,6,7,8,10,11,13,16,19,22,26,32,43,64,128
+
+mt_VibratoTable
+       dc.b 000,024,049,074,097,120,141,161
+       dc.b 180,197,212,224,235,244,250,253
+       dc.b 255,253,250,244,235,224,212,197
+       dc.b 180,161,141,120,097,074,049,024
+
+mt_PeriodTable
+; Tuning 0, Normal
+       dc.w    856,808,762,720,678,640,604,570,538,508,480,453
+       dc.w    428,404,381,360,339,320,302,285,269,254,240,226
+       dc.w    214,202,190,180,170,160,151,143,135,127,120,113
+; Tuning 1
+       dc.w    850,802,757,715,674,637,601,567,535,505,477,450
+       dc.w    425,401,379,357,337,318,300,284,268,253,239,225
+       dc.w    213,201,189,179,169,159,150,142,134,126,119,113
+; Tuning 2
+       dc.w    844,796,752,709,670,632,597,563,532,502,474,447
+       dc.w    422,398,376,355,335,316,298,282,266,251,237,224
+       dc.w    211,199,188,177,167,158,149,141,133,125,118,112
+; Tuning 3
+       dc.w    838,791,746,704,665,628,592,559,528,498,470,444
+       dc.w    419,395,373,352,332,314,296,280,264,249,235,222
+       dc.w    209,198,187,176,166,157,148,140,132,125,118,111
+; Tuning 4
+       dc.w    832,785,741,699,660,623,588,555,524,495,467,441
+       dc.w    416,392,370,350,330,312,294,278,262,247,233,220
+       dc.w    208,196,185,175,165,156,147,139,131,124,117,110
+; Tuning 5
+       dc.w    826,779,736,694,655,619,584,551,520,491,463,437
+       dc.w    413,390,368,347,328,309,292,276,260,245,232,219
+       dc.w    206,195,184,174,164,155,146,138,130,123,116,109
+; Tuning 6
+       dc.w    820,774,730,689,651,614,580,547,516,487,460,434
+       dc.w    410,387,365,345,325,307,290,274,258,244,230,217
+       dc.w    205,193,183,172,163,154,145,137,129,122,115,109
+; Tuning 7
+       dc.w    814,768,725,684,646,610,575,543,513,484,457,431
+       dc.w    407,384,363,342,323,305,288,272,256,242,228,216
+       dc.w    204,192,181,171,161,152,144,136,128,121,114,108
+; Tuning -8
+       dc.w    907,856,808,762,720,678,640,604,570,538,508,480
+       dc.w    453,428,404,381,360,339,320,302,285,269,254,240
+       dc.w    226,214,202,190,180,170,160,151,143,135,127,120
+; Tuning -7
+       dc.w    900,850,802,757,715,675,636,601,567,535,505,477
+       dc.w    450,425,401,379,357,337,318,300,284,268,253,238
+       dc.w    225,212,200,189,179,169,159,150,142,134,126,119
+; Tuning -6
+       dc.w    894,844,796,752,709,670,632,597,563,532,502,474
+       dc.w    447,422,398,376,355,335,316,298,282,266,251,237
+       dc.w    223,211,199,188,177,167,158,149,141,133,125,118
+; Tuning -5
+       dc.w    887,838,791,746,704,665,628,592,559,528,498,470
+       dc.w    444,419,395,373,352,332,314,296,280,264,249,235
+       dc.w    222,209,198,187,176,166,157,148,140,132,125,118
+; Tuning -4
+       dc.w    881,832,785,741,699,660,623,588,555,524,494,467
+       dc.w    441,416,392,370,350,330,312,294,278,262,247,233
+       dc.w    220,208,196,185,175,165,156,147,139,131,123,117
+; Tuning -3
+       dc.w    875,826,779,736,694,655,619,584,551,520,491,463
+       dc.w    437,413,390,368,347,328,309,292,276,260,245,232
+       dc.w    219,206,195,184,174,164,155,146,138,130,123,116
+; Tuning -2
+       dc.w    868,820,774,730,689,651,614,580,547,516,487,460
+       dc.w    434,410,387,365,345,325,307,290,274,258,244,230
+       dc.w    217,205,193,183,172,163,154,145,137,129,122,115
+; Tuning -1
+       dc.w    862,814,768,725,684,646,610,575,543,513,484,457
+       dc.w    431,407,384,363,342,323,305,288,272,256,242,228
+       dc.w    216,203,192,181,171,161,152,144,136,128,121,114
+
+;/* End of File */
+
+
+
+*-----------------------------------------------------------------------*
+;
+; 14Bit/32Voices-NotePlayer
+
+NoteSignal
+       movem.l a2-a4,-(sp)
+
+       move.l  NoteInfo,d0                     ; get NoteStructure
+       beq     AO_End
+       move.l  d0,a4
+
+       move.l  nst_Channels(a4),d0             ; pointer to first channel in list
+AO_PlayLoop
+       move.l  d0,a2                           ; ^NoteChannel
+
+       cmpi.w  #NCHD_Ignore,nch_Stereo(a2)     ; skip this channel ?
+       beq     AO_PlayNext                     ; yes !
+
+       move.l  nch_NotePlayer(a2),d0           ; channel array
+       beq     AO_PlayNext                     ; no array !
+       move.l  d0,a3
+
+       move.l  (a3),a0
+       tst.l   (a0)                            ; Channel disabled ?
+       beq     AO_PlayNext                     ; yes !
+
+       move.l  4(a3),a3                        ; ^TrackStructure
+AO_Sample
+       btst.b  #NCHB_Sample,nch_Changed(a2)
+       beq.s   AO_Repeat                       ; no new sample !
+
+       move.l  nch_SampleStart(a2),a0
+       move.l  nch_SampleLength(a2),d0
+       add.l   d0,a0
+       move.l  a0,td_SPtr(a3)                  ; Pointer auf das Sampleende
+       neg.l   d0
+       move.l  d0,td_SLen(a3)                  ; negative Sample-Restbytes
+
+       clr.l   td_LLen(a3)                     ; Kein LoopSample vorhanden
+
+       clr.w   td_FreC(a3)                     ; -> Kein Überlauf bei erstem Samplebyte!
+AO_Repeat
+       btst.b  #NCHB_Repeat,nch_Changed(a2)
+       beq.s   AO_Frequency                    ; no new repeat !
+
+       move.l  nch_RepeatStart(a2),a0
+       move.l  nch_RepeatLength(a2),d0
+       moveq   #2,d1
+       cmp.l   d0,d1                           ; Repeat-Length only 1 Word ?
+       bne.s   AO_RepSamp                      ; No !
+       move.l  a0,a1
+       move.b  (a1)+,d1
+       cmp.b   (a1)+,d1                        ; Is it a NullSample ?
+       bne.s   AO_RepSamp                      ; No !
+       moveq   #0,d0
+AO_RepSamp
+       add.l   d0,a0
+       move.l  a0,td_LPtr(a3)                  ; LoopSample-Endadr.
+       neg.l   d0
+       move.l  d0,td_LLen(a3)                  ; negative LoopSample-Länge
+AO_Frequency
+       btst.b  #NCHB_Frequency,nch_Changed(a2)
+       beq.s   AO_Volume                       ; no new freq !
+
+       move.l  nch_Frequency(a2),td_Fre(a3)    ; Frequency for this channel
+AO_Volume
+       btst.b  #NCHB_Volume,nch_Changed(a2)
+       beq.s   AO_PlayNext                     ; no new vol !
+
+       moveq   #0,d0
+       move.w  nch_Volume(a2),d0
+       lsl.l   #6,d0
+       divu    nst_MaxVolume(a4),d0
+       move.w  d0,td_Vol(a3)                   ; Volume for this channel
+AO_PlayNext
+       move.l  nch_NextChannel(a2),d0
+       bne     AO_PlayLoop                     ; next channel
+AO_End
+       movem.l (sp)+,a2-a4
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Set the master volume/balance
+
+;SetVolBal
+;      move.l  NoteInfo,d0                     ; get NoteStructure
+;      beq     .End
+;      move.l  d0,a4
+;      move.l  delibase,a5                     ; get DeliBase
+;
+;      move.w  dtg_SndVol(a5),d0
+;      mulu    dtg_SndLBal(a5),d0
+;      lsr.w   #6,d0
+;      move.w  d0,Volume07                     ; left volume
+;
+;      move.w  dtg_SndVol(a5),d0
+;      mulu    dtg_SndRBal(a5),d0
+;      lsr.w   #6,d0
+;      move.w  d0,Volume8F                     ; right volume
+;.End
+;      rts
+
+*-----------------------------------------------------------------------*
+;
+; Set the mixing frequency
+
+NewMixFreq:
+       move.l  ClockBase(pc),d0
+       move.l  PlayFreq,d1
+       divu    d1,d0
+       move.w  d0,MixPeriod
+       move.l  ClockBase(pc),d1
+       divu    d0,d1
+       move.w  d1,MixFreq
+       rts
+
+
+*-----------------------------------------------------------------------*
+;
+; Shut down the NotePlayer
+
+NoteFinish
+       move.l  NoteInfo,d0                     ; get NoteStructure
+       beq     .End
+       move.l  d0,a4                           ; copy Ptr
+
+       lea     custom,a0
+       move.w  #$000f,dmacon(a0)               ; Audio-DMA aus
+       move.w  #$00ff,adkcon(a0)
+       move.w  #$0780,intreq(a0)               ; Audio-Req. löschen
+       move.w  #$0780,intena(a0)               ; Audio-Int. aus
+
+       move.l  nst_Channels(a4),d0             ; pointer to first channel in list
+.FreeChan
+       move.l  d0,a0
+       clr.l   nch_NotePlayer(a0)              ; clear channel array pointer
+.FreeNext
+       move.l  nch_NextChannel(a0),d0
+       bne.s   .FreeChan                       ; next channel
+
+       moveq   #INTB_AUD1,d0
+       move.l  _OldAudio1,a1
+       JSRLIB  SetIntVector                    ; set old Audio1-IRQ
+
+       moveq   #INTB_AUD0,d0
+       move.l  _OldAudio0,a1
+       JSRLIB  SetIntVector                    ; set old Audio0-IRQ
+
+       lea     AllocIORequest,a1               ; close audio.device
+       JSRLIB  CloseDevice
+
+       clr.l   NoteInfo                        ; shutdown complete
+.End
+       moveq   #0,d0
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Boost-Value automatisch berechnen
+
+CalcBoostValue
+       tst.l   BoostFlag                       ; Boost-Wert automatisch berechnen ?
+       beq.s   .End                            ; nein, dann raus !
+
+       moveq   #0,d0
+       move.w  ChannelNum,d0
+       lsl.w   #7,d0
+       add.w   #128+256,d0
+       move.w  d0,d1
+       lsr.w   #1,d1
+       sub.w   d1,d0
+       move.w  #8*256,d1
+       cmp.w   d0,d1
+       bcc.s   .End
+       move.w  d1,d0
+.End
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; 14Bit-Output-Tabelle erzeugen (14Bit Routine by Markus "maw" Weichselbaum)
+
+       IFEQ USE_CALIBRATION
+
+InitBoostTable
+       lea     BitTable,a0                     ; 14Bit-Tabelle erzeugen
+
+       move.w  #32768-1,d0
+       move.w  #-32768,d1
+       move.l  BoostFactor,d2
+.Loop  move.w  d1,d3
+       muls    d2,d3
+       asr.l   #8,d3
+       move.l  #-32768,d4
+       cmp.l   d3,d4
+       bgt.s   .Fail
+       move.l  #32767,d4
+       cmp.l   d3,d4
+       bgt.s   .Okay
+.Fail  move.w  d4,d3
+.Okay  move.w  d3,d4                           ; ist das 16 bit sample negativ?
+       bmi.s   .IsNeg
+.IsPos lsr.w   #8,d3                           ; hi byte fertig machen
+       lsr.b   #2,d4                           ; vom lo nehmen wir nur die oberen 6 bits
+       bra.s   .Write
+.IsNeg neg.w   d3                              ; erstmal auf positiv
+       lsr.w   #8,d3                           ; das hi byte fertig machen
+       neg.b   d3                              ; hi byte zurueck auf negativ
+       neg.w   d4                              ; nochmal auf positiv
+       lsr.b   #2,d4                           ; runter schieben
+       neg.w   d4                              ; jetzt noch zurueck auf negativ
+.Write move.b  d3,(a0)+                        ; hier haben wir das hi byte
+       move.b  d4,(a0)+                        ; hier haben wir das lo byte
+       addq.w  #2,d1
+       dbra    d0,.Loop
+       rts
+
+       ENDC
+
+*-----------------------------------------------------------------------*
+;
+; 14Bit-Output-Tabelle erzeugen (14Bit Routine by Christian Buchner)
+
+       IFNE USE_CALIBRATION
+
+TABLE_ENTRIES  EQU     32768
+
+InitBoostTable movem.l a2-a3/d2-d7,-(sp)
+
+               lea     CalibData+128,a2
+
+.negative      move.l  a2,a1                   ; count the number of steps
+               moveq   #128-1,d0               ; in the negative range
+               moveq   #0,d5
+.count1                move.b  -(a1),d1
+               ext.w   d1
+               ext.l   d1
+               add.l   d1,d5
+               dbra    d0,.count1              ; d5=number of steps
+
+               move.l  #256*TABLE_ENTRIES/2,d6
+               divu    BoostFactor+2,d6        ; divide by boost factor
+               divu    d6,d5                   ; calculate stepsize (d5) and
+               moveq   #0,d7
+               swap    d5
+               move.w  d5,d7                   ; fractional part (d7)
+               clr.w   d5
+               swap    d5
+               move.w  d6,a3
+
+               lea     BTable,a0               ; place at the middle of table
+               move.w  #TABLE_ENTRIES/2-1,d0   ; number of negative values -1
+               moveq   #-1,d1                  ; HI value
+               moveq   #-1,d2                  ; LO value
+               moveq   #0,d3                   ; counter
+.fetchnext1    move.b  (a2,d1.w),d4            ; add calibtable to counter
+               ext.w   d4
+               add.w   d4,d3
+               add.w   d4,d2                   ; maximize LO value
+.outerloop1    tst.w   d3
+               bgt.s   .positive1
+.negative1     subq.w  #1,d1
+               cmp.w   #$FF7F,d1
+               beq.s   .fillup1
+               bra.s   .fetchnext1
+.positive1     move.b  d2,-(a0)                ; store LO and HI value
+               move.b  d1,-(a0)
+               sub.w   d5,d2                   ; subtract step size
+               sub.w   d5,d3                   ; decrement counter
+               sub.w   d7,d6                   ; sum up fractional part
+               bpl.s   .repeat1
+               add.w   a3,d6
+               subq.w  #1,d2                   ; decrement lo value
+               subq.w  #1,d3                   ; decrement counter
+.repeat1       dbra    d0,.outerloop1
+               bra.s   .positive
+
+.fillup1       move.w  #$8000,-(a0)            ; fill up the rest of the
+               dbra    d0,.fillup1             ; table with negative minimum
+
+.positive      move.l  a2,a1                   ; count the number of steps
+               moveq   #128-1,d0               ; in the positive range
+               moveq   #0,d5
+.count2                move.b  (a1)+,d1
+               ext.w   d1
+               ext.l   d1
+               add.l   d1,d5
+               dbra    d0,.count2              ; d5=number of steps
+
+               move.l  #256*TABLE_ENTRIES/2,d6
+               divu    BoostFactor+2,d6        ; divide by boost factor
+               divu    d6,d5                   ; calculate stepsize (d5) and
+               moveq   #0,d7
+               swap    d5
+               move.w  d5,d7                   ; fractional part (d7)
+               clr.w   d5
+               swap    d5
+               move.w  d6,a3
+
+               lea     BTable,a0               ; place at the middle of table
+               move.w  #TABLE_ENTRIES/2-1,d0   ; number of positive values -1
+               moveq   #0,d1                   ; HI value
+               moveq   #0,d2                   ; LO value
+               moveq   #0,d3                   ; counter
+.fetchnext2    move.b  (a2,d1.w),d4            ; add calibtable to counter
+               ext.w   d4
+               add.w   d4,d3
+.outerloop2    tst.w   d3
+               bgt.s   .positive2
+.negative2     addq.w  #1,d1                   ; increment HI value
+               cmp.w   #$80,d1
+               beq.s   .fillup2
+               sub.w   d4,d2                   ; reset LO value
+               bra.s   .fetchnext2
+.positive2     move.b  d1,(a0)+                ; store HI and LO value
+               move.b  d2,(a0)+
+               add.w   d5,d2                   ; add step size
+               sub.w   d5,d3                   ; decrement counter
+               sub.w   d7,d6                   ; sum up fractional part
+               bpl.s   .repeat2
+               add.w   a3,d6
+               addq.w  #1,d2                   ; increment LO value
+               subq.w  #1,d3                   ; decrement counter
+.repeat2       dbra    d0,.outerloop2
+               bra.s   .finished
+
+.fillup2       move.w  #$7F7E,(a0)+            ; fill up the rest of the
+               dbra    d0,.fillup2             ; table with positive maximum
+
+.finished      movem.l (sp)+,a2-a3/d2-d7
+               rts
+
+       ENDC
+
+*-----------------------------------------------------------------------*
+;
+; Volume-Tabelle (signed) erzeugen
+
+InitSVolTable
+       lea     VTable,a0                       ; Volume-Tabelle erzeugen
+
+       moveq   #65-1,d2
+       moveq   #0,d4
+       move.w  ScaleFactor,d5
+.Loop1 moveq   #0,d0
+       move.w  #256-1,d1
+.Loop2 move.w  d0,d3
+       ext.w   d3
+       muls    d4,d3                           ; Tabellennummer (0...64) * Samplewert
+       lsr.l   #2,d3
+       andi.w  #~1,d3
+       move.w  d3,(a0)+
+       addq.w  #1,d0
+       dbra    d1,.Loop2
+       add.w   d5,d4                           ; > 1 = Oversampling
+       dbra    d2,.Loop1
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Volume-Tabelle (unsigned) erzeugen
+
+InitUVolTable
+       lea     VTable,a0                       ; Volume-Tabelle erzeugen
+
+       moveq   #65-1,d2
+       moveq   #0,d4
+       move.w  ScaleFactor,d5
+.Loop1 moveq   #-128,d0
+       move.w  #256-1,d1
+.Loop2 move.w  d0,d3
+       ext.w   d3
+       muls    d4,d3                           ; Tabellennummer (0...64) * Samplewert
+       lsr.l   #2,d3
+       andi.w  #~1,d3
+       move.w  d3,(a0)+
+       addq.w  #1,d0
+       dbra    d1,.Loop2
+       add.w   d5,d4                           ; > 1 = Oversampling
+       dbra    d2,.Loop1
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Alle 32 Software-Tracks initialisieren
+
+InitTracksNChannels
+       lea     T00Data(pc),a0
+       lea     QuietInstr(pc),a1
+       moveq   #32-1,d0
+.Loop  move.l  (a1),td_SPtr(a0)                ; Zum Abspielen vorbereiten
+       move.l  4(a1),td_SLen(a0)
+       move.l  8(a1),td_LPtr(a0)
+       move.l  12(a1),td_LLen(a0)
+       move.w  #64,td_Vol(a0)                  ; Volle Lautstärke
+       move.l  #8287,td_Fre(a0)                ; C 0
+       clr.w   td_FreC(a0)
+       lea     td_SIZE(a0),a0
+       dbra    d0,.Loop
+
+* Alle 4 Audiokanäle initialisieren:
+
+       lea     custom+aud0,a0
+       moveq   #4-1,d0
+.Init  move.l  #EmptyCBuf,ac_ptr(a0)           ; Erstmal nichts spielen!
+       move.w  #MBuf_SIZE/2,ac_len(a0)
+       move.w  MixPeriod(pc),ac_per(a0)
+       move.w  #0,ac_vol(a0)
+       adda.w  #ac_SIZEOF,a0
+       dbra    d0,.Init
+
+       clr.w   C03BufOff                       ; Erste Songdaten kommen in Buffer 0
+       clr.w   C12BufOff
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Init Sound
+
+InitSnd
+       move.l  NoteInfo,d0                     ; get NoteStructure
+       beq.s   .End
+       move.l  d0,a4
+
+       bsr     InitTracksNChannels
+
+       IFNE DEBUG_VERSION
+       move.l  #InitSndTxt,-(sp)
+       lea     InformationFmt(pc),a0
+       bsr     WriteInfoTxt
+       addq.w  #4,sp
+       ENDC
+.End
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Remove Sound
+
+EndSnd
+       move.l  NoteInfo,d0                     ; get NoteStructure
+       beq.s   .End
+       move.l  d0,a4
+
+       bsr     InitTracksNChannels
+
+       IFNE DEBUG_VERSION
+       move.l  #EndSndTxt,-(sp)
+       lea     InformationFmt(pc),a0
+       bsr     WriteInfoTxt
+       addq.w  #4,sp
+       ENDC
+.End
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Start playing
+
+StartSnd
+       lea     custom,a0
+       move.w  #$800f,dmacon(a0)               ; Audio-DMA an
+       move.w  #$00ff,adkcon(a0)
+       move.w  #$0780,intreq(a0)               ; Audio-Req. löschen
+       move.w  #$8180,intena(a0)               ; Audio-Int. an
+       moveq   #64,d0
+       move.w  d0,aud0+ac_vol(a0)
+       move.w  d0,aud1+ac_vol(a0)
+       moveq   #1,d0
+       move.w  d0,aud2+ac_vol(a0)
+       move.w  d0,aud3+ac_vol(a0)
+.End
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Stop playing
+
+StopSnd
+       lea     custom,a0
+       move.w  #$000f,dmacon(a0)               ; Audio-DMA aus
+       move.w  #$00ff,adkcon(a0)
+       move.w  #$0780,intreq(a0)               ; Audio-Req. löschen
+       move.w  #$0780,intena(a0)               ; Audio-Int. aus
+.End
+       rts
+
+*-----------------------------------------------------------------------*
+;
+; Audio-Interrupts für 68000 und 68010
+
+* Tracks 00-0F zusammenmischen:
+
+Int0_000:
+
+* Achtung! Soeben begann das Abspielen des beim
+* letzten Interrupt gefüllten Channel-Buffers!
+
+       move.w  MixPeriod(pc),d0
+       lea     Softy0IRQ_000,a1
+       cmpi.b  #NT_SOFTINT,LN_TYPE(a1)
+       bne.s   .Period
+       addi.w  #32,d0                          ; step down
+       move.w  d0,MixPeriod
+       move.l  ClockBase(pc),d1
+       divu    d0,d1
+       move.w  d1,MixFreq
+.Period
+       move.w  d0,aud0+ac_per(a0)
+       move.w  d0,aud3+ac_per(a0)
+       move.w  d0,aud1+ac_per(a0)
+       move.w  d0,aud2+ac_per(a0)
+
+       JSRLIB  Cause
+
+       move.w  #INTF_AUD0,custom+intreq        ; AudioInt-Request löschen
+       rts
+
+Soft0_000:
+       movem.l d2-d7/a2-a6,-(sp)
+
+* Das rechenaufwendige Mixen passiert im Softint
+
+       lea     custom,a0
+
+;      move.w  #$f00,color(a0)
+
+       lea     T00Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T01Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T02Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T03Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T04Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T05Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T06Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T07Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T08Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T09Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T0AData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T0BData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T0CData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T0DData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T0EData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+       lea     T0FData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_000
+
+;      move.w  #$00f,color(a0)
+
+       lea     C0Buf,a2                ; Mix-Buffer konvertieren & löschen
+       adda.w  C03BufOff(pc),a2
+       move.l  a2,aud0+ac_ptr(a0)
+       lea     C3Buf,a3
+       adda.w  C03BufOff(pc),a3
+       move.l  a3,aud3+ac_ptr(a0)
+       lea     MBuffer,a4
+       lea     BTable,a5
+       move.w  #MBuf_SIZE/2-1,d0
+       moveq   #0,d1
+.Convert
+       movem.w (a4),d2-d3
+       move.l  d1,(a4)+
+       move.b  0(a5,d2.w),(a2)+        ; hier haben wir das hi byte
+       move.b  0(a5,d3.w),(a2)+
+       move.b  1(a5,d2.w),(a3)+        ; hier haben wir das lo byte
+       move.b  1(a5,d3.w),(a3)+
+       dbra    d0,.Convert
+
+       eor.w   #MBuf_SIZE,C03BufOff    ; Buffer flippen
+
+;      move.w  #$000,color(a0)
+
+       movem.l (sp)+,d2-d7/a2-a6
+       rts
+
+
+* Tracks 10-1F zusammenmischen:
+
+Int1_000:
+
+* Achtung! Soeben begann das Abspielen des beim
+* letzten Interrupt gefüllten Channel-Buffers!
+
+       move.w  MixPeriod(pc),d0
+       lea     Softy1IRQ_000,a1
+       cmpi.b  #NT_SOFTINT,LN_TYPE(a1)
+       bne.s   .Period
+       addi.w  #32,d0                          ; step down
+       move.w  d0,MixPeriod
+       move.l  ClockBase(pc),d1
+       divu    d0,d1
+       move.w  d1,MixFreq
+.Period
+       move.w  d0,aud0+ac_per(a0)
+       move.w  d0,aud3+ac_per(a0)
+       move.w  d0,aud1+ac_per(a0)
+       move.w  d0,aud2+ac_per(a0)
+
+       JSRLIB  Cause
+
+       move.w  #INTF_AUD1,custom+intreq        ; AudioInt-Request löschen
+       rts
+
+Soft1_000:
+       movem.l d2-d7/a2-a6,-(sp)
+
+* Das rechenaufwendige Mixen passiert im Softint
+
+       lea     custom,a0
+
+;      move.w  #$0f0,color(a0)
+
+       lea     T10Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T11Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T12Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T13Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T14Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T15Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T16Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T17Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T18Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T19Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T1AData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T1BData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T1CData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T1DData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T1EData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+       lea     T1FData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_000
+
+;      move.w  #$00f,color(a0)
+
+       lea     C1Buf,a2                ; Mix-Buffer konvertieren & löschen
+       adda.w  C12BufOff(pc),a2
+       move.l  a2,aud1+ac_ptr(a0)
+       lea     C2Buf,a3
+       adda.w  C12BufOff(pc),a3
+       move.l  a3,aud2+ac_ptr(a0)
+       lea     MBuffer,a4
+       lea     BTable,a5
+       move.w  #MBuf_SIZE/2-1,d0
+       moveq   #0,d1
+.Convert
+       movem.w (a4),d2-d3
+       move.l  d1,(a4)+
+       move.b  0(a5,d2.w),(a2)+        ; hier haben wir das hi byte
+       move.b  0(a5,d3.w),(a2)+
+       move.b  1(a5,d2.w),(a3)+        ; hier haben wir das lo byte
+       move.b  1(a5,d3.w),(a3)+
+       dbra    d0,.Convert
+
+       eor.w   #MBuf_SIZE,C12BufOff    ; Buffer flippen
+
+;      move.w  #$000,color(a0)
+
+       movem.l (sp)+,d2-d7/a2-a6
+       rts
+
+
+*
+* Ein Sample zum Play-Buffer dazumixen
+*
+* D2   = Gesamt-Volume des Channels
+* A2   = ^TrackData des zu mixenden Tracks
+*
+
+MixChannel_000:
+       move.l  td_SPtr(a2),a3          ; Pointer auf das Sampleende
+       move.l  td_SLen(a2),d3          ; negative Restbytes des Samples
+       bmi.s   .MxCalc                 ; noch nicht am Sampleende angekommen!
+       move.l  td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl     .MxEnd                  ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+.MxCalc        mulu.w  td_Vol(a2),d2           ; Volume des Samples
+       lsr.w   #6,d2
+       add.w   d2,d2
+       lsl.w   #8,d2
+       lea     VTable,a4               ; ^Volume-Table
+       adda.l  d2,a4
+       move.l  td_Fre(a2),d5           ; individuelle Sample-Frequency
+       divu    MixFreq(pc),d5          ; globale Mix-Frequency
+       moveq   #0,d4
+       move.w  d5,d4                   ; Period-Vorkommastellen
+       clr.w   d5
+       divu    MixFreq(pc),d5          ; Period-Nachkommastellen
+       moveq   #0,d6
+       move.w  td_FreC(a2),d6          ; Period-Nachkomma-Zähler
+       move.w  d4,d7
+       mulu    #MBuf_SIZE,d7
+       move.w  d5,d0
+       mulu    #MBuf_SIZE,d0
+       add.l   d6,d0
+       clr.w   d0
+       swap    d0
+       add.l   d0,d7
+       add.l   d3,d7                   ; Restbytes nach dem Mixen
+
+*
+* Hauptschleife - Registerbelegung:
+* D0.W = Temp.Register für die Samplebytes
+* D1.W = Temp.Register für die Samplewords
+* D2.W = Zähler für die MixLoop
+* D3.L = Restbytes vom Sample
+* D4.L = Vorkomma Faktor
+* D5.W = Nachkomma Faktor
+* D6.W = Nachkomma Zähler
+* D7   = Restbytes nach dem Mixen
+* A0   =
+* A1   =
+* A2   = Zeiger auf TrackData des Tracks
+* A3   = Zeiger auf das Ende des Samples
+* A4   = Zeiger auf Volume-Tabelle des Samples
+* A5   = Zeiger auf den Mix-Buffer
+* A6   =
+*
+
+.MxSamp        lea     MBuffer,a5              ; Pointer auf den Mix-Buffer
+       move.w  #MBuf_SIZE/2-1,d2       ; Zähler, bis MixBuffer voll
+
+       tst.l   d7                      ; wird das Sampleende beim Mixen erreicht ?
+       bmi     .MxTst2                 ; Nein, dann schnelle Mix-Routine benutzen!
+
+.MxTst1        tst.w   d4                      ; Period-Vorkommastellen Null?
+       bne.s   .MxLop3                 ; Nein, dann normale Mix-Routine benutzen!
+       moveq   #1,d4
+
+       adda.l  d3,a3
+       moveq   #0,d0
+       move.b  (a3)+,d0                ; erstes Samplebyte holen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+.MxLop0        add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       bcc.s   .MxLop1
+       add.l   d4,d3
+       bmi.s   .MxGet1                 ; noch nicht am Sampleende angekommen!
+       add.l   td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl.s   .MxPerC                 ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+       adda.l  d3,a3
+.MxGet1        moveq   #0,d0
+       move.b  (a3)+,d0                ; ein Sample zum Buffer dazumixen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+.MxLop1        add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       bcc.s   .MxLop2
+       add.l   d4,d3
+       bmi.s   .MxGet2                 ; noch nicht am Sampleende angekommen!
+       add.l   td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl.s   .MxPerC                 ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+       adda.l  d3,a3
+.MxGet2        moveq   #0,d0
+       move.b  (a3)+,d0                ; ein Sample zum Buffer dazumixen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+.MxLop2        dbra    d2,.MxLop0              ; bis MixBuffer voll
+       add.l   d3,d4
+       suba.l  d4,a3
+       bra.s   .MxPerC
+
+.MxLop3        moveq   #0,d0
+       move.b  0(a3,d3.l),d0           ; ein Sample zum Buffer dazumixen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+       add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       addx.l  d4,d3
+       bmi.s   .MxLop4                 ; noch nicht am Sampleende angekommen!
+       add.l   td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl.s   .MxPerC                 ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+.MxLop4        moveq   #0,d0
+       move.b  0(a3,d3.l),d0           ; ein Sample zum Buffer dazumixen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+       add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       addx.l  d4,d3
+       bmi.s   .MxLop5                 ; noch nicht am Sampleende angekommen!
+       add.l   td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl.s   .MxPerC                 ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+.MxLop5        dbra    d2,.MxLop3              ; bis MixBuffer voll
+
+.MxPerC        move.w  d6,td_FreC(a2)          ; Neuer Stand des Runtersample-Zählers
+.MxSPtr        move.l  a3,td_SPtr(a2)          ; Endadresse des Samples sichern
+.MxSLen        move.l  d3,td_SLen(a2)          ; Neue Position im Sample erreicht
+
+.MxEnd rts
+
+.MxTst2        tst.w   d4                      ; Period-Vorkommastellen Null?
+       bne.s   .MxLop9                 ; Nein, dann normale Mix-Routine benutzen!
+       moveq   #1,d4
+
+       adda.l  d3,a3
+       move.l  d7,d3
+       moveq   #0,d0
+       move.b  (a3)+,d0                ; erstes Samplebyte holen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+.MxLop6        add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       bcc.s   .MxLop7
+       moveq   #0,d0
+       move.b  (a3)+,d0                ; ein Sample zum Buffer dazumixen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+.MxLop7        add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       bcc.s   .MxLop8
+       moveq   #0,d0
+       move.b  (a3)+,d0                ; ein Sample zum Buffer dazumixen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+.MxLop8        dbra    d2,.MxLop6              ; bis MixBuffer voll
+       add.l   d3,d4
+       suba.l  d4,a3
+       bra.s   .MxPerC
+
+.MxLop9        moveq   #0,d0
+       move.b  0(a3,d3.l),d0           ; ein Sample zum Buffer dazumixen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+       add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       addx.l  d4,d3
+       moveq   #0,d0
+       move.b  0(a3,d3.l),d0           ; ein Sample zum Buffer dazumixen
+       add.w   d0,d0
+       move.w  0(a4,d0.w),d1
+       add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       addx.l  d4,d3
+       dbra    d2,.MxLop9              ; bis MixBuffer voll
+       bra.s   .MxPerC
+
+*-----------------------------------------------------------------------*
+;
+; Audio-Interrupts für 68020 und höher
+
+* Tracks 00-0F zusammenmischen:
+
+Int0_020:
+
+* Achtung! Soeben begann das Abspielen des beim
+* letzten Interrupt gefüllten Channel-Buffers!
+
+       move.w  MixPeriod(pc),d0
+       lea     Softy0IRQ_020,a1
+       cmpi.b  #NT_SOFTINT,LN_TYPE(a1)
+       bne.s   .Period
+       addi.w  #32,d0                          ; step down
+       move.w  d0,MixPeriod
+       move.l  ClockBase(pc),d1
+       divu    d0,d1
+       move.w  d1,MixFreq
+.Period
+       move.w  d0,aud0+ac_per(a0)
+       move.w  d0,aud3+ac_per(a0)
+       move.w  d0,aud1+ac_per(a0)
+       move.w  d0,aud2+ac_per(a0)
+
+       JSRLIB  Cause
+
+       move.w  #INTF_AUD0,custom+intreq        ; AudioInt-Request löschen
+       rts
+
+Soft0_020:
+       movem.l d2-d7/a2-a6,-(sp)
+
+* Das rechenaufwendige Mixen passiert im Softint
+
+       lea     custom,a0
+
+;      move.w  #$f00,color(a0)
+
+       lea     T00Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T01Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T02Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T03Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T04Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T05Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T06Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T07Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T08Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T09Data(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T0AData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T0BData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T0CData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T0DData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T0EData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+       lea     T0FData(pc),a2
+       move.w  Volume07(pc),d2
+       bsr     MixChannel_020
+
+;      move.w  #$00f,color(a0)
+
+       lea     C0Buf,a2                ; Mix-Buffer konvertieren & löschen
+       adda.w  C03BufOff(pc),a2
+       move.l  a2,aud0+ac_ptr(a0)
+       lea     C3Buf,a3
+       adda.w  C03BufOff(pc),a3
+       move.l  a3,aud3+ac_ptr(a0)
+       lea     MBuffer,a4
+       lea     BTable,a5
+       move.w  #MBuf_SIZE/2-1,d0
+       moveq   #0,d1
+.Convert
+       movem.w (a4),d2-d3
+       move.l  d1,(a4)+
+       move.b  0(a5,d2.w),(a2)+        ; hier haben wir das hi byte
+       move.b  0(a5,d3.w),(a2)+
+       move.b  1(a5,d2.w),(a3)+        ; hier haben wir das lo byte
+       move.b  1(a5,d3.w),(a3)+
+       dbra    d0,.Convert
+
+       eor.w   #MBuf_SIZE,C03BufOff    ; Buffer flippen
+
+;      move.w  #$000,color(a0)
+
+       movem.l (sp)+,d2-d7/a2-a6
+       rts
+
+
+* Tracks 10-1F zusammenmischen:
+
+Int1_020:
+
+* Achtung! Soeben begann das Abspielen des beim
+* letzten Interrupt gefüllten Channel-Buffers!
+
+       move.w  MixPeriod(pc),d0
+       lea     Softy1IRQ_020,a1
+       cmpi.b  #NT_SOFTINT,LN_TYPE(a1)
+       bne.s   .Period
+       addi.w  #32,d0                          ; step down
+       move.w  d0,MixPeriod
+       move.l  ClockBase(pc),d1
+       divu    d0,d1
+       move.w  d1,MixFreq
+.Period
+       move.w  d0,aud0+ac_per(a0)
+       move.w  d0,aud3+ac_per(a0)
+       move.w  d0,aud1+ac_per(a0)
+       move.w  d0,aud2+ac_per(a0)
+
+       JSRLIB  Cause
+
+       move.w  #INTF_AUD1,custom+intreq        ; AudioInt-Request löschen
+       rts
+
+Soft1_020:
+       movem.l d2-d7/a2-a6,-(sp)
+
+* Das rechenaufwendige Mixen passiert im Softint
+
+       lea     custom,a0
+
+;      move.w  #$0f0,color(a0)
+
+       lea     T10Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T11Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T12Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T13Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T14Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T15Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T16Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T17Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T18Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T19Data(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T1AData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T1BData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T1CData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T1DData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T1EData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+       lea     T1FData(pc),a2
+       move.w  Volume8F(pc),d2
+       bsr     MixChannel_020
+
+;      move.w  #$00f,color(a0)
+
+       lea     C1Buf,a2                ; Mix-Buffer konvertieren & löschen
+       adda.w  C12BufOff(pc),a2
+       move.l  a2,aud1+ac_ptr(a0)
+       lea     C2Buf,a3
+       adda.w  C12BufOff(pc),a3
+       move.l  a3,aud2+ac_ptr(a0)
+       lea     MBuffer,a4
+       lea     BTable,a5
+       move.w  #MBuf_SIZE/2-1,d0
+       moveq   #0,d1
+.Convert
+       movem.w (a4),d2-d3
+       move.l  d1,(a4)+
+       move.b  0(a5,d2.w),(a2)+        ; hier haben wir das hi byte
+       move.b  0(a5,d3.w),(a2)+
+       move.b  1(a5,d2.w),(a3)+        ; hier haben wir das lo byte
+       move.b  1(a5,d3.w),(a3)+
+       dbra    d0,.Convert
+
+       eor.w   #MBuf_SIZE,C12BufOff    ; Buffer flippen
+
+;      move.w  #$000,color(a0)
+
+       movem.l (sp)+,d2-d7/a2-a6
+       rts
+
+
+*
+* Ein Sample zum Play-Buffer dazumixen
+*
+* D2   = Gesamt-Volume des Channels
+* A2   = ^TrackData des zu mixenden Tracks
+*
+
+MixChannel_020:
+       move.l  td_SPtr(a2),a3          ; Pointer auf das Sampleende
+       move.l  td_SLen(a2),d3          ; negative Restbytes des Samples
+       bmi.s   .MxCalc                 ; noch nicht am Sampleende angekommen!
+       move.l  td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl     .MxEnd                  ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+.MxCalc        mulu.w  td_Vol(a2),d2           ; Volume des Samples
+       lsr.w   #6,d2
+       add.w   d2,d2
+       lsl.w   #8,d2
+       lea     VTable,a4               ; ^Volume-Table
+       adda.l  d2,a4
+       move.l  td_Fre(a2),d5           ; individuelle Sample-Frequency
+       divu    MixFreq(pc),d5          ; globale Mix-Frequency
+       moveq   #0,d4
+       move.w  d5,d4                   ; Period-Vorkommastellen
+       clr.w   d5
+       divu    MixFreq(pc),d5          ; Period-Nachkommastellen
+       moveq   #0,d6
+       move.w  td_FreC(a2),d6          ; Period-Nachkomma-Zähler
+       move.w  d4,d7
+       mulu    #MBuf_SIZE,d7
+       move.w  d5,d0
+       mulu    #MBuf_SIZE,d0
+       add.l   d6,d0
+       clr.w   d0
+       swap    d0
+       add.l   d0,d7
+       add.l   d3,d7                   ; Restbytes nach dem Mixen
+
+*
+* Hauptschleife - Registerbelegung:
+* D0.W = Temp.Register für die Samplebytes
+* D1.W = Temp.Register für die Samplewords
+* D2.W = Zähler für die MixLoop
+* D3.L = Restbytes vom Sample
+* D4.L = Vorkomma Faktor
+* D5.W = Nachkomma Faktor
+* D6.W = Nachkomma Zähler
+* D7   = Restbytes nach dem Mixen
+* A0   =
+* A1   =
+* A2   = Zeiger auf TrackData des Tracks
+* A3   = Zeiger auf das Ende des Samples
+* A4   = Zeiger auf Volume-Tabelle des Samples
+* A5   = Zeiger auf den Mix-Buffer
+* A6   =
+*
+
+.MxSamp        lea     MBuffer,a5              ; Pointer auf den Mix-Buffer
+       move.w  #MBuf_SIZE/2-1,d2       ; Zähler, bis MixBuffer voll
+
+       tst.l   d7                      ; wird das Sampleende beim Mixen erreicht ?
+       bmi     .MxTst2                 ; Nein, dann schnelle Mix-Routine benutzen!
+
+.MxTst1        moveq   #0,d0
+       tst.w   d4                      ; Period-Vorkommastellen Null?
+       bne.s   .MxLop3                 ; Nein, dann normale Mix-Routine benutzen!
+       moveq   #1,d4
+
+       adda.l  d3,a3
+       move.b  (a3)+,d0                ; erstes Samplebyte holen
+       move.w  0(a4,d0.w*2),d1
+.MxLop0        add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       bcc.s   .MxLop1
+       add.l   d4,d3
+       bmi.s   .MxGet1                 ; noch nicht am Sampleende angekommen!
+       add.l   td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl.s   .MxPerC                 ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+       adda.l  d3,a3
+.MxGet1        move.b  (a3)+,d0                ; ein Sample zum Buffer dazumixen
+       move.w  0(a4,d0.w*2),d1
+.MxLop1        add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       bcc.s   .MxLop2
+       add.l   d4,d3
+       bmi.s   .MxGet2                 ; noch nicht am Sampleende angekommen!
+       add.l   td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl.s   .MxPerC                 ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+       adda.l  d3,a3
+.MxGet2        move.b  (a3)+,d0                ; ein Sample zum Buffer dazumixen
+       move.w  0(a4,d0.w*2),d1
+.MxLop2        dbra    d2,.MxLop0              ; bis MixBuffer voll
+       add.l   d3,d4
+       suba.l  d4,a3
+       bra.s   .MxPerC
+
+.MxLop3        move.b  0(a3,d3.l),d0           ; ein Sample zum Buffer dazumixen
+       move.w  0(a4,d0.w*2),d1
+       add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       addx.l  d4,d3
+       bmi.s   .MxLop4                 ; noch nicht am Sampleende angekommen!
+       add.l   td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl.s   .MxPerC                 ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+.MxLop4        move.b  0(a3,d3.l),d0           ; ein Sample zum Buffer dazumixen
+       move.w  0(a4,d0.w*2),d1
+       add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       addx.l  d4,d3
+       bmi.s   .MxLop5                 ; noch nicht am Sampleende angekommen!
+       add.l   td_LLen(a2),d3          ; ist ein Loop-Sample vorhanden ?
+       bpl.s   .MxPerC                 ; Kein Loopsample da, also raus!
+       move.l  td_LPtr(a2),a3          ; Pointer auf das Loopende
+.MxLop5        dbra    d2,.MxLop3              ; bis MixBuffer voll
+
+.MxPerC        move.w  d6,td_FreC(a2)          ; Neuer Stand des Runtersample-Zählers
+.MxSPtr        move.l  a3,td_SPtr(a2)          ; Endadresse des Samples sichern
+.MxSLen        move.l  d3,td_SLen(a2)          ; Neue Position im Sample erreicht
+
+.MxEnd rts
+
+.MxTst2        moveq   #0,d0
+       tst.w   d4                      ; Period-Vorkommastellen Null?
+       bne.s   .MxLop9                 ; Nein, dann normale Mix-Routine benutzen!
+       moveq   #1,d4
+
+       adda.l  d3,a3
+       move.l  d7,d3
+       move.b  (a3)+,d0                ; erstes Samplebyte holen
+       move.w  0(a4,d0.w*2),d1
+.MxLop6        add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       bcc.s   .MxLop7
+       move.b  (a3)+,d0                ; ein Sample zum Buffer dazumixen
+       move.w  0(a4,d0.w*2),d1
+.MxLop7        add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       bcc.s   .MxLop8
+       move.b  (a3)+,d0                ; ein Sample zum Buffer dazumixen
+       move.w  0(a4,d0.w*2),d1
+.MxLop8        dbra    d2,.MxLop6              ; bis MixBuffer voll
+       add.l   d3,d4
+       suba.l  d4,a3
+       bra.s   .MxPerC
+
+.MxLop9        move.b  0(a3,d3.l),d0           ; ein Sample zum Buffer dazumixen
+       move.w  0(a4,d0.w*2),d1
+       add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       addx.l  d4,d3
+       move.b  0(a3,d3.l),d0           ; ein Sample zum Buffer dazumixen
+       move.w  0(a4,d0.w*2),d1
+       add.w   d1,(a5)+
+       add.w   d5,d6                   ; Sample fortschalten
+       addx.l  d4,d3
+       dbra    d2,.MxLop9              ; bis MixBuffer voll
+       bra.s   .MxPerC
+
+*-----------------------------------------------------------------------*
+;
+; Daten für die Audio-Interrupt Routine
+
+       CNOP    0,4
+
+ClockBase:     dc.l    0       ;Clock-Konstante
+MixPeriod:     dc.w    256     ;Gesamt-Period für alle Kanäle
+MixFreq:       dc.w    8287    ;Gesamt-Frequenz für alle Kanäle
+
+* Variablen der Audio-Interrupt-Routine:
+
+Volume07:      dc.w    64      ;Channel-Volumes für links und rechts
+Volume8F:      dc.w    64
+
+C03BufOff:     dc.w    0       ;Buffer-Flip-Offset (0 oder MBuf_SIZE)
+C12BufOff:     dc.w    0
+
+
+* 32 Track-Data-Strukturen:
+
+       rsreset
+td_SPtr        rs.l    1       ; Zeiger auf das Sampleende
+td_SLen        rs.l    1       ; negative Sample-Restbytes
+td_LPtr        rs.l    1       ; Endadr. des Loopteils
+td_LLen        rs.l    1       ; negative Länge des Loopteils
+td_Vol rs.w    1       ; Volume-Wert 0...64
+td_Fre rs.l    1       ; Frequenz-Wert
+td_FreC        rs.w    1       ; Frequenz-Nachkomma-Zähler
+td_SIZE        rs.l    0
+
+
+T00Data:       ds.b    td_SIZE
+T01Data:       ds.b    td_SIZE
+T02Data:       ds.b    td_SIZE
+T03Data:       ds.b    td_SIZE
+T04Data:       ds.b    td_SIZE
+T05Data:       ds.b    td_SIZE
+T06Data:       ds.b    td_SIZE
+T07Data:       ds.b    td_SIZE
+T08Data:       ds.b    td_SIZE
+T09Data:       ds.b    td_SIZE
+T0AData:       ds.b    td_SIZE
+T0BData:       ds.b    td_SIZE
+T0CData:       ds.b    td_SIZE
+T0DData:       ds.b    td_SIZE
+T0EData:       ds.b    td_SIZE
+T0FData:       ds.b    td_SIZE
+T10Data:       ds.b    td_SIZE
+T11Data:       ds.b    td_SIZE
+T12Data:       ds.b    td_SIZE
+T13Data:       ds.b    td_SIZE
+T14Data:       ds.b    td_SIZE
+T15Data:       ds.b    td_SIZE
+T16Data:       ds.b    td_SIZE
+T17Data:       ds.b    td_SIZE
+T18Data:       ds.b    td_SIZE
+T19Data:       ds.b    td_SIZE
+T1AData:       ds.b    td_SIZE
+T1BData:       ds.b    td_SIZE
+T1CData:       ds.b    td_SIZE
+T1DData:       ds.b    td_SIZE
+T1EData:       ds.b    td_SIZE
+T1FData:       ds.b    td_SIZE
+
+
+* Stummes Instrument:
+
+QuietInstr:
+       dc.l    QuietInstr      ; Endadresse
+       dc.l    0               ; Restlänge
+       dc.l    QuietInstr      ; Ende LoopSample
+       dc.l    0               ; Länge LoopSample
+
+
+
+*-----------------------------------------------------------------------*
+
+;
+;
+       SECTION GenieTabBuff,BSS
+;
+;
+
+* Umrechnungs-Tabelle für versch. Lautstärken:
+* (wird durch NoteInit initialisiert)
+
+VTable:        ds.w    256*65
+
+
+*-----------------------------------------------------------------------*
+
+;
+;
+       SECTION GenieBitBuff,BSS
+;
+;
+
+* Umrechnungs-Tabelle für 14Bit Ausgabe:
+* (wird beim Start initialisiert)
+
+BitTable:
+       ds.b    256*128
+BTable:
+       ds.b    256*128
+
+
+*-----------------------------------------------------------------------*
+
+;
+;
+       SECTION GenieMixBuff,BSS
+;
+;
+
+* Mix-Buffer:
+
+MBuf_SIZE      equ     200
+
+MBuffer:
+       ds.w    MBuf_SIZE
+MBufferEnd:
+
+
+
+*-----------------------------------------------------------------------*
+;
+; NoteChannels - From TakeTracker player
+
+;
+;
+       SECTION NoteChan,BSS
+;
+;
+
+NoteChannels   ds.b    NoteChannel_SIZE*32
+
+
+
+*-----------------------------------------------------------------------*
+
+;
+;
+       SECTION GenieSampBuff,BSS_C
+;
+;
+
+* Channel-Buffer:
+
+CBuffers:
+C0Buf: ds.b    MBuf_SIZE
+       ds.b    MBuf_SIZE       ;Für Double-Buffering!
+C1Buf: ds.b    MBuf_SIZE
+       ds.b    MBuf_SIZE       ;Für Double-Buffering!
+C2Buf: ds.b    MBuf_SIZE
+       ds.b    MBuf_SIZE       ;Für Double-Buffering!
+C3Buf: ds.b    MBuf_SIZE
+       ds.b    MBuf_SIZE       ;Für Double-Buffering!
+CBuffersEnd:
+
+EmptyCBuf:
+       ds.b    MBuf_SIZE
+
+
+*-----------------------------------------------------------------------*
+
+       section __MERGED,data
+
+
+**********************************************************************
+* Player Data
+**********************************************************************
+
+MyTask         dc.l    0
+ParentTask     dc.l    0
+
+; Library bases
+
+_SysBase       dc.l    0
+_GFXBase       dc.l    0
+
+retval         dc.l    0
+
+_OldAudio0:    dc.l    0       ; Old audio interrupt vectors
+_OldAudio1:    dc.l    0
+
+NoteInfo:      dc.l    0
+
+PlayFreq       dc.l    14433
+ScaleFactor    dc.w    0
+ChannelNum     dc.w    0
+BoostFlag      dc.l    1       ; Automatic Volume Boost Calculation
+BoostFactor    dc.l    0
+
+
+
+**********************************************************************
+* From TakeTracker player
+**********************************************************************
+
+delibase       dc.l 0
+
+songinfo       dc.l 0
+mt_size                dc.l 0
+
+mt_NumCh       dc.w 0
+mt_rsize       dc.w 0
+mt_psize       dc.w 0
+
+NotePlay       dc.l NoteStructure
+
+NoteStructure  dc.l NoteChannels               ; NoteChannel Array
+               dc.l NSTF_Period!NSTF_Signed!NSTF_8Bit  ; 8-Bit signed sample data
+               dc.l 28867                      ; max. frequency
+               dc.w 64                         ; max. volume
+               dcb.b 18,0
+
+mt_ChanTemp    ds.b    pn_SIZEOF * MAXCHANNELS
+
+mt_speed       dc.w 0
+mt_counter     dc.w 0
+mt_SongPos     dc.w 0
+mt_StartPos    dc.w 0
+mt_MaxPos      dc.w 0
+mt_PBreakPos   dc.w 0
+mt_PatternPos  dc.w 0
+mt_PosJumpFlag dc.b 0
+mt_PBreakFlag  dc.b 0
+mt_LowMask     dc.b 0
+mt_PattDelTime dc.b 0
+mt_PattDelTime2        dc.b 0
+mt_Reset       dc.b 0
+
+
+**********************************************************************
+* AudioInterrupts
+**********************************************************************
+
+Audio0IRQ_000
+       dc.l 0                  ; Succ
+       dc.l 0                  ; Pred
+       dc.b NT_INTERRUPT       ; Type
+       dc.b 0                  ; Pri
+       dc.l audiointname       ; Name
+       dc.l 0                  ; Data
+       dc.l Int0_000           ; Code
+
+Audio1IRQ_000
+       dc.l 0                  ; Succ
+       dc.l 0                  ; Pred
+       dc.b NT_INTERRUPT       ; Type
+       dc.b 0                  ; Pri
+       dc.l audiointname       ; Name
+       dc.l 0                  ; Data
+       dc.l Int1_000           ; Code
+
+Audio0IRQ_020
+       dc.l 0                  ; Succ
+       dc.l 0                  ; Pred
+       dc.b NT_INTERRUPT       ; Type
+       dc.b 0                  ; Pri
+       dc.l audiointname       ; Name
+       dc.l 0                  ; Data
+       dc.l Int0_020           ; Code
+
+Audio1IRQ_020
+       dc.l 0                  ; Succ
+       dc.l 0                  ; Pred
+       dc.b NT_INTERRUPT       ; Type
+       dc.b 0                  ; Pri
+       dc.l audiointname       ; Name
+       dc.l 0                  ; Data
+       dc.l Int1_020           ; Code
+
+
+**********************************************************************
+* SoftInterrupts
+**********************************************************************
+
+Softy0IRQ_000
+       dc.l 0                                  ; Succ
+       dc.l 0                                  ; Pred
+       dc.b NT_INTERRUPT                       ; Type
+       dc.b 0                                  ; Pri
+       dc.l audiointname                       ; Name
+       dc.l 0                                  ; Data
+       dc.l Soft0_000                          ; Code
+
+Softy1IRQ_000
+       dc.l 0                                  ; Succ
+       dc.l 0                                  ; Pred
+       dc.b NT_INTERRUPT                       ; Type
+       dc.b 0                                  ; Pri
+       dc.l audiointname                       ; Name
+       dc.l 0                                  ; Data
+       dc.l Soft1_000                          ; Code
+
+
+Softy0IRQ_020
+       dc.l 0                                  ; Succ
+       dc.l 0                                  ; Pred
+       dc.b NT_INTERRUPT                       ; Type
+       dc.b 0                                  ; Pri
+       dc.l audiointname                       ; Name
+       dc.l 0                                  ; Data
+       dc.l Soft0_020                          ; Code
+
+Softy1IRQ_020
+       dc.l 0                                  ; Succ
+       dc.l 0                                  ; Pred
+       dc.b NT_INTERRUPT                       ; Type
+       dc.b 0                                  ; Pri
+       dc.l audiointname                       ; Name
+       dc.l 0                                  ; Data
+       dc.l Soft1_020                          ; Code
+
+
+**********************************************************************
+* AudioRequest
+**********************************************************************
+
+AllocIORequest:
+       dc.l 0                  ; LN_SUCC
+       dc.l 0                  ; LN_PRED
+       dc.b 0                  ; LN_TYPE
+       dc.b ADALLOC_MAXPREC    ; LN_PRI
+       dc.l 0                  ; LN_NAME
+
+       dc.l 0                  ; message reply port
+       dc.w 0                  ; message len in bytes
+
+       dc.l 0                  ; device node pointer
+       dc.l 0                  ; unit (driver private)
+       dc.w 0                  ; device command
+       dc.b 0                  ; special flags
+       dc.b 0                  ; error or warning code
+       dc.w 0                  ; ioa_AllocKey
+       dc.l ChannelMap         ; ioa_Data
+       dc.l 1                  ; ioa_Length
+       dc.w 0                  ; ioa_Period
+       dc.w 0                  ; ioa_Volume
+       dc.w 0                  ; ioa_Cycles
+       dc.l 0                  ; LN_SUCC
+       dc.l 0                  ; LN_PRED
+       dc.b 0                  ; LN_TYPE
+       dc.b 0                  ; LN_PRI
+       dc.l 0                  ; LN_NAME
+       dc.l 0                  ; message reply port
+       dc.w 0                  ; message len in bytes
+
+
+ChannelMap     dc.b $0f                        ; Alle Kanäle belegen
+
+audiointname   dc.b 'XModule Player',0
+       even
+
+       END
diff --git a/Player.h b/Player.h
new file mode 100644 (file)
index 0000000..5aad2fe
--- /dev/null
+++ b/Player.h
@@ -0,0 +1,24 @@
+/*     Player.h
+**
+**     Copyright (C) 1993,94,95 Bernardo Innocenti
+**
+**     Use 4 chars wide TABs to read this source
+**
+**     Definitions for the player interface.
+*/
+
+struct PlayerCmd
+{
+       struct Message  pcmd_Message;
+       ULONG                   pcmd_ID;
+       APTR                    pcmd_Data;
+       LONG                    pcmd_Err;
+};
+
+
+enum
+{
+       PCMD_SETUP = -1,
+       PCMD_INIT,
+       PCMD_PLAY,
+};
diff --git a/Player.i b/Player.i
new file mode 100644 (file)
index 0000000..1f9dde5
--- /dev/null
+++ b/Player.i
@@ -0,0 +1,103 @@
+       IFND    XMREPLAY_I
+XMREPLAY_I     SET     1
+**
+**     XModule replay routine 1.0
+**
+**     Copyright (C) 1995 Bernardo Innocenti
+**
+**     Assembler structure definitions for player
+**
+
+       IFND    EXEC_TYPES_I
+       include exec/types.i
+       ENDC
+
+
+       STRUCTURE PlayerCmd,MN_SIZE
+       ULONG   pcmd_ID
+       APTR    pcmd_Data
+       LONG    pcmd_Err
+       LABEL   PlayerCmd_size
+
+
+       ENUM -1
+       EITEM PCMD_SETUP
+       EITEM PCMD_PLAY
+       EITEM PCMD_INIT
+       EITEM PCMD_COUNT
+
+       ENUM 0
+       EITEM PERR_OK
+       EITEM PERR_UNKNOWN_COMMAND
+       EITEM PERR_NO_AUDIO
+
+
+
+       STRUCTURE NoteStruct,0
+       APTR    nst_Channels            ; pointer to a list of notechannels
+       ULONG   nst_Flags               ; misc flags (see below)
+       ULONG   nst_MaxFrequency        ; max. frequency of this player (28,867 Hz in DMA mode)
+       UWORD   nst_MaxVolume           ; max. volume of this player (in most cases 64)
+       STRUCT  nst_Reserved,18         ; reserved for future use (must be 0 for now)
+       LABEL   NoteStruct_SIZE
+
+
+       BITDEF  NST,Dummy,0             ; only a dummy-NoteStruct (no NotePlayer needed)
+       BITDEF  NST,Period,1            ; Amiga period supplied instead of frequency
+       BITDEF  NST,ExtPeriod,2         ; Extended period (period*4) supplied instead of frequency
+       BITDEF  NST,NTSCTiming,3        ; Period/ExtPeriod supplied in NTSC instead of PAL
+       BITDEF  NST,EvenLength,4        ; Samplelength supplied as WORD instead of LONG
+       BITDEF  NST,AllRepeats,5        ; play Repeats even if no One-Shot part was played yet
+
+       BITDEF  NST,Reverse,8           ; little endian byte ordering
+       BITDEF  NST,Signed,9            ; sample data is signed linear (2's complement)
+       BITDEF  NST,Unsigned,10         ;       -"-     unsigned linear
+       BITDEF  NST,Ulaw,11             ;       -"-     U-law (logarithmic)
+       BITDEF  NST,Alaw,12             ;       -"-     A-law (logarithmic)
+       BITDEF  NST,Float,13            ;       -"-     IEEE floats
+
+       BITDEF  NST,7Bit,16             ; sample data is in 7-bit format
+       BITDEF  NST,8Bit,17             ;       -"-     bytes
+       BITDEF  NST,16Bit,18            ;       -"-     16-bit words
+       BITDEF  NST,24Bit,19            ;       -"-     24-bit data
+       BITDEF  NST,32Bit,20            ;       -"-     longwords
+       BITDEF  NST,64Bit,21            ;       -"-     quadwords
+
+NSTD_TypeMask EQU NSTF_Reverse!NSTF_Signed!NSTF_Unsigned!NSTF_Ulaw!NSTF_Alaw!NSTF_Float
+NSTD_SizeMask EQU NSTF_7Bit!NSTF_8Bit!NSTF_16Bit!NSTF_24Bit!NSTF_32Bit!NSTF_64Bit
+
+
+       STRUCTURE NoteChannel,0
+       APTR    nch_NextChannel         ; next channel in the list (NULL if last)
+       ULONG   nch_NotePlayer          ; for use by the noteplayer (the deliplayer must ignore this)
+       WORD    nch_Reserved0           ; reserved for future use (must be 0 for now)
+       UBYTE   nch_Private             ; just what it says
+       UBYTE   nch_Changed             ; what has changed since last call
+       WORD    nch_StereoPos           ; set this field when the InitNote function is called
+       WORD    nch_Stereo              ; describes "where" this channel is supposed to play
+       APTR    nch_SampleStart         ; ^sampledata
+       ULONG   nch_SampleLength        ; size of sample
+       APTR    nch_RepeatStart         ; ^repeat part of sample
+       ULONG   nch_RepeatLength        ; size of repeat part
+       ULONG   nch_Frequency           ; frequency (or period) of sample
+       UWORD   nch_Volume              ; volume of sample
+       STRUCT  nch_Reserved1,26        ; reserved for future use (must be 0 for now)
+       LABEL   NoteChannel_SIZE
+
+
+       BITDEF  NCH,Stereo,0            ; pan position of channel has changed (???)
+       BITDEF  NCH,Sample,1            ; one-shot part of sample has changed
+       BITDEF  NCH,Repeat,2            ; repeat part of sample has changed
+       BITDEF  NCH,Frequency,3         ; frequency has changed
+       BITDEF  NCH,Volume,4            ; volume (or pan position ???) has changed
+       BITDEF  NCH,Trigger,5           ; trigger sample
+
+       BITDEF  NCH,Loop,16             ; sample has looped at least once (set by DeliTracker)
+
+NCHD_Ignore    EQU     -32768          ; ignore this notechannel
+NCHD_Balanced  EQU     0               ; play balanced on both sides
+NCHD_FarLeft   EQU     -32767          ; play only on left speaker
+NCHD_FarRight  EQU     32767           ; play only on right speaker
+
+
+       ENDC    ; !XMREPLAY_I
diff --git a/Players/32Channels.player b/Players/32Channels.player
new file mode 100644 (file)
index 0000000..ad443d2
Binary files /dev/null and b/Players/32Channels.player differ
diff --git a/Prefs.c b/Prefs.c
new file mode 100644 (file)
index 0000000..809ce6f
--- /dev/null
+++ b/Prefs.c
@@ -0,0 +1,645 @@
+/*
+**     Prefs.c
+**
+**     Copyright (C) 1994,95,96,97 Bernardo Innocenti
+**
+**     Save and load preferences.
+*/
+
+
+#include <exec/memory.h>
+#include <prefs/prefhdr.h>
+#include <libraries/iffparse.h>
+#include <libraries/asl.h>
+#include <libraries/reqtools.h>
+#include <libraries/xmodule.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/iffparse.h>
+#include <proto/intuition.h>
+#include <proto/asl.h>
+#include <proto/reqtools.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+#define PRHD_VERS 6    /* Current version of XModule's Preferences file */
+
+#define ID_XMPR MAKE_ID('X','M','P','R')       /* XModule PRefs                        */
+#define ID_XMFN MAKE_ID('X','M','F','N')       /* XModule FoNts                        */
+#define ID_XMSC MAKE_ID('X','M','S','C')       /* XModule SCreen                       */
+#define ID_XMWN MAKE_ID('X','M','W','N')       /* XModule WiNdow                       */
+#define ID_XMFR MAKE_ID('X','M','F','R')       /* XModule File Requester       */
+
+/*
+ *     XModule Preferences format:
+ *
+ *     FORM PREF       Standard preferences file format
+ *     PRHD            Standard preferences chunk
+ *     XMPR            XModulePRefs
+ *     XMFN            XModuleFoNts
+ *     XMSC            XModuleSCreen
+ *     XMWN            XModuleWiNdow (appears several times, once per window)
+ *     XMFR            XModuleFileRequester (appears several times, once per requester)
+ *     EOF
+ */
+
+struct XMPrefs
+{
+       struct GuiSwitches              GuiSwitches;
+       struct SaveSwitches             SaveSwitches;
+       struct ClearSwitches    ClearSwitches;
+       struct OptSwitches              OptSwitches;
+       struct PattSwitches             PattSwitches;
+       UBYTE                                   SaverName[32];
+};
+
+
+struct XMWindow
+{
+       LONG            WindowID;
+       BOOL            WindowOpen,
+                               WindowZoomed;
+       struct IBox     WindowSize,
+                               WindowZoom;
+};
+
+struct XMFonts
+{
+       /* The TextAttr->ta_Name field does not point
+        * to the font name!
+        */
+       struct TextAttr ScreenAttr,
+                                       WindowAttr,
+                                       ListAttr,
+                                       EditorAttr;
+       UBYTE                   ScreenFontName[32],
+                                       WindowFontName[32],
+                                       ListFontName[32],
+                                       EditorFontName[32];
+};
+
+
+
+struct XMFRPrefs
+{
+       struct IBox     FReqSize;
+       UBYTE           Dir[PATHNAME_MAX],
+                               Pattern[PATHNAME_MAX];
+};
+
+
+
+/* Local function prototypes */
+
+static LONG SaveXMWN (struct IFFHandle *iff, struct WinUserData *wud);
+static LONG LoadXMWN (struct IFFHandle *iff);
+static LONG SaveXMFR (struct IFFHandle *iff, struct XMFileReq *xmfr);
+static LONG LoadXMFR (struct IFFHandle *iff, struct XMFileReq *xmfr);
+
+
+
+GLOBALCALL LONG LoadPrefs (CONST_STRPTR filename)
+{
+       struct IFFHandle        *iff;
+       struct ContextNode      *cn;
+       LONG                             err;
+       UWORD                            frcount = 0;
+       BOOL                             update_screen = FALSE;
+
+       static LONG stopchunks[] =
+       {
+               ID_PREF, ID_PRHD,
+               ID_PREF, ID_XMPR,
+               ID_PREF, ID_XMFN,
+               ID_PREF, ID_XMSC,
+               ID_PREF, ID_XMWN,
+               ID_PREF, ID_XMFR
+       };
+
+
+       if (!(iff = AllocIFF()))
+               return ERROR_NO_FREE_STORE;
+
+       if (!(iff->iff_Stream = (ULONG) Open (filename, MODE_OLDFILE)))
+       {
+               err = IoErr();
+               goto error1;
+       }
+
+       InitIFFasDOS (iff);
+
+       if (err = OpenIFF (iff, IFFF_READ))
+               goto error2;
+
+       if (err = StopChunks (iff, stopchunks, 6))
+               goto error3;
+
+       while (1)
+       {
+               if (err = ParseIFF (iff, IFFPARSE_SCAN))
+               {
+                       if (err == IFFERR_EOC) continue;
+                       else
+                       {
+                               if (err == IFFERR_EOF) err = 0;
+                               break; /* Free resources & exit */
+                       }
+               }
+
+               if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_PREF))
+               {
+                       switch (cn->cn_ID)
+                       {
+                               case ID_PRHD:
+                               {
+                                       struct PrefHeader prhd;
+
+                                       if ((err = ReadChunkBytes (iff, &prhd, sizeof (prhd))) !=
+                                               sizeof (prhd)) goto error3;
+
+                                       if (prhd.ph_Version != PRHD_VERS)
+                                       {
+                                               ShowRequestArgs (MSG_BAD_PREFS_VERSION, 0, NULL);
+                                               goto error3;
+                                       }
+
+                                       break;
+                               }
+
+                               case ID_XMPR:
+                               {
+                                       struct XMPrefs xmpr;
+
+                                       if ((err = ReadChunkBytes (iff, &xmpr, sizeof (xmpr))) !=
+                                               sizeof (xmpr)) goto error3;
+
+                                       memcpy (&GuiSwitches, &xmpr.GuiSwitches, sizeof (GuiSwitches));
+                                       memcpy (&SaveSwitches, &xmpr.SaveSwitches, sizeof (SaveSwitches));
+                                       memcpy (&ClearSwitches, &xmpr.ClearSwitches, sizeof (ClearSwitches));
+                                       memcpy (&OptSwitches, &xmpr.OptSwitches, sizeof (OptSwitches));
+                                       memcpy (&PattSwitches, &xmpr.PattSwitches, sizeof (PattSwitches));
+
+                                       {
+                                               struct XMHook *saver;
+
+                                               /* FIXME: move this code in a private library
+                                                * function such as SetDefaultSaver()
+                                                */
+                                               ObtainSemaphore (&XModuleBase->xm_BaseLock);
+                                               if (saver = (struct XMHook *)FindName (
+                                                       (struct List *)&XModuleBase->xm_Savers, xmpr.SaverName))
+                                                       XModuleBase->xm_DefaultSaver = saver;
+                                               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+                                       }
+
+                                       UpdateGuiSwitches ();
+                                       UpdateInstrSwitches ();
+                                       UpdateSaveSwitches ();
+                                       UpdateClearSwitches ();
+                                       UpdateOptSwitches ();
+                                       UpdateSampleMenu ();
+                                       UpdatePattPrefs ();
+
+                                       break;
+                               }
+
+                               case ID_XMFN:
+                               {
+                                       struct XMFonts xmfn;
+
+                                       if ((err = ReadChunkBytes (iff, &xmfn, sizeof (xmfn))) !=
+                                               sizeof (xmfn)) goto error3;
+
+                                       xmfn.ScreenAttr.ta_Name = xmfn.ScreenFontName;
+                                       xmfn.WindowAttr.ta_Name = xmfn.WindowFontName;
+                                       xmfn.ListAttr.ta_Name   = xmfn.ListFontName;
+                                       xmfn.EditorAttr.ta_Name = xmfn.EditorFontName;
+
+                                       if (CmpTextAttr (&xmfn.ScreenAttr, &ScreenAttr) ||
+                                               CmpTextAttr (&xmfn.WindowAttr, &WindowAttr) ||
+                                               CmpTextAttr (&xmfn.ListAttr,   &ListAttr)   ||
+                                               CmpTextAttr (&xmfn.EditorAttr, &EditorAttr))
+                                       {
+                                               CopyTextAttrPooled (Pool, &xmfn.ScreenAttr, &ScreenAttr);
+                                               CopyTextAttrPooled (Pool, &xmfn.WindowAttr, &WindowAttr);
+                                               CopyTextAttrPooled (Pool, &xmfn.ListAttr,   &ListAttr);
+                                               CopyTextAttrPooled (Pool, &xmfn.EditorAttr, &EditorAttr);
+                                               update_screen = TRUE;
+                                       }
+
+                                       break;
+                               }
+
+                               case ID_XMSC:
+                               {
+                                       struct ScrInfo newscrinfo;
+
+                                       if ((err = ReadChunkBytes (iff, &newscrinfo, sizeof (newscrinfo))) !=
+                                               sizeof (newscrinfo)) goto error3;
+
+                                       if (memcmp (&ScrInfo, &newscrinfo, sizeof (ScrInfo)))
+                                       {
+                                               if (Scr)
+                                               {
+                                                       CloseDownScreen();
+                                                       update_screen = TRUE;
+                                               }
+
+                                               memcpy (&ScrInfo, &newscrinfo, sizeof (ScrInfo));
+                                       }
+
+                                       break;
+                               }
+
+                               case ID_XMWN:
+                                       if (err = LoadXMWN (iff))
+                                               goto error3;
+                                       break;
+
+                               case ID_XMFR:
+                                       if (frcount < FREQ_COUNT)
+                                       {
+                                               if (err = LoadXMFR (iff, &FileReqs[frcount]))
+                                                       goto error3;
+                                               frcount++;
+                                       }
+                                       break;
+
+                               default:
+                                       break;
+                       }
+               }
+       }
+
+error3:
+       CloseIFF (iff);
+error2:
+       Close (iff->iff_Stream);
+error1:
+       FreeIFF (iff);
+
+       if (Scr)
+       {
+               if (update_screen)
+                       err = SetupScreen();
+               else ReopenWindows();
+       }
+
+       return err;
+}
+
+
+
+GLOBALCALL LONG SavePrefs (CONST_STRPTR filename)
+{
+       struct IFFHandle *iff;
+       LONG err;
+
+       if (!(iff = AllocIFF()))
+               return ERROR_NO_FREE_STORE;
+
+       if (!(iff->iff_Stream = (ULONG) Open (filename, MODE_NEWFILE)))
+       {
+               err = IoErr();
+               goto error1;
+       }
+
+       InitIFFasDOS (iff);
+
+       if (err = OpenIFF (iff, IFFF_WRITE))
+               goto error2;
+
+
+       /* Write PREF */
+
+       if (err = PushChunk (iff, ID_PREF, ID_FORM, IFFSIZE_UNKNOWN))
+               goto error3;
+
+       /* Store PRHD chunk */
+       {
+               struct PrefHeader prhd = {PRHD_VERS, 0, 0};
+
+               if (err = PushChunk (iff, 0, ID_PRHD, sizeof (struct PrefHeader)))
+                       goto error3;
+
+               if ((err = WriteChunkBytes (iff, &prhd, sizeof (struct PrefHeader))) !=
+                       sizeof (struct PrefHeader))
+                       goto error3;
+
+               if (err = PopChunk (iff))
+                       goto error3;
+       }
+
+       /* Store XMPR Chunk */
+       {
+               struct XMPrefs xmpr;
+
+               memcpy (&xmpr.GuiSwitches, &GuiSwitches, sizeof (GuiSwitches));
+               memcpy (&xmpr.SaveSwitches, &SaveSwitches, sizeof (SaveSwitches));
+               memcpy (&xmpr.ClearSwitches, &ClearSwitches, sizeof (ClearSwitches));
+               memcpy (&xmpr.OptSwitches, &OptSwitches, sizeof (OptSwitches));
+               memcpy (&xmpr.PattSwitches, &PattSwitches, sizeof (PattSwitches));
+
+               ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+               if (XModuleBase->xm_DefaultSaver)
+                       strncpy (xmpr.SaverName, XModuleBase->xm_DefaultSaver->xmh_Link.ln_Name, 32);
+               else
+                       xmpr.SaverName[0] = '\0';
+               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+
+               if (err = PushChunk (iff, 0, ID_XMPR, sizeof (xmpr)))
+                       goto error3;
+
+               if ((err = WriteChunkBytes (iff, &xmpr, sizeof (xmpr))) !=
+                       sizeof (xmpr))
+                       goto error3;
+
+               if (err = PopChunk (iff))
+                       goto error3;
+       }
+
+
+       /* Store XMFN chunk */
+       {
+               struct XMFonts xmfn;
+
+               if (err = PushChunk (iff, 0, ID_XMFN, sizeof (xmfn)))
+                       goto error3;
+
+               xmfn.ScreenAttr = ScreenAttr;
+               xmfn.WindowAttr = WindowAttr;
+               xmfn.ListAttr   = ListAttr;
+               xmfn.EditorAttr = EditorAttr;
+
+               if (ScreenAttr.ta_Name)
+                       strncpy (xmfn.ScreenFontName,   ScreenAttr.ta_Name,     32);
+               if (WindowAttr.ta_Name)
+                       strncpy (xmfn.WindowFontName,   WindowAttr.ta_Name,     32);
+               if (ListAttr.ta_Name)
+                       strncpy (xmfn.ListFontName,             ListAttr.ta_Name,       32);
+               if (EditorAttr.ta_Name)
+                       strncpy (xmfn.EditorFontName,   EditorAttr.ta_Name,     32);
+
+               if ((err = WriteChunkBytes (iff, &xmfn, sizeof (xmfn))) !=
+                       sizeof (xmfn))
+                       goto error3;
+
+               if (err = PopChunk (iff))
+                       goto error3;
+       }
+
+       /* Store XMSC Chunk */
+       {
+               if (err = PushChunk (iff, 0, ID_XMSC, sizeof (ScrInfo)))
+                       goto error3;
+
+               if ((err = WriteChunkBytes (iff, &ScrInfo, sizeof (ScrInfo))) !=
+                       sizeof (ScrInfo))
+                       goto error3;
+
+               if (err = PopChunk (iff))
+                       goto error3;
+       }
+
+
+       /* Store XMWN Chunks */
+       {
+               struct WinUserData *wud;
+
+               for (wud = (struct WinUserData *) WindowList.lh_Head;
+                       wud->Link.mln_Succ;
+                       wud = (struct WinUserData *)wud->Link.mln_Succ)
+               {
+                       if (err = SaveXMWN (iff, wud))
+                               goto error3;
+               }
+       }
+
+
+       /* Store XMFR Chunks */
+       {
+               ULONG i;
+
+               for (i = 0; i < FREQ_COUNT; i++)
+                       SaveXMFR (iff, &FileReqs[i]);
+       }
+
+       err = PopChunk (iff);   /* Pop PREF */
+
+error3:
+       CloseIFF (iff);
+
+error2:
+       Close (iff->iff_Stream);
+
+error1:
+       FreeIFF (iff);
+
+       return err;
+}
+
+
+
+static LONG LoadXMWN (struct IFFHandle *iff)
+{
+       LONG err;
+       struct XMWindow xmwn;
+       struct WinUserData *wud;
+       struct Window *win;
+
+       if ((err = ReadChunkBytes (iff, &xmwn, sizeof (xmwn))) != sizeof (xmwn))
+               return err;
+
+       if (xmwn.WindowID >= WID_COUNT) return RETURN_OK;
+
+       if (!(wud = WDescr[xmwn.WindowID].Wud))
+               if (!(wud = CreateWUD (xmwn.WindowID)))
+                       return ERROR_NO_FREE_STORE;
+
+       win = wud->Win;
+
+       if (!win)
+               wud->WUDFlags |= xmwn.WindowOpen ? WUDF_REOPENME : 0;   /* Open this window later */
+       else if (!xmwn.WindowOpen)
+               MyCloseWindow (wud);            /* Close this window now */
+
+
+       memcpy (&wud->WindowSize, &xmwn.WindowSize, sizeof (struct IBox));
+       memcpy (&wud->WindowZoom, &xmwn.WindowZoom, sizeof (struct IBox));
+
+//     wud->WindowSize.Left    = xmwn.WindowSize.Left;
+//     wud->WindowSize.Top             = xmwn.WindowSize.Top;
+//     wud->WindowZoom.Left    = xmwn.WindowSize.Left;
+//     wud->WindowZoom.Top             = xmwn.WindowZoom.Top;
+
+//     if (wud->Flags & WFLG_SIZEGADGET)
+//     {
+//             wud->WindowSize.Width   = xmwn.WindowSize.Width;
+//             wud->WindowSize.Height  = xmwn.WindowSize.Height;
+//             wud->WindowZoom.Left    = xmwn.WindowSize.Width;
+//             wud->WindowZoom.Height  = xmwn.WindowZoom.Height;
+//     }
+
+       if (win)
+       {
+               if (xmwn.WindowZoomed)
+               {
+                       if (!(win->Flags & WFLG_ZOOMED)) ZipWindow (win);
+                       ChangeWindowBox (win, wud->WindowZoom.Left, wud->WindowZoom.Top,
+                               win->Width, win->Height);
+               }
+               else
+               {
+                       if (win->Flags & WFLG_ZOOMED) ZipWindow (win);
+                       ChangeWindowBox (win, wud->WindowSize.Left, wud->WindowSize.Top,
+                               win->Width, win->Height);
+               }
+       }
+
+       return RETURN_OK;
+}
+
+
+
+static LONG SaveXMWN (struct IFFHandle *iff, struct WinUserData *wud)
+{
+       LONG err;
+       struct XMWindow xmwn;
+       struct Window *win = wud->Win;
+
+       if (err = PushChunk (iff, 0, ID_XMWN, sizeof (xmwn)))
+               return err;
+
+       xmwn.WindowID = wud->WindowID;
+
+       memcpy (&xmwn.WindowSize, &wud->WindowSize, sizeof (struct IBox));
+       memcpy (&xmwn.WindowZoom, &wud->WindowZoom, sizeof (struct IBox));
+
+       if (win)
+       {
+               if (win->Flags == WFLG_ZOOMED)
+               {
+                       memcpy (&xmwn.WindowZoom, &win->LeftEdge, sizeof (struct IBox));
+                       xmwn.WindowZoom.Width   -= win->BorderLeft + win->BorderRight;
+                       xmwn.WindowZoom.Height  -= win->BorderTop + win->BorderBottom;
+               }
+               else
+               {
+                       memcpy (&xmwn.WindowSize, &win->LeftEdge, sizeof (struct IBox));
+                       xmwn.WindowSize.Width -= win->BorderLeft + win->BorderRight;
+                       xmwn.WindowSize.Height -= win->BorderTop + win->BorderBottom;
+               }
+       }
+
+       xmwn.WindowOpen = win ? TRUE : FALSE;
+       xmwn.WindowZoomed = win ? (win->Flags & WFLG_ZOOMED) : FALSE;
+
+       if ((err = WriteChunkBytes (iff, &xmwn, sizeof (xmwn))) != sizeof (xmwn))
+               return err;
+
+       if (err = PopChunk (iff))
+               return err;
+
+       return RETURN_OK;
+}
+
+
+
+static LONG LoadXMFR (struct IFFHandle *iff, struct XMFileReq *xmfr)
+{
+       LONG err;
+       struct XMFRPrefs xmfrp;
+
+       if (!ReqToolsBase && !AslBase)
+               SetupRequesters();
+
+       if (!xmfr->FReq) return RETURN_FAIL;
+
+       if ((err = ReadChunkBytes (iff, &xmfrp, sizeof (xmfrp))) != sizeof (xmfrp))
+               return err;
+
+       if (ReqToolsBase)
+       {
+               rtChangeReqAttr (xmfr->FReq,
+                       RT_TopOffset,   xmfrp.FReqSize.Top,
+                       RT_LeftOffset,  xmfrp.FReqSize.Left,
+                       RTFI_Height,    xmfrp.FReqSize.Height,
+                       /* Width not available in ReqTools */
+                       RTFI_Dir,               xmfrp.Dir,
+                       RTFI_MatchPat,  xmfrp.Pattern,
+                       TAG_DONE);
+       }
+       else if (AslBase)
+       {
+               struct FileRequester *fr;
+
+               if (!(fr = AllocAslRequestTags (ASL_FileRequest,
+                       (xmfr->Title == -1) ? TAG_IGNORE : ASLFR_TitleText, (xmfr->Title == -1) ? NULL : STR(xmfr->Title),
+                       ASLFR_Flags1,                   xmfr->Flags | FRF_PRIVATEIDCMP,
+                       ASLFR_Flags2,                   FRF_REJECTICONS,
+                       ASLFR_InitialLeftEdge,  xmfrp.FReqSize.Left,
+                       ASLFR_InitialTopEdge,   xmfrp.FReqSize.Top,
+                       ASLFR_InitialWidth,             xmfrp.FReqSize.Width,
+                       ASLFR_InitialHeight,    xmfrp.FReqSize.Height,
+                       ASLFR_InitialDrawer,    xmfrp.Dir,
+                       ASLFR_InitialPattern,   xmfrp.Pattern,
+                       TAG_DONE)))
+                       return RETURN_FAIL;
+
+               FreeAslRequest (xmfr->FReq);
+               xmfr->FReq = fr;
+       }
+
+       return RETURN_OK;
+}
+
+
+
+static LONG SaveXMFR (struct IFFHandle *iff, struct XMFileReq *xmfr)
+{
+       LONG err;
+       struct XMFRPrefs xmfrp = { 0 };
+
+       if (err = PushChunk (iff, 0, ID_XMFR, sizeof (xmfrp)))
+               return err;
+
+       if (xmfr->FReq)
+       {
+               if (AslBase)
+               {
+                       struct FileRequester *fr = (struct FileRequester *)xmfr->FReq;
+
+                       xmfrp.FReqSize.Left             = fr->fr_LeftEdge;
+                       xmfrp.FReqSize.Top              = fr->fr_TopEdge;
+                       xmfrp.FReqSize.Width    = fr->fr_Width;
+                       xmfrp.FReqSize.Height   = fr->fr_Height;
+                       strncpy (xmfrp.Dir, fr->fr_Drawer, PATHNAME_MAX);
+                       strncpy (xmfrp.Pattern, fr->fr_Pattern, PATHNAME_MAX);
+               }
+               else if (ReqToolsBase)
+               {
+                       struct rtFileRequester *fr = (struct rtFileRequester *)xmfr->FReq;
+
+                       xmfrp.FReqSize.Left             = fr->LeftOffset;
+                       xmfrp.FReqSize.Top              = fr->TopOffset;
+                       xmfrp.FReqSize.Width    = 0;    /* Width not available in ReqTools */
+                       xmfrp.FReqSize.Height   = fr->ReqHeight;
+                       strncpy (xmfrp.Dir, fr->Dir, PATHNAME_MAX);
+                       strncpy (xmfrp.Pattern, fr->MatchPat, PATHNAME_MAX);
+               }
+       }
+
+       if ((err = WriteChunkBytes (iff, &xmfrp, sizeof (xmfrp))) != sizeof (xmfrp))
+               return err;
+
+       if (err = PopChunk (iff)) /* Pop XMFR */
+               return err;
+
+       return RETURN_OK;
+}
diff --git a/PrefsWin.c b/PrefsWin.c
new file mode 100644 (file)
index 0000000..463d1eb
--- /dev/null
@@ -0,0 +1,406 @@
+/*
+**     PrefsWin.c
+**
+**     Copyright (C) 1994,95,96,97 by Bernardo Innocenti
+**
+**     Preferences panel handling routines.
+*/
+
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+#include <libraries/asl.h>
+#include <graphics/displayinfo.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/graphics.h>
+#include <proto/gadtools.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+#include "CustomClasses.h"
+
+
+
+/* Gadgets IDs */
+
+enum {
+       GD_PrefsGroup0,
+               GD_PrefsGroup1,
+                       GD_PrefsGroup2,
+                               GD_PubScreen,
+                               GD_GetPubScreen,
+                       GD_PrefsGroup3,
+                               GD_DisplayMode,
+                               GD_GetDisplayMode,
+                       GD_PrefsGroup4,
+                               GD_WindowFont,
+                               GD_GetWindowFont,
+                       GD_PrefsGroup5,
+                               GD_ListViewFont,
+                               GD_GetListViewFont,
+               GD_PrefsGroup6,
+                       GD_PrefsGroup7,
+                               GD_Requesters,
+                               GD_Refresh,
+                       GD_PrefsGroup8,
+                               GD_UseDataTypes,
+                               GD_AppIcon,
+               GD_PrefsGroup9,
+                       GD_LogLevel,
+                       GD_PrefsGroup10,
+                               GD_LogToFile,
+                               GD_LogFile,
+               GD_PrefsGroup11,
+                       GD_PrefsGroup12,
+                               GD_AutosaveTime,
+                               GD_AskAutosave,
+                       GD_PrefsGroup13,
+                               GD_DoBackups,
+                               GD_BackupVersions,
+                       GD_BackupTemplate,
+               GD_PrefsGroup14,
+                       GD_PrefsUse,
+                       GD_PrefsCancel,
+
+       Prefs_CNT
+};
+
+
+
+/* Local functions prototypes */
+
+static void PrefsPostOpen                      (struct WinUserData *wud);
+static void PrefsPostClose                     (void);
+
+static void PubScreenClicked           (struct WinUserData *wud);
+static void GetPubScreenClicked                (struct WinUserData *wud);
+static void GetDisplayModeClicked      (struct WinUserData *wud);
+static void GetWindowFontClicked       (struct WinUserData *wud);
+static void GetListViewFontClicked     (struct WinUserData *wud);
+static void RequestersClicked          (struct WinUserData *wud);
+static void RefreshClicked                     (struct WinUserData *wud);
+static void LogLevelClicked                    (struct WinUserData *wud);
+static void LogFileClicked                     (struct WinUserData *wud);
+static void    BackupTemplateClicked   (struct WinUserData *wud);
+static void BackupVersionsClicked      (struct WinUserData *wud);
+static void AutosaveTimeClicked                (struct WinUserData *wud);
+static void PrefsUseClicked                    (struct WinUserData *wud);
+static void PrefsCancelClicked         (struct WinUserData *wud);
+
+
+
+/* Local data */
+
+static struct ScrInfo NewScrInfo;
+static struct GuiSwitches NewGuiSwitches;
+static struct TextAttr NewWindowAttr;
+static struct TextAttr NewListAttr;
+
+
+
+static UBYTE *RequestersLabels[] = {
+       (UBYTE *)MSG_ASL_GAD,
+       (UBYTE *)MSG_REQTOOLS_GAD,
+       NULL
+};
+
+
+static UBYTE *RefreshLabels[] = {
+       (UBYTE *)MSG_SIMPLE_GAD,
+       (UBYTE *)MSG_SMART_GAD,
+       NULL
+};
+
+
+
+static ULONG PrefsArgs[] =
+{
+       VGROUP_KIND, BBFT_RIDGE,
+               HGROUP_KIND, 0,
+                       STRING_KIND,            (ULONG)PubScreenClicked,        MSG_PUBLIC_SCREEN_GAD,  32,     TAG_DONE,
+                       IMAGEBUTTON_KIND,       (ULONG)GetPubScreenClicked,     IM_PICK,                                        TAG_DONE,
+                       ENDGROUP_KIND,
+               HGROUP_KIND, 0,
+                       TEXT_KIND,                      MSG_DISPLAY_MODE_GAD,                   0,                      GTTX_Border,    TRUE,   TAG_DONE,
+                       IMAGEBUTTON_KIND,       (ULONG)GetDisplayModeClicked,   IM_PICK,        TAG_DONE,
+                       ENDGROUP_KIND,
+               HGROUP_KIND, 0,
+                       TEXT_KIND,                      MSG_WINDOW_FONT_GAD,                    0,                      GTTX_Border,    TRUE,   TAG_DONE,
+                       IMAGEBUTTON_KIND,       (ULONG)GetWindowFontClicked,    IM_PICK,        TAG_DONE,
+                       ENDGROUP_KIND,
+               HGROUP_KIND, 0,
+                       TEXT_KIND,                      MSG_LISTVIEW_FONT_GAD,                  0,                      GTTX_Border,    TRUE,   TAG_DONE,
+                       IMAGEBUTTON_KIND,       (ULONG)GetListViewFontClicked,  IM_PICK,        TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       HGROUP_KIND, BBFT_RIDGE,
+               VGROUP_KIND, 0,
+                       CYCLE_KIND,             (ULONG)RequestersClicked,       MSG_REQUESTERS_GAD,     (ULONG)RequestersLabels,        TAG_DONE,
+                       CYCLE_KIND,             (ULONG)RefreshClicked,          MSG_REFRESH_GAD,        (ULONG)RefreshLabels,           TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       CHECKBOX_KIND,  NULL,   MSG_USE_DATATYPES_GAD,  (ULONG)&NewGuiSwitches.UseDataTypes,    TAG_DONE,
+                       CHECKBOX_KIND,  NULL,   MSG_APPICON_GAD,                (ULONG)&NewGuiSwitches.ShowAppIcon,             TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+               VGROUP_KIND, BBFT_RIDGE,
+                       SLIDER_KIND,    (ULONG)LogLevelClicked, MSG_LOG_LEVEL_GAD, 0, 8, (ULONG)"%lu", 2, GA_Immediate, TRUE, TAG_DONE,
+                       HGROUP_KIND,    0,
+                               CHECKBOX_KIND,  NULL,                                   MSG_LOG_TO_FILE_GAD,    (ULONG)&NewGuiSwitches.LogToFile,       TAG_DONE,
+                               STRING_KIND,    (ULONG)LogFileClicked,  0,                                              128, TAG_DONE,
+                               ENDGROUP_KIND,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, BBFT_RIDGE,
+                       HGROUP_KIND, 0,
+                               CHECKBOX_KIND,  NULL,                                                   MSG_ASK_AUTOSAVE_GAD, (ULONG)&NewGuiSwitches.AskAutosave, TAG_DONE,
+                               SLIDER_KIND,    (ULONG)AutosaveTimeClicked,             MSG_AUTOSAVE_TIME_GAD,          0,      99,     (ULONG)"%lu",   3, TAG_DONE,
+                               ENDGROUP_KIND,
+               HGROUP_KIND, 0,
+                       CHECKBOX_KIND,  NULL,                                                   MSG_DO_BACKUPS_GAD, (ULONG)&NewGuiSwitches.DoBackups, TAG_DONE,
+                       SLIDER_KIND,    (ULONG)BackupVersionsClicked,   MSG_BACKUP_VERSIONS_GAD,        1,      99,     (ULONG)"%lu",   3, TAG_DONE,
+                       ENDGROUP_KIND,
+               STRING_KIND,    (ULONG)BackupTemplateClicked,   MSG_BACKUP_TEMPLATE_GAD, 64, TAG_DONE,
+               ENDGROUP_KIND,
+       HGROUP_KIND, 0,
+               BUTTON_KIND,    (ULONG)PrefsUseClicked,         MSG_UNDERSCORE_USE_GAD,         TAG_DONE,
+               BUTTON_KIND,    (ULONG)PrefsCancelClicked,      MSG_UNDERSCORE_CANCEL_GAD,      TAG_DONE,
+               ENDGROUP_KIND,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG PrefsWinTags[] =
+{
+       XMWIN_LayoutArgs,       (LONG)PrefsArgs,
+       XMWIN_GCount,           Prefs_CNT,
+       XMWIN_Title,            MSG_PREFS_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       TEXTIDCMP|STRINGIDCMP|CHECKBOXIDCMP|BUTTONIDCMP|CYCLEIDCMP|SLIDERIDCMP|IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW,
+       XMWIN_PostOpenFunc,     (LONG)PrefsPostOpen,
+       XMWIN_PostCloseFunc,(LONG)PrefsPostClose,
+       XMWIN_HelpNode,         (LONG)"Prefs",
+       TAG_DONE
+};
+
+
+
+static void PrefsPostOpen (struct WinUserData *wud)
+{
+       memcpy (&NewScrInfo, &ScrInfo, sizeof (struct ScrInfo));
+       memcpy (&NewGuiSwitches, &GuiSwitches, sizeof (struct GuiSwitches));
+       CopyTextAttrPooled (Pool, &WindowAttr,  &NewWindowAttr);
+       CopyTextAttrPooled (Pool, &ListAttr,    &NewListAttr);
+
+       UpdatePrefsWindow();
+}
+
+
+
+static void PrefsPostClose (void)
+{
+       FreeVecPooled (Pool, NewWindowAttr.ta_Name);    NewWindowAttr.ta_Name   = NULL;
+       FreeVecPooled (Pool, NewListAttr.ta_Name);              NewListAttr.ta_Name             = NULL;
+}
+
+
+
+/********************/
+/* Prefs Functions  */
+/********************/
+
+GLOBALCALL void UpdatePrefsWindow()
+{
+       /* These variables are declared static because gadtools text
+        * gadgets do not buffer their texts and require them to be
+        * accessible whenever a refresh is needed :-(.
+        */
+       static UBYTE windowfont[40], listfont[40];
+       static struct NameInfo nameinfo;
+
+       struct WinUserData *wud = WDescr[WID_PREFS].Wud;
+
+
+       if (wud && wud->Win)
+       {
+               if (NewScrInfo.DisplayID)
+                       GetDisplayInfoData (NULL, (void *)&nameinfo, sizeof (nameinfo), DTAG_NAME, NewScrInfo.DisplayID);
+               else
+                       strcpy (nameinfo.Name, STR(MSG_CLONE_DEF_SCREEN));
+
+               SPrintf (windowfont, "%s/%ld", NewWindowAttr.ta_Name, NewWindowAttr.ta_YSize);
+               SPrintf (listfont, "%s/%ld", NewListAttr.ta_Name, NewListAttr.ta_YSize);
+
+               SetGadgets (wud,
+                       GD_Requesters,          NewGuiSwitches.UseReqTools,
+                       GD_UseDataTypes,        NewGuiSwitches.UseDataTypes,
+                       GD_Refresh,                     NewGuiSwitches.SmartRefresh,
+                       GD_AppIcon,                     NewGuiSwitches.ShowAppIcon,
+                       GD_DisplayMode,         nameinfo.Name,
+                       GD_PubScreen,           NewScrInfo.PubScreenName,
+                       GD_WindowFont,          windowfont,
+                       GD_ListViewFont,        listfont,
+                       GD_LogLevel,            NewGuiSwitches.LogLevel,
+                       GD_LogToFile,           NewGuiSwitches.LogToFile,
+                       GD_LogFile,                     NewGuiSwitches.LogFile,
+                       GD_AskAutosave,         NewGuiSwitches.AskAutosave,
+                       GD_AutosaveTime,        NewGuiSwitches.AutosaveTime,
+                       GD_DoBackups,           NewGuiSwitches.DoBackups,
+                       GD_BackupTemplate,      NewGuiSwitches.BackupTemplate,
+                       GD_BackupVersions,      NewGuiSwitches.BackupVersions,
+                       -1);
+       }
+}
+
+
+
+/******************/
+/* Prefs Gadgets  */
+/******************/
+
+static void PubScreenClicked (struct WinUserData *wud)
+{
+       strcpy (NewScrInfo.PubScreenName, GetString (wud->Gadgets[GD_PubScreen]));
+}
+
+
+
+static void GetPubScreenClicked (struct WinUserData *wud)
+{
+       /**/
+}
+
+
+
+static void GetDisplayModeClicked (struct WinUserData *wud)
+{
+       ScrModeRequest (&NewScrInfo);
+       UpdatePrefsWindow();
+}
+
+static void GetWindowFontClicked (struct WinUserData *wud)
+{
+       FontRequest (&NewWindowAttr, 0);
+       UpdatePrefsWindow();
+}
+
+static void GetListViewFontClicked (struct WinUserData *wud)
+{
+       FontRequest (&NewListAttr, 0);
+       UpdatePrefsWindow();
+}
+
+
+
+static void RequestersClicked (struct WinUserData *wud)
+{
+       NewGuiSwitches.UseReqTools ^= 1;
+}
+
+
+
+static void RefreshClicked (struct WinUserData *wud)
+{
+       NewGuiSwitches.SmartRefresh ^= 1;
+}
+
+
+
+static void LogLevelClicked    (struct WinUserData *wud)
+{
+       NewGuiSwitches.LogLevel = IntuiMsg.Code;
+}
+
+
+
+static void LogFileClicked (struct WinUserData *wud)
+{
+       strcpy (NewGuiSwitches.LogFile, GetString (wud->Gadgets[GD_LogFile]));
+}
+
+
+
+static void AutosaveTimeClicked (struct WinUserData *wud)
+{
+       NewGuiSwitches.AutosaveTime = IntuiMsg.Code;
+}
+
+
+
+static void    BackupTemplateClicked (struct WinUserData *wud)
+{
+       strcpy (NewGuiSwitches.BackupTemplate, GetString (wud->Gadgets[GD_BackupTemplate]));
+}
+
+
+
+static void BackupVersionsClicked (struct WinUserData *wud)
+{
+       NewGuiSwitches.BackupVersions = IntuiMsg.Code;
+}
+
+
+
+static void PrefsUseClicked (struct WinUserData *wud)
+{
+       BOOL    change_screen   = FALSE,
+                       change_reqs             = FALSE;
+
+       if (memcmp (&ScrInfo, &NewScrInfo, sizeof (struct ScrInfo)))
+       {
+               change_screen = TRUE;
+               memcpy (&ScrInfo, &NewScrInfo, sizeof (struct ScrInfo));
+       }
+
+       if (memcmp (&GuiSwitches, &NewGuiSwitches, sizeof (struct GuiSwitches)))
+       {
+               if (GuiSwitches.UseReqTools != NewGuiSwitches.UseReqTools)
+                       change_reqs = TRUE;
+               if (GuiSwitches.SmartRefresh != NewGuiSwitches.SmartRefresh)
+                       change_screen = TRUE;
+
+               memcpy (&GuiSwitches, &NewGuiSwitches, sizeof (struct GuiSwitches));
+       }
+
+       if (CmpTextAttr (&NewWindowAttr, &WindowAttr))
+       {
+               CopyTextAttrPooled (Pool, &NewWindowAttr, &WindowAttr);
+               change_screen = TRUE;
+       }
+
+       if (CmpTextAttr (&NewListAttr, &ListAttr))
+       {
+               CopyTextAttrPooled (Pool, &NewListAttr, &ListAttr);
+               change_screen = TRUE;
+       }
+
+       MyCloseWindow (wud);
+
+       if (change_reqs)
+               SetupRequesters();
+
+       if (GuiSwitches.ShowAppIcon)
+               CreateAppIcon (ToolBoxDropIcon);
+       else
+               DeleteAppIcon ();
+
+       if (change_screen)
+       {
+               CloseDownScreen();
+               if (SetupScreen())
+               {
+                       /* For some reason we have lost the screen: exit immediatly! */
+                       Quit = TRUE;
+                       ShowRequesters = FALSE;
+               }
+       }
+}
+
+
+
+static void PrefsCancelClicked (struct WinUserData *wud)
+{
+       MyCloseWindow (wud);
+}
diff --git a/ProgressWin.c b/ProgressWin.c
new file mode 100644 (file)
index 0000000..0e19fcc
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+**     ProgressWin.c
+**
+**     Copyright (C) 1993,94,95,96 Bernardo Innocenti
+**
+**     Parts of this code are:
+**
+**     Copyright © 1990-1993 by Olaf `Olsen' Barthel & MXM
+**             All Rights Reserved
+**
+**     Report status information for an operation in progress.
+*/
+
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+#include <libraries/asl.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/graphics.h>
+#include <proto/layers.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Gadgets IDs */
+enum
+{
+       GD_ProgressGroup0,
+               GD_Action,
+               GD_Percent,
+               GD_ProgressAbort,
+
+       Progress_CNT
+};
+
+enum
+{
+       GD_LogGroup0,
+               GD_LogList,
+
+       Log_CNT
+};
+
+
+/* Local functions prototypes */
+
+static void ShowStats                          (struct Gadget *Gadget, LONG Value, LONG Max);
+static void ProgressAbortClicked       (void);
+static void LogPostClose                       (void);
+static LONG ProgressPreOpen                    (struct WinUserData *wud);
+
+
+
+static UWORD                    LogLines = 0;
+static UWORD                    ProgressOpenCount = 0;
+struct List                             LogList;
+static struct IntuiText         ProgressIT = { 0 };
+static BOOL                             ProgressAborting = FALSE;
+
+
+
+static LONG ProgressArgs[] =
+{
+       TEXT_KIND,              NULL,                                           20,                                                     GTTX_Clipped,   TRUE, TAG_DONE,
+       TEXT_KIND,              NULL,                                           20,                                                     GTTX_Border,    TRUE, TAG_DONE,
+       BUTTON_KIND,    (LONG)ProgressAbortClicked,     MSG_UNDERSCORE_ABORT_GAD,       TAG_DONE,
+       ENDGROUP_KIND
+};
+
+
+
+static LONG LogArgs[] =
+{
+       LISTVIEW_KIND,  NULL,   NULL, (LONG)&LogList,   GTLV_ReadOnly, TRUE, TAG_DONE,
+       ENDGROUP_KIND
+};
+
+
+
+
+LONG ProgressWinTags[] =
+{
+       XMWIN_LayoutArgs,               (LONG)ProgressArgs,
+       XMWIN_GCount,                   Progress_CNT,
+       XMWIN_Title,                    MSG_PROGRESS_TITLE,
+       XMWIN_IDCMPFlags,               BUTTONIDCMP|TEXTIDCMP|IDCMP_REFRESHWINDOW,
+       XMWIN_PostCloseFunc,    (LONG)LogPostClose,
+       TAG_DONE
+};
+
+
+
+LONG LogWinTags[] =
+{
+       XMWIN_LayoutArgs,               (LONG)LogArgs,
+       XMWIN_GCount,                   Log_CNT,
+       XMWIN_Title,                    MSG_LOG_TITLE,
+       XMWIN_WindowFlags,              WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,               LISTVIEWIDCMP|IDCMP_REFRESHWINDOW|IDCMP_CLOSEWINDOW,
+       XMWIN_PreOpenFunc,              (LONG)ProgressPreOpen,
+       TAG_DONE
+};
+
+
+
+GLOBALCALL void OpenProgressWindow (void)
+{
+       ProgressOpenCount++;
+       if (ProgressOpenCount > 1) return;
+
+       LockWindows();
+
+       NewWindow (WID_PROGRESS);
+}
+
+
+
+GLOBALCALL void CloseProgressWindow (void)
+{
+       ProgressOpenCount--;
+       if (ProgressOpenCount) return;
+
+       UnlockWindows ();
+
+       MyCloseWindow (WDescr[WID_PROGRESS].Wud);
+}
+
+
+
+static LONG ProgressPreOpen (struct WinUserData *wud)
+{
+       ProgressIT.FrontPen     = DrawInfo->dri_Pens[FILLTEXTPEN] ? DrawInfo->dri_Pens[FILLTEXTPEN] : DrawInfo->dri_Pens[FILLPEN];
+
+       ProgressIT.DrawMode = (DrawInfo->dri_Pens[FILLPEN] == DrawInfo->dri_Pens[FILLTEXTPEN] || !DrawInfo->dri_Pens[FILLTEXTPEN]) ?
+               (JAM1 | COMPLEMENT) : (JAM1);
+
+       ProgressIT.ITextFont = wud->Attr;
+
+       return RETURN_OK;
+}
+
+
+
+static void LogPostClose (void)
+{
+       /* Free ListView nodes */
+       while (!IsListEmpty(&LogList))
+               RemListViewNode (LogList.lh_Head);
+       LogLines = 0;
+}
+
+
+
+GLOBALCALL void DisplayAction (ULONG msg)
+
+/* Tell user what is happening in the Progress window. */
+{
+       DisplayActionStr (STR(msg));
+}
+
+
+
+GLOBALCALL void DisplayActionStr (CONST_STRPTR str)
+
+/* Tell user what is happening in the Progress window. */
+{
+       struct WinUserData *wud;
+
+       if ((wud = WDescr[WID_PROGRESS].Wud) && wud->Win)
+               GT_SetGadgetAttrs (wud->Gadgets[GD_Action], wud->Win, NULL,
+                       GTTX_Text, str,
+                       TAG_DONE);
+}
+
+
+
+/* Tell user how are things going.  Also check for abort */
+GLOBALCALL LONG DisplayProgress (LONG Num, LONG Max)
+{
+       struct WinUserData *wud;
+
+       if ((wud = WDescr[WID_PROGRESS].Wud) && wud->Win)
+       {
+               /* Check for CTRL-C Break */
+               if (SetSignal (0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
+                       return ERROR_BREAK;
+
+
+               /* Check abort and resizing */
+               HandleIDCMP();
+
+               if (ProgressAborting)
+               {
+                       ProgressAborting = FALSE;
+                       return ERROR_BREAK;
+               }
+
+               if (wud->Win)
+               {
+                       struct Layer_Info       *layerinfo;
+                       UBYTE buf[48];
+
+                       /* Attempt to lock the LayerInfo associated with the
+                        * screen where the Progress window resides in.
+                        *
+                        * We do this to prevent blocking the operation in progress
+                        * when someone else (e.g. Intuition) is keeping the lock.
+                        *
+                        * Note that there is no AttemptLockLayerInfo() function
+                        * and LockLayerInfo() will wait if the layer is already
+                        * locked, which is exactly what we are trying to avoid.
+                        *
+                        * Our workaround is checking the LockLayersCount before
+                        * locking the LayerInfo.
+                        *
+                        * TODO: Experiment with AttemptLockLayerRom().
+                        */
+
+                       /* Perhaps using Scr->LayerInfo would be the same */
+                       layerinfo = wud->Win->RPort->Layer->LayerInfo;
+
+                       Forbid();
+                       if (!layerinfo->LockLayersCount)
+                       {
+                               struct Gadget *g;
+
+                               LockLayerInfo (layerinfo);
+                               Permit();
+
+                               g = wud->Gadgets[GD_Percent];
+
+                               /* Update Stats */
+                               ShowStats (g, Num, Max);
+
+                               /* Display progress string */
+
+                               SPrintf (buf, STR(MSG_PERCENT_DONE), Num, Max, (Num * 100) / Max);
+
+                               ProgressIT.IText = buf;
+
+                               PrintIText (wud->Win->RPort, &ProgressIT,
+                                       g->LeftEdge + 2 + (g->Width - 4 - IntuiTextLength (&ProgressIT)) / 2,
+                                       g->TopEdge + 1 + (g->Height - 2 - wud->Win->RPort->TxHeight) / 2);
+
+                               UnlockLayerInfo (layerinfo);
+                       }
+                       else Permit();
+               }
+       }
+
+       return FALSE;
+}
+
+
+
+static void ShowStats (struct Gadget *Gadget, LONG Value, LONG Max)
+
+/* Show the percentage bars. */
+{
+       struct RastPort *RPort = WDescr[WID_PROGRESS].Wud->Win->RPort;
+       LONG    MaxWidth = Gadget->Width - 4,
+                       Width;
+
+
+       if (Max < 1)            Max = 0;
+       if (Value > Max)        Value = Max;
+
+
+       if((Width = (MaxWidth * Value) / Max) > 0)
+       {
+               if(Width != MaxWidth)
+               {
+                       SetAPen (RPort,0);
+                       RectFill (RPort, Gadget->LeftEdge + 2 + Width - 1, Gadget->TopEdge + 1,
+                               Gadget->LeftEdge + Gadget->Width - 3, Gadget->TopEdge + Gadget->Height - 2);
+               }
+
+               SetAPen (RPort, DrawInfo->dri_Pens[FILLPEN]);
+               RectFill (RPort,Gadget->LeftEdge + 2,Gadget->TopEdge + 1,
+                       Gadget->LeftEdge + Width + 1, Gadget->TopEdge + Gadget->Height - 2);
+       }
+       else
+       {
+               SetAPen (RPort, 0);
+               RectFill (RPort, Gadget->LeftEdge + 2, Gadget->TopEdge + 1,
+                       Gadget->LeftEdge + Gadget->Width - 3, Gadget->TopEdge + Gadget->Height - 2);
+       }
+}
+
+
+
+
+GLOBALCALL void ShowMessage (ULONG msg, ...)
+
+/* Localized interface to ShowString(). */
+{
+       ShowString (STR(msg), (LONG *)(&msg+1));
+}
+
+
+GLOBALCALL void ShowString (CONST_STRPTR s, LONG *args)
+
+/* Formats a string and shows it to the user in the Log Window.
+ * If the Log Window can't be opened, this function will fall
+ * to ShowRequest() or to Printf().
+ */
+{
+       struct WinUserData *wud;
+
+       if (!IntuitionBase)
+       {
+               if (StdOut) VPrintf ((STRPTR)s, args);
+               return;
+       }
+
+       wud = WDescr[WID_LOG].Wud;
+
+       if (!wud || !wud->Win)
+       {
+               NewWindow (WID_LOG);
+               wud = WDescr[WID_LOG].Wud;
+       }
+
+       if (wud && wud->Win)
+       {
+               GT_SetGadgetAttrs (wud->Gadgets[GD_LogList], wud->Win, NULL,
+                       GTLV_Labels, ~0,
+                       TAG_DONE);
+
+               if (LogLines > 30)
+               {
+                       RemListViewNode (LogList.lh_Head);
+                       LogLines--;
+               }
+
+               if (AddListViewNodeA (&LogList, s, args))
+                       LogLines++;
+               else
+                       DisplayBeep(Scr);
+
+               GT_SetGadgetAttrs (wud->Gadgets[GD_LogList], wud->Win, NULL,
+                       GTLV_Labels, &LogList,
+                       GTLV_Top, 30,
+                       TAG_DONE);
+       }
+       else
+               ShowRequestStr (s, NULL, args);
+}
+
+
+
+static void ProgressAbortClicked (void)
+{
+       ProgressAborting = TRUE;
+}
diff --git a/Requesters.c b/Requesters.c
new file mode 100644 (file)
index 0000000..7e4fe8e
--- /dev/null
@@ -0,0 +1,771 @@
+/*
+**     Requesters.c
+**
+**     Copyright (C) 1994,95,96,97 Bernardo Innocenti
+**
+**     Handle asyncronous file requester
+*/
+
+#include <exec/memory.h>
+#include <libraries/asl.h>
+#include <libraries/reqtools.h>
+#include <dos/dostags.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/graphics.h>
+#include <proto/intuition.h>
+#include <proto/asl.h>
+#include <proto/reqtools.h>
+
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Local function prototypes */
+static LONG CheckOverwrite (CONST_STRPTR path);
+
+
+
+/* This structure is used for passing parameters to
+ * the requester process and to get the result back.
+ */
+struct FileReqMsg
+{
+       struct Message           ExecMsg;
+       struct XMFileReq        *XMFReq;        /* XMFileReq structure to use           */
+
+       /* User call back function which does something with the file name.
+        * If the user picked multiple files, the user function will be called
+        * once on each selection.  The user function can tell which entry is
+        * being processed examining the <num> and <count> arguments.
+        */
+       void    (*Func)(STRPTR file, ULONG num, ULONG count);
+       APTR    Result;                                 /* Replied by FileRequester process     */
+       UBYTE   PathName[PATHNAME_MAX]; /* Replied by FileRequester process     */
+};
+
+
+
+XDEF struct XMFileReq FileReqs[FREQ_COUNT] =
+{
+       { NULL, MSG_SELECT_MODULES,                     FRF_DOMULTISELECT | FRF_DOPATTERNS },
+       { NULL, MSG_SAVE_MODULE,                        FRF_DOSAVEMODE | FRF_DOPATTERNS },
+       { NULL, MSG_SELECT_INSTRUMENTS,         FRF_DOMULTISELECT | FRF_DOPATTERNS },
+       { NULL, MSG_SAVE_INSTRUMENT,            FRF_DOSAVEMODE | FRF_DOPATTERNS },
+       { NULL, MSG_SELECT_PATTERN,             FRF_DOPATTERNS },
+       { NULL, MSG_SAVE_PATTERN,                       FRF_DOSAVEMODE | FRF_DOPATTERNS },
+       { NULL, 0,                                                      FRF_DOPATTERNS },
+       { NULL, 0,                                                      FRF_DOSAVEMODE | FRF_DOPATTERNS },
+};
+
+
+static APTR FontReq = NULL;
+static struct Process *FileReqTask = NULL;
+static struct MsgPort *FileReqPort = NULL;
+XDEF ULONG FileReqSig;
+
+
+
+
+/* This function is a process that puts out
+ * an asyncronous ASL FileRequester.
+ */
+HOOKCALL static void AslRequestProc (void)
+{
+       struct MsgPort          *prMsgPort = &(((struct Process *)FindTask (NULL))->pr_MsgPort);
+       struct FileReqMsg       *frmsg;
+       struct XMFileReq        *xmfr;
+
+       /* Wait startup packet */
+       WaitPort (prMsgPort);
+       frmsg = (struct FileReqMsg *)GetMsg (prMsgPort);
+       xmfr = frmsg->XMFReq;
+       frmsg->Result = NULL;
+
+       if (AslRequestTags (xmfr->FReq,
+               ASLFR_Window,   ThisTask->pr_WindowPtr,
+               TAG_DONE))
+       {
+               /* Build file path */
+               strncpy (frmsg->PathName,
+                       ((struct FileRequester *)xmfr->FReq)->fr_Drawer, PATHNAME_MAX);
+               AddPart (frmsg->PathName,
+                       ((struct FileRequester *)xmfr->FReq)->fr_File, PATHNAME_MAX);
+               frmsg->Result = frmsg->PathName;
+       }
+
+       /* Signal that we are done.
+        * We forbid here to avoid beeing unloaded until exit.
+        */
+       Forbid();
+       ReplyMsg ((struct Message *)frmsg);
+}
+
+
+
+/* This function is a process that puts out
+ * an asyncronous ReqTools FileRequester.
+ */
+HOOKCALL static void RtRequestProc (void)
+{
+       struct MsgPort *prMsgPort = &(((struct Process *)FindTask(NULL))->pr_MsgPort);
+       struct FileReqMsg *frmsg;
+       struct XMFileReq *xmfr;
+       ULONG flags = 0;
+       UBYTE filename[PATHNAME_MAX];
+
+       filename[0] = 0;
+
+       /* Wait startup packet */
+       WaitPort (prMsgPort);
+       frmsg = (struct FileReqMsg *)GetMsg (prMsgPort);
+       xmfr = frmsg->XMFReq;
+
+       if (xmfr->Flags & FRF_DOSAVEMODE)               flags |= FREQF_SAVE;
+       if (xmfr->Flags & FRF_DOPATTERNS)               flags |= FREQF_PATGAD;
+       if (xmfr->Flags & FRF_DOMULTISELECT)    flags |= FREQF_MULTISELECT;
+
+       if (!(frmsg->Result = rtFileRequest (xmfr->FReq, filename, STR(xmfr->Title),
+               RT_ShareIDCMP,  FALSE,
+               RT_Window,              ThisTask->pr_WindowPtr,
+               RTFI_Flags,             flags,
+               TAG_DONE)))
+               goto error;
+
+       /* Build file path */
+       strncpy (frmsg->PathName, ((struct rtFileRequester *)xmfr->FReq)->Dir,
+               PATHNAME_MAX);
+       AddPart (frmsg->PathName, filename, PATHNAME_MAX);
+
+       if (!(xmfr->Flags & FRF_DOMULTISELECT)) frmsg->Result = frmsg->PathName;
+
+error:
+       /* Signal that we are done.
+        * We forbid here to avoid beeing unloaded until exit.
+        */
+       Forbid();
+       ReplyMsg ((struct Message *)frmsg);
+}
+
+
+
+GLOBALCALL LONG StartFileRequest (ULONG freq, void (*func)(STRPTR file, ULONG num, ULONG count))
+
+/* Spawns a process that opens a FileRequester.
+ * Do not touch freq until the child process returns!
+ *
+ * INPUTS
+ *     freq - Pointer to an ASL or ReqTools FileRequester
+ *     func - Pointer to a function to call when FileRequester returns
+ *
+ * RETURN
+ *
+ *  err - 0 if OK, IOErr()-style error otherwise.
+ */
+{
+       struct FileReqMsg *frmsg;
+
+       /* Do not spawn more than one file requester process at a time */
+       if (FileReqTask)
+       {
+               LastErr = ~0;
+               return RETURN_FAIL;
+       }
+
+       if (!(frmsg = AllocMem (sizeof (struct FileReqMsg), MEMF_PUBLIC)))
+               return ERROR_NO_FREE_STORE;
+
+       if (!(FileReqTask = CreateNewProcTags (
+               NP_Entry,               (ReqToolsBase ? RtRequestProc : AslRequestProc),
+               NP_Name,                PRGNAME " FileRequester",
+               NP_CopyVars,    FALSE,
+               TAG_DONE)))
+       {
+               FreeMem (frmsg, sizeof (struct FileReqMsg));
+               return ERROR_NO_FREE_STORE;
+       }
+
+       /* Now setup & send FReqMsg */
+       frmsg->ExecMsg.mn_ReplyPort = FileReqPort;
+       frmsg->XMFReq = &FileReqs[freq];
+       frmsg->Func = func;
+       PutMsg (&(FileReqTask->pr_MsgPort), (struct Message *)frmsg);
+
+       return RETURN_OK;
+}
+
+
+
+GLOBALCALL void HandleFileRequest (void)
+{
+       struct FileReqMsg *frmsg;
+       struct XMFileReq *xmfr;
+
+       if (!(frmsg = (struct FileReqMsg *)GetMsg (FileReqPort)))
+               return;
+
+       xmfr = frmsg->XMFReq;
+
+       FileReqTask = NULL;     /* The FileRequest Process is now gone. */
+
+       if (frmsg->Result)
+       {
+               if (xmfr->Flags & FRF_DOMULTISELECT)
+               {
+                       if (AslBase)
+                       {
+                               struct FileRequester *fr = xmfr->FReq;
+                               UBYTE Name[PATHNAME_MAX];
+                               LONG i;
+
+                               for (i = 0; i < fr->fr_NumArgs; i++)
+                               {
+                                       if (NameFromLock (fr->fr_ArgList[i].wa_Lock, Name, PATHNAME_MAX))
+                                               if (AddPart (Name, fr->fr_ArgList[i].wa_Name, PATHNAME_MAX))
+                                                       frmsg->Func (Name, i, fr->fr_NumArgs);
+                               }
+                       }
+                       else if (ReqToolsBase)
+                       {
+                               struct rtFileList *fl;
+                               UBYTE Name[PATHNAME_MAX];
+                               ULONG num_entries = 0, i = 0;
+
+                               /* Calculate number of entries in FileList */
+                               fl = (struct rtFileList *)frmsg->Result;
+                               while (fl)
+                               {
+                                       fl = fl->Next;
+                                       num_entries++;
+                               }
+
+                               fl = (struct rtFileList *)frmsg->Result;
+                               while (fl)
+                               {
+                                       strncpy (Name, ((struct rtFileRequester *)xmfr->FReq)->Dir, PATHNAME_MAX);
+                                       AddPart (Name, fl->Name, PATHNAME_MAX);
+                                       frmsg->Func (Name, i++, num_entries);
+                                       fl = fl->Next;
+                               }
+
+                               rtFreeFileList (frmsg->Result); /* Free multiselect buffer */
+                       }
+               }
+               else
+                       /* Call user hook */
+                       frmsg->Func (frmsg->Result, 0, 1);
+       }
+
+       FreeMem (frmsg, sizeof (struct FileReqMsg));    /* Free FileReqMsg */
+}
+
+
+
+GLOBALCALL STRPTR FileRequest (ULONG freq, STRPTR file)
+
+/* Puts up a simple FileRequester to ask the user for a filename.
+ * If the requester is in save mode, will optionally check for
+ * overwrite and ask the user to confirm.
+ */
+{
+       STRPTR retval = NULL;
+       struct XMFileReq *xmfr = &FileReqs[freq];
+       BOOL again;
+
+       if (!xmfr->FReq) return NULL;
+
+       LockWindows();
+
+       do
+       {
+               again = FALSE;
+
+               if (AslBase)
+               {
+                       if (AslRequestTags (xmfr->FReq,
+                               ASLFR_Window,           ThisTask->pr_WindowPtr,
+                               ASLFR_InitialFile,      FilePart (file),
+                               TAG_DONE))
+                       {
+                               /* Build file path */
+                               strncpy (file, ((struct FileRequester *)xmfr->FReq)->fr_Drawer,
+                                       PATHNAME_MAX);
+                               AddPart (file, ((struct FileRequester *)xmfr->FReq)->fr_File,
+                                       PATHNAME_MAX);
+
+                               retval = file;
+                       }
+               }
+               else if (ReqToolsBase)
+               {
+                       UBYTE filename[PATHNAME_MAX];
+                       ULONG flags = 0;
+
+
+                       strncpy (filename, FilePart (file), PATHNAME_MAX);
+
+                       if (xmfr->Flags & FRF_DOSAVEMODE)               flags |= FREQF_SAVE;
+                       if (xmfr->Flags & FRF_DOPATTERNS)               flags |= FREQF_PATGAD;
+                       if (xmfr->Flags & FRF_DOMULTISELECT)    flags |= FREQF_MULTISELECT;
+
+                       if (rtFileRequest (xmfr->FReq, filename, (xmfr->Title == -1) ? NULL : STR(xmfr->Title),
+                               RT_ShareIDCMP,  TRUE,
+                               RTFI_Flags,             flags,
+                               TAG_DONE))
+                       {
+                               /* Build file path */
+                               strncpy (file, ((struct rtFileRequester *)(xmfr->FReq))->Dir, PATHNAME_MAX);
+                               AddPart (file, filename, PATHNAME_MAX);
+
+                               retval = file;
+                       }
+               }
+
+
+               if (retval && (xmfr->Flags & FRF_DOSAVEMODE))
+               {
+                       switch (CheckOverwrite (retval))
+                       {
+                               case 1:                 /* Yes */
+                                       break;
+
+                               case 2:                 /* Choose Another */
+                                       again = TRUE;
+                                       break;
+
+                               default:                /* No */
+                                       retval = NULL;
+                                       break;
+                       }
+               }
+
+       } while (again);
+
+       UnlockWindows();
+
+       return retval;
+}
+
+
+
+static LONG CheckOverwrite (CONST_STRPTR path)
+
+/* Checks if the given file already exists and
+ * kindly asks the user if he knows what he's doing.
+ *
+ * RETURN
+ *             0 - Abort
+ *             1 - Continue
+ *             2 - Open the requester again
+ */
+{
+       if (GuiSwitches.AskOverwrite)
+       {
+               BPTR fh;
+
+               if (fh = Open (path, MODE_OLDFILE))
+               {
+                       Close (fh);
+
+                       return ShowRequest (MSG_FILE_EXISTS, MSG_OVERWRITE, FilePart (path));
+               }
+       }
+
+       return 1;
+}
+
+
+
+GLOBALCALL LONG ShowRequestStr (CONST_STRPTR text, CONST_STRPTR gtext, APTR args)
+
+/* Simple N-way requester function. Passing NULL as <gtext>
+ * will put a single "Ok" button.  Will fall to a Printf()
+ * when IntuitionBase is NULL.
+ */
+{
+       LONG ret;
+
+
+       if (!gtext) gtext = STR(MSG_OK);
+
+       if (!ShowRequesters) return 1;
+
+       LockWindows();
+
+       if (ReqToolsBase)
+       {
+               /* This silly thing gets around a nasty SAS/C 6.56 bug which
+                * causes a CXERR (and generates wrong code) when making a
+                * libcall with a parameter in A4.
+                */
+               volatile APTR rtargs = args;
+
+               static LONG tags[] =
+               {
+                       RTEZ_ReqTitle,  0,
+                       RT_ShareIDCMP,  FALSE,
+                       TAG_DONE
+               };
+
+               tags[1] = (LONG)STR(MSG_XMODULE_REQUEST);
+
+               ret = rtEZRequestA (text, gtext, NULL, rtargs, (struct TagItem *)tags);
+       }
+       else if (IntuitionBase)
+       {
+               static struct EasyStruct es =
+               {
+                       sizeof (struct EasyStruct),
+                       0,
+                       NULL,
+                       NULL,
+                       NULL
+               };
+
+               es.es_Title                     = STR(MSG_XMODULE_REQUEST);
+               es.es_TextFormat        = (STRPTR)text;
+               es.es_GadgetFormat      = (STRPTR)gtext;
+               ret = EasyRequestArgs (ThisTask->pr_WindowPtr, &es, NULL, args);
+       }
+       else
+       {
+               VPrintf (text, args);
+               FPutC (StdOut, (LONG)'\n');
+               ret = 1;
+       }
+
+       UnlockWindows();
+
+       return ret;
+}
+
+
+
+GLOBALCALL LONG ShowRequestArgs (ULONG msg, ULONG gadgets, APTR args)
+
+/* Localized interface to ShowRequestStr(). The <msg> and
+ * <gadgets> arguments are any MSG_#? from Locale.h.
+ */
+{
+       return ShowRequestStr (STR(msg), STR(gadgets), args);
+}
+
+
+
+GLOBALCALL LONG ShowRequest (ULONG msg, ULONG gadgets, ...)
+
+/* Localized, Variable arguments stub for ShowRequestStr()
+ * The <msg> and <gadgets> arguments are any MSG_#? from Locale.h.
+ */
+{
+       return ShowRequestStr (STR(msg), STR(gadgets), (APTR)(((LONG *)(&gadgets))+1));
+}
+
+
+
+GLOBALCALL LONG ScrModeRequest (struct ScrInfo *scrinfo)
+
+/* Let user choose a new screen mode and store mode attributes in the ScrInfo
+ * structure.
+ *
+ * Returns: TRUE for success, FALSE for failure.
+ */
+{
+       BOOL ChangeScreen = FALSE;
+
+       if (AslBase && AslBase->lib_Version >= 38)      /* ASL */
+       {
+               struct ScreenModeRequester *ScrModeReq;
+               struct List customsmlist;
+               struct DisplayMode clonemode;
+
+
+               /* Setup custom screen mode for Workbench cloning */
+
+               memset (&clonemode, 0, sizeof (clonemode));
+
+               clonemode.dm_Node.ln_Name = STR(MSG_CLONE_WB);
+               clonemode.dm_DimensionInfo.Header.StructID = DTAG_DIMS;
+               clonemode.dm_DimensionInfo.Header.DisplayID = 0xFFFFFFFF;
+               clonemode.dm_DimensionInfo.Header.SkipID = TAG_SKIP;
+               clonemode.dm_DimensionInfo.Header.Length = (sizeof (struct DimensionInfo) - sizeof (struct QueryHeader)) / 2;
+               clonemode.dm_PropertyFlags = DIPF_IS_WB;
+
+               NEWLIST (&customsmlist);
+               ADDHEAD (&customsmlist, (struct Node *)&clonemode);
+
+
+               if (ScrModeReq = AllocAslRequest (ASL_ScreenModeRequest, NULL))
+               {
+                       LockWindows();
+
+                       if (AslRequestTags (ScrModeReq,
+                               ASLSM_Window,                           ThisTask->pr_WindowPtr,
+                               ASLSM_DoWidth,                          TRUE,
+                               ASLSM_DoHeight,                         TRUE,
+                               ASLSM_DoDepth,                          TRUE,
+                               ASLSM_DoOverscanType,           TRUE,
+                               ASLSM_DoAutoScroll,                     TRUE,
+                               ASLSM_InitialDisplayID,         GetVPModeID (&Scr->ViewPort),
+                               ASLSM_InitialDisplayWidth,      Scr->Width,
+                               ASLSM_InitialDisplayHeight,     Scr->Height,
+                               ASLSM_InitialDisplayDepth,      DrawInfo->dri_Depth,
+                               ASLSM_InitialAutoScroll,        Scr->Flags & AUTOSCROLL,
+                               ASLSM_InitialOverscanType,      scrinfo->OverscanType,
+                               ASLSM_MinWidth,                         640,
+                               ASLSM_MinHeight,                        200,
+                               ASLSM_CustomSMList,                     &customsmlist,
+                               TAG_DONE))
+                       {
+                               if (ScrModeReq->sm_DisplayID == 0xFFFFFFFF)
+                                       scrinfo->DisplayID = 0; /* Picked special clone WB mode */
+                               else
+                               {
+                                       scrinfo->DisplayID = ScrModeReq->sm_DisplayID;
+                                       scrinfo->Width = ScrModeReq->sm_DisplayWidth;
+                                       scrinfo->Height = ScrModeReq->sm_DisplayHeight;
+                                       scrinfo->Depth = ScrModeReq->sm_DisplayDepth;
+                                       scrinfo->OverscanType = ScrModeReq->sm_OverscanType;
+                                       scrinfo->AutoScroll = ScrModeReq->sm_AutoScroll;
+                               }
+                               ChangeScreen = TRUE;
+                       }
+
+                       FreeAslRequest (ScrModeReq);
+                       UnlockWindows();
+               }
+       }
+       else    /* ReqTools */
+       {
+               struct rtScreenModeRequester *ScrModeReq;
+               BOOL CloseReqTools = FALSE;
+
+               if (!ReqToolsBase)
+               {
+                       if (!(ReqToolsBase = (struct ReqToolsBase *)
+                               OpenLibrary ("reqtools.library", 38)))
+                       {
+                               CantOpenLib ("reqtools.library", 38);
+                               return FALSE;
+                       }
+                       CloseReqTools = TRUE;
+               }
+
+               if (ScrModeReq = rtAllocRequestA (RT_SCREENMODEREQ, NULL))
+               {
+                       LockWindows();
+
+                       if (rtScreenModeRequest (ScrModeReq, NULL,
+                               RTSC_Flags,             SCREQF_OVERSCANGAD | SCREQF_AUTOSCROLLGAD |
+                                                               SCREQF_SIZEGADS | SCREQF_DEPTHGAD | SCREQF_GUIMODES,
+                               RT_ShareIDCMP,  TRUE,
+                               TAG_DONE))
+                       {
+                               scrinfo->DisplayID = ScrModeReq->DisplayID;
+                               scrinfo->Width = ScrModeReq->DisplayWidth;
+                               scrinfo->Height = ScrModeReq->DisplayHeight;
+                               scrinfo->Depth = ScrModeReq->DisplayDepth;
+                               scrinfo->OverscanType = ScrModeReq->OverscanType;
+                               scrinfo->AutoScroll = ScrModeReq->AutoScroll;
+                               ChangeScreen = TRUE;
+                       }
+
+                       rtFreeRequest (ScrModeReq);
+                       UnlockWindows();
+               }
+
+               if (CloseReqTools)
+               {
+                       CloseLibrary ((struct Library *)ReqToolsBase);
+                       ReqToolsBase = NULL;
+               }
+       }
+
+       return ChangeScreen;
+}
+
+
+
+GLOBALCALL LONG FontRequest (struct TextAttr *ta, ULONG flags)
+
+/* Requests a font to the user and copies the selected font to the
+ * passed TextAttr structure.  The ta_Name field is allocated with
+ * AllocVec() and the font name is copied to it.
+ *
+ * Returns: FALSE for failure, anything else for success.
+ */
+{
+       struct TextAttr *result = NULL;
+
+       LockWindows();
+
+       if (AslBase)
+       {
+               if (AslRequestTags (FontReq,
+                       ASLFO_Window,   ThisTask->pr_WindowPtr,
+                       ASLFO_Flags,    FOF_DOSTYLE | flags,
+                       TAG_DONE))
+                               result = &((struct FontRequester *)FontReq)->fo_Attr;
+       }
+       else if (ReqToolsBase)
+       {
+               if (rtFontRequest (FontReq, NULL,
+                       RT_ShareIDCMP,  TRUE,
+                       RTFO_Flags,             FREQF_SCALE | FREQF_STYLE | ((flags & FOF_FIXEDWIDTHONLY) ? FREQF_FIXEDWIDTH : 0),
+                       TAG_DONE))
+                               result = &((struct rtFontRequester *)FontReq)->Attr;
+
+       }
+
+       if (result) CopyTextAttrPooled (Pool, result, ta);
+
+       UnlockWindows();
+
+       return result != NULL;
+}
+
+
+
+GLOBALCALL void FreeFReq (void)
+{
+       ULONG i;
+
+       /* Terminate async requester */
+       if (FileReqTask)
+       {
+               while (!(SetSignal (0L, FileReqSig) & FileReqSig))
+                       ShowRequest (MSG_CLOSE_FILEREQUESTER, MSG_CONTINUE, NULL);
+
+               FileReqTask = NULL;
+       }
+
+       if (FileReqPort)
+       {
+               struct FileReqMsg *frmsg = (struct FileReqMsg *) GetMsg (FileReqPort);
+
+               if (frmsg)
+               {
+                       if ((frmsg->XMFReq->Flags & FRF_DOMULTISELECT) && ReqToolsBase)
+                               rtFreeFileList (frmsg->Result);
+                       FreeMem (frmsg, sizeof (struct FileReqMsg));
+               }
+
+               DeleteMsgPort (FileReqPort);    FileReqPort = NULL;
+               Signals &= ~FileReqSig;
+       }
+
+       if (AslBase)
+       {
+               for (i = 0; i < FREQ_COUNT; i++)
+               {
+                       FreeAslRequest (FileReqs[i].FReq);
+                       FileReqs[i].FReq = NULL;
+               }
+
+               FreeAslRequest (FontReq);               FontReq = NULL;
+               CloseLibrary (AslBase);                 AslBase = NULL;
+       }
+
+       if (ReqToolsBase)
+       {
+               for (i = 0; i < FREQ_COUNT; i++)
+               {
+                       rtFreeRequest (FileReqs[i].FReq);
+                       FileReqs[i].FReq = NULL;
+               }
+
+               rtFreeRequest (FontReq); FontReq = NULL;
+               CloseLibrary ((struct Library *)ReqToolsBase); ReqToolsBase = NULL;
+       }
+}
+
+
+
+GLOBALCALL LONG SetupAsl (void)
+{
+       ULONG i;
+       struct XMFileReq *xmfr;
+
+       if (!AslBase)
+       {
+               if (!(AslBase = OpenLibrary ("asl.library", 37)))
+               {
+                       CantOpenLib ("asl.library", 37);
+                       return RETURN_FAIL;
+               }
+       }
+
+       for (i = 0; i < FREQ_COUNT; i++)
+       {
+               xmfr = &FileReqs[i];
+
+               if (!(xmfr->FReq = AllocAslRequestTags (ASL_FileRequest,
+                       (xmfr->Title == -1) ? TAG_IGNORE : ASLFR_TitleText, (xmfr->Title == -1) ? NULL : STR(xmfr->Title),
+                       ASLFR_Flags1,           xmfr->Flags | FRF_PRIVATEIDCMP,
+                       ASLFR_Flags2,           FRF_REJECTICONS,
+                       TAG_DONE)))
+               return RETURN_FAIL;
+
+       }
+
+       if (!(FontReq = AllocAslRequestTags (ASL_FontRequest,
+               TAG_DONE)))
+               return RETURN_FAIL;
+
+       return RETURN_OK;
+}
+
+
+
+GLOBALCALL LONG SetupReqTools (void)
+{
+       ULONG i;
+
+       if (!(ReqToolsBase = (struct ReqToolsBase *)OpenLibrary ("reqtools.library", 38)))
+       {
+               CantOpenLib ("reqtools.library", 38);
+               return RETURN_FAIL;
+       }
+
+       for (i = 0; i < FREQ_COUNT; i++)
+               if (!(FileReqs[i].FReq = rtAllocRequestA (RT_FILEREQ, NULL)))
+                       return RETURN_FAIL;
+
+       if (!(FontReq = rtAllocRequestA (RT_FONTREQ, NULL)))
+               return RETURN_FAIL;
+
+       return RETURN_OK;
+}
+
+
+
+GLOBALCALL LONG SetupRequesters (void)
+{
+       FreeFReq();
+
+       if (!FileReqPort)       /* Create FileRequester reply port */
+       {
+               if (!(FileReqPort = CreateMsgPort ())) return ERROR_NO_FREE_STORE;
+               FileReqSig = 1 << FileReqPort->mp_SigBit;
+               Signals |= FileReqSig;
+       }
+
+       if (GuiSwitches.UseReqTools)
+       {
+               if (SetupReqTools())
+               {
+                       GuiSwitches.UseReqTools = FALSE;
+                       return SetupAsl();
+               }
+       }
+       else if (SetupAsl())
+       {
+               GuiSwitches.UseReqTools = TRUE;
+               return SetupReqTools();
+       }
+
+       return 0;
+}
diff --git a/Requesters.h b/Requesters.h
new file mode 100644 (file)
index 0000000..56d4ca8
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+**     $Id:$
+**
+**     Copyright (C) 1993,94,95,96,98,99 by Bernardo Innocenti
+**
+**     Requesters support functions
+*/
+
+GLOBALCALL STRPTR      FileRequest                             (ULONG freq, STRPTR file);
+GLOBALCALL LONG                StartFileRequest                (ULONG freq, void (*func)(STRPTR file, ULONG num, ULONG count));
+GLOBALCALL void                HandleFileRequest               (void);
+GLOBALCALL LONG                ShowRequestStr                  (CONST_STRPTR text, CONST_STRPTR gtext, APTR args);
+GLOBALCALL LONG                ShowRequestArgs                 (ULONG msg, ULONG gadgets, APTR args);
+GLOBALCALL LONG                ShowRequest                             (ULONG msg, ULONG gadgets, ...);
+GLOBALCALL void                FreeFReq                                (void);
+GLOBALCALL LONG                SetupRequesters                 (void);
+GLOBALCALL LONG                ScrModeRequest                  (struct ScrInfo *scrinfo);
+GLOBALCALL LONG                FontRequest                             (struct TextAttr *ta, ULONG flags);
diff --git a/Rexx.c b/Rexx.c
new file mode 100644 (file)
index 0000000..b0ea25a
--- /dev/null
+++ b/Rexx.c
@@ -0,0 +1,635 @@
+/*
+**     Rexx.c
+**
+**     Copyright (C) 1994,95,96,97 by Bernardo Innocenti
+**
+**     ARexx interface handling routines
+*/
+
+#include <exec/memory.h>
+#include <rexx/rxslib.h>
+#include <rexx/storage.h>
+#include <rexx/errors.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/utility.h>
+#include <proto/rexxsyslib.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+struct RexxCmd
+{
+       const STRPTR Cmd;
+       const STRPTR Template;
+       LONG (*Func)(struct RexxMsg *, LONG *);
+       LONG Pad;       /* Makes each entry 16 bytes long for faster array access */
+};
+
+
+/* Local function prototypes */
+
+static LONG ExecRexxCmd                        (struct RexxMsg *msg, struct RexxCmd *cmd, const UBYTE *rexxargs);
+
+static LONG RexxActivate               (struct RexxMsg *msg, LONG *args);
+static LONG RexxClear                  (struct RexxMsg *msg, LONG *args);
+static LONG RexxClose                  (struct RexxMsg *msg, LONG *args);
+static LONG RexxColumn                 (struct RexxMsg *msg, LONG *args);
+static LONG RexxCopy                   (struct RexxMsg *msg, LONG *args);
+static LONG RexxCursor                 (struct RexxMsg *msg, LONG *args);
+static LONG RexxCut                            (struct RexxMsg *msg, LONG *args);
+static LONG RexxDeactivate             (struct RexxMsg *msg, LONG *args);
+static LONG RexxErase                  (struct RexxMsg *msg, LONG *args);
+static LONG RexxGotoBookmark   (struct RexxMsg *msg, LONG *args);
+static LONG RexxHelp                   (struct RexxMsg *msg, LONG *args);
+static LONG RexxIconify                        (struct RexxMsg *msg, LONG *args);
+static LONG RexxLine                   (struct RexxMsg *msg, LONG *args);
+static LONG RexxLockGui                        (struct RexxMsg *msg, LONG *args);
+static LONG RexxNew                            (struct RexxMsg *msg, LONG *args);
+static LONG RexxOpen                   (struct RexxMsg *msg, LONG *args);
+static LONG RexxOptimize               (struct RexxMsg *msg, LONG *args);
+static LONG RexxPaste                  (struct RexxMsg *msg, LONG *args);
+static LONG RexxPrint                  (struct RexxMsg *msg, LONG *args);
+static LONG RexxQuit                   (struct RexxMsg *msg, LONG *args);
+static LONG RexxRequestFile            (struct RexxMsg *msg, LONG *args);
+static LONG RexxRequestResponse        (struct RexxMsg *msg, LONG *args);
+static LONG RexxRequestNotify  (struct RexxMsg *msg, LONG *args);
+static LONG RexxSave                   (struct RexxMsg *msg, LONG *args);
+static LONG RexxSaveInstrument (struct RexxMsg *msg, LONG *args);
+static LONG RexxScreenToBack   (struct RexxMsg *msg, LONG *args);
+static LONG RexxScreenToFront  (struct RexxMsg *msg, LONG *args);
+static LONG RexxSelectInstrument(struct RexxMsg *msg, LONG *args);
+static LONG RexxSetBookmark            (struct RexxMsg *msg, LONG *args);
+static LONG RexxShowMessage            (struct RexxMsg *msg, LONG *args);
+static LONG RexxUnLockGui              (struct RexxMsg *msg, LONG *args);
+static LONG RexxVersion                        (struct RexxMsg *msg, LONG *args);
+
+
+static struct Library *RexxSysBase = NULL;
+
+XDEF struct MsgPort *PubPort = NULL;
+XDEF ULONG PubPortSig = 0L;
+XDEF UBYTE PubPortName[16];    /* ARexx host name */
+
+
+static struct RexxCmd RexxCmds[] =
+{
+       { "ACTIVATE",           NULL,                           RexxActivate },
+       { "CLEAR",                      "FORCE/S",                      RexxClear },
+       { "CLOSE",                      "FORCE/S",                      RexxClose },
+       { "COLUMN",                     "/N/A",                         RexxColumn },
+       { "COPY",                       NULL,                           RexxCopy },
+       { "CURSOR",                     "UP/S,DOWN/S,LEFT/S,RIGHT/S",   RexxCursor },
+       { "CUT",                        NULL,                           RexxCut },
+       { "DEACTIVATE",         NULL,                           RexxDeactivate },
+       { "ERASE",                      "FORCE/S",                      RexxErase },
+       { "GOTOBOOKMARK",       NULL,                           RexxGotoBookmark },
+       { "HELP",                       "COMMAND,PROMPT/S",     RexxHelp },
+       { "ICONIFY",            NULL,                           RexxIconify },
+       { "LINE",                       "/N/A",                         RexxLine },
+       { "LOCKGUI",            NULL,                           RexxLockGui },
+       { "NEW",                        "PORTNAME/K",           RexxNew },
+       { "OPEN",                       "FILENAME,FORCE/S",     RexxOpen },
+       { "OPTIMIZE",           NULL,                           RexxOptimize },
+       { "PASTE",                      NULL,                           RexxPaste },
+       { "PRINT",                      "PROMPT/S",                     RexxPrint },
+       { "QUIT",                       "FORCE/S",                      RexxQuit },
+       { "REQUESTFILE",        "TITLE/K,PATH/K,FILE/K,PATTERN/K", RexxRequestFile },
+       { "REQUESTRESPONSE","TITLE/K,PROMPT/K", RexxRequestResponse },
+       { "REQUESTNOTIFY",      "PROMPT/K",                     RexxRequestNotify },
+       { "SAVE",                       "NAME,FORMAT",          RexxSave },
+       { "SAVEINSTRUMENT",     "NAME",                         RexxSaveInstrument },
+       { "SCREENTOBACK",       NULL,                           RexxScreenToBack },
+       { "SCREENTOFRONT",      NULL,                           RexxScreenToFront },
+       { "SELECTINSTRUMENT","/N/A",                    RexxSelectInstrument },
+       { "SETBOOKMARK",        NULL,                           RexxSetBookmark },
+       { "SHOWMESSAGE",        "MSG/A",                        RexxShowMessage },
+       { "UNLOCKGUI",          NULL,                           RexxUnLockGui },
+       { "VERSION",            NULL,                           RexxVersion }
+};
+
+#define COMMAND_CNT (sizeof (RexxCmds) / sizeof (struct RexxCmd))
+
+
+GLOBALCALL void HandleRexxMsg (void)
+
+/* Arexx command handler */
+{
+       struct RexxMsg *msg;
+       LONG     compare, high, low, mid, skip, cmdlen;
+       STRPTR   arg0;
+       UBYTE    cmd[32];
+
+       while (msg = (struct RexxMsg *) GetMsg (PubPort))
+       {
+               if (IsRexxMsg (msg))
+               {
+                       msg->rm_Result1 = RETURN_FAIL;
+
+                       /* Find command name length */
+                       arg0 = ARG0(msg);
+                       cmdlen = 0;
+
+                       while (*arg0 && (*arg0 != ' ') && (*arg0 != '\t')
+                               && (*arg0 != '\n') && (cmdlen < 31))
+                       {
+                               cmd[cmdlen] = *arg0 & ~(1 << 5);        /* Fast toupper() */
+                               cmdlen++;
+                               arg0++;
+                       }
+                       cmd[cmdlen] = '\0';
+
+                       skip = 0;
+                       low = 0;
+                       high = COMMAND_CNT - 1;
+
+                       /* Perform an optimized binary serch to find the ARexx
+                        * command in the ARexx commands array.
+                        */
+                       do
+                       {
+                               /* Search optimization. Skip first characters until they
+                                * are different.
+                                * We must test low != high because otherwise this
+                                * loop would go past the end of the string, as the
+                                * two strings we compare are identical).
+                                */
+                               if (low != high)
+                                       while ((RexxCmds[low].Cmd[skip] == RexxCmds[high].Cmd[skip])
+                                               && (RexxCmds[low].Cmd[skip] == cmd[skip]))
+                                               skip++;
+
+                               mid = (low + high) >> 1;
+                               compare = strcmp (RexxCmds[mid].Cmd + skip, cmd + skip);
+
+                               if (compare > 0)
+                                       high = mid - 1;
+                               else if (compare < 0)
+                                       low = mid + 1;
+                               else
+                               {
+                                       msg->rm_Result1 = ExecRexxCmd (msg, &RexxCmds[mid],
+                                               ARG0(msg) + cmdlen);
+                                       break; /* Exit from loop */
+                               }
+                       }
+                       while (low <= high);
+               }
+
+               ReplyMsg ((struct Message *)msg);
+       }
+}
+
+
+
+static LONG ExecRexxCmd (struct RexxMsg *msg, struct RexxCmd *cmd, const UBYTE *rexxargs)
+{
+       struct RDArgs *rdargs;
+       UBYTE *argbuf;
+       ULONG arglen = strlen (rexxargs) + 1;   /* Space for newline */
+       LONG rc = RC_ERROR;
+       LONG argarray[6] = { 0L };                      /* Max 6 arguments allowed! */
+
+       ShowRequesters = FALSE;
+
+       if (!cmd->Template)
+               rc = (cmd->Func)(msg, NULL);    /* Call command directly */
+       else if (argbuf = AllocVec (arglen, MEMF_ANY))
+       {
+               /* Copy arguments to temporary buffer.
+                * ReadArgs() also requires a newline.
+                */
+               strcpy (argbuf, rexxargs);
+               argbuf[arglen-1] = '\n';
+
+               if (rdargs = AllocDosObject (DOS_RDARGS, NULL))
+               {
+                       rdargs->RDA_Source.CS_Buffer = argbuf;
+                       rdargs->RDA_Source.CS_Length = arglen;
+                       rdargs->RDA_Flags |= RDAF_NOPROMPT;
+
+                       if (ReadArgs ((volatile STRPTR)cmd->Template, argarray, rdargs))
+                       {
+                               /* Call Command server */
+                               rc = (cmd->Func)(msg, argarray);
+
+                               FreeArgs (rdargs);
+                       }
+                       FreeDosObject (DOS_RDARGS, rdargs);
+               }
+               FreeVec (argbuf);
+       }
+
+       ShowRequesters = TRUE;
+
+       return rc;
+}
+
+
+
+GLOBALCALL LONG CreateRexxPort (void)
+
+/* Setup public port for ARexx host */
+{
+       ULONG i = 0;
+
+       if (!PubPortName[0]) return RETURN_FAIL;
+
+       if (!(RexxSysBase = OpenLibrary (RXSNAME, 36L)))
+               return RETURN_FAIL;
+
+       if (!(PubPort = CreateMsgPort ())) return ERROR_NO_FREE_STORE;
+
+       PubPortSig = 1 << PubPort->mp_SigBit;
+       Signals |= PubPortSig;
+
+       Forbid();
+       while (FindPort (PubPortName))
+               SPrintf (PubPortName, "XMODULE.%ld", ++i);
+
+       PubPort->mp_Node.ln_Name = PubPortName;
+       PubPort->mp_Node.ln_Pri = 1;
+       AddPort (PubPort);
+       Permit();
+
+       return RETURN_OK;
+}
+
+
+GLOBALCALL void DeleteRexxPort (void)
+{
+       if (PubPort)
+       {
+               RemPort (PubPort);
+               KillMsgPort (PubPort); PubPort = NULL;
+       }
+
+       if (RexxSysBase)
+               { CloseLibrary (RexxSysBase); RexxSysBase = NULL; }
+}
+
+/************************/
+/* Rexx Command servers */
+/************************/
+
+static LONG RexxActivate               (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+static LONG RexxClear                  (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+static LONG RexxClose                  (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+static LONG RexxColumn                 (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+static LONG RexxCopy                   (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+static LONG RexxCursor                 (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+static LONG RexxCut                            (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+static LONG RexxDeactivate             (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxErase                  (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxGotoBookmark   (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxHelp                   (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxIconify                        (struct RexxMsg *msg, LONG *args)
+{
+       Iconify();
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxLine                   (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxLockGui                (struct RexxMsg *msg, LONG *args)
+{
+       LockWindows();
+
+       return RETURN_OK;
+}
+
+
+
+static LONG RexxNew                            (struct RexxMsg *msg, LONG *args)
+{
+       struct SongInfo *si;
+
+       if (si = xmCreateSong (
+               SNGA_ReadyToUse,        TRUE,
+               XMSNG_AddToList,        -1,
+               XMSNG_Active,           TRUE,
+               TAG_DONE))
+       {
+               ReleaseSemaphore (&si->Lock);
+               return RETURN_OK;
+       }
+
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxOpen (struct RexxMsg *msg, LONG *args)
+{
+       struct SongInfo *si;
+
+       LockWindows();
+
+       if (args[0])
+       {
+               if (si = xmLoadModule ((STRPTR)args[0],
+                       XMSNG_OldSong,          NULL,
+                       XMSNG_AddToList,        TRUE,
+                       XMSNG_Active,           TRUE,
+                       TAG_DONE))
+                       ReleaseSemaphore (&si->Lock);
+       }
+       else
+       {
+               UBYTE filename[PATHNAME_MAX];
+
+               filename[0] = '\0';
+
+               if (FileRequest (FREQ_LOADMOD, filename))
+               {
+                       if (si = xmLoadModule (filename,
+                               XMSNG_OldSong,          NULL,
+                               XMSNG_AddToList,        TRUE,
+                               XMSNG_Active,           TRUE,
+                               TAG_DONE))
+                               ReleaseSemaphore (&si->Lock);
+               }
+       }
+
+       UnlockWindows();
+
+       return LastErr;
+}
+
+
+
+static LONG RexxOptimize (struct RexxMsg *msg, LONG *args)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               xmProcessSong (si, NULL,
+                       XMSNG_Optimize, XMOF_DEFAULT,
+                       TAG_DONE);
+
+               UpdateSongInfo();
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       return RETURN_OK;
+}
+
+
+
+static LONG RexxPaste                  (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxPrint                  (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxQuit (struct RexxMsg *msg, LONG *args)
+{
+       Quit = 1;
+       if (args[0]) GuiSwitches.AskExit = FALSE;
+
+       return RETURN_OK;
+}
+
+
+
+static LONG RexxRequestFile            (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxRequestResponse        (struct RexxMsg *msg, LONG *args)
+{
+       return ShowRequestStr ((STRPTR)args[0], (STRPTR)args[1], NULL);
+}
+
+
+
+static LONG RexxRequestNotify  (struct RexxMsg *msg, LONG *args)
+{
+       ShowRequestStr ((STRPTR)args[0], NULL, NULL);
+       return RETURN_OK;
+}
+
+
+
+static LONG RexxSave (struct RexxMsg *msg, LONG *args)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               if (args[0])
+                       SetAttrs (si,
+                               SNGA_Path, (STRPTR)args[0],
+                               TAG_DONE);
+
+               if (args[1])
+               {
+                       struct XMHook *saver;
+
+                       /* Workaround for Pre-V39 ObtainSemaphoreShared() bug (see autodoc) */
+
+                       /* Try to get the shared semaphore */
+                       if (!AttemptSemaphoreShared (&XModuleBase->xm_BaseLock))
+                               /* Check if we can get the exclusive version */
+                               if (!AttemptSemaphore (&XModuleBase->xm_BaseLock))
+                                       /* Oh well, wait for the shared lock */
+                                       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+
+                       if (saver = (struct XMHook *)FindName ((struct List *)&XModuleBase->xm_Loaders, (STRPTR)args[1]))
+                               LastErr = xmSaveModuleA (si, si->Path, saver, NULL);
+                       else
+                               LastErr = ERROR_OBJECT_NOT_FOUND;
+
+                       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+               }
+               else
+                       LastErr = xmSaveModuleA (si, si->Path, NULL, NULL);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       return LastErr;
+}
+
+
+
+static LONG RexxSaveInstrument (struct RexxMsg *msg, LONG *args)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               struct Instrument *instr;
+
+               if (instr = si->Instr[si->CurrentInst])
+               {
+                       if (args[0])
+                               LastErr = SaveInstrument (instr, (STRPTR)args[0]);
+                       else
+                               LastErr = SaveInstrument (instr, instr->Name);
+               }
+               else LastErr = ERROR_OBJECT_NOT_FOUND;
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       return LastErr;
+}
+
+
+
+static LONG RexxSelectInstrument (struct RexxMsg *msg, LONG *args)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               if (*((ULONG *)args[0]) >= MAXINSTRUMENTS)
+                       return RETURN_FAIL;
+
+               si->CurrentInst = *((ULONG *)args[0]);
+
+               ReleaseSemaphore (&si->Lock);
+
+               UpdateInstrInfo();
+       }
+
+       return RETURN_OK;
+}
+
+
+
+static LONG RexxScreenToBack (struct RexxMsg *msg, LONG *args)
+{
+       if (Scr) ScreenToBack (Scr);
+
+       return RETURN_OK;
+}
+
+
+
+static LONG RexxScreenToFront (struct RexxMsg *msg, LONG *args)
+{
+       if (Scr)
+       {
+               ScreenToFront (Scr);
+               if (ThisTask->pr_WindowPtr) ActivateWindow (ThisTask->pr_WindowPtr);
+       }
+
+       return RETURN_OK;
+}
+
+
+
+static LONG RexxSetBookmark (struct RexxMsg *msg, LONG *args)
+{
+       return RETURN_FAIL;
+}
+
+
+
+static LONG RexxShowMessage (struct RexxMsg *msg, LONG *args)
+{
+       ShowString ((STRPTR)args[0], NULL);
+
+       return RETURN_OK;
+}
+
+
+
+static LONG RexxUnLockGui (struct RexxMsg *msg, LONG *args)
+{
+       UnlockWindows();
+       return RETURN_OK;
+}
+
+
+
+static LONG RexxVersion (struct RexxMsg *msg, LONG *args)
+{
+       UBYTE RexxVer[8];
+
+       SPrintf (RexxVer, "%ld.%ld", VERSION, REVISION);
+
+//     SetRexxVar ((struct Message *)msg, "RESULT", RexxVer, strlen (RexxVer));
+       msg->rm_Result2=(LONG)CreateArgstring (RexxVer, (LONG)strlen(RexxVer));
+
+       return RETURN_OK;
+}
diff --git a/RomTag.asm b/RomTag.asm
new file mode 100644 (file)
index 0000000..4f808a8
--- /dev/null
@@ -0,0 +1,301 @@
+******************************************************************************
+*
+* $VER: RomTag.asm (1.21.97)
+*
+* Copyright (C) 1995,96,97 by Bernardo Innocenti
+*
+******************************************************************************
+*
+* This source is based on the RomTag.asm source I've found somewhere :-)
+*
+*
+       INCLUDE "exec/types.i"
+       INCLUDE "exec/macros.i"
+       INCLUDE "exec/libraries.i"
+       INCLUDE "exec/lists.i"
+       INCLUDE "exec/alerts.i"
+       INCLUDE "exec/initializers.i"
+       INCLUDE "exec/resident.i"
+       INCLUDE "exec/execbase.i"
+       INCLUDE "libraries/dos.i"
+
+; BOOPSI class libraries should use this structure as the base for their
+; library data.  This allows developers to obtain the class pointer for
+; performing object-less inquiries.
+
+       STRUCTURE ClassBase,0
+       STRUCT   cl_Lib,LIB_SIZE        ; Embedded library
+       UWORD    cl_Pad                 ; Align the structure
+       APTR     cl_Class               ; Class pointer
+       APTR     cl_SegList             ; SegList pointer
+       LABEL    ClassLibrary_SIZEOF
+
+;---------------------------------------------------------------------------
+
+       XDEF    _LibFuncTable
+       XDEF    _LibDataTable
+
+       XREF    _LibName
+       XREF    _LibId
+       XREF    __UserLibInit
+       XREF    __UserLibCleanup
+
+       XREF    __GetEngine
+
+       IFD             XMHOOK
+       XREF    __SetupXMHook
+       ENDC
+
+;---------------------------------------------------------------------------
+
+       SECTION Code
+
+; First executable location, must return an error to the caller
+
+       moveq   #-1,d0
+       rts
+
+;---------------------------------------------------------------------------
+
+_ROMTAG
+       DC.W    RTC_MATCHWORD   ; UWORD RT_MATCHWORD
+       DC.L    _ROMTAG         ; APTR  RT_MATCHTAG
+       DC.L    _ENDCODE        ; APTR  RT_ENDSKIP
+       DC.B    RTF_AUTOINIT    ; UBYTE RT_FLAGS
+       DC.B    LIBVERSION      ; UBYTE RT_VERSION
+       DC.B    NT_LIBRARY      ; UBYTE RT_TYPE
+       DC.B    0               ; BYTE  RT_PRI  <--- WARNING: Using negative values here will cause trouble!
+       DC.L    _LibName        ; APTR  RT_NAME
+       DC.L    _LibId          ; APTR  RT_IDSTRING
+       DC.L    _LibInitTable   ; APTR  RT_INIT
+
+
+* The RomTag specified that we were RTF_AUTOINIT. This means that rt_Init
+* points to the table below. (Without RTF_AUTOINIT it would point to a
+* routine to run.)
+*
+* Our library base is a standard struct Library, followed by a WORD
+* padding, a pointer to the boopsi Class structure of the external
+* boopsi class and a pointer to our SegList.  The SegList pointer
+* will be returned by LibExpunge() in order to have our code UnloadSeg()'ed
+* The Class pointer should be initialized by UserLibInit().
+
+_LibInitTable
+       dc.l    ClassLibrary_SIZEOF
+       dc.l    _LibFuncTable
+       dc.l    _LibDataTable
+       dc.l    _LibInit
+
+
+
+* Table of functions included in this library; the first 4 are the same
+* for any library and for internal Exec use only.
+
+V_DEF  MACRO
+       dc.w    \1 + (* - _LibFuncTable)
+       ENDM
+
+_LibFuncTable
+*      dc.w    -1              ; It's weird... The cool way didn't work for me 8-(
+*      V_DEF   _LibOpen
+*      V_DEF   _LibClose
+*      V_DEF   _LibExpunge
+*      V_DEF   _LibExtFunc
+*      V_DEF   __GetEngine
+*      IFD     XMHOOK
+*      V_DEF   __SetupXMHook
+*      ENDC
+*      dc.w    -1
+
+       dc.l    _LibOpen
+       dc.l    _LibClose
+       dc.l    _LibExpunge
+       dc.l    _LibExtFunc
+       dc.l    __GetEngine
+       IFD     XMHOOK
+       dc.l    __SetupXMHook
+       ENDC
+       dc.l    -1
+
+
+_LibDataTable
+       INITBYTE        LN_TYPE,NT_LIBRARY
+       INITLONG        LN_NAME,_LibName
+       INITBYTE        LN_PRI,-5
+       INITBYTE        LIB_FLAGS,(LIBF_SUMUSED!LIBF_CHANGED)
+       INITWORD        LIB_VERSION,LIBVERSION
+       INITWORD        LIB_REVISION,LIBREVISION
+       INITLONG        LIB_IDSTRING,_LibId
+       dc.w            0
+
+
+       CNOP    0,4
+
+
+* The following function will be called at startup time.
+*
+* Inputs:
+*      LibPtr (d0) - Pointer to the library base, initialized due to the
+*                      specifications in DataTable
+*      SegList (a0) - BPTR to the segment list
+*      _SysBase (a6) - The usual ExecBase pointer
+*
+* Result:
+*      LibPtr, if all was okay and the library may be linked into the
+*      system library list. NULL otherwise.
+*
+_LibInit:
+       move.l  d0,a1
+       move.l  a0,cl_SegList(a1)               ; Save SegList
+
+; Check CPU for 68020 or better
+       IFD             _MC68020_
+       move.w  AttnFlags(a6),d1
+       btst.w  #AFB_68020,d1
+       beq.s   fail$
+       ENDC
+
+       move.l  a6,-(sp)                        ; Save SysBase
+       move.l  d0,a6                           ; Put our base in a6
+       jsr     __UserLibInit                   ; Call user init
+       move.l  a6,a1                           ; save our base to a1
+       move.l  (sp)+,a6                        ; Retrieve SysBase
+       tst.l   d0
+       beq.s   fail$
+       rts
+
+fail$
+       bsr     FreeBase                        ; Free library base
+       moveq   #0,d0
+       rts
+
+
+* The following functions are called from exec.library/OpenLibrary(),
+* exec.library/CloseLibrary() and exec.library/ExpungeLibrary(),
+* respectively. Exec passes our library base pointer in A6.
+*
+* Task switching will be turned off while these functions are being
+* executed, so they must be as short as possible.  As the data inside
+* the library base is protected with Forbid(), these functions must
+* not make calls which would explicitly or implicitly turn on multitasking.
+* This includes opening other disk based libraries.  The problem may be
+* overcame by protecting the library base with a SignalSemaphore.
+*
+
+
+* This function is called from exec.library/OpenLibrary().
+*
+* Inputs:
+*      LibPtr (a6) - Pointer to the library base
+*      Version (d0) - The suggested version number
+*
+* Result:
+*      LibPtr, if successful, NULL otherwise
+*
+
+_LibOpen:
+       addq.w  #1,LIB_OPENCNT(a6)
+       bclr.b  #LIBB_DELEXP,LIB_FLAGS(a6)      ; Prevent delayed expunge
+       move.l  a6,d0
+       rts
+
+
+
+* This function is called from exec/CloseLibrary().
+*
+* Inputs:
+*      LibPtr (A6) - pointer to the library base as returned from OpenLibrary().
+*
+* Result:
+*      Segment list of the library (see arguments of _LibInit), if there
+*      was a delayed expunge and the library is no longer open, NULL
+*      otherwise.
+*
+_LibClose:
+       subq.w  #1,LIB_OPENCNT(a6)
+       tst.w   LIB_OPENCNT(a6)
+       bne.s   .NoExpunge
+       btst.b  #LIBB_DELEXP,LIB_FLAGS(a6)
+       beq.s   .NoExpunge
+
+       bra.s   _LibExpunge
+
+.NoExpunge
+       moveq.l #0,d0
+       rts
+
+
+
+* This function is called from exec.library/RemoveLibrary().
+*
+* Inputs:
+*      LibPtr (A6) - pointer to the library base.
+*
+* Result:
+*      Segment list of the library (see arguments of _LibInit()),
+*      if the library isn't opened currently, NULL otherwise.
+*
+
+_LibExpunge:
+
+       ; Flag library base for delayed expunge
+       bset.b  #LIBB_DELEXP,LIB_FLAGS(a6)
+       tst.w   LIB_OPENCNT(a6)         ; Only expunge if OpenCnt == 0
+       bne.s   .DoNotExpunge
+
+.NotOpen
+
+       jsr     __UserLibCleanup        ; Call user cleanup code
+       tst.l   d0
+       beq.s   .DoNotExpunge
+
+       move.l  cl_SegList(a6),-(sp)    ; Save SegList pointer
+
+       move.l  a6,a1
+       REMOVE                          ; Remove us from Exec library list.
+
+
+; Free the library base
+
+       move.l  a6,a1                   ; LibBase
+       move.l  a6,-(sp)                ; Save A6
+       move.l  4.w,a6                  ; Load SysBase
+       bsr             FreeBase                ; Free our library base
+       move.l  (sp)+,a6                ; Restore A6
+
+       move.l  (sp)+,d0                ; Return our SegList
+       rts
+
+.DoNotExpunge
+
+; NOTE: I'm falling in _LibExtFunc from here!
+
+
+
+* Dummy function to return 0
+
+_LibExtFunc:
+       moveq   #0,d0
+       rts
+
+
+* Frees our library base
+*
+* Inputs:
+*      LibBase (a1) - Pointer to Library structure.
+*      SysBase (a6) - Pointer to SysBase
+*
+FreeBase:
+       moveq.l #0,d0
+       move.l  a1,a0
+       move.w  LIB_NEGSIZE(a0),d0
+       suba.l  d0,a1                   ; Get pointer to real start of library base
+       add.w   LIB_POSSIZE(a0),d0      ; Total library size (LIB_POSSIZE + LIB_NEGSIZE)
+       JSRLIB  FreeMem
+       rts
+
+;-----------------------------------------------------------------------
+
+_ENDCODE
+
+       END
diff --git a/SCOPTIONS b/SCOPTIONS
new file mode 100644 (file)
index 0000000..c7c88af
--- /dev/null
+++ b/SCOPTIONS
@@ -0,0 +1,25 @@
+CODE=FAR
+PARAMETERS=REGISTERS
+NOSTACKCHECK
+STRINGMERGE
+ERRORREXX
+NOMULTIPLEINCLUDES
+SMALLCODE
+SMALLDATA
+NOVERSION
+ABSFUNCPOINTER
+UTILITYLIBRARY
+NOICONS
+MEMORYSIZE=HUGE
+ONERROR=CONTINUE
+OPTIMIZERTIME
+MULTIPLECHARACTERCONSTANTS
+DEFINE NO_REQTOOLS_OBSOLETE
+DEFINE ASL_V38_NAMES_ONLY
+DEFINE INTUI_V36_NAMES_ONLY
+DEFINE IFFPARSE_V37_NAMES_ONLY
+DEFINE USE_BUILTIN_MATH
+DEFINE __USE_SYSBASE
+IGNORE=193
+IGNORE=306
+PUBSCREEN=Workbench
diff --git a/SampleWin.c b/SampleWin.c
new file mode 100644 (file)
index 0000000..dda02db
--- /dev/null
@@ -0,0 +1,953 @@
+/*
+**     SampleWin.c
+**
+**     Copyright (C) 1994,95,96 Bernardo Innocenti
+**
+**     Sample editor handling functions.
+*/
+
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+#include <graphics/rpattr.h>
+#include <graphics/gfxmacros.h>
+
+#include <clib/exec_protos.h>
+#include <clib/intuition_protos.h>
+#include <clib/graphics_protos.h>
+#include <clib/gadtools_protos.h>
+#include <clib/xmodule_protos.h>
+
+#include <pragmas/exec_sysbase_pragmas.h>
+#include <pragmas/intuition_pragmas.h>
+#include <pragmas/gadtools_pragmas.h>
+#include <pragmas/graphics_pragmas.h>
+#include <pragmas/xmodule_pragmas.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Gadgets IDs */
+
+enum {
+       GD_SampScroll,
+       GD_SampZoomIn,
+       GD_SampRangeAll,
+       GD_SampShowRange,
+       GD_SampZoomOut,
+       GD_SampClearRange,
+       GD_SampShowAll,
+       GD_SampSize,
+       GD_SampPlayR,
+       GD_SampPlayD,
+       GD_SampPlayL,
+       GD_SampPlayA,
+       GD_DisplayStart,
+       GD_DisplayEnd,
+       GD_DisplayLen,
+       GD_RangeStart,
+       GD_RangeEnd,
+       GD_RangeLen,
+       GD_RepStart,
+       GD_RepEnd,
+       GD_RepLen,
+       GD_SampBox,
+
+       Sample_CNT
+};
+
+
+/* Sample rendering modes */
+
+enum {
+       SAMP_PIXEL,
+       SAMP_LINE,
+       SAMP_FILLED
+};
+
+
+/* Local functions prototypes */
+
+static void SampleRender       (struct WinUserData *wud);
+static LONG SampBoxSetup       (struct Gadget *g);
+static void HandleSampleIDCMP (void);
+static void DrawRange          (void);
+static void UpdateRange                (WORD newend);
+static void UpdateRangeInfo    (void);
+
+static void DrawPixelGraph (struct RastPort *rp, BYTE *samp,
+       UWORD xmin, UWORD xmax, UWORD ycoord, UWORD height, UWORD step);
+static void DrawLineGraph (struct RastPort *rp, BYTE *samp,
+       UWORD xmin, UWORD xmax, UWORD ycoord, UWORD height, UWORD step);
+static void DrawFilledGraph (struct RastPort *rp, BYTE *samp,
+       UWORD xmin, UWORD xmax, UWORD ycoord, UWORD height, UWORD step);
+
+static void SampScrollClicked (void);
+static void SampZoomInClicked (void);
+static void SampRangeAllClicked (void);
+static void SampShowRangeClicked (void);
+static void SampZoomOutClicked (void);
+static void SampClearRangeClicked (void);
+static void SampShowAllClicked (void);
+static void SampPlayRClicked (void);
+static void SampPlayDClicked (void);
+static void SampPlayLClicked (void);
+static void SampPlayAClicked (void);
+static void DisplayStartClicked (void);
+static void DisplayEndClicked (void);
+static void DisplayLenClicked (void);
+static void RangeStartClicked (void);
+static void RangeEndClicked (void);
+static void RangeLenClicked (void);
+static void RepStartClicked (void);
+static void RepLenClicked (void);
+static void RepEndClicked (void);
+static void SampBoxClicked (void);
+
+static void SampleMiCut (void);
+static void SampleMiCopy (void);
+static void SampleMiPaste (void);
+static void SampleMiPoints (void);
+static void SampleMiLines (void);
+static void SampleMiFilled (void);
+
+
+static struct IBox SampBox;            /* Sample Box Coordinates */
+static WORD            RangeStartX, RangeEndX, RangePole;
+static WORD            LoopPole1, LoopPole2;
+static LONG            DisplayStart, DisplayEnd;
+
+
+
+static struct Gadget   *SampleGadgets[Sample_CNT];
+
+static struct NewMenu SampleNewMenu[] = {
+       NM_TITLE, (STRPTR)MSG_EDIT_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_CUT_MEN, (STRPTR)"X", 0, 0L, (APTR)SampleMiCut,
+       NM_ITEM, (STRPTR)MSG_COPY_MEN, (STRPTR)"C", 0, 0L, (APTR)SampleMiCopy,
+       NM_ITEM, (STRPTR)MSG_PASTE_MEN, (STRPTR)"V", 0, 0L, (APTR)SampleMiPaste,
+       NM_TITLE, (STRPTR)MSG_RENDER_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_POINTS_MEN, NULL, CHECKIT, 6L, (APTR)SampleMiPoints,
+       NM_ITEM, (STRPTR)MSG_LINES_MEN, NULL, CHECKIT|CHECKED, 5L, (APTR)SampleMiLines,
+       NM_ITEM, (STRPTR)MSG_FILLED_MEN, NULL, CHECKIT, 3L, (APTR)SampleMiFilled,
+       NM_END, NULL, NULL, 0, 0L, NULL
+};
+
+static UWORD SampleGTypes[] = {
+       SCROLLER_KIND,
+       BUTTON_KIND,
+       BUTTON_KIND,
+       BUTTON_KIND,
+       BUTTON_KIND,
+       BUTTON_KIND,
+       BUTTON_KIND,
+       NUMBER_KIND,
+       BUTTON_KIND,
+       BUTTON_KIND,
+       BUTTON_KIND,
+       BUTTON_KIND,
+       INTEGER_KIND,
+       INTEGER_KIND,
+       INTEGER_KIND,
+       INTEGER_KIND,
+       INTEGER_KIND,
+       INTEGER_KIND,
+       INTEGER_KIND,
+       INTEGER_KIND,
+       INTEGER_KIND,
+       GENERIC_KIND
+};
+
+struct NewGadget SampleNGad[] = {
+       4, 154, 623, 8, NULL, NULL, GD_SampScroll, 0, NULL, (APTR)SampScrollClicked,
+       4, 1, 105, 12, (UBYTE *)"Zoom _In", NULL, GD_SampZoomIn, PLACETEXT_IN, NULL, (APTR)SampZoomInClicked,
+       112, 1, 105, 12, (UBYTE *)"Range _All", NULL, GD_SampRangeAll, PLACETEXT_IN, NULL, (APTR)SampRangeAllClicked,
+       112, 27, 105, 12, (UBYTE *)"Show Range", NULL, GD_SampShowRange, PLACETEXT_IN, NULL, (APTR)SampShowRangeClicked,
+       4, 14, 105, 12, (UBYTE *)"Zoom _Out", NULL, GD_SampZoomOut, PLACETEXT_IN, NULL, (APTR)SampZoomOutClicked,
+       112, 14, 105, 12, (UBYTE *)"_Clear Range", NULL, GD_SampClearRange, PLACETEXT_IN, NULL, (APTR)SampClearRangeClicked,
+       4, 27, 105, 12, (UBYTE *)"Show All", NULL, GD_SampShowAll, PLACETEXT_IN, NULL, (APTR)SampShowAllClicked,
+       112, 40, 105, 12, (UBYTE *)"Sample Size", NULL, GD_SampSize, PLACETEXT_LEFT, NULL, NULL,
+       220, 27, 105, 12, (UBYTE *)"Play Range", NULL, GD_SampPlayR, PLACETEXT_IN, NULL, (APTR)SampPlayRClicked,
+       220, 1, 105, 12, (UBYTE *)"Play Display", NULL, GD_SampPlayD, PLACETEXT_IN, NULL, (APTR)SampPlayDClicked,
+       220, 40, 105, 12, (UBYTE *)"Loop Play", NULL, GD_SampPlayL, PLACETEXT_IN, NULL, (APTR)SampPlayLClicked,
+       220, 14, 105, 12, (UBYTE *)"Play All", NULL, GD_SampPlayA, PLACETEXT_IN, NULL, (APTR)SampPlayAClicked,
+       414, 11, 69, 13, (UBYTE *)"_Display", NULL, GD_DisplayStart, PLACETEXT_LEFT, NULL, (APTR)DisplayStartClicked,
+       486, 11, 69, 13, NULL, NULL, GD_DisplayEnd, 0, NULL, (APTR)DisplayEndClicked,
+       558, 11, 69, 13, NULL, NULL, GD_DisplayLen, 0, NULL, (APTR)DisplayLenClicked,
+       414, 25, 69, 13, (UBYTE *)"_Range", NULL, GD_RangeStart, PLACETEXT_LEFT, NULL, (APTR)RangeStartClicked,
+       486, 25, 69, 13, NULL, NULL, GD_RangeEnd, 0, NULL, (APTR)RangeEndClicked,
+       558, 25, 69, 13, NULL, NULL, GD_RangeLen, 0, NULL, (APTR)RangeLenClicked,
+       414, 39, 69, 13, (UBYTE *)"R_epeat", NULL, GD_RepStart, PLACETEXT_LEFT, NULL, (APTR)RepStartClicked,
+       486, 39, 69, 13, NULL, NULL, GD_RepEnd, 0, NULL, (APTR)RepEndClicked,
+       558, 39, 69, 13, NULL, NULL, GD_RepLen, 0, NULL, (APTR)RepLenClicked,
+       0, 0, 0, 0, NULL, NULL, GD_SampBox, 0, NULL, (APTR)SampBoxClicked
+};
+
+static static ULONG SampleGTags[] = {
+       PGA_Freedom, LORIENT_HORIZ, GA_RelVerify, TRUE, TAG_DONE,
+       TAG_DONE,
+       TAG_DONE,
+       TAG_DONE,
+       TAG_DONE,
+       TAG_DONE,
+       TAG_DONE,
+       GTNM_Border, TRUE, TAG_DONE,
+       TAG_DONE,
+       TAG_DONE,
+       TAG_DONE,
+       TAG_DONE,
+       GTIN_MaxChars, 6, TAG_DONE,
+       GTIN_MaxChars, 6, TAG_DONE,
+       GTIN_MaxChars, 6, TAG_DONE,
+       GTIN_MaxChars, 6, TAG_DONE,
+       GTIN_MaxChars, 6, TAG_DONE,
+       GTIN_MaxChars, 6, TAG_DONE,
+       GTIN_MaxChars, 6, TAG_DONE,
+       GTIN_MaxChars, 6, TAG_DONE,
+       GTIN_MaxChars, 6, TAG_DONE,
+       XMGAD_SetupFunc, (ULONG)SampBoxSetup, TAG_DONE
+};
+
+
+static struct IntuiText SampleIText[] = {
+       1, 0, JAM1,445, 5, NULL, (UBYTE *)"Start", NULL,
+       1, 0, JAM1,516, 5, NULL, (UBYTE *)"End", NULL,
+       1, 0, JAM1,589, 5, NULL, (UBYTE *)"Length", NULL };
+
+#define Sample_TNUM 3
+
+
+
+struct WinUserData SampleWUD =
+{
+       { NULL, NULL },
+       NULL,
+       SampleGadgets,
+       NULL,
+       NULL,
+       NULL,
+       { 0, 0, 0, 0 },
+       { 0, 0, 0, 0 },
+       0,
+
+       CloseSampleWindow,
+       NULL,
+       HandleSampleIDCMP,
+
+       SampleNewMenu,
+       NULL,
+       Sample_CNT,
+       WFLG_DRAGBAR|WFLG_DEPTHGADGET|WFLG_CLOSEGADGET|WFLG_ACTIVATE,
+       SCROLLERIDCMP|ARROWIDCMP|BUTTONIDCMP|INTEGERIDCMP|IDCMP_MENUPICK|IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW|IDCMP_MOUSEMOVE,
+       (STRPTR)MSG_SAMPLE_TITLE,
+       NULL,
+       NULL,
+       NULL
+};
+
+
+
+static void SampleRender (struct WinUserData *wud)
+{
+/*     DrawBevelBox (wud->Win->RPort, SampBox.Left - 2, SampBox.Top - 1,
+                                       SampBox.Width + 4, SampBox.Height + 2,
+                                       GT_VisualInfo, VisualInfo,
+                                       GTBB_Recessed, TRUE,
+                                       TAG_DONE );
+
+       RenderWindowTexts (wud, SampleIText, Sample_TNUM);
+*/
+       UpdateSampGraph();
+       UpdateSampInfo();
+}
+
+
+
+GLOBALCALL LONG OpenSampleWindow (void)
+{
+       struct Window *win;
+       struct SongInfo *si;
+
+       DisplayStart = RangeStartX = RangeEndX = 0;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               DisplayEnd = si->Instr[si->CurrentInst]->Length - 1;
+               ReleaseSemaphore (&si->Lock);
+       }
+       else
+               DisplayEnd = 0;
+
+       if (DisplayEnd < 0) DisplayEnd = 0;
+       LoopPole1 = LoopPole2 = 0;
+
+       win = MyOpenWindow (&SampleWUD);
+
+       UpdateSampleMenu();
+
+       return (!win);
+}
+
+
+
+GLOBALCALL void CloseSampleWindow (void)
+{
+       MyCloseWindow (SampleWUD.Win);
+}
+
+
+
+static LONG SampBoxSetup (struct Gadget *g)
+{
+       /**/
+       SampBox.Left    = OffX; // + ComputeX (&SampleWUD, 4) + 2;
+       SampBox.Top             = OffY; // + ComputeY (&SampleWUD, 53) + 1;
+       SampBox.Width   = 500;//ComputeX (&SampleWUD, 622) - 4;
+       SampBox.Height  = 300;//ComputeY (&SampleWUD, 100) - 2;
+
+       g->LeftEdge = SampBox.Left;
+       g->TopEdge = SampBox.Top;
+       g->Width = SampBox.Width;
+       g->Height = SampBox.Height;
+
+       g->Flags                |= GFLG_GADGHNONE;
+       g->Activation   |= GACT_IMMEDIATE | GACT_FOLLOWMOUSE | GACT_RELVERIFY;
+       g->GadgetType   |= GTYP_BOOLGADGET;     /* Preserve GadTools special flags */
+
+       return RETURN_OK;
+}
+
+
+
+static void HandleSampleIDCMP (void)
+{
+       WORD mousex; //, mousey;
+
+       if (IntuiMsg.Class != IDCMP_MOUSEMOVE)
+               return;
+
+       mousex = IntuiMsg.MouseX - SampBox.Left;
+//     mousey = IntuiMsg.MouseY - SampBox.Top;
+
+       /* Clip mouse position */
+       if (mousex < 0) mousex = 0;
+//     if (mousey < 0) mousey = 0;
+       if (mousex >= SampBox.Width) mousex = SampBox.Width - 1;
+//     if (mousey >= SampBox.Height) mousey = SampBox.Height - 1;
+
+       if (mousex == RangeEndX) /* Do not redraw until mouse position has changed */
+               return;
+
+       UpdateRange (mousex);
+}
+
+
+/********************/
+/* Sample Functions */
+/********************/
+
+static void DrawPixelGraph (struct RastPort *rp, BYTE *samp,
+       UWORD xmin, UWORD xmax, UWORD ycoord, UWORD height, UWORD step)
+{
+       ULONG i;
+       UWORD x;
+
+       for (i = 0, x = xmin ; x < xmax ; i += step, x++)
+               WritePixel (rp, x, (samp[i]*height) >> 8 + ycoord);
+}
+
+
+
+static void DrawLineGraph (struct RastPort *rp, BYTE *samp,
+       UWORD xmin, UWORD xmax, UWORD ycoord, UWORD height, UWORD step)
+{
+       ULONG i;
+       UWORD x;
+
+       Move (rp, xmin, ycoord);
+
+       for (i = 0, x = xmin ; x < xmax ; i += step, x++)
+               Draw (rp, x, (samp[i]*height)/256 + ycoord);
+}
+
+
+
+static void DrawFilledGraph (struct RastPort *rp, BYTE *samp,
+       UWORD xmin, UWORD xmax, UWORD ycoord, UWORD height, UWORD step)
+{
+       UWORD x;
+       ULONG i;
+
+       for (i = 0, x = xmin ; x < xmax ; i += step, x++)
+       {
+               Move (rp, x, ycoord);
+               Draw (rp, x, (samp[i]*height)/256 + ycoord);
+       }
+}
+
+
+
+GLOBALCALL void UpdateSample (void)
+
+/* You call this function when the selected instrument has changed. */
+{
+       struct SongInfo *si;
+
+       if (SampleWUD.Win)
+       {
+               if (si = xmLockActiveSong (SM_SHARED))
+               {
+                       DisplayStart = RangeStartX = RangeEndX = DisplayEnd = 0;
+
+                       if (si->CurrentInst)
+                               DisplayEnd = si->Instr[si->CurrentInst]->Length - 1;
+
+                       if (DisplayEnd < 0) DisplayEnd = 0;
+
+                       UpdateSampGraph();
+                       UpdateSampInfo();
+
+                       ReleaseSemaphore (&si->Lock);
+               }
+       }
+}
+
+
+
+GLOBALCALL void UpdateSampleMenu (void)
+{
+       if (SampleWUD.Win)
+       {
+               struct Menu *menu = SampleWUD.Win->MenuStrip;
+               struct MenuItem *item = menu->NextMenu->FirstItem;
+
+               ClearMenuStrip (SampleWUD.Win);
+
+
+               /* Clear checkmarks */
+
+               item->Flags &= ~CHECKED;
+               item->NextItem->Flags &= ~CHECKED;
+               item->NextItem->NextItem->Flags &= ~CHECKED;
+
+
+               /* Set checkmark */
+
+               switch (GuiSwitches.SampDrawMode)
+               {
+                       case SAMP_PIXEL:
+                               item->Flags |= CHECKED;
+                               break;
+
+                       case SAMP_LINE:
+                               item->NextItem->Flags |= CHECKED;
+                               break;
+
+                       case SAMP_FILLED:
+                               item->NextItem->NextItem->Flags |= CHECKED;
+                               break;
+               }
+
+               ResetMenuStrip (SampleWUD.Win, menu);
+       }
+}
+
+
+
+GLOBALCALL void UpdateSampInfo (void)
+{
+       struct SongInfo *si;
+
+       if (!SampleWUD.Win)     return;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               struct Instrument *instr = si->Instr[si->CurrentInst];
+               LONG repend;
+
+               repend = instr->Repeat + instr->Replen - 1;
+               if (repend < 0) repend = 0;
+
+               SetGadgets (&SampleWUD,
+                       GD_RepStart,            instr->Repeat,
+                       GD_RepEnd,                      repend,
+                       GD_RepLen,                      instr->Replen,
+                       GD_SampSize,            instr->Length,
+                       GD_DisplayStart,        DisplayStart,
+                       GD_DisplayEnd,          DisplayEnd,
+                       GD_DisplayLen,          DisplayEnd ? (DisplayEnd - DisplayStart + 1) : 0,
+                       -1);
+
+               /* Loop markers */
+               {
+                       struct RastPort *rp = SampleWUD.Win->RPort;
+
+                       SetDrMd (rp, COMPLEMENT);
+                       SetDrPt (rp, 0xFF00);
+
+                       /* Delete previous loop */
+                       if (LoopPole1)
+                       {
+                               Move (rp, LoopPole1, SampBox.Top);
+                               Draw (rp, LoopPole1, SampBox.Top + SampBox.Height);
+                               LoopPole1 = 0;
+                       }
+                       if (LoopPole2)
+                       {
+                               Move (rp, LoopPole2, SampBox.Top);
+                               Draw (rp, LoopPole2, SampBox.Top + SampBox.Height);
+                               LoopPole2 = 0;
+                       }
+
+                       if (instr->Replen) /* Draw new loop */
+                       {
+                               if (DisplayStart <= instr->Repeat && instr->Repeat <= DisplayEnd)
+                               {
+                                       LoopPole1 = SampBox.Left + ((SampBox.Width * (instr->Repeat - DisplayStart)) / (DisplayEnd - DisplayStart + 1));
+
+                                       Move (rp, LoopPole1, SampBox.Top);
+                                       Draw (rp, LoopPole1, SampBox.Top + SampBox.Height - 1);
+                               }
+
+                               if (DisplayStart <= repend && repend <= DisplayEnd+1)
+                               {
+                                       LoopPole2 = SampBox.Left + ((SampBox.Width * (repend - DisplayStart)) / (DisplayEnd - DisplayStart + 1));
+
+                                       Move (rp, LoopPole2, SampBox.Top);
+                                       Draw (rp, LoopPole2, SampBox.Top + SampBox.Height - 1);
+                               }
+                       }
+
+                       SetDrPt (rp, 0xFFFF);
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void UpdateRangeInfo (void)
+{
+       WORD    rs = DisplayStart + RangeStartX,
+                       re = DisplayStart + RangeEndX;
+
+       if (!SampleWUD.Win) return;
+
+       SetGadgets (&SampleWUD,
+               GD_RangeStart,          rs,
+               GD_RangeEnd,            re,
+               GD_RangeLen,            abs(re - rs),
+               -1);
+}
+
+
+
+GLOBALCALL void UpdateSampGraph (void)
+{
+       struct SongInfo *si;
+       struct RastPort *rp;
+       BYTE *samp;
+       ULONG maxpen;
+       UWORD step, xmin, xmax, height, ycoord;
+
+       if (!SampleWUD.Win) return;
+
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               rp = SampleWUD.Win->RPort;
+               samp = si->Instr[si->CurrentInst]->Sample;
+
+               xmin    = SampBox.Left;
+               xmax    = xmin + SampBox.Width;
+               height  = SampBox.Height;
+               ycoord  = SampBox.Top + height/2;
+               step    = si->Instr[si->CurrentInst]->Length / SampBox.Width;
+
+               /* This helps with samples smaller than the graphic x size */
+               if (step == 0) step = 1;
+
+               /* Clear instrument rectangle */
+
+               SetDrMd (rp, JAM1);
+               SetAPen (rp, DrawInfo->dri_Pens[BACKGROUNDPEN]);
+               RectFill (rp, SampBox.Left, SampBox.Top,
+                       SampBox.Left + SampBox.Width - 1, SampBox.Top + SampBox.Height - 1);
+               LoopPole1 = LoopPole2 = 0;
+
+
+               if (GfxBase->lib_Version >= 39) /* Optimized drawing with V39 */
+               {
+                       GetRPAttrs (rp,
+                               RPTAG_MaxPen, &maxpen,
+                               TAG_DONE);
+                       SetMaxPen (rp, max(DrawInfo->dri_Pens[TEXTPEN], DrawInfo->dri_Pens[FILLPEN]));
+               }
+
+               /* Draw mid line */
+               SetAPen (rp, DrawInfo->dri_Pens[TEXTPEN]);
+               Move (rp, xmin, ycoord);
+               Draw (rp, xmax-1, ycoord);
+
+               /* Draw sample graphic */
+
+               if (samp)
+               {
+                       switch (GuiSwitches.SampDrawMode)
+                       {
+                               case SAMP_PIXEL:
+                                       DrawPixelGraph (rp, samp, xmin, xmax, ycoord, height, step);
+                                       break;
+
+                               case SAMP_LINE:
+                                       DrawLineGraph (rp, samp, xmin, xmax, ycoord, height, step);
+                                       break;
+
+                               case SAMP_FILLED:
+                                       DrawFilledGraph (rp, samp, xmin, xmax, ycoord, height, step);
+                                       break;
+                       }
+               }
+
+               DrawRange();    /* Redraw range */
+
+               /* Restore MaxPen if appropriate */
+               if (GfxBase->lib_Version >= 39) SetMaxPen (rp, maxpen);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void DrawRange (void)
+{
+       ULONG maxpen;
+       WORD xmin, xmax;
+       struct RastPort *rp = SampleWUD.Win->RPort;
+
+       if (RangeStartX > RangeEndX)
+       {
+               xmin = RangeEndX;
+               xmax = RangeStartX;
+       }
+       else
+       {
+               xmin = RangeStartX;
+               xmax = RangeEndX;
+       }
+
+       /* Optimized drawing for V39 */
+
+       if (GfxBase->lib_Version >= 39)
+       {
+               GetRPAttrs (rp,
+                       RPTAG_MaxPen, &maxpen,
+                       TAG_DONE);
+               SetMaxPen (rp, max(DrawInfo->dri_Pens[TEXTPEN], DrawInfo->dri_Pens[FILLPEN]));
+       }
+
+       SetDrMd (rp, COMPLEMENT);
+       RectFill (rp, SampBox.Left + xmin, SampBox.Top,
+               SampBox.Left + xmax, SampBox.Top + SampBox.Height - 1);
+
+       /* Restore MaxPen if appropriate */
+       if (GfxBase->lib_Version >= 39) SetMaxPen (rp, maxpen);
+}
+
+
+static void UpdateRange (WORD newend)
+
+/* Optimized range offset drawing */
+{
+       WORD pole = RangeStartX;        /* The fixed end of the range */
+
+       RangeStartX = RangeEndX;
+       RangeEndX = newend;
+
+       if (RangeEndX < pole  && RangeStartX <= pole)           /* Range _left_ of pole */
+       {
+               if (RangeStartX > RangeEndX) RangeStartX--;             /* Grow range   */
+               else if (RangeStartX < RangeEndX) RangeEndX--;  /* Reduce range */
+               DrawRange();                                                                    /* Draw/clear offset area */
+
+       }
+       else if (RangeEndX > pole && RangeStartX >= pole)       /* Range _right_ of pole */
+       {
+               if (RangeStartX < RangeEndX) RangeStartX++;             /* Grow range   */
+               else if (RangeStartX > RangeEndX) RangeEndX++;  /* Reduce range */
+               DrawRange();                                                                    /* Draw/clear offset area */
+       }
+       else    /* Mouse has crossed the pole: it must be redrawn */
+       {
+               DrawRange();
+               RangeStartX = RangeEndX = pole;
+               DrawRange();
+       }
+
+       RangeStartX = pole;
+       RangeEndX = newend;
+       UpdateRangeInfo();
+}
+
+
+
+/******************/
+/* Sample Gadgets */
+/******************/
+
+static void SampScrollClicked (void)
+{
+}
+
+
+
+static void SampZoomInClicked (void)
+{
+}
+
+
+
+static void SampRangeAllClicked (void)
+{
+       DrawRange();    /* Delete previous range */
+       RangeStartX = 0;
+       RangeEndX = SampBox.Width;
+       DrawRange();
+       UpdateRangeInfo();
+}
+
+
+
+static void SampShowRangeClicked (void)
+{
+}
+
+
+
+static void SampZoomOutClicked (void)
+{
+}
+
+
+
+static void SampClearRangeClicked (void)
+{
+       if (RangeStartX == RangeEndX) return;
+
+       DrawRange();
+       RangeStartX = RangeEndX = 0;
+       DrawRange();
+       UpdateRangeInfo();
+}
+
+
+
+static void SampShowAllClicked (void)
+{
+       struct SongInfo *si;
+
+       DisplayStart = 0;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               DisplayEnd = si->Instr[si->CurrentInst]->Length - 1;
+               ReleaseSemaphore (&si->Lock);
+       }
+       else
+               DisplayEnd = 0;
+       UpdateSampGraph();
+       UpdateSampInfo();
+}
+
+
+
+static void SampPlayRClicked (void)
+{
+}
+
+static void SampPlayDClicked (void)
+{
+}
+
+static void SampPlayLClicked (void)
+{
+}
+
+static void SampPlayAClicked (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               struct Instrument *instr = si->Instr[si->CurrentInst];
+
+               PlaySample (instr->Sample, instr->Length, instr->Volume, 0x1AC);        /**/
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+static void DisplayStartClicked (void)
+{
+}
+
+static void DisplayEndClicked (void)
+{
+}
+
+static void DisplayLenClicked (void)
+{
+}
+
+static void RangeStartClicked (void)
+{
+}
+
+static void RangeEndClicked (void)
+{
+}
+
+static void RangeLenClicked (void)
+{
+}
+
+
+
+static void RepStartClicked (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               struct Instrument *instr = si->Instr[si->CurrentInst];
+
+               instr->Repeat = GetNumber (SampleGadgets[GD_RepStart]);
+
+               if (instr->Repeat & 1) instr->Repeat--;
+
+               if (instr->Repeat >= instr->Length)
+                       instr->Repeat = instr->Length - 2;
+
+               if (((LONG)instr->Repeat) < 0)
+                       instr->Repeat = 0;
+
+               if (instr->Repeat + instr->Replen > instr->Length - 2)
+                       instr->Replen = instr->Length - instr->Repeat;
+
+               if (((LONG)instr->Replen) < 0)
+                       instr->Replen = 0;
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UpdateSampInfo();
+}
+
+
+
+static void RepLenClicked (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               struct Instrument *instr = si->Instr[si->CurrentInst];
+
+               instr->Replen = GetNumber (SampleGadgets[GD_RepLen]);
+
+               if (instr->Replen & 1) instr->Replen++;
+
+               if (instr->Replen + instr->Repeat >= instr->Length)
+                       instr->Replen = instr->Length - instr->Repeat;
+
+               if (((LONG)instr->Replen) < 0)
+                       instr->Replen = 0;
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UpdateSampInfo();
+}
+
+
+
+static void RepEndClicked (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               struct Instrument *instr = si->Instr[si->CurrentInst];
+
+               instr->Replen = GetNumber (SampleGadgets[GD_RepEnd]) - instr->Repeat;
+
+               if (instr->Replen & 1) instr->Replen++;
+
+               if (instr->Replen + instr->Repeat >= instr->Length)
+                       instr->Replen = instr->Length - instr->Repeat;
+
+               if (((LONG)instr->Replen) < 0) instr->Replen = 0;
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UpdateSampInfo();
+}
+
+
+
+static void SampBoxClicked (void)
+{
+       if (IntuiMsg.Class == IDCMP_GADGETDOWN)
+       {
+               DrawRange(); /* Clear old range */
+               RangePole = RangeStartX = RangeEndX = IntuiMsg.MouseX - SampBox.Left;
+               DrawRange(); /* Draw pole */
+               UpdateRangeInfo();
+
+               //SetPointer (SampleWUD.Win, BlockPointer, 16, 16, -8, -7);
+       }
+       else if (IntuiMsg.Class == IDCMP_GADGETUP)
+               ;       //ClearPointer (SampleWUD.Win);
+}
+
+
+/****************/
+/* Sample Menus */
+/****************/
+
+static void SampleMiCut (void)
+{
+       /* routine when (sub)item "Cut" is selected. */
+}
+
+static void SampleMiCopy (void)
+{
+       /* routine when (sub)item "Copy" is selected. */
+}
+
+static void SampleMiPaste (void)
+{
+       /* routine when (sub)item "Paste" is selected. */
+}
+
+static void SampleMiPoints (void)
+{
+       GuiSwitches.SampDrawMode = SAMP_PIXEL;
+       UpdateSampGraph();
+}
+
+static void SampleMiLines (void)
+{
+       GuiSwitches.SampDrawMode = SAMP_LINE;
+       UpdateSampGraph();
+}
+
+static void SampleMiFilled (void)
+{
+       GuiSwitches.SampDrawMode = SAMP_FILLED;
+       UpdateSampGraph();
+}
diff --git a/SaversWin.c b/SaversWin.c
new file mode 100644 (file)
index 0000000..1d51d79
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+**     SaversWin.c
+**
+**     Copyright (C) 1994,95,96 Bernardo Innocenti
+**
+**     Savers panel handling functions.
+*/
+
+
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/*!*/
+/* Replaced by format properties stored inside hooks
+UBYTE FormatProperties[] =
+{
+       XMSF_NO_INSTRUMENTS|XMSF_NO_NAMES,                      // MED
+       XMSF_NO_INSTRUMENTS|XMSF_NO_NAMES,                      // OctaMED
+       ALLOWS_NO_INST|ALLOWS_NO_NAMES                          // MIDI File
+};
+
+       "MED",
+       "OctaMED",
+       "ScreamTracker",
+       "MIDI File",
+*/
+
+
+
+
+/* Gadgets IDs */
+enum {
+       GD_SaversGroup0,
+               GD_SaversGroup1,
+                       GD_SaversList,
+//                     GD_SaversGroup2,
+                               GD_SaversGroup3,
+                                       GD_SaveSequence,
+                                       GD_SaveInstruments,
+                                       GD_SavePatterns,
+                                       GD_SaveNames,
+                                       GD_SaveIcons,
+//                             GD_SaversGroup4,
+//                                     GD_PackMode,
+//                                     GD_PackOptions,
+               GD_SaversGroup5,
+                       GD_FormatDescription,
+                       GD_FormatAuthor,
+                       GD_SaversGroup6,
+                               GD_SaversGroup7,
+                                       GD_MaxLength,
+                                       GD_MaxTracks,
+                                       GD_MaxInstruments,
+                               GD_SaversGroup8,
+                                       GD_MaxPatterns,
+                                       GD_MaxPattLen,
+                                       GD_MaxSampleLen,
+               GD_SaversGroup9,
+                       GD_FormatUse,
+                       GD_FormatCancel,
+       Savers_CNT
+};
+
+
+
+
+/*****************************/
+/* Local function prototypes */
+/*****************************/
+
+static void SaversPostOpen     (void);
+static void UpdateSaverInfo            (void);
+
+static void SaversListClicked  (struct WinUserData *wud);
+/* static void PackModeClicked         (struct WinUserData *wud);
+static void PackOptionsClicked (struct WinUserData *wud); */
+static void FormatUseClicked   (struct WinUserData *wud);
+static void FormatCancelClicked        (struct WinUserData *wud);
+
+
+
+XDEF struct SaveSwitches SaveSwitches = {1, 1, 1, 0, 1};
+XDEF struct SaveSwitches SaveSwitchesBackup;
+
+
+
+/*
+static STRPTR PackModeLabels[] =
+{
+       (STRPTR)MSG_NONE_GAD,
+       (STRPTR)MSG_XPK_GAD,
+       (STRPTR)MSG_LHA_GAD,
+       (STRPTR)0
+};
+*/
+
+
+static ULONG SaversArgs[] =
+{
+       HGROUP_KIND, 0,
+               LISTVIEW_KIND,  (ULONG)SaversListClicked, 0, NULL, TAG_DONE,
+//             VGROUP_KIND, 0,
+                       VGROUP_KIND, BBFT_RIDGE,
+                               CHECKBOX_KIND,  NULL, MSG_SF_SEQUENCE_GAD,              (ULONG)&SaveSwitches.SaveSeq,   TAG_DONE,
+                               CHECKBOX_KIND,  NULL, MSG_SF_INSTRUMENTS_GAD,   (ULONG)&SaveSwitches.SaveInstr, TAG_DONE,
+                               CHECKBOX_KIND,  NULL, MSG_SF_PATTERNS_GAD,              (ULONG)&SaveSwitches.SavePatt,  TAG_DONE,
+                               CHECKBOX_KIND,  NULL, MSG_SF_NAMES_GAD,                 (ULONG)&SaveSwitches.SaveNames, TAG_DONE,
+                               CHECKBOX_KIND,  NULL, MSG_ADD_ICON_GAD,                 (ULONG)&SaveSwitches.SaveIcons, TAG_DONE,
+                               ENDGROUP_KIND,
+//                     VGROUP_KIND, BBFT_RIDGE,
+//                             CYCLE_KIND,             (ULONG)PackModeClicked,         MSG_MODE_GAD,                   (ULONG)PackModeLabels,  TAG_DONE,
+//                             BUTTON_KIND,    (ULONG)PackOptionsClicked,      MSG_OPTIONS_DOTS_GAD,                                                   TAG_DONE,
+//                             ENDGROUP_KIND,
+//                     ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       VGROUP_KIND, BBFT_RIDGE,
+               TEXT_KIND,              MSG_DESCRIPTION,        25,             GTTX_Border, TRUE, TAG_DONE,
+               TEXT_KIND,              MSG_AUTHOR,                     15,             GTTX_Border, TRUE, TAG_DONE,
+               HGROUP_KIND,    0,
+                       VGROUP_KIND,    0,
+                               NUMBER_KIND,    MSG_MAXLENGTH,          3,      TAG_DONE,
+                               NUMBER_KIND,    MSG_MAXTRACKS,          3,      TAG_DONE,
+                               NUMBER_KIND,    MSG_MAXINSTRUMENTS,     3,      TAG_DONE,
+                               ENDGROUP_KIND,
+                       VGROUP_KIND,    0,
+                               NUMBER_KIND,    MSG_MAXPATTERNS,        5,      TAG_DONE,
+                               NUMBER_KIND,    MSG_MAXPATTLEN,         5,      TAG_DONE,
+                               NUMBER_KIND,    MSG_MAXSAMPLELEN,       9,      TAG_DONE,
+                               ENDGROUP_KIND,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       HGROUP_KIND, 0,
+               BUTTON_KIND,    (ULONG)FormatUseClicked,        MSG_UNDERSCORE_USE_GAD,         TAG_DONE,
+               BUTTON_KIND,    (ULONG)FormatCancelClicked,     MSG_UNDERSCORE_CANCEL_GAD,      TAG_DONE,
+               ENDGROUP_KIND,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG SaversWinTags[] =
+{
+       XMWIN_LayoutArgs,       (LONG)SaversArgs,
+       XMWIN_GCount,           Savers_CNT,
+       XMWIN_Title,            MSG_SAVERS_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       CHECKBOXIDCMP|LISTVIEWIDCMP|BUTTONIDCMP|CYCLEIDCMP|IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW,
+       XMWIN_PostOpenFunc,     (LONG)SaversPostOpen,
+       XMWIN_HelpNode,         (LONG)"Savers",
+       TAG_DONE
+};
+
+
+
+static void SaversPostOpen (void)
+{
+       UpdateSaveSwitches();
+
+       /* Backup SaveSwitches for "Cancel" option */
+       memcpy (&SaveSwitchesBackup, &SaveSwitches, sizeof (SaveSwitchesBackup));
+}
+
+
+
+GLOBALCALL void UpdateSaveSwitches (void)
+{
+       struct WinUserData *wud = WDescr[WID_SAVERS].Wud;
+       struct XMHook *saver;
+
+       if (wud && wud->Win)
+       {
+               LONG savernum;
+
+#ifdef OS30_ONLY
+               ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#else
+               /* Workaround for Pre-V39 ObtainSemaphoreShared() bug (see autodoc) */
+
+               /* Try to get the shared semaphore */
+               if (!AttemptSemaphoreShared (&XModuleBase->xm_BaseLock))
+                       /* Check if we can get the exclusive version */
+                       if (!AttemptSemaphore (&XModuleBase->xm_BaseLock))
+                               /* Oh well, wait for the shared lock */
+                               ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#endif
+
+               if (saver = XModuleBase->xm_DefaultSaver)
+               {
+                       struct XMHook *tmp = (struct XMHook *)XModuleBase->xm_Savers.mlh_Head;
+                       savernum = 0;
+
+                       while (tmp != saver)
+                       {
+                               tmp = (struct XMHook *)tmp->xmh_Link.ln_Succ;
+                               savernum++;
+                       }
+               }
+               else savernum = ~0;
+
+               GT_SetGadgetAttrs (wud->Gadgets[GD_SaversList], wud->Win, NULL,
+                       GTLV_Labels,            &XModuleBase->xm_Savers,
+                       GTLV_Selected,          savernum,
+                       GTLV_MakeVisible,       savernum,
+                       TAG_DONE);
+
+               UpdateSaverInfo ();
+
+               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+       }
+}
+
+
+
+static void UpdateSaverInfo (void)
+{
+       struct XMHook *saver = XModuleBase->xm_DefaultSaver;
+       struct WinUserData *wud = WDescr[WID_SAVERS].Wud;
+
+       if (wud && wud->Win)
+       {
+               SetGadgets (wud,
+                       GD_SaveSequence,                SaveSwitches.SaveSeq,
+                       GD_SaveInstruments,             SaveSwitches.SaveInstr,
+                       GD_SavePatterns,                SaveSwitches.SavePatt,
+                       GD_SaveIcons,                   SaveSwitches.SaveIcons,
+                       GD_SaveNames,                   SaveSwitches.SaveNames,
+                       GD_FormatDescription,   saver ? saver->xmh_Descr : NULL,
+                       GD_FormatAuthor,                saver ? saver->xmh_Author : NULL,
+                       GD_MaxLength,                   saver ? saver->xmh_MaxLength : 0,
+                       GD_MaxTracks,                   saver ? saver->xmh_MaxTracks : 0,
+                       GD_MaxInstruments,              saver ? saver->xmh_MaxInstruments : 0,
+                       GD_MaxPatterns,                 saver ? saver->xmh_MaxPatterns : 0,
+                       GD_MaxPattLen,                  saver ? saver->xmh_MaxPattLen : 0,
+                       GD_MaxSampleLen,                saver ? saver->xmh_MaxSampleLen : 0,
+                       -1);
+
+               if (saver)
+               {
+                       GT_SetGadgetAttrs (wud->Gadgets[GD_SaveSequence], wud->Win, NULL,
+                               GA_Disabled, !(saver->xmh_Flags & XMHF_EXCLUDE_SEQUENCE),
+                               TAG_DONE);
+
+                       GT_SetGadgetAttrs (wud->Gadgets[GD_SavePatterns], wud->Win, NULL,
+                               GA_Disabled, !(saver->xmh_Flags & XMHF_EXCLUDE_PATTERNS),
+                               TAG_DONE);
+
+                       GT_SetGadgetAttrs (wud->Gadgets[GD_SaveInstruments], wud->Win, NULL,
+                               GA_Disabled, !(saver->xmh_Flags & XMHF_EXCLUDE_INSTRUMENTS),
+                               TAG_DONE);
+
+                       GT_SetGadgetAttrs (wud->Gadgets[GD_SaveNames], wud->Win, NULL,
+                               GA_Disabled, !(saver->xmh_Flags & XMHF_EXCLUDE_NAMES),
+                               TAG_DONE);
+               }
+       }
+}
+
+
+
+/******************/
+/* Savers Gadgets */
+/******************/
+
+static void SaversListClicked (struct WinUserData *wud)
+{
+       WORD i;
+       struct XMHook *tmp;
+
+       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+
+       tmp = (struct XMHook *)XModuleBase->xm_Savers.mlh_Head;
+
+       for (i = IntuiMsg.Code; (i > 0) && tmp ; i--)
+               tmp = (struct XMHook *)tmp->xmh_Link.ln_Succ;
+
+       if (tmp) XModuleBase->xm_DefaultSaver = tmp;
+
+       UpdateSaverInfo();
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+}
+
+
+
+/*
+static void PackModeClicked (struct WinUserData *wud)
+{
+
+}
+
+
+
+static void PackOptionsClicked (struct WinUserData *wud)
+{
+
+}
+*/
+
+
+
+static void FormatUseClicked (struct WinUserData *wud)
+{
+       MyCloseWindow (wud);
+}
+
+
+
+static void FormatCancelClicked (struct WinUserData *wud)
+{
+       /* Restore old SaveSwitches */
+       memcpy (&SaveSwitches, &SaveSwitchesBackup, sizeof (SaveSwitchesBackup));
+
+       MyCloseWindow (wud);
+}
diff --git a/SequenceWin.c b/SequenceWin.c
new file mode 100644 (file)
index 0000000..c138def
--- /dev/null
@@ -0,0 +1,504 @@
+/*
+**     SequenceWin.c
+**
+**     Copyright (C) 1993,94,95,96 Bernardo Innocenti
+**
+**     Sequence editor handling functions.
+*/
+
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/xmodule.h>
+
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Gadget IDs */
+
+enum
+{
+       GD_SequenceGroup0,
+               GD_SequenceGroup1,
+                       GD_SequenceGroup2,
+                               GD_PattList,
+                               GD_PattName,
+                       GD_SequenceGroup3,
+                               GD_PattAdd,
+                               GD_PattDel,
+                               GD_PattUp,
+                               GD_PattDown,
+               GD_SequenceGroup4,
+                       GD_SeqList,
+               GD_SequenceGroup5,
+                       GD_SeqInsert,
+                       GD_SeqDel,
+                       GD_SeqUp,
+                       GD_SeqDown,
+
+       Sequence_CNT
+};
+
+
+
+/* Local function prototypes */
+
+static void SequencePostClose  (void);
+
+static void SeqListClicked             (struct WinUserData *wud);
+static void PattAddClicked             (struct WinUserData *wud);
+static void PattDelClicked             (struct WinUserData *wud);
+static void PattUpClicked              (struct WinUserData *wud);
+static void PattDownClicked            (struct WinUserData *wud);
+static void PattNameClicked            (struct WinUserData *wud);
+static void PattListClicked            (struct WinUserData *wud);
+static void SeqDelClicked              (struct WinUserData *wud);
+static void SeqUpClicked               (struct WinUserData *wud);
+static void SeqDownClicked             (struct WinUserData *wud);
+static void SeqInsertClicked   (struct WinUserData *wud);
+
+
+struct Gadget  *SequenceGadgets[Sequence_CNT];
+struct List             SequenceList;
+struct List             PatternsList;
+static ULONG    SequenceSecs = 0, SequenceMicros = 0;
+static ULONG    PatternSecs = 0, PatternMicros = 0;
+
+
+
+static ULONG SequenceArgs[] =
+{
+       HGROUP_KIND, BBFT_RIDGE,
+               VGROUP_KIND, 0,
+                       LISTVIEW_KIND,  (ULONG)PattListClicked, MSG_PATTERNS_GAD,       (ULONG)&PatternsList,   TAG_DONE,
+                       STRING_KIND,    (ULONG)PattNameClicked, MSG_UNDERSCORE_NAME_GAD,        128, TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       BUTTON_KIND,    (ULONG)PattAddClicked,  MSG_UNDERSCORE_ADD_GAD, TAG_DONE,
+                       BUTTON_KIND,    (ULONG)PattDelClicked,  MSG_DEL_GAD,                    TAG_DONE,
+                       BUTTON_KIND,    (ULONG)PattUpClicked,   MSG_UP_GAD,                             TAG_DONE,
+                       BUTTON_KIND,    (ULONG)PattDownClicked, MSG_DOWN_GAD,                   TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       HGROUP_KIND, BBFT_RIDGE,
+               LISTVIEW_KIND,  (ULONG)SeqListClicked,  MSG_SEQUENCE_GAD,       (ULONG)&SequenceList,   TAG_DONE,
+               VGROUP_KIND, 0,
+                       BUTTON_KIND,    (ULONG)SeqInsertClicked,        MSG_UNDERSCORE_INS_GAD,         TAG_DONE,
+                       BUTTON_KIND,    (ULONG)SeqDelClicked,           MSG_DEL_GAD,                            TAG_DONE,
+                       BUTTON_KIND,    (ULONG)SeqUpClicked,            MSG_UNDERSCORE_UP_GAD,          TAG_DONE,
+                       BUTTON_KIND,    (ULONG)SeqDownClicked,          MSG_UNDERSCORE_DOWN_GAD,        TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG SequenceWinTags[] =
+{
+       XMWIN_LayoutArgs,       (LONG)SequenceArgs,
+       XMWIN_GCount,           Sequence_CNT,
+       XMWIN_Title,            MSG_SEQUENCE_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       BUTTONIDCMP|LISTVIEWIDCMP|IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW,
+       XMWIN_PostOpenFunc,     (LONG)UpdatePatternList,
+       XMWIN_PostCloseFunc,(LONG)SequencePostClose,
+       XMWIN_HelpNode,         (LONG)"Sequence",
+       TAG_DONE
+};
+
+
+
+static void SequencePostClose (void)
+{
+       while (!IsListEmpty (&SequenceList))
+               RemListViewNode (SequenceList.lh_Head);
+
+       while (!IsListEmpty (&PatternsList))
+               RemListViewNode (PatternsList.lh_Head);
+}
+
+
+
+/**********************/
+/* Sequence Functions */
+/**********************/
+
+GLOBALCALL void UpdateSequenceList (void)
+{
+       struct WinUserData *wud = WDescr[WID_SEQUENCE].Wud;
+       struct SongInfo *si;
+       ULONG i;
+       UWORD pos;
+
+       if (wud && wud->Win)
+       {
+               GT_SetGadgetAttrs (wud->Gadgets[GD_SeqList], wud->Win, NULL,
+                       GTLV_Labels, ~0,
+                       TAG_DONE);
+
+               while (!IsListEmpty (&SequenceList))
+                       RemListViewNode (SequenceList.lh_Head);
+
+               if (si = xmLockActiveSong (SM_SHARED))
+               {
+                       for (i = 0 ; i < si->Length; i++)
+                       {
+                               pos = si->Sequence[i];
+                               AddListViewNode (&SequenceList, "%03lu - %03ld %s", i, pos,
+                                       (pos >= si->NumPatterns) ? (STRPTR)"--none--" : si->Patt[pos]->Name);
+                       }
+
+                       GT_SetGadgetAttrs (wud->Gadgets[GD_SeqList], wud->Win, NULL,
+                               GTLV_Labels, &SequenceList,
+                               GTLV_Selected, si->CurrentPos,
+                               GTLV_MakeVisible, si->CurrentPos,
+                               TAG_DONE);
+
+                       ReleaseSemaphore (&si->Lock);
+               }
+       }
+}
+
+
+
+GLOBALCALL void UpdatePatternList (void)
+{
+       struct WinUserData *wud = WDescr[WID_SEQUENCE].Wud;
+       struct SongInfo *si;
+       struct Pattern *patt;
+       ULONG i;
+
+       if (wud && wud->Win)
+       {
+               GT_SetGadgetAttrs (wud->Gadgets[GD_PattList], wud->Win, NULL,
+                       GTLV_Labels, ~0,
+                       TAG_DONE);
+
+               while (!IsListEmpty (&PatternsList))
+                       RemListViewNode (PatternsList.lh_Head);
+
+
+               if (si = xmLockActiveSong (SM_SHARED))
+               {
+                       for (i = 0 ; i < si->NumPatterns; i++)
+                       {
+                               if (patt = si->Patt[i])
+                               {
+                                       AddListViewNode (&PatternsList, "%03lu (%lu,%lu) %s",
+                                               i, patt->Tracks, patt->Lines,
+                                               patt->Name ? patt->Name : (STRPTR) STR(MSG_UNNAMED));
+                               }
+                               else AddListViewNode (&PatternsList, STR(MSG_EMPTY));
+                       }
+
+                       GT_SetGadgetAttrs (wud->Gadgets[GD_PattName], wud->Win, NULL,
+                               GTST_String, si->NumPatterns ? si->Patt[si->CurrentPatt]->Name : NULL,
+                               TAG_DONE);
+
+                       GT_SetGadgetAttrs (wud->Gadgets[GD_PattList], wud->Win, NULL,
+                               GTLV_Labels,            &PatternsList,
+                               GTLV_Selected,          si->CurrentPatt,
+                               GTLV_MakeVisible,       si->CurrentPatt,
+                               TAG_DONE);
+
+                       ReleaseSemaphore (&si->Lock);
+               }
+
+               UpdateSequenceList();
+       }
+
+       UpdatePattern();
+}
+
+
+
+/********************/
+/* Sequence Gadgets */
+/********************/
+
+static void SeqListClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               if (IntuiMsg.Code == si->CurrentPos)
+               {
+                       /* Check Double Click */
+                       if (DoubleClick (SequenceSecs, SequenceMicros, IntuiMsg.Seconds, IntuiMsg.Micros))
+                       {
+                               si->Sequence[si->CurrentPos] = si->CurrentPatt;
+                               UpdateSequenceList();
+                       }
+               }
+
+               si->CurrentPos = IntuiMsg.Code;
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       SequenceSecs    = IntuiMsg.Seconds;
+       SequenceMicros  = IntuiMsg.Micros;
+}
+
+
+
+static void PattAddClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               if (si->NumPatterns >= MAXPATTERNS)
+                       DisplayBeep (Scr);
+               else
+               {
+                       xmAddPatternA (si, NULL);
+                       UpdateSongInfo();       /* this also calls UpdatePatternList() */
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PattDelClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               if (si->NumPatterns <= 1)
+                       DisplayBeep (Scr);
+               else
+                       xmRemPattern (si, si->CurrentPatt, 0);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UpdateSongInfo();
+}
+
+
+
+static void PattUpClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               if (si->CurrentPatt == 0)
+                       DisplayBeep (Scr);
+               else
+               {
+                       struct Pattern *tmp_patt;
+
+                       /* Swap pattern with its predecessor */
+                       tmp_patt = si->Patt[si->CurrentPatt];
+                       si->Patt[si->CurrentPatt] = si->Patt[si->CurrentPatt - 1];
+                       si->Patt[si->CurrentPatt - 1] = tmp_patt;
+
+                       SetAttrs (si,
+                               SNGA_CurrentPatt, si->CurrentPatt - 1,
+                               TAG_DONE);
+
+                       UpdatePatternList();
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PattDownClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               if (si->CurrentPatt >= si->NumPatterns - 1)
+                       DisplayBeep (Scr);
+               else
+               {
+                       struct Pattern *tmp_patt;
+
+                       /* Swap pattern with its successor */
+                       tmp_patt = si->Patt[si->CurrentPatt];
+                       si->Patt[si->CurrentPatt] = si->Patt[si->CurrentPatt + 1];
+                       si->Patt[si->CurrentPatt + 1] = tmp_patt;
+
+                       SetAttrs (si,
+                               SNGA_CurrentPatt, si->CurrentPatt + 1,
+                               TAG_DONE);
+
+                       UpdatePatternList();
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PattNameClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               xmSetPattern (si, si->CurrentPatt,
+                       PATTA_Name,     GetString (wud->Gadgets[GD_PattName]),
+                       TAG_DONE);
+
+               UpdatePatternList();
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void PattListClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_SHARED))
+       {
+               if (IntuiMsg.Code == si->CurrentPatt)
+               {
+                       /* Check Double Click */
+                       if (DoubleClick (PatternSecs, PatternMicros, IntuiMsg.Seconds, IntuiMsg.Micros))
+                               NewWindow(WID_PATTERN);
+               }
+               else
+               {
+                       si->CurrentPatt = IntuiMsg.Code;
+
+                       GT_SetGadgetAttrs (wud->Gadgets[GD_PattName], wud->Win, NULL,
+                               GTST_String, si->Patt[si->CurrentPatt]->Name,
+                               TAG_DONE);
+
+                       UpdatePattern();
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       PatternSecs = IntuiMsg.Seconds;
+       PatternMicros = IntuiMsg.Micros;
+}
+
+
+
+static void SeqDelClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               ULONG i;
+
+               /* You can't have an empty sequence */
+               if (si->Length == 1)
+                       DisplayBeep (Scr);
+               else
+               {
+                       /* Shift positions back */
+                       for (i = si->CurrentPos ; i < si->Length; i++)
+                               si->Sequence[i] = si->Sequence[i+1];
+
+                       xmSetSongLen (si, si->Length - 1);
+
+                       UpdateSongInfo();       /* Will call also UpdateSequenceList() */
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void SeqUpClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               UWORD temp;
+
+               if (si->CurrentPos < 1)
+                       DisplayBeep (Scr);
+               else
+               {
+                       temp = si->Sequence[si->CurrentPos];
+                       si->Sequence[si->CurrentPos] = si->Sequence[si->CurrentPos - 1];
+                       si->Sequence[si->CurrentPos - 1] = temp;
+
+                       si->CurrentPos--;
+                       UpdateSequenceList();
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void SeqDownClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               UWORD temp;
+
+               if (si->CurrentPos >= si->Length - 1)
+                       DisplayBeep (Scr);
+               else
+               {
+                       temp = si->Sequence[si->CurrentPos];
+                       si->Sequence[si->CurrentPos] = si->Sequence[si->CurrentPos + 1];
+                       si->Sequence[si->CurrentPos + 1] = temp;
+
+                       si->CurrentPos++;
+                       UpdateSequenceList();
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void SeqInsertClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               if (!(xmSetSongLen (si, si->Length + 1)))
+                       DisplayBeep (Scr);
+               else
+               {
+                       ULONG i;
+
+                       for (i = si->Length - 1; i > si->CurrentPos; i--)
+                               si->Sequence[i] = si->Sequence[i-1];
+
+                       si->Sequence[si->CurrentPos] = si->CurrentPatt;
+               }
+
+               ReleaseSemaphore (&si->Lock);
+
+               UpdateSongInfo();       /* This will also update the sequence list */
+       }
+}
diff --git a/SongClass.c b/SongClass.c
new file mode 100644 (file)
index 0000000..1278584
--- /dev/null
@@ -0,0 +1,1174 @@
+/*
+**     SongClass.c
+**
+**     Copyright (C) 1994,95,96,97 Bernardo Innocenti
+**
+**     Song class dispatcher and handling functions.
+*/
+
+/****** songclass/--background-- *******************************************
+*
+*      NAME
+*              songclass -- XModule 'boopsi'-oriented song implementation.
+*
+*      DESCRIPTION
+*              The song class is an object oriented way to handle a song.  The song
+*              class handles all data storing mechanisms for you and adds a layer
+*              of abstraction between the song internal data structures and the
+*              application.  The advantage is that the internal structures can be
+*              changed while keeping compatibility with existing software.
+*
+*              Another great advantage of being a 'boopsi' class is that the song
+*              can notify other boopsi objects whenever its attributes change.
+*              This simplifies the task of keeping the user interface updated each
+*              time the user (or an ARexx macro, or whatever) changes something.
+*
+*              For speed reasons, the song class does also allow 'white box'
+*              istance access.  This means that you can also directly access
+*              the internal data structures of the song, without using standard
+*              boopsi methods.  You are ONLY allowed to READ public fields, but not
+*              to write any of them.  The main reason to forbid direct writing is
+*              that the song class must send notifications to its targets, but it
+*              does also allow the song implementation to change in future version
+*              without breaking existing applications.
+*
+*              When you create a new istance of the song class, the object handle
+*              you get is actually a SongInfo structure.  This is only possible
+*              because the song class is a subclass of the rootclass, whose istance
+*              is placed at a negative offset in the object handle.
+*              Future song class implementations could require to be subclasses
+*              of other classes, such as the gadget class or even the datatypes
+*              class.  This problem will be probably got around by keeping the
+*              root class as the real superclass of the song and creating an
+*              istance of the other superclass which will be passed all the
+*              methods which are not recognized by the song its-self.  Call this
+*              boopsi polymorphism, if you like to :-)
+*
+*      QUOTATION
+*              Don't be a tuna head.
+*
+****************************************************************************
+*/
+
+
+#include <exec/memory.h>
+#include <intuition/classes.h>
+#include <intuition/classusr.h>
+#include <libraries/xmodule.h>
+
+#include <clib/alib_protos.h>
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/utility.h>
+#include <proto/icon.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Local function prototypes */
+
+static ULONG HOOKCALL SongDispatcher (
+       REG(a0, Class *cl),
+       REG(a2, struct SongInfo *si),
+       REG(a1, Msg msg));
+
+INLINE ULONG                    SongNewMethod          (Class *cl, struct SongInfo *si, struct opSet *opset);
+INLINE ULONG                    SongDisposeMethod      (Class *cl, struct SongInfo *si, Msg msg);
+INLINE ULONG                    SongSetMethod          (Class *cl, struct SongInfo *si, struct opSet *opset);
+
+static BOOL                                     GuessAuthor            (struct SongInfo *si);
+static UWORD                           *SetSongLen                     (struct SongInfo *si, ULONG len);
+static struct Pattern          *AddPattern                     (struct SongInfo *si, struct TagItem *tags);
+static LONG                                     SetPattern                     (struct SongInfo *si, ULONG patnum, struct TagItem *tags);
+static void                                     RemPattern                     (struct SongInfo *si, ULONG patnum, ULONG newpatt);
+static struct Instrument       *AddInstrument          (struct SongInfo *si, ULONG num, struct TagItem *tags);
+static LONG                                     SetInstrument          (struct SongInfo *si, ULONG num, struct TagItem *tags);
+static void                                     RemInstrument          (struct SongInfo *si, ULONG num);
+static void                                     SwapInstruments        (struct SongInfo *si, LONG n, LONG m);
+
+
+
+
+GLOBALCALL Class *InitSongClass (void)
+{
+       Class *class;
+
+       if (class = MakeClass (NULL, ROOTCLASS, NULL, sizeof (struct SongInfo), 0))
+               class->cl_Dispatcher.h_Entry = (ULONG (*)()) SongDispatcher;
+
+       return class;
+}
+
+
+
+GLOBALCALL void FreeSongClass (Class *cl)
+{
+       while (!(FreeClass (cl)))
+               ShowRequest (MSG_CLOSE_ALL_SONGS, MSG_CONTINUE, NULL);
+}
+
+
+
+static ULONG HOOKCALL SongDispatcher (
+       REG(a0, Class *cl),
+       REG(a2, struct SongInfo *si),
+       REG(a1, Msg msg))
+{
+       ULONG result = 0;
+
+       /* Warning: the song class can only be a subclass of the rootclass because
+        * its istance must be at offset 0 in the object handle.
+        */
+
+       switch (msg->MethodID)
+       {
+               case OM_NEW:
+                       result = SongNewMethod (cl, si, (struct opSet *)msg);
+                       break;
+
+               case OM_DISPOSE:
+                       result = SongDisposeMethod (cl, si, msg);
+                       break;
+
+               case OM_SET:
+               case OM_UPDATE:
+                       result = SongSetMethod (cl, si, (struct opSet *)msg);
+                       break;
+
+               case OM_GET:
+                       break;
+
+               case OM_NOTIFY:
+                       break;
+
+               case SNGM_ADDPATTERN:
+                       result = (ULONG) AddPattern (si, ((struct opSet *)msg)->ops_AttrList);
+                       break;
+
+               case SNGM_SETPATTERN:
+                       result = (ULONG) SetPattern (si, ((struct spSetPattern *)msg)->spsp_PattNum,
+                               ((struct spSetPattern *)msg)->spsp_AttrList);
+
+               case SNGM_REMPATTERN:
+                       RemPattern (si,
+                               ((struct spRemPattern *)msg)->sprp_PattNum,
+                               ((struct spRemPattern *)msg)->sprp_NewPatt);
+                       break;
+
+               case SNGM_ADDINSTRUMENT:
+                       result = (ULONG) AddInstrument (si,
+                               ((struct spAddInstrument *)msg)->spsi_InstrNum,
+                               ((struct spAddInstrument *)msg)->spsi_AttrList);
+                       break;
+
+               case SNGM_SETINSTRUMENT:
+                       result = (ULONG) SetInstrument (si,
+                               ((struct spSetInstrument *)msg)->spsi_InstrNum,
+                               ((struct spSetInstrument *)msg)->spsi_AttrList);
+                       break;
+
+               case SNGM_REMINSTRUMENT:
+                       RemInstrument (si,
+                               ((struct spRemInstrument *)msg)->spri_Num);
+                       break;
+
+               case SNGM_SWAPINSTRUMENTS:
+                       SwapInstruments (si,
+                               ((struct spSwapInstruments *)msg)->spsi_Num1,
+                               ((struct spSwapInstruments *)msg)->spsi_Num2);
+                       break;
+
+               default:
+
+                       /* Unsupported method: let our superclass take a look at it. */
+                       result = DoSuperMethodA (cl, (Object *)si, msg);
+                       break;
+       }
+
+       return result;
+}
+
+
+
+INLINE ULONG SongNewMethod (Class *cl, struct SongInfo *si, struct opSet *opset)
+
+/* OM_NEW */
+{
+       if (si = (struct SongInfo *)DoSuperMethodA (cl, (Object *)si, (Msg)opset))
+       {
+               ULONG dummy;
+
+               /* Clear object instance */
+               memset (si, 0, sizeof (struct SongInfo));
+
+               /* Initialize song instance to defaults values */
+               si->Pool                        = XModuleBase->xm_Pool;
+               si->DefNumTracks        = DEF_NUMTRACKS;
+               si->DefPattLen          = DEF_PATTLEN;
+               si->GlobalSpeed         = DEF_SONGSPEED;
+               si->GlobalTempo         = DEF_SONGTEMPO;
+               si->Instr                       = si->InstrumentsTable;
+               DupStringPooled (si->Pool, STR(MSG_AUTHOR_UNKNOWN), &si->Author);
+               DupStringPooled (si->Pool, STR(MSG_SONG_UNTITLED), &si->Title);
+               si->Link.ln_Name        = si->Title;
+               CurrentTime                     (&si->CreationDate, &dummy);
+               InitSemaphore           (&si->Lock);
+
+
+               if (opset->ops_AttrList)
+               {
+                       /* Set generic attributes */
+
+                       SongSetMethod (cl, si, opset);
+                       si->Changes = 0;
+
+
+                       /* Set initialization-only attributes */
+
+                       if (GetTagData (SNGA_ReadyToUse, FALSE, opset->ops_AttrList))
+                       {
+                               if (!DoMethod ((Object *)si, SNGM_ADDPATTERN, NULL))
+                               {
+                                       DisposeObject (si);
+                                       return NULL;
+                               }
+
+                               /* Add one position to the new song */
+                               SetAttrs (si, SNGA_Length, 1, TAG_DONE);
+
+                               if (!si->Sequence)
+                               {
+                                       DisposeObject (si);
+                                       return NULL;
+                               }
+                       }
+               }
+       }
+
+       return (ULONG)si;
+}
+
+
+
+INLINE ULONG SongDisposeMethod (Class *cl, struct SongInfo *si, Msg msg)
+
+/* OM_DISPOSE */
+{
+       LONG i;
+
+       /* All this stuff could be removed as all allocations are made
+        * inside a memory pool.
+        */
+
+       /* Remove song sequence */
+       SetAttrs (si, SNGA_Length, 0, TAG_DONE);
+
+       /* Free patterns */
+       for (i = si->NumPatterns - 1; i >= 0  ; i--)
+               DoMethod ((Object *)si, SNGM_REMPATTERN, i, 0);
+
+       /* Free instruments */
+       for (i = 1 ; i <= si->LastInstrument ; i++)
+               DoMethod ((Object *)si, SNGM_REMINSTRUMENT, i);
+
+       FreeVecPooled (si->Pool, si->Title);
+       FreeVecPooled (si->Pool, si->Author);
+       FreeVecPooled (si->Pool, si->Description);
+       FreeVecPooled (si->Pool, si->Path);
+
+       /* And let our superclass free the istance */
+       return DoSuperMethodA (cl, (Object *)si, msg);
+}
+
+
+
+INLINE ULONG SongSetMethod (Class *cl, struct SongInfo *si, struct opSet *opset)
+
+/* OM_SET */
+{
+       struct TagItem  *ti, *tstate = opset->ops_AttrList;
+       BOOL    do_supermethod  = FALSE;
+       ULONG   oldchanges              = si->Changes;
+
+
+       while (ti = NextTagItem(&tstate))
+       {
+               switch (ti->ti_Tag)
+               {
+                       case SNGA_CurrentPatt:
+                               si->CurrentPatt = ti->ti_Data;
+                               break;
+
+                       case SNGA_CurrentPos:
+                               si->CurrentPos = ti->ti_Data;
+                               break;
+
+                       case SNGA_CurrentInst:
+                               si->CurrentInst = ti->ti_Data;
+                               break;
+
+                       case SNGA_CurrentLine:
+                               si->CurrentLine = ti->ti_Data;
+                               break;
+
+                       case SNGA_CurrentTrack:
+                               si->CurrentTrack = ti->ti_Data;
+                               break;
+
+                       case SNGA_Length:
+                               SetSongLen (si, ti->ti_Data);
+                               break;
+
+                       case SNGA_Title:
+                               if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Title))
+                               {
+                                       if (si->Title)
+                                               si->Link.ln_Name = si->Title;
+                                       else
+                                               si->Link.ln_Name = STR(MSG_UNNAMED);
+                                       si->Changes++;
+                               }
+                               break;
+
+                       case SNGA_Author:
+                               if (ti->ti_Data == -1)
+                               {
+                                       if (GuessAuthor (si))
+                                               si->Changes++;
+                               }
+                               else if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Author))
+                                       si->Changes++;
+                               break;
+
+                       case SNGA_Description:
+                               if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Description))
+                                       si->Changes++;
+                               break;
+
+                       case SNGA_Path:
+                               if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Path))
+                                       si->Changes++;
+                               break;
+
+                       case SNGA_Changes:
+                               if (ti->ti_Data == -1)
+                                       si->Changes++;
+                               else
+                                       si->Changes = ti->ti_Data;
+                               break;
+
+                       case SNGA_TotalChanges:
+                               si->TotalChanges = ti->ti_Data;
+                               break;
+
+                       case SNGA_CreationDate:
+                               si->CreationDate = ti->ti_Data;
+                               break;
+
+                       case SNGA_LastChanged:
+                               si->LastChanged = ti->ti_Data;
+                               break;
+
+                       case SNGA_DefaultTracks:
+                               si->DefNumTracks = ti->ti_Data;
+                               break;
+
+                       case SNGA_DefaultPattLen:
+                               si->DefPattLen = ti->ti_Data;
+                               break;
+
+                       case SNGA_GlobalSpeed:
+                       {
+                               LONG speed = ti->ti_Data;
+
+                               if (speed < 1) speed = 1;
+                               if (speed > 31) speed = 31;
+
+                               if (si->GlobalSpeed != speed)
+                               {
+                                       si->GlobalSpeed = speed;
+                                       si->Changes++;
+                               }
+                               break;
+                       }
+
+                       case SNGA_GlobalTempo:
+                       {
+                               LONG tempo = ti->ti_Data;
+
+                               if (tempo > 255) tempo = 255;
+                               if (tempo < 32) tempo = 32;
+
+                               if (si->GlobalTempo != tempo)
+                               {
+                                       si->GlobalTempo = tempo;
+                                       si->Changes++;
+                               }
+                               break;
+                       }
+
+                       case SNGA_RestartPos:
+                       {
+                               LONG restart = ti->ti_Data;
+
+                               if (restart < 0) restart = 0;
+                               if (restart >= si->Length) restart = si->Length - 1;
+
+                               if (si->RestartPos != restart)
+                               {
+                                       si->RestartPos = restart;
+                                       si->Changes++;
+                               }
+                               break;
+                       }
+
+                       default:
+                               do_supermethod = TRUE;
+                               break;
+               }
+       }       /* End while (NextTagItem()) */
+
+       if (do_supermethod)
+               return (DoSuperMethodA (cl, (Object *)si, (Msg)opset));
+       else
+               /* Returns the number of attributes which have changed */
+               return si->Changes - oldchanges;
+}
+
+
+
+static UWORD *SetSongLen (struct SongInfo *si, ULONG len)
+{
+       ULONG len_quantized;
+
+       if (len == si->Length)
+               return si->Sequence;
+
+       si->Changes++;
+
+       if (len == 0)
+       {
+               /* Deallocate sequence */
+
+               FreeVecPooled (si->Pool, si->Sequence);
+               si->Sequence = NULL;
+               si->Length = 0;
+               return NULL;
+       }
+
+       /* Check for too many song positions */
+
+       if (len > MAXPOSITIONS)
+               return NULL;
+
+
+       len_quantized = (len + SEQUENCE_QUANTUM - 1) & ~(SEQUENCE_QUANTUM - 1);
+
+
+       if (!si->Sequence)
+       {
+               /* Create a new sequence table */
+
+               if (si->Sequence = AllocVecPooled (si->Pool,
+                       len_quantized * sizeof (UWORD)))
+               {
+                       si->Length = len;
+                       memset (si->Sequence, 0, len * sizeof (UWORD)); /* Clear sequence table */
+               }
+       }
+       else if (si->Length > len_quantized)
+       {
+               UWORD *newseq;
+
+               /* Shrink sequence table */
+
+               si->Length = len;
+
+               if (newseq = AllocVecPooled (si->Pool, len_quantized * sizeof (UWORD)))
+               {
+                       CopyMem (si->Sequence, newseq, len * sizeof (UWORD));
+                       FreeVecPooled (si->Pool, si->Sequence);
+                       si->Sequence = newseq;
+               }
+               /* If the previous allocation failed we ignore it and continue
+                * without shrinking the sequence table.
+                */
+       }
+       else if (si->Length <= len_quantized - SEQUENCE_QUANTUM)
+       {
+               UWORD *newseq;
+
+               /* Expand the sequence table */
+
+               if (!(newseq = AllocVecPooled (si->Pool, len_quantized * sizeof (UWORD))))
+                       return NULL;
+
+               /* Now replace the the old sequence with the new and delete the old one */
+
+               CopyMem (si->Sequence, newseq, si->Length * sizeof (UWORD));
+               FreeVecPooled (si->Pool, si->Sequence);
+
+               /* Clear the new sequence table entries */
+               memset (newseq + si->Length, 0, (len - si->Length) * sizeof (UWORD));
+               si->Length = len;
+               si->Sequence = newseq;
+       }
+       else
+       {
+               /* No reallocation */
+
+               if (si->Length > len)
+                       /* Clear the new sequence table entries */
+                       memset (si->Sequence + si->Length, 0, (si->Length - len) * sizeof (UWORD));
+
+               si->Length = len;
+       }
+
+       if (si->CurrentPos >= si->Length)
+               si->CurrentPos = si->Length - 1;
+
+       return si->Sequence;
+}
+
+
+
+static struct Pattern *AddPattern (struct SongInfo *si, struct TagItem *tags)
+
+/* SNGM_ADDPATTERN
+ *
+ * Allocates a pattern structure and adds it to the the passed song.
+ * The tracks are also allocated and attached to the pattern structure.
+ *
+ * PATTA_Num - When this attribute is passed, the pattern will be inserted
+ * before the existing pattern <patnum>.  Patterns >= <patnum> will be moved
+ * ahead one slot.  The position table is updated inserting references to
+ * the new pattern immediately before each occurence of patnum, so that the
+ * two patterns are allways played together.
+ * If patnum is -1, the pattern will be allocated but NOT inserted in the song.
+ *
+ * PATTA_Replace - If TRUE, the existing pattern <patnum> will be replaced by
+ *     the new one.  The other pattern will be freed after the new one is
+ *     allocated.
+ *
+ * PATTA_Pattern - specifies an already allocated pattern structure to be
+ *     used instead of allocating a new one.  Adding patterns allocated
+ *     by another istance of the song class is illegal.
+ *
+ * PATTA_Lines - Number of lines in each track.  If lines is 0, track data
+ *     is not allocated.
+ *
+ * RETURNS
+ *    Pointer to the newly allocated pattern structure, NULL for failure.
+ */
+{
+       struct Pattern *patt;
+       ULONG numpatt_quantized;
+       ULONG i;
+       ULONG tracks, lines, patnum, replace;
+
+       tracks  = GetTagData (PATTA_Tracks,             si->DefNumTracks, tags);
+       lines   = GetTagData (PATTA_Lines,              si->DefPattLen, tags);
+       patnum  = GetTagData (PATTA_Num,                si->NumPatterns, tags);
+       replace = GetTagData (PATTA_Replace,    FALSE, tags);
+       patt    = (struct Pattern *)GetTagData (PATTA_Pattern,  NULL, tags);
+
+       if (!tracks || (si->NumPatterns == MAXPATTERNS))
+               return NULL;
+
+
+       /* Round up to an even number of quantums */
+       numpatt_quantized = (si->NumPatterns + PATTERNS_QUANTUM - 1) & ~(PATTERNS_QUANTUM - 1);
+
+       if (!si->Patt)
+       {
+               /* Create a new pattern table */
+
+               si->Patt = AllocVecPooled (si->Pool,
+                       PATTERNS_QUANTUM * sizeof (struct Pattern *));
+       }
+       else if (si->NumPatterns + 1 >= numpatt_quantized)
+       {
+               struct Pattern **newpatt;
+
+               /* Expand patterns table */
+
+               if (newpatt = AllocVecPooled (si->Pool,
+                       (numpatt_quantized + PATTERNS_QUANTUM) * sizeof (struct Pattern *)))
+               {
+                       CopyMem (si->Patt, newpatt, si->NumPatterns * sizeof (struct Pattern *));
+                       FreeVecPooled (si->Pool, si->Patt);
+                       si->Patt = newpatt;
+               }
+               else return NULL;
+       }
+
+
+       /* Pattern allocation */
+
+       if (!patt)
+       {
+               if (patt = CAllocPooled (si->Pool, sizeof (struct Pattern) +
+                       tracks * sizeof (struct Note *)))
+               {
+                       patt->Tracks    = tracks;
+                       patt->Lines             = lines;
+
+                       if (lines)
+                               for (i = 0 ; i < tracks ; i++)
+                               {
+                                       if (!(patt->Notes[i] = (struct Note *)
+                                               CAllocPooled (si->Pool, sizeof (struct Note) * lines)))
+                                       {
+                                               ULONG j;
+
+                                               for (j = 0 ; j < tracks ; j++)
+                                                       FreePooled (si->Pool, patt->Notes[i], lines * sizeof (struct Note));
+
+                                               FreePooled (si->Pool, patt,  sizeof (struct Pattern) +
+                                                       tracks * sizeof (struct Note *));
+                                               return NULL;
+                                       }
+                               }
+
+                       DupStringPooled (si->Pool, (STRPTR)GetTagData (PATTA_Name, NULL, tags), &patt->Name);
+               }
+       }
+
+       if (patt)
+       {
+               if (patnum != -1)
+               {
+                       if (replace)    /* Free old pattern */
+                       {
+                               ULONG j;
+
+                               for (j = 0 ; j < si->Patt[patnum]->Tracks ; j++)
+                                       FreePooled (si->Pool, si->Patt[patnum]->Notes[j],
+                                               si->Patt[patnum]->Lines * sizeof (struct Note));
+
+                               FreeVecPooled (si->Pool, si->Patt[patnum]->Name);
+                               FreePooled (si->Pool, si->Patt[patnum],  sizeof (struct Pattern) +
+                                       patt->Tracks * sizeof (struct Note *));
+                       }
+
+                       si->Patt[patnum] = patt;
+
+                       if (!replace)
+                       {
+                               if (patnum < si->NumPatterns)
+                               {
+                                       /* Shift subsequent patterns one position ahead */
+                                       for (i = si->NumPatterns ; i > patnum ; i--)
+                                               si->Patt[i] = si->Patt[i-1];
+                               }
+
+                               si->NumPatterns++;
+                       }
+
+                       si->Changes++;
+               }
+
+               if (tracks > si->MaxTracks)
+                       si->MaxTracks = tracks;
+       }
+
+       return patt;
+}
+
+
+
+static LONG SetPattern (struct SongInfo *si, ULONG num, struct TagItem *tags)
+
+/* SNGM_SETPATTERN */
+{
+       struct TagItem *ti;
+       struct Pattern *patt;
+
+       if (patt = si->Patt[num])
+               if (ti = FindTagItem (PATTA_Name, tags))
+               {
+                       DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Patt[num]->Name);
+                       si->Changes++;
+                       return 1;
+               }
+
+       return 0;
+}
+
+
+
+static void RemPattern (struct SongInfo *si, ULONG patnum, ULONG newpatt)
+
+/* SNGM_REMPATTERN
+ *
+ * Remove a pattern from a song.  All patterns >= <patnum> will be moved
+ * back one slot.  The position table is updated replacing each
+ * occurence of <patnum> with pattern <newpatt>.  If <patnum> is -1,
+ * newpatt will be used as a pointer to a pattern structure which has been
+ * created by the song, but has not been added to the pattern table.  This
+ * is useful for freeing patterns created with num = -1 in the
+ * SNGM_ADDPATTERN method.
+ */
+{
+       struct Pattern *patt;
+       ULONG i;
+
+       if (patnum == -1)
+       {
+               patt = (struct Pattern *)newpatt;
+               newpatt = - 1;
+       }
+       else
+               patt = si->Patt[patnum];
+
+       if (patt)
+       {
+               ULONG j;
+
+               if (patnum < si->NumPatterns)
+                       si->Patt[patnum] = NULL;
+
+               for (j = 0 ; j < patt->Tracks ; j++)
+                       FreePooled (si->Pool, patt->Notes[j], patt->Lines * sizeof (struct Note));
+
+               FreeVecPooled (si->Pool, patt->Name);
+               FreePooled (si->Pool, patt,  sizeof (struct Pattern) +
+                       patt->Tracks * sizeof (struct Note *));
+       }
+
+       if (patnum < si->NumPatterns)
+       {
+               /* Scroll subsequent patterns */
+               for (i = patnum; i < si->NumPatterns; i++)
+                       si->Patt[i] = si->Patt[i+1];
+       }
+
+       if (si->Sequence)
+       {
+               /* Adjust position table */
+
+               for (i = 0 ; i < si->Length ; i++)
+               {
+                       /* Substitute references to the old pattern in the position table */
+                       if (si->Sequence[i] == patnum) si->Sequence[i] = newpatt;
+
+                       /* Fix pattern numbers */
+                       if (si->Sequence[i] > patnum) si->Sequence[i]--;
+               }
+       }
+
+       if (patnum != -1)
+       {
+               si->Changes++;
+               si->NumPatterns--;
+
+               if (si->NumPatterns)
+               {
+                       if (si->CurrentPatt >= si->NumPatterns)
+                               si->CurrentPatt = si->NumPatterns - 1;
+               }
+               else
+               {
+                       FreeVecPooled (si->Pool, si->Patt);
+                       si->Patt = NULL;
+               }
+       }
+}
+
+
+
+static struct Instrument *AddInstrument (struct SongInfo *si, ULONG num, struct TagItem *tags)
+
+/* SNGM_ADDINSTRUMENT
+ *
+ * Allocates an instrument structure and adds it to the the passed song.
+ * It will also free the old instrument if there is one already.
+ * If <num> is -1, it will load the instrument in the slot right after
+ * the last instrument.  If <num> is 0, it will load the instrument in
+ * the first free slot.
+ *
+ * RETURNS
+ *    Pointer to the newly allocated instrument structure, NULL for failure.
+ */
+{
+       struct Instrument       *instr;
+
+       if (num == -1)
+               num = si->LastInstrument + 1;
+
+       if (num == 0)
+       {
+               for (num = 1; num <= si->LastInstrument; num++)
+                       if (!si->Instr[num]) break;
+
+               if (num == MAXINSTRUMENTS) return NULL;
+       }
+
+
+       if (num > MAXINSTRUMENTS)
+               return NULL;
+
+       if (si->Instr[num])
+               RemInstrument (si, num);
+
+       if (!(instr = CAllocPooled (si->Pool, sizeof (struct Instrument))))
+               return NULL;
+
+       si->Instr[num] = instr;
+       if (num > si->LastInstrument)
+               si->LastInstrument = num;
+
+       SetInstrument (si, num, tags);
+
+       return instr;
+}
+
+
+
+static void SwapInstruments (struct SongInfo *si, LONG n, LONG m)
+
+/* SNGM_SWAPINSTRUMENTS
+ *
+ * Swaps two instruments and updates the LastInstrument field if required.
+ *
+ * RETURNS
+ *    Pointer to the newly allocated instrument structure, NULL for failure.
+ */
+{
+       struct Instrument *tmp;
+
+       if (n == m) return;
+
+       tmp = si->Instr[n];
+       si->Instr[n] = si->Instr[m];
+       si->Instr[m] = tmp;
+
+       n = max (n,m);
+
+       if (n >= si->LastInstrument)
+       {
+               for ( ; n >= 0; n--)
+                       if (si->Instr[n]) break;
+
+               DB (kprintf ("n = %ld\n", n));
+
+               si->LastInstrument = n;
+       }
+}
+
+
+
+static LONG SetInstrument (struct SongInfo *si, ULONG num, struct TagItem *tags)
+
+/* SNGM_SETINSTRUMENT */
+{
+       struct Instrument       *instr;
+       struct TagItem          *ti;
+
+       if ((num <= si->LastInstrument) && (instr = si->Instr[num]))
+       {
+               while (ti = NextTagItem (&tags))
+                       switch (ti->ti_Tag)
+                       {
+                               case INSTRA_Type:
+                                       instr->Type             = ti->ti_Data;
+                                       break;
+
+                               case INSTRA_Name:
+                                       DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &instr->Name);
+                                       break;
+
+                               case INSTRA_Volume:
+                                       instr->Volume   = ti->ti_Data;
+                                       break;
+
+                               case INSTRA_Sample:
+                                       instr->Sample   = (BYTE *) ti->ti_Data;
+                                       break;
+
+                               case INSTRA_Length:
+                                       instr->Length   = ti->ti_Data;
+                                       break;
+
+                               case INSTRA_Flags:
+                                       instr->Flags    = ti->ti_Data;
+                                       break;
+
+                               case INSTRA_LoopStart:
+                                       instr->Repeat   = ti->ti_Data;
+                                       break;
+
+                               case INSTRA_LoopLen:
+                                       instr->Replen   = ti->ti_Data;
+                                       break;
+
+                               case INSTRA_LoopEnd:
+                                       instr->Replen   = ti->ti_Data - instr->Repeat - 2;
+                                       break;
+
+                               case INSTRA_FineTune:
+                                       instr->FineTune = ti->ti_Data;
+                                       /* Note: I'm falling through here! */
+
+                               default:
+                                       break;
+                       }
+               /* End while (NextTagItem()) */
+
+               si->Changes++;
+               return 1;
+       }
+
+       return 0;
+}
+
+
+static void RemInstrument (struct SongInfo *si, ULONG num)
+
+/* SNGM_REMINSTRUMENT */
+{
+       if ((num <= si->LastInstrument) && (si->Instr[num]))
+       {
+               FreeVec                 (si->Instr[num]->Sample);
+               FreeVecPooled   (si->Pool, si->Instr[num]->Name);
+               FreePooled              (si->Pool, si->Instr[num], sizeof (struct Instrument));
+               si->Instr[num] = NULL;
+               si->Changes++;
+       }
+
+       if (num == si->LastInstrument)
+       {
+               ULONG i;
+
+               for (i = num - 1; i > 0 ; i--)
+                       if (si->Instr[i])
+                               break;
+
+               si->LastInstrument = i;
+       }
+}
+
+
+
+#if 0 /* --- This part has been removed --- */
+
+static WORD ModType (BPTR fh)
+
+/* Guess source module type */
+{
+       UBYTE __aligned str[0x30];
+       WORD trackertype;
+
+       if (Read (fh, str, 0x30) != 0x30)
+               return -1;
+
+       /* Check XModule */
+       if (!(strncmp (str, "FORM", 4) || strncmp (str+8, "XMOD", 4)))
+               return FMT_XMODULE;
+
+       /* Check MED */
+       if (!(strncmp (str, "MMD", 3)))
+               return FMT_MED;
+
+       /* Check Oktalyzer */
+       if (!(strncmp (str, "OKTASONG", 8)))
+               return FMT_OKTALYZER;
+
+       if (!(strncmp (&str[0x2C], "SCRM", 4)))
+               return FMT_SCREAMTRACKER;
+
+       if (!(strncmp (str, "Extended module: ", 17)))
+               return FMT_FASTTRACKER2;
+
+       /* Check #?Tracker */
+       if ((trackertype = IsTracker (fh)) != FMT_UNKNOWN)
+               return trackertype;
+
+       switch (ShowRequestArgs (MSG_UNKNOWN_MOD_FORMAT, MSG_SOUND_PRO_CANCEL, NULL))
+       {
+               case 1:
+                       return FMT_STRACKER;
+                       break;
+
+               case 2:
+                       return FMT_PTRACKER;
+                       break;
+
+               default:
+                       break;
+       }
+
+       return FMT_UNKNOWN;
+}
+#endif /* --- This part has been removed --- */
+
+
+
+#define tolower(c) ((c) | (1<<5))
+
+static BOOL GuessAuthor (struct SongInfo *si)
+
+/* Tries to find the author of the song by looking up the instrument
+ * names.
+ */
+{
+       UBYTE *name;
+       ULONG i, j;
+
+       if (!si->Instr) return FALSE;
+
+       for (i = 1; i <= si->LastInstrument; i++)
+       {
+               if (!si->Instr[i]) continue;
+
+               name = si->Instr[i]->Name;
+
+               /* Check for IntuiTracker-style embedded author name */
+               if (name[0] == '#')
+               {
+                       for (j = 1; name[j] == ' '; j++); /* Skip extra blanks */
+
+                       /* Skip "by " */
+                       if ((tolower(name[j]) == 'b') && (tolower(name[j+1]) == 'y') && name[j+2] == ' ')
+                               j += 3;
+
+                       for (; name[j] == ' '; j++); /* Skip extra blanks */
+
+                       if (name[j])
+                       {
+                               SetAttrs (si, SNGA_Author, &(name[j]), TAG_DONE);
+                               return TRUE; /* Stop looking for author */
+                       }
+               }
+
+               /* Now look for the occurence of "by ", "by:", "by\0" or "(c)".  Ignore case. */
+               for (j = 0; name[j]; j++)
+               {
+                       if (( (tolower(name[j]) == 'b') && (tolower(name[j+1]) == 'y') &&
+                               ((name[j+2] == ' ') || (name[j+2] == ':') || (name[j+2] == '\0')) ) ||
+                               ((name[j] == '(') && (tolower(name[j+1]) == 'c') && (name[j+2] == ')') ))
+                       {
+                               j+=3;   /* Skip 'by ' */
+
+                               /* Skip extra blanks/punctuations */
+                               while (name[j] == ' ' || name[j] == ':' || name[j] == '.') j++;
+
+                               if (name[j]) /* Check if the end is reached */
+                                       /* The name of the author comes (hopefully) right after 'by ' */
+                                       SetAttrs (si, SNGA_Author, &(name[j]), TAG_DONE);
+                               else
+                                       /* The name of the author is stored in the next instrument */
+                                       if (i < si->LastInstrument - 1)
+                                       SetAttrs (si, SNGA_Author, si->Instr[i+1]->Name, TAG_DONE);
+
+                               return TRUE; /* Stop loop */
+                       }
+               }
+       }
+
+       return FALSE;
+}
+
+
+
+GLOBALCALL ULONG CalcInstSize (struct SongInfo *si)
+
+/* Calculate total size of all instruments in a song. */
+{
+       ULONG i;
+       ULONG size = 0;
+
+       for (i = 1; i < si->LastInstrument; i++)
+       {
+               if (!si->Instr[i]) continue;
+                       size += si->Instr[i]->Length + sizeof (struct Instrument);
+       }
+
+       return size;
+}
+
+
+
+GLOBALCALL ULONG CalcSongSize (struct SongInfo *si)
+
+/* Calculate total size of a song */
+{
+       ULONG i;
+       struct Pattern *patt;
+       ULONG size = sizeof (struct SongInfo) + CalcInstSize (si);
+
+       /* Calculate total patterns size */
+
+       for ( i = 0; i < si->NumPatterns; i++)
+       {
+               if (patt = si->Patt[i])
+                       size += patt->Lines * patt->Tracks * sizeof (struct Note) + sizeof (struct Pattern);
+       }
+
+       return size;
+}
+
+
+
+GLOBALCALL ULONG CalcSongTime (struct SongInfo *si)
+
+/* Calculate song length in seconds
+ *
+ * One note lasts speed/50 seconds at 125bpm.
+ */
+{
+       ULONG i, j, k;
+       struct Pattern *patt;
+       struct Note *note;
+       ULONG speed = si->GlobalSpeed,
+//             tempo = si->GlobalTempo,
+               ticks = speed * 20,
+               millisecs = 0;
+
+
+       for (i = 0; i < si->Length; i++)
+       {
+               patt = si->Patt[si->Sequence[i]];
+
+               for (j = 0; j < patt->Lines; j++)
+                       for (k = 0; k < patt->Tracks; k++)
+                       {
+                               note = &patt->Notes[k][j];
+
+                               switch (note->EffNum)
+                               {
+                                       case EFF_POSJUMP:
+                                               if (note->EffVal > i)
+                                               {
+                                                       i = note->EffVal;
+                                                       j = patt->Lines;
+                                                       k = patt->Tracks;
+                                               }
+                                               else return millisecs;
+                                               break;
+
+                                       case EFF_SETSPEED:
+                                               /* At speed 1, one line lasts one VBlank,
+                                                * that is 20 milliseconds.
+                                                */
+                                               ticks = note->EffVal * 20;
+                                               break;
+
+                                       case EFF_PATTERNBREAK:
+                                               j = patt->Lines;
+                                               k = patt->Tracks;
+                                               break;
+
+                                       /* case EFF_MISC: Loop */
+                               }
+
+                               millisecs += ticks;
+                       }
+       }
+
+       return millisecs;
+}
diff --git a/SongClass.i b/SongClass.i
new file mode 100644 (file)
index 0000000..7a15d09
--- /dev/null
@@ -0,0 +1,151 @@
+       IFND    LIBRARIES_SONGLCASS_I
+LIBRARIES_SONGCLASS_I  SET     1
+**
+**     $VER: songclass.i 3.9 (7.1.96)
+**     Copyright (C) 1995,96 Bernardo Innocenti
+**
+**     Set TAB size to 8 chars to read this header file
+**
+**     Assembler structure definitions for the song class
+**
+
+
+       IFND    EXEC_TYPES_I
+       include exec/types.i
+       ENDC    ; EXEC_TYPES_I
+
+       IFND    EXEC_NODES_I
+       include exec/nodes.i
+       ENDC    ; EXEC_NODES_I
+
+       IFND    EXEC_SEMAPHORES_I
+       include exec/semaphores.i
+       ENDC    ; EXEC_SEMAPHORES_I
+
+
+MAXOCTAVES     EQU     6                       ; Number of octaves currently supported
+MAXNOTES       EQU     (12*MAXOCTAVES+1)       ; Entries in note conversion table
+MAXTRACKS      EQU     255                     ; Maximum number of tracks in a pattern
+MAXINSTRUMENTS EQU     255                     ; Maximum number of instruments in a song
+MAXPATTLINES   EQU     32767                   ; Maximum number of lines in a pattern
+MAXPOSITIONS   EQU     32767                   ; Maximum number of song positions
+MAXPATTERNS    EQU     32767                   ; Maximum number of patterns
+
+
+
+**************************************************************************
+** Some definitions transcripted from "XModule.h"
+**************************************************************************
+
+       ENUM 0
+
+       EITEM EFF_NULL
+       EITEM EFF_PORTAMENTOUP
+       EITEM EFF_PORTAMENTODOWN
+       EITEM EFF_TONEPORTAMENTO
+       EITEM EFF_VIBRATO
+       EITEM EFF_TONEPVOLSLIDE
+       EITEM EFF_VIBRATOVOLSLIDE
+       EITEM EFF_TREMOLO
+       EITEM EFF_UNUSED
+       EITEM EFF_SAMPLEOFFSET
+       EITEM EFF_VOLSLIDE
+       EITEM EFF_POSJUMP
+       EITEM EFF_SETVOLUME
+       EITEM EFF_PATTERNBREAK
+       EITEM EFF_MISC
+       EITEM EFF_SETSPEED
+       EITEM EFF_SETTEMPO
+       EITEM EFF_ARPEGGIO
+
+       EITEM EFF_COUNT
+
+
+
+       STRUCTURE       Note,0
+       UBYTE note_Note
+       UBYTE note_Inst
+       UBYTE note_Vol
+       UBYTE note_Pad
+       UBYTE note_EffNum
+       UBYTE note_EffVal
+       LABEL Note_SIZEOF
+
+
+       STRUCTURE       Instrument,0
+       UWORD   in_InstType             ; Instrument type (See defs)
+       UWORD   in_Volume               ; Volume (max $40)
+       APTR    in_Name                 ; Instrument Name
+       APTR    in_SampleData           ; Sampled data
+       ULONG   in_Length               ; Length of instr
+       ULONG   in_Repeat               ; Loop start (No loop = 0)
+       ULONG   in_Replen               ; Loop size (No loop = 1)
+       WORD    in_FineTune             ; Instrument FineTune (-8..+7)
+       UWORD   in_Flags                ; Unused
+       LABEL   in_SIZEOF
+
+
+       STRUCTURE       Pattern,0
+       UWORD   pa_Tracks               ; Support for variable number of tracks
+       UWORD   pa_Lines                ; Number of lines in pattern
+       APTR    pa_Name                 ; Pattern Name
+       STRUCT  pa_Reserved,8   ; Reserved for future enhancements
+       LABEL   pa_Notes,0              ; Pointers to all tracks follow
+       LABEL   pa_SIZEOF
+
+
+* Song class white box istance
+*
+* You can read from this structure directly for speed critical operations.
+* Modifying the data contained in the SongInfo structure is only allowed
+* by means of object methods.
+*
+* Before using a public song, please protect yourself against other tasks
+* modifying these fields. Always obtainin a (shared) lock on the
+* SignalSemaphore contained in the SongInfo structure before doing anything
+* with it. The methods provided by the song class will not handle access
+* arbitration for you.
+*
+       STRUCTURE       SongInfo,LN_SIZE
+       STRUCT  si_Lock,SS_SIZE
+       UWORD   si_Length               ; Number of positions in song
+       UWORD   si_MaxTracks            ; Number of tracks in song
+       UWORD   si_NumPatterns          ; Number of patterns in song
+       UWORD   si_LastInstrument       ; Unused
+       UWORD   si_CurrentPatt
+       UWORD   si_CurrentPos
+       UWORD   si_CurrentInst
+       UWORD   si_CurrentLine
+       UWORD   si_CurrentTrack
+       UWORD   si_DefNumTracks         ; Default number of tracks in new patterns
+       UWORD   si_DefPattLen           ; Default number of lines in new patterns
+
+       UWORD   si_GlobalSpeed          ; Default song speed
+       UWORD   si_GlobalTempo          ; Default song tempo
+       UWORD   si_RestartPos           ; Position to restart from
+       STRUCT  si_Reserved,8           ; Reserved for future enhancements
+
+* Note: all data beyond this point is longword aligned
+
+       ULONG   si_Flags                ; See definitions below
+       APTR    si_Pool                 ; The memory pool where song data
+                                       ; must be allocated from.
+       APTR    si_Sequence             ; UWORD *: Pointer to song sequence
+       APTR    si_Patt
+       APTR    si_Instr
+       APTR    si_ActiveTracks         ; UWORD *: Active Tracks (-1 = disabled)
+       APTR    si_Title                ; Song name
+       APTR    si_Author               ; Author of song
+       APTR    si_Path                 ; Original song path
+       APTR    si_Description          ; Verbose song description
+
+       ULONG   si_Changes              ; Number of changes made to this song
+       ULONG   si_TotalChanges         ; Total number of changes made so far
+       ULONG   si_CreationDate         ; Date of OM_NEW
+       ULONG   si_LastChanged          ; Date of last saving
+
+       LABEL   si_SIZEOF
+
+
+
+       ENDC    ; !LIBRARIES_SONGCLASS_I
diff --git a/SongInfoWin.c b/SongInfoWin.c
new file mode 100644 (file)
index 0000000..68f300a
--- /dev/null
@@ -0,0 +1,533 @@
+/*
+**     SongInfoWin.c
+**
+**     Copyright (C) 1993,94,95,96 Bernardo Innocenti
+**
+**     Handle Song Information panel.
+*/
+
+#include <exec/nodes.h>
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/gadtools.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+/* Gadgets IDs */
+
+enum
+{
+       GD_SongInfoGroup0,
+               GD_SongInfoGroup1,
+                       GD_SongList,
+                       GD_SongInfoGroup2,
+                               GD_NewSong,
+                               GD_OpenSong,
+                               GD_SaveSong,
+                               GD_DelSong,
+               GD_SongInfoGroup3,
+                       GD_SongName,
+                       GD_AuthorName,
+               GD_SongInfoGroup4,
+                       GD_SongInfoGroup5,
+                               GD_Tempo,
+                               GD_Speed,
+                               GD_Restart,
+                       GD_SongInfoGroup6,
+                               GD_SongLength,
+                               GD_Patterns,
+                               GD_Tracks,
+               GD_SongInfoGroup7,
+                       GD_TotalSize,
+                       GD_InstSize,
+
+       SongInfo_CNT
+};
+
+
+
+
+/* Local function Prototypes */
+
+static void SongInfoPostOpen   (void);
+
+static void SongListClicked            (struct WinUserData *wud);
+static void SongNameClicked            (struct WinUserData *wud);
+static void AuthorNameClicked  (struct WinUserData *wud);
+static void TempoClicked               (struct WinUserData *wud);
+static void SpeedClicked               (struct WinUserData *wud);
+static void RestartClicked             (struct WinUserData *wud);
+static void NewSongClicked             (struct WinUserData *wud);
+static void OpenSongClicked            (struct WinUserData *wud);
+static void DelSongClicked             (struct WinUserData *wud);
+static void SaveSongClicked            (struct WinUserData *wud);
+
+static void SongInfoMiMergeSongs       (void);
+static void SongInfoMiJoinSongs                (void);
+static void SongInfoMiClearSong                (void);
+
+
+
+static struct NewMenu SongInfoNewMenu[] =
+{
+       NM_TITLE, (STRPTR)MSG_SONG_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_MERGE_SONGS_MEN, "M", 0, 0L, (APTR)SongInfoMiMergeSongs,
+       NM_ITEM, (STRPTR)MSG_JOIN_SONGS_MEN, "J", 0, 0L, (APTR)SongInfoMiJoinSongs,
+       NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
+       NM_ITEM, (STRPTR)MSG_CLEAR_MEN, "K", 0, 0L, (APTR)SongInfoMiClearSong,
+       NM_END, NULL, NULL, 0, 0L, NULL
+};
+
+
+
+static ULONG SongInfoArgs[] =
+{
+       HGROUP_KIND, 0,
+               LISTVIEW_KIND,  (ULONG)SongListClicked,         0,      NULL, TAG_DONE,
+               VGROUP_KIND, 0,
+                       BUTTON_KIND,    (ULONG)NewSongClicked,  MSG_UNDERSCORE_NEW_GAD, TAG_DONE,
+                       BUTTON_KIND,    (ULONG)OpenSongClicked, MSG_OPEN_GAD,                   TAG_DONE,
+                       BUTTON_KIND,    (ULONG)SaveSongClicked, MSG_SAVE_GAD,                   TAG_DONE,
+                       BUTTON_KIND,    (ULONG)DelSongClicked,  MSG_DEL_GAD,                    TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       VGROUP_KIND,    BBFT_RIDGE,
+               STRING_KIND,    (ULONG)SongNameClicked,         MSG_SONG_NAME_GAD,              31,     TAG_DONE,
+               STRING_KIND,    (ULONG)AuthorNameClicked,       MSG_AUTHOR_NAME_GAD,    63,     TAG_DONE,
+               ENDGROUP_KIND,
+       HGROUP_KIND,    BBFT_RIDGE,
+               VGROUP_KIND, 0,
+                       INTEGER_KIND,   (ULONG)TempoClicked,    MSG_DEF_TEMPO_GAD,      3,      TAG_DONE,
+                       INTEGER_KIND,   (ULONG)SpeedClicked,    MSG_DEF_SPEED_GAD,      3,      TAG_DONE,
+                       INTEGER_KIND,   (ULONG)RestartClicked,  MSG_RESTART_GAD,        3,      TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       NUMBER_KIND, MSG_LENGHT_GAD,            3, TAG_DONE,
+                       NUMBER_KIND, MSG_NUM_PATTS_GAD,         3, TAG_DONE,
+                       NUMBER_KIND, MSG_NUM_TRACKS_GAD,        3, TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       VGROUP_KIND, BBFT_RIDGE,
+               NUMBER_KIND, MSG_TOT_MOD_SIZE_GAD,      7, TAG_DONE,
+               NUMBER_KIND, MSG_TOT_INST_SIZE_GAD,     7, TAG_DONE,
+               ENDGROUP_KIND,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG SongInfoWinTags[] =
+{
+       XMWIN_NewMenu,          (LONG)SongInfoNewMenu,
+       XMWIN_LayoutArgs,       (LONG)SongInfoArgs,
+       XMWIN_GCount,           SongInfo_CNT,
+       XMWIN_Title,            MSG_SONGINFO_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET,
+       XMWIN_IDCMPFlags,       NUMBERIDCMP | STRINGIDCMP | LISTVIEWIDCMP | INTEGERIDCMP | BUTTONIDCMP | IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW | IDCMP_MENUPICK,
+       XMWIN_PostOpenFunc,     (LONG)SongInfoPostOpen,
+       XMWIN_DropIconFunc,     (LONG)ToolBoxDropIcon,
+       XMWIN_HelpNode,         (LONG)"SongInfo",
+       TAG_DONE
+};
+
+
+
+static void SongInfoPostOpen (void)
+{
+       UpdateSongInfoList();
+       UpdateSongInfo();
+}
+
+
+
+/**********************/
+/* SongInfo Functions */
+/**********************/
+
+
+GLOBALCALL void DetatchSongInfoList (void)
+
+/* Detatches the SongInfo list from the listview gadget.
+ * Call UpdateSongInfoList() to re-attach it when you are done modifying it.
+ */
+{
+       struct WinUserData *wud = WDescr[WID_SONGINFO].Wud;
+
+       if (wud && wud->Win)
+               GT_SetGadgetAttrs (wud->Gadgets[GD_SongList], wud->Win, NULL,
+                       GTLV_Labels, ~0,
+                       TAG_DONE);
+}
+
+
+
+GLOBALCALL void UpdateSongInfoList (void)
+
+/* Updates the SongInfo listview gadget */
+{
+       struct WinUserData *wud = WDescr[WID_SONGINFO].Wud;
+
+       if (wud && wud->Win)
+       {
+#ifdef OS30_ONLY
+               ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#else
+               /* Workaround for Pre-V39 ObtainSemaphoreShared() bug (see autodoc) */
+
+               /* Try to get the shared semaphore */
+               if (!AttemptSemaphoreShared (&XModuleBase->xm_BaseLock))
+                       /* Check if we can get the exclusive version */
+                       if (!AttemptSemaphore (&XModuleBase->xm_BaseLock))
+                               /* Oh well, wait for the shared lock */
+                               ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+#endif /* OS30_ONLY */
+
+               if (XModuleBase->xm_CurrentSong)
+               {
+                       struct SongInfo *tmp = (struct SongInfo *)XModuleBase->xm_Songs.mlh_Head;
+                       ULONG songnum = 0;
+
+                       /* Find current song number */
+
+                       while (tmp != XModuleBase->xm_CurrentSong)
+                       {
+                               tmp = (struct SongInfo *)tmp->Link.ln_Succ;
+                               songnum++;
+                       }
+
+                       GT_SetGadgetAttrs (wud->Gadgets[GD_SongList], wud->Win, NULL,
+                               GTLV_Labels,            &XModuleBase->xm_Songs,
+                               GTLV_Selected,          songnum,
+                               GTLV_MakeVisible,       songnum,
+                               TAG_DONE);
+               }
+
+               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+       }
+}
+
+
+
+GLOBALCALL void UpdateSongInfo (void)
+
+/* Update information on the current song */
+{
+       struct WinUserData *wud = WDescr[WID_SONGINFO].Wud;
+       struct SongInfo *si;
+       DB(ULONG millisecs);
+
+       if (wud && wud->Win)
+               if (si = xmLockActiveSong (SM_SHARED))
+               {
+                       SetGadgets (wud,
+                               GD_SongName,    si->Title,
+                               GD_AuthorName,  si->Author,
+                               GD_Tempo,               si->GlobalTempo,
+                               GD_Speed,               si->GlobalSpeed,
+                               GD_SongLength,  si->Length,
+                               GD_Patterns,    si->NumPatterns,
+                               GD_Tracks,              si->MaxTracks,
+                               GD_Restart,             si->RestartPos,
+                               GD_TotalSize,   CalcSongSize (si),
+                               GD_InstSize,    CalcInstSize (si),
+                               -1);
+
+                       /* TODO */
+                       DB(millisecs = CalcSongTime (si));
+                       DB(xmDisplayMessage (XMDMF_DEBUG, "Song Time: %02ld:%02ld", millisecs / 60000, (millisecs / 1000) % 60));
+
+                       ReleaseSemaphore (&si->Lock);
+               }
+
+       UpdateInstrList();
+       UpdatePatternList();
+}
+
+
+
+/********************/
+/* SongInfo Gadgets */
+/********************/
+
+
+static void SongListClicked (struct WinUserData *wud)
+{
+       WORD i;
+       struct SongInfo *tmp;
+
+       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+
+       tmp = (struct SongInfo *)XModuleBase->xm_Songs.mlh_Head;
+
+       for (i = IntuiMsg.Code; (i > 0) && tmp ; i--)
+               tmp = (struct SongInfo *)tmp->Link.ln_Succ;
+
+       if (tmp) ObtainSemaphoreShared (&tmp->Lock);
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+       if (tmp)
+       {
+               xmActivateSong (tmp);
+               ReleaseSemaphore (&tmp->Lock);
+       }
+}
+
+
+
+static void SongNameClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong(SM_EXCLUSIVE))
+       {
+               DetatchSongInfoList();
+
+               SetAttrs (si,
+                       SNGA_Title,     GetString (wud->Gadgets[GD_SongName]),
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+
+               UpdateSongInfoList();
+       }
+}
+
+
+
+static void AuthorNameClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong(SM_EXCLUSIVE))
+       {
+               SetAttrs (si,
+                       SNGA_Author,    GetString (wud->Gadgets[GD_AuthorName]),
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void TempoClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong(SM_EXCLUSIVE))
+       {
+               SetAttrs (si,
+                       SNGA_GlobalTempo,       GetNumber (wud->Gadgets[GD_Tempo]),
+                       TAG_END);
+               GT_SetGadgetAttrs (wud->Gadgets[GD_Tempo], wud->Win, NULL,
+                       GTIN_Number, si->GlobalTempo,
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void RestartClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong(SM_EXCLUSIVE))
+       {
+               SetAttrs (si,
+                       SNGA_RestartPos, GetNumber (wud->Gadgets[GD_Restart]),
+                       TAG_END);
+               GT_SetGadgetAttrs (wud->Gadgets[GD_Restart], wud->Win, NULL,
+                       GTIN_Number, si->RestartPos,
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void SpeedClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmLockActiveSong(SM_EXCLUSIVE))
+       {
+               SetAttrs (si,
+                       SNGA_GlobalSpeed, GetNumber (wud->Gadgets[GD_Speed]),
+                       TAG_END);
+               GT_SetGadgetAttrs (wud->Gadgets[GD_Speed], wud->Win, NULL,
+                       GTIN_Number, si->GlobalSpeed,
+                       TAG_DONE);
+
+               ReleaseSemaphore (&si->Lock);
+       }
+}
+
+
+
+static void NewSongClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       if (si = xmCreateSong (
+               SNGA_ReadyToUse,        TRUE,
+               XMSNG_AddToList,        -1,
+               XMSNG_Active,           TRUE,
+               TAG_DONE))
+               ReleaseSemaphore (&si->Lock);   /* This is for XMSNG_AddToList */
+       else
+               DisplayBeep (NULL);
+}
+
+
+
+static void OpenSongClicked (struct WinUserData *wud)
+{
+       StartFileRequest (FREQ_LOADMOD, ToolBoxOpenModule);
+}
+
+
+
+static void DelSongClicked (struct WinUserData *wud)
+{
+       if (ShowRequestArgs (MSG_DISCARD_CURRENT_SONG, MSG_YES_OR_NO, NULL))
+       {
+               struct SongInfo *si;
+
+               if (si = xmLockActiveSong (SM_EXCLUSIVE))
+                       xmDeleteSong (si);
+
+               if (!XModuleBase->xm_CurrentSong)
+                       NewSongClicked(wud);
+       }
+}
+
+
+static void SaveSongClicked (struct WinUserData *wud)
+{
+       struct SongInfo *si;
+
+       LockWindows();
+
+       if (si = xmLockActiveSong (SM_EXCLUSIVE))
+       {
+               LastErr = xmSaveModuleA (si, si->Path, NULL, NULL);
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       UnlockWindows();
+}
+
+
+
+/******************/
+/* SongInfo Menus */
+/******************/
+
+static void SongInfoMiMergeSongs (void)
+{
+       struct SongInfo *si1, *si2, *si3;
+
+       LockWindows();
+
+       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+
+       if (si1 = XModuleBase->xm_CurrentSong)
+       {
+               si2 = (struct SongInfo *)si1->Link.ln_Succ;
+
+               if (si2->Link.ln_Succ)
+               {
+                       /* Risking a deadlock??  Hmmm... No!
+                        * Shared locks do not prevent other tasks from
+                        * locking these two songs.
+                        */
+                       ObtainSemaphoreShared (&si1->Lock);
+                       ObtainSemaphoreShared (&si2->Lock);
+                       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+                       si3 = MergeSongs (si1, si2);
+
+                       ReleaseSemaphore (&si1->Lock);
+                       ReleaseSemaphore (&si2->Lock);
+
+                       if (si3) xmAddSongA (si3, si2, NULL);
+               }
+               else
+               {
+                       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+                       xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
+                               (APTR)MSG_MERGE_REQUIRES_TWO_SONGS, NULL);
+               }
+       }
+       else
+       {
+               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+               DisplayBeep (Scr);
+       }
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+       UnlockWindows();
+}
+
+
+
+static void SongInfoMiJoinSongs (void)
+{
+       struct SongInfo *si1, *si2, *si3;
+
+       LockWindows();
+
+       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+
+       if (si1 = XModuleBase->xm_CurrentSong)
+       {
+               si2 = (struct SongInfo *)si1->Link.ln_Succ;
+
+               if (si2->Link.ln_Succ)
+               {
+                       /* Risking a deadlock??  Hmmm... No!
+                        * Shared locks do not prevent other tasks from
+                        * locking these two songs.
+                        */
+                       ObtainSemaphoreShared (&si1->Lock);
+                       ObtainSemaphoreShared (&si2->Lock);
+                       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+                       si3 = JoinSongs (si1, si2);
+
+                       ReleaseSemaphore (&si1->Lock);
+                       ReleaseSemaphore (&si2->Lock);
+
+                       xmAddSongA (si3, si2, NULL);
+               }
+               else
+               {
+                       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+                       xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
+                               (APTR)MSG_JOIN_REQUIRES_TWO_SONGS, NULL);
+               }
+       }
+       else
+       {
+               ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+               DisplayBeep (Scr);
+       }
+
+       UnlockWindows();
+}
+
+
+
+static void SongInfoMiClearSong (void)
+{
+       NewWindow (WID_CLEAR);
+}
diff --git a/Startup.asm b/Startup.asm
new file mode 100644 (file)
index 0000000..a56c210
--- /dev/null
@@ -0,0 +1,532 @@
+;**
+;** Startup.asm
+;**
+;** Copyright (C) 1994,95,96,97 Bernardo Innocenti
+;**
+;** Amiga/m68k startup code for XModule
+;**
+
+       INCLUDE "exec/types.i"
+       INCLUDE "exec/alerts.i"
+       INCLUDE "exec/nodes.i"
+       INCLUDE "exec/lists.i"
+       INCLUDE "exec/ports.i"
+       INCLUDE "exec/libraries.i"
+       INCLUDE "exec/tasks.i"
+       INCLUDE "exec/memory.i"
+       INCLUDE "exec/macros.i"
+       INCLUDE "exec/execbase.i"
+       INCLUDE "libraries/dos.i"
+       INCLUDE "libraries/dosextens.i"
+       INCLUDE "workbench/startup.i"
+
+
+AbsExecBase    EQU     4
+
+;**************************************************************************
+; External symbol definitions and references
+;**************************************************************************
+
+       xdef    _Start
+       xdef    @_XCEXIT
+       xdef    _SysBase,_DOSBase
+       xdef    _ThisTask
+       xdef    _StdOut
+       xdef    _WBenchMsg
+
+       xdef    _SPrintf
+       xdef    _VSPrintf
+       xdef    _AsmAllocVecPooled
+       xdef    _AsmFreeVecPooled
+       xdef    _AsmCAllocPooled
+
+       xref    _LinkerDB               ; linker defined base value
+       xref    __BSSBAS                ; linker defined base of BSS
+       xref    __BSSLEN                ; linker defined length of BSS
+       xref    ___main                 ; Name of C entry point
+
+       xref    _AsmAllocPooled
+       xref    _AsmFreePooled
+
+       IFD     DEBUG
+       xref    _kprintf
+       ENDC
+
+       IFND    STKSIZE
+STKSIZE        EQU     8192
+       ENDC
+
+;**************************************************************************
+; Macro definitions
+;**************************************************************************
+
+
+PUSH   MACRO                           ; push to stack
+       move.\0 \1,-(sp)                ; push.(w|l) <ea>
+       ENDM
+
+POP    MACRO                           ; pop from stack
+       move.\0 (sp)+,\1              ; pop.(w|l) <ea>
+       ENDM
+
+
+
+;**************************************************************************
+; Code entry point
+;**************************************************************************
+
+       SECTION __MERGED,code
+
+_Start:
+
+       NEAR
+       lea     _LinkerDB,a4            ; load base register
+       move.l  AbsExecBase.w,a6
+
+; The BSS segment is being cleared by LoadSeg() starting from V37
+;
+;      lea     __BSSBAS,a3             ; get base of BSS
+;      moveq   #0,d1
+;      move.l  #__BSSLEN,d0            ; get length of BSS in longwords
+;      bra.s   .clr_lp                 ; and clear for length given
+;.clr_bss:
+;      move.l  d1,(a3)+
+;.clr_lp:
+;      dbf     d0,.clr_bss
+
+       move.l  a7,_StackPtr(a4)        ; Save StackPtr
+       move.l  a6,_SysBase(a4)         ; Save SysBase
+
+
+; Find our Task (Process) structure
+       suba.l  a1,a1
+       JSRLIB  FindTask                ; FindTask (NULL)
+       move.l  d0,a3
+       move.l  d0,_ThisTask(a4)
+
+
+;**************************************************************************
+; CPU checking
+;**************************************************************************
+
+       IFD     _MC68020_
+       btst.b  #AFB_68020,AttnFlags+1(a6)
+       beq.s   returnfail
+       ENDC
+
+
+;**************************************************************************
+; Open dos.library
+;**************************************************************************
+
+       lea     DOSName(PC),a1
+       moveq.l #37,d0
+       JSRLIB  OpenLibrary
+       move.l  d0,_DOSBase(a4)
+       beq.s   returnfail
+
+; Find output
+       move.l  d0,a6                   ; Load DOSBase in A6
+       JSRLIB  Output                  ; Call Output()
+       move.l  d0,_StdOut(a4)          ; Save result
+       move.l  _SysBase(a4),a6         ; Restore SysBase in A6
+
+; are we running as a son of Workbench?
+       move.l  pr_CurrentDir(a3),__curdir(a4)
+       move.l  pr_CLI(a3),d0
+       bne.s   cli_stack
+
+
+;**************************************************************************
+;* Workbench Startup Code: get for startup message
+;**************************************************************************
+
+       lea     pr_MsgPort(a3),a0       ; our process message port
+       JSRLIB  WaitPort
+       lea     pr_MsgPort(a3),a0       ; our process message port
+       JSRLIB  GetMsg
+       move.l  d0,_WBenchMsg(a4)
+
+       move.l  d0,a2                   ; get first argument
+       move.l  sm_ArgList(a2),d0
+       beq.s   .wb_stack
+
+       move.l  _DOSBase(a4),a6         ; CurrentDir()
+       move.l  d0,a0
+       move.l  wa_Lock(a0),d1
+       JSRLIB  DupLock
+       move.l  d0,__curdir(a4)
+       move.l  d0,d1
+       JSRLIB  CurrentDir
+       move.l  _SysBase(a4),a6 ; Restore SysBase in A6
+
+; Now calculate the remaining stack size (SP - TC_SPLOWER)
+
+.wb_stack:
+       move.l  sp,d0
+       sub.l   TC_SPLOWER(a3),d0
+       bra.s   swapstack
+
+
+cli_stack:
+
+; get the size of the stack cli_DefaultStack
+; D0 still holds process->pr_CLI
+
+       lsl.l   #2,d0                   ; Shift BPTR
+       move.l  d0,a0
+       move.l  cli_DefaultStack(a0),d0
+       lsl.l   #2,d0                   ; # longwords -> # bytes
+
+
+
+;**************************************************************************
+;* Stack swapping code
+;**************************************************************************
+
+swapstack:
+       cmpi.l  #STKSIZE,d0             ; D0 = current stack size
+       bcc.s   do_main
+
+
+; current stack is not as big as STKSIZE says it needs
+; to be.  Allocate a new one.
+
+       IFD     DEBUG
+
+       lea     .msg(pc),a0
+       PUSH.l  a0                      ; push format string
+       jsr     _kprintf                ; call kprintf()
+       addq    #4,sp                   ; fix stack
+       bra.s   .end_debug
+
+.msg:  dc.b    'Startup.asm: Allocating new stack...',$0A, 0
+       even
+.end_debug:
+
+       ENDC
+
+       move.l  #STKSIZE,d0
+
+       moveq.l #MEMF_PUBLIC,d1
+       JSRLIB  AllocMem
+       tst.l   d0
+       bne.s   .do_swap
+
+       moveq   #RETURN_FAIL,d7         ; Fail
+       bra.s   exitdos
+
+.do_swap:
+
+; Call StackSwap to set up the new stack
+
+       lea     stackswapstruct(a4),a0
+       move.l  d0,(a0)                 ; stk_Lower
+       add.l   #STKSIZE,d0             ; SP to top of new stack (base + size)
+       move.l  d0,stk_Pointer(a0)      ; stk_Pointer
+       move.l  d0,stk_Upper(a0)        ; stk_Upper
+       JSRLIB  StackSwap
+
+
+
+;**************************************************************************
+; Call main()
+;**************************************************************************
+
+do_main:
+
+       IFD     DEBUG
+; Fill all free stack space with a magic number to check
+; maximum program stack usage later.
+
+       move.l  #$DEADF00D,d0
+       move.l  TC_SPLOWER(a3),a0
+
+.fillstack:
+       move.l  d0,(a0)+
+       cmp.l   a0,sp
+       bgt.s   .fillstack
+
+       ENDC
+
+
+       jsr     ___main(pc)             ; call C entrypoint
+
+
+@XCEXIT:                               ; XCEXIT() routine
+@_XCEXIT:
+
+; Save Return Code
+       move.l  d0,d7
+
+       IFD     DEBUG
+
+; Check maximum stack used during execution.
+; Search the first longword not containing our magic number.
+;
+;           <- TC_SPUPPER     <- top of stack
+; |xxxxxxxx|               \
+; |xxxxxxxx|               |- stack used by startup code
+; |xxxxxxxx|               /
+; |yyyyyyyy|<- sp          \  <- current stack position
+; |yyyyyyyy|               |
+; |yyyyyyyy|               |
+; |yyyyyyyy|               |- stack used by C program
+; |yyyyyyyy|               |
+; |yyyyyyyy|               |
+; |yyyyyyyy|<- Pivot (d0)  /  <- last used longword
+; |DEADF00D|               \
+; |DEADF00D|               |
+; |DEADF00D|               |- unused stack
+; |DEADF00D|               |
+; |DEADF00D|<- TC_SPLOWER  /  <- bottom of stack
+;
+
+       move.l  TC_SPLOWER(a3),a0
+       move.l  #$DEADF00D,d0
+
+.cmpstack:
+       move.l  (a0)+,d1                ; read one longword and go on next
+       cmp.l   d0,d1                   ; is the magic number still there?
+       beq.s   .cmpstack               ; yes: loop again
+
+       move.l  a0,d0                   ; make a copy of the Pivot
+
+       move.l  TC_SPLOWER(a3),d1
+       sub.l   d1,d0                   ; Pivot - TC_SPLOWER
+       PUSH.l  d0                      ; push unused stack size (argument 4)
+
+       move.l  TC_SPUPPER(a3),d0
+       sub.l   a0,d0                   ; TC_SPUPPER - Pivot
+       PUSH.l  d0                      ; push used stack size (argument 3)
+
+       move.l  TC_SPUPPER(a3),d0
+       sub.l   d1,d0                   ; TC_SPUPPER - TC_SPLOWER
+       PUSH.l  d0                      ; push total stack size (argument 2)
+
+       pea     .msg(pc)                ; push format string (argument 1)
+       jsr     _kprintf                ; output debug string with kprintf()
+
+       lea     16(sp),sp               ; fix stack (4 arguments)
+       bra.s   .end_debug
+
+.msg:  dc.b    'Startup.asm: Total stack size: %ld,  Used: %ld,  Unused: %ld.',$0A, 0
+       even
+.end_debug:
+
+       move.l  d7,d0                   ; restore return code
+       ENDC
+
+
+
+; Swap back to original stack
+       move.l  _SysBase(a4),a6
+       lea     stackswapstruct(a4),a0
+       tst.l   (a0)
+       beq.s   .noswap
+
+       JSRLIB  StackSwap
+
+; free the stack
+       move.l  stackswapstruct(a4),a1  ; FreeMem (stk_Lower,STKSIZE)
+       move.l  #STKSIZE,d0
+       JSRLIB  FreeMem
+
+.noswap:
+       move.l  _StackPtr(a4),a2        ; restore original StackPtr
+
+; if we ran from CLI, skip workbench cleanup:
+       tst.l   _WBenchMsg(a4)
+       beq.s   exitdos
+       move.l  _DOSBase(a4),a6
+       move.l  __curdir(a4),d1
+       beq.s   .nounlock
+       JSRLIB  UnLock
+
+.nounlock:
+
+; return the startup message to our parent.
+; We Forbid() so Workbench can't UnLoadSeg() us
+; before we are done.
+
+       move.l  _SysBase(a4),a6
+       JSRLIB  Forbid
+       move.l  _WBenchMsg(a4),a1
+       JSRLIB  ReplyMsg
+
+exitdos:
+       move.l  _DOSBase(a4),a1         ; Close dos.library
+       JSRLIB  CloseLibrary
+       move.l  d7,d0                   ; Put return code in D0
+       rts                             ; This RTS sends us back to our caller
+
+returnfail:
+       moveq.l #RETURN_FAIL,d0
+       rts
+
+
+
+;**************************************************************************
+;* Simple (V)SPrintf routines
+;**************************************************************************
+
+_SPrintf:
+       movem.l a2-a3/a6,-(sp)          ; Save registers
+
+       move.l   4+12(sp),a3            ; Get destination buffer
+       move.l   8+12(sp),a0            ; Get format string
+       lea.l   12+12(sp),a1            ; Get arguments
+       lea.l   StuffChar(pc),a2        ; Get formatting routine
+
+       move.l  _SysBase(a4),a6         ; Get ExecBase
+       JSRLIB  RawDoFmt                ; Format the string
+
+       movem.l (sp)+,a2-a3/a6          ; Restore registers
+
+       rts
+
+
+
+_VSPrintf:
+       movem.l a2/a3/a6,-(sp)
+
+       move.l   4+12(sp),a3
+       move.l   8+12(sp),a0
+       move.l  12+12(sp),a1
+       lea     StuffChar(pc),a2
+
+       move.l  _SysBase(a4),a6
+       JSRLIB  RawDoFmt
+
+       movem.l (sp)+,a2/a3/a6
+
+       rts
+
+StuffChar:
+       move.b  d0,(a3)+
+       rts
+
+
+
+;**************************************************************************
+;* Memory pools support
+;**************************************************************************
+
+;
+; AsmAllocVecPooled (Pool, memSize, SysBase)
+;                    a0    d0       a6
+;
+_AsmAllocVecPooled:
+       addq.l  #4,d0           ; Get space for tracking
+       move.l  d0,-(sp)        ; Save the size
+
+ IFD   OS30_ONLY
+       JSRLIB  AllocPooled
+ ELSE
+       jsr     _AsmAllocPooled ; Call pool...
+ ENDC
+
+       move.l  (sp)+,d1        ; Get size back...
+       tst.l   d0              ; Check for error
+       beq.s   .fail           ; If NULL, failed!
+       move.l  d0,a0           ; Get pointer...
+       move.l  d1,(a0)+        ; Store size
+       move.l  a0,d0           ; Get result
+.fail  rts                     ; Return
+
+
+
+;
+; AsmFreeVecPooled (Pool, Memory, SysBase)
+;                   a0    a1      a6
+
+_AsmFreeVecPooled:
+       move.l  a1,d0           ; Test for NULL
+       beq.s   .noblock
+       move.l  -(a1),d0        ; Get size / ajust pointer
+
+ IFD OS30_ONLY
+       JMPLIB  FreePooled
+ ELSE
+       jmp     _AsmFreePooled
+ ENDC
+
+.noblock
+       rts
+
+;
+; CAllocPooled (Pool, memSize, SysBase)
+;               a0    d0       a6
+
+_AsmCAllocPooled:
+       move.l  d0,-(sp)        ; Save the size
+ IFD OS30_ONLY
+       JSRLIB  AllocPooled     ; Allocate memory from pool...
+ ELSE
+       jsr     _AsmAllocPooled ; Allocate memory from pool...
+ ENDC
+       move.l  (sp)+,d1        ; Get size back...
+       move.l  d0,a0
+       tst.l   d0              ; Check for error
+       beq.s   .fail           ; If NULL, failed!
+
+       move.l  d0,-(sp)        ; Save result
+
+
+       moveq   #$F,d0          ; Check for odd length (not multiple of 16)
+       and.w   d1,d0
+       beq.s   .noneed
+
+       move.l  a0,a1
+       add.l   d1,a1
+       subq.w  #1,d0           ; DBRA loops once too much for us
+
+.clear2        move.b  #0,-(a1)        ; Clear up to 15 bytes
+       dbra    d0,.clear2
+
+.noneed
+       lsr.l   #4,d1           ; Divide by 16
+       tst.l   d1              ; Check for 0
+       beq.s   .endclear
+
+       moveq   #0,d0           ; D0 will be used to clear our memory buffer
+       subq.l  #1,d1           ; DBRA does one cycle more!
+
+.clear move.l  d0,(a0)+        ; Clear memory block
+       move.l  d0,(a0)+
+       move.l  d0,(a0)+
+       move.l  d0,(a0)+
+       dbra    d1,.clear
+       swap    d1              ; dbra only works on words
+       subq.w  #1,d1
+       bmi.s   .endclear
+       swap    d1
+       bra.s   .clear
+.endclear
+
+       move.l  (sp)+,d0        ; Get result back...
+.fail  rts                     ; Return
+
+
+
+DOSName        dc.b    'dos.library',0
+
+
+
+;**************************************************************************
+;* Data section
+;**************************************************************************
+
+       SECTION __MERGED,BSS
+
+_SysBase       ds.l    1
+_DOSBase       ds.l    1
+_ThisTask      ds.l    1
+_StdOut                ds.l    1
+_StackPtr      ds.l    1
+_WBenchMsg     ds.l    1
+__curdir       ds.l    1
+
+stackswapstruct        ds.b    StackSwapStruct_SIZEOF
+
+       END
diff --git a/ToolBoxWin.c b/ToolBoxWin.c
new file mode 100644 (file)
index 0000000..b01908d
--- /dev/null
@@ -0,0 +1,490 @@
+/*
+**     ToolBoxWin.c
+**
+**     Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
+**
+**     Handle ToolBox panel
+*/
+
+#include <exec/memory.h>
+#include <intuition/intuition.h>
+#include <intuition/gadgetclass.h>
+#include <libraries/gadtools.h>
+#include <libraries/locale.h>
+#include <workbench/workbench.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/wb.h>
+#include <proto/asl.h>
+#include <proto/icon.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* Gadgets IDs */
+
+enum
+{
+       GD_ToolBoxGroup0,
+               GD_ToolBoxGroup1,
+                       GD_ToolBoxGroup2,
+                               GD_Play,
+                               GD_EditSongs,
+                       GD_ToolBoxGroup3,
+                               GD_EditPatterns,
+                               GD_EditInstruments,
+                       GD_ToolBoxGroup4,
+                               GD_EditSequence,
+                               GD_Optimization,
+       ToolBox_CNT
+};
+
+
+
+/*****************************/
+/* Local function prototypes */
+/*****************************/
+
+static void EditInstrumentsClicked     (void);
+static void EditSequenceClicked        (void);
+static void EditPatternsClicked        (void);
+static void OptimizationClicked        (void);
+static void PlayClicked                                (void);
+static void EditSongsClicked           (void);
+
+static void ToolBoxMiNew                       (void);
+static void ToolBoxMiOpen                      (void);
+static void ToolBoxMiOpenNew           (void);
+static void ToolBoxMiSave                      (void);
+static void ToolBoxMiSaveAs                    (void);
+static void ToolBoxMiSaveFormat                (void);
+static void ToolBoxMiClearMod          (void);
+static void ToolBoxMiAbout                     (void);
+static void ToolBoxMiHelp                      (void);
+static void ToolBoxMiIconify           (void);
+static void ToolBoxMiQuit                      (void);
+static void ToolBoxMiUserInterface     (void);
+static void ToolBoxMiSaveIcons         (void);
+static void ToolBoxMiOverwrite         (void);
+static void ToolBoxMiAskExit           (void);
+static void ToolBoxMiOpenSettings      (void);
+static void ToolBoxMiSaveSettings      (void);
+static void ToolBoxMiSaveSettingsAs    (void);
+
+
+
+static struct NewMenu ToolBoxNewMenu[] = {
+       NM_TITLE, (STRPTR)MSG_PROJECT_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_NEW_MEN, (STRPTR)"N", 0, 0L, (APTR)ToolBoxMiNew,
+       NM_ITEM, (STRPTR)MSG_OPEN_MEN, (STRPTR)"O", 0, 0L, (APTR)ToolBoxMiOpen,
+       NM_ITEM, (STRPTR)MSG_OPEN_NEW_MEN, (STRPTR)"Y", 0, 0L, (APTR)ToolBoxMiOpenNew,
+       NM_ITEM, (STRPTR)MSG_SAVE_MEN, (STRPTR)"S", 0, 0L, (APTR)ToolBoxMiSave,
+       NM_ITEM, (STRPTR)MSG_SAVE_AS_MEN, (STRPTR)"A", 0, 0L, (APTR)ToolBoxMiSaveAs,
+       NM_ITEM, (STRPTR)MSG_SAVE_FORMAT_MEN, (STRPTR)"F", 0, 0L, (APTR)ToolBoxMiSaveFormat,
+       NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
+       NM_ITEM, (STRPTR)MSG_CLEAR_MEN, (STRPTR)"K", 0, 0L, (APTR)ToolBoxMiClearMod,
+       NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
+       NM_ITEM, (STRPTR)MSG_ABOUT_MEN, (STRPTR)"?", 0, 0L, (APTR)ToolBoxMiAbout,
+       NM_ITEM, (STRPTR)MSG_HELP_MEN, NULL, 0, 0L, (APTR)ToolBoxMiHelp,
+       NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
+       NM_ITEM, (STRPTR)MSG_ICONIFY_MEN, (STRPTR)"I", 0, 0L, (APTR)ToolBoxMiIconify,
+       NM_ITEM, (STRPTR)MSG_QUIT_MEN, (STRPTR)"Q", 0, 0L, (APTR)ToolBoxMiQuit,
+       NM_TITLE, (STRPTR)MSG_SETTINGS_MEN, NULL, 0, NULL, NULL,
+       NM_ITEM, (STRPTR)MSG_USER_INTERFACE_MEN, NULL, 0, 0L, (APTR)ToolBoxMiUserInterface,
+       NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
+       NM_ITEM, (STRPTR)MSG_SAVE_ICONS_MEN, NULL, CHECKIT|MENUTOGGLE, 0L, (APTR)ToolBoxMiSaveIcons,
+       NM_ITEM, (STRPTR)MSG_CONFIRM_OVERWRITE_MEN, NULL, CHECKIT|MENUTOGGLE, 0L, (APTR)ToolBoxMiOverwrite,
+       NM_ITEM, (STRPTR)MSG_CONFIRM_EXIT_MEN, NULL, CHECKIT|MENUTOGGLE, 0L, (APTR)ToolBoxMiAskExit,
+       NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
+       NM_ITEM, (STRPTR)MSG_OPEN_SETTINGS_MEN, NULL, 0, 0L, (APTR)ToolBoxMiOpenSettings,
+       NM_ITEM, (STRPTR)MSG_SAVE_SETTINGS_MEN, NULL, 0, 0L, (APTR)ToolBoxMiSaveSettings,
+       NM_ITEM, (STRPTR)MSG_SAVE_SETTINGS_AS_MEN, NULL, 0, 0L, (APTR)ToolBoxMiSaveSettingsAs,
+       NM_END, NULL, NULL, 0, 0L, NULL
+};
+
+
+
+static ULONG ToolBoxArgs[] =
+{
+       HGROUP_KIND, 0,
+               VGROUP_KIND, 0,
+                       BUTTON_KIND, (ULONG)PlayClicked,                        MSG_PLAY_GAD,                   TAG_DONE,
+                       BUTTON_KIND, (ULONG)EditSongsClicked,           MSG_SONGS_GAD,                  TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       BUTTON_KIND, (ULONG)EditPatternsClicked,        MSG_PATTERNS_DOTS_GAD,  TAG_DONE,
+                       BUTTON_KIND, (ULONG)EditInstrumentsClicked,     MSG_INSTRUMENTS_GAD,    TAG_DONE,
+                       ENDGROUP_KIND,
+               VGROUP_KIND, 0,
+                       BUTTON_KIND, (ULONG)EditSequenceClicked,        MSG_SEQUENCE_DOTS_GAD,  TAG_DONE,
+                       BUTTON_KIND, (ULONG)OptimizationClicked,        MSG_OPTIMIZATION_GAD,   TAG_DONE,
+                       ENDGROUP_KIND,
+               ENDGROUP_KIND,
+       ENDGROUP_KIND
+};
+
+
+
+XDEF LONG ToolBoxWinTags[] =
+{
+       XMWIN_NewMenu,          (LONG)ToolBoxNewMenu,
+       XMWIN_LayoutArgs,       (LONG)ToolBoxArgs,
+       XMWIN_GCount,           ToolBox_CNT,
+       XMWIN_Title,            MSG_TOOLBOX_TITLE,
+       XMWIN_WindowFlags,      WFLG_CLOSEGADGET | WFLG_ACTIVATE,
+       XMWIN_IDCMPFlags,       BUTTONIDCMP|IDCMP_MENUPICK|IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW,
+       XMWIN_PostOpenFunc,     (LONG)UpdateGuiSwitches,
+       XMWIN_HelpNode,         (LONG)"ToolBox",
+       TAG_DONE
+};
+
+
+
+
+/*********************/
+/* ToolBox Functions */
+/*********************/
+
+
+
+GLOBALCALL void ToolBoxDropIcon (struct AppMessage *msg)
+{
+       struct WBArg    *wba = msg->am_ArgList;
+       BPTR olddir;
+       ULONG i;
+       struct SongInfo *si;
+
+       LockWindows();
+
+       ObtainSemaphore (&XModuleBase->xm_BaseLock);
+
+       olddir = CurrentDir (wba->wa_Lock);
+
+       for (i = 0; i < msg->am_NumArgs; i++)
+       {
+               CurrentDir (wba->wa_Lock);
+
+               if (si = xmLoadModule (wba->wa_Name,
+                       XMSNG_OldSong,          i ? NULL : XModuleBase->xm_CurrentSong,
+                       XMSNG_AddToList,        -1,
+                       XMSNG_Active,           i == (msg->am_NumArgs - 1),
+                       TAG_DONE))
+                       ReleaseSemaphore (&si->Lock);   /* For XMSNG_AddToList */
+
+               wba++;
+       }
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+       CurrentDir (olddir);
+       UnlockWindows();
+}
+
+
+
+GLOBALCALL void ToolBoxOpenModule (CONST_STRPTR file, ULONG num, ULONG count)
+
+/* Handle FileRequester Open Module message */
+{
+       struct SongInfo *si;
+
+       LockWindows();
+
+       ObtainSemaphore (&XModuleBase->xm_BaseLock);
+
+       DB(kprintf ("opening song %ld of %ld\n", num, count));
+
+       if (si = xmLoadModule (file,
+               XMSNG_AddToList,        num ? (ULONG)XModuleBase->xm_CurrentSong : (ULONG)~0,
+               XMSNG_Active,           num == (count - 1),
+               XMSNG_OldSong,          num ? (ULONG)NULL : (ULONG)XModuleBase->xm_CurrentSong,
+               TAG_DONE))
+               ReleaseSemaphore (&si->Lock);   /* This is for XMSNG_AddToList */
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+       UnlockWindows();
+}
+
+
+GLOBALCALL void UpdateGuiSwitches ()
+{
+       struct MenuItem *item;
+       struct WinUserData *wud = WDescr[WID_TOOLBOX].Wud;
+
+       if (wud && wud->MenuStrip)
+       {
+               if (wud->Win) ClearMenuStrip (wud->Win);
+
+               item = ItemAddress (wud->MenuStrip, SHIFTMENU(1) | SHIFTITEM(2) );
+
+               /* Save Icons? */
+               if (GuiSwitches.SaveIcons)
+                       item->Flags |= CHECKED;
+               else
+                       item->Flags &= ~CHECKED;
+
+               /* Confirm Overwrite? */
+               item = item->NextItem;
+               if (GuiSwitches.AskOverwrite)
+                       item->Flags |= CHECKED;
+               else
+                       item->Flags &= ~CHECKED;
+
+               /* Confirm Exit? */
+               item = item->NextItem;
+               if (GuiSwitches.AskExit)
+                       item->Flags |= CHECKED;
+               else
+                       item->Flags &= ~CHECKED;
+
+               if (wud->Win) ResetMenuStrip (wud->Win, wud->MenuStrip);
+       }
+}
+
+
+
+/*******************/
+/* ToolBox Gadgets */
+/*******************/
+
+static void EditInstrumentsClicked (void)
+{
+       NewWindow (WID_INSTRUMENTS);
+}
+
+static void EditSequenceClicked (void)
+{
+       NewWindow (WID_SEQUENCE);
+}
+
+static void OptimizationClicked (void)
+{
+       NewWindow (WID_OPTIMIZATION);
+}
+
+static void PlayClicked (void)
+{
+       NewWindow (WID_PLAY);
+}
+
+static void EditSongsClicked (void)
+{
+       NewWindow (WID_SONGINFO);
+}
+
+static void EditPatternsClicked (void)
+{
+       NewWindow (WID_PATTERN);
+}
+
+
+
+/**************/
+/* Menu Items */
+/**************/
+
+static void ToolBoxMiNew (void)
+{
+       struct SongInfo *si;
+
+       if (si = xmCreateSong (
+               SNGA_ReadyToUse,        TRUE,
+               XMSNG_AddToList,        -1,
+               XMSNG_Active,           TRUE,
+               TAG_DONE))
+               ReleaseSemaphore (&si->Lock);   /* This is for XMSNG_AddToList */
+       else
+               DisplayBeep (NULL);
+}
+
+
+
+static void ToolBoxMiOpen (void)
+{
+       StartFileRequest (FREQ_LOADMOD, ToolBoxOpenModule);
+}
+
+
+
+static void ToolBoxMiOpenNew (void)
+{
+       ToolBoxMiNew();
+       ToolBoxMiOpen();
+}
+
+
+
+static void ToolBoxMiSave (void)
+{
+       struct SongInfo *si;
+
+       LockWindows();
+
+       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+
+       if (si = XModuleBase->xm_CurrentSong)
+       {
+               ObtainSemaphoreShared (&si->Lock);
+
+               if (!(LastErr = xmSaveModuleA (si, si->Path, NULL, NULL)))
+                       si->Changes = 0;
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+       UnlockWindows();
+}
+
+
+
+static void ToolBoxMiSaveAs (void)
+{
+       struct SongInfo *si;
+       UBYTE pathname[PATHNAME_MAX];
+
+       LockWindows();
+
+       ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
+
+       if (si = XModuleBase->xm_CurrentSong)
+       {
+               ObtainSemaphoreShared (&si->Lock);
+
+               if (si->Path)
+               {
+                       strncpy (pathname, si->Path, PATHNAME_MAX - 1);
+                       pathname[PATHNAME_MAX - 1] = '\0';
+               }
+               else pathname[0] = '\0';
+
+               if (FileRequest (FREQ_SAVEMOD, pathname))
+               {
+                       SetAttrs (si,
+                               SNGA_Path, pathname,
+                               TAG_DONE);
+
+                       ToolBoxMiSave();
+               }
+
+               ReleaseSemaphore (&si->Lock);
+       }
+
+       ReleaseSemaphore (&XModuleBase->xm_BaseLock);
+
+       UnlockWindows();
+}
+
+
+
+static void ToolBoxMiClearMod (void)
+{
+       NewWindow (WID_CLEAR);
+}
+
+
+
+static void ToolBoxMiAbout (void)
+{
+       ShowRequest (MSG_ABOUT_TEXT, MSG_CONTINUE,
+               XMODULEVER " " XMODULEDATE " " BUILDMODE,
+               XMODULECOPY,
+               AvailMem (MEMF_CHIP) >> 10,
+               AvailMem (MEMF_FAST) >> 10,
+               ScrInfo.PubScreenName[0] ? ScrInfo.PubScreenName : STR(MSG_DEFAULT),
+               PubPort ? PubPortName : STR(MSG_DISABLED),
+               CxPort ? CxPopKey : STR(MSG_DISABLED),
+               Catalog ? Catalog->cat_Language : (UBYTE *)"English");
+}
+
+
+
+static void ToolBoxMiHelp (void)
+{
+       HandleHelp (NULL);
+}
+
+static void ToolBoxMiIconify (void)
+{
+       Iconify();
+       DoNextSelect = FALSE;
+}
+
+
+
+static void ToolBoxMiQuit (void)
+{
+       DoNextSelect = 0;
+       Quit = 1;
+}
+
+
+
+static void ToolBoxMiSaveFormat (void)
+{
+       NewWindow (WID_SAVERS);
+}
+
+static void ToolBoxMiUserInterface (void)
+{
+       NewWindow (WID_PREFS);
+}
+
+
+
+static void ToolBoxMiSaveIcons (void)
+{
+       GuiSwitches.SaveIcons ^= 1;
+}
+
+
+
+static void ToolBoxMiOverwrite (void)
+{
+       GuiSwitches.AskOverwrite ^= 1;
+}
+
+
+
+static void ToolBoxMiAskExit (void)
+{
+       GuiSwitches.AskExit ^= 1;
+}
+
+
+
+static void ToolBoxMiOpenSettings (void)
+{
+       UBYTE filename[PATHNAME_MAX];
+
+       strcpy (filename, "XModule.prefs");
+
+       if (FileRequest (FREQ_LOADMISC, filename))
+               LastErr = LoadPrefs (filename);
+}
+
+
+
+static void ToolBoxMiSaveSettings (void)
+{
+       LastErr = SavePrefs ("PROGDIR:XModule.prefs");
+}
+
+
+
+static void ToolBoxMiSaveSettingsAs (void)
+{
+       UBYTE filename[PATHNAME_MAX];
+
+       strcpy (filename, "PROGDIR:XModule.prefs");
+
+       if (FileRequest (FREQ_SAVEMISC, filename))
+               LastErr = SavePrefs (filename);
+}
diff --git a/TrackerHook.c b/TrackerHook.c
new file mode 100644 (file)
index 0000000..edc7fbf
--- /dev/null
@@ -0,0 +1,1049 @@
+/*
+**     TrackerHook.c
+**
+**     Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
+**
+**
+**     Load a Sound/Noise/ProTracker module with 15 or 31 instruments, decode
+**     it and store into internal structures.
+**
+**     Save internal structures to a SoundTracker module with 15 instruments
+**     or to a 31 instruments Noise/ProTracker unpacked module.
+*/
+
+#include <exec/memory.h>
+#include <dos/stdio.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/iffparse.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+
+/* This structure holds a tracker row */
+
+struct StRow
+{
+       UWORD Note;
+       UBYTE InstEff;
+       UBYTE EffVal;
+};
+
+
+
+/*     Definitions for various xxxTracker IDs. */
+
+#define ID_SOUNDTRACKER                'ST15'          /* "ST15" */
+#define ID_TAKETRACKER         '32CH'          /* "32CH" */
+#define ID_FASTTRACKER1                '8CHN'          /* "8CHN" */
+#define ID_NOISETRACKER                0x4D264B21      /* "M&K!" */
+#define ID_PROTRACKER          0x4D2E4B2E      /* "M.K." */
+#define ID_PROTRACKER100       0x4D214B21      /* "M!K!" */
+#define ID_STARTREKKER4                0x464C5434      /* "FLT4" */
+#define ID_STARTREKKER8                0x464C5438      /* "FLT8" */
+#define ID_UNICTRACKER         0x454D5733      /* "EMW3" */
+#define ID_POWERMUSIC          0x21504D21      /* "!PM!" */
+
+
+
+/* Local function prototypes */
+static HOOKCALL struct XMHook *IdentifyTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags));
+static HOOKCALL struct XMHook *IdentifySoundTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags));
+static HOOKCALL LONG SaveTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *saver),
+       REG(a2, ULONG *tags));
+static HOOKCALL LONG LoadTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *loader),
+       REG(a2, ULONG *tags));
+
+static void SetGlobalSpeed (struct SongInfo *si, UWORD tracks);
+INLINE UWORD DecodeNote (UWORD Note, UWORD Patt, UWORD Line, UWORD Track);
+INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval);
+
+
+static const ULONG TakeTrackerIDs[32] =
+{
+       'TDZ1', 'TDZ2', 'TDZ3', 'M.K.', '5CHN', '6CHN', '7CHN', '8CHN',
+       '9CHN', '10CH', '11CH', '12CH', '13CH', '14CH', '15CH', '16CH',
+       '17CH', '18CH', '19CH', '20CH', '21CH', '22CH', '23CH', '24CH',
+       '25CH', '26CH', '27CH', '28CH', '29CH', '30CH', '31CH', '32CH'
+};
+
+
+/* Note conversion table for Sound/Noise/ProTracker */
+static const UWORD TrackerNotes[] =
+{
+       0x000,                                                                          /* Null note */
+
+       0x6B0, 0x650, 0x5F5, 0x5A0, 0x54D, 0x501,       /* Octave 0 */
+       0x4B9, 0x475, 0x435, 0x3F9, 0x3C1, 0x38B,
+
+       0x358, 0x328, 0x2FA, 0x2D0, 0x2A6, 0x280,       /* Octave 1 */
+       0x25C, 0x23A, 0x21A, 0x1FC, 0x1E0, 0x1C5,
+
+       0x1AC, 0x194, 0x17D, 0x168, 0x153, 0x140,       /* Octave 2 */
+       0x12E, 0x11d, 0x10D, 0x0FE, 0x0F0, 0x0E2,
+
+       0x0D6, 0x0CA, 0x0BE, 0x0B4, 0x0AA, 0x0A0,       /* Octave 3 */
+       0x097, 0x08F, 0x087, 0x07F, 0x078, 0x071,
+
+       0x06B, 0x065, 0x05F, 0x05A, 0x055, 0x050,       /* Octave 4 */
+       0x04C, 0x047, 0x043, 0x040, 0x03C, 0x039,
+
+       0x035, 0x032, 0x030, 0x02D, 0x02A, 0x028,       /* Octave 5 */
+       0x026, 0x024, 0x022, 0x020, 0x01E, 0x01C
+};
+
+
+
+static const UBYTE Effects[MAXTABLEEFFECTS] =
+{
+/*    STRK     XModule                 Val */
+
+       0x00,   /*      Null effect             $00     */
+
+       0x01,   /*      Portamento Up   $01     */
+       0x02,   /*      Portamento Down $02     */
+       0x03,   /*      Tone Portamento $03     */
+       0x04,   /*      Vibrato                 $04     */
+       0x05,   /*      ToneP + VolSl   $05     */
+       0x06,   /*      Vibra + VolSl   $06     */
+       0x07,   /*      Tremolo                 $07     */
+       0x08,   /*      Set Hold/Decay  $08     */
+       0x09,   /*      Sample Offset   $09     */
+       0x0A,   /*      Volume Slide    $0A     */
+       0x0B,   /*      Position Jump   $0B     */
+       0x0C,   /*      Set Volume              $0C     */
+       0x0D,   /*      Pattern break   $0D     */
+       0x0E,   /*      Misc                    $0E     */
+       0x0F,   /*      Set Speed               $0F     */
+       0x0F,   /*      Set Tempo               $10     */
+       0x00,   /*      Arpeggio                $11     */
+
+       0x03,   /*      Oktalyzer H                     */
+       0x03,   /*      Oktalyzer L                     */
+};
+
+
+
+static struct XMHook *ProTrackerLoader = NULL;
+
+
+
+static HOOKCALL struct XMHook *IdentifyTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags))
+
+/* Determine if the given file is an XyzTracker module.
+ * Note: the file position will be changed on exit.
+ */
+{
+       LONG id;
+       ULONG i;
+
+       Seek (fh, 1080, OFFSET_BEGINNING);
+       if (FRead (fh, &id, 4, 1) != 1)
+               return NULL;
+
+       /* Check Noise/ProTracker */
+       if ((id == ID_PROTRACKER) || (id == ID_PROTRACKER100) || (id == ID_NOISETRACKER) ||
+               (id == ID_UNICTRACKER) || (id == ID_STARTREKKER4))
+               return loader;
+
+       /* Check for Multi/Fast Tracker */
+       for (i = 0; i < 32; i++)
+               if (id == TakeTrackerIDs[i])
+                       return loader;
+
+       return NULL;
+}
+
+
+
+static HOOKCALL struct XMHook *IdentifySoundTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags))
+
+/* Last chance loader: SoundTracker15 or weird ProTracker module */
+{
+       switch (ShowRequestArgs (MSG_UNKNOWN_MOD_FORMAT, MSG_SOUND_PRO_CANCEL, NULL))
+       {
+               case 1:
+                       return loader;
+
+               case 2:
+                       return ProTrackerLoader;
+
+               default:
+                       return NULL;
+       }
+}
+
+
+
+static HOOKCALL LONG SaveTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *saver),
+       REG(a2, ULONG *tags))
+{
+       struct Instrument *instr;
+       ULONG   pattcount = 0, maxnumpatt = 64, numtracks = 4, songlength;
+       ULONG   l;
+       ULONG   i, j, k;        /* Loop counters */
+       UBYTE c;
+       struct { UWORD Note; UBYTE InstEff; UBYTE EffVal; }
+               strow; /* Temporary storage for a SoundTracker row */
+
+
+
+       /* Calculate pattcount */
+
+       songlength = min (si->Length, 128);
+
+       for (i = 0 ; i < songlength ; i++)
+               if (si->Sequence[i] > pattcount)
+                       pattcount = si->Sequence[i];
+
+       pattcount++;    /* Pattern numbering starts from 0 */
+
+
+       /* Write Song name */
+       {
+               UBYTE name[20] = {0};
+
+               /* Tracker modules do not require the NULL terminator */
+               if (si->Title) strncpy (name, si->Title, 20);
+
+               if (FWrite (fh, name, 20, 1) != 1) return ERROR_IOERR;
+       }
+
+
+       /* Write instruments name, length, volume, repeat, replen */
+
+       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+               (APTR)MSG_WRITING_INSTINFO, NULL);
+
+
+       for ( j = 1 ; j < (((ULONG)saver->xmh_ID == ID_SOUNDTRACKER) ? 16 : 32) ; j++ )
+       {
+               struct
+               {
+                       UBYTE namebuf[22];
+                       UWORD length;
+                       UWORD volume;
+                       UWORD repeat;
+                       UWORD replen;
+               } modinstr;
+
+
+               memset (&modinstr, 0, sizeof (modinstr));
+
+               if (instr = si->Instr[j])
+               {
+                       /* Some Trackers require the instrument buffer to be
+                        * padded with 0's.
+                        */
+                       strncpy (modinstr.namebuf, instr->Name, 22);
+
+                       /* SoundTracker cannot handle instruments longer than 64K */
+                       if (instr->Length > 0xFFFE)
+                       {
+                               modinstr.length = 0x7FFF;
+                               xmDisplayMessage (XMDMF_USECATALOG | XMDMF_WARNING,
+                                       (APTR)MSG_INSTR_TOO_LONG, j);
+                       }
+                       else modinstr.length = instr->Length >> 1;
+
+                       if (!instr->Sample) modinstr.length = 0;
+
+
+                       if (saver->xmh_ID == ID_SOUNDTRACKER)
+                               modinstr.volume = instr->Volume;
+                       else
+                               modinstr.volume = instr->Volume | ((instr->FineTune & 0x0F) << 8);
+
+                       modinstr.repeat = instr->Repeat >> 1;
+                       modinstr.replen = instr->Replen >> 1;
+                       if (!modinstr.replen) modinstr.replen = 1;
+               }
+
+               if (FWrite (fh, &modinstr, sizeof (modinstr), 1) != 1)
+                       return ERROR_IOERR;
+       }
+
+
+       /******************************************/
+       /* Put number of positions in song (BYTE) */
+       /******************************************/
+
+       if (FPutC (fh, songlength) == ENDSTREAMCH)
+               return ERROR_IOERR;
+
+
+       /*****************************************/
+       /* Put number of patterns in song (BYTE) */
+       /*****************************************/
+
+       switch (saver->xmh_ID)
+       {
+               case ID_SOUNDTRACKER:
+                       /* SoundTracker stores the number of patterns here */
+                       c = pattcount;
+                       break;
+
+               case ID_STARTREKKER4:
+                       /* StarTrekker modules store restart value here */
+                       c = si->RestartPos;
+                       break;
+
+               default:
+                       /* Noise/ProTracker stores $7F as the number of patterns.
+                        * The correct number of patterns is calculated by looking
+                        * for the highest pattern referenced in the position table.
+                        * Therefore, unused patterns MUST be removed, or the Tracker
+                        * will get confused loading the module.
+                        */
+                       c = 0x7F;
+                       break;
+       }
+
+       if (FPutC (fh, c) == ENDSTREAMCH)
+               return ERROR_IOERR;
+
+
+       /********************/
+       /* Choose module ID */
+       /********************/
+
+       l = saver->xmh_ID;
+
+       switch (l)
+       {
+               case ID_PROTRACKER:
+                       if (pattcount > 64)
+                       {
+                               l = ID_PROTRACKER100;
+                               maxnumpatt = 100;
+                               xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
+                                       (APTR)MSG_EXCEEDS_64_PATTS, NULL);
+                       }
+                       break;
+
+               case ID_PROTRACKER100:
+                       maxnumpatt = 100;
+                       if (pattcount <= 64)
+                               l = ID_PROTRACKER;
+                       break;
+
+               case ID_TAKETRACKER:
+                       numtracks = si->MaxTracks;
+                       l = TakeTrackerIDs[si->MaxTracks - 1];
+                       maxnumpatt = 100;
+                       break;
+
+               default:
+                       break;
+       }
+
+       if (pattcount >= maxnumpatt)
+       {
+               pattcount = maxnumpatt;
+               xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
+                       (APTR)MSG_EXCEEDS_MAXPAATTS, maxnumpatt);
+       }
+
+
+       /************************/
+       /* Write position table */
+       /************************/
+       {
+               UBYTE postable[128];
+
+               memset (postable, 0, 128);
+
+               /* All this is needed because ProTracker has serious
+                * problems dealing with modules whose postable has
+                * references to inexistant patterns.
+                */
+               for (i = 0; i < songlength; i++)
+               {
+                       postable[i] = si->Sequence[i];
+
+                       if (postable[i] >= pattcount)
+                               postable[i] = 0;
+               }
+
+               if (FWrite (fh, postable, 1, 128) != 128) return ERROR_IOERR;
+       }
+
+
+       /*******************/
+       /* Write module ID */
+       /*******************/
+
+       if (l != ID_SOUNDTRACKER)
+               if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
+
+
+
+       /**********************/
+       /* Write pattern data */
+       /**********************/
+
+       SetGlobalSpeed (si, numtracks);
+
+       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+               (APTR)MSG_WRITING_PATTS, NULL);
+
+       for (i = 0 ; i < pattcount ; i++)
+       {
+               struct Note **tracks = si->Patt[i]->Notes;
+
+               if (xmDisplayProgress (i, pattcount))
+                       return ERROR_BREAK;
+
+               for (j = 0 ; j < 0x40 ; j++)
+               {
+                       for (k = 0 ; k < numtracks ; k++)
+                       {
+                               struct Note *note = &tracks[k][j];
+
+                               /* Translate Note */
+                               strow.Note = TrackerNotes[note->Note];
+
+                               /* Translate instr # (high nibble) & effect (low nibble) */
+                               c = note->Inst;
+
+                               if ((c > 0x0F) && (saver->xmh_ID != ID_SOUNDTRACKER))
+                               {
+                                       /* Noise/ProTracker stores the high bit of the
+                                        * instrument number in bit 15 of the note value.
+                                        */
+                                       strow.Note |= 0x1000;
+                               }
+
+                               strow.InstEff = (c << 4) | Effects[note->EffNum];
+
+                               /* Copy effect value */
+                               strow.EffVal = note->EffVal;
+
+                               /* Write the Row */
+                               if (FWrite (fh, &strow, 4, 1) != 1) return ERROR_IOERR;
+                       }
+               }
+       }
+
+       /* Instruments are written using non buffered I/O because it's
+        * generally more efficient when writing big buffers.
+        */
+       Flush (fh);
+
+       /********************/
+       /* Save Instruments */
+       /********************/
+
+       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+               (APTR)MSG_WRITING_INSTDATA, NULL);
+
+       for (i = 1 ; i < (saver->xmh_ID == ID_SOUNDTRACKER ? 16 : 32) ; i++)
+       {
+               ULONG len;
+
+               if (!(instr = si->Instr[i])) continue;
+
+               len = instr->Length & (~1);
+
+               /* Adapt instrument to SoundTracker 64K limit */
+               if (len > 0xFFFE) len = 0xFFFE;
+
+               if (xmDisplayProgress (i, (saver->xmh_ID == ID_SOUNDTRACKER) ? 16 : 32))
+                       return ERROR_BREAK;
+
+               if (len)
+                       if (Write (fh, instr->Sample, len) != len)
+                               return ERROR_IOERR;
+       }
+
+       return (0);
+}
+
+
+
+static HOOKCALL LONG LoadTracker (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *loader),
+       REG(a2, ULONG *tags))
+{
+       struct Note *note;
+       struct Instrument       *instr;
+       struct Pattern          *patt;
+       struct StRow            *stpatt,        /* Temporary storage for a Tracker pattern */
+                                               *strow;
+
+       ULONG i, j, k;          /* Loop counters */
+       ULONG pattsize;
+       UWORD numtracks = 4, numpatterns, songlen;
+       UBYTE c;                        /* Read buffers */
+
+       ULONG soundtracker = (loader->xmh_ID == ID_SOUNDTRACKER);
+
+
+       /* Get the score name */
+       {
+               UBYTE name[21];
+
+               if (FRead (fh, name, 20, 1) != 1) return ERROR_IOERR;
+               name[20] = '\0'; /* Ensure the string is Null-terminated */
+
+               SetAttrs (si,
+                       SNGA_Title, name,
+                       TAG_DONE);
+       }
+
+       /* Get the Instruments name, length, cycle, effects */
+
+       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+               (APTR)MSG_READING_INSTS_INFO, NULL);
+
+       for ( j = 1 ; j < (soundtracker ? 16 : 32); j++ )
+       {
+               struct
+               {
+                       UBYTE   namebuf[22];
+                       UWORD   length;
+                       BYTE    finetune;
+                       UBYTE   volume;
+                       UWORD   repeat;
+                       UWORD   replen;
+               } modinstr;
+
+
+               memset (&modinstr, 0, sizeof (modinstr));
+
+               if (FRead (fh, &modinstr, sizeof (modinstr), 1) != 1)
+                       return ERROR_IOERR;
+
+               if (modinstr.namebuf[0] || modinstr.length)
+               {
+                       modinstr.namebuf[21] = '\0'; /* Ensure the string is Null-terminated */
+
+                       if (!xmAddInstrument (si, j,
+                               INSTRA_Name,            modinstr.namebuf,
+                               INSTRA_Length,          modinstr.length << 1,
+                               INSTRA_FineTune,        (modinstr.finetune & 0x08) ?    /* Fix sign */
+                                       (modinstr.finetune | 0xF0) : modinstr.finetune,
+                               INSTRA_Volume,          modinstr.volume,
+                               INSTRA_Repeat,          modinstr.repeat << 1,
+                               INSTRA_Replen,          (modinstr.replen == 1) ? 0 : (modinstr.replen << 1),
+                               TAG_DONE))
+                               return ERROR_NO_FREE_STORE;
+               }
+       }
+
+
+       /* Read song length */
+
+       if ((songlen = FGetC (fh)) == ENDSTREAMCH) return ERROR_IOERR;
+
+       /* Read & Ignore number of song patterns.
+        *
+        * Noise/ProTracker stores $7F as the number of patterns.
+        * The correct number of patterns is calculated by looking
+        * for the highest pattern referenced in the position table.
+        * The OctaMED saver wrongly puts the number of patterns
+        * when saving as a ProTracker module.
+        * SoundTracker should save the correct number of patterns,
+        * but some 15 instrument modules I found had incorrect
+        * values here.  So we always ignore this field.
+        */
+       if (FGetC (fh) == ENDSTREAMCH) return ERROR_IOERR;
+
+
+       /* Read the song sequence */
+       {
+               UBYTE postable[128];
+
+               if (FRead (fh, postable, 1, 128) != 128) return ERROR_IOERR;
+
+               if (!(xmSetSongLen (si, songlen)))
+                       return ERROR_NO_FREE_STORE;
+
+               for (i = 0; i < si->Length; i++)
+                       si->Sequence[i] = postable[i];
+
+               /* Search for the highest pattern referenced in the position
+                * table to find out the actual number of patterns.
+                *
+                * Important note: Some modules do have position table
+                * entries beyond the song length.  These references *must*
+                * be taken into account to calculate the last pattern, so
+                * looping only <songlen> times here is *wrong*.  The
+                * correct behaviour is to always look all of the 128
+                * position table entries.
+                */
+
+               numpatterns = 0;
+
+               for (i = 0 ; i < 128 ; i++)
+                       if (postable[i] > numpatterns)
+                               numpatterns = postable[i];
+
+               numpatterns++;  /* Pattern numbering starts from 0 */
+       }
+
+
+
+       /* Check module ID */
+
+       if (!soundtracker)
+       {
+               ULONG __aligned id = 0;
+
+               /* The module ID could have been stripped away in modules
+                * coming from games and intros to make the module harder
+                * to rip, therefore its absence is acceptable.
+                */
+               if (FRead (fh, &id, 4, 1) != 1) return ERROR_IOERR;
+
+
+               /* Check for FastTracker/TakeTracker IDs */
+
+               for (i = 0; i < 32; i++)
+               {
+                       if (id == TakeTrackerIDs[i])
+                       {
+                               numtracks = i + 1;
+                               break;
+                       }
+               }
+
+               if (id)
+               {
+                       UBYTE buf[5];
+
+                       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_INFORMATION,
+                               (APTR)MSG_MODULE_ID, IDtoStr (id, buf));
+               }
+       }
+
+
+       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_INFORMATION,
+               (APTR)MSG_MODULE_HAS_N_CHN, numtracks);
+
+
+       /* Allocate memory for a full SoundTracker pattern */
+
+       si->MaxTracks = numtracks;
+       pattsize = (sizeof (struct StRow) * 0x40) * numtracks;
+
+       if (!(stpatt = AllocMem (pattsize, 0)))
+               return ERROR_NO_FREE_STORE;
+
+
+       /* Read pattern data */
+
+       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+               (APTR)MSG_READING_PATTS, NULL);
+
+       for (i = 0; i < numpatterns; i++)
+       {
+               if (xmDisplayProgress (i, numpatterns))
+               {
+                       FreeMem (stpatt, pattsize);
+                       return ERROR_BREAK;
+               }
+
+               /* Read a whole Tracker row */
+               if (FRead (fh, stpatt, pattsize, 1) != 1)
+               {
+                       FreeMem (stpatt, pattsize);
+                       return ERROR_IOERR;
+               }
+
+               /* Reset note counter */
+               strow = stpatt;
+
+               /* Allocate memory for pattern */
+               if (!(patt = xmAddPattern (si,
+                       PATTA_Tracks,   numtracks,
+                       PATTA_Lines,    64,
+                       TAG_DONE)))
+               {
+                       FreeMem (stpatt, pattsize);
+                       return ERROR_NO_FREE_STORE;
+               }
+
+               for ( j = 0 ; j < 0x40 ; j++ )
+               {
+                       for ( k = 0 ; k < numtracks ; k++, strow++ )
+                       {
+                               /* Get address of the current pattern row */
+                               note = &patt->Notes[k][j];
+
+                               /* Decode note (highest nibble is cleared) */
+                               note->Note = DecodeNote (strow->Note & 0xFFF, i, j, k);
+
+                               /* Decode instrument number (high nibble) */
+                               c = strow->InstEff >> 4; /* Get instrument nr. */
+                               if (!soundtracker && (strow->Note & 0x1000))
+                                       c |= 0x10;      /* High bit of Noise/ProTracker instrument */
+                               note->Inst = c;
+
+                               /* Decode effect (low nibble) */
+                               note->EffNum = DecodeEff (strow->InstEff & 0x0F, strow->EffVal);
+
+                               /* Copy effect value */
+                               note->EffVal = strow->EffVal;
+                       }
+               }
+       }
+
+       FreeMem (stpatt, pattsize);
+
+
+       /* Look for a SetSpeed command ($F) in the first row and
+        * set si->GlobalSpeed.  If Speed is higher than 31,
+        * si->GlobalTempo should be initialized instead. (TODO)
+        */
+
+       SetAttrs (si,
+               SNGA_Author, -1,
+               SNGA_GlobalSpeed,       6,
+               SNGA_GlobalTempo,       125,
+               TAG_DONE);
+
+
+       /* Load Instruments */
+
+       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+               (APTR)MSG_READING_INSTS, NULL);
+
+
+       for (j = 1 ; j < (soundtracker ? 16 : 32) ; j++)
+       {
+               BYTE *sample;
+
+               /* Check if instrument exists */
+
+               if (!(instr = si->Instr[j])) continue;
+               if (instr->Length == 0) continue;
+
+               if (xmDisplayProgress (j, soundtracker ? 16 : 32))
+                       return ERROR_BREAK;
+
+               if (!(sample = (BYTE *) AllocVec (instr->Length, MEMF_SAMPLE)))
+                       return ERROR_NO_FREE_STORE;
+
+               if (FRead (fh, sample, instr->Length, 1) != 1)
+               {
+                       FreeVec (sample);
+
+                       /* Clear instrument lengths */
+                       for (i = j; i < 31; i++)
+                               if (si->Instr[i]) si->Instr[i]->Length = 0;
+
+                       if (j == 0)
+                       {
+                               xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
+                                       (APTR)MSG_SONG_HAS_NO_INSTS, NULL);
+
+                               return RETURN_WARN;     /* Tell 'em this is a damn song */
+                       }
+
+                       return ERROR_IOERR;
+               }
+
+               xmSetInstrument (si, j,
+                       INSTRA_Sample, sample,
+                       TAG_DONE);
+       }
+
+       /* Check for extra data following the module */
+       if (FGetC (fh) != ENDSTREAMCH)
+               xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_NOTE,
+                       (APTR)MSG_EXTRA_DATA_AFTER_MOD, NULL);
+
+       return RETURN_OK;
+}
+
+
+
+INLINE UWORD DecodeNote (UWORD Note, UWORD Patt, UWORD Line, UWORD Track)
+{
+       if (!Note) return 0;
+
+       {
+               ULONG n, mid, low = 1, high = MAXNOTES-1;
+
+               /* The nice binary search learnt at school ;-) */
+               do
+               {
+                       mid = (low + high) / 2;
+                       if ((n = TrackerNotes[mid]) > Note) low = mid + 1;
+                       else if (n < Note) high = mid - 1;
+                       else return (UWORD)mid;
+               } while (low <= high);
+       }
+       xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
+               (APTR)MSG_INVALID_NOTE, Note, Patt, Track, Line);
+       return 0;
+}
+
+
+
+INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval)
+{
+       UBYTE i;
+
+       if (eff == 0 && effval)
+               return (EFF_ARPEGGIO);
+
+       if (eff == 0x0F) /* Speed */
+       {
+               if (effval < 0x20)
+                       return EFF_SETSPEED;
+               else
+                       return EFF_SETTEMPO;
+       }
+
+       for ( i = 0 ; i < MAXTABLEEFFECTS ; i++ )
+               if (eff == Effects[i])
+                       return i;
+
+       return 0;
+}
+
+
+
+static void SetGlobalSpeed (struct SongInfo *si, UWORD tracks)
+
+/* Put a speed command ($F) at the first line played in the song */
+{
+       struct Pattern *patt = si->Patt[si->Sequence[0]];
+       struct Note **pn = patt->Notes;
+       ULONG i;
+
+       tracks = min (tracks, patt->Tracks);
+
+       /* Do it only if required */
+       if (si->GlobalSpeed != DEF_SONGSPEED)
+       {
+               /* Ensure a SetSpeed command does not exist yet in the first row... */
+               for (i = 0 ; i < tracks ; i++)
+                       if (pn[i][0].EffNum == EFF_SETSPEED)
+                               goto settempo;  /* Speed is already set, now for the Tempo... */
+
+               /* Try to find a free effect slot in the row... */
+               for (i = 0 ; i < tracks ; i++)
+                       if (pn[i][0].EffNum == EFF_NULL && pn[i][0].EffVal == 0)
+                                       break;
+
+               pn[i][0].EffNum = EFF_SETSPEED;
+               pn[i][0].EffVal = si->GlobalSpeed;
+       }
+
+settempo:
+       if (si->GlobalTempo != DEF_SONGTEMPO)
+       {
+               /* Ensure a SetTempo command does not exist yet in the first row... */
+               for (i = 0 ; i < tracks ; i++)
+                       if (pn[i][0].EffNum == EFF_SETTEMPO)
+                               return; /* Tempo is already set, nothing else to do... */
+
+               /* Try to find a free effect slot in the row... */
+               for (i = 0 ; i < tracks ; i++)
+                       if (pn[i][0].EffNum == 0 && pn[i][0].EffVal == 0)
+                               break;
+
+               pn[i][0].EffNum = EFF_SETTEMPO;
+               pn[i][0].EffVal = si->GlobalTempo;
+       }
+}
+
+
+
+GLOBALCALL void AddTrackerHooks (void)
+
+/* Adds Tracker loaders and savers */
+{
+       ProTrackerLoader = xmAddHook (
+               XMHOOK_Type,                    NT_XMLOADER,
+               XMHOOK_Name,                    (LONG)"Tracker",
+               XMHOOK_Priority,                10,
+               XMHOOK_Descr,                   (LONG)"Sound/Noise/Star/Pro/Fast/TakeTracker",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_ID,                              ID_PROTRACKER,
+               XMHOOK_Flags,                   XMHF_INTERNAL,
+               XMHOOK_LoadModFunc,             LoadTracker,
+               XMHOOK_IdentifyModFunc, IdentifyTracker,
+               XMHOOK_MaxTracks,               32,
+               XMHOOK_MaxPatterns,             255,
+               XMHOOK_MaxInstruments,  31,
+               XMHOOK_MaxLength,               128,
+               XMHOOK_MaxSampleLen,    65534,
+               XMHOOK_MaxPattLen,              64,
+               TAG_DONE);
+
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMLOADER,
+               XMHOOK_Name,                    (LONG)"SoundTracker",
+               XMHOOK_Priority,                -100,
+               XMHOOK_Descr,                   (LONG)"SoundTracker 15 instruments",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_ID,                              ID_SOUNDTRACKER,
+               XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_FIXED_PATT_LEN,
+               XMHOOK_LoadModFunc,             LoadTracker,
+               XMHOOK_IdentifyModFunc, IdentifySoundTracker,
+               XMHOOK_MaxTracks,               4,
+               XMHOOK_MaxPatterns,             64,
+               XMHOOK_MaxInstruments,  15,
+               XMHOOK_MaxLength,               128,
+               XMHOOK_MaxSampleLen,    65534,
+               XMHOOK_MaxPattLen,              64,
+               TAG_DONE);
+
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMSAVER,
+               XMHOOK_Name,                    (LONG)"TakeTracker",
+               XMHOOK_Priority,                20,
+               XMHOOK_Descr,                   (LONG)"TakeTracker",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_ID,                              ID_TAKETRACKER,
+               XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
+                                                               XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
+               XMHOOK_SaveModFunc,             SaveTracker,
+               XMHOOK_MaxTracks,               32,
+               XMHOOK_MaxPatterns,             100,
+               XMHOOK_MaxInstruments,  31,
+               XMHOOK_MaxLength,               128,
+               XMHOOK_MaxSampleLen,    65534,
+               XMHOOK_MaxPattLen,              64,
+               TAG_DONE);
+
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMSAVER,
+               XMHOOK_Name,                    (LONG)"FastTracker1",
+               XMHOOK_Priority,                20,
+               XMHOOK_Descr,                   (LONG)"FastTracker 1.0",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_ID,                              ID_FASTTRACKER1,
+               XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
+                                                               XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
+               XMHOOK_SaveModFunc,             SaveTracker,
+               XMHOOK_MaxTracks,               8,
+               XMHOOK_MaxPatterns,             128,
+               XMHOOK_MaxInstruments,  31,
+               XMHOOK_MaxLength,               128,
+               XMHOOK_MaxSampleLen,    65534,
+               XMHOOK_MaxPattLen,              64,
+               TAG_DONE);
+
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMSAVER,
+               XMHOOK_Name,                    (LONG)"ProTracker",
+               XMHOOK_Priority,                20,
+               XMHOOK_Descr,                   (LONG)"ProTracker 2.x - 3.x",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_ID,                              ID_PROTRACKER,
+               XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
+                                                               XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
+               XMHOOK_SaveModFunc,             SaveTracker,
+               XMHOOK_MaxTracks,               4,
+               XMHOOK_MaxPatterns,             64,
+               XMHOOK_MaxInstruments,  31,
+               XMHOOK_MaxLength,               128,
+               XMHOOK_MaxSampleLen,    65534,
+               XMHOOK_MaxPattLen,              64,
+               TAG_DONE);
+
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMSAVER,
+               XMHOOK_Name,                    (LONG)"ProTracker100",
+               XMHOOK_Priority,                15,
+               XMHOOK_Descr,                   (LONG)"ProTracker 2.3 (100 patterns)",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_ID,                              ID_PROTRACKER100,
+               XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
+                                                               XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
+               XMHOOK_SaveModFunc,             SaveTracker,
+               XMHOOK_MaxTracks,               4,
+               XMHOOK_MaxPatterns,             100,
+               XMHOOK_MaxInstruments,  31,
+               XMHOOK_MaxLength,               128,
+               XMHOOK_MaxSampleLen,    65534,
+               XMHOOK_MaxPattLen,              64,
+               TAG_DONE);
+
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMSAVER,
+               XMHOOK_Name,                    (LONG)"NoiseTracker",
+               XMHOOK_Priority,                15,
+               XMHOOK_Descr,                   (LONG)"NoiseTracker 31 instruments",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_ID,                              ID_NOISETRACKER,
+               XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
+                                                               XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
+               XMHOOK_SaveModFunc,             SaveTracker,
+               XMHOOK_MaxTracks,               4,
+               XMHOOK_MaxPatterns,             64,
+               XMHOOK_MaxInstruments,  31,
+               XMHOOK_MaxLength,               128,
+               XMHOOK_MaxSampleLen,    65534,
+               XMHOOK_MaxPattLen,              64,
+               TAG_DONE);
+
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMSAVER,
+               XMHOOK_Name,                    (LONG)"StarTrekker4",
+               XMHOOK_Priority,                10,
+               XMHOOK_Descr,                   (LONG)"StarTrakker 4 channels",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_ID,                              ID_STARTREKKER4,
+               XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
+                                                               XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
+               XMHOOK_SaveModFunc,             SaveTracker,
+               XMHOOK_MaxTracks,               4,
+               XMHOOK_MaxPatterns,             64,
+               XMHOOK_MaxInstruments,  31,
+               XMHOOK_MaxLength,               128,
+               XMHOOK_MaxSampleLen,    65534,
+               XMHOOK_MaxPattLen,              64,
+               TAG_DONE);
+
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMSAVER,
+               XMHOOK_Name,                    (LONG)"SoundTracker",
+               XMHOOK_Descr,                   (LONG)"Old SoundTracker 15 instruments",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_ID,                              ID_SOUNDTRACKER,
+               XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
+                                                               XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
+               XMHOOK_SaveModFunc,             SaveTracker,
+               XMHOOK_UserData,                ID_SOUNDTRACKER,
+               XMHOOK_MaxTracks,               4,
+               XMHOOK_MaxPatterns,             64,
+               XMHOOK_MaxInstruments,  15,
+               XMHOOK_MaxLength,               128,
+               XMHOOK_MaxSampleLen,    65534,
+               XMHOOK_MaxPattLen,              64,
+               TAG_DONE);
+}
diff --git a/XModule b/XModule
new file mode 100644 (file)
index 0000000..b209ae9
Binary files /dev/null and b/XModule differ
diff --git a/XModule.i b/XModule.i
new file mode 100644 (file)
index 0000000..0fdebc0
--- /dev/null
+++ b/XModule.i
@@ -0,0 +1,121 @@
+       IFND    XMODULE_I
+XMODULE_I      SET     1
+**
+**     XModule replay routine 1.0
+**
+**     Copyright (C) 1995 Bernardo Innocenti
+**
+**     Assembler structure definitions for player
+**
+
+       IFND    EXEC_TYPES_I
+       include exec/types.i
+       ENDC    ; EXEC_TYPES_I
+
+       IFND    EXEC_NODES_I
+       include exec/nodes.i
+       ENDC    ; EXEC_NODES_I
+
+
+
+MAXINSTRUMENTS EQU     64      ; Maximum number of instruments loaded
+MAXTABLENOTE   EQU     (12*3)  ; Number of entries in note conversion table
+MAXTABLEEFFECTS        EQU     20      ; Number of entries in effect conversion table
+MAXPATTERNS    EQU     128     ; Maximum number of patterns
+MAXTRACKS      EQU     32      ; Maximum number of tracks in a pattern
+MAXPATTLINES   EQU     32768   ; Maximum number of lines in a pattern
+MAXPOSITIONS   EQU     32768   ; Maximum number of song positions
+MAXINSTNAME    EQU     32
+MAXSONGNAME    EQU     32
+MAXAUTHNAME    EQU     64
+MAXPATTNAME    EQU     16
+
+PATHNAME_MAX   EQU     256
+
+
+**************************************************************************
+** Some definitions transcripted from "XModule.h"
+**************************************************************************
+
+       ENUM 0
+
+       EITEM EFF_NULL
+       EITEM EFF_PORTAMENTOUP
+       EITEM EFF_PORTAMENTODOWN
+       EITEM EFF_TONEPORTAMENTO
+       EITEM EFF_VIBRATO
+       EITEM EFF_TONEPVOLSLIDE
+       EITEM EFF_VIBRATOVOLSLIDE
+       EITEM EFF_TREMOLO
+       EITEM EFF_UNUSED
+       EITEM EFF_SAMPLEOFFSET
+       EITEM EFF_VOLSLIDE
+       EITEM EFF_POSJUMP
+       EITEM EFF_SETVOLUME
+       EITEM EFF_PATTERNBREAK
+       EITEM EFF_MISC
+       EITEM EFF_SETSPEED
+       EITEM EFF_SETTEMPO
+       EITEM EFF_ARPEGGIO
+
+       EITEM EFF_COUNT
+
+
+
+       STRUCTURE Note,0
+       UBYTE note_Note
+       UBYTE note_Inst
+       UBYTE note_Vol
+       UBYTE note_Pad
+       UBYTE note_EffNum
+       UBYTE note_EffVal
+       LABEL Note_SIZEOF
+
+
+       STRUCTURE Instrument,0
+       UWORD   in_InstType                     ; Instrument type (See defs)
+       UWORD   in_Volume                       ; Volume (max $40)
+       STRUCT  in_Name,MAXINSTNAME             ; Instrument Name
+       APTR    in_SampleData                   ; Sampled data
+       ULONG   in_Length                       ; Length of instr
+       ULONG   in_Repeat                       ; Loop start (No loop = 0)
+       ULONG   in_Replen                       ; Loop size (No loop = 1)
+       WORD    in_FineTune                     ; Instrument FineTune (-8..+7)
+       UWORD   in_Flags                        ; Unused
+       LABEL   in_SIZEOF
+
+
+       STRUCTURE       Pattern,0
+       UWORD   pa_Tracks                       ; Support for variable number of tracks
+       UWORD   pa_Lines                        ; Number of lines in pattern
+       STRUCT  pa_PattName,MAXPATTNAME         ; Pattern Name
+       STRUCT  pa_Notes,MAXTRACKS * 4          ; Pointers to the lines
+       LABEL   pa_SIZEOF
+
+
+       STRUCTURE       SongInfo,LN_SIZE
+       UWORD   si_Length                       ; Number of positions in song
+       UWORD   si_MaxTracks                    ; Number of tracks in song
+       UWORD   si_NumPatterns                  ; Number of patterns in song
+       UWORD   si_NumInstruments               ; Unused
+       UWORD   si_GlobalSpeed                  ; Default song speed
+       UWORD   si_GlobalTempo                  ; Default song tempo
+       UWORD   si_Restart                      ; Position to restart from
+       UWORD   si_CurrentPatt
+       UWORD   si_CurrentPos
+       UWORD   si_CurrentInst
+       UWORD   si_Flags                        ; See definitions below
+       ULONG   si_Changes                      ; Number of changes made to this song
+       APTR    si_Sequence                     ; *UWORD: Pointer to song sequence
+       STRUCT  si_PattData,pa_SIZEOF*MAXPATTERNS
+       STRUCT  si_Inst,in_SIZEOF*MAXINSTRUMENTS
+       STRUCT  si_ActiveTracks,MAXTRACKS       ; UBYTE []: Active Tracks (0 = disabled)
+       STRUCT  si_SongName,MAXSONGNAME         ; UBYTE []: Song name
+       STRUCT  si_Author,MAXAUTHNAME           ; Author of song
+       STRUCT  si_SongPath,PATHNAME_MAX;       ; Original song path
+       APTR    si_Pool                         ;  The memory pool where song data
+                                               ; must be allocated from.
+       LABEL   si_SIZEOF
+
+
+       ENDC    ; !XMODULE_I
diff --git a/XModule.info b/XModule.info
new file mode 100644 (file)
index 0000000..fb47624
Binary files /dev/null and b/XModule.info differ
diff --git a/XModule.prefs b/XModule.prefs
new file mode 100644 (file)
index 0000000..1f87e29
Binary files /dev/null and b/XModule.prefs differ
diff --git a/XModuleHook.c b/XModuleHook.c
new file mode 100644 (file)
index 0000000..d1e1d6c
--- /dev/null
@@ -0,0 +1,716 @@
+/*
+**     XModuleHook.c
+**
+**     Copyright (C) 1994,95,96,97 Bernardo Innocenti
+**
+**     Internal loader/saver hook for the IFF XMOD module format
+*/
+
+#include <exec/memory.h>
+#include <libraries/xmoduleclass.h>
+
+#include <proto/exec.h>
+#include <proto/dos.h>
+#include <proto/intuition.h>
+#include <proto/iffparse.h>
+#include <proto/xmodule.h>
+
+#include "XModulePriv.h"
+#include "Gui.h"
+
+
+/* Local functions prototypes */
+
+static HOOKCALL struct XMHook *IdentifyXModule (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags));
+static HOOKCALL LONG LoadXModule (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *loader),
+       REG(a2, ULONG *tags));
+static HOOKCALL LONG SaveXModule (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *saver),
+       REG(a2, ULONG *tags));
+
+
+
+GLOBALCALL void AddXModuleHooks (void)
+
+/* Adds XModule loader and saver */
+{
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMLOADER,
+               XMHOOK_Name,                    (LONG)"XModule",
+               XMHOOK_Priority,                50,
+               XMHOOK_Descr,                   (LONG)"XModule internal format",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_Flags,                   XMHF_INTERNAL,
+               XMHOOK_LoadModFunc,             LoadXModule,
+               XMHOOK_IdentifyModFunc, IdentifyXModule,
+               TAG_DONE);
+
+       xmAddHook (
+               XMHOOK_Type,                    NT_XMSAVER,
+               XMHOOK_Name,                    (LONG)"XModule",
+               XMHOOK_Priority,                50,
+               XMHOOK_Descr,                   (LONG)"XModule Internal format",
+               XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
+               XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
+                                                               XMHF_EXCLUDE_NAMES | XMHF_EXCLUDE_SEQUENCE,
+               XMHOOK_SaveModFunc,             SaveXModule,
+               TAG_DONE);
+}
+
+
+
+static HOOKCALL struct XMHook *IdentifyXModule (
+       REG(d0, BPTR fh),
+       REG(a0, struct XMHook *loader),
+       REG(a1, ULONG *tags))
+
+/* Determine if the given file is an XModule module.
+ * Note: the file position will be changed on exit.
+ */
+{
+       ULONG id[3];
+
+       Seek (fh, 0, OFFSET_BEGINNING);
+       if (FRead (fh, &id, 12, 1) != 1)
+               return NULL;
+
+       if ((id[0] == ID_FORM) && (id[2] == ID_XMOD))
+               return loader;
+
+       return NULL;
+}
+
+
+
+static HOOKCALL LONG LoadXModule (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *loader),
+       REG(a2, ULONG *tags))
+{
+       struct IFFHandle *iff;
+       struct ContextNode *cn;
+       LONG err;
+
+       if (iff = AllocIFF())
+       {
+               iff->iff_Stream = (ULONG) fh;
+
+               InitIFFasDOS (iff);
+
+               if (!(err = OpenIFF (iff, IFFF_READ)))
+               {
+                       struct ModuleHeader mhdr;
+
+                       static LONG stopchunks[] =
+                       {
+                               ID_XMOD,        ID_NAME,
+                               ID_XMOD,        ID_MHDR,
+                               ID_SONG,        ID_FORM
+                       };
+
+                       if (err = StopChunks (iff, stopchunks, 3))
+                               goto error;
+
+                       if (err = StopOnExit (iff, ID_XMOD, ID_FORM))
+                               goto error;
+
+                       /* Scan module */
+
+                       while (1)
+                       {
+                               if (err = ParseIFF (iff, IFFPARSE_SCAN))
+                               {
+                                       if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
+                                       break; /* Free resources & exit */
+                               }
+
+                               if (cn = CurrentChunk (iff))
+                               {
+                                       switch (cn->cn_ID)
+                                       {
+                                               case ID_NAME:
+                                               {
+                                                       UBYTE name[128];
+
+                                                       ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
+                                                       name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
+                                                       SetAttrs (si,
+                                                               SNGA_Title,     name,
+                                                               TAG_DONE);
+                                                       break;
+                                               }
+
+                                               case ID_MHDR:
+                                                       if ((err = ReadChunkBytes (iff, &mhdr, sizeof (mhdr))) != sizeof(mhdr))
+                                                               goto error;
+                                                       break;
+
+                                               case ID_FORM:
+                                                       if (cn->cn_Type == ID_SONG)
+                                                               if (err = LoadSong (iff, si))
+                                                                       goto error;
+                                                       break;
+
+                                               default:
+                                                       break;
+                                       }
+                               }
+                       }
+error:
+                       CloseIFF (iff);
+               }
+
+               FreeIFF (iff);
+       }
+       else err = ERROR_NO_FREE_STORE;
+
+       return err;
+}
+
+
+
+GLOBALCALL LONG LoadSong (struct IFFHandle *iff, struct SongInfo *si)
+{
+       LONG err, len;
+       struct ContextNode *cn;
+       struct SongHeader shdr;
+
+       static LONG stopchunks[] =
+       {
+               ID_SONG,        ID_NAME,
+               ID_SONG,        ID_AUTH,
+               ID_SONG,        ID_ANNO,
+               ID_SONG,        ID_SHDR,
+               ID_SONG,        ID_SEQN,
+               ID_PATT,        ID_FORM,
+               ID_8SVX,        ID_FORM
+       };
+
+
+       memset (&shdr, 0, sizeof (shdr));
+
+       if (err = StopChunks (iff, stopchunks, 7))
+               return err;
+
+       if (err = StopOnExit (iff, ID_SONG, ID_FORM))
+               return err;
+
+       /* Scan song */
+
+       while (1)
+       {
+               if (err = ParseIFF (iff, IFFPARSE_SCAN))
+               {
+                       if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
+                       break; /* Free resources & exit */
+               }
+
+               if (cn = CurrentChunk (iff))
+               {
+                       switch (cn->cn_ID)
+                       {
+                               case ID_NAME:
+                               {
+                                       UBYTE name[128];
+
+                                       ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
+                                       name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
+                                       SetAttrs (si,
+                                               SNGA_Title, name,
+                                               TAG_DONE);
+                                       break;
+                               }
+
+                               case ID_AUTH:
+                               {
+                                       UBYTE name[128];
+
+                                       ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
+                                       name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
+                                       SetAttrs (si,
+                                               SNGA_Author, name,
+                                               TAG_DONE);
+                                       break;
+                               }
+
+                               case ID_ANNO:
+                               {
+                                       UBYTE name[128];
+
+                                       ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
+                                       name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
+                                       SetAttrs (si,
+                                               SNGA_Description, name,
+                                               TAG_DONE);
+                                       break;
+                               }
+
+
+                               case ID_SHDR:
+                                       if ((err = ReadChunkBytes (iff, &shdr, sizeof (shdr))) == 0)
+                                               return err;
+
+                                       SetAttrs (si,
+                                               SNGA_GlobalSpeed,               shdr.GlobalSpeed,
+                                               SNGA_GlobalTempo,               shdr.GlobalTempo,
+                                               SNGA_RestartPos,                shdr.RestartPos,
+                                               SNGA_CurrentPatt,               shdr.CurrentPatt,
+                                               SNGA_CurrentPos,                shdr.CurrentPos,
+                                               SNGA_CurrentInst,               shdr.CurrentInst,
+                                               SNGA_DefaultTracks,             shdr.DefNumTracks ? shdr.DefNumTracks : shdr.MaxTracks,
+                                               SNGA_DefaultPattLen,    shdr.DefPattLen ? shdr.DefPattLen : DEF_PATTLEN,
+                                               SNGA_CreationDate,              shdr.CreationDate,
+                                               SNGA_LastChanged,               shdr.LastChanged,
+                                               SNGA_TotalChanges,              shdr.TotalChanges,
+                                               TAG_DONE);
+                                       break;
+
+                               case ID_SEQN:
+                                       len     = min (cn->cn_Size / 2, MAXPOSITIONS);
+
+                                       if (xmSetSongLen (si, len))
+                                       {
+                                               if ((err = ReadChunkBytes (iff, si->Sequence, si->Length * 2))
+                                                       != si->Length * 2)
+                                                       return err;
+                                       }
+                                       else return ERROR_NO_FREE_STORE;
+
+                                       break;
+
+                               case ID_FORM:
+                                       if (cn->cn_Type == ID_PATT)
+                                       {
+                                               if (!si->NumPatterns)
+                                                       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+                                                               (APTR)MSG_READING_PATTS, NULL);
+
+                                               if (xmDisplayProgress (si->NumPatterns, shdr.NumPatterns))
+                                                       return ERROR_BREAK;
+
+                                               if (!LoadPattern (si, si->NumPatterns, iff))
+                                                       return IoErr();
+                                       }
+                                       else if (cn->cn_Type == ID_8SVX)
+                                       {
+                                               if (!si->LastInstrument)
+                                                       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+                                                               (APTR)MSG_READING_INSTS, NULL);
+
+                                               if (xmDisplayProgress (si->LastInstrument, shdr.LastInstrument))
+                                                       return ERROR_BREAK;
+
+                                               if (err = Load8SVXInstrument (si, 0, iff, NULL))
+                                                       return err;
+                                       }
+
+                                       break;
+
+                               default:
+                                       break;
+                       }
+               }
+       }
+
+       return err;
+}
+
+
+
+GLOBALCALL struct Pattern *LoadPattern (struct SongInfo *si, ULONG num, struct IFFHandle *iff)
+{
+       LONG    err, tracksize;
+       struct Pattern          *patt = NULL;
+       struct ContextNode      *cn;
+       struct PatternHeader phdr;
+       UWORD   i;
+       BYTE    name[64];
+
+       static LONG stopchunks[] =
+       {
+               ID_PATT, ID_NAME,
+               ID_PATT, ID_PHDR,
+               ID_PATT, ID_BODY
+       };
+
+       name[0] = '\0';
+
+       if (err = StopChunks (iff, stopchunks, 3))
+       {
+               SetIoErr (err);
+               return NULL;
+       }
+
+       if (err = StopOnExit (iff, ID_PATT, ID_FORM))
+       {
+               SetIoErr (err);
+               return NULL;
+       }
+
+       /* Scan Pattern */
+
+       while (1)
+       {
+               if (err = ParseIFF (iff, IFFPARSE_SCAN))
+               {
+                       if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
+                       break; /* Free resources & exit */
+               }
+
+               if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_PATT))
+               {
+                       switch (cn->cn_ID)
+                       {
+                               case ID_NAME:
+                               {
+                                       ReadChunkBytes (iff, name, min(cn->cn_Size, 63));
+                                       name[min(cn->cn_Size, 63)] = '\0'; /* Ensure string termination */
+                                       break;
+                               }
+
+                               case ID_PHDR:
+                                       if ((err = ReadChunkBytes (iff, &phdr, sizeof (phdr))) != sizeof(phdr))
+                                       {
+                                               SetIoErr (err);
+                                               return NULL;
+                                       }
+
+                                       if (!(patt = xmAddPattern (si,
+                                               PATTA_Lines,    phdr.Lines,
+                                               PATTA_Tracks,   phdr.Tracks,
+                                               PATTA_Num,              num,
+                                               TAG_DONE)))
+                                       {
+                                               SetIoErr (ERROR_NO_FREE_STORE);
+                                               return NULL;
+                                       }
+
+                                       tracksize = phdr.Lines * sizeof (struct Note);
+
+                                       break;
+
+                               case ID_BODY:
+                               {
+                                       if (!patt)
+                                       {
+                                               SetIoErr (IFFERR_SYNTAX);
+                                               return NULL;
+                                       }
+
+                                       for (i = 0; i < patt->Tracks; i++)
+                                       {
+                                               if ((err = ReadChunkBytes (iff, patt->Notes[i], tracksize)) != tracksize)
+                                               {
+                                                       SetIoErr (err);
+                                                       return NULL;
+                                               }
+                                       }
+
+                                       break;
+                               }
+
+                               default:
+                                       break;
+                       }
+               }
+       }
+
+       if (name[0])
+               xmSetPattern (si, si->NumPatterns - 1,
+                       PATTA_Name,     name,
+                       TAG_DONE);
+
+       if (!err && !patt)
+               err = ERROR_OBJECT_NOT_FOUND;
+
+       if (err) SetIoErr (err);
+
+       return patt;
+}
+
+
+
+static HOOKCALL LONG SaveXModule (
+       REG(d0, BPTR fh),
+       REG(a0, struct SongInfo *si),
+       REG(a1, struct XMHook *saver),
+       REG(a2, ULONG *tags))
+{
+       struct IFFHandle *iff;
+       LONG err;
+
+       if (iff = AllocIFF())
+       {
+               iff->iff_Stream = (ULONG) fh;
+
+               InitIFFasDOS (iff);
+
+               if (!(err = OpenIFF (iff, IFFF_WRITE)))
+               {
+
+                       /* Write XMOD */
+                       if (err = PushChunk (iff, ID_XMOD, ID_FORM, IFFSIZE_UNKNOWN))
+                               goto error;
+
+                       /* Write module NAME */
+                       if (err = WriteStringChunk (iff, FilePart (si->Path), ID_NAME))
+                               goto error;
+
+                       /* Write module ANNO */
+                       if (err = WriteStringChunk (iff, VERS, ID_ANNO))
+                               goto error;
+
+                       /* Write Module Header (MHDR) */
+                       {
+                               struct ModuleHeader mhdr;
+
+                               mhdr.XModuleVersion = VERSION;
+                               mhdr.XModuleRevision = REVISION;
+                               mhdr.NumSongs   = 1;
+                               mhdr.ActiveSong = 1;
+                               mhdr.MasterVolume = 0xFFFF;
+                               mhdr.MixingRate = 44100;
+
+                               if (err = PushChunk (iff, ID_XMOD, ID_MHDR, sizeof (mhdr)))
+                                       goto error;
+                               if ((err = WriteChunkBytes (iff, &mhdr, sizeof (mhdr))) != sizeof(mhdr))
+                                       goto error;
+                               if (err = PopChunk (iff)) goto error;   /* Pop MHDR */
+                       }
+
+                       if (err = SaveSong (iff, si))
+                               goto error;
+
+                       err = PopChunk (iff);           /* Pop FORM XMOD */
+
+error:
+                       CloseIFF (iff);
+               }
+               else err = IFFERR_NOTIFF;
+
+               FreeIFF (iff);
+       }
+       else err = ERROR_NO_FREE_STORE;
+
+       return (UWORD) err;
+}
+
+
+
+GLOBALCALL LONG SaveSong (struct IFFHandle *iff, struct SongInfo *si)
+{
+       LONG err;
+
+       if (err = PushChunk (iff, ID_SONG, ID_FORM, IFFSIZE_UNKNOWN))
+               return err;
+
+       /* Write Song Name */
+       WriteStringChunk (iff, si->Title, ID_NAME);
+
+       /* Write Author Name */
+       WriteStringChunk (iff, si->Author, ID_AUTH);
+
+       /* Write Annotations */
+       WriteStringChunk (iff, si->Description, ID_ANNO);
+
+       /* Write Song Header (SHDR) */
+       {
+               struct SongHeader shdr;
+
+               if (err = PushChunk  (iff, ID_SONG, ID_SHDR, IFFSIZE_UNKNOWN))
+                       return err;
+
+
+               /* Set last changed date */
+               {
+                       ULONG dummy;
+                       CurrentTime                     (&si->LastChanged, &dummy);
+               }
+
+               shdr.Length                     = si->Length;
+               shdr.MaxTracks          = si->MaxTracks;
+               shdr.NumPatterns        = si->NumPatterns;
+               shdr.LastInstrument     = si->LastInstrument;
+               shdr.GlobalSpeed        = si->GlobalSpeed;
+               shdr.GlobalTempo        = si->GlobalTempo;
+               shdr.RestartPos         = si->RestartPos;
+               shdr.CurrentPatt        = si->CurrentPatt;
+               shdr.CurrentLine        = si->CurrentLine;
+               shdr.CurrentTrack       = si->CurrentTrack;
+               shdr.CurrentPos         = si->CurrentPos;
+               shdr.CurrentInst        = si->CurrentInst;
+               shdr.DefNumTracks       = si->DefNumTracks;
+               shdr.DefPattLen         = si->DefPattLen;
+               shdr.TotalChanges       = si->TotalChanges + si->Changes;
+               shdr.CreationDate       = si->CreationDate;
+               shdr.LastChanged        = si->LastChanged;
+
+
+               if ((err = WriteChunkBytes (iff, &shdr, sizeof (shdr))) != sizeof (shdr))
+                       return err;
+
+               if (err = PopChunk (iff))       /* Pop SHDR */
+                       return err;
+       }
+
+
+       if (err = SaveSequence (iff, si))
+               return err;
+       if (err = SavePatterns (iff, si))
+               return err;
+       if (err = SaveInstruments (iff, si))
+               return err;
+
+       err = PopChunk (iff);   /* Pop FORM SONG */
+
+       return err;
+}
+
+
+
+GLOBALCALL LONG SaveSequence (struct IFFHandle *iff, struct SongInfo *si)
+{
+       LONG    err;
+
+       if (err = PushChunk (iff, 0, ID_SEQN, si->Length * 2))
+               return err;
+
+       if ((err = WriteChunkBytes (iff, si->Sequence, si->Length * 2)) != si->Length * 2)
+               return err;
+
+       err = PopChunk (iff);
+
+       return err;
+}
+
+
+
+GLOBALCALL LONG SavePatterns (struct IFFHandle *iff, struct SongInfo *si)
+{
+       LONG    err;
+       ULONG   i;
+
+       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+               (APTR)MSG_WRITING_PATTS, NULL);
+
+
+       for (i = 0; i < si->NumPatterns; i++)
+       {
+               if (xmDisplayProgress (i, si->NumPatterns))
+                       return ERROR_BREAK;
+
+               if (si->Patt[i])
+                       if (err = SavePattern (iff, si->Patt[i]))
+                               return err;
+       }
+
+       return RETURN_OK;
+}
+
+
+
+GLOBALCALL LONG SaveInstruments (struct IFFHandle *iff, struct SongInfo *si)
+{
+       LONG    err;
+       ULONG   i;
+
+       xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
+               (APTR)MSG_WRITING_INSTS, NULL);
+
+       for (i = 1; i <= si->LastInstrument; i++)
+       {
+               if (!(si->Instr[i])) continue;
+
+               if (xmDisplayProgress (i - 1, si->LastInstrument))
+                       return ERROR_BREAK;
+
+               if (err = Save8SVXInstrument (si->Instr[i], i, iff))
+                       return err;
+       }
+
+       return RETURN_OK;
+}
+
+
+
+GLOBALCALL LONG SavePattern (struct IFFHandle *iff, struct Pattern *patt)
+{
+       LONG err;
+
+       if (err = PushChunk (iff, ID_PATT, ID_FORM, IFFSIZE_UNKNOWN))
+               return err;
+
+       /* Write pattern NAME */
+
+       if (err = WriteStringChunk (iff, patt->Name, ID_NAME))
+               return err;
+
+       /* Write pattern header (PHDR) */
+       {
+               struct PatternHeader phdr;
+
+               phdr.Lines = patt->Lines;
+               phdr.Tracks = patt->Tracks;
+
+               if (err = PushChunk (iff, 0, ID_PHDR, sizeof (struct PatternHeader)))
+                       return err;
+
+               if ((err = WriteChunkBytes (iff, &phdr, sizeof (phdr))) != sizeof (phdr))
+                       return err;
+
+               if (err = PopChunk (iff))       /* PHDR */
+                       return err;
+       }
+
+       /* Write pattern BODY */
+       {
+               ULONG tracksize = sizeof (struct Note) * patt->Lines;
+               ULONG i;
+
+               if (err = PushChunk (iff, 0, ID_BODY, tracksize * patt->Tracks))
+                       return err;
+
+               for (i = 0; i < patt->Tracks; i++)
+               {
+                       if ((err = WriteChunkBytes (iff, patt->Notes[i], tracksize)) != tracksize)
+                               return err;
+               }
+
+               if (err = PopChunk (iff))       /* BODY */
+                       return err;
+       }
+
+       err = PopChunk (iff);   /* PATT */
+
+       return err;
+}
+
+
+
+GLOBALCALL LONG WriteStringChunk (struct IFFHandle *iff, CONST_STRPTR str, ULONG id)
+{
+       LONG err;
+       ULONG len;
+
+       if (!str || !str[0] || !SaveSwitches.SaveNames)
+               return RETURN_OK;
+
+       if (err = PushChunk (iff, 0, id, len = strlen (str)))
+               return err;
+
+       if ((err = WriteChunkBytes (iff, str, len)) != len)
+               return err;
+
+       err = PopChunk (iff);
+
+       return err;
+}
diff --git a/XModulePriv.h b/XModulePriv.h
new file mode 100644 (file)
index 0000000..2a5c648
--- /dev/null
@@ -0,0 +1,290 @@
+#ifndef XMODULE_PRIV_H
+#define XMODULE_PRIV_H
+/*
+**     XModulePriv.h
+**
+**     Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
+**
+**     Use 4 chars wide TABs to read this source
+**
+**     Private definitions for internal XModule use
+*/
+
+#ifndef        EXEC_NODES_H
+#include <exec/nodes.h>
+#endif /* !EXEC_NODES_H */
+
+#ifndef        EXEC_LIBRARIES_H
+#include <exec/libraries.h>
+#endif /* !EXEC_LIBRARIES_H */
+
+#ifndef        DOS_DOS_H
+#include <dos/dos.h>
+#endif /* !DOS_DOS_H */
+
+#ifndef        DOS_DOS_H
+#include <dos/dos.h>
+#endif /* !DOS_DOS_H */
+
+#ifndef        LIBRARIES_IFFPARSE_H
+#include "libraries/iffparse.h"
+#endif /* LIBRARIES_IFFPARSE_H */
+
+#ifndef LIBRARIES_XMODULE_H
+#include <libraries/xmodule.h>
+#endif /* !LIBRARIES_XMODULE_H */
+
+#ifndef LIBRARIES_SONGCLASS_H
+#include <libraries/songclass.h>
+#endif /* !LIBRARIES_SONGCLASS_H */
+
+#include "XModule_rev.h"
+#include "LocaleStrings.h"
+#include "ListMacros.h"
+#include "CompilerSpecific.h"
+#include "Debug.h"                     /* Debug support */
+
+/* Single object compilation requires different attributes for global symbols */
+#ifdef SINGLE_OBJECT
+       #undef XREF
+       #undef XDEF
+       #undef GLOBALCALL
+       #define XREF            extern
+       #define XDEF            static
+       #define GLOBALCALL      static
+       #ifdef __SASC
+//             #pragma msg 72 ignore
+               #pragma msg 181 ignore
+       #endif
+#endif
+
+
+/* Version information: updated auto-magically on every compilation ;-) */
+
+#define XMODULEVER     VERS
+#define XMODULEDATE    "(" DATE ")"
+#define XMODULECOPY    "Copyright © 1993,94,95,96,97 by Bernardo Innocenti"
+
+#ifdef OS30_ONLY
+       #ifdef _M68020
+               #define BUILDMODE "[M68020 OS3.0]"
+       #else
+               #define BUILDMODE "[M68000 OS3.0]"
+       #endif
+#else
+       #ifdef _M68020
+               #define BUILDMODE "[M68020 OS2.0]"
+       #else
+               #define BUILDMODE "[M68000 OS2.0]"
+       #endif
+#endif
+
+
+
+/*************************/
+/* Constants Definitions */
+/*************************/
+
+/* Maximum values */
+#define MAXTABLEEFFECTS         20                     /* Number of entries in effect conversion table */
+#define PATHNAME_MAX   108                     /* Maximum length of an AmigaDOS path name              */
+
+
+/* Specific error codes */
+#define ERROR_NOTMODULE        ERROR_OBJECT_WRONG_TYPE /* File format not recognized           */
+#define ERROR_IOERR            RETURN_FAIL                             /* A DOS call (e.g.: Read()) failed.
+                                                                                                * check IoErr() for specific error.
+                                                                                                */
+
+/* Default directory for external hooks */
+#define DEF_HOOKSDIR "PROGDIR:Hooks/"
+
+
+/***********************/
+/* Function Prototypes */
+/***********************/
+
+/* From "Audio.c" */
+GLOBALCALL void                HandleAudio                             (void);
+GLOBALCALL void                PlaySample                              (BYTE *samp, ULONG len, UWORD vol, UWORD per);
+GLOBALCALL ULONG       SetupAudio                              (void);
+GLOBALCALL void                CleanupAudio                    (void);
+
+/* From "Misc.c" */
+GLOBALCALL struct Library *MyOpenLibrary       (CONST_STRPTR name, ULONG ver);
+GLOBALCALL void                CantOpenLib                             (CONST_STRPTR name, LONG vers);
+GLOBALCALL void                KillMsgPort                             (struct MsgPort *mp);
+GLOBALCALL struct TextAttr *CopyTextAttrPooled (void *pool, const struct TextAttr *source, struct TextAttr *dest);
+GLOBALCALL UWORD       CmpTextAttr                             (const struct TextAttr *ta1, const struct TextAttr *ta2);
+GLOBALCALL STRPTR      DupStringPooled                 (void *pool, CONST_STRPTR source, STRPTR *dest);
+GLOBALCALL void                FilterName                              (STRPTR name);
+GLOBALCALL struct DiskObject *GetProgramIcon (void);
+GLOBALCALL LONG                PutIcon                                 (CONST_STRPTR source, CONST_STRPTR dest);
+GLOBALCALL LONG                BackupFile                              (CONST_STRPTR src, CONST_STRPTR template, ULONG versions);
+
+/* From "Gui.c" (More functions prototyped in "Gui.h") */
+GLOBALCALL LONG                HandleGui                               (void);
+
+/* From "Rexx.c" */
+GLOBALCALL void                HandleRexxMsg                   (void);
+GLOBALCALL LONG                CreateRexxPort                  (void);
+GLOBALCALL void                DeleteRexxPort                  (void);
+
+/* From "ToolBoxWin.c" */
+GLOBALCALL void                ToolBoxOpenModule               (CONST_STRPTR file, ULONG num, ULONG count);
+
+/* From "Help.c" */
+GLOBALCALL void                HandleHelp                              (struct IntuiMessage *msg);
+GLOBALCALL void                HandleAmigaGuide                (void);
+GLOBALCALL void                CleanupHelp                             (void);
+
+/* From "Cx.c" */
+GLOBALCALL LONG                SetupCx                                 (void);
+GLOBALCALL void                HandleCx                                (void);
+GLOBALCALL void                CleanupCx                               (void);
+
+/* From "ProgressWin.c" */
+GLOBALCALL void                DisplayAction                   (ULONG msg);
+GLOBALCALL void                DisplayActionStr                (CONST_STRPTR str);
+GLOBALCALL LONG                DisplayProgress                 (LONG Num, LONG Max);
+GLOBALCALL void                ShowMessage                             (ULONG msg, ...);
+GLOBALCALL void                ShowString                              (CONST_STRPTR s, LONG *args);
+
+/* From "PlayWin.c" */
+GLOBALCALL LONG                SetupPlayer                             (void);
+GLOBALCALL void                CleanupPlayer                   (void);
+
+/* From "Compress.c" */
+GLOBALCALL BPTR                DecompressFile                  (CONST_STRPTR name, UWORD type);
+GLOBALCALL void                DecompressFileDone              (void);
+GLOBALCALL LONG                CruncherType                    (BPTR file);
+
+/* From "Song.c" */
+GLOBALCALL Class *     InitSongClass                   (void);
+GLOBALCALL void                FreeSongClass                   (Class *cl);
+GLOBALCALL void                FixSong                                 (struct SongInfo *si);
+GLOBALCALL ULONG       CalcInstSize                    (struct SongInfo *si);
+GLOBALCALL ULONG       CalcSongSize                    (struct SongInfo *si);
+GLOBALCALL ULONG       CalcSongTime                    (struct SongInfo *si);
+
+/* From "Instr.c" */
+GLOBALCALL LONG                LoadInstrument                  (struct SongInfo *si, ULONG num, CONST_STRPTR filename);
+GLOBALCALL LONG                SaveInstrument                  (struct Instrument *inst, CONST_STRPTR filename);
+GLOBALCALL LONG                Load8SVXInstrument              (struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename);
+GLOBALCALL LONG                Save8SVXInstrument              (struct Instrument *inst, ULONG num, struct IFFHandle *iff);
+GLOBALCALL void                OptimizeInstruments             (struct SongInfo *si);
+GLOBALCALL void                RemDupInstruments               (struct SongInfo *si);
+GLOBALCALL void                RemUnusedInstruments    (struct SongInfo *si);
+GLOBALCALL void                RemapInstruments                (struct SongInfo *si);
+GLOBALCALL void                SampChangeSign8                 (UBYTE *samp, ULONG len);
+
+/* From "Operators.c" */
+GLOBALCALL ULONG       InsertPattern                   (struct SongInfo *si, struct Pattern *patt, UWORD patnum);
+GLOBALCALL struct SongInfo *MergeSongs         (struct SongInfo *songa, struct SongInfo *songb);
+GLOBALCALL struct SongInfo *JoinSongs          (struct SongInfo *songa, struct SongInfo *songb);
+GLOBALCALL void                RemDupPatterns                  (struct SongInfo *si);
+GLOBALCALL void                DiscardPatterns                 (struct SongInfo *si);
+GLOBALCALL void                CutPatterns                             (struct SongInfo *si);
+GLOBALCALL LONG                ResizePatterns                  (struct SongInfo *si, ULONG min, ULONG max);
+GLOBALCALL struct Pattern      *CopyPattern    (struct SongInfo *si, struct Pattern *src, ULONG destNum);
+
+/* From "Prefs.c" */
+GLOBALCALL LONG                LoadPrefs                               (CONST_STRPTR filename);
+GLOBALCALL LONG                SavePrefs                               (CONST_STRPTR filename);
+
+/* From "Library.c" */
+GLOBALCALL ULONG       MakeXModuleLibrary              (void);
+GLOBALCALL void                DisposeXModuleLibrary   (void);
+
+/* From "Locale.c" */
+GLOBALCALL void                SetupLocale                             (void);
+GLOBALCALL void                CleanupLocale                   (void);
+
+/* From "XModuleHook.c" */
+GLOBALCALL void                AddXModuleHooks                 (void);
+GLOBALCALL LONG                LoadSong                                (struct IFFHandle *iff, struct SongInfo *si);
+GLOBALCALL struct Pattern *LoadPattern         (struct SongInfo *si, ULONG num, struct IFFHandle *iff);
+GLOBALCALL LONG                SaveSong                                (struct IFFHandle *iff, struct SongInfo *si);
+GLOBALCALL LONG                SaveSequence                    (struct IFFHandle *iff, struct SongInfo *si);
+GLOBALCALL LONG                SavePatterns                    (struct IFFHandle *iff, struct SongInfo *si);
+GLOBALCALL LONG                SaveInstruments                 (struct IFFHandle *iff, struct SongInfo *si);
+GLOBALCALL LONG                SavePattern                             (struct IFFHandle *iff, struct Pattern *patt);
+GLOBALCALL LONG                WriteStringChunk                (struct IFFHandle *iff, CONST_STRPTR name, ULONG id);
+
+/* From "TrackerHook.c" */
+GLOBALCALL void        AddTrackerHooks (void);
+
+/* From "Startup.asm" */
+extern void                    _XCEXIT         (LONG err);
+extern void                    SPrintf         (UBYTE *buf, CONST_STRPTR formatstr, ...);
+extern void STDARGS    VSPrintf        (UBYTE *buf, CONST_STRPTR formatstr, void *argv);
+extern struct WBStartup        *WBenchMsg;
+extern struct Process  *ThisTask;
+
+
+#ifndef OS30_ONLY
+
+/* Memory pools support for pre-V39 OS */
+
+#define CreatePool(f,p,t)      LibCreatePool((f),(p),(t))
+#define DeletePool(p)          LibDeletePool(p)
+#define AllocPooled(p,s)       AsmAllocPooled((p), (s), SysBase)
+#define FreePooled(p,m,s)      AsmFreePooled((p), (m), (s), SysBase)
+
+extern ASMCALL void *AsmAllocPooled            (REG(a0, void *pool), REG(d0, ULONG size), REG(a6, struct ExecBase *SysBase));
+extern ASMCALL void *AsmFreePooled             (REG(a0, void *pool), REG(a1, void *drop), REG(d0, ULONG size), REG(a6, struct ExecBase *SysBase));
+
+#endif /* !OS30_ONLY */
+
+
+/* More memory pools support */
+
+#ifdef PORTABLE
+       GLOBALCALL void *CAllocPooled   (void *pool, ULONG size);
+       GLOBALCALL void *AllocVecPooled (void *pool, ULONG size);
+       GLOBALCALL void FreeVecPooled   (void *pool, void *drop);
+#else
+       #define AllocVecPooled(p,s)     AsmAllocVecPooled((p), (s), SysBase)
+       #define FreeVecPooled(p,m)      AsmFreeVecPooled((p), (m), SysBase)
+       #define CAllocPooled(p,s)       AsmCAllocPooled((p), (s), SysBase)
+
+       extern ASMCALL void *AsmCAllocPooled    (REG(a0, void *pool), REG(d0, ULONG size), REG(a6, struct ExecBase *SysBase));
+       extern ASMCALL void *AsmAllocVecPooled  (REG(a0, void *pool), REG(d0, ULONG size), REG(a6, struct ExecBase *SysBase));
+       extern ASMCALL void AsmFreeVecPooled    (REG(a0, void *pool), REG(a1, void *drop), REG(a6, struct ExecBase *SysBase));
+#endif
+
+
+/* Use these macros when the corresponding functions are not available.
+ * GLOBALCALL void     InstallGfxFunctions             (void);
+ * GLOBALCALL ULONG (*ReadAPen)(struct RastPort *RPort);
+ * GLOBALCALL ULONG (*ReadBPen)(struct RastPort *RPort);
+ * GLOBALCALL ULONG (*ReadDrMd)(struct RastPort *RPort);
+ */
+
+
+
+/********************/
+/* Global variables */
+/********************/
+
+XREF struct Catalog    *Catalog;
+XREF void                      *Pool;
+XREF STRPTR                     AppStrings[];
+XREF BOOL                       Verbose;
+XREF BPTR                       StdOut;
+XREF LONG                       LastErr;
+XREF UBYTE                      PubPortName[];
+
+XREF BYTE                       CxPri;
+XREF UBYTE                      CxPopKey[32];
+XREF BOOL                       CxPopup;
+
+XREF LONG                       IconX;
+XREF LONG                       IconY;
+XREF UBYTE                      IconName[16];
+
+XREF const UBYTE        Version[];
+XREF const UBYTE        BaseName[];    /* All caps             */
+XREF const UBYTE        PrgName[];             /* Mixed case   */
+
+#endif /* !XMODULE_PRIV_H */
diff --git a/XModule_final.c b/XModule_final.c
new file mode 100644 (file)
index 0000000..19be4a4
--- /dev/null
@@ -0,0 +1,32 @@
+/* This source file is automatically generated - do not edit */
+#include "Main.c"
+#include "Misc.c"
+#include "Rexx.c"
+#include "Gui.c"
+#include "Requesters.c"
+#include "Prefs.c"
+#include "Locale.c"
+#include "Audio.c"
+#include "Compress.c"
+#include "App.c"
+#include "Cx.c"
+#include "Help.c"
+#include "Operators.c"
+#include "Instr.c"
+#include "SongClass.c"
+#include "Library.c"
+#include "XModuleHook.c"
+#include "TrackerHook.c"
+#include "CustomClasses.c"
+#include "ToolBoxWin.c"
+#include "OptimizationWin.c"
+#include "ClearWin.c"
+#include "InstrumentsWin.c"
+#include "SongInfoWin.c"
+#include "SequenceWin.c"
+#include "ProgressWin.c"
+#include "SaversWin.c"
+#include "PlayWin.c"
+#include "PrefsWin.c"
+#include "PatternWin.c"
+#include "PattPrefsWin.c"
diff --git a/XModule_rev.h b/XModule_rev.h
new file mode 100644 (file)
index 0000000..68ef7c0
--- /dev/null
@@ -0,0 +1,12 @@
+/* C Headerfile generated by RevUp 1.2 */
+
+#define VERSION                3
+#define REVISION       5
+#define DATE   "24.6.95"
+#define VERS   "XModule 3.5"
+#define VSTRING        "XModule 3.5 (24.6.95)\r\n"
+#define VERSTAG        "\0$VER: XModule 3.5 (24.6.95)"
+#define TIME   "01:28:57"
+#define PRGNAME        "XModule"
+#define BASENAME       "XMODULE"
+#define VSTR   "XModule 3.5 (24.6.95)"
diff --git a/XModule_rev.rev b/XModule_rev.rev
new file mode 100644 (file)
index 0000000..62f9457
--- /dev/null
@@ -0,0 +1 @@
+6
\ No newline at end of file
diff --git a/config.mk b/config.mk
new file mode 100644 (file)
index 0000000..33d5557
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,165 @@
+##
+## $Id:$
+##
+## Build environment configuration parameters
+## Copyright (C) 1999 by Bernardo Innocenti
+
+###########################################################
+# Package configuration
+###########################################################
+
+# set to OS30_ONLY to leave out support for old V37
+# set to ANY_OS to make an executable for V37 with V39 support
+#
+OSVER := OS30_ONLY
+
+# CPU to compile for (eg: "68020").
+#
+CPU := 68020
+
+# Compiler to use. Possible options are:
+# sc     - SAS/C 6.58 or better
+# gcc    - gcc 2.7.2 or EGCS 1.1b
+# vbcc   - Not yet supported
+# stormc - Not yet supported
+#
+COMPILER := gcc
+
+# Set to 1 to build debug executables
+#
+DEBUG := 0
+
+# Minimum stack size to allocate
+#
+STACKSIZE := 16384
+
+###########################################################
+# Paths
+###########################################################
+#
+PROJNAME       := XModule
+
+ifeq ($(COMPILER),gcc)
+TOP                    := /SC/src/xm
+else
+TOP                    := SC:src/xm
+endif
+
+OBJDIR         := $(TOP)/obj
+INCDIR         := $(TOP)/include
+DISTPREFIX     := $(TOP)/dist
+ARCNAME                := $(DISTPREFIX)/$(PROJNAME).lzx
+SRCARCNAME     := $(DISTPREFIX)/$(PROJNAME)_src.lzx
+
+###########################################################
+# Tools used in the Makefiles
+###########################################################
+#
+MAKEINFO       := GG:bin/makeinfo
+FD2PRAGMA      := fd2pragma
+FLEXCAT                := FlexCat
+ARCHIVER       := lzx -3 -e -r a
+
+ifeq ($(COMPILER),sc)
+       CC              := sc
+       AS              := PhxAss
+       OBJCONV := echo >NIL:
+       LD              := PhxLnk
+       CP              := copy CLONE
+       MV              := rename
+       RM              := delete
+endif
+
+ifeq  ($(COMPILER),gcc)
+       CC              := gcc
+       AS              := PhxAss
+       OBJCONV := hunk2aout
+       LD              := gcc
+       CP              := cp -p
+       MV              := mv
+       RM              := rm
+endif
+
+###########################################################
+# SAS/C compiler, assembler and linker flags
+###########################################################
+#
+ifeq ($(COMPILER),sc)
+       CMN_CFLAGS      := CODE=FAR PARM=REGISTERS STRMERGE STRINGSCONST NOSTKCHK AFP \
+               UTILLIB MCCONS NOMINC NOLINK CNEST INCDIR=$(TOP) INCDIR=$(INCDIR)
+       OPT_CFLAGS      := OPTIMIZE OPTTIME OPTINLINELOCAL
+       DBG_CFLAGS      := DEBUG=FULLFLUSH DEF DEBUG
+       CC_OUT  := OBJNAME
+
+       CMN_ASFLAGS     := SMALLDATA SMALLCODE ALIGN MACHINE=$(CPU) \
+               INCPATH=INCLUDE:,$(INCDIR) NOEXE QUIET
+       OPT_ASFLAGS     := OPT !
+       DBG_ASFLAGS     := SYMDEBUG LINEDEBUG SET "DEBUG"
+
+       CMN_LDFLAGS     := SMALLCODE SMALLDATA DEFINE \
+                "__CXM33=__UCXM33,__CXD33=__UCXD33,__CXM22=__UCXM22,__CXD22=__UCXD22"
+       OPT_LDFLAGS     := NODEBUG
+       DBG_LDFLAGS     := NOSHORTRELOCS
+
+       CMN_LIBS        := LIB:sc.lib LIB:small.lib
+       OPT_LIBS        :=
+       DBG_LIBS        := LIB:debug.lib
+
+# GST usage must be disabled due to bugs in SAS/C 6.58
+#      GST                     := $(OBJDIR)/$(PROJNAME).gst
+#      CMN_CFLAGS += GST=$(GST)
+
+# Old flags used with slink:
+#      CMN_LDFLAGS += SMALLCODE SMALLDATA NOALVS NOICONS XREF \
+#              DEFINE __CXM33=__UCXM33 DEFINE __CXD33=__UCXD33 \
+#              DEFINE __CXM22=__UCXM22 DEFINE __CXD22=__UCXD22
+#      DBG_LDFLAGS += BATCH ADDSYM
+
+endif
+
+
+###########################################################
+# GCC compiler, assembler and linker flags
+###########################################################
+#
+ifeq ($(COMPILER),gcc)
+       CMN_CFLAGS := -c -Wall -W -Wundef -Wimplicit -Wreturn-type \
+               -m$(CPU) -D$(OSVER) -I$(INCDIR) -I$(TOP) -I/gg/include -I/include
+       OPT_CFLAGS := -O2 -msmall-code -mregparm -fomit-frame-pointer \
+               -funroll-loops -fstrict-aliasing -finline-functions \
+               -fno-implement-inlines
+       DBG_CFLAGS := -DDEBUG -g
+       CC_OUT  := -o
+
+       CMN_ASFLAGS     := SMALLDATA SMALLCODE ALIGN MACHINE=$(CPU) \
+               INCPATH=INCLUDE:,$(INCDIR) NOEXE QUIET
+       OPT_ASFLAGS     := OPT !
+       DBG_ASFLAGS     := SYMDEBUG LINEDEBUG SET "DEBUG"
+
+       OPT_LDFLAGS := -s
+       DBG_LDFLAGS :=
+       CMN_LDFLAGS :=
+endif
+
+###########################################################
+# Build flags for Debug/Release version
+###########################################################
+#
+ifeq ($(DEBUG),1)
+       CFLAGS  := $(CMN_CFLAGS) $(DBG_CFLAGS)
+       ASFLAGS := $(CMN_ASFLAGS) $(DBG_ASFLAGS)
+       LDFLAGS := $(CMN_LDFLAGS) $(DBG_LDFLAGS)
+       LIBS    := $(CMN_LIBS) $(DBG_LIBS)
+else
+       CFLAGS  := $(CMN_CFLAGS) $(OPT_CFLAGS)
+       ASFLAGS := $(CMN_ASFLAGS) $(OPT_ASFLAGS)
+       LDFLAGS := $(CMN_LDFLAGS) $(OPT_LDFLAGS)
+       LIBS    := $(CMN_LIBS) $(OPT_LIBS)
+endif
+
+###########################################################
+# GNU make quirks
+###########################################################
+#
+# clear implicit rules for known suffixes such as .c and .o
+.SUFFIXES:
diff --git a/delta.asm b/delta.asm
new file mode 100644 (file)
index 0000000..1aa1d4b
--- /dev/null
+++ b/delta.asm
@@ -0,0 +1,56 @@
+**
+** Delta.asm
+**
+** Copyright (C) 1995 Bernardo Innocenti
+**
+** Delta encoding/decoding routine
+**
+
+_DeltaDecode:
+
+* void DeltaDecode (BYTE *dataBlock, ULONG blockSize);
+*                   A0                D0
+*
+*      old=0
+*      for i=0 to blockSize-1
+*              new=dataBlock[i]+old
+*              dataBlock[i]=new
+*              old=new
+
+       MOVEQ   #0,D1
+
+$1
+       ADD.B   (A0),D1
+       MOVE.B  D1,(A0)+
+
+       SUBQ.L  #1,D0           ; Loop
+       BNE             $1
+
+       RTS
+
+
+_DeltaEncode:
+
+* void DeltaEncode (BYTE *dataBlock, ULONG blockSize);
+*                   A0                D0
+*
+*      old=0
+*      for i=0 to blockSize-1
+*              old=dataBlock[i]
+*              new=old-new
+*              datablock[i]=new
+
+
+       MOVE.W  D2,-(SP)
+       MOVEQ   #0,D1
+
+$1
+       MOVE.B  (A0),D2
+       SUB.B   D2,D1
+       MOVE.B  D1,(A0)+
+
+       SUBQ.L  #1,D0           ; Loop
+       BNE             $1
+
+       MOVE.W  (SP)+,D2
+       RTS
diff --git a/include/clib/xmodule_protos.h b/include/clib/xmodule_protos.h
new file mode 100644 (file)
index 0000000..c1f6120
--- /dev/null
@@ -0,0 +1,67 @@
+#ifndef  CLIB_XMODULE_PROTOS_H
+#define  CLIB_XMODULE_PROTOS_H
+
+/*
+**     $VER: xmodule_protos.h 4.0 (16.1.96)
+**
+**     C prototypes. For use with 32 bit integers only.
+**
+**     (C) Copyright 1995,96 Bernardo Innocenti
+**         All Rights Reserved
+*/
+
+#ifndef  EXEC_TYPES_H
+#include <exec/types.h>
+#endif
+#ifndef  UTILITY_TAGITEM_H
+#include <utility/tagitem.h>
+#endif
+#ifndef  LIBRARIES_XMODULE_H
+#include <libraries/xmodule.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*--- functions in V1 or higher (Release 4.0) ---*/
+
+/* Public entries */
+
+struct SongInfo *xmCreateSongA( struct TagItem * tagList );
+struct SongInfo *xmCreateSong( ULONG tag1, ... );
+BOOL xmDeleteSong( struct SongInfo *si );
+ULONG xmAddSongA( struct SongInfo *si, struct SongInfo *position, struct TagItem *tagList );
+ULONG xmRemSong( struct SongInfo *si );
+ULONG xmActivateSong( struct SongInfo *si );
+struct SongInfo *xmLockActiveSong( UWORD mode );
+struct XMHook *xmAddHookA( struct TagItem * tagList );
+struct XMHook *xmAddHook( ULONG tag1, ... );
+void xmRemHook ( struct XMHook *hook );
+struct XMHook *xmIdentifyModule( BPTR fh, struct TagItem * tagList );
+struct SongInfo *xmLoadModuleA( CONST_STRPTR fileName, struct TagItem * tagList );
+struct SongInfo *xmLoadModule( CONST_STRPTR fileName, ULONG tag1, ... );
+LONG xmSaveModuleA( struct SongInfo *si, CONST_STRPTR filename, struct XMHook *saver, struct TagItem * tagList );
+LONG xmSaveModule( struct SongInfo *si, CONST_STRPTR filename, struct XMHook *saver, ULONG tag1, ... );
+UWORD *xmSetSongLen( struct SongInfo *si, UWORD length );
+struct Pattern *xmAddPatternA( struct SongInfo *, struct TagItem *);
+struct Pattern *xmAddPattern( struct SongInfo *, LONG Tag1, ...);
+struct Pattern *xmSetPatternA( struct SongInfo *, ULONG pattNum, struct TagItem * );
+struct Pattern *xmSetPattern( struct SongInfo *, ULONG pattNum, LONG Tag1, ...);
+void xmRemPattern( struct SongInfo *, LONG, LONG );
+struct Instrument *xmAddInstrumentA( struct SongInfo *, LONG instrNum, struct TagItem * );
+struct Instrument *xmAddInstrument( struct SongInfo *, LONG instrNum, LONG, ... );
+struct Instrument *xmSetInstrumentA( struct SongInfo *, LONG instrNum, struct TagItem * );
+struct Instrument *xmSetInstrument( struct SongInfo *, LONG instrNum, LONG, ... );
+void xmRemInstrument( struct SongInfo *, LONG );
+LONG xmProcessSongA( struct SongInfo *, void *, struct TagItem * );
+LONG xmProcessSong( struct SongInfo *, void *, LONG, ... );
+LONG xmDisplayMessageA( LONG level, APTR messsage, APTR args );
+LONG xmDisplayMessage( LONG level, APTR messsage, ... );
+LONG xmDisplayProgress( ULONG actual, ULONG total );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* CLIB_XMODULE_PROTOS_H */
diff --git a/include/libraries/patteditclass.h b/include/libraries/patteditclass.h
new file mode 100644 (file)
index 0000000..430e6c4
--- /dev/null
@@ -0,0 +1,233 @@
+#ifndef LIBRARIES_PATTEDITCLASS_H
+#define LIBRARIES_PATTEDITCLASS_H
+/*
+**     PattEditClass.h
+**
+**     Copyright (C) 1995 by Bernardo Innocenti
+**
+**     Pattern editor class built on top of the "gadgetclass".
+**
+**     Note: Use 4 chars wide TABs to read this file.
+*/
+
+#ifndef DATATYPES_DATATYPESCLASS_H
+#include <datatypes/datatypesclass.h>
+#endif
+
+
+#define PATTEDITNAME   "gadgets/pattedit.gadget"
+#define PATTEDITCLASS  "patteditclass"
+#define PATTEDITVERS   2
+
+
+/********************/
+/* Class Attributes */
+/********************/
+
+#define PEA_Dummy                              (TAG_USER + 'PE' << 8)
+
+#define PEA_CursTrack                  (PEA_Dummy+1)
+#define PEA_CursColumn                 (PEA_Dummy+2)
+#define PEA_CursLine                   (PEA_Dummy+3)
+       /* (IGSNU) Cursor position.
+        */
+
+#define PEA_LeftTrack                  DTA_TopHoriz
+#define PEA_TopLine                            DTA_TopVert
+       /* (IGSNU) Top line and leftmost track of the editor view.
+        */
+
+#define PEA_Left                               (PEA_Dummy+6)
+#define PEA_Right                              (PEA_Dummy+7)
+#define PEA_Up                                 (PEA_Dummy+8)
+#define PEA_Down                               (PEA_Dummy+9)
+#define PEA_CursLeft                   (PEA_Dummy+10)
+#define PEA_CursRight                  (PEA_Dummy+11)
+#define PEA_CursUp                             (PEA_Dummy+12)
+#define PEA_CursDown                   (PEA_Dummy+13)
+       /* (S) PEA_Left and PEA_Right scroll the view left and right one
+        * line.  The cursor is positioned to the leftmost or rightmost
+        * position, respectively. PEA_Up and PEA_Down scroll the view up
+        * and down of one line.  The cursor is positioned to the upper or
+        * lower visible line, respectively.   PEA_Curs#? attributes move
+        * the cursor one position towards the direction specified.  The
+        * contents of ti_Data is meaningless for all these attributes.
+        */
+
+#define PEA_UndoChange                 (PEA_Dummy+14)
+       /* (S) If ti_Data is > 0, one change made to the pattern is
+        * undone.  If ti_Data is < 0, a previously undone change is
+        * redone.  The cursor is always moved on the affected position.
+        */
+
+#define PEA_Changes                            (PEA_Dummy+15)
+       /* (IGS) ti_Data contains the number of changes made to the pattern.
+        */
+
+#define PEA_MarkRegion                 (PEA_Dummy+16)
+       /* (GS) ti_Data points to a struct Rectangle containing the limits
+        * (tracks and lines) of a pattern sub-region.  When set, this
+        * attribute automatically starts range mode.  Passing NULL in
+        * ti_Data causes the editor to clear the marked region.  Passing
+        * ~0 in ti_Data toggles mark mode on and off.
+        * Getting this attribute returns a copy of the currently
+        * marked region, or {0,0,0,0} if no region is currently
+        * selected.  The following relations must be true when setting
+        * this attribute and are guaranteed when getting it:
+        *
+        *      MinX <= MaxX
+        *      MinY <= MaxY
+        *      MaxX < Patt->Tracks
+        *      MaxY < Patt->Lines
+        */
+
+#define PEA_Flags                              (PEA_Dummy+17)
+       /* (IGSN) See PEF_#? flags definitions below.
+        */
+
+#define PEA_DisplayTracks              DTA_VisibleHoriz
+#define PEA_DisplayLines               DTA_VisibleVert
+       /* (GN) Maximum number of tracks and lines that fit in the gadget
+        * bounds.
+        */
+
+#define PEA_Pattern                    (PEA_Dummy+20)
+       /* (ISGU) ti_Data is a pointer to the Pattern
+        * structure to be displayed by the PatternEditor.
+        */
+
+#define PEA_CurrentInst                (PEA_Dummy+21)
+       /* (IS) ti_Data is the default instrument
+        * number to be used when entering notes in the
+        * pattern editor.
+        */
+
+#define PEA_MaxUndoLevels              (PEA_Dummy+22)
+       /* (IS) ti_Data is the maximum size of the undo buffer in number
+        * of slots.  0 disables undo feature.  Defaults to 16.
+        */
+
+#define PEA_MaxUndoMem                 (PEA_Dummy+23)
+       /* (IS) ti_Data is the maximum memory used by the undo buffers.
+        * Setting it to 0 means unlimited memory.  Defaults to 8192 bytes.
+        */
+
+#define PEA_TextFont                   DTA_TextFont
+       /* (I) ti_Data points to the TextFont to be
+        * used with the PatternEditor.  The font must
+        * be mono spaced.
+        */
+
+#define PEA_AdvanceCurs                        (PEA_Dummy+25)
+       /* (IS) The lower 16 bits of ti_Data contain the number of lines
+        * the cursor moves when a note is typed. The upper 16 bits contain
+        * the number of tracks. Both numbers are signed, so backwards
+        * cursor advancement is possible.
+        */
+
+#define PEA_CursWrap                   (PEA_Dummy+26)
+       /* (IS) Can be 0 for no wrapping, PEF_HWRAP, PEF_VWRAP or both.
+        */
+
+#define PEA_BGPen                              (PEA_Dummy+27)
+#define PEA_TextPen                            (PEA_Dummy+28)
+#define PEA_LinesPen                   (PEA_Dummy+29)
+#define PEA_TinyLinesPen               (PEA_Dummy+30)
+       /* (IS) Pens to be used to render the various editor elements.
+        * PEA_TextPen must be a power of two (1, 2, 4, 8...) because
+        * the text is rendered on one bitplane only. PEA_LinesPen
+        * and PEA_TinyLinesPen must not have the PEA_TextPen bit
+        * set because text scrolling happens on the text bitplane only
+        * and must not disturb the other elements.  The defaults are
+        * 1 for PEA_TextPen and 2 for both PEA_LinesPen and
+        * PEA_TinyLinesPen.
+        */
+
+#define PEA_KeyboardMap                (PEA_Dummy+31)
+       /* (IS) ti_Data points to a table which describes the notes associated
+        * to of the keys.  The table starts with 1 Word specifying the number
+        * of keys following.
+        *
+        * Each key definition has this format:
+        *
+        *      1 Byte with the note associated with the first key.
+        *      1 Byte with the instrument associated with the first key.
+        *              This value may be 0, in which case the current instrument
+        *              will be used.
+        *
+        * Passing NULL in ti_Data disables keyboard mapping.
+        *
+        * NOTE: The table is referenced, not copied, so it must stay in
+        * memory until the pattern editor is disposed or the table is
+        * disabled.
+        */
+
+#define PEA_TrackChars         (PEA_Dummy+32)
+       /* (IGSNU) Number of straight characters in a track.  Minimum
+        * value is MINTRACKCHARS, maximum is MAXTRACKCHARS.
+        * Defaults to 10.
+        */
+
+#define PEA_ChangeNote         (PEA_Dummy+33)
+       /* (GNU) ti_Data contains a packed track/line pair of coordinates
+        * (the line in the low order 16 bits and the track in the upper
+        * 16bits). The editor sends update messages containing this tag
+        * whenever a note has been changed, and redraws the specified note
+        * when it receives this tag.  This is a way to keep two or more
+        * editors synchronized while they display the same pattern.  It can
+        * also be used to count the changes made to the song.
+        * ti_Data will be -1L when more than one note has changed (for
+        * istance when the user has pasted a block), and other editors
+        * will have to redraw their display completely.
+        */
+
+#define PEA_SongInfo           (PEA_Dummy+34)
+       /* (ISGU) ti_Data is a pointer to the songclass object which
+        * contains the pattern to be displayed.  Without this tag, the
+        * pattern editor is unable to do some operations.
+        */
+
+#define PEA_PattNum                    (PEA_Dummy+35)
+       /* (ISGNU) The pattern number to display. To use this tag,
+        * the editor needs to be passed the PEA_SongInfo tag too.
+        */
+
+
+/* Definitions for PEA_Flags attribute */
+
+#define PEF_MARKING                    (1<<0)  /* Range mode                                           */
+#define PEF_HWRAP                      (1<<1)  /* Horizontal cursor wrap                       */
+#define PEF_VWRAP                      (1<<2)  /* Vertical cursor wrap                         */
+#define PEF_HEXMODE                    (1<<3)  /* Use hexadecimal numbers                      */
+#define PEF_BLANKZERO          (1<<4)  /* Blank zero digits                            */
+#define PEF_INVERSETEXT                (1<<5)  /* Show backfilled text                         */
+#define PEF_DOTINYLINES                (1<<6)  /* Show tiny separator lines            */
+#define PEF_DOCURSORRULER      (1<<7)  /* Show a ruler under the cursor        */
+#define PEF_MARKFULLTRACKS     (1<<8)  /* Mark full tracks only                        */
+
+/* Private flags - Not settable by application */
+#define PEF_ADJUSTSCROLLBOX    (1<<28) /* User is adjusting the scroll box     */
+#define PEF_RESIZETRACKS       (1<<29) /* User is resizing the tracks          */
+#define PEF_SCROLLING          (1<<30) /* View is scrolling (read only)        */
+#define PEF_DRAGGING           (1<<31) /* Cursor drag mode (read only)         */
+
+
+
+/* Width of a track expressed in chars */
+#define MAXTRACKCHARS  14
+#define MINTRACKCHARS  4
+
+
+/* Cursor column names */
+enum {
+       COL_NOTE,       /* Cursor on note field                                 */
+       COL_INSTH,      /* Cursor on instrument high nibble             */
+       COL_INSTL,      /* Cursor on instrument low  nibble     */
+       COL_EFF,        /* Cursor on effect field                               */
+       COL_VALH,       /* Cursor on effect value high nibble   */
+       COL_VALL,       /* Cursor on effect value low  nibble   */
+
+       COL_COUNT
+};
+
+#endif /* !LIBRARIES_PATTEDITCLASS_H */
diff --git a/include/libraries/sampeditclass.h b/include/libraries/sampeditclass.h
new file mode 100644 (file)
index 0000000..402afb1
--- /dev/null
@@ -0,0 +1,123 @@
+#ifndef SAMPEDITCLASS_H
+#define SAMPEDITCLASS_H
+/*
+**     SampEditClass.h
+**
+**     Copyright (C) 1995,96 by Bernardo Innocenti
+**
+**     Sample editor class built on top of the "gadgetclass".
+**
+**     Note: Use 4 chars wide TABs to read this file.
+*/
+
+
+#define SAMPEDITNAME   "gadgets/sampedit.gadget"
+#define SAMPEDITCLASS  "sampeditclass"
+#define SAMPEDITVERS   1
+
+
+/********************/
+/* Class Attributes */
+/********************/
+
+#define SEA_CursXPos                   (TAG_USER+1)
+#define SEA_CursYPos                   (TAG_USER+2)
+       /* (IGSNU) Cursor position. CursYPos is unused.
+        */
+
+#define SEA_XStart                     (TAG_USER+2)
+#define SEA_YStart                     (TAG_USER+3)
+       /* (IGSNU) Left and top offsett of the display relative to the start of the sample.
+        */
+
+#define SEA_XRatio                     (TAG_USER+4)
+#define SEA_YRatio                     (TAG_USER+5)
+       /* (ISGNU) Fixed point 16+16 bit number for the zoom factor. The upper word holds
+        * the integer part and the low word represents the decimals.  Setting the ratio
+        * to 65536 sets the zoom factor to 1:1.  Bigger numbers produce an higher zoom
+        * factor (e.g.: 131072 is 2:1).
+        */
+
+#define SEA_ScrollLeft         (TAG_USER+6)
+#define SEA_ScrollRight                (TAG_USER+7)
+#define SEA_ScrollUp           (TAG_USER+8)
+#define SEA_ScrollDown         (TAG_USER+9)
+#define SEA_CursLeft           (TAG_USER+10)
+#define SEA_CursRight          (TAG_USER+11)
+#define SEA_CursUp                     (TAG_USER+12)
+#define SEA_CursDown           (TAG_USER+13)
+       /* (S) SEA_ScrollLeft and SEA_ScrollRight scroll the display left
+        * and right one unit.  SEA_ScrollUp and SEA_ScrollDown move the
+        * display up and down of one unit. SEA_Curs#? attributes move
+        * the cursor one position towards the direction specified.  The
+        * contents of ti_Data is meaningless for all these attributes.
+        */
+
+#define SEA_UndoChange         (TAG_USER+14)
+       /* (S) If ti_Data is > 0, one change made to the pattern is
+        * undone.  If ti_Data is < 0, a previously undone change is
+        * redone.  The cursor is always moved on the affected position.
+        */
+
+#define SEA_Changes                    (TAG_USER+15)
+       /* (IGS) ti_Data contains the number of changes made to the sample.
+        */
+
+#define SEA_MarkRegion         (TAG_USER+16)
+       /* (GS) ti_Data points to a struct Rectangle containing the
+        * limits (tracks and lines) of a pattern sub-region.
+        * When set, this attribute automatically starts range mode.
+        * Passing NULL in ti_Data causes the editor to clear the
+        * marked region. Passing ~0 in ti_Data toggles mark mode.
+        * Getting this attribute returns a copy of the currently
+        * marked region, or {0,0,0,0} if no region is currently
+        * selected.
+        */
+
+#define SEA_Flags                      (TAG_USER+17)
+       /* (IGSN) See PEF_#? flags definitions below.
+        */
+
+#define SEA_Instrument (TAG_USER+18)
+       /* (ISGU) ti_Data is a pointer to the Instrument
+        * structure to be displayed by the SampleEditor.
+        */
+
+#define SEA_MaxUndoLevels              (TAG_USER+19)
+       /* (IS) ti_Data is the maximum size of the undo buffer in number
+        * of slots.  0 disables undo feature.  Defaults to 8.
+        */
+
+#define SEA_MaxUndoMem         (TAG_USER+20)
+       /* (IS) ti_Data is the maximum memory used by the undo buffers.
+        * Setting it to 0 means unlimited memory.  Defaults to 32768 bytes.
+        */
+
+#define SEA_BackgroundPen      (TAG_USER+21)
+#define SEA_LinePen            (TAG_USER+22)
+#define SEA_FillPen            (TAG_USER+23)
+#define SEA_GridPen            (TAG_USER+24)
+       /* (IS) Pens to be used to render the various editor elements.
+        * SEA_TextPen must be a power of two (1, 2, 4, 8...) because
+        * the text is rendered on one bitplane only. SEA_LinesPen
+        * and SEA_TinyLinesPen must not have the SEA_TextPen bit
+        * set because text scrolling happens on the text bitplane only
+        * and must not disturb the other elements.  The defaults are
+        * 1 for SEA_TextPen and 2 for both SEA_LinesPen and
+        * SEA_TinyLinesPen.
+        */
+
+
+
+/* Definitions for SEA_Flags attribute */
+
+#define SEF_MARKING                    (1<<0)  /* Range mode                                           */
+#define SEF_LOGMODE                    (1<<1)  /* Logaritmic mode on                           */
+#define SEF_DOGRID                     (1<<2)  /* Show superimposed grid                       */
+
+/* Private flags - Not settable by application */
+#define SEF_SCROLLING          (1<<30) /* View is scrolling (read only)        */
+#define SEF_DRAGGING           (1<<31) /* Cursor drag mode (read only)         */
+
+
+#endif /* SAMPEDITCLASS_H */
diff --git a/include/libraries/songclass.h b/include/libraries/songclass.h
new file mode 100644 (file)
index 0000000..126f25a
--- /dev/null
@@ -0,0 +1,341 @@
+#ifndef LIBRARIES_SONGCLASS_H
+#define LIBRARIES_SONGCLASS_H
+/*
+**     $VER: songclass.h 3.9 (16.1.96)
+**     Copyright (C) 1996 Bernardo Innocenti
+**
+**     Set TAB size to 4 chars to read this header file
+**
+**     Song class definition.
+*/
+
+#ifndef        EXEC_SEMAPHORES_H
+#include <exec/semaphores.h>
+#endif /* EXEC_SEMAPHORES_H */
+
+#ifndef        INTUITION_CLASSES_H
+#include <intuition/classes.h>
+#endif /* INTUITION_CLASSES_H */
+
+
+
+/* The name of the songclass */
+#define SONGCLASS "songclass"
+
+
+
+/* Song methods */
+#define SNGM_Dummy                             (TAG_USER + ('SG'<<8))
+#define SNGM_ADDPATTERN                        (SNGM_Dummy + 1)
+#define SNGM_SETPATTERN                        (SNGM_Dummy + 2)
+#define SNGM_REMPATTERN                        (SNGM_Dummy + 3)
+#define SNGM_ADDINSTRUMENT             (SNGM_Dummy + 4)
+#define SNGM_SETINSTRUMENT             (SNGM_Dummy + 5)
+#define SNGM_REMINSTRUMENT             (SNGM_Dummy + 6)
+#define SNGM_SWAPINSTRUMENTS   (SNGM_Dummy + 7)
+
+
+#define spAddPattern opSet             /* No GadgetInfo field required */
+
+struct spSetPattern
+{
+       ULONG                    MethodID;
+       ULONG                    spsp_PattNum;
+       struct TagItem  *spsp_AttrList;
+};
+
+struct spRemPattern
+{
+       ULONG   MethodID;
+       ULONG   sprp_PattNum;
+       ULONG   sprp_NewPatt;
+};
+
+struct spSetInstrument
+{
+       ULONG                    MethodID;
+       ULONG                    spsi_InstrNum;
+       struct TagItem  *spsi_AttrList;
+};
+
+#define spAddInstrument spSetInstrument
+
+struct spRemInstrument
+{
+       ULONG                    MethodID;
+       ULONG                    spri_Num;
+};
+
+struct spSwapInstruments
+{
+       ULONG                    MethodID;
+       ULONG                    spsi_Num1;
+       ULONG                    spsi_Num2;
+};
+
+
+
+/* Song attributes */
+
+#define SNGA_Dummy                     (TAG_USER + 'SG'<<8)    /* use  */
+
+#define SNGA_CurrentPatt       (SNGA_Dummy + 1)                /* ISG  */
+#define SNGA_CurrentPos                (SNGA_Dummy + 2)                /* ISG  */
+#define SNGA_CurrentInst       (SNGA_Dummy + 3)                /* ISG  */
+#define SNGA_CurrentLine       (SNGA_Dummy + 4)                /* ISG  */
+#define SNGA_CurrentTrack      (SNGA_Dummy + 5)                /* ISG  */
+
+#define SNGA_Length                    (SNGA_Dummy + 6)                /* ISG  */
+#define SNGA_NumPatterns       (SNGA_Dummy + 7)                /* G    */
+#define SNGA_NumInstruments    (SNGA_Dummy + 8)                /* G    */
+#define SNGA_MaxTracks         (SNGA_Dummy + 9)                /* G    */
+
+#define SNGA_Title                     (SNGA_Dummy + 10)               /* ISG  */
+#define SNGA_Author                    (SNGA_Dummy + 11)               /* ISG  */
+#define SNGA_Description       (SNGA_Dummy + 12)               /* ISG  */
+/* (UBYTE *) Long description/comment for the
+ * song. The string may contain newlines and there is no
+ * length limit.
+ */
+
+#define SNGA_Path                      (SNGA_Dummy + 13)               /* ISG  */
+/* The complete path name of the file the song was
+ * loaded from.
+ */
+
+#define SNGA_Changes           (SNGA_Dummy + 14)               /* ISG  */
+/* (ULONG) Sets the number of changes made to the song
+ * since the last time it was saved.
+ */
+
+#define SNGA_TotalChanges      (SNGA_Dummy + 15)               /* ISG  */
+/* (ULONG) Total number of changes made to the song
+ * since it has been created.
+ */
+
+#define SNGA_CreationDate      (SNGA_Dummy + 16)               /* ISG  */
+#define SNGA_LastChanged       (SNGA_Dummy + 17)               /* ISG  */
+/* (ULONG) Datestamp of last time the song was saved
+ */
+
+#define SNGA_DefaultTracks     (SNGA_Dummy + 32)               /* ISG  */
+#define SNGA_DefaultPattLen    (SNGA_Dummy + 33)               /* ISG  */
+#define SNGA_GlobalSpeed       (SNGA_Dummy + 34)               /* ISG  */
+#define SNGA_GlobalTempo       (SNGA_Dummy + 35)               /* ISG  */
+#define SNGA_RestartPos                (SNGA_Dummy + 36)               /* ISG  */
+
+#define SNGA_ReadyToUse                (SNGA_Dummy + 37)               /* I    */
+/* (BOOL) Add one position and one pattern when
+ * creating a new song.
+ */
+
+#define PATTA_Dummy                    (TAG_USER + 'PA'<<8)
+#define PATTA_Lines                    (PATTA_Dummy + 1)               /* I    */
+#define PATTA_Tracks           (PATTA_Dummy + 2)               /* I    */
+#define PATTA_Name                     (PATTA_Dummy + 3)               /* IS   */
+#define        PATTA_Pattern           (PATTA_Dummy + 4)               /* I    */
+#define PATTA_Num                      (PATTA_Dummy + 5)               /* I    */
+#define PATTA_Replace          (PATTA_Dummy + 6)               /* I    */
+
+
+#define INSTRA_Dummy           (TAG_USER + 'IN'<<8)
+#define INSTRA_Type                    (INSTRA_Dummy + 1)              /* I    */
+#define INSTRA_Name                    (INSTRA_Dummy + 2)              /* IS   */
+#define INSTRA_Volume          (INSTRA_Dummy + 3)              /* IS   */
+#define INSTRA_Sample          (INSTRA_Dummy + 4)              /* IS   */
+#define INSTRA_Length          (INSTRA_Dummy + 5)              /* IS   */
+#define INSTRA_Flags           (INSTRA_Dummy + 6)              /* IS   */
+#define INSTRA_LoopStart       (INSTRA_Dummy + 7)              /* IS   */
+#define INSTRA_LoopLen         (INSTRA_Dummy + 8)              /* IS   */
+#define INSTRA_LoopEnd         (INSTRA_Dummy + 9)              /* IS   */
+#define INSTRA_FineTune                (INSTRA_Dummy + 10)             /* IS   */
+
+
+/* ProTracker-style aliases for loop tags */
+#define INSTRA_Repeat  INSTRA_LoopStart
+#define INSTRA_Replen  INSTRA_LoopLen
+#define INSTRA_Repend  INSTRA_LoopEnd
+
+
+/* Default values for some song attributes */
+#define DEF_PATTLEN             64
+#define DEF_NUMTRACKS    4
+#define DEF_SONGSPEED    6
+#define DEF_SONGTEMPO  125
+
+
+/* Maximum values */
+#define MAXOCTAVES                     6                       /* Number of octaves currently supported        */
+#define MAXNOTES       (12*MAXOCTAVES+1)       /* Number of entries in notes table                     */
+#define MAXTRACKS                      255                     /* Maximum number of tracks in a pattern        */
+#define MAXINSTRUMENTS         255                     /* Maximum number of instruments in a song      */
+#define MAXPATTERNS              32767                 /* Maximum number of patterns                           */
+#define MAXPATTLINES     32767                 /* Maximum number of lines in a pattern         */
+#define MAXPOSITIONS     32767                 /* Maximum number of song positions                     */
+
+#define        SEQUENCE_QUANTUM        32              /* Multiples of SEQUENCE_QUANTUM positions are
+                                                                        * allocated in the sequence array
+                                                                        */
+#define        PATTERNS_QUANTUM        16              /* Multiples of PATTERNS_QUANTUM pointers are
+                                                                        * allocated in the patterns array
+                                                                        */
+
+struct Instrument
+{
+       UWORD    Type;                          /* Instrument type (See defs)   */
+       UWORD    Volume;                        /* Volume (max $40)                             */
+       STRPTR   Name;                          /* Instrument Name                              */
+       ULONG    Length;                        /* Length of instr                              */
+       BYTE    *Sample;                        /* Sampled data                                 */
+       ULONG    Repeat;                        /* Loop start (No loop = 0)             */
+       ULONG    Replen;                        /* Loop size (No loop = 1)              */
+       WORD     FineTune;                      /* Instrument FineTune (-8..+7) */
+       UWORD    Flags;                         /* Unused                                               */
+};
+
+
+#ifndef LIBRARIES_XMODULECLASS_H
+
+/* Possibile values for Instrument->InstType */
+enum {
+       ITYPE_SAMPLE8,          /* 8 bit sampled waveform                               */
+       ITYPE_SAMPLE16,         /* TODO: 16 bit sampled waveform                */
+       ITYPE_SYNTH,            /* TODO: Synthetic instrument                   */
+       ITYPE_HYBRID,           /* TODO: Both Synth & Sample                    */
+       ITYPE_MIDI                      /* TODO: Played by external MIDI device */
+};
+#endif /* !LIBRARIES_XMODULECLASS_H */
+
+
+
+/* Pass this value to AllocVec() to allocate sample memory */
+#define MEMF_SAMPLE    MEMF_ANY
+
+
+#ifndef LIBRARIES_XMODULECLASS_H
+struct Note
+{
+       UBYTE Note;                             /* See below for more info.     */
+       UBYTE Inst;                             /* Instrument number            */
+       UBYTE Vol;                              /* Volume for this note         */
+       UBYTE Pad;                              /* Unused                                       */
+       UBYTE EffNum;                   /* See definitions below.       */
+       UBYTE EffVal;                   /* Effect value ($00-$FF)       */
+};
+#endif /* !LIBRARIES_XMODULECLASS_H */
+
+
+
+struct Pattern
+{
+       UWORD   Tracks;                                 /* Support for variable number of tracks        */
+       UWORD   Lines;                                  /* Number of lines in pattern                           */
+       STRPTR  Name;                                   /* Name of this pattern                                         */
+       ULONG   Reserved[2];                    /* Reserved for future enhancements                     */
+       struct Note *Notes[0];                  /* Pointers to the lines                                        */
+
+       /* Note: dynamic size structure!        */
+};
+
+
+
+/*
+ * Effects
+ */
+#ifndef LIBRARIES_XMODULECLASS_H
+enum {
+       EFF_NULL,                               /* $00 */
+       EFF_PORTAMENTOUP,               /* $01 */
+       EFF_PORTAMENTODOWN,             /* $02 */
+       EFF_TONEPORTAMENTO,             /* $03 */
+       EFF_VIBRATO,                    /* $04 */
+       EFF_TONEPVOLSLIDE,              /* $05 */
+       EFF_VIBRATOVOLSLIDE,    /* $06 */
+       EFF_TREMOLO,                    /* $07 */
+       EFF_UNUSED,                             /* $08 */
+       EFF_SAMPLEOFFSET,               /* $09 */
+       EFF_VOLSLIDE,                   /* $0A */
+       EFF_POSJUMP,                    /* $0B */
+       EFF_SETVOLUME,                  /* $0C */
+       EFF_PATTERNBREAK,               /* $0D */
+       EFF_MISC,                               /* $0E */
+       EFF_SETSPEED,                   /* $0F */
+       EFF_SETTEMPO,                   /* $10 */
+       EFF_ARPEGGIO,                   /* $11 */
+
+       EFF_COUNT                               /* $12 */
+};
+#endif /* !LIBRARIES_XMODULECLASS_H */
+
+
+
+/* Song class white box istance
+ *
+ * You can read from this structure directly for speed critical operations.
+ * Modifying the data contained in the SongInfo structure is only allowed
+ * by means of object methods.
+ *
+ * Before using a public song, please protect yourself against other tasks
+ * modifying these fields. Always obtainin a (shared) lock on the
+ * SignalSemaphore contained in the SongInfo structure before doing anything
+ * with it. The methods provided by the song class will not handle access
+ * arbitration for you.
+ */
+struct SongInfo
+{
+       struct Node Link;                               /* Link for the song list                                       */
+       struct SignalSemaphore Lock;    /* Prevent other tasks from disturbing you!     */
+       UWORD           Length;                         /* Number of positions in song                          */
+       UWORD           MaxTracks;                      /* Maximum number of tracks in song                     */
+       UWORD           NumPatterns;            /* Number of patterns in song                           */
+       UWORD           LastInstrument;         /* Unused                                                                       */
+       UWORD           CurrentPatt;
+       UWORD           CurrentPos;
+       UWORD           CurrentInst;
+       UWORD           CurrentLine;
+       UWORD           CurrentTrack;
+       UWORD           DefNumTracks;           /* Default number of tracks in new patterns     */
+       UWORD           DefPattLen;                     /* Default number of lines in new patterns      */
+       UWORD           GlobalSpeed;            /* Default song speed                                           */
+       UWORD           GlobalTempo;            /* Default song tempo                                           */
+       UWORD           RestartPos;                     /* Position to restart from                                     */
+       UWORD           Reserved[4];            /* Reserved for future enhancements                     */
+
+       /* Note: all data beyond this point is longword aligned */
+
+       ULONG           Flags;                          /* See definitions below                                        */
+       void            *Pool;                          /* Memory pool from which all song data
+                                                                        * must be allocated from.
+                                                                        */
+       UWORD                           *Sequence;      /* Pointer to song sequence                                     */
+       struct Pattern          **Patt;         /* Pointer to patterns array                            */
+       struct Instrument       **Instr;        /* Pointer to instruments array                         */
+       UWORD           *ActiveTracks;          /* (-1 = disabled) *yet unused*                         */
+       STRPTR          Title;                          /* Song name                                                            */
+       STRPTR          Author;                         /* Name of the author                                           */
+       STRPTR          Path;                           /* Original song path                                           */
+       STRPTR          Description;            /* Verbose song description                                     */
+
+       ULONG           Changes;                        /* Changes made since song was last saved       */
+       ULONG           TotalChanges;           /* Total number of changes made so far          */
+       ULONG           CreationDate;           /* Date of OM_NEW                                                       */
+       ULONG           LastChanged;            /* Date of last saving                                          */
+
+
+       /* PRIVATE DATA --- HANDS OFF!
+        *
+        * All fields beyond this point are subject to change and
+        * *will* change in future releases of XModule. Do not
+        * even think of using them for whatever reason. Right? :-)
+        */
+       struct Instrument *InstrumentsTable[MAXINSTRUMENTS + 1];
+};
+
+
+
+/* Flags for SongInfo->Flags */
+/* No flags are defined yet */
+
+
+
+#endif /* !LIBRARIES_SONGCLASS_H */
diff --git a/include/libraries/songclass.i b/include/libraries/songclass.i
new file mode 100644 (file)
index 0000000..7a15d09
--- /dev/null
@@ -0,0 +1,151 @@
+       IFND    LIBRARIES_SONGLCASS_I
+LIBRARIES_SONGCLASS_I  SET     1
+**
+**     $VER: songclass.i 3.9 (7.1.96)
+**     Copyright (C) 1995,96 Bernardo Innocenti
+**
+**     Set TAB size to 8 chars to read this header file
+**
+**     Assembler structure definitions for the song class
+**
+
+
+       IFND    EXEC_TYPES_I
+       include exec/types.i
+       ENDC    ; EXEC_TYPES_I
+
+       IFND    EXEC_NODES_I
+       include exec/nodes.i
+       ENDC    ; EXEC_NODES_I
+
+       IFND    EXEC_SEMAPHORES_I
+       include exec/semaphores.i
+       ENDC    ; EXEC_SEMAPHORES_I
+
+
+MAXOCTAVES     EQU     6                       ; Number of octaves currently supported
+MAXNOTES       EQU     (12*MAXOCTAVES+1)       ; Entries in note conversion table
+MAXTRACKS      EQU     255                     ; Maximum number of tracks in a pattern
+MAXINSTRUMENTS EQU     255                     ; Maximum number of instruments in a song
+MAXPATTLINES   EQU     32767                   ; Maximum number of lines in a pattern
+MAXPOSITIONS   EQU     32767                   ; Maximum number of song positions
+MAXPATTERNS    EQU     32767                   ; Maximum number of patterns
+
+
+
+**************************************************************************
+** Some definitions transcripted from "XModule.h"
+**************************************************************************
+
+       ENUM 0
+
+       EITEM EFF_NULL
+       EITEM EFF_PORTAMENTOUP
+       EITEM EFF_PORTAMENTODOWN
+       EITEM EFF_TONEPORTAMENTO
+       EITEM EFF_VIBRATO
+       EITEM EFF_TONEPVOLSLIDE
+       EITEM EFF_VIBRATOVOLSLIDE
+       EITEM EFF_TREMOLO
+       EITEM EFF_UNUSED
+       EITEM EFF_SAMPLEOFFSET
+       EITEM EFF_VOLSLIDE
+       EITEM EFF_POSJUMP
+       EITEM EFF_SETVOLUME
+       EITEM EFF_PATTERNBREAK
+       EITEM EFF_MISC
+       EITEM EFF_SETSPEED
+       EITEM EFF_SETTEMPO
+       EITEM EFF_ARPEGGIO
+
+       EITEM EFF_COUNT
+
+
+
+       STRUCTURE       Note,0
+       UBYTE note_Note
+       UBYTE note_Inst
+       UBYTE note_Vol
+       UBYTE note_Pad
+       UBYTE note_EffNum
+       UBYTE note_EffVal
+       LABEL Note_SIZEOF
+
+
+       STRUCTURE       Instrument,0
+       UWORD   in_InstType             ; Instrument type (See defs)
+       UWORD   in_Volume               ; Volume (max $40)
+       APTR    in_Name                 ; Instrument Name
+       APTR    in_SampleData           ; Sampled data
+       ULONG   in_Length               ; Length of instr
+       ULONG   in_Repeat               ; Loop start (No loop = 0)
+       ULONG   in_Replen               ; Loop size (No loop = 1)
+       WORD    in_FineTune             ; Instrument FineTune (-8..+7)
+       UWORD   in_Flags                ; Unused
+       LABEL   in_SIZEOF
+
+
+       STRUCTURE       Pattern,0
+       UWORD   pa_Tracks               ; Support for variable number of tracks
+       UWORD   pa_Lines                ; Number of lines in pattern
+       APTR    pa_Name                 ; Pattern Name
+       STRUCT  pa_Reserved,8   ; Reserved for future enhancements
+       LABEL   pa_Notes,0              ; Pointers to all tracks follow
+       LABEL   pa_SIZEOF
+
+
+* Song class white box istance
+*
+* You can read from this structure directly for speed critical operations.
+* Modifying the data contained in the SongInfo structure is only allowed
+* by means of object methods.
+*
+* Before using a public song, please protect yourself against other tasks
+* modifying these fields. Always obtainin a (shared) lock on the
+* SignalSemaphore contained in the SongInfo structure before doing anything
+* with it. The methods provided by the song class will not handle access
+* arbitration for you.
+*
+       STRUCTURE       SongInfo,LN_SIZE
+       STRUCT  si_Lock,SS_SIZE
+       UWORD   si_Length               ; Number of positions in song
+       UWORD   si_MaxTracks            ; Number of tracks in song
+       UWORD   si_NumPatterns          ; Number of patterns in song
+       UWORD   si_LastInstrument       ; Unused
+       UWORD   si_CurrentPatt
+       UWORD   si_CurrentPos
+       UWORD   si_CurrentInst
+       UWORD   si_CurrentLine
+       UWORD   si_CurrentTrack
+       UWORD   si_DefNumTracks         ; Default number of tracks in new patterns
+       UWORD   si_DefPattLen           ; Default number of lines in new patterns
+
+       UWORD   si_GlobalSpeed          ; Default song speed
+       UWORD   si_GlobalTempo          ; Default song tempo
+       UWORD   si_RestartPos           ; Position to restart from
+       STRUCT  si_Reserved,8           ; Reserved for future enhancements
+
+* Note: all data beyond this point is longword aligned
+
+       ULONG   si_Flags                ; See definitions below
+       APTR    si_Pool                 ; The memory pool where song data
+                                       ; must be allocated from.
+       APTR    si_Sequence             ; UWORD *: Pointer to song sequence
+       APTR    si_Patt
+       APTR    si_Instr
+       APTR    si_ActiveTracks         ; UWORD *: Active Tracks (-1 = disabled)
+       APTR    si_Title                ; Song name
+       APTR    si_Author               ; Author of song
+       APTR    si_Path                 ; Original song path
+       APTR    si_Description          ; Verbose song description
+
+       ULONG   si_Changes              ; Number of changes made to this song
+       ULONG   si_TotalChanges         ; Total number of changes made so far
+       ULONG   si_CreationDate         ; Date of OM_NEW
+       ULONG   si_LastChanged          ; Date of last saving
+
+       LABEL   si_SIZEOF
+
+
+
+       ENDC    ; !LIBRARIES_SONGCLASS_I
diff --git a/include/libraries/xmodule.h b/include/libraries/xmodule.h
new file mode 100644 (file)
index 0000000..1b08f04
--- /dev/null
@@ -0,0 +1,240 @@
+#ifndef LIBRARIES_XMODULE_H
+#define LIBRARIES_XMODULE_H
+/*
+**     XModule.h
+**
+**     Copyright (C) 1995,96,97 Bernardo Innocenti
+**
+**     Set TAB size to 8 chars to read this header file
+**
+**     Public structures and constants for the xmodule.library
+*/
+
+#ifndef        DOS_DOS_H
+#include <dos/dos.h>
+#endif /* DOS_DOS_H */
+
+#ifndef        EXEC_LISTS_H
+#include <exec/lists.h>
+#endif /* EXEC_LISTS_H */
+
+#ifndef        EXEC_LIBRARIES_H
+#include <exec/libraries.h>
+#endif /* EXEC_LIBRARIES_H */
+
+#ifndef        EXEC_SEMAPHORES_H
+#include <exec/semaphores.h>
+#endif /* EXEC_SEMAPHORES_H */
+
+#ifndef        INTUITION_CLASSES_H
+#include <intuition/classes.h>
+#endif /* INTUITION_CLASSES_H */
+
+#ifndef COMPILERSPECIFIC_H
+#include "CompilerSpecific.h"
+#endif /* COMPILERSPECIFIC_H */
+
+
+/***************/
+/* XModuleBase */
+/***************/
+
+struct XModuleBase
+{
+       struct Library                   xm_Library;            /* Standard Library node structure      */
+       UWORD                                    xm_Pad0;                       /* Struct Library is misaligned!        */
+       struct SignalSemaphore   xm_BaseLock;           /* Obtain this before browsing
+                                                                                                * in any of the fields!
+                                                                                                */
+       UWORD                                    xm_Pad1;                       /* SignalSemaphore is misaligned!       */
+
+       /* The following data is longword aligned */
+
+       struct MinList                   xm_Songs;                      /* List of public SongInfo structs      */
+       struct MinList                   xm_Loaders;            /* List of available loaders            */
+       struct MinList                   xm_Savers;                     /* List of available savers                     */
+
+       void                                    *xm_Pool;                       /* Public mem pool used by XModule      */
+       Class                                   *xm_SongClass;          /* Song boopsi class                            */
+       struct SongInfo                 *xm_CurrentSong;        /* Pointer to currently active song     */
+       struct XMHook                   *xm_DefaultSaver;       /* Currently selected saver hook        */
+       struct Screen                   *xm_Screen;                     /* XModule screen (might be NULL)       */
+       ULONG                                    xm_Flags;                      /* See definitions below                        */
+       STRPTR                                   xm_ScreenName;         /* Name of XModule's public screen      */
+       STRPTR                                   xm_RexxName;           /* Name of XModule's ARexx port         */
+
+       ULONG                                    xm_Reserved[4];
+
+       struct DosLibrary               *xm_DOSBase;            /* Some useful library bases that       */
+       struct UtilityBase              *xm_UtilityBase;        /* can be grabbed and used by           */
+       struct Library                  *xm_IFFParseBase;       /* external hooks.  These four are      */
+       struct IntuitionBase    *xm_IntuitionBase;      /* guaranteed to be always open.        */
+};
+
+
+
+struct XMHook
+{
+       struct Node     xmh_Link;                                       /* Link to other hooks in a list.
+                                                                                        * ln_Name contains the Hook name.
+                                                                                        */
+       UWORD           xmh_Version;                            /* Will be set to XMHOOK_VERSION        */
+       STRPTR          xmh_Descr;                                      /* Short description of this hook       */
+       STRPTR          xmh_Author;                                     /* Author's name                                        */
+       ULONG           xmh_Flags;                                      /* See below for definitions            */
+       ULONG           xmh_ID;                                         /* IFF-style ID.  Set it to the FORM
+                                                                                        * type for IFF loaders/savers.
+                                                                                        */
+       APTR            xmh_UserData;                           /* User's private data                          */
+       struct Library *xmh_LibraryBase;                /* External hook's library base         */
+       ASMCALL void    (*xmh_RemoveHook) (
+               REG(a0,struct XMHook *hook));           /* Hook remotion entrypoint                     */
+       ASMCALL LONG    (*xmh_LoadMod) (
+               REG(d0, BPTR fh),
+               REG(a0, struct SongInfo *song),
+               REG(a1, struct XMHook *loader),
+               REG(a2, struct TagItem *tags));         /* Module loader entrypoint                     */
+       ASMCALL LONG    (*xmh_SaveMod) (
+               REG(d0, BPTR fh),
+               REG(a0, struct SongInfo *song),
+               REG(a1, struct XMHook *saver),
+               REG(a2, struct TagItem *tags));         /* Module saver entrypoint                      */
+       ASMCALL struct XMHook * (*xmh_IdentifyMod) (
+               REG(d0, BPTR fh),
+               REG(a0, struct XMHook *hook),
+               REG(a1, struct TagItem *tags));         /* Module identification entrypoint     */
+
+       ULONG           xmh_MaxTracks;                          /* Max. number of tracks                        */
+       ULONG           xmh_MaxPatterns;                        /* Max. number of patterns                      */
+       ULONG           xmh_MaxInstruments;                     /* Max. number of instruments           */
+       ULONG           xmh_MaxLength;                          /* Max. number of positions                     */
+       ULONG           xmh_MaxSampleLen;                       /* Max. length of each sample           */
+       ULONG           xmh_MaxPattLen;                         /* Max. lines in a pattern                      */
+
+       ULONG           xmh_Reserved[2];                        /* Avoid recompilation :-)                      */
+};
+
+
+
+#define XMHOOK_VERSION         0
+
+#define NT_XMSONG              'X'
+#define NT_XMLOADER            'L'
+#define NT_XMSAVER             'S'
+
+
+
+/* Flag definitions for XMHook->xmh_Flags */
+
+/* Basic type */
+#define XMHF_BINARY                                    0x0000
+#define XMHF_ASCII                                     0x0001
+#define XMHF_IFF                                       0x0002
+#define XMHF_MISC                                      0x0003
+
+/* These specify that the saver is able to exclude
+ * the corresponding items when saving in its own format.
+ */
+#define XMHF_EXCLUDE_SEQUENCE          (1<<5)
+#define XMHF_EXCLUDE_INSTRUMENTS       (1<<6)
+#define XMHF_EXCLUDE_PATTERNS          (1<<7)
+#define XMHF_EXCLUDE_NAMES                     (1<<8)
+
+/* This module format does not support variable length
+ * patterns.  Use in conjunction with XMHOOK_MaxPattLen tag.
+ */
+#define XMHF_FIXED_PATT_LEN                    (1<<9)
+
+/* This is an internal hook */
+#define XMHF_INTERNAL  (1<<31)
+
+
+
+/* Flags for xmDisplayMessage() */
+
+
+
+#define XMDMF_ALERT                    0x00000000
+#define XMDMF_FAIL                     0x00000001
+#define XMDMF_ERROR                    0x00000002
+#define XMDMF_WARNING          0x00000003
+#define XMDMF_NOTE                     0x00000004
+#define XMDMF_INFORMATION      0x00000005
+#define XMDMF_COMMENT          0x00000006
+#define XMDMF_DEBUG                    0x00000007
+#define XMDMF_LEVELMASK                0x00000007
+
+#define XMDMF_DOSERRORMASK     0x0000FFFF
+#define XMDMF_DOSFAULT         (1<<28)
+#define XMDMF_ACTION           (1<<29)
+#define XMDMF_USEREQUESTER     (1<<30)
+#define XMDMF_USECATALOG       (1<<31)
+
+
+
+/* Specific error codes */
+
+#define ERROR_NOTMODULE        ERROR_OBJECT_WRONG_TYPE /* File format not recognized           */
+#define ERROR_IOERR            RETURN_FAIL                             /* A DOS call (e.g.: Read()) failed.
+                                                                                                * check IoErr() for specific error.
+                                                                                                */
+
+
+/********/
+/* Tags */
+/********/
+
+#define XMSNG_Dummy                            (TAG_USER + 'XS'<<8)
+
+#define XMSNG_AddToList                        (XMSNG_Dummy + 1)
+#define XMSNG_OldSong                  (XMSNG_Dummy + 2)
+#define XMSNG_Loader                   (XMSNG_Dummy + 3)
+#define XMSNG_FileHandle               (XMSNG_Dummy + 4)
+#define XMSNG_Active                   (XMSNG_Dummy + 5)
+
+/* Tags for xmOptimizeSongA() */
+
+#define XMSNG_Optimize                 (XMSNG_Dummy + 16)
+#define XMSNG_RemapInstruments (XMSNG_Dummy + 17)
+#define XMSNG_LimitPatterns            (XMSNG_Dummy + 18)
+#define XMSNG_Join                             (XMSNG_Dummy + 19)
+#define XMSNG_Merge                            (XMSNG_Dummy + 20)
+
+/* Flags for XMSNG_Optimize */
+#define XMOF_DEFAULT                           (1<<0)
+#define XMOF_REM_UNUSED_PATTERNS       (1<<1)
+#define XMOF_REM_DUP_PATTERNS          (1<<2)
+#define XMOF_REM_UNUSED_INSTRS         (1<<3)
+#define XMOF_REM_DUP_INSTRS                    (1<<4)
+#define XMOF_CUT_INSTR_LOOPS           (1<<5)
+#define XMOF_CUT_INSTR_TAILS           (1<<6)
+#define XMOF_CUT_PATTERNS                      (1<<7)
+#define XMOF_REMAP_INSTRS                      (1<<8)
+
+
+/* XModule hook tags */
+
+#define XMHOOK_Dummy                   (TAG_USER + 'XH'<<8)
+
+#define XMHOOK_Type                            (XMHOOK_Dummy + 1)
+#define XMHOOK_Name                            (XMHOOK_Dummy + 2)
+#define XMHOOK_Priority                        (XMHOOK_Dummy + 3)
+#define XMHOOK_Descr                   (XMHOOK_Dummy + 4)
+#define XMHOOK_Author                  (XMHOOK_Dummy + 5)
+#define XMHOOK_ID                              (XMHOOK_Dummy + 6)
+#define XMHOOK_Flags                   (XMHOOK_Dummy + 7)
+#define XMHOOK_UserData                        (XMHOOK_Dummy + 8)
+#define XMHOOK_LibraryBase             (XMHOOK_Dummy + 9)
+#define XMHOOK_RemoveFunc              (XMHOOK_Dummy + 10)
+#define XMHOOK_LoadModFunc             (XMHOOK_Dummy + 11)
+#define XMHOOK_SaveModFunc             (XMHOOK_Dummy + 12)
+#define XMHOOK_IdentifyModFunc (XMHOOK_Dummy + 13)
+#define XMHOOK_MaxTracks               (XMHOOK_Dummy + 14)
+#define XMHOOK_MaxPatterns             (XMHOOK_Dummy + 15)
+#define XMHOOK_MaxInstruments  (XMHOOK_Dummy + 16)
+#define XMHOOK_MaxLength               (XMHOOK_Dummy + 17)
+#define XMHOOK_MaxSampleLen            (XMHOOK_Dummy + 18)
+#define XMHOOK_MaxPattLen              (XMHOOK_Dummy + 19)
+
+
+#endif /* !LIBRARIES_XMODULE_H */
diff --git a/include/libraries/xmodule.i b/include/libraries/xmodule.i
new file mode 100644 (file)
index 0000000..0fdebc0
--- /dev/null
@@ -0,0 +1,121 @@
+       IFND    XMODULE_I
+XMODULE_I      SET     1
+**
+**     XModule replay routine 1.0
+**
+**     Copyright (C) 1995 Bernardo Innocenti
+**
+**     Assembler structure definitions for player
+**
+
+       IFND    EXEC_TYPES_I
+       include exec/types.i
+       ENDC    ; EXEC_TYPES_I
+
+       IFND    EXEC_NODES_I
+       include exec/nodes.i
+       ENDC    ; EXEC_NODES_I
+
+
+
+MAXINSTRUMENTS EQU     64      ; Maximum number of instruments loaded
+MAXTABLENOTE   EQU     (12*3)  ; Number of entries in note conversion table
+MAXTABLEEFFECTS        EQU     20      ; Number of entries in effect conversion table
+MAXPATTERNS    EQU     128     ; Maximum number of patterns
+MAXTRACKS      EQU     32      ; Maximum number of tracks in a pattern
+MAXPATTLINES   EQU     32768   ; Maximum number of lines in a pattern
+MAXPOSITIONS   EQU     32768   ; Maximum number of song positions
+MAXINSTNAME    EQU     32
+MAXSONGNAME    EQU     32
+MAXAUTHNAME    EQU     64
+MAXPATTNAME    EQU     16
+
+PATHNAME_MAX   EQU     256
+
+
+**************************************************************************
+** Some definitions transcripted from "XModule.h"
+**************************************************************************
+
+       ENUM 0
+
+       EITEM EFF_NULL
+       EITEM EFF_PORTAMENTOUP
+       EITEM EFF_PORTAMENTODOWN
+       EITEM EFF_TONEPORTAMENTO
+       EITEM EFF_VIBRATO
+       EITEM EFF_TONEPVOLSLIDE
+       EITEM EFF_VIBRATOVOLSLIDE
+       EITEM EFF_TREMOLO
+       EITEM EFF_UNUSED
+       EITEM EFF_SAMPLEOFFSET
+       EITEM EFF_VOLSLIDE
+       EITEM EFF_POSJUMP
+       EITEM EFF_SETVOLUME
+       EITEM EFF_PATTERNBREAK
+       EITEM EFF_MISC
+       EITEM EFF_SETSPEED
+       EITEM EFF_SETTEMPO
+       EITEM EFF_ARPEGGIO
+
+       EITEM EFF_COUNT
+
+
+
+       STRUCTURE Note,0
+       UBYTE note_Note
+       UBYTE note_Inst
+       UBYTE note_Vol
+       UBYTE note_Pad
+       UBYTE note_EffNum
+       UBYTE note_EffVal
+       LABEL Note_SIZEOF
+
+
+       STRUCTURE Instrument,0
+       UWORD   in_InstType                     ; Instrument type (See defs)
+       UWORD   in_Volume                       ; Volume (max $40)
+       STRUCT  in_Name,MAXINSTNAME             ; Instrument Name
+       APTR    in_SampleData                   ; Sampled data
+       ULONG   in_Length                       ; Length of instr
+       ULONG   in_Repeat                       ; Loop start (No loop = 0)
+       ULONG   in_Replen                       ; Loop size (No loop = 1)
+       WORD    in_FineTune                     ; Instrument FineTune (-8..+7)
+       UWORD   in_Flags                        ; Unused
+       LABEL   in_SIZEOF
+
+
+       STRUCTURE       Pattern,0
+       UWORD   pa_Tracks                       ; Support for variable number of tracks
+       UWORD   pa_Lines                        ; Number of lines in pattern
+       STRUCT  pa_PattName,MAXPATTNAME         ; Pattern Name
+       STRUCT  pa_Notes,MAXTRACKS * 4          ; Pointers to the lines
+       LABEL   pa_SIZEOF
+
+
+       STRUCTURE       SongInfo,LN_SIZE
+       UWORD   si_Length                       ; Number of positions in song
+       UWORD   si_MaxTracks                    ; Number of tracks in song
+       UWORD   si_NumPatterns                  ; Number of patterns in song
+       UWORD   si_NumInstruments               ; Unused
+       UWORD   si_GlobalSpeed                  ; Default song speed
+       UWORD   si_GlobalTempo                  ; Default song tempo
+       UWORD   si_Restart                      ; Position to restart from
+       UWORD   si_CurrentPatt
+       UWORD   si_CurrentPos
+       UWORD   si_CurrentInst
+       UWORD   si_Flags                        ; See definitions below
+       ULONG   si_Changes                      ; Number of changes made to this song
+       APTR    si_Sequence                     ; *UWORD: Pointer to song sequence
+       STRUCT  si_PattData,pa_SIZEOF*MAXPATTERNS
+       STRUCT  si_Inst,in_SIZEOF*MAXINSTRUMENTS
+       STRUCT  si_ActiveTracks,MAXTRACKS       ; UBYTE []: Active Tracks (0 = disabled)
+       STRUCT  si_SongName,MAXSONGNAME         ; UBYTE []: Song name
+       STRUCT  si_Author,MAXAUTHNAME           ; Author of song
+       STRUCT  si_SongPath,PATHNAME_MAX;       ; Original song path
+       APTR    si_Pool                         ;  The memory pool where song data
+                                               ; must be allocated from.
+       LABEL   si_SIZEOF
+
+
+       ENDC    ; !XMODULE_I
diff --git a/include/libraries/xmodule_lib.i b/include/libraries/xmodule_lib.i
new file mode 100644 (file)
index 0000000..b9ebac4
--- /dev/null
@@ -0,0 +1,27 @@
+               IFND LIBRARIES_XMODULE_LIB_I
+LIBRARIES_XMODULE_LIB_I        SET     1
+
+
+_LVOxmCreateSongA              EQU     -54
+_LVOxmDeleteSong               EQU     -60
+_LVOxmAddSongA                 EQU     -66
+_LVOxmRemSong                  EQU     -72
+_LVOxmActivateSong             EQU     -78
+_LVOxmLockActiveSong           EQU     -84
+_LVOxmAddHookA                 EQU     -90
+_LVOxmRemHook                  EQU     -96
+_LVOxmIdentifyModule           EQU     -102
+_LVOxmLoadModuleA              EQU     -108
+_LVOxmSaveModuleA              EQU     -114
+_LVOxmSetSongLen               EQU     -120
+_LVOxmAddPatternA              EQU     -126
+_LVOxmSetPatternA              EQU     -132
+_LVOxmRemPattern               EQU     -138
+_LVOxmAddInstrumentA           EQU     -144
+_LVOxmSetInstrumentA           EQU     -150
+_LVOxmRemInstrument            EQU     -156
+_LVOxmProcessSongA             EQU     -162
+_LVOxmDisplayMessageA          EQU     -168
+_LVOxmDisplayProgress          EQU     -174
+
+               ENDC
\ No newline at end of file
diff --git a/include/libraries/xmoduleclass.h b/include/libraries/xmoduleclass.h
new file mode 100644 (file)
index 0000000..6937813
--- /dev/null
@@ -0,0 +1,164 @@
+#ifndef LIBRARIES_XMODULECLASS_H
+#define LIBRARIES_XMODULECLASS_H
+/*
+**     XModuleClass.h
+**
+**     Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
+**
+**     Use 4 chars wide TABs to read this file
+**
+**     IFF XMOD file format definition.
+*/
+
+
+#ifndef IFF_IFFPARSE_H
+#include <libraries/iffparse.h>
+#endif /* !IFF_IFFPARSE_H */
+
+#ifndef        DATATYPES_SOUNDCLASS_H
+#include <datatypes/soundclass.h>
+#endif /* !DATATYPES_SOUNDCLASS_H */
+
+
+#define ID_AUTH MAKE_ID('A','U','T','H')
+#define ID_ANNO MAKE_ID('A','N','N','O')
+
+#define ID_XMOD MAKE_ID('X','M','O','D')
+
+/* The following chunk ID definition conflicts with <libraries/maud.h> */
+#ifndef ID_MHDR
+#define ID_MHDR MAKE_ID('M','H','D','R')
+#endif
+
+#define ID_SONG MAKE_ID('S','O','N','G')
+#define ID_SHDR MAKE_ID('S','H','D','R')
+#define ID_SEQN MAKE_ID('S','E','Q','N')
+
+#define ID_PATT MAKE_ID('P','A','T','T')
+#define ID_PHDR MAKE_ID('P','H','D','R')
+
+#define ID_INST MAKE_ID('I','N','S','T')
+
+
+struct ModuleHeader
+{
+       UWORD XModuleVersion;   /* XModule version used to save this file       */
+       UWORD XModuleRevision;  /* XModule revision used to save this file      */
+       UWORD NumSongs;                 /* Number of songs in this module                       */
+       UWORD ActiveSong;
+       UWORD MasterVolume;
+       UWORD MixingMode;
+       ULONG MixingRate;
+};
+
+
+
+struct SongHeader
+{
+       UWORD   MaxTracks;              /* Number of tracks in song                     */
+       UWORD   Length;                 /* Number of positions in song          */
+       UWORD   NumPatterns;    /* Number of patterns in song           */
+       UWORD   LastInstrument; /* Last used instrument                         */
+       UWORD   GlobalSpeed;    /* Global song speed                            */
+       UWORD   GlobalTempo;    /* Global song tempo                            */
+       UWORD   RestartPos;             /* Position to restart from                     */
+       UWORD   CurrentPatt;
+       UWORD   CurrentLine;
+       UWORD   CurrentTrack;
+       UWORD   CurrentPos;
+       UWORD   CurrentInst;
+
+       /* New fields added in XModule version 3.5 -
+        * not present in modules saved by version 3.4 and below
+        */
+       UWORD   DefNumTracks;   /* Default number of tracks in new patterns     */
+       UWORD   DefPattLen;             /* Default number of lines in new patterns      */
+       ULONG   TotalChanges;   /* Total number of changes made so far          */
+       ULONG   CreationDate;   /* Date of OM_NEW                                                       */
+       ULONG   LastChanged;    /* Date of last saving                                          */
+};
+
+
+
+struct PatternHeader
+{
+       UWORD   Tracks;                 /* Number of tracks in pattern          */
+       UWORD   Lines;                  /* Number of lines in pattern           */
+};
+
+
+
+#ifndef LIBRARIES_SONGCLASS_H
+
+struct Note
+{
+       UBYTE Note;                             /* See below for more info      */
+       UBYTE Inst;                             /* Instrument number            */
+       UBYTE Vol;                              /* Volume for this note         */
+       UBYTE Pad;                              /* Unused                                       */
+       UBYTE EffNum;                   /* See definitions below.       */
+       UBYTE EffVal;                   /* Effect value ($00-$FF)       */
+};
+/* Note values:
+ *
+ *  0  - no note
+ *  1  - C-0
+ *  2  - C#0
+ * ...
+ * 13  - C-1
+ * ...
+ * 72  - B-5
+ */
+
+
+/* Values for Note->EffNum */
+
+enum {
+       EFF_NULL,                               /* $00 */
+       EFF_PORTAMENTOUP,               /* $01 */
+       EFF_PORTAMENTODOWN,             /* $02 */
+       EFF_TONEPORTAMENTO,             /* $03 */
+       EFF_VIBRATO,                    /* $04 */
+       EFF_TONEPVOLSLIDE,              /* $05 */
+       EFF_VIBRATOVOLSLIDE,    /* $06 */
+       EFF_TREMOLO,                    /* $07 */
+       EFF_UNUSED,                             /* $08 */
+       EFF_SAMPLEOFFSET,               /* $09 */
+       EFF_VOLSLIDE,                   /* $0A */
+       EFF_POSJUMP,                    /* $0B */
+       EFF_SETVOLUME,                  /* $0C */
+       EFF_PATTERNBREAK,               /* $0D */
+       EFF_MISC,                               /* $0E */
+       EFF_SETSPEED,                   /* $0F */
+       EFF_SETTEMPO,                   /* $10 */
+       EFF_ARPEGGIO,                   /* $11 */
+
+       EFF_COUNT                               /* $12 */
+};
+
+#endif /* !LIBRARIES_SONGCLASS_H */
+
+
+
+struct InstrumentHeader
+{
+       WORD     Num;                   /* Instrument slot number                       */
+       UWORD    Type;                  /* Instrument type (see defs)           */
+       WORD     FineTune;              /* Instrument FineTune (-8..+7)         */
+};
+
+
+
+#ifndef LIBRARIES_SONGCLASS_H
+
+/* Possible values for Instrument->InstType */
+enum {
+       ITYPE_SAMPLE8,          /* signed 8bit sample                                   */
+       ITYPE_SAMPLE16,         /* TODO: signed 16bit sample                    */
+       ITYPE_SYNTH,            /* TODO: Synthetic instrument                   */
+       ITYPE_HYBRID,           /* TODO: Both Synth & Sample                    */
+       ITYPE_MIDI                      /* TODO: Played by external MIDI device */
+};
+#endif /* !LIBRARIES_SONGCLASS_H */
+
+#endif /* !LIBRARIES_XMODULECLASS_H */
diff --git a/include/pragmas/xmodule_pragmas.h b/include/pragmas/xmodule_pragmas.h
new file mode 100644 (file)
index 0000000..10ac355
--- /dev/null
@@ -0,0 +1,83 @@
+#ifndef _INCLUDE_PRAGMA_XMODULE_LIB_H
+#define _INCLUDE_PRAGMA_XMODULE_LIB_H
+
+#ifndef CLIB_XMODULE_PROTOS_H
+#include <clib/xmodule_protos.h>
+#endif
+
+#if defined(AZTEC_C) || defined(__MAXON__) || defined(__STORM__)
+#pragma amicall(XModuleBase,0x036,xmCreateSongA(a0))
+#pragma amicall(XModuleBase,0x03C,xmDeleteSong(a0))
+#pragma amicall(XModuleBase,0x042,xmAddSongA(a0,a1,a2))
+#pragma amicall(XModuleBase,0x048,xmRemSong(a0))
+#pragma amicall(XModuleBase,0x04E,xmActivateSong(a0))
+#pragma amicall(XModuleBase,0x054,xmLockActiveSong(d0))
+#pragma amicall(XModuleBase,0x05A,xmAddHookA(a0))
+#pragma amicall(XModuleBase,0x060,xmRemHook(a0))
+#pragma amicall(XModuleBase,0x066,xmIdentifyModule(d0,a0))
+#pragma amicall(XModuleBase,0x06C,xmLoadModuleA(a0,a1))
+#pragma amicall(XModuleBase,0x072,xmSaveModuleA(a0,a1,a2,a3))
+#pragma amicall(XModuleBase,0x078,xmSetSongLen(a0,d0))
+#pragma amicall(XModuleBase,0x07E,xmAddPatternA(a0,a1))
+#pragma amicall(XModuleBase,0x084,xmSetPatternA(a0,d0,a1))
+#pragma amicall(XModuleBase,0x08A,xmRemPattern(a0,d0,d1))
+#pragma amicall(XModuleBase,0x090,xmAddInstrumentA(a0,d0,a1))
+#pragma amicall(XModuleBase,0x096,xmSetInstrumentA(a0,d0,a1))
+#pragma amicall(XModuleBase,0x09C,xmRemInstrument(a0,d0))
+#pragma amicall(XModuleBase,0x0A2,xmProcessSongA(a0,a1,a2))
+#pragma amicall(XModuleBase,0x0A8,xmDisplayMessageA(d0,a0,a1))
+#pragma amicall(XModuleBase,0x0AE,xmDisplayProgress(d0,d1))
+#endif
+#if defined(_DCC) || defined(__SASC)
+#pragma libcall XModuleBase xmCreateSongA        036 801
+#pragma libcall XModuleBase xmDeleteSong         03C 801
+#pragma libcall XModuleBase xmAddSongA           042 A9803
+#pragma libcall XModuleBase xmRemSong            048 801
+#pragma libcall XModuleBase xmActivateSong       04E 801
+#pragma libcall XModuleBase xmLockActiveSong     054 001
+#pragma libcall XModuleBase xmAddHookA           05A 801
+#pragma libcall XModuleBase xmRemHook            060 801
+#pragma libcall XModuleBase xmIdentifyModule     066 8002
+#pragma libcall XModuleBase xmLoadModuleA        06C 9802
+#pragma libcall XModuleBase xmSaveModuleA        072 BA9804
+#pragma libcall XModuleBase xmSetSongLen         078 0802
+#pragma libcall XModuleBase xmAddPatternA        07E 9802
+#pragma libcall XModuleBase xmSetPatternA        084 90803
+#pragma libcall XModuleBase xmRemPattern         08A 10803
+#pragma libcall XModuleBase xmAddInstrumentA     090 90803
+#pragma libcall XModuleBase xmSetInstrumentA     096 90803
+#pragma libcall XModuleBase xmRemInstrument      09C 0802
+#pragma libcall XModuleBase xmProcessSongA       0A2 A9803
+#pragma libcall XModuleBase xmDisplayMessageA    0A8 98003
+#pragma libcall XModuleBase xmDisplayProgress    0AE 1002
+#endif
+#ifdef __STORM__
+#pragma tagcall(XModuleBase,0x036,xmCreateSong(a0))
+#pragma tagcall(XModuleBase,0x042,xmAddSong(a0,a1,a2))
+#pragma tagcall(XModuleBase,0x05A,xmAddHook(a0))
+#pragma tagcall(XModuleBase,0x066,xmIdentifyModuleTags(d0,a0))
+#pragma tagcall(XModuleBase,0x06C,xmLoadModule(a0,a1))
+#pragma tagcall(XModuleBase,0x072,xmSaveModule(a0,a1,a2,a3))
+#pragma tagcall(XModuleBase,0x07E,xmAddPattern(a0,a1))
+#pragma tagcall(XModuleBase,0x084,xmSetPattern(a0,d0,a1))
+#pragma tagcall(XModuleBase,0x090,xmAddInstrument(a0,d0,a1))
+#pragma tagcall(XModuleBase,0x096,xmSetInstrument(a0,d0,a1))
+#pragma tagcall(XModuleBase,0x0A2,xmProcessSong(a0,a1,a2))
+#pragma tagcall(XModuleBase,0x0A8,xmDisplayMessage(d0,a0,a1))
+#endif
+#ifdef __SASC_60
+#pragma tagcall XModuleBase xmCreateSong         036 801
+#pragma tagcall XModuleBase xmAddSong            042 A9803
+#pragma tagcall XModuleBase xmAddHook            05A 801
+#pragma tagcall XModuleBase xmIdentifyModuleTags 066 8002
+#pragma tagcall XModuleBase xmLoadModule         06C 9802
+#pragma tagcall XModuleBase xmSaveModule         072 BA9804
+#pragma tagcall XModuleBase xmAddPattern         07E 9802
+#pragma tagcall XModuleBase xmSetPattern         084 90803
+#pragma tagcall XModuleBase xmAddInstrument      090 90803
+#pragma tagcall XModuleBase xmSetInstrument      096 90803
+#pragma tagcall XModuleBase xmProcessSong        0A2 A9803
+#pragma tagcall XModuleBase xmDisplayMessage     0A8 98003
+#endif
+
+#endif /*  _INCLUDE_PRAGMA_XMODULE_LIB_H  */
\ No newline at end of file
diff --git a/include/proto/xmodule.h b/include/proto/xmodule.h
new file mode 100644 (file)
index 0000000..afb01e2
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef _PROTO_XMODULE_H
+#define _PROTO_XMODULE_H
+
+#include <exec/types.h>
+
+extern struct XModuleBase *XModuleBase;
+
+#include <clib/xmodule_protos.h>
+#include <pragmas/xmodule_pragmas.h>
+
+#endif
diff --git a/manuals/HISTORY b/manuals/HISTORY
new file mode 100644 (file)
index 0000000..5f36b2d
--- /dev/null
@@ -0,0 +1,445 @@
+Note: The history log is reverse sorted: newer changes are added from the top.
+
+
+New for version 4.0
+===================
+
+CHANGE:        Split the Makefile into several smaller Makefiles, one for each
+       subdirectory. Added "config.mk" file with global definitions for
+       make.
+
+CHANGE:        Recompiled with SAS/C 6.58. I had to quit using GST because it
+       seems whis new version has bugs with them.
+
+ADD:    Added 3 new versions of the XModule main executable:
+
+         - XModule_020        optimized for 68020 or better processors
+         - XModule_OS30       does not include support code for V36-38
+         - XModule_020_OS30   both 68020 code and OS 3.0-only code
+
+        Patch files are distributed to generate these new versions from
+        the generic executable.  The generic version still has support
+        for OS 3.0 and works fine with any 68K CPU.
+
+CHANGE: Changed the Makefile to use PhxLnk instead of Slink. This made
+        the exetuable a bit smaller and the linking process much faster.
+
+CHANGE: Recompiled with SAS/C 6.57...  Those guys at SAS again did a
+        very good job, and thanks to the new warnings, I was able to
+        catch a couple of nasty bugs hidden inside the code.
+
+ADD:    The installer script will now choose a sensible default path
+        for the XModule drawer.
+
+CHANGE: Changed the Makefile to support GNU make and to use the ADE
+        version of makeinfo.  Hopefully one day I'll switch
+        definitely to GCC...
+
+CHANGE: Changed the installation script to use the spatch program to
+        generate 020 versions of some executables.
+        This makes the distribution archive a bit smaller and the
+        users a bit happier :)
+
+
+New for version 3.9
+===================
+
+FIX:    Fixed assembler startup to use FindTask(NULL) instead of
+        grabbing the pointer from ExecBase->ThisTask because this
+        field has been declared private by Amiga Technologies. 
+
+ADD:    Brand new user interface with scalable windows (like MUI)
+        and localized labels!  WOW!
+
+CHANGE: Songs are now represented internally as `boopsi' objects.
+        And, once again, XModule has been almost rewritten! :-)
+
+ADD:    External loader/saver hooks are now supported through the
+        xmodule.library interface routines.  Other kinds of
+        external hooks are possible, but not used at the moment.
+        Useless to say, it required very hard work.
+
+ADD:    xmodule.library.  This library is contained inside the XModule
+        executable, so you can't see it in your Libs: drawer. It is used
+        by all external hooks and by internal functions. Implementing it
+        almost required a total rewrite of all the XModule code.
+
+ADD:    Internal support for volume column.  The pattern editor does
+        not show it yet, but it's there.
+
+FIX:    Fixed a stupid bug in automatic pattern breaking routines.
+        (It inserted a '0' effect instead of 'D' to break the pattern :-)
+
+ADD:    Copying, cutting and pasting in the pattern editor is now possible.
+
+ADD:    Patterns can now be loaded/saved.
+
+ADD:    Menu items are now localized.
+
+ADD:    Added french catalog translation. Thanks to Julien Wilk.
+
+ADD:    Added german catalog translation and Installer script.  Thanks to
+        Michael Reichenbach.
+
+FIX:    Fixed font handling problems.  XModule does not require
+        diskfont.library anymore.
+
+CHANGE: Changed the "Get" buttons in the Prefs window into nice vectorial
+        images.
+
+FIX:    Fixed a tiny bug in the #?Tracker loader.  It would not look for
+        position table entries beyond the end when calculating the
+        number of patterns, so it would fail when loading some modules.
+
+
+New for version 3.5
+===================
+
+ Version 3.5 was released to beta testers only, therefore version
+3.9 is the official release version of 3.5.
+
+
+
+New for version 3.4
+===================
+
+ADD:    Full featured Pattern Editor!!!  Another big step towards a
+        complete n-channels music editor...
+
+ADD:    New loader for ScreamTracker 3.01! Conversion is not very good,
+        but at least it works.
+
+ADD:    Supports loading and saving TakeTracker with up to 32 channels.
+
+ADD:    Locale support.  There is only an italian catalog yet, translated
+        by my friend Steven Cantini.  If you want more translations, please
+        do them yourself and send me the .ct files! :-)
+
+ADD:    The installer script has been localized.  Thanks to Steven Cantini
+        for translating Italiano strings.
+
+FIX:    XModule did not load the last sample in (Octa)MED modules. Thanks
+        to Brian Tietz for reporting this.
+
+ADD:    XModule now stores the current directory and pattern of all
+        file requesters in the settings file.
+
+ADD:    Added window cycling through ALT-TAB and ALT-SHIFT-TAB.
+
+CHANGE: Rearranged song sequence handling.  Now it is possible to have
+        an arbitrary song length (let's say 32768) and it will be easy
+        to add support for song Sections ala OctaMED 5, but I don't
+        think this is a useful option.  Tell me if you want it.
+
+CHANGE: Now uses memory pools for storing songs.  This will
+        reduce memory fragmentation and will make XModule a little
+        bit faster at the cost of a little (say 15%) memory waste.
+
+ADD:    Fonts preferences are now loaded and saved (sorry, I forgot
+        to do it in the last version :-).
+
+FIX:    Fixed a bug in the ASL file requester handling routines which
+        caused XModule to save songs to its own current directory
+        instead of the directories where they were loaded from.
+
+FIX:    Loading garbage as a Sound/ProTracker module will no longer lead
+        to a crash.  I've put a lot of efford in trying to catch all
+        dangerous situations, like modules with no patterns or with
+        invalid sequence.
+
+ADD:    Added support for MMD1 (OctaMED) modules.  Improved MED loader and
+        saver.
+
+FIX:    The "Play All" button in the Sample Editor no longer crashes.
+        I'm sorry, it was a really stupid bug. :-))
+
+FIX:    Fixed a nasty bug in the instrument clearing routine that also
+        affected module optimization.  The Repeat/Replen of killed
+        instruments was not cleared, causing ProTracker 3.0 to refuse
+        loading them.  Added some sanity checks in the
+        Sound/Noise/ProTracker loader to fix these corrupted modules.
+        Thanks to Steven Cantini for reporting this bug.
+
+CHANGE: Default icons are now kept in a separate "Icons" drawer.
+
+ADD:    The SaveIcons switch in the Instruments window does now work
+        properly.
+
+CHANGE: Double clicking in the Instruments list brings up the Sample Editor.
+
+CHANGE: Changed the way to insert patterns in the song sequence.  Now when
+        double-click on the song position, the current pattern gets inserted
+        there.  Double clicking on a pattern causes opens the pattern editor
+        window.
+
+FIX:    The SongInfo ListView would not hilight the correct song when opened.
+
+FIX:    Some string gadgets (The Song Name string gadget in the SongInfo
+        window, to name one) allowed too many characters.
+
+ADD:    Context sensitive help in all windows: pressing HELP now brings up
+        help on the active window.
+
+CHANGE:        When cloning the default screen, the overscan ClipRect is used to
+        calculate the screen size.  This behavior is more pleasing for those
+        who use a very big autoscrolling workbench screen. (Hi Alfonso!)
+
+FIX:    OctaMed loader now recognizes and converts decimal volumes.
+
+ADD:   Instruments window "Add" button now spawns a file requester and lets
+        you load an instrument.
+
+CHANGE: When an instrument has no name and has no sample associated, it
+        will be marked "--empty--" in the Instruments list.
+
+FIX:    Removed a serious bug that could crash the machine when a
+        compressed module was loaded.
+
+ADD:    The Instruments window is now an AppWindow and you can drop sample
+        icons over it.
+
+ADD:    The SongInfo window is now an AppWindow and you can drop module
+        icons over it.
+
+FIX:    Dropping multiple module icons in the ToolBox window or on XModule's
+        AppIcon now loads all the modules correctly instead of loading all
+        them one over the other.
+
+FIX:    Low memory handling generally improved: XModule now has much more
+        chances of surviving when memory is very short.
+
+FIX:    Fixed a really serious bug in Clear Pattern option of Clear Module
+        panel.  The song was left with no patterns at all.  Now one empty
+        pattern is always allocated after clearing the patterns.
+
+FIX:    When you delete an instrument, the "Total Module Size" and "Total
+        Instrument Size" boxes in the SongInfo panel are now correctly
+        updated to show the change.
+
+FIX:    The setting editor did not notice when one of the fonts was changed
+        with another font with the same width and heigth.
+
+CHANGE: The progress indicator will not be updated when the user is playing
+        with Intuition.  This way operations do not lock while a window is
+        being resized or dragged.
+
+ADD:    Menu items in Sample window are now updated correctly when
+        preferences are loaded/saved.
+
+
+New for versions 3.2, 3.3
+=========================
+
+ Version 3.2 and 3.3 were never officially released.  Some of the changes
+logged for version 3.4 were actually introduced in these internal versions.
+
+
+New for version 3.1
+===================
+
+ Version 3.0 was released to beta testers only, therefore version
+3.1 is the official release version of 3.0.
+
+
+New for version 3.0
+===================
+
+ Version 3.0 took a lot more time than I originally planned.  My machine
+has been used for an animation for a local TV, and the people working on it
+have been so clever that they deleted almost all my WORK: partition.
+I had a backup on a streamer tape, but somehow I couldn't restore it.  The
+only thing that actually survived the catastrophe was XModule and its source,
+except the history log and a short assembly file.  I've rewritten the
+assembler part, but I'm not going to type the history again (who remembers?).
+There were really lots of news (the list was two pages long),  but you'll
+have to discover all them by yourself.
+
+
+New for version 2.8
+===================
+
+FIX:    Fixed a problem with NT/PT FineTune.
+
+FIX:    Some memory did not free when user aborted loading/saving of a
+        ST/NT/PT module.
+
+ADD:    Support for 6CHN/8CHN modules (not tested).  Thanks to A.Booker
+        for suggesting this.
+
+ADD:    XModule now supports loading signed and unsigned 8-bit samples.
+
+FIX:    Instrument longer than 128K are correctly clipped when saving
+        to ST/NT/PT.
+
+FIX:    Memory wasn't freed when loading an instrument over an already
+        existing one.
+
+CHANGE: Windows are now simple refreshed.  Slower but eats a lot less memory.
+
+FIX:    Now the DefaultTool for icons created by XModule contain the
+        correct pathname.
+
+CHANGE: Code has been optimized and rearranged in many parts,
+        especially the user interface.  This should fix some potential
+        bugs and make it easier to add new features quickly.  Locale
+        support and context-sensitive help are almost ready.
+
+ADD:    Some new shortcut keys; more keyboard control over gadgets
+        available for Kickstart V39+.
+
+ADD:    Documentation cleaned up and some parts added.
+
+FIX:    Possible small memory loss when closing a window.
+
+FIX:    When iconifies, XModule now tries to use its-own icon instead of the
+        Workbench default Tool icon.
+
+FIX:    Screen re-opening bug which caused XModule to illegally close
+        someone else's screen in some cases.
+
+ADD:    Many new gadgets in Sample editor (yet some are inactive).
+
+ADD:    Range marker in Sample window.
+
+FIX:    SaveFormat window text positions were incorrect.
+
+FIX:    Newly created songs now have 1 empty pattern allocated.
+        This fixed enforcer hits when saving empty songs.
+
+ADD:    Three different methods for rendering samples (the code was already
+        there since version 2.7 but I didn't add the menu items).
+
+CHANGE: Removed DETACH Shell option, it caused too many bugs.
+
+ADD:    Some new (undocumented) ARexx commands.
+
+ADD:    Added settings file saving/loading.
+
+ADD:    Added Log window.
+
+
+New for version 2.7
+===================
+
+FIX:    Fixed occasional hits when closing a window.
+
+FIX:    Bug in IFF 8SVX saver found & killed.
+
+CHANGE: Faster sample graph drawing under V39+.
+
+ADD:    ScreenMode requester.
+
+
+New for version 2.6
+===================
+
+CHANGE: XModule now requires Kickstart V37 instead of V36.
+
+FIX:    Instruments ViewList under Kick 2.0 is now displayed correctly.
+
+CHANGE: Assembler startup as been rewritten (smaller & bugs fixed).
+
+ADD:    Sequence editor is now fully operative!
+
+ADD:    Progress indicator finally sports a percentage bar!
+
+ADD:    Can now detach from the Shell.
+
+FIX:    The documentation has been revised and corrected.
+
+FIX:    Enforcer hits occurrred in Progress Panel under some rare conditions.
+
+
+New for version 2.5
+===================
+
+FIX:    Problem (read: guru) when trying to save as Sound/Noise/ProTracker
+        a module having the first pattern longer than 64 lines.
+
+FIX:    Oktalyzer saver used to set mode 8 for all instruments.
+
+FIX:    Sample editor did not update Repeat and Replen when opened.
+
+FIX:    the Repeat value was doubled when loading SoundTracker modules and
+        halved when saving.
+
+CHANGE: Numbers in sequence list are now displayed in decimal format.
+
+FIX:    Some weird SoundTracker 15 instrument module did not
+        load correctly.
+
+ADD:    Installer script included in the distribution.
+
+ADD:    MagicWB icons for XModule and its documentation.
+
+CHANGE: Documentation improved a little.
+
+
+New for version 2.4
+===================
+
+ADD:    Can now load raw samples.
+
+ADD:    Added DataTypes support to load instruments.
+
+ADD:    Can now load raw data as instrument.
+
+FIX:    Illegal characters in instrument names are now filtered out.
+
+FIX:    Sample graphic now clips correctly.
+
+FIX:    Fine tune was not saved in SoundTracker modules.
+
+
+New for version 2.3
+===================
+
+ADD:    Asyncronous FileRequesters.
+
+ADD:    Asl & ReqTools FileRequester support.
+
+FIX:    Didn't accept some SoundTracker effects.
+
+
+New for version 2.2
+===================
+
+ADD:    XModule now sports an ARexx port featuring two (2) commands,
+        one of which does not work yet!
+
+ADD:    When opening on the Workbench screen, the main window is now an
+        AppWindow.  You can drop module icons on it and see them loaded
+        auto-magically!
+
+ADD:    Command line & Tool Types arguments are now supported.
+
+FIX:    Save Fromat panel works a little better now.
+
+
+New for version 2.1
+===================
+
+FIX:    Status display routines did sometimes crash.
+
+ADD:    Sample graphic display is now a little nicer and clips (almost)
+        correctly.
+
+ADD:    There is now a Save Format requester, but it is still not fully
+        functional.
+
+ADD:    Sound/Noise/ProTracker loader now warns notes with 0 as instrument
+        number.  I discovered that some composers (for istance
+        AudioMonster) use this trick instead of command C00 to kill sound
+        on a track.  The problem is that some players (e.g.: MultiPlayer 1.32)
+        will play some random data instead.  I had no time to investigate
+        deeper, but a solution could be to replace such notes with a C00
+        effect, loosing the original effect if any (such as speed commands).
+        In this cases you will have to fix the module manually.
+
+CHANGE: Sequence window look has drastically changed and includes a
+        list to pick patterns from ( It is not operative, thoug :-( ).
+
+CHANGE: Repeat and Replen moved to Sample window.
+
+
+*** Versions below 2.1 have never been released ***
diff --git a/manuals/Makefile b/manuals/Makefile
new file mode 100644 (file)
index 0000000..a6954cc
--- /dev/null
@@ -0,0 +1,33 @@
+###########################################################
+# Make the AmigaGuide and ASCII documentation
+###########################################################
+
+include $(TOP)/config.mk
+
+ALL_DOCS := XModule.guide_34 XModule.guide_39 XModule.guide_40 XModule.doc XModule.txt
+
+all: $(ALL_DOCS)
+
+clean:
+       delete $(ALL_DOCS)
+
+XModule.guide_34: XModule.texi
+#      $(MAKEINFO) --amiga-34 --no-split -D "agver 34" --output XModule.guide XModule.texi
+#      Delete $@
+#      Rename XModule.guide $@
+
+XModule.guide_39: XModule.texi
+#      $(MAKEINFO) --amiga-39 --no-split -D "agver 39" --output XModule.guide XModule.texi
+#      Delete $@
+#      Move XModule.guide $@
+
+XModule.guide_40: XModule.texi
+       $(MAKEINFO) --amiga --no-split -D "agver 40" --output XModule.guide XModule.texi
+       Delete $@
+       Move XModule.guide $@
+
+XModule.doc: XModule.texi
+       $(MAKEINFO) --amiga --no-headers --no-split --output $@ XModule.texi
+
+XModule.txt: XModule.texi
+       $(MAKEINFO) --no-headers --no-split --output $@ XModule.texi
diff --git a/manuals/README b/manuals/README
new file mode 100644 (file)
index 0000000..4b89897
--- /dev/null
@@ -0,0 +1,66 @@
+XModule beta release notes
+**************************
+
+
+Installation
+============
+
+    The "Install" drawer contains the XModule installation script.
+Double-click on your preferred language and follow the instrutions.
+The installation requires the Commodore Installer utility located
+somewhere in the Workbench path (usually in "SYS:Utilities/").
+We recommend version 43.3 or better of the Installer program, which
+can be retrieved on Aminet.  The installation script will probably
+work with older versions of the Installer, but it has not been tested.
+
+    To update from a previous version of XModule, follow the
+instructions in the installatation program. To uninstall XModule,
+just delete its drawer and all its contents.
+
+    If you don't have the Installer or if you don't want to use it,
+you can still install XModule manually.  Just make a new drawer and
+copy all the XModule files in it. Manual installation is strongly
+discouraged because you will miss the automatic tooltypes substituitions
+and the 020+/OS30 patches made for you by the Installer.
+
+
+New Pattern Editor
+==================
+
+    Starting with release 3.2, XModule incorporates a full featured pattern
+editor with keyboard and mouse driven scrolling, range selection and
+multiple undo/redo function.  The supported keyboard sequences are
+described in the documentation (have a look at it).  I'm sorry that there
+is still no replay routine, but I have already contacted Jarno Paanen (the
+author of PS3M) and Peter Kunath (the author of DeliTracker).  Both of them
+have been so kind to give me all the sources and help I needed to add their
+32 channel mixing engines to XModule.  Please, be patient.
+
+
+Beta Version
+============
+
+    This package contains a *BETA* release of XModule.
+Before running it, you should be warned about a few things:
+
+- Many gadgets and menu items are still no-ops.
+
+- The documentation is sometimes incorrect and some parts
+  have not been written yet.
+
+- Please do not base your opinion of XModule on this beta release.
+  The final version will be fully operative and bug free.
+
+- Do not report obvious bugs: I know that the Sample editor is
+  not working.  If you see a little, hard to discover, bug
+  such as a small memory leak or strange errors when converting a
+  particular module, then do not hesitate reporting it immediatly.
+  Failure to do so will probably result in the bug being
+  still there in the next release of XModule.
+
+
+    Thank you for beta testing my hard work.
+
+
+                                          // Bernardo Innocenti
+                                        \X/  bernie@shock.nervous.com
diff --git a/manuals/TODO b/manuals/TODO
new file mode 100644 (file)
index 0000000..a89d612
--- /dev/null
@@ -0,0 +1,49 @@
+
+ Description                                  Pri
+ -------------------------------------------------
+ Add volume column to notes                                            4
+ Split view in pattern editor                                  2
+ Backdrop window in pattern editor                             2
+ Measure in pattern editor                                             3
+ Infobar in pattern editor                                             4
+ Separate rendering task for pattern editor            3
+ Scrollbars position in prefs                                  3
+ Play routine in C                                                             7
+ XM loader                                                                             6
+ XM saver                                                                              5
+ S3M saver                                                                             4
+ Pack patterns in XMOD format                                  2
+ Compression options                                                   3
+ Fix GUI bugs                                                                  7
+ SampEditClass                                                                 6
+ SplineEditClass                                                               3
+ ARexx commands                                                                        5
+ ARexx menu                                                                            4
+ Recently used mods quick menu                                 3
+ Long text comment to modules                                  4
+ Module Datatype                                                               1
+ Autosave                                                                              3
+ Load instruments inside module                                        2
+ Optimize sample usage                                                 4
+ Fix Playtime                                                                  5
+ Operator hooks                                                                        5
+ Move edit variables away from MHDR chunk              3
+ Move MasterVolume inside SONG                                 3
+
+ song.datatype proposal:
+
+ +-------------------------------------------+
+ | +------------+    View:  * Patterns       |
+ | |  Tapedeck  |                            |
+ | +------------+           * Sequence       |
+ | +------------+                            |
+ | |   Volume   |           * Instruments    |
+ | +------------+                            |
+ | +---------------------------------------+ |
+ | |                                       | |
+ | |                                       | |
+ | |  Show patterns, sequence, instruments | |
+ | |                                       | |
+ | |                                       | |
+ | +---------------------------------------+ |
+ +-------------------------------------------+
diff --git a/manuals/XModule.texi b/manuals/XModule.texi
new file mode 100644 (file)
index 0000000..7623675
--- /dev/null
@@ -0,0 +1,2403 @@
+\input amigatexinfo
+\input texinfo
+@c $VER: XModule.texi 3.9 (21.1.97)
+
+@c Remember to update these variables with each release
+@set prgname XModule
+@set prgver 3.9
+
+@setfilename XModule.guide
+@settitle Documentation for XModule @value{prgver} (@value{prgdate})
+@setchapternewpage odd
+@c @set amiga_convert_nodes <-- this makes node names look like shit!
+
+@titlepage
+
+@title CrossModule
+@subtitle @value{prgver}  @today{}
+@author by Bernardo Innocenti
+
+Copyright @copyright{} 1993,94,95,96,97 by Bernardo Innocenti, all rights reserved.
+Freely distributable.
+
+@page
+@vskip 0pt plus 1filll
+
+@end titlepage
+
+@c ******************
+@c *      Top       *
+@c ******************
+
+@ifinfo
+@node Top, Overview, , (dir)
+@top
+
+This manual documents @value{prgname} version @value{prgver} released on
+@w{@today{}}. It is formatted for viewing with @w{AmigaGuide V@value{agver}}.
+
+Copyright @copyright{} 1993,94,95,96,97 by Bernardo Innocenti, all rights reserved.
+Freely distributable.
+
+If you haven't done it yet, please take a look to the release notes first.
+@c does not work with latest GeekGadgets snapshot
+@c @ref{Top, , release notes, README, README file}.
+
+@menu
+* Overview::             An overview of @value{prgname}.
+* Distribution::         Licence & Disclaimer.
+* Getting Started::      How to run @value{prgname}.
+* User Interface::       General notes on the user interface.
+
+* Panels::               Description of all @value{prgname} windows.
+
+* Effects::              List of all the player effects.
+* Format Conversion::    Description of module conversion with @value{prgname}.
+* Optimization::         How to save memory and disk space with modules.
+* ARexx Interface::      Every program should have one nowadays, doesn't it?
+
+* Known Bugs::           Notes on things that are still not working@dots{}
+* Future Plans::         What @value{prgname} should feature in the next releases.
+* Programmers::          Notes on the C source and TexInfo documentation.
+* XModule Format::       Description of the module format used by @value{prgname}.
+
+* History:(History)Main. See what has changed since the last version of @value{prgname}. READ THIS!
+
+* Credits::              Some people I wish to express my gratitude to.
+* Author Info::          How to contact me.
+
+* Concept Index::
+@end menu
+@end ifinfo
+
+@c ******************
+@c *    Overview    *
+@c ******************
+
+@node Overview, Distribution, Top, Top
+@chapter Overview
+@cindex Overview of @value{prgname}
+@cindex Introduction
+
+ The Amiga computer has standard file formats for almost
+everything.  Graphics, text, animation, hypertext and many other
+data types can be easily imported into any program that supports the
+given standards, but this is not true for music.  Too bad the music
+interchange file format defined by Electronic Arts (the old IFF SMUS)
+was too poor to be usable in high-quality music sequencers.  Therefore,
+every music editor created its own proprietary module format, so that,
+for instance, you cannot load into ProTracker music written with
+Oktalyzer.
+
+@dfn{@value{prgname}} (pronounced @dfn{Cross Module}) born as a simple
+music module conversion utility that tried to solve this problem.  The
+project started in early 1993, as a friend of mine (FBY) needed to
+convert his modules to ProTracker to perform some effects.  He had an
+old PD conversion utility called @dfn{Tracker} that worked only in
+particular conditions and was very bogus.  I decided to contact
+the author and he let me have the source for Lit.30000 (about $20).
+
+The original program has been completely rewritten from scratch, as the
+source was poorly coded.  Now @value{prgname} is a completely different
+program, which sports many other features, as module optimization, a
+nice user interface and complex module editing functions and it is
+quickly turning into a full featured music editor.
+
+This is why I like to call @value{prgname} a @dfn{module processor}. @value{prgname}
+is to music processing what ADPro and ImageFX are to image processing.
+
+
+
+@c ******************
+@c *  Distribution  *
+@c ******************
+
+@node Distribution, Getting Started, Overview, Top
+@chapter Distribution
+@cindex Distribution
+
+@value{prgname} is a @dfn{Freeware} program.  This means that you (probably)
+have got it free and @strong{you must not ask any money} if you
+decide to copy it for someone else.  If, instead, someone sold you
+@value{prgname} for a price higher than the media used to store it on
+(i.e.@:, $1 for a DD disk), tell him he should try making money honestly.
+
+I am not asking you to pay for @value{prgname} because I hate the
+Shareware concept.  I use a lot of great PD software on my computer,
+so I feel that I somehow have to pay my debt and I want to distribute
+@value{prgname} as free software; a little gift to all the Amiga
+community.  Feel free to distribute @value{prgname} to your friends and
+enemies, but, if you do so, you @strong{must} keep the documentation and
+the other distribution files together with the executable. The source
+code is not required, but I am distributing it to let others take advantage
+of my work, so, if possible, try to also include the source.
+
+Aminet and @w{Fred Fish} are especially granted permission to include
+@value{prgname} into their PD libraries.  Other PD libraries
+are welcome as well.
+
+If you intend to include @value{prgname} or parts of it in a commercial package,
+please at least send me a free copy of the product.  I would be honored
+to see one of my programs being part of a commercial package.
+
+You can modify or improve @value{prgname} (@pxref{Programmers}), only be
+so kind to return the new source code to me, so I can put the new features
+in the next release.  @emph{Please}, do not strip my name from the
+documentation, the program source, or the executable.
+
+This product is provided @strong{as is}@footnote{In other words:
+@value{prgname} never formatted my hard disk nor killed my dog, so I suppose
+it won't cause any damage to your system either, but, just in case, do not
+blame me if something nasty happens.}, without warranties of any kind: the
+author of this program cannot be held liable for any defects in the executable
+nor in the documentation or in any other files contained in this package.
+Any damage directly or indirectly caused by the use/misuse of @value{prgname}
+is the sole responsibility of the user her/him-self.
+
+
+
+@c *******************
+@c * Getting Started *
+@c *******************
+
+@node Getting Started, User Interface, Distribution, Top
+@chapter Getting Started
+@cindex Usage
+@cindex ToolTypes
+@cindex Arguments, command line
+@cindex Starting @value{prgname}
+
+
+@value{prgname} requires Kickstart version 2.0 or greater.  The reason is
+that I hate programming on old 1.3 and I will not do it any more.  If you
+still have not upgraded to 2.0, you'll miss the chance to run
+@value{prgname} and many other wonderful programs.  So what are you
+waiting for?
+
+@value{prgname} also takes advantage of some 2.1 and 3.0 features, such as
+DataTypes, AmigaGuide, Locale and many enhancements for the user
+interface.
+
+You can invoke @value{prgname} either from the Shell or Workbench.
+@value{prgname} accepts both command line and ToolTypes arguments.
+Workbench and Shell parameters are the same.  The full AmigaDOS
+template is:
+
+@example
+@group
+@value{prgname} FROM/M,PUBSCREEN/K,PORTNAME/K,SETTINGS/K,
+ CX_POPUP/T,CX_POPKEY/K,CX_PRIORITY/K/N,
+ ICONXPOS/K/N,ICONYPOS/K/N,ICONNAME/K
+@end group
+@end example
+
+@noindent
+The @code{FROM} keyword specifies modules to load at startup time.
+Standard AmigaDOS wildcards can be used to match more than one module.
+From Workbench, you multi-select module icons instead of specifying a
+ToolType.
+
+Specifying the @code{PUBSCREEN} keyword, followed by a public screen name,
+allows you to open @value{prgname} on an existing public screen.  The given
+name should match exactly (case matters), or the system won't be able to
+find the requested screen.  If the requested screen does not exist, a new
+public screen will be opened, cloning the resolution and colors of the default
+public screen (usually the Workbench screen).  Specifying an empty string
+(@samp{""}) you will automatically pick the default public screen.
+Note that this behavior is not standard: most commodities will abort if the
+requested public screen does not exist.  The default is to use or create a
+public screen called @samp{@value{prgname}}.
+
+@code{PORTNAME} will change the name of @value{prgname}'s ARexx port.
+If a port with this name already exists, a number (eg: @samp{.1}) will be
+appended to the name, until a unique name is found.  You can disable the ARexx
+port providing an empty string (eg: @samp{""}) for @code{PORTNAME}.  The port name
+defaults to @samp{XMODULE}.  For more information about the ARexx port, see
+@ref{ARexx Interface}.
+
+@code{SETTINGS} causes @value{prgname} to use the given settings file instead
+of looking for the default preferences file.
+
+@code{CX_POPUP} controls wether the user interface should show when
+@value{prgname} starts.  The default is to always show the interface.  When used
+from the shell, @code{CX_POPUP} acts as a toggle, turning off the user interface.
+From Workbench, you specify @code{CX_POPUP=YES} or @code{CX_POPUP=NO} to show and
+hide the user interface, respectively.  @xref{Commodities}.
+
+@code{CX_POPKEY} specifies the hotkey that pops up @value{prgname}'s user interface.
+The given string should be a valid Commodities input description.  You can disable
+@value{prgname}'s Commodities broker specifying an empty string (eg: @samp{""})
+for @code{CX_POPUP}.  The default is @samp{ctrl alt x}.  @xref{Commodities}.
+
+@code{CX_PRIORITY} sets the Commodities priority for the @value{prgname} broker.
+An high priority causes the input to be processed by @value{prgname} before other
+commodities with a lower priority. The priority ranges from +127 to -128.
+The default priority is 0.  @xref{Commodities}.
+
+@code{ICONXPOS} specifies the X position of @value{prgname}'s AppIcon.  The default is
+to let Workbench choose a suitable position for the icon.
+
+@code{ICONYPOS} specifies the Y position of @value{prgname}'s AppIcon.  The default is
+to let Workbench choose a suitable position for the icon.
+
+@code{ICONNAME} specifies the name of @value{prgname}'s AppIcon.  The default name is
+@samp{@value{prgname}}.
+
+
+@c ******************
+@c * User Interface *
+@c ******************
+
+@node User Interface, Panels, Getting Started, Top
+@chapter User Interface
+@cindex User Interface
+@cindex GUI
+
+@value{prgname}'s GUI follows Commodore's 2.0 style guidelines.  Under Kickstart 3.0
+and above, @value{prgname} takes advantage of new OS capabilities to enhance some
+aspects of the GUI.
+
+@menu
+* Windows Layout::
+* Shortcut Keys::
+* On-Line Help::
+* Localization::
+* Commodities::
+* Default Icons::
+@end menu
+
+
+@c ******************
+@c * Windows Layout *
+@c ******************
+
+@node Windows Layout, Shortcut Keys, , User Interface
+@section Windows Layout
+@cindex Windows Layout
+@cindex Layout, of Windows
+@cindex Font Sensivity
+
+Gadget and menu layout is font sensitive, but using some fancy fonts could
+result in a somewhat weird look.  If you are using a very big font and a
+window becomes too big to fit in the screen, @file{topaz/8} will be used as
+a fallback for rendering that window.
+
+Most windows are completely scalable and their contents will adapt to the
+size you choose, just like a MUI application.  But @value{prgname} does
+not use MUI, so I had to do all the GUI engine myself, and it was quite an
+hard work for me.  @value{prgname} GUI is not as nice as can be MUI, but
+it's much faster and requires much less memory.
+
+When you close a window, it will remember its position and size when you
+open it again.  The same is true for window zooming operations.
+
+@value{prgname} will also try to make the active window visible by
+scrolling the screen automatically.  This feature comes handy if you use
+a virtual screen which is wider and/or taller than the default.
+
+
+@c ******************
+@c *  Shortcut Keys *
+@c ******************
+
+@node Shortcut Keys, On-Line Help, Windows Layout, User Interface
+@section Shortcut Keys
+@cindex Shortcut Keys
+@cindex Keys, Shortcuts
+
+
+ In all windows, the following shortcut keys are active:
+
+@table @kbd
+@item ESC
+Close the active window.  Does not work with ToolBox window, as it would
+make it too easy to accidentally exit the program.
+
+@item HELP
+Bring up AmigaGuide on-line help.  @xref{On-Line Help}.
+
+@item TAB
+Cycles forward through string and numeric gadgets within the active
+window.  @kbd{SHIFT-TAB} will cycle backwards.  @kbd{TAB} also
+activates the first string or numeric gadget if none is selected.
+
+@item Alt-TAB
+Activates the next @value{prgname} window. @kbd{ALT-SHIFT-TAB}
+activates the previous window.
+
+@item RETURN
+Activate the first string or numeric gadget in the active window.
+
+@item Cursor UP/DOWN
+Move up and down in a viewlist whenever the active window contains one.
+@kbd{SHIFT-Cursor} moves 5 items up/down. @kbd{ALT-Cursor} moves to the
+top or to the bottom of the list.  This shortcut requires at least
+Kickstart 3.0.
+@end table
+
+In addition, gadgets with underscored characters in labels, can be
+operated hitting the highlighted key.  @kbd{SHIFT-key} acts the opposite
+of the unshifted key (sliders are decremented by one, cycle gadgets will
+cycle backwards, etc.@:).  Keyboard shortcuts will not work for all gadget
+kinds under Kickstart 2.0.
+
+
+
+@c ******************
+@c *  On-Line Help  *
+@c ******************
+
+@node On-Line Help, Localization, Shortcut Keys, User Interface
+@section On-Line Help
+@cindex On-Line Help
+@cindex Help, On-Line
+
+This feature requires @file{amigaguide.library}, which is distributed
+with Workbench version 2.1 and up, and @file{@value{prgname}.guide}
+located in the current directory or in the same directory of
+@value{prgname}'s executable or in the @file{HELP:language/} directory,
+where @samp{language} is any of your Locale preferred languages.
+AmigaGuide(TM) is also available as a freely distributable package
+for 1.3-2.0 users.
+
+To get help on a gadget, press @key{HELP} while the mouse pointer is
+over the gadget.  To get help on a string gadget, activate it and
+press @key{HELP}.  Hitting @key{HELP} while the mouse isn't over a
+gadget, will give you help on the active window.
+
+To get help on a menu item, hilight the item and press @key{HELP} while
+keeping the right mouse button pressed.
+
+
+
+@c ******************
+@c *  Localization  *
+@c ******************
+
+@node Localization, Commodities, On-Line Help, User Interface
+@section Localization
+@cindex Localization
+@cindex Language selection
+@cindex Writing a new language catalog
+
+Localization requires @file{locale.library},  which is distributed
+with Workbench version 2.1 and up, and the catalog file
+@file{@value{prgname}.catalog} located in the same directory of
+@value{prgname}'s executable or in the @file{LOCALE:catalogs/language/}
+directory, where @samp{langage} is is any of your Locale preferred
+languages.
+
+If you speak a language for which a catalog isn't yet available, please
+consider writing it yourself and then send it to me, so it will be
+included in the next release of @value{prgname}.  Look at the empty
+catalog translation file @file{Empty.ct} in the @file{Catalogs/}.
+It can be used to create new catalog descriptions for @value{prgname}.
+Then, use a tool like @cite{FlexCat}, @cite{KitKat} or @cite{CatComp}
+to compile it into a catalog file.  Please read the @cite{HowToTranslate.doc}
+before you start translating: it contains many tips and suggestions
+which may come useful.
+
+
+@c ******************
+@c *   Commodities  *
+@c ******************
+
+@node Commodities, Default Icons, Localization, User Interface
+@section Commodities support
+@cindex Break signals
+
+@value{prgname} is a Commodity and can be controlled by the
+Commodities Exchange program.
+
+@value{prgname} will pop up when its hotkey is typed; the main window
+will activate and the screen will move to the front.  If
+@value{prgname}'s interface is hidden, it will be revealed.
+
+The user interface can be hidden with the Exchange program.
+When the user interface is hidden, the only ways you have to still
+operate @value{prgname} is through the ARexx port or the AppIcon.
+
+You can also control @value{prgname} by sending the following
+signals to its process:
+
+@table @bullet
+@item @kbd{CTRL-C}
+Quit @value{prgname}.
+
+@item @kbd{CTRL-D}
+Disable the Commodities broker (equivalent to selecting @code{Inactive}
+with Commodities Exchange).
+
+@item @kbd{CTRL-E}
+Enable the Commodities broker (equivalent to selecting @code{Active}
+with Commodities Exchange).
+
+@item @kbd{CTRL-F}
+Deiconify @value{prgname} (equivalent to double-clicking on the AppIcon).
+@end table
+
+
+
+@c ******************
+@c * Default  Icons *
+@c ******************
+
+@node Default Icons, , Commodities, User Interface
+@section Default Icons
+@cindex Icons, changing defaults
+@cindex def_Instrument
+@cindex def_Module
+
+When @value{prgname} creates an icon for a file, it looks for the user
+preferred icon for the type of data being saved.
+
+The template icons can be placed in the @file{Icons} drawer which is
+located in the same drawer where the @value{prgname} executable resides.
+@value{prgname} will also look for its icons in the @file{ENV:Sys/}
+directory.  If you choose to use @file{ENV:Sys/}, you will probably want
+to store the icons in @file{ENVARC:Sys/} to have them copied automatically
+to @file{ENV:Sys/} every time the system boots up.
+
+Template icons should be only of type @dfn{Project}.  When @value{prgname}
+does not find a template icon, it uses the default Project icon instead.
+
+Whenever the default icon does not have its own default tool, it will be
+set to @value{prgname}'s executable.
+
+The following default icons are supported:
+
+@table @minus
+@item @file{def_Module}
+@item @file{def_Instrument}
+@end table
+
+
+
+@c ************
+@c *  Panels  *
+@c ************
+
+@node Panels, Effects, User Interface, Top
+@chapter Panels
+@cindex Panels
+@cindex Windows
+
+Sorry, XModule's documentation is still incomplete.  Not all
+the following links are finished.
+
+@menu
+* ToolBox::
+* Pattern Editor::
+* Instruments::
+* Song Information::
+* Sequence::
+* Save Format::
+* Clear Song::
+* Sample Editor::
+@end menu
+
+
+
+@c ******************
+@c * ToolBox Window *
+@c ******************
+
+@node ToolBox, Pattern Editor, , Panels
+@section ToolBox Window
+
+@subsection Gadgets
+
+@vtable @code
+@item Patterns (P)
+Opens or activates the @ref{Pattern Editor} window.
+
+@item Instruments (I)
+Opens or activates the @ref{Instruments} window.
+
+@item Sequence (Q)
+Opens or activates the @ref{Sequence} editor.
+
+@item Songs (S)
+Opens or activates the @ref{Song Information} window.
+
+@item Optimization (O)
+Opens or activates the @ref{Optimization} window.
+
+@item Play (P)
+This option has not been implemented yet.
+@end vtable
+
+
+
+@subsection Project Menu
+
+@vtable @code
+@item New (Amiga-N)
+Creates a new empty song.
+
+@item Open (Amiga-O)
+Opens a song over the current one.  A file requester will be put
+up to let you choose the module.  The file requester is asynchronous,
+so you can continue operating @value{prgname} while keeping the requester
+open.  You can use multiselection to open several modules at once.
+
+@item Save (Amiga-S)
+Saves the current song to the same path it was loaded from.
+
+@item Save As (Amiga-A)
+Opens a file requester and saves the current song to the selected file
+name and path.
+
+@item Clear Module (Amiga-K)
+Opens or activates the @ref{Clear Song} panel.
+
+@item About (Amiga-?)
+Brings up a requester showing various information on @value{prgname}.
+
+@item Help (HELP)
+Shows the main node of the AmigaGuide on-line help. @xref{On-Line Help}.
+
+@item Iconify (Amiga-I)
+Closes all open windows and screens and puts an AppIcon on the Workbench.
+You can De-Iconify @value{prgname} by double clicking on the icon or by
+using @code{Show Interface} in the Commodities Exchange program.
+@xref{Commodities}.
+
+@item Quit (Amiga-Q)
+Quits @value{prgname}.  A requester will ask if you are sure if the
+@code{Settings/Confirm Exit} menu item is checked.
+@end vtable
+
+
+
+@subsection Settings Menu
+
+@vtable @code
+@item Save Format
+Opens or activates the @ref{Save Format} window.
+
+@item User Interface
+Opens or activates the @ref{User Interface} window.
+
+@item Save Icons?
+When this menu item is checked, @value{prgname} will automatically add
+an icon to the files it saves. @xref{Default Icons}.
+
+@item Confirm Overwrite?
+When this menu item is checked, @value{prgname} will ask before
+overwriting an existing file when saving something.
+
+@item Confirm Exit?
+When this menu item is checked, @value{prgname} will ask
+if you are really sure before quitting.
+
+@item Verbose?
+When this menu item is checked, @value{prgname} will be output more
+detailed information on various operations.
+
+@item Open Settings
+Opens a file requester asking for an XModule preferences file.  The
+changes will take place immediately.
+
+@item Save Settings
+Saves the current settings to @file{PROGDIR:XModule.prefs}, which
+is where preferences are looked for first.
+
+@item Save Settings As
+Opens a file requester and saves the current settings to the selected
+file and path name.
+@end vtable
+
+
+
+@c ******************
+@c * Pattern Editor *
+@c ******************
+
+@node Pattern Editor, Instruments, ToolBox, Panels
+@section Pattern Editor Window
+@cindex Pattern Editor Window
+@cindex Notation
+
+@subsection Overview
+
+ The pattern editor is used to actually edit notes into your song.
+A song is made up of several patterns which are played one after the
+other following the order defined in the @ref{Sequence} editor.
+
+ A pattern is subdivided into @dfn{tracks}. Each track is made of
+several note slots.  Each note slot has four basic fields: the
+actual note, the number of the instrument used to play this note,
+a field for the @dfn{effect} (also called @dfn{command}) and a
+@dfn{value} for the effect, the meaning of which depends from
+the type of effect (@pxref{Effects}).  A @dfn{line} is an horizontal
+set of note slots.  The lines are numbered starting from 0 (line
+numbers are displayed in the leftmost column of the pattern editor).
+
+ A pattern can have 1 to 32 tracks.  The more the tracks, the more the
+notes that can be played at the same time.  When @value{prgname} plays
+a pattern, it reads the lines one by one, playing all the notes and
+executing all the effects as soon as it encounters them.  A pattern
+line can be imagined as one sixteenth of a beat when the pattern is
+played at the default speed and tempo.
+
+ A pattern row looks like this:
+
+@example
+C-2 5 C40
+~~~ | |~~
+|   | ||
+|   | |+---- Effect Value
+|   | +----- Effect Command
+|   +------- Instrument
++----------- Note
+@end example
+
+ Notes are shown in anglosaxon notation, followed by the octave number.
+So @code{C-2} is a second octave @code{DO} in classic notation and
+@code{A#3} is a third octave diesis @code{LA}.  There are 5 octaves
+numbered from 0 to 4; a @code{C-2} note is twice the frequency of a
+@code{C-1}, and it's instrument will be played at double speed.
+
+ The duration of the note is determined by the space left from one note
+to the next.  For example, if a note is immediately followed by another
+note on the same track, it's duration will be one sixteenth of a beat
+at four quarters.  If two notes are put one at line n and the other
+at line n+4 (leaving three blank spaces between), the duration of the
+first note will be exactly one quarter.
+
+ Calculating the duration of a note can become trickier when weird
+combinations of speed and tempo are used.  Also, notes can be
+associated to instruments which have an endless loop (like a flute)
+or to instruments that only last for a short time and then stop
+(like a snare drum).  In the latter case, the duration of a note could
+be shorter than it's distance from the following note.  Experience
+will help understanding these simple rules.
+
+
+@subsection Editor Keys
+
+@table @kbd
+@item up
+@itemx down
+@itemx left
+@itemx right
+Move the cursor one position towards the respective direction. Keeping
+cursor keys pressed will repeat the movement until the key is released.
+
+ Cursor keys in combination with the @kbd{SHIFT} key will move the cursor
+a whole page up/down or one track left/right, keeping it on the same
+column.
+
+ Cursor keys in combination with the @kbd{ALT} key will move the cursor
+to the top/bottom/leftmost/rightmost position.
+
+ Cursor keys in combination with the @kbd{CTRL} key will scroll the view
+without moving the cursor. This is useful to reveal something without
+having to reposition the cursor.
+
+@strong{NOTE:} You can adjust the repeat speed and delay with the
+@cite{Input} system preferences.
+
+@item TAB
+Move to the note column of the next track.  Pressing @kbd{TAB} on the last
+track, will bring the cursor to the first track.  @kbd{SHIFT-TAB} moves
+backwards.
+
+@item DEL
+Delete the field under the cursor.  @kbd{SHIFT-DEL} kills all the note slot
+(note, effect and value).
+@end table
+
+
+
+@subsection Project Menu
+
+@vtable @code
+@item Open Pattern (Amiga-O)
+Opens a file requester where you can select a pattern to be loaded in
+place of the current one.
+
+@item Save Pattern (Amiga-S)
+Saves the current pattern to a file with the same name of the pattern.
+If the pattern has no name, a file requester will open.
+
+@item Save Pattern As (Amiga-A)
+Opens a file requester where you can select the file and path name where
+the current pattern will be saved to.
+@end vtable
+
+
+
+@subsection Edit Menu
+
+@vtable @code
+@item Mark (Amiga-M)
+Toggles mark mode in the pattern editor.
+
+@item Cut (Amiga-X)
+Copies the currently marked region to the clipboard and erases it from
+the pattern.
+
+@item Copy (Amiga-C)
+Copies the currently marked region to the clipboard.
+
+@item Paste (Amiga-V)
+Gets the last pattern stored into the clipboard and pastes it in the
+current pattern starting at the current cursor position.
+
+@item Erase (Amiga-E)
+Clears the currently marked region.
+
+@item Undo (Amiga-U)
+Restores the pattern to the state it was before the last
+modification.
+
+@item Redo (Amiga-Y)
+Remakes the last change undone with the Undo function.
+@end vtable
+
+
+
+@c ******************
+@c *   Instruments  *
+@c ******************
+
+@node Instruments, Song Information, Pattern Editor, Panels
+@section Instruments Editor Window
+@cindex Instruments Editor Window
+
+@subsection Gadgets
+
+@vtable @code
+@item Instruments List (Cursor up/down)
+This list shows all the instruments of a song.  The leftmost column
+is the instrument number (hexadecimal), followed by its name.
+Some musicians usually use the instrument names to include short
+texts inside the modules they distribute.  Picking an instrument
+from this list will set it as the current instrument.  An empty
+instrument slot with no name will have the word @samp{--empty--}
+in place of its name.
+
+@item Instrument Name (RETURN or TAB)
+(The string gadget immediately below the Instruments list). Allows
+editing the name of the currently selected instrument.
+
+@item Volume (V)
+Default instrument volume.  Ranges from 0 (mute) to 64 (maximum
+volume).
+
+@item Fine Tune (F)
+Sets the instrument fine tune.  0 is the default tuning.  Negative
+values cause the instrument to be played at a slightly lower
+frequency.  Positive values cause the instrument to be played at
+a slightly higher frequency.
+
+@item Lenght
+Displays the instrument lenght in bytes.
+
+@item Kind (K)
+This function has not yet been implemented.
+
+@item Edit (E)
+Opens or activates the @ref{Sample Editor} panel.
+@end vtable
+
+
+
+@subsection Instruments Menu
+
+@vtable @code
+@item Load (Amiga-L)
+Loads an instrument in the current intrument slot.  A file requester
+will be put up to let you choose the instrument.  The file requester
+is asynchronous, so you can continue operating @value{prgname} while
+keeping the requester open.
+@item Save (Amiga-S)
+Saves the current instrument to a file with its current name.
+
+@item SaveAs (Amiga-A)
+Opens a file requester and saves the current instrument to the
+selected file name and path.
+
+@item Remap (Amiga-R)
+Remaps all the instruments removing all empty slots between them.
+The song patterns are updated to keep the correct instruments.
+This function is useful to save a module to a format which has a
+limited number of instruments, like @ref{SoundTracker}.
+@end vtable
+
+
+
+@c ********************
+@c * Song Information *
+@c ********************
+
+@node Song Information, Sequence, Instruments, Panels
+@section Song Information Window
+@cindex Song Information Window
+
+@subsection Gadgets
+
+@vtable @code
+@item New
+Creates a new module and makes it the current module.
+
+@item Del
+Deletes the current module from memory. If the module has been
+modified since last saving, a requester will give you the chance
+to abort the operation.
+
+@item Open... (O)
+Opens a song over the current one.  A file requester will be put
+up to let you choose the module.  The file requester is asynchronous,
+so you can continue operating @value{prgname} while keeping the requester
+open.  You can use multiselection to open several modules at once.
+This gadget behaves exactly like the @code{Open} menu item in the @ref{ToolBox}
+panel.
+
+@item Save (S)
+Saves the current song to the same path it was loaded from.
+This gadget behaves exactly like the @code{Save} menu item in the @ref{ToolBox}
+panel.
+
+@item Song Name (N)
+Displays the name of the current song.  You can use any character in the song
+name, but keep in mind that the name you type will be used by default as the
+filename for the song, so be careful with special characters such as @samp{/}
+and @samp{:}!
+
+@item Author (A)
+Displays the author of the current song.  @value{prgname} tries to guess the
+author when it loads a module whose format has no direct support for embedded
+author name. @value{prgname} scans the instruments names looking for a line
+beginning with a pound sign (@samp{#}). This convention started with
+@cite{IntuiTracker}, an old module player for the Amiga. If the author name
+can't still be found, @value{prgname} looks again for an occurence of @samp{by },
+@samp{by:} or @samp{(c)}, which is usually followed by the author name.
+The author name is ignored when saving to a format which does not support
+embedding the author name.
+
+@item Tempo (T)
+Displays the default tempo for the current song.  Valid range is 32-255, the
+dafult is 125. The default tempo is used when the song starts playing and can
+be overridden by the Set Tempo command.  @xref{Effects}.
+
+@item Speed (P)
+Displays the default speed for the current song.  Valid range is 1-31, the
+dafult is 6. The default speed is used when the song starts playing and can
+be overridden by the Set Speed command.  @xref{Effects}.
+
+@item Restart (R)
+Displays the position where playback will continue once the song end has
+been reached.  The restart position is lost when saving to formats which
+do not support it.
+
+@item Patterns
+Displays the total number of patterns in the song.
+
+@item Tracks
+Displays the maximum number of tracks in the song.
+
+@item Length
+Displays the number of positions in the song sequence.
+
+@item Total Module Size
+Shows an approximation of the memory used up by the song and its data.
+This value will usually be similar to the file size.
+
+@item Total Instruments Size
+Shows an approximation of the memory used by all the instruments of the
+current song.
+@end vtable
+
+@subsection Song Menu
+
+@vtable @code
+@item Merge Songs (Amiga-M)
+
+@item Join Songs (Amiga-J)
+
+@item Clear (Amiga-K)
+
+@end vtable
+
+
+
+@c ************
+@c * Sequence *
+@c ************
+
+@node Sequence, Save Format, Song Information, Panels
+@section Sequence Editor Window
+@cindex Sequence Editor Window
+
+@subsection Gadgets
+
+ Had no time to write it, sorry. ;-)
+
+@subsection Menu
+
+ Had no time to write it, sorry. ;-)
+
+
+
+@c ***************
+@c * Save Format *
+@c ***************
+
+@node Save Format, Clear Song, Sequence, Panels
+@section Save Format Window
+@cindex Save Format Window
+
+@subsection Gadgets
+
+ Had no time to write it, sorry. ;-)
+
+@subsection Menu
+
+ Had no time to write it, sorry. ;-)
+
+
+
+@c **************
+@c * Clear Song *
+@c **************
+
+@node Clear Song, Sample Editor, Save Format, Panels
+@section Clear Song Window
+
+@subsection Gadgets
+
+ Had no time to write it, sorry. ;-)
+
+@subsection Menu
+
+ Had no time to write it, sorry. ;-)
+
+
+
+@c *****************
+@c * Sample Editor *
+@c *****************
+
+@node Sample Editor, , Clear Song, Panels
+@section Sample Editor Window
+
+@subsection Gadgets
+
+ Had no time to write it, sorry. ;-)
+
+@subsection Menu
+
+ Had no time to write it, sorry. ;-)
+
+
+
+@c *********************
+@c *      Effects      *
+@c *********************
+
+@node Effects, Format Conversion, Panels, Top
+@chapter Effects
+@cindex Effects
+@cindex Commands
+
+ Had no time to write it, sorry. ;-)
+
+
+
+@c *********************
+@c * Format Conversion *
+@c *********************
+
+@node Format Conversion, Optimization, Effects, Top
+@chapter Format Conversion
+@cindex Format Conversion
+@cindex Module Formats
+
+@value{prgname} can load and save modules created by other
+music editors.  The following is a list of all supported module formats:
+
+@menu
+* XModule: XModule Format.
+* NoiseTracker::
+* ProTracker::
+* SoundTracker::
+* StarTrekker::
+* UNIC-Tracker::
+* FastTracker::
+* TakeTracker::
+* Oktalyzer::
+* MED::
+* OctaMED::
+* ScreamTracker::
+* MIDI File::
+@end menu
+
+
+As a matter of fact, there are things that simply cannot be done
+because of the big differences between different module formats.
+Below is a list of the internal limitations of each module format.
+When you save to a particular format and the module exceeds one of
+these values, or makes use of features not implemented, some data
+will be stripped away to produce at least a partial conversion.
+
+@example
+@group
+@c Is there a better way to make a table like this?
+
+Program          Instr  MaxPatt PattLen SongLen InstrLen Tracks
+---------------------------------------------------------------
+Oktalyzer         36      128     128     128     128K      4
+SoundTracker      15       64      64     128      64K      4
+NoiseTracker      31       64      64     128      64K      4
+StarTrekker       31       64      64     128      64K     4/8
+ProTracker        31      100      64     128      64K      4
+Fast Tracker I    31      100      64     128      64K    4/6/8
+MED/OctaMED       63      256     256     256   No Limit  1-64
+
+XModule           63      256   32768   32768   No Limit  1-32
+
+
+Where:
+#Instr   is the maximum number of instruments,
+MaxPatt  is the maximum number of patterns,
+PattLen  is the maximum number of lines per pattern,
+SongLen  is the maximum number of positions in a song,
+InstrLen is the maximum size of an instrument in bytes,
+Tracks   is the maximum number of tracks in a pattern.
+@end group
+@end example
+
+ Effects are the hardest thing to convert.  While Speed and Volume
+are almost the same between different music editors, some effects (such as
+Oktalyzer's H and L) cannot be converted because there are no equivalents in
+other music editors.  Therefore, you will have to modify the score manually
+if you intend to convert a module while keeping such effects.  In addition,
+some effects behave in different ways even if they claim to do the same
+thing.
+
+ Last but not least, some music editors support synthetic or hybrid
+instruments.  @value{prgname} does not convert such instruments,
+because I can hardly think of a way to implement a sample to synth
+conversion (any ideas?). I used to like synthetic music, but it
+seems that all those great composers on the good old C64 have
+vanished@dots{}  Why have you musicians become so lazy? 8-(
+
+
+
+@c ******************
+@c *  NoiseTracker  *
+@c ******************
+
+@node NoiseTracker, ProTracker, , Format Conversion
+@section NoiseTracker
+@cindex NoiseTracker
+
+Noise/ProTracker 31 instruments module.  This is absolutely the
+most common module format; almost any music editor and module
+player (even the crap ones for the PeeCee) understand this
+format.  Noise/ProTracker modules can be recognized by looking for
+the ID @samp{M.K.} at offset 1080 ($438 in hex) in the file.
+Andrew Scott, the author of MidiMod, says that @samp{M.K.} are
+the initials of Mahoney & Kaktus, the two guys who designed this
+format.  Peter Kunath, the author of DeliTracker, points out that
+@samp{M.K.} are more likely to be the initials of Michael Kleps
+(aka Unknown/DOC).
+
+Unfortunately, there is an infinite variety of standard
+NoiseTracker modules with fancy IDs. In fact, most tracker
+clones put their own ID instead of the standard one even if
+there is no reason to do it.  If you find such a module, try
+replacing the ID with an hex editor and you will be able to
+load it most of the times.
+
+
+
+@c ******************
+@c *   ProTracker   *
+@c ******************
+
+@node ProTracker, SoundTracker, NoiseTracker, Format Conversion
+@section ProTracker 100 Patterns
+@cindex ProTracker
+@cindex M!K!
+@cindex 100 Patterns ProTracker modules
+
+@value{prgname} will automatically switch to @code{ProTracker 100}
+whenever the module being saved exceeds the 64 patterns limit of
+NoiseTracker modules.
+Modules with more than 64 patterns have the ID @samp{M!K!} instead
+of the standard Noise/ProTracker ID (@samp{M.K.}).
+
+@strong{Note:} You @emph{must} load such a module with ProTracker 2.3 or
+better in order to play/edit it.  Older players will either refuse
+to load it or they'll turn the module into scratch dance.
+
+ProTracker 3.0+ has a completely new file format, but this isn't
+supported yet, as I've never seen a module of this kind yet.
+
+
+
+@c ******************
+@c *  SoundTracker  *
+@c ******************
+
+@node SoundTracker, StarTrekker, ProTracker, Format Conversion
+@section SoundTracker 15 Instruments
+@cindex SoundTracker
+
+This format is pretty useless as SoundTracker is becoming really rare.
+Do not save SoundTracker modules unless you really need to, because
+most players/editors will stop supporting this format very soon.
+There is no way to identify a SoundTracker module, therefore
+@value{prgname} will ask you to confirm a SoundTracker module when
+the file being loaded matches no other known module format.
+
+
+
+@c ******************
+@c *  StarTrekker   *
+@c ******************
+
+@node StarTrekker, UNIC-Tracker, SoundTracker, Format Conversion
+@section StarTrekker
+@cindex StarTrekker
+
+StarTrekker is another NoiseTracker clone, which is capable of
+playing modules with 4 or 8 channels and supports MIDI devices.
+The format of 4 channels modules is the same of NoiseTracker, except
+for the @samp{FLT4} ID.  Modules with 8 channels have the ID
+@samp{FLT8}, and keep the extra data in a second file, whose
+structure I don't know.  If you want support for 8 channels
+StarTrekker modules, please send me some information on its
+format.
+
+
+
+@c ******************
+@c *  UNIC-Tracker  *
+@c ******************
+
+@node UNIC-Tracker, FastTracker, StarTrekker, Format Conversion
+@section UNIC-Tracker
+@cindex UNIC-Tracker
+
+Actually, I've never seen UnicTracker, but I sometimes ripp Unic modules
+with ExoticRipper, so I decided to give support for this weird Tracker
+clone too.  The format is very similar to that of ProTracker modules,
+except for the @samp{EMW3} ID.
+
+
+
+@c ******************
+@c *  FastTracker   *
+@c ******************
+
+@node FastTracker, TakeTracker, UNIC-Tracker, Format Conversion
+@section FastTracker 1.0
+@cindex FastTracker
+@cindex 6CHN
+@cindex 8CHN
+
+The format of FastTracker 1.0 modules is exactly the same
+of @ref{TakeTracker}, but they can have only 6 or 8 channels.
+@value{prgname} does not support FastTracker 2.0 modules (those
+with extension @file{.xm}) yet.
+
+@c ******************
+@c *  TakeTracker   *
+@c ******************
+
+@node TakeTracker, Oktalyzer, FastTracker, Format Conversion
+@section TakeTracker
+@cindex TakeTracker
+@cindex 32 Channels ProTracker Modules
+
+These are Standard ProTracker modules, but they can have up to 32
+channels.  Their ID is @dfn{5CHN}, @dfn{6CHN}, and so on, up to
+@dfn{32CH}.  This format comes from some damn PeeCee tracker, which
+I've never seen.  Anyway, adding this format was a matter of five
+minutes.
+
+
+@c ******************
+@c *   Oktalyzer    *
+@c ******************
+
+@node Oktalyzer, MED, TakeTracker, Format Conversion
+@section Oktalyzer 1.1
+@cindex Oktalyzer
+
+Oktalyzer is an old editor which can play up to 8 channels by mixing
+instruments in real time.  Oktalyzer modules start with the ID
+@samp{OCTASONGGMOD}.  Development of Oktalyzer has been
+discontinued, as the author seems to be programming on Windoze now.
+It's really a shame, as many musicians found that Oktalyzer was one
+of the most powerful trackers for the Amiga.
+
+
+
+@c ******************
+@c *      MED       *
+@c ******************
+
+@node MED, OctaMED, Oktalyzer, Format Conversion
+@section MED
+@cindex MED
+
+ Not all features of MED are supported by @value{prgname}.
+Synthetic and hybrid instruments are ignored.
+
+
+
+@c ******************
+@c *    OctaMED     *
+@c ******************
+
+@node OctaMED, ScreamTracker, MED, Format Conversion
+@section OctaMED
+@cindex OctaMED
+@cindex MMD Format
+
+ MMD2 modules are not supported.  @value{prgname} will only
+load the first song in multisong modules.
+
+ @value{prgname} currently has an internal limit of only
+32 tracks, while OctaMED can do up to 128 tracks.  However,
+OctaMED can only play the first 8 tracks, so what are
+the other tracks for? ;-)
+
+
+
+@c ******************
+@c * ScreamTracker  *
+@c ******************
+
+@node ScreamTracker, MIDI File, OctaMED, Format Conversion
+@section ScreamTracker
+@cindex ScreamTracker
+@cindex S3M Format
+
+ ScreamTracker 3.01 is a damn PeeCee program made by those Future Crew
+guys who are trying to turn clones into Amigas.  ScreamTracker has the
+amazing feature of playing 32 tracks, 16 of which are for 8-bit
+digital samples.  The other 16 tracks play ADLib FM synths (not
+supported by @value{prgname}).
+
+
+
+@c ******************
+@c *   MIDI  File   *
+@c ******************
+
+@node MIDI File, , ScreamTracker, Format Conversion
+@section MIDI File
+@cindex MIDI File
+
+ MIDI is the industry standard musical score format (the same way Windoze
+is the industry standard graphic interface :-).  The MIDI concept is very
+different from tracker modules.  There are no sequence and patterns, but a
+long stream of events (notes) that have to be sent to some MIDI compliant
+device, one after the other, at specific time intrvals, based on the
+duration of each note.  There are no instruments inside MIDI files;
+instead, each note is associated with a preset number that matches a
+particular instrument in the device to which the MIDI events are sent.
+
+ @value{prgname}'s MIDI file saver is based on Andrew Scott's MidiMod
+program and I wish to thank him for granting me permission to use his
+code in @value{prgname}.
+
+
+
+@c ******************
+@c *  Optimization  *
+@c ******************
+
+@node Optimization, ARexx Interface, Format Conversion, Top
+@chapter Optimization
+@cindex Optimization
+
+ Module optimization is achieved by removing all unused data in such a way
+that the module will sound the same as before.  @value{prgname} can process a
+module to reduce its size as much as possible.  It is generally a good idea
+to optimize a module before distributing it, but never while you are still
+composing it, because you could easily loose some data that you didn't want
+to discard.  Do not expect to gain a lot of bytes optimizing modules: saving
+20% of the total size is usually a very good result.
+
+@table @bullet{} @code
+@item Unused patterns
+ Some formats (e.g.: Sound/Noise/ProTracker) store empty patterns
+in a module if a pattern with a greater number is used.  For instance,
+a song using patterns 1, 2 and 5 will contain patterns 3 and 4 as well.
+
+@strong{Warning}: If a song contains a part that has not been finished
+yet and has not already been inserted in the position table, you will
+lose it.
+
+@strong{Warning}: When a song is saved to Noise/ProTracker, patterns beyond
+the last used patterns are discarded regardless of this switch.  This
+is due to a limitation of Noise/ProTracker module format.
+
+@item Pattern Cutting
+When a pattern is breaked with the Pattern Break effect, it is cut to the
+line containing the break.  When saving to a format that does not support
+variable length patterns (eg.: @ref{SoundTracker}), the pattern is grown
+again, so this optimization won't help with such formats.
+
+@item Unused instruments
+ A composer may decide to use an instrument and then change his mind,
+but forget to clear the instrument.  In this case the module will
+contain an instrument that is never played.  These instruments
+are simply stripped away.
+
+@item Sample data after a loop
+ The part of an instrument following a loop is never played and is
+therefore discarded.
+
+@item Instruments zero tails
+ Long zero tails eat up memory and produce no sound.  This optimization
+is performed only on loopless instruments, as a zero sequence inside a
+loop @emph{does} make difference.  @value{prgname} leaves at least two zero
+bytes to avoid the nasty click produced by the speaker dropping its volume
+to zero too quickly.
+@end table
+
+
+@c *********************
+@c *  ARexx Interface  *
+@c *********************
+
+@node ARexx Interface, Known Bugs, Optimization, Top
+@chapter ARexx Interface
+
+WARNING: XModule's ARexx port is a little brain dead at the
+moment, sorry.
+
+
+List of ARexx commands:
+
+@menu
+* Activate: ARexx ACTIVATE.             Not yet implemented
+* Clear: ARexx CLEAR.                   Not yet implemented
+* Close: ARexx CLOSE.                   Not yet implemented
+* Column: ARexx COLUMN.                 Not yet implemented
+* Copy: ARexx COPY.                     Not yet implemented
+* Cursor: ARexx CURSOR.                 Not yet implemented
+* Cut: ARexx CUT.                       Not yet implemented
+* Deactivate: ARexx DEACTIVATE.         Not yet implemented
+* Erase: ARexx ERASE.                   Not yet implemented
+* GoToBookMark: ARexx GOTOBOOKMARK.     Not yet implemented
+* Help: ARexx HELP.                     Displays help on a particular topic
+* Line: ARexx LINE.                     Not yet implemented
+* LockGUI: ARexx LOCKGUI.               Blocks user input in @value{prgname} windows
+* New: ARexx NEW.                       Creates a new song
+* Open: ARexx OPEN.                     Opens a song over the current one
+* Optimize: ARexx OPTIMIZE.             Performs an @ref{Optimization}
+* Paste: ARexx PASTE.                   Not yet implemented
+* Print: ARexx PRINT.                   Not yet implemented
+* Quit: ARexx QUIT.                     Exits @value{prgname}
+* RequestFile: ARexx REQUESTFILE.       Not yet implemented
+* RequestResponse: ARexx REQUESTRESPONSE. Not yet implemented
+* RequestNotify: ARexx REQUESTNOTIFY.   Not yet implemented
+* Save: ARexx SAVE.                     Saves the current song
+* SaveInstrument: ARexx SAVEINSTRUMENT. Saves the current instrument
+* ScreenToBack: ARexx SCREENTOBACK.     Brings @value{prgname}'s screen to front
+* ScreenToFront: ARexx SCREENTOFRONT.   Brings @value{prgname}'s screen to back
+* SelectInstrument: ARexx SELECTINSTRUMENT. Selects the current instrument
+* SetBookMark: ARexx SETBOOKMARK.       Not yet implemented
+* ShowMessage: ARexx SHOWMESSAGE.       Prints a message in the log window
+* UnLockGUI: ARexx UNLOCKGUI.           Restores user input in the user interface
+* Version: ARexx VERSION.               Returns version information about @value{prgname}
+@end menu
+
+
+
+
+@c **************
+@c *  ACTIVATE  *
+@c **************
+
+@node ARexx ACTIVATE, ARexx CLEAR, , ARexx Interface
+@section ACTIVATE
+
+@subsection Template
+@example
+ACTIVATE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+-
+
+
+
+@c ***********
+@c *  CLEAR  *
+@c ***********
+
+@node ARexx CLEAR, ARexx CLOSE, ARexx ACTIVATE, ARexx Interface
+@section CLEAR
+
+@subsection Template
+@example
+CLEAR
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+-
+
+@c ***********
+@c *  CLOSE  *
+@c ***********
+
+@node ARexx CLOSE, ARexx COLUMN, ARexx CLEAR, ARexx Interface
+@section CLOSE
+
+@subsection Template
+@example
+CLOSE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+-
+
+@c ************
+@c *  COLUMN  *
+@c ************
+
+@node ARexx COLUMN, ARexx COPY, ARexx CLOSE, ARexx Interface
+@section COLUMN
+
+@subsection Template
+@example
+COLUMN
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c **********
+@c *  COPY  *
+@c **********
+
+@node ARexx COPY, ARexx CURSOR, ARexx COLUMN, ARexx Interface
+@section COPY
+
+@subsection Template
+@example
+COPY
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+@xref{ARexx CUT,CUT}, @xref{ARexx PASTE,PASTE}, @xref{ARexx ERASE,ERASE}.
+
+
+
+@c ************
+@c *  CURSOR  *
+@c ************
+
+@node ARexx CURSOR, ARexx CUT, ARexx COPY, ARexx Interface
+@section CURSOR
+
+@subsection Template
+@example
+CURSOR
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *********
+@c *  CUT  *
+@c *********
+
+@node ARexx CUT, ARexx DEACTIVATE, ARexx CURSOR, ARexx Interface
+@section CUT
+
+@subsection Template
+@example
+CUT
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c ****************
+@c *  DEACTIVATE  *
+@c ****************
+
+@node ARexx DEACTIVATE, ARexx ERASE, ARexx CUT, ARexx Interface
+@section DEACTIVATE
+
+@subsection Template
+@example
+DEACTIVATE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c ***********
+@c *  ERASE  *
+@c ***********
+
+@node ARexx ERASE, ARexx GOTOBOOKMARK, ARexx DEACTIVATE, ARexx Interface
+@section ERASE
+
+@subsection Template
+@example
+ERASE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c ******************
+@c *  GOTOBOOKMARK  *
+@c ******************
+
+@node ARexx GOTOBOOKMARK, ARexx HELP, ARexx ERASE, ARexx Interface
+@section GOTOBOOKMARK
+
+@subsection Template
+@example
+GOTOBOOKMARK
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c **********
+@c *  HELP  *
+@c **********
+
+@node ARexx HELP, ARexx LINE, ARexx GOTOBOOKMARK, ARexx Interface
+@section HELP
+
+@subsection Template
+@example
+HELP
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c **********
+@c *  LINE  *
+@c **********
+
+@node ARexx LINE, ARexx LOCKGUI, ARexx HELP, ARexx Interface
+@section LINE
+
+@subsection Template
+@example
+LINE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *************
+@c *  LOCKGUI  *
+@c *************
+
+@node ARexx LOCKGUI, ARexx NEW, ARexx LINE, ARexx Interface
+@section LOCKGUI
+
+@subsection Template
+@example
+LOCKGUI
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *********
+@c *  NEW  *
+@c *********
+
+@node ARexx NEW, ARexx OPEN, ARexx LOCKGUI, ARexx Interface
+@section NEW
+
+@subsection Template
+@example
+NEW
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c **********
+@c *  OPEN  *
+@c **********
+
+@node ARexx OPEN, ARexx OPTIMIZE, ARexx NEW, ARexx Interface
+@section OPEN
+
+@subsection Template
+@example
+OPEN
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c **************
+@c *  OPTIMIZE  *
+@c **************
+
+@node ARexx OPTIMIZE, ARexx PASTE, ARexx OPEN, ARexx Interface
+@section OPTIMIZE
+
+@subsection Template
+@example
+OPTIMIZE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c ***********
+@c *  PASTE  *
+@c ***********
+
+@node ARexx PASTE, ARexx PRINT, ARexx OPTIMIZE, ARexx Interface
+@section PASTE
+
+@subsection Template
+@example
+PASTE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c ***********
+@c *  PRINT  *
+@c ***********
+
+@node ARexx PRINT, ARexx QUIT, ARexx PASTE, ARexx Interface
+@section PRINT
+
+@subsection Template
+@example
+PRINT
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c **********
+@c *  QUIT  *
+@c **********
+
+@node ARexx QUIT, ARexx REQUESTFILE, ARexx PRINT, ARexx Interface
+@section QUIT
+
+@subsection Template
+@example
+QUIT
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *****************
+@c *  REQUESTFILE  *
+@c *****************
+
+@node ARexx REQUESTFILE, ARexx REQUESTRESPONSE, ARexx QUIT, ARexx Interface
+@section REQUESTFILE
+
+@subsection Template
+@example
+REQUESTFILE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *********************
+@c *  REQUESTRESPONSE  *
+@c *********************
+
+@node ARexx REQUESTRESPONSE, ARexx REQUESTNOTIFY, ARexx REQUESTFILE, ARexx Interface
+@section REQUESTRESPONSE
+
+@subsection Template
+@example
+REQUESTRESPONSE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *******************
+@c *  REQUESTNOTIFY  *
+@c *******************
+
+@node ARexx REQUESTNOTIFY, ARexx SAVE, ARexx REQUESTRESPONSE, ARexx Interface
+@section REQUESTNOTIFY
+
+@subsection Template
+@example
+REQUESTNOTIFY
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c **********
+@c *  SAVE  *
+@c **********
+
+@node ARexx SAVE, ARexx SAVEINSTRUMENT, ARexx REQUESTNOTIFY, ARexx Interface
+@section SAVE
+
+@subsection Template
+@example
+SAVE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c ********************
+@c *  SAVEINSTRUMENT  *
+@c ********************
+
+@node ARexx SAVEINSTRUMENT, ARexx SCREENTOBACK, ARexx SAVE, ARexx Interface
+@section SAVEINSTRUMENT
+
+@subsection Template
+@example
+SAVEINSTRUMENT
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c ******************
+@c *  SCREENTOBACK  *
+@c ******************
+
+@node ARexx SCREENTOBACK, ARexx SCREENTOFRONT, ARexx SAVEINSTRUMENT, ARexx Interface
+@section SCREENTOBACK
+
+@subsection Template
+@example
+SCREENTOBACK
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *******************
+@c *  SCREENTOFRONT  *
+@c *******************
+
+@node ARexx SCREENTOFRONT, ARexx SELECTINSTRUMENT, ARexx SCREENTOBACK, ARexx Interface
+@section SCREENTOFRONT
+
+@subsection Template
+@example
+SCREENTOFRONT
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c **********************
+@c *  SELECTINSTRUMENT  *
+@c **********************
+
+@node ARexx SELECTINSTRUMENT, ARexx SETBOOKMARK, ARexx SCREENTOFRONT, ARexx Interface
+@section SELECTINSTRUMENT
+
+@subsection Template
+@example
+SELECTINSTRUMENT
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *****************
+@c *  SETBOOKMARK  *
+@c *****************
+
+@node ARexx SETBOOKMARK, ARexx SHOWMESSAGE, ARexx SELECTINSTRUMENT, ARexx Interface
+@section SETBOOKMARK
+
+@subsection Template
+@example
+SETBOOKMARK
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *****************
+@c *  SHOWMESSAGE  *
+@c *****************
+
+@node ARexx SHOWMESSAGE, ARexx UNLOCKGUI, ARexx SETBOOKMARK, ARexx Interface
+@section SHOWMESSAGE
+
+@subsection Template
+@example
+SHOWMESSAGE
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c ***************
+@c *  UNLOCKGUI  *
+@c ***************
+
+@node ARexx UNLOCKGUI, ARexx VERSION, ARexx SHOWMESSAGE, ARexx Interface
+@section UNLOCKGUI
+
+@subsection Template
+@example
+UNLOCKGUI
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+@c *************
+@c *  VERSION  *
+@c *************
+
+@node ARexx VERSION, , ARexx UNLOCKGUI, ARexx Interface
+@section VERSION
+
+@subsection Template
+@example
+VERSION
+@end example
+
+@subsection Description
+
+-
+
+@subsection See Also
+
+
+
+
+
+
+@c ******************
+@c *   Known Bugs   *
+@c ******************
+
+@node Known Bugs, Future Plans, ARexx Interface, Top
+@chapter Known Bugs
+@cindex Known Bugs
+@cindex Bugs, known ones
+@cindex Bug reports
+
+ Murphy's laws state that nothing can be perfect, not even computer
+programs.  @value{prgname} is far from being perfect and I need
+your help to fix as many bugs as possible.  If you discover a bug
+which isn't reported in the list below (note: there is no list at
+the moment; this is a beta release, remember?), please notify me.
+
+ When you report a bug, state exactly what happens and indicate
+how to reproduce the conditions that generated the problem.
+Reports like @cite{@value{prgname} sometimes crashes converting my
+favorite module} does not help at all.  Include your name if you want
+to be credited in future @value{prgname} releases.
+
+ If the program refuses to convert a module, mail it to me on
+a disk or upload it somewhere I can reach it and I'll try to fix the
+problem as soon as possible.
+
+ @value{prgname} has been tested on several machines running Kickstart 2.0,
+3.0 and 3.1.  It should work on any 68K family processor (was tested on
+68000, 68030 and 68040) and is Enforcer and Mungwall proof.  I have also
+tested @value{prgname} with virtual memory programs such as VMM and found
+that everything seems to work correctly.
+
+
+
+@c ******************
+@c *  Future Plans  *
+@c ******************
+
+@node Future Plans, Programmers, Known Bugs, Top
+@chapter Future plans
+
+@table @bullet{} @code
+
+
+@item Other module formats
+Some formats I'd like to give support for in @value{prgname} as soon
+as possible:
+
+@itemize @minus
+@item
+FastTracker II (@file{.XM})
+@item
+MultiTracker (@file{.MTM})
+@item
+OctaMED MMD2
+@item
+Face The Music (@file{.FTM})
+@item
+Quadra Composer
+@end itemize
+
+If you have documentation regarding a format you would like to see in the
+next release, please send it to me together with a couple of modules and,
+if possible and legal, the program that created them.  I'll try to
+implement the new format only if it is somewhat easy: it is very hard to
+convert, say, Future Composer to SoundTracker.  Please, do not send a
+module without at least a text file describing its structure, as I am
+not willing to spend my nights hex-dumping modules :-).
+
+If you want to write a new loader/saver hook, please get the
+@value{prgame} developer package and read the included documentation.
+You will also find many examples and also the full source code of
+@value{prgname}.
+
+@item @TeX{}
+Another format I'd like to add is Amiga@TeX{}.  If you know
+@TeX{}, you'll surely be wondering what a typesetting program can be
+used for in music.  Well, Amiga@TeX{} is at the moment is the best
+implementation available on any computer of the @TeX{} typesetting
+environment, and its distribution includes @dfn{Music@TeX{}}, a powerful
+music language which can output notes in a great standard staff notation.
+I know no other program on the Amiga that can print notes of such fine
+quality (if you do, let me know!), so I think that many musicians would
+appreciate such an option.
+
+@end table
+
+
+
+@c ******************
+@c *  Programmers   *
+@c ******************
+
+@node Programmers, XModule Format, Future Plans, Top
+@chapter Programmer notes
+@cindex Programmers, notes for
+@cindex Modifying @value{prgname}
+@cindex Compiling @value{prgname}
+
+ @value{prgname} has been written in C and compiled with SAS C 6.51,
+Other compilers may require some changes in the source code.
+
+ The program is written in respect of the Amiga multitasking operative system
+and complies with Commodore programming guidelines.  All allocated resources
+will be (hopefully) freed before program termination.  I wrote @value{prgname}
+with the goal in mind of keeping the executable fast and small.
+
+ This manual has been typeset using MakeInfo, a GNU PD utility ported
+to the Amiga by Reinhard Spisser and Sebastiano Vigna.
+I found that the time I saved writing one file for three was nearly
+compensated by the time spent learning how to operate MakeInfo.
+
+ Besides, English is not my first language (as you might have guessed) and
+I bet this document is full of grammar mistakes.
+
+ If you intend to translate, improve or somewhat edit this document, please
+work on the supplied TexInfo file and then process it with the utilities
+supplied in the MakeGuide distribution.  You can find MakeGuide in several
+PD libraries such as Aminet.
+
+ Do not hesitate sending criticisms to my work, and, even better,
+advices on how to improve the program and its documentation.  @xref{Author Info}.
+
+
+
+@c ******************
+@c * XModule Format *
+@c ******************
+
+@node XModule Format, Credits, Programmers, Top
+@chapter XModule Module Format
+@cindex File Format used by XModule
+
+Before going into the details of the @value{prgname} module format, I'd like to
+explain why I felt the need to invent yet another module format.  There were
+plenty of them, but none had the flexibility I was looking for. The only one
+exception is in my opinion OctaMED's MMD.  But the MMD file format is very
+complicated, hard to read and poorly documented.  When I designed it, I wanted
+XMOD to be as flexible, easy, efficient and expandable as possible.  If you are
+developing a music program, please consider supporting XMOD.  If you have troubles
+with the following format description, contact me and I'll be glad to help you.
+
+@value{prgname} modules are standard IFF files.  They consist of a sequence of chunks
+(also called @dfn{proprieties}) each storing a particular type of information related
+to the module.  Text chunks, such as NAME and AUTH chunks are variable sized and NOT
+null terminated.  When you read such a chunk, you should take care to clip the string
+to your maximum buffer size.  NAME and AUTH fields are optional and should be assumed
+empty when missing.
+
+As with every IFF file, you should not depend on the order and size of any chunk.
+However, as usual with most IFF formats, header chunks must always precede related
+BODY chunks.  XModule requires this for PHDR chunks because it isn't possible to load
+a pattern without knowing its length and number of tracks.  Some chunks may grow in
+size in future versions of XModule.
+
+@example
+FORM XMOD
+
+  [NAME]
+    The name chunk contains the original name used to save the module.
+
+  MHDR
+    Module header.  Contains a ModuleHeader structure as defined in
+    <XModuleClass.h>.
+
+  [ANNO]
+    Some XModule copyright information.
+
+  FORM SONG
+    The song FORM contains all data relative to one song.  Any
+    number of songs can be stored into one module.
+
+    [NAME]
+      Name of the song.
+
+    [AUTH]
+      Author of the song.
+
+    [ANNO]
+      Song description.
+
+    SHDR
+      Song Header.  Contains a SongHeader structure as defined in
+      <XModuleClass.h>.
+
+    SEQN
+      Song Sequence.  The contents of this chunk are unsigned words
+      representing the pattern to play at a particular position.
+      The size of this chunk should be twice the Length field of
+      the SongHeader.
+      When a song contains multiple sections, one SEQN chunk will
+      be stored for each of the song sections (not yet implemented).
+
+    FORM PATT
+      Each occurrence of this FORM contains one pattern.  Patterns
+      are numbered starting from 0.  Each pattern has its own
+      length and number of tracks.
+
+      [NAME]
+        Name of this Pattern.
+
+      PHDR
+        Pattern Header.  Contains a PatternHeader structure as
+        defined in <XModuleClass.h>.
+
+      BODY
+        The BODY chunk is stored as an array of PatternNote
+        structures.  Tracks are stored one after the other.
+        There are NumTracks*Length Note structures in the BODY
+        chunk.
+
+    END FORM PATT
+
+    FORM 8SVX
+      Each SONG contains one or more occurences of this FORM,
+      one for each sample.  XModule currently saves and loads
+      only the NAME, VHDR and BODY chunks.  Be prepared to
+      decode Fibonacci Delta Encoded BODY chunks. An additional
+      non-standard chunk is saved:
+
+      INST
+        XModule instrument info.  Contains an InstrumentInfo
+        structure  as defined in <XModuleClass.h>.
+
+      When the BODY chunk is missing, the sample must be
+      loaded from the instrument library using the contents
+      of the NAME chunk as the file name.
+
+      See the IFF 8SVX documentation for more details.
+
+    END FORM 8SVX
+
+  END FORM SONG
+
+END FORM XMOD
+@end example
+
+
+
+@c ******************
+@c *    Credits     *
+@c ******************
+
+@node Credits, Author Info, XModule Format, Top
+@chapter Credits
+@cindex Credits
+@cindex Acknowledgements
+@cindex Thanks
+
+ These people have helped me in developing @value{prgname}:
+
+@itemize @bullet
+@item
+Fabio Barzagli, for beta testing @value{prgname} and for giving me
+all his awesome modules!
+
+Keep on making good music, Fabio!
+
+@item
+Jarno Paanen, for sending me the obfuscated source of his 32 channel
+replay engine and for the e-mail support.
+
+Perhaps I would have understood more by disassembling the PS3M executable. :-))
+
+@item
+Peter Kunath, for his unvaluable help.
+
+No comment... :-)
+
+@item Stephen Cantini, Michael Reichenbach and Julien Wilk, respectively for
+writing the italian, german and french catalog.
+
+Thank you all! I'm sure @value{prgname} users in your countries will apreciate
+your efford.
+
+@item Pauli Porkka, for his kindness, for letting me win a free DASMP
+key, for the sources and for all the e-mails.
+
+Will you support my format in the next DASMP release?
+
+@item
+The Amiga, for being the best computer in the world. :^)
+
+Commodore: You've put really a lot of effort trying to kill the Amiga,
+but you didn't succeed!
+@end itemize
+
+
+
+@c ******************
+@c *  Author  Info  *
+@c ******************
+
+@node Author Info, Concept Index, Credits, Top
+@chapter How to contact the author
+@cindex How to contact the author
+@cindex Author's address
+@cindex Contact the author, how to
+
+If you want to make me aware of your suggestions, bug reports, ideas
+or you want to send me a gift, a good module or, why not, some money :-),
+reach me in any of the following ways:
+
+@table @bullet{} @code
+@item E-Mail
+@example
+@group
+UseNet:   xmodule@@shock.nervous.com (preferred)
+FidoNet:  Bernardo Innocenti 2:332/125.1
+AmigaNet: Bernardo Innocenti 39:102/205.1
+@end group
+@end example
+
+@item SystemShock BBS
+@example
+@group
+Line 1:  +39-55-499038 (28,800bps - V34)
+Line 2:  +39-55-472514 (14,400bps - V32bis)
+Sysop:   Bernardo Innocenti (me)
+Message area 300: XModule Support
+File Request magic name "XMODULE" to get the latest public version.
+@end group
+@end example
+
+@item Snail-Mail
+@example
+@group
+Bernardo Innocenti
+Via Ventiquattro Maggio, 14
+50129 - Firenze
+ITALY
+@end group
+@end example
+
+@item FAX
+@example
+@group
++39-55-8877771
+Attn:  Bernardo Innocenti
+Subj:  Amiga/@value{prgname}
+@end group
+@end example
+@end table
+
+
+I'll try to reply to all my e-mail, but I'm too lazy to reply
+snail-mail, unless it is very important.  Your suggestions
+will be taken into account unless they are @cite{I want
+@value{prgname} to do ice tea}-style.
+
+
+@node Concept Index, , Author Info, Top
+@unnumbered Concept Index
+
+@printindex cp
+
+@contents
+@bye
diff --git a/xmodule_lib.fd b/xmodule_lib.fd
new file mode 100644 (file)
index 0000000..2132a77
--- /dev/null
@@ -0,0 +1,35 @@
+##base _XModuleBase
+##bias 30
+##public
+*--- functions in V1 or higher (Release 4.0) ---
+##private
+xmPrivate1()()
+xmPrivate2()()
+xmPrivate3()()
+xmPrivate4()()
+##public
+*
+* Public entries
+*
+xmCreateSongA(tagList)(a0)
+xmDeleteSong(si)(a0)
+xmAddSongA(si,position,tagList)(a0/a1/a2)
+xmRemSong(si)(a0)
+xmActivateSong(si)(a0)
+xmLockActiveSong(mode)(d0)
+xmAddHookA(tagList)(a0)
+xmRemHook(hook)(a0)
+xmIdentifyModule(fh,tagList)(d0,a0)
+xmLoadModuleA(fileName,tagList)(a0/a1)
+xmSaveModuleA(si,fileName,saver,tagList)(a0/a1/a2/a3)
+xmSetSongLen(si,length)(a0,d0)
+xmAddPatternA(si,tagList)(a0/a1)
+xmSetPatternA(si,pattNum,tagList)(a0,d0,a1)
+xmRemPattern(si,pattNum,replaceWith)(a0,d0,d1)
+xmAddInstrumentA(si,instrNum,tagList)(a0,d0,a1)
+xmSetInstrumentA(si,instrNum,tagList)(a0,d0,a1)
+xmRemInstrument(si,instrNum)(a0,d0)
+xmProcessSongA(si,reserved,tagList)(a0/a1/a2)
+xmDisplayMessageA(level,message,args)(d0,a0/a1)
+xmDisplayProgress(actual,total)(d0/d1)
+##end