1 /*
2  * gbsplay is a Gameboy sound player
3  *
4  * 2006 (C) by Tobias Diedrich <ranma+gbsplay@tdiedrich.de>
5  *
6  * MIDI output plugin
7  *
8  * 2008,2018 (C) by Vegard Nossum
9  *                  Christian Garbs <mitch@cgarbs.de>
10  *
11  * Licensed under GNU GPL v1 or, at your option, any later version.
12  */
13 
14 #include "common.h"
15 
16 #include <math.h>
17 #include <stdint.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <time.h>
21 #include <unistd.h>
22 
23 #include "plugout.h"
24 
25 #define FILENAMESIZE 32
26 #define LN2 .69314718055994530941
27 #define MAGIC 5.78135971352465960412
28 #define FREQ(x) (262144 / (x))
29 #define NOTE(x) ((long)((log(FREQ(x))/LN2 - MAGIC)*12 + .2))
30 
midi_open(enum plugout_endian endian,long rate)31 static long regparm midi_open(enum plugout_endian endian, long rate)
32 {
33 	return 0;
34 }
35 
36 static FILE *file = NULL;
37 
38 static long track_length;
39 static long track_length_offset;
40 
41 static long cycles_prev = 0;
42 
43 static int note[4] = {0, 0, 0, 0};
44 
midi_write_u32(uint32_t x)45 static int midi_write_u32(uint32_t x)
46 {
47 	uint8_t s[4];
48 
49 	s[0] = x >> 24;
50 	s[1] = x >> 16;
51 	s[2] = x >> 8;
52 	s[3] = x;
53 
54 	if (fwrite(s, 1, 4, file) != 4)
55 		return 1;
56 	return 0;
57 }
58 
midi_write_u16(uint16_t x)59 static int midi_write_u16(uint16_t x)
60 {
61 	uint8_t s[2];
62 
63 	s[0] = x >> 8;
64 	s[1] = x;
65 
66 	if (fwrite(s, 1, 2, file) != 2)
67 		return 1;
68 	return 0;
69 }
70 
midi_write_varlen(unsigned long x)71 static int midi_write_varlen(unsigned long x)
72 {
73 	uint8_t data[4];
74 	unsigned int i;
75 
76 	for (i = 0; i < 4; ++i) {
77 		data[3 - i] = (x & 0x7f) | 0x80;
78 		x >>= 7;
79 
80 		if (!x) {
81 			++i;
82 			break;
83 		}
84 	}
85 
86 	data[3] &= ~0x80;
87 	if (fwrite(data + 4 - i, 1, i, file) != i)
88 		return 1;
89 
90 	track_length += i;
91 	return 0;
92 }
93 
midi_write_event(long cycles,const uint8_t * s,unsigned int n)94 static int midi_write_event(long cycles, const uint8_t *s, unsigned int n)
95 {
96 	if (midi_write_varlen((cycles - cycles_prev) >> 14))
97 		return 1;
98 
99 	if (fwrite(s, 1, n, file) != n)
100 		return 1;
101 
102 	track_length += n;
103 
104 	cycles_prev = cycles;
105 	return 0;
106 }
107 
midi_open_track(int subsong)108 static int midi_open_track(int subsong)
109 {
110 	char *filename;
111 	if ((filename = malloc(FILENAMESIZE)) == NULL)
112 		goto out;
113 
114 	if (snprintf(filename, FILENAMESIZE, "gbsplay-%d.mid", subsong + 1) >= FILENAMESIZE)
115 		goto out;
116 
117 	file = fopen(filename, "wb");
118 	if (!file) {
119 		free(filename);
120 		goto out;
121 	}
122 
123 	free(filename);
124 
125 	/* Header type */
126 	if (fwrite("MThd", 1, 4, file) != 4)
127 		goto out_file;
128 
129 	/* Header length */
130 	if (midi_write_u32(6))
131 		goto out_file;
132 
133 	/* Format */
134 	if (midi_write_u16(0))
135 		goto out_file;
136 
137 	/* Tracks */
138 	if (midi_write_u16(1))
139 		goto out_file;
140 
141 	/* Division */
142 	/* XXX: Do some real calculation instead of this magic number :-) */
143 	if (midi_write_u16(124))
144 		goto out_file;
145 
146 	/* Track type */
147 	if (fwrite("MTrk", 1, 4, file) != 4)
148 		goto out_file;
149 
150 	/* Track length (placeholder) */
151 	track_length = 0;
152 	track_length_offset = ftell(file);
153 	if (track_length_offset == -1)
154 		goto out_file;
155 
156 	if (midi_write_u32(0))
157 		goto out_file;
158 
159 	return 0;
160 
161 out_file:
162 	fclose(file);
163 	file = NULL;
164 out:
165 	return 1;
166 }
167 
midi_close_track()168 static int midi_close_track()
169 {
170 	uint8_t event[3];
171 
172 	/* End of track */
173 	event[0] = 0xff;
174 	event[1] = 0x2f;
175 	event[2] = 0x00;
176 
177 	if (midi_write_event(cycles_prev, event, 3))
178 		goto out;
179 
180 	if (fseek(file, track_length_offset, SEEK_SET) == -1)
181 		goto out;
182 
183 	if (midi_write_u32(track_length))
184 		goto out;
185 
186 	fclose(file);
187 	file = NULL;
188 	return 0;
189 
190 out:
191 	file = NULL;
192 	return 1;
193 }
194 
midi_skip(int subsong)195 static int regparm midi_skip(int subsong)
196 {
197 	cycles_prev = 0;
198 
199 	if (file) {
200 		if (midi_close_track())
201 			return 1;
202 	}
203 
204 	return midi_open_track(subsong);
205 }
206 
note_on(long cycles,int channel,int new_note,int velocity)207 static int note_on(long cycles, int channel, int new_note, int velocity)
208 {
209 	uint8_t event[3];
210 
211 	event[0] = 0x90 | channel;
212 	event[1] = new_note;
213 	event[2] = velocity;
214 
215 	if (midi_write_event(cycles, event, 3))
216 		return 1;
217 
218 	note[channel] = new_note;
219 
220 	return 0;
221 }
222 
note_off(long cycles,int channel)223 static int note_off(long cycles, int channel)
224 {
225 	uint8_t event[3];
226 
227 	if (!note[channel])
228 		return 0;
229 
230 	event[0] = 0x80 | channel;
231 	event[1] = note[channel];
232 	event[2] = 0;
233 
234 	if (midi_write_event(cycles, event, 3))
235 		return 1;
236 
237 	note[channel] = 0;
238 
239 	return 0;
240 }
241 
pan(long cycles,int channel,int pan)242 static int pan(long cycles, int channel, int pan)
243 {
244 	uint8_t event[3];
245 
246 	event[0] = 0xb0 | channel;
247 	event[1] = 0x0a;
248 	event[2] = pan;
249 
250 	if (midi_write_event(cycles, event, 3))
251 		return 1;
252 
253 	return 0;
254 }
255 
midi_io(long cycles,uint32_t addr,uint8_t val)256 static int regparm midi_io(long cycles, uint32_t addr, uint8_t val)
257 {
258 	static long div[4] = {0, 0, 0, 0};
259 	static int volume[4] = {0, 0, 0, 0};
260 	static int running[4] = {0, 0, 0, 0};
261 	static int master[4] = {0, 0, 0, 0};
262 	int new_note;
263 
264 	long chan = (addr - 0xff10) / 5;
265 
266 	if (!file)
267 		return 1;
268 
269 	switch (addr) {
270 	case 0xff12:
271 	case 0xff17:
272 		volume[chan] = 8 * (val >> 4);
273 		master[chan] = (val & 0xf8) != 0;
274 		if (!master[chan] && running[chan]) {
275 			/* DAC turned off, disable channel */
276 			if (note_off(cycles, chan))
277 				return 1;
278 			running[chan] = 0;
279 		}
280 		if (volume[chan]) {
281 			/* volume set to >0, restart current note */
282 			if (running[chan] && !note[chan]) {
283 				new_note = NOTE(2048 - div[chan]) + 21;
284 				if (new_note < 0 || new_note >= 0x80)
285 					break;
286 				if (note_on(cycles, chan, new_note, volume[chan]))
287 					return 1;
288 			}
289 		} else {
290 			/* volume set to 0, stop note (if any) */
291 			if (note_off(cycles, chan))
292 				return 1;
293 		}
294 		break;
295 	case 0xff13:
296 	case 0xff18:
297 	case 0xff1d:
298 		div[chan] &= 0xff00;
299 		div[chan] |= val;
300 
301 		if (running[chan]) {
302 			new_note = NOTE(2048 - div[chan]) + 21;
303 
304 			if (new_note != note[chan]) {
305 				/* portamento: retrigger with new note */
306 				if (note_off(cycles, chan))
307 					return 1;
308 
309 				if (new_note < 0 || new_note >= 0x80)
310 					break;
311 
312 				if (note_on(cycles, chan, new_note, volume[chan]))
313 					return 1;
314 			}
315 		}
316 
317 		break;
318 	case 0xff14:
319 	case 0xff19:
320 	case 0xff1e:
321 		div[chan] &= 0x00ff;
322 		div[chan] |= ((long) (val & 7)) << 8;
323 
324 		new_note = NOTE(2048 - div[chan]) + 21;
325 
326 		/* Channel start trigger */
327 		if ((val & 0x80) == 0x80) {
328 			if (note_off(cycles, chan))
329 				return 1;
330 
331 			if (new_note < 0 || new_note >= 0x80)
332 				break;
333 
334 			if (master[chan]) {
335 				if (note_on(cycles, chan, new_note, volume[chan]))
336 					return 1;
337 				running[chan] = 1;
338 			}
339 		} else {
340 			if (running[chan]) {
341 				if (new_note != note[chan]) {
342 					/* portamento: retrigger with new note */
343 					if (note_off(cycles, chan))
344 						return 1;
345 
346 					if (new_note < 0 || new_note >= 0x80)
347 						break;
348 
349 					if (note_on(cycles, chan, new_note, volume[chan]))
350 						return 1;
351 				}
352 			}
353 		}
354 
355 		break;
356 	case 0xff1a:
357 		master[2] = (val & 0x80) == 0x80;
358 		if (!master[2] && running[2]) {
359 			/* DAC turned off, disable channel */
360 			if (note_off(cycles, 2))
361 				return 1;
362 			running[2] = 0;
363 		}
364 		break;
365 	case 0xff1c:
366 		volume[2] = 32 * ((4 - (val >> 5)) & 3);
367 		if (volume[2]) {
368 			/* volume set to >0, restart current note */
369 			if (running[2] && !note[2]) {
370 				new_note = NOTE(2048 - div[chan]) + 21;
371 				if (new_note < 0 || new_note >= 0x80)
372 					break;
373 				if (note_on(cycles, 2, new_note, volume[2]))
374 					return 1;
375 			}
376 		} else {
377 			/* volume set to 0, stop note (if any) */
378 			if (note_off(cycles, 2))
379 				return 1;
380 		}
381 		break;
382 	case 0xff25:
383 		for (chan = 0; chan < 4; chan++)
384 			switch ((val >> chan) & 0x11) {
385 			case 0x10:
386 				pan(cycles, chan, 0);
387 				break;
388 			case 0x01:
389 				pan(cycles, chan, 127);
390 				break;
391 			default:
392 				pan(cycles, chan, 64);
393 			}
394 		break;
395 	case 0xff26:
396 		if ((val & 0x80) == 0) {
397 			for (chan = 0; chan < 4; chan++) {
398 				div[chan] = 0;
399 				volume[chan] = 0;
400 				running[chan] = 0;
401 				master[chan] = 1;
402 				if (note_off(cycles, 2))
403 					return 1;
404 			}
405 		}
406 		break;
407 	}
408 
409 	return 0;
410 }
411 
midi_close(void)412 static void regparm midi_close(void)
413 {
414 	int chan;
415 
416 	if (!file)
417 		return;
418 
419 	for (chan = 0; chan < 4; chan++)
420 		if (note_off(cycles_prev + 1, chan))
421 			return;
422 
423 	midi_close_track();
424 }
425 
426 const struct output_plugin plugout_midi = {
427 	.name = "midi",
428 	.description = "MIDI file writer",
429 	.open = midi_open,
430 	.skip = midi_skip,
431 	.io = midi_io,
432 	.close = midi_close,
433 };
434