Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
engine.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "engine.hh"
3#include "atquit.hh"
4#include "processor.hh"
5#include "utils.hh"
6#include "loop.hh"
7#include "driver.hh"
8#include "server.hh"
9#include "datautils.hh"
10#include "atomics.hh"
11#include "project.hh"
12#include "wave.hh"
13#include "main.hh" // main_loop_autostop_mt
14#include "strings.hh"
15#include "memory.hh"
16#include "internal.hh"
17
18#define EDEBUG(...) Ase::debug ("engine", __VA_ARGS__)
19
20namespace Ase {
21
22constexpr const uint FIXED_N_CHANNELS = 2;
23constexpr const uint FIXED_SAMPLE_RATE = 48000;
24constexpr const uint FIXED_N_MIDI_DRIVERS = 4;
25
26// == decls ==
27using VoidFunc = std::function<void()>;
28using StartQueue = AsyncBlockingQueue<char>;
29ASE_CLASS_DECLS (EngineMidiInput);
30static void apply_driver_preferences ();
31
32// == EngineJobImpl ==
34 VoidFunc func;
35 std::atomic<EngineJobImpl*> next = nullptr;
36 explicit EngineJobImpl (const VoidFunc &jobfunc) : func (jobfunc) {}
37};
38static inline std::atomic<EngineJobImpl*>&
39atomic_next_ptrref (EngineJobImpl *j)
40{
41 return j->next;
42}
43
44struct DriverSet {
45 PcmDriverP null_pcm_driver;
46 String pcm_name;
47 PcmDriverP pcm_driver;
48 StringS midi_names;
49 MidiDriverS midi_drivers;
50};
51
52// == AudioEngineThread ==
54public:
55 static constexpr uint fixed_n_channels = 2;
56 PcmDriverP null_pcm_driver_, pcm_driver_;
57 constexpr static size_t MAX_BUFFER_SIZE = AUDIO_BLOCK_MAX_RENDER_SIZE;
58 std::atomic<uint64_t> buffer_size_ = MAX_BUFFER_SIZE; // mono buffer size
59 float chbuffer_data_[MAX_BUFFER_SIZE * fixed_n_channels] = { 0, };
60 uint64 write_stamp_ = 0;
62 EngineMidiInputP midi_proc_;
63 bool schedule_invalid_ = true;
64 bool output_needsrunning_ = false;
65 AtomicIntrusiveStack<EngineJobImpl> async_jobs_, const_jobs_, trash_jobs_;
66 const VoidF owner_wakeup_;
67 std::thread *thread_ = nullptr;
68 MainLoopP event_loop_ = MainLoop::create();
69 AudioProcessorS oprocs_;
70 ProjectImplP project_;
71 WaveWriterP wwriter_;
72 FastMemory::Block transport_block_;
73 DriverSet driver_set_ml; // accessed by main_loop thread
74 std::atomic<uint64> autostop_ = U64MAX;
75 struct UserNoteJob {
76 std::atomic<UserNoteJob*> next = nullptr;
77 UserNote note;
78 };
80public:
81 virtual ~AudioEngineThread ();
83 void schedule_clear ();
84 void schedule_add (AudioProcessor &aproc, uint level);
85 void schedule_queue_update ();
86 void schedule_render (uint64 frames);
87 void enable_output (AudioProcessor &aproc, bool onoff);
88 void wakeup_thread_mt ();
89 void capture_start (const String &filename, bool needsrunning);
90 void capture_stop ();
91 bool ipc_pending ();
92 void ipc_dispatch ();
93 AudioProcessorP get_event_source ();
94 void add_job_mt (EngineJobImpl *aejob, const AudioEngine::JobQueue *jobqueue);
95 bool pcm_check_write (bool write_buffer, int64 *timeout_usecs_p = nullptr);
96 bool driver_dispatcher (const LoopState &state);
97 bool process_jobs (AtomicIntrusiveStack<EngineJobImpl> &joblist);
98 void run (StartQueue *sq);
99 void queue_user_note (const String &channel, UserNote::Flags flags, const String &text);
100 void set_project (ProjectImplP project);
101 ProjectImplP get_project ();
102 void update_driver_set (DriverSet &dset);
103 void start_threads_ml ();
104 void stop_threads_ml ();
105 void create_processors_ml ();
106 String engine_stats_string (uint64_t stats) const;
107};
108
109static std::thread::id audio_engine_thread_id = {};
110const ThreadId &AudioEngine::thread_id = audio_engine_thread_id;
111
113atomic_next_ptrref (AudioEngineThread::UserNoteJob *j)
114{
115 return j->next;
116}
117
118template<int ADDING> static void
119interleaved_stereo (const size_t n_frames, float *buffer, AudioProcessor &proc, OBusId obus)
120{
121 if (proc.n_ochannels (obus) >= 2)
122 {
123 const float *src0 = proc.ofloats (obus, 0);
124 const float *src1 = proc.ofloats (obus, 1);
125 float *d = buffer, *const b = d + n_frames;
126 do {
127 if_constexpr (ADDING == 0)
128 {
129 *d++ = *src0++;
130 *d++ = *src1++;
131 }
132 else
133 {
134 *d++ += *src0++;
135 *d++ += *src1++;
136 }
137 } while (d < b);
138 }
139 else if (proc.n_ochannels (obus) >= 1)
140 {
141 const float *src = proc.ofloats (obus, 0);
142 float *d = buffer, *const b = d + n_frames;
143 do {
144 if_constexpr (ADDING == 0)
145 {
146 *d++ = *src;
147 *d++ = *src++;
148 }
149 else
150 {
151 *d++ += *src;
152 *d++ += *src++;
153 }
154 } while (d < b);
155 }
156}
157
158void
159AudioEngineThread::schedule_queue_update()
160{
161 schedule_invalid_ = true;
162}
163
164void
165AudioEngineThread::schedule_clear()
166{
167 while (schedule_.size() != 0)
168 {
169 AudioProcessor *cur = schedule_.back();
170 schedule_.pop_back();
171 while (cur)
172 {
173 AudioProcessor *const proc = cur;
174 cur = proc->sched_next_;
175 proc->flags_ &= ~AudioProcessor::SCHEDULED;
176 proc->sched_next_ = nullptr;
177 }
178 }
179 schedule_invalid_ = true;
180}
181
182void
183AudioEngineThread::schedule_add (AudioProcessor &aproc, uint level)
184{
185 return_unless (0 == (aproc.flags_ & AudioProcessor::SCHEDULED));
186 assert_return (aproc.sched_next_ == nullptr);
187 if (schedule_.size() <= level)
188 schedule_.resize (level + 1);
189 aproc.sched_next_ = schedule_[level];
190 schedule_[level] = &aproc;
191 aproc.flags_ |= AudioProcessor::SCHEDULED;
192 if (aproc.render_stamp_ != render_stamp_)
193 aproc.reset_state (render_stamp_);
194}
195
196void
197AudioEngineThread::schedule_render (uint64 frames)
198{
199 assert_return (0 == (frames & (8 - 1)));
200 // render scheduled AudioProcessor nodes
201 const uint64 target_stamp = render_stamp_ + frames;
202 for (size_t l = 0; l < schedule_.size(); l++)
203 {
204 AudioProcessor *proc = schedule_[l];
205 while (proc)
206 {
207 proc->render_block (target_stamp);
208 proc = proc->sched_next_;
209 }
210 }
211 // render output buffer interleaved
212 constexpr auto MAIN_OBUS = OBusId (1);
213 size_t n = 0;
214 for (size_t i = 0; i < oprocs_.size(); i++)
215 if (oprocs_[i]->n_obuses())
216 {
217 if (n++ == 0)
218 interleaved_stereo<0> (buffer_size_ * fixed_n_channels, chbuffer_data_, *oprocs_[i], MAIN_OBUS);
219 else
220 interleaved_stereo<1> (buffer_size_ * fixed_n_channels, chbuffer_data_, *oprocs_[i], MAIN_OBUS);
221 static_assert (2 == fixed_n_channels);
222 }
223 if (n == 0)
224 floatfill (chbuffer_data_, 0.0, buffer_size_ * fixed_n_channels);
225 render_stamp_ = target_stamp;
226 transport_.advance (frames);
227}
228
229void
230AudioEngineThread::enable_output (AudioProcessor &aproc, bool onoff)
231{
232 AudioProcessorP procp = shared_ptr_cast<AudioProcessor> (&aproc);
233 assert_return (procp != nullptr);
234 if (onoff && !(aproc.flags_ & AudioProcessor::ENGINE_OUTPUT))
235 {
236 oprocs_.push_back (procp);
237 aproc.flags_ |= AudioProcessor::ENGINE_OUTPUT;
238 schedule_queue_update();
239 }
240 else if (!onoff && (aproc.flags_ & AudioProcessor::ENGINE_OUTPUT))
241 {
242 const bool foundproc = Aux::erase_first (oprocs_, [procp] (AudioProcessorP c) { return c == procp; });
243 aproc.flags_ &= ~AudioProcessor::ENGINE_OUTPUT;
244 schedule_queue_update();
245 assert_return (foundproc);
246 }
247}
248
249void
250AudioEngineThread::capture_start (const String &filename, bool needsrunning)
251{
252 const uint sample_rate = transport_.samplerate;
253 capture_stop();
254 output_needsrunning_ = needsrunning;
255 if (string_endswith (filename, ".wav"))
256 {
257 wwriter_ = wave_writer_create_wav (sample_rate, fixed_n_channels, filename);
258 if (!wwriter_)
259 printerr ("%s: failed to open file: %s\n", filename, strerror (errno));
260 }
261 else if (string_endswith (filename, ".opus"))
262 {
263 wwriter_ = wave_writer_create_opus (sample_rate, fixed_n_channels, filename);
264 if (!wwriter_)
265 printerr ("%s: failed to open file: %s\n", filename, strerror (errno));
266 }
267 else if (string_endswith (filename, ".flac"))
268 {
269 wwriter_ = wave_writer_create_flac (sample_rate, fixed_n_channels, filename);
270 if (!wwriter_)
271 printerr ("%s: failed to open file: %s\n", filename, strerror (errno));
272 }
273 else if (!filename.empty())
274 printerr ("%s: unknown sample file: %s\n", filename, strerror (ENOSYS));
275}
276
277void
278AudioEngineThread::capture_stop()
279{
280 if (wwriter_)
281 {
282 wwriter_->close();
283 wwriter_ = nullptr;
284 }
285}
286
287void
288AudioEngineThread::run (StartQueue *sq)
289{
290 assert_return (null_pcm_driver_);
291 assert_return (owner_wakeup_ != nullptr);
292 if (!pcm_driver_)
293 pcm_driver_ = null_pcm_driver_;
294 floatfill (chbuffer_data_, 0.0, MAX_BUFFER_SIZE * fixed_n_channels);
295 buffer_size_ = std::min (MAX_BUFFER_SIZE, size_t (pcm_driver_->pcm_block_length()));
296 write_stamp_ = render_stamp_ - buffer_size_; // write an initial buffer of zeros
297 this_thread_set_name ("AudioEngine-0"); // max 16 chars
298 audio_engine_thread_id = std::this_thread::get_id();
299 sched_fast_priority (this_thread_gettid());
300 event_loop_->exec_dispatcher (std::bind (&AudioEngineThread::driver_dispatcher, this, std::placeholders::_1));
301 sq->push ('R'); // StartQueue becomes invalid after this call
302 sq = nullptr;
303 event_loop_->run();
304 // TODO: do we need to cleanup / throw away job lists here?
305}
306
307bool
308AudioEngineThread::process_jobs (AtomicIntrusiveStack<EngineJobImpl> &joblist)
309{
310 EngineJobImpl *const jobs = joblist.pop_reversed(), *last = nullptr;
311 for (EngineJobImpl *job = jobs; job; last = job, job = job->next)
312 job->func();
313 if (last)
314 {
315 if (trash_jobs_.push_chain (jobs, last))
316 owner_wakeup_();
317 }
318 return last != nullptr;
319}
320
321bool
322AudioEngineThread::pcm_check_write (bool write_buffer, int64 *timeout_usecs_p)
323{
324 int64 timeout_usecs = INT64_MAX;
325 const bool can_write = pcm_driver_->pcm_check_io (&timeout_usecs) || timeout_usecs == 0;
326 if (timeout_usecs_p)
327 *timeout_usecs_p = timeout_usecs;
328 if (!write_buffer)
329 return can_write;
330 if (!can_write || write_stamp_ >= render_stamp_)
331 return false;
332 pcm_driver_->pcm_write (buffer_size_ * fixed_n_channels, chbuffer_data_);
333 if (wwriter_ && fixed_n_channels == 2 && write_stamp_ < autostop_ &&
334 (!output_needsrunning_ || transport_.running()))
335 wwriter_->write (chbuffer_data_, buffer_size_);
336 write_stamp_ += buffer_size_;
337 if (write_stamp_ >= autostop_)
338 main_loop_autostop_mt();
339 assert_warn (write_stamp_ == render_stamp_);
340 return false;
341}
342
343bool
344AudioEngineThread::driver_dispatcher (const LoopState &state)
345{
346 int64 *timeout_usecs = nullptr;
347 switch (state.phase)
348 {
349 case LoopState::PREPARE:
350 timeout_usecs = const_cast<int64*> (&state.timeout_usecs);
351 /* fall-through */
352 case LoopState::CHECK:
353 if (atquit_triggered())
354 return false; // stall engine once program is aborted
355 if (!const_jobs_.empty() || !async_jobs_.empty())
356 return true; // jobs pending
357 if (render_stamp_ <= write_stamp_)
358 return true; // must render
359 return pcm_check_write (false, timeout_usecs);
360 case LoopState::DISPATCH:
361 pcm_check_write (true);
362 if (render_stamp_ <= write_stamp_)
363 {
364 process_jobs (async_jobs_); // apply pending modifications before render
365 if (schedule_invalid_)
366 {
367 schedule_clear();
368 for (AudioProcessorP &proc : oprocs_)
369 proc->schedule_processor();
370 schedule_invalid_ = false;
371 }
372 if (render_stamp_ <= write_stamp_) // async jobs may have adjusted stamps
373 schedule_render (buffer_size_);
374 pcm_check_write (true); // minimize drop outs
375 }
376 if (!const_jobs_.empty()) { // owner may be blocking for const_jobs_ execution
377 process_jobs (async_jobs_); // apply pending modifications first
378 process_jobs (const_jobs_);
379 }
380 if (ipc_pending())
381 owner_wakeup_(); // owner needs to ipc_dispatch()
382 return true; // keep alive
383 default: ;
384 }
385 return false;
386}
387
388void
389AudioEngineThread::queue_user_note (const String &channel, UserNote::Flags flags, const String &text)
390{
391 UserNoteJob *uj = new UserNoteJob { nullptr, { 0, flags, channel, text } };
392 if (user_notes_.push (uj))
393 owner_wakeup_();
394}
395
396bool
397AudioEngineThread::ipc_pending ()
398{
399 const bool have_jobs = !trash_jobs_.empty() || !user_notes_.empty();
400 return have_jobs || AudioProcessor::enotify_pending();
401}
402
403void
404AudioEngineThread::ipc_dispatch ()
405{
406 UserNoteJob *uj = user_notes_.pop_reversed();
407 while (uj)
408 {
409 ASE_SERVER.user_note (uj->note.text, uj->note.channel, uj->note.flags);
410 UserNoteJob *const old = uj;
411 uj = old->next;
412 delete old;
413 }
414 if (AudioProcessor::enotify_pending())
415 AudioProcessor::enotify_dispatch();
416 EngineJobImpl *job = trash_jobs_.pop_all();
417 while (job)
418 {
419 EngineJobImpl *old = job;
420 job = job->next;
421 delete old;
422 }
423}
424
425void
426AudioEngineThread::wakeup_thread_mt()
427{
428 assert_return (event_loop_);
429 event_loop_->wakeup();
430}
431
432void
433AudioEngineThread::start_threads_ml()
434{
435 assert_return (this_thread_is_ase()); // main_loop thread
436 assert_return (thread_ == nullptr);
437 assert_return (midi_proc_ == nullptr);
438 schedule_.reserve (8192);
439 create_processors_ml();
440 update_drivers ("null", 0, {}); // create drivers
441 null_pcm_driver_ = driver_set_ml.null_pcm_driver;
442 schedule_queue_update();
443 StartQueue start_queue;
444 thread_ = new std::thread (&AudioEngineThread::run, this, &start_queue);
445 const char reply = start_queue.pop(); // synchronize with thread start
446 assert_return (reply == 'R');
447 apply_driver_preferences();
448}
449
450void
451AudioEngineThread::stop_threads_ml()
452{
453 assert_return (this_thread_is_ase()); // main_loop thread
454 assert_return (thread_ != nullptr);
455 event_loop_->quit (0);
456 thread_->join();
457 audio_engine_thread_id = {};
458 auto oldthread = thread_;
459 thread_ = nullptr;
460 delete oldthread;
461}
462
463void
464AudioEngineThread::add_job_mt (EngineJobImpl *job, const AudioEngine::JobQueue *jobqueue)
465{
466 assert_return (job != nullptr);
467 AudioEngineThread &engine = *dynamic_cast<AudioEngineThread*> (this);
468 // engine not running, run job right away
469 if (!engine.thread_)
470 {
471 job->func();
472 delete job;
473 return;
474 }
475 // enqueue async_jobs
476 if (jobqueue == &async_jobs) // non-blocking, via async_jobs_ queue
477 { // run asynchronously
478 const bool was_empty = engine.async_jobs_.push (job);
479 if (was_empty)
480 wakeup_thread_mt();
481 return;
482 }
483 // blocking jobs, queue wrapper that synchronizes via Semaphore
484 ScopedSemaphore sem;
485 VoidFunc jobfunc = job->func;
486 std::function<void()> wrapper = [&sem, jobfunc] () {
487 jobfunc();
488 sem.post();
489 };
490 job->func = wrapper;
491 bool need_wakeup;
492 if (jobqueue == &const_jobs) // blocking, via const_jobs_ queue
493 need_wakeup = engine.const_jobs_.push (job);
494 else if (jobqueue == &synchronized_jobs) // blocking, via async_jobs_ queue
495 need_wakeup = engine.async_jobs_.push (job);
496 else
498 if (need_wakeup)
499 wakeup_thread_mt();
500 sem.wait();
501}
502
503void
504AudioEngineThread::set_project (ProjectImplP project)
505{
506 if (project)
507 {
508 assert_return (project_ == nullptr);
509 assert_return (!project->is_active());
510 }
511 if (project_)
512 project_->_deactivate();
513 const ProjectImplP old = project_;
514 project_ = project;
515 if (project_)
516 project_->_activate();
517 // dtor of old runs here
518}
519
520String
521AudioEngineThread::engine_stats_string (uint64_t stats) const
522{
523 String s;
524 for (size_t i = 0; i < oprocs_.size(); i++) {
525 AudioProcessorInfo pinfo;
526 pinfo.label = "INTERNAL";
527 AudioProcessor::registry_foreach ([&] (const String &aseid, AudioProcessor::StaticInfo static_info) {
528 if (aseid == oprocs_[i]->aseid_)
529 static_info (pinfo); // TODO: this is a bit awkward to fetch AudioProcessorInfo for an AudioProcessor
530 });
531 s += string_format ("%s: %s (MUST_SCHEDULE)\n", pinfo.label, oprocs_[i]->debug_name());
532 }
533 return s;
534}
535
536ProjectImplP
537AudioEngineThread::get_project ()
538{
539 return project_;
540}
541
542AudioEngineThread::~AudioEngineThread ()
543{
544 FastMemory::Block transport_block = transport_block_; // keep alive until after ~AudioEngine
545 main_jobs += [transport_block] () { ServerImpl::instancep()->telemem_release (transport_block); };
546}
547
548AudioEngineThread::AudioEngineThread (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement,
549 const FastMemory::Block &transport_block) :
550 AudioEngine (*this, *new (transport_block.block_start) AudioTransport (speakerarrangement, sample_rate)),
551 owner_wakeup_ (owner_wakeup), transport_block_ (transport_block)
552{
553 render_stamp_ = MAX_BUFFER_SIZE; // enforce non-0 start offset for all modules
554 oprocs_.reserve (16);
555 assert_return (transport_.samplerate == 48000);
556}
557
558AudioEngine&
559make_audio_engine (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement)
560{
561 ASE_ASSERT_WARN (sample_rate == FIXED_SAMPLE_RATE);
562 ASE_ASSERT_WARN (speaker_arrangement_count_channels (speakerarrangement) == FIXED_N_CHANNELS);
563 FastMemory::Block transport_block = ServerImpl::instancep()->telemem_allocate (sizeof (AudioTransport));
564 return *new AudioEngineThread (owner_wakeup, sample_rate, speakerarrangement, transport_block);
565}
566
567// == AudioEngine ==
568AudioEngine::AudioEngine (AudioEngineThread &audio_engine_thread, AudioTransport &transport) :
569 transport_ (transport), async_jobs (audio_engine_thread), const_jobs (audio_engine_thread), synchronized_jobs (audio_engine_thread)
570{}
571
572AudioEngine::~AudioEngine()
573{
574 // some ref-counted objects keep AudioEngine& members around
575 fatal_error ("AudioEngine must not be destroyed");
576}
577
578String
579AudioEngine::engine_stats (uint64_t stats) const
580{
581 String strstats;
582 const AudioEngineThread &engine_thread = static_cast<const AudioEngineThread&> (*this);
583 const_cast<AudioEngine*> (this)->synchronized_jobs += [&] () { strstats = engine_thread.engine_stats_string (stats); };
584 return strstats;
585}
586
587uint64
588AudioEngine::block_size() const
589{
590 const AudioEngineThread &impl = static_cast<const AudioEngineThread&> (*this);
591 return impl.buffer_size_;
592}
593
594void
595AudioEngine::set_autostop (uint64_t nsamples)
596{
597 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
598 impl.autostop_ = nsamples;
599}
600
601void
602AudioEngine::schedule_queue_update()
603{
604 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
605 impl.schedule_queue_update();
606}
607
608void
609AudioEngine::schedule_add (AudioProcessor &aproc, uint level)
610{
611 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
612 impl.schedule_add (aproc, level);
613}
614
615void
616AudioEngine::enable_output (AudioProcessor &aproc, bool onoff)
617{
618 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
619 return impl.enable_output (aproc, onoff);
620}
621
622void
623AudioEngine::start_threads()
624{
625 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
626 return impl.start_threads_ml();
627}
628
629void
630AudioEngine::stop_threads()
631{
632 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
633 return impl.stop_threads_ml();
634}
635
636void
637AudioEngine::queue_capture_start (CallbackS &callbacks, const String &filename, bool needsrunning)
638{
639 AudioEngineThread *impl = static_cast<AudioEngineThread*> (this);
640 String file = filename;
641 callbacks.push_back ([impl,file,needsrunning] () {
642 impl->capture_start (file, needsrunning);
643 });
644}
645
646void
647AudioEngine::queue_capture_stop (CallbackS &callbacks)
648{
649 AudioEngineThread *impl = static_cast<AudioEngineThread*> (this);
650 callbacks.push_back ([impl] () {
651 impl->capture_stop();
652 });
653}
654
655void
656AudioEngine::wakeup_thread_mt ()
657{
658 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
659 return impl.wakeup_thread_mt();
660}
661
662bool
663AudioEngine::ipc_pending ()
664{
665 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
666 return impl.ipc_pending();
667}
668
669void
670AudioEngine::ipc_dispatch ()
671{
672 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
673 return impl.ipc_dispatch();
674}
675
676AudioProcessorP
677AudioEngine::get_event_source ()
678{
679 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
680 return impl.get_event_source();
681}
682
683void
684AudioEngine::set_project (ProjectImplP project)
685{
686 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
687 return impl.set_project (project);
688}
689
690ProjectImplP
691AudioEngine::get_project ()
692{
693 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
694 return impl.get_project();
695}
696
697AudioEngine::JobQueue::JobQueue (AudioEngine &aet) :
698 queue_tag_ (ptrdiff_t (this) - ptrdiff_t (&aet))
699{
700 assert_return (ptrdiff_t (this) < 256 + ptrdiff_t (&aet));
701}
702
703void
704AudioEngine::JobQueue::operator+= (const std::function<void()> &job)
705{
706 AudioEngine *audio_engine = reinterpret_cast<AudioEngine*> (ptrdiff_t (this) - queue_tag_);
707 AudioEngineThread &audio_engine_thread = static_cast<AudioEngineThread&> (*audio_engine);
708 return audio_engine_thread.add_job_mt (new EngineJobImpl (job), this);
709}
710
711bool
712AudioEngine::update_drivers (const String &pcm_name, uint latency_ms, const StringS &midi_prefs)
713{
714 AudioEngineThread &engine_thread = static_cast<AudioEngineThread&> (*this);
715 DriverSet &dset = engine_thread.driver_set_ml;
716 const char *const null_driver = "null";
717 int must_update = 0;
718 // PCM Config
719 const PcmDriverConfig pcm_config { .n_channels = engine_thread.fixed_n_channels, .mix_freq = FIXED_SAMPLE_RATE,
720 .block_length = AUDIO_BLOCK_MAX_RENDER_SIZE, .latency_ms = latency_ms };
721 // PCM Fallback
722 if (!dset.null_pcm_driver) {
723 must_update++;
724 Error er = {};
725 dset.null_pcm_driver = PcmDriver::open (null_driver, Driver::WRITEONLY, Driver::WRITEONLY, pcm_config, &er);
726 if (!dset.null_pcm_driver || er != 0)
727 fatal_error ("failed to open internal PCM driver ('%s'): %s", null_driver, ase_error_blurb (er));
728 }
729 // PCM Driver
730 if (pcm_name != dset.pcm_name) {
731 must_update++;
732 dset.pcm_name = pcm_name;
733 Error er = {};
734 dset.pcm_driver = dset.pcm_name == null_driver ? dset.null_pcm_driver :
735 PcmDriver::open (dset.pcm_name, Driver::WRITEONLY, Driver::WRITEONLY, pcm_config, &er);
736 if (!dset.pcm_driver || er != 0) {
737 dset.pcm_driver = dset.null_pcm_driver;
738 log ("Audio Driver: Failed to open audio device: %s: %s", dset.pcm_name, ase_error_blurb (er));
739 const String errmsg = string_format ("# Audio I/O Error\n" "Failed to open audio device:\n" "%s:\n" "%s",
740 dset.pcm_name, ase_error_blurb (er));
741 engine_thread.queue_user_note ("driver.pcm", UserNote::CLEAR, errmsg);
742 printerr ("%s\n", string_replace (errmsg, "\n", " "));
743 }
744 }
745 // Deduplicate MIDI Drivers
746 StringS midis = midi_prefs;
747 midis.resize (FIXED_N_MIDI_DRIVERS);
748 for (size_t i = 0; i < midis.size(); i++)
749 if (midis[i].empty())
750 midis[i] = null_driver;
751 else
752 for (size_t j = 0; j < i; j++) // dedup
753 if (midis[i] != null_driver && midis[i] == midis[j]) {
754 midis[i] = null_driver;
755 break;
756 }
757 // MIDI Drivers
758 dset.midi_names.resize (midis.size());
759 dset.midi_drivers.resize (dset.midi_names.size());
760 for (size_t i = 0; i < dset.midi_drivers.size(); i++) {
761 if (midis[i] == dset.midi_names[i])
762 continue;
763 must_update++;
764 dset.midi_names[i] = midis[i];
765 Error er = {};
766 dset.midi_drivers[i] = dset.midi_names[i] == null_driver ? nullptr :
767 MidiDriver::open (dset.midi_names[i], Driver::READONLY, &er);
768 if (er != 0) {
769 dset.midi_drivers[i] = nullptr;
770 const String errmsg = string_format ("# MIDI I/O Error\n" "Failed to open MIDI device #%u:\n" "%s:\n" "%s",
771 1 + i, dset.midi_names[i], ase_error_blurb (er));
772 engine_thread.queue_user_note ("driver.midi", UserNote::CLEAR, errmsg);
773 printerr ("%s\n", string_replace (errmsg, "\n", " "));
774 }
775 }
776 // Update running engine
777 if (must_update) {
778 Mutable<DriverSet> mdset = dset; // use Mutable so Job can swap and the remains are cleaned up in ~Job
779 synchronized_jobs += [mdset,&engine_thread] () { engine_thread.update_driver_set (mdset.value); };
780 return true;
781 }
782 return false;
783}
784
785// == EngineMidiInput ==
787 // Processor providing MIDI device events
788 void
789 initialize (SpeakerArrangement busses) override
790 {
791 prepare_event_output();
792 }
793 void
794 reset (uint64 target_stamp) override
795 {
796 MidiEventOutput &estream = midi_event_output();
797 estream.clear();
798 estream.reserve (256);
799 }
800 void
801 render (uint n_frames) override
802 {
803 MidiEventOutput &estream = midi_event_output();
804 estream.clear();
805 for (size_t i = 0; i < midi_drivers_.size(); i++)
806 if (midi_drivers_[i])
807 midi_drivers_[i]->fetch_events (estream, sample_rate());
808 }
809public:
810 MidiDriverS midi_drivers_;
811 EngineMidiInput (const ProcessorSetup &psetup) :
812 AudioProcessor (psetup)
813 {}
814};
815
816void
817AudioEngineThread::create_processors_ml ()
818{
819 assert_return (this_thread_is_ase()); // main_loop thread
820 assert_return (midi_proc_ == nullptr);
821 AudioProcessorP aprocp = AudioProcessor::create_processor<EngineMidiInput> (*this);
822 assert_return (aprocp);
823 midi_proc_ = std::dynamic_pointer_cast<EngineMidiInput> (aprocp);
824 assert_return (midi_proc_);
825 EngineMidiInputP midi_proc = midi_proc_;
826 async_jobs += [midi_proc] () {
827 midi_proc->enable_engine_output (true); // MUST_SCHEDULE
828 };
829}
830
831AudioProcessorP
832AudioEngineThread::get_event_source ()
833{
834 return midi_proc_;
835}
836
837void
838AudioEngineThread::update_driver_set (DriverSet &dset)
839{
840 // use swap() to defer dtor to user thread
841 assert_return (midi_proc_);
842 // PCM Driver
843 if (pcm_driver_ != dset.pcm_driver) {
844 pcm_driver_.swap (dset.pcm_driver);
845 floatfill (chbuffer_data_, 0.0, MAX_BUFFER_SIZE * fixed_n_channels);
846 buffer_size_ = std::min (MAX_BUFFER_SIZE, size_t (pcm_driver_->pcm_block_length()));
847 write_stamp_ = render_stamp_ - buffer_size_; // write an initial buffer of zeros
848 EDEBUG ("AudioEngineThread::%s: update PCM to \"%s\": channels=%d pcmblock=%d enginebuffer=%d ws=%u rs=%u bs=%u\n", __func__,
849 dset.pcm_name, fixed_n_channels, pcm_driver_->pcm_block_length(), buffer_size_, write_stamp_, render_stamp_, buffer_size_);
850 }
851 // MIDI Drivers
852 if (midi_proc_->midi_drivers_ != dset.midi_drivers) {
853 midi_proc_->midi_drivers_.swap (dset.midi_drivers);
854 EDEBUG ("AudioEngineThread::%s: swapping %u MIDI drivers: \"%s\"\n", __func__, midi_proc_->midi_drivers_.size(), string_join ("\" \"", dset.midi_names));
855 }
856}
857
858// == DriverSet ==
859static Choice
860choice_from_driver_entry (const DriverEntry &e, const String &icon_keywords)
861{
862 String blurb;
863 if (!e.device_info.empty() && !e.capabilities.empty())
864 blurb = e.capabilities + "\n" + e.device_info;
865 else if (!e.capabilities.empty())
866 blurb = e.capabilities;
867 else
868 blurb = e.device_info;
869 Choice c (e.devid, e.device_name, blurb);
870 if (string_startswith (string_tolower (e.notice), "warn"))
871 c.warning = e.notice;
872 else
873 c.notice = e.notice;
874 // e.priority
875 // e.readonly
876 // e.writeonly
877 // e.modem
878 c.icon = MakeIcon::KwIcon (icon_keywords + "," + e.hints);
879 return c;
880}
881
882static ChoiceS
883pcm_driver_pref_list_choices (const CString &ident)
884{
885 static ChoiceS choices;
886 static uint64 cache_age = 0;
887 if (choices.empty() || timestamp_realtime() > cache_age + 500 * 1000) {
888 choices.clear();
889 for (const DriverEntry &e : PcmDriver::list_drivers())
890 choices.push_back (choice_from_driver_entry (e, "pcm"));
891 cache_age = timestamp_realtime();
892 }
893 return choices;
894}
895
896static ChoiceS
897midi_driver_pref_list_choices (const CString &ident)
898{
899 static ChoiceS choices;
900 static uint64 cache_age = 0;
901 if (choices.empty() || timestamp_realtime() > cache_age + 500 * 1000) {
902 choices.clear();
903 for (const DriverEntry &e : MidiDriver::list_drivers())
904 if (!e.writeonly)
905 choices.push_back (choice_from_driver_entry (e, "midi"));
906 cache_age = timestamp_realtime();
907 }
908 return choices;
909}
910
911static Preference pcm_driver_pref =
912 Preference ({
913 "driver.pcm.devid", _("PCM Driver"), "", "auto", "ms",
914 { pcm_driver_pref_list_choices }, STANDARD, {
915 String ("descr=") + _("Driver and device to be used for PCM input and output"), } },
916 [] (const CString&,const Value&) { apply_driver_preferences(); });
917
918static Preference synth_latency_pref =
919 Preference ({
920 "driver.pcm.synth_latency", _("Synth Latency"), "", 15, "ms",
921 MinMaxStep { 0, 3000, 5 }, STANDARD + String ("step=5"), {
922 String ("descr=") + _("Processing duration between input and output of a single sample, smaller values increase CPU load"), } },
923 [] (const CString&,const Value&) { apply_driver_preferences(); });
924
925static Preference midi1_driver_pref =
926 Preference ({
927 "driver.midi1.devid", _("MIDI Controller (1)"), "", "auto", "ms",
928 { midi_driver_pref_list_choices }, STANDARD, {
929 String ("descr=") + _("MIDI controller device to be used for MIDI input"), } },
930 [] (const CString&,const Value&) { apply_driver_preferences(); });
931static Preference midi2_driver_pref =
932 Preference ({
933 "driver.midi2.devid", _("MIDI Controller (2)"), "", "auto", "ms",
934 { midi_driver_pref_list_choices }, STANDARD, {
935 String ("descr=") + _("MIDI controller device to be used for MIDI input"), } },
936 [] (const CString&,const Value&) { apply_driver_preferences(); });
937static Preference midi3_driver_pref =
938 Preference ({
939 "driver.midi3.devid", _("MIDI Controller (3)"), "", "auto", "ms",
940 { midi_driver_pref_list_choices }, STANDARD, {
941 String ("descr=") + _("MIDI controller device to be used for MIDI input"), } },
942 [] (const CString&,const Value&) { apply_driver_preferences(); });
943static Preference midi4_driver_pref =
944 Preference ({
945 "driver.midi4.devid", _("MIDI Controller (4)"), "", "auto", "ms",
946 { midi_driver_pref_list_choices }, STANDARD, {
947 String ("descr=") + _("MIDI controller device to be used for MIDI input"), } },
948 [] (const CString&,const Value&) { apply_driver_preferences(); });
949
950static void
951apply_driver_preferences ()
952{
953 static uint engine_driver_set_timerid = 0;
954 main_loop->exec_once (97, &engine_driver_set_timerid,
955 []() {
956 String pcm_driver = pcm_driver_pref.gets();
957 if (!App.pcm_override.empty())
958 pcm_driver = App.pcm_override;
959 StringS midis = { midi1_driver_pref.gets(), midi2_driver_pref.gets(), midi3_driver_pref.gets(), midi4_driver_pref.gets(), };
960 if (!App.midi_override.empty())
961 midis = { App.midi_override, "null", "null", "null", };
962 App.engine->update_drivers (pcm_driver, synth_latency_pref.getn(), midis);
963 });
964}
965
966} // Ase
T bind(T... args)
This is a thread-safe asyncronous queue which blocks in pop() until data is provided through push().
Definition platform.hh:118
JobQueue async_jobs
Executed asynchronously, may modify AudioProcessor objects.
Definition engine.hh:65
JobQueue const_jobs
Blocks during execution, must treat AudioProcessor objects read-only.
Definition engine.hh:66
Audio signal AudioProcessor base class, implemented by all effects and instruments.
Definition processor.hh:76
static void registry_foreach(const std::function< void(const String &aseid, StaticInfo)> &fun)
Iterate over the known AudioProcessor types.
Definition processor.cc:909
static MainLoopP create()
Create a MainLoop shared pointer handle.
Definition loop.cc:369
A stream of writable MidiEvent structures.
Definition midievent.hh:96
#define ASE_ASSERT_WARN(expr)
Issue an assertion warning if expr evaluates to false.
Definition cxxaux.hh:94
T empty(T... args)
T get_id(T... args)
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:71
#define assert_warn(expr)
Issue an assertion warning if expr evaluates to false.
Definition internal.hh:33
#define _(...)
Retrieve the translation of a C or C++ string.
Definition internal.hh:18
#define if_constexpr
Indentation helper for editors that cannot (yet) decipher if constexpr
Definition internal.hh:40
#define assert_unreached()
Explicitely mark unreachable code locations.
Definition internal.hh:37
T join(T... args)
log
T min(T... args)
size_t erase_first(C &container, const std::function< bool(typename C::value_type const &value)> &pred)
Erase first element for which pred() is true in vector or list.
Definition utils.hh:337
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:25
void floatfill(float *dst, float f, size_t n)
Fill n values of dst with f.
Definition datautils.hh:29
String string_join(const String &junctor, const StringS &strvec)
Definition strings.cc:452
SpeakerArrangement
Definition transport.hh:11
OBusId
ID type for AudioProcessor output buses, buses are numbered with increasing index.
Definition processor.hh:25
String string_tolower(const String &str)
Convert all string characters into Unicode lower case characters.
Definition strings.cc:136
std::tuple< double, double, double > MinMaxStep
Min, max, stepping for double ranges.
Definition parameter.hh:12
std::vector< String > StringS
Convenience alias for a std::vector<std::string>.
Definition cxxaux.hh:36
int64_t int64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:29
JobQueue main_jobs(call_main_loop)
Execute a job callback in the Ase main loop.
Definition main.hh:39
Error
Enum representing Error states.
Definition api.hh:22
const char * ase_error_blurb(Error error)
Describe Error condition.
Definition server.cc:227
String string_replace(const String &input, const String &marker, const String &replacement, size_t maxn)
Replace substring marker in input with replacement, at most maxn times.
Definition strings.cc:1144
bool sched_fast_priority(int tid)
Try to acquire low latency scheduling priority, returns true if nice level is < 0.
Definition platform.cc:939
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
constexpr const char STANDARD[]
STORAGE GUI READABLE WRITABLE.
Definition api.hh:14
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
bool string_endswith(const String &string, const String &fragment)
Returns whether string ends with fragment.
Definition strings.cc:863
uint64 timestamp_realtime()
Return the current time as uint64 in µseconds.
Definition platform.cc:578
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
Definition strings.cc:846
T push_back(T... args)
T resize(T... args)
#define INT64_MAX
const uint samplerate
Sample rate (mixing frequency) in Hz used for rendering.
Definition transport.hh:115
Reference for an allocated memory block.
Definition memory.hh:90
Contents of user interface notifications.
Definition api.hh:386
T swap(T... args)