Initial commit.
[amiga/xmodule.git] / XModuleHook.c
1 /*
2 **      XModuleHook.c
3 **
4 **      Copyright (C) 1994,95,96,97 Bernardo Innocenti
5 **
6 **      Internal loader/saver hook for the IFF XMOD module format
7 */
8
9 #include <exec/memory.h>
10 #include <libraries/xmoduleclass.h>
11
12 #include <proto/exec.h>
13 #include <proto/dos.h>
14 #include <proto/intuition.h>
15 #include <proto/iffparse.h>
16 #include <proto/xmodule.h>
17
18 #include "XModulePriv.h"
19 #include "Gui.h"
20
21
22 /* Local functions prototypes */
23
24 static HOOKCALL struct XMHook *IdentifyXModule (
25         REG(d0, BPTR fh),
26         REG(a0, struct XMHook *loader),
27         REG(a1, ULONG *tags));
28 static HOOKCALL LONG LoadXModule (
29         REG(d0, BPTR fh),
30         REG(a0, struct SongInfo *si),
31         REG(a1, struct XMHook *loader),
32         REG(a2, ULONG *tags));
33 static HOOKCALL LONG SaveXModule (
34         REG(d0, BPTR fh),
35         REG(a0, struct SongInfo *si),
36         REG(a1, struct XMHook *saver),
37         REG(a2, ULONG *tags));
38
39
40
41 GLOBALCALL void AddXModuleHooks (void)
42
43 /* Adds XModule loader and saver */
44 {
45         xmAddHook (
46                 XMHOOK_Type,                    NT_XMLOADER,
47                 XMHOOK_Name,                    (LONG)"XModule",
48                 XMHOOK_Priority,                50,
49                 XMHOOK_Descr,                   (LONG)"XModule internal format",
50                 XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
51                 XMHOOK_Flags,                   XMHF_INTERNAL,
52                 XMHOOK_LoadModFunc,             LoadXModule,
53                 XMHOOK_IdentifyModFunc, IdentifyXModule,
54                 TAG_DONE);
55
56         xmAddHook (
57                 XMHOOK_Type,                    NT_XMSAVER,
58                 XMHOOK_Name,                    (LONG)"XModule",
59                 XMHOOK_Priority,                50,
60                 XMHOOK_Descr,                   (LONG)"XModule Internal format",
61                 XMHOOK_Author,                  (LONG)"Bernardo Innocenti",
62                 XMHOOK_Flags,                   XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
63                                                                 XMHF_EXCLUDE_NAMES | XMHF_EXCLUDE_SEQUENCE,
64                 XMHOOK_SaveModFunc,             SaveXModule,
65                 TAG_DONE);
66 }
67
68
69
70 static HOOKCALL struct XMHook *IdentifyXModule (
71         REG(d0, BPTR fh),
72         REG(a0, struct XMHook *loader),
73         REG(a1, ULONG *tags))
74
75 /* Determine if the given file is an XModule module.
76  * Note: the file position will be changed on exit.
77  */
78 {
79         ULONG id[3];
80
81         Seek (fh, 0, OFFSET_BEGINNING);
82         if (FRead (fh, &id, 12, 1) != 1)
83                 return NULL;
84
85         if ((id[0] == ID_FORM) && (id[2] == ID_XMOD))
86                 return loader;
87
88         return NULL;
89 }
90
91
92
93 static HOOKCALL LONG LoadXModule (
94         REG(d0, BPTR fh),
95         REG(a0, struct SongInfo *si),
96         REG(a1, struct XMHook *loader),
97         REG(a2, ULONG *tags))
98 {
99         struct IFFHandle *iff;
100         struct ContextNode *cn;
101         LONG err;
102
103         if (iff = AllocIFF())
104         {
105                 iff->iff_Stream = (ULONG) fh;
106
107                 InitIFFasDOS (iff);
108
109                 if (!(err = OpenIFF (iff, IFFF_READ)))
110                 {
111                         struct ModuleHeader mhdr;
112
113                         static LONG stopchunks[] =
114                         {
115                                 ID_XMOD,        ID_NAME,
116                                 ID_XMOD,        ID_MHDR,
117                                 ID_SONG,        ID_FORM
118                         };
119
120                         if (err = StopChunks (iff, stopchunks, 3))
121                                 goto error;
122
123                         if (err = StopOnExit (iff, ID_XMOD, ID_FORM))
124                                 goto error;
125
126                         /* Scan module */
127
128                         while (1)
129                         {
130                                 if (err = ParseIFF (iff, IFFPARSE_SCAN))
131                                 {
132                                         if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
133                                         break; /* Free resources & exit */
134                                 }
135
136                                 if (cn = CurrentChunk (iff))
137                                 {
138                                         switch (cn->cn_ID)
139                                         {
140                                                 case ID_NAME:
141                                                 {
142                                                         UBYTE name[128];
143
144                                                         ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
145                                                         name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
146                                                         SetAttrs (si,
147                                                                 SNGA_Title,     name,
148                                                                 TAG_DONE);
149                                                         break;
150                                                 }
151
152                                                 case ID_MHDR:
153                                                         if ((err = ReadChunkBytes (iff, &mhdr, sizeof (mhdr))) != sizeof(mhdr))
154                                                                 goto error;
155                                                         break;
156
157                                                 case ID_FORM:
158                                                         if (cn->cn_Type == ID_SONG)
159                                                                 if (err = LoadSong (iff, si))
160                                                                         goto error;
161                                                         break;
162
163                                                 default:
164                                                         break;
165                                         }
166                                 }
167                         }
168 error:
169                         CloseIFF (iff);
170                 }
171
172                 FreeIFF (iff);
173         }
174         else err = ERROR_NO_FREE_STORE;
175
176         return err;
177 }
178
179
180
181 GLOBALCALL LONG LoadSong (struct IFFHandle *iff, struct SongInfo *si)
182 {
183         LONG err, len;
184         struct ContextNode *cn;
185         struct SongHeader shdr;
186
187         static LONG stopchunks[] =
188         {
189                 ID_SONG,        ID_NAME,
190                 ID_SONG,        ID_AUTH,
191                 ID_SONG,        ID_ANNO,
192                 ID_SONG,        ID_SHDR,
193                 ID_SONG,        ID_SEQN,
194                 ID_PATT,        ID_FORM,
195                 ID_8SVX,        ID_FORM
196         };
197
198
199         memset (&shdr, 0, sizeof (shdr));
200
201         if (err = StopChunks (iff, stopchunks, 7))
202                 return err;
203
204         if (err = StopOnExit (iff, ID_SONG, ID_FORM))
205                 return err;
206
207         /* Scan song */
208
209         while (1)
210         {
211                 if (err = ParseIFF (iff, IFFPARSE_SCAN))
212                 {
213                         if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
214                         break; /* Free resources & exit */
215                 }
216
217                 if (cn = CurrentChunk (iff))
218                 {
219                         switch (cn->cn_ID)
220                         {
221                                 case ID_NAME:
222                                 {
223                                         UBYTE name[128];
224
225                                         ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
226                                         name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
227                                         SetAttrs (si,
228                                                 SNGA_Title, name,
229                                                 TAG_DONE);
230                                         break;
231                                 }
232
233                                 case ID_AUTH:
234                                 {
235                                         UBYTE name[128];
236
237                                         ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
238                                         name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
239                                         SetAttrs (si,
240                                                 SNGA_Author, name,
241                                                 TAG_DONE);
242                                         break;
243                                 }
244
245                                 case ID_ANNO:
246                                 {
247                                         UBYTE name[128];
248
249                                         ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
250                                         name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
251                                         SetAttrs (si,
252                                                 SNGA_Description, name,
253                                                 TAG_DONE);
254                                         break;
255                                 }
256
257
258                                 case ID_SHDR:
259                                         if ((err = ReadChunkBytes (iff, &shdr, sizeof (shdr))) == 0)
260                                                 return err;
261
262                                         SetAttrs (si,
263                                                 SNGA_GlobalSpeed,               shdr.GlobalSpeed,
264                                                 SNGA_GlobalTempo,               shdr.GlobalTempo,
265                                                 SNGA_RestartPos,                shdr.RestartPos,
266                                                 SNGA_CurrentPatt,               shdr.CurrentPatt,
267                                                 SNGA_CurrentPos,                shdr.CurrentPos,
268                                                 SNGA_CurrentInst,               shdr.CurrentInst,
269                                                 SNGA_DefaultTracks,             shdr.DefNumTracks ? shdr.DefNumTracks : shdr.MaxTracks,
270                                                 SNGA_DefaultPattLen,    shdr.DefPattLen ? shdr.DefPattLen : DEF_PATTLEN,
271                                                 SNGA_CreationDate,              shdr.CreationDate,
272                                                 SNGA_LastChanged,               shdr.LastChanged,
273                                                 SNGA_TotalChanges,              shdr.TotalChanges,
274                                                 TAG_DONE);
275                                         break;
276
277                                 case ID_SEQN:
278                                         len     = min (cn->cn_Size / 2, MAXPOSITIONS);
279
280                                         if (xmSetSongLen (si, len))
281                                         {
282                                                 if ((err = ReadChunkBytes (iff, si->Sequence, si->Length * 2))
283                                                         != si->Length * 2)
284                                                         return err;
285                                         }
286                                         else return ERROR_NO_FREE_STORE;
287
288                                         break;
289
290                                 case ID_FORM:
291                                         if (cn->cn_Type == ID_PATT)
292                                         {
293                                                 if (!si->NumPatterns)
294                                                         xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
295                                                                 (APTR)MSG_READING_PATTS, NULL);
296
297                                                 if (xmDisplayProgress (si->NumPatterns, shdr.NumPatterns))
298                                                         return ERROR_BREAK;
299
300                                                 if (!LoadPattern (si, si->NumPatterns, iff))
301                                                         return IoErr();
302                                         }
303                                         else if (cn->cn_Type == ID_8SVX)
304                                         {
305                                                 if (!si->LastInstrument)
306                                                         xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
307                                                                 (APTR)MSG_READING_INSTS, NULL);
308
309                                                 if (xmDisplayProgress (si->LastInstrument, shdr.LastInstrument))
310                                                         return ERROR_BREAK;
311
312                                                 if (err = Load8SVXInstrument (si, 0, iff, NULL))
313                                                         return err;
314                                         }
315
316                                         break;
317
318                                 default:
319                                         break;
320                         }
321                 }
322         }
323
324         return err;
325 }
326
327
328
329 GLOBALCALL struct Pattern *LoadPattern (struct SongInfo *si, ULONG num, struct IFFHandle *iff)
330 {
331         LONG    err, tracksize;
332         struct Pattern          *patt = NULL;
333         struct ContextNode      *cn;
334         struct PatternHeader phdr;
335         UWORD   i;
336         BYTE    name[64];
337
338         static LONG stopchunks[] =
339         {
340                 ID_PATT, ID_NAME,
341                 ID_PATT, ID_PHDR,
342                 ID_PATT, ID_BODY
343         };
344
345         name[0] = '\0';
346
347         if (err = StopChunks (iff, stopchunks, 3))
348         {
349                 SetIoErr (err);
350                 return NULL;
351         }
352
353         if (err = StopOnExit (iff, ID_PATT, ID_FORM))
354         {
355                 SetIoErr (err);
356                 return NULL;
357         }
358
359         /* Scan Pattern */
360
361         while (1)
362         {
363                 if (err = ParseIFF (iff, IFFPARSE_SCAN))
364                 {
365                         if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
366                         break; /* Free resources & exit */
367                 }
368
369                 if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_PATT))
370                 {
371                         switch (cn->cn_ID)
372                         {
373                                 case ID_NAME:
374                                 {
375                                         ReadChunkBytes (iff, name, min(cn->cn_Size, 63));
376                                         name[min(cn->cn_Size, 63)] = '\0'; /* Ensure string termination */
377                                         break;
378                                 }
379
380                                 case ID_PHDR:
381                                         if ((err = ReadChunkBytes (iff, &phdr, sizeof (phdr))) != sizeof(phdr))
382                                         {
383                                                 SetIoErr (err);
384                                                 return NULL;
385                                         }
386
387                                         if (!(patt = xmAddPattern (si,
388                                                 PATTA_Lines,    phdr.Lines,
389                                                 PATTA_Tracks,   phdr.Tracks,
390                                                 PATTA_Num,              num,
391                                                 TAG_DONE)))
392                                         {
393                                                 SetIoErr (ERROR_NO_FREE_STORE);
394                                                 return NULL;
395                                         }
396
397                                         tracksize = phdr.Lines * sizeof (struct Note);
398
399                                         break;
400
401                                 case ID_BODY:
402                                 {
403                                         if (!patt)
404                                         {
405                                                 SetIoErr (IFFERR_SYNTAX);
406                                                 return NULL;
407                                         }
408
409                                         for (i = 0; i < patt->Tracks; i++)
410                                         {
411                                                 if ((err = ReadChunkBytes (iff, patt->Notes[i], tracksize)) != tracksize)
412                                                 {
413                                                         SetIoErr (err);
414                                                         return NULL;
415                                                 }
416                                         }
417
418                                         break;
419                                 }
420
421                                 default:
422                                         break;
423                         }
424                 }
425         }
426
427         if (name[0])
428                 xmSetPattern (si, si->NumPatterns - 1,
429                         PATTA_Name,     name,
430                         TAG_DONE);
431
432         if (!err && !patt)
433                 err = ERROR_OBJECT_NOT_FOUND;
434
435         if (err) SetIoErr (err);
436
437         return patt;
438 }
439
440
441
442 static HOOKCALL LONG SaveXModule (
443         REG(d0, BPTR fh),
444         REG(a0, struct SongInfo *si),
445         REG(a1, struct XMHook *saver),
446         REG(a2, ULONG *tags))
447 {
448         struct IFFHandle *iff;
449         LONG err;
450
451         if (iff = AllocIFF())
452         {
453                 iff->iff_Stream = (ULONG) fh;
454
455                 InitIFFasDOS (iff);
456
457                 if (!(err = OpenIFF (iff, IFFF_WRITE)))
458                 {
459
460                         /* Write XMOD */
461                         if (err = PushChunk (iff, ID_XMOD, ID_FORM, IFFSIZE_UNKNOWN))
462                                 goto error;
463
464                         /* Write module NAME */
465                         if (err = WriteStringChunk (iff, FilePart (si->Path), ID_NAME))
466                                 goto error;
467
468                         /* Write module ANNO */
469                         if (err = WriteStringChunk (iff, VERS, ID_ANNO))
470                                 goto error;
471
472                         /* Write Module Header (MHDR) */
473                         {
474                                 struct ModuleHeader mhdr;
475
476                                 mhdr.XModuleVersion = VERSION;
477                                 mhdr.XModuleRevision = REVISION;
478                                 mhdr.NumSongs   = 1;
479                                 mhdr.ActiveSong = 1;
480                                 mhdr.MasterVolume = 0xFFFF;
481                                 mhdr.MixingRate = 44100;
482
483                                 if (err = PushChunk (iff, ID_XMOD, ID_MHDR, sizeof (mhdr)))
484                                         goto error;
485                                 if ((err = WriteChunkBytes (iff, &mhdr, sizeof (mhdr))) != sizeof(mhdr))
486                                         goto error;
487                                 if (err = PopChunk (iff)) goto error;   /* Pop MHDR */
488                         }
489
490                         if (err = SaveSong (iff, si))
491                                 goto error;
492
493                         err = PopChunk (iff);           /* Pop FORM XMOD */
494
495 error:
496                         CloseIFF (iff);
497                 }
498                 else err = IFFERR_NOTIFF;
499
500                 FreeIFF (iff);
501         }
502         else err = ERROR_NO_FREE_STORE;
503
504         return (UWORD) err;
505 }
506
507
508
509 GLOBALCALL LONG SaveSong (struct IFFHandle *iff, struct SongInfo *si)
510 {
511         LONG err;
512
513         if (err = PushChunk (iff, ID_SONG, ID_FORM, IFFSIZE_UNKNOWN))
514                 return err;
515
516         /* Write Song Name */
517         WriteStringChunk (iff, si->Title, ID_NAME);
518
519         /* Write Author Name */
520         WriteStringChunk (iff, si->Author, ID_AUTH);
521
522         /* Write Annotations */
523         WriteStringChunk (iff, si->Description, ID_ANNO);
524
525         /* Write Song Header (SHDR) */
526         {
527                 struct SongHeader shdr;
528
529                 if (err = PushChunk  (iff, ID_SONG, ID_SHDR, IFFSIZE_UNKNOWN))
530                         return err;
531
532
533                 /* Set last changed date */
534                 {
535                         ULONG dummy;
536                         CurrentTime                     (&si->LastChanged, &dummy);
537                 }
538
539                 shdr.Length                     = si->Length;
540                 shdr.MaxTracks          = si->MaxTracks;
541                 shdr.NumPatterns        = si->NumPatterns;
542                 shdr.LastInstrument     = si->LastInstrument;
543                 shdr.GlobalSpeed        = si->GlobalSpeed;
544                 shdr.GlobalTempo        = si->GlobalTempo;
545                 shdr.RestartPos         = si->RestartPos;
546                 shdr.CurrentPatt        = si->CurrentPatt;
547                 shdr.CurrentLine        = si->CurrentLine;
548                 shdr.CurrentTrack       = si->CurrentTrack;
549                 shdr.CurrentPos         = si->CurrentPos;
550                 shdr.CurrentInst        = si->CurrentInst;
551                 shdr.DefNumTracks       = si->DefNumTracks;
552                 shdr.DefPattLen         = si->DefPattLen;
553                 shdr.TotalChanges       = si->TotalChanges + si->Changes;
554                 shdr.CreationDate       = si->CreationDate;
555                 shdr.LastChanged        = si->LastChanged;
556
557
558                 if ((err = WriteChunkBytes (iff, &shdr, sizeof (shdr))) != sizeof (shdr))
559                         return err;
560
561                 if (err = PopChunk (iff))       /* Pop SHDR */
562                         return err;
563         }
564
565
566         if (err = SaveSequence (iff, si))
567                 return err;
568         if (err = SavePatterns (iff, si))
569                 return err;
570         if (err = SaveInstruments (iff, si))
571                 return err;
572
573         err = PopChunk (iff);   /* Pop FORM SONG */
574
575         return err;
576 }
577
578
579
580 GLOBALCALL LONG SaveSequence (struct IFFHandle *iff, struct SongInfo *si)
581 {
582         LONG    err;
583
584         if (err = PushChunk (iff, 0, ID_SEQN, si->Length * 2))
585                 return err;
586
587         if ((err = WriteChunkBytes (iff, si->Sequence, si->Length * 2)) != si->Length * 2)
588                 return err;
589
590         err = PopChunk (iff);
591
592         return err;
593 }
594
595
596
597 GLOBALCALL LONG SavePatterns (struct IFFHandle *iff, struct SongInfo *si)
598 {
599         LONG    err;
600         ULONG   i;
601
602         xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
603                 (APTR)MSG_WRITING_PATTS, NULL);
604
605
606         for (i = 0; i < si->NumPatterns; i++)
607         {
608                 if (xmDisplayProgress (i, si->NumPatterns))
609                         return ERROR_BREAK;
610
611                 if (si->Patt[i])
612                         if (err = SavePattern (iff, si->Patt[i]))
613                                 return err;
614         }
615
616         return RETURN_OK;
617 }
618
619
620
621 GLOBALCALL LONG SaveInstruments (struct IFFHandle *iff, struct SongInfo *si)
622 {
623         LONG    err;
624         ULONG   i;
625
626         xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
627                 (APTR)MSG_WRITING_INSTS, NULL);
628
629         for (i = 1; i <= si->LastInstrument; i++)
630         {
631                 if (!(si->Instr[i])) continue;
632
633                 if (xmDisplayProgress (i - 1, si->LastInstrument))
634                         return ERROR_BREAK;
635
636                 if (err = Save8SVXInstrument (si->Instr[i], i, iff))
637                         return err;
638         }
639
640         return RETURN_OK;
641 }
642
643
644
645 GLOBALCALL LONG SavePattern (struct IFFHandle *iff, struct Pattern *patt)
646 {
647         LONG err;
648
649         if (err = PushChunk (iff, ID_PATT, ID_FORM, IFFSIZE_UNKNOWN))
650                 return err;
651
652         /* Write pattern NAME */
653
654         if (err = WriteStringChunk (iff, patt->Name, ID_NAME))
655                 return err;
656
657         /* Write pattern header (PHDR) */
658         {
659                 struct PatternHeader phdr;
660
661                 phdr.Lines = patt->Lines;
662                 phdr.Tracks = patt->Tracks;
663
664                 if (err = PushChunk (iff, 0, ID_PHDR, sizeof (struct PatternHeader)))
665                         return err;
666
667                 if ((err = WriteChunkBytes (iff, &phdr, sizeof (phdr))) != sizeof (phdr))
668                         return err;
669
670                 if (err = PopChunk (iff))       /* PHDR */
671                         return err;
672         }
673
674         /* Write pattern BODY */
675         {
676                 ULONG tracksize = sizeof (struct Note) * patt->Lines;
677                 ULONG i;
678
679                 if (err = PushChunk (iff, 0, ID_BODY, tracksize * patt->Tracks))
680                         return err;
681
682                 for (i = 0; i < patt->Tracks; i++)
683                 {
684                         if ((err = WriteChunkBytes (iff, patt->Notes[i], tracksize)) != tracksize)
685                                 return err;
686                 }
687
688                 if (err = PopChunk (iff))       /* BODY */
689                         return err;
690         }
691
692         err = PopChunk (iff);   /* PATT */
693
694         return err;
695 }
696
697
698
699 GLOBALCALL LONG WriteStringChunk (struct IFFHandle *iff, CONST_STRPTR str, ULONG id)
700 {
701         LONG err;
702         ULONG len;
703
704         if (!str || !str[0] || !SaveSwitches.SaveNames)
705                 return RETURN_OK;
706
707         if (err = PushChunk (iff, 0, id, len = strlen (str)))
708                 return err;
709
710         if ((err = WriteChunkBytes (iff, str, len)) != len)
711                 return err;
712
713         err = PopChunk (iff);
714
715         return err;
716 }