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