Saving of edited MIDI data to disk (on session save).
authorDavid Robillard <d@drobilla.net>
Sat, 11 Aug 2007 06:17:42 +0000 (06:17 +0000)
committerDavid Robillard <d@drobilla.net>
Sat, 11 Aug 2007 06:17:42 +0000 (06:17 +0000)
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

16 files changed:
gtk2_ardour/editor_mouse.cc
gtk2_ardour/midi_region_view.cc
gtk2_ardour/midi_region_view.h
libs/ardour/ardour/midi_model.h
libs/ardour/ardour/midi_region.h
libs/ardour/ardour/midi_source.h
libs/ardour/ardour/region.h
libs/ardour/ardour/smf_source.h
libs/ardour/ardour/source.h
libs/ardour/audio_diskstream.cc
libs/ardour/filter.cc
libs/ardour/midi_diskstream.cc
libs/ardour/midi_model.cc
libs/ardour/midi_region.cc
libs/ardour/midi_source.cc
libs/ardour/smf_source.cc

index c5a17684963a7444ac4ebb6a3a61f88872e8de29..e8bc98a73c0d22ad4fac3b67d31b12e38b23595e 100644 (file)
@@ -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());
index 241eb0309119718145bc9790340c39a705813cce..e96a65d893e640201fc82189bfc8bff7359a3d15 100644 (file)
@@ -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());
+}
 
index a15ce31e92883d5a03367430d280f18fc86f0c8d..22196846c50cfe46971bd31bacfe64bcd86d6a91 100644 (file)
@@ -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);
index f1fcabfb1f1948fd0d7fec23a071015fe01fa09d..63b87c9683ae510e817a9dc2686b7430603eb3e0 100644 (file)
@@ -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():
        
index ade839ab3e7a3359b18df29b6edc08a6f6c71c06..294c2d3291e36605775df517ee5b5a64e666e646 100644 (file)
@@ -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);
index 2de31f879ebb5e1400f67ba0fa898016c748402e..088175ab75fca8eed70fe297754c21f2e3acee5d 100644 (file)
@@ -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()
 
index e6b799fdcf0ae8c7bb39291e4d84e0bb2439dd3c..496dc7874ebed8d416fbab9bb291753d460dcd80 100644 (file)
@@ -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 */
index fde91ed946cd20bf03e15b868145238b2068008e..c852266e9c9a98c97c7766c51483c92233329f6f 100644 (file)
@@ -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;
index bbb7d798b26a9c2861fb768bb29bcb34b23b8a97..6f323dd8786ea8001e2c6acc54bb243f83d34107 100644 (file)
@@ -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);
index 135aa4747fa23b404afd19ac4b289ba53cb201b1..7d95b7870636e716aa8368b31a1ac5204fc95dbb 100644 (file)
@@ -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>
index f655f71668005b7518c5cca49288bd481032cd87..208377cc4ab2a4a159dd9292f3f20b694102c794 100644 (file)
@@ -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 ();
                }
        }
index 493a29f4ce46db715b0b26298c31a305e473b558..54f4c1069880e09ed9482f47bb73ac01ee9e7357 100644 (file)
@@ -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;
index c68dcff6b6eb8f9c68e184c9ddd7ce29d79ace34..34c345254b6eb786087ff6fd4aef78dbae5f3673 100644 (file)
@@ -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;
-
-#if 0
-               SourceFactory::createWritable (region->data_type(), session, path, false, session.frame_rate());
-
-               catch (failed_constructor& err) {
-                       error << string_compose (_("filter: error creating new file %1 (%2)"), path, strerror (errno)) << endmsg;
-                       return -1;
+       cerr << "Writing model to " << source->name() << endl;
+
+       /* 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.
+        */
+
+       /* 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;
 }
index 1ef52c743d80dedfdd80a85a6d3d10460e8ed94b..dc115cd55a6391a3ba4bb2cedeeec47ef0cd261c 100644 (file)
@@ -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());
+}
+
index e52e39e12102fa7cf3ed31fd0f58a2ac3d3bdb4b..c6e3bd0a08833ca6a0559598d72b729518125c7b 100644 (file)
 
 #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);
+       }
 }
 
index 92dfed3cec15e66bb246deade7ee912eefbd63d7..1f7221c3169145776bc257d93884dbe8a989d739 100644 (file)
@@ -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();
 }