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