1 /*
2  * MIDI streaming music support using WildMIDI library.
3  *
4  * wildmidi at least v0.2.3.x is required at both compile and runtime:
5  * Latest stable v0.3.12 (as of this writing) is highly recommended:
6  * - wildmidi-0.2.2 has a horrific mistake of freeing the buffer that
7  *   you pass with WildMidi_OpenBuffer() when you do WildMidi_Close().
8  * - wildmidi-0.2.3.x-0.3.x had a regression, resulting in perversely
9  *   high amount of heap usage (up to about 500mb) because of crazily
10  *   repetitive malloc/free calls; fixed as of 0.3.5.
11  * - wildmidi-0.2.x-0.3.x had a seek-to-0 bug, which might result in
12  *   truncated start issues with some midis; fixed as of 0.3.8.
13  * - wildmidi-0.2.x-0.3.x had a 'source' directive config parsing bug;
14  *   fixed as of 0.3.12.
15  * - the new wildmidi-0.4.x has some api changes against 0.2.3/0.3.x.
16  *   our client is adjusted for them, see LIBWILDMIDI_VERSION ifdefs
17  *   below.
18  *
19  * Copyright (C) 2010-2015 O.Sezer <sezero@users.sourceforge.net>
20  *
21  * $Id: snd_wildmidi.c 5850 2017-04-30 08:51:03Z sezero $
22  *
23  * This program is free software; you can redistribute it and/or modify
24  * it under the terms of the GNU General Public License as published by
25  * the Free Software Foundation; either version 2 of the License, or (at
26  * your option) any later version.
27  *
28  * This program is distributed in the hope that it will be useful, but
29  * WITHOUT ANY WARRANTY; without even the implied warranty of
30  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
31  *
32  * See the GNU General Public License for more details.
33  *
34  * You should have received a copy of the GNU General Public License along
35  * with this program; if not, write to the Free Software Foundation, Inc.,
36  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
37  *
38  */
39 
40 #include "quakedef.h"
41 
42 #if defined(USE_CODEC_WILDMIDI)
43 #include "snd_codec.h"
44 #include "snd_codeci.h"
45 #include "snd_wildmidi.h"
46 #include <wildmidi_lib.h>
47 #include "filenames.h"
48 
49 #if !defined(LIBWILDMIDI_VERSION) || (LIBWILDMIDI_VERSION-0 < 0x000400L)
50 #if !defined(WM_MO_ENHANCED_RESAMPLING)
51 #error wildmidi version 0.2.3.4 or newer is required.
52 #endif
53 #endif
54 
55 #define CACHEBUFFER_SIZE 4096
56 
57 typedef struct _midi_buf_t
58 {
59 	midi *song;
60 #if defined(LIBWILDMIDI_VERSION) && (LIBWILDMIDI_VERSION-0 >= 0x000400L)
61 	int8_t midi_buffer[CACHEBUFFER_SIZE];
62 #else
63 	char midi_buffer[CACHEBUFFER_SIZE];
64 #endif
65 	int pos, last;
66 } midi_buf_t;
67 
68 static unsigned short wildmidi_rate;
69 static unsigned short wildmidi_opts;
70 static const char *cfgfile[] = {
71 #if defined(__DJGPP__)  /* prefer '/' instead of '\\' */
72 	"C:/TIMIDITY",
73 #elif defined(PLATFORM_DOS) || defined(PLATFORM_WINDOWS) || defined(PLATFORM_OS2)
74 	"C:\\TIMIDITY",
75 #elif defined(__MORPHOS__)
76 	"LIBS:GerontoPlayer",
77 #elif defined(__AROS__)
78 	"Timidity:",/* disable the system requester for this (see below) */
79 #elif defined(PLATFORM_AMIGA)
80 	/**/
81 #else /* unix, osx, riscos, ... */
82 	"/etc",
83 	"/etc/wildmidi",
84 	"/etc/timidity",
85 	"/usr/share/timidity",
86 	"/usr/local/share/wildmidi",
87 	"/usr/local/share/timidity",
88 	"/usr/local/lib/timidity",
89 #endif
90 	NULL /* last entry must be NULL */
91 };
92 
WILDMIDI_InitHelper(const char * cfgdir)93 static int WILDMIDI_InitHelper (const char *cfgdir)
94 {
95 	char path[MAX_OSPATH];
96 	int len;
97 
98 	len = q_strlcpy(path, cfgdir, sizeof(path));
99 	if (len >= (int)sizeof(path) - 1)
100 		return -1;
101 	if (len && !IS_DIR_SEPARATOR(path[len - 1]))
102 		path[len++] = DIR_SEPARATOR_CHAR;
103 
104 	path[len] = '\0';
105 	q_strlcat(path, "wildmidi.cfg", sizeof(path));
106 	Con_DPrintf("WildMIDI: trying %s\n", path);
107 #ifdef PLATFORM_AMIGA
108 	if(Sys_PathExistsQuiet(path))
109 #endif
110 	if (WildMidi_Init(path, wildmidi_rate, wildmidi_opts) == 0)
111 		return 0;
112 
113 	path[len] = '\0';
114 	q_strlcat(path, "timidity.cfg", sizeof(path));
115 	Con_DPrintf("WildMIDI: trying %s\n", path);
116 #ifdef PLATFORM_AMIGA
117 	if (!Sys_PathExistsQuiet(path)) return -1;
118 #endif
119 	return WildMidi_Init(path, wildmidi_rate, wildmidi_opts);
120 }
121 
S_WILDMIDI_CodecInitialize(void)122 static qboolean S_WILDMIDI_CodecInitialize (void)
123 {
124 	const char *timi_env;
125 	int i, err;
126 
127 	if (wildmidi_codec.initialized)
128 		return true;
129 
130 	wildmidi_opts = 0;/* WM_MO_ENHANCED_RESAMPLING is a cpu hog: no need. */
131 	if (shm->speed < 11025)
132 		wildmidi_rate = 11025;
133 	else if (shm->speed > 48000)
134 		wildmidi_rate = 44100;
135 	else	wildmidi_rate = shm->speed;
136 
137 	err = -1;
138 	timi_env = getenv("WILDMIDI_CFG");
139 	if (timi_env == NULL)
140 		timi_env = getenv("TIMIDITY_CFG");
141 	if (timi_env)
142 	{
143 		Con_DPrintf("WildMIDI: trying %s\n", timi_env);
144 		/* env is an override: if it fails, we
145 		 * don't bother trying anything else. */
146 		err = WildMidi_Init(timi_env, wildmidi_rate, wildmidi_opts);
147 		goto _finish;
148 	}
149 #if DO_USERDIRS
150 	/* check under the user's directory first: */
151 	err = WILDMIDI_InitHelper(FS_GetUserbase());
152 #endif
153 	/* then, check under the installation dir: */
154 	if (err != 0)
155 		err = WILDMIDI_InitHelper(FS_GetBasedir());
156 	/* lastly, check with the system locations: */
157 	for (i = 0; err != 0 && cfgfile[i] != NULL; ++i)
158 		err = WILDMIDI_InitHelper(cfgfile[i]);
159 
160 	_finish:
161 	if (err != 0)
162 	{
163 		Con_Printf ("Could not initialize WildMIDI\n");
164 		return false;
165 	}
166 
167 	Con_Printf ("WildMIDI initialized\n");
168 	wildmidi_codec.initialized = true;
169 
170 	return true;
171 }
172 
S_WILDMIDI_CodecShutdown(void)173 static void S_WILDMIDI_CodecShutdown (void)
174 {
175 	if (!wildmidi_codec.initialized)
176 		return;
177 	wildmidi_codec.initialized = false;
178 	Con_Printf("Shutting down WildMIDI.\n");
179 	WildMidi_Shutdown();
180 }
181 
S_WILDMIDI_CodecOpenStream(snd_stream_t * stream)182 static qboolean S_WILDMIDI_CodecOpenStream (snd_stream_t *stream)
183 {
184 	midi_buf_t *data;
185 	unsigned char *temp;
186 	long len;
187 	int mark;
188 
189 	if (!wildmidi_codec.initialized)
190 		return false;
191 
192 	len = FS_filelength(&stream->fh);
193 	mark = Hunk_LowMark();
194 	temp = (unsigned char *) Hunk_Alloc(len);
195 	FS_fread(temp, 1, len, &stream->fh);
196 
197 	data = (midi_buf_t *) Z_Malloc(sizeof(midi_buf_t), Z_MAINZONE);
198 	data->song = WildMidi_OpenBuffer (temp, len);
199 	Hunk_FreeToLowMark(mark); /* free original file data */
200 	if (data->song == NULL)
201 	{
202 		Con_Printf ("%s is not a valid MIDI file\n", stream->name);
203 		Z_Free(data);
204 		return false;
205 	}
206 	stream->info.rate = wildmidi_rate;
207 	stream->info.width = 2; /* WildMIDI does 16 bit signed */
208 	stream->info.bits = 16;
209 	stream->info.channels = 2; /* WildMIDI does stereo */
210 	stream->priv = data;
211 	WildMidi_MasterVolume (100);
212 
213 	return true;
214 }
215 
216 #if !defined(LIBWILDMIDI_VERSION) || (LIBWILDMIDI_VERSION-0 < 0x000400L)
S_WILDMIDI_ConvertSamples(char * dat,int samples)217 static inline void S_WILDMIDI_ConvertSamples (char *dat, int samples) {
218 	/* libWildMidi-0.2.x/0.3.x return little-endian samples. */
219 	short *swp = (short *) dat;
220 	int i = 0;
221 	for (; i < samples; i++)
222 		swp[i] = LittleShort(swp[i]);
223 }
224 #else /* libWildMidi >= 0.4.x */
S_WILDMIDI_ConvertSamples(int8_t * dat,int samples)225 static inline void S_WILDMIDI_ConvertSamples (int8_t *dat, int samples) {
226 	/* libWildMidi >= 0.4.x returns host-endian samples: no swap. */
227 }
228 #endif /* LIBWILDMIDI_VERSION */
229 
S_WILDMIDI_CodecReadStream(snd_stream_t * stream,int bytes,void * buffer)230 static int S_WILDMIDI_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer)
231 {
232 	midi_buf_t *data = (midi_buf_t *) stream->priv;
233 	if (data->pos == 0)
234 	{
235 		data->last = WildMidi_GetOutput (data->song, data->midi_buffer,
236 							CACHEBUFFER_SIZE);
237 		if (data->last == 0)
238 			return 0;
239 		if (bytes > data->last)
240 			bytes = data->last;
241 	}
242 	else if (data->pos + bytes > data->last)
243 	{
244 		bytes = data->last - data->pos;
245 	}
246 	S_WILDMIDI_ConvertSamples (& data->midi_buffer[data->pos], bytes / 2);
247 	memcpy (buffer, & data->midi_buffer[data->pos], bytes);
248 	data->pos += bytes;
249 	if (data->pos == data->last)
250 		data->pos = 0;
251 	return bytes;
252 }
253 
S_WILDMIDI_CodecCloseStream(snd_stream_t * stream)254 static void S_WILDMIDI_CodecCloseStream (snd_stream_t *stream)
255 {
256 	WildMidi_Close (((midi_buf_t *)stream->priv)->song);
257 	Z_Free(stream->priv);
258 	S_CodecUtilClose(&stream);
259 }
260 
S_WILDMIDI_CodecRewindStream(snd_stream_t * stream)261 static int S_WILDMIDI_CodecRewindStream (snd_stream_t *stream)
262 {
263 	unsigned long pos = 0;
264 	return WildMidi_FastSeek (((midi_buf_t *)stream->priv)->song, &pos);
265 }
266 
267 snd_codec_t wildmidi_codec =
268 {
269 	CODECTYPE_MIDI,
270 	false,
271 	"mid",
272 	S_WILDMIDI_CodecInitialize,
273 	S_WILDMIDI_CodecShutdown,
274 	S_WILDMIDI_CodecOpenStream,
275 	S_WILDMIDI_CodecReadStream,
276 	S_WILDMIDI_CodecRewindStream,
277 	S_WILDMIDI_CodecCloseStream,
278 	NULL
279 };
280 
281 #endif	/* USE_CODEC_WILDMIDI */
282 
283