source: trunk/src/winmm/waveindart.cpp@ 9902

Last change on this file since 9902 was 9902, checked in by sandervl, 22 years ago

waveoutGetPosition: return 0 if stream is not active (DART); waveinGetPosition: return 0 if stream is not active (DART); Don't print a warning for CALLBACK_NULL callbacks; Added ODIN_waveInSetFixedBuffers, renamed SetFixedWaveBufferSize to ODIN_waveOutSetFixedBuffers. Used to tell WINMM to use the waveOutWrite buffer size for the DART buffers

File size: 20.1 KB
Line 
1/* $Id: waveindart.cpp,v 1.6 2003-03-05 14:49:04 sandervl Exp $ */
2
3/*
4 * Wave record class
5 *
6 * Copyright 2001 Sander van Leeuwen (sandervl@xs4all.nl)
7 *
8 * TODO: mulaw, alaw & adpcm
9 *
10 * Project Odin Software License can be found in LICENSE.TXT
11 *
12 *
13 */
14
15
16/****************************************************************************
17 * Includes *
18 ****************************************************************************/
19
20
21
22#define INCL_BASE
23#define INCL_OS2MM
24#include <os2wrap.h> //Odin32 OS/2 api wrappers
25#include <os2mewrap.h> //Odin32 OS/2 MMPM/2 api wrappers
26#include <stdlib.h>
27#include <string.h>
28#define OS2_ONLY
29#include <win32api.h>
30#include <wprocess.h>
31
32#include "misc.h"
33#include "waveindart.h"
34#include "initwinmm.h"
35
36#define DBG_LOCALLOG DBG_waveindart
37#include "dbglocal.h"
38
39#ifndef min
40#define min(a, b) ((a > b) ? b : a)
41#endif
42
43#ifndef max
44#define max(a, b) ((a > b) ? a : b)
45#endif
46
47LONG APIENTRY WaveInHandler(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, ULONG ulFlags);
48
49static BOOL fwaveInFixedBuffers = FALSE;
50
51//******************************************************************************
52// ODIN_waveInSetFixedBuffers
53//
54// Tell WINMM to use DART buffers of the same size as the first buffer delivered
55// by waveInAddBuffer
56//
57//******************************************************************************
58void WIN32API ODIN_waveInSetFixedBuffers()
59{
60 fwaveInFixedBuffers = TRUE;
61}
62/******************************************************************************/
63/******************************************************************************/
64DartWaveIn::DartWaveIn(LPWAVEFORMATEX pwfx, ULONG fdwOpen, ULONG nCallback, ULONG dwInstance)
65 : WaveInOut(pwfx, fdwOpen, nCallback, dwInstance)
66{
67 MCI_GENERIC_PARMS GenericParms;
68 MCI_AMP_OPEN_PARMS AmpOpenParms;
69 APIRET rc;
70
71 fOverrun = FALSE;
72 fMixerSetup = FALSE;
73
74 MixBuffer = (MCI_MIX_BUFFER *)malloc(PREFILLBUF_DART_REC*sizeof(MCI_MIX_BUFFER));
75 MixSetupParms = (MCI_MIXSETUP_PARMS *)malloc(sizeof(MCI_MIXSETUP_PARMS));
76 BufferParms = (MCI_BUFFER_PARMS *)malloc(sizeof(MCI_BUFFER_PARMS));
77 if(!MixBuffer || !MixSetupParms || !BufferParms) {
78 dprintf(("ERROR: malloc failed!!"));
79 ulError = MMSYSERR_NOMEM;
80 return;
81 }
82
83 ulBufSize = DART_BUFSIZE_REC;
84
85 dprintf(("waveInOpen: samplerate %d, numChan %d bps %d (%d), format %x", SampleRate, nChannels, BitsPerSample, pwfx->nBlockAlign, pwfx->wFormatTag));
86 // Setup the open structure, pass the playlist and tell MCI_OPEN to use it
87 memset(&AmpOpenParms,0,sizeof(AmpOpenParms));
88
89 AmpOpenParms.usDeviceID = ( USHORT ) 0;
90 AmpOpenParms.pszDeviceType = ( PSZ ) MCI_DEVTYPE_AUDIO_AMPMIX;
91
92 rc = mymciSendCommand(0, MCI_OPEN, MCI_WAIT | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE,
93 (PVOID) &AmpOpenParms, 0);
94 DeviceId = AmpOpenParms.usDeviceID;
95 if(rc) {
96 dprintf(("MCI_OPEN failed\n"));
97 mciError(rc);
98 ulError = MMSYSERR_NODRIVER;
99 }
100 if(rc == 0) {
101 //Grab exclusive rights to device instance (NOT entire device)
102 GenericParms.hwndCallback = 0; //Not needed, so set to 0
103 rc = mymciSendCommand(DeviceId, MCI_ACQUIREDEVICE, MCI_EXCLUSIVE_INSTANCE,
104 (PVOID)&GenericParms, 0);
105 if(rc) {
106 dprintf(("MCI_ACQUIREDEVICE failed\n"));
107 mciError(rc);
108 ulError = MMSYSERR_NOTENABLED;
109 }
110 }
111
112 if(!ulError)
113 callback(WIM_OPEN, 0, 0);
114}
115/******************************************************************************/
116/******************************************************************************/
117DartWaveIn::~DartWaveIn()
118{
119 MCI_GENERIC_PARMS GenericParms;
120
121 State = STATE_STOPPED;
122
123 if(!ulError)
124 {
125 // Generic parameters
126 GenericParms.hwndCallback = 0; //hwndFrame
127
128 // Stop recording.
129 mymciSendCommand(DeviceId, MCI_STOP,MCI_WAIT, (PVOID)&GenericParms,0);
130
131 mymciSendCommand(DeviceId,
132 MCI_BUFFER,
133 MCI_WAIT | MCI_DEALLOCATE_MEMORY,
134 (PVOID)&BufferParms,
135 0);
136
137 // Generic parameters
138 GenericParms.hwndCallback = 0; //hwndFrame
139
140 // Close the device
141 mymciSendCommand(DeviceId, MCI_CLOSE, MCI_WAIT, (PVOID)&GenericParms, 0);
142 }
143 if(!ulError)
144 {
145 callback(WIM_CLOSE, 0, 0);
146 }
147
148 if(MixBuffer)
149 free(MixBuffer);
150 if(MixSetupParms)
151 free(MixSetupParms);
152 if(BufferParms)
153 free(BufferParms);
154}
155/******************************************************************************/
156/******************************************************************************/
157MMRESULT DartWaveIn::start()
158{
159 MCI_GENERIC_PARMS GenericParms = {0};
160 MCI_AMP_OPEN_PARMS AmpOpenParms;
161 MCI_CONNECTOR_PARMS ConnectorParms;
162 MCI_AMP_SET_PARMS AmpSetParms ;
163 APIRET rc;
164 int i, curbuf, buflength;
165
166 dprintf(("DartWaveIn::start"));
167 if(State == STATE_RECORDING)
168 return(MMSYSERR_NOERROR);
169
170 //if the app hasn't yet given us any buffers, then we can't possibly determine the
171 //right buffer size for DART recording, so postpone recording until the
172 //first buffer has been added
173 if(wavehdr == NULL) {
174 wmutex.enter();
175 State = STATE_POSTPONE_RECORDING;
176 wmutex.leave();
177 return MMSYSERR_NOERROR;
178 }
179
180 if(fMixerSetup == FALSE)
181 {
182 dprintf(("device acquired\n"));
183 /* Set the MixSetupParms data structure to match the loaded file.
184 * This is a global that is used to setup the mixer.
185 */
186 memset(MixSetupParms, 0, sizeof( MCI_MIXSETUP_PARMS ) );
187
188 MixSetupParms->ulBitsPerSample = BitsPerSample;
189 MixSetupParms->ulSamplesPerSec = SampleRate;
190 MixSetupParms->ulFormatTag = MCI_WAVE_FORMAT_PCM;
191 MixSetupParms->ulChannels = nChannels;
192
193 dprintf(("bps %d, sps %d chan %d\n", BitsPerSample, SampleRate, nChannels));
194
195 /* Setup the mixer for recording of wave data
196 */
197 MixSetupParms->ulFormatMode = MCI_RECORD;
198 MixSetupParms->ulDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO;
199 MixSetupParms->pmixEvent = WaveInHandler;
200
201 rc = mymciSendCommand(DeviceId, MCI_MIXSETUP, MCI_WAIT | MCI_MIXSETUP_INIT,
202 (PVOID)MixSetupParms, 0);
203
204 if ( rc != MCIERR_SUCCESS ) {
205 mciError(rc);
206 mymciSendCommand(DeviceId, MCI_RELEASEDEVICE, MCI_WAIT,
207 (PVOID)&GenericParms, 0);
208 return(MMSYSERR_NOTSUPPORTED);
209 }
210
211 /*
212 * Set up the BufferParms data structure and allocate
213 * device buffers from the Amp-Mixer
214 */
215
216 int consumerate = getAvgBytesPerSecond();
217 //SvL: Recording usually isn't as sensitive to timing as playback
218 // Dividing by 8 instead of 32 here. Change if necessary.
219 int minbufsize = consumerate/8;
220
221 LPWAVEHDR pwh = wavehdr;
222 if(pwh) {
223 if(fwaveInFixedBuffers) {
224 ulBufSize = pwh->dwBufferLength;
225 }
226 else
227 {
228 dprintf(("mix setup %d, %d\n", pwh->dwBufferLength, pwh->dwBufferLength));
229
230 ulBufSize = pwh->dwBufferLength/2;
231 }
232 if(ulBufSize > minbufsize) {
233 dprintf(("set buffer size to %d bytes (org size = %d)", minbufsize, pwh->dwBufferLength));
234 ulBufSize = minbufsize;
235 }
236 }
237 else ulBufSize = minbufsize;
238
239 MixSetupParms->ulBufferSize = ulBufSize;
240
241 BufferParms->ulNumBuffers = PREFILLBUF_DART_REC;
242 BufferParms->ulBufferSize = MixSetupParms->ulBufferSize;
243 BufferParms->pBufList = MixBuffer;
244
245 for(i=0;i<PREFILLBUF_DART_REC;i++) {
246 MixBuffer[i].ulUserParm = (ULONG)this;
247 }
248
249 rc = mymciSendCommand(DeviceId,
250 MCI_BUFFER,
251 MCI_WAIT | MCI_ALLOCATE_MEMORY,
252 (PVOID)BufferParms,
253 0);
254
255 if(ULONG_LOWD(rc) != MCIERR_SUCCESS) {
256 mciError(rc);
257 mymciSendCommand(DeviceId, MCI_RELEASEDEVICE, MCI_WAIT,
258 (PVOID)&GenericParms, 0);
259 return(MMSYSERR_NOTSUPPORTED);
260 }
261
262 //TODO: don't do this here. Select line or mic depending on aux settings
263 //(until we really support the mixer extensions anyway)
264 /* Set the connector to 'line in' */
265 memset( &ConnectorParms, '\0', sizeof( MCI_CONNECTOR_PARMS ) );
266 ConnectorParms.ulConnectorType = MCI_LINE_IN_CONNECTOR;
267 rc = mymciSendCommand(DeviceId, MCI_CONNECTOR, MCI_WAIT |
268 MCI_ENABLE_CONNECTOR | MCI_CONNECTOR_TYPE,
269 ( PVOID ) &ConnectorParms, 0 );
270
271 /* Allow the user to hear what is being recorded
272 * by turning the monitor on
273 */
274 memset( &AmpSetParms, '\0', sizeof( MCI_AMP_SET_PARMS ) );
275 AmpSetParms.ulItem = MCI_AMP_SET_MONITOR;
276 rc = mymciSendCommand(DeviceId,
277 MCI_SET,
278 MCI_WAIT | MCI_SET_ON | MCI_SET_ITEM,
279 ( PVOID ) &AmpSetParms,
280 0 );
281
282 wmutex.enter();
283 fMixerSetup = TRUE;
284 }
285
286 for(i=0;i<PREFILLBUF_DART_REC;i++) {
287 memset(MixBuffer[i].pBuffer, 0, MixBuffer[i].ulBufferLength);
288 }
289 dprintf(("Dart opened, bufsize = %d\n", MixBuffer[0].ulBufferLength));
290
291 dprintf(("MixSetupParms = %X\n", MixSetupParms));
292 State = STATE_RECORDING;
293 fOverrun = FALSE;
294 wmutex.leave();
295
296 dprintf(("Dart recording"));
297
298 // Start recording.
299 USHORT selTIB = RestoreOS2FS(); // save current FS selector
300 MixSetupParms->pmixRead(MixSetupParms->ulMixHandle, &MixBuffer[0], PREFILLBUF_DART_REC);
301 SetFS(selTIB); // switch back to the saved FS selector
302
303 return(MMSYSERR_NOERROR);
304}
305/******************************************************************************/
306/******************************************************************************/
307MMRESULT DartWaveIn::stop()
308{
309 MCI_GENERIC_PARMS Params;
310
311 wmutex.enter();
312 if(State != STATE_RECORDING) {
313 State = STATE_STOPPED;
314 wmutex.leave();
315 return(MMSYSERR_NOERROR);
316 }
317
318 State = STATE_STOPPED;
319 wmutex.leave();
320
321 memset(&Params, 0, sizeof(Params));
322
323 // Stop recording.
324 mymciSendCommand(DeviceId, MCI_STOP, MCI_WAIT, (PVOID)&Params, 0);
325
326 return(MMSYSERR_NOERROR);
327}
328/******************************************************************************/
329/******************************************************************************/
330MMRESULT DartWaveIn::reset()
331{
332 MCI_GENERIC_PARMS Params;
333 LPWAVEHDR tmpwavehdr;
334
335 dprintf(("DartWaveIn::reset %s", (State == STATE_RECORDING) ? "recording" : "stopped"));
336
337 wmutex.enter();
338 if(State == STATE_POSTPONE_RECORDING) {
339 dprintf(("reset postponed recording"));
340 State = STATE_STOPPED;
341 wmutex.leave();
342 return MMSYSERR_NOERROR;
343 }
344 if(State != STATE_RECORDING) {
345 wmutex.leave();
346 return(MMSYSERR_HANDLEBUSY);
347 }
348 wmutex.leave();
349
350 memset(&Params, 0, sizeof(Params));
351
352 // Stop recording
353 mymciSendCommand(DeviceId, MCI_STOP, MCI_WAIT, (PVOID)&Params, 0);
354
355 wmutex.enter();
356 while(wavehdr)
357 {
358 wavehdr->dwFlags |= WHDR_DONE;
359 wavehdr->dwFlags &= ~WHDR_INQUEUE;
360 tmpwavehdr = wavehdr;
361 wavehdr = wavehdr->lpNext;
362 tmpwavehdr->lpNext = NULL;
363
364 wmutex.leave();
365
366 callback(WIM_DATA, (ULONG)tmpwavehdr, 0);
367
368 wmutex.enter();
369 }
370 wavehdr = NULL;
371 State = STATE_STOPPED;
372 fOverrun = FALSE;
373
374 wmutex.leave();
375 return(MMSYSERR_NOERROR);
376}
377/******************************************************************************/
378/******************************************************************************/
379MMRESULT DartWaveIn::addBuffer(LPWAVEHDR pwh, UINT cbwh)
380{
381 int i;
382
383 wmutex.enter();
384 pwh->lpNext = NULL;
385 pwh->dwBytesRecorded = 0;
386 if(wavehdr) {
387 WAVEHDR *chdr = wavehdr;
388 while(chdr->lpNext) {
389 chdr = chdr->lpNext;
390 }
391 chdr->lpNext = pwh;
392 }
393 else wavehdr = pwh;
394 wmutex.leave();
395
396 if(State == STATE_POSTPONE_RECORDING) {
397 //if recording was postponed due to lack of buffers, then start now
398 start();
399 }
400 return(MMSYSERR_NOERROR);
401}
402/******************************************************************************/
403/******************************************************************************/
404ULONG DartWaveIn::getPosition()
405{
406 MCI_STATUS_PARMS mciStatus = {0};
407 ULONG rc, nrbytes;
408
409 if(State == STATE_STOPPED) {
410 dprintf(("Not recording; return 0 position"));
411 return 0;
412 }
413
414 mciStatus.ulItem = MCI_STATUS_POSITION;
415 rc = mymciSendCommand(DeviceId, MCI_STATUS, MCI_STATUS_ITEM|MCI_WAIT, (PVOID)&mciStatus, 0);
416 if((rc & 0xFFFF) == MCIERR_SUCCESS) {
417 nrbytes = (mciStatus.ulReturn * (getAvgBytesPerSecond()/1000));
418 return nrbytes;;
419 }
420 mciError(rc);
421 return 0xFFFFFFFF;
422}
423/******************************************************************************/
424/******************************************************************************/
425int DartWaveIn::getNumDevices()
426{
427 MCI_GENERIC_PARMS GenericParms;
428 MCI_AMP_OPEN_PARMS AmpOpenParms;
429 APIRET rc;
430
431 // Setup the open structure, pass the playlist and tell MCI_OPEN to use it
432 memset(&AmpOpenParms,0,sizeof(AmpOpenParms));
433
434 AmpOpenParms.usDeviceID = ( USHORT ) 0;
435 AmpOpenParms.pszDeviceType = ( PSZ ) MCI_DEVTYPE_AUDIO_AMPMIX;
436
437 rc = mymciSendCommand(0, MCI_OPEN,
438 MCI_WAIT | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE,
439 (PVOID) &AmpOpenParms,
440 0);
441
442 if(rc) {
443 return 0; //no devices present
444 }
445
446 // Generic parameters
447 GenericParms.hwndCallback = 0; //hwndFrame
448
449 // Close the device
450 mymciSendCommand(AmpOpenParms.usDeviceID, MCI_CLOSE, MCI_WAIT, (PVOID)&GenericParms, 0);
451
452 return 1;
453}
454/******************************************************************************/
455/******************************************************************************/
456BOOL DartWaveIn::queryFormat(ULONG formatTag, ULONG nChannels,
457 ULONG nSamplesPerSec, ULONG wBitsPerSample)
458{
459 MCI_WAVE_GETDEVCAPS_PARMS mciAudioCaps;
460 MCI_GENERIC_PARMS GenericParms;
461 MCI_OPEN_PARMS mciOpenParms; /* open parms for MCI_OPEN */
462 int i, freqbits = 0;
463 ULONG rc, DeviceId;
464 BOOL winrc;
465
466 dprintf(("DartWaveIn::queryFormat %x srate=%d, nchan=%d, bps=%d", formatTag, nSamplesPerSec, nChannels, wBitsPerSample));
467
468 memset(&mciOpenParms, /* Object to fill with zeros. */
469 0, /* Value to place into the object. */
470 sizeof( mciOpenParms ) ); /* How many zero's to use. */
471
472 mciOpenParms.pszDeviceType = (PSZ)MCI_DEVTYPE_WAVEFORM_AUDIO;
473
474 rc = mymciSendCommand( (USHORT) 0,
475 MCI_OPEN,
476 MCI_WAIT | MCI_OPEN_TYPE_ID,
477 (PVOID) &mciOpenParms,
478 0);
479 if((rc & 0xFFFF) != MCIERR_SUCCESS) {
480 mciError(rc);
481 return(FALSE);
482 }
483 DeviceId = mciOpenParms.usDeviceID;
484
485 memset( &mciAudioCaps , 0, sizeof(MCI_WAVE_GETDEVCAPS_PARMS));
486
487 mciAudioCaps.ulBitsPerSample = wBitsPerSample;
488 mciAudioCaps.ulFormatTag = DATATYPE_WAVEFORM;
489 mciAudioCaps.ulSamplesPerSec = nSamplesPerSec;
490 mciAudioCaps.ulChannels = nChannels;
491 mciAudioCaps.ulFormatMode = MCI_RECORD;
492 mciAudioCaps.ulItem = MCI_GETDEVCAPS_WAVE_FORMAT;
493
494 rc = mymciSendCommand(DeviceId, /* Device ID */
495 MCI_GETDEVCAPS,
496 MCI_WAIT | MCI_GETDEVCAPS_EXTENDED | MCI_GETDEVCAPS_ITEM,
497 (PVOID) &mciAudioCaps,
498 0);
499 if((rc & 0xFFFF) != MCIERR_SUCCESS) {
500 mciError(rc);
501 winrc = FALSE;
502 }
503 else winrc = TRUE;
504
505 // Close the device
506 mymciSendCommand(DeviceId,MCI_CLOSE,MCI_WAIT,(PVOID)&GenericParms,0);
507 return(winrc);
508}
509/******************************************************************************/
510/******************************************************************************/
511void DartWaveIn::mciError(ULONG ulError)
512{
513#ifdef DEBUG
514 char szError[256] = "";
515
516 mymciGetErrorString(ulError, szError, sizeof(szError));
517 dprintf(("WINMM: DartWaveIn: %s\n", szError));
518#endif
519}
520/******************************************************************************/
521//Simple implementation of recording. We fill up buffers queued by the application
522//until we run out of data or buffers. If we run out of buffers, the recorded data
523//is thrown away. We will continue when when the applications adds more buffers.
524/******************************************************************************/
525void DartWaveIn::handler(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, ULONG ulFlags)
526{
527 ULONG buflength, bufpos, bytestocopy;
528
529 dprintf2(("WINMM: DartWaveIn handler %x\n", pBuffer));
530 if(ulFlags == MIX_STREAM_ERROR) {
531 if(ulStatus == ERROR_DEVICE_OVERRUN) {
532 dprintf(("WINMM: ERROR: WaveIn handler ERROR_DEVICE_OVERRUN!\n"));
533 if(State == STATE_RECORDING) {
534 fOverrun = TRUE;
535 stop(); //out of buffers, so stop playback
536 }
537 return;
538 }
539 dprintf(("WINMM: WaveIn handler, Unknown error %X\n", ulStatus));
540 return;
541 }
542 wmutex.enter();
543
544 if(wavehdr == NULL) {
545 wmutex.leave();
546 //last buffer recorded -> no new ones -> overrun
547 //Windows doesn't care -> just continue
548 dprintf(("WINMM: WARNING: WaveIn handler OVERRUN!\n"));
549 return;
550 }
551
552 buflength = pBuffer->ulBufferLength;
553 bufpos = 0;
554 while(buflength) {
555 if(wavehdr) {
556 dprintf2(("WINMM: DartWaveIn handler: bytes recorded %d, buffer length %d, room %d", buflength, wavehdr->dwBufferLength, wavehdr->dwBytesRecorded));
557 bytestocopy = min(buflength, wavehdr->dwBufferLength - wavehdr->dwBytesRecorded);
558 if(bytestocopy) {
559 memcpy(wavehdr->lpData + wavehdr->dwBytesRecorded, (char *)pBuffer->pBuffer + bufpos, bytestocopy);
560 }
561 else DebugInt3(); //should never happen
562
563 bufpos += bytestocopy;
564 wavehdr->dwBytesRecorded += bytestocopy;
565 buflength -= bytestocopy;
566
567 if(wavehdr->dwBytesRecorded == wavehdr->dwBufferLength)
568 {
569 WAVEHDR *whdr = wavehdr;
570
571 dprintf2(("WINMM: DartWaveIn handler buf %X done\n", whdr));
572 whdr->dwFlags |= WHDR_DONE;
573 whdr->dwFlags &= ~WHDR_INQUEUE;
574
575 wavehdr = whdr->lpNext;
576 whdr->lpNext = NULL;
577 wmutex.leave();
578
579 callback(WIM_DATA, (ULONG)whdr, whdr->dwBytesRecorded);
580
581 wmutex.enter();
582 }
583 }
584 else break;
585 }
586 wmutex.leave();
587
588 //Transfer buffer to DART
589 // MCI_MIXSETUP_PARMS->pMixWrite does alter FS: selector!
590 USHORT selTIB = RestoreOS2FS(); // save current FS selector
591 MixSetupParms->pmixRead(MixSetupParms->ulMixHandle, pBuffer, 1);
592 SetFS(selTIB); // switch back to the saved FS selector
593}
594/******************************************************************************/
595/******************************************************************************/
596LONG APIENTRY WaveInHandler(ULONG ulStatus,
597 PMCI_MIX_BUFFER pBuffer,
598 ULONG ulFlags)
599{
600 DartWaveIn *dwave;
601
602 if(pBuffer && pBuffer->ulUserParm)
603 {
604 dwave = (DartWaveIn *)pBuffer->ulUserParm;
605 dwave->handler(ulStatus, pBuffer, ulFlags);
606 }
607 return(TRUE);
608}
609/******************************************************************************/
610/******************************************************************************/
611
Note: See TracBrowser for help on using the repository browser.