1 /*
2  * Schism Tracker - a cross-platform Impulse Tracker clone
3  * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
4  * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
5  * copyright (c) 2009 Storlek & Mrs. Brisby
6  * copyright (c) 2010-2012 Storlek
7  * URL: http://schismtracker.org/
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #define NEED_BYTESWAP
25 #include "headers.h"
26 #include "slurp.h"
27 #include "fmt.h"
28 
29 #include "sndfile.h"
30 
31 /* --------------------------------------------------------------------- */
32 
fmt_far_read_info(dmoz_file_t * file,const uint8_t * data,size_t length)33 int fmt_far_read_info(dmoz_file_t *file, const uint8_t *data, size_t length)
34 {
35 	/* The magic for this format is truly weird (which I suppose is good, as the chance of it
36 	being "accidentally" correct is pretty low) */
37 	if (!(length > 47 && memcmp(data + 44, "\x0d\x0a\x1a", 3) == 0 && memcmp(data, "FAR\xfe", 4) == 0))
38 		return 0;
39 
40 	file->description = "Farandole Module";
41 	/*file->extension = str_dup("far");*/
42 	file->title = strn_dup((const char *)data + 4, 40);
43 	file->type = TYPE_MODULE_S3M;
44 	return 1;
45 }
46 
47 /* --------------------------------------------------------------------------------------------------------- */
48 /* This loader sucks. Mostly it was implemented based on what Modplug does, which is
49 kind of counterproductive, but I can't get Farandole to run in Dosbox to test stuff */
50 
51 #pragma pack(push, 1)
52 struct far_header {
53 	uint8_t magic[4];
54 	char title[40];
55 	uint8_t eof[3];
56 	uint16_t header_len;
57 	uint8_t version;
58 	uint8_t onoff[16];
59 	uint8_t editing_state[9]; // stuff we don't care about
60 	uint8_t default_speed;
61 	uint8_t chn_panning[16];
62 	uint8_t pattern_state[4]; // more stuff we don't care about
63 	uint16_t message_len;
64 };
65 
66 struct far_sample {
67 	char name[32];
68 	uint32_t length;
69 	uint8_t finetune;
70 	uint8_t volume;
71 	uint32_t loopstart;
72 	uint32_t loopend;
73 	uint8_t type;
74 	uint8_t loop;
75 };
76 #pragma pack(pop)
77 
78 static uint8_t far_effects[] = {
79 	FX_NONE,
80 	FX_PORTAMENTOUP,
81 	FX_PORTAMENTODOWN,
82 	FX_TONEPORTAMENTO,
83 	FX_RETRIG,
84 	FX_VIBRATO, // depth
85 	FX_VIBRATO, // speed
86 	FX_VOLUMESLIDE, // up
87 	FX_VOLUMESLIDE, // down
88 	FX_VIBRATO, // sustained (?)
89 	FX_NONE, // actually slide-to-volume
90 	FX_PANNING,
91 	FX_SPECIAL, // note offset => note delay?
92 	FX_NONE, // fine tempo down
93 	FX_NONE, // fine tempo up
94 	FX_SPEED,
95 };
96 
far_import_note(song_note_t * note,const uint8_t data[4])97 static void far_import_note(song_note_t *note, const uint8_t data[4])
98 {
99 	if (data[0] > 0 && data[0] < 85) {
100 		note->note = data[0] + 36;
101 		note->instrument = data[1] + 1;
102 	}
103 	if (data[2] & 0x0F) {
104 		note->voleffect = VOLFX_VOLUME;
105 		note->volparam = (data[2] & 0x0F) << 2; // askjdfjasdkfjasdf
106 	}
107 	note->param = data[3] & 0xf;
108 	switch (data[3] >> 4) {
109 	case 3: // porta to note
110 		note->param <<= 2;
111 		break;
112 	case 4: // retrig
113 		note->param = 6 / (1 + (note->param & 0xf)) + 1; // ugh?
114 		break;
115 	case 6: // vibrato speed
116 	case 7: // volume slide up
117 	case 0xb: // panning
118 		note->param <<= 4;
119 		break;
120 	case 0xa: // volume-portamento (what!)
121 		note->voleffect = VOLFX_VOLUME;
122 		note->volparam = (note->param << 2) + 4;
123 		break;
124 	case 0xc: // note offset
125 		note->param = 6 / (1 + (note->param & 0xf)) + 1;
126 		note->param |= 0xd;
127 	}
128 	note->effect = far_effects[data[3] >> 4];
129 }
130 
131 
fmt_far_load_song(song_t * song,slurp_t * fp,unsigned int lflags)132 int fmt_far_load_song(song_t *song, slurp_t *fp, unsigned int lflags)
133 {
134 	struct far_header fhdr;
135 	struct far_sample fsmp;
136 	song_sample_t *smp;
137 	int nord, restartpos;
138 	int n, pat, row, chn;
139 	uint8_t orderlist[256];
140 	uint16_t pattern_size[256];
141 	uint8_t data[8];
142 
143 	slurp_read(fp, &fhdr, sizeof(fhdr));
144 	if (memcmp(fhdr.magic, "FAR\xfe", 4) != 0 || memcmp(fhdr.eof, "\x0d\x0a\x1a", 3) != 0)
145 		return LOAD_UNSUPPORTED;
146 
147 	fhdr.title[25] = '\0';
148 	strcpy(song->title, fhdr.title);
149 	fhdr.header_len = bswapLE16(fhdr.header_len);
150 	fhdr.message_len = bswapLE16(fhdr.message_len);
151 
152 	for (n = 0; n < 16; n++) {
153 		/* WHAT A GREAT WAY TO STORE THIS INFORMATION */
154 		song->channels[n].panning = SHORT_PANNING(fhdr.chn_panning[n] & 0xf);
155 		song->channels[n].panning *= 4; //mphack
156 		if (!fhdr.onoff[n])
157 			song->channels[n].flags |= CHN_MUTE;
158 	}
159 	for (; n < 64; n++)
160 		song->channels[n].flags |= CHN_MUTE;
161 
162 	song->initial_speed = fhdr.default_speed;
163 	song->initial_tempo = 80;
164 
165 	// to my knowledge, no other program is insane enough to save in this format
166 	strcpy(song->tracker_id, "Farandole Composer");
167 
168 	/* Farandole's song message doesn't have line breaks, and the tracker runs in
169 	some screwy ultra-wide text mode, so this displays more or less like crap. */
170 	read_lined_message(song->message, fp, fhdr.message_len, 132);
171 
172 
173 	slurp_seek(fp, sizeof(fhdr) + fhdr.message_len, SEEK_SET);
174 
175 	if ((lflags & (LOAD_NOSAMPLES | LOAD_NOPATTERNS)) == (LOAD_NOSAMPLES | LOAD_NOPATTERNS))
176 		return LOAD_SUCCESS;
177 
178 	slurp_read(fp, orderlist, 256);
179 	slurp_getc(fp); // supposed to be "number of patterns stored in the file"; apparently that's wrong
180 	nord = slurp_getc(fp);
181 	restartpos = slurp_getc(fp);
182 
183 	nord = MIN(nord, MAX_ORDERS);
184 	memcpy(song->orderlist, orderlist, nord);
185 	memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord);
186 
187 	slurp_read(fp, pattern_size, 256 * 2); // byteswapped later
188 	slurp_seek(fp, fhdr.header_len - (869 + fhdr.message_len), SEEK_CUR);
189 
190 	for (pat = 0; pat < 256; pat++) {
191 		int breakpos, rows;
192 		song_note_t *note;
193 
194 		pattern_size[pat] = bswapLE16(pattern_size[pat]);
195 
196 		if (pat >= MAX_PATTERNS || pattern_size[pat] < (2 + 16 * 4)) {
197 			slurp_seek(fp, pattern_size[pat], SEEK_CUR);
198 			continue;
199 		}
200 		breakpos = slurp_getc(fp);
201 		slurp_getc(fp); // apparently, this value is *not* used anymore!!! I will not support it!!
202 
203 		rows = (pattern_size[pat] - 2) / (16 * 4);
204 		if (!rows)
205 			continue;
206 		note = song->patterns[pat] = csf_allocate_pattern(rows);
207 		song->pattern_size[pat] = song->pattern_alloc_size[pat] = rows;
208 		breakpos = breakpos && breakpos < rows - 2 ? breakpos + 1 : -1;
209 		for (row = 0; row < rows; row++, note += 48) {
210 			for (chn = 0; chn < 16; chn++, note++) {
211 				slurp_read(fp, data, 4);
212 				far_import_note(note, data);
213 			}
214 			if (row == breakpos)
215 				note->effect = FX_PATTERNBREAK;
216 		}
217 	}
218 	csf_insert_restart_pos(song, restartpos);
219 
220 	if (lflags & LOAD_NOSAMPLES)
221 		return LOAD_SUCCESS;
222 
223 	slurp_read(fp, data, 8);
224 	smp = song->samples + 1;
225 	for (n = 0; n < 64; n++, smp++) {
226 		if (!(data[n / 8] & (1 << (n % 8)))) /* LOLWHAT */
227 			continue;
228 		slurp_read(fp, &fsmp, sizeof(fsmp));
229 		fsmp.name[25] = '\0';
230 		strcpy(smp->name, fsmp.name);
231 		smp->length = fsmp.length = bswapLE32(fsmp.length);
232 		smp->loop_start = bswapLE32(fsmp.loopstart);
233 		smp->loop_end = bswapLE32(fsmp.loopend);
234 		smp->volume = fsmp.volume << 4; // "not supported", but seems to exist anyway
235 		if (fsmp.type & 1) {
236 			smp->length >>= 1;
237 			smp->loop_start >>= 1;
238 			smp->loop_end >>= 1;
239 		}
240 		if (smp->loop_end > smp->loop_start && (fsmp.loop & 8))
241 			smp->flags |= CHN_LOOP;
242 		smp->c5speed = 16726;
243 		smp->global_volume = 64;
244 		csf_read_sample(smp, SF_LE | SF_M | SF_PCMS | ((fsmp.type & 1) ? SF_16 : SF_8),
245 			fp->data + fp->pos, fp->length - fp->pos);
246 		slurp_seek(fp, fsmp.length, SEEK_CUR);
247 	}
248 
249 	return LOAD_SUCCESS;
250 }
251 
252