diff --git a/ase/clapplugin.cc b/ase/clapplugin.cc index 595c36ce..aa6516a5 100644 --- a/ase/clapplugin.cc +++ b/ase/clapplugin.cc @@ -24,7 +24,7 @@ namespace Ase { -ASE_CLASS_DECLS (ClapAudioWrapper); +ASE_CLASS_DECLS (ClapAudioProcessor); ASE_CLASS_DECLS (ClapPluginHandleImpl); using ClapEventParamS = std::vector; union ClapEventUnion; @@ -220,8 +220,8 @@ union ClapEventUnion { clap_event_midi2_t midi2; // CLAP_NOTE_DIALECT_MIDI2 }; -// == ClapAudioWrapper == -class ClapAudioWrapper : public AudioProcessor { +// == ClapAudioProcessor == +class ClapAudioProcessor : public AudioProcessor { ClapPluginHandle *handle_ = nullptr; const clap_plugin *clapplugin_ = nullptr; IBusId ibusid = {}; @@ -236,17 +236,17 @@ class ClapAudioWrapper : public AudioProcessor { static void static_info (AudioProcessorInfo &info) { - info.label = "Anklang.Devices.ClapAudioWrapper"; + info.label = "Anklang.Devices.ClapAudioProcessor"; } - ClapAudioWrapper (AudioEngine &engine) : + ClapAudioProcessor (AudioEngine &engine) : AudioProcessor (engine) {} - ~ClapAudioWrapper() + ~ClapAudioProcessor() { while (enqueued_events_.size()) { - BorrowedPtr pevents_b = enqueued_events_.back(); + ClapEventParamS *pevents = enqueued_events_.back(); enqueued_events_.pop_back(); - pevents_b.dispose (engine_); + main_rt_jobs += RtCall (delete_clap_event_params, pevents); // delete in main_thread } } void @@ -312,7 +312,7 @@ class ClapAudioWrapper : public AudioProcessor { static uint32_t input_events_size (const clap_input_events *evlist) { - ClapAudioWrapper *self = (ClapAudioWrapper*) evlist->ctx; + ClapAudioProcessor *self = (ClapAudioProcessor*) evlist->ctx; size_t param_events_size = 0; for (const auto &pevents_b : self->enqueued_events_) param_events_size += pevents_b->size(); @@ -321,7 +321,7 @@ class ClapAudioWrapper : public AudioProcessor { static const clap_event_header_t* input_events_get (const clap_input_events *evlist, uint32_t index) { - ClapAudioWrapper *self = (ClapAudioWrapper*) evlist->ctx; + ClapAudioProcessor *self = (ClapAudioProcessor*) evlist->ctx; for (const auto &pevents_b : self->enqueued_events_) { if (index < pevents_b->size()) return &(*pevents_b)[index].header; @@ -332,35 +332,35 @@ class ClapAudioWrapper : public AudioProcessor { static bool output_events_try_push (const clap_output_events *evlist, const clap_event_header_t *event) { - ClapAudioWrapper *self = (ClapAudioWrapper*) evlist->ctx; + ClapAudioProcessor *self = (ClapAudioProcessor*) evlist->ctx; return event_unions_try_push (self->output_events_, event); } const clap_input_events_t plugin_input_events = { - .ctx = (ClapAudioWrapper*) this, + .ctx = (ClapAudioProcessor*) this, .size = input_events_size, .get = input_events_get, }; const clap_output_events_t plugin_output_events = { - .ctx = (ClapAudioWrapper*) this, + .ctx = (ClapAudioProcessor*) this, .try_push = output_events_try_push, }; const ClapParamInfoMap *param_info_map_ = nullptr; const ClapParamInfoImpl *param_info_map_start_ = nullptr; clap_process_t processinfo = { 0, }; clap_event_transport_t transportinfo = { { 0, }, }; - std::vector> enqueued_events_; + std::vector enqueued_events_; void - enqueue_events (BorrowedPtr pevents_b) + enqueue_events (ClapEventParamS *pevents) { // insert 0-time event list *before* other events - if (pevents_b && pevents_b->size() && pevents_b->back().header.time == 0) + if (pevents && pevents->size() && pevents->back().header.time == 0) for (size_t i = 0; i < enqueued_events_.size(); i++) if (enqueued_events_[i]->size() && enqueued_events_[i]->back().header.time > 0) { - enqueued_events_.insert (enqueued_events_.begin() + i, pevents_b); + enqueued_events_.insert (enqueued_events_.begin() + i, pevents); return; } // or add to existing queue - enqueued_events_.push_back (pevents_b); + enqueued_events_.push_back (pevents); } bool start_processing (const ClapParamInfoMap *param_info_map, const ClapParamInfoImpl *map_start, size_t map_size) @@ -487,16 +487,22 @@ class ClapAudioWrapper : public AudioProcessor { // TODO: need proper time stamp handling bool need_wakeup = false; while (enqueued_events_.size() && (enqueued_events_[0]->empty() || enqueued_events_[0]->back().header.time < nframes)) { - BorrowedPtr pevents_b = enqueued_events_[0]; + ClapEventParamS *const pevents = enqueued_events_[0]; enqueued_events_.erase (enqueued_events_.begin()); - for (const auto &e : *pevents_b) + for (const auto &e : *pevents) need_wakeup |= apply_param_value_event (e); - pevents_b.dispose (engine_); + main_rt_jobs += RtCall (delete_clap_event_params, pevents); // delete in main_thread } return need_wakeup; } + static void + delete_clap_event_params (ClapEventParamS *p) + { + assert_return (this_thread_is_ase()); + delete p; + } }; -static CString clap_audio_wrapper_aseid = register_audio_processor(); +static CString clap_audio_wrapper_aseid = register_audio_processor(); static inline clap_event_midi* setup_midi1 (ClapEventUnion *evunion, uint32_t time, uint16_t port_index) @@ -538,7 +544,7 @@ setup_expression (ClapEventUnion *evunion, uint32_t time, uint16_t port_index) } void -ClapAudioWrapper::convert_clap_events (const clap_process_t &process, const bool as_clapnotes) +ClapAudioProcessor::convert_clap_events (const clap_process_t &process, const bool as_clapnotes) { MidiEventRange erange = get_event_input(); if (input_events_.capacity() < erange.events_pending()) @@ -651,7 +657,7 @@ class ClapPluginHandleImpl : public ClapPluginHandle { .request_process = host_request_process_mt, .request_callback = host_request_callback_mt, }; - ClapAudioWrapperP proc_; + ClapAudioProcessorP proc_; const clap_plugin_t *plugin_ = nullptr; const clap_plugin_gui *plugin_gui = nullptr; const clap_plugin_state *plugin_state = nullptr; @@ -663,7 +669,7 @@ class ClapPluginHandleImpl : public ClapPluginHandle { const clap_plugin_note_ports *plugin_note_ports = nullptr; const clap_plugin_posix_fd_support *plugin_posix_fd_support = nullptr; ClapPluginHandleImpl (const ClapPluginDescriptor &descriptor_, AudioProcessorP aproc) : - ClapPluginHandle (descriptor_), proc_ (shared_ptr_cast (aproc)) + ClapPluginHandle (descriptor_), proc_ (shared_ptr_cast (aproc)) { assert_return (proc_ != nullptr); const clap_plugin_entry *pluginentry = descriptor.entry(); @@ -932,10 +938,9 @@ class ClapPluginHandleImpl : public ClapPluginHandle { { ClapPluginHandleImplP selfp = shared_ptr_cast (this); return_unless (clap_activated(), false); - ClapEventParamS *pevents = convert_param_updates (updates); - BorrowedPtr pevents_b (pevents); // moves ownership - proc_->engine().async_jobs += [selfp, pevents_b] () { - selfp->proc_->enqueue_events (pevents_b); // use BorrowedPtr in audio-thread, dispose in main-thread + ClapEventParamS *pevents = convert_param_updates (updates); // allocated in main_thread + proc_->engine().async_jobs += [selfp, pevents] () { + selfp->proc_->enqueue_events (pevents); }; return true; } diff --git a/ase/clapplugin.hh b/ase/clapplugin.hh index 44ec96e7..ce9c7f5d 100644 --- a/ase/clapplugin.hh +++ b/ase/clapplugin.hh @@ -8,7 +8,7 @@ namespace Ase { class ClapFileHandle; -class ClapAudioWrapper; +class ClapAudioProcessor; // == ClapPluginDescriptor == class ClapPluginDescriptor { @@ -88,7 +88,7 @@ public: virtual AudioProcessorP audio_processor () = 0; static ClapPluginHandleP make_clap_handle (const ClapPluginDescriptor &descriptor, AudioProcessorP audio_processor); static CString audio_processor_type(); - friend class ClapAudioWrapper; + friend class ClapAudioProcessor; }; // == CLAP utilities == diff --git a/ase/defs.hh b/ase/defs.hh index 24049590..6d457c49 100644 --- a/ase/defs.hh +++ b/ase/defs.hh @@ -38,7 +38,7 @@ ASE_STRUCT_DECLS (UserNote); ASE_CLASS_DECLS (AudioChain); ASE_CLASS_DECLS (AudioCombo); ASE_CLASS_DECLS (AudioCombo); -ASE_CLASS_DECLS (AudioEngineImpl); +ASE_CLASS_DECLS (AudioEngineThread); ASE_CLASS_DECLS (AudioProcessor); ASE_CLASS_DECLS (ClapDeviceImpl); ASE_CLASS_DECLS (ClapPluginHandle); diff --git a/ase/engine.cc b/ase/engine.cc index 26b46457..fb8ede8e 100644 --- a/ase/engine.cc +++ b/ase/engine.cc @@ -23,13 +23,8 @@ using StartQueue = AsyncBlockingQueue; ASE_CLASS_DECLS (EngineMidiInput); constexpr uint fixed_sample_rate = 48000; -// == JobQueue == -AudioEngine::JobQueue AudioEngine::async_jobs; -AudioEngine::JobQueue AudioEngine::const_jobs; -static AudioEngine::JobQueue synchronized_jobs; - // == EngineJobImpl == -struct EngineJobImpl : AudioEngineJob { +struct EngineJobImpl { VoidFunc func; std::atomic next = nullptr; explicit EngineJobImpl (const VoidFunc &jobfunc) : func (jobfunc) {} @@ -39,10 +34,9 @@ atomic_next_ptrref (EngineJobImpl *j) { return j->next; } -AudioEngineJob::~AudioEngineJob() {} -// == AudioEngineImpl == -class AudioEngineImpl : public AudioEngine { +// == AudioEngineThread == +class AudioEngineThread : public AudioEngine { PcmDriverP null_pcm_driver_, pcm_driver_; MidiDriverS midi_drivers_; static constexpr uint fixed_n_channels = 2; @@ -54,7 +48,6 @@ class AudioEngineImpl : public AudioEngine { EngineMidiInputP midi_proc_; bool schedule_invalid_ = true; bool output_needsrunning_ = false; - FastMemory::Block transport_block; AtomicIntrusiveStack async_jobs_, const_jobs_, trash_jobs_; const VoidF owner_wakeup_; std::thread *thread_ = nullptr; @@ -63,6 +56,7 @@ class AudioEngineImpl : public AudioEngine { AudioProcessorS oprocs_; ProjectImplP project_; WaveWriterP wwriter_; + FastMemory::Block transport_block_; public: std::atomic autostop_ = U64MAX; struct UserNoteJob { @@ -71,8 +65,8 @@ class AudioEngineImpl : public AudioEngine { }; AtomicIntrusiveStack user_notes_; public: - virtual ~AudioEngineImpl (); - explicit AudioEngineImpl (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement); + virtual ~AudioEngineThread (); + explicit AudioEngineThread (const VoidF&, uint, SpeakerArrangement, const FastMemory::Block&); void schedule_clear (); void schedule_add (AudioProcessor &aproc, uint level); void schedule_queue_update (); @@ -87,7 +81,7 @@ class AudioEngineImpl : public AudioEngine { bool ipc_pending (); void ipc_dispatch (); AudioProcessorP get_event_source (); - void add_job_mt (AudioEngineJob *aejob, const AudioEngine::JobQueue *jobqueue); + void add_job_mt (EngineJobImpl *aejob, const AudioEngine::JobQueue *jobqueue); bool pcm_check_write (bool write_buffer, int64 *timeout_usecs_p = nullptr); bool driver_dispatcher (const LoopState &state); bool process_jobs (AtomicIntrusiveStack &joblist); @@ -102,28 +96,12 @@ class AudioEngineImpl : public AudioEngine { static std::thread::id audio_engine_thread_id = {}; const ThreadId &AudioEngine::thread_id = audio_engine_thread_id; -static inline std::atomic& -atomic_next_ptrref (AudioEngineImpl::UserNoteJob *j) +static inline std::atomic& +atomic_next_ptrref (AudioEngineThread::UserNoteJob *j) { return j->next; } -AudioEngineImpl::~AudioEngineImpl () -{ - fatal_error ("AudioEngine references must persist"); - transport_ = nullptr; - ServerImpl::instancep()->telemem_release (transport_block); -} - -AudioEngineImpl::AudioEngineImpl (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement) : - owner_wakeup_ (owner_wakeup) -{ - transport_block = ServerImpl::instancep()->telemem_allocate (sizeof (*transport_)); - transport_ = new (transport_block.block_start) AudioTransport (speakerarrangement, sample_rate); - oprocs_.reserve (16); - assert_return (sample_rate == 48000); -} - template static void interleaved_stereo (const size_t n_frames, float *buffer, AudioProcessor &proc, OBusId obus) { @@ -165,13 +143,13 @@ interleaved_stereo (const size_t n_frames, float *buffer, AudioProcessor &proc, } void -AudioEngineImpl::schedule_queue_update() +AudioEngineThread::schedule_queue_update() { schedule_invalid_ = true; } void -AudioEngineImpl::schedule_clear() +AudioEngineThread::schedule_clear() { while (schedule_.size() != 0) { @@ -189,7 +167,7 @@ AudioEngineImpl::schedule_clear() } void -AudioEngineImpl::schedule_add (AudioProcessor &aproc, uint level) +AudioEngineThread::schedule_add (AudioProcessor &aproc, uint level) { return_unless (0 == (aproc.flags_ & AudioProcessor::SCHEDULED)); assert_return (aproc.sched_next_ == nullptr); @@ -203,7 +181,7 @@ AudioEngineImpl::schedule_add (AudioProcessor &aproc, uint level) } void -AudioEngineImpl::schedule_render (uint64 frames) +AudioEngineThread::schedule_render (uint64 frames) { assert_return (0 == (frames & (8 - 1))); // render scheduled AudioProcessor nodes @@ -232,11 +210,11 @@ AudioEngineImpl::schedule_render (uint64 frames) if (n == 0) floatfill (chbuffer_data_, 0.0, buffer_size_ * fixed_n_channels); render_stamp_ = target_stamp; - transport_->advance (frames); + transport_.advance (frames); } void -AudioEngineImpl::enable_output (AudioProcessor &aproc, bool onoff) +AudioEngineThread::enable_output (AudioProcessor &aproc, bool onoff) { AudioProcessorP procp = shared_ptr_cast (&aproc); assert_return (procp != nullptr); @@ -256,7 +234,7 @@ AudioEngineImpl::enable_output (AudioProcessor &aproc, bool onoff) } void -AudioEngineImpl::update_drivers (bool fullio, uint latency) +AudioEngineThread::update_drivers (bool fullio, uint latency) { Error er = {}; // PCM fallback @@ -301,7 +279,7 @@ AudioEngineImpl::update_drivers (bool fullio, uint latency) buffer_size_ = std::min (MAX_BUFFER_SIZE, size_t (pcm_driver_->block_length())); floatfill (chbuffer_data_, 0.0, buffer_size_ * fixed_n_channels); write_stamp_ = render_stamp_ - buffer_size_; // force zeros initially - EDEBUG ("AudioEngineImpl::update_drivers: PCM: channels=%d pcmblock=%d enginebuffer=%d\n", + EDEBUG ("AudioEngineThread::update_drivers: PCM: channels=%d pcmblock=%d enginebuffer=%d\n", fixed_n_channels, pcm_driver_->block_length(), buffer_size_); // MIDI Driver List MidiDriverS old_drivers = midi_drivers_, new_drivers; @@ -361,25 +339,26 @@ AudioEngineImpl::update_drivers (bool fullio, uint latency) } void -AudioEngineImpl::capture_start (const String &filename, bool needsrunning) +AudioEngineThread::capture_start (const String &filename, bool needsrunning) { + const uint sample_rate = transport_.samplerate; capture_stop(); output_needsrunning_ = needsrunning; if (string_endswith (filename, ".wav")) { - wwriter_ = wave_writer_create_wav (sample_rate(), fixed_n_channels, filename); + wwriter_ = wave_writer_create_wav (sample_rate, fixed_n_channels, filename); if (!wwriter_) printerr ("%s: failed to open file: %s\n", filename, strerror (errno)); } else if (string_endswith (filename, ".opus")) { - wwriter_ = wave_writer_create_opus (sample_rate(), fixed_n_channels, filename); + wwriter_ = wave_writer_create_opus (sample_rate, fixed_n_channels, filename); if (!wwriter_) printerr ("%s: failed to open file: %s\n", filename, strerror (errno)); } else if (string_endswith (filename, ".flac")) { - wwriter_ = wave_writer_create_flac (sample_rate(), fixed_n_channels, filename); + wwriter_ = wave_writer_create_flac (sample_rate, fixed_n_channels, filename); if (!wwriter_) printerr ("%s: failed to open file: %s\n", filename, strerror (errno)); } @@ -388,7 +367,7 @@ AudioEngineImpl::capture_start (const String &filename, bool needsrunning) } void -AudioEngineImpl::capture_stop() +AudioEngineThread::capture_stop() { if (wwriter_) { @@ -398,21 +377,21 @@ AudioEngineImpl::capture_stop() } void -AudioEngineImpl::run (StartQueue *sq) +AudioEngineThread::run (StartQueue *sq) { assert_return (pcm_driver_); // FIXME: assert owner_wakeup and free trash this_thread_set_name ("AudioEngine-0"); // max 16 chars audio_engine_thread_id = std::this_thread::get_id(); sched_fast_priority (this_thread_gettid()); - event_loop_->exec_dispatcher (std::bind (&AudioEngineImpl::driver_dispatcher, this, std::placeholders::_1)); + event_loop_->exec_dispatcher (std::bind (&AudioEngineThread::driver_dispatcher, this, std::placeholders::_1)); sq->push ('R'); // StartQueue becomes invalid after this call sq = nullptr; event_loop_->run(); } bool -AudioEngineImpl::process_jobs (AtomicIntrusiveStack &joblist) +AudioEngineThread::process_jobs (AtomicIntrusiveStack &joblist) { EngineJobImpl *const jobs = joblist.pop_reversed(), *last = nullptr; for (EngineJobImpl *job = jobs; job; last = job, job = job->next) @@ -426,7 +405,7 @@ AudioEngineImpl::process_jobs (AtomicIntrusiveStack &joblist) } bool -AudioEngineImpl::pcm_check_write (bool write_buffer, int64 *timeout_usecs_p) +AudioEngineThread::pcm_check_write (bool write_buffer, int64 *timeout_usecs_p) { int64 timeout_usecs = INT64_MAX; const bool can_write = pcm_driver_->pcm_check_io (&timeout_usecs) || timeout_usecs == 0; @@ -438,7 +417,7 @@ AudioEngineImpl::pcm_check_write (bool write_buffer, int64 *timeout_usecs_p) return false; pcm_driver_->pcm_write (buffer_size_ * fixed_n_channels, chbuffer_data_); if (wwriter_ && fixed_n_channels == 2 && write_stamp_ < autostop_ && - (!output_needsrunning_ || transport_->running())) + (!output_needsrunning_ || transport_.running())) wwriter_->write (chbuffer_data_, buffer_size_); write_stamp_ += buffer_size_; if (write_stamp_ >= autostop_) @@ -448,7 +427,7 @@ AudioEngineImpl::pcm_check_write (bool write_buffer, int64 *timeout_usecs_p) } bool -AudioEngineImpl::driver_dispatcher (const LoopState &state) +AudioEngineThread::driver_dispatcher (const LoopState &state) { int64 *timeout_usecs = nullptr; switch (state.phase) @@ -493,7 +472,7 @@ AudioEngineImpl::driver_dispatcher (const LoopState &state) } void -AudioEngineImpl::queue_user_note (const String &channel, UserNote::Flags flags, const String &text) +AudioEngineThread::queue_user_note (const String &channel, UserNote::Flags flags, const String &text) { UserNoteJob *uj = new UserNoteJob { nullptr, { 0, flags, channel, text } }; if (user_notes_.push (uj)) @@ -501,14 +480,14 @@ AudioEngineImpl::queue_user_note (const String &channel, UserNote::Flags flags, } bool -AudioEngineImpl::ipc_pending () +AudioEngineThread::ipc_pending () { const bool have_jobs = !trash_jobs_.empty() || !user_notes_.empty(); return have_jobs || AudioProcessor::enotify_pending(); } void -AudioEngineImpl::ipc_dispatch () +AudioEngineThread::ipc_dispatch () { UserNoteJob *uj = user_notes_.pop_reversed(); while (uj) @@ -530,14 +509,14 @@ AudioEngineImpl::ipc_dispatch () } void -AudioEngineImpl::wakeup_thread_mt() +AudioEngineThread::wakeup_thread_mt() { assert_return (event_loop_); event_loop_->wakeup(); } void -AudioEngineImpl::start_threads() +AudioEngineThread::start_threads() { schedule_.reserve (8192); assert_return (thread_ == nullptr); @@ -545,7 +524,7 @@ AudioEngineImpl::start_threads() update_drivers (false, latency); schedule_queue_update(); StartQueue start_queue; - thread_ = new std::thread (&AudioEngineImpl::run, this, &start_queue); + thread_ = new std::thread (&AudioEngineThread::run, this, &start_queue); const char reply = start_queue.pop(); // synchronize with thread start assert_return (reply == 'R'); onchange_prefs_ = ASE_SERVER.on_event ("change:prefs", [this, latency] (auto...) { @@ -555,9 +534,9 @@ AudioEngineImpl::start_threads() } void -AudioEngineImpl::stop_threads() +AudioEngineThread::stop_threads() { - AudioEngineImpl &engine = *dynamic_cast (this); + AudioEngineThread &engine = *dynamic_cast (this); assert_return (engine.thread_ != nullptr); onchange_prefs_.reset(); engine.event_loop_->quit (0); @@ -569,11 +548,10 @@ AudioEngineImpl::stop_threads() } void -AudioEngineImpl::add_job_mt (AudioEngineJob *aejob, const AudioEngine::JobQueue *jobqueue) +AudioEngineThread::add_job_mt (EngineJobImpl *job, const AudioEngine::JobQueue *jobqueue) { - EngineJobImpl *job = dynamic_cast (aejob); assert_return (job != nullptr); - AudioEngineImpl &engine = *dynamic_cast (this); + AudioEngineThread &engine = *dynamic_cast (this); // engine not running, run job right away if (!engine.thread_) { @@ -582,7 +560,7 @@ AudioEngineImpl::add_job_mt (AudioEngineJob *aejob, const AudioEngine::JobQueue return; } // enqueue async_jobs - if (jobqueue == &AudioEngine::async_jobs) // non-blocking, via async_jobs_ queue + if (jobqueue == &async_jobs) // non-blocking, via async_jobs_ queue { // run asynchronously const bool was_empty = engine.async_jobs_.push (job); if (was_empty) @@ -598,7 +576,7 @@ AudioEngineImpl::add_job_mt (AudioEngineJob *aejob, const AudioEngine::JobQueue }; job->func = wrapper; bool need_wakeup; - if (jobqueue == &AudioEngine::const_jobs) // blocking, via const_jobs_ queue + if (jobqueue == &const_jobs) // blocking, via const_jobs_ queue need_wakeup = engine.const_jobs_.push (job); else if (jobqueue == &synchronized_jobs) // blocking, via async_jobs_ queue need_wakeup = engine.async_jobs_.push (job); @@ -610,7 +588,7 @@ AudioEngineImpl::add_job_mt (AudioEngineJob *aejob, const AudioEngine::JobQueue } void -AudioEngineImpl::set_project (ProjectImplP project) +AudioEngineThread::set_project (ProjectImplP project) { if (project) { @@ -627,82 +605,97 @@ AudioEngineImpl::set_project (ProjectImplP project) } ProjectImplP -AudioEngineImpl::get_project () +AudioEngineThread::get_project () { return project_; } -static AudioEngineImpl *audio_engine_impl = nullptr; +AudioEngineThread::~AudioEngineThread () +{ + FastMemory::Block transport_block = transport_block_; // keep alive until after ~AudioEngine + main_jobs += [transport_block] () { ServerImpl::instancep()->telemem_release (transport_block); }; +} + +AudioEngineThread::AudioEngineThread (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement, + const FastMemory::Block &transport_block) : + AudioEngine (*this, *new (transport_block.block_start) AudioTransport (speakerarrangement, sample_rate)), + owner_wakeup_ (owner_wakeup), transport_block_ (transport_block) +{ + oprocs_.reserve (16); + assert_return (transport_.samplerate == 48000); +} AudioEngine& make_audio_engine (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement) { - assert_return (audio_engine_impl == nullptr, *audio_engine_impl); - audio_engine_impl = new AudioEngineImpl (owner_wakeup, sample_rate, speakerarrangement); - return *audio_engine_impl; + FastMemory::Block transport_block = ServerImpl::instancep()->telemem_allocate (sizeof (AudioTransport)); + return *new AudioEngineThread (owner_wakeup, sample_rate, speakerarrangement, transport_block); } // == AudioEngine == -AudioEngineJob* -AudioEngine::new_engine_job (const std::function &jobfunc) +AudioEngine::AudioEngine (AudioEngineThread &audio_engine_thread, AudioTransport &transport) : + transport_ (transport), async_jobs (audio_engine_thread), const_jobs (audio_engine_thread), synchronized_jobs (audio_engine_thread) +{} + +AudioEngine::~AudioEngine() { - EngineJobImpl *job = new EngineJobImpl (jobfunc); - return job; + // some ref-counted objects keep AudioEngine& members around + fatal_error ("AudioEngine must not be destroyed"); } uint64 AudioEngine::frame_counter () const { - const AudioEngineImpl &impl = static_cast (*this); + const AudioEngineThread &impl = static_cast (*this); return impl.frame_counter(); } void AudioEngine::set_autostop (uint64_t nsamples) { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); impl.autostop_ = nsamples; } void AudioEngine::schedule_queue_update() { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); impl.schedule_queue_update(); } void AudioEngine::schedule_add (AudioProcessor &aproc, uint level) { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); impl.schedule_add (aproc, level); } void AudioEngine::enable_output (AudioProcessor &aproc, bool onoff) { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); return impl.enable_output (aproc, onoff); } void AudioEngine::start_threads() { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); return impl.start_threads(); } void AudioEngine::stop_threads() { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); return impl.stop_threads(); } void AudioEngine::queue_capture_start (CallbackS &callbacks, const String &filename, bool needsrunning) { - AudioEngineImpl *impl = static_cast (this); + AudioEngineThread *impl = static_cast (this); String file = filename; callbacks.push_back ([impl,file,needsrunning] () { impl->capture_start (file, needsrunning); @@ -712,7 +705,7 @@ AudioEngine::queue_capture_start (CallbackS &callbacks, const String &filename, void AudioEngine::queue_capture_stop (CallbackS &callbacks) { - AudioEngineImpl *impl = static_cast (this); + AudioEngineThread *impl = static_cast (this); callbacks.push_back ([impl] () { impl->capture_stop(); }); @@ -721,55 +714,57 @@ AudioEngine::queue_capture_stop (CallbackS &callbacks) void AudioEngine::wakeup_thread_mt () { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); return impl.wakeup_thread_mt(); } bool AudioEngine::ipc_pending () { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); return impl.ipc_pending(); } void AudioEngine::ipc_dispatch () { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); return impl.ipc_dispatch(); } AudioProcessorP AudioEngine::get_event_source () { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); return impl.get_event_source(); } void AudioEngine::set_project (ProjectImplP project) { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); return impl.set_project (project); } ProjectImplP AudioEngine::get_project () { - AudioEngineImpl &impl = static_cast (*this); + AudioEngineThread &impl = static_cast (*this); return impl.get_project(); } -void -AudioEngine::JobQueue::operator+= (const std::function &job) +AudioEngine::JobQueue::JobQueue (AudioEngine &aet) : + queue_tag_ (ptrdiff_t (this) - ptrdiff_t (&aet)) { - return audio_engine_impl->add_job_mt (new_engine_job (job), this); + assert_return (ptrdiff_t (this) < 256 + ptrdiff_t (&aet)); } void -AudioEngine::JobQueue::operator+= (AudioEngineJob *job) +AudioEngine::JobQueue::operator+= (const std::function &job) { - return audio_engine_impl->add_job_mt (job, this); + AudioEngine *audio_engine = reinterpret_cast (ptrdiff_t (this) - queue_tag_); + AudioEngineThread &audio_engine_thread = static_cast (*audio_engine); + return audio_engine_thread.add_job_mt (new EngineJobImpl (job), this); } // == MidiInput == @@ -809,7 +804,7 @@ template struct Mutable { }; void -AudioEngineImpl::swap_midi_drivers_sync (const MidiDriverS &midi_drivers) +AudioEngineThread::swap_midi_drivers_sync (const MidiDriverS &midi_drivers) { if (!midi_proc_) { @@ -831,7 +826,7 @@ AudioEngineImpl::swap_midi_drivers_sync (const MidiDriverS &midi_drivers) } AudioProcessorP -AudioEngineImpl::get_event_source () +AudioEngineThread::get_event_source () { return midi_proc_; } diff --git a/ase/engine.hh b/ase/engine.hh index 4dcc3a6d..ecc8d136 100644 --- a/ase/engine.hh +++ b/ase/engine.hh @@ -8,23 +8,23 @@ namespace Ase { -struct AudioEngineJob { - virtual ~AudioEngineJob(); - void *voidp = nullptr; -}; +class AudioEngineThread; /** Main handle for AudioProcessor administration and audio rendering. + * Use make_audio_engine() to create a new engine and start_threads() to run + * its synthesis threads. AudioEngine objects cannot be deleted, because other + * ref-counted objects may hold `AudioEngine&` members until after main(). * Use async_jobs to have the engine execute arbitrary code. * Use const_jobs for synchronous read-only data gathering, this may take quite long. * Use main_rt_jobs (see main.hh) for obstruction free enqueueing of main_loop callbacks. */ class AudioEngine : VirtualBase { - std::atomic processor_count_ = 0; - friend class AudioEngineImpl; - friend class AudioProcessor; protected: - AudioTransport *transport_ = nullptr; - explicit AudioEngine () {} + friend class AudioProcessor; + std::atomic processor_count_ alignas (64) = 0; + AudioTransport &transport_; + explicit AudioEngine (AudioEngineThread&, AudioTransport&); + virtual ~AudioEngine (); void enable_output (AudioProcessor &aproc, bool onoff); void schedule_queue_update (); void schedule_add (AudioProcessor &aproc, uint level); @@ -40,7 +40,7 @@ public: ProjectImplP get_project (); // MT-Safe API uint64_t frame_counter () const; - const AudioTransport& transport () const { return *transport_; } + const AudioTransport& transport () const { return transport_; } uint sample_rate () const ASE_CONST { return transport().samplerate; } uint nyquist () const ASE_CONST { return transport().nyquist; } double inyquist () const ASE_CONST { return transport().inyquist; } @@ -48,71 +48,23 @@ public: void set_autostop (uint64_t nsamples); void queue_capture_start (CallbackS&, const String &filename, bool needsrunning); void queue_capture_stop (CallbackS&); - static AudioEngineJob* new_engine_job (const std::function &jobfunc); static bool thread_is_engine () { return std::this_thread::get_id() == thread_id; } static const ThreadId &thread_id; // JobQueues - struct JobQueue { + class JobQueue { + friend class AudioEngine; + const uint8_t queue_tag_; + explicit JobQueue (AudioEngine&); + public: void operator+= (const std::function &job); - void operator+= (AudioEngineJob *job); }; - static JobQueue async_jobs; ///< Executed asynchronously, may modify AudioProcessor objects - static JobQueue const_jobs; ///< Blocks during execution, must treat AudioProcessor objects read-only -}; - -AudioEngine& make_audio_engine (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement); - -/// A BorrowedPtr wraps an object pointer until it is disposed, i.e. returned to the main-thread and deleted. -template -class BorrowedPtr { - AudioEngineJob *job_ = nullptr; - T* ptr () const { return job_ ? (T*) job_->voidp : nullptr; } -public: - explicit BorrowedPtr (T *ptr = nullptr, const std::function &deleter = std::default_delete()); - /*copy*/ BorrowedPtr (const BorrowedPtr &other) { *this = other; } - BorrowedPtr& operator= (const BorrowedPtr &other) noexcept; - explicit operator bool () const { return ptr() != nullptr; } - T& operator* () const { return *ptr(); } - T* operator-> () const { return ptr(); } - T* get () const { return ptr(); } - void dispose (AudioEngine &engine); + JobQueue async_jobs; ///< Executed asynchronously, may modify AudioProcessor objects + JobQueue const_jobs; ///< Blocks during execution, must treat AudioProcessor objects read-only +protected: + JobQueue synchronized_jobs; }; -// == implementations == -template BorrowedPtr& -BorrowedPtr::operator= (const BorrowedPtr &other) noexcept -{ - job_ = other.job_; - return *this; -} - -template void -BorrowedPtr::dispose (AudioEngine &engine) -{ - ASE_ASSERT_RETURN (ptr() != nullptr); - if (job_) { - job_->voidp = nullptr; - engine.async_jobs += job_; - job_ = nullptr; - } -} -template -BorrowedPtr::BorrowedPtr (T *ptr, const std::function &deleter) -{ - if (!ptr) - return; - std::shared_ptr deleter_p (nullptr, [ptr, deleter] (void*) { - // printerr ("%s::deleter_p: is_main_thread=%d\n", typeid_name>(), this_thread_is_ase()); - deleter (ptr); - }); // destroyed in [main-thread] - auto jobfunc = [deleter_p] () { - // printerr ("%s::jobfunc: thread_is_engine_thread=%d\n", typeid_name>(), AudioEngine::thread_is_engine()); - }; // runs in [audio-thread] - job_ = AudioEngine::new_engine_job (jobfunc); - ASE_ASSERT_RETURN (job_->voidp == nullptr); // user data - job_->voidp = ptr; - ASE_ASSERT_RETURN (job_->voidp != nullptr); -} +AudioEngine& make_audio_engine (const VoidF &owner_wakeup, uint sample_rate, SpeakerArrangement speakerarrangement); } // Ase diff --git a/ase/main.cc b/ase/main.cc index 4fcc0b73..0ef18429 100644 --- a/ase/main.cc +++ b/ase/main.cc @@ -504,18 +504,19 @@ main (int argc, char *argv[]) } // start audio engine - AudioEngine &ae = make_audio_engine (main_loop_wakeup, 48000, SpeakerArrangement::STEREO); - main_config_.engine = &ae; - ae.start_threads (); - const uint loopdispatcherid = main_loop->exec_dispatcher ([&ae] (const LoopState &state) -> bool { + AudioEngine &audio_engine = make_audio_engine (main_loop_wakeup, 48000, SpeakerArrangement::STEREO); + main_config_.engine = &audio_engine; + audio_engine.start_threads (); + /*const uint loopdispatcherid =*/ + main_loop->exec_dispatcher ([&audio_engine] (const LoopState &state) -> bool { switch (state.phase) { case LoopState::PREPARE: - return main_rt_jobs_pending() || ae.ipc_pending(); + return main_rt_jobs_pending() || audio_engine.ipc_pending(); case LoopState::CHECK: - return main_rt_jobs_pending() || ae.ipc_pending(); + return main_rt_jobs_pending() || audio_engine.ipc_pending(); case LoopState::DISPATCH: - ae.ipc_dispatch(); + audio_engine.ipc_dispatch(); main_rt_jobs_process(); return true; default: @@ -609,16 +610,15 @@ main (int argc, char *argv[]) const int exitcode = main_loop->run(); assert_return (main_loop, -1); // ptr must be kept around - // loop ended, close socket and shutdown - wss->shutdown(); + // cleanup + wss->shutdown(); // close socket, allow no more calls main_config_.web_socket_server = nullptr; wss = nullptr; // halt audio engine, join its threads, dispatch cleanups - ae.stop_threads(); - main_loop->remove (loopdispatcherid); - while (ae.ipc_pending()) - ae.ipc_dispatch(); + audio_engine.set_project (nullptr); + audio_engine.stop_threads(); + main_loop->iterate_pending(); main_config_.engine = nullptr; return exitcode; diff --git a/ase/processor.hh b/ase/processor.hh index 9265f6fd..0759e2d0 100644 --- a/ase/processor.hh +++ b/ase/processor.hh @@ -99,7 +99,7 @@ class AudioProcessor : public std::enable_shared_from_this, publ friend class ProcessorManager; friend class DeviceImpl; friend class NativeDeviceImpl; - friend class AudioEngineImpl; + friend class AudioEngineThread; struct OConnection { AudioProcessor *proc = nullptr; IBusId ibusid = {}; bool operator== (const OConnection &o) const { return proc == o.proc && ibusid == o.ibusid; } diff --git a/ase/project.cc b/ase/project.cc index 1c204bde..d529981d 100644 --- a/ase/project.cc +++ b/ase/project.cc @@ -14,7 +14,7 @@ namespace Ase { -static std::vector all_projects; +static std::vector &all_projects = *new std::vector(); // == Project == ProjectP