1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5
6 #include "mumble_pch.hpp"
7
8 #include "ASIOInput.h"
9
10 #include "MainWindow.h"
11 #include "Global.h"
12
13 // From os_win.cpp.
14 extern HWND mumble_mw_hwnd;
15
16 class ASIOAudioInputRegistrar : public AudioInputRegistrar {
17 public:
18 ASIOAudioInputRegistrar();
19 virtual AudioInput *create();
20 virtual const QList<audioDevice> getDeviceChoices();
21 virtual void setDeviceChoice(const QVariant &, Settings &);
22 virtual bool canEcho(const QString &) const;
23
24 };
25
ASIOAudioInputRegistrar()26 ASIOAudioInputRegistrar::ASIOAudioInputRegistrar() : AudioInputRegistrar(QLatin1String("ASIO")) {
27 }
28
create()29 AudioInput *ASIOAudioInputRegistrar::create() {
30 return new ASIOInput();
31 }
getDeviceChoices()32 const QList<audioDevice> ASIOAudioInputRegistrar::getDeviceChoices() {
33 QList<audioDevice> qlReturn;
34 return qlReturn;
35 }
36
canEcho(const QString &) const37 bool ASIOAudioInputRegistrar::canEcho(const QString &) const {
38 return true;
39 }
40
setDeviceChoice(const QVariant &,Settings &)41 void ASIOAudioInputRegistrar::setDeviceChoice(const QVariant &, Settings &) {
42 }
43
ASIOConfigDialogNew(Settings & st)44 static ConfigWidget *ASIOConfigDialogNew(Settings &st) {
45 return new ASIOConfig(st);
46 }
47
48 class ASIOInit : public DeferInit {
49 ASIOAudioInputRegistrar *airASIO;
50 ConfigRegistrar *crASIO;
51 public:
ASIOInit()52 ASIOInit() : airASIO(NULL), crASIO(NULL) {}
53 void initialize();
54 void destroy();
55 };
56
initialize()57 void ASIOInit::initialize() {
58 HKEY hkDevs;
59 HKEY hk;
60 FILETIME ft;
61
62 airASIO = NULL;
63 crASIO = NULL;
64
65 bool bFound = false;
66
67 if (!g.s.bASIOEnable) {
68 qWarning("ASIOInput: ASIO forcefully disabled via 'asio/enable' config option.");
69 return;
70 }
71
72 // List of devices known to misbehave or be totally useless
73 QStringList blacklist;
74 blacklist << QLatin1String("{a91eaba1-cf4c-11d3-b96a-00a0c9c7b61a}"); // ASIO DirectX
75 blacklist << QLatin1String("{e3186861-3a74-11d1-aef8-0080ad153287}"); // ASIO Multimedia
76 #ifdef QT_NO_DEBUG
77 blacklist << QLatin1String("{232685c6-6548-49d8-846d-4141a3ef7560}"); // ASIO4ALL
78 #endif
79 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\ASIO", 0, KEY_READ, &hkDevs) == ERROR_SUCCESS) {
80 DWORD idx = 0;
81 DWORD keynamelen = 255;
82 WCHAR keyname[255];
83 while (RegEnumKeyEx(hkDevs, idx++, keyname, &keynamelen, NULL, NULL, NULL, &ft) == ERROR_SUCCESS) {
84 QString name=QString::fromUtf16(reinterpret_cast<ushort *>(keyname),keynamelen);
85 if (RegOpenKeyEx(hkDevs, keyname, 0, KEY_READ, &hk) == ERROR_SUCCESS) {
86 DWORD dtype = REG_SZ;
87 WCHAR wclsid[255];
88 DWORD datasize = 255;
89 CLSID clsid;
90 if (RegQueryValueEx(hk, L"CLSID", 0, &dtype, reinterpret_cast<BYTE *>(wclsid), &datasize) == ERROR_SUCCESS) {
91 if (datasize > 76)
92 datasize = 76;
93 QString qsCls = QString::fromUtf16(reinterpret_cast<ushort *>(wclsid), datasize / 2).toLower().trimmed();
94 if (! blacklist.contains(qsCls.toLower()) && ! FAILED(CLSIDFromString(wclsid, &clsid))) {
95 bFound = true;
96 }
97 }
98 RegCloseKey(hk);
99 }
100 keynamelen = 255;
101 }
102 RegCloseKey(hkDevs);
103 }
104
105 if (bFound) {
106 airASIO = new ASIOAudioInputRegistrar();
107 crASIO = new ConfigRegistrar(2002, ASIOConfigDialogNew);
108 } else {
109 qWarning("ASIO: No valid devices found, disabling");
110 }
111 }
112
destroy()113 void ASIOInit::destroy() {
114 delete airASIO;
115 delete crASIO;
116 }
117
118 static class ASIOInit asioinit;
119
120 ASIOInput *ASIOInput::aiSelf;
121
ASIOConfig(Settings & st)122 ASIOConfig::ASIOConfig(Settings &st) : ConfigWidget(st) {
123 setupUi(this);
124
125 // List of devices known to misbehave or be totally useless
126 QStringList blacklist;
127 blacklist << QLatin1String("{a91eaba1-cf4c-11d3-b96a-00a0c9c7b61a}"); // ASIO DirectX
128 blacklist << QLatin1String("{e3186861-3a74-11d1-aef8-0080ad153287}"); // ASIO Multimedia
129 #ifdef QT_NO_DEBUG
130 blacklist << QLatin1String("{232685c6-6548-49d8-846d-4141a3ef7560}"); // ASIO4ALL
131 #endif
132 HKEY hkDevs;
133 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\ASIO", 0, KEY_READ, &hkDevs) == ERROR_SUCCESS) {
134 const DWORD keynamebufsize = 255;
135 WCHAR keyname[keynamebufsize];
136
137 FILETIME ft;
138 DWORD idx = 0;
139 DWORD keynamelen = keynamebufsize;
140 while (RegEnumKeyEx(hkDevs, idx++, keyname, &keynamelen, NULL, NULL, NULL, &ft) == ERROR_SUCCESS) {
141 QString name=QString::fromUtf16(reinterpret_cast<ushort *>(keyname), keynamelen);
142 HKEY hk;
143 if (RegOpenKeyEx(hkDevs, keyname, 0, KEY_READ, &hk) == ERROR_SUCCESS) {
144 DWORD dtype = REG_SZ;
145 WCHAR wclsid[255];
146 DWORD datasize = 255;
147 if (RegQueryValueEx(hk, L"CLSID", 0, &dtype, reinterpret_cast<BYTE *>(wclsid), &datasize) == ERROR_SUCCESS) {
148 if (datasize > 76)
149 datasize = 76;
150 QString qsCls = QString::fromUtf16(reinterpret_cast<ushort *>(wclsid), datasize / 2).toLower().trimmed();
151 CLSID clsid;
152 if (! blacklist.contains(qsCls) && ! FAILED(CLSIDFromString(wclsid, &clsid))) {
153 ASIODev ad(name, qsCls);
154 qlDevs << ad;
155 }
156 }
157 RegCloseKey(hk);
158 }
159 keynamelen = keynamebufsize;
160 }
161 RegCloseKey(hkDevs);
162 }
163
164 bOk = false;
165
166 ASIODev ad;
167
168 foreach(ad, qlDevs) {
169 qcbDevice->addItem(ad.first, QVariant(ad.second));
170 }
171
172 if (qlDevs.count() == 0) {
173 qpbQuery->setEnabled(false);
174 qpbConfig->setEnabled(false);
175 }
176 }
177
on_qpbQuery_clicked()178 void ASIOConfig::on_qpbQuery_clicked() {
179 QString qsCls = qcbDevice->itemData(qcbDevice->currentIndex()).toString();
180 CLSID clsid;
181 IASIO *iasio;
182
183 clearQuery();
184
185 CLSIDFromString(const_cast<wchar_t *>(reinterpret_cast<const wchar_t *>(qsCls.utf16())), &clsid);
186 if (CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, clsid, reinterpret_cast<void **>(&iasio)) == S_OK) {
187 SleepEx(10, false);
188 if (iasio->init(mumble_mw_hwnd)) {
189 SleepEx(10, false);
190 char buff[512];
191 memset(buff, 0, 512);
192
193 iasio->getDriverName(buff);
194 SleepEx(10, false);
195
196 long ver = iasio->getDriverVersion();
197 SleepEx(10, false);
198
199 ASIOSampleRate srate = 0.0;
200 iasio->setSampleRate(48000.0);
201 iasio->getSampleRate(&srate);
202 SleepEx(10, false);
203
204 long minSize, maxSize, prefSize, granSize;
205 iasio->getBufferSize(&minSize, &maxSize, &prefSize, &granSize);
206 SleepEx(10, false);
207
208 QString str = tr("%1 (version %2)").arg(QLatin1String(buff)).arg(ver);
209 qlName->setText(str);
210
211 str = tr("%1 -> %2 samples buffer, with %3 sample resolution (%4 preferred) at %5 Hz").arg(minSize).arg(maxSize).arg(granSize).arg(prefSize).arg(srate,0,'f',0);
212
213 qlBuffers->setText(str);
214
215 long ichannels, ochannels;
216 iasio->getChannels(&ichannels, &ochannels);
217 SleepEx(10, false);
218 long cnum;
219
220 bool match = (s.qsASIOclass == qsCls);
221
222 for (cnum=0;cnum<ichannels;cnum++) {
223 ASIOChannelInfo aci;
224 aci.channel = cnum;
225 aci.isInput = true;
226 iasio->getChannelInfo(&aci);
227 SleepEx(10, false);
228 switch (aci.type) {
229 case ASIOSTFloat32LSB:
230 case ASIOSTInt32LSB:
231 case ASIOSTInt24LSB:
232 case ASIOSTInt16LSB: {
233 QListWidget *widget = qlwUnused;
234 QVariant v = static_cast<int>(cnum);
235 if (match && s.qlASIOmic.contains(v))
236 widget = qlwMic;
237 else if (match && s.qlASIOspeaker.contains(v))
238 widget = qlwSpeaker;
239 QListWidgetItem *item = new QListWidgetItem(QLatin1String(aci.name), widget);
240 item->setData(Qt::UserRole, static_cast<int>(cnum));
241 }
242 break;
243 default:
244 qWarning("ASIOInput: Channel %ld %s (Unusable format %ld)", cnum, aci.name,aci.type);
245 }
246 }
247
248 bOk = true;
249 } else {
250 SleepEx(10, false);
251 char err[255];
252 iasio->getErrorMessage(err);
253 SleepEx(10, false);
254 QMessageBox::critical(this, QLatin1String("Mumble"), tr("ASIO Initialization failed: %1").arg(Qt::escape(QLatin1String(err))), QMessageBox::Ok, QMessageBox::NoButton);
255 }
256 iasio->Release();
257 } else {
258 QMessageBox::critical(this, QLatin1String("Mumble"), tr("Failed to instantiate ASIO driver"), QMessageBox::Ok, QMessageBox::NoButton);
259 }
260 }
261
on_qpbConfig_clicked()262 void ASIOConfig::on_qpbConfig_clicked() {
263 QString qsCls = qcbDevice->itemData(qcbDevice->currentIndex()).toString();
264 CLSID clsid;
265 IASIO *iasio;
266
267 CLSIDFromString(const_cast<wchar_t *>(reinterpret_cast<const wchar_t *>(qsCls.utf16())), &clsid);
268 if (CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, clsid, reinterpret_cast<void **>(&iasio)) == S_OK) {
269 SleepEx(10, false);
270 if (iasio->init(mumble_mw_hwnd)) {
271 SleepEx(10, false);
272 iasio->controlPanel();
273 SleepEx(10, false);
274 } else {
275 SleepEx(10, false);
276 char err[255];
277 iasio->getErrorMessage(err);
278 SleepEx(10, false);
279 QMessageBox::critical(this, QLatin1String("Mumble"), tr("ASIO Initialization failed: %1").arg(Qt::escape(QLatin1String(err))), QMessageBox::Ok, QMessageBox::NoButton);
280 }
281 iasio->Release();
282 } else {
283 QMessageBox::critical(this, QLatin1String("Mumble"), tr("Failed to instantiate ASIO driver"), QMessageBox::Ok, QMessageBox::NoButton);
284 }
285 }
286
on_qcbDevice_activated(int)287 void ASIOConfig::on_qcbDevice_activated(int) {
288 clearQuery();
289 }
290
on_qpbAddMic_clicked()291 void ASIOConfig::on_qpbAddMic_clicked() {
292 int row = qlwUnused->currentRow();
293 if (row < 0)
294 return;
295 qlwMic->addItem(qlwUnused->takeItem(row));
296 }
297
on_qpbRemMic_clicked()298 void ASIOConfig::on_qpbRemMic_clicked() {
299 int row = qlwMic->currentRow();
300 if (row < 0)
301 return;
302 qlwUnused->addItem(qlwMic->takeItem(row));
303 }
304
on_qpbAddSpeaker_clicked()305 void ASIOConfig::on_qpbAddSpeaker_clicked() {
306 int row = qlwUnused->currentRow();
307 if (row < 0)
308 return;
309 qlwSpeaker->addItem(qlwUnused->takeItem(row));
310 }
311
on_qpbRemSpeaker_clicked()312 void ASIOConfig::on_qpbRemSpeaker_clicked() {
313 int row = qlwSpeaker->currentRow();
314 if (row < 0)
315 return;
316 qlwUnused->addItem(qlwSpeaker->takeItem(row));
317 }
318
title() const319 QString ASIOConfig::title() const {
320 return tr("ASIO");
321 }
322
icon() const323 QIcon ASIOConfig::icon() const {
324 return QIcon(QLatin1String("skin:config_asio.png"));
325 }
326
save() const327 void ASIOConfig::save() const {
328 if (! bOk)
329 return;
330
331 s.qsASIOclass = qcbDevice->itemData(qcbDevice->currentIndex()).toString();
332
333 QList<QVariant> list;
334
335 for (int i=0;i<qlwMic->count();i++) {
336 QListWidgetItem *item = qlwMic->item(i);
337 list << item->data(Qt::UserRole);
338 }
339
340 s.qlASIOmic = list;
341
342 list.clear();
343
344 for (int i=0;i<qlwSpeaker->count();i++) {
345 QListWidgetItem *item = qlwSpeaker->item(i);
346 list << item->data(Qt::UserRole);
347 }
348
349 s.qlASIOspeaker = list;
350 }
351
load(const Settings & r)352 void ASIOConfig::load(const Settings &r) {
353 int i = 0;
354 ASIODev ad;
355 foreach(ad, qlDevs) {
356 if (ad.second == r.qsASIOclass) {
357 loadComboBox(qcbDevice, i);
358 }
359 i++;
360 }
361 s.qlASIOmic = r.qlASIOmic;
362 s.qlASIOspeaker = r.qlASIOspeaker;
363
364 qlName->setText(QString());
365 qlBuffers->setText(QString());
366 qlwMic->clear();
367 qlwUnused->clear();
368 qlwSpeaker->clear();
369 }
370
clearQuery()371 void ASIOConfig::clearQuery() {
372 bOk = false;
373 qlName->setText(QString());
374 qlBuffers->setText(QString());
375 qlwMic->clear();
376 qlwUnused->clear();
377 qlwSpeaker->clear();
378 }
379
ASIOInput()380 ASIOInput::ASIOInput() {
381 QString qsCls = g.s.qsASIOclass;
382 CLSID clsid;
383
384 iasio = NULL;
385 abiInfo = NULL;
386 aciInfo = NULL;
387
388 // Sanity check things first.
389
390 iNumMic=g.s.qlASIOmic.count();
391 iNumSpeaker=g.s.qlASIOspeaker.count();
392
393 if ((iNumMic == 0) || (iNumSpeaker == 0)) {
394 QMessageBox::warning(NULL, QLatin1String("Mumble"), tr("You need to select at least one microphone and one speaker source to use ASIO. "
395 "If you just need microphone sampling, use DirectSound."), QMessageBox::Ok, QMessageBox::NoButton);
396 return;
397 }
398
399 CLSIDFromString(const_cast<wchar_t *>(reinterpret_cast<const wchar_t *>(qsCls.utf16())), &clsid);
400 if (CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, clsid, reinterpret_cast<void **>(&iasio)) == S_OK) {
401 if (iasio->init(NULL)) {
402 iasio->setSampleRate(48000.0);
403 ASIOSampleRate srate = 0.0;
404 iasio->getSampleRate(&srate);
405
406 if (srate <= 0.0)
407 return;
408
409 long minSize, maxSize, prefSize, granSize;
410 iasio->getBufferSize(&minSize, &maxSize, &prefSize, &granSize);
411
412 bool halfit = true;
413
414 double wbuf = (srate / 100.0);
415 long wantBuf = lround(wbuf);
416 lBufSize = wantBuf;
417
418 if (static_cast<double>(wantBuf) == wbuf) {
419 qWarning("ASIOInput: Exact buffer match possible.");
420 if ((wantBuf >= minSize) && (wantBuf <= maxSize)) {
421 if (wantBuf == minSize)
422 halfit = false;
423 else if ((granSize >= 1) && (((wantBuf-minSize)%granSize)==0))
424 halfit = false;
425 }
426 }
427
428 if (halfit) {
429 if (granSize == 0) {
430 qWarning("ASIOInput: Single buffer size");
431 lBufSize = minSize;
432 } else {
433 long target = wantBuf / 2;
434 lBufSize = target;
435 while (lBufSize < target) {
436 if (granSize < 0)
437 lBufSize *= 2;
438 else
439 lBufSize += granSize;
440 }
441 }
442 qWarning("ASIOInput: Buffer mismatch mode. Wanted %li, got %li", wantBuf, lBufSize);
443 }
444
445
446 abiInfo = new ASIOBufferInfo[iNumMic + iNumSpeaker];
447 aciInfo = new ASIOChannelInfo[iNumMic + iNumSpeaker];
448
449 int i, idx = 0;
450 for (i=0;i<iNumMic;i++) {
451 abiInfo[idx].isInput = true;
452 abiInfo[idx].channelNum = g.s.qlASIOmic[i].toInt();
453
454 aciInfo[idx].channel = abiInfo[idx].channelNum;
455 aciInfo[idx].isInput = true;
456 iasio->getChannelInfo(&aciInfo[idx]);
457 SleepEx(10, false);
458
459 idx++;
460 }
461 for (i=0;i<iNumSpeaker;i++) {
462 abiInfo[idx].isInput = true;
463 abiInfo[idx].channelNum = g.s.qlASIOspeaker[i].toInt();
464
465 aciInfo[idx].channel = abiInfo[idx].channelNum;
466 aciInfo[idx].isInput = true;
467 iasio->getChannelInfo(&aciInfo[idx]);
468 SleepEx(10, false);
469
470 idx++;
471 }
472
473 iEchoChannels = iNumSpeaker;
474 iMicChannels = iNumMic;
475 iEchoFreq = iMicFreq = iroundf(srate);
476
477 initializeMixer();
478
479 ASIOCallbacks asioCallbacks;
480 ZeroMemory(&asioCallbacks, sizeof(asioCallbacks));
481 asioCallbacks.bufferSwitch = &bufferSwitch;
482 asioCallbacks.sampleRateDidChange = &sampleRateChanged;
483 asioCallbacks.asioMessage = &asioMessages;
484 asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo;
485
486 if (iasio->createBuffers(abiInfo, idx, lBufSize, &asioCallbacks) == ASE_OK) {
487 bRunning = true;
488 return;
489 }
490 }
491 }
492
493 if (iasio) {
494 iasio->Release();
495 iasio = NULL;
496 }
497
498 QMessageBox::critical(NULL, QLatin1String("Mumble"), tr("Opening selected ASIO device failed. No input will be done."),
499 QMessageBox::Ok, QMessageBox::NoButton);
500
501 }
502
~ASIOInput()503 ASIOInput::~ASIOInput() {
504 qwDone.wakeAll();
505 wait();
506 if (iasio) {
507 iasio->stop();
508 iasio->disposeBuffers();
509 iasio->Release();
510 iasio = NULL;
511 }
512
513 delete [] abiInfo;
514 abiInfo = NULL;
515
516 delete [] aciInfo;
517 aciInfo = NULL;
518 }
519
run()520 void ASIOInput::run() {
521 QMutex m;
522 m.lock();
523 if (iasio) {
524 aiSelf = this;
525 iasio->start();
526 qwDone.wait(&m);
527 }
528 }
529
bufferSwitchTimeInfo(ASIOTime *,long index,ASIOBool)530 ASIOTime *ASIOInput::bufferSwitchTimeInfo(ASIOTime *, long index, ASIOBool) {
531 aiSelf->bufferReady(index);
532 return 0L;
533 }
534
535 void
addBuffer(ASIOSampleType sampType,int interleave,void * src,float * RESTRICT dst)536 ASIOInput::addBuffer(ASIOSampleType sampType, int interleave, void *src, float * RESTRICT dst) {
537 switch (sampType) {
538 case ASIOSTInt16LSB: {
539 const float m = 1.0f / 32768.f;
540 const short * RESTRICT buf=static_cast<short *>(src);
541 for (int i=0;i<lBufSize;i++)
542 dst[i*interleave]=buf[i] * m;
543 }
544 break;
545 case ASIOSTInt32LSB: {
546 const float m = 1.0f / 2147483648.f;
547 const int * RESTRICT buf=static_cast<int *>(src);
548 for (int i=0;i<lBufSize;i++)
549 dst[i*interleave]=buf[i] * m;
550 }
551 break;
552 case ASIOSTInt24LSB: {
553 const float m = 1.0f / static_cast<float>(0x7FFFFFFF - 0xFF);
554 const unsigned char * RESTRICT buf=static_cast<unsigned char *>(src);
555 for (int i=0;i<lBufSize;i++)
556 dst[i * interleave] = (buf[i*3] << 24 | buf[i*3+1] << 16 | buf[i*3+2] << 8) * m;
557 }
558 break;
559 case ASIOSTFloat32LSB: {
560 const float * RESTRICT buf=static_cast<float *>(src);
561 for (int i=0;i<lBufSize;i++)
562 dst[i*interleave]=buf[i];
563 }
564 break;
565 }
566 }
567
568 void
bufferReady(long buffindex)569 ASIOInput::bufferReady(long buffindex) {
570 STACKVAR(float, buffer, lBufSize * qMax(iNumMic,iNumSpeaker));
571
572 for (int c=0;c<iNumSpeaker;++c)
573 addBuffer(aciInfo[iNumMic+c].type, iNumSpeaker, abiInfo[iNumMic+c].buffers[buffindex], buffer+c);
574 addEcho(buffer, lBufSize);
575
576 for (int c=0;c<iNumMic;++c)
577 addBuffer(aciInfo[c].type, iNumMic, abiInfo[c].buffers[buffindex], buffer+c);
578 addMic(buffer, lBufSize);
579 }
580
bufferSwitch(long index,ASIOBool processNow)581 void ASIOInput::bufferSwitch(long index, ASIOBool processNow) {
582 ASIOTime timeInfo;
583 memset(&timeInfo, 0, sizeof(timeInfo));
584
585 if (aiSelf->iasio->getSamplePosition(&timeInfo.timeInfo.samplePosition, &timeInfo.timeInfo.systemTime) == ASE_OK)
586 timeInfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid;
587
588 bufferSwitchTimeInfo(&timeInfo, index, processNow);
589 }
590
sampleRateChanged(ASIOSampleRate)591 void ASIOInput::sampleRateChanged(ASIOSampleRate) {
592 qFatal("ASIOInput: sampleRateChanged");
593 }
594
asioMessages(long selector,long value,void *,double *)595 long ASIOInput::asioMessages(long selector, long value, void*, double*) {
596 long ret = 0;
597 switch (selector) {
598 case kAsioSelectorSupported:
599 if (value == kAsioResetRequest
600 || value == kAsioEngineVersion
601 || value == kAsioResyncRequest
602 || value == kAsioLatenciesChanged
603 || value == kAsioSupportsTimeInfo
604 || value == kAsioSupportsTimeCode
605 || value == kAsioSupportsInputMonitor)
606 ret = 1L;
607 break;
608 case kAsioResetRequest:
609 qFatal("ASIOInput: kAsioResetRequest");
610 ret = 1L;
611 break;
612 case kAsioResyncRequest:
613 ret = 1L;
614 break;
615 case kAsioLatenciesChanged:
616 ret = 1L;
617 break;
618 case kAsioEngineVersion:
619 ret = 2L;
620 break;
621 case kAsioSupportsTimeInfo:
622 ret = 1;
623 break;
624 case kAsioSupportsTimeCode:
625 ret = 0;
626 break;
627 }
628 return ret;
629 }
630