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