]> git.saurik.com Git - wxWidgets.git/blob - utils/wxMMedia2/lib/sndwin.cpp
Fixed bug in wxSound windows driver (Recording is in progress: don't try it
[wxWidgets.git] / utils / wxMMedia2 / lib / sndwin.cpp
1 // --------------------------------------------------------------------------
2 // Name: sndwin.cpp
3 // Purpose:
4 // Date: 08/11/1999
5 // Author: Guilhem Lavaux <lavaux@easynet.fr> (C) 1999
6 // CVSID: $Id$
7 // --------------------------------------------------------------------------
8 #include <wx/wxprec.h>
9
10 #include <wx/app.h>
11 #include <wx/module.h>
12 #include <wx/msw/private.h>
13 #include <string.h>
14 #include "sndbase.h"
15 #include "sndwin.h"
16 #include "sndpcm.h"
17
18 #include <windows.h>
19 #include <mmsystem.h>
20
21 typedef struct _wxSoundInternal wxSoundInternal;
22 typedef struct _wxSoundInfoHeader wxSoundInfoHeader;
23
24 extern char wxCanvasClassName[];
25
26 wxList *wxSoundHandleList = NULL;
27
28 static inline wxSoundStreamWin *wxFindSoundFromHandle(WXHWND hWnd)
29 {
30 wxNode *node = wxSoundHandleList->Find((long)hWnd);
31 if (!node)
32 return NULL;
33 return (wxSoundStreamWin *)node->Data();
34 }
35
36 struct _wxSoundInternal {
37 HWND m_sndWin;
38 HWAVEIN m_devin;
39 HWAVEOUT m_devout;
40 bool m_output_enabled, m_input_enabled;
41 };
42
43 struct _wxSoundInfoHeader {
44 HGLOBAL m_h_header, m_h_data;
45 char *m_data;
46 WAVEHDR *m_header;
47 int m_mode;
48 bool m_playing, m_recording;
49 wxUint32 m_position, m_size;
50
51 wxSoundStreamWin *m_driver;
52 };
53
54 #define WXSOUND_MAX_QUEUE 10
55
56 wxSoundStreamWin::wxSoundStreamWin()
57 {
58 wxSoundFormatPcm pcm;
59
60 m_production_started = FALSE;
61 m_internal = new wxSoundInternal;
62 m_snderror = wxSOUND_NOERR;
63
64 // Setup defaults
65 CreateSndWindow();
66 SetSoundFormat(pcm);
67
68 m_internal->m_input_enabled = FALSE;
69 m_internal->m_output_enabled = FALSE;
70
71 if (!OpenDevice(wxSOUND_OUTPUT))
72 return;
73
74 CloseDevice();
75 }
76
77 wxSoundStreamWin::~wxSoundStreamWin()
78 {
79 if (m_production_started)
80 StopProduction();
81 DestroySndWindow();
82
83 delete m_internal;
84 }
85
86 LRESULT APIENTRY _EXPORT _wxSoundHandlerWndProc(HWND hWnd, UINT message,
87 WPARAM wParam, LPARAM lParam)
88 {
89 wxSoundStreamWin *sndwin;
90
91 sndwin = wxFindSoundFromHandle((WXHWND)hWnd);
92 if (!sndwin)
93 return (LRESULT)0;
94
95 switch (message) {
96 case MM_WOM_DONE:
97 sndwin->NotifyDoneBuffer(wParam, wxSOUND_OUTPUT);
98 break;
99 case MM_WIM_DATA:
100 sndwin->NotifyDoneBuffer(wParam, wxSOUND_INPUT);
101 break;
102 default:
103 break;
104 }
105 return (LRESULT)0;
106 }
107
108 // -----------------------------------------------------------------------
109 // CreateSndWindow() creates an hidden window which will receive the sound
110 // events
111 // -----------------------------------------------------------------------
112
113 void wxSoundStreamWin::CreateSndWindow()
114 {
115 FARPROC proc = MakeProcInstance((FARPROC)_wxSoundHandlerWndProc,
116 wxGetInstance());
117 int error;
118
119 m_internal->m_sndWin = ::CreateWindow(wxCanvasClassName, NULL, 0,
120 0, 0, 0, 0, NULL, (HMENU) NULL,
121 wxGetInstance(), NULL);
122
123 error = GetLastError();
124
125 ::SetWindowLong(m_internal->m_sndWin, GWL_WNDPROC, (LONG)proc);
126
127 // Add this window to the sound handle list so we'll be able to redecode
128 // the "magic" number.
129 wxSoundHandleList->Append((long)m_internal->m_sndWin, (wxObject *)this);
130 }
131
132 // -----------------------------------------------------------------------
133 // DestroySndWindow() destroys the hidden window
134 // -----------------------------------------------------------------------
135
136 void wxSoundStreamWin::DestroySndWindow()
137 {
138 if (m_internal->m_sndWin) {
139 ::DestroyWindow(m_internal->m_sndWin);
140 wxSoundHandleList->DeleteObject((wxObject *)this);
141 }
142 }
143
144 // -------------------------------------------------------------------------
145 // OpenDevice(int mode) initializes the windows driver for a "mode"
146 // operation. mode is a bit mask: if the bit "wxSOUND_OUTPUT" is set,
147 // the driver is opened for output operation, and if the bit "wxSOUND_INPUT"
148 // is set, then the driver is opened for input operation. The two modes
149 // aren't exclusive.
150 // The initialization parameters (sample rate, ...) are taken from the
151 // m_sndformat object.
152 // At the end, OpenDevice() calls AllocHeaders() to initialize the Sound IO
153 // queue.
154 // -------------------------------------------------------------------------
155 bool wxSoundStreamWin::OpenDevice(int mode)
156 {
157 wxSoundFormatPcm *pcm;
158 WAVEFORMATEX wformat;
159
160 if (!m_sndformat) {
161 m_snderror = wxSOUND_INVFRMT;
162 return FALSE;
163 }
164
165 pcm = (wxSoundFormatPcm *)m_sndformat;
166
167 wformat.wFormatTag = WAVE_FORMAT_PCM;
168 wformat.nChannels = pcm->GetChannels();
169 wformat.nBlockAlign = wformat.nChannels * pcm->GetBPS() / 8;
170 wformat.nSamplesPerSec = pcm->GetSampleRate();
171 wformat.nAvgBytesPerSec = wformat.nSamplesPerSec * wformat.nBlockAlign;
172 wformat.wBitsPerSample = pcm->GetBPS();
173 wformat.cbSize = 0;
174
175 // -----------------------------------
176 // Open the driver for Output operation
177 // -----------------------------------
178 if (mode & wxSOUND_OUTPUT) {
179 MMRESULT result;
180
181 result = waveOutOpen(&m_internal->m_devout,
182 WAVE_MAPPER, &wformat,
183 (DWORD)m_internal->m_sndWin, 0,
184 CALLBACK_WINDOW);
185
186 if (result != MMSYSERR_NOERROR) {
187 m_snderror = wxSOUND_INVDEV;
188 return FALSE;
189 }
190
191 m_output_frag_out = WXSOUND_MAX_QUEUE-1;
192 m_current_frag_out = 0;
193
194 m_internal->m_output_enabled = TRUE;
195 }
196 // -----------------------------------
197 // Open the driver for Input operation
198 // -----------------------------------
199 if (mode & wxSOUND_INPUT) {
200 MMRESULT result;
201
202 result = waveInOpen(&m_internal->m_devin,
203 WAVE_MAPPER, &wformat,
204 (DWORD)m_internal->m_sndWin, 0,
205 CALLBACK_WINDOW);
206
207 if (result != MMSYSERR_NOERROR) {
208 m_snderror = wxSOUND_INVDEV;
209 return FALSE;
210 }
211
212 m_current_frag_in = WXSOUND_MAX_QUEUE-1;
213 m_input_frag_in = 0;
214
215 m_internal->m_input_enabled = TRUE;
216 }
217
218 if (mode & wxSOUND_OUTPUT) {
219 if (!AllocHeaders(wxSOUND_OUTPUT)) {
220 CloseDevice();
221 return FALSE;
222 }
223 }
224 if (mode & wxSOUND_INPUT) {
225 if (!AllocHeaders(wxSOUND_INPUT)) {
226 CloseDevice();
227 return FALSE;
228 }
229 }
230
231 return TRUE;
232 }
233
234 // -------------------------------------------------------------------------
235 // CloseDevice() closes the driver handles and frees memory allocated for
236 // IO queues.
237 // -------------------------------------------------------------------------
238 void wxSoundStreamWin::CloseDevice()
239 {
240 if (m_internal->m_output_enabled) {
241 m_internal->m_output_enabled = FALSE;
242 waveOutReset(m_internal->m_devout);
243 FreeHeaders(wxSOUND_OUTPUT);
244 waveOutClose(m_internal->m_devout);
245 }
246
247 if (m_internal->m_input_enabled) {
248 m_internal->m_input_enabled = FALSE;
249 waveInReset(m_internal->m_devin);
250 FreeHeaders(wxSOUND_INPUT);
251 waveInClose(m_internal->m_devin);
252 }
253 }
254
255 // -------------------------------------------------------------------------
256 // AllocHeader(int mode)
257 //
258 // mode has the same mean as in OpenDevice() except that here the two flags
259 // must be exclusive.
260 // AllocHeader() initializes an element of an operation (this can be input
261 // or output). It means it allocates the sound header's memory block
262 // and "prepares" it (It is needed by Windows). At the same time, it sets
263 // private datas so we can the header's owner (See callback).
264 //
265 // It returns the new allocated block or NULL.
266 // -------------------------------------------------------------------------
267 wxSoundInfoHeader *wxSoundStreamWin::AllocHeader(int mode)
268 {
269 wxSoundInfoHeader *info;
270 WAVEHDR *header;
271
272 // Some memory allocation
273 info = new wxSoundInfoHeader;
274 info->m_h_data = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, GetBestSize());
275 info->m_h_header = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
276 if (!info->m_h_data || !info->m_h_header) {
277 delete info;
278 m_snderror = wxSOUND_MEMERR;
279 return NULL;
280 }
281
282 // Get the two pointers from the system
283 info->m_data = (char *)GlobalLock(info->m_h_data);
284 info->m_header = (WAVEHDR *)GlobalLock(info->m_h_header);
285 // Set the header's mode
286 info->m_mode = mode;
287 // Set the parent of the header
288 info->m_driver = this;
289 // Clean it up
290 ClearHeader(info);
291
292 header = info->m_header;
293 // Initialize Windows variables
294 header->lpData = info->m_data;
295 header->dwBufferLength = GetBestSize();
296 header->dwUser = (DWORD)info;
297 header->dwFlags = WHDR_DONE;
298
299
300 // "Prepare" the header
301 if (mode == wxSOUND_INPUT) {
302 MMRESULT result;
303
304 result = waveInPrepareHeader(m_internal->m_devin, header,
305 sizeof(WAVEHDR));
306
307 if (result != MMSYSERR_NOERROR) {
308 // If something goes wrong, free everything.
309 GlobalUnlock(info->m_data);
310 GlobalUnlock(info->m_header);
311 GlobalFree(info->m_h_data);
312 GlobalFree(info->m_h_header);
313 delete info;
314
315 m_snderror = wxSOUND_IOERR;
316 return NULL;
317 }
318 } else if (mode == wxSOUND_OUTPUT) {
319 MMRESULT result;
320
321 result = waveOutPrepareHeader(m_internal->m_devout, header,
322 sizeof(WAVEHDR));
323
324 if (result != MMSYSERR_NOERROR) {
325 // If something goes wrong, free everything.
326 GlobalUnlock(info->m_data);
327 GlobalUnlock(info->m_header);
328 GlobalFree(info->m_h_data);
329 GlobalFree(info->m_h_header);
330 delete info;
331
332 m_snderror = wxSOUND_IOERR;
333 return NULL;
334 }
335 }
336 return info;
337 }
338
339 // -------------------------------------------------------------------------
340 // AllocHeaders(int mode)
341 //
342 // "mode" has the same mean as for OpenDevice() except that the two flags must
343 // be exclusive.
344 // AllocHeaders() allocates WXSOUND_MAX_QUEUE (= 128) blocks for an operation
345 // queue. It uses AllocHeader() for each element.
346 //
347 // Once it has allocated all blocks, it returns TRUE and if an error occured
348 // it returns FALSE.
349 // -------------------------------------------------------------------------
350 bool wxSoundStreamWin::AllocHeaders(int mode)
351 {
352 int i;
353 wxSoundInfoHeader **headers;
354
355 if (mode == wxSOUND_OUTPUT)
356 headers = m_headers_play = new wxSoundInfoHeader *[WXSOUND_MAX_QUEUE];
357 else
358 headers = m_headers_rec = new wxSoundInfoHeader *[WXSOUND_MAX_QUEUE];
359
360 memset(headers, 0, WXSOUND_MAX_QUEUE*sizeof(wxSoundInfoHeader *));
361
362 for (i=0;i<WXSOUND_MAX_QUEUE;i++) {
363 headers[i] = AllocHeader(mode);
364 if (!headers[i]) {
365 FreeHeaders(mode);
366 return FALSE;
367 }
368 }
369 return TRUE;
370 }
371
372 // -------------------------------------------------------------------------
373 // FreeHeader(int mode)
374 //
375 // "mode" has the same mean as for OpenDevice() except that the two flags must
376 // be exclusive.
377 // FreeHeader() frees a memory block and "unprepares" it.
378 // -------------------------------------------------------------------------
379 void wxSoundStreamWin::FreeHeader(wxSoundInfoHeader *header, int mode)
380 {
381 if (mode == wxSOUND_OUTPUT)
382 waveOutUnprepareHeader(m_internal->m_devout, header->m_header, sizeof(WAVEHDR));
383 else
384 waveInUnprepareHeader(m_internal->m_devin, header->m_header, sizeof(WAVEHDR));
385
386 GlobalUnlock(header->m_data);
387 GlobalUnlock(header->m_header);
388 GlobalFree(header->m_h_header);
389 GlobalFree(header->m_h_data);
390 delete header;
391 }
392
393 // -------------------------------------------------------------------------
394 // FreeHeaders(int mode)
395 //
396 // "mode" has the same mean as for OpenDevice() except that the two flags must
397 // be exclusive.
398 // FreeHeaders() frees all an operation queue once it has checked that
399 // all buffers have been terminated.
400 // -------------------------------------------------------------------------
401 void wxSoundStreamWin::FreeHeaders(int mode)
402 {
403 int i;
404 wxSoundInfoHeader ***headers;
405
406 if (mode == wxSOUND_OUTPUT)
407 headers = &m_headers_play;
408 else
409 headers = &m_headers_rec;
410
411 for (i=0;i<WXSOUND_MAX_QUEUE;i++) {
412 if ((*headers)[i]) {
413 // We wait for the end of the buffer
414 WaitFor((*headers)[i]);
415 // Then, we free the header
416 FreeHeader((*headers)[i], mode);
417 }
418 }
419 delete[] (*headers);
420 (*headers) = NULL;
421 }
422
423 // -------------------------------------------------------------------------
424 // WaitFor(wxSoundInfoHeader *info)
425 //
426 // "info" is one element of an IO queue
427 // WaitFor() checks whether the specified block has been terminated.
428 // If it hasn't been terminated, it waits for its termination.
429 //
430 // NB: if it's a partially filled buffer it adds it to the Windows queue
431 // -------------------------------------------------------------------------
432 void wxSoundStreamWin::WaitFor(wxSoundInfoHeader *info)
433 {
434 // If the buffer is finished, we return immediately
435 if (!info->m_playing) {
436
437 // We begun filling it: we must send it to the Windows queue
438 if (info->m_position != 0) {
439 memset(info->m_data + info->m_position, 0, info->m_size);
440 AddToQueue(info);
441 }
442
443 return;
444 }
445
446 // Else, we wait for its termination
447 while (info->m_playing || info->m_recording)
448 wxYield();
449 }
450
451 // -------------------------------------------------------------------------
452 // AddToQueue(wxSoundInfoHeader *info)
453 //
454 // For "info", see WaitFor()
455 // AddToQueue() sends the IO queue element to the Windows queue.
456 //
457 // Warning: in the current implementation, it partially assume we send the
458 // element in the right order. This is true in that implementation but if
459 // you use it elsewhere, be careful: it may shuffle all your sound datas.
460 // -------------------------------------------------------------------------
461 bool wxSoundStreamWin::AddToQueue(wxSoundInfoHeader *info)
462 {
463 MMRESULT result;
464
465 if (info->m_mode == wxSOUND_INPUT) {
466 // Increment the input fragment pointer
467 result = waveInAddBuffer(m_internal->m_devin,
468 info->m_header, sizeof(WAVEHDR));
469 if (result == MMSYSERR_NOERROR)
470 info->m_recording = TRUE;
471 else
472 return FALSE;
473 } else if (info->m_mode == wxSOUND_OUTPUT) {
474 result = waveOutWrite(m_internal->m_devout,
475 info->m_header, sizeof(WAVEHDR));
476 if (result == MMSYSERR_NOERROR)
477 info->m_playing = TRUE;
478 else
479 return FALSE;
480 }
481 return TRUE;
482 }
483
484 // -------------------------------------------------------------------------
485 // ClearHeader(wxSoundInfoHeader *info)
486 //
487 // ClearHeader() reinitializes the parameters of "info" to their default
488 // value.
489 // -------------------------------------------------------------------------
490 void wxSoundStreamWin::ClearHeader(wxSoundInfoHeader *info)
491 {
492 info->m_playing = FALSE;
493 info->m_recording = FALSE;
494 info->m_position = 0;
495 info->m_size = GetBestSize();
496 }
497
498 // -------------------------------------------------------------------------
499 // wxSoundInfoHeader *NextFragmentOutput()
500 //
501 // NextFragmentOutput() looks for a free output block. It will always
502 // return you a non-NULL pointer but it may waits for an empty buffer a long
503 // time.
504 // -------------------------------------------------------------------------
505 wxSoundInfoHeader *wxSoundStreamWin::NextFragmentOutput()
506 {
507 if (m_headers_play[m_current_frag_out]->m_playing) {
508 m_current_frag_out = (m_current_frag_out + 1) % WXSOUND_MAX_QUEUE;
509
510 if (m_headers_play[m_current_frag_out]->m_playing)
511 WaitFor(m_headers_play[m_current_frag_out]);
512 }
513 if (m_current_frag_out == m_output_frag_out)
514 m_queue_filled = TRUE;
515 return m_headers_play[m_current_frag_out];
516 }
517
518 // -------------------------------------------------------------------------
519 // The behaviour of Write is documented in the global documentation.
520 // -------------------------------------------------------------------------
521 wxSoundStream& wxSoundStreamWin::Write(const void *buffer, wxUint32 len)
522 {
523 m_lastcount = 0;
524 if (!m_internal->m_output_enabled)
525 return *this;
526
527 while (len > 0) {
528 wxSoundInfoHeader *header;
529 wxUint32 to_copy;
530
531 // Get a new output fragment
532 header = NextFragmentOutput();
533
534 to_copy = (len > header->m_size) ? header->m_size : len;
535 memcpy(header->m_data + header->m_position, buffer, to_copy);
536
537 header->m_position += to_copy;
538 header->m_size -= to_copy;
539 buffer = (((const char *)buffer) + to_copy);
540 len -= to_copy;
541 m_lastcount += to_copy;
542
543 // If the fragment is full, we send it to the Windows queue.
544 if (header->m_size == 0)
545 if (!AddToQueue(header)) {
546 m_snderror = wxSOUND_IOERR;
547 return *this;
548 }
549 }
550 return *this;
551 }
552
553 // -------------------------------------------------------------------------
554 // NextFragmentInput is not functional.
555 // -------------------------------------------------------------------------
556 wxSoundInfoHeader *wxSoundStreamWin::NextFragmentInput()
557 {
558 wxSoundInfoHeader *header;
559
560 m_current_frag_in = (m_current_frag_in + 1) % WXSOUND_MAX_QUEUE;
561
562 header = m_headers_rec[m_current_frag_in];
563 if (header->m_recording)
564 WaitFor(header);
565
566 if (m_current_frag_in == m_input_frag_in)
567 m_queue_filled = TRUE;
568
569 return header;
570 }
571
572 // -------------------------------------------------------------------------
573 // The behaviour of Read is documented in the global documentation.
574 // -------------------------------------------------------------------------
575 wxSoundStream& wxSoundStreamWin::Read(void *buffer, wxUint32 len)
576 {
577 wxSoundInfoHeader *header;
578 wxUint32 to_copy;
579
580 m_lastcount = 0;
581 if (!m_internal->m_input_enabled)
582 return *this;
583
584 while (len > 0) {
585 header = NextFragmentInput();
586
587 to_copy = (len > header->m_size) ? header->m_size : len;
588 memcpy(buffer, header->m_data + header->m_position, to_copy);
589
590 header->m_position += to_copy;
591 header->m_size -= to_copy;
592 buffer = (((char *)buffer) + to_copy);
593 len -= to_copy;
594 m_lastcount += to_copy;
595
596 if (header->m_size == 0) {
597 ClearHeader(header);
598 if (!AddToQueue(header)) {
599 m_snderror = wxSOUND_IOERR;
600 return *this;
601 }
602 }
603 }
604 return *this;
605 }
606
607 // -------------------------------------------------------------------------
608 // NotifyDoneBuffer(wxUint32 dev_handle)
609 //
610 // NotifyDoneBuffer() is called by wxSoundHandlerProc each time a sound
611 // fragment finished. It reinitializes the parameters of the fragment and
612 // sends an event to the clients.
613 // -------------------------------------------------------------------------
614 void wxSoundStreamWin::NotifyDoneBuffer(wxUint32 dev_handle, int flag)
615 {
616 wxSoundInfoHeader *info;
617
618 if (flag == wxSOUND_OUTPUT) {
619 if (!m_internal->m_output_enabled)
620 return;
621
622 m_output_frag_out = (m_output_frag_out + 1) % WXSOUND_MAX_QUEUE;
623 info = m_headers_play[m_output_frag_out];
624 ClearHeader(info);
625 m_queue_filled = FALSE;
626 OnSoundEvent(wxSOUND_OUTPUT);
627 } else {
628 if (!m_internal->m_input_enabled)
629 return;
630
631 m_input_frag_in = (m_input_frag_in + 1) % WXSOUND_MAX_QUEUE;
632 m_headers_rec[m_input_frag_in]->m_recording = FALSE;
633 OnSoundEvent(wxSOUND_INPUT);
634 m_queue_filled = FALSE;
635 }
636 }
637
638 // -------------------------------------------------------------------------
639 // -------------------------------------------------------------------------
640 bool wxSoundStreamWin::SetSoundFormat(wxSoundFormatBase& base)
641 {
642 return wxSoundStream::SetSoundFormat(base);
643 }
644
645 // -------------------------------------------------------------------------
646 // -------------------------------------------------------------------------
647 bool wxSoundStreamWin::StartProduction(int evt)
648 {
649 if ((m_internal->m_output_enabled && (evt & wxSOUND_OUTPUT)) ||
650 (m_internal->m_input_enabled && (evt & wxSOUND_INPUT)))
651 CloseDevice();
652
653 if (!OpenDevice(evt))
654 return FALSE;
655
656 m_production_started = TRUE;
657 m_queue_filled = FALSE;
658 // Send a dummy event to start.
659 if (evt & wxSOUND_OUTPUT)
660 OnSoundEvent(evt);
661
662 if (evt & wxSOUND_INPUT) {
663 int i;
664 for (i=0;i<WXSOUND_MAX_QUEUE;i++)
665 AddToQueue(m_headers_rec[i]);
666
667 waveInStart(m_internal->m_devin);
668 }
669
670 return TRUE;
671 }
672
673 // -------------------------------------------------------------------------
674 // -------------------------------------------------------------------------
675 bool wxSoundStreamWin::StopProduction()
676 {
677 if (m_internal->m_input_enabled)
678 waveInStop(m_internal->m_devin);
679
680 m_production_started = FALSE;
681 CloseDevice();
682 return TRUE;
683 }
684
685 // -------------------------------------------------------------------------
686 // -------------------------------------------------------------------------
687 bool wxSoundStreamWin::QueueFilled() const
688 {
689 return (!m_production_started || m_queue_filled);
690 }
691
692
693 // --------------------------------------------------------------------------
694 // wxSoundWinModule
695 // --------------------------------------------------------------------------
696
697 class WXDLLEXPORT wxSoundWinModule : public wxModule {
698 DECLARE_DYNAMIC_CLASS(wxSoundWinModule)
699 public:
700 bool OnInit();
701 void OnExit();
702 };
703
704 IMPLEMENT_DYNAMIC_CLASS(wxSoundWinModule, wxModule)
705
706 bool wxSoundWinModule::OnInit() {
707 wxSoundHandleList = new wxList(wxKEY_INTEGER);
708 return TRUE;
709 }
710
711 void wxSoundWinModule::OnExit() {
712 delete wxSoundHandleList;
713 }