1 /*
2 ** i_music.cpp
3 ** Plays music
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2010 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34 
35 #ifdef _WIN32
36 #define WIN32_LEAN_AND_MEAN
37 #include <windows.h>
38 #include <mmsystem.h>
39 #else
40 #include <sys/types.h>
41 #include <sys/wait.h>
42 #include <sys/stat.h>
43 #include <fcntl.h>
44 #include <signal.h>
45 #include <unistd.h>
46 #include <wordexp.h>
47 #include <stdio.h>
48 #include "mus2midi.h"
49 #define FALSE 0
50 #define TRUE 1
51 extern void ChildSigHandler (int signum);
52 #endif
53 
54 #include <ctype.h>
55 #include <assert.h>
56 #include <stdio.h>
57 
58 #include "i_musicinterns.h"
59 #include "doomtype.h"
60 #include "m_argv.h"
61 #include "i_music.h"
62 #include "w_wad.h"
63 #include "c_console.h"
64 #include "c_dispatch.h"
65 #include "i_system.h"
66 #include "i_sound.h"
67 #include "s_sound.h"
68 #include "m_swap.h"
69 #include "i_cd.h"
70 #include "tempfiles.h"
71 #include "templates.h"
72 #include "stats.h"
73 #include "timidity/timidity.h"
74 
75 #define GZIP_ID1		31
76 #define GZIP_ID2		139
77 #define GZIP_CM			8
78 #define GZIP_ID			MAKE_ID(GZIP_ID1,GZIP_ID2,GZIP_CM,0)
79 
80 #define GZIP_FTEXT		1
81 #define GZIP_FHCRC		2
82 #define GZIP_FEXTRA		4
83 #define GZIP_FNAME		8
84 #define GZIP_FCOMMENT	16
85 
86 enum EMIDIType
87 {
88 	MIDI_NOTMIDI,
89 	MIDI_MIDI,
90 	MIDI_HMI,
91 	MIDI_XMI,
92 	MIDI_MUS
93 };
94 
95 extern int MUSHeaderSearch(const BYTE *head, int len);
96 
97 EXTERN_CVAR (Int, snd_samplerate)
98 EXTERN_CVAR (Int, snd_mididevice)
99 
100 static bool MusicDown = true;
101 
102 static bool ungzip(BYTE *data, int size, TArray<BYTE> &newdata);
103 
104 MusInfo *currSong;
105 int		nomusic = 0;
106 float	relative_volume = 1.f;
107 float	saved_relative_volume = 1.0f;	// this could be used to implement an ACS FadeMusic function
108 
109 //==========================================================================
110 //
111 // CVAR snd_musicvolume
112 //
113 // Maximum volume of MOD/stream music.
114 //==========================================================================
115 
116 CUSTOM_CVAR (Float, snd_musicvolume, 0.5f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
117 {
118 	if (self < 0.f)
119 		self = 0.f;
120 	else if (self > 1.f)
121 		self = 1.f;
122 	else
123 	{
124 		// Set general music volume.
125 		if (GSnd != NULL)
126 		{
127 			GSnd->SetMusicVolume(clamp<float>(self * relative_volume, 0, 1));
128 		}
129 		// For music not implemented through the digital sound system,
130 		// let them know about the change.
131 		if (currSong != NULL)
132 		{
133 			currSong->MusicVolumeChanged();
134 		}
135 		else
136 		{ // If the music was stopped because volume was 0, start it now.
137 			S_RestartMusic();
138 		}
139 	}
140 }
141 
142 //==========================================================================
143 //
144 //
145 //
146 //==========================================================================
147 
I_InitMusic(void)148 void I_InitMusic (void)
149 {
150 	static bool setatterm = false;
151 
152 	Timidity::LoadConfig();
153 
154 	snd_musicvolume.Callback ();
155 
156 	nomusic = !!Args->CheckParm("-nomusic") || !!Args->CheckParm("-nosound");
157 
158 #ifdef _WIN32
159 	I_InitMusicWin32 ();
160 #endif // _WIN32
161 
162 	if (!setatterm)
163 	{
164 		setatterm = true;
165 		atterm (I_ShutdownMusicExit);
166 
167 #ifndef _WIN32
168 		signal (SIGCHLD, ChildSigHandler);
169 #endif
170 	}
171 	MusicDown = false;
172 }
173 
174 
175 //==========================================================================
176 //
177 //
178 //
179 //==========================================================================
180 
I_ShutdownMusic(bool onexit)181 void I_ShutdownMusic(bool onexit)
182 {
183 	if (MusicDown)
184 		return;
185 	MusicDown = true;
186 	if (currSong)
187 	{
188 		S_StopMusic (true);
189 		assert (currSong == NULL);
190 	}
191 	Timidity::FreeAll();
192 	if (onexit) WildMidi_Shutdown();
193 #ifdef _WIN32
194 	I_ShutdownMusicWin32();
195 #endif // _WIN32
196 }
197 
I_ShutdownMusicExit()198 void I_ShutdownMusicExit()
199 {
200 	I_ShutdownMusic(true);
201 }
202 
203 
204 //==========================================================================
205 //
206 //
207 //
208 //==========================================================================
209 
MusInfo()210 MusInfo::MusInfo()
211 : m_Status(STATE_Stopped), m_Looping(false), m_NotStartedYet(true)
212 {
213 }
214 
~MusInfo()215 MusInfo::~MusInfo ()
216 {
217 	if (currSong == this) currSong = NULL;
218 }
219 
220 //==========================================================================
221 //
222 // starts playing this song
223 //
224 //==========================================================================
225 
Start(bool loop,float rel_vol,int subsong)226 void MusInfo::Start(bool loop, float rel_vol, int subsong)
227 {
228 	if (nomusic) return;
229 
230 	if (rel_vol > 0.f) saved_relative_volume = relative_volume = rel_vol;
231 	Stop ();
232 	Play (loop, subsong);
233 	m_NotStartedYet = false;
234 
235 	if (m_Status == MusInfo::STATE_Playing)
236 		currSong = this;
237 	else
238 		currSong = NULL;
239 
240 	// Notify the sound system of the changed relative volume
241 	snd_musicvolume.Callback();
242 }
243 
244 //==========================================================================
245 //
246 //
247 //
248 //==========================================================================
249 
SetPosition(unsigned int ms)250 bool MusInfo::SetPosition (unsigned int ms)
251 {
252 	return false;
253 }
254 
IsMIDI() const255 bool MusInfo::IsMIDI() const
256 {
257 	return false;
258 }
259 
SetSubsong(int subsong)260 bool MusInfo::SetSubsong (int subsong)
261 {
262 	return false;
263 }
264 
Update()265 void MusInfo::Update ()
266 {
267 }
268 
MusicVolumeChanged()269 void MusInfo::MusicVolumeChanged()
270 {
271 }
272 
TimidityVolumeChanged()273 void MusInfo::TimidityVolumeChanged()
274 {
275 }
276 
FluidSettingInt(const char *,int)277 void MusInfo::FluidSettingInt(const char *, int)
278 {
279 }
280 
FluidSettingNum(const char *,double)281 void MusInfo::FluidSettingNum(const char *, double)
282 {
283 }
284 
FluidSettingStr(const char *,const char *)285 void MusInfo::FluidSettingStr(const char *, const char *)
286 {
287 }
288 
WildMidiSetOption(int opt,int set)289 void MusInfo::WildMidiSetOption(int opt, int set)
290 {
291 }
292 
GetStats()293 FString MusInfo::GetStats()
294 {
295 	return "No stats available for this song";
296 }
297 
GetOPLDumper(const char * filename)298 MusInfo *MusInfo::GetOPLDumper(const char *filename)
299 {
300 	return NULL;
301 }
302 
GetWaveDumper(const char * filename,int rate)303 MusInfo *MusInfo::GetWaveDumper(const char *filename, int rate)
304 {
305 	return NULL;
306 }
307 
308 //==========================================================================
309 //
310 // create a streamer based on MIDI file type
311 //
312 //==========================================================================
313 
CreateMIDIStreamer(FileReader & reader,EMidiDevice devtype,EMIDIType miditype,const char * args)314 static MIDIStreamer *CreateMIDIStreamer(FileReader &reader, EMidiDevice devtype, EMIDIType miditype, const char *args)
315 {
316 	switch (miditype)
317 	{
318 	case MIDI_MUS:
319 		return new MUSSong2(reader, devtype, args);
320 
321 	case MIDI_MIDI:
322 		return new MIDISong2(reader, devtype, args);
323 
324 	case MIDI_HMI:
325 		return new HMISong(reader, devtype, args);
326 
327 	case MIDI_XMI:
328 		return new XMISong(reader, devtype, args);
329 
330 	default:
331 		return NULL;
332 	}
333 }
334 
335 //==========================================================================
336 //
337 // identify MIDI file type
338 //
339 //==========================================================================
340 
IdentifyMIDIType(DWORD * id,int size)341 static EMIDIType IdentifyMIDIType(DWORD *id, int size)
342 {
343 	// Check for MUS format
344 	// Tolerate sloppy wads by searching up to 32 bytes for the header
345 	if (MUSHeaderSearch((BYTE*)id, size) >= 0)
346 	{
347 		return MIDI_MUS;
348 	}
349 	// Check for HMI format
350 	else
351 	if (id[0] == MAKE_ID('H','M','I','-') &&
352 		id[1] == MAKE_ID('M','I','D','I') &&
353 		id[2] == MAKE_ID('S','O','N','G'))
354 	{
355 		return MIDI_HMI;
356 	}
357 	// Check for HMP format
358 	else
359 	if (id[0] == MAKE_ID('H','M','I','M') &&
360 		id[1] == MAKE_ID('I','D','I','P'))
361 	{
362 		return MIDI_HMI;
363 	}
364 	// Check for XMI format
365 	else
366 	if ((id[0] == MAKE_ID('F','O','R','M') &&
367 		 id[2] == MAKE_ID('X','D','I','R')) ||
368 		((id[0] == MAKE_ID('C','A','T',' ') || id[0] == MAKE_ID('F','O','R','M')) &&
369 		 id[2] == MAKE_ID('X','M','I','D')))
370 	{
371 		return MIDI_XMI;
372 	}
373 	// Check for MIDI format
374 	else if (id[0] == MAKE_ID('M','T','h','d'))
375 	{
376 		return MIDI_MIDI;
377 	}
378 	else
379 	{
380 		return MIDI_NOTMIDI;
381 	}
382 }
383 
384 //==========================================================================
385 //
386 // identify a music lump's type and set up a player for it
387 //
388 //==========================================================================
389 
I_RegisterSong(FileReader * reader,MidiDeviceSetting * device)390 MusInfo *I_RegisterSong (FileReader *reader, MidiDeviceSetting *device)
391 {
392 	MusInfo *info = NULL;
393 	const char *fmt;
394 	DWORD id[32/4];
395 
396 	if (nomusic)
397 	{
398 		delete reader;
399 		return 0;
400 	}
401 
402 	if(reader->Read(id, 32) != 32 || reader->Seek(-32, SEEK_CUR) != 0)
403 	{
404 		delete reader;
405 		return 0;
406 	}
407 
408     // Check for gzip compression. Some formats are expected to have players
409     // that can handle it, so it simplifies things if we make all songs
410     // gzippable.
411 	if ((id[0] & MAKE_ID(255, 255, 255, 0)) == GZIP_ID)
412 	{
413 		int len = reader->GetLength();
414 		BYTE *gzipped = new BYTE[len];
415 		if (reader->Read(gzipped, len) != len)
416 		{
417 			delete[] gzipped;
418 			delete reader;
419 			return NULL;
420 		}
421 		delete reader;
422 
423 		MemoryArrayReader *memreader = new MemoryArrayReader(NULL, 0);
424 		if (!ungzip(gzipped, len, memreader->GetArray()))
425 		{
426 			delete[] gzipped;
427 			delete memreader;
428 			return 0;
429 		}
430 		delete[] gzipped;
431 		memreader->UpdateLength();
432 
433 		if (memreader->Read(id, 32) != 32 || memreader->Seek(-32, SEEK_CUR) != 0)
434 		{
435 			delete memreader;
436 			return 0;
437 		}
438 		reader = memreader;
439 	}
440 
441 	EMIDIType miditype = IdentifyMIDIType(id, sizeof(id));
442 	if (miditype != MIDI_NOTMIDI)
443 	{
444 		EMidiDevice devtype = device == NULL? MDEV_DEFAULT : (EMidiDevice)device->device;
445 #ifndef _WIN32
446 		// non-Windows platforms don't support MDEV_MMAPI so map to MDEV_SNDSYS
447 		if (devtype == MDEV_MMAPI)
448 			devtype = MDEV_SNDSYS;
449 #endif
450 
451 retry_as_sndsys:
452 		info = CreateMIDIStreamer(*reader, devtype, miditype, device != NULL? device->args.GetChars() : "");
453 		if (info != NULL && !info->IsValid())
454 		{
455 			delete info;
456 			info = NULL;
457 		}
458 		if (info == NULL && devtype != MDEV_SNDSYS && snd_mididevice < 0)
459 		{
460 			devtype = MDEV_SNDSYS;
461 			goto retry_as_sndsys;
462 		}
463 #ifdef _WIN32
464 		if (info == NULL && devtype != MDEV_MMAPI && snd_mididevice >= 0)
465 		{
466 			info = CreateMIDIStreamer(*reader, MDEV_MMAPI, miditype, "");
467 		}
468 #endif
469 	}
470 
471 	// Check for various raw OPL formats
472 	else if (
473 		(id[0] == MAKE_ID('R','A','W','A') && id[1] == MAKE_ID('D','A','T','A')) ||		// Rdos Raw OPL
474 		(id[0] == MAKE_ID('D','B','R','A') && id[1] == MAKE_ID('W','O','P','L')) ||		// DosBox Raw OPL
475 		(id[0] == MAKE_ID('A','D','L','I') && *((BYTE *)id + 4) == 'B'))		// Martin Fernandez's modified IMF
476 	{
477 		info = new OPLMUSSong (*reader, device != NULL? device->args.GetChars() : "");
478 	}
479 	// Check for game music
480 	else if ((fmt = GME_CheckFormat(id[0])) != NULL && fmt[0] != '\0')
481 	{
482 		info = GME_OpenSong(*reader, fmt);
483 	}
484 	// Check for module formats
485 	else
486 	{
487 		info = MOD_OpenSong(*reader);
488 	}
489 
490     if (info == NULL)
491     {
492         // Check for CDDA "format"
493         if (id[0] == (('R')|(('I')<<8)|(('F')<<16)|(('F')<<24)))
494         {
495             DWORD subid;
496 
497             reader->Seek(8, SEEK_CUR);
498             if (reader->Read (&subid, 4) != 4)
499             {
500                 delete reader;
501                 return 0;
502             }
503             reader->Seek(-12, SEEK_CUR);
504 
505             if (subid == (('C')|(('D')<<8)|(('D')<<16)|(('A')<<24)))
506             {
507                 // This is a CDDA file
508                 info = new CDDAFile (*reader);
509             }
510         }
511 
512         // no support in sound system => no modules/streams
513         // 1024 bytes is an arbitrary restriction. It's assumed that anything
514         // smaller than this can't possibly be a valid music file if it hasn't
515         // been identified already, so don't even bother trying to load it.
516         // Of course MIDIs shorter than 1024 bytes should pass.
517         if (info == NULL && (reader->GetLength() >= 1024 || id[0] == MAKE_ID('M','T','h','d')))
518         {
519             // Let the sound system figure out what it is.
520             info = new StreamSong (reader);
521 			// Assumed ownership
522 			reader = NULL;
523         }
524     }
525 
526 	if (reader != NULL) delete reader;
527 
528 	if (info && !info->IsValid ())
529 	{
530 		delete info;
531 		info = NULL;
532 	}
533 
534 	return info;
535 }
536 
537 //==========================================================================
538 //
539 // play CD music
540 //
541 //==========================================================================
542 
I_RegisterCDSong(int track,int id)543 MusInfo *I_RegisterCDSong (int track, int id)
544 {
545 	MusInfo *info = new CDSong (track, id);
546 
547 	if (info && !info->IsValid ())
548 	{
549 		delete info;
550 		info = NULL;
551 	}
552 
553 	return info;
554 }
555 
556 //==========================================================================
557 //
558 //
559 //
560 //==========================================================================
561 
I_RegisterURLSong(const char * url)562 MusInfo *I_RegisterURLSong (const char *url)
563 {
564 	StreamSong *song;
565 
566 	song = new StreamSong(url);
567 	if (song->IsValid())
568 	{
569 		return song;
570 	}
571 	delete song;
572 	return NULL;
573 }
574 
575 //==========================================================================
576 //
577 // ungzip
578 //
579 // VGZ files are compressed with gzip, so we need to uncompress them before
580 // handing them to GME.
581 //
582 //==========================================================================
583 
ungzip(BYTE * data,int complen,TArray<BYTE> & newdata)584 static bool ungzip(BYTE *data, int complen, TArray<BYTE> &newdata)
585 {
586 	const BYTE *max = data + complen - 8;
587 	const BYTE *compstart = data + 10;
588 	BYTE flags = data[3];
589 	unsigned isize;
590 	z_stream stream;
591 	int err;
592 
593 	// Find start of compressed data stream
594 	if (flags & GZIP_FEXTRA)
595 	{
596 		compstart += 2 + LittleShort(*(WORD *)(data + 10));
597 	}
598 	if (flags & GZIP_FNAME)
599 	{
600 		while (compstart < max && *compstart != 0)
601 		{
602 			compstart++;
603 		}
604 	}
605 	if (flags & GZIP_FCOMMENT)
606 	{
607 		while (compstart < max && *compstart != 0)
608 		{
609 			compstart++;
610 		}
611 	}
612 	if (flags & GZIP_FHCRC)
613 	{
614 		compstart += 2;
615 	}
616 	if (compstart >= max - 1)
617 	{
618 		return false;
619 	}
620 
621 	// Decompress
622 	isize = LittleLong(*(DWORD *)(data + complen - 4));
623     newdata.Resize(isize);
624 
625 	stream.next_in = (Bytef *)compstart;
626 	stream.avail_in = (uInt)(max - compstart);
627 	stream.next_out = &newdata[0];
628 	stream.avail_out = isize;
629 	stream.zalloc = (alloc_func)0;
630 	stream.zfree = (free_func)0;
631 
632 	err = inflateInit2(&stream, -MAX_WBITS);
633 	if (err != Z_OK)
634 	{
635 		return false;
636 	}
637 	err = inflate(&stream, Z_FINISH);
638 	if (err != Z_STREAM_END)
639 	{
640 		inflateEnd(&stream);
641 		return false;
642 	}
643 	err = inflateEnd(&stream);
644 	if (err != Z_OK)
645 	{
646 		return false;
647 	}
648 	return true;
649 }
650 
651 //==========================================================================
652 //
653 //
654 //
655 //==========================================================================
656 
I_UpdateMusic()657 void I_UpdateMusic()
658 {
659 	if (currSong != NULL)
660 	{
661 		currSong->Update();
662 	}
663 }
664 
665 //==========================================================================
666 //
667 // Sets relative music volume. Takes $musicvolume in SNDINFO into consideration
668 //
669 //==========================================================================
670 
I_SetMusicVolume(float factor)671 void I_SetMusicVolume (float factor)
672 {
673 	factor = clamp<float>(factor, 0, 2.0f);
674 	relative_volume = saved_relative_volume * factor;
675 	snd_musicvolume.Callback();
676 }
677 
678 //==========================================================================
679 //
680 // test a relative music volume
681 //
682 //==========================================================================
683 
CCMD(testmusicvol)684 CCMD(testmusicvol)
685 {
686 	if (argv.argc() > 1)
687 	{
688 		relative_volume = (float)strtod(argv[1], NULL);
689 		snd_musicvolume.Callback();
690 	}
691 	else
692 		Printf("Current relative volume is %1.2f\n", relative_volume);
693 }
694 
695 //==========================================================================
696 //
697 // STAT music
698 //
699 //==========================================================================
700 
ADD_STAT(music)701 ADD_STAT(music)
702 {
703 	if (currSong != NULL)
704 	{
705 		return currSong->GetStats();
706 	}
707 	return "No song playing";
708 }
709 
710 //==========================================================================
711 //
712 // CCMD writeopl
713 //
714 // If the current song can be played with OPL instruments, dump it to
715 // the specified file on disk.
716 //
717 //==========================================================================
718 
CCMD(writeopl)719 CCMD (writeopl)
720 {
721 	if (argv.argc() == 2)
722 	{
723 		if (currSong == NULL)
724 		{
725 			Printf ("No song is currently playing.\n");
726 		}
727 		else
728 		{
729 			MusInfo *dumper = currSong->GetOPLDumper(argv[1]);
730 			if (dumper == NULL)
731 			{
732 				Printf ("Current song cannot be saved as OPL data.\n");
733 			}
734 			else
735 			{
736 				dumper->Play(false, 0);		// FIXME: Remember subsong.
737 				delete dumper;
738 			}
739 		}
740 	}
741 	else
742 	{
743 		Printf ("Usage: writeopl <filename>\n");
744 	}
745 }
746 
747 //==========================================================================
748 //
749 // CCMD writewave
750 //
751 // If the current song can be represented as a waveform, dump it to
752 // the specified file on disk. The sample rate parameter is merely a
753 // suggestion, and the dumper is free to ignore it.
754 //
755 //==========================================================================
756 
CCMD(writewave)757 CCMD (writewave)
758 {
759 	if (argv.argc() >= 2 && argv.argc() <= 3)
760 	{
761 		if (currSong == NULL)
762 		{
763 			Printf ("No song is currently playing.\n");
764 		}
765 		else
766 		{
767 			MusInfo *dumper = currSong->GetWaveDumper(argv[1], argv.argc() == 3 ? atoi(argv[2]) : 0);
768 			if (dumper == NULL)
769 			{
770 				Printf ("Current song cannot be saved as wave data.\n");
771 			}
772 			else
773 			{
774 				dumper->Play(false, 0);		// FIXME: Remember subsong
775 				delete dumper;
776 			}
777 		}
778 	}
779 	else
780 	{
781 		Printf ("Usage: writewave <filename> [sample rate]");
782 	}
783 }
784 
785 //==========================================================================
786 //
787 // CCMD writemidi
788 //
789 // If the currently playing song is a MIDI variant, write it to disk.
790 // If successful, the current song will restart, since MIDI file generation
791 // involves a simulated playthrough of the song.
792 //
793 //==========================================================================
794 
CCMD(writemidi)795 CCMD (writemidi)
796 {
797 	if (argv.argc() != 2)
798 	{
799 		Printf("Usage: writemidi <filename>");
800 		return;
801 	}
802 	if (currSong == NULL)
803 	{
804 		Printf("No song is currently playing.\n");
805 		return;
806 	}
807 	if (!currSong->IsMIDI())
808 	{
809 		Printf("Current song is not MIDI-based.\n");
810 		return;
811 	}
812 
813 	TArray<BYTE> midi;
814 	FILE *f;
815 	bool success;
816 
817 	static_cast<MIDIStreamer *>(currSong)->CreateSMF(midi, 1);
818 	f = fopen(argv[1], "wb");
819 	if (f == NULL)
820 	{
821 		Printf("Could not open %s.\n", argv[1]);
822 		return;
823 	}
824 	success = (fwrite(&midi[0], 1, midi.Size(), f) == (size_t)midi.Size());
825 	fclose (f);
826 
827 	if (!success)
828 	{
829 		Printf("Could not write to music file.\n");
830 	}
831 }
832