1 /*
2 rtwinmm.c:
3
4 Copyright (C) 2005 Istvan Varga
5
6 This file is part of Csound.
7
8 The Csound Library is free software; you can redistribute it
9 and/or modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 Csound is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with Csound; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 02110-1301 USA
22 */
23
24 #include <windows.h>
25 #include "csdl.h"
26 #include "soundio.h"
27
28 #ifdef MAXBUFFERS
29 #undef MAXBUFFERS
30 #endif
31 #define MAXBUFFERS 256
32
33 typedef struct rtWinMMDevice_ {
34 HWAVEIN inDev;
35 HWAVEOUT outDev;
36 int cur_buf;
37 int nBuffers;
38 int seed; /* random seed for dithering */
39 int enable_buf_timer;
40 /* playback sample conversion function */
41 void (*playconv)(int, MYFLT*, void*, int*);
42 /* record sample conversion function */
43 void (*rec_conv)(int, void*, MYFLT*);
44 int64_t prv_time;
45 float timeConv, bufTime;
46 WAVEHDR buffers[MAXBUFFERS];
47 } rtWinMMDevice;
48
49 typedef struct rtWinMMGlobals_ {
50 rtWinMMDevice *inDev;
51 rtWinMMDevice *outDev;
52 int enable_buf_timer;
53 } rtWinMMGlobals;
54
55 #define MBUFSIZE 1024
56
57 typedef struct rtmidi_mme_globals_ {
58 HMIDIIN inDev;
59 int inBufWritePos;
60 int inBufReadPos;
61 DWORD inBuf[MBUFSIZE];
62 CRITICAL_SECTION threadLock;
63 } RTMIDI_MME_GLOBALS;
64
65 static const unsigned char msg_bytes[32] = {
66 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
67 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 0, 1
68 };
69
large_integer_to_int64(LARGE_INTEGER * p)70 static inline int64_t large_integer_to_int64(LARGE_INTEGER *p)
71 {
72 return ((int64_t) p->LowPart + ((int64_t) p->HighPart << 32));
73 }
74
err_msg(CSOUND * csound,const char * fmt,...)75 static int err_msg(CSOUND *csound, const char *fmt, ...)
76 {
77 va_list args;
78 va_start(args, fmt);
79 csound->ErrMsgV(csound, Str("winmm: error: "), fmt, args);
80 va_end(args);
81 return -1;
82 }
83
allocate_buffers(CSOUND * csound,rtWinMMDevice * dev,const csRtAudioParams * parm,int is_playback)84 static int allocate_buffers(CSOUND *csound, rtWinMMDevice *dev,
85 const csRtAudioParams *parm,
86 int is_playback)
87 {
88 HGLOBAL ptr;
89 int i, err = 0, bufFrames, bufSamples, bufBytes;
90
91 bufFrames = parm->bufSamp_SW;
92 bufSamples = bufFrames * parm->nChannels;
93 bufBytes = bufSamples * (parm->sampleFormat == AE_SHORT ? 2 : 4);
94 dev->nBuffers = parm->bufSamp_HW / parm->bufSamp_SW;
95 if (dev->nBuffers < 2)
96 dev->nBuffers = 2;
97 if (dev->enable_buf_timer)
98 dev->nBuffers *= 2;
99 if (UNLIKELY(dev->nBuffers > MAXBUFFERS)) {
100 dev->nBuffers = 0;
101 return err_msg(csound, Str("too many buffers"));
102 }
103 for (i = 0; i < dev->nBuffers; i++) {
104 ptr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (SIZE_T) bufBytes);
105 if (UNLIKELY(ptr == (HGLOBAL) NULL)) {
106 dev->nBuffers = i;
107 return err_msg(csound, Str("memory allocation failure"));
108 }
109 dev->buffers[i].lpData = (LPSTR) GlobalLock(ptr);
110 memset((void*) dev->buffers[i].lpData, 0, (size_t) bufBytes);
111 dev->buffers[i].dwBufferLength = (DWORD) bufBytes;
112 if (is_playback)
113 err = (int) waveOutPrepareHeader(dev->outDev,
114 (LPWAVEHDR) &(dev->buffers[i]),
115 sizeof(WAVEHDR));
116 else
117 err = (int) waveInPrepareHeader(dev->inDev,
118 (LPWAVEHDR) &(dev->buffers[i]),
119 sizeof(WAVEHDR));
120 if (UNLIKELY(err != MMSYSERR_NOERROR))
121 return err_msg(csound, Str("failed to prepare buffers"));
122 dev->buffers[i].dwFlags |= WHDR_DONE;
123 }
124 return 0;
125 }
126
set_format_params(CSOUND * csound,WAVEFORMATEX * wfx,const csRtAudioParams * parm)127 static int set_format_params(CSOUND *csound, WAVEFORMATEX *wfx,
128 const csRtAudioParams *parm)
129 {
130 int sampsize = 4, framsize;
131 memset(wfx, 0, sizeof(WAVEFORMATEX));
132 switch (parm->sampleFormat) {
133 case AE_SHORT:
134 sampsize = 2;
135 break;
136 case AE_LONG:
137 case AE_FLOAT:
138 break;
139 default:
140 return err_msg(csound, Str("invalid sample format: "
141 "must be -s, -l, or -f"));
142 }
143 framsize = sampsize * parm->nChannels;
144 wfx->wFormatTag = (WORD) (parm->sampleFormat == AE_FLOAT ? 3 : 1);
145 wfx->nChannels = (WORD) parm->nChannels;
146 wfx->nSamplesPerSec = (DWORD) ((double) parm->sampleRate + 0.5);
147 wfx->nAvgBytesPerSec = (DWORD) ((int) wfx->nSamplesPerSec * framsize);
148 wfx->nBlockAlign = (DWORD) framsize;
149 wfx->wBitsPerSample = (DWORD) (sampsize << 3);
150 return 0;
151 }
152
153 /* sample conversion routines for playback */
154
MYFLT_to_short(int nSmps,MYFLT * inBuf,int16_t * outBuf,int * seed)155 static void MYFLT_to_short(int nSmps, MYFLT *inBuf, int16_t *outBuf, int *seed)
156 {
157 MYFLT tmp_f;
158 int tmp_i;
159 int n;
160 for (n=0; n<nSmps; n++){
161 int rnd = (((*seed) * 15625) + 1) & 0xFFFF;
162 *seed = (((rnd) * 15625) + 1) & 0xFFFF;
163 rnd += *seed; /* triangular distribution */
164 tmp_f = (MYFLT) ((rnd>>1) - 0x8000) * (FL(1.0) / (MYFLT) 0x10000);
165 tmp_f += inBuf[n] * (MYFLT) 0x8000;
166 tmp_i = (int) MYFLT2LRND(tmp_f);
167 if (tmp_i < -0x8000) tmp_i = -0x8000;
168 if (tmp_i > 0x7FFF) tmp_i = 0x7FFF;
169 outBuf[n] = (int16_t) tmp_i;
170 }
171 }
172
MYFLT_to_short_u(int nSmps,MYFLT * inBuf,int16_t * outBuf,int * seed)173 static void MYFLT_to_short_u(int nSmps, MYFLT *inBuf, int16_t *outBuf, int *seed)
174 {
175 MYFLT tmp_f;
176 int tmp_i;
177 int n;
178 for (n=0; n<nSmps; n++) {
179 int rnd = (((*seed) * 15625) + 1) & 0xFFFF;
180 *seed = rnd;
181 tmp_f = (MYFLT) (rnd - 0x8000) * (FL(1.0) / (MYFLT) 0x10000);
182 tmp_f += inBuf[n] * (MYFLT) 0x8000;
183 tmp_i = (int) MYFLT2LRND(tmp_f);
184 if (tmp_i < -0x8000) tmp_i = -0x8000;
185 if (tmp_i > 0x7FFF) tmp_i = 0x7FFF;
186 outBuf[n] = (int16_t) tmp_i;
187 }
188 }
189
MYFLT_to_short_no_dither(int nSmps,MYFLT * inBuf,int16_t * outBuf,int * seed)190 static void MYFLT_to_short_no_dither(int nSmps, MYFLT *inBuf,
191 int16_t *outBuf, int *seed)
192 {
193 MYFLT tmp_f;
194 int tmp_i;
195 int n;
196 for (n=0; n<nSmps; n++){
197 tmp_f = inBuf[n] * (MYFLT) 0x8000;
198 tmp_i = (int) MYFLT2LRND(tmp_f);
199 if (tmp_i < -0x8000) tmp_i = -0x8000;
200 if (tmp_i > 0x7FFF) tmp_i = 0x7FFF;
201 outBuf[n] = (int16_t) tmp_i;
202 }
203 }
204
MYFLT_to_long(int nSmps,MYFLT * inBuf,int32_t * outBuf,int * seed)205 static void MYFLT_to_long(int nSmps, MYFLT *inBuf, int32_t *outBuf, int *seed)
206 {
207 MYFLT tmp_f;
208 int64_t tmp_i;
209 (void) seed;
210 int n;
211 for (n=0; n<nSmps; n++) {
212 tmp_f = inBuf[n] * (MYFLT) 0x80000000UL;
213 tmp_i = (int64_t) (tmp_f + (tmp_f < FL(0.0) ? FL(-0.5) : FL(0.5)));
214 if (tmp_i < -((int64_t) 0x80000000UL))
215 tmp_i = -((int64_t) 0x80000000UL);
216 if (tmp_i > (int64_t) 0x7FFFFFFF) tmp_i = (int64_t) 0x7FFFFFFF;
217 outBuf[n] = (int32_t) tmp_i;
218 }
219 }
220
MYFLT_to_float(int nSmps,MYFLT * inBuf,float * outBuf,int * seed)221 static void MYFLT_to_float(int nSmps, MYFLT *inBuf, float *outBuf, int *seed)
222 {
223 (void) seed;
224 int n;
225 for (n=0; n<nSmps; n++)
226 outBuf[n] = (float) inBuf[n];
227 }
228
229 /* sample conversion routines for recording */
230
short_to_MYFLT(int nSmps,int16_t * inBuf,MYFLT * outBuf)231 static void short_to_MYFLT(int nSmps, int16_t *inBuf, MYFLT *outBuf)
232 {
233 while (nSmps--)
234 *(outBuf++) = (MYFLT) *(inBuf++) * (FL(1.0) / (MYFLT) 0x8000);
235 }
236
long_to_MYFLT(int nSmps,int32_t * inBuf,MYFLT * outBuf)237 static void long_to_MYFLT(int nSmps, int32_t *inBuf, MYFLT *outBuf)
238 {
239 int n;
240 for (n=0; n<nSmps; n++)
241 outBuf[n] = (MYFLT) inBuf[n] * (FL(1.0) / (MYFLT) 0x80000000UL);
242 }
243
float_to_MYFLT(int nSmps,float * inBuf,MYFLT * outBuf)244 static void float_to_MYFLT(int nSmps, float *inBuf, MYFLT *outBuf)
245 {
246 int n;
247 for (n=0; n<nSmps; n++)
248 outBuf[n] = (MYFLT) inBuf[n];
249 }
250
open_device(CSOUND * csound,const csRtAudioParams * parm,int is_playback)251 static int open_device(CSOUND *csound,
252 const csRtAudioParams *parm, int is_playback)
253 {
254 rtWinMMGlobals *p;
255 rtWinMMDevice *dev;
256 WAVEFORMATEX wfx;
257 LARGE_INTEGER pp;
258 int i, ndev, devNum, conv_idx;
259 DWORD openFlags = CALLBACK_NULL;
260
261 if (UNLIKELY(parm->devName != NULL))
262 return err_msg(csound, Str("Must specify a device number, not a name"));
263 if (set_format_params(csound, &wfx, parm) != 0)
264 return -1;
265 devNum = (parm->devNum == 1024 ? 0 : parm->devNum);
266 if (parm->sampleFormat != AE_FLOAT && ((int) GetVersion() & 0xFF) >= 0x05)
267 openFlags |= WAVE_FORMAT_DIRECT;
268
269 if (is_playback) {
270 WAVEOUTCAPSA caps;
271 ndev = (int) waveOutGetNumDevs();
272 csound->Message(csound, Str("The available output devices are:\n"));
273 for (i = 0; i < ndev; i++) {
274 waveOutGetDevCapsA((unsigned int) i, (LPWAVEOUTCAPSA) &caps,
275 sizeof(WAVEOUTCAPSA));
276 csound->Message(csound, Str("%3d: %s\n"), i, (char*) caps.szPname);
277 }
278 if (UNLIKELY(ndev < 1))
279 return err_msg(csound, Str("No output device is available"));
280 if (UNLIKELY(devNum < 0 || devNum >= ndev)) {
281 return err_msg(csound, Str("Device number is out of range"));
282 }
283 waveOutGetDevCapsA((unsigned int) devNum, (LPWAVEOUTCAPSA) &caps,
284 sizeof(WAVEOUTCAPSA));
285 csound->Message(csound, Str("winmm: opening output device %d (%s)\n"),
286 devNum, (char*) caps.szPname);
287 }
288 else {
289 WAVEINCAPSA caps;
290 ndev = (int) waveInGetNumDevs();
291 csound->Message(csound, Str("The available input devices are:\n"));
292 for (i = 0; i < ndev; i++) {
293 waveInGetDevCapsA((unsigned int) i, (LPWAVEINCAPSA) &caps,
294 sizeof(WAVEINCAPSA));
295 csound->Message(csound, Str("%3d: %s\n"), i, (char*) caps.szPname);
296 }
297 if (UNLIKELY(ndev < 1))
298 return err_msg(csound, Str("no input device is available"));
299 if (UNLIKELY(devNum < 0 || devNum >= ndev)) {
300 return err_msg(csound, Str("device number is out of range"));
301 }
302 waveInGetDevCapsA((unsigned int) devNum, (LPWAVEINCAPSA) &caps,
303 sizeof(WAVEINCAPSA));
304 csound->Message(csound, Str("winmm: opening input device %d (%s)\n"),
305 devNum, (char*) caps.szPname);
306 }
307 p = (rtWinMMGlobals*)
308 csound->QueryGlobalVariable(csound, "_rtwinmm_globals");
309 dev = (rtWinMMDevice*) csound->Malloc(csound, sizeof(rtWinMMDevice));
310 if (UNLIKELY(dev == NULL))
311 return err_msg(csound, Str("memory allocation failure"));
312 memset(dev, 0, sizeof(rtWinMMDevice));
313 conv_idx = (parm->sampleFormat == AE_SHORT ?
314 0 : (parm->sampleFormat == AE_LONG ? 1 : 2));
315 if (is_playback) {
316 p->outDev = dev;
317 *(csound->GetRtPlayUserData(csound)) = (void*) dev;
318 dev->enable_buf_timer = p->enable_buf_timer;
319 if (UNLIKELY(waveOutOpen((LPHWAVEOUT) &(dev->outDev), (unsigned int) devNum,
320 (LPWAVEFORMATEX) &wfx, 0, 0,
321 openFlags) != MMSYSERR_NOERROR)) {
322 dev->outDev = (HWAVEOUT) 0;
323 return err_msg(csound, Str("failed to open device"));
324 }
325 switch (conv_idx) {
326 case 0:
327 if (csound->GetDitherMode(csound)==1)
328 dev->playconv =
329 (void (*)(int, MYFLT*, void*, int*)) MYFLT_to_short;
330 else if (csound->GetDitherMode(csound)==2)
331 dev->playconv =
332 (void (*)(int, MYFLT*, void*, int*)) MYFLT_to_short_u;
333 else
334 dev->playconv =
335 (void (*)(int, MYFLT*, void*, int*)) MYFLT_to_short_no_dither;
336 break;
337 case 1: dev->playconv =
338 (void (*)(int, MYFLT*, void*, int*)) MYFLT_to_long; break;
339 case 2: dev->playconv =
340 (void (*)(int, MYFLT*, void*, int*)) MYFLT_to_float; break;
341 }
342 }
343 else {
344 p->inDev = dev;
345 *(csound->GetRtRecordUserData(csound)) = (void*) dev;
346 /* disable playback timer in full-duplex mode */
347 dev->enable_buf_timer = p->enable_buf_timer = 0;
348 if (UNLIKELY(waveInOpen((LPHWAVEIN) &(dev->inDev), (unsigned int) devNum,
349 (LPWAVEFORMATEX) &wfx, 0, 0,
350 openFlags) != MMSYSERR_NOERROR)) {
351 dev->inDev = (HWAVEIN) 0;
352 return err_msg(csound, Str("failed to open device"));
353 }
354 switch (conv_idx) {
355 case 0: dev->rec_conv =
356 (void (*)(int, void*, MYFLT*)) short_to_MYFLT; break;
357 case 1: dev->rec_conv =
358 (void (*)(int, void*, MYFLT*)) long_to_MYFLT; break;
359 case 2: dev->rec_conv =
360 (void (*)(int, void*, MYFLT*)) float_to_MYFLT; break;
361 }
362 }
363 if (UNLIKELY(allocate_buffers(csound, dev, parm, is_playback) != 0))
364 return -1;
365 if (!is_playback)
366 waveInStart(dev->inDev);
367 QueryPerformanceFrequency(&pp);
368 dev->timeConv = 1000.0f / (float) large_integer_to_int64(&pp);
369 dev->bufTime = 1000.0f * (float) parm->bufSamp_SW / parm->sampleRate;
370 QueryPerformanceCounter(&pp);
371 dev->prv_time = large_integer_to_int64(&pp);
372 return 0;
373 }
374
375 /* open for audio input */
376
recopen_(CSOUND * csound,const csRtAudioParams * parm)377 static int recopen_(CSOUND *csound, const csRtAudioParams *parm)
378 {
379 return open_device(csound, parm, 0);
380 }
381
382 /* open for audio output */
383
playopen_(CSOUND * csound,const csRtAudioParams * parm)384 static int playopen_(CSOUND *csound, const csRtAudioParams *parm)
385 {
386 return open_device(csound, parm, 1);
387 }
388
389 /* get samples from ADC */
390
rtrecord_(CSOUND * csound,MYFLT * inBuf,int nbytes)391 static int rtrecord_(CSOUND *csound, MYFLT *inBuf, int nbytes)
392 {
393 rtWinMMDevice *dev = (rtWinMMDevice*) *(csound->GetRtRecordUserData(csound));
394 WAVEHDR *buf = &(dev->buffers[dev->cur_buf]);
395 volatile DWORD *dwFlags = &(buf->dwFlags);
396
397 dev->rec_conv(nbytes / (int) sizeof(MYFLT), (void*) buf->lpData, inBuf);
398 while (!(*dwFlags & WHDR_DONE))
399 Sleep(1);
400 waveInAddBuffer(dev->inDev, (LPWAVEHDR) buf, sizeof(WAVEHDR));
401 if (++(dev->cur_buf) >= dev->nBuffers)
402 dev->cur_buf = 0;
403 return nbytes;
404 }
405
406 /* put samples to DAC */
407
408 /* N.B. This routine serves as a THROTTLE in Csound Realtime Performance, */
409 /* delaying the actual writes and return until the hardware output buffer */
410 /* passes a sample-specific THRESHOLD. If the I/O BLOCKING functionality */
411 /* is implemented ACCURATELY by the vendor-supplied audio-library write, */
412 /* that is sufficient. Otherwise, requires some kind of IOCTL from here. */
413 /* This functionality is IMPORTANT when other realtime I/O is occurring, */
414 /* such as when external MIDI data is being collected from a serial port. */
415 /* Since Csound polls for MIDI input at the software synthesis K-rate */
416 /* (the resolution of all software-synthesized events), the user can */
417 /* eliminate MIDI jitter by requesting that both be made synchronous with */
418 /* the above audio I/O blocks, i.e. by setting -b to some 1 or 2 K-prds. */
419
rtplay_(CSOUND * csound,const MYFLT * outBuf,int nbytes)420 static void rtplay_(CSOUND *csound, const MYFLT *outBuf, int nbytes)
421 {
422 rtWinMMDevice *dev = (rtWinMMDevice*) *(csound->GetRtPlayUserData(csound));
423 WAVEHDR *buf = &(dev->buffers[dev->cur_buf]);
424 volatile DWORD *dwFlags = &(buf->dwFlags);
425 LARGE_INTEGER pp;
426 int64_t curTime;
427 float timeDiff, timeWait;
428 int i, nbufs;
429
430 while (!(*dwFlags & WHDR_DONE))
431 Sleep(1);
432 dev->playconv(nbytes / (int) sizeof(MYFLT),
433 (MYFLT*) outBuf, (void*) buf->lpData, &(dev->seed));
434 waveOutWrite(dev->outDev, (LPWAVEHDR) buf, sizeof(WAVEHDR));
435 if (++(dev->cur_buf) >= dev->nBuffers)
436 dev->cur_buf = 0;
437
438 if (!dev->enable_buf_timer)
439 return;
440
441 QueryPerformanceCounter(&pp);
442 curTime = large_integer_to_int64(&pp);
443 timeDiff = (float) (curTime - dev->prv_time) * dev->timeConv;
444 dev->prv_time = curTime;
445 for (i = nbufs = 0; i < dev->nBuffers; i++) {
446 if (!(dev->buffers[i].dwFlags & WHDR_DONE))
447 nbufs++;
448 }
449 if (nbufs <= 1)
450 return;
451 timeWait = dev->bufTime;
452 timeWait *= (((float) nbufs / (float) dev->nBuffers) * 0.25f + 0.875f);
453 timeWait -= timeDiff;
454 i = MYFLT2LRND(timeWait);
455 if (i > 0)
456 Sleep((DWORD) i);
457 }
458
rtclose_(CSOUND * csound)459 static void rtclose_(CSOUND *csound)
460 {
461 rtWinMMGlobals *pp;
462 rtWinMMDevice *inDev, *outDev;
463 int i;
464
465 *(csound->GetRtPlayUserData(csound)) = NULL;
466 *(csound->GetRtRecordUserData(csound)) = NULL;
467 pp = (rtWinMMGlobals*) csound->QueryGlobalVariable(csound,
468 "_rtwinmm_globals");
469 if (pp == NULL)
470 return;
471 inDev = pp->inDev;
472 outDev = pp->outDev;
473 csound->DestroyGlobalVariable(csound, "_rtwinmm_globals");
474 if (inDev != NULL) {
475 waveInStop(inDev->inDev);
476 waveInReset(inDev->inDev);
477 for (i = 0; i < inDev->nBuffers; i++) {
478 waveInUnprepareHeader(inDev->inDev, (LPWAVEHDR) &(inDev->buffers[i]),
479 sizeof(WAVEHDR));
480 GlobalUnlock((HGLOBAL) inDev->buffers[i].lpData);
481 GlobalFree((HGLOBAL) inDev->buffers[i].lpData);
482 }
483 waveInClose(inDev->inDev);
484 csound->Free(csound,inDev);
485 }
486 if (outDev != NULL) {
487 volatile DWORD *dwFlags = &(outDev->buffers[outDev->cur_buf].dwFlags);
488 while (!(*dwFlags & WHDR_DONE))
489 Sleep(1);
490 waveOutReset(outDev->outDev);
491 for (i = 0; i < outDev->nBuffers; i++) {
492 waveOutUnprepareHeader(outDev->outDev,
493 (LPWAVEHDR) &(outDev->buffers[i]),
494 sizeof(WAVEHDR));
495 GlobalUnlock((HGLOBAL) outDev->buffers[i].lpData);
496 GlobalFree((HGLOBAL) outDev->buffers[i].lpData);
497 }
498 waveOutClose(outDev->outDev);
499 csound->Free(csound,outDev);
500 }
501 }
502
midi_in_handler(HMIDIIN hmin,UINT wMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)503 static void CALLBACK midi_in_handler(HMIDIIN hmin, UINT wMsg, DWORD_PTR dwInstance,
504 DWORD_PTR dwParam1, DWORD_PTR dwParam2)
505 {
506 RTMIDI_MME_GLOBALS *p = (RTMIDI_MME_GLOBALS*) dwInstance;
507 int new_pos;
508
509 (void) hmin;
510 (void) dwParam2;
511 if (wMsg != MIM_DATA) /* ignore non-data messages */
512 return;
513 EnterCriticalSection(&(p->threadLock));
514 new_pos = (p->inBufWritePos + 1) & (MBUFSIZE - 1);
515 if (new_pos != p->inBufReadPos) {
516 p->inBuf[p->inBufWritePos] = dwParam1;
517 p->inBufWritePos = new_pos;
518 }
519 LeaveCriticalSection(&(p->threadLock));
520 }
521
midi_in_open(CSOUND * csound,void ** userData,const char * devName)522 static int midi_in_open(CSOUND *csound, void **userData, const char *devName)
523 {
524 int i, ndev, devnum = 0;
525 RTMIDI_MME_GLOBALS *p;
526 MIDIINCAPS caps;
527
528 *userData = NULL;
529 ndev = (int) midiInGetNumDevs();
530 if (UNLIKELY(ndev < 1)) {
531 csound->ErrorMsg(csound, Str("rtmidi: no input devices are available"));
532 return -1;
533 }
534 if (devName != NULL && devName[0] != '\0' &&
535 strcmp(devName, "default") != 0) {
536 if (UNLIKELY(devName[0] < '0' || devName[0] > '9')) {
537 csound->ErrorMsg(csound, Str("rtmidi: must specify a device number, "
538 "not a name"));
539 return -1;
540 }
541 devnum = (int) atoi(devName);
542 }
543 csound->Message(csound, Str("The available MIDI input devices are:\n"));
544 for (i = 0; i < ndev; i++) {
545 midiInGetDevCaps((unsigned int) i, &caps, sizeof(MIDIINCAPS));
546 csound->Message(csound, "%3d: %s\n", i, &(caps.szPname[0]));
547 }
548 if (UNLIKELY(devnum < 0 || devnum >= ndev)) {
549 csound->ErrorMsg(csound,
550 Str("rtmidi: input device number is out of range"));
551 return -1;
552 }
553 p = (RTMIDI_MME_GLOBALS*) csound->Calloc(csound,
554 (size_t) sizeof(RTMIDI_MME_GLOBALS));
555 if (UNLIKELY(p == NULL)) {
556 csound->ErrorMsg(csound, Str("rtmidi: memory allocation failure"));
557 return -1;
558 }
559 InitializeCriticalSection(&(p->threadLock));
560 *userData = (void*) p;
561 midiInGetDevCaps((unsigned int) devnum, &caps, sizeof(MIDIINCAPS));
562 csound->Message(csound, Str("Opening MIDI input device %d (%s)\n"),
563 devnum, &(caps.szPname[0]));
564 if (midiInOpen(&(p->inDev), (unsigned int) devnum,
565 (DWORD_PTR) midi_in_handler, (DWORD_PTR) p, CALLBACK_FUNCTION)
566 != MMSYSERR_NOERROR) {
567 p->inDev = (HMIDIIN) 0;
568 csound->ErrorMsg(csound, Str("rtmidi: could not open input device"));
569 return -1;
570 }
571 midiInStart(p->inDev);
572
573 return 0;
574 }
575
midi_in_read(CSOUND * csound,void * userData,unsigned char * buf,int nbytes)576 static int midi_in_read(CSOUND *csound,
577 void *userData, unsigned char *buf, int nbytes)
578 {
579 RTMIDI_MME_GLOBALS *p = (RTMIDI_MME_GLOBALS*) userData;
580 unsigned int tmp;
581 unsigned char s, d1, d2, len;
582 int nread = 0;
583
584 (void) csound;
585 EnterCriticalSection(&(p->threadLock));
586 while (p->inBufReadPos != p->inBufWritePos) {
587 tmp = (unsigned int) p->inBuf[p->inBufReadPos++];
588 p->inBufReadPos &= (MBUFSIZE - 1);
589 s = (unsigned char) tmp; tmp >>= 8;
590 d1 = (unsigned char) tmp; tmp >>= 8;
591 d2 = (unsigned char) tmp;
592 len = msg_bytes[(int) s >> 3];
593 if (nread + len > nbytes)
594 break;
595 if (len) {
596 buf[nread++] = s;
597 if (--len) {
598 buf[nread++] = d1;
599 if (--len)
600 buf[nread++] = d2;
601 }
602 }
603 }
604 LeaveCriticalSection(&(p->threadLock));
605
606 return nread;
607 }
608
midi_in_close(CSOUND * csound,void * userData)609 static int midi_in_close(CSOUND *csound, void *userData)
610 {
611 RTMIDI_MME_GLOBALS *p = (RTMIDI_MME_GLOBALS*) userData;
612
613 (void) csound;
614 if (p == NULL)
615 return 0;
616 if (p->inDev != (HMIDIIN) 0) {
617 midiInStop(p->inDev);
618 midiInReset(p->inDev);
619 midiInClose(p->inDev);
620 }
621 DeleteCriticalSection(&(p->threadLock));
622 csound->Free(csound,p);
623
624 return 0;
625 }
626
midi_out_open(CSOUND * csound,void ** userData,const char * devName)627 static int midi_out_open(CSOUND *csound, void **userData, const char *devName)
628 {
629 int i, ndev, devnum = 0;
630 MIDIOUTCAPS caps;
631 HMIDIOUT outDev = (HMIDIOUT) 0;
632
633 *userData = NULL;
634 ndev = (int) midiOutGetNumDevs();
635 if (UNLIKELY(ndev < 1)) {
636 csound->ErrorMsg(csound, Str("rtmidi: no output devices are available"));
637 return -1;
638 }
639 if (devName != NULL && devName[0] != '\0' &&
640 strcmp(devName, "default") != 0) {
641 if (UNLIKELY(devName[0] < '0' || devName[0] > '9')) {
642 csound->ErrorMsg(csound, Str("rtmidi: must specify a device number, "
643 "not a name"));
644 return -1;
645 }
646 devnum = (int) atoi(devName);
647 }
648 csound->Message(csound, Str("The available MIDI output devices are:\n"));
649 for (i = 0; i < ndev; i++) {
650 midiOutGetDevCaps((unsigned int) i, &caps, sizeof(MIDIOUTCAPS));
651 csound->Message(csound, "%3d: %s\n", i, &(caps.szPname[0]));
652 }
653 if (UNLIKELY(devnum < 0 || devnum >= ndev)) {
654 csound->ErrorMsg(csound,
655 Str("rtmidi: output device number is out of range"));
656 return -1;
657 }
658 midiOutGetDevCaps((unsigned int) devnum, &caps, sizeof(MIDIOUTCAPS));
659 csound->Message(csound, Str("Opening MIDI output device %d (%s)\n"),
660 devnum, &(caps.szPname[0]));
661 if (UNLIKELY(midiOutOpen(&outDev, (unsigned int) devnum,
662 (DWORD) 0, (DWORD) 0,
663 CALLBACK_NULL) != MMSYSERR_NOERROR)) {
664 csound->ErrorMsg(csound, Str("rtmidi: could not open output device"));
665 return -1;
666 }
667 *userData = (void*) outDev;
668
669 return 0;
670 }
671
midi_out_write(CSOUND * csound,void * userData,const unsigned char * buf,int nbytes)672 static int midi_out_write(CSOUND *csound,
673 void *userData, const unsigned char *buf, int nbytes)
674 {
675 HMIDIOUT outDev = (HMIDIOUT) userData;
676 unsigned int tmp;
677 unsigned char s, len;
678 int pos = 0;
679
680 (void) csound;
681 while (pos < nbytes) {
682 s = buf[pos++];
683 len = msg_bytes[(int) s >> 3];
684 if (!len)
685 continue;
686 tmp = (unsigned int) s;
687 if (--len) {
688 if (pos >= nbytes)
689 break;
690 tmp |= ((unsigned int) buf[pos++] << 8);
691 if (--len) {
692 if (pos >= nbytes)
693 break;
694 tmp |= ((unsigned int) buf[pos++] << 16);
695 }
696 }
697 midiOutShortMsg(outDev, (DWORD) tmp);
698 }
699
700 return pos;
701 }
702
midi_out_close(CSOUND * csound,void * userData)703 static int midi_out_close(CSOUND *csound, void *userData)
704 {
705 (void) csound;
706 if (userData != NULL) {
707 HMIDIOUT outDev = (HMIDIOUT) userData;
708 midiOutReset(outDev);
709 midiOutClose(outDev);
710 }
711
712 return 0;
713 }
714
715 /* module interface functions */
716
csoundModuleCreate(CSOUND * csound)717 PUBLIC int csoundModuleCreate(CSOUND *csound)
718 {
719 rtWinMMGlobals *pp;
720 OPARMS oparms;
721 csound->GetOParms(csound, &oparms);
722
723 if (UNLIKELY(oparms.msglevel & 0x400))
724 csound->Message(csound, Str("Windows MME real time audio and MIDI module "
725 "for Csound by Istvan Varga\n"));
726
727 if (UNLIKELY(csound->CreateGlobalVariable(csound, "_rtwinmm_globals",
728 sizeof(rtWinMMGlobals)) != 0))
729 return err_msg(csound, Str("could not allocate global structure"));
730 pp = (rtWinMMGlobals*) csound->QueryGlobalVariable(csound,
731 "_rtwinmm_globals");
732 pp->inDev = NULL;
733 pp->outDev = NULL;
734 pp->enable_buf_timer = 1;
735 return (csound->CreateConfigurationVariable(
736 csound, "mme_playback_timer", &(pp->enable_buf_timer),
737 CSOUNDCFG_BOOLEAN, 0, NULL, NULL,
738 "Attempt to reduce timing jitter "
739 "in MME sound output (default: on)", NULL));
740 }
741
check_name(const char * s)742 static CS_NOINLINE int check_name(const char *s)
743 {
744 if (s != NULL) {
745 char buf[8];
746 int i = 0;
747 do {
748 buf[i] = s[i] | (char) 0x20;
749 i++;
750 } while (i < 7 && s[i] != '\0');
751 buf[i] = '\0';
752 if (strcmp(buf, "winmm") == 0 || strcmp(buf, "mme") == 0)
753 return 1;
754 }
755 return 0;
756 }
757
csoundModuleInit(CSOUND * csound)758 PUBLIC int csoundModuleInit(CSOUND *csound)
759 {
760 if (check_name((char*) csound->QueryGlobalVariable(csound, "_RTAUDIO"))) {
761 csound->Message(csound, Str("rtaudio: WinMM module enabled\n"));
762 csound->SetPlayopenCallback(csound, playopen_);
763 csound->SetRecopenCallback(csound, recopen_);
764 csound->SetRtplayCallback(csound, rtplay_);
765 csound->SetRtrecordCallback(csound, rtrecord_);
766 csound->SetRtcloseCallback(csound, rtclose_);
767 }
768 if (check_name((char*) csound->QueryGlobalVariable(csound, "_RTMIDI"))) {
769 csound->Message(csound, Str("rtmidi: WinMM module enabled\n"));
770 csound->SetExternalMidiInOpenCallback(csound, midi_in_open);
771 csound->SetExternalMidiReadCallback(csound, midi_in_read);
772 csound->SetExternalMidiInCloseCallback(csound, midi_in_close);
773 csound->SetExternalMidiOutOpenCallback(csound, midi_out_open);
774 csound->SetExternalMidiWriteCallback(csound, midi_out_write);
775 csound->SetExternalMidiOutCloseCallback(csound, midi_out_close);
776 }
777 return 0;
778 }
779
csoundModuleInfo(void)780 PUBLIC int csoundModuleInfo(void)
781 {
782 return ((CS_APIVERSION << 16) + (CS_APISUBVER << 8) + (int) sizeof(MYFLT));
783 }
784