/*
- Copyright (C) 1999-2004 Paul Davis
+ Copyright (C) 1999-2004 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <cmath>
#include <unistd.h>
-#include "ardour/timestamps.h"
-
#include "pbd/error.h"
-#include <glibmm/thread.h>
+#include "pbd/enumwriter.h"
+#include "pbd/stacktrace.h"
+#include "pbd/pthread_utils.h"
-#include "ardour/ardour.h"
-#include "ardour/session.h"
-#include "ardour/audio_diskstream.h"
+#include "ardour/debug.h"
+#include "ardour/session_event.h"
#include "i18n.h"
using namespace ARDOUR;
using namespace PBD;
-MultiAllocSingleReleasePool Session::Event::pool ("event", sizeof (Session::Event), 512);
-
-static const char* event_names[] = {
- "SetTransportSpeed",
- "SetDiskstreamSpeed",
- "Locate",
- "LocateRoll",
- "LocateRollLocate",
- "SetLoop",
- "PunchIn",
- "PunchOut",
- "RangeStop",
- "RangeLocate",
- "Overwrite",
- "SetSlaveSource",
- "Audition",
- "InputConfigurationChange",
- "SetAudioRange",
- "SetMusicRange",
- "SetPlayRange",
- "StopOnce",
- "AutoLoop"
-};
+PerThreadPool* SessionEvent::pool;
+
+void
+SessionEvent::init_event_pool ()
+{
+ pool = new PerThreadPool;
+}
+
+bool
+SessionEvent::has_per_thread_pool ()
+{
+ return pool->has_per_thread_pool ();
+}
void
-Session::add_event (nframes_t frame, Event::Type type, nframes_t target_frame)
+SessionEvent::create_per_thread_pool (const std::string& name, uint32_t nitems)
{
- Event* ev = new Event (type, Event::Add, frame, target_frame, 0);
+ /* this is a per-thread call that simply creates a thread-private ptr to
+ a CrossThreadPool for use by this thread whenever events are allocated/released
+ from SessionEvent::pool()
+ */
+ pool->create_per_thread_pool (name, sizeof (SessionEvent), nitems);
+}
+
+SessionEvent::SessionEvent (Type t, Action a, framepos_t when, framepos_t where, double spd, bool yn, bool yn2, bool yn3)
+ : type (t)
+ , action (a)
+ , action_frame (when)
+ , target_frame (where)
+ , speed (spd)
+ , yes_or_no (yn)
+ , second_yes_or_no (yn2)
+ , third_yes_or_no (yn3)
+ , event_loop (0)
+{
+ DEBUG_TRACE (DEBUG::SessionEvents, string_compose ("NEW SESSION EVENT, type = %1 action = %2\n", enum_2_string (type), enum_2_string (action)));
+}
+
+void *
+SessionEvent::operator new (size_t)
+{
+ CrossThreadPool* p = pool->per_thread_pool ();
+ SessionEvent* ev = static_cast<SessionEvent*> (p->alloc ());
+ DEBUG_TRACE (DEBUG::SessionEvents, string_compose ("%1 Allocating SessionEvent from %2 ev @ %3 pool size %4 free %5 used %6\n", pthread_name(), p->name(), ev,
+ p->total(), p->available(), p->used()));
+
+ ev->own_pool = p;
+ return ev;
+}
+
+void
+SessionEvent::operator delete (void *ptr, size_t /*size*/)
+{
+ Pool* p = pool->per_thread_pool (false);
+ SessionEvent* ev = static_cast<SessionEvent*> (ptr);
+
+ DEBUG_TRACE (DEBUG::SessionEvents, string_compose (
+ "%1 Deleting SessionEvent @ %2 type %3 action %4 ev thread pool = %5 ev pool = %6 size %7 free %8 used %9\n",
+ pthread_name(), ev, enum_2_string (ev->type), enum_2_string (ev->action), p->name(), ev->own_pool->name(), ev->own_pool->total(), ev->own_pool->available(), ev->own_pool->used()
+ ));
+
+ if (p && p == ev->own_pool) {
+ p->release (ptr);
+ } else {
+ assert(ev->own_pool);
+ ev->own_pool->push (ev);
+ DEBUG_TRACE (DEBUG::SessionEvents, string_compose ("%1 was wrong thread for this pool, pushed event onto pending list, will be deleted on next alloc from %2 pool size %3 free %4 used %5 pending %6\n",
+ pthread_name(), ev->own_pool->name(),
+ ev->own_pool->total(), ev->own_pool->available(), ev->own_pool->used(),
+ ev->own_pool->pending_size()));
+ }
+}
+
+void
+SessionEventManager::add_event (framepos_t frame, SessionEvent::Type type, framepos_t target_frame)
+{
+ SessionEvent* ev = new SessionEvent (type, SessionEvent::Add, frame, target_frame, 0);
queue_event (ev);
}
void
-Session::remove_event (nframes_t frame, Event::Type type)
+SessionEventManager::remove_event (framepos_t frame, SessionEvent::Type type)
{
- Event* ev = new Event (type, Event::Remove, frame, 0, 0);
+ SessionEvent* ev = new SessionEvent (type, SessionEvent::Remove, frame, 0, 0);
queue_event (ev);
}
void
-Session::replace_event (Event::Type type, nframes_t frame, nframes_t target)
+SessionEventManager::replace_event (SessionEvent::Type type, framepos_t frame, framepos_t target)
{
- Event* ev = new Event (type, Event::Replace, frame, target, 0);
+ SessionEvent* ev = new SessionEvent (type, SessionEvent::Replace, frame, target, 0);
queue_event (ev);
}
void
-Session::clear_events (Event::Type type)
+SessionEventManager::clear_events (SessionEvent::Type type)
{
- Event* ev = new Event (type, Event::Clear, 0, 0, 0);
+ SessionEvent* ev = new SessionEvent (type, SessionEvent::Clear, SessionEvent::Immediate, 0, 0);
queue_event (ev);
}
+void
+SessionEventManager::clear_events (SessionEvent::Type type, boost::function<void (void)> after)
+{
+ SessionEvent* ev = new SessionEvent (type, SessionEvent::Clear, SessionEvent::Immediate, 0, 0);
+ ev->rt_slot = after;
+
+ /* in the calling thread, after the clear is complete, arrange to flush things from the event
+ pool pending list (i.e. to make sure they are really back in the free list and available
+ for future events).
+ */
+
+ ev->event_loop = PBD::EventLoop::get_event_loop_for_thread ();
+ if (ev->event_loop) {
+ ev->rt_return = boost::bind (&CrossThreadPool::flush_pending_with_ev, ev->event_pool(), _1);
+ }
+
+ queue_event (ev);
+}
void
-Session::dump_events () const
+SessionEventManager::dump_events () const
{
cerr << "EVENT DUMP" << endl;
for (Events::const_iterator i = events.begin(); i != events.end(); ++i) {
- cerr << "\tat " << (*i)->action_frame << ' ' << (*i)->type << " target = " << (*i)->target_frame << endl;
+
+ cerr << "\tat " << (*i)->action_frame << ' ' << enum_2_string ((*i)->type) << " target = " << (*i)->target_frame << endl;
}
cerr << "Next event: ";
- if ((Events::const_iterator) next_event == events.end()) {
+ if ((Events::const_iterator) next_event == events.end()) {
cerr << "none" << endl;
} else {
- cerr << "at " << (*next_event)->action_frame << ' '
- << (*next_event)->type << " target = "
+ cerr << "at " << (*next_event)->action_frame << ' '
+ << enum_2_string ((*next_event)->type) << " target = "
<< (*next_event)->target_frame << endl;
}
cerr << "Immediate events pending:\n";
for (Events::const_iterator i = immediate_events.begin(); i != immediate_events.end(); ++i) {
- cerr << "\tat " << (*i)->action_frame << ' ' << (*i)->type << " target = " << (*i)->target_frame << endl;
+ cerr << "\tat " << (*i)->action_frame << ' ' << enum_2_string((*i)->type) << " target = " << (*i)->target_frame << endl;
}
cerr << "END EVENT_DUMP" << endl;
}
void
-Session::queue_event (Event* ev)
-{
- if (_state_of_the_state & Loading) {
- merge_event (ev);
- } else {
- pending_events.write (&ev, 1);
- }
-}
-
-void
-Session::merge_event (Event* ev)
+SessionEventManager::merge_event (SessionEvent* ev)
{
switch (ev->action) {
- case Event::Remove:
+ case SessionEvent::Remove:
_remove_event (ev);
delete ev;
return;
- case Event::Replace:
+ case SessionEvent::Replace:
_replace_event (ev);
return;
- case Event::Clear:
+ case SessionEvent::Clear:
_clear_event_type (ev->type);
- delete ev;
+ /* run any additional realtime callback, if any */
+ if (ev->rt_slot) {
+ ev->rt_slot ();
+ }
+ if (ev->event_loop) {
+ /* run non-realtime callback (in some other thread) */
+ ev->event_loop->call_slot (MISSING_INVALIDATOR, boost::bind (ev->rt_return, ev));
+ } else {
+ delete ev;
+ }
return;
-
- case Event::Add:
+
+ case SessionEvent::Add:
break;
}
/* try to handle immediate events right here */
- if (ev->action_frame == 0) {
+ if (ev->action_frame == SessionEvent::Immediate) {
process_event (ev);
return;
}
-
+
switch (ev->type) {
- case Event::AutoLoop:
- case Event::StopOnce:
+ case SessionEvent::AutoLoop:
+ case SessionEvent::AutoLoopDeclick:
+ case SessionEvent::StopOnce:
_clear_event_type (ev->type);
break;
default:
for (Events::iterator i = events.begin(); i != events.end(); ++i) {
if ((*i)->type == ev->type && (*i)->action_frame == ev->action_frame) {
- error << string_compose(_("Session: cannot have two events of type %1 at the same frame (%2)."),
- event_names[ev->type], ev->action_frame) << endmsg;
+ error << string_compose(_("Session: cannot have two events of type %1 at the same frame (%2)."),
+ enum_2_string (ev->type), ev->action_frame) << endmsg;
return;
}
}
}
events.insert (events.begin(), ev);
- events.sort (Event::compare);
+ events.sort (SessionEvent::compare);
next_event = events.begin();
set_next_event ();
}
/** @return true when @a ev is deleted. */
bool
-Session::_replace_event (Event* ev)
+SessionEventManager::_replace_event (SessionEvent* ev)
{
bool ret = false;
Events::iterator i;
events.insert (events.begin(), ev);
}
- events.sort (Event::compare);
+ events.sort (SessionEvent::compare);
next_event = events.end();
set_next_event ();
/** @return true when @a ev is deleted. */
bool
-Session::_remove_event (Session::Event* ev)
+SessionEventManager::_remove_event (SessionEvent* ev)
{
bool ret = false;
Events::iterator i;
-
+
for (i = events.begin(); i != events.end(); ++i) {
if ((*i)->type == ev->type && (*i)->action_frame == ev->action_frame) {
if ((*i) == ev) {
if (i == next_event) {
++next_event;
}
- events.erase (i);
+ i = events.erase (i);
break;
}
}
}
void
-Session::_clear_event_type (Event::Type type)
+SessionEventManager::_clear_event_type (SessionEvent::Type type)
{
Events::iterator i, tmp;
-
+
for (i = events.begin(); i != events.end(); ) {
tmp = i;
set_next_event ();
}
-void
-Session::set_next_event ()
-{
- if (events.empty()) {
- next_event = events.end();
- return;
- }
-
- if (next_event == events.end()) {
- next_event = events.begin();
- }
-
- if ((*next_event)->action_frame > _transport_frame) {
- next_event = events.begin();
- }
-
- for (; next_event != events.end(); ++next_event) {
- if ((*next_event)->action_frame >= _transport_frame) {
- break;
- }
- }
-}
-
-void
-Session::process_event (Event* ev)
-{
- bool remove = true;
- bool del = true;
-
- /* if we're in the middle of a state change (i.e. waiting
- for the butler thread to complete the non-realtime
- part of the change), we'll just have to queue this
- event for a time when the change is complete.
- */
-
- if (non_realtime_work_pending()) {
-
- /* except locates, which we have the capability to handle */
-
- if (ev->type != Event::Locate) {
- immediate_events.insert (immediate_events.end(), ev);
- _remove_event (ev);
- return;
- }
- }
-
- //printf("Processing event: %s\n", event_names[ev->type]);
-
- switch (ev->type) {
- case Event::SetLoop:
- set_play_loop (ev->yes_or_no);
- break;
-
- case Event::AutoLoop:
- if (play_loop) {
- start_locate (ev->target_frame, true, false, Config->get_seamless_loop());
- }
- remove = false;
- del = false;
- break;
-
- case Event::Locate:
- if (ev->yes_or_no) {
- // cerr << "forced locate to " << ev->target_frame << endl;
- locate (ev->target_frame, false, true, false);
- } else {
- // cerr << "soft locate to " << ev->target_frame << endl;
- start_locate (ev->target_frame, false, true, false);
- }
- _send_smpte_update = true;
- break;
-
- case Event::LocateRoll:
- if (ev->yes_or_no) {
- // cerr << "forced locate to+roll " << ev->target_frame << endl;
- locate (ev->target_frame, true, true, false);
- } else {
- // cerr << "soft locate to+roll " << ev->target_frame << endl;
- start_locate (ev->target_frame, true, true, false);
- }
- _send_smpte_update = true;
- break;
-
- case Event::LocateRollLocate:
- // locate is handled by ::request_roll_at_and_return()
- _requested_return_frame = ev->target_frame;
- request_locate (ev->target2_frame, true);
- break;
-
-
- case Event::SetTransportSpeed:
- set_transport_speed (ev->speed, ev->yes_or_no);
- break;
-
- case Event::PunchIn:
- // cerr << "PunchIN at " << transport_frame() << endl;
- if (config.get_punch_in() && record_status() == Enabled) {
- enable_record ();
- }
- remove = false;
- del = false;
- break;
-
- case Event::PunchOut:
- // cerr << "PunchOUT at " << transport_frame() << endl;
- if (config.get_punch_out()) {
- step_back_from_record ();
- }
- remove = false;
- del = false;
- break;
-
- case Event::StopOnce:
- if (!non_realtime_work_pending()) {
- stop_transport (ev->yes_or_no);
- _clear_event_type (Event::StopOnce);
- }
- remove = false;
- del = false;
- break;
-
- case Event::RangeStop:
- if (!non_realtime_work_pending()) {
- stop_transport (ev->yes_or_no);
- }
- remove = false;
- del = false;
- break;
-
- case Event::RangeLocate:
- start_locate (ev->target_frame, true, true, false);
- remove = false;
- del = false;
- break;
-
- case Event::Overwrite:
- overwrite_some_buffers (static_cast<Diskstream*>(ev->ptr));
- break;
-
- case Event::SetDiskstreamSpeed:
- set_diskstream_speed (static_cast<Diskstream*> (ev->ptr), ev->speed);
- break;
-
- case Event::SetSlaveSource:
- set_slave_source (ev->slave);
- break;
-
- case Event::Audition:
- set_audition (ev->region);
- // drop reference to region
- ev->region.reset ();
- break;
-
- case Event::InputConfigurationChange:
- post_transport_work = PostTransportWork (post_transport_work | PostTransportInputChange);
- schedule_butler_transport_work ();
- break;
-
- case Event::SetAudioRange:
- current_audio_range = ev->audio_range;
- setup_auto_play ();
- break;
-
- case Event::SetPlayRange:
- set_play_range (ev->yes_or_no);
- break;
-
- default:
- fatal << string_compose(_("Programming error: illegal event type in process_event (%1)"), ev->type) << endmsg;
- /*NOTREACHED*/
- break;
- };
-
- if (remove) {
- del = del && !_remove_event (ev);
- }
-
- if (del) {
- delete ev;
- }
-}