Saving of edited MIDI data to disk (on session save).
Seems to be a pretty random problem with note duration restoring though... git-svn-id: svn://localhost/ardour2/trunk@2290 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
861181d742
commit
5156998e6e
@ -375,11 +375,6 @@ Editor::set_midi_edit_mode (MidiEditMode m, bool force)
|
||||
break;
|
||||
}
|
||||
|
||||
if (mouse_mode == MouseNote)
|
||||
midi_toolbar_frame.show();
|
||||
else
|
||||
midi_toolbar_frame.hide();
|
||||
|
||||
ignore_midi_edit_mode_toggle = false;
|
||||
|
||||
set_midi_edit_cursor (current_midi_edit_mode());
|
||||
|
@ -122,6 +122,8 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
|
||||
}
|
||||
_model->ContentsChanged.connect(sigc::mem_fun(this, &MidiRegionView::redisplay_model));
|
||||
}
|
||||
|
||||
midi_region()->midi_source(0)->Switched.connect(sigc::mem_fun(this, &MidiRegionView::switch_source));
|
||||
|
||||
group->signal_event().connect (mem_fun (this, &MidiRegionView::canvas_event), false);
|
||||
|
||||
@ -393,6 +395,8 @@ void
|
||||
MidiRegionView::redisplay_model()
|
||||
{
|
||||
clear_events();
|
||||
|
||||
//cerr << "Redisplaying model " << _model << endl;
|
||||
|
||||
if (_model) {
|
||||
begin_write();
|
||||
@ -758,4 +762,13 @@ MidiRegionView::note_entered(ArdourCanvas::CanvasMidiEvent* ev)
|
||||
note_selected(ev, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MidiRegionView::switch_source(boost::shared_ptr<Source> src)
|
||||
{
|
||||
boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
|
||||
if (msrc)
|
||||
display_model(msrc->model());
|
||||
}
|
||||
|
||||
|
@ -161,6 +161,7 @@ class MidiRegionView : public RegionView
|
||||
private:
|
||||
|
||||
void clear_events();
|
||||
void switch_source(boost::shared_ptr<ARDOUR::Source> src);
|
||||
|
||||
bool canvas_event(GdkEvent* ev);
|
||||
bool note_canvas_event(GdkEvent* ev);
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include <queue>
|
||||
#include <boost/utility.hpp>
|
||||
#include <glibmm/thread.h>
|
||||
#include <pbd/command.h>
|
||||
#include <ardour/types.h>
|
||||
#include <ardour/midi_buffer.h>
|
||||
@ -31,6 +32,7 @@
|
||||
namespace ARDOUR {
|
||||
|
||||
class Session;
|
||||
class MidiSource;
|
||||
|
||||
|
||||
/** This is a slightly higher level (than MidiBuffer) model of MIDI note data.
|
||||
@ -143,14 +145,15 @@ public:
|
||||
MidiModel::DeltaCommand* new_delta_command(const std::string name="midi edit");
|
||||
void apply_command(Command* cmd);
|
||||
|
||||
bool write_new_source(const std::string& path);
|
||||
bool edited() const { return _edited; }
|
||||
bool write_to(boost::shared_ptr<MidiSource> source);
|
||||
|
||||
sigc::signal<void> ContentsChanged;
|
||||
|
||||
private:
|
||||
friend class DeltaCommand;
|
||||
void add_note(const Note& note);
|
||||
void remove_note(const Note& note);
|
||||
void add_note_unlocked(const Note& note);
|
||||
void remove_note_unlocked(const Note& note);
|
||||
|
||||
bool is_sorted() const;
|
||||
|
||||
@ -159,12 +162,15 @@ private:
|
||||
|
||||
Session& _session;
|
||||
|
||||
Glib::RWLock _lock;
|
||||
|
||||
Notes _notes;
|
||||
NoteMode _note_mode;
|
||||
|
||||
typedef std::vector<size_t> WriteNotes;
|
||||
WriteNotes _write_notes;
|
||||
bool _writing;
|
||||
bool _edited;
|
||||
|
||||
// note state for read():
|
||||
|
||||
|
@ -90,6 +90,8 @@ class MidiRegion : public Region
|
||||
void recompute_at_start ();
|
||||
void recompute_at_end ();
|
||||
|
||||
void switch_source(boost::shared_ptr<Source> source);
|
||||
|
||||
protected:
|
||||
|
||||
int set_live_state (const XMLNode&, Change&, bool send);
|
||||
|
@ -51,12 +51,17 @@ class MidiSource : public Source
|
||||
|
||||
virtual nframes_t read (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset) const;
|
||||
virtual nframes_t write (MidiRingBuffer& src, nframes_t cnt);
|
||||
virtual void append_event_unlocked(const MidiEvent& ev) = 0;
|
||||
|
||||
virtual void flush() {}
|
||||
|
||||
virtual void mark_for_remove() = 0;
|
||||
virtual void mark_streaming_midi_write_started (NoteMode mode);
|
||||
virtual void mark_streaming_write_started ();
|
||||
virtual void mark_streaming_write_completed ();
|
||||
|
||||
void set_timeline_position (nframes_t when) { _timeline_position = when; }
|
||||
|
||||
virtual void session_saved();
|
||||
|
||||
string captured_for() const { return _captured_for; }
|
||||
@ -80,6 +85,7 @@ class MidiSource : public Source
|
||||
virtual bool model_loaded() const { return _model_loaded; }
|
||||
|
||||
boost::shared_ptr<MidiModel> model() { return _model; }
|
||||
void set_model(boost::shared_ptr<MidiModel> m) { _model = m; _model_loaded = true; }
|
||||
|
||||
protected:
|
||||
virtual nframes_t read_unlocked (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset) const = 0;
|
||||
@ -87,6 +93,7 @@ class MidiSource : public Source
|
||||
|
||||
mutable Glib::Mutex _lock;
|
||||
string _captured_for;
|
||||
uint64_t _timeline_position;
|
||||
mutable uint32_t _read_data_count; ///< modified in read()
|
||||
mutable uint32_t _write_data_count; ///< modified in write()
|
||||
|
||||
|
@ -237,10 +237,10 @@ class Region : public PBD::StatefulDestructible, public boost::enable_shared_fro
|
||||
string _name;
|
||||
DataType _type;
|
||||
Flag _flags;
|
||||
nframes_t _start;
|
||||
nframes_t _length;
|
||||
nframes_t _position;
|
||||
nframes_t _sync_position;
|
||||
nframes_t _start;
|
||||
nframes_t _length;
|
||||
nframes_t _position;
|
||||
nframes_t _sync_position;
|
||||
layer_t _layer;
|
||||
mutable RegionEditState _first_edit;
|
||||
int _frozen;
|
||||
@ -248,10 +248,11 @@ class Region : public PBD::StatefulDestructible, public boost::enable_shared_fro
|
||||
Change _pending_changed;
|
||||
uint64_t _last_layer_op; ///< timestamp
|
||||
Glib::Mutex _lock;
|
||||
boost::weak_ptr<ARDOUR::Playlist> _playlist;
|
||||
SourceList _sources;
|
||||
/** Used when timefx are applied, so we can always use the original source */
|
||||
SourceList _master_sources;
|
||||
|
||||
boost::weak_ptr<ARDOUR::Playlist> _playlist;
|
||||
};
|
||||
|
||||
} /* namespace ARDOUR */
|
||||
|
@ -57,9 +57,9 @@ class SMFSource : public MidiSource {
|
||||
// FIXME and thus are useless for MIDI.. but make MidiDiskstream compile easier! :)
|
||||
|
||||
virtual nframes_t last_capture_start_frame() const { return 0; }
|
||||
virtual void mark_capture_start (nframes_t) {}
|
||||
virtual void mark_capture_end () {}
|
||||
virtual void clear_capture_marks() {}
|
||||
virtual void mark_capture_start (nframes_t) {}
|
||||
virtual void mark_capture_end () {}
|
||||
virtual void clear_capture_marks() {}
|
||||
|
||||
bool set_name (const std::string& newname) { return (set_source_name(newname, false) == 0); }
|
||||
int set_source_name (string newname, bool destructive);
|
||||
@ -69,9 +69,12 @@ class SMFSource : public MidiSource {
|
||||
void set_allow_remove_if_empty (bool yn);
|
||||
void mark_for_remove();
|
||||
|
||||
int update_header (nframes_t when, struct tm&, time_t);
|
||||
void append_event_unlocked(const MidiEvent& ev);
|
||||
|
||||
int flush_header ();
|
||||
int flush_footer ();
|
||||
|
||||
void flush() { flush_header(); flush_footer(); }
|
||||
|
||||
int move_to_trash (const string trash_dir_name);
|
||||
|
||||
@ -120,7 +123,6 @@ class SMFSource : public MidiSource {
|
||||
Flag _flags;
|
||||
string _take_id;
|
||||
bool _allow_remove_if_empty;
|
||||
uint64_t _timeline_position;
|
||||
FILE* _fd;
|
||||
double _last_ev_time; // last frame time written, relative to source start
|
||||
uint32_t _track_size;
|
||||
|
@ -71,8 +71,8 @@ class Source : public SessionObject
|
||||
|
||||
uint32_t used() const;
|
||||
|
||||
|
||||
static sigc::signal<void,Source*> SourceCreated;
|
||||
static sigc::signal<void,Source*> SourceCreated;
|
||||
sigc::signal<void,boost::shared_ptr<Source> > Switched;
|
||||
|
||||
protected:
|
||||
void update_length (nframes_t pos, nframes_t cnt);
|
||||
|
@ -31,7 +31,6 @@
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <pbd/error.h>
|
||||
#include <pbd/basename.h>
|
||||
#include <glibmm/thread.h>
|
||||
#include <pbd/xml++.h>
|
||||
#include <pbd/memento_command.h>
|
||||
|
@ -87,7 +87,7 @@ Filter::finish (boost::shared_ptr<Region> region, SourceList& nsrcs)
|
||||
|
||||
boost::shared_ptr<SMFSource> smfs = boost::dynamic_pointer_cast<SMFSource>(*si);
|
||||
if (smfs) {
|
||||
smfs->update_header (region->position(), *now, xnow);
|
||||
smfs->set_timeline_position (region->position());
|
||||
smfs->flush_footer ();
|
||||
}
|
||||
}
|
||||
|
@ -1009,7 +1009,7 @@ MidiDiskstream::transport_stopped (struct tm& when, time_t twhen, bool abort_cap
|
||||
/* figure out the name for this take */
|
||||
|
||||
srcs.push_back (_write_source);
|
||||
_write_source->update_header (capture_info.front()->start, when, twhen);
|
||||
_write_source->set_timeline_position (capture_info.front()->start);
|
||||
_write_source->set_captured_for (_name);
|
||||
|
||||
string whole_file_region_name;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <pbd/enumwriter.h>
|
||||
#include <ardour/midi_model.h>
|
||||
#include <ardour/midi_events.h>
|
||||
#include <ardour/midi_source.h>
|
||||
#include <ardour/types.h>
|
||||
#include <ardour/session.h>
|
||||
|
||||
@ -102,6 +103,7 @@ MidiModel::MidiModel(Session& s, size_t size)
|
||||
, _notes(size)
|
||||
, _note_mode(Sustained)
|
||||
, _writing(false)
|
||||
, _edited(false)
|
||||
, _active_notes(LaterNoteEndComparator())
|
||||
{
|
||||
}
|
||||
@ -116,7 +118,7 @@ MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframe
|
||||
{
|
||||
size_t read_events = 0;
|
||||
|
||||
//cerr << "MM READ @ " << start << " + " << nframes << endl;
|
||||
cerr << "MM READ @ " << start << " + " << nframes << endl;
|
||||
|
||||
/* FIXME: cache last lookup value to avoid the search */
|
||||
|
||||
@ -125,13 +127,13 @@ MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframe
|
||||
/* FIXME: cache last lookup value to avoid the search */
|
||||
for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
|
||||
|
||||
//cerr << "MM ON " << n->time() << endl;
|
||||
cerr << "MM NOTE " << n->time() << endl;
|
||||
|
||||
while ( ! _active_notes.empty() ) {
|
||||
const Note* const earliest_off = _active_notes.top();
|
||||
const MidiEvent& ev = earliest_off->off_event();
|
||||
if (ev.time() < start + nframes && ev.time() <= n->time()) {
|
||||
dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
|
||||
const MidiEvent& off_ev = earliest_off->off_event();
|
||||
if (off_ev.time() < start + nframes && off_ev.time() <= n->time()) {
|
||||
dst.write(off_ev.time() + stamp_offset, off_ev.size(), off_ev.buffer());
|
||||
_active_notes.pop();
|
||||
++read_events;
|
||||
} else {
|
||||
@ -144,8 +146,8 @@ MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframe
|
||||
|
||||
// Note on
|
||||
if (n->time() >= start) {
|
||||
const MidiEvent& ev = n->on_event();
|
||||
dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
|
||||
const MidiEvent& on_ev = n->on_event();
|
||||
dst.write(on_ev.time() + stamp_offset, on_ev.size(), on_ev.buffer());
|
||||
_active_notes.push(&(*n));
|
||||
++read_events;
|
||||
}
|
||||
@ -234,6 +236,8 @@ MidiModel::append(const MidiBuffer& buf)
|
||||
{
|
||||
assert(_writing);
|
||||
|
||||
_lock.writer_lock();
|
||||
|
||||
for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
|
||||
const MidiEvent& ev = *i;
|
||||
|
||||
@ -244,6 +248,8 @@ MidiModel::append(const MidiBuffer& buf)
|
||||
else if (ev.type() == MIDI_CMD_NOTE_OFF)
|
||||
append_note_off(ev.time(), ev.note());
|
||||
}
|
||||
|
||||
_lock.writer_unlock();
|
||||
}
|
||||
|
||||
|
||||
@ -311,16 +317,18 @@ MidiModel::append_note_off(double time, uint8_t note_num)
|
||||
|
||||
|
||||
void
|
||||
MidiModel::add_note(const Note& note)
|
||||
MidiModel::add_note_unlocked(const Note& note)
|
||||
{
|
||||
cerr << "MidiModel " << this << " add note " << (int)note.note() << " @ " << note.time() << endl;
|
||||
Notes::iterator i = upper_bound(_notes.begin(), _notes.end(), note, note_time_comparator);
|
||||
_notes.insert(i, note);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MidiModel::remove_note(const Note& note)
|
||||
MidiModel::remove_note_unlocked(const Note& note)
|
||||
{
|
||||
cerr << "MidiModel " << this << " remove note " << (int)note.note() << " @ " << note.time() << endl;
|
||||
Notes::iterator n = find(_notes.begin(), _notes.end(), note);
|
||||
if (n != _notes.end())
|
||||
_notes.erase(n);
|
||||
@ -367,6 +375,7 @@ MidiModel::apply_command(Command* cmd)
|
||||
(*cmd)();
|
||||
assert(is_sorted());
|
||||
_session.commit_reversible_command(cmd);
|
||||
_edited = true;
|
||||
}
|
||||
|
||||
|
||||
@ -399,11 +408,15 @@ MidiModel::DeltaCommand::operator()()
|
||||
// This could be made much faster by using a priority_queue for added and
|
||||
// removed notes (or sort here), and doing a single iteration over _model
|
||||
|
||||
_model._lock.writer_lock();
|
||||
|
||||
for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
|
||||
_model.add_note(*i);
|
||||
_model.add_note_unlocked(*i);
|
||||
|
||||
for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
|
||||
_model.remove_note(*i);
|
||||
_model.remove_note_unlocked(*i);
|
||||
|
||||
_model._lock.writer_unlock();
|
||||
|
||||
_model.ContentsChanged(); /* EMIT SIGNAL */
|
||||
}
|
||||
@ -414,31 +427,73 @@ MidiModel::DeltaCommand::undo()
|
||||
{
|
||||
// This could be made much faster by using a priority_queue for added and
|
||||
// removed notes (or sort here), and doing a single iteration over _model
|
||||
|
||||
_model._lock.writer_lock();
|
||||
|
||||
for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
|
||||
_model.remove_note(*i);
|
||||
_model.remove_note_unlocked(*i);
|
||||
|
||||
for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
|
||||
_model.add_note(*i);
|
||||
_model.add_note_unlocked(*i);
|
||||
|
||||
_model._lock.writer_unlock();
|
||||
|
||||
_model.ContentsChanged(); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
MidiModel::write_new_source(const std::string& path)
|
||||
MidiModel::write_to(boost::shared_ptr<MidiSource> source)
|
||||
{
|
||||
cerr << "Writing model to " << path << endl;
|
||||
cerr << "Writing model to " << source->name() << endl;
|
||||
|
||||
#if 0
|
||||
SourceFactory::createWritable (region->data_type(), session, path, false, session.frame_rate());
|
||||
/* This could be done using a temporary MidiRingBuffer and using
|
||||
* MidiModel::read and MidiSource::write, but this is more efficient
|
||||
* and doesn't require any buffer size assumptions (ie it's worth
|
||||
* the code duplication).
|
||||
*
|
||||
* This is also different from read in that note off events are written
|
||||
* regardless of the track mode. This is so the user can switch a
|
||||
* recorded track (with note durations from some instrument) to percussive,
|
||||
* save, reload, then switch it back to sustained preserving the original
|
||||
* note durations.
|
||||
*/
|
||||
|
||||
catch (failed_constructor& err) {
|
||||
error << string_compose (_("filter: error creating new file %1 (%2)"), path, strerror (errno)) << endmsg;
|
||||
return -1;
|
||||
/* Percussive
|
||||
for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
|
||||
const MidiEvent& ev = n->on_event();
|
||||
source->append_event_unlocked(ev);
|
||||
}*/
|
||||
|
||||
_lock.reader_lock();
|
||||
|
||||
LaterNoteEndComparator cmp;
|
||||
ActiveNotes active_notes(cmp);
|
||||
|
||||
// Foreach note
|
||||
for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
|
||||
|
||||
// Write any pending note offs earlier than this note on
|
||||
while ( ! active_notes.empty() ) {
|
||||
const Note* const earliest_off = active_notes.top();
|
||||
const MidiEvent& off_ev = earliest_off->off_event();
|
||||
if (off_ev.time() <= n->time()) {
|
||||
source->append_event_unlocked(off_ev);
|
||||
active_notes.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write this note on
|
||||
source->append_event_unlocked(n->on_event());
|
||||
if (n->duration() > 0)
|
||||
active_notes.push(&(*n));
|
||||
}
|
||||
#endif
|
||||
|
||||
_edited = false;
|
||||
|
||||
_lock.reader_unlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ MidiRegion::MidiRegion (boost::shared_ptr<MidiSource> src, nframes_t start, nfra
|
||||
: Region (src, start, length, PBD::basename_nosuffix(src->name()), DataType::MIDI, 0, Region::Flag(Region::DefaultFlags|Region::External))
|
||||
{
|
||||
assert(_name.find("/") == string::npos);
|
||||
midi_source(0)->Switched.connect(sigc::mem_fun(this, &MidiRegion::switch_source));
|
||||
}
|
||||
|
||||
/* Basic MidiRegion constructor (one channel) */
|
||||
@ -60,6 +61,7 @@ MidiRegion::MidiRegion (boost::shared_ptr<MidiSource> src, nframes_t start, nfra
|
||||
: Region (src, start, length, name, DataType::MIDI, layer, flags)
|
||||
{
|
||||
assert(_name.find("/") == string::npos);
|
||||
midi_source(0)->Switched.connect(sigc::mem_fun(this, &MidiRegion::switch_source));
|
||||
}
|
||||
|
||||
/* Basic MidiRegion constructor (many channels) */
|
||||
@ -67,6 +69,7 @@ MidiRegion::MidiRegion (SourceList& srcs, nframes_t start, nframes_t length, con
|
||||
: Region (srcs, start, length, name, DataType::MIDI, layer, flags)
|
||||
{
|
||||
assert(_name.find("/") == string::npos);
|
||||
midi_source(0)->Switched.connect(sigc::mem_fun(this, &MidiRegion::switch_source));
|
||||
}
|
||||
|
||||
|
||||
@ -75,12 +78,14 @@ MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, nframes_t off
|
||||
: Region (other, offset, length, name, layer, flags)
|
||||
{
|
||||
assert(_name.find("/") == string::npos);
|
||||
midi_source(0)->Switched.connect(sigc::mem_fun(this, &MidiRegion::switch_source));
|
||||
}
|
||||
|
||||
MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
|
||||
: Region (other)
|
||||
{
|
||||
assert(_name.find("/") == string::npos);
|
||||
midi_source(0)->Switched.connect(sigc::mem_fun(this, &MidiRegion::switch_source));
|
||||
}
|
||||
|
||||
MidiRegion::MidiRegion (boost::shared_ptr<MidiSource> src, const XMLNode& node)
|
||||
@ -90,6 +95,7 @@ MidiRegion::MidiRegion (boost::shared_ptr<MidiSource> src, const XMLNode& node)
|
||||
throw failed_constructor();
|
||||
}
|
||||
|
||||
midi_source(0)->Switched.connect(sigc::mem_fun(this, &MidiRegion::switch_source));
|
||||
assert(_name.find("/") == string::npos);
|
||||
assert(_type == DataType::MIDI);
|
||||
}
|
||||
@ -101,6 +107,7 @@ MidiRegion::MidiRegion (SourceList& srcs, const XMLNode& node)
|
||||
throw failed_constructor();
|
||||
}
|
||||
|
||||
midi_source(0)->Switched.connect(sigc::mem_fun(this, &MidiRegion::switch_source));
|
||||
assert(_name.find("/") == string::npos);
|
||||
assert(_type == DataType::MIDI);
|
||||
}
|
||||
@ -302,3 +309,18 @@ MidiRegion::midi_source (uint32_t n) const
|
||||
return boost::dynamic_pointer_cast<MidiSource>(source(n));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MidiRegion::switch_source(boost::shared_ptr<Source> src)
|
||||
{
|
||||
boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
|
||||
if (!msrc)
|
||||
return;
|
||||
|
||||
// MIDI regions have only one source
|
||||
_sources.clear();
|
||||
_sources.push_back(msrc);
|
||||
|
||||
set_name(msrc->name());
|
||||
}
|
||||
|
||||
|
@ -30,9 +30,13 @@
|
||||
|
||||
#include <pbd/xml++.h>
|
||||
#include <pbd/pthread_utils.h>
|
||||
#include <pbd/basename.h>
|
||||
|
||||
#include <ardour/midi_source.h>
|
||||
#include <ardour/midi_ring_buffer.h>
|
||||
#include <ardour/session.h>
|
||||
#include <ardour/session_directory.h>
|
||||
#include <ardour/source_factory.h>
|
||||
|
||||
#include "i18n.h"
|
||||
|
||||
@ -44,6 +48,7 @@ sigc::signal<void,MidiSource *> MidiSource::MidiSourceCreated;
|
||||
|
||||
MidiSource::MidiSource (Session& s, string name)
|
||||
: Source (s, name, DataType::MIDI)
|
||||
, _timeline_position(0)
|
||||
, _model(new MidiModel(s))
|
||||
, _model_loaded (false)
|
||||
, _writing (false)
|
||||
@ -54,6 +59,7 @@ MidiSource::MidiSource (Session& s, string name)
|
||||
|
||||
MidiSource::MidiSource (Session& s, const XMLNode& node)
|
||||
: Source (s, node)
|
||||
, _timeline_position(0)
|
||||
, _model(new MidiModel(s))
|
||||
, _model_loaded (false)
|
||||
, _writing (false)
|
||||
@ -158,6 +164,40 @@ MidiSource::mark_streaming_write_completed ()
|
||||
void
|
||||
MidiSource::session_saved()
|
||||
{
|
||||
cerr << "MidiSource saving, name = " << _name << endl;
|
||||
flush();
|
||||
|
||||
if (_model && _model_loaded && _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);
|
||||
|
||||
newsrc->set_model(_model);
|
||||
_model.reset();
|
||||
_model_loaded = false;
|
||||
|
||||
newsrc->flush();
|
||||
|
||||
Switched.emit(newsrc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,6 @@ SMFSource::SMFSource (Session& s, std::string path, Flag flags)
|
||||
, _channel(0)
|
||||
, _flags (Flag(flags | Writable)) // FIXME: this needs to be writable for now
|
||||
, _allow_remove_if_empty(true)
|
||||
, _timeline_position (0)
|
||||
, _fd (0)
|
||||
, _last_ev_time(0)
|
||||
, _track_size(4) // 4 bytes for the ever-present EOT event
|
||||
@ -70,6 +69,8 @@ SMFSource::SMFSource (Session& s, std::string path, Flag flags)
|
||||
if (open()) {
|
||||
throw failed_constructor ();
|
||||
}
|
||||
|
||||
cerr << "SMF Source path: " << path << endl;
|
||||
|
||||
assert(_name.find("/") == string::npos);
|
||||
}
|
||||
@ -79,7 +80,6 @@ SMFSource::SMFSource (Session& s, const XMLNode& node)
|
||||
, _channel(0)
|
||||
, _flags (Flag (Writable|CanRename))
|
||||
, _allow_remove_if_empty(true)
|
||||
, _timeline_position (0)
|
||||
, _fd (0)
|
||||
, _last_ev_time(0)
|
||||
, _track_size(4) // 4 bytes for the ever-present EOT event
|
||||
@ -99,6 +99,8 @@ SMFSource::SMFSource (Session& s, const XMLNode& node)
|
||||
throw failed_constructor ();
|
||||
}
|
||||
|
||||
cerr << "SMF Source name: " << _name << endl;
|
||||
|
||||
assert(_name.find("/") == string::npos);
|
||||
}
|
||||
|
||||
@ -156,21 +158,16 @@ SMFSource::open()
|
||||
_fd = fopen(path().c_str(), "w+");
|
||||
_track_size = 0;
|
||||
|
||||
// write a tentative header just to pad things out so writing happens in the right spot
|
||||
// Write a tentative header just to pad things out so writing happens in the right spot
|
||||
set_timeline_position(0);
|
||||
flush_header();
|
||||
// FIXME: write the footer here too so it's a valid SMF (screw up writing ATM though)
|
||||
|
||||
// Note file isn't a valid SMF at this point (no footer). Does it matter?
|
||||
}
|
||||
|
||||
return (_fd == 0) ? -1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
SMFSource::update_header (nframes_t when, struct tm&, time_t)
|
||||
{
|
||||
_timeline_position = when;
|
||||
return flush_header();
|
||||
}
|
||||
|
||||
int
|
||||
SMFSource::flush_header ()
|
||||
{
|
||||
@ -205,12 +202,12 @@ SMFSource::flush_header ()
|
||||
int
|
||||
SMFSource::flush_footer()
|
||||
{
|
||||
//cerr << "SMF - Writing EOT\n";
|
||||
cerr << "SMF " << name() << " writing EOT\n";
|
||||
|
||||
fseek(_fd, 0, SEEK_END);
|
||||
write_var_len(1); // whatever...
|
||||
char eot[4] = { 0xFF, 0x2F, 0x00 }; // end-of-track meta-event
|
||||
fwrite(eot, 1, 4, _fd);
|
||||
write_var_len(0);
|
||||
char eot[3] = { 0xFF, 0x2F, 0x00 }; // end-of-track meta-event
|
||||
fwrite(eot, 1, 3, _fd);
|
||||
fflush(_fd);
|
||||
return 0;
|
||||
}
|
||||
@ -290,8 +287,7 @@ SMFSource::read_event(jack_midi_event_t& ev) const
|
||||
ev.buffer[0] = (unsigned char)status;
|
||||
fread(ev.buffer+1, 1, ev.size - 1, _fd);
|
||||
|
||||
/*printf("SMF - read event, delta = %u, size = %zu, data = ",
|
||||
delta_time, ev.size);
|
||||
/*printf("%s read event: delta = %u, size = %zu, data = ", _name.c_str(), delta_time, ev.size);
|
||||
for (size_t i=0; i < ev.size; ++i) {
|
||||
printf("%X ", ev.buffer[i]);
|
||||
}
|
||||
@ -375,32 +371,14 @@ SMFSource::write_unlocked (MidiRingBuffer& src, nframes_t cnt)
|
||||
src.read(buf, /*_length*/0, _length + cnt); // FIXME?
|
||||
|
||||
fseek(_fd, 0, SEEK_END);
|
||||
|
||||
// FIXME: assumes tempo never changes after start
|
||||
const double frames_per_beat = _session.tempo_map().tempo_at(_timeline_position).frames_per_beat(
|
||||
_session.engine().frame_rate());
|
||||
|
||||
for (MidiBuffer::iterator i = buf.begin(); i != buf.end(); ++i) {
|
||||
MidiEvent& ev = *i;
|
||||
assert(ev.time() >= _timeline_position);
|
||||
ev.time() -= _timeline_position;
|
||||
assert(ev.time() >= _last_ev_time);
|
||||
const uint32_t delta_time = (uint32_t)((ev.time() - _last_ev_time) / frames_per_beat * _ppqn);
|
||||
|
||||
/*printf("SMF - writing event, delta = %u, size = %zu, data = ",
|
||||
delta_time, ev.size);
|
||||
for (size_t i=0; i < ev.size; ++i) {
|
||||
printf("%X ", ev.buffer[i]);
|
||||
}
|
||||
printf("\n");
|
||||
*/
|
||||
size_t stamp_size = write_var_len(delta_time);
|
||||
fwrite(ev.buffer(), 1, ev.size(), _fd);
|
||||
|
||||
_track_size += stamp_size + ev.size();
|
||||
_write_data_count += ev.size();
|
||||
|
||||
_last_ev_time = ev.time();
|
||||
append_event_unlocked(ev);
|
||||
}
|
||||
|
||||
fflush(_fd);
|
||||
@ -419,6 +397,34 @@ SMFSource::write_unlocked (MidiRingBuffer& src, nframes_t cnt)
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SMFSource::append_event_unlocked(const MidiEvent& ev)
|
||||
{
|
||||
printf("SMF - writing event, time = %lf, size = %u, data = ", ev.time(), ev.size());
|
||||
for (size_t i=0; i < ev.size(); ++i) {
|
||||
printf("%X ", ev.buffer()[i]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
assert(ev.time() >= _last_ev_time);
|
||||
|
||||
// FIXME: assumes tempo never changes after start
|
||||
const double frames_per_beat = _session.tempo_map().tempo_at
|
||||
(_timeline_position).frames_per_beat(_session.engine().frame_rate());
|
||||
|
||||
const uint32_t delta_time = (uint32_t)((ev.time() - _last_ev_time) / frames_per_beat * _ppqn);
|
||||
|
||||
const size_t stamp_size = write_var_len(delta_time);
|
||||
fwrite(ev.buffer(), 1, ev.size(), _fd);
|
||||
|
||||
_track_size += stamp_size + ev.size();
|
||||
_write_data_count += ev.size();
|
||||
|
||||
_last_ev_time = ev.time();
|
||||
}
|
||||
|
||||
|
||||
XMLNode&
|
||||
SMFSource::get_state ()
|
||||
@ -810,9 +816,10 @@ SMFSource::load_model(bool lock, bool force_reload)
|
||||
}
|
||||
|
||||
if (! _model) {
|
||||
cerr << "SMFSource: Loading new model " << _model.get() << endl;
|
||||
_model = boost::shared_ptr<MidiModel>(new MidiModel(_session));
|
||||
} else {
|
||||
cerr << "SMFSource: Reloading model." << endl;
|
||||
cerr << "SMFSource: Reloading model " << _model.get() << endl;
|
||||
_model->clear();
|
||||
}
|
||||
|
||||
@ -860,6 +867,7 @@ SMFSource::load_model(bool lock, bool force_reload)
|
||||
void
|
||||
SMFSource::destroy_model()
|
||||
{
|
||||
cerr << "SMFSource: Destroying model " << _model.get() << endl;
|
||||
_model.reset();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user