Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* NesYar/tools/ftm-exporter.c */
- /* Converts FamiTracker "FTM" files into ca65 sources.
- Based on FamiTracker 0.3.5, http://famitracker.shoodot.net/downloads.php
- Inspired by "Famitone", Copyright 2010, Shiru (nesdev.parodius.com)
- Please see "./source/DocumentFile.cpp" and "./source/FamiTrackerDoc.cpp"
- */
- /*
- ** ftm-exporter
- ** Copyright (C) 2011 Dennis Jenkins
- **
- ** This code is in the public domain. Do as you wish with it, but
- ** don't sue me if it breaks anything. Use at your own risk.
- */
- #if defined(_WIN32)
- #pragma warning (disable: 4786)
- #define _CRT_SECURE_NO_WARNINGS
- #endif
- #include <stddef.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdarg.h>
- #include <string.h>
- #include <assert.h>
- #include <time.h>
- #include <math.h>
- #if defined(_WIN32)
- #define snprintf _snprintf
- #endif
- typedef unsigned char byte;
- typedef unsigned short int word;
- typedef unsigned long dword;
- // FamiTracker specific constants.
- #define BLOCK_ID_SIZE 16
- #define MAX_BLOCK_SIZE 0x80000
- #define MAX_STRINGZ_LEN 256
- #define SEQ_COUNT 5 // FamiTrackerDoc.h, line #126
- #define MAX_SEQUENCES 128 // FamiTrackerDoc.h, line #47
- #define MAX_SEQUENCE_ITEMS 128 // FamiTrackerDoc.h, line #50
- #define MAX_INSTRUMENTS 64 // FamiTrackerDoc.h, line #44
- #define MAX_EFFECT_COLUMNS 4 // FamiTrackerDoc.h, line #58
- #define MAX_CHANNELS 16 // I'm just guessing...
- #define MAX_TUNES 64 // FamiTrackerDoc.h, line #74 (MAX_TRACKS)
- #define MAX_PATTERNS 128 // FamiTrackerDoc.h, line #52
- #define MAX_PATTERN_LENGTH 256 // FamiTrackerDoc.h, line #54
- #define OCTAVE_RANGE 8
- #define FILE_VERSION 0x0420 // FT v 0.3.6-beta4, 2010-12-30.
- static const char FILE_HEADER[] = "FamiTracker Module";
- // My own constants.
- #define MAX_NOTE_NAMES 15
- #define NOTE_MAX_RANGE (12*9)
- #define NES_APU_CHANNELS 5
- // Passed as first argument into "doWarning"
- #define NON_FATAL 0
- #define FATAL 1
- typedef enum eSyntax
- {
- SYNTAX_CA65,
- SYNTAX_NESASM
- } eSyntax;
- typedef enum eTarget
- {
- TARGET_FAMITONE, // Designed for Shiru's Famitone player.
- TARGET_GRADUALORE, // Same output as Gradualore's Famitracker plugin.
- TARGET_ECOLIGAMES, // For my own music player.
- } eTarget;
- typedef enum eInstruments
- {
- INST_2A03 = 1,
- INST_VRC6,
- INST_VRC7,
- INST_FDS,
- INST_N106,
- INST_5B
- } eInstruments;
- typedef enum eSequences
- {
- SEQ_VOLUME,
- SEQ_ARPEGGIO,
- SEQ_PITCH,
- SEQ_HIPITCH,
- SEQ_DUTYCYCLE
- } eSequences;
- typedef enum eEffects
- {
- EF_NONE = 0,
- EF_SPEED, // 'F'
- EF_JUMP, // 'B'
- EF_SKIP, // 'D'
- EF_HALT, // 'C'
- EF_VOLUME, // 'E'
- EF_PORTAMENTO, // '3'
- EF_PORTAOFF, // unused!!
- EF_SWEEPUP, // 'H'
- EF_SWEEPDOWN, // 'I'
- EF_ARPEGGIO, // '0'
- EF_VIBRATO, // '4'
- EF_TREMOLO, // '7'
- EF_PITCH, // 'P'
- EF_DELAY, // 'G'
- EF_DAC, // 'Z'
- EF_PORTA_UP, // '1'
- EF_PORTA_DOWN, // '2'
- EF_DUTY_CYCLE, // 'V'
- EF_SAMPLE_OFFSET, // 'Y'
- EF_SLIDE_UP, // 'Q'
- EF_SLIDE_DOWN, // 'R'
- EF_VOLUME_SLIDE, // 'A'
- EF_NOTE_CUT, // 'S'
- EF_RETRIGGER, // 'X'
- // EF_DELAYED_VOLUME, // 'J'
- EF_COUNT
- } eEffects;
- // Channel effect letters
- static const char EFF_CHAR[] =
- {
- 'F', // Speed
- 'B', // Jump
- 'D', // Skip
- 'C', // Halt
- 'E', // Volume
- '3', // Porta on
- ' ', // Porta off // unused
- 'H', // Sweep up
- 'I', // Sweep down
- '0', // Arpeggio
- '4', // Vibrato
- '7', // Tremolo
- 'P', // Pitch
- 'G', // Note delay
- 'Z', // DAC setting
- '1', // Portamento up
- '2', // Portamento down
- 'V', // Duty cycle
- 'Y', // Sample offset
- 'Q', // Slide up
- 'R', // Slide down
- 'A', // Volume slide
- 'S', // Note cut
- 'X', // DPCM retrigger
- // 'J', // Delayed volume
- };
- typedef enum eNotes
- {
- NOTE_NONE = 0,
- NOTE_C,
- NOTE_Cs,
- NOTE_D,
- NOTE_Ds,
- NOTE_E,
- NOTE_F,
- NOTE_Fs,
- NOTE_G,
- NOTE_Gs,
- NOTE_A,
- NOTE_As,
- NOTE_B,
- NOTE_RELEASE, // Release, note release
- NOTE_HALT, // Halt, note cut (a ***)
- } eNotes;
- typedef enum eMachine
- {
- NTSC,
- PAL
- } eMachine;
- typedef enum eVibrato
- {
- VIBRATO_OLD = 0,
- VIBRATO_NEW,
- } eVibrato;
- typedef struct BLOCK
- {
- char id[BLOCK_ID_SIZE + 1];
- int ver;
- int len;
- size_t file_pos;
- byte *data;
- byte *ptr;
- } BLOCK;
- typedef struct PARAMS
- {
- int expansion_chip; // FTM ver 2 (char)
- int channels_avail;
- int machine;
- int engine_speed;
- int vibrato_style; // FTM ver 3
- } PARAMS;
- typedef struct INFO
- {
- char song_name[33];
- char artist[33];
- char copyright[33];
- } INFO;
- typedef struct HEADER
- {
- int nTuneCount;
- int vChannelTypes[MAX_CHANNELS];
- } HEADER;
- typedef struct INST_SEQ
- {
- int nIndex;
- int nEnabled;
- } INST_SEQ;
- typedef struct INSTRUMENT
- {
- int nUsed;
- int nIndex; // FIXME: We probably don't need this...
- int nType;
- char *pszName;
- INST_SEQ vSequences[SEQ_COUNT];
- } INSTRUMENT;
- typedef struct SEQUENCE
- {
- int nUsed;
- int nIndex;
- int nType;
- int nLoopPoint;
- int nSampleCount;
- byte *vSamples; // 1-D array
- byte nReleasePoint;
- int nSetting;
- } SEQUENCE;
- typedef struct TUNE
- {
- // Read from "FRAME"
- int nIndex;
- int nFrameCount; // Rows of 64 patterns.
- int nSongSpeed;
- int nSongTempo;
- int nPatternLength;
- int *vPatterns; // 2-D array, [frame][channel]
- char *pszName; // From "HEADER".
- // Read from "HEADER"
- int nEffectColumns[MAX_CHANNELS];
- } TUNE;
- typedef struct EFFECT
- {
- byte number;
- byte param;
- } EFFECT;
- typedef struct NOTE
- {
- eNotes note; // Represents frequency of note being played.
- byte octave;
- byte vol;
- byte instrument;
- EFFECT effects[MAX_EFFECT_COLUMNS];
- } NOTE;
- typedef struct PATTERN
- {
- int nTune;
- int nChannel; // sq1, sq2, etc...
- int nIndex;
- int nNotes;
- NOTE notes[MAX_PATTERN_LENGTH];
- } PATTERN;
- typedef struct FTM_FILE
- {
- // Data read from file.
- int file_version;
- PARAMS params;
- INFO info;
- HEADER header;
- INSTRUMENT instruments[MAX_INSTRUMENTS];
- SEQUENCE sequences[SEQ_COUNT][MAX_SEQUENCES]; // [type][nIndex]
- TUNE *tunes[MAX_TUNES]; // Called "Frames" in FTM file.
- PATTERN *patterns[MAX_TUNES][MAX_CHANNELS][MAX_PATTERNS];
- } FTM_FILE;
- typedef struct BLOCK_FUNC
- {
- char *id;
- int (*func)(FTM_FILE *ftm);
- } BLOCK_FUNC;
- // Globals:
- static char symbol_prefix[32];
- static char *segname = NULL;
- static char *ifname = "stdin";
- static FILE *ifp;
- static FILE *ofp;
- static int verbose = 0;
- static int warnings_are_fatal = 0;
- static BLOCK block = {{0}};
- static byte blockdata[MAX_BLOCK_SIZE]; // Leave in BSS, do not initialize.
- static eSyntax syntax = SYNTAX_CA65;
- static eTarget target = TARGET_ECOLIGAMES;
- static int bytes_out = 0;
- static int warnings = 0;
- static void doWarning (int nFatal, int src_line, const FTM_FILE *ftm, const char *fmt, ...)
- {
- va_list va;
- const char *err = nFatal ? "ERROR" : "WARNING";
- fprintf (stderr, "\n%s: ", err);
- va_start (va, fmt);
- vfprintf (stderr, fmt, va);
- fprintf (stderr, "\n");
- va_end (va);
- if (ifname) { fprintf (stderr, " Input file: %s\n", ifname); }
- if (ifp) { fprintf (stderr, " Input byte offset: %ld\n", ftell (ifp)); }
- if (src_line) { fprintf (stderr, " ftm-exporter src line %d:\n", src_line); }
- if (warnings_are_fatal || nFatal)
- {
- exit (-1);
- }
- warnings++;
- }
- static __inline PATTERN* getPattern (FTM_FILE *ftm, int tune, int channel, int pattern)
- {
- PATTERN *p;
- assert (ftm);
- assert (tune >= 0);
- assert (tune < ftm->header.nTuneCount);
- assert (channel >= 0);
- assert (channel < ftm->params.channels_avail);
- assert (pattern >= 0);
- assert (pattern < MAX_PATTERNS);
- if (NULL == (p = ftm->patterns[tune][channel][pattern]))
- {
- p = ftm->patterns[tune][channel][pattern] = (PATTERN*)calloc (1, sizeof(PATTERN));
- p->nTune = tune;
- p->nChannel = channel;
- p->nIndex = pattern;
- }
- return p;
- }
- static __inline int getPatternForTune (const FTM_FILE *ftm, int tune, int frame, int channel)
- {
- assert (ftm);
- assert (tune >= 0);
- assert (tune < ftm->header.nTuneCount);
- assert (ftm->tunes[tune]);
- assert (frame >= 0);
- assert (frame < ftm->tunes[tune]->nFrameCount);
- assert (channel >= 0);
- assert (channel < ftm->params.channels_avail);
- return ftm->tunes[tune]->vPatterns[frame * ftm->params.channels_avail + channel];
- }
- static __inline TUNE* getTune (FTM_FILE *ftm, int tune)
- {
- assert (ftm);
- assert (tune >= 0);
- assert (tune < ftm->header.nTuneCount);
- if (ftm->tunes[tune])
- {
- return ftm->tunes[tune];
- }
- ftm->tunes[tune] = (TUNE*)calloc (1, sizeof(TUNE));
- return ftm->tunes[tune];
- }
- static const char *szNoteNames[12] =
- {
- "C-", "C#", "D-", "D#",
- "E-", "F-", "F#", "G-",
- "G#", "A-", "A#", "B-",
- };
- // "dest" should be 4 bytes or larger!
- static __inline int noteName(char *dest, int destlen, const NOTE *pNote)
- {
- switch (pNote->note)
- {
- case NOTE_NONE:
- return snprintf (dest, destlen, "none");
- case NOTE_RELEASE:
- return snprintf (dest, destlen, "release");
- case NOTE_HALT:
- return snprintf (dest, destlen, "halt");
- default:
- if ((pNote->note >= NOTE_C) && (pNote->note <= NOTE_B))
- {
- return snprintf (dest, destlen, "%2s%d", szNoteNames[pNote->note - NOTE_C], pNote->octave);
- }
- }
- return snprintf (dest, destlen, "unknown[%d,%d]", pNote->note, pNote->octave);
- }
- static const char *szSequenceNames[SEQ_COUNT] =
- {
- "vol", "arp", "pitch", "hipitch", "duty"
- };
- static const float fDutyCycles[4] =
- {
- 12.5, 25.0, 50.0, 75.0
- };
- static const char *szChannelNames[NES_APU_CHANNELS] =
- {
- "sq1", "sq2", "tri", "noise", "dpcm"
- };
- static char blk_char (void)
- {
- char ch = *(char*)(block.ptr);
- block.ptr += sizeof (char);
- return ch;
- }
- static byte blk_byte (void)
- {
- byte by = *(byte*)(block.ptr);
- block.ptr += sizeof (byte);
- return by;
- }
- static int blk_int (void)
- {
- int i = *(int*)(block.ptr);
- block.ptr += sizeof (int);
- return i;
- }
- // "dest" MUST be "len+1" characters.
- static char* blk_stringf (char *dest, int len)
- {
- strncpy (dest, (const char*)block.ptr, len);
- dest[len] = 0;
- block.ptr += len;
- return dest;
- }
- // zero terminated string.
- static char* blk_stringz (char *dest, int maxlen)
- {
- block.ptr += snprintf (dest, maxlen, "%s", (const char*)block.ptr);
- block.ptr++; // Skip 'NUL' at end of string.
- return dest;
- }
- static int read_params (FTM_FILE *ftm)
- {
- ftm->params.expansion_chip = blk_char (); // Read as char, stored as int.
- ftm->params.channels_avail = blk_int ();
- ftm->params.machine = blk_int ();
- ftm->params.engine_speed = blk_int ();
- ftm->params.vibrato_style = 0;
- if (block.ver >= 3)
- {
- ftm->params.vibrato_style = blk_int();
- }
- if (ftm->params.channels_avail >= MAX_CHANNELS)
- {
- doWarning (FATAL, __LINE__, ftm, "params.channels_avail (%d) >= MAX_CHANNELS (%d).", ftm->params.channels_avail, MAX_CHANNELS);
- return -1;
- }
- return 0;
- }
- static int read_info (FTM_FILE *ftm)
- {
- blk_stringf (ftm->info.song_name, 32);
- blk_stringf (ftm->info.artist, 32);
- blk_stringf (ftm->info.copyright, 32);
- return 0;
- }
- static int read_header (FTM_FILE *ftm)
- {
- int tune;
- int chan;
- char name[MAX_STRINGZ_LEN + 1];
- // FamiTracker stores "tunes" minus one in the disk file.
- // I hate doing math that way in for loops, so I bump the value by one.
- ftm->header.nTuneCount = 1 + blk_char(); // CFamiTrackerDoc::WriteBlock_Header, line #483.
- for (tune = 0; tune < ftm->header.nTuneCount; tune++)
- {
- if (block.ver >= 3)
- {
- blk_stringz (name, sizeof(name));
- }
- else
- {
- name[0] = 0;
- }
- getTune (ftm, tune)->pszName = strdup (name);
- }
- for (chan = 0; chan < ftm->params.channels_avail; chan++)
- {
- ftm->header.vChannelTypes[chan] = blk_char(); // Read as char, stored as int.
- for (tune = 0; tune < ftm->header.nTuneCount; tune++)
- {
- getTune (ftm, tune)->nEffectColumns[chan] = blk_char ();
- }
- }
- return 0;
- }
- static int read_instruments (FTM_FILE *ftm)
- {
- INSTRUMENT *p = NULL;
- int nCount = 0;
- int nIndex = 0;
- int nSeqCount = 0;
- int nOctaves = 0;
- int index = 0;
- int seq = 0;
- int octave = 0;
- int namelen = 0;
- int j = 0;
- if (block.ver != 2)
- {
- doWarning (FATAL, __LINE__, ftm, "INSTRUMENT block version (%d) != 2.", block.ver);
- return -1;
- }
- nCount = blk_int (); // Line #254.
- if (nCount >= MAX_INSTRUMENTS)
- {
- doWarning (FATAL, __LINE__, ftm, "Too many instruments (%d). Max = %d.", nCount, MAX_INSTRUMENTS);
- return -1;
- }
- for (index = 0; index < nCount; index++)
- {
- nIndex = blk_int();
- if (nIndex != index)
- {
- doWarning (NON_FATAL, __LINE__, ftm, "Instrument '%d' is out of order, expected '%d'.", nIndex, index);
- }
- p = ftm->instruments + nIndex;
- p->nUsed = 1;
- p->nIndex = nIndex;
- p->nType = blk_char();
- // Read vSequences().
- // bool CInstrument2A03::Load(CDocumentFile *pDocFile), Line #85
- nSeqCount = blk_int();
- if (nSeqCount > SEQ_COUNT)
- {
- doWarning (FATAL, __LINE__, ftm, "Instrument '%d' has too many sequences (%d), max = %d.", nIndex, nSeqCount, SEQ_COUNT);
- return -1;
- }
- for (seq = 0; seq < nSeqCount; seq++)
- {
- p->vSequences[seq].nEnabled = blk_char();
- p->vSequences[seq].nIndex = blk_char();
- if (p->vSequences[seq].nIndex > MAX_SEQUENCES)
- {
- doWarning (FATAL, __LINE__, ftm, "Instrument '%d' references out-of-range sequence %d.", nIndex, seq);
- return -1;
- }
- }
- // Eat and discard the "octaves".
- nOctaves = (block.ver == 1) ? 6 : OCTAVE_RANGE; // Line #102.
- for (octave = 0; octave < nOctaves; octave++)
- {
- for (j = 0; j < 12; j++)
- {
- blk_char(); // index
- blk_char(); // pitch
- }
- }
- if (0 < (namelen = blk_int()))
- {
- p->pszName = (char*)malloc (namelen + 1);
- blk_stringf (p->pszName, namelen);
- }
- }
- return 0;
- }
- // void CFamiTrackerDoc::WriteBlock_Sequences(CDocumentFile *pDocFile)
- // bool CFamiTrackerDoc::ReadBlock_Sequences(CDocumentFile *pDocFile), Line #1363
- static int read_sequences (FTM_FILE *ftm)
- {
- SEQUENCE *p = NULL;
- int nSeqCount = 0;
- int seq = 0;
- int nIndex = 0;
- int nType = 0;
- int i = 0;
- int map[MAX_SEQUENCES] = {0};
- if (block.ver < 3)
- {
- doWarning (FATAL, __LINE__, ftm, "Unable to read SEQEUNCES version %d (want >= 3).", block.ver);
- return -1;
- }
- nSeqCount = blk_int (); // Line #524
- if (nSeqCount >= MAX_SEQUENCES)
- {
- doWarning (FATAL, __LINE__, ftm, "Too many sequences (%d), max = %d.", nSeqCount, MAX_SEQUENCES);
- return -1;
- }
- for (seq = 0; seq < nSeqCount; seq++)
- {
- nIndex = blk_int ();
- if (nIndex > MAX_SEQUENCES)
- {
- doWarning (FATAL, __LINE__, ftm, "Sequence '%d' is out of range (%d).", nIndex, MAX_SEQUENCES);
- return -1;
- }
- nType = blk_int();
- if (nType > SEQ_COUNT)
- {
- doWarning (FATAL, __LINE__, ftm, "Sequence '%d' type (%d) is out of range.", nIndex, nType);
- return -1;
- }
- p = &(ftm->sequences[nType][nIndex]);
- p->nUsed = 1;
- p->nIndex = nIndex;
- p->nType = nType;
- p->nSampleCount = blk_byte ();
- p->nLoopPoint = blk_int ();
- map[nIndex] = seq;
- if (p->nSampleCount >= MAX_SEQUENCE_ITEMS)
- {
- doWarning (FATAL, __LINE__, ftm, "Sequence '%d' is too long.", nIndex);
- return -1;
- }
- if (block.ver == 4) // FamiTrackerDoc.cpp, line #1433.
- {
- p->nReleasePoint = blk_int ();
- p->nSetting = blk_int ();
- }
- p->vSamples = (byte*)calloc (p->nSampleCount, sizeof (p->vSamples[0]));
- for (i = 0; i < p->nSampleCount; i++)
- {
- p->vSamples[i] = blk_byte ();
- }
- }
- // FIXME: This code is WRONG. See FamiTrackerDoc.cpp, line #1446.
- // "m_Sequences2A03[][] is two dimensional, yet my crap above is one-dimensional...
- if (block.ver >= 5)
- {
- for (i = 0; i < nSeqCount; i++)
- {
- // ftm->sequences[map[i]].nReleasePoint = blk_int ();
- // ftm->sequences[map[i]].nSetting = blk_int ();
- blk_int ();
- blk_int ();
- }
- }
- return 0;
- }
- static int read_frames (FTM_FILE *ftm)
- {
- int tune = 0;
- TUNE *s = NULL;
- int nPatterns = 0;
- int i = 0;
- if (block.ver != 3)
- {
- doWarning (FATAL, __LINE__, ftm, "Unable to load FRAMES version %d, expected %d.", block.ver, 3);
- return -1;
- }
- for (tune = 0; tune < ftm->header.nTuneCount; tune++)
- {
- s = getTune (ftm, tune);
- s->nFrameCount = blk_int (); // FamiTrackerDoc.cpp, line #679.
- s->nSongSpeed = blk_int ();
- s->nSongTempo = blk_int ();
- s->nPatternLength = blk_int ();
- s->nIndex = tune;
- nPatterns = s->nFrameCount * ftm->params.channels_avail;
- s->vPatterns = (int*)calloc (nPatterns, sizeof (s->vPatterns[0]));
- for (i = 0; i < nPatterns; i++)
- {
- // 2-D array, [frame][channel]
- s->vPatterns[i] = blk_char (); // Converting from char to int.
- }
- }
- return 0;
- }
- // bool CFamiTrackerDoc::ReadBlock_Patterns(CDocumentFile *pDocFile)
- // FamiTrackerDoc.cpp, line #1610.
- static int read_patterns (FTM_FILE *ftm)
- {
- int nTune = 0;
- int nChannel = 0;
- int nPattern = 0;
- int nItems = 0;
- int item = 0; // Loop variable.
- int nItem = 0; // Read from FTM file.
- int effect = 0; // Loop variable.
- NOTE *pNote = NULL;
- PATTERN *pPattern = NULL;
- if (block.ver != 4)
- {
- doWarning (FATAL, __LINE__, ftm, "Unable to load PATTERNS version %d, expected %d.", block.ver, 4);
- return -1;
- }
- // File does not encode how many to read. We just read until we hit end of the block.
- while (block.ptr < block.data + block.len) // Line #1622
- {
- nTune = blk_int (); // FT calls this a "track".
- nChannel = blk_int ();
- nPattern = blk_int ();
- nItems = blk_int ();
- if (nTune >= MAX_TUNES)
- {
- doWarning (FATAL, __LINE__, ftm, "Pattern.track (%d) is out of range (%d).", nTune, MAX_TUNES);
- return -1;
- }
- if (nChannel >= MAX_CHANNELS)
- {
- doWarning (FATAL, __LINE__, ftm, "Pattern.channel (%d) is out of range (%d).", nChannel, MAX_CHANNELS);
- return -1;
- }
- if (nPattern >= MAX_PATTERNS)
- {
- doWarning (FATAL, __LINE__, ftm, "Pattern.index (%d) is out of range (%d).", nPattern, MAX_PATTERNS);
- return -1;
- }
- if ((nItems - 1) >= MAX_PATTERN_LENGTH)
- {
- doWarning (FATAL, __LINE__, ftm, "Pattern.length (%d) is out of range (%d).", nItems, MAX_PATTERN_LENGTH);
- return -1;
- }
- pPattern = getPattern (ftm, nTune, nChannel, nPattern);
- for (item = 0; item < nItems; item++)
- {
- nItem = blk_int(); // Line #1643 (min file ver > 0x0200, so this is ok).
- if (nItem >= MAX_PATTERN_LENGTH)
- {
- doWarning (FATAL, __LINE__, ftm, "Pattern[%d,%d,%d] contains item %d, out of range (%d).",
- nTune, nChannel, nPattern, nItem, MAX_PATTERN_LENGTH);
- return -1;
- }
- pPattern->nNotes++;
- pNote = pPattern->notes + nItem;
- pNote->note = blk_byte (); // Line #1650
- pNote->octave = blk_byte ();
- pNote->instrument = blk_byte ();
- pNote->vol = blk_byte ();
- // 2011-04-01, Instruments == MAX_INSTRUMENTS mean "note cut".
- if (pNote->instrument > MAX_INSTRUMENTS)
- {
- doWarning (FATAL, __LINE__, ftm, "Note's instrument is %d (> %d).", pNote->instrument, MAX_INSTRUMENTS);
- }
- // Read effects (line #1676)
- for (effect = 0; effect < getTune (ftm, nTune)->nEffectColumns[nChannel] + 1; effect++)
- {
- pNote->effects[effect].number = blk_byte ();
- pNote->effects[effect].param = blk_byte ();
- if (block.ver < 3)
- {
- if (pNote->effects[effect].number == EF_PORTAOFF)
- {
- pNote->effects[effect].number = EF_PORTAMENTO;
- pNote->effects[effect].param = 0;
- }
- else if (pNote->effects[effect].number == EF_PORTAMENTO)
- {
- if (pNote->effects[effect].param < 0xff)
- {
- pNote->effects[effect].param++;
- }
- }
- }
- }
- }
- }
- return 0;
- }
- static int read_dpcm_samples (FTM_FILE *ftm)
- {
- return 0;
- }
- // Returns # bytes read, or -1 on error (its an error to fail to read nBytes).
- static int ftm_read (FTM_FILE *ftm, void *pBuffer, size_t nBytes)
- {
- size_t nPos = 0;
- size_t nRead = 0;
- nPos = ftell (ifp);
- if (nBytes != (nRead = fread (pBuffer, 1, nBytes, ifp)))
- {
- doWarning (FATAL, __LINE__, ftm, "Failed to read %d bytes at offset %d.", (int)nBytes, (int)nPos);
- return -1;
- }
- return nRead;
- }
- // Returns '1' if block is read ok, '0' on normal EOF, '-1' on error.
- static int blockRead (FTM_FILE *ftm)
- {
- // 16-byte "Block Id" (string) -OR- 3 byte "END"
- // 4-byte "Block version" (int, 1-3)
- // 4-byte "Block size" (int)
- // NN-byte "Block data" (size = "Block Size")
- size_t nRead = 0;
- memset (blockdata, 0, sizeof(blockdata));
- block.ptr = block.data = blockdata;
- block.file_pos = ftell (ifp);
- if (BLOCK_ID_SIZE != (nRead = fread (block.id, 1, BLOCK_ID_SIZE, ifp)))
- {
- if ((nRead == 3) && !strncmp (block.id, "END", 3))
- {
- return 0; // Normal EOF detected.
- }
- doWarning (FATAL, __LINE__, ftm, "Failed to read %d bytes at offset %d.", BLOCK_ID_SIZE, (int)block.file_pos);
- return -1; // Error.
- }
- // Terminate string (makes printing block name easier).
- block.id[BLOCK_ID_SIZE] = 0;
- if (-1 == ftm_read (ftm, &block.ver, 4))
- {
- return -1;
- }
- if (-1 == ftm_read (ftm, &block.len, 4))
- {
- return -1;
- }
- if (-1 == ftm_read (ftm, blockdata, block.len))
- {
- return -1;
- }
- if (verbose)
- {
- fprintf (stderr, "BLOCK: '%s', ver %d, len %d.\n", block.id, block.ver, block.len);
- }
- return 1;
- }
- // Returns '0' on success, '-1' on fatal error.
- static int verifyHeader (FTM_FILE *ftm)
- {
- char hdr[18];
- int ver = 0;
- // File begins with a NON-terminated 18-character string.
- if (-1 == ftm_read (ftm, hdr, 18))
- {
- return -1;
- }
- if (strncmp (hdr, FILE_HEADER, strlen (FILE_HEADER)))
- {
- doWarning (FATAL, __LINE__, ftm, "Invalid file header.");
- return -1;
- }
- // Followed by a 4-byte version number.
- if (-1 == ftm_read (ftm, &ver, 4))
- {
- return -1;
- }
- if (ver != FILE_VERSION)
- {
- doWarning (FATAL, __LINE__, ftm, "File version %d, expected %d.", ver, FILE_VERSION);
- return -1;
- }
- ftm->file_version = ver;
- return 0;
- }
- static BLOCK_FUNC block_func[] =
- {
- { "PARAMS", read_params },
- { "INFO", read_info },
- { "HEADER", read_header },
- { "INSTRUMENTS", read_instruments },
- { "SEQUENCES", read_sequences },
- { "FRAMES", read_frames },
- { "PATTERNS", read_patterns },
- { "DPCM SAMPLES", read_dpcm_samples },
- { NULL, NULL }
- };
- static int import (FTM_FILE *ftm)
- {
- int nResult = 0;
- BLOCK_FUNC *pHandler = NULL;
- if (-1 == verifyHeader (ftm))
- {
- return -1;
- }
- while (0 < (nResult = blockRead (ftm)))
- {
- for (pHandler = block_func; pHandler->id; ++pHandler)
- {
- if (!strcmp (pHandler->id, block.id))
- {
- if (-1 == pHandler->func (ftm))
- {
- return -1;
- }
- if (block.ptr > (block.data + block.len))
- {
- doWarning (FATAL, __LINE__, ftm, "Read past end of block '%s'.", block.id);
- return -1;
- }
- break;
- }
- }
- if (!pHandler->id)
- {
- doWarning (NON_FATAL, __LINE__, ftm, "No handler for block type '%s'.", block.id);
- }
- }
- return nResult;
- }
- static int export_famitone_nesasm (const FTM_FILE *ftm)
- {
- int i;
- int env;
- int out_size = 0; // Count of bytes emitted.
- const SEQUENCE *seq = NULL;
- fprintf (ofp, "\n%smodule\n", symbol_prefix);
- fprintf (ofp, "\t.dw ");
- for (i = 0; i < ftm->params.channels_avail; i++)
- {
- fprintf (ofp, ".chn%d,", i);
- out_size += 2;
- }
- fprintf (ofp, ".ins\n");
- out_size += 2;
- fprintf (ofp, "\t.db $%02x\n", ftm->params.engine_speed);
- out_size++;
- fprintf (ofp,".env_default\n\t.db $c0,$7f,$00\n");
- out_size += 3;
- for (env = 0; env < MAX_SEQUENCES; env++)
- {
- if (NULL == (seq = ftm->sequences[env]))
- {
- continue;
- }
- if (!seq->nUsed)
- {
- continue;
- }
- fprintf (ofp, ".env_%s%d\n", szSequenceNames[seq->nType], seq->nIndex);
- }
- fprintf (ofp, "#ERROR - Unfinished export routine.\n\n");
- return 0;
- }
- static void emit_byte_stream (const byte *pBuffer, int nCount)
- {
- int i;
- for (i = 0; i < nCount; i++)
- {
- if (!(i % 8))
- {
- fprintf (ofp, "\n\t.byte\t");
- }
- else if (i)
- {
- fprintf (ofp, ", ");
- }
- fprintf (ofp, "$%02x", *pBuffer);
- pBuffer++;
- bytes_out++;
- }
- fprintf (ofp, "\n");
- }
- // Same format as Famitone:
- // <127 is a number of repeats of previous output value
- // 127 is end of an envelope, next byte is new offset in envelope
- // 128..255 is output value + 192 (it is in -64..63 range)
- // Envelopes can't be longer than 255 bytes
- static void emit_sequence_ca65 (const SEQUENCE *seq)
- {
- int i;
- int samples = seq->nSampleCount;
- byte loop_point = seq->nLoopPoint;
- byte buffer[MAX_SEQUENCE_ITEMS + 2];
- byte *p = NULL;
- if (seq->nType == SEQ_DUTYCYCLE)
- {
- samples = 1;
- }
- else if (samples > MAX_SEQUENCE_ITEMS)
- {
- samples = MAX_SEQUENCE_ITEMS;
- }
- // If no loop-point is set, repeat last note forever?
- if (seq->nLoopPoint < 0)
- {
- loop_point = seq->nSampleCount - 1;
- }
- for (i = 0, p = buffer; i < samples; i++, p++)
- {
- *p = seq->vSamples[i] + 0xc0;
- }
- *(p++) = 0x7f; // Terminate envelope token.
- *(p++) = loop_point;
- fprintf (ofp, "\n%senv_%s%d:", symbol_prefix, szSequenceNames[seq->nType], seq->nIndex);
- emit_byte_stream (buffer, p - buffer);
- }
- static const byte env_default[3] = { 0xc0, 0x7f, 0x00 };
- static void emit_sequences_ca65 (const FTM_FILE *ftm)
- {
- int seq_type = 0;
- int seq_idx = 0;
- fprintf (ofp, "\n%senv_default:", symbol_prefix);
- emit_byte_stream (env_default, sizeof(env_default));
- for (seq_type = 0; seq_type < SEQ_COUNT; seq_type++)
- {
- for (seq_idx = 0; seq_idx < MAX_SEQUENCES; seq_idx++)
- {
- const SEQUENCE *seq = &(ftm->sequences[seq_type][seq_idx]);
- if (!seq->nUsed) continue;
- if (!seq->nSampleCount) continue;
- if ((seq->nType == SEQ_PITCH) || (seq->nType == SEQ_HIPITCH))
- {
- doWarning (NON_FATAL, __LINE__, ftm, "Sequence ignored: %d, %s.", seq->nIndex, szSequenceNames[seq->nType]);
- continue;
- }
- if ((seq->nType == SEQ_DUTYCYCLE) && (seq->nSampleCount > 1))
- {
- doWarning (NON_FATAL, __LINE__, ftm, "Duty-Cycle sequence length error: (seq %d, len %d).", seq->nIndex, seq->nSampleCount);
- // Emit anyway, for debugging.
- }
- if ((seq->nType == SEQ_DUTYCYCLE) && (seq->nSampleCount <= 1))
- {
- continue;
- }
- emit_sequence_ca65 (seq);
- }
- }
- fprintf (ofp, "\n");
- }
- static void emit_instru_half_ptr (const FTM_FILE *ftm, eSequences seq_type, int _msb)
- {
- const INSTRUMENT *ins = NULL;
- const char *oper = _msb ? ">" : "<";
- fprintf (ofp, "\n%sinstruments_%s_%s:\n", symbol_prefix, szSequenceNames[seq_type], _msb ? "msb" : "lsb");
- for (ins = ftm->instruments; ins < ftm->instruments + MAX_INSTRUMENTS; ins++)
- {
- if (!ins->nUsed) continue;
- fprintf (ofp, "\t.byte\t");
- if (ins->vSequences[seq_type].nEnabled)
- {
- fprintf (ofp, "%s%senv_%s%d", oper, symbol_prefix, szSequenceNames[seq_type], ins->vSequences[seq_type].nIndex);
- }
- else
- {
- fprintf (ofp, "%s%senv_default", oper, symbol_prefix);
- }
- bytes_out++;
- fprintf (ofp, "\t;; %02x, %s\n", ins->nIndex, ins->pszName);
- }
- }
- static void emit_instru_dutycycle (const FTM_FILE *ftm)
- {
- const INSTRUMENT *ins;
- byte duty;
- fprintf (ofp, "\n%sinstruments_dutycycle:\n", symbol_prefix);
- for (ins = ftm->instruments; ins < ftm->instruments + MAX_INSTRUMENTS; ins++)
- {
- if (!ins->nUsed) continue;
- duty = 0;
- if (ins->vSequences[SEQ_DUTYCYCLE].nEnabled)
- {
- const SEQUENCE *seq = ftm->sequences[SEQ_DUTYCYCLE] + ins->vSequences[SEQ_DUTYCYCLE].nIndex;
- if (seq && seq->nSampleCount)
- {
- duty = seq->vSamples[0];
- }
- }
- fprintf (ofp, "\t.byte\t$%02x\t\t;; (%5.2f%%) %02x, %s\n", duty, fDutyCycles[duty], ins->nIndex, ins->pszName);
- bytes_out++;
- }
- }
- static void emit_instruments_ca65 (const FTM_FILE *ftm)
- {
- emit_instru_half_ptr (ftm, SEQ_VOLUME, 0);
- emit_instru_half_ptr (ftm, SEQ_VOLUME, 1);
- emit_instru_half_ptr (ftm, SEQ_ARPEGGIO, 0);
- emit_instru_half_ptr (ftm, SEQ_ARPEGGIO, 1);
- emit_instru_dutycycle (ftm);
- fprintf (ofp, "\n");
- }
- /* Famitone channel stream format:
- %00nnnnnn is a note (0..59 are octaves 1-5, 63 note stop)
- %01iiiiii is an instrument number (0 is default, silence)
- %10rrrrrr is a empty rows (up to 63)
- %11eeeeee is a special tag or effect
- eeeeee $01..19 speed
- %11111110 is end of the stream, two next bytes are new pointer
- %11111111 is a reference (next two bytes of absolute address, and number of rows)
- No octaves on Noise channel, it is always 0..15;
- */
- /* My stream format:
- $00 - $6f 112 (0 to 111) = Note (C-0 to B-8, 9 octaves, 12 notes each)
- $70 - $7f 16 (112 to 127) = RLE empty row (count = n - $6f)
- $80 - $bf 64 (128 to 191) = Select instrument
- $c0 - $fd 62 (192 to 253) = Reserved
- $fe 1 (254) = End of stream. Next two bytes are ptr to new stream.
- $ff 1 (255) = Jmp to new ptr (next two bytes are ptr).
- */
- static void emit_stream_byte (int row, byte value, const char *c1, const char *c2)
- {
- fprintf (ofp, "\t.byte\t$%02x\t\t; [%02x]\t%-14.14s\t%s\n", value, row, c1, c2);
- bytes_out++;
- }
- static void emit_rle_silence_ca65 (int row, byte count)
- {
- char c2[16] = "";
- byte value = 0;
- byte rle = 0;
- while (count > 0)
- {
- value = (count > 16) ? 16 : count;
- rle = (value - 1) + 112;
- snprintf (c2, sizeof(c2), "%d rows", value);
- emit_stream_byte (row, rle, "empty row", c2);
- count -= value;
- }
- }
- static void emit_note_ca65 (int row, const NOTE *pNote)
- {
- char c2[16] = "";
- byte note = ((pNote->note - 1) + pNote->octave * 12);
- noteName (c2, sizeof(c2), pNote);
- emit_stream_byte (row, note, "note", c2);
- }
- static void emit_set_instrument_ca65 (const FTM_FILE *ftm, int row, byte instrument)
- {
- emit_stream_byte (row, (byte)(instrument + 128), "instrument", ftm->instruments[instrument].pszName);
- }
- static void emit_channel_ca65 (const FTM_FILE *ftm, const PATTERN *pat)
- {
- const NOTE *pNote = pat->notes;
- const NOTE *pEnd = pat->notes + MAX_PATTERN_LENGTH;
- int nNotes = pat->nNotes;
- char chan_name[128];
- byte prev_instru = (byte)-1;
- byte silence = 0;
- int effidx = 0;
- snprintf (chan_name, sizeof(chan_name), "%schan_%d_%d_%d", symbol_prefix, pat->nTune, pat->nChannel, pat->nIndex);
- fprintf (ofp, "\n%s:\t\t\t; '%s', '%s', %d\n", chan_name, ftm->tunes[pat->nTune]->pszName,
- szChannelNames[pat->nChannel], pat->nIndex);
- fprintf (ofp, "%s_loop:\n", chan_name);
- for (pNote = pat->notes; nNotes && (pNote < pEnd); pNote++)
- {
- for (effidx = 0; effidx < MAX_EFFECT_COLUMNS; effidx++)
- {
- if (pNote->effects[effidx].number == EF_HALT)
- {
- goto channel_done;
- }
- }
- if (!pNote->note)
- {
- silence++;
- continue;
- }
- if (silence)
- {
- emit_rle_silence_ca65 ((pNote - pat->notes) - silence, silence);
- silence = 0;
- }
- if (pNote->instrument == MAX_INSTRUMENTS) // "Note cut"
- {
- // Not a real instrument. Don't change active instrument.
- emit_note_ca65 (pNote - pat->notes, pNote);
- nNotes--;
- continue;
- }
- if (pNote->instrument >= MAX_INSTRUMENTS)
- {
- fprintf (ofp, ";; ERROR: pNote->instrument = %d, skipping note.\n", pNote->instrument);
- continue;
- }
- if (pNote->instrument != prev_instru)
- {
- if (ftm->instruments[pNote->instrument].nUsed)
- {
- emit_set_instrument_ca65 (ftm, pNote - pat->notes, pNote->instrument);
- }
- else
- {
- doWarning (FATAL, __LINE__, ftm, "Channel '%s' references non-existant instrument '%d'.",
- chan_name, pNote->instrument);
- }
- prev_instru = pNote->instrument;
- }
- emit_note_ca65 (pNote - pat->notes, pNote);
- nNotes--;
- }
- channel_done:
- if (silence)
- {
- emit_rle_silence_ca65 ((pNote - pat->notes) - silence, silence);
- silence = 0;
- }
- fprintf (ofp, "\t.byte\t$%02x\t\t; End stream code.\n", 0xfe);
- fprintf (ofp, "\t.word\t%s_loop\n", chan_name);
- bytes_out += 3;
- }
- static void emit_channels_ca65 (const FTM_FILE *ftm)
- {
- const PATTERN *pat;
- int tune;
- int chan;
- int npat;
- for (tune = 0; tune < ftm->header.nTuneCount; tune++)
- {
- for (chan = 0; chan < ftm->params.channels_avail; chan++)
- {
- for (npat = 0; npat < MAX_PATTERNS; npat++)
- {
- if (NULL == (pat = ftm->patterns[tune][chan][npat]))
- {
- continue;
- }
- if (!ftm->tunes[pat->nTune])
- {
- doWarning (NON_FATAL, __LINE__, ftm, "pattern(%d,%d,%d) referse to a tune that does not exist (%d).",
- tune, chan, npat, tune);
- continue;
- }
- emit_channel_ca65 (ftm, pat);
- }
- }
- }
- }
- static const double CLOCK_NTSC = 21477277 / 12;
- static const double CLOCK_PAL = 26601712 / 16;
- // http://nesdev.parodius.com/bbs/viewtopic.php?p=22795#22795
- static void emit_freq_table (const FTM_FILE *ftm)
- {
- const double clock = (ftm->params.machine == NTSC) ? CLOCK_NTSC : CLOCK_PAL;
- const double base_freq = 32.70;
- int period_table[NOTE_MAX_RANGE];
- byte hi_table[NOTE_MAX_RANGE];
- byte lo_table[NOTE_MAX_RANGE];
- int i;
- char nn[32];
- for (i = 0; i < NOTE_MAX_RANGE; i++)
- {
- // Subtract ONE from each period (makes output match Shiru's table in Famitone).
- period_table[i] = (int)((clock / 16.0) / (base_freq * pow (2, (double)i / 12.0)) - 1);
- hi_table[i] = period_table[i] >> 8;
- lo_table[i] = period_table[i] & 0xff;
- }
- if (verbose)
- {
- fprintf (ofp, "\n;; APU period register table (11 bits)\n\n");
- for (i = 0; i < NOTE_MAX_RANGE; i++)
- {
- NOTE note = {0};
- note.note = (eNotes)(i % 12) + 1;
- note.octave = i / 12;
- noteName (nn, sizeof(nn), ¬e);
- fprintf (ofp, ";\t%3d:\t%s\t%d\t$%04x\n", i, nn, period_table[i], period_table[i]);
- }
- }
- fprintf (ofp, "\n.align\t\t%d\n", 256);
- fprintf (ofp, ".export\t\tapu_period_table_lo\n");
- fprintf (ofp, "apu_period_table_lo:");
- emit_byte_stream (lo_table, NOTE_MAX_RANGE);
- fprintf (ofp, "\n.export\t\tapu_period_table_hi\n");
- fprintf (ofp, "apu_period_table_hi:");
- emit_byte_stream (hi_table, NOTE_MAX_RANGE);
- }
- static int export_ecoligames_ca65 (const FTM_FILE *ftm)
- {
- time_t now = time(NULL);
- struct tm *tm = localtime(&now);
- char temp[256];
- strftime (temp, sizeof(temp), "%Y-%m-%d %H:%M:%S", tm);
- fprintf (ofp, ";; Source: \t%s\n", ifname);
- fprintf (ofp, ";; Converted: \t%s\n\n", temp);
- fprintf (ofp, ";; Title: \t%s\n", ftm->info.song_name);
- fprintf (ofp, ";; Artist: \t%s\n", ftm->info.artist);
- fprintf (ofp, ";; Copyright: \t%s\n\n", ftm->info.copyright);
- if (segname && strlen(segname))
- {
- fprintf (ofp, ".segment\t\t\"%s\"\n\n", segname);
- }
- emit_sequences_ca65 (ftm);
- emit_instruments_ca65 (ftm);
- emit_channels_ca65 (ftm);
- emit_freq_table (ftm);
- fprintf (ofp, "\n\n;; Size: %d bytes\n", bytes_out);
- return 0;
- }
- int main (int argc, char *argv[])
- {
- int i = 0;
- int ret_code = 0;
- FTM_FILE ftm = {0};
- strcpy (symbol_prefix, "snd_");
- ofp = stdout;
- bytes_out = 0;
- for (i = 1; i < argc; i++)
- {
- if (!strcmp (argv[i], "-v"))
- {
- verbose++;
- }
- else if (!strcmp (argv[i], "-p"))
- {
- snprintf (symbol_prefix, sizeof(symbol_prefix), "%s_", argv[++i]);
- }
- else if (!strcmp (argv[i], "-s"))
- {
- segname = argv[++i];
- }
- else if (!strcmp (argv[i], "-w"))
- {
- warnings_are_fatal = 1;
- }
- else if (!strcmp (argv[i], "-famitone"))
- {
- target = TARGET_FAMITONE;
- }
- else if (!strcmp (argv[i], "-nesasm"))
- {
- syntax = SYNTAX_NESASM;
- }
- else if (!strcmp (argv[i], "-ca65"))
- {
- syntax = SYNTAX_CA65;
- }
- else if (argv[i][0] == '-')
- {
- fprintf (stderr, "ERROR: Unrecognized option: '%s'\n", argv[i]);
- }
- else
- {
- ifname = argv[i];
- if (NULL == (ifp = fopen (argv[i], "rb")))
- {
- fprintf (stderr, "ERROR: Failed to open '%s' for reading.\n", argv[i]);
- perror ("fopen");
- exit (-1);
- }
- }
- }
- if (ifp)
- {
- if (-1 == import (&ftm))
- {
- exit (-1);
- }
- if (ifp != stdin) fclose (ifp);
- ifp = NULL;
- }
- if (target == TARGET_FAMITONE)
- {
- if (syntax == SYNTAX_NESASM)
- {
- ret_code = export_famitone_nesasm (&ftm);
- }
- }
- else if (target == TARGET_ECOLIGAMES)
- {
- if (syntax == SYNTAX_CA65)
- {
- ret_code = export_ecoligames_ca65 (&ftm);
- }
- }
- if (warnings)
- {
- fprintf (stderr, "There were %d warnings.\n", warnings);
- }
- return ret_code;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement