4 ** Copyright (C) 1994,95,96,97 Bernardo Innocenti
6 ** Song class dispatcher and handling functions.
9 /****** songclass/--background-- *******************************************
12 * songclass -- XModule 'boopsi'-oriented song implementation.
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.
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.
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.
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 :-)
48 * Don't be a tuna head.
50 ****************************************************************************
54 #include <exec/memory.h>
55 #include <intuition/classes.h>
56 #include <intuition/classusr.h>
57 #include <libraries/xmodule.h>
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>
67 #include "XModulePriv.h"
72 /* Local function prototypes */
74 static ULONG HOOKCALL SongDispatcher (
76 REG(a2, struct SongInfo *si),
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);
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);
96 GLOBALCALL Class *InitSongClass (void)
100 if (class = MakeClass (NULL, ROOTCLASS, NULL, sizeof (struct SongInfo), 0))
101 class->cl_Dispatcher.h_Entry = (ULONG (*)()) SongDispatcher;
108 GLOBALCALL void FreeSongClass (Class *cl)
110 while (!(FreeClass (cl)))
111 ShowRequest (MSG_CLOSE_ALL_SONGS, MSG_CONTINUE, NULL);
116 static ULONG HOOKCALL SongDispatcher (
118 REG(a2, struct SongInfo *si),
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.
127 switch (msg->MethodID)
130 result = SongNewMethod (cl, si, (struct opSet *)msg);
134 result = SongDisposeMethod (cl, si, msg);
139 result = SongSetMethod (cl, si, (struct opSet *)msg);
148 case SNGM_ADDPATTERN:
149 result = (ULONG) AddPattern (si, ((struct opSet *)msg)->ops_AttrList);
152 case SNGM_SETPATTERN:
153 result = (ULONG) SetPattern (si, ((struct spSetPattern *)msg)->spsp_PattNum,
154 ((struct spSetPattern *)msg)->spsp_AttrList);
156 case SNGM_REMPATTERN:
158 ((struct spRemPattern *)msg)->sprp_PattNum,
159 ((struct spRemPattern *)msg)->sprp_NewPatt);
162 case SNGM_ADDINSTRUMENT:
163 result = (ULONG) AddInstrument (si,
164 ((struct spAddInstrument *)msg)->spsi_InstrNum,
165 ((struct spAddInstrument *)msg)->spsi_AttrList);
168 case SNGM_SETINSTRUMENT:
169 result = (ULONG) SetInstrument (si,
170 ((struct spSetInstrument *)msg)->spsi_InstrNum,
171 ((struct spSetInstrument *)msg)->spsi_AttrList);
174 case SNGM_REMINSTRUMENT:
176 ((struct spRemInstrument *)msg)->spri_Num);
179 case SNGM_SWAPINSTRUMENTS:
181 ((struct spSwapInstruments *)msg)->spsi_Num1,
182 ((struct spSwapInstruments *)msg)->spsi_Num2);
187 /* Unsupported method: let our superclass take a look at it. */
188 result = DoSuperMethodA (cl, (Object *)si, msg);
197 INLINE ULONG SongNewMethod (Class *cl, struct SongInfo *si, struct opSet *opset)
201 if (si = (struct SongInfo *)DoSuperMethodA (cl, (Object *)si, (Msg)opset))
205 /* Clear object instance */
206 memset (si, 0, sizeof (struct SongInfo));
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);
222 if (opset->ops_AttrList)
224 /* Set generic attributes */
226 SongSetMethod (cl, si, opset);
230 /* Set initialization-only attributes */
232 if (GetTagData (SNGA_ReadyToUse, FALSE, opset->ops_AttrList))
234 if (!DoMethod ((Object *)si, SNGM_ADDPATTERN, NULL))
240 /* Add one position to the new song */
241 SetAttrs (si, SNGA_Length, 1, TAG_DONE);
257 INLINE ULONG SongDisposeMethod (Class *cl, struct SongInfo *si, Msg msg)
263 /* All this stuff could be removed as all allocations are made
264 * inside a memory pool.
267 /* Remove song sequence */
268 SetAttrs (si, SNGA_Length, 0, TAG_DONE);
271 for (i = si->NumPatterns - 1; i >= 0 ; i--)
272 DoMethod ((Object *)si, SNGM_REMPATTERN, i, 0);
274 /* Free instruments */
275 for (i = 1 ; i <= si->LastInstrument ; i++)
276 DoMethod ((Object *)si, SNGM_REMINSTRUMENT, i);
278 FreeVecPooled (si->Pool, si->Title);
279 FreeVecPooled (si->Pool, si->Author);
280 FreeVecPooled (si->Pool, si->Description);
281 FreeVecPooled (si->Pool, si->Path);
283 /* And let our superclass free the istance */
284 return DoSuperMethodA (cl, (Object *)si, msg);
289 INLINE ULONG SongSetMethod (Class *cl, struct SongInfo *si, struct opSet *opset)
293 struct TagItem *ti, *tstate = opset->ops_AttrList;
294 BOOL do_supermethod = FALSE;
295 ULONG oldchanges = si->Changes;
298 while (ti = NextTagItem(&tstate))
302 case SNGA_CurrentPatt:
303 si->CurrentPatt = ti->ti_Data;
306 case SNGA_CurrentPos:
307 si->CurrentPos = ti->ti_Data;
310 case SNGA_CurrentInst:
311 si->CurrentInst = ti->ti_Data;
314 case SNGA_CurrentLine:
315 si->CurrentLine = ti->ti_Data;
318 case SNGA_CurrentTrack:
319 si->CurrentTrack = ti->ti_Data;
323 SetSongLen (si, ti->ti_Data);
327 if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Title))
330 si->Link.ln_Name = si->Title;
332 si->Link.ln_Name = STR(MSG_UNNAMED);
338 if (ti->ti_Data == -1)
340 if (GuessAuthor (si))
343 else if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Author))
347 case SNGA_Description:
348 if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Description))
353 if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Path))
358 if (ti->ti_Data == -1)
361 si->Changes = ti->ti_Data;
364 case SNGA_TotalChanges:
365 si->TotalChanges = ti->ti_Data;
368 case SNGA_CreationDate:
369 si->CreationDate = ti->ti_Data;
372 case SNGA_LastChanged:
373 si->LastChanged = ti->ti_Data;
376 case SNGA_DefaultTracks:
377 si->DefNumTracks = ti->ti_Data;
380 case SNGA_DefaultPattLen:
381 si->DefPattLen = ti->ti_Data;
384 case SNGA_GlobalSpeed:
386 LONG speed = ti->ti_Data;
388 if (speed < 1) speed = 1;
389 if (speed > 31) speed = 31;
391 if (si->GlobalSpeed != speed)
393 si->GlobalSpeed = speed;
399 case SNGA_GlobalTempo:
401 LONG tempo = ti->ti_Data;
403 if (tempo > 255) tempo = 255;
404 if (tempo < 32) tempo = 32;
406 if (si->GlobalTempo != tempo)
408 si->GlobalTempo = tempo;
414 case SNGA_RestartPos:
416 LONG restart = ti->ti_Data;
418 if (restart < 0) restart = 0;
419 if (restart >= si->Length) restart = si->Length - 1;
421 if (si->RestartPos != restart)
423 si->RestartPos = restart;
430 do_supermethod = TRUE;
433 } /* End while (NextTagItem()) */
436 return (DoSuperMethodA (cl, (Object *)si, (Msg)opset));
438 /* Returns the number of attributes which have changed */
439 return si->Changes - oldchanges;
444 static UWORD *SetSongLen (struct SongInfo *si, ULONG len)
448 if (len == si->Length)
455 /* Deallocate sequence */
457 FreeVecPooled (si->Pool, si->Sequence);
463 /* Check for too many song positions */
465 if (len > MAXPOSITIONS)
469 len_quantized = (len + SEQUENCE_QUANTUM - 1) & ~(SEQUENCE_QUANTUM - 1);
474 /* Create a new sequence table */
476 if (si->Sequence = AllocVecPooled (si->Pool,
477 len_quantized * sizeof (UWORD)))
480 memset (si->Sequence, 0, len * sizeof (UWORD)); /* Clear sequence table */
483 else if (si->Length > len_quantized)
487 /* Shrink sequence table */
491 if (newseq = AllocVecPooled (si->Pool, len_quantized * sizeof (UWORD)))
493 CopyMem (si->Sequence, newseq, len * sizeof (UWORD));
494 FreeVecPooled (si->Pool, si->Sequence);
495 si->Sequence = newseq;
497 /* If the previous allocation failed we ignore it and continue
498 * without shrinking the sequence table.
501 else if (si->Length <= len_quantized - SEQUENCE_QUANTUM)
505 /* Expand the sequence table */
507 if (!(newseq = AllocVecPooled (si->Pool, len_quantized * sizeof (UWORD))))
510 /* Now replace the the old sequence with the new and delete the old one */
512 CopyMem (si->Sequence, newseq, si->Length * sizeof (UWORD));
513 FreeVecPooled (si->Pool, si->Sequence);
515 /* Clear the new sequence table entries */
516 memset (newseq + si->Length, 0, (len - si->Length) * sizeof (UWORD));
518 si->Sequence = newseq;
522 /* No reallocation */
524 if (si->Length > len)
525 /* Clear the new sequence table entries */
526 memset (si->Sequence + si->Length, 0, (si->Length - len) * sizeof (UWORD));
531 if (si->CurrentPos >= si->Length)
532 si->CurrentPos = si->Length - 1;
539 static struct Pattern *AddPattern (struct SongInfo *si, struct TagItem *tags)
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.
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.
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
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.
561 * PATTA_Lines - Number of lines in each track. If lines is 0, track data
565 * Pointer to the newly allocated pattern structure, NULL for failure.
568 struct Pattern *patt;
569 ULONG numpatt_quantized;
571 ULONG tracks, lines, patnum, replace;
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);
579 if (!tracks || (si->NumPatterns == MAXPATTERNS))
583 /* Round up to an even number of quantums */
584 numpatt_quantized = (si->NumPatterns + PATTERNS_QUANTUM - 1) & ~(PATTERNS_QUANTUM - 1);
588 /* Create a new pattern table */
590 si->Patt = AllocVecPooled (si->Pool,
591 PATTERNS_QUANTUM * sizeof (struct Pattern *));
593 else if (si->NumPatterns + 1 >= numpatt_quantized)
595 struct Pattern **newpatt;
597 /* Expand patterns table */
599 if (newpatt = AllocVecPooled (si->Pool,
600 (numpatt_quantized + PATTERNS_QUANTUM) * sizeof (struct Pattern *)))
602 CopyMem (si->Patt, newpatt, si->NumPatterns * sizeof (struct Pattern *));
603 FreeVecPooled (si->Pool, si->Patt);
610 /* Pattern allocation */
614 if (patt = CAllocPooled (si->Pool, sizeof (struct Pattern) +
615 tracks * sizeof (struct Note *)))
617 patt->Tracks = tracks;
621 for (i = 0 ; i < tracks ; i++)
623 if (!(patt->Notes[i] = (struct Note *)
624 CAllocPooled (si->Pool, sizeof (struct Note) * lines)))
628 for (j = 0 ; j < tracks ; j++)
629 FreePooled (si->Pool, patt->Notes[i], lines * sizeof (struct Note));
631 FreePooled (si->Pool, patt, sizeof (struct Pattern) +
632 tracks * sizeof (struct Note *));
637 DupStringPooled (si->Pool, (STRPTR)GetTagData (PATTA_Name, NULL, tags), &patt->Name);
645 if (replace) /* Free old pattern */
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));
653 FreeVecPooled (si->Pool, si->Patt[patnum]->Name);
654 FreePooled (si->Pool, si->Patt[patnum], sizeof (struct Pattern) +
655 patt->Tracks * sizeof (struct Note *));
658 si->Patt[patnum] = patt;
662 if (patnum < si->NumPatterns)
664 /* Shift subsequent patterns one position ahead */
665 for (i = si->NumPatterns ; i > patnum ; i--)
666 si->Patt[i] = si->Patt[i-1];
675 if (tracks > si->MaxTracks)
676 si->MaxTracks = tracks;
684 static LONG SetPattern (struct SongInfo *si, ULONG num, struct TagItem *tags)
686 /* SNGM_SETPATTERN */
689 struct Pattern *patt;
691 if (patt = si->Patt[num])
692 if (ti = FindTagItem (PATTA_Name, tags))
694 DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Patt[num]->Name);
704 static void RemPattern (struct SongInfo *si, ULONG patnum, ULONG newpatt)
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.
717 struct Pattern *patt;
722 patt = (struct Pattern *)newpatt;
726 patt = si->Patt[patnum];
732 if (patnum < si->NumPatterns)
733 si->Patt[patnum] = NULL;
735 for (j = 0 ; j < patt->Tracks ; j++)
736 FreePooled (si->Pool, patt->Notes[j], patt->Lines * sizeof (struct Note));
738 FreeVecPooled (si->Pool, patt->Name);
739 FreePooled (si->Pool, patt, sizeof (struct Pattern) +
740 patt->Tracks * sizeof (struct Note *));
743 if (patnum < si->NumPatterns)
745 /* Scroll subsequent patterns */
746 for (i = patnum; i < si->NumPatterns; i++)
747 si->Patt[i] = si->Patt[i+1];
752 /* Adjust position table */
754 for (i = 0 ; i < si->Length ; i++)
756 /* Substitute references to the old pattern in the position table */
757 if (si->Sequence[i] == patnum) si->Sequence[i] = newpatt;
759 /* Fix pattern numbers */
760 if (si->Sequence[i] > patnum) si->Sequence[i]--;
771 if (si->CurrentPatt >= si->NumPatterns)
772 si->CurrentPatt = si->NumPatterns - 1;
776 FreeVecPooled (si->Pool, si->Patt);
784 static struct Instrument *AddInstrument (struct SongInfo *si, ULONG num, struct TagItem *tags)
786 /* SNGM_ADDINSTRUMENT
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.
795 * Pointer to the newly allocated instrument structure, NULL for failure.
798 struct Instrument *instr;
801 num = si->LastInstrument + 1;
805 for (num = 1; num <= si->LastInstrument; num++)
806 if (!si->Instr[num]) break;
808 if (num == MAXINSTRUMENTS) return NULL;
812 if (num > MAXINSTRUMENTS)
816 RemInstrument (si, num);
818 if (!(instr = CAllocPooled (si->Pool, sizeof (struct Instrument))))
821 si->Instr[num] = instr;
822 if (num > si->LastInstrument)
823 si->LastInstrument = num;
825 SetInstrument (si, num, tags);
832 static void SwapInstruments (struct SongInfo *si, LONG n, LONG m)
834 /* SNGM_SWAPINSTRUMENTS
836 * Swaps two instruments and updates the LastInstrument field if required.
839 * Pointer to the newly allocated instrument structure, NULL for failure.
842 struct Instrument *tmp;
847 si->Instr[n] = si->Instr[m];
852 if (n >= si->LastInstrument)
855 if (si->Instr[n]) break;
857 DB (kprintf ("n = %ld\n", n));
859 si->LastInstrument = n;
865 static LONG SetInstrument (struct SongInfo *si, ULONG num, struct TagItem *tags)
867 /* SNGM_SETINSTRUMENT */
869 struct Instrument *instr;
872 if ((num <= si->LastInstrument) && (instr = si->Instr[num]))
874 while (ti = NextTagItem (&tags))
878 instr->Type = ti->ti_Data;
882 DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &instr->Name);
886 instr->Volume = ti->ti_Data;
890 instr->Sample = (BYTE *) ti->ti_Data;
894 instr->Length = ti->ti_Data;
898 instr->Flags = ti->ti_Data;
901 case INSTRA_LoopStart:
902 instr->Repeat = ti->ti_Data;
906 instr->Replen = ti->ti_Data;
910 instr->Replen = ti->ti_Data - instr->Repeat - 2;
913 case INSTRA_FineTune:
914 instr->FineTune = ti->ti_Data;
915 /* Note: I'm falling through here! */
920 /* End while (NextTagItem()) */
930 static void RemInstrument (struct SongInfo *si, ULONG num)
932 /* SNGM_REMINSTRUMENT */
934 if ((num <= si->LastInstrument) && (si->Instr[num]))
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;
943 if (num == si->LastInstrument)
947 for (i = num - 1; i > 0 ; i--)
951 si->LastInstrument = i;
957 #if 0 /* --- This part has been removed --- */
959 static WORD ModType (BPTR fh)
961 /* Guess source module type */
963 UBYTE __aligned str[0x30];
966 if (Read (fh, str, 0x30) != 0x30)
970 if (!(strncmp (str, "FORM", 4) || strncmp (str+8, "XMOD", 4)))
974 if (!(strncmp (str, "MMD", 3)))
977 /* Check Oktalyzer */
978 if (!(strncmp (str, "OKTASONG", 8)))
979 return FMT_OKTALYZER;
981 if (!(strncmp (&str[0x2C], "SCRM", 4)))
982 return FMT_SCREAMTRACKER;
984 if (!(strncmp (str, "Extended module: ", 17)))
985 return FMT_FASTTRACKER2;
987 /* Check #?Tracker */
988 if ((trackertype = IsTracker (fh)) != FMT_UNKNOWN)
991 switch (ShowRequestArgs (MSG_UNKNOWN_MOD_FORMAT, MSG_SOUND_PRO_CANCEL, NULL))
1007 #endif /* --- This part has been removed --- */
1011 #define tolower(c) ((c) | (1<<5))
1013 static BOOL GuessAuthor (struct SongInfo *si)
1015 /* Tries to find the author of the song by looking up the instrument
1022 if (!si->Instr) return FALSE;
1024 for (i = 1; i <= si->LastInstrument; i++)
1026 if (!si->Instr[i]) continue;
1028 name = si->Instr[i]->Name;
1030 /* Check for IntuiTracker-style embedded author name */
1033 for (j = 1; name[j] == ' '; j++); /* Skip extra blanks */
1036 if ((tolower(name[j]) == 'b') && (tolower(name[j+1]) == 'y') && name[j+2] == ' ')
1039 for (; name[j] == ' '; j++); /* Skip extra blanks */
1043 SetAttrs (si, SNGA_Author, &(name[j]), TAG_DONE);
1044 return TRUE; /* Stop looking for author */
1048 /* Now look for the occurence of "by ", "by:", "by\0" or "(c)". Ignore case. */
1049 for (j = 0; name[j]; j++)
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] == ')') ))
1055 j+=3; /* Skip 'by ' */
1057 /* Skip extra blanks/punctuations */
1058 while (name[j] == ' ' || name[j] == ':' || name[j] == '.') j++;
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);
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);
1068 return TRUE; /* Stop loop */
1078 GLOBALCALL ULONG CalcInstSize (struct SongInfo *si)
1080 /* Calculate total size of all instruments in a song. */
1085 for (i = 1; i < si->LastInstrument; i++)
1087 if (!si->Instr[i]) continue;
1088 size += si->Instr[i]->Length + sizeof (struct Instrument);
1096 GLOBALCALL ULONG CalcSongSize (struct SongInfo *si)
1098 /* Calculate total size of a song */
1101 struct Pattern *patt;
1102 ULONG size = sizeof (struct SongInfo) + CalcInstSize (si);
1104 /* Calculate total patterns size */
1106 for ( i = 0; i < si->NumPatterns; i++)
1108 if (patt = si->Patt[i])
1109 size += patt->Lines * patt->Tracks * sizeof (struct Note) + sizeof (struct Pattern);
1117 GLOBALCALL ULONG CalcSongTime (struct SongInfo *si)
1119 /* Calculate song length in seconds
1121 * One note lasts speed/50 seconds at 125bpm.
1125 struct Pattern *patt;
1127 ULONG speed = si->GlobalSpeed,
1128 // tempo = si->GlobalTempo,
1133 for (i = 0; i < si->Length; i++)
1135 patt = si->Patt[si->Sequence[i]];
1137 for (j = 0; j < patt->Lines; j++)
1138 for (k = 0; k < patt->Tracks; k++)
1140 note = &patt->Notes[k][j];
1142 switch (note->EffNum)
1145 if (note->EffVal > i)
1151 else return millisecs;
1155 /* At speed 1, one line lasts one VBlank,
1156 * that is 20 milliseconds.
1158 ticks = note->EffVal * 20;
1161 case EFF_PATTERNBREAK:
1166 /* case EFF_MISC: Loop */