4 ** Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
6 ** General pourpose module handling/processing functions.
9 #include <exec/memory.h>
11 #include <proto/exec.h>
12 #include <proto/xmodule.h>
14 #include "XModulePriv.h"
18 /* Local function prototypes */
20 static void BreakPattern (struct Note **arr, UWORD rows, UWORD tracks);
24 GLOBALCALL ULONG InsertPattern (struct SongInfo *si, struct Pattern *patt, UWORD patnum)
26 /* Inserts a pattern at any song position. Patterns >= patnum will be moved
27 * ahead one slot. The position table is updated inserting references to
28 * the new pattern immediately before each occurence of patnum, so that the
29 * two patterns are always played together.
33 * any other value means failure.
44 xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
45 (APTR)MSG_CANT_INSERT_PATT, NULL);
49 /* Adjust position table */
50 for (i = 0 ; i < si->Length ; i++)
52 /* Song can't grow bigger than MAXPOSITIONS */
53 if (si->Length >= MAXPOSITIONS)
54 return 2; /* TODO: better error handling */
56 /* Fix pattern numbers */
57 if (si->Sequence[i] > patnum) si->Sequence[i]++;
59 /* Insert references to the new pattern in the position table */
60 if (si->Sequence[i] == patnum)
63 if (xmSetSongLen (si, si->Length + 1))
65 /* Shift subsequent positions ahead 1 slot */
66 for (k = si->Length - 1; k > i ; k--)
67 si->Sequence[k] = si->Sequence[k-1];
69 si->Sequence[i+1] = patnum+1; /* Restore old pattern */
71 i++; /* Avoid processing this pattern again */
73 /* TODO: It would be nice to fix Pattern Jump commands too... */
84 GLOBALCALL void DiscardPatterns (struct SongInfo *si)
86 /* Discard patterns beyond the last pattern referenced
87 * in the song sequence.
94 if (!(used = AllocVecPooled (Pool, si->NumPatterns)))
97 /* Flag patterns REALLY used in the song */
98 for (i = 0; i < si->Length ; i++)
99 used[si->Sequence[i]]++;
102 for (i = 0; i < si->NumPatterns; i++)
104 if (!(patt = si->Patt[i])) continue;
106 if (!used[i] && patt)
108 xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
109 (APTR)MSG_PATT_UNUSED, i);
111 xmRemPattern (si, i, 0);
113 /* Shift all subsequent patterns one position back */
114 for (j = i; j < si->NumPatterns; j++)
117 /* Rearrange Position Table */
118 for (j = 0; j < si->Length; j++)
119 if (si->Sequence[j] > i) si->Sequence[j]--;
121 /* TODO: It would be nice to fix Pattern Jump commands too... */
123 i--; /* Process this pattern # again, since it's now another pattern */
127 FreeVecPooled (Pool, used);
132 GLOBALCALL void CutPatterns (struct SongInfo *si)
134 /* Find out what patterns are breaked (effect D) and resize them */
137 struct Pattern *patt, *newpatt;
139 for (i = 0; i < si->NumPatterns; i++)
141 if (!(patt = si->Patt[i])) continue;
143 for (j = 0; j < patt->Lines; j++)
144 for (k = 0; k < patt->Tracks; k++)
145 if (patt->Notes[k][j].EffNum == EFF_PATTERNBREAK)
147 xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
148 (APTR)MSG_PATT_CUT, i, j+1);
150 if (newpatt = xmAddPattern (si,
151 PATTA_Name, patt->Name,
152 PATTA_Tracks, patt->Tracks,
154 PATTA_Num, -1, /* Do not add it */
157 /* Remove break command */
158 patt->Notes[k][j].EffNum = 0;
159 patt->Notes[k][j].EffVal = 0;
162 for (l = 0; l < patt->Tracks; l++)
163 CopyMem (patt->Notes[l], newpatt->Notes[l], sizeof (struct Note) * (j+1));
165 /* Replace with new pattern */
172 /* stop the loop on this pattern */
183 GLOBALCALL void RemDupPatterns (struct SongInfo *si)
185 /* Find out identical patterns and cut them out */
188 struct Pattern *patta, *pattb;
190 if (si->NumPatterns < 2) return;
192 for (i = 0; i < si->NumPatterns-1; i++)
194 if (!(patta = si->Patt[i])) continue;
196 for (j = i+1; j < si->NumPatterns; j++)
198 if (!(pattb = si->Patt[j])) continue;
200 if ((patta->Lines == pattb->Lines) && (patta->Tracks == pattb->Tracks))
202 for (k = 0; k < patta->Tracks; k++)
203 if (memcmp (patta->Notes[k], pattb->Notes[k], sizeof (struct Note) * patta->Lines))
206 if (k == patta->Tracks)
208 xmRemPattern (si, j, i);
209 xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
210 (APTR)MSG_PATT_DUPE, i, j);
220 static void BreakPattern (struct Note **arr, UWORD row, UWORD tracks)
222 /* Put a break command at the end of a pattern */
226 /* Try to find a free effect slot in the row... */
227 for (i = 0 ; i < tracks ; i++)
228 if (arr[i][row].EffNum == 0 && arr[i][row].EffVal == 0)
231 if (i == tracks) i = 0;
233 arr[i][row].EffNum = EFF_PATTERNBREAK; /* ...and break the pattern */
234 arr[i][row].EffVal = 0;
239 GLOBALCALL LONG ResizePatterns (struct SongInfo *si, ULONG min, ULONG max)
241 /* Find out what patterns are less than <min> and more than <max>
242 * lines long and either grow them or split them in shorter
243 * patterns. Pattern splitting will automatically recurse when
244 * more than one split is required.
247 * 0 to mean succes, any other value means failure.
250 struct Pattern *patt, *newpatt, *newpatt2;
253 BOOL do_update = FALSE;
255 for (i = 0 ; i < si->NumPatterns ; i++)
257 if (!(patt = si->Patt[i])) continue;
259 if ((len = patt->Lines) < min)
263 xmDisplayMessage (XMDMF_USECATALOG | XMDMF_COMMENT,
264 (APTR)MSG_PATT_WILL_GROW, i, len);
266 if (!(newpatt = xmAddPattern (si,
269 PATTA_Tracks, patt->Tracks,
271 return ERROR_NO_FREE_STORE;
274 /* Copy the old notes in the new (longer) pattern */
275 for (j = 0 ; j < patt->Tracks ; j++)
276 memcpy (newpatt->Notes[j], patt->Notes[j],
277 (sizeof (struct Note)) * len);
279 /* Break the new pattern */
280 BreakPattern (newpatt->Notes, len-1, newpatt->Tracks);
284 PATTA_Pattern, newpatt,
292 xmDisplayMessage (XMDMF_USECATALOG | XMDMF_COMMENT,
293 (APTR)MSG_SPLITTING_PATT, i, len);
295 if (!(newpatt = xmAddPattern (si,
298 PATTA_Tracks, patt->Tracks,
300 return ERROR_NO_FREE_STORE;
303 /* If len - max is still above max, this pattern
304 * will be splitted or breaked once again in the
308 if (!(newpatt2 = xmAddPattern (si,
310 PATTA_Lines, len - max,
311 PATTA_Tracks, patt->Tracks,
314 xmRemPattern (si, -1, (ULONG)newpatt);
315 return ERROR_NO_FREE_STORE;
318 /* Copy first <max> rows of the pattern */
319 for (j = 0 ; j < si->MaxTracks ; j++)
320 memcpy (newpatt->Notes[j], patt->Notes[j],
321 (sizeof (struct Note)) * max);
323 /* Copy the rest of the pattern */
324 for (j = 0 ; j < si->MaxTracks ; j++)
325 memcpy (newpatt2->Notes[j], patt->Notes[j] + max,
326 (sizeof (struct Note)) * (len - max));
329 /* Make room for the new pattern */
330 if (InsertPattern (si, newpatt, i))
332 xmRemPattern (si, -1, (ULONG)newpatt);
333 xmRemPattern (si, -1, (ULONG)newpatt2);
337 /* Substitute old pattern */
338 if (!(xmAddPattern (si,
340 PATTA_Pattern, newpatt2,
343 xmRemPattern (si, -1, (ULONG)newpatt2);
354 } /* End ResizePatterns */
358 GLOBALCALL struct Pattern *CopyPattern (struct SongInfo *si, struct Pattern *src, ULONG destNum)
360 /* Makes a copy of the notes and attributes of the <src>
361 * pattern to the <dest> pattern. The destination pattern is
362 * created with xmAddPattern().
365 struct Pattern *dest;
368 if (!src) return NULL;
370 if (dest = xmAddPattern (si, destNum,
371 PATTA_Lines, src->Lines,
372 PATTA_Tracks, src->Tracks,
373 PATTA_Name, src->Name))
375 for (i = 0; i < src->Tracks; i++)
376 CopyMem (src->Notes[i], dest->Notes[i],
377 sizeof (struct Note) * src->Lines);
385 GLOBALCALL struct SongInfo *MergeSongs (struct SongInfo *songa, struct SongInfo *songb)
387 /* Merges <songa> with <songb> in a new song where the notes of the
388 * two sources are played at the same time.
391 struct SongInfo *songc;
392 struct Pattern *patta, *pattb, *pattc;
394 struct Instrument *source;
396 LONG slen, plen, ntrk, i, j, k;
399 if (songa->Length != songb->Length)
400 xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
401 (APTR)MSG_SONG_LEN_DIFFERENT, NULL);
403 /* Create new song */
405 UBYTE newtitle[64], newauthor[64];
409 strncpy (newtitle, songa->Title, 63);
410 strncat (newtitle, " + ", 63);
411 strncat (newtitle, songb->Title, 63);
413 /* Make new author */
415 newauthor[63] = '\0';
416 if (songa->Author && songa->Author[0])
417 strncpy (newauthor, songa->Author, 63);
418 if (songb->Author && songb->Author[0])
420 if (songa->Author && songa->Author[0])
422 strncat (newauthor, " & ", 63);
423 strncat (newauthor, songb->Author, 63);
425 else strncpy (newauthor, songb->Author, 63);
428 if (!(songc = xmCreateSong (
429 SNGA_GlobalSpeed, songa->GlobalSpeed,
430 SNGA_GlobalTempo, songa->GlobalTempo,
431 SNGA_RestartPos, songa->RestartPos,
432 SNGA_Title, newtitle,
433 SNGA_Author, newauthor,
438 slen = min (songa->Length, songb->Length);
439 xmSetSongLen (songc, slen);
442 for (i = 0; i < slen; i++)
446 patta = songa->Patt[songa->Sequence[i]];
447 pattb = songb->Patt[songb->Sequence[i]];
449 if (patta->Lines != pattb->Lines)
450 xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
451 (APTR)MSG_PATT_LEN_DIFFERENT, i);
453 plen = min (patta->Lines, pattb->Lines);
454 ntrk = patta->Tracks + pattb->Tracks;
456 if (ntrk > MAXTRACKS)
458 xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
459 (APTR)MSG_PATT_TOO_MANY_TRACKS, MAXTRACKS);
464 /* Set pattern name */
465 if (patta->Name && pattb->Name)
468 strncpy (pattname, patta->Name, 63);
469 strncat (pattname, " + ", 63);
470 strncat (pattname, pattb->Name, 63);
473 SPrintf (pattname, "%ld + %ld", songa->Sequence[i], songb->Sequence[i]);
475 if (!(pattc = xmAddPattern (songc,
478 PATTA_Name, pattname,
481 xmDeleteSong (songc);
482 LastErr = ERROR_NO_FREE_STORE;
486 songc->Sequence[i] = i;
488 /* Copy tracks from source A */
489 for (j = 0; j < patta->Tracks; j++)
490 CopyMem (patta->Notes[j], pattc->Notes[j], sizeof (struct Note) * plen);
492 /* Copy tracks from source B */
493 for (j = 0; j < pattb->Tracks; j++)
495 if (j + patta->Tracks >= MAXTRACKS) break;
497 note = pattc->Notes[j + patta->Tracks];
498 CopyMem (pattb->Notes[j], note, sizeof (struct Note) * plen);
500 for (k = 0; k < plen; k++)
503 if ((note[k].Inst + songa->LastInstrument) >= MAXINSTRUMENTS)
505 xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
506 (APTR)MSG_ERR_INSTR_OVERFLOW, NULL);
507 xmDeleteSong (songc);
511 note[k].Inst += songa->LastInstrument - 1;
517 /* Copy instruments */
519 for (i = 1; i < songa->LastInstrument + songb->LastInstrument; i++)
521 if (i <= songa->LastInstrument)
522 source = songa->Instr[i];
524 source = songb->Instr[i - songa->LastInstrument + 1];
530 if (!(newsample = AllocVec (source->Length, MEMF_SAMPLE)))
532 xmDeleteSong (songc);
533 LastErr = ERROR_NO_FREE_STORE;
536 CopyMem (source->Sample, newsample, source->Length);
541 if (!xmAddInstrument (songc, i,
542 INSTRA_Type, source->Type,
543 INSTRA_Name, source->Name,
544 INSTRA_Volume, source->Volume,
545 INSTRA_Sample, newsample,
546 INSTRA_Length, source->Length,
547 INSTRA_LoopStart, source->Repeat,
548 INSTRA_LoopLen, source->Replen,
549 INSTRA_FineTune, source->FineTune,
553 xmDeleteSong (songc);
554 LastErr = ERROR_NO_FREE_STORE;
565 GLOBALCALL struct SongInfo *JoinSongs (struct SongInfo *songa, struct SongInfo *songb)
567 /* Joins <songa> with <songb> in a new song where the notes of the
568 * two sources are played one after the oder.
571 struct SongInfo *songc;
572 struct Pattern *patta, *pattb, *pattc;
574 struct Instrument *inst, *source;
576 ULONG plen, ntrk, i, j, k;
580 /* Check maximum values */
582 if ((songa->Length + songb->Length) > MAXPOSITIONS)
584 xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
585 (APTR)MSG_SONG_TOO_LONG, NULL);
586 songclen = MAXPOSITIONS;
588 else songclen = songa->Length + songb->Length;
590 if ((songa->NumPatterns + songb->NumPatterns) > MAXPATTERNS)
591 xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
592 (APTR)MSG_SONG_HAS_TOO_MANY_PATT, NULL);
595 /* Create new song */
597 UBYTE newtitle[64], newauthor[64];
601 strncpy (newtitle, songa->Title, 63);
602 strncat (newtitle, " & ", 63);
603 strncat (newtitle, songb->Title, 63);
605 /* Make new author */
607 newauthor[63] = '\0';
608 if (songa->Author && songa->Author[0])
609 strncpy (newauthor, songa->Author, 63);
610 if (songb->Author && songb->Author[0])
612 if (songa->Author && songa->Author[0])
614 strncat (newauthor, " & ", 63);
615 strncat (newauthor, songb->Author, 63);
617 else strncpy (newauthor, songb->Author, 63);
620 if (!(songc = xmCreateSong (
621 SNGA_GlobalSpeed, songa->GlobalSpeed,
622 SNGA_GlobalTempo, songa->GlobalTempo,
623 SNGA_RestartPos, songa->RestartPos,
624 SNGA_Title, newtitle,
625 SNGA_Author, newauthor,
630 if (xmSetSongLen (songc, songclen))
632 /* Copy position table of song A */
633 memcpy (songc->Sequence, songa->Sequence, songa->Length * sizeof (UWORD));
635 /* Append position table of song B */
636 for (i = 0; i < songb->Length; i++)
638 if (i + songa->Length >= songc->Length) break;
639 songc->Sequence[i + songa->Length] = (songb->Sequence[i] + songa->NumPatterns > MAXPATTERNS) ?
640 songb->Sequence[i] : (songb->Sequence[i] + songa->NumPatterns);
645 xmDeleteSong (songc);
646 LastErr = ERROR_NO_FREE_STORE;
651 /* Copy song A patterns */
653 for (i = 0; i < songa->NumPatterns; i++)
655 patta = songa->Patt[i];
657 ntrk = patta->Tracks;
659 if (!(pattc = xmAddPattern (songc,
662 PATTA_Name, patta->Name,
665 xmDeleteSong (songc);
666 LastErr = ERROR_NO_FREE_STORE;
670 /* Copy tracks from source A */
672 for (j = 0; j < ntrk; j++)
673 CopyMem (patta->Notes[j], pattc->Notes[j], sizeof (struct Note) * plen);
677 /* Append song B patterns */
679 for (i = 0; i < songb->NumPatterns; i++)
681 pattb = songb->Patt[i];
683 ntrk = pattb->Tracks;
685 if (!(pattc = xmAddPattern (songc,
688 PATTA_Name, pattb->Name,
691 xmDeleteSong (songc);
692 LastErr = ERROR_NO_FREE_STORE;
696 /* Copy tracks from source B */
698 for (j = 0; j < ntrk; j++)
699 CopyMem (pattb->Notes[j], pattc->Notes[j], sizeof (struct Note) * plen);
701 /* Adjust instruments references */
703 for (j = 0; j < ntrk; j++)
704 for (k = 0; k < plen; k++)
706 note = &pattc->Notes[j][k];
710 if ((note->Inst + songa->LastInstrument) >= MAXINSTRUMENTS)
712 xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
713 (APTR)MSG_ERR_INSTR_OVERFLOW, NULL);
714 xmDeleteSong (songc);
718 note->Inst += songa->LastInstrument - 1;
725 /* Copy instruments */
727 for (i = 1; i < songa->LastInstrument + songb->LastInstrument; i++)
729 if (i <= songa->LastInstrument)
730 source = songa->Instr[i];
732 source = songb->Instr[i - songa->LastInstrument + 1];
738 if (!(newsample = AllocVec (source->Length, MEMF_ANY)))
740 xmDeleteSong (songc);
741 LastErr = ERROR_NO_FREE_STORE;
744 CopyMem (source->Sample, newsample, source->Length);
749 if (!(inst = xmAddInstrument (songc, i,
750 INSTRA_Type, source->Type,
751 INSTRA_Name, source->Name,
752 INSTRA_Volume, source->Volume,
753 INSTRA_Sample, newsample,
754 INSTRA_Length, source->Length,
755 INSTRA_Repeat, source->Repeat,
756 INSTRA_Replen, source->Replen,
757 INSTRA_FineTune, source->FineTune,
761 xmDeleteSong (songc);
762 LastErr = ERROR_NO_FREE_STORE;
773 GLOBALCALL void FixSong (struct SongInfo *si)
775 /* Fixes dangerous errors in song structure */
777 struct Instrument *instr;
780 /* Check instruments */
782 for (i = 1 ; i < si->LastInstrument ; i++)
784 if (!(instr = si->Instr[i])) continue;
786 /* Filter instrument names */
787 FilterName (instr->Name);
789 /* Some sensible sanity checks on loops */
791 if (instr->Repeat && (instr->Repeat >= instr->Length))
793 instr->Repeat = instr->Replen = 0;
794 xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
795 (APTR)MSG_INVALID_LOOP_REMOVED, i);
797 else if (instr->Repeat + instr->Replen > instr->Length)
799 instr->Replen = instr->Length - instr->Repeat;
800 xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
801 (APTR)MSG_INVALID_LOOP_FIXED, i);
806 for (i = 0; i < si->NumPatterns; i++)
807 if (si->Patt[i]) FilterName (si->Patt[i]->Name);
809 /* Can't have a song with no patterns!!! */
810 if (si->NumPatterns == 0)
812 xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_NOTE,
813 (APTR)MSG_SONG_HAS_NO_PATTS, NULL);
814 xmAddPattern (si, NULL);
817 /* Better forcing the song to a minimum length of 1. */
820 xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_NOTE,
821 (APTR)MSG_SONG_HAS_NO_SEQ, NULL);
822 xmSetSongLen (si, 1);
826 for (i = 0; i < si->Length; i++)
828 if (si->Sequence[i] >= si->NumPatterns)
830 xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
831 (APTR)MSG_INVALID_SONG_POS, i, si->Sequence[i]);
832 si->Sequence[i] = si->NumPatterns - 1;
836 FilterName (si->Title);
837 FilterName (si->Author);