Anklang-0.3.0.dev712+gdc4e642f anklang-0.3.0.dev712+gdc4e642f
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 LoopP event_loop_;
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_ = Loop::current();
301 event_loop_->exec_dispatcher (std::bind (&AudioEngineThread::driver_dispatcher, this, std::placeholders::_1));
302 sq->push ('R'); // StartQueue becomes invalid after this call
303 sq = nullptr;
304 event_loop_->run();
305 // TODO: do we need to cleanup / throw away job lists here?
306}
307
308bool
309AudioEngineThread::process_jobs (AtomicIntrusiveStack<EngineJobImpl> &joblist)
310{
311 EngineJobImpl *const jobs = joblist.pop_reversed(), *last = nullptr;
312 for (EngineJobImpl *job = jobs; job; last = job, job = job->next)
313 job->func();
314 if (last)
315 {
316 if (trash_jobs_.push_chain (jobs, last))
317 owner_wakeup_();
318 }
319 return last != nullptr;
320}
321
322bool
323AudioEngineThread::pcm_check_write (bool write_buffer, int64 *timeout_usecs_p)
324{
325 int64 timeout_usecs = INT64_MAX;
326 const bool can_write = pcm_driver_->pcm_check_io (&timeout_usecs) || timeout_usecs == 0;
327 if (timeout_usecs_p)
328 *timeout_usecs_p = timeout_usecs;
329 if (!write_buffer)
330 return can_write;
331 if (!can_write || write_stamp_ >= render_stamp_)
332 return false;
333 pcm_driver_->pcm_write (buffer_size_ * fixed_n_channels, chbuffer_data_);
334 if (wwriter_ && fixed_n_channels == 2 && write_stamp_ < autostop_ &&
335 (!output_needsrunning_ || transport_.running()))
336 wwriter_->write (chbuffer_data_, buffer_size_);
337 write_stamp_ += buffer_size_;
338 if (write_stamp_ >= autostop_)
340 assert_warn (write_stamp_ == render_stamp_);
341 return false;
342}
343
344bool
345AudioEngineThread::driver_dispatcher (const LoopState &state)
346{
347 int64 *timeout_usecs = nullptr;
348 switch (state.phase)
349 {
350 case LoopState::PREPARE:
351 timeout_usecs = const_cast<int64*> (&state.timeout_usecs);
352 /* fall-through */
353 case LoopState::CHECK:
354 if (atquit_triggered())
355 return false; // stall engine once program is aborted
356 if (!const_jobs_.empty() || !async_jobs_.empty())
357 return true; // jobs pending
358 if (render_stamp_ <= write_stamp_)
359 return true; // must render
360 return pcm_check_write (false, timeout_usecs);
361 case LoopState::DISPATCH:
362 pcm_check_write (true);
363 if (render_stamp_ <= write_stamp_)
364 {
365 process_jobs (async_jobs_); // apply pending modifications before render
366 if (schedule_invalid_)
367 {
368 schedule_clear();
369 for (AudioProcessorP &proc : oprocs_)
370 proc->schedule_processor();
371 schedule_invalid_ = false;
372 }
373 if (render_stamp_ <= write_stamp_) // async jobs may have adjusted stamps
374 schedule_render (buffer_size_);
375 pcm_check_write (true); // minimize drop outs
376 }
377 if (!const_jobs_.empty()) { // owner may be blocking for const_jobs_ execution
378 process_jobs (async_jobs_); // apply pending modifications first
379 process_jobs (const_jobs_);
380 }
381 if (ipc_pending())
382 owner_wakeup_(); // owner needs to ipc_dispatch()
383 return true; // keep alive
384 default: ;
385 }
386 return false;
387}
388
389void
390AudioEngineThread::queue_user_note (const String &channel, UserNote::Flags flags, const String &text)
391{
392 UserNoteJob *uj = new UserNoteJob { nullptr, { 0, flags, channel, text } };
393 if (user_notes_.push (uj))
394 owner_wakeup_();
395}
396
397bool
398AudioEngineThread::ipc_pending ()
399{
400 const bool have_jobs = !trash_jobs_.empty() || !user_notes_.empty();
401 return have_jobs || AudioProcessor::enotify_pending();
402}
403
404void
405AudioEngineThread::ipc_dispatch ()
406{
407 UserNoteJob *uj = user_notes_.pop_reversed();
408 while (uj)
409 {
410 ASE_SERVER.user_note (uj->note.text, uj->note.channel, uj->note.flags);
411 UserNoteJob *const old = uj;
412 uj = old->next;
413 delete old;
414 }
415 if (AudioProcessor::enotify_pending())
416 AudioProcessor::enotify_dispatch();
417 EngineJobImpl *job = trash_jobs_.pop_all();
418 while (job)
419 {
420 EngineJobImpl *old = job;
421 job = job->next;
422 delete old;
423 }
424}
425
426void
427AudioEngineThread::wakeup_thread_mt()
428{
429 assert_return (event_loop_);
430 event_loop_->wakeup();
431}
432
433void
434AudioEngineThread::start_threads_ml()
435{
436 assert_return (this_thread_is_ase()); // main_loop thread
437 assert_return (thread_ == nullptr);
438 assert_return (midi_proc_ == nullptr);
439 schedule_.reserve (8192);
440 create_processors_ml();
441 update_drivers ("null", 0, {}); // create drivers
442 null_pcm_driver_ = driver_set_ml.null_pcm_driver;
443 schedule_queue_update();
444 StartQueue start_queue;
445 thread_ = new std::thread (&AudioEngineThread::run, this, &start_queue);
446 const char reply = start_queue.pop(); // synchronize with thread start
447 assert_return (reply == 'R');
448 apply_driver_preferences();
449}
450
451void
452AudioEngineThread::stop_threads_ml()
453{
454 assert_return (this_thread_is_ase()); // main_loop thread
455 assert_return (thread_ != nullptr);
456 event_loop_->quit (0);
457 thread_->join();
458 audio_engine_thread_id = {};
459 auto oldthread = thread_;
460 thread_ = nullptr;
461 delete oldthread;
462}
463
464void
465AudioEngineThread::add_job_mt (EngineJobImpl *job, const AudioEngine::JobQueue *jobqueue)
466{
467 assert_return (job != nullptr);
468 AudioEngineThread &engine = *dynamic_cast<AudioEngineThread*> (this);
469 // engine not running, run job right away
470 if (!engine.thread_)
471 {
472 job->func();
473 delete job;
474 return;
475 }
476 // enqueue async_jobs
477 if (jobqueue == &async_jobs) // non-blocking, via async_jobs_ queue
478 { // run asynchronously
479 const bool was_empty = engine.async_jobs_.push (job);
480 if (was_empty)
481 wakeup_thread_mt();
482 return;
483 }
484 // blocking jobs, queue wrapper that synchronizes via Semaphore
485 ScopedSemaphore sem;
486 VoidFunc jobfunc = job->func;
487 std::function<void()> wrapper = [&sem, jobfunc] () {
488 jobfunc();
489 sem.post();
490 };
491 job->func = wrapper;
492 bool need_wakeup;
493 if (jobqueue == &const_jobs) // blocking, via const_jobs_ queue
494 need_wakeup = engine.const_jobs_.push (job);
495 else if (jobqueue == &synchronized_jobs) // blocking, via async_jobs_ queue
496 need_wakeup = engine.async_jobs_.push (job);
497 else
499 if (need_wakeup)
500 wakeup_thread_mt();
501 sem.wait();
502}
503
504void
505AudioEngineThread::set_project (ProjectImplP project)
506{
507 if (project)
508 {
509 assert_return (project_ == nullptr);
510 assert_return (!project->is_active());
511 }
512 if (project_)
513 project_->_deactivate();
514 const ProjectImplP old = project_;
515 project_ = project;
516 if (project_)
517 project_->_activate();
518 // dtor of old runs here
519}
520
521String
522AudioEngineThread::engine_stats_string (uint64_t stats) const
523{
524 String s;
525 for (size_t i = 0; i < oprocs_.size(); i++) {
526 AudioProcessorInfo pinfo;
527 pinfo.label = "INTERNAL";
528 AudioProcessor::registry_foreach ([&] (const String &aseid, AudioProcessor::StaticInfo static_info) {
529 if (aseid == oprocs_[i]->aseid_)
530 static_info (pinfo); // TODO: this is a bit awkward to fetch AudioProcessorInfo for an AudioProcessor
531 });
532 s += string_format ("%s: %s (MUST_SCHEDULE)\n", pinfo.label, oprocs_[i]->debug_name());
533 }
534 return s;
535}
536
537ProjectImplP
538AudioEngineThread::get_project ()
539{
540 return project_;
541}
542
543AudioEngineThread::~AudioEngineThread ()
544{
545 FastMemory::Block transport_block = transport_block_; // keep alive until after ~AudioEngine
546 main_jobs += [transport_block] () { ServerImpl::instancep()->telemem_release (transport_block); };
547}
548
549AudioEngineThread::AudioEngineThread (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement,
550 const FastMemory::Block &transport_block) :
551 AudioEngine (*this, *new (transport_block.block_start) AudioTransport (speakerarrangement, sample_rate)),
552 owner_wakeup_ (owner_wakeup), transport_block_ (transport_block)
553{
554 render_stamp_ = MAX_BUFFER_SIZE; // enforce non-0 start offset for all modules
555 oprocs_.reserve (16);
556 assert_return (transport_.samplerate == 48000);
557}
558
559AudioEngine&
560make_audio_engine (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement)
561{
562 ASE_ASSERT_WARN (sample_rate == FIXED_SAMPLE_RATE);
563 ASE_ASSERT_WARN (speaker_arrangement_count_channels (speakerarrangement) == FIXED_N_CHANNELS);
564 FastMemory::Block transport_block = ServerImpl::instancep()->telemem_allocate (sizeof (AudioTransport));
565 return *new AudioEngineThread (owner_wakeup, sample_rate, speakerarrangement, transport_block);
566}
567
568// == AudioEngine ==
569AudioEngine::AudioEngine (AudioEngineThread &audio_engine_thread, AudioTransport &transport) :
570 transport_ (transport), async_jobs (audio_engine_thread), const_jobs (audio_engine_thread), synchronized_jobs (audio_engine_thread)
571{}
572
573AudioEngine::~AudioEngine()
574{
575 // some ref-counted objects keep AudioEngine& members around
576 fatal_error ("AudioEngine must not be destroyed");
577}
578
579String
580AudioEngine::engine_stats (uint64_t stats) const
581{
582 String strstats;
583 const AudioEngineThread &engine_thread = static_cast<const AudioEngineThread&> (*this);
584 const_cast<AudioEngine*> (this)->synchronized_jobs += [&] () { strstats = engine_thread.engine_stats_string (stats); };
585 return strstats;
586}
587
588uint64
589AudioEngine::block_size() const
590{
591 const AudioEngineThread &impl = static_cast<const AudioEngineThread&> (*this);
592 return impl.buffer_size_;
593}
594
595void
596AudioEngine::set_autostop (uint64_t nsamples)
597{
598 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
599 impl.autostop_ = nsamples;
600}
601
602void
603AudioEngine::schedule_queue_update()
604{
605 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
606 impl.schedule_queue_update();
607}
608
609void
610AudioEngine::schedule_add (AudioProcessor &aproc, uint level)
611{
612 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
613 impl.schedule_add (aproc, level);
614}
615
616void
617AudioEngine::enable_output (AudioProcessor &aproc, bool onoff)
618{
619 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
620 return impl.enable_output (aproc, onoff);
621}
622
623void
624AudioEngine::start_threads()
625{
626 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
627 return impl.start_threads_ml();
628}
629
630void
631AudioEngine::stop_threads()
632{
633 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
634 return impl.stop_threads_ml();
635}
636
637void
638AudioEngine::queue_capture_start (CallbackS &callbacks, const String &filename, bool needsrunning)
639{
640 AudioEngineThread *impl = static_cast<AudioEngineThread*> (this);
641 String file = filename;
642 callbacks.push_back ([impl,file,needsrunning] () {
643 impl->capture_start (file, needsrunning);
644 });
645}
646
647void
648AudioEngine::queue_capture_stop (CallbackS &callbacks)
649{
650 AudioEngineThread *impl = static_cast<AudioEngineThread*> (this);
651 callbacks.push_back ([impl] () {
652 impl->capture_stop();
653 });
654}
655
656void
657AudioEngine::wakeup_thread_mt ()
658{
659 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
660 return impl.wakeup_thread_mt();
661}
662
663bool
664AudioEngine::ipc_pending ()
665{
666 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
667 return impl.ipc_pending();
668}
669
670void
671AudioEngine::ipc_dispatch ()
672{
673 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
674 return impl.ipc_dispatch();
675}
676
677AudioProcessorP
678AudioEngine::get_event_source ()
679{
680 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
681 return impl.get_event_source();
682}
683
684void
685AudioEngine::set_project (ProjectImplP project)
686{
687 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
688 return impl.set_project (project);
689}
690
691ProjectImplP
692AudioEngine::get_project ()
693{
694 AudioEngineThread &impl = static_cast<AudioEngineThread&> (*this);
695 return impl.get_project();
696}
697
698AudioEngine::JobQueue::JobQueue (AudioEngine &aet) :
699 queue_tag_ (ptrdiff_t (this) - ptrdiff_t (&aet))
700{
701 assert_return (ptrdiff_t (this) < 256 + ptrdiff_t (&aet));
702}
703
704void
705AudioEngine::JobQueue::operator+= (const std::function<void()> &job)
706{
707 AudioEngine *audio_engine = reinterpret_cast<AudioEngine*> (ptrdiff_t (this) - queue_tag_);
708 AudioEngineThread &audio_engine_thread = static_cast<AudioEngineThread&> (*audio_engine);
709 return audio_engine_thread.add_job_mt (new EngineJobImpl (job), this);
710}
711
712bool
713AudioEngine::update_drivers (const String &pcm_name, uint latency_ms, const StringS &midi_prefs)
714{
715 AudioEngineThread &engine_thread = static_cast<AudioEngineThread&> (*this);
716 DriverSet &dset = engine_thread.driver_set_ml;
717 const char *const null_driver = "null";
718 int must_update = 0;
719 // PCM Config
720 const PcmDriverConfig pcm_config { .n_channels = engine_thread.fixed_n_channels, .mix_freq = FIXED_SAMPLE_RATE,
721 .block_length = AUDIO_BLOCK_MAX_RENDER_SIZE, .latency_ms = latency_ms };
722 // PCM Fallback
723 if (!dset.null_pcm_driver) {
724 must_update++;
725 Error er = {};
726 dset.null_pcm_driver = PcmDriver::open (null_driver, Driver::WRITEONLY, Driver::WRITEONLY, pcm_config, &er);
727 if (!dset.null_pcm_driver || er != 0)
728 fatal_error ("failed to open internal PCM driver ('%s'): %s", null_driver, ase_error_blurb (er));
729 }
730 // PCM Driver
731 if (pcm_name != dset.pcm_name) {
732 must_update++;
733 dset.pcm_name = pcm_name;
734 Error er = {};
735 dset.pcm_driver = dset.pcm_name == null_driver ? dset.null_pcm_driver :
736 PcmDriver::open (dset.pcm_name, Driver::WRITEONLY, Driver::WRITEONLY, pcm_config, &er);
737 if (!dset.pcm_driver || er != 0) {
738 dset.pcm_driver = dset.null_pcm_driver;
739 diag ("Audio Driver: Failed to open audio device: %s: %s", dset.pcm_name, ase_error_blurb (er));
740 const String errmsg = string_format ("# Audio I/O Error\n" "Failed to open audio device:\n" "%s:\n" "%s",
741 dset.pcm_name, ase_error_blurb (er));
742 engine_thread.queue_user_note ("driver.pcm", UserNote::CLEAR, errmsg);
743 printerr ("%s\n", string_replace (errmsg, "\n", " "));
744 }
745 }
746 // Deduplicate MIDI Drivers
747 StringS midis = midi_prefs;
748 midis.resize (FIXED_N_MIDI_DRIVERS);
749 for (size_t i = 0; i < midis.size(); i++)
750 if (midis[i].empty())
751 midis[i] = null_driver;
752 else
753 for (size_t j = 0; j < i; j++) // dedup
754 if (midis[i] != null_driver && midis[i] == midis[j]) {
755 midis[i] = null_driver;
756 break;
757 }
758 // MIDI Drivers
759 dset.midi_names.resize (midis.size());
760 dset.midi_drivers.resize (dset.midi_names.size());
761 for (size_t i = 0; i < dset.midi_drivers.size(); i++) {
762 if (midis[i] == dset.midi_names[i])
763 continue;
764 must_update++;
765 dset.midi_names[i] = midis[i];
766 Error er = {};
767 dset.midi_drivers[i] = dset.midi_names[i] == null_driver ? nullptr :
768 MidiDriver::open (dset.midi_names[i], Driver::READONLY, &er);
769 if (er != 0) {
770 dset.midi_drivers[i] = nullptr;
771 const String errmsg = string_format ("# MIDI I/O Error\n" "Failed to open MIDI device #%u:\n" "%s:\n" "%s",
772 1 + i, dset.midi_names[i], ase_error_blurb (er));
773 engine_thread.queue_user_note ("driver.midi", UserNote::CLEAR, errmsg);
774 printerr ("%s\n", string_replace (errmsg, "\n", " "));
775 }
776 }
777 // Update running engine
778 if (must_update) {
779 Mutable<DriverSet> mdset = dset; // use Mutable so Job can swap and the remains are cleaned up in ~Job
780 synchronized_jobs += [mdset,&engine_thread] () { engine_thread.update_driver_set (mdset.value); };
781 return true;
782 }
783 return false;
784}
785
786// == EngineMidiInput ==
788 // Processor providing MIDI device events
789 void
790 initialize (SpeakerArrangement busses) override
791 {
792 prepare_event_output();
793 }
794 void
795 reset (uint64 target_stamp) override
796 {
797 MidiEventOutput &estream = midi_event_output();
798 estream.clear();
799 estream.reserve (256);
800 }
801 void
802 render (uint n_frames) override
803 {
804 MidiEventOutput &estream = midi_event_output();
805 estream.clear();
806 for (size_t i = 0; i < midi_drivers_.size(); i++)
807 if (midi_drivers_[i])
808 midi_drivers_[i]->fetch_events (estream, sample_rate());
809 }
810public:
811 MidiDriverS midi_drivers_;
812 EngineMidiInput (const ProcessorSetup &psetup) :
813 AudioProcessor (psetup)
814 {}
815};
816
817void
818AudioEngineThread::create_processors_ml ()
819{
820 assert_return (this_thread_is_ase()); // main_loop thread
821 assert_return (midi_proc_ == nullptr);
822 AudioProcessorP aprocp = AudioProcessor::create_processor<EngineMidiInput> (*this);
823 assert_return (aprocp);
824 midi_proc_ = std::dynamic_pointer_cast<EngineMidiInput> (aprocp);
825 assert_return (midi_proc_);
826 EngineMidiInputP midi_proc = midi_proc_;
827 async_jobs += [midi_proc] () {
828 midi_proc->enable_engine_output (true); // MUST_SCHEDULE
829 };
830}
831
832AudioProcessorP
833AudioEngineThread::get_event_source ()
834{
835 return midi_proc_;
836}
837
838void
839AudioEngineThread::update_driver_set (DriverSet &dset)
840{
841 // use swap() to defer dtor to user thread
842 assert_return (midi_proc_);
843 // PCM Driver
844 if (pcm_driver_ != dset.pcm_driver) {
845 pcm_driver_.swap (dset.pcm_driver);
846 floatfill (chbuffer_data_, 0.0, MAX_BUFFER_SIZE * fixed_n_channels);
847 buffer_size_ = std::min (MAX_BUFFER_SIZE, size_t (pcm_driver_->pcm_block_length()));
848 write_stamp_ = render_stamp_ - buffer_size_; // write an initial buffer of zeros
849 EDEBUG ("AudioEngineThread::%s: update PCM to \"%s\": channels=%d pcmblock=%d enginebuffer=%d ws=%u rs=%u bs=%u\n", __func__,
850 dset.pcm_name, fixed_n_channels, pcm_driver_->pcm_block_length(), buffer_size_, write_stamp_, render_stamp_, buffer_size_);
851 }
852 // MIDI Drivers
853 if (midi_proc_->midi_drivers_ != dset.midi_drivers) {
854 midi_proc_->midi_drivers_.swap (dset.midi_drivers);
855 EDEBUG ("AudioEngineThread::%s: swapping %u MIDI drivers: \"%s\"\n", __func__, midi_proc_->midi_drivers_.size(), string_join ("\" \"", dset.midi_names));
856 }
857}
858
859// == DriverSet ==
860static Choice
861choice_from_driver_entry (const DriverEntry &e, const String &icon_keywords)
862{
863 String blurb;
864 if (!e.device_info.empty() && !e.capabilities.empty())
865 blurb = e.capabilities + "\n" + e.device_info;
866 else if (!e.capabilities.empty())
867 blurb = e.capabilities;
868 else
869 blurb = e.device_info;
870 Choice c (e.devid, e.device_name, blurb);
871 if (string_startswith (string_tolower (e.notice), "warn"))
872 c.warning = e.notice;
873 else
874 c.notice = e.notice;
875 // e.priority
876 // e.readonly
877 // e.writeonly
878 // e.modem
879 c.icon = MakeIcon::KwIcon (icon_keywords + "," + e.hints);
880 return c;
881}
882
883static ChoiceS
884pcm_driver_pref_list_choices (const CString &ident)
885{
886 static ChoiceS choices;
887 static uint64 cache_age = 0;
888 if (choices.empty() || timestamp_realtime() > cache_age + 500 * 1000) {
889 choices.clear();
890 for (const DriverEntry &e : PcmDriver::list_drivers())
891 choices.push_back (choice_from_driver_entry (e, "pcm"));
892 cache_age = timestamp_realtime();
893 }
894 return choices;
895}
896
897static ChoiceS
898midi_driver_pref_list_choices (const CString &ident)
899{
900 static ChoiceS choices;
901 static uint64 cache_age = 0;
902 if (choices.empty() || timestamp_realtime() > cache_age + 500 * 1000) {
903 choices.clear();
904 for (const DriverEntry &e : MidiDriver::list_drivers())
905 if (!e.writeonly)
906 choices.push_back (choice_from_driver_entry (e, "midi"));
907 cache_age = timestamp_realtime();
908 }
909 return choices;
910}
911
912static Preference pcm_driver_pref =
913 Preference ({
914 "driver.pcm.devid", _("PCM Driver"), "", "auto", "ms",
915 { pcm_driver_pref_list_choices }, STANDARD, {
916 String ("descr=") + _("Driver and device to be used for PCM input and output"), } },
917 [] (const CString&,const Value&) { apply_driver_preferences(); });
918
919static Preference synth_latency_pref =
920 Preference ({
921 "driver.pcm.synth_latency", _("Synth Latency"), "", 15, "ms",
922 MinMaxStep { 0, 3000, 5 }, STANDARD + String ("step=5"), {
923 String ("descr=") + _("Processing duration between input and output of a single sample, smaller values increase CPU load"), } },
924 [] (const CString&,const Value&) { apply_driver_preferences(); });
925
926static Preference midi1_driver_pref =
927 Preference ({
928 "driver.midi1.devid", _("MIDI Controller (1)"), "", "auto", "ms",
929 { midi_driver_pref_list_choices }, STANDARD, {
930 String ("descr=") + _("MIDI controller device to be used for MIDI input"), } },
931 [] (const CString&,const Value&) { apply_driver_preferences(); });
932static Preference midi2_driver_pref =
933 Preference ({
934 "driver.midi2.devid", _("MIDI Controller (2)"), "", "auto", "ms",
935 { midi_driver_pref_list_choices }, STANDARD, {
936 String ("descr=") + _("MIDI controller device to be used for MIDI input"), } },
937 [] (const CString&,const Value&) { apply_driver_preferences(); });
938static Preference midi3_driver_pref =
939 Preference ({
940 "driver.midi3.devid", _("MIDI Controller (3)"), "", "auto", "ms",
941 { midi_driver_pref_list_choices }, STANDARD, {
942 String ("descr=") + _("MIDI controller device to be used for MIDI input"), } },
943 [] (const CString&,const Value&) { apply_driver_preferences(); });
944static Preference midi4_driver_pref =
945 Preference ({
946 "driver.midi4.devid", _("MIDI Controller (4)"), "", "auto", "ms",
947 { midi_driver_pref_list_choices }, STANDARD, {
948 String ("descr=") + _("MIDI controller device to be used for MIDI input"), } },
949 [] (const CString&,const Value&) { apply_driver_preferences(); });
950
951static void
952apply_driver_preferences ()
953{
954 static LoopID engine_driver_set_timerid = LoopID::INVALID;
955 main_loop->exec_once (97, &engine_driver_set_timerid,
956 []() {
957 String pcm_driver = pcm_driver_pref.gets();
958 if (!App.pcm_override.empty())
959 pcm_driver = App.pcm_override;
960 StringS midis = { midi1_driver_pref.gets(), midi2_driver_pref.gets(), midi3_driver_pref.gets(), midi4_driver_pref.gets(), };
961 if (!App.midi_override.empty())
962 midis = { App.midi_override, "null", "null", "null", };
963 App.engine->update_drivers (pcm_driver, synth_latency_pref.getn(), midis);
964 });
965}
966
967} // 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:119
Lock-free stack with atomic push() and pop_all operations.
Definition atomics.hh:21
Main handle for AudioProcessor administration and audio rendering.
Definition engine.hh:21
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 LoopP current()
Return the thread-local singleton loop, created on first call.
Definition loop.cc:306
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:73
#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:42
#define assert_unreached()
Explicitely mark unreachable code locations.
Definition internal.hh:37
T join(T... args)
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:268
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)
Join a number of strings.
Definition strings.cc:452
SpeakerArrangement
Flags to indicate channel arrangements of a bus.
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 event loop.
Definition main.hh:43
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:929
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
void main_loop_autostop_mt()
Stop the event loop after a timeout.
Definition main.cc:361
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:579
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
Definition strings.cc:846
const char8 * CString
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)