Initial commit.
[amiga/xmodule.git] / Hooks / ScreamTrackerHook.c
1 /*
2 **      ScreamTrackerHook.c
3 **
4 **      Copyright (C) 1995,96,97 Bernardo Innocenti
5 **
6 **      Load a ScreamTracker 3.01 module with any number of tracks.
7 **      Only sample instruments are supported.
8 */
9
10
11 #include <exec/memory.h>
12 #include <libraries/xmodule.h>
13 #include <libraries/songclass.h>
14
15 #include <proto/exec.h>
16 #include <proto/dos.h>
17 #include <proto/intuition.h>
18 #include <proto/xmodule.h>
19
20 #include "XModulePriv.h"
21
22
23 /* Local function prototypes */
24
25 APTR HOOKCALL _GetEngine (
26         REG(a6, struct Library *MyBase));
27 APTR HOOKCALL _SetupXMHook (
28         REG(a0, struct XModuleBase *_XModuleBase),
29         REG(a6, struct Library *mybase));
30 static HOOKCALL struct XMHook *IdentifyScreamTracker (
31         REG(d0, BPTR fh),
32         REG(a0, struct XMHook *loader),
33         REG(a1, ULONG *tags));
34 static HOOKCALL LONG LoadScreamTracker (
35         REG(d0, BPTR fh),
36         REG(a0, struct SongInfo *si),
37         REG(a1, struct XMHook *loader),
38         REG(a2, ULONG *tags));
39 INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval);
40
41
42 #ifndef MAKE_ID
43 #define MAKE_ID(a,b,c,d) \
44         ((ULONG) (a)<<24 | (ULONG) (b)<<16 | (ULONG) (c)<<8 | (ULONG) (d))
45 #endif
46
47 /* Convert an Intel style WORD to Motorola format */
48 #define I2M(x) ( (UWORD) ( (((UWORD)(x)) >> 8) | (((UWORD)(x)) << 8) ) )
49
50 /* Convert an Intel style LONG to Motorola format */
51 #define I2ML(x) ( I2M((x)>>16) | (I2M((x))<<16) )
52
53 /* Convert a ParaPointer to a normal file offset */
54 #define PARA(x) (((ULONG)I2M(x)) << 4)
55
56 /* Convert a 3-byte ParaPointer to a normal file offset */
57 #define PARAL(x) (( (ULONG)x[0]<<16 | (ULONG)x[2]<<8 | (ULONG)x[1] ) << 4)
58
59
60 struct S3MHeader
61 {
62         UBYTE SongName[28];
63         UBYTE Constant;
64         UBYTE Type;
65         UWORD Pad0;
66         UWORD OrdNum;           /* Number of positions */
67         UWORD InsNum;
68         UWORD PatNum;
69         UWORD Flags;
70         UWORD CreatedWith;
71         UWORD FileFormat;       /* See below */
72         ULONG ID;                       /* Should be ID_SCRM */
73         UBYTE GlobalVolume;
74         UBYTE InitialSpeed;
75         UBYTE InitialTempo;
76         UBYTE MasterVolume;
77         UBYTE Pad1[10];
78         UWORD Special;
79         UBYTE Channels[32];
80 };
81
82
83
84 /* Values for S3MHeader->FileFormat */
85
86 #define S3MFF_SIGNEDSAMPLES             1
87 #define S3MFF_UNSIGNEDSAMPLES   2
88
89
90
91 struct S3MSamp
92 {
93         UBYTE Type;                     /* See ITYPE_#? definitions below.      */
94         UBYTE DosName[12];
95         UBYTE MemSeg[3];        /* :-))) Parapointer to sample data     */
96         ULONG Length;
97         ULONG LoopBeg;
98         ULONG LoopEnd;
99         UBYTE Volume;
100         UBYTE Pad0;
101         UBYTE Pack;                     /* See SPACK_#? definitions below. */
102         UBYTE Flags;            /* See SFLAG_#? definitions below. */
103         ULONG C2Spd;
104         ULONG Pad1;
105         UWORD Reserved0[2];
106         ULONG Reserved1[1];
107         UBYTE SampleName[28];
108         ULONG SampID;           /* Should be ID_SCRS */
109 };
110
111
112 /* S3M Instrument types */
113
114 #define ITYPE_NONE                      0
115 #define ITYPE_SAMPLE            1
116 #define ITYPE_ADLIB_MELODY      2
117 #define ITYPE_ADLIB_SNARE       3
118 #define ITYPE_ADLIB_TOM         4
119 #define ITYPE_ADLIB_CYMBAL      5
120 #define ITYPE_ADLIB_HIHAT       6
121
122
123 /* S3M Sample packing */
124
125 #define SPACK_NONE                      0
126 #define SPACK_ADPCM                     1       /* Unsupported by ST3.01 */
127
128
129 /* S3M Sample Flags */
130 #define SFLAG_LOOP                      1
131 #define SFLAG_STEREO            2       /* Unsupported by ST3.01 */
132 #define SFLAG_16BIT                     4       /* Unsupported by ST3.01 */
133
134
135 /* S3M IDs */
136
137 #define ID_S3M  MAKE_ID('S','3','M','\0')       /* ScreamTracker ID             */
138 #define ID_SCRM MAKE_ID('S','C','R','M')        /* ScreamTracker Module */
139 #define ID_SCRS MAKE_ID('S','C','R','S')        /* ScreamTracker Sample */
140 #define ID_SCRI MAKE_ID('S','C','R','I')        /* ScreamTracker Instr  */
141
142
143
144 /* Effects conversion table */
145 static const UBYTE Effects[MAXTABLEEFFECTS] =
146 {
147 /*   S3M                XModule                 Val */
148
149         0x00,   /*      Null effect             $00     */
150
151         0x05,   /*      Portamento Up   $01     */
152         0x04,   /*      Portamento Down $02     */
153         0x06,   /*      Tone Portamento $03     */
154         0x07,   /*      Vibrato                 $04     */
155         0x0B,   /*      ToneP + VolSl   $05     */
156         0x0A,   /*      Vibra + VolSl   $06     */
157         0x08,   /*      Tremolo                 $07     */
158         0x00,   /*      Set Hold/Decay  $08     */
159         0x0E,   /*      Sample Offset   $09     */
160         0x03,   /*      Volume Slide    $0A     */
161         0x01,   /*      Position Jump   $0B     */
162         0x12,   /*      Set Volume              $0C     */
163         0x02,   /*      Pattern break   $0D     */
164         0x10,   /*      Misc                    $0E     */
165         0x00,   /*      Set Speed               $0F     */
166         0x11,   /*      Set Tempo               $10     */
167         0x09,   /*      Arpeggio                $11     */
168
169         0x03,   /*      Oktalyzer H             */
170         0x03    /*      Oktalyzer L             */
171 };
172
173
174
175 /* Library data */
176
177 const UBYTE LibName[] = "screamtracker.xmhook";
178 const UBYTE LibVer[] = { '$', 'V', 'E', 'R', ':', ' ' };
179 const UBYTE LibId[] = "screamtracker.xmhook 1.0 (17.3.96) © 1995-96 by Bernardo Innocenti";
180
181
182 /* Get around a SAS/C bug which causes some annoying warnings
183  * with the library bases defined below.  The problem happens
184  * when the GST has been compiled with the CODE=FAR switch and
185  * the source is being compiled with CODE=NEAR.
186  */
187 #ifdef __SASC
188 #pragma msg 72 ignore push
189 #endif /* __SASC */
190
191 struct ExecBase                 *SysBase                = NULL;
192 struct XModuleBase              *XModuleBase    = NULL;
193 struct DosLibrary               *DOSBase                = NULL;
194 struct IntuitionBase    *IntuitionBase  = NULL;
195
196 #ifdef __SASC
197 #pragma msg 72 ignore pop
198 #endif /* __SASC */
199
200
201 static HOOKCALL struct XMHook *IdentifyScreamTracker (
202         REG(d0, BPTR fh),
203         REG(a0, struct XMHook *loader),
204         REG(a1, ULONG *tags))
205
206 /* Determine if the given file is a ScreamTracker 3.0 module.
207  * Note: the file position will be changed on exit.
208  */
209 {
210         ULONG id;
211
212         Seek (fh, 0x2C, OFFSET_BEGINNING);
213         if (FRead (fh, &id, 4, 1) != 1)
214                 return NULL;
215
216         return ((id == ID_SCRM) ? loader : NULL);
217 }
218
219
220
221 static HOOKCALL LONG LoadScreamTracker (
222         REG(d0, BPTR fh),
223         REG(a0, struct SongInfo *si),
224         REG(a1, struct XMHook *loader),
225         REG(a2, ULONG *tags))
226 {
227         UWORD                           *ParaInst,
228                                                 *ParaPatt;
229         struct Note                     *note;
230         struct Pattern          *patt;
231         struct S3MHeader         s3mhd;
232
233         ULONG           i, j;                   /* Loop counters */
234         LONG            err = 0;
235         UWORD           numchannels = 0, w;
236         LONG            l;                              /* Dummy read buffer */
237
238
239         /* Read module header */
240         if (FRead (fh, &s3mhd, sizeof (s3mhd), 1) != 1)
241                 return ERROR_IOERR;
242
243         /* Fix Intel WORD format */
244         s3mhd.OrdNum            = I2M (s3mhd.OrdNum);
245         s3mhd.InsNum            = I2M (s3mhd.InsNum);
246         s3mhd.PatNum            = I2M (s3mhd.PatNum);
247         s3mhd.Flags                     = I2M (s3mhd.Flags);
248         s3mhd.CreatedWith       = I2M (s3mhd.CreatedWith);
249         s3mhd.FileFormat        = I2M (s3mhd.FileFormat);
250         s3mhd.Special           = I2M (s3mhd.Special);
251
252
253         for (i = 0; i < 32; i++)
254                 if (s3mhd.Channels[i] != 255) numchannels++;
255
256         s3mhd.SongName[27] = '\0';      /* Ensure Null-termination */
257
258
259         /* Read the pattern sequence */
260
261         if (!xmSetSongLen (si, s3mhd.OrdNum))
262                 return ERROR_NO_FREE_STORE;
263
264         for (i = 0; i < s3mhd.OrdNum; i++)
265         {
266                 if ((l = FGetC (fh)) != -1L)
267                         si->Sequence[i] = l;
268                 else
269                         return ERROR_IOERR;
270         }
271
272         if (s3mhd.OrdNum & 1) FGetC (fh);       /* Keep WORD alignament */
273
274
275
276         /*******************/
277         /* Get Instruments */
278         /*******************/
279
280         xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
281                 (APTR)MSG_READING_INSTS, NULL);
282
283         /* Get parapointers to instruments */
284
285         if (!(ParaInst = AllocVec (s3mhd.InsNum * sizeof (UWORD), MEMF_PUBLIC)))
286                 return ERROR_NO_FREE_STORE;
287
288         if (!(ParaPatt = AllocVec (s3mhd.PatNum * sizeof (UWORD), MEMF_PUBLIC)))
289         {
290                 FreeVec (ParaInst);
291                 return ERROR_NO_FREE_STORE;
292         }
293
294         if (FRead (fh, ParaInst, sizeof (UWORD), s3mhd.InsNum) == s3mhd.InsNum)
295         {
296                 if (FRead (fh, ParaPatt, sizeof (UWORD), s3mhd.PatNum) == s3mhd.PatNum)
297                 {
298                         struct S3MSamp samp;
299
300                         /* Note: Need to Flush() here?  Hmm... I think not. */
301
302                         for (i = 0; i < s3mhd.InsNum; i++)
303                         {
304                                 if (xmDisplayProgress (i + 1, s3mhd.InsNum))
305                                 {
306                                         err = ERROR_BREAK;
307                                         break;
308                                 }
309
310                                 if (Seek (fh, PARA(ParaInst[i]), OFFSET_BEGINNING) == -1)
311                                 {
312                                         err = ERROR_IOERR;
313                                         break;
314                                 }
315                                 else
316                                 {
317                                         struct Instrument *inst;
318                                         BYTE *sample;
319
320                                         if (Read (fh, &samp, sizeof (samp)) != sizeof (samp))
321                                         {
322                                                 err = ERROR_IOERR;
323                                                 break;
324                                         }
325
326                                         samp.SampleName[27] = '\0';     /* Ensure NULL termination */
327
328                                         if (samp.Type == 0)
329                                                 ;       /* Do nothing */
330                                         else if (samp.Type == ITYPE_SAMPLE)
331                                         {
332                                                 if (samp.Pack != SPACK_NONE)
333                                                         xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
334                                                                 (APTR)MSG_UNKNOWN_SAMPLE_COMPRESSION, i + 1);
335
336                                                 if (samp.Flags & SFLAG_STEREO)
337                                                         xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
338                                                         (APTR)MSG_INST_IS_STEREO, i + 1);
339
340                                                 if (samp.Flags & SFLAG_16BIT)
341                                                 {
342                                                         xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
343                                                                 (APTR)MSG_INST_IS_16BIT, i + 1);
344                                                         continue;
345                                                 }
346
347                                                 if (!(inst = xmAddInstrument (si, i + 1,
348                                                         INSTRA_Name,    samp.SampleName,
349                                                         INSTRA_Length,  I2ML(samp.Length),
350                                                         INSTRA_Volume,  (samp.Volume > 64) ? 64 : samp.Volume,
351                                                         (samp.Flags & SFLAG_LOOP) ? INSTRA_LoopStart : TAG_IGNORE, I2ML(samp.LoopBeg),
352                                                         (samp.Flags & SFLAG_LOOP) ? INSTRA_LoopEnd : TAG_IGNORE,        I2ML(samp.LoopEnd),
353                                                         TAG_DONE)))
354                                                         return ERROR_NO_FREE_STORE;
355
356
357                                                 /* Seek where the sample is stored */
358                                                 if (Seek (fh, PARAL(samp.MemSeg), OFFSET_BEGINNING) == -1)
359                                                 {
360                                                         err = ERROR_IOERR;
361                                                         break;
362                                                 }
363
364                                                 /* Allocate memory for sample */
365
366                                                 if (!(sample = AllocVec (inst->Length, MEMF_SAMPLE)))
367                                                 {
368                                                         err = ERROR_NO_FREE_STORE;
369                                                         break;
370                                                 }
371
372                                                 /* Load sample data */
373
374                                                 if (Read (fh, sample, inst->Length) != inst->Length)
375                                                 {
376                                                         FreeVec (sample);
377                                                         err = ERROR_IOERR;
378                                                         break;
379                                                 }
380
381                                                 if (s3mhd.FileFormat >= S3MFF_UNSIGNEDSAMPLES)
382                                                 {
383                                                         /* unsigned -> signed  conversion */
384                                                         for (j = 0; j < inst->Length; j++)
385                                                                 sample[j] ^= 0x80;
386                                                 }
387
388                                                 xmSetInstrument (si, i,
389                                                         INSTRA_Sample,  sample,
390                                                         TAG_DONE);
391                                         }
392                                         else if (samp.Type <= ITYPE_ADLIB_HIHAT)
393                                         {
394                                                 static UBYTE *ADLibNames[] =
395                                                 {
396                                                         "Melody",
397                                                         "Snare Drum",
398                                                         "Tom",
399                                                         "Cymbal",
400                                                         "Hihat"
401                                                 };
402
403                                                 xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
404                                                         (APTR)MSG_ADLIB_INSTR, i + 1, ADLibNames[samp.Type - 2]);
405                                         }
406                                         else
407                                                 xmDisplayMessage (XMDMF_ERROR | XMDMF_USECATALOG,
408                                                         (APTR)MSG_ERR_NOT_A_SAMPLE, i + 1);
409                                 }
410                         }
411                 }
412                 else err = ERROR_IOERR;
413         }
414         else err = ERROR_IOERR;
415
416         FreeVec (ParaInst);
417
418         SetAttrs (si,
419                 SNGA_Title,                     s3mhd.SongName,
420                 SNGA_Author,            -1,
421                 SNGA_GlobalSpeed,       s3mhd.InitialSpeed,
422                 SNGA_GlobalTempo,       s3mhd.InitialTempo,
423                 TAG_DONE);
424
425         if (err)
426         {
427                 FreeVec (ParaPatt);
428                 return err;
429         }
430
431
432         /****************/
433         /* Get Patterns */
434         /****************/
435
436         xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
437                 (APTR)MSG_READING_PATTS, NULL);
438
439         for (i = 0; i < s3mhd.PatNum; i++)
440         {
441                 if (xmDisplayProgress (i + 1, s3mhd.PatNum))
442                 {
443                         err = ERROR_BREAK;
444                         break;
445                 }
446
447                 if (Seek (fh, PARA(ParaPatt[i]), OFFSET_BEGINNING) == -1)
448                 {
449                         err = ERROR_IOERR;
450                         break;
451                 }
452                 else
453                 {
454                         LONG ch = 0;    /* Channel number */
455
456                         /* Read size */
457
458                         if (Read (fh, &w, sizeof (UWORD)) != sizeof (UWORD))
459                         {
460                                 err = ERROR_IOERR;
461                                 break;
462                         }
463
464                         if (patt = xmAddPattern (si,
465                                 PATTA_Tracks,   numchannels,
466                                 PATTA_Lines,    64,
467                                 TAG_DONE))
468                         {
469                                 /* Loop on rows */
470                                 for (j = 0; (j < 64) && (ch != -1L); j++)
471                                 {
472                                         while ((ch = FGetC (fh)) != -1L)
473                                         {
474                                                 if (ch == 0)    /* End of row */
475                                                         break;
476
477                                                 if ((ch & 31) >= numchannels)
478                                                 {
479                                                         xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
480                                                                 (APTR)MSG_TRACK_OUT_OF_RANGE, ch & 31);
481                                                         note = &patt->Notes[0][j];
482                                                 }
483                                                 else
484                                                         note = &patt->Notes[ch & 31][j];
485
486                                                 if (ch & 32)    /* Note and instrument follows */
487                                                 {
488                                                         l = FGetC (fh);
489                                                         if (l != 255)
490                                                                 note->Note = ((((l >> 4) - 2) * 12) | (l & 0x0F)) + 1;
491                                                         note->Inst = FGetC (fh);
492                                                 }
493                                                 if (ch & 64)    /* Volume */
494                                                 {
495                                                         l = FGetC (fh);
496                                                         note->EffNum = EFF_SETVOLUME;
497                                                         note->EffVal = l;
498                                                 }
499
500                                                 if (ch & 128)   /* Command and Info */
501                                                 {
502                                                         note->EffNum = FGetC (fh);
503                                                         note->EffVal = FGetC (fh);
504                                                         note->EffNum = DecodeEff (note->EffNum, note->EffVal);
505                                                 }
506                                         }
507                                 }
508
509                                 if (ch == -1)
510                                 {
511                                         err = ERROR_IOERR;
512                                         break;
513                                 }
514                         }
515                         else
516                         {
517                                 err = ERROR_NO_FREE_STORE;
518                                 break;
519                         }
520
521                         /* Flush (fh); */
522                 }
523         }
524
525         FreeVec (ParaPatt);
526
527         return err;
528 }
529
530
531
532 INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval)
533 {
534         UBYTE i;
535
536         if ((eff == 0) && effval) /* Speed/Tempo */
537         {
538                 if (effval < 0x20)
539                         return EFF_SETSPEED;
540                 else
541                         return EFF_SETTEMPO;
542         }
543
544         for (i = 0 ; i < MAXTABLEEFFECTS; i++)
545                 if (eff == Effects[i])
546                         return i;
547
548         return 0;
549 }
550
551
552
553 HOOKCALL struct Library * _UserLibInit (REG(a6, struct Library *mybase))
554 {
555         return mybase;
556 }
557
558
559
560 HOOKCALL struct Library * _UserLibCleanup (REG(a6, struct Library *mybase))
561 {
562         return mybase;
563 }
564
565
566
567 HOOKCALL APTR _GetEngine (REG(a6, struct Library *mybase))
568 {
569         return NULL;
570 }
571
572
573
574 HOOKCALL APTR _SetupXMHook (
575         REG(a0, struct XModuleBase *_XModuleBase),
576         REG(a6, struct Library *mybase))
577 {
578         if (!(strcmp (_XModuleBase->xm_Library.lib_Node.ln_Name, "xmodule.library")))
579         {
580                 SysBase = *((struct ExecBase **)4);
581                 XModuleBase             = _XModuleBase;
582                 IntuitionBase   = XModuleBase->xm_IntuitionBase;
583                 DOSBase                 = XModuleBase->xm_DOSBase;
584
585                 xmAddHook (
586                         XMHOOK_Type,                    NT_XMLOADER,
587                         XMHOOK_Name,                    (LONG)"ScreamTracker",
588                         XMHOOK_Priority,                0,
589                         XMHOOK_Descr,                   (LONG)"ScreamTracker 3.01",
590                         XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
591                         XMHOOK_ID,                              ID_S3M,
592                         XMHOOK_Flags,                   0,
593                         XMHOOK_LibraryBase,             mybase,
594                         XMHOOK_LoadModFunc,             LoadScreamTracker,
595                         XMHOOK_IdentifyModFunc, IdentifyScreamTracker,
596                         XMHOOK_MaxTracks,               16,
597                         XMHOOK_MaxPatterns,             100,
598                         XMHOOK_MaxInstruments,  99,
599                         XMHOOK_MaxLength,               255,
600                         XMHOOK_MaxSampleLen,    64000,
601                         XMHOOK_MaxPattLen,              64,
602                         TAG_DONE);
603         }
604
605         return NULL;
606 }