The time stamp of an event is now always tempo, from file to model and back again. Frame time is only relevant at playback or recording time, in the audio thread (MidiModel and MidiBuffer). I think perhaps we don't need to change the actual time from double (which is convenient for math), it is the time base conversion that caused problems. Using a correct equality comparison (i.e. not == which is not correct for floating point) should probably make the undo issues go away, in 99.99% of cases anyway. There's almost certainly some regressions in here somewhere, but they do not seem to be time related. The bugs I'm hitting in testing are old ones that seem unrelated now, so it's checkpoint time. This sets us up for fancy things like tempo map import and tempo/meter changes halfway through MIDI regions, but for now it's still assumed that the tempo at the start of the region is valid for the duration of the entire region. git-svn-id: svn://localhost/ardour2/branches/3.0@4582 d708f5d6-7413-0410-9779-e7cbd77b26cf
240 lines
5.5 KiB
C++
240 lines
5.5 KiB
C++
/*
|
|
Copyright (C) 2006 Paul Davis
|
|
Written by Dave Robillard, 2006
|
|
|
|
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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <float.h>
|
|
#include <cerrno>
|
|
#include <ctime>
|
|
#include <cmath>
|
|
#include <iomanip>
|
|
#include <algorithm>
|
|
|
|
#include <pbd/xml++.h>
|
|
#include <pbd/pthread_utils.h>
|
|
#include <pbd/basename.h>
|
|
|
|
#include <ardour/audioengine.h>
|
|
#include <ardour/midi_ring_buffer.h>
|
|
#include <ardour/midi_source.h>
|
|
#include <ardour/session.h>
|
|
#include <ardour/session_directory.h>
|
|
#include <ardour/source_factory.h>
|
|
#include <ardour/tempo.h>
|
|
|
|
#include "i18n.h"
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
|
|
sigc::signal<void,MidiSource *> MidiSource::MidiSourceCreated;
|
|
|
|
MidiSource::MidiSource (Session& s, string name)
|
|
: Source (s, name, DataType::MIDI)
|
|
, _timeline_position(0)
|
|
, _model(new MidiModel(this))
|
|
, _writing (false)
|
|
, _model_iter(*_model.get(), 0.0)
|
|
, _last_read_end(0)
|
|
{
|
|
_read_data_count = 0;
|
|
_write_data_count = 0;
|
|
}
|
|
|
|
MidiSource::MidiSource (Session& s, const XMLNode& node)
|
|
: Source (s, node)
|
|
, _timeline_position(0)
|
|
, _model(new MidiModel(this))
|
|
, _writing (false)
|
|
, _model_iter(*_model.get(), 0.0)
|
|
, _last_read_end(0)
|
|
{
|
|
_read_data_count = 0;
|
|
_write_data_count = 0;
|
|
|
|
if (set_state (node)) {
|
|
throw failed_constructor();
|
|
}
|
|
}
|
|
|
|
MidiSource::~MidiSource ()
|
|
{
|
|
}
|
|
|
|
XMLNode&
|
|
MidiSource::get_state ()
|
|
{
|
|
XMLNode& node (Source::get_state());
|
|
|
|
if (_captured_for.length()) {
|
|
node.add_property ("captured-for", _captured_for);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
int
|
|
MidiSource::set_state (const XMLNode& node)
|
|
{
|
|
const XMLProperty* prop;
|
|
|
|
Source::set_state (node);
|
|
|
|
if ((prop = node.property ("captured-for")) != 0) {
|
|
_captured_for = prop->value();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
nframes_t
|
|
MidiSource::midi_read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_t cnt,
|
|
nframes_t stamp_offset, nframes_t negative_stamp_offset) const
|
|
{
|
|
|
|
Glib::Mutex::Lock lm (_lock);
|
|
if (_model) {
|
|
// FIXME: assumes tempo never changes after start
|
|
const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position);
|
|
const double frames_per_beat = tempo.frames_per_beat(
|
|
_session.engine().frame_rate(),
|
|
_session.tempo_map().meter_at(_timeline_position));
|
|
#define BEATS_TO_FRAMES(t) (((t) * frames_per_beat) + stamp_offset - negative_stamp_offset)
|
|
|
|
Evoral::Sequence<double>::const_iterator& i = _model_iter;
|
|
|
|
if (_last_read_end == 0 || start != _last_read_end) {
|
|
cerr << "MidiSource::midi_read seeking to frame " << start << endl;
|
|
for (i = _model->begin(); i != _model->end(); ++i) {
|
|
if (BEATS_TO_FRAMES(i->time()) >= start) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
_last_read_end = start + cnt;
|
|
|
|
for (; i != _model->end(); ++i) {
|
|
const nframes_t time_frames = BEATS_TO_FRAMES(i->time());
|
|
if (time_frames < start + cnt) {
|
|
dst.write(time_frames, i->event_type(), i->size(), i->buffer());
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return cnt;
|
|
} else {
|
|
return read_unlocked (dst, start, cnt, stamp_offset, negative_stamp_offset);
|
|
}
|
|
}
|
|
|
|
nframes_t
|
|
MidiSource::midi_write (MidiRingBuffer<nframes_t>& dst, nframes_t cnt)
|
|
{
|
|
Glib::Mutex::Lock lm (_lock);
|
|
return write_unlocked (dst, cnt);
|
|
}
|
|
|
|
bool
|
|
MidiSource::file_changed (string path)
|
|
{
|
|
struct stat stat_file;
|
|
|
|
int e1 = stat (path.c_str(), &stat_file);
|
|
|
|
return !e1;
|
|
}
|
|
|
|
void
|
|
MidiSource::mark_streaming_midi_write_started (NoteMode mode, nframes_t start_frame)
|
|
{
|
|
set_timeline_position(start_frame); // why do I have a feeling this can break somehow...
|
|
|
|
if (_model) {
|
|
_model->set_note_mode(mode);
|
|
_model->start_write();
|
|
}
|
|
|
|
_writing = true;
|
|
}
|
|
|
|
void
|
|
MidiSource::mark_streaming_write_started ()
|
|
{
|
|
if (_model) {
|
|
_model->start_write();
|
|
}
|
|
|
|
_writing = true;
|
|
}
|
|
|
|
void
|
|
MidiSource::mark_streaming_write_completed ()
|
|
{
|
|
if (_model) {
|
|
_model->end_write(false); // FIXME: param?
|
|
}
|
|
|
|
_writing = false;
|
|
}
|
|
|
|
void
|
|
MidiSource::session_saved()
|
|
{
|
|
flush_midi();
|
|
|
|
if (_model && _model->edited()) {
|
|
string newname;
|
|
const string basename = PBD::basename_nosuffix(_name);
|
|
string::size_type last_dash = basename.find_last_of("-");
|
|
if (last_dash == string::npos || last_dash == basename.find_first_of("-")) {
|
|
newname = basename + "-1";
|
|
} else {
|
|
stringstream ss(basename.substr(last_dash+1));
|
|
unsigned write_count = 0;
|
|
ss >> write_count;
|
|
cerr << "WRITE COUNT: " << write_count << endl;
|
|
++write_count; // start at 1
|
|
ss.clear();
|
|
ss << basename.substr(0, last_dash) << "-" << write_count;
|
|
newname = ss.str();
|
|
}
|
|
|
|
string newpath = _session.session_directory().midi_path().to_string() +"/"+ newname + ".mid";
|
|
|
|
boost::shared_ptr<MidiSource> newsrc = boost::dynamic_pointer_cast<MidiSource>(
|
|
SourceFactory::createWritable(DataType::MIDI, _session, newpath, 1, 0, true));
|
|
|
|
newsrc->set_timeline_position(_timeline_position);
|
|
_model->write_to(newsrc);
|
|
|
|
// cyclic dependency here, ugly :(
|
|
newsrc->set_model(_model);
|
|
_model->set_midi_source(newsrc.get());
|
|
|
|
newsrc->flush_midi();
|
|
|
|
Switched.emit(newsrc);
|
|
}
|
|
}
|
|
|