Initial commit.
[amiga/xmodule.git] / SongClass.c
1 /*
2 **      SongClass.c
3 **
4 **      Copyright (C) 1994,95,96,97 Bernardo Innocenti
5 **
6 **      Song class dispatcher and handling functions.
7 */
8
9 /****** songclass/--background-- *******************************************
10 *
11 *       NAME
12 *               songclass -- XModule 'boopsi'-oriented song implementation.
13 *
14 *       DESCRIPTION
15 *               The song class is an object oriented way to handle a song.  The song
16 *               class handles all data storing mechanisms for you and adds a layer
17 *               of abstraction between the song internal data structures and the
18 *               application.  The advantage is that the internal structures can be
19 *               changed while keeping compatibility with existing software.
20 *
21 *               Another great advantage of being a 'boopsi' class is that the song
22 *               can notify other boopsi objects whenever its attributes change.
23 *               This simplifies the task of keeping the user interface updated each
24 *               time the user (or an ARexx macro, or whatever) changes something.
25 *
26 *               For speed reasons, the song class does also allow 'white box'
27 *               istance access.  This means that you can also directly access
28 *               the internal data structures of the song, without using standard
29 *               boopsi methods.  You are ONLY allowed to READ public fields, but not
30 *               to write any of them.  The main reason to forbid direct writing is
31 *               that the song class must send notifications to its targets, but it
32 *               does also allow the song implementation to change in future version
33 *               without breaking existing applications.
34 *
35 *               When you create a new istance of the song class, the object handle
36 *               you get is actually a SongInfo structure.  This is only possible
37 *               because the song class is a subclass of the rootclass, whose istance
38 *               is placed at a negative offset in the object handle.
39 *               Future song class implementations could require to be subclasses
40 *               of other classes, such as the gadget class or even the datatypes
41 *               class.  This problem will be probably got around by keeping the
42 *               root class as the real superclass of the song and creating an
43 *               istance of the other superclass which will be passed all the
44 *               methods which are not recognized by the song its-self.  Call this
45 *               boopsi polymorphism, if you like to :-)
46 *
47 *       QUOTATION
48 *               Don't be a tuna head.
49 *
50 ****************************************************************************
51 */
52
53
54 #include <exec/memory.h>
55 #include <intuition/classes.h>
56 #include <intuition/classusr.h>
57 #include <libraries/xmodule.h>
58
59 #include <clib/alib_protos.h>
60 #include <proto/exec.h>
61 #include <proto/dos.h>
62 #include <proto/intuition.h>
63 #include <proto/utility.h>
64 #include <proto/icon.h>
65 #include <proto/xmodule.h>
66
67 #include "XModulePriv.h"
68 #include "Gui.h"
69
70
71
72 /* Local function prototypes */
73
74 static ULONG HOOKCALL SongDispatcher (
75         REG(a0, Class *cl),
76         REG(a2, struct SongInfo *si),
77         REG(a1, Msg msg));
78
79 INLINE ULONG                     SongNewMethod          (Class *cl, struct SongInfo *si, struct opSet *opset);
80 INLINE ULONG                     SongDisposeMethod      (Class *cl, struct SongInfo *si, Msg msg);
81 INLINE ULONG                     SongSetMethod          (Class *cl, struct SongInfo *si, struct opSet *opset);
82
83 static BOOL                                      GuessAuthor            (struct SongInfo *si);
84 static UWORD                            *SetSongLen                     (struct SongInfo *si, ULONG len);
85 static struct Pattern           *AddPattern                     (struct SongInfo *si, struct TagItem *tags);
86 static LONG                                      SetPattern                     (struct SongInfo *si, ULONG patnum, struct TagItem *tags);
87 static void                                      RemPattern                     (struct SongInfo *si, ULONG patnum, ULONG newpatt);
88 static struct Instrument        *AddInstrument          (struct SongInfo *si, ULONG num, struct TagItem *tags);
89 static LONG                                      SetInstrument          (struct SongInfo *si, ULONG num, struct TagItem *tags);
90 static void                                      RemInstrument          (struct SongInfo *si, ULONG num);
91 static void                                      SwapInstruments        (struct SongInfo *si, LONG n, LONG m);
92
93
94
95
96 GLOBALCALL Class *InitSongClass (void)
97 {
98         Class *class;
99
100         if (class = MakeClass (NULL, ROOTCLASS, NULL, sizeof (struct SongInfo), 0))
101                 class->cl_Dispatcher.h_Entry = (ULONG (*)()) SongDispatcher;
102
103         return class;
104 }
105
106
107
108 GLOBALCALL void FreeSongClass (Class *cl)
109 {
110         while (!(FreeClass (cl)))
111                 ShowRequest (MSG_CLOSE_ALL_SONGS, MSG_CONTINUE, NULL);
112 }
113
114
115
116 static ULONG HOOKCALL SongDispatcher (
117         REG(a0, Class *cl),
118         REG(a2, struct SongInfo *si),
119         REG(a1, Msg msg))
120 {
121         ULONG result = 0;
122
123         /* Warning: the song class can only be a subclass of the rootclass because
124          * its istance must be at offset 0 in the object handle.
125          */
126
127         switch (msg->MethodID)
128         {
129                 case OM_NEW:
130                         result = SongNewMethod (cl, si, (struct opSet *)msg);
131                         break;
132
133                 case OM_DISPOSE:
134                         result = SongDisposeMethod (cl, si, msg);
135                         break;
136
137                 case OM_SET:
138                 case OM_UPDATE:
139                         result = SongSetMethod (cl, si, (struct opSet *)msg);
140                         break;
141
142                 case OM_GET:
143                         break;
144
145                 case OM_NOTIFY:
146                         break;
147
148                 case SNGM_ADDPATTERN:
149                         result = (ULONG) AddPattern (si, ((struct opSet *)msg)->ops_AttrList);
150                         break;
151
152                 case SNGM_SETPATTERN:
153                         result = (ULONG) SetPattern (si, ((struct spSetPattern *)msg)->spsp_PattNum,
154                                 ((struct spSetPattern *)msg)->spsp_AttrList);
155
156                 case SNGM_REMPATTERN:
157                         RemPattern (si,
158                                 ((struct spRemPattern *)msg)->sprp_PattNum,
159                                 ((struct spRemPattern *)msg)->sprp_NewPatt);
160                         break;
161
162                 case SNGM_ADDINSTRUMENT:
163                         result = (ULONG) AddInstrument (si,
164                                 ((struct spAddInstrument *)msg)->spsi_InstrNum,
165                                 ((struct spAddInstrument *)msg)->spsi_AttrList);
166                         break;
167
168                 case SNGM_SETINSTRUMENT:
169                         result = (ULONG) SetInstrument (si,
170                                 ((struct spSetInstrument *)msg)->spsi_InstrNum,
171                                 ((struct spSetInstrument *)msg)->spsi_AttrList);
172                         break;
173
174                 case SNGM_REMINSTRUMENT:
175                         RemInstrument (si,
176                                 ((struct spRemInstrument *)msg)->spri_Num);
177                         break;
178
179                 case SNGM_SWAPINSTRUMENTS:
180                         SwapInstruments (si,
181                                 ((struct spSwapInstruments *)msg)->spsi_Num1,
182                                 ((struct spSwapInstruments *)msg)->spsi_Num2);
183                         break;
184
185                 default:
186
187                         /* Unsupported method: let our superclass take a look at it. */
188                         result = DoSuperMethodA (cl, (Object *)si, msg);
189                         break;
190         }
191
192         return result;
193 }
194
195
196
197 INLINE ULONG SongNewMethod (Class *cl, struct SongInfo *si, struct opSet *opset)
198
199 /* OM_NEW */
200 {
201         if (si = (struct SongInfo *)DoSuperMethodA (cl, (Object *)si, (Msg)opset))
202         {
203                 ULONG dummy;
204
205                 /* Clear object instance */
206                 memset (si, 0, sizeof (struct SongInfo));
207
208                 /* Initialize song instance to defaults values */
209                 si->Pool                        = XModuleBase->xm_Pool;
210                 si->DefNumTracks        = DEF_NUMTRACKS;
211                 si->DefPattLen          = DEF_PATTLEN;
212                 si->GlobalSpeed         = DEF_SONGSPEED;
213                 si->GlobalTempo         = DEF_SONGTEMPO;
214                 si->Instr                       = si->InstrumentsTable;
215                 DupStringPooled (si->Pool, STR(MSG_AUTHOR_UNKNOWN), &si->Author);
216                 DupStringPooled (si->Pool, STR(MSG_SONG_UNTITLED), &si->Title);
217                 si->Link.ln_Name        = si->Title;
218                 CurrentTime                     (&si->CreationDate, &dummy);
219                 InitSemaphore           (&si->Lock);
220
221
222                 if (opset->ops_AttrList)
223                 {
224                         /* Set generic attributes */
225
226                         SongSetMethod (cl, si, opset);
227                         si->Changes = 0;
228
229
230                         /* Set initialization-only attributes */
231
232                         if (GetTagData (SNGA_ReadyToUse, FALSE, opset->ops_AttrList))
233                         {
234                                 if (!DoMethod ((Object *)si, SNGM_ADDPATTERN, NULL))
235                                 {
236                                         DisposeObject (si);
237                                         return NULL;
238                                 }
239
240                                 /* Add one position to the new song */
241                                 SetAttrs (si, SNGA_Length, 1, TAG_DONE);
242
243                                 if (!si->Sequence)
244                                 {
245                                         DisposeObject (si);
246                                         return NULL;
247                                 }
248                         }
249                 }
250         }
251
252         return (ULONG)si;
253 }
254
255
256
257 INLINE ULONG SongDisposeMethod (Class *cl, struct SongInfo *si, Msg msg)
258
259 /* OM_DISPOSE */
260 {
261         LONG i;
262
263         /* All this stuff could be removed as all allocations are made
264          * inside a memory pool.
265          */
266
267         /* Remove song sequence */
268         SetAttrs (si, SNGA_Length, 0, TAG_DONE);
269
270         /* Free patterns */
271         for (i = si->NumPatterns - 1; i >= 0  ; i--)
272                 DoMethod ((Object *)si, SNGM_REMPATTERN, i, 0);
273
274         /* Free instruments */
275         for (i = 1 ; i <= si->LastInstrument ; i++)
276                 DoMethod ((Object *)si, SNGM_REMINSTRUMENT, i);
277
278         FreeVecPooled (si->Pool, si->Title);
279         FreeVecPooled (si->Pool, si->Author);
280         FreeVecPooled (si->Pool, si->Description);
281         FreeVecPooled (si->Pool, si->Path);
282
283         /* And let our superclass free the istance */
284         return DoSuperMethodA (cl, (Object *)si, msg);
285 }
286
287
288
289 INLINE ULONG SongSetMethod (Class *cl, struct SongInfo *si, struct opSet *opset)
290
291 /* OM_SET */
292 {
293         struct TagItem  *ti, *tstate = opset->ops_AttrList;
294         BOOL    do_supermethod  = FALSE;
295         ULONG   oldchanges              = si->Changes;
296
297
298         while (ti = NextTagItem(&tstate))
299         {
300                 switch (ti->ti_Tag)
301                 {
302                         case SNGA_CurrentPatt:
303                                 si->CurrentPatt = ti->ti_Data;
304                                 break;
305
306                         case SNGA_CurrentPos:
307                                 si->CurrentPos = ti->ti_Data;
308                                 break;
309
310                         case SNGA_CurrentInst:
311                                 si->CurrentInst = ti->ti_Data;
312                                 break;
313
314                         case SNGA_CurrentLine:
315                                 si->CurrentLine = ti->ti_Data;
316                                 break;
317
318                         case SNGA_CurrentTrack:
319                                 si->CurrentTrack = ti->ti_Data;
320                                 break;
321
322                         case SNGA_Length:
323                                 SetSongLen (si, ti->ti_Data);
324                                 break;
325
326                         case SNGA_Title:
327                                 if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Title))
328                                 {
329                                         if (si->Title)
330                                                 si->Link.ln_Name = si->Title;
331                                         else
332                                                 si->Link.ln_Name = STR(MSG_UNNAMED);
333                                         si->Changes++;
334                                 }
335                                 break;
336
337                         case SNGA_Author:
338                                 if (ti->ti_Data == -1)
339                                 {
340                                         if (GuessAuthor (si))
341                                                 si->Changes++;
342                                 }
343                                 else if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Author))
344                                         si->Changes++;
345                                 break;
346
347                         case SNGA_Description:
348                                 if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Description))
349                                         si->Changes++;
350                                 break;
351
352                         case SNGA_Path:
353                                 if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Path))
354                                         si->Changes++;
355                                 break;
356
357                         case SNGA_Changes:
358                                 if (ti->ti_Data == -1)
359                                         si->Changes++;
360                                 else
361                                         si->Changes = ti->ti_Data;
362                                 break;
363
364                         case SNGA_TotalChanges:
365                                 si->TotalChanges = ti->ti_Data;
366                                 break;
367
368                         case SNGA_CreationDate:
369                                 si->CreationDate = ti->ti_Data;
370                                 break;
371
372                         case SNGA_LastChanged:
373                                 si->LastChanged = ti->ti_Data;
374                                 break;
375
376                         case SNGA_DefaultTracks:
377                                 si->DefNumTracks = ti->ti_Data;
378                                 break;
379
380                         case SNGA_DefaultPattLen:
381                                 si->DefPattLen = ti->ti_Data;
382                                 break;
383
384                         case SNGA_GlobalSpeed:
385                         {
386                                 LONG speed = ti->ti_Data;
387
388                                 if (speed < 1) speed = 1;
389                                 if (speed > 31) speed = 31;
390
391                                 if (si->GlobalSpeed != speed)
392                                 {
393                                         si->GlobalSpeed = speed;
394                                         si->Changes++;
395                                 }
396                                 break;
397                         }
398
399                         case SNGA_GlobalTempo:
400                         {
401                                 LONG tempo = ti->ti_Data;
402
403                                 if (tempo > 255) tempo = 255;
404                                 if (tempo < 32) tempo = 32;
405
406                                 if (si->GlobalTempo != tempo)
407                                 {
408                                         si->GlobalTempo = tempo;
409                                         si->Changes++;
410                                 }
411                                 break;
412                         }
413
414                         case SNGA_RestartPos:
415                         {
416                                 LONG restart = ti->ti_Data;
417
418                                 if (restart < 0) restart = 0;
419                                 if (restart >= si->Length) restart = si->Length - 1;
420
421                                 if (si->RestartPos != restart)
422                                 {
423                                         si->RestartPos = restart;
424                                         si->Changes++;
425                                 }
426                                 break;
427                         }
428
429                         default:
430                                 do_supermethod = TRUE;
431                                 break;
432                 }
433         }       /* End while (NextTagItem()) */
434
435         if (do_supermethod)
436                 return (DoSuperMethodA (cl, (Object *)si, (Msg)opset));
437         else
438                 /* Returns the number of attributes which have changed */
439                 return si->Changes - oldchanges;
440 }
441
442
443
444 static UWORD *SetSongLen (struct SongInfo *si, ULONG len)
445 {
446         ULONG len_quantized;
447
448         if (len == si->Length)
449                 return si->Sequence;
450
451         si->Changes++;
452
453         if (len == 0)
454         {
455                 /* Deallocate sequence */
456
457                 FreeVecPooled (si->Pool, si->Sequence);
458                 si->Sequence = NULL;
459                 si->Length = 0;
460                 return NULL;
461         }
462
463         /* Check for too many song positions */
464
465         if (len > MAXPOSITIONS)
466                 return NULL;
467
468
469         len_quantized = (len + SEQUENCE_QUANTUM - 1) & ~(SEQUENCE_QUANTUM - 1);
470
471
472         if (!si->Sequence)
473         {
474                 /* Create a new sequence table */
475
476                 if (si->Sequence = AllocVecPooled (si->Pool,
477                         len_quantized * sizeof (UWORD)))
478                 {
479                         si->Length = len;
480                         memset (si->Sequence, 0, len * sizeof (UWORD)); /* Clear sequence table */
481                 }
482         }
483         else if (si->Length > len_quantized)
484         {
485                 UWORD *newseq;
486
487                 /* Shrink sequence table */
488
489                 si->Length = len;
490
491                 if (newseq = AllocVecPooled (si->Pool, len_quantized * sizeof (UWORD)))
492                 {
493                         CopyMem (si->Sequence, newseq, len * sizeof (UWORD));
494                         FreeVecPooled (si->Pool, si->Sequence);
495                         si->Sequence = newseq;
496                 }
497                 /* If the previous allocation failed we ignore it and continue
498                  * without shrinking the sequence table.
499                  */
500         }
501         else if (si->Length <= len_quantized - SEQUENCE_QUANTUM)
502         {
503                 UWORD *newseq;
504
505                 /* Expand the sequence table */
506
507                 if (!(newseq = AllocVecPooled (si->Pool, len_quantized * sizeof (UWORD))))
508                         return NULL;
509
510                 /* Now replace the the old sequence with the new and delete the old one */
511
512                 CopyMem (si->Sequence, newseq, si->Length * sizeof (UWORD));
513                 FreeVecPooled (si->Pool, si->Sequence);
514
515                 /* Clear the new sequence table entries */
516                 memset (newseq + si->Length, 0, (len - si->Length) * sizeof (UWORD));
517                 si->Length = len;
518                 si->Sequence = newseq;
519         }
520         else
521         {
522                 /* No reallocation */
523
524                 if (si->Length > len)
525                         /* Clear the new sequence table entries */
526                         memset (si->Sequence + si->Length, 0, (si->Length - len) * sizeof (UWORD));
527
528                 si->Length = len;
529         }
530
531         if (si->CurrentPos >= si->Length)
532                 si->CurrentPos = si->Length - 1;
533
534         return si->Sequence;
535 }
536
537
538
539 static struct Pattern *AddPattern (struct SongInfo *si, struct TagItem *tags)
540
541 /* SNGM_ADDPATTERN
542  *
543  * Allocates a pattern structure and adds it to the the passed song.
544  * The tracks are also allocated and attached to the pattern structure.
545  *
546  * PATTA_Num - When this attribute is passed, the pattern will be inserted
547  * before the existing pattern <patnum>.  Patterns >= <patnum> will be moved
548  * ahead one slot.  The position table is updated inserting references to
549  * the new pattern immediately before each occurence of patnum, so that the
550  * two patterns are allways played together.
551  * If patnum is -1, the pattern will be allocated but NOT inserted in the song.
552  *
553  * PATTA_Replace - If TRUE, the existing pattern <patnum> will be replaced by
554  *      the new one.  The other pattern will be freed after the new one is
555  *      allocated.
556  *
557  * PATTA_Pattern - specifies an already allocated pattern structure to be
558  *      used instead of allocating a new one.  Adding patterns allocated
559  *      by another istance of the song class is illegal.
560  *
561  * PATTA_Lines - Number of lines in each track.  If lines is 0, track data
562  *      is not allocated.
563  *
564  * RETURNS
565  *    Pointer to the newly allocated pattern structure, NULL for failure.
566  */
567 {
568         struct Pattern *patt;
569         ULONG numpatt_quantized;
570         ULONG i;
571         ULONG tracks, lines, patnum, replace;
572
573         tracks  = GetTagData (PATTA_Tracks,             si->DefNumTracks, tags);
574         lines   = GetTagData (PATTA_Lines,              si->DefPattLen, tags);
575         patnum  = GetTagData (PATTA_Num,                si->NumPatterns, tags);
576         replace = GetTagData (PATTA_Replace,    FALSE, tags);
577         patt    = (struct Pattern *)GetTagData (PATTA_Pattern,  NULL, tags);
578
579         if (!tracks || (si->NumPatterns == MAXPATTERNS))
580                 return NULL;
581
582
583         /* Round up to an even number of quantums */
584         numpatt_quantized = (si->NumPatterns + PATTERNS_QUANTUM - 1) & ~(PATTERNS_QUANTUM - 1);
585
586         if (!si->Patt)
587         {
588                 /* Create a new pattern table */
589
590                 si->Patt = AllocVecPooled (si->Pool,
591                         PATTERNS_QUANTUM * sizeof (struct Pattern *));
592         }
593         else if (si->NumPatterns + 1 >= numpatt_quantized)
594         {
595                 struct Pattern **newpatt;
596
597                 /* Expand patterns table */
598
599                 if (newpatt = AllocVecPooled (si->Pool,
600                         (numpatt_quantized + PATTERNS_QUANTUM) * sizeof (struct Pattern *)))
601                 {
602                         CopyMem (si->Patt, newpatt, si->NumPatterns * sizeof (struct Pattern *));
603                         FreeVecPooled (si->Pool, si->Patt);
604                         si->Patt = newpatt;
605                 }
606                 else return NULL;
607         }
608
609
610         /* Pattern allocation */
611
612         if (!patt)
613         {
614                 if (patt = CAllocPooled (si->Pool, sizeof (struct Pattern) +
615                         tracks * sizeof (struct Note *)))
616                 {
617                         patt->Tracks    = tracks;
618                         patt->Lines             = lines;
619
620                         if (lines)
621                                 for (i = 0 ; i < tracks ; i++)
622                                 {
623                                         if (!(patt->Notes[i] = (struct Note *)
624                                                 CAllocPooled (si->Pool, sizeof (struct Note) * lines)))
625                                         {
626                                                 ULONG j;
627
628                                                 for (j = 0 ; j < tracks ; j++)
629                                                         FreePooled (si->Pool, patt->Notes[i], lines * sizeof (struct Note));
630
631                                                 FreePooled (si->Pool, patt,  sizeof (struct Pattern) +
632                                                         tracks * sizeof (struct Note *));
633                                                 return NULL;
634                                         }
635                                 }
636
637                         DupStringPooled (si->Pool, (STRPTR)GetTagData (PATTA_Name, NULL, tags), &patt->Name);
638                 }
639         }
640
641         if (patt)
642         {
643                 if (patnum != -1)
644                 {
645                         if (replace)    /* Free old pattern */
646                         {
647                                 ULONG j;
648
649                                 for (j = 0 ; j < si->Patt[patnum]->Tracks ; j++)
650                                         FreePooled (si->Pool, si->Patt[patnum]->Notes[j],
651                                                 si->Patt[patnum]->Lines * sizeof (struct Note));
652
653                                 FreeVecPooled (si->Pool, si->Patt[patnum]->Name);
654                                 FreePooled (si->Pool, si->Patt[patnum],  sizeof (struct Pattern) +
655                                         patt->Tracks * sizeof (struct Note *));
656                         }
657
658                         si->Patt[patnum] = patt;
659
660                         if (!replace)
661                         {
662                                 if (patnum < si->NumPatterns)
663                                 {
664                                         /* Shift subsequent patterns one position ahead */
665                                         for (i = si->NumPatterns ; i > patnum ; i--)
666                                                 si->Patt[i] = si->Patt[i-1];
667                                 }
668
669                                 si->NumPatterns++;
670                         }
671
672                         si->Changes++;
673                 }
674
675                 if (tracks > si->MaxTracks)
676                         si->MaxTracks = tracks;
677         }
678
679         return patt;
680 }
681
682
683
684 static LONG SetPattern (struct SongInfo *si, ULONG num, struct TagItem *tags)
685
686 /* SNGM_SETPATTERN */
687 {
688         struct TagItem *ti;
689         struct Pattern *patt;
690
691         if (patt = si->Patt[num])
692                 if (ti = FindTagItem (PATTA_Name, tags))
693                 {
694                         DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Patt[num]->Name);
695                         si->Changes++;
696                         return 1;
697                 }
698
699         return 0;
700 }
701
702
703
704 static void RemPattern (struct SongInfo *si, ULONG patnum, ULONG newpatt)
705
706 /* SNGM_REMPATTERN
707  *
708  * Remove a pattern from a song.  All patterns >= <patnum> will be moved
709  * back one slot.  The position table is updated replacing each
710  * occurence of <patnum> with pattern <newpatt>.  If <patnum> is -1,
711  * newpatt will be used as a pointer to a pattern structure which has been
712  * created by the song, but has not been added to the pattern table.  This
713  * is useful for freeing patterns created with num = -1 in the
714  * SNGM_ADDPATTERN method.
715  */
716 {
717         struct Pattern *patt;
718         ULONG i;
719
720         if (patnum == -1)
721         {
722                 patt = (struct Pattern *)newpatt;
723                 newpatt = - 1;
724         }
725         else
726                 patt = si->Patt[patnum];
727
728         if (patt)
729         {
730                 ULONG j;
731
732                 if (patnum < si->NumPatterns)
733                         si->Patt[patnum] = NULL;
734
735                 for (j = 0 ; j < patt->Tracks ; j++)
736                         FreePooled (si->Pool, patt->Notes[j], patt->Lines * sizeof (struct Note));
737
738                 FreeVecPooled (si->Pool, patt->Name);
739                 FreePooled (si->Pool, patt,  sizeof (struct Pattern) +
740                         patt->Tracks * sizeof (struct Note *));
741         }
742
743         if (patnum < si->NumPatterns)
744         {
745                 /* Scroll subsequent patterns */
746                 for (i = patnum; i < si->NumPatterns; i++)
747                         si->Patt[i] = si->Patt[i+1];
748         }
749
750         if (si->Sequence)
751         {
752                 /* Adjust position table */
753
754                 for (i = 0 ; i < si->Length ; i++)
755                 {
756                         /* Substitute references to the old pattern in the position table */
757                         if (si->Sequence[i] == patnum) si->Sequence[i] = newpatt;
758
759                         /* Fix pattern numbers */
760                         if (si->Sequence[i] > patnum) si->Sequence[i]--;
761                 }
762         }
763
764         if (patnum != -1)
765         {
766                 si->Changes++;
767                 si->NumPatterns--;
768
769                 if (si->NumPatterns)
770                 {
771                         if (si->CurrentPatt >= si->NumPatterns)
772                                 si->CurrentPatt = si->NumPatterns - 1;
773                 }
774                 else
775                 {
776                         FreeVecPooled (si->Pool, si->Patt);
777                         si->Patt = NULL;
778                 }
779         }
780 }
781
782
783
784 static struct Instrument *AddInstrument (struct SongInfo *si, ULONG num, struct TagItem *tags)
785
786 /* SNGM_ADDINSTRUMENT
787  *
788  * Allocates an instrument structure and adds it to the the passed song.
789  * It will also free the old instrument if there is one already.
790  * If <num> is -1, it will load the instrument in the slot right after
791  * the last instrument.  If <num> is 0, it will load the instrument in
792  * the first free slot.
793  *
794  * RETURNS
795  *    Pointer to the newly allocated instrument structure, NULL for failure.
796  */
797 {
798         struct Instrument       *instr;
799
800         if (num == -1)
801                 num = si->LastInstrument + 1;
802
803         if (num == 0)
804         {
805                 for (num = 1; num <= si->LastInstrument; num++)
806                         if (!si->Instr[num]) break;
807
808                 if (num == MAXINSTRUMENTS) return NULL;
809         }
810
811
812         if (num > MAXINSTRUMENTS)
813                 return NULL;
814
815         if (si->Instr[num])
816                 RemInstrument (si, num);
817
818         if (!(instr = CAllocPooled (si->Pool, sizeof (struct Instrument))))
819                 return NULL;
820
821         si->Instr[num] = instr;
822         if (num > si->LastInstrument)
823                 si->LastInstrument = num;
824
825         SetInstrument (si, num, tags);
826
827         return instr;
828 }
829
830
831
832 static void SwapInstruments (struct SongInfo *si, LONG n, LONG m)
833
834 /* SNGM_SWAPINSTRUMENTS
835  *
836  * Swaps two instruments and updates the LastInstrument field if required.
837  *
838  * RETURNS
839  *    Pointer to the newly allocated instrument structure, NULL for failure.
840  */
841 {
842         struct Instrument *tmp;
843
844         if (n == m) return;
845
846         tmp = si->Instr[n];
847         si->Instr[n] = si->Instr[m];
848         si->Instr[m] = tmp;
849
850         n = max (n,m);
851
852         if (n >= si->LastInstrument)
853         {
854                 for ( ; n >= 0; n--)
855                         if (si->Instr[n]) break;
856
857                 DB (kprintf ("n = %ld\n", n));
858
859                 si->LastInstrument = n;
860         }
861 }
862
863
864
865 static LONG SetInstrument (struct SongInfo *si, ULONG num, struct TagItem *tags)
866
867 /* SNGM_SETINSTRUMENT */
868 {
869         struct Instrument       *instr;
870         struct TagItem          *ti;
871
872         if ((num <= si->LastInstrument) && (instr = si->Instr[num]))
873         {
874                 while (ti = NextTagItem (&tags))
875                         switch (ti->ti_Tag)
876                         {
877                                 case INSTRA_Type:
878                                         instr->Type             = ti->ti_Data;
879                                         break;
880
881                                 case INSTRA_Name:
882                                         DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &instr->Name);
883                                         break;
884
885                                 case INSTRA_Volume:
886                                         instr->Volume   = ti->ti_Data;
887                                         break;
888
889                                 case INSTRA_Sample:
890                                         instr->Sample   = (BYTE *) ti->ti_Data;
891                                         break;
892
893                                 case INSTRA_Length:
894                                         instr->Length   = ti->ti_Data;
895                                         break;
896
897                                 case INSTRA_Flags:
898                                         instr->Flags    = ti->ti_Data;
899                                         break;
900
901                                 case INSTRA_LoopStart:
902                                         instr->Repeat   = ti->ti_Data;
903                                         break;
904
905                                 case INSTRA_LoopLen:
906                                         instr->Replen   = ti->ti_Data;
907                                         break;
908
909                                 case INSTRA_LoopEnd:
910                                         instr->Replen   = ti->ti_Data - instr->Repeat - 2;
911                                         break;
912
913                                 case INSTRA_FineTune:
914                                         instr->FineTune = ti->ti_Data;
915                                         /* Note: I'm falling through here! */
916
917                                 default:
918                                         break;
919                         }
920                 /* End while (NextTagItem()) */
921
922                 si->Changes++;
923                 return 1;
924         }
925
926         return 0;
927 }
928
929
930 static void RemInstrument (struct SongInfo *si, ULONG num)
931
932 /* SNGM_REMINSTRUMENT */
933 {
934         if ((num <= si->LastInstrument) && (si->Instr[num]))
935         {
936                 FreeVec                 (si->Instr[num]->Sample);
937                 FreeVecPooled   (si->Pool, si->Instr[num]->Name);
938                 FreePooled              (si->Pool, si->Instr[num], sizeof (struct Instrument));
939                 si->Instr[num] = NULL;
940                 si->Changes++;
941         }
942
943         if (num == si->LastInstrument)
944         {
945                 ULONG i;
946
947                 for (i = num - 1; i > 0 ; i--)
948                         if (si->Instr[i])
949                                 break;
950
951                 si->LastInstrument = i;
952         }
953 }
954
955
956
957 #if 0 /* --- This part has been removed --- */
958
959 static WORD ModType (BPTR fh)
960
961 /* Guess source module type */
962 {
963         UBYTE __aligned str[0x30];
964         WORD trackertype;
965
966         if (Read (fh, str, 0x30) != 0x30)
967                 return -1;
968
969         /* Check XModule */
970         if (!(strncmp (str, "FORM", 4) || strncmp (str+8, "XMOD", 4)))
971                 return FMT_XMODULE;
972
973         /* Check MED */
974         if (!(strncmp (str, "MMD", 3)))
975                 return FMT_MED;
976
977         /* Check Oktalyzer */
978         if (!(strncmp (str, "OKTASONG", 8)))
979                 return FMT_OKTALYZER;
980
981         if (!(strncmp (&str[0x2C], "SCRM", 4)))
982                 return FMT_SCREAMTRACKER;
983
984         if (!(strncmp (str, "Extended module: ", 17)))
985                 return FMT_FASTTRACKER2;
986
987         /* Check #?Tracker */
988         if ((trackertype = IsTracker (fh)) != FMT_UNKNOWN)
989                 return trackertype;
990
991         switch (ShowRequestArgs (MSG_UNKNOWN_MOD_FORMAT, MSG_SOUND_PRO_CANCEL, NULL))
992         {
993                 case 1:
994                         return FMT_STRACKER;
995                         break;
996
997                 case 2:
998                         return FMT_PTRACKER;
999                         break;
1000
1001                 default:
1002                         break;
1003         }
1004
1005         return FMT_UNKNOWN;
1006 }
1007 #endif /* --- This part has been removed --- */
1008
1009
1010
1011 #define tolower(c) ((c) | (1<<5))
1012
1013 static BOOL GuessAuthor (struct SongInfo *si)
1014
1015 /* Tries to find the author of the song by looking up the instrument
1016  * names.
1017  */
1018 {
1019         UBYTE *name;
1020         ULONG i, j;
1021
1022         if (!si->Instr) return FALSE;
1023
1024         for (i = 1; i <= si->LastInstrument; i++)
1025         {
1026                 if (!si->Instr[i]) continue;
1027
1028                 name = si->Instr[i]->Name;
1029
1030                 /* Check for IntuiTracker-style embedded author name */
1031                 if (name[0] == '#')
1032                 {
1033                         for (j = 1; name[j] == ' '; j++); /* Skip extra blanks */
1034
1035                         /* Skip "by " */
1036                         if ((tolower(name[j]) == 'b') && (tolower(name[j+1]) == 'y') && name[j+2] == ' ')
1037                                 j += 3;
1038
1039                         for (; name[j] == ' '; j++); /* Skip extra blanks */
1040
1041                         if (name[j])
1042                         {
1043                                 SetAttrs (si, SNGA_Author, &(name[j]), TAG_DONE);
1044                                 return TRUE; /* Stop looking for author */
1045                         }
1046                 }
1047
1048                 /* Now look for the occurence of "by ", "by:", "by\0" or "(c)".  Ignore case. */
1049                 for (j = 0; name[j]; j++)
1050                 {
1051                         if (( (tolower(name[j]) == 'b') && (tolower(name[j+1]) == 'y') &&
1052                                 ((name[j+2] == ' ') || (name[j+2] == ':') || (name[j+2] == '\0')) ) ||
1053                                 ((name[j] == '(') && (tolower(name[j+1]) == 'c') && (name[j+2] == ')') ))
1054                         {
1055                                 j+=3;   /* Skip 'by ' */
1056
1057                                 /* Skip extra blanks/punctuations */
1058                                 while (name[j] == ' ' || name[j] == ':' || name[j] == '.') j++;
1059
1060                                 if (name[j]) /* Check if the end is reached */
1061                                         /* The name of the author comes (hopefully) right after 'by ' */
1062                                         SetAttrs (si, SNGA_Author, &(name[j]), TAG_DONE);
1063                                 else
1064                                         /* The name of the author is stored in the next instrument */
1065                                         if (i < si->LastInstrument - 1)
1066                                         SetAttrs (si, SNGA_Author, si->Instr[i+1]->Name, TAG_DONE);
1067
1068                                 return TRUE; /* Stop loop */
1069                         }
1070                 }
1071         }
1072
1073         return FALSE;
1074 }
1075
1076
1077
1078 GLOBALCALL ULONG CalcInstSize (struct SongInfo *si)
1079
1080 /* Calculate total size of all instruments in a song. */
1081 {
1082         ULONG i;
1083         ULONG size = 0;
1084
1085         for (i = 1; i < si->LastInstrument; i++)
1086         {
1087                 if (!si->Instr[i]) continue;
1088                         size += si->Instr[i]->Length + sizeof (struct Instrument);
1089         }
1090
1091         return size;
1092 }
1093
1094
1095
1096 GLOBALCALL ULONG CalcSongSize (struct SongInfo *si)
1097
1098 /* Calculate total size of a song */
1099 {
1100         ULONG i;
1101         struct Pattern *patt;
1102         ULONG size = sizeof (struct SongInfo) + CalcInstSize (si);
1103
1104         /* Calculate total patterns size */
1105
1106         for ( i = 0; i < si->NumPatterns; i++)
1107         {
1108                 if (patt = si->Patt[i])
1109                         size += patt->Lines * patt->Tracks * sizeof (struct Note) + sizeof (struct Pattern);
1110         }
1111
1112         return size;
1113 }
1114
1115
1116
1117 GLOBALCALL ULONG CalcSongTime (struct SongInfo *si)
1118
1119 /* Calculate song length in seconds
1120  *
1121  * One note lasts speed/50 seconds at 125bpm.
1122  */
1123 {
1124         ULONG i, j, k;
1125         struct Pattern *patt;
1126         struct Note *note;
1127         ULONG speed = si->GlobalSpeed,
1128 //              tempo = si->GlobalTempo,
1129                 ticks = speed * 20,
1130                 millisecs = 0;
1131
1132
1133         for (i = 0; i < si->Length; i++)
1134         {
1135                 patt = si->Patt[si->Sequence[i]];
1136
1137                 for (j = 0; j < patt->Lines; j++)
1138                         for (k = 0; k < patt->Tracks; k++)
1139                         {
1140                                 note = &patt->Notes[k][j];
1141
1142                                 switch (note->EffNum)
1143                                 {
1144                                         case EFF_POSJUMP:
1145                                                 if (note->EffVal > i)
1146                                                 {
1147                                                         i = note->EffVal;
1148                                                         j = patt->Lines;
1149                                                         k = patt->Tracks;
1150                                                 }
1151                                                 else return millisecs;
1152                                                 break;
1153
1154                                         case EFF_SETSPEED:
1155                                                 /* At speed 1, one line lasts one VBlank,
1156                                                  * that is 20 milliseconds.
1157                                                  */
1158                                                 ticks = note->EffVal * 20;
1159                                                 break;
1160
1161                                         case EFF_PATTERNBREAK:
1162                                                 j = patt->Lines;
1163                                                 k = patt->Tracks;
1164                                                 break;
1165
1166                                         /* case EFF_MISC: Loop */
1167                                 }
1168
1169                                 millisecs += ticks;
1170                         }
1171         }
1172
1173         return millisecs;
1174 }