2008-06-02 17:41:35 -04:00
|
|
|
/*
|
2019-08-02 22:01:25 -04:00
|
|
|
* Copyright (C) 2006-2016 David Robillard <d@drobilla.net>
|
|
|
|
* Copyright (C) 2007-2018 Paul Davis <paul@linuxaudiosystems.com>
|
|
|
|
* Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com>
|
|
|
|
* Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
|
|
|
|
* Copyright (C) 2012-2016 Tim Mayberry <mojofunk@gmail.com>
|
|
|
|
* Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
|
|
|
|
* Copyright (C) 2016 Nick Mainsbridge <mainsbridge@gmail.com>
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <float.h>
|
|
|
|
#include <cerrno>
|
|
|
|
#include <ctime>
|
|
|
|
#include <cmath>
|
|
|
|
#include <iomanip>
|
|
|
|
#include <algorithm>
|
|
|
|
|
2010-05-18 23:03:28 -04:00
|
|
|
#include <glibmm/fileutils.h>
|
2012-06-23 01:06:54 -04:00
|
|
|
#include <glibmm/miscutils.h>
|
2010-05-18 23:03:28 -04:00
|
|
|
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "pbd/xml++.h"
|
|
|
|
#include "pbd/pthread_utils.h"
|
|
|
|
#include "pbd/basename.h"
|
2019-10-14 21:00:32 -04:00
|
|
|
#include "pbd/timing.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2019-10-25 15:13:51 -04:00
|
|
|
#include "evoral/Control.h"
|
|
|
|
#include "evoral/EventSink.h"
|
2014-11-30 18:51:24 -05:00
|
|
|
|
2009-10-24 09:26:56 -04:00
|
|
|
#include "ardour/debug.h"
|
2015-03-28 23:24:41 -04:00
|
|
|
#include "ardour/file_source.h"
|
|
|
|
#include "ardour/midi_channel_filter.h"
|
2016-11-08 20:34:45 -05:00
|
|
|
#include "ardour/midi_cursor.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/midi_model.h"
|
|
|
|
#include "ardour/midi_source.h"
|
2015-03-28 23:24:41 -04:00
|
|
|
#include "ardour/midi_state_tracker.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/session.h"
|
|
|
|
#include "ardour/session_directory.h"
|
|
|
|
#include "ardour/source_factory.h"
|
2016-11-08 20:34:45 -05:00
|
|
|
#include "ardour/tempo.h"
|
2022-07-21 20:01:10 -04:00
|
|
|
#include "ardour/evoral_types_convert.h"
|
|
|
|
#include "ardour/types_convert.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-07-14 14:44:52 -04:00
|
|
|
#include "pbd/i18n.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2012-05-24 02:09:29 -04:00
|
|
|
namespace ARDOUR { template <typename T> class MidiRingBuffer; }
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
using namespace std;
|
|
|
|
using namespace ARDOUR;
|
|
|
|
using namespace PBD;
|
|
|
|
|
2009-02-16 13:08:22 -05:00
|
|
|
MidiSource::MidiSource (Session& s, string name, Source::Flag flags)
|
2009-10-22 10:46:03 -04:00
|
|
|
: Source(s, DataType::MIDI, name, flags)
|
|
|
|
, _writing(false)
|
Fix MIDI loop recording.
This changes how things work a bit, but I am committing it for 3.0 since the
previous revision often crashed (and never worked), this one seems to work
fine, and the code is quite a bit more cogent.
I have tested the following use cases with live input and audible output:
* Non-loop recording, armed before roll
* Non-loop recording, arm while rolling
* Loop recording, armed before roll
* Loop recording, arm during roll
In the last case, the source/region is created starting at the loop start
rather than the current transport frame as usual so time makes sense when it
wraps around.
See the documentation added to the code for details, but the basic idea here is
to simply push MIDI events to the source with increasing monotonic time,
ignoring looping altogether. Essentially we pretend the loop does not exist,
but the source knows all the details so it can implement whatever behaviour is
appropriate.
Currently, this is simply recording a complete non-destructive copy of the
input, which is a good thing. Perhaps not what the user expects of loop
recording, but at least it works and is one sensible option. We will need to
add more later.
Display while recording is a little bit wacky, but whatever.
git-svn-id: svn://localhost/ardour2/branches/3.0@13940 d708f5d6-7413-0410-9779-e7cbd77b26cf
2013-01-21 01:00:15 -05:00
|
|
|
, _capture_length(0)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2009-10-14 12:10:01 -04:00
|
|
|
MidiSource::MidiSource (Session& s, const XMLNode& node)
|
2009-10-22 10:46:03 -04:00
|
|
|
: Source(s, node)
|
|
|
|
, _writing(false)
|
Fix MIDI loop recording.
This changes how things work a bit, but I am committing it for 3.0 since the
previous revision often crashed (and never worked), this one seems to work
fine, and the code is quite a bit more cogent.
I have tested the following use cases with live input and audible output:
* Non-loop recording, armed before roll
* Non-loop recording, arm while rolling
* Loop recording, armed before roll
* Loop recording, arm during roll
In the last case, the source/region is created starting at the loop start
rather than the current transport frame as usual so time makes sense when it
wraps around.
See the documentation added to the code for details, but the basic idea here is
to simply push MIDI events to the source with increasing monotonic time,
ignoring looping altogether. Essentially we pretend the loop does not exist,
but the source knows all the details so it can implement whatever behaviour is
appropriate.
Currently, this is simply recording a complete non-destructive copy of the
input, which is a good thing. Perhaps not what the user expects of loop
recording, but at least it works and is one sensible option. We will need to
add more later.
Display while recording is a little bit wacky, but whatever.
git-svn-id: svn://localhost/ardour2/branches/3.0@13940 d708f5d6-7413-0410-9779-e7cbd77b26cf
2013-01-21 01:00:15 -05:00
|
|
|
, _capture_length(0)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2009-10-15 14:56:11 -04:00
|
|
|
if (set_state (node, Stateful::loading_state_version)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
throw failed_constructor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MidiSource::~MidiSource ()
|
|
|
|
{
|
2017-02-28 11:27:21 -05:00
|
|
|
/* invalidate any existing iterators */
|
|
|
|
Invalidated (false);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode&
|
2022-04-06 23:56:32 -04:00
|
|
|
MidiSource::get_state () const
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
XMLNode& node (Source::get_state());
|
|
|
|
|
|
|
|
if (_captured_for.length()) {
|
2016-08-28 03:56:16 -04:00
|
|
|
node.set_property ("captured-for", _captured_for);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2010-07-13 20:58:15 -04:00
|
|
|
for (InterpolationStyleMap::const_iterator i = _interpolation_style.begin(); i != _interpolation_style.end(); ++i) {
|
|
|
|
XMLNode* child = node.add_child (X_("InterpolationStyle"));
|
2016-08-28 03:56:16 -04:00
|
|
|
child->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (i->first));
|
2022-07-21 20:01:10 -04:00
|
|
|
child->set_property (X_("style"), i->second);
|
2010-07-13 20:58:15 -04:00
|
|
|
}
|
2010-08-09 18:23:23 -04:00
|
|
|
|
|
|
|
for (AutomationStateMap::const_iterator i = _automation_state.begin(); i != _automation_state.end(); ++i) {
|
|
|
|
XMLNode* child = node.add_child (X_("AutomationState"));
|
2016-08-28 03:56:16 -04:00
|
|
|
child->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (i->first));
|
2022-07-21 19:33:30 -04:00
|
|
|
child->set_property (X_("state"), i->second);
|
2010-08-09 18:23:23 -04:00
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2009-10-20 20:15:42 -04:00
|
|
|
MidiSource::set_state (const XMLNode& node, int /*version*/)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2016-08-28 03:56:16 -04:00
|
|
|
node.get_property ("captured-for", _captured_for);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2016-08-28 03:56:16 -04:00
|
|
|
std::string str;
|
2010-07-13 20:58:15 -04:00
|
|
|
XMLNodeList children = node.children ();
|
|
|
|
for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
|
|
|
|
if ((*i)->name() == X_("InterpolationStyle")) {
|
2016-08-28 03:56:16 -04:00
|
|
|
if (!(*i)->get_property (X_("parameter"), str)) {
|
2010-07-13 20:58:15 -04:00
|
|
|
error << _("Missing parameter property on InterpolationStyle") << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
2016-08-28 03:56:16 -04:00
|
|
|
Evoral::Parameter p = EventTypeMap::instance().from_symbol (str);
|
2010-07-13 20:58:15 -04:00
|
|
|
|
2018-02-23 14:56:58 -05:00
|
|
|
switch (p.type()) {
|
|
|
|
case MidiCCAutomation:
|
|
|
|
case MidiPgmChangeAutomation: break;
|
|
|
|
case MidiChannelPressureAutomation: break;
|
|
|
|
case MidiNotePressureAutomation: break;
|
|
|
|
case MidiPitchBenderAutomation: break;
|
|
|
|
case MidiSystemExclusiveAutomation:
|
|
|
|
cerr << "Parameter \"" << str << "\" is system exclusive - no automation possible!\n";
|
|
|
|
continue;
|
|
|
|
default:
|
|
|
|
cerr << "Parameter \"" << str << "\" found for MIDI source ... not legal; ignoring this parameter\n";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-07-21 20:01:10 -04:00
|
|
|
/* backwards compat, older versions (<= 7000) saved an empty string for non default */
|
|
|
|
if ((*i)->get_property (X_("style"), str)) {
|
|
|
|
if (str.empty ()) {
|
|
|
|
set_interpolation_of (p, EventTypeMap::instance().interpolation_of (p) == AutomationList::Discrete ? AutomationList::Linear : AutomationList::Discrete);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
AutomationList::InterpolationStyle s;
|
|
|
|
if (!(*i)->get_property (X_("style"), s)) {
|
2010-07-13 20:58:15 -04:00
|
|
|
error << _("Missing style property on InterpolationStyle") << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
set_interpolation_of (p, s);
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2010-08-09 18:23:23 -04:00
|
|
|
} else if ((*i)->name() == X_("AutomationState")) {
|
2016-08-28 03:56:16 -04:00
|
|
|
if (!(*i)->get_property (X_("parameter"), str)) {
|
2010-08-09 18:23:23 -04:00
|
|
|
error << _("Missing parameter property on AutomationState") << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
2016-08-28 03:56:16 -04:00
|
|
|
Evoral::Parameter p = EventTypeMap::instance().from_symbol (str);
|
2010-08-09 18:23:23 -04:00
|
|
|
|
2022-07-21 19:33:30 -04:00
|
|
|
/* backwards compat, older versions (<= 7000) saved an empty string for "off" */
|
|
|
|
if ((*i)->get_property (X_("state"), str)) {
|
|
|
|
if (str.empty ()) {
|
|
|
|
set_automation_state_of (p, Off);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AutoState s;
|
|
|
|
if (!(*i)->get_property (X_("state"), s)) {
|
2010-08-09 18:23:23 -04:00
|
|
|
error << _("Missing state property on AutomationState") << endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
set_automation_state_of (p, s);
|
2010-07-13 20:58:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-20 15:36:11 -05:00
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiSource::invalidate (const WriterLock& lock)
|
2014-11-20 15:36:11 -05:00
|
|
|
{
|
2016-11-08 20:34:45 -05:00
|
|
|
Invalidated(_session.transport_rolling());
|
2014-11-20 15:36:11 -05:00
|
|
|
}
|
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
timecnt_t
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiSource::midi_read (const ReaderLock& lm,
|
2019-04-07 21:45:12 -04:00
|
|
|
Evoral::EventSink<samplepos_t>& dst,
|
2020-09-20 18:34:09 -04:00
|
|
|
timepos_t const & source_start,
|
2021-03-03 12:03:54 -05:00
|
|
|
timepos_t const & start,
|
2020-09-20 18:34:09 -04:00
|
|
|
timecnt_t const & cnt,
|
|
|
|
Temporal::Range* loop_range,
|
2016-11-08 20:34:45 -05:00
|
|
|
MidiCursor& cursor,
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiNoteTracker* tracker,
|
2015-03-28 23:24:41 -04:00
|
|
|
MidiChannelFilter* filter,
|
2020-09-20 18:34:09 -04:00
|
|
|
const std::set<Evoral::Parameter>& filtered)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2022-03-30 14:56:04 -04:00
|
|
|
Timing t;
|
|
|
|
|
2014-11-19 20:47:18 -05:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO,
|
|
|
|
string_compose ("MidiSource::midi_read() %5 sstart %1 start %2 cnt %3 tracker %4\n",
|
|
|
|
source_start, start, cnt, tracker, name()));
|
2011-05-30 17:37:58 -04:00
|
|
|
|
2016-09-13 15:10:04 -04:00
|
|
|
if (!_model) {
|
2021-03-03 12:03:54 -05:00
|
|
|
return timecnt_t (read_unlocked (lm, dst, source_start, start, cnt, loop_range, tracker, filter), start);
|
2016-09-13 15:10:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find appropriate model iterator
|
2020-09-20 18:34:09 -04:00
|
|
|
|
2016-11-08 20:34:45 -05:00
|
|
|
const bool linear_read = cursor.last_read_end != 0 && start == cursor.last_read_end;
|
2020-09-20 18:34:09 -04:00
|
|
|
if (!linear_read || !cursor.iter.valid()) {
|
2016-11-08 20:34:45 -05:00
|
|
|
/* Cached iterator is invalid, search for the first event past start.
|
|
|
|
Note that multiple tracks can use a MidiSource simultaneously, so
|
|
|
|
all playback state must be in parameters (the cursor) and must not
|
|
|
|
be cached in the source of model itself.
|
2022-10-23 22:58:42 -04:00
|
|
|
See https://tracker.ardour.org/view.php?id=6541
|
2016-11-08 20:34:45 -05:00
|
|
|
*/
|
|
|
|
cursor.connect(Invalidated);
|
2020-09-20 18:34:09 -04:00
|
|
|
cursor.iter = _model->begin (start.beats(), false, filtered, &cursor.active_notes);
|
2016-11-08 20:34:45 -05:00
|
|
|
cursor.active_notes.clear();
|
2016-09-13 15:10:04 -04:00
|
|
|
}
|
2014-11-20 15:36:11 -05:00
|
|
|
|
2016-11-08 20:34:45 -05:00
|
|
|
cursor.last_read_end = start + cnt;
|
2014-11-20 15:36:11 -05:00
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
// Find appropriate model iterator
|
|
|
|
Evoral::Sequence<Temporal::Beats>::const_iterator& i = cursor.iter;
|
|
|
|
|
2016-09-13 15:10:04 -04:00
|
|
|
// Copy events in [start, start + cnt) into dst
|
2020-09-20 18:34:09 -04:00
|
|
|
|
|
|
|
const Temporal::Beats source_start_beats = source_start.beats();
|
|
|
|
const Temporal::Beats region_start_beats = start.beats();
|
|
|
|
const Temporal::Beats cnt_beats = cnt.beats ();
|
|
|
|
|
|
|
|
const Temporal::Beats end = source_start_beats + region_start_beats + cnt_beats;
|
|
|
|
const Temporal::Beats session_source_start = (source_start + start).beats();
|
|
|
|
|
2016-09-13 15:10:04 -04:00
|
|
|
for (; i != _model->end(); ++i) {
|
2016-06-28 11:19:59 -04:00
|
|
|
|
2016-09-13 15:10:04 -04:00
|
|
|
// Offset by source start to convert event time to session time
|
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
const Temporal::Beats session_event_beats = source_start_beats + i->time();
|
2010-07-27 10:09:16 -04:00
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
if (session_event_beats < session_source_start) {
|
2016-09-13 15:10:04 -04:00
|
|
|
/* event too early */
|
2020-09-20 18:34:09 -04:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("%1: skip event, too early @ %2 for %3\n", _name, session_event_beats, session_source_start));
|
2016-09-13 15:10:04 -04:00
|
|
|
continue;
|
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
} else if (session_event_beats >= end) {
|
2016-09-13 15:10:04 -04:00
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("%1: reached end (%2) with event @ %3\n", _name, end, session_event_beats));
|
2016-09-13 15:10:04 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* in range */
|
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
timepos_t seb = timepos_t (session_event_beats);
|
|
|
|
samplepos_t time_samples = seb.samples();
|
|
|
|
|
2016-09-13 15:10:04 -04:00
|
|
|
if (loop_range) {
|
2020-09-20 18:34:09 -04:00
|
|
|
time_samples = loop_range->squish (seb).samples();
|
2016-09-13 15:10:04 -04:00
|
|
|
}
|
|
|
|
|
2016-11-06 18:12:49 -05:00
|
|
|
const uint8_t status = i->buffer()[0];
|
|
|
|
const bool is_channel_event = (0x80 <= (status & 0xF0)) && (status <= 0xE0);
|
2020-09-20 18:34:09 -04:00
|
|
|
|
2016-11-06 18:12:49 -05:00
|
|
|
if (filter && is_channel_event) {
|
|
|
|
/* Copy event so the filter can modify the channel. I'm not
|
2020-09-20 18:34:09 -04:00
|
|
|
* sure if this is necessary here (channels are mapped later in
|
|
|
|
* buffers anyway), but it preserves existing behaviour without
|
|
|
|
* destroying events in the model during read.
|
|
|
|
*/
|
2017-09-24 12:03:54 -04:00
|
|
|
Evoral::Event<Temporal::Beats> ev(*i, true);
|
2020-09-20 18:34:09 -04:00
|
|
|
|
2016-11-06 18:12:49 -05:00
|
|
|
if (!filter->filter(ev.buffer(), ev.size())) {
|
2019-10-14 21:00:32 -04:00
|
|
|
dst.write (time_samples, ev.event_type(), ev.size(), ev.buffer());
|
2016-11-06 18:12:49 -05:00
|
|
|
} else {
|
2020-09-20 18:34:09 -04:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("%1: filter event @ %2 type %3 size %4\n", _name, time_samples, i->event_type(), i->size()));
|
2016-11-06 18:12:49 -05:00
|
|
|
}
|
|
|
|
} else {
|
2017-09-18 12:39:17 -04:00
|
|
|
dst.write (time_samples, i->event_type(), i->size(), i->buffer());
|
2016-11-06 18:12:49 -05:00
|
|
|
}
|
2016-09-13 15:10:04 -04:00
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
|
2016-09-13 15:10:04 -04:00
|
|
|
#ifndef NDEBUG
|
|
|
|
if (DEBUG_ENABLED(DEBUG::MidiSourceIO)) {
|
|
|
|
DEBUG_STR_DECL(a);
|
2020-09-20 18:34:09 -04:00
|
|
|
DEBUG_STR_APPEND(a, string_compose ("%1 added event @ %2 (%3) sz %4 within %5 .. %6 ", _name, time_samples, session_event_beats, i->size(), source_start + start, end));
|
2016-09-13 15:10:04 -04:00
|
|
|
for (size_t n=0; n < i->size(); ++n) {
|
|
|
|
DEBUG_STR_APPEND(a,hex);
|
|
|
|
DEBUG_STR_APPEND(a,"0x");
|
|
|
|
DEBUG_STR_APPEND(a,(int)i->buffer()[n]);
|
|
|
|
DEBUG_STR_APPEND(a,' ');
|
2009-10-19 13:05:22 -04:00
|
|
|
}
|
2016-09-13 15:10:04 -04:00
|
|
|
DEBUG_STR_APPEND(a,'\n');
|
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, DEBUG_STR(a).str());
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (tracker) {
|
|
|
|
tracker->track (*i);
|
2009-02-15 12:30:42 -05:00
|
|
|
}
|
2009-02-14 20:24:26 -05:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2022-03-30 14:56:04 -04:00
|
|
|
t.update ();
|
2016-09-13 15:10:04 -04:00
|
|
|
|
|
|
|
return cnt;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
timecnt_t
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiSource::midi_write (const WriterLock& lm,
|
2017-09-18 12:39:17 -04:00
|
|
|
MidiRingBuffer<samplepos_t>& source,
|
2020-09-20 18:34:09 -04:00
|
|
|
timepos_t const & source_start,
|
|
|
|
timecnt_t const & cnt)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2020-09-20 18:34:09 -04:00
|
|
|
const timecnt_t ret = write_unlocked (lm, source, source_start, cnt);
|
2011-07-20 14:13:03 -04:00
|
|
|
|
2021-12-05 11:24:13 -05:00
|
|
|
if (cnt == timecnt_t::max (cnt.time_domain())) {
|
2014-12-17 16:05:27 -05:00
|
|
|
invalidate(lm);
|
2014-11-20 15:36:11 -05:00
|
|
|
} else {
|
2020-09-20 18:34:09 -04:00
|
|
|
_capture_length += cnt.samples();
|
2011-07-20 14:13:03 -04:00
|
|
|
}
|
|
|
|
|
2009-02-19 19:30:42 -05:00
|
|
|
return ret;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiSource::mark_streaming_midi_write_started (const WriterLock& lock, NoteMode mode)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
if (_model) {
|
2022-03-30 12:38:50 -04:00
|
|
|
/* XXX do something with note mode? */
|
2011-03-02 12:05:16 -05:00
|
|
|
_model->start_write ();
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-02-19 19:30:42 -05:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
_writing = true;
|
|
|
|
}
|
|
|
|
|
2011-02-28 21:03:52 -05:00
|
|
|
void
|
2021-01-22 15:41:15 -05:00
|
|
|
MidiSource::mark_write_starting_now (timepos_t const & position, samplecnt_t capture_length)
|
2011-02-28 21:03:52 -05:00
|
|
|
{
|
|
|
|
/* I'm not sure if this is the best way to approach this, but
|
2017-09-18 12:39:17 -04:00
|
|
|
_capture_length needs to be set up with the transport sample
|
2011-02-28 21:03:52 -05:00
|
|
|
when a record actually starts, as it is used by
|
|
|
|
SMFSource::write_unlocked to decide whether incoming notes
|
|
|
|
are within the correct time range.
|
|
|
|
mark_streaming_midi_write_started (perhaps a more logical
|
|
|
|
place to do this) is not called at exactly the time when
|
|
|
|
record starts, and I don't think it necessarily can be
|
|
|
|
because it is not RT-safe.
|
|
|
|
*/
|
|
|
|
|
2021-01-22 15:41:15 -05:00
|
|
|
set_natural_position (position);
|
|
|
|
|
Fix MIDI loop recording.
This changes how things work a bit, but I am committing it for 3.0 since the
previous revision often crashed (and never worked), this one seems to work
fine, and the code is quite a bit more cogent.
I have tested the following use cases with live input and audible output:
* Non-loop recording, armed before roll
* Non-loop recording, arm while rolling
* Loop recording, armed before roll
* Loop recording, arm during roll
In the last case, the source/region is created starting at the loop start
rather than the current transport frame as usual so time makes sense when it
wraps around.
See the documentation added to the code for details, but the basic idea here is
to simply push MIDI events to the source with increasing monotonic time,
ignoring looping altogether. Essentially we pretend the loop does not exist,
but the source knows all the details so it can implement whatever behaviour is
appropriate.
Currently, this is simply recording a complete non-destructive copy of the
input, which is a good thing. Perhaps not what the user expects of loop
recording, but at least it works and is one sensible option. We will need to
add more later.
Display while recording is a little bit wacky, but whatever.
git-svn-id: svn://localhost/ardour2/branches/3.0@13940 d708f5d6-7413-0410-9779-e7cbd77b26cf
2013-01-21 01:00:15 -05:00
|
|
|
_capture_length = capture_length;
|
|
|
|
|
2020-12-08 22:34:15 -05:00
|
|
|
/* currently prefer to compute length in beats, since that matches 6.x
|
|
|
|
* and earlier behavior
|
|
|
|
*/
|
|
|
|
|
|
|
|
timecnt_t distance = timecnt_t (timepos_t (capture_length), timepos_t (position));
|
|
|
|
_length = timecnt_t (distance.beats(), timepos_t (position));
|
2011-02-28 21:03:52 -05:00
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiSource::mark_streaming_write_started (const WriterLock& lock)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2022-03-30 12:38:50 -04:00
|
|
|
/* as of March 2022 or long before , the note mode argument does nothing */
|
|
|
|
mark_streaming_midi_write_started (lock, Sustained);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiSource::mark_midi_streaming_write_completed (const WriterLock& lock,
|
2017-09-24 12:03:54 -04:00
|
|
|
Evoral::Sequence<Temporal::Beats>::StuckNoteOption option,
|
2019-04-07 21:45:12 -04:00
|
|
|
Temporal::Beats end)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2009-01-27 23:55:31 -05:00
|
|
|
if (_model) {
|
2011-07-20 14:13:03 -04:00
|
|
|
_model->end_write (option, end);
|
2014-12-05 00:15:40 -05:00
|
|
|
|
|
|
|
/* Make captured controls discrete to play back user input exactly. */
|
|
|
|
for (MidiModel::Controls::iterator i = _model->controls().begin(); i != _model->controls().end(); ++i) {
|
|
|
|
if (i->second->list()) {
|
2022-07-21 20:01:10 -04:00
|
|
|
i->second->list()->set_interpolation (AutomationList::Discrete);
|
|
|
|
_interpolation_style.insert(std::make_pair(i->second->parameter(), AutomationList::Discrete));
|
2014-12-05 00:15:40 -05:00
|
|
|
}
|
|
|
|
}
|
2009-01-27 23:55:31 -05:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2014-12-30 14:45:11 -05:00
|
|
|
invalidate(lock);
|
2008-06-02 17:41:35 -04:00
|
|
|
_writing = false;
|
|
|
|
}
|
|
|
|
|
2011-07-20 14:13:03 -04:00
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiSource::mark_streaming_write_completed (const WriterLock& lock)
|
2011-07-20 14:13:03 -04:00
|
|
|
{
|
2017-09-24 12:03:54 -04:00
|
|
|
mark_midi_streaming_write_completed (lock, Evoral::Sequence<Temporal::Beats>::DeleteStuckNotes);
|
2011-07-20 14:13:03 -04:00
|
|
|
}
|
|
|
|
|
2016-07-19 19:53:31 -04:00
|
|
|
int
|
2023-02-16 18:33:28 -05:00
|
|
|
MidiSource::export_write_to (const ReaderLock& lock, std::shared_ptr<MidiSource> newsrc, Temporal::Beats begin, Temporal::Beats end)
|
2016-07-19 19:53:31 -04:00
|
|
|
{
|
2022-03-30 14:56:04 -04:00
|
|
|
WriterLock newsrc_lock (newsrc->mutex ());
|
2016-07-19 19:53:31 -04:00
|
|
|
|
|
|
|
if (!_model) {
|
|
|
|
error << string_compose (_("programming error: %1"), X_("no model for MidiSource during export"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
_model->write_section_to (newsrc, newsrc_lock, begin, end, true);
|
|
|
|
|
|
|
|
newsrc->flush_midi(newsrc_lock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-04-13 10:29:07 -04:00
|
|
|
int
|
2023-02-16 18:33:28 -05:00
|
|
|
MidiSource::write_to (const ReaderLock& lock, std::shared_ptr<MidiSource> newsrc, Temporal::Beats begin, Temporal::Beats end)
|
2010-05-18 23:03:28 -04:00
|
|
|
{
|
2022-03-30 14:56:04 -04:00
|
|
|
WriterLock newsrc_lock (newsrc->mutex ());
|
2014-12-17 16:05:27 -05:00
|
|
|
|
2018-11-15 10:33:54 -05:00
|
|
|
newsrc->set_natural_position (_natural_position);
|
2010-07-13 20:58:15 -04:00
|
|
|
newsrc->copy_interpolation_from (this);
|
2010-08-09 18:23:23 -04:00
|
|
|
newsrc->copy_automation_state_from (this);
|
2010-05-18 23:03:28 -04:00
|
|
|
|
2010-12-20 17:51:17 -05:00
|
|
|
if (_model) {
|
2017-09-24 12:03:54 -04:00
|
|
|
if (begin == Temporal::Beats() && end == std::numeric_limits<Temporal::Beats>::max()) {
|
2014-12-17 16:05:27 -05:00
|
|
|
_model->write_to (newsrc, newsrc_lock);
|
2010-12-20 17:51:17 -05:00
|
|
|
} else {
|
2014-12-17 16:05:27 -05:00
|
|
|
_model->write_section_to (newsrc, newsrc_lock, begin, end);
|
2010-12-20 17:51:17 -05:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
error << string_compose (_("programming error: %1"), X_("no model for MidiSource during ::clone()"));
|
2014-04-13 10:29:07 -04:00
|
|
|
return -1;
|
2010-12-20 17:51:17 -05:00
|
|
|
}
|
2010-05-18 23:03:28 -04:00
|
|
|
|
2014-12-17 16:05:27 -05:00
|
|
|
newsrc->flush_midi(newsrc_lock);
|
2010-05-18 23:03:28 -04:00
|
|
|
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2017-09-24 12:03:54 -04:00
|
|
|
if (begin != Temporal::Beats() || end != std::numeric_limits<Temporal::Beats>::max()) {
|
2021-03-19 11:27:16 -04:00
|
|
|
/* force a reload of the model if the range is partial */
|
2014-12-17 16:05:27 -05:00
|
|
|
newsrc->load_model (newsrc_lock, true);
|
2010-12-20 17:51:17 -05:00
|
|
|
} else {
|
2021-03-19 11:27:16 -04:00
|
|
|
/* re-create model */
|
|
|
|
newsrc->destroy_model (newsrc_lock);
|
|
|
|
newsrc->load_model (newsrc_lock);
|
2010-05-24 17:45:50 -04:00
|
|
|
}
|
2014-11-19 20:47:18 -05:00
|
|
|
|
2014-04-04 15:26:44 -04:00
|
|
|
/* this file is not removable (but since it is MIDI, it is mutable) */
|
|
|
|
|
2023-02-16 18:33:28 -05:00
|
|
|
std::dynamic_pointer_cast<FileSource> (newsrc)->prevent_deletion ();
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2014-04-13 10:29:07 -04:00
|
|
|
return 0;
|
2010-05-18 23:03:28 -04:00
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
void
|
|
|
|
MidiSource::session_saved()
|
|
|
|
{
|
2022-03-30 14:56:04 -04:00
|
|
|
WriterLock lm (_lock);
|
2014-12-17 16:05:27 -05:00
|
|
|
|
2011-06-01 12:50:12 -04:00
|
|
|
/* this writes a copy of the data to disk.
|
2010-12-20 17:51:17 -05:00
|
|
|
XXX do we need to do this every time?
|
|
|
|
*/
|
2010-06-26 09:45:59 -04:00
|
|
|
|
2010-07-01 14:54:19 -04:00
|
|
|
if (_model && _model->edited()) {
|
2014-11-19 20:47:18 -05:00
|
|
|
/* The model is edited, write its contents into the current source
|
|
|
|
file (overwiting previous contents). */
|
2010-07-16 10:55:11 -04:00
|
|
|
|
2014-11-19 20:47:18 -05:00
|
|
|
/* Temporarily drop our reference to the model so that as the model
|
|
|
|
pushes its current state to us, we don't try to update it. */
|
2023-02-16 18:33:28 -05:00
|
|
|
std::shared_ptr<MidiModel> mm = _model;
|
2011-06-01 12:50:12 -04:00
|
|
|
_model.reset ();
|
2010-07-16 10:55:11 -04:00
|
|
|
|
2014-11-19 20:47:18 -05:00
|
|
|
/* Flush model contents to disk. */
|
2014-12-17 16:05:27 -05:00
|
|
|
mm->sync_to_source (lm);
|
2010-07-16 10:55:11 -04:00
|
|
|
|
2014-11-19 20:47:18 -05:00
|
|
|
/* Reacquire model. */
|
2010-12-20 17:51:17 -05:00
|
|
|
_model = mm;
|
2010-07-01 14:54:19 -04:00
|
|
|
|
2010-12-20 17:51:17 -05:00
|
|
|
} else {
|
2014-12-17 16:05:27 -05:00
|
|
|
flush_midi(lm);
|
2010-12-20 17:51:17 -05:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2010-05-20 18:38:12 -04:00
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiSource::drop_model (const WriterLock& lock)
|
2010-05-20 18:38:12 -04:00
|
|
|
{
|
2011-06-01 12:50:12 -04:00
|
|
|
_model.reset();
|
2014-12-17 16:05:27 -05:00
|
|
|
invalidate(lock);
|
2010-06-25 16:47:09 -04:00
|
|
|
ModelChanged (); /* EMIT SIGNAL */
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2023-02-16 18:33:28 -05:00
|
|
|
MidiSource::set_model (const WriterLock& lock, std::shared_ptr<MidiModel> m)
|
2010-06-25 16:47:09 -04:00
|
|
|
{
|
|
|
|
_model = m;
|
2014-12-17 16:05:27 -05:00
|
|
|
invalidate(lock);
|
2010-06-25 16:47:09 -04:00
|
|
|
ModelChanged (); /* EMIT SIGNAL */
|
2010-05-20 18:38:12 -04:00
|
|
|
}
|
2010-07-13 20:58:15 -04:00
|
|
|
|
2022-07-21 20:01:10 -04:00
|
|
|
AutomationList::InterpolationStyle
|
|
|
|
MidiSource::interpolation_of (Evoral::Parameter const& p) const
|
2010-07-13 20:58:15 -04:00
|
|
|
{
|
|
|
|
InterpolationStyleMap::const_iterator i = _interpolation_style.find (p);
|
|
|
|
if (i == _interpolation_style.end()) {
|
|
|
|
return EventTypeMap::instance().interpolation_of (p);
|
|
|
|
}
|
|
|
|
|
|
|
|
return i->second;
|
|
|
|
}
|
|
|
|
|
2010-08-09 18:23:23 -04:00
|
|
|
AutoState
|
2022-07-21 20:01:10 -04:00
|
|
|
MidiSource::automation_state_of (Evoral::Parameter const& p) const
|
2010-08-09 18:23:23 -04:00
|
|
|
{
|
|
|
|
AutomationStateMap::const_iterator i = _automation_state.find (p);
|
|
|
|
if (i == _automation_state.end()) {
|
2010-09-01 19:08:42 -04:00
|
|
|
/* default to `play', otherwise if MIDI is recorded /
|
|
|
|
imported with controllers etc. they are by default
|
|
|
|
not played back, which is a little surprising.
|
|
|
|
*/
|
|
|
|
return Play;
|
2010-08-09 18:23:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return i->second;
|
|
|
|
}
|
|
|
|
|
2010-07-13 20:58:15 -04:00
|
|
|
/** Set interpolation style to be used for a given parameter. This change will be
|
|
|
|
* propagated to anyone who needs to know.
|
|
|
|
*/
|
|
|
|
void
|
2022-07-21 20:01:10 -04:00
|
|
|
MidiSource::set_interpolation_of (Evoral::Parameter const& p, AutomationList::InterpolationStyle s)
|
2010-07-13 20:58:15 -04:00
|
|
|
{
|
|
|
|
if (interpolation_of (p) == s) {
|
|
|
|
return;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2010-07-13 20:58:15 -04:00
|
|
|
if (EventTypeMap::instance().interpolation_of (p) == s) {
|
|
|
|
/* interpolation type is being set to the default, so we don't need a note in our map */
|
|
|
|
_interpolation_style.erase (p);
|
|
|
|
} else {
|
|
|
|
_interpolation_style[p] = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
InterpolationChanged (p, s); /* EMIT SIGNAL */
|
|
|
|
}
|
|
|
|
|
2010-08-09 18:23:23 -04:00
|
|
|
void
|
2022-07-21 20:01:10 -04:00
|
|
|
MidiSource::set_automation_state_of (Evoral::Parameter const& p, AutoState s)
|
2010-08-09 18:23:23 -04:00
|
|
|
{
|
|
|
|
if (automation_state_of (p) == s) {
|
|
|
|
return;
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2010-09-01 19:08:42 -04:00
|
|
|
if (s == Play) {
|
2010-08-09 18:23:23 -04:00
|
|
|
/* automation state is being set to the default, so we don't need a note in our map */
|
|
|
|
_automation_state.erase (p);
|
|
|
|
} else {
|
|
|
|
_automation_state[p] = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
AutomationStateChanged (p, s); /* EMIT SIGNAL */
|
|
|
|
}
|
|
|
|
|
2010-07-13 20:58:15 -04:00
|
|
|
void
|
2023-02-16 18:33:28 -05:00
|
|
|
MidiSource::copy_interpolation_from (std::shared_ptr<MidiSource> s)
|
2010-07-13 20:58:15 -04:00
|
|
|
{
|
|
|
|
copy_interpolation_from (s.get ());
|
|
|
|
}
|
|
|
|
|
2010-08-09 18:23:23 -04:00
|
|
|
void
|
2023-02-16 18:33:28 -05:00
|
|
|
MidiSource::copy_automation_state_from (std::shared_ptr<MidiSource> s)
|
2010-08-09 18:23:23 -04:00
|
|
|
{
|
|
|
|
copy_automation_state_from (s.get ());
|
|
|
|
}
|
|
|
|
|
2010-07-13 20:58:15 -04:00
|
|
|
void
|
|
|
|
MidiSource::copy_interpolation_from (MidiSource* s)
|
|
|
|
{
|
|
|
|
_interpolation_style = s->_interpolation_style;
|
|
|
|
|
|
|
|
/* XXX: should probably emit signals here */
|
|
|
|
}
|
2010-08-09 18:23:23 -04:00
|
|
|
|
|
|
|
void
|
|
|
|
MidiSource::copy_automation_state_from (MidiSource* s)
|
|
|
|
{
|
|
|
|
_automation_state = s->_automation_state;
|
|
|
|
|
|
|
|
/* XXX: should probably emit signals here */
|
|
|
|
}
|