4 ** Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
6 ** Originally based on Gerardo Iula's Tracker sources.
8 ** External hook for Oktalyzer 1.1-1.57 module format.
12 #include <exec/memory.h>
13 #include <dos/stdio.h>
14 #include <libraries/xmodule.h>
15 #include <libraries/songclass.h>
17 #include <proto/exec.h>
18 #include <proto/dos.h>
19 #include <proto/intuition.h>
20 #include <proto/xmodule.h>
22 #include "XModulePriv.h"
27 /* Local functions prototypes */
29 HOOKCALL APTR _GetEngine (
30 REG(a6, struct Library *MyBase));
31 HOOKCALL APTR _SetupXMHook (
32 REG(a0, struct XModuleBase *_XModuleBase),
33 REG(a6, struct Library *mybase));
34 static HOOKCALL struct XMHook *IdentifyOktalyzer (
36 REG(a0, struct XMHook *loader),
37 REG(a1, ULONG *tags));
38 static HOOKCALL LONG LoadOktalyzer (
40 REG(a0, struct SongInfo *si),
41 REG(a1, struct XMHook *loader),
42 REG(a2, ULONG *tags));
43 static HOOKCALL LONG SaveOktalyzer (
45 REG(a0, struct SongInfo *si),
46 REG(a1, struct XMHook *saver),
47 REG(a2, ULONG *tags));
49 INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval, UWORD patt, UWORD line, UWORD track);
53 #define OKT_MODE4 1 /* Mode 4: play 8 bit instruments */
54 #define OKT_MODE8 0 /* Mode 8: play 7 bit instruments */
55 #define OKT_MODEB 2 /* Mode B: play both 8 & 7 bit instruments */
58 /* Oktalyzer chunk IDs */
60 #define ID_OKTA 'OKTA'
61 #define ID_CMOD 'CMOD'
62 #define ID_SAMP 'SAMP'
63 #define ID_SPEE 'SPEE'
64 #define ID_SLEN 'SLEN'
65 #define ID_PLEN 'PLEN'
66 #define ID_PBOD 'PBOD'
67 #define ID_SBOD 'SBOD'
70 #define ID_SONG 'SONG'
74 #define ID_PATT 'PATT'
81 const UBYTE LibName[] = "oktalyzer.xmhook";
82 const UBYTE LibVer[] = { '$', 'V', 'E', 'R', ':', ' ' };
83 const UBYTE LibId[] = "oktalyzer.xmhook 1.0 (28.1.96) © 1993-96 by Bernardo Innocenti";
87 /* Effects conversion table
88 * Originally based on Gerardo Iula's "Tracker" source.
90 static const UBYTE Effects[MAXTABLEEFFECTS] =
92 /* OKTA XModule Val */
94 0x00, /* Null effect $00 */
96 0x01, /* Portamento Up $01 */
97 0x02, /* Portamento Down $02 */
98 0x00, /* Tone Portamento $03 */
99 0x00, /* Vibrato $04 */
100 0x00, /* ToneP + VolSl $05 */
101 0x00, /* Vibra + VolSl $06 */
102 0x00, /* Tremolo $07 */
103 0x00, /* Set Hold/Decay $08 */
104 0x00, /* Sample Offset $09 */
105 0x1E, /* Volume Slide $0A */
106 0x19, /* Position Jump $0B */
107 0x1F, /* Set Volume $0C */
108 0x00, /* Pattern break $0D */
110 0x1C, /* Set Speed $0F */
111 0x00, /* Set Tempo $10 */
112 0x1A, /* Arpeggio $11 */
114 0x11, /* Oktalyzer H */
115 0x15 /* Oktalyzer L */
120 /* Get around a SAS/C bug which causes some annoying warnings
121 * with the library bases defined below. The problem happens
122 * when the GST has been compiled with the CODE=FAR switch and
123 * the source is being compiled with CODE=NEAR.
126 #pragma msg 72 ignore push
129 struct ExecBase *SysBase = NULL;
130 struct XModuleBase *XModuleBase = NULL;
131 struct DosLibrary *DOSBase = NULL;
132 struct IntuitionBase *IntuitionBase = NULL;
140 static HOOKCALL struct XMHook *IdentifyOktalyzer (
142 REG(a0, struct XMHook *loader),
143 REG(a1, ULONG *tags))
145 /* Determine if the given file is an Oktalyzer module.
146 * Note: the file position will be changed on exit.
151 Seek (fh, 0, OFFSET_BEGINNING);
152 if (FRead (fh, &id, 12, 1) != 1)
155 if ((id[0] == ID_OKTA) && (id[1] == ID_SONG) && (id[2] == ID_CMOD))
163 INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval, UWORD patt, UWORD line, UWORD track)
165 /* Inputs: old effect & old type.
166 * Output: new effect in requested newtype.
171 for ( i = 0 ; i < MAXTABLEEFFECTS ; i++ )
172 if (eff == Effects[i])
175 xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
176 (APTR)MSG_UNKNOWN_EFF, 5, eff, patt, track, line);
182 static HOOKCALL LONG LoadOktalyzer (
184 REG(a0, struct SongInfo *si),
185 REG(a1, struct XMHook *loader),
186 REG(a2, ULONG *tags))
188 struct Instrument *instr;
189 struct Pattern *patt;
190 ULONG i, j, k; /* Loop counters */
191 ULONG size; /* Read buffer */
192 ULONG l; /* Read buffer */
193 UWORD voices, numpatts, songlen, instr_mode[37];
198 /* Check file header OKTASONGCMOD */
202 if (FRead (fh, id, 12, 1) != 1) return ERROR_IOERR;
203 if ((id[0] != ID_OKTA) || (id[1] != ID_SONG) || (id[2] != ID_CMOD))
204 return ERROR_NOTMODULE;
208 /* TODO: set si->SongName */
211 /* CMOD Chunk size ($0000 0008) */
212 Seek (fh, 4, OFFSET_CURRENT); /* Skip 4 bytes */
214 voices = 4; /* Set minimum voices and check others */
215 for (i = 0 ; i < 4 ; i++)
217 if (FRead (fh, &w, 2, 1) != 1) return ERROR_IOERR;
221 /* Get Sample Name, Length, Repeat Replen, Volume for each instr. */
223 /* Check header SAMP */
224 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
225 if (l != ID_SAMP) return ERROR_NOTMODULE;
227 /* SAMP Chunk Size ($0000 0480) */
228 Seek (fh, 4, OFFSET_CURRENT); /* Skip 4 bytes */
230 xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
231 (APTR)MSG_READING_INSTS_INFO, NULL);
233 /* Read in 36 instruments */
234 for ( j = 1 ; j <= 36 ; j++ )
246 /* Get instrument data */
247 if (FRead (fh, &oktainstr, sizeof (oktainstr), 1) != 1)
250 oktainstr.name[19] = '\0';
252 /* Oktalyzer sometimes saves odd lengths for instruments,
253 * but the data saved in SBOD is always rounded _down_ to an even
254 * number! It took me two weeks to find and kill this bug :-(
256 oktainstr.size &= (~1);
258 /* Oktalyzer instrument modes:
259 * mode 8 $00 7 bit instruments
260 * mode 4 $01 normal instruments
261 * mode B $02 both 8bit & 7bit
263 instr_mode[j] = oktainstr.mode;
266 if (!(xmAddInstrument (si, j,
267 INSTRA_Name, oktainstr.name,
268 INSTRA_Length, oktainstr.size,
269 INSTRA_Repeat, oktainstr.repeat << 1,
270 INSTRA_Replen, oktainstr.replen << 1,
271 INSTRA_Volume, oktainstr.volume,
273 return ERROR_NO_FREE_STORE;
277 /* Get global song speed */
279 /* Check speed header "SPEE" */
280 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
281 if (l != ID_SPEE) return ERROR_NOTMODULE;
283 /* SPEE Chunk size ($0000 0002) */
284 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
286 /* Get Song Global Speed */
287 if (FRead (fh, &w, 2, 1) != 1) return ERROR_IOERR;
294 /* Get number of patterns */
296 /* Check header SLEN */
297 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
298 if (l != ID_SLEN) return ERROR_NOTMODULE;
300 /* SLEN Chunk size ($0000 0002) */
301 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
303 /* Get number of patterns */
304 if (FRead (fh, &numpatts, 2, 1) != 1) return ERROR_IOERR;
307 if (numpatts > MAXPATTERNS)
309 xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
310 (APTR)MSG_SONG_HAS_TOO_MANY_PATT, NULL);
311 numpatts = MAXPATTERNS - 1;
315 /* Get number of positions in song (Length) */
317 /* Check header PLEN */
318 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
319 if (l != ID_PLEN) return ERROR_NOTMODULE;
321 /* Chunk size ($0000 0002) */
322 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
324 /* Get number of patterns */
325 if (FRead (fh, &songlen, 2, 1) != 1) return ERROR_IOERR;
328 if (songlen > MAXPOSITIONS)
330 xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
331 (APTR)MSG_SONG_TOO_LONG, NULL);
332 songlen = MAXPOSITIONS;
336 /* Get position table */
338 /* Check header PATT */
339 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
340 if (l != ID_PATT) return ERROR_NOTMODULE;
342 /* PATT Chunk size ($0000 0080) */
343 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
346 /* Read song sequence */
350 if (FRead (fh, postable, 1, 128) != 128) return ERROR_IOERR;
352 if (!(xmSetSongLen (si, songlen)))
353 return ERROR_NO_FREE_STORE;
355 for (i = 0; i < si->Length; i++)
356 si->Sequence[i] = postable[i];
360 /* Get pattern bodies and convert them */
362 xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
363 (APTR)MSG_READING_PATTS, NULL);
365 for (j = 0; j < numpatts; j++)
367 if (xmDisplayProgress (j + 1, numpatts))
370 /* Check header "PBOD" */
371 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
372 if (l != ID_PBOD) return ERROR_NOTMODULE;
374 /* Skip Chunk Length (Lines * Tracks * 4 + 2) */
375 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
377 /* Get pattern length */
378 if (FRead (fh, &w, 2, 1) != 1) return ERROR_IOERR;
380 /* Allocate memory for all tracks */
381 if (!(patt = xmAddPattern (si,
382 PATTA_Tracks, voices,
385 return ERROR_NO_FREE_STORE;
387 for ( k = 0 ; k < patt->Lines ; k++)
389 for (i = 0; i < voices; i++)
391 struct Note *n = &patt->Notes[i][k];
393 /* Read a whole track row */
394 if (FRead (fh, oktanote, 4, 1) != 1) return ERROR_IOERR;
398 * Oktalyzer supports 3 octaves (1 to 3).
399 * Notes are numbered from 1 (C-1) to 36 (B-3).
402 if (oktanote[0] <= 36)
403 n->Note = (oktanote[0] ? (oktanote[0] + 12) : 0); /* Add one octave */
405 xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
406 (APTR)MSG_INVALID_NOTE, oktanote[0], j, i, k);
408 /* Store Instrument Number */
409 n->Inst = (n->Note ? (oktanote[1] + 1) : 0);
412 n->EffNum = DecodeEff (oktanote[2], oktanote[3], j, k, i);
414 /* Store Effect Value */
415 n->EffVal = oktanote[3];
417 /* Effect Exceptions */
418 if (n->EffNum == EFF_MISC)
420 /* Oktalyzer has SetFilter values inverted! */
421 if ((n->EffVal >> 4) == 1)
422 n->EffVal = 0x10 | (n->EffVal & 0x0F ? 0 : 1);
429 /* Load instruments */
431 xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
432 (APTR)MSG_READING_INSTS, NULL);
434 for (j = 1 ; j <= 36 ; j++)
438 if (!(instr = si->Instr[j])) continue;
440 if (xmDisplayProgress(j, 36))
443 /* Check header SBOD */
444 if (FRead (fh, &l, 4, 1) != 1) return ERROR_IOERR;
445 if (l != ID_SBOD) return ERROR_NOTMODULE;
447 /* Read chunk size (length of instrument) & check it */
448 if (FRead (fh, &size, 4, 1) != 1) return ERROR_IOERR;
449 if (size != instr->Length) return ERROR_NOTMODULE;
451 if (!(sample = AllocVec (instr->Length, MEMF_SAMPLE)))
452 return ERROR_NO_FREE_STORE;
454 /* Read instrument body */
455 if (FRead (fh, sample, 1, instr->Length) != instr->Length)
461 /* Double sample data */
462 if (instr_mode[j] == OKT_MODE8 || instr_mode[j] == OKT_MODEB)
464 for (i = 0; i < instr->Length; i++)
468 xmSetInstrument (si, j,
469 INSTRA_Sample, sample,
473 /* Check for extra data following the module */
474 if (FGetC (fh) != ENDSTREAMCH)
475 xmDisplayMessageA (XMDMF_NOTE | XMDMF_USECATALOG,
476 (APTR)MSG_EXTRA_DATA_AFTER_MOD, NULL);
483 static HOOKCALL LONG SaveOktalyzer (
485 REG(a0, struct SongInfo *si),
486 REG(a1, struct XMHook *saver),
487 REG(a2, ULONG *tags))
489 struct Instrument *instr;
491 UWORD voices, instr_mode, songlen;
492 ULONG l; /* Write buffers */
496 /* Check number of tracks and fix data */
497 voices = si->MaxTracks;
498 if (voices < 4) voices = 4;
499 if (voices > 8) voices = 8;
502 /* Oktalyzer does not support pattern break (D) command */
504 xmProcessSong (si, NULL,
505 XMSNG_Optimize, XMOF_CUT_PATTERNS,
509 /* Write file header */
510 if (FWrite (fh, "OKTASONGCMOD", 12, 1) != 1) return ERROR_IOERR;
513 /* Write maximum number of tracks */
515 if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
517 /* Write active tracks (only 5 to 8: tracks 1..4 are always on)
518 * TODO: Ask user what tracks should be made active, or loadnig and
519 * saving back the same module will result in the lost of the original
522 for (i = 5 ; i <= 8 ; i++)
524 if (voices >= i) w = 1;
526 if (FWrite (fh, &w, 2, 1) != 1) return ERROR_IOERR;
529 /* Choose mode for instruments.
530 * When the module is 4 channels, we can always use mode 4.
531 * On modules with 5-7 channels, it is hard to guess which instruments
532 * could be made mode 4, 8 or B, so we always choose mode B.
533 * for 8 channels modules, we just use mode 8 for all instruments.
538 instr_mode = OKT_MODE4;
542 instr_mode = OKT_MODE8;
546 instr_mode = OKT_MODEB;
551 /* Write sample names, length, effects, volume */
552 xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
553 (APTR)MSG_WRITING_INSTINFO, NULL);
555 if (FWrite (fh, "SAMP", 4, 1) != 1) return ERROR_IOERR;
557 /* Write chunk length */
559 if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
561 for ( j = 1 ; j <= 36 ; j++ )
574 if (instr = si->Instr[j])
576 strncpy (oktainstr.name, instr->Name, 19);
577 oktainstr.size = instr->Length;
578 oktainstr.repeat = instr->Repeat >> 1;
579 oktainstr.replen = instr->Replen >> 1;
580 oktainstr.volume = instr->Volume;
581 oktainstr.mode = instr->Length ? instr_mode : 0;
584 /* Write instrument data */
585 if (FWrite (fh, &oktainstr, sizeof (oktainstr), 1) != 1)
590 /* Write global song speed */
592 if (FWrite (fh, "SPEE", 4, 1) != 1) return ERROR_IOERR;
594 if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
597 if (FWrite (fh, &w, 2, 1) != 1) return ERROR_IOERR;
600 if (FWrite (fh, "SLEN", 4, 1) != 1) return ERROR_IOERR;
602 if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
605 if (FWrite (fh, &w, 2, 1) != 1) return ERROR_IOERR;
608 /* Write patterns number */
610 if (FWrite (fh, "PLEN", 4, 1) != 1) return ERROR_IOERR;
612 if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
614 songlen = min (si->Length, 128);
615 if (FWrite (fh, &songlen, 2, 1) != 1) return ERROR_IOERR;
618 /* Write patterns sequence */
620 if (FWrite (fh, "PATT", 4, 1) != 1) return ERROR_IOERR;
622 if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
627 memset (postable, 0, 128);
629 for (i = 0; i < songlen; i++)
630 postable[i] = si->Sequence[i];
632 if (FWrite (fh, postable, 1, 128) != 128) return ERROR_IOERR;
637 xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
638 (APTR)MSG_WRITING_PATTS, NULL);
640 for ( j = 0 ; j < si->NumPatterns ; j++)
642 if (xmDisplayProgress (j + 1, si->NumPatterns))
645 if (FWrite (fh, "PBOD", 4, 1) != 1) return ERROR_IOERR;
646 l = si->Patt[j]->Lines * si->MaxTracks * 4 + 2;
647 if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
649 /* Write pattern length (WORD) */
650 w = si->Patt[j]->Lines;
651 if (FWrite (fh, &w, 2, 1) != 1) return ERROR_IOERR;
653 for (k = 0 ; k < si->Patt[j]->Lines ; k++)
655 for ( i = 0 ; i < voices ; i++)
657 struct Note *n = &(si->Patt[j]->Notes[i][k]);
663 xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
664 (APTR)MSG_NOTE_TOO_LOW, j, i, k);
665 oktanote[0] = n->Note;
671 xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
672 (APTR)MSG_NOTE_TOO_HIGH, j, i, k);
673 oktanote[0] = n->Note - 24;
676 oktanote[0] = n->Note - 12;
679 else oktanote[0] = 0;
681 oktanote[1] = (n->Inst ? (n->Inst-1) : 0);
682 oktanote[2] = Effects[n->EffNum];
684 /* Effect Exceptions */
688 if ((n->EffVal >> 4) == 1) /* Filter On/Off */
689 oktanote[3] = (n->EffVal ? 0 : 1);
693 if (n->EffVal > 0x0F)
696 oktanote[3] = n->EffVal;
700 oktanote[3] = n->EffVal;
704 if (FWrite (fh, oktanote, 4, 1) != 1) return ERROR_IOERR;
711 /* Write Instruments Data */
713 xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_ACTION | XMDMF_USECATALOG,
714 (APTR)MSG_WRITING_INSTDATA, NULL);
716 for (j = 1 ; j <= 36 ; j++)
719 BOOL free_samp = FALSE;
721 /* Skip empty instruments slots */
723 if (!(instr = si->Instr[j]))
725 if (!(l = instr->Length))
728 if (xmDisplayProgress(j, 36))
732 if (instr_mode == OKT_MODE8 || instr_mode == OKT_MODEB)
734 if (samp = AllocVec (l, MEMF_ANY))
737 for (i = 0; i < l; i++)
738 samp[i] = instr->Sample[i] >> 1;
740 free_samp = TRUE; /* Free this when done */
744 xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
745 (APTR)MSG_NO_MEM_TO_HALVE, j);
746 samp = instr->Sample;
749 else samp = instr->Sample;
754 if (FWrite (fh, "SBOD", 4, 1) != 1) return ERROR_IOERR;
755 if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;
760 if (Write (fh, samp, l) != l)
764 if (free_samp) FreeVec (samp);
772 HOOKCALL struct Library * _UserLibInit (REG(a6, struct Library *mybase))
779 HOOKCALL struct Library * _UserLibCleanup (REG(a6, struct Library *mybase))
786 HOOKCALL APTR _GetEngine (REG(a6, struct Library *mybase))
793 HOOKCALL APTR _SetupXMHook (
794 REG(a0, struct XModuleBase *_XModuleBase),
795 REG(a6, struct Library *mybase))
797 if (!(strcmp (_XModuleBase->xm_Library.lib_Node.ln_Name, "xmodule.library")))
799 SysBase = *((struct ExecBase **)4);
800 XModuleBase = _XModuleBase;
801 IntuitionBase = XModuleBase->xm_IntuitionBase;
802 DOSBase = XModuleBase->xm_DOSBase;
805 XMHOOK_Type, NT_XMLOADER,
806 XMHOOK_Name, (LONG)"Oktalyzer",
808 XMHOOK_Descr, (LONG)"Oktalyzer 1.57",
809 XMHOOK_Author, (LONG)"Bernardo Innocenti",
811 XMHOOK_LibraryBase, mybase,
812 XMHOOK_LoadModFunc, LoadOktalyzer,
813 XMHOOK_IdentifyModFunc, IdentifyOktalyzer,
815 XMHOOK_MaxPatterns, 64,
816 XMHOOK_MaxInstruments, 36,
817 XMHOOK_MaxLength, 128,
818 XMHOOK_MaxSampleLen, 131070,
819 XMHOOK_MaxPattLen, 128,
823 XMHOOK_Type, NT_XMSAVER,
824 XMHOOK_Name, (LONG)"Oktalyzer",
826 XMHOOK_Descr, (LONG)"Oktalyzer 1.57",
827 XMHOOK_Author, (LONG)"Bernardo Innocenti",
829 XMHOOK_LibraryBase, mybase,
830 XMHOOK_SaveModFunc, SaveOktalyzer,
832 XMHOOK_MaxPatterns, 64,
833 XMHOOK_MaxInstruments, 36,
834 XMHOOK_MaxLength, 128,
835 XMHOOK_MaxSampleLen, 131070,
836 XMHOOK_MaxPattLen, 128,