OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
asio_protocol.hpp
1#pragma once
2#include <ossia/detail/config.hpp>
3
4#if defined(OSSIA_ENABLE_ASIO)
5#include <ossia/audio/audio_engine.hpp>
6#include <ossia/detail/fmt.hpp>
8#include <ossia/detail/pod_vector.hpp>
9#include <ossia/detail/thread.hpp>
10
11#if !defined(WIN32_LEAN_AND_MEAN)
12#define WIN32_LEAN_AND_MEAN
13#endif
14#if !defined(NOMINMAX)
15#define NOMINMAX
16#endif
17
18// iasiodrv.h uses IUnknown without including unknwn.h, and WIN32_LEAN_AND_MEAN
19// stops windows.h from pulling it in.
20#include <windows.h>
21#include <unknwn.h>
22
23#include <asiodrivers.h>
24#include <asio.h>
25#include <iasiodrv.h>
26
27bool loadAsioDriver(char *name);
28extern AsioDrivers* asioDrivers;
29
30#include <algorithm>
31#include <cmath>
32#include <cstring>
33#include <string>
34#include <vector>
35
36#define OSSIA_AUDIO_ASIO 1
37
38namespace ossia
39{
40struct asio_card
41{
42 std::string name;
43 int driver_index{-1};
44};
45
46// Must be a singleton — ASIO SDK only supports one loaded driver at a time.
47// We store the active engine pointer in a global so the static ASIO callbacks
48// can reach it.
49class asio_engine;
50namespace detail
51{
52inline asio_engine* current_asio_engine = nullptr;
53}
54
55class asio_engine final : public audio_engine
56{
57public:
58 asio_engine(
59 std::string driver_name, int inputs, int outputs, int rate, int bs)
60 {
61 if(detail::current_asio_engine)
62 {
63 throw std::runtime_error(
64 "ASIO error: driver '" + detail::current_asio_engine->m_driver_name
65 + "' is still active, cannot open '" + driver_name + "'");
66 }
67
68 detail::current_asio_engine = this;
69 m_driver_name = driver_name;
70
71 // Load the ASIO driver
72 if(!loadAsioDriver(const_cast<char*>(driver_name.c_str())))
73 {
74 cleanup();
75 throw std::runtime_error("ASIO error: could not load driver '" + driver_name + "'");
76 }
77
78 // Initialize the driver
79 ASIODriverInfo driverInfo{};
80 driverInfo.asioVersion = 2;
81 driverInfo.sysRef = nullptr; // no window handle needed for headless operation
82
83 if(ASIOInit(&driverInfo) != ASE_OK)
84 {
85 cleanup();
86 throw std::runtime_error(
87 std::string("ASIO error: ASIOInit failed: ") + driverInfo.errorMessage);
88 }
89 m_initialized = true;
90
91 // Query driver capabilities
92 long maxInputChannels = 0, maxOutputChannels = 0;
93 if(ASIOGetChannels(&maxInputChannels, &maxOutputChannels) != ASE_OK)
94 {
95 cleanup();
96 throw std::runtime_error("ASIO error: ASIOGetChannels failed");
97 }
98
99 inputs = std::min((long)inputs, maxInputChannels);
100 outputs = std::min((long)outputs, maxOutputChannels);
101
102 if(inputs == 0 && outputs == 0)
103 {
104 cleanup();
105 throw std::runtime_error("ASIO error: no channels available");
106 }
107
108 // Set sample rate
109 if(ASIOCanSampleRate((ASIOSampleRate)rate) != ASE_OK)
110 {
111 // Try to use the driver's current rate
112 ASIOSampleRate currentRate = 0;
113 if(ASIOGetSampleRate(&currentRate) == ASE_OK && currentRate > 0)
114 {
115 rate = (int)currentRate;
116 }
117 else
118 {
119 cleanup();
120 throw std::runtime_error("ASIO error: sample rate not supported");
121 }
122 }
123 else
124 {
125 if(ASIOSetSampleRate((ASIOSampleRate)rate) != ASE_OK)
126 {
127 cleanup();
128 throw std::runtime_error("ASIO error: could not set sample rate");
129 }
130 }
131
132 // Query buffer size
133 long minSize = 0, maxSize = 0, preferredSize = 0, granularity = 0;
134 if(ASIOGetBufferSize(&minSize, &maxSize, &preferredSize, &granularity) != ASE_OK)
135 {
136 cleanup();
137 throw std::runtime_error("ASIO error: ASIOGetBufferSize failed");
138 }
139
140 // Clamp requested buffer size to driver constraints
141 if(bs <= 0)
142 bs = preferredSize;
143 bs = std::max(bs, (int)minSize);
144 bs = std::min(bs, (int)maxSize);
145
146 // For power-of-2 granularity, snap to nearest valid power of 2
147 if(granularity == -1)
148 {
149 int pot = minSize;
150 while(pot < bs && pot < maxSize)
151 pot *= 2;
152 bs = pot;
153 }
154 else if(granularity > 0)
155 {
156 bs = ((bs + granularity - 1) / granularity) * granularity;
157 bs = std::min(bs, (int)maxSize);
158 }
159
160 m_bufferSize = bs;
161 m_inputCount = inputs;
162 m_outputCount = outputs;
163
164 // Allocate buffer info structures
165 int totalChannels = inputs + outputs;
166 m_bufferInfos.resize(totalChannels);
167 m_channelInfos.resize(totalChannels);
168
169 int idx = 0;
170 for(int i = 0; i < inputs; i++, idx++)
171 {
172 m_bufferInfos[idx].isInput = ASIOTrue;
173 m_bufferInfos[idx].channelNum = i;
174 m_bufferInfos[idx].buffers[0] = m_bufferInfos[idx].buffers[1] = nullptr;
175 }
176 for(int i = 0; i < outputs; i++, idx++)
177 {
178 m_bufferInfos[idx].isInput = ASIOFalse;
179 m_bufferInfos[idx].channelNum = i;
180 m_bufferInfos[idx].buffers[0] = m_bufferInfos[idx].buffers[1] = nullptr;
181 }
182
183 // Set up callbacks
184 m_asioCallbacks.bufferSwitch = &asio_bufferSwitch;
185 m_asioCallbacks.sampleRateDidChange = &asio_sampleRateDidChange;
186 m_asioCallbacks.asioMessage = &asio_message;
187 m_asioCallbacks.bufferSwitchTimeInfo = &asio_bufferSwitchTimeInfo;
188
189 // Create buffers
190 if(ASIOCreateBuffers(
191 m_bufferInfos.data(), totalChannels, m_bufferSize, &m_asioCallbacks)
192 != ASE_OK)
193 {
194 cleanup();
195 throw std::runtime_error("ASIO error: ASIOCreateBuffers failed");
196 }
197 m_buffersCreated = true;
198
199 // Get channel info (for sample format)
200 for(int i = 0; i < totalChannels; i++)
201 {
202 m_channelInfos[i].channel = m_bufferInfos[i].channelNum;
203 m_channelInfos[i].isInput = m_bufferInfos[i].isInput;
204 if(ASIOGetChannelInfo(&m_channelInfos[i]) != ASE_OK)
205 {
206 cleanup();
207 throw std::runtime_error("ASIO error: ASIOGetChannelInfo failed");
208 }
209 }
210
211 // Check if ASIOOutputReady is supported
212 m_postOutput = (ASIOOutputReady() == ASE_OK);
213
214 // Allocate float conversion buffers (non-interleaved, one per channel)
215 m_floatInputs.resize(inputs);
216 m_floatOutputs.resize(outputs);
217 m_inputPtrs.resize(inputs);
218 m_outputPtrs.resize(outputs);
219 for(int i = 0; i < inputs; i++)
220 {
221 m_floatInputs[i].resize(m_bufferSize);
222 m_inputPtrs[i] = m_floatInputs[i].data();
223 }
224 for(int i = 0; i < outputs; i++)
225 {
226 m_floatOutputs[i].resize(m_bufferSize);
227 m_outputPtrs[i] = m_floatOutputs[i].data();
228 }
229
230 // Set effective parameters
231 this->effective_sample_rate = rate;
232 this->effective_buffer_size = m_bufferSize;
233 this->effective_inputs = inputs;
234 this->effective_outputs = outputs;
235
236 // Start processing
237 if(ASIOStart() != ASE_OK)
238 {
239 cleanup();
240 throw std::runtime_error("ASIO error: ASIOStart failed");
241 }
242 m_started = true;
243 }
244
245 bool running() const override { return m_started && !stop_processing; }
246
247 void stop() override
248 {
249 audio_engine::stop();
250
251 // Released here, not in the destructor: hosts defer destruction and ASIO
252 // allows a single loaded driver per process.
253 cleanup();
254
255 // No further callbacks after ASIOStop(), so acknowledge the stop ourselves.
256 stop_received = true;
257 }
258
259 ~asio_engine() override { stop(); }
260
261 static std::vector<asio_card> enumerate_drivers()
262 {
263 std::vector<asio_card> cards;
264
265 AsioDrivers drivers;
266 const long numDrivers = drivers.asioGetNumDev();
267 ossia::logger().info(fmt::format("ASIO: {} driver(s) installed", numDrivers));
268
269 for(long i = 0; i < numDrivers; i++)
270 {
271 char name[MAXDRVNAMELEN]{};
272 const long rc = drivers.asioGetDriverName(i, name, sizeof(name));
273 if(rc == 0)
274 {
275 ossia::logger().info(fmt::format("ASIO: [{}] '{}'", i, name));
276 cards.push_back({name, (int)i});
277 }
278 else
279 {
280 // Should not happen: the index came from asioGetNumDev().
281 ossia::logger().warn(
282 fmt::format("ASIO: asioGetDriverName({}) failed, rc={}, driver skipped", i, rc));
283 }
284 }
285
286 return cards;
287 }
288
289 // Name of the driver currently loaded by an active engine, empty if none.
290 static std::string active_driver()
291 {
292 if(auto* e = detail::current_asio_engine)
293 return e->m_driver_name;
294 return {};
295 }
296
297 enum class control_panel_result
298 {
299 ok,
301 other_driver_active,
302 load_failed,
303 init_failed
304 };
305
306 // Targets driver_name specifically: ASIOControlPanel() talks to whichever
307 // driver happens to be loaded, and loading another would release that one.
308 static control_panel_result open_control_panel(const std::string& driver_name)
309 {
310 if(auto* engine = detail::current_asio_engine)
311 {
312 if(engine->m_driver_name != driver_name)
313 {
314 ossia::logger().warn(fmt::format(
315 "ASIO: cannot open the control panel of '{}': '{}' is currently streaming",
316 driver_name, engine->m_driver_name));
317 return control_panel_result::other_driver_active;
318 }
319
320 ASIOControlPanel();
321 return control_panel_result::ok;
322 }
323
324 // Nothing running: load the requested driver just long enough to show it.
325 if(!loadAsioDriver(const_cast<char*>(driver_name.c_str())))
326 {
327 ossia::logger().warn(
328 fmt::format("ASIO: could not load '{}' to show its control panel", driver_name));
329 return control_panel_result::load_failed;
330 }
331
332 ASIODriverInfo info{};
333 info.asioVersion = 2;
334 if(ASIOInit(&info) != ASE_OK)
335 {
336 if(asioDrivers)
337 asioDrivers->removeCurrentDriver();
338 ossia::logger().warn(fmt::format(
339 "ASIO: ASIOInit failed for '{}', cannot show its control panel ({})",
340 driver_name, info.errorMessage[0] ? info.errorMessage : "no message"));
341 return control_panel_result::init_failed;
342 }
343
344 ASIOControlPanel();
345
346 // Unload, so a later request for a different driver still works.
347 ASIOExit();
348 if(asioDrivers)
349 asioDrivers->removeCurrentDriver();
350
351 return control_panel_result::ok;
352 }
353
354private:
355 // Idempotent, and a no-op unless this engine still owns the driver.
356 void cleanup()
357 {
358 if(detail::current_asio_engine != this)
359 return;
360
361 if(m_started)
362 {
363 ASIOStop();
364 m_started = false;
365 }
366
367 if(m_buffersCreated)
368 {
369 ASIODisposeBuffers();
370 m_buffersCreated = false;
371 }
372
373 if(m_initialized)
374 {
375 ASIOExit();
376 m_initialized = false;
377 }
378
379 if(asioDrivers)
380 asioDrivers->removeCurrentDriver();
381
382 // Cleared last: the callbacks reach the engine through this pointer.
383 detail::current_asio_engine = nullptr;
384 }
385
386 // Convert ASIO native buffer to float for one channel
387 static void convertToFloat(
388 void* src, float* dst, long frames, ASIOSampleType type)
389 {
390 switch(type)
391 {
392 case ASIOSTFloat32LSB:
393 {
394 std::memcpy(dst, src, frames * sizeof(float));
395 break;
396 }
397 case ASIOSTFloat64LSB:
398 {
399 auto* s = static_cast<double*>(src);
400 for(long i = 0; i < frames; i++)
401 dst[i] = (float)s[i];
402 break;
403 }
404 case ASIOSTInt32LSB:
405 {
406 auto* s = static_cast<int32_t*>(src);
407 constexpr float scale = 1.0f / 2147483648.0f;
408 for(long i = 0; i < frames; i++)
409 dst[i] = s[i] * scale;
410 break;
411 }
412 case ASIOSTInt24LSB:
413 {
414 auto* s = static_cast<uint8_t*>(src);
415 constexpr float scale = 1.0f / 8388608.0f;
416 for(long i = 0; i < frames; i++)
417 {
418 int32_t val = (int32_t(s[i * 3 + 2]) << 24) | (int32_t(s[i * 3 + 1]) << 16)
419 | (int32_t(s[i * 3]) << 8);
420 dst[i] = (val >> 8) * scale;
421 }
422 break;
423 }
424 case ASIOSTInt16LSB:
425 {
426 auto* s = static_cast<int16_t*>(src);
427 constexpr float scale = 1.0f / 32768.0f;
428 for(long i = 0; i < frames; i++)
429 dst[i] = s[i] * scale;
430 break;
431 }
432 case ASIOSTInt32LSB16:
433 {
434 auto* s = static_cast<int32_t*>(src);
435 constexpr float scale = 1.0f / 32768.0f;
436 for(long i = 0; i < frames; i++)
437 dst[i] = (s[i] & 0xFFFF) * scale;
438 break;
439 }
440 case ASIOSTInt32LSB18:
441 {
442 auto* s = static_cast<int32_t*>(src);
443 constexpr float scale = 1.0f / 131072.0f;
444 for(long i = 0; i < frames; i++)
445 dst[i] = (s[i] & 0x3FFFF) * scale;
446 break;
447 }
448 case ASIOSTInt32LSB20:
449 {
450 auto* s = static_cast<int32_t*>(src);
451 constexpr float scale = 1.0f / 524288.0f;
452 for(long i = 0; i < frames; i++)
453 dst[i] = (s[i] & 0xFFFFF) * scale;
454 break;
455 }
456 case ASIOSTInt32LSB24:
457 {
458 auto* s = static_cast<int32_t*>(src);
459 constexpr float scale = 1.0f / 8388608.0f;
460 for(long i = 0; i < frames; i++)
461 dst[i] = (s[i] & 0xFFFFFF) * scale;
462 break;
463 }
464 // MSB formats (big-endian) — rare on x86 but must be handled
465 case ASIOSTFloat32MSB:
466 {
467 auto* s = static_cast<uint8_t*>(src);
468 for(long i = 0; i < frames; i++)
469 {
470 uint32_t val = (uint32_t(s[i * 4]) << 24) | (uint32_t(s[i * 4 + 1]) << 16)
471 | (uint32_t(s[i * 4 + 2]) << 8) | uint32_t(s[i * 4 + 3]);
472 float f;
473 std::memcpy(&f, &val, 4);
474 dst[i] = f;
475 }
476 break;
477 }
478 case ASIOSTInt32MSB:
479 {
480 auto* s = static_cast<uint8_t*>(src);
481 constexpr float scale = 1.0f / 2147483648.0f;
482 for(long i = 0; i < frames; i++)
483 {
484 int32_t val = (int32_t(s[i * 4]) << 24) | (int32_t(s[i * 4 + 1]) << 16)
485 | (int32_t(s[i * 4 + 2]) << 8) | int32_t(s[i * 4 + 3]);
486 dst[i] = val * scale;
487 }
488 break;
489 }
490 case ASIOSTInt16MSB:
491 {
492 auto* s = static_cast<uint8_t*>(src);
493 constexpr float scale = 1.0f / 32768.0f;
494 for(long i = 0; i < frames; i++)
495 {
496 int16_t val = (int16_t(s[i * 2]) << 8) | int16_t(s[i * 2 + 1]);
497 dst[i] = val * scale;
498 }
499 break;
500 }
501 default:
502 std::memset(dst, 0, frames * sizeof(float));
503 break;
504 }
505 }
506
507 // Convert float to ASIO native buffer for one channel
508 static void convertFromFloat(
509 const float* src, void* dst, long frames, ASIOSampleType type)
510 {
511 switch(type)
512 {
513 case ASIOSTFloat32LSB:
514 {
515 std::memcpy(dst, src, frames * sizeof(float));
516 break;
517 }
518 case ASIOSTFloat64LSB:
519 {
520 auto* d = static_cast<double*>(dst);
521 for(long i = 0; i < frames; i++)
522 d[i] = (double)src[i];
523 break;
524 }
525 case ASIOSTInt32LSB:
526 {
527 auto* d = static_cast<int32_t*>(dst);
528 constexpr double scale = 2147483647.0;
529 for(long i = 0; i < frames; i++)
530 {
531 double val = std::max(-1.0, std::min(1.0, (double)src[i]));
532 d[i] = (int32_t)(val * scale);
533 }
534 break;
535 }
536 case ASIOSTInt24LSB:
537 {
538 auto* d = static_cast<uint8_t*>(dst);
539 constexpr double scale = 8388607.0;
540 for(long i = 0; i < frames; i++)
541 {
542 double val = std::max(-1.0, std::min(1.0, (double)src[i]));
543 int32_t s = (int32_t)(val * scale);
544 d[i * 3] = (uint8_t)(s & 0xFF);
545 d[i * 3 + 1] = (uint8_t)((s >> 8) & 0xFF);
546 d[i * 3 + 2] = (uint8_t)((s >> 16) & 0xFF);
547 }
548 break;
549 }
550 case ASIOSTInt16LSB:
551 {
552 auto* d = static_cast<int16_t*>(dst);
553 constexpr double scale = 32767.0;
554 for(long i = 0; i < frames; i++)
555 {
556 double val = std::max(-1.0, std::min(1.0, (double)src[i]));
557 d[i] = (int16_t)(val * scale);
558 }
559 break;
560 }
561 case ASIOSTInt32LSB16:
562 {
563 auto* d = static_cast<int32_t*>(dst);
564 constexpr double scale = 32767.0;
565 for(long i = 0; i < frames; i++)
566 {
567 double val = std::max(-1.0, std::min(1.0, (double)src[i]));
568 d[i] = (int32_t)(val * scale);
569 }
570 break;
571 }
572 case ASIOSTInt32LSB18:
573 {
574 auto* d = static_cast<int32_t*>(dst);
575 constexpr double scale = 131071.0;
576 for(long i = 0; i < frames; i++)
577 {
578 double val = std::max(-1.0, std::min(1.0, (double)src[i]));
579 d[i] = (int32_t)(val * scale);
580 }
581 break;
582 }
583 case ASIOSTInt32LSB20:
584 {
585 auto* d = static_cast<int32_t*>(dst);
586 constexpr double scale = 524287.0;
587 for(long i = 0; i < frames; i++)
588 {
589 double val = std::max(-1.0, std::min(1.0, (double)src[i]));
590 d[i] = (int32_t)(val * scale);
591 }
592 break;
593 }
594 case ASIOSTInt32LSB24:
595 {
596 auto* d = static_cast<int32_t*>(dst);
597 constexpr double scale = 8388607.0;
598 for(long i = 0; i < frames; i++)
599 {
600 double val = std::max(-1.0, std::min(1.0, (double)src[i]));
601 d[i] = (int32_t)(val * scale);
602 }
603 break;
604 }
605 case ASIOSTFloat32MSB:
606 {
607 auto* d = static_cast<uint8_t*>(dst);
608 for(long i = 0; i < frames; i++)
609 {
610 uint32_t val;
611 std::memcpy(&val, &src[i], 4);
612 d[i * 4] = (uint8_t)(val >> 24);
613 d[i * 4 + 1] = (uint8_t)(val >> 16);
614 d[i * 4 + 2] = (uint8_t)(val >> 8);
615 d[i * 4 + 3] = (uint8_t)(val);
616 }
617 break;
618 }
619 case ASIOSTInt32MSB:
620 {
621 auto* d = static_cast<uint8_t*>(dst);
622 constexpr double scale = 2147483647.0;
623 for(long i = 0; i < frames; i++)
624 {
625 double val = std::max(-1.0, std::min(1.0, (double)src[i]));
626 int32_t s = (int32_t)(val * scale);
627 d[i * 4] = (uint8_t)(s >> 24);
628 d[i * 4 + 1] = (uint8_t)(s >> 16);
629 d[i * 4 + 2] = (uint8_t)(s >> 8);
630 d[i * 4 + 3] = (uint8_t)(s);
631 }
632 break;
633 }
634 case ASIOSTInt16MSB:
635 {
636 auto* d = static_cast<uint8_t*>(dst);
637 constexpr double scale = 32767.0;
638 for(long i = 0; i < frames; i++)
639 {
640 double val = std::max(-1.0, std::min(1.0, (double)src[i]));
641 int16_t s = (int16_t)(val * scale);
642 d[i * 2] = (uint8_t)(s >> 8);
643 d[i * 2 + 1] = (uint8_t)(s);
644 }
645 break;
646 }
647 default:
648 break;
649 }
650 }
651
652 // === ASIO Callbacks (static, use global engine pointer) ===
653
654 static ASIOTime* asio_bufferSwitchTimeInfo(
655 ASIOTime* params, long doubleBufferIndex, ASIOBool directProcess)
656 {
657 auto* self = detail::current_asio_engine;
658 if(!self)
659 return nullptr;
660
661 [[maybe_unused]] static const thread_local auto _ = [] {
662 ossia::set_thread_name("ossia audio 0");
663 ossia::set_thread_pinned(thread_type::Audio, 0);
664 return 0;
665 }();
666
667 self->tick_start();
668
669 if(self->stop_processing)
670 {
671 self->tick_clear();
672 // Clear output buffers
673 for(int i = 0; i < self->m_outputCount; i++)
674 {
675 auto& info = self->m_bufferInfos[self->m_inputCount + i];
676 auto& chanInfo = self->m_channelInfos[self->m_inputCount + i];
677 void* buf = info.buffers[doubleBufferIndex];
678 if(buf)
679 {
680 std::memset(buf, 0, self->sampleSize(chanInfo.type) * self->m_bufferSize);
681 }
682 }
683 if(self->m_postOutput)
684 ASIOOutputReady();
685 return nullptr;
686 }
687
688 const long frames = self->m_bufferSize;
689
690 // Convert ASIO input buffers -> float
691 for(int i = 0; i < self->m_inputCount; i++)
692 {
693 void* src = self->m_bufferInfos[i].buffers[doubleBufferIndex];
694 convertToFloat(src, self->m_inputPtrs[i], frames, self->m_channelInfos[i].type);
695 }
696
697 // Clear float output buffers
698 for(int i = 0; i < self->m_outputCount; i++)
699 {
700 std::memset(self->m_outputPtrs[i], 0, frames * sizeof(float));
701 }
702
703 // Compute time — ASIO has no transport, so we only provide wall-clock
704 // seconds (like PortAudio). samplePosition is a monotonic counter since
705 // ASIOStart() and must NOT be used as position_in_frames (which is a
706 // transport position that resets on stop/play).
707 double seconds = 0.0;
708 if(params && (params->timeInfo.flags & kSystemTimeValid))
709 {
710 seconds = asioSamplesToDouble(params->timeInfo.systemTime) * 1e-9;
711 }
712
713 // Call audio tick
714 ossia::audio_tick_state ts{
715 const_cast<float* const*>(self->m_inputPtrs.data()),
716 self->m_outputPtrs.data(),
717 self->m_inputCount,
718 self->m_outputCount,
719 (uint64_t)frames,
720 seconds};
721
722 self->audio_tick(ts);
723 self->tick_end();
724
725 // Convert float output buffers -> ASIO native format
726 for(int i = 0; i < self->m_outputCount; i++)
727 {
728 int chIdx = self->m_inputCount + i;
729 void* dst = self->m_bufferInfos[chIdx].buffers[doubleBufferIndex];
730 convertFromFloat(
731 self->m_outputPtrs[i], dst, frames, self->m_channelInfos[chIdx].type);
732 }
733
734 if(self->m_postOutput)
735 ASIOOutputReady();
736
737 return nullptr;
738 }
739
740 static void asio_bufferSwitch(long doubleBufferIndex, ASIOBool directProcess)
741 {
742 // Construct time info and delegate to the time-info callback
743 ASIOTime timeInfo{};
744 std::memset(&timeInfo, 0, sizeof(ASIOTime));
745
746 ASIOSamples sPos;
747 ASIOTimeStamp tStamp;
748 if(ASIOGetSamplePosition(&sPos, &tStamp) == ASE_OK)
749 {
750 timeInfo.timeInfo.samplePosition = sPos;
751 timeInfo.timeInfo.systemTime = tStamp;
752 timeInfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid;
753 }
754
755 ASIOSampleRate sRate = 0;
756 if(ASIOGetSampleRate(&sRate) == ASE_OK)
757 {
758 timeInfo.timeInfo.sampleRate = sRate;
759 timeInfo.timeInfo.flags |= kSampleRateValid;
760 }
761
762 asio_bufferSwitchTimeInfo(&timeInfo, doubleBufferIndex, directProcess);
763 }
764
765 static void asio_sampleRateDidChange(ASIOSampleRate sRate)
766 {
767 auto* self = detail::current_asio_engine;
768 if(self && sRate > 0)
769 {
770 self->effective_sample_rate = (int)sRate;
771 }
772 }
773
774 static long asio_message(long selector, long value, void* message, double* opt)
775 {
776 switch(selector)
777 {
778 case kAsioSelectorSupported:
779 switch(value)
780 {
781 case kAsioEngineVersion:
782 case kAsioSupportsTimeInfo:
783 case kAsioResetRequest:
784 case kAsioResyncRequest:
785 case kAsioLatenciesChanged:
786 case kAsioOverload:
787 return 1;
788 default:
789 return 0;
790 }
791
792 case kAsioEngineVersion:
793 return 2;
794
795 case kAsioSupportsTimeInfo:
796 return 1;
797
798 case kAsioResetRequest:
799 // Driver requests reset — for now, just acknowledge
800 return 1;
801
802 case kAsioResyncRequest:
803 return 1;
804
805 case kAsioLatenciesChanged:
806 return 1;
807
808 case kAsioOverload:
809 return 1;
810
811 default:
812 return 0;
813 }
814 }
815
816 static double asioSamplesToDouble(const ASIOSamples& s)
817 {
818#if NATIVE_INT64
819 return (double)s;
820#else
821 return s.hi * 4294967296.0 + s.lo;
822#endif
823 }
824
825 static double asioSamplesToDouble(const ASIOTimeStamp& s)
826 {
827#if NATIVE_INT64
828 return (double)s;
829#else
830 return s.hi * 4294967296.0 + s.lo;
831#endif
832 }
833
834 static long sampleSize(ASIOSampleType type)
835 {
836 switch(type)
837 {
838 case ASIOSTInt16MSB:
839 case ASIOSTInt16LSB:
840 return 2;
841 case ASIOSTInt24MSB:
842 case ASIOSTInt24LSB:
843 return 3;
844 case ASIOSTInt32MSB:
845 case ASIOSTInt32LSB:
846 case ASIOSTFloat32MSB:
847 case ASIOSTFloat32LSB:
848 case ASIOSTInt32MSB16:
849 case ASIOSTInt32MSB18:
850 case ASIOSTInt32MSB20:
851 case ASIOSTInt32MSB24:
852 case ASIOSTInt32LSB16:
853 case ASIOSTInt32LSB18:
854 case ASIOSTInt32LSB20:
855 case ASIOSTInt32LSB24:
856 return 4;
857 case ASIOSTFloat64MSB:
858 case ASIOSTFloat64LSB:
859 return 8;
860 default:
861 return 4;
862 }
863 }
864
865 std::vector<ASIOBufferInfo> m_bufferInfos;
866 std::vector<ASIOChannelInfo> m_channelInfos;
867 ASIOCallbacks m_asioCallbacks{};
868
869 // Float conversion buffers
870 std::vector<ossia::float_vector> m_floatInputs;
871 std::vector<ossia::float_vector> m_floatOutputs;
872 ossia::pod_vector<float*> m_inputPtrs;
873 ossia::pod_vector<float*> m_outputPtrs;
874
875 std::string m_driver_name;
876
877 int m_bufferSize{};
878 int m_inputCount{};
879 int m_outputCount{};
880 bool m_postOutput{};
881 bool m_initialized{};
882 bool m_buffersCreated{};
883 bool m_started{};
884};
885}
886
887#endif
Definition git_info.h:7
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:120