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-2011 Carl Hetherington <carl@carlh.net>
|
|
|
|
* Copyright (C) 2012-2016 Tim Mayberry <mojofunk@gmail.com>
|
|
|
|
* Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
|
|
|
|
* Copyright (C) 2014-2016 John Emmas <john@creativepost.co.uk>
|
|
|
|
* 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 <vector>
|
|
|
|
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
2013-01-16 12:09:52 -05:00
|
|
|
#include <regex.h>
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2014-07-08 00:53:06 -04:00
|
|
|
#include "pbd/file_utils.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "pbd/stl_delete.h"
|
|
|
|
#include "pbd/strsplit.h"
|
2019-10-14 21:00:32 -04:00
|
|
|
#include "pbd/timing.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2015-10-05 16:10:58 -04:00
|
|
|
#include "pbd/gstdio_compat.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
#include <glibmm/miscutils.h>
|
2014-02-27 20:35:23 -05:00
|
|
|
#include <glibmm/fileutils.h>
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2019-10-25 15:13:51 -04:00
|
|
|
#include "evoral/Control.h"
|
|
|
|
#include "evoral/SMF.h"
|
2008-09-29 20:45:26 -04:00
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
#include "temporal/tempo.h"
|
|
|
|
|
2015-03-28 23:24:41 -04:00
|
|
|
#include "ardour/debug.h"
|
|
|
|
#include "ardour/midi_channel_filter.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/midi_model.h"
|
|
|
|
#include "ardour/midi_ring_buffer.h"
|
2009-10-19 13:05:22 -04:00
|
|
|
#include "ardour/midi_state_tracker.h"
|
2014-11-01 23:29:10 -04:00
|
|
|
#include "ardour/parameter_types.h"
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "ardour/session.h"
|
|
|
|
#include "ardour/smf_source.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
|
|
|
|
|
|
|
using namespace ARDOUR;
|
2009-02-16 21:11:49 -05:00
|
|
|
using namespace Glib;
|
2010-05-24 18:39:36 -04:00
|
|
|
using namespace PBD;
|
2014-04-28 16:44:40 -04:00
|
|
|
using namespace Evoral;
|
2014-11-30 18:27:04 -05:00
|
|
|
using namespace std;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-02-15 12:30:42 -05:00
|
|
|
/** Constructor used for new internal-to-session files. File cannot exist. */
|
2010-09-14 11:45:21 -04:00
|
|
|
SMFSource::SMFSource (Session& s, const string& path, Source::Flag flags)
|
2009-02-16 21:11:49 -05:00
|
|
|
: Source(s, DataType::MIDI, path, flags)
|
2011-02-28 21:04:34 -05:00
|
|
|
, MidiSource(s, path, flags)
|
2010-11-09 13:19:53 -05:00
|
|
|
, FileSource(s, DataType::MIDI, path, string(), flags)
|
2009-02-14 20:53:06 -05:00
|
|
|
, Evoral::SMF()
|
2014-12-10 18:28:55 -05:00
|
|
|
, _open (false)
|
2017-09-18 12:39:17 -04:00
|
|
|
, _last_ev_time_samples(0)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2010-12-20 17:51:17 -05:00
|
|
|
/* note that origin remains empty */
|
2010-11-09 13:19:53 -05:00
|
|
|
|
2014-04-14 03:03:35 -04:00
|
|
|
if (init (_path, false)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
throw failed_constructor ();
|
|
|
|
}
|
2015-10-04 14:51:05 -04:00
|
|
|
|
2014-04-14 03:03:35 -04:00
|
|
|
assert (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
|
|
|
|
existence_check ();
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2014-08-28 14:01:52 -04:00
|
|
|
_flags = Source::Flag (_flags | Empty);
|
|
|
|
|
2021-08-27 18:52:34 -04:00
|
|
|
if (_flags & Writable) {
|
|
|
|
if (open_for_write ()) {
|
|
|
|
throw failed_constructor ();
|
|
|
|
}
|
|
|
|
/* no fd left open here */
|
|
|
|
} else {
|
2022-09-24 20:46:59 -04:00
|
|
|
if (open (_path, 1, false)) {
|
2021-08-27 18:52:34 -04:00
|
|
|
throw failed_constructor ();
|
|
|
|
}
|
|
|
|
_open = true;
|
2014-01-20 07:38:28 -05:00
|
|
|
}
|
2014-04-04 15:26:44 -04:00
|
|
|
|
Change the type of reference held by a MidiModel to its MidiSource
This also requires a change in the type of reference held by
a MidiAutomationListBinder.
Both the MidiSource and MidiModel have a reference to each other, and it is
important that we avoid circular references to avoid problems with object
destruction. We had been accomplishing this by having the Model hold a
weak_ptr<MidiSource>. However, the lifetime of a MidiSource and its MidiModel
are coincident and there's really no need to use a smart ptr at all. A normal
reference is just fine. However, due to constructors that accept a serialized
state, we cannot use an actual reference (we cannot set the constructor in the
initializer list), so we use a bare ptr instead.
This forces a similar change in MidiAutomationListBinder, which also maintains
a reference to the Source. However, the only purpose of this object is to
ensure that if the Source is destroyed, relevant commands will be removed from
the undo/redo history, and so all that matters here is that the binder connects
to the Destroyed signal of the source, and arranges for its own destruction
when received.
Note that the previous construction of the binder, actually holding a
shared_ptr<MidiSource> would appear have prevented the Destroyed signal from
ever being emitted (from ~Destructible), and so this may also be a bug fix that
allows MidiSources to actually be deleted (the memory object, not the file).
2022-03-30 14:49:00 -04:00
|
|
|
/* there's no data to load into the model but create it anyway */
|
|
|
|
|
|
|
|
_model = boost::shared_ptr<MidiModel> (new MidiModel (*this));
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2014-04-28 10:37:17 -04:00
|
|
|
/** Constructor used for external-to-session files. File must exist. */
|
|
|
|
SMFSource::SMFSource (Session& s, const string& path)
|
|
|
|
: Source(s, DataType::MIDI, path, Source::Flag (0))
|
|
|
|
, MidiSource(s, path, Source::Flag (0))
|
|
|
|
, FileSource(s, DataType::MIDI, path, string(), Source::Flag (0))
|
|
|
|
, Evoral::SMF()
|
2014-12-10 18:28:55 -05:00
|
|
|
, _open (false)
|
2017-09-18 12:39:17 -04:00
|
|
|
, _last_ev_time_samples(0)
|
2014-04-28 10:37:17 -04:00
|
|
|
{
|
|
|
|
/* note that origin remains empty */
|
|
|
|
|
2014-09-15 12:38:54 -04:00
|
|
|
if (init (_path, true)) {
|
2014-04-28 10:37:17 -04:00
|
|
|
throw failed_constructor ();
|
|
|
|
}
|
2015-10-04 14:51:05 -04:00
|
|
|
|
2014-04-28 10:37:17 -04:00
|
|
|
assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
|
|
|
|
existence_check ();
|
|
|
|
|
2022-09-24 20:46:59 -04:00
|
|
|
if (open (_path, 1, false)) {
|
2014-04-28 10:37:17 -04:00
|
|
|
throw failed_constructor ();
|
|
|
|
}
|
|
|
|
|
|
|
|
_open = true;
|
Change the type of reference held by a MidiModel to its MidiSource
This also requires a change in the type of reference held by
a MidiAutomationListBinder.
Both the MidiSource and MidiModel have a reference to each other, and it is
important that we avoid circular references to avoid problems with object
destruction. We had been accomplishing this by having the Model hold a
weak_ptr<MidiSource>. However, the lifetime of a MidiSource and its MidiModel
are coincident and there's really no need to use a smart ptr at all. A normal
reference is just fine. However, due to constructors that accept a serialized
state, we cannot use an actual reference (we cannot set the constructor in the
initializer list), so we use a bare ptr instead.
This forces a similar change in MidiAutomationListBinder, which also maintains
a reference to the Source. However, the only purpose of this object is to
ensure that if the Source is destroyed, relevant commands will be removed from
the undo/redo history, and so all that matters here is that the binder connects
to the Destroyed signal of the source, and arranges for its own destruction
when received.
Note that the previous construction of the binder, actually holding a
shared_ptr<MidiSource> would appear have prevented the Destroyed signal from
ever being emitted (from ~Destructible), and so this may also be a bug fix that
allows MidiSources to actually be deleted (the memory object, not the file).
2022-03-30 14:49:00 -04:00
|
|
|
|
|
|
|
/* no lock required since we do not actually exist yet */
|
|
|
|
load_model_unlocked (true);
|
2014-04-28 10:37:17 -04:00
|
|
|
}
|
|
|
|
|
2009-02-16 21:11:49 -05:00
|
|
|
/** Constructor used for existing internal-to-session files. */
|
|
|
|
SMFSource::SMFSource (Session& s, const XMLNode& node, bool must_exist)
|
|
|
|
: Source(s, node)
|
|
|
|
, MidiSource(s, node)
|
|
|
|
, FileSource(s, node, must_exist)
|
2014-12-10 18:28:55 -05:00
|
|
|
, _open (false)
|
2017-09-18 12:39:17 -04:00
|
|
|
, _last_ev_time_samples(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 ();
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2014-08-28 14:01:52 -04:00
|
|
|
/* we expect the file to exist, but if no MIDI data was ever added
|
|
|
|
it will have been removed at last session close. so, we don't
|
|
|
|
require it to exist if it was marked Empty.
|
|
|
|
*/
|
|
|
|
|
2014-09-15 12:38:54 -04:00
|
|
|
try {
|
|
|
|
|
|
|
|
if (init (_path, true)) {
|
|
|
|
throw failed_constructor ();
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (MissingSource& err) {
|
2020-02-17 19:26:20 -05:00
|
|
|
if (0 == (_flags & Source::Empty)) {
|
|
|
|
/* Don't throw, create the source.
|
|
|
|
* Since MIDI is writable, we cannot use a SilentFileSource.
|
|
|
|
*/
|
|
|
|
_flags = Source::Flag (_flags | Source::Empty | Source::Missing);
|
2014-09-15 12:38:54 -04:00
|
|
|
}
|
2020-02-17 19:26:20 -05:00
|
|
|
|
|
|
|
/* we don't care that the file was not found, because
|
|
|
|
it was empty. But FileSource::init() will have
|
|
|
|
failed to set our _path correctly, so we have to do
|
|
|
|
this ourselves. Use the first entry in the search
|
|
|
|
path for MIDI files, which is assumed to be the
|
|
|
|
correct "main" location.
|
|
|
|
*/
|
|
|
|
std::vector<string> sdirs = s.source_search_path (DataType::MIDI);
|
|
|
|
_path = Glib::build_filename (sdirs.front(), _path);
|
|
|
|
/* This might be important, too */
|
|
|
|
_file_is_new = true;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2014-08-28 14:01:52 -04:00
|
|
|
if (!(_flags & Source::Empty)) {
|
|
|
|
assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
|
|
|
|
existence_check ();
|
2022-09-24 20:46:59 -04:00
|
|
|
if (open (_path, 1, false)) {
|
2021-08-27 18:52:34 -04:00
|
|
|
throw failed_constructor ();
|
|
|
|
}
|
|
|
|
_open = true;
|
2014-08-28 14:01:52 -04:00
|
|
|
} else {
|
|
|
|
assert (_flags & Source::Writable);
|
2021-08-27 18:52:34 -04:00
|
|
|
if (open_for_write ()) {
|
|
|
|
throw failed_constructor ();
|
|
|
|
}
|
|
|
|
/* no fd left open here */
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2011-03-02 12:05:16 -05:00
|
|
|
|
Change the type of reference held by a MidiModel to its MidiSource
This also requires a change in the type of reference held by
a MidiAutomationListBinder.
Both the MidiSource and MidiModel have a reference to each other, and it is
important that we avoid circular references to avoid problems with object
destruction. We had been accomplishing this by having the Model hold a
weak_ptr<MidiSource>. However, the lifetime of a MidiSource and its MidiModel
are coincident and there's really no need to use a smart ptr at all. A normal
reference is just fine. However, due to constructors that accept a serialized
state, we cannot use an actual reference (we cannot set the constructor in the
initializer list), so we use a bare ptr instead.
This forces a similar change in MidiAutomationListBinder, which also maintains
a reference to the Source. However, the only purpose of this object is to
ensure that if the Source is destroyed, relevant commands will be removed from
the undo/redo history, and so all that matters here is that the binder connects
to the Destroyed signal of the source, and arranges for its own destruction
when received.
Note that the previous construction of the binder, actually holding a
shared_ptr<MidiSource> would appear have prevented the Destroyed signal from
ever being emitted (from ~Destructible), and so this may also be a bug fix that
allows MidiSources to actually be deleted (the memory object, not the file).
2022-03-30 14:49:00 -04:00
|
|
|
/* no lock required since we do not actually exist yet */
|
|
|
|
load_model_unlocked (true);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
SMFSource::~SMFSource ()
|
|
|
|
{
|
|
|
|
if (removable()) {
|
2013-07-15 14:29:00 -04:00
|
|
|
::g_unlink (_path.c_str());
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-02 12:05:16 -05:00
|
|
|
int
|
|
|
|
SMFSource::open_for_write ()
|
|
|
|
{
|
|
|
|
if (create (_path)) {
|
2013-01-20 21:35:53 -05:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
_open = true;
|
|
|
|
return 0;
|
2011-03-02 12:05:16 -05:00
|
|
|
}
|
|
|
|
|
2015-04-20 16:31:06 -04:00
|
|
|
void
|
|
|
|
SMFSource::close ()
|
|
|
|
{
|
2021-03-19 11:27:16 -04:00
|
|
|
/* nothing to do: file descriptor is never kept open.
|
|
|
|
* Note, keep `_open = true` regardless.
|
|
|
|
*/
|
2015-04-20 16:31:06 -04:00
|
|
|
}
|
|
|
|
|
2019-10-14 21:00:32 -04:00
|
|
|
extern PBD::Timing minsert;
|
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
timecnt_t
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::read_unlocked (const ReaderLock& lock,
|
2017-09-18 12:39:17 -04:00
|
|
|
Evoral::EventSink<samplepos_t>& destination,
|
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 & duration,
|
|
|
|
Temporal::Range* loop_range,
|
2022-03-30 14:56:04 -04:00
|
|
|
MidiNoteTracker* tracker,
|
2020-09-20 18:34:09 -04:00
|
|
|
MidiChannelFilter* filter) const
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2009-02-16 21:11:49 -05:00
|
|
|
int ret = 0;
|
2020-09-20 18:34:09 -04:00
|
|
|
timepos_t time; // in SMF ticks, 1 tick per _ppqn
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2013-01-20 21:35:53 -05:00
|
|
|
if (writable() && !_open) {
|
|
|
|
/* nothing to read since nothing has ben written */
|
2020-09-20 18:34:09 -04:00
|
|
|
return timecnt_t();
|
2013-01-20 21:35:53 -05:00
|
|
|
}
|
2011-03-02 12:05:16 -05:00
|
|
|
|
2010-05-24 18:39:36 -04:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start %1 duration %2\n", start, duration));
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
// Output parameters for read_event (which will allocate scratch in buffer as needed)
|
|
|
|
uint32_t ev_delta_t = 0;
|
2009-02-14 20:53:06 -05:00
|
|
|
uint32_t ev_size = 0;
|
|
|
|
uint8_t* ev_buffer = 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
size_t scratch_size = 0; // keep track of scratch to minimize reallocs
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
/* start of read in SMF ticks (which may differ from our own musical ticks */
|
|
|
|
const uint64_t start_ticks = llrint (start.beats().to_ticks() * (Temporal::Beats::PPQN / ppqn()));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2010-05-24 18:39:36 -04:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start in ticks %1\n", start_ticks));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2021-12-11 08:42:36 -05:00
|
|
|
if (_smf_last_read_end.is_zero() || start != _smf_last_read_end) {
|
2010-05-24 18:39:36 -04:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: seek to %1\n", start));
|
2009-02-14 21:09:58 -05:00
|
|
|
Evoral::SMF::seek_to_start();
|
|
|
|
while (time < start_ticks) {
|
2021-03-19 01:29:03 -04:00
|
|
|
Evoral::event_id_t ignored;
|
2010-07-20 12:27:34 -04:00
|
|
|
|
|
|
|
ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored);
|
2009-02-14 21:09:58 -05:00
|
|
|
if (ret == -1) { // EOF
|
2009-09-15 21:08:51 -04:00
|
|
|
_smf_last_read_end = start + duration;
|
2020-09-20 18:34:09 -04:00
|
|
|
return timecnt_t();
|
2009-02-14 21:09:58 -05:00
|
|
|
}
|
2020-12-02 00:30:04 -05:00
|
|
|
time += timepos_t::from_ticks (ev_delta_t); // accumulate delta time
|
2009-02-14 21:09:58 -05:00
|
|
|
}
|
2010-05-24 11:42:27 -04:00
|
|
|
} else {
|
2010-05-24 18:39:36 -04:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: set time to %1\n", _smf_last_read_time));
|
2010-05-24 11:42:27 -04:00
|
|
|
time = _smf_last_read_time;
|
2009-09-21 11:43:11 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2009-09-15 21:08:51 -04:00
|
|
|
_smf_last_read_end = start + duration;
|
2009-02-14 21:09:58 -05:00
|
|
|
|
2009-02-15 14:44:27 -05:00
|
|
|
while (true) {
|
2021-03-19 01:29:03 -04:00
|
|
|
Evoral::event_id_t ignored; /* XXX don't ignore note id's ??*/
|
2010-07-20 12:27:34 -04:00
|
|
|
|
|
|
|
ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored);
|
2008-06-02 17:41:35 -04:00
|
|
|
if (ret == -1) { // EOF
|
|
|
|
break;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2020-12-02 00:30:04 -05:00
|
|
|
time += timepos_t::from_ticks (ev_delta_t); // accumulate delta time
|
2010-05-24 11:42:27 -04:00
|
|
|
_smf_last_read_time = time;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (ret == 0) { // meta-event (skipped, just accumulate time)
|
|
|
|
continue;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2016-11-07 05:14:55 -05:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked delta %1, time %2, buf[0] %3\n",
|
|
|
|
ev_delta_t, time, ev_buffer[0]));
|
2009-09-21 11:43:11 -04:00
|
|
|
|
2009-02-14 21:09:58 -05:00
|
|
|
assert(time >= start_ticks);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-09-18 12:39:17 -04:00
|
|
|
/* Note that we add on the source start time (in session samples) here so that ev_sample_time
|
|
|
|
is in session samples.
|
2010-08-31 21:50:24 -04:00
|
|
|
*/
|
2020-12-08 11:02:41 -05:00
|
|
|
const samplepos_t ev_sample_time = (time + timepos_t (source_start.beats())).samples();
|
2020-09-20 18:34:09 -04:00
|
|
|
timepos_t est (ev_sample_time);
|
2009-09-21 11:43:11 -04:00
|
|
|
|
2016-09-13 15:10:04 -04:00
|
|
|
if (loop_range) {
|
2020-09-20 18:34:09 -04:00
|
|
|
est = loop_range->squish (est);
|
2016-09-13 15:10:04 -04:00
|
|
|
}
|
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
if (est < start + duration) {
|
2015-03-28 23:24:41 -04:00
|
|
|
if (!filter || !filter->filter(ev_buffer, ev_size)) {
|
2020-09-20 18:34:09 -04:00
|
|
|
destination.write (est.samples(), Evoral::MIDI_EVENT, ev_size, ev_buffer);
|
2015-03-28 23:24:41 -04:00
|
|
|
if (tracker) {
|
|
|
|
tracker->track(ev_buffer);
|
|
|
|
}
|
2009-10-19 13:05:22 -04:00
|
|
|
}
|
2009-02-14 21:09:58 -05:00
|
|
|
} else {
|
|
|
|
break;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-10-22 18:14:25 -04:00
|
|
|
|
2009-02-14 20:53:06 -05:00
|
|
|
if (ev_size > scratch_size) {
|
2008-06-02 17:41:35 -04:00
|
|
|
scratch_size = ev_size;
|
2009-02-14 20:53:06 -05:00
|
|
|
}
|
2009-02-14 22:51:49 -05:00
|
|
|
ev_size = scratch_size; // ensure read_event only allocates if necessary
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2009-04-14 06:13:06 -04:00
|
|
|
return duration;
|
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
|
|
|
SMFSource::write_unlocked (const WriterLock& lock,
|
2017-09-18 12:39:17 -04:00
|
|
|
MidiRingBuffer<samplepos_t>& source,
|
2020-09-20 18:34:09 -04:00
|
|
|
timepos_t const & position,
|
|
|
|
timecnt_t const & cnt)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2020-09-20 18:34:09 -04:00
|
|
|
|
2013-01-20 21:35:53 -05:00
|
|
|
if (!_writing) {
|
2014-12-17 16:05:27 -05:00
|
|
|
mark_streaming_write_started (lock);
|
2013-01-20 21:35:53 -05:00
|
|
|
}
|
2011-03-02 12:05:16 -05:00
|
|
|
|
2017-09-18 12:39:17 -04:00
|
|
|
samplepos_t time;
|
2020-09-20 18:34:09 -04:00
|
|
|
const samplepos_t pos_samples = position.samples();
|
|
|
|
const samplecnt_t cnt_samples = cnt.samples();
|
2009-04-16 10:34:56 -04:00
|
|
|
Evoral::EventType type;
|
|
|
|
uint32_t size;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-02-14 20:53:06 -05:00
|
|
|
size_t buf_capacity = 4;
|
|
|
|
uint8_t* buf = (uint8_t*)malloc(buf_capacity);
|
2009-10-14 12:10:01 -04:00
|
|
|
|
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
|
|
|
if (_model && !_model->writing()) {
|
2008-06-02 17:41:35 -04:00
|
|
|
_model->start_write();
|
2009-02-14 20:53:06 -05:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2017-09-18 12:39:17 -04:00
|
|
|
Evoral::Event<samplepos_t> ev;
|
2008-06-02 17:41:35 -04:00
|
|
|
while (true) {
|
2017-09-18 12:39:17 -04:00
|
|
|
/* Get the event time, in samples since session start but ignoring looping. */
|
2011-07-20 14:13:03 -04:00
|
|
|
bool ret;
|
|
|
|
if (!(ret = source.peek ((uint8_t*)&time, sizeof (time)))) {
|
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
|
|
|
/* Ring is empty, no more events. */
|
2008-06-02 17:41:35 -04:00
|
|
|
break;
|
2009-02-14 20:53:06 -05:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2021-12-05 11:23:05 -05:00
|
|
|
if ((cnt != timecnt_t::max (cnt.time_domain())) &&
|
2020-09-20 18:34:09 -04:00
|
|
|
(time > pos_samples + _capture_length + cnt_samples)) {
|
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
|
|
|
/* The diskstream doesn't want us to write everything, and this
|
|
|
|
event is past the end of this block, so we're done for now. */
|
2011-07-20 14:13:03 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
/* Read the time, type, and size of the event. */
|
2011-07-20 14:13:03 -04:00
|
|
|
if (!(ret = source.read_prefix (&time, &type, &size))) {
|
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
|
|
|
error << _("Unable to read event prefix, corrupt MIDI ring") << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
break;
|
2009-02-14 20:53:06 -05:00
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
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
|
|
|
/* Enlarge body buffer if necessary now that we know the size. */
|
2009-04-16 10:34:56 -04:00
|
|
|
if (size > buf_capacity) {
|
|
|
|
buf_capacity = size;
|
|
|
|
buf = (uint8_t*)realloc(buf, size);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
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
|
|
|
/* Read the event body into buffer. */
|
2009-04-16 10:34:56 -04:00
|
|
|
ret = source.read_contents(size, buf);
|
2008-06-02 17:41:35 -04:00
|
|
|
if (!ret) {
|
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
|
|
|
error << _("Event has time and size but no body, corrupt MIDI ring") << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
break;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
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
|
|
|
/* Convert event time from absolute to source relative. */
|
2020-09-20 18:34:09 -04:00
|
|
|
if (time < pos_samples) {
|
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
|
|
|
error << _("Event time is before MIDI source position") << endmsg;
|
|
|
|
break;
|
|
|
|
}
|
2020-09-20 18:34:09 -04:00
|
|
|
time -= pos_samples;
|
2015-10-05 10:17:49 -04:00
|
|
|
|
2009-04-16 10:34:56 -04:00
|
|
|
ev.set(buf, size, time);
|
2016-11-07 05:14:55 -05:00
|
|
|
ev.set_event_type(Evoral::MIDI_EVENT);
|
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
|
|
|
ev.set_id(Evoral::next_event_id());
|
2010-07-20 12:27:34 -04:00
|
|
|
|
2009-02-14 20:53:06 -05:00
|
|
|
if (!(ev.is_channel_event() || ev.is_smf_meta_event() || ev.is_sysex())) {
|
2008-06-02 17:41:35 -04:00
|
|
|
continue;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2020-09-20 18:34:09 -04:00
|
|
|
append_event_samples(lock, ev, pos_samples);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2012-07-16 23:10:40 -04:00
|
|
|
Evoral::SMF::flush ();
|
2011-07-14 08:59:51 -04:00
|
|
|
free (buf);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
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
|
|
|
return cnt;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2021-11-14 15:25:31 -05:00
|
|
|
void
|
2022-01-28 17:52:27 -05:00
|
|
|
SMFSource::update_length (timepos_t const & dur)
|
2021-11-14 15:25:31 -05:00
|
|
|
{
|
2022-01-28 17:52:27 -05:00
|
|
|
assert (!_length || (_length.time_domain() == dur.time_domain()));
|
|
|
|
_length = dur;
|
2021-11-14 15:25:31 -05:00
|
|
|
}
|
|
|
|
|
2014-11-22 04:05:42 -05:00
|
|
|
/** Append an event with a timestamp in beats */
|
2008-06-02 17:41:35 -04:00
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::append_event_beats (const WriterLock& lock,
|
2017-09-24 12:03:54 -04:00
|
|
|
const Evoral::Event<Temporal::Beats>& ev)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
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
|
|
|
if (!_writing || ev.size() == 0) {
|
2008-06-02 17:41:35 -04:00
|
|
|
return;
|
2009-02-01 16:04:12 -05:00
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2021-03-28 17:13:47 -04:00
|
|
|
#if 0
|
2015-09-18 09:13:09 -04:00
|
|
|
printf("SMFSource: %s - append_event_beats ID = %d time = %lf, size = %u, data = ",
|
2010-07-20 12:27:34 -04:00
|
|
|
name().c_str(), ev.id(), ev.time(), ev.size());
|
2015-09-18 09:13:09 -04:00
|
|
|
for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");
|
|
|
|
#endif
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2017-09-24 12:03:54 -04:00
|
|
|
Temporal::Beats time = ev.time();
|
2014-11-14 21:19:09 -05:00
|
|
|
if (time < _last_ev_time_beats) {
|
2017-09-24 12:03:54 -04:00
|
|
|
const Temporal::Beats difference = _last_ev_time_beats - time;
|
2020-07-13 20:02:57 -04:00
|
|
|
if (difference < Temporal::Beats::ticks (ppqn())) {
|
2014-11-14 21:19:09 -05:00
|
|
|
/* Close enough. This problem occurs because Sequence is not
|
|
|
|
actually ordered due to fuzzy time comparison. I'm pretty sure
|
|
|
|
this is inherently a bad idea which causes problems all over the
|
|
|
|
place, but tolerate it here for now anyway. */
|
|
|
|
time = _last_ev_time_beats;
|
|
|
|
} else {
|
|
|
|
/* Out of order by more than a tick. */
|
|
|
|
warning << string_compose(_("Skipping event with unordered beat time %1 < %2 (off by %3 beats, %4 ticks)"),
|
2020-07-13 20:02:57 -04:00
|
|
|
ev.time(), _last_ev_time_beats, difference, difference)
|
2014-11-14 21:19:09 -05:00
|
|
|
<< endmsg;
|
|
|
|
return;
|
|
|
|
}
|
2009-02-15 12:30:42 -05:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2010-12-20 17:51:17 -05:00
|
|
|
Evoral::event_id_t event_id;
|
2010-07-20 12:27:34 -04:00
|
|
|
|
2010-12-20 17:51:17 -05:00
|
|
|
if (ev.id() < 0) {
|
|
|
|
event_id = Evoral::next_event_id();
|
|
|
|
} else {
|
|
|
|
event_id = ev.id();
|
|
|
|
}
|
2010-07-20 12:27:34 -04:00
|
|
|
|
|
|
|
if (_model) {
|
|
|
|
_model->append (ev, event_id);
|
|
|
|
}
|
|
|
|
|
2022-01-28 17:52:27 -05:00
|
|
|
assert (!_length || (_length.time_domain() == Temporal::BeatTime));
|
|
|
|
_length = timepos_t (max (_length.beats(), time));
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2017-09-24 12:03:54 -04:00
|
|
|
const Temporal::Beats delta_time_beats = time - _last_ev_time_beats;
|
2015-01-07 00:12:07 -05:00
|
|
|
const uint32_t delta_time_ticks = delta_time_beats.to_ticks(ppqn());
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2010-07-20 12:27:34 -04:00
|
|
|
Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id);
|
2014-11-14 21:19:09 -05:00
|
|
|
_last_ev_time_beats = time;
|
2014-08-28 14:01:52 -04:00
|
|
|
_flags = Source::Flag (_flags & ~Empty);
|
2020-02-17 19:26:20 -05:00
|
|
|
_flags = Source::Flag (_flags & ~Missing);
|
2009-02-15 12:30:42 -05:00
|
|
|
}
|
|
|
|
|
2017-09-18 12:39:17 -04:00
|
|
|
/** Append an event with a timestamp in samples (samplepos_t) */
|
2009-02-15 12:30:42 -05:00
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::append_event_samples (const WriterLock& lock,
|
2017-09-18 12:39:17 -04:00
|
|
|
const Evoral::Event<samplepos_t>& ev,
|
|
|
|
samplepos_t position)
|
2009-02-15 12:30:42 -05:00
|
|
|
{
|
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
|
|
|
if (!_writing || ev.size() == 0) {
|
2008-06-02 17:41:35 -04:00
|
|
|
return;
|
|
|
|
}
|
2009-02-15 12:30:42 -05:00
|
|
|
|
2021-04-04 19:55:01 -04:00
|
|
|
// printf("SMFSource: %s - append_event_samples ID = %d time = %u, size = %u, data = ",
|
|
|
|
// name().c_str(), ev.id(), ev.time(), ev.size());
|
|
|
|
// for (size_t i=0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2017-09-18 12:39:17 -04:00
|
|
|
if (ev.time() < _last_ev_time_samples) {
|
|
|
|
warning << string_compose(_("Skipping event with unordered sample time %1 < %2"),
|
|
|
|
ev.time(), _last_ev_time_samples)
|
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
|
|
|
<< endmsg;
|
2009-02-15 12:30:42 -05:00
|
|
|
return;
|
|
|
|
}
|
2011-02-28 21:04:34 -05:00
|
|
|
|
2020-12-08 11:02:41 -05:00
|
|
|
/* a distance measure that starts at @param position (audio time) and
|
|
|
|
extends for ev.time() (audio time)
|
|
|
|
*/
|
|
|
|
const timecnt_t distance (timepos_t (ev.time()), timepos_t (position));
|
|
|
|
const Temporal::Beats ev_time_beats = distance.beats ();
|
2015-01-07 00:12:07 -05:00
|
|
|
Evoral::event_id_t event_id;
|
2010-07-20 12:27:34 -04:00
|
|
|
|
2010-12-20 17:51:17 -05:00
|
|
|
if (ev.id() < 0) {
|
|
|
|
event_id = Evoral::next_event_id();
|
|
|
|
} else {
|
|
|
|
event_id = ev.id();
|
|
|
|
}
|
2010-07-20 12:27:34 -04:00
|
|
|
|
|
|
|
if (_model) {
|
2017-09-24 12:03:54 -04:00
|
|
|
const Evoral::Event<Temporal::Beats> beat_ev (ev.event_type(),
|
2020-09-20 18:34:09 -04:00
|
|
|
ev_time_beats,
|
|
|
|
ev.size(),
|
|
|
|
const_cast<uint8_t*>(ev.buffer()));
|
2010-07-20 12:27:34 -04:00
|
|
|
_model->append (beat_ev, event_id);
|
2011-06-01 12:50:12 -04:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2022-01-28 17:52:27 -05:00
|
|
|
assert (!_length || (_length.time_domain() == Temporal::BeatTime));
|
|
|
|
_length = timepos_t (max (_length.beats(), ev_time_beats));
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2020-12-08 11:02:41 -05:00
|
|
|
/* a distance measure that starts at @param _last_ev_time_samples (audio time) and
|
|
|
|
extends for ev.time() (audio time)
|
|
|
|
*/
|
|
|
|
const timecnt_t delta_distance (timepos_t (ev.time()), timepos_t (_last_ev_time_samples));
|
|
|
|
const Temporal::Beats delta_time_beats = delta_distance.beats ();
|
|
|
|
const uint32_t delta_time_ticks = delta_time_beats.to_ticks(ppqn());
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2020-12-08 11:02:41 -05:00
|
|
|
Evoral::SMF::append_event_delta (delta_time_ticks, ev.size(), ev.buffer(), event_id);
|
2017-09-18 12:39:17 -04:00
|
|
|
_last_ev_time_samples = ev.time();
|
2014-08-28 14:01:52 -04:00
|
|
|
_flags = Source::Flag (_flags & ~Empty);
|
2020-02-17 19:26:20 -05:00
|
|
|
_flags = Source::Flag (_flags & ~Missing);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode&
|
2022-04-06 23:56:32 -04:00
|
|
|
SMFSource::get_state () const
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2010-12-20 17:51:17 -05:00
|
|
|
XMLNode& node = MidiSource::get_state();
|
2016-08-28 07:19:27 -04:00
|
|
|
node.set_property (X_("origin"), _origin);
|
2010-12-20 17:51:17 -05:00
|
|
|
return node;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2009-10-14 20:57:55 -04:00
|
|
|
SMFSource::set_state (const XMLNode& node, int version)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2009-10-15 14:56:11 -04:00
|
|
|
if (Source::set_state (node, version)) {
|
2009-02-16 21:11:49 -05:00
|
|
|
return -1;
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-10-15 14:56:11 -04:00
|
|
|
if (MidiSource::set_state (node, version)) {
|
2008-06-02 17:41:35 -04:00
|
|
|
return -1;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2009-10-15 14:56:11 -04:00
|
|
|
if (FileSource::set_state (node, version)) {
|
2009-02-16 21:11:49 -05:00
|
|
|
return -1;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::mark_streaming_midi_write_started (const WriterLock& lock, NoteMode mode)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2013-01-20 21:35:53 -05:00
|
|
|
if (!_open && open_for_write()) {
|
|
|
|
error << string_compose (_("cannot open MIDI file %1 for write"), _path) << endmsg;
|
|
|
|
/* XXX should probably throw or return something */
|
|
|
|
return;
|
|
|
|
}
|
2011-03-02 22:54:28 -05:00
|
|
|
|
2014-12-17 16:05:27 -05:00
|
|
|
MidiSource::mark_streaming_midi_write_started (lock, mode);
|
2009-02-14 20:32:41 -05:00
|
|
|
Evoral::SMF::begin_write ();
|
2017-09-24 12:03:54 -04:00
|
|
|
_last_ev_time_beats = Temporal::Beats();
|
2017-09-18 12:39:17 -04:00
|
|
|
_last_ev_time_samples = 0;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::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
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::mark_midi_streaming_write_completed (const WriterLock& lm, Evoral::Sequence<Temporal::Beats>::StuckNoteOption stuck_notes_option, Temporal::Beats when)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2014-12-17 16:05:27 -05:00
|
|
|
MidiSource::mark_midi_streaming_write_completed (lm, stuck_notes_option, when);
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
if (!writable()) {
|
2012-07-16 23:10:40 -04:00
|
|
|
warning << string_compose ("attempt to write to unwritable SMF file %1", _path) << endmsg;
|
2008-06-02 17:41:35 -04:00
|
|
|
return;
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2010-06-27 21:12:21 -04:00
|
|
|
if (_model) {
|
|
|
|
_model->set_edited(false);
|
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2018-07-05 13:34:47 -04:00
|
|
|
try {
|
|
|
|
Evoral::SMF::end_write (_path);
|
|
|
|
} catch (std::exception & e) {
|
|
|
|
error << string_compose (_("Exception while writing %1, file may be corrupt/unusable"), _path) << endmsg;
|
|
|
|
}
|
2010-07-01 14:54:19 -04:00
|
|
|
|
2010-12-20 17:51:17 -05:00
|
|
|
/* data in the file now, not removable */
|
2010-07-01 14:54:19 -04:00
|
|
|
|
2011-06-01 12:50:12 -04:00
|
|
|
mark_nonremovable ();
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2014-04-28 16:44:40 -04:00
|
|
|
bool
|
|
|
|
SMFSource::valid_midi_file (const string& file)
|
|
|
|
{
|
|
|
|
if (safe_midi_file_extension (file) ) {
|
|
|
|
return (SMF::test (file) );
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
bool
|
2010-09-14 11:45:21 -04:00
|
|
|
SMFSource::safe_midi_file_extension (const string& file)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
2013-01-16 12:09:52 -05:00
|
|
|
static regex_t compiled_pattern;
|
|
|
|
static bool compile = true;
|
|
|
|
const int nmatches = 2;
|
|
|
|
regmatch_t matches[nmatches];
|
2015-10-05 10:17:49 -04:00
|
|
|
|
2014-02-27 20:35:23 -05:00
|
|
|
if (Glib::file_test (file, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
if (!Glib::file_test (file, Glib::FILE_TEST_IS_REGULAR)) {
|
|
|
|
/* exists but is not a regular file */
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (compile && regcomp (&compiled_pattern, "\\.[mM][iI][dD][iI]?$", REG_EXTENDED)) {
|
2013-01-16 12:09:52 -05:00
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
compile = false;
|
|
|
|
}
|
2015-10-05 10:17:49 -04:00
|
|
|
|
2013-01-16 12:09:52 -05:00
|
|
|
if (regexec (&compiled_pattern, file.c_str(), nmatches, matches, 0)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
static bool compare_eventlist (
|
2017-09-24 12:03:54 -04:00
|
|
|
const std::pair< const Evoral::Event<Temporal::Beats>*, gint >& a,
|
|
|
|
const std::pair< const Evoral::Event<Temporal::Beats>*, gint >& b) {
|
2014-01-20 07:44:31 -05:00
|
|
|
return ( a.first->time() < b.first->time() );
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::load_model (const WriterLock& lock, bool force_reload)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
Change the type of reference held by a MidiModel to its MidiSource
This also requires a change in the type of reference held by
a MidiAutomationListBinder.
Both the MidiSource and MidiModel have a reference to each other, and it is
important that we avoid circular references to avoid problems with object
destruction. We had been accomplishing this by having the Model hold a
weak_ptr<MidiSource>. However, the lifetime of a MidiSource and its MidiModel
are coincident and there's really no need to use a smart ptr at all. A normal
reference is just fine. However, due to constructors that accept a serialized
state, we cannot use an actual reference (we cannot set the constructor in the
initializer list), so we use a bare ptr instead.
This forces a similar change in MidiAutomationListBinder, which also maintains
a reference to the Source. However, the only purpose of this object is to
ensure that if the Source is destroyed, relevant commands will be removed from
the undo/redo history, and so all that matters here is that the binder connects
to the Destroyed signal of the source, and arranges for its own destruction
when received.
Note that the previous construction of the binder, actually holding a
shared_ptr<MidiSource> would appear have prevented the Destroyed signal from
ever being emitted (from ~Destructible), and so this may also be a bug fix that
allows MidiSources to actually be deleted (the memory object, not the file).
2022-03-30 14:49:00 -04:00
|
|
|
invalidate (lock);
|
|
|
|
load_model_unlocked (force_reload);
|
|
|
|
invalidate (lock);
|
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
Change the type of reference held by a MidiModel to its MidiSource
This also requires a change in the type of reference held by
a MidiAutomationListBinder.
Both the MidiSource and MidiModel have a reference to each other, and it is
important that we avoid circular references to avoid problems with object
destruction. We had been accomplishing this by having the Model hold a
weak_ptr<MidiSource>. However, the lifetime of a MidiSource and its MidiModel
are coincident and there's really no need to use a smart ptr at all. A normal
reference is just fine. However, due to constructors that accept a serialized
state, we cannot use an actual reference (we cannot set the constructor in the
initializer list), so we use a bare ptr instead.
This forces a similar change in MidiAutomationListBinder, which also maintains
a reference to the Source. However, the only purpose of this object is to
ensure that if the Source is destroyed, relevant commands will be removed from
the undo/redo history, and so all that matters here is that the binder connects
to the Destroyed signal of the source, and arranges for its own destruction
when received.
Note that the previous construction of the binder, actually holding a
shared_ptr<MidiSource> would appear have prevented the Destroyed signal from
ever being emitted (from ~Destructible), and so this may also be a bug fix that
allows MidiSources to actually be deleted (the memory object, not the file).
2022-03-30 14:49:00 -04:00
|
|
|
void
|
|
|
|
SMFSource::load_model_unlocked (bool force_reload)
|
|
|
|
{
|
|
|
|
assert (!_writing);
|
|
|
|
|
2010-12-09 16:34:31 -05:00
|
|
|
if (!_model) {
|
Change the type of reference held by a MidiModel to its MidiSource
This also requires a change in the type of reference held by
a MidiAutomationListBinder.
Both the MidiSource and MidiModel have a reference to each other, and it is
important that we avoid circular references to avoid problems with object
destruction. We had been accomplishing this by having the Model hold a
weak_ptr<MidiSource>. However, the lifetime of a MidiSource and its MidiModel
are coincident and there's really no need to use a smart ptr at all. A normal
reference is just fine. However, due to constructors that accept a serialized
state, we cannot use an actual reference (we cannot set the constructor in the
initializer list), so we use a bare ptr instead.
This forces a similar change in MidiAutomationListBinder, which also maintains
a reference to the Source. However, the only purpose of this object is to
ensure that if the Source is destroyed, relevant commands will be removed from
the undo/redo history, and so all that matters here is that the binder connects
to the Destroyed signal of the source, and arranges for its own destruction
when received.
Note that the previous construction of the binder, actually holding a
shared_ptr<MidiSource> would appear have prevented the Destroyed signal from
ever being emitted (from ~Destructible), and so this may also be a bug fix that
allows MidiSources to actually be deleted (the memory object, not the file).
2022-03-30 14:49:00 -04:00
|
|
|
_model = boost::shared_ptr<MidiModel> (new MidiModel (*this));
|
2008-06-02 17:41:35 -04:00
|
|
|
} else {
|
|
|
|
_model->clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
_model->start_write();
|
2009-02-14 20:32:41 -05:00
|
|
|
Evoral::SMF::seek_to_start();
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
uint64_t time = 0; /* in SMF ticks */
|
2017-09-24 12:03:54 -04:00
|
|
|
Evoral::Event<Temporal::Beats> ev;
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2011-10-19 14:11:31 -04:00
|
|
|
uint32_t scratch_size = 0; // keep track of scratch and minimize reallocs
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
uint32_t delta_t = 0;
|
|
|
|
uint32_t size = 0;
|
|
|
|
uint8_t* buf = NULL;
|
|
|
|
int ret;
|
2021-03-19 01:29:03 -04:00
|
|
|
Evoral::event_id_t event_id;
|
2014-01-20 07:44:31 -05:00
|
|
|
bool have_event_id;
|
2010-07-20 12:27:34 -04:00
|
|
|
|
2022-03-01 09:02:49 -05:00
|
|
|
_num_channels = 0;
|
|
|
|
_n_note_on_events = 0;
|
|
|
|
_has_pgm_change = false;
|
|
|
|
_used_channels.reset ();
|
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
// TODO simplify event allocation
|
2017-09-24 12:03:54 -04:00
|
|
|
std::list< std::pair< Evoral::Event<Temporal::Beats>*, gint > > eventlist;
|
2010-07-20 12:27:34 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
for (unsigned i = 1; i <= num_tracks(); ++i) {
|
|
|
|
if (seek_to_track(i)) continue;
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
time = 0;
|
|
|
|
have_event_id = false;
|
2010-07-20 12:27:34 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
time += delta_t;
|
2010-07-20 12:27:34 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
if (ret == 0) {
|
|
|
|
/* meta-event : did we get an event ID ? */
|
|
|
|
if (event_id >= 0) {
|
|
|
|
have_event_id = true;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2009-09-14 12:01:32 -04:00
|
|
|
|
2022-03-01 09:02:49 -05:00
|
|
|
/* aggregate information about channels and pgm-changes */
|
|
|
|
uint8_t type = buf[0] & 0xf0;
|
|
|
|
uint8_t chan = buf[0] & 0x0f;
|
|
|
|
if (type >= 0x80 && type <= 0xE0) {
|
|
|
|
_used_channels.set(chan);
|
|
|
|
switch (type) {
|
|
|
|
case MIDI_CMD_NOTE_ON:
|
|
|
|
++_n_note_on_events;
|
|
|
|
break;
|
|
|
|
case MIDI_CMD_PGM_CHANGE:
|
|
|
|
_has_pgm_change = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
if (ret > 0) {
|
|
|
|
/* not a meta-event */
|
2010-05-30 16:13:29 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
if (!have_event_id) {
|
|
|
|
event_id = Evoral::next_event_id();
|
|
|
|
}
|
2017-09-24 12:03:54 -04:00
|
|
|
const Temporal::Beats event_time = Temporal::Beats::ticks_at_rate(time, ppqn());
|
2010-06-01 16:40:45 -04:00
|
|
|
#ifndef NDEBUG
|
2014-01-20 07:44:31 -05:00
|
|
|
std::string ss;
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
for (uint32_t xx = 0; xx < size; ++xx) {
|
|
|
|
char b[8];
|
|
|
|
snprintf (b, sizeof (b), "0x%x ", buf[xx]);
|
|
|
|
ss += b;
|
|
|
|
}
|
2010-12-20 17:51:17 -05:00
|
|
|
|
2016-11-07 05:14:55 -05:00
|
|
|
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF %7 load model delta %1, time %2, size %3 buf %4, id %6\n",
|
|
|
|
delta_t, time, size, ss, event_id, name()));
|
2010-06-01 16:40:45 -04:00
|
|
|
#endif
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
eventlist.push_back(make_pair (
|
2017-09-24 12:03:54 -04:00
|
|
|
new Evoral::Event<Temporal::Beats> (
|
2016-11-07 05:14:55 -05:00
|
|
|
Evoral::MIDI_EVENT, event_time,
|
2014-01-20 07:44:31 -05:00
|
|
|
size, buf, true)
|
|
|
|
, event_id));
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
// Set size to max capacity to minimize allocs in read_event
|
|
|
|
scratch_size = std::max(size, scratch_size);
|
|
|
|
size = scratch_size;
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2022-01-28 17:52:27 -05:00
|
|
|
assert (!_length || (_length.time_domain() == Temporal::BeatTime));
|
|
|
|
_length = max (_length, timepos_t (event_time));
|
2014-01-20 07:44:31 -05:00
|
|
|
}
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
/* event ID's must immediately precede the event they are for */
|
|
|
|
have_event_id = false;
|
2010-12-20 17:51:17 -05:00
|
|
|
}
|
2014-01-20 07:44:31 -05:00
|
|
|
}
|
2009-10-14 12:10:01 -04:00
|
|
|
|
2022-03-01 09:02:49 -05:00
|
|
|
_num_channels = _used_channels.size();
|
|
|
|
|
2014-01-20 07:44:31 -05:00
|
|
|
eventlist.sort(compare_eventlist);
|
2011-06-01 12:50:12 -04:00
|
|
|
|
2017-09-24 12:03:54 -04:00
|
|
|
std::list< std::pair< Evoral::Event<Temporal::Beats>*, gint > >::iterator it;
|
2014-01-20 07:44:31 -05:00
|
|
|
for (it=eventlist.begin(); it!=eventlist.end(); ++it) {
|
|
|
|
_model->append (*it->first, it->second);
|
|
|
|
delete it->first;
|
2009-01-27 23:55:31 -05:00
|
|
|
}
|
|
|
|
|
2015-09-18 09:13:09 -04:00
|
|
|
// cerr << "----SMF-SRC-----\n";
|
|
|
|
// _playback_buf->dump (cerr);
|
|
|
|
// cerr << "----------------\n";
|
|
|
|
|
2020-12-08 22:36:42 -05:00
|
|
|
_model->end_write (Evoral::Sequence<Temporal::Beats>::ResolveStuckNotes, _length.beats());
|
2011-07-14 08:59:51 -04:00
|
|
|
_model->set_edited (false);
|
2009-01-28 19:18:26 -05:00
|
|
|
|
2022-03-30 12:33:10 -04:00
|
|
|
free (buf);
|
2009-01-28 19:18:26 -05:00
|
|
|
}
|
|
|
|
|
2022-03-01 09:02:49 -05:00
|
|
|
Evoral::SMF::UsedChannels
|
|
|
|
SMFSource::used_midi_channels()
|
|
|
|
{
|
|
|
|
return _used_channels;
|
|
|
|
}
|
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::destroy_model (const WriterLock& lock)
|
2008-06-02 17:41:35 -04:00
|
|
|
{
|
|
|
|
//cerr << _name << " destroying model " << _model.get() << endl;
|
|
|
|
_model.reset();
|
2014-12-17 16:05:27 -05:00
|
|
|
invalidate(lock);
|
2008-06-02 17:41:35 -04:00
|
|
|
}
|
|
|
|
|
2008-09-29 20:45:26 -04:00
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::flush_midi (const WriterLock& lock)
|
2008-09-29 20:45:26 -04:00
|
|
|
{
|
2021-12-11 08:42:36 -05:00
|
|
|
if (!writable() || _length.is_zero()) {
|
2010-12-20 17:51:17 -05:00
|
|
|
return;
|
|
|
|
}
|
2010-06-26 09:45:59 -04:00
|
|
|
|
2014-12-17 16:05:27 -05:00
|
|
|
ensure_disk_file (lock);
|
2014-08-28 14:01:52 -04:00
|
|
|
|
2015-04-20 15:44:20 -04:00
|
|
|
Evoral::SMF::end_write (_path);
|
2010-12-20 17:51:17 -05:00
|
|
|
/* data in the file means its no longer removable */
|
2011-06-01 12:50:12 -04:00
|
|
|
mark_nonremovable ();
|
2014-11-20 15:36:11 -05:00
|
|
|
|
2014-12-17 16:05:27 -05:00
|
|
|
invalidate(lock);
|
2008-09-29 20:45:26 -04:00
|
|
|
}
|
|
|
|
|
2010-07-16 10:55:11 -04:00
|
|
|
void
|
|
|
|
SMFSource::set_path (const string& p)
|
|
|
|
{
|
2010-12-20 17:51:17 -05:00
|
|
|
FileSource::set_path (p);
|
2010-07-16 10:55:11 -04:00
|
|
|
}
|
2011-05-08 19:45:33 -04:00
|
|
|
|
|
|
|
/** Ensure that this source has some file on disk, even if it's just a SMF header */
|
|
|
|
void
|
2022-03-30 14:56:04 -04:00
|
|
|
SMFSource::ensure_disk_file (const WriterLock& lock)
|
2011-05-08 19:45:33 -04:00
|
|
|
{
|
2014-09-15 12:38:54 -04:00
|
|
|
if (!writable()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-05-08 19:45:33 -04:00
|
|
|
if (_model) {
|
|
|
|
/* We have a model, so write it to disk; see MidiSource::session_saved
|
|
|
|
for an explanation of what we are doing here.
|
|
|
|
*/
|
|
|
|
boost::shared_ptr<MidiModel> mm = _model;
|
|
|
|
_model.reset ();
|
2014-12-17 16:05:27 -05:00
|
|
|
mm->sync_to_source (lock);
|
2011-05-08 19:45:33 -04:00
|
|
|
_model = mm;
|
2014-12-17 16:05:27 -05:00
|
|
|
invalidate(lock);
|
2011-05-08 19:45:33 -04:00
|
|
|
} else {
|
|
|
|
/* No model; if it's not already open, it's an empty source, so create
|
|
|
|
and open it for writing.
|
|
|
|
*/
|
|
|
|
if (!_open) {
|
|
|
|
open_for_write ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-04 15:26:44 -04:00
|
|
|
void
|
|
|
|
SMFSource::prevent_deletion ()
|
|
|
|
{
|
|
|
|
/* Unlike the audio case, the MIDI file remains mutable (because we can
|
|
|
|
edit MIDI data)
|
|
|
|
*/
|
2015-10-04 14:51:05 -04:00
|
|
|
|
2014-04-04 15:26:44 -04:00
|
|
|
_flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
|
|
|
|
}
|