Actually added the code mentioned in my last commit. Whoops.
authorDavid Robillard <d@drobilla.net>
Mon, 26 Jun 2006 20:29:45 +0000 (20:29 +0000)
committerDavid Robillard <d@drobilla.net>
Mon, 26 Jun 2006 20:29:45 +0000 (20:29 +0000)
git-svn-id: svn://localhost/ardour2/branches/midi@643 d708f5d6-7413-0410-9779-e7cbd77b26cf

15 files changed:
libs/ardour/ardour/buffer.h [new file with mode: 0644]
libs/ardour/ardour/diskstream.h [new file with mode: 0644]
libs/ardour/ardour/midi_diskstream.h [new file with mode: 0644]
libs/ardour/ardour/midi_playlist.h [new file with mode: 0644]
libs/ardour/ardour/midi_region.h [new file with mode: 0644]
libs/ardour/ardour/midi_source.h [new file with mode: 0644]
libs/ardour/ardour/midi_track.h [new file with mode: 0644]
libs/ardour/ardour/smf_source.h [new file with mode: 0644]
libs/ardour/diskstream.cc [new file with mode: 0644]
libs/ardour/midi_diskstream.cc [new file with mode: 0644]
libs/ardour/midi_playlist.cc [new file with mode: 0644]
libs/ardour/midi_region.cc [new file with mode: 0644]
libs/ardour/midi_source.cc [new file with mode: 0644]
libs/ardour/midi_track.cc [new file with mode: 0644]
libs/ardour/smf_source.cc [new file with mode: 0644]

diff --git a/libs/ardour/ardour/buffer.h b/libs/ardour/ardour/buffer.h
new file mode 100644 (file)
index 0000000..5171f50
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+    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.
+*/
+
+#ifndef __ardour_buffer_h__
+#define __ardour_buffer_h__
+
+#define _XOPEN_SOURCE 600
+#include <cstdlib> // for posix_memalign
+#include <cassert>
+#include <ardour/types.h>
+#include <jack/jack.h>
+
+namespace ARDOUR {
+
+
+/** A buffer of recordable/playable data.
+ *
+ * This is a datatype-agnostic base class for all buffers (there are no
+ * methods to actually access the data).  This provides a way for code that
+ * doesn't care about the data type to still deal with buffers (which is
+ * why the base class can't be a template).
+ * 
+ * To actually read/write buffer contents, use the appropriate derived class.
+ */
+class Buffer
+{
+public:
+       /** Unfortunately using RTTI and dynamic_cast to find the type of the
+        * buffer is just too slow, this is done in very performance critical
+        * bits of the code. */
+       enum Type { NIL = 0, AUDIO, MIDI };
+
+       Buffer(Type type, size_t capacity)
+       : _type(type), _capacity(capacity), _size(0) 
+       {}
+
+       virtual ~Buffer() {}
+
+       /** Maximum capacity of buffer.
+        * Note in some cases the entire buffer may not contain valid data, use size. */
+       size_t capacity() const { return _capacity; }
+
+       /** Amount of valid data in buffer.  Use this over capacity almost always. */
+       size_t size() const { return _size; }
+
+       /** Type of this buffer.
+        * Based on this you can cast a Buffer* to the desired type. */
+       virtual Type type() const { return _type; }
+
+       /** Jack type (eg JACK_DEFAULT_AUDIO_TYPE) */
+       const char* jack_type() const { return type_to_jack_type(type()); }
+       
+       /** Separate for creating ports (before a buffer exists to call jack_type on) */
+       static const char* type_to_jack_type(Type t) {
+               switch (t) {
+                       case AUDIO: return JACK_DEFAULT_AUDIO_TYPE;
+                       case MIDI:  return JACK_DEFAULT_MIDI_TYPE;
+                       default:    return "";
+               }
+       }
+
+protected:
+       Type   _type;
+       size_t _capacity;
+       size_t _size;
+};
+
+
+/* Since we only have two types, templates aren't worth it, yet.. */
+
+
+/** Buffer containing 32-bit floating point (audio) data. */
+class AudioBuffer : public Buffer
+{
+public:
+       AudioBuffer(size_t capacity)
+               : Buffer(AUDIO, capacity)
+               , _data(NULL)
+       {
+               _size = capacity; // For audio buffers, size = capacity always
+               posix_memalign((void**)_data, 16, sizeof(Sample) * capacity);
+               assert(_data);
+               memset(_data, 0, sizeof(Sample) * capacity);
+       }
+
+       const Sample* data() const { return _data; }
+       Sample*       data()       { return _data; }
+
+private:
+       // These are undefined (prevent copies)
+       AudioBuffer(const AudioBuffer& copy);            
+       AudioBuffer& operator=(const AudioBuffer& copy);
+
+       Sample* const _data; ///< Actual buffer contents
+};
+
+
+
+/** Buffer containing 8-bit unsigned char (MIDI) data. */
+class MidiBuffer : public Buffer
+{
+public:
+       MidiBuffer(size_t capacity)
+               : Buffer(MIDI, capacity)
+               , _data(NULL)
+       {
+               posix_memalign((void**)_data, 16, sizeof(RawMidi) * capacity);
+               assert(_data);
+               assert(_size == 0);
+               memset(_data, 0, sizeof(Sample) * capacity);
+       }
+
+       const RawMidi* data() const { return _data; }
+       RawMidi*       data()       { return _data; }
+
+private:
+       // These are undefined (prevent copies)
+       MidiBuffer(const MidiBuffer& copy);            
+       MidiBuffer& operator=(const MidiBuffer& copy);
+
+       RawMidi* const _data; ///< Actual buffer contents
+};
+
+
+} // namespace ARDOUR
+
+#endif // __ardour_buffer_h__
diff --git a/libs/ardour/ardour/diskstream.h b/libs/ardour/ardour/diskstream.h
new file mode 100644 (file)
index 0000000..f43e827
--- /dev/null
@@ -0,0 +1,341 @@
+/*
+    Copyright (C) 2000 Paul Davis 
+
+    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.
+
+    $Id: diskstream.h 579 2006-06-12 19:56:37Z essej $
+*/
+
+#ifndef __ardour_diskstream_h__
+#define __ardour_diskstream_h__
+
+#include <sigc++/signal.h>
+
+#include <cmath>
+#include <string>
+#include <queue>
+#include <map>
+#include <vector>
+
+#include <time.h>
+
+#include <pbd/fastlog.h>
+#include <pbd/ringbufferNPT.h>
+
+#include <ardour/ardour.h>
+#include <ardour/configuration.h>
+#include <ardour/session.h>
+#include <ardour/route_group.h>
+#include <ardour/route.h>
+#include <ardour/port.h>
+#include <ardour/utils.h>
+#include <ardour/stateful.h>
+
+struct tm;
+
+namespace ARDOUR {
+
+class AudioEngine;
+class Send;
+class Session;
+class Playlist;
+//class FileSource;
+class IO;
+
+/* FIXME: There are (obviously) far too many virtual functions in this ATM.
+ * Just to get things off the ground, they'll be removed. */
+
+class Diskstream : public Stateful, public sigc::trackable
+{      
+  public:
+       enum Flag {
+               Recordable = 0x1,
+               Hidden = 0x2,
+               Destructive = 0x4
+       };
+
+       Diskstream (Session &, const string& name, Flag f = Recordable);
+       Diskstream (Session &, const XMLNode&);
+
+       string name () const { return _name; }
+       virtual int set_name (string str, void* src);
+
+       ARDOUR::IO* io() const { return _io; }
+       virtual void set_io (ARDOUR::IO& io) = 0;
+
+       virtual Diskstream& ref() { _refcnt++; return *this; }
+       void unref() { if (_refcnt) _refcnt--; if (_refcnt == 0) delete this; }
+       uint32_t refcnt() const { return _refcnt; }
+
+       void set_flag (Flag f)   { _flags |= f; }
+       void unset_flag (Flag f) { _flags &= ~f; }
+
+       AlignStyle alignment_style() const { return _alignment_style; }
+       void set_align_style (AlignStyle);
+       void set_persistent_align_style (AlignStyle a) { _persistent_alignment_style = a; }
+       
+       jack_nframes_t roll_delay() const { return _roll_delay; }
+       void set_roll_delay (jack_nframes_t);
+
+       bool record_enabled() const { return g_atomic_int_get (&_record_enabled); }
+       virtual void set_record_enabled (bool yn, void *src) = 0;
+
+       bool destructive() const { return _flags & Destructive; }
+       virtual void set_destructive (bool yn);
+
+       id_t   id()          const { return _id; }
+       bool   hidden()      const { return _flags & Hidden; }
+       bool   recordable()  const { return _flags & Recordable; }
+       bool   reversed()    const { return _actual_speed < 0.0f; }
+       double speed()       const { return _visible_speed; }
+       
+       virtual void punch_in()  {}
+       virtual void punch_out() {}
+
+       virtual void set_speed (double);
+
+       virtual Playlist *playlist () = 0;
+       virtual int use_new_playlist () = 0;
+       virtual int use_playlist (Playlist *) = 0;
+       virtual int use_copy_playlist () = 0;
+
+       virtual void start_scrub (jack_nframes_t where) = 0;
+       virtual void end_scrub () = 0;
+
+       jack_nframes_t current_capture_start() const { return capture_start_frame; }
+       jack_nframes_t current_capture_end()   const { return capture_start_frame + capture_captured; }
+       jack_nframes_t get_capture_start_frame (uint32_t n=0);
+       jack_nframes_t get_captured_frames (uint32_t n=0);
+       
+       uint32_t n_channels() { return _n_channels; }
+
+       static jack_nframes_t disk_io_frames() { return disk_io_chunk_frames; }
+       static void set_disk_io_chunk_frames (uint32_t n) { disk_io_chunk_frames = n; }
+
+       /* Stateful */
+       virtual XMLNode& get_state(void) = 0;
+       virtual int      set_state(const XMLNode& node) = 0;
+
+       jack_nframes_t capture_offset() const { return _capture_offset; }
+       virtual void   set_capture_offset ();
+
+       bool slaved() const      { return _slaved; }
+       void set_slaved(bool yn) { _slaved = yn; }
+
+       virtual int set_loop (Location *loc);
+       sigc::signal<void,Location *> LoopSet;
+
+       std::list<Region*>& last_capture_regions () { return _last_capture_regions; }
+
+       virtual void handle_input_change (IOChange, void *src);
+
+       sigc::signal<void,void*> record_enable_changed;
+       sigc::signal<void>       speed_changed;
+       sigc::signal<void,void*> reverse_changed;
+       sigc::signal<void>       PlaylistChanged;
+       sigc::signal<void>       AlignmentStyleChanged;
+
+       static sigc::signal<void>                DiskOverrun;
+       static sigc::signal<void>                DiskUnderrun;
+       static sigc::signal<void,Diskstream*>    DiskstreamCreated; // XXX use a ref with sigc2
+       //static sigc::signal<void,list<Source*>*> DeleteSources;
+       
+       XMLNode* deprecated_io_node;
+
+  protected:
+       friend class Session;
+
+       /* the Session is the only point of access for these
+          because they require that the Session is "inactive"
+          while they are called.
+       */
+
+       virtual void set_pending_overwrite (bool) = 0;
+       virtual int  overwrite_existing_buffers () = 0;
+       virtual void reverse_scrub_buffer (bool to_forward) = 0;
+       //void set_block_size (jack_nframes_t);
+       virtual int  internal_playback_seek (jack_nframes_t distance) = 0;
+       virtual int  can_internal_playback_seek (jack_nframes_t distance) = 0;
+       virtual int  rename_write_sources () = 0;
+       virtual void reset_write_sources (bool, bool force = false) = 0;
+       virtual void non_realtime_input_change () = 0;
+
+       uint32_t read_data_count() const { return _read_data_count; }
+       uint32_t write_data_count() const { return _write_data_count; }
+
+  protected:
+       friend class Auditioner;
+       virtual int  seek (jack_nframes_t which_sample, bool complete_refill = false) = 0;
+
+  protected:
+       friend class Track;
+
+       virtual void prepare ();
+       virtual int  process (jack_nframes_t transport_frame, jack_nframes_t nframes, jack_nframes_t offset, bool can_record, bool rec_monitors_input) = 0;
+       virtual bool commit  (jack_nframes_t nframes) = 0;
+       virtual void recover (); /* called if commit will not be called, but process was */
+
+       //private:
+       
+       /* use unref() to destroy a diskstream */
+       virtual ~Diskstream();
+
+       enum TransitionType {
+               CaptureStart = 0,
+               CaptureEnd
+       };
+       
+       struct CaptureTransition {
+               TransitionType   type;
+               // the start or end file frame pos
+               jack_nframes_t   capture_val;
+       };
+
+       /* the two central butler operations */
+
+       virtual int do_flush (char * workbuf, bool force = false) = 0;
+       //int do_refill (Sample *mixdown_buffer, float *gain_buffer, char *workbuf);
+       
+       virtual int non_realtime_do_refill() = 0;
+
+       //int read (Sample* buf, Sample* mixdown_buffer, float* gain_buffer, char * workbuf, jack_nframes_t& start, jack_nframes_t cnt, 
+       //        ChannelInfo& channel_info, int channel, bool reversed);
+       
+       /* XXX fix this redundancy ... */
+
+       virtual void playlist_changed (Change);
+       virtual void playlist_modified ();
+       virtual void playlist_deleted (Playlist*) = 0;
+       virtual void session_controls_changed (Session::ControlType) = 0;
+
+       virtual void finish_capture (bool rec_monitors_input) = 0;
+       virtual void clean_up_capture (struct tm&, time_t, bool abort) = 0;
+       virtual void transport_stopped (struct tm&, time_t, bool abort) = 0;
+
+       struct CaptureInfo {
+           uint32_t start;
+           uint32_t frames;
+       };
+
+       virtual void init (Flag);
+
+       //void init_channel (ChannelInfo &chan);
+       //void destroy_channel (ChannelInfo &chan);
+
+       virtual int use_new_write_source (uint32_t n=0) = 0;
+       virtual int use_new_fade_source (uint32_t n=0) = 0;
+
+       virtual int find_and_use_playlist (const string&) = 0;
+
+       //void allocate_temporary_buffers ();
+
+       virtual int  create_input_port () = 0;
+       virtual int  connect_input_port () = 0;
+       virtual int  seek_unlocked (jack_nframes_t which_sample) = 0;
+
+       virtual int ports_created () = 0;
+
+       virtual bool realtime_set_speed (double, bool global_change);
+       //void non_realtime_set_speed ();
+
+       std::list<Region*> _last_capture_regions;
+       //std::vector<FileSource*> capturing_sources;
+       virtual int use_pending_capture_data (XMLNode& node) = 0;
+
+       virtual void get_input_sources () = 0;
+       virtual void check_record_status (jack_nframes_t transport_frame, jack_nframes_t nframes, bool can_record) = 0;
+       //void set_align_style_from_io();
+       virtual void setup_destructive_playlist () = 0;
+       //void use_destructive_playlist ();
+
+       // Wouldn't hurt for this thing to do on a diet:
+       
+       static jack_nframes_t disk_io_chunk_frames;
+       vector<CaptureInfo*>  capture_info;
+       Glib::Mutex           capture_info_lock;
+
+       uint32_t i_am_the_modifier;
+
+       string            _name;
+       ARDOUR::Session&  _session;
+       ARDOUR::IO*       _io;
+       uint32_t          _n_channels;
+       id_t              _id;
+
+       mutable gint             _record_enabled;
+       double                   _visible_speed;
+       double                   _actual_speed;
+       /* items needed for speed change logic */
+       bool                     _buffer_reallocation_required;
+       bool                     _seek_required;
+       
+       bool                      force_refill;
+       jack_nframes_t            capture_start_frame;
+       jack_nframes_t            capture_captured;
+       bool                      was_recording;
+       jack_nframes_t            adjust_capture_position;
+       jack_nframes_t           _capture_offset;
+       jack_nframes_t           _roll_delay;
+       jack_nframes_t            first_recordable_frame;
+       jack_nframes_t            last_recordable_frame;
+       int                       last_possibly_recording;
+       AlignStyle               _alignment_style;
+       bool                     _scrubbing;
+       bool                     _slaved;
+       bool                     _processed;
+       Location*                 loop_location;
+       jack_nframes_t            overwrite_frame;
+       off_t                     overwrite_offset;
+       bool                      pending_overwrite;
+       bool                      overwrite_queued;
+       IOChange                  input_change_pending;
+       jack_nframes_t            wrap_buffer_size;
+       jack_nframes_t            speed_buffer_size;
+
+       uint64_t                  last_phase;
+       uint64_t                  phi;
+       
+       jack_nframes_t            file_frame;           
+       jack_nframes_t            playback_sample;
+       jack_nframes_t            playback_distance;
+
+       uint32_t                 _read_data_count;
+       uint32_t                 _write_data_count;
+
+       bool                      in_set_state;
+       AlignStyle               _persistent_alignment_style;
+       bool                      first_input_change;
+
+       Glib::Mutex  state_lock;
+
+       jack_nframes_t scrub_start;
+       jack_nframes_t scrub_buffer_size;
+       jack_nframes_t scrub_offset;
+
+       uint32_t _refcnt;
+
+       sigc::connection ports_created_c;
+       sigc::connection plmod_connection;
+       sigc::connection plstate_connection;
+       sigc::connection plgone_connection;
+       
+       unsigned char _flags;
+
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_diskstream_h__ */
diff --git a/libs/ardour/ardour/midi_diskstream.h b/libs/ardour/ardour/midi_diskstream.h
new file mode 100644 (file)
index 0000000..b6121d1
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+    Copyright (C) 2000 Paul Davis 
+
+    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.
+
+    $Id: diskstream.h 579 2006-06-12 19:56:37Z essej $
+*/
+
+#ifndef __ardour_midi_diskstream_h__
+#define __ardour_midi_diskstream_h__
+
+#include <sigc++/signal.h>
+
+#include <cmath>
+#include <string>
+#include <queue>
+#include <map>
+#include <vector>
+
+#include <time.h>
+
+#include <pbd/fastlog.h>
+#include <pbd/ringbufferNPT.h>
+
+#include <ardour/ardour.h>
+#include <ardour/configuration.h>
+#include <ardour/session.h>
+#include <ardour/route_group.h>
+#include <ardour/route.h>
+#include <ardour/port.h>
+#include <ardour/utils.h>
+#include <ardour/diskstream.h>
+#include <ardour/midi_playlist.h>
+struct tm;
+
+namespace ARDOUR {
+
+class MidiEngine;
+class Send;
+class Session;
+class MidiPlaylist;
+class SMFSource;
+class IO;
+
+class MidiDiskstream : public Diskstream
+{      
+  public:
+       MidiDiskstream (Session &, const string& name, Diskstream::Flag f = Recordable);
+       MidiDiskstream (Session &, const XMLNode&);
+
+       void set_io (ARDOUR::IO& io);
+
+       MidiDiskstream& ref() { _refcnt++; return *this; }
+       //void unref() { if (_refcnt) _refcnt--; if (_refcnt == 0) delete this; }
+       //uint32_t refcnt() const { return _refcnt; }
+
+       float playback_buffer_load() const;
+       float capture_buffer_load() const;
+
+       //void set_align_style (AlignStyle);
+       //void set_persistent_align_style (AlignStyle);
+
+       void set_record_enabled (bool yn, void *src);
+       //void set_speed (double);
+
+       int use_playlist (Playlist *);
+       int use_new_playlist ();
+       int use_copy_playlist ();
+
+       void start_scrub (jack_nframes_t where) {} // FIXME?
+       void end_scrub () {} // FIXME?
+
+       Playlist *playlist () { return _playlist; }
+
+       static sigc::signal<void,list<SMFSource*>*> DeleteSources;
+
+       /* stateful */
+
+       XMLNode& get_state(void);
+       int set_state(const XMLNode& node);
+
+       void monitor_input (bool);
+
+       //void handle_input_change (IOChange, void *src);
+
+  protected:
+       friend class Session;
+
+       /* the Session is the only point of access for these
+          because they require that the Session is "inactive"
+          while they are called.
+       */
+
+       void set_pending_overwrite(bool);
+       int  overwrite_existing_buffers ();
+       void reverse_scrub_buffer (bool to_forward) {} // FIXME?
+       void set_block_size (jack_nframes_t);
+       int  internal_playback_seek (jack_nframes_t distance);
+       int  can_internal_playback_seek (jack_nframes_t distance);
+       int  rename_write_sources ();
+       void reset_write_sources (bool, bool force = false);
+       void non_realtime_input_change ();
+
+       uint32_t read_data_count() const { return _read_data_count; }
+       uint32_t write_data_count() const { return _write_data_count; }
+
+  protected:
+       friend class Auditioner;
+       int  seek (jack_nframes_t which_sample, bool complete_refill = false);
+
+  protected:
+       friend class MidiTrack;
+
+       int  process (jack_nframes_t transport_frame, jack_nframes_t nframes, jack_nframes_t offset, bool can_record, bool rec_monitors_input);
+       bool commit  (jack_nframes_t nframes);
+
+  private:
+
+       /* use unref() to destroy a diskstream */
+       ~MidiDiskstream();
+
+       MidiPlaylist* _playlist;
+
+       /* the two central butler operations */
+
+       int do_flush (char * workbuf, bool force = false);
+       int do_refill (RawMidi *mixdown_buffer, float *gain_buffer, char *workbuf);
+       
+       virtual int non_realtime_do_refill() { return do_refill(0, 0, 0); }
+
+       int read (RawMidi* buf, RawMidi* mixdown_buffer, char * workbuf, jack_nframes_t& start, jack_nframes_t cnt, bool reversed);
+
+       /* XXX fix this redundancy ... */
+
+       //void playlist_changed (Change);
+       //void playlist_modified ();
+       void playlist_deleted (Playlist*);
+       void session_controls_changed (Session::ControlType) {} // FIXME?
+
+       void finish_capture (bool rec_monitors_input);
+       void clean_up_capture (struct tm&, time_t, bool abort) {} // FIXME?
+       void transport_stopped (struct tm&, time_t, bool abort);
+
+       struct CaptureInfo {
+           uint32_t start;
+           uint32_t frames;
+       };
+
+       void init (Diskstream::Flag);
+
+       int use_new_write_source (uint32_t n=0);
+       int use_new_fade_source (uint32_t n=0) { return 0; } // FIXME?
+
+       int find_and_use_playlist (const string&);
+
+       void allocate_temporary_buffers ();
+
+       int  create_input_port () { return 0; } // FIXME?
+       int  connect_input_port () { return 0; } // FIXME?
+       int  seek_unlocked (jack_nframes_t which_sample) { return 0; } // FIXME?
+
+       int ports_created () { return 0; } // FIXME?
+
+       //bool realtime_set_speed (double, bool global_change);
+       void non_realtime_set_speed ();
+
+       int use_pending_capture_data (XMLNode& node);
+
+       void get_input_sources ();
+       void check_record_status (jack_nframes_t transport_frame, jack_nframes_t nframes, bool can_record);
+       void set_align_style_from_io();
+       void setup_destructive_playlist ();
+       void use_destructive_playlist ();
+       
+       std::list<Region*>      _last_capture_regions;
+       std::vector<SMFSource*> _capturing_sources;
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_midi_diskstream_h__ */
diff --git a/libs/ardour/ardour/midi_playlist.h b/libs/ardour/ardour/midi_playlist.h
new file mode 100644 (file)
index 0000000..da3a72a
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+    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.
+*/
+
+#ifndef __ardour_midi_playlist_h__
+#define __ardour_midi_playlist_h__
+
+#include <vector>
+#include <list>
+
+#include <ardour/ardour.h>
+#include <ardour/playlist.h>
+
+namespace ARDOUR
+{
+
+class Session;
+class Region;
+class MidiRegion;
+class Source;
+
+class MidiPlaylist : public ARDOUR::Playlist
+{
+private:
+
+       struct State : public ARDOUR::StateManager::State
+       {
+               RegionList regions;
+               std::list<UndoAction> region_states;
+
+               State (std::string why) : ARDOUR::StateManager::State (why)
+               {}
+               ~State ();
+       };
+
+public:
+       MidiPlaylist (Session&, const XMLNode&, bool hidden = false);
+       MidiPlaylist (Session&, string name, bool hidden = false);
+       MidiPlaylist (const MidiPlaylist&, string name, bool hidden = false);
+       MidiPlaylist (const MidiPlaylist&, jack_nframes_t start, jack_nframes_t cnt,
+                     string name, bool hidden = false);
+
+       jack_nframes_t read (unsigned char *dst, unsigned char *mixdown,
+                            char * workbuf, jack_nframes_t start, jack_nframes_t cnt, uint32_t chan_n=0);
+
+       int set_state (const XMLNode&);
+       UndoAction get_memento() const;
+
+       template<class T>
+       void apply_to_history (T& obj, void (T::*method)(const ARDOUR::StateManager::StateMap&, state_id_t))
+       {
+               RegionLock rlock (this);
+               (obj.*method) (states, _current_state_id);
+       }
+
+       bool destroy_region (Region*);
+
+       void get_equivalent_regions (const MidiRegion&, std::vector<MidiRegion*>&);
+       void get_region_list_equivalent_regions (const MidiRegion&, std::vector<MidiRegion*>&);
+
+       void drop_all_states ();
+
+protected:
+
+       /* state management */
+
+       StateManager::State* state_factory (std::string) const;
+       Change restore_state (StateManager::State&);
+       void send_state_change (Change);
+
+       /* playlist "callbacks" */
+       void flush_notifications ();
+
+       void finalize_split_region (Region *orig, Region *left, Region *right);
+
+       void refresh_dependents (Region& region);
+       void check_dependents (Region& region, bool norefresh);
+       void remove_dependents (Region& region);
+
+protected:
+       ~MidiPlaylist (); /* public should use unref() */
+
+private:
+       XMLNode& state (bool full_state);
+       void dump () const;
+
+       bool region_changed (Change, Region*);
+};
+
+} /* namespace ARDOUR */
+
+#endif /* __ardour_midi_playlist_h__ */
+
+
diff --git a/libs/ardour/ardour/midi_region.h b/libs/ardour/ardour/midi_region.h
new file mode 100644 (file)
index 0000000..e4d8f48
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+    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.
+*/
+
+#ifndef __ardour_midi_region_h__
+#define __ardour_midi_region_h__
+
+#include <vector>
+
+#include <pbd/fastlog.h>
+#include <pbd/undo.h>
+
+#include <ardour/ardour.h>
+#include <ardour/region.h>
+#include <ardour/export.h>
+
+class XMLNode;
+
+namespace ARDOUR {
+
+class Route;
+class Playlist;
+class Session;
+class MidiFilter;
+class MidiSource;
+
+struct MidiRegionState : public RegionState 
+{
+    MidiRegionState (std::string why);
+
+};
+
+class MidiRegion : public Region
+{
+  public:
+       typedef vector<MidiSource *> SourceList;
+
+       MidiRegion (MidiSource&, jack_nframes_t start, jack_nframes_t length, bool announce = true);
+       MidiRegion (MidiSource&, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true);
+       MidiRegion (SourceList &, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true);
+       MidiRegion (const MidiRegion&, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t = 0, Region::Flag flags = Region::DefaultFlags, bool announce = true);
+       MidiRegion (const MidiRegion&);
+       MidiRegion (MidiSource&, const XMLNode&);
+       MidiRegion (SourceList &, const XMLNode&);
+       ~MidiRegion();
+
+       bool region_list_equivalent (const MidiRegion&) const ;
+       bool source_equivalent (const MidiRegion&) const;
+       bool equivalent (const MidiRegion&) const;
+       bool size_equivalent (const MidiRegion&) const;
+       bool overlap_equivalent (const MidiRegion&) const;
+
+       bool speed_mismatch (float) const;
+
+       void lock_sources ();
+       void unlock_sources ();
+       MidiSource& source (uint32_t n=0) const { if (n < sources.size()) return *sources[n]; else return *sources[0]; } 
+
+       uint32_t n_channels() { return sources.size(); }
+       vector<string> master_source_names();
+       
+       bool captured() const { return !(_flags & (Region::Flag (Region::Import|Region::External))); }
+
+       virtual jack_nframes_t read_at (unsigned char *buf, unsigned char *mixdown_buffer, 
+                                       char * workbuf, jack_nframes_t position, jack_nframes_t cnt, 
+                                       uint32_t chan_n = 0,
+                                       jack_nframes_t read_frames = 0,
+                                       jack_nframes_t skip_frames = 0) const;
+
+       jack_nframes_t master_read_at (unsigned char *buf, unsigned char *mixdown_buffer, 
+                                      char * workbuf, jack_nframes_t position, jack_nframes_t cnt, uint32_t chan_n=0) const;
+
+
+       XMLNode& state (bool);
+       XMLNode& get_state ();
+       int      set_state (const XMLNode&);
+
+       enum FadeShape {
+               Linear,
+               Fast,
+               Slow,
+               LogA,
+               LogB,
+
+       };
+
+       int separate_by_channel (ARDOUR::Session&, vector<MidiRegion*>&) const;
+
+       uint32_t read_data_count() const { return _read_data_count; }
+
+       ARDOUR::Playlist* playlist() const { return _playlist; }
+
+       UndoAction get_memento() const;
+
+       /* export */
+
+       //int exportme (ARDOUR::Session&, ARDOUR::AudioExportSpecification&);
+
+       Region* get_parent();
+
+  private:
+       friend class Playlist;
+
+  private:
+       SourceList        sources;
+       SourceList        master_sources; /* used when timefx are applied, so 
+                                            we can always use the original
+                                            source.
+                                         */
+       StateManager::State* state_factory (std::string why) const;
+       Change restore_state (StateManager::State&);
+
+       bool copied() const { return _flags & Copied; }
+       void maybe_uncopy ();
+       void rename_after_first_edit ();
+
+       jack_nframes_t _read_at (const SourceList&, unsigned char *buf, unsigned char *mixdown_buffer, 
+                                char * workbuf, jack_nframes_t position, jack_nframes_t cnt, 
+                                uint32_t chan_n = 0,
+                                jack_nframes_t read_frames = 0,
+                                jack_nframes_t skip_frames = 0) const;
+
+       bool verify_start (jack_nframes_t position);
+       bool verify_length (jack_nframes_t position);
+       bool verify_start_mutable (jack_nframes_t& start);
+       bool verify_start_and_length (jack_nframes_t start, jack_nframes_t length);
+
+       void recompute_at_start() {}
+       void recompute_at_end() {}
+
+       void source_deleted (Source*);
+};
+
+} /* namespace ARDOUR */
+
+
+#endif /* __ardour_midi_region_h__ */
diff --git a/libs/ardour/ardour/midi_source.h b/libs/ardour/ardour/midi_source.h
new file mode 100644 (file)
index 0000000..735ebba
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+    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.
+*/
+
+#ifndef __ardour_midi_source_h__
+#define __ardour_midi_source_h__
+
+#include <string>
+
+#include <time.h>
+
+#include <glibmm/thread.h>
+
+#include <sigc++/signal.h>
+
+#include <ardour/source.h>
+#include <ardour/ardour.h>
+#include <ardour/stateful.h>
+#include <pbd/xml++.h>
+
+using std::string;
+
+namespace ARDOUR {
+
+/** Source for raw MIDI data */
+class MidiSource : public Source
+{
+  public:
+       MidiSource (string name);
+       MidiSource (const XMLNode&);
+       virtual ~MidiSource ();
+
+       /* returns the number of items in this `midi_source' */
+
+       // Applicable to MIDI?  With what unit? [DR]
+       virtual jack_nframes_t length() const {
+               return _length;
+       }
+
+       virtual jack_nframes_t read (unsigned char *dst, jack_nframes_t start, jack_nframes_t cnt, char * workbuf) const;
+       virtual jack_nframes_t write (unsigned char *src, jack_nframes_t cnt, char * workbuf);
+
+       virtual void mark_for_remove() = 0;
+       virtual void mark_streaming_write_completed () {}
+
+       void set_captured_for (string str) { _captured_for = str; }
+       string captured_for() const { return _captured_for; }
+
+       uint32_t read_data_count() const { return _read_data_count; }
+       uint32_t write_data_count() const { return _write_data_count; }
+
+       static sigc::signal<void,MidiSource*> MidiSourceCreated;
+              
+       mutable sigc::signal<void>  PeaksReady;
+       mutable sigc::signal<void,jack_nframes_t,jack_nframes_t>  PeakRangeReady;
+       
+       XMLNode& get_state ();
+       int set_state (const XMLNode&);
+
+  protected:
+       jack_nframes_t   _length;
+       string           _captured_for;
+
+       mutable uint32_t _read_data_count;  // modified in read()
+       mutable uint32_t _write_data_count; // modified in write()
+
+       virtual jack_nframes_t read_unlocked (unsigned char *dst, jack_nframes_t start, jack_nframes_t cnt, char * workbuf) const = 0;
+       virtual jack_nframes_t write_unlocked (unsigned char *dst, jack_nframes_t cnt, char * workbuf) = 0;
+       
+       void update_length (jack_nframes_t pos, jack_nframes_t cnt);
+
+  private:
+       bool file_changed (string path);
+};
+
+}
+
+#endif /* __ardour_midi_source_h__ */
diff --git a/libs/ardour/ardour/midi_track.h b/libs/ardour/ardour/midi_track.h
new file mode 100644 (file)
index 0000000..1ef9bed
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+    Copyright (C) 2006 Paul Davis 
+       Written by Dave Robillard
+    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.
+*/
+
+#ifndef __ardour_midi_track_h__
+#define __ardour_midi_track_h__
+
+#include <ardour/route.h>
+
+namespace ARDOUR
+{
+
+class Session;
+class MidiDiskstream;
+class MidiPlaylist;
+class RouteGroup;
+
+class MidiTrack : public Route
+{
+public:
+       MidiTrack (Session&, string name, Route::Flag f = Route::Flag (0), TrackMode m = Normal);
+       MidiTrack (Session&, const XMLNode&);
+       ~MidiTrack ();
+
+       int set_name (string str, void *src);
+
+       int  roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame,
+                  jack_nframes_t offset, int declick, bool can_record, bool rec_monitors_input);
+
+       int  no_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame,
+                     jack_nframes_t offset, bool state_changing, bool can_record, bool rec_monitors_input);
+
+       int  silent_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame,
+                         jack_nframes_t offset, bool can_record, bool rec_monitors_input);
+
+       void toggle_monitor_input ();
+
+       bool can_record() const { return true; }
+
+       void set_record_enable (bool yn, void *src);
+
+       MidiDiskstream& disk_stream() const { return *diskstream; }
+
+       int set_diskstream (MidiDiskstream&, void *);
+       int use_diskstream (string name);
+       int use_diskstream (id_t id);
+
+       TrackMode mode() const { return _mode; }
+
+       void set_mode (TrackMode m);
+       sigc::signal<void> ModeChanged;
+
+       jack_nframes_t update_total_latency();
+       void           set_latency_delay (jack_nframes_t);
+
+       int export_stuff (vector<unsigned char*>& buffers, char * workbuf, uint32_t nbufs,
+               jack_nframes_t nframes, jack_nframes_t end_frame);
+
+       sigc::signal<void,void*> diskstream_changed;
+
+       enum FreezeState {
+           NoFreeze,
+           Frozen,
+           UnFrozen
+       };
+
+       FreezeState freeze_state() const;
+
+       sigc::signal<void> FreezeChange;
+
+       void freeze (InterThreadInfo&);
+       void unfreeze ();
+
+       void bounce (InterThreadInfo&);
+       void bounce_range (jack_nframes_t start, jack_nframes_t end, InterThreadInfo&);
+
+       XMLNode& get_state();
+       XMLNode& get_template();
+       int set_state(const XMLNode& node);
+
+       MIDI::Controllable& midi_rec_enable_control() { return _midi_rec_enable_control; }
+
+       void reset_midi_control (MIDI::Port*, bool);
+       void send_all_midi_feedback ();
+
+       bool record_enabled() const;
+       void set_meter_point (MeterPoint, void* src);
+
+protected:
+       MidiDiskstream *diskstream;
+       MeterPoint _saved_meter_point;
+       TrackMode _mode;
+
+       XMLNode& state (bool full);
+
+       void passthru_silence (jack_nframes_t start_frame, jack_nframes_t end_frame,
+                              jack_nframes_t nframes, jack_nframes_t offset, int declick,
+                              bool meter);
+
+       uint32_t n_process_buffers ();
+
+private:
+       struct FreezeRecordInsertInfo
+       {
+               FreezeRecordInsertInfo(XMLNode& st)
+                               : state (st), insert (0)
+               {}
+
+               XMLNode    state;
+               Insert*    insert;
+               id_t       id;
+               UndoAction memento;
+       };
+
+       struct FreezeRecord
+       {
+               FreezeRecord()
+               {
+                       playlist = 0;
+                       have_mementos = false;
+               }
+
+               ~FreezeRecord();
+
+               MidiPlaylist* playlist;
+               vector<FreezeRecordInsertInfo*> insert_info;
+               bool have_mementos;
+               FreezeState state;
+       };
+
+       FreezeRecord _freeze_record;
+       XMLNode* pending_state;
+
+       void diskstream_record_enable_changed (void *src);
+       void diskstream_input_channel_changed (void *src);
+
+       void input_change_handler (void *src);
+
+       sigc::connection recenable_connection;
+       sigc::connection ic_connection;
+
+       int deprecated_use_diskstream_connections ();
+       void set_state_part_two ();
+       void set_state_part_three ();
+
+       struct MIDIRecEnableControl : public MIDI::Controllable
+       {
+               MIDIRecEnableControl (MidiTrack&, MIDI::Port *);
+               void set_value (float);
+               void send_feedback (bool);
+               MIDI::byte* write_feedback (MIDI::byte* buf, int32_t& bufsize, bool val, bool force = false);
+               MidiTrack& track;
+               bool setting;
+               bool last_written;
+       };
+
+       MIDIRecEnableControl _midi_rec_enable_control;
+
+       bool _destructive;
+};
+
+}
+; /* namespace ARDOUR*/
+
+#endif /* __ardour_midi_track_h__ */
diff --git a/libs/ardour/ardour/smf_source.h b/libs/ardour/ardour/smf_source.h
new file mode 100644 (file)
index 0000000..00f8cb2
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+    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.
+
+*/
+
+#ifndef __ardour_smf_filesource_h__ 
+#define __ardour_smf_filesource_h__
+
+#include <time.h>
+
+#include <ardour/midi_source.h>
+
+namespace ARDOUR {
+
+/** Standard Midi File (Type 0) Source */
+class SMFSource : public MidiSource {
+  public:
+       enum Flag {
+               Writable = 0x1,
+               CanRename = 0x2,
+               Broadcast = 0x4,
+               Removable = 0x8,
+               RemovableIfEmpty = 0x10,
+               RemoveAtDestroy = 0x20,
+               BuildPeaks = 0x40
+       };
+       
+       /** Constructor for existing external-to-session files */
+       SMFSource (std::string path, Flag flags);
+
+       /* Constructor for existing in-session files */
+       SMFSource (const XMLNode&);
+
+       virtual ~SMFSource ();
+
+       int set_name (string newname, bool destructive);
+
+       string path() const { return _path; }
+
+       void set_allow_remove_if_empty (bool yn);
+       void mark_for_remove();
+
+       virtual int update_header (jack_nframes_t when, struct tm&, time_t) = 0;
+       virtual int flush_header () = 0;
+
+       int move_to_trash (const string trash_dir_name);
+
+       static bool is_empty (string path);
+       void mark_streaming_write_completed ();
+
+       void   mark_take (string);
+       string take_id() const { return _take_id; }
+
+       static void set_search_path (string);
+       static void set_header_position_offset (jack_nframes_t offset, bool negative);
+
+       XMLNode& get_state ();
+       int set_state (const XMLNode&);
+
+  protected:
+
+       int init (string idstr, bool must_exist);
+
+       bool find (std::string path, bool must_exist, bool& is_new);
+       bool removable() const;
+       bool writable() const { return _flags & Writable; }
+       
+       uint16_t      _channel;
+       string        _path;
+       Flag          _flags;
+       string        _take_id;
+       bool          _allow_remove_if_empty;
+       uint64_t      _timeline_position;
+
+       static string _search_path;
+};
+
+}; /* namespace ARDOUR */
+
+#endif /* __ardour_smf_filesource_h__ */
+
diff --git a/libs/ardour/diskstream.cc b/libs/ardour/diskstream.cc
new file mode 100644 (file)
index 0000000..050f234
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+    Copyright (C) 2000-2003 Paul Davis 
+
+    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.
+
+    $Id: diskstream.cc 567 2006-06-07 14:54:12Z trutkin $
+*/
+
+#include <fstream>
+#include <cstdio>
+#include <unistd.h>
+#include <cmath>
+#include <cerrno>
+#include <string>
+#include <climits>
+#include <fcntl.h>
+#include <cstdlib>
+#include <ctime>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <pbd/error.h>
+#include <pbd/basename.h>
+#include <glibmm/thread.h>
+#include <pbd/xml++.h>
+
+#include <ardour/ardour.h>
+#include <ardour/audioengine.h>
+#include <ardour/diskstream.h>
+#include <ardour/utils.h>
+#include <ardour/configuration.h>
+#include <ardour/audiofilesource.h>
+#include <ardour/destructive_filesource.h>
+#include <ardour/send.h>
+#include <ardour/playlist.h>
+#include <ardour/cycle_timer.h>
+#include <ardour/region.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+
+jack_nframes_t Diskstream::disk_io_chunk_frames;
+
+sigc::signal<void,Diskstream*>    Diskstream::DiskstreamCreated;
+//sigc::signal<void,list<AudioFileSource*>*> Diskstream::DeleteSources;
+sigc::signal<void>                Diskstream::DiskOverrun;
+sigc::signal<void>                Diskstream::DiskUnderrun;
+
+Diskstream::Diskstream (Session &sess, const string &name, Flag flag)
+       : _name (name)
+       , _session (sess)
+{
+#if 0
+       /* prevent any write sources from being created */
+
+       in_set_state = true;
+
+       init (flag);
+       //use_new_playlist ();
+
+       in_set_state = false;
+       DiskstreamCreated (this); /* EMIT SIGNAL */
+#endif
+}
+       
+Diskstream::Diskstream (Session& sess, const XMLNode& node)
+       : _session (sess)
+       
+{
+#if 0
+       in_set_state = true;
+       init (Recordable);
+
+       /*if (set_state (node)) {
+               in_set_state = false;
+               throw failed_constructor();
+       }*/
+
+       in_set_state = false;
+
+       //if (destructive()) {
+       //      use_destructive_playlist ();
+       //}
+       DiskstreamCreated (this); /* EMIT SIGNAL */
+#endif
+}
+
+void
+Diskstream::init (Flag f)
+{
+       _id = new_id();
+       _refcnt = 0;
+       _flags = f;
+       _io = 0;
+       _alignment_style = ExistingMaterial;
+       _persistent_alignment_style = ExistingMaterial;
+       first_input_change = true;
+       i_am_the_modifier = 0;
+       g_atomic_int_set (&_record_enabled, 0);
+       was_recording = false;
+       capture_start_frame = 0;
+       capture_captured = 0;
+       _visible_speed = 1.0f;
+       _actual_speed = 1.0f;
+       _buffer_reallocation_required = false;
+       _seek_required = false;
+       first_recordable_frame = max_frames;
+       last_recordable_frame = max_frames;
+       _roll_delay = 0;
+       _capture_offset = 0;
+       _processed = false;
+       _slaved = false;
+       adjust_capture_position = 0;
+       last_possibly_recording = 0;
+       loop_location = 0;
+       wrap_buffer_size = 0;
+       speed_buffer_size = 0;
+       last_phase = 0;
+       phi = (uint64_t) (0x1000000);
+       file_frame = 0;
+       playback_sample = 0;
+       playback_distance = 0;
+       _read_data_count = 0;
+       _write_data_count = 0;
+       deprecated_io_node = 0;
+
+       /* there are no channels at this point, so these
+          two calls just get speed_buffer_size and wrap_buffer
+          size setup without duplicating their code.
+       */
+
+       //set_block_size (_session.get_block_size());
+       //allocate_temporary_buffers ();
+
+       pending_overwrite = false;
+       overwrite_frame = 0;
+       overwrite_queued = false;
+       input_change_pending = NoChange;
+
+       //add_channel ();
+       _n_channels = 0;//1;
+}
+
+Diskstream::~Diskstream ()
+{
+       // Taken by child.. assure lock?
+       //Glib::Mutex::Lock lm (state_lock);
+
+       //if (_playlist) {
+       //      _playlist->unref ();
+       //}
+
+       //for (ChannelList::iterator chan = channels.begin(); chan != channels.end(); ++chan) {
+       //      destroy_channel((*chan));
+       //}
+       
+       //channels.clear();
+}
+
+void
+Diskstream::handle_input_change (IOChange change, void *src)
+{
+       Glib::Mutex::Lock lm (state_lock);
+
+       if (!(input_change_pending & change)) {
+               input_change_pending = IOChange (input_change_pending|change);
+               _session.request_input_change_handling ();
+       }
+}
+
+bool
+Diskstream::realtime_set_speed (double sp, bool global)
+{
+       bool changed = false;
+       double new_speed = sp * _session.transport_speed();
+       
+       if (_visible_speed != sp) {
+               _visible_speed = sp;
+               changed = true;
+       }
+       
+       if (new_speed != _actual_speed) {
+               
+               jack_nframes_t required_wrap_size = (jack_nframes_t) floor (_session.get_block_size() * 
+                                                                           fabs (new_speed)) + 1;
+               
+               if (required_wrap_size > wrap_buffer_size) {
+                       _buffer_reallocation_required = true;
+               }
+               
+               _actual_speed = new_speed;
+               phi = (uint64_t) (0x1000000 * fabs(_actual_speed));
+       }
+
+       if (changed) {
+               if (!global) {
+                       _seek_required = true;
+               }
+                speed_changed (); /* EMIT SIGNAL */
+       }
+
+       return _buffer_reallocation_required || _seek_required;
+}
+
+void
+Diskstream::prepare ()
+{
+       _processed = false;
+       playback_distance = 0;
+}
+
+void
+Diskstream::recover ()
+{
+       state_lock.unlock();
+       _processed = false;
+}
+
+void
+Diskstream::set_capture_offset ()
+{
+       if (_io == 0) {
+               /* can't capture, so forget it */
+               return;
+       }
+
+       _capture_offset = _io->input_latency();
+}
+
+void
+Diskstream::set_align_style (AlignStyle a)
+{
+       if (record_enabled() && _session.actively_recording()) {
+               return;
+       }
+
+
+       if (a != _alignment_style) {
+               _alignment_style = a;
+               AlignmentStyleChanged ();
+       }
+}
+
+int
+Diskstream::set_loop (Location *location)
+{
+       if (location) {
+               if (location->start() >= location->end()) {
+                       error << string_compose(_("Location \"%1\" not valid for track loop (start >= end)"), location->name()) << endl;
+                       return -1;
+               }
+       }
+
+       loop_location = location;
+
+        LoopSet (location); /* EMIT SIGNAL */
+       return 0;
+}
+
+jack_nframes_t
+Diskstream::get_capture_start_frame (uint32_t n)
+{
+       Glib::Mutex::Lock lm (capture_info_lock);
+
+       if (capture_info.size() > n) {
+               return capture_info[n]->start;
+       }
+       else {
+               return capture_start_frame;
+       }
+}
+
+jack_nframes_t
+Diskstream::get_captured_frames (uint32_t n)
+{
+       Glib::Mutex::Lock lm (capture_info_lock);
+
+       if (capture_info.size() > n) {
+               return capture_info[n]->frames;
+       }
+       else {
+               return capture_captured;
+       }
+}
+
+void
+Diskstream::set_roll_delay (jack_nframes_t nframes)
+{
+       _roll_delay = nframes;
+}
+
+void
+Diskstream::set_speed (double sp)
+{
+       _session.request_diskstream_speed (*this, sp);
+
+       /* to force a rebuffering at the right place */
+       playlist_modified();
+}
+
+void
+Diskstream::playlist_changed (Change ignored)
+{
+       playlist_modified ();
+}
+
+void
+Diskstream::playlist_modified ()
+{
+       if (!i_am_the_modifier && !overwrite_queued) {
+               _session.request_overwrite_buffer (this);
+               overwrite_queued = true;
+       } 
+}
+
+int
+Diskstream::set_name (string str, void *src)
+{
+       if (str != _name) {
+               assert(playlist());
+               playlist()->set_name (str);
+               _name = str;
+               
+               if (!in_set_state && recordable()) {
+                       /* rename existing capture files so that they have the correct name */
+                       return rename_write_sources ();
+               } else {
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+void
+Diskstream::set_destructive (bool yn)
+{
+       if (yn != destructive()) {
+               reset_write_sources (true, true);
+               if (yn) {
+                       _flags |= Destructive;
+               } else {
+                       _flags &= ~Destructive;
+               }
+       }
+}
diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc
new file mode 100644 (file)
index 0000000..0af4af4
--- /dev/null
@@ -0,0 +1,624 @@
+/*
+    Copyright (C) 2000-2003 Paul Davis 
+
+    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.
+
+    $Id: diskstream.cc 567 2006-06-07 14:54:12Z trutkin $
+*/
+
+#include <fstream>
+#include <cstdio>
+#include <unistd.h>
+#include <cmath>
+#include <cerrno>
+#include <string>
+#include <climits>
+#include <fcntl.h>
+#include <cstdlib>
+#include <ctime>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <pbd/error.h>
+#include <pbd/basename.h>
+#include <glibmm/thread.h>
+#include <pbd/xml++.h>
+
+#include <ardour/ardour.h>
+#include <ardour/audioengine.h>
+#include <ardour/midi_diskstream.h>
+#include <ardour/utils.h>
+#include <ardour/configuration.h>
+#include <ardour/smf_source.h>
+#include <ardour/destructive_filesource.h>
+#include <ardour/send.h>
+#include <ardour/midi_playlist.h>
+#include <ardour/cycle_timer.h>
+#include <ardour/midi_region.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+
+//sigc::signal<void,MidiDiskstream*>    MidiDiskstream::MidiDiskstreamCreated;
+sigc::signal<void,list<SMFSource*>*> MidiDiskstream::DeleteSources;
+//sigc::signal<void>                MidiDiskstream::DiskOverrun;
+//sigc::signal<void>                MidiDiskstream::DiskUnderrun;
+
+MidiDiskstream::MidiDiskstream (Session &sess, const string &name, Diskstream::Flag flag)
+       : Diskstream(sess, name, flag)
+       , _playlist(NULL)
+{
+       /* prevent any write sources from being created */
+
+       in_set_state = true;
+
+       init (flag);
+       use_new_playlist ();
+
+       in_set_state = false;
+
+       DiskstreamCreated (this); /* EMIT SIGNAL */
+}
+       
+MidiDiskstream::MidiDiskstream (Session& sess, const XMLNode& node)
+       : Diskstream(sess, node)
+       , _playlist(NULL)
+{
+       in_set_state = true;
+       init (Recordable);
+
+       if (set_state (node)) {
+               in_set_state = false;
+               throw failed_constructor();
+       }
+
+       in_set_state = false;
+
+       if (destructive()) {
+               use_destructive_playlist ();
+       }
+
+       DiskstreamCreated (this); /* EMIT SIGNAL */
+}
+
+void
+MidiDiskstream::init (Diskstream::Flag f)
+{
+       Diskstream::init(f);
+
+       /* there are no channels at this point, so these
+          two calls just get speed_buffer_size and wrap_buffer
+          size setup without duplicating their code.
+       */
+
+       set_block_size (_session.get_block_size());
+       allocate_temporary_buffers ();
+
+       /* FIXME: this is now done before the above.  OK? */
+       /*pending_overwrite = false;
+       overwrite_frame = 0;
+       overwrite_queued = false;
+       input_change_pending = NoChange;*/
+
+       _n_channels = 1;
+}
+
+MidiDiskstream::~MidiDiskstream ()
+{
+       Glib::Mutex::Lock lm (state_lock);
+
+       if (_playlist)
+               _playlist->unref ();
+}
+/*
+void
+MidiDiskstream::handle_input_change (IOChange change, void *src)
+{
+       Glib::Mutex::Lock lm (state_lock);
+
+       if (!(input_change_pending & change)) {
+               input_change_pending = IOChange (input_change_pending|change);
+               _session.request_input_change_handling ();
+       }
+}
+*/
+void
+MidiDiskstream::non_realtime_input_change ()
+{
+}
+
+void
+MidiDiskstream::get_input_sources ()
+{
+}              
+
+int
+MidiDiskstream::find_and_use_playlist (const string& name)
+{
+       Playlist* pl;
+       MidiPlaylist* playlist;
+               
+       if ((pl = _session.get_playlist (name)) == 0) {
+               error << string_compose(_("MidiDiskstream: Session doesn't know about a Playlist called \"%1\""), name) << endmsg;
+               return -1;
+       }
+
+       if ((playlist = dynamic_cast<MidiPlaylist*> (pl)) == 0) {
+               error << string_compose(_("MidiDiskstream: Playlist \"%1\" isn't an midi playlist"), name) << endmsg;
+               return -1;
+       }
+
+       return use_playlist (playlist);
+}
+
+int
+MidiDiskstream::use_playlist (Playlist* playlist)
+{
+       assert(dynamic_cast<MidiPlaylist*>(playlist));
+
+       {
+               Glib::Mutex::Lock lm (state_lock);
+
+               if (playlist == _playlist) {
+                       return 0;
+               }
+
+               plstate_connection.disconnect();
+               plmod_connection.disconnect ();
+               plgone_connection.disconnect ();
+
+               if (_playlist) {
+                       _playlist->unref();
+               }
+                       
+               _playlist = dynamic_cast<MidiPlaylist*>(playlist);
+               _playlist->ref();
+
+               if (!in_set_state && recordable()) {
+                       reset_write_sources (false);
+               }
+               
+               plstate_connection = _playlist->StateChanged.connect (mem_fun (*this, &MidiDiskstream::playlist_changed));
+               plmod_connection = _playlist->Modified.connect (mem_fun (*this, &MidiDiskstream::playlist_modified));
+               plgone_connection = _playlist->GoingAway.connect (mem_fun (*this, &MidiDiskstream::playlist_deleted));
+       }
+
+       if (!overwrite_queued) {
+               _session.request_overwrite_buffer (this);
+               overwrite_queued = true;
+       }
+       
+       PlaylistChanged (); /* EMIT SIGNAL */
+       _session.set_dirty ();
+
+       return 0;
+}
+
+int
+MidiDiskstream::use_new_playlist ()
+{
+       string newname;
+       MidiPlaylist* playlist;
+
+       if (!in_set_state && destructive()) {
+               return 0;
+       }
+
+       if (_playlist) {
+               newname = Playlist::bump_name (_playlist->name(), _session);
+       } else {
+               newname = Playlist::bump_name (_name, _session);
+       }
+
+       if ((playlist = new MidiPlaylist (_session, newname, hidden())) != 0) {
+               playlist->set_orig_diskstream_id (id());
+               return use_playlist (playlist);
+       } else { 
+               return -1;
+       }
+}
+
+int
+MidiDiskstream::use_copy_playlist ()
+{
+       if (destructive()) {
+               return 0;
+       }
+
+       if (_playlist == 0) {
+               error << string_compose(_("MidiDiskstream %1: there is no existing playlist to make a copy of!"), _name) << endmsg;
+               return -1;
+       }
+
+       string newname;
+       MidiPlaylist* playlist;
+
+       newname = Playlist::bump_name (_playlist->name(), _session);
+       
+       if ((playlist  = new MidiPlaylist (*_playlist, newname)) != 0) {
+               playlist->set_orig_diskstream_id (id());
+               return use_playlist (playlist);
+       } else { 
+               return -1;
+       }
+}
+
+
+void
+MidiDiskstream::playlist_deleted (Playlist* pl)
+{
+       /* this catches an ordering issue with session destruction. playlists 
+          are destroyed before diskstreams. we have to invalidate any handles
+          we have to the playlist.
+       */
+
+       _playlist = 0;
+}
+
+
+void
+MidiDiskstream::setup_destructive_playlist ()
+{
+       /* a single full-sized region */
+
+       //MidiRegion* region = new MidiRegion (srcs, 0, max_frames, _name);
+       //_playlist->add_region (*region, 0);           
+}
+
+void
+MidiDiskstream::use_destructive_playlist ()
+{
+       /* use the sources associated with the single full-extent region */
+       
+       Playlist::RegionList* rl = _playlist->regions_at (0);
+
+       if (rl->empty()) {
+               reset_write_sources (false, true);
+               return;
+       }
+
+       MidiRegion* region = dynamic_cast<MidiRegion*> (rl->front());
+
+       if (region == 0) {
+               throw failed_constructor();
+       }
+
+       delete rl;
+
+       /* the source list will never be reset for a destructive track */
+}
+
+void
+MidiDiskstream::set_io (IO& io)
+{
+       _io = &io;
+       set_align_style_from_io ();
+}
+
+void
+MidiDiskstream::non_realtime_set_speed ()
+{
+       if (_buffer_reallocation_required)
+       {
+               Glib::Mutex::Lock lm (state_lock);
+               allocate_temporary_buffers ();
+
+               _buffer_reallocation_required = false;
+       }
+
+       if (_seek_required) {
+               if (speed() != 1.0f || speed() != -1.0f) {
+                       seek ((jack_nframes_t) (_session.transport_frame() * (double) speed()), true);
+               }
+               else {
+                       seek (_session.transport_frame(), true);
+               }
+
+               _seek_required = false;
+       }
+}
+
+void
+MidiDiskstream::check_record_status (jack_nframes_t transport_frame, jack_nframes_t nframes, bool can_record)
+{
+}
+
+int
+MidiDiskstream::process (jack_nframes_t transport_frame, jack_nframes_t nframes, jack_nframes_t offset, bool can_record, bool rec_monitors_input)
+{
+       return 0;
+}
+
+bool
+MidiDiskstream::commit (jack_nframes_t nframes)
+{
+       return 0;
+}
+
+void
+MidiDiskstream::set_pending_overwrite (bool yn)
+{
+       /* called from audio thread, so we can use the read ptr and playback sample as we wish */
+       
+       pending_overwrite = yn;
+
+       overwrite_frame = playback_sample;
+       //overwrite_offset = channels.front().playback_buf->get_read_ptr();
+}
+
+int
+MidiDiskstream::overwrite_existing_buffers ()
+{
+       return 0;
+}
+
+int
+MidiDiskstream::seek (jack_nframes_t frame, bool complete_refill)
+{
+       return 0;
+}
+
+int
+MidiDiskstream::can_internal_playback_seek (jack_nframes_t distance)
+{
+       return 0;
+}
+
+int
+MidiDiskstream::internal_playback_seek (jack_nframes_t distance)
+{
+       return 0;
+}
+
+int
+MidiDiskstream::read (RawMidi* buf, RawMidi* mixdown_buffer, char * workbuf, jack_nframes_t& start, jack_nframes_t cnt, bool reversed)
+{
+       return 0;
+}
+
+int
+MidiDiskstream::do_refill (RawMidi* mixdown_buffer, float* gain_buffer, char * workbuf)
+{
+       return 0;
+}      
+
+int
+MidiDiskstream::do_flush (char * workbuf, bool force_flush)
+{
+       return 0;
+}
+
+void
+MidiDiskstream::transport_stopped (struct tm& when, time_t twhen, bool abort_capture)
+{
+}
+
+void
+MidiDiskstream::finish_capture (bool rec_monitors_input)
+{
+}
+
+void
+MidiDiskstream::set_record_enabled (bool yn, void* src)
+{
+}
+
+XMLNode&
+MidiDiskstream::get_state ()
+{
+       XMLNode* node = new XMLNode ("MidiDiskstream");
+       char buf[64];
+       LocaleGuard lg (X_("POSIX"));
+
+       snprintf (buf, sizeof(buf), "0x%x", _flags);
+       node->add_property ("flags", buf);
+
+       node->add_property ("playlist", _playlist->name());
+       
+       snprintf (buf, sizeof(buf), "%f", _visible_speed);
+       node->add_property ("speed", buf);
+
+       node->add_property("name", _name);
+       snprintf (buf, sizeof(buf), "%" PRIu64, id());
+       node->add_property("id", buf);
+
+       if (!_capturing_sources.empty() && _session.get_record_enabled()) {
+
+               XMLNode* cs_child = new XMLNode (X_("CapturingSources"));
+               XMLNode* cs_grandchild;
+
+               for (vector<SMFSource*>::iterator i = _capturing_sources.begin(); i != _capturing_sources.end(); ++i) {
+                       cs_grandchild = new XMLNode (X_("file"));
+                       cs_grandchild->add_property (X_("path"), (*i)->path());
+                       cs_child->add_child_nocopy (*cs_grandchild);
+               }
+
+               /* store the location where capture will start */
+
+               Location* pi;
+
+               if (_session.get_punch_in() && ((pi = _session.locations()->auto_punch_location()) != 0)) {
+                       snprintf (buf, sizeof (buf), "%" PRIu32, pi->start());
+               } else {
+                       snprintf (buf, sizeof (buf), "%" PRIu32, _session.transport_frame());
+               }
+
+               cs_child->add_property (X_("at"), buf);
+               node->add_child_nocopy (*cs_child);
+       }
+
+       if (_extra_xml) {
+               node->add_child_copy (*_extra_xml);
+       }
+
+       return* node;
+}
+
+int
+MidiDiskstream::set_state (const XMLNode& node)
+{
+       const XMLProperty* prop;
+       XMLNodeList nlist = node.children();
+       XMLNodeIterator niter;
+       uint32_t nchans = 1;
+       XMLNode* capture_pending_node = 0;
+       LocaleGuard lg (X_("POSIX"));
+
+       in_set_state = true;
+
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+               if ((*niter)->name() == IO::state_node_name) {
+                       deprecated_io_node = new XMLNode (**niter);
+               }
+
+               if ((*niter)->name() == X_("CapturingSources")) {
+                       capture_pending_node = *niter;
+               }
+       }
+
+       /* prevent write sources from being created */
+       
+       in_set_state = true;
+       
+       if ((prop = node.property ("name")) != 0) {
+               _name = prop->value();
+       } 
+
+       if (deprecated_io_node) {
+               if ((prop = deprecated_io_node->property ("id")) != 0) {
+                       sscanf (prop->value().c_str(), "%" PRIu64, &_id);
+               }
+       } else {
+               if ((prop = node.property ("id")) != 0) {
+                       sscanf (prop->value().c_str(), "%" PRIu64, &_id);
+               }
+       }
+
+       if ((prop = node.property ("flags")) != 0) {
+               _flags = strtol (prop->value().c_str(), 0, 0);
+       }
+
+       if ((prop = node.property ("channels")) != 0) {
+               nchans = atoi (prop->value().c_str());
+       }
+       
+       if ((prop = node.property ("playlist")) == 0) {
+               return -1;
+       }
+
+       {
+               bool had_playlist = (_playlist != 0);
+       
+               if (find_and_use_playlist (prop->value())) {
+                       return -1;
+               }
+
+               if (!had_playlist) {
+                       _playlist->set_orig_diskstream_id (_id);
+               }
+               
+               if (!destructive() && capture_pending_node) {
+                       /* destructive streams have one and only one source per channel,
+                          and so they never end up in pending capture in any useful
+                          sense.
+                       */
+                       use_pending_capture_data (*capture_pending_node);
+               }
+
+       }
+
+       if ((prop = node.property ("speed")) != 0) {
+               double sp = atof (prop->value().c_str());
+
+               if (realtime_set_speed (sp, false)) {
+                       non_realtime_set_speed ();
+               }
+       }
+
+       in_set_state = false;
+
+       /* make sure this is clear before we do anything else */
+
+       _capturing_sources.clear ();
+
+       /* write sources are handled when we handle the input set 
+          up of the IO that owns this DS (::non_realtime_input_change())
+       */
+               
+       in_set_state = false;
+
+       return 0;
+}
+
+int
+MidiDiskstream::use_new_write_source (uint32_t n)
+{
+       return 0;
+}
+
+void
+MidiDiskstream::reset_write_sources (bool mark_write_complete, bool force)
+{
+}
+
+int
+MidiDiskstream::rename_write_sources ()
+{
+       return 0;
+}
+
+void
+MidiDiskstream::set_block_size (jack_nframes_t nframes)
+{
+}
+
+void
+MidiDiskstream::allocate_temporary_buffers ()
+{
+}
+
+void
+MidiDiskstream::monitor_input (bool yn)
+{
+}
+
+void
+MidiDiskstream::set_align_style_from_io ()
+{
+}
+
+
+float
+MidiDiskstream::playback_buffer_load () const
+{
+       return 0;
+}
+
+float
+MidiDiskstream::capture_buffer_load () const
+{
+       return 0;
+}
+
+
+int
+MidiDiskstream::use_pending_capture_data (XMLNode& node)
+{
+       return 0;
+}
diff --git a/libs/ardour/midi_playlist.cc b/libs/ardour/midi_playlist.cc
new file mode 100644 (file)
index 0000000..007856e
--- /dev/null
@@ -0,0 +1,634 @@
+/*
+    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 <cassert>
+
+#include <algorithm>
+
+#include <stdlib.h>
+
+#include <sigc++/bind.h>
+
+#include <ardour/types.h>
+#include <ardour/configuration.h>
+#include <ardour/midi_playlist.h>
+#include <ardour/midi_region.h>
+#include <ardour/session.h>
+
+#include <pbd/error.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace sigc;
+using namespace std;
+
+MidiPlaylist::State::~State ()
+{}
+
+MidiPlaylist::MidiPlaylist (Session& session, const XMLNode& node, bool hidden)
+               : Playlist (session, node, hidden)
+{
+       in_set_state = true;
+       set_state (node);
+       in_set_state = false;
+
+       save_state (_("initial state"));
+
+       if (!hidden) {
+               PlaylistCreated (this); /* EMIT SIGNAL */
+       }
+}
+
+MidiPlaylist::MidiPlaylist (Session& session, string name, bool hidden)
+               : Playlist (session, name, hidden)
+{
+       save_state (_("initial state"));
+
+       if (!hidden) {
+               PlaylistCreated (this); /* EMIT SIGNAL */
+       }
+
+}
+
+MidiPlaylist::MidiPlaylist (const MidiPlaylist& other, string name, bool hidden)
+               : Playlist (other, name, hidden)
+{
+       save_state (_("initial state"));
+
+       /*
+       list<Region*>::const_iterator in_o  = other.regions.begin();
+       list<Region*>::iterator in_n = regions.begin();
+
+       while (in_o != other.regions.end()) {
+               MidiRegion *ar = dynamic_cast<MidiRegion *>( (*in_o) );
+
+               for (list<Crossfade *>::const_iterator xfades = other._crossfades.begin(); xfades != other._crossfades.end(); ++xfades) {
+                       if ( &(*xfades)->in() == ar) {
+                               // We found one! Now copy it!
+
+                               list<Region*>::const_iterator out_o = other.regions.begin();
+                               list<Region*>::const_iterator out_n = regions.begin();
+
+                               while (out_o != other.regions.end()) {
+
+                                       MidiRegion *ar2 = dynamic_cast<MidiRegion *>( (*out_o) );
+
+                                       if ( &(*xfades)->out() == ar2) {
+                                               MidiRegion *in  = dynamic_cast<MidiRegion*>( (*in_n) );
+                                               MidiRegion *out = dynamic_cast<MidiRegion*>( (*out_n) );
+                                               Crossfade *new_fade = new Crossfade( *(*xfades), in, out);
+                                               add_crossfade(*new_fade);
+                                               break;
+                                       }
+
+                                       out_o++;
+                                       out_n++;
+                               }
+                               //                              cerr << "HUH!? second region in the crossfade not found!" << endl;
+                       }
+               }
+
+               in_o++;
+               in_n++;
+       }
+*/
+       if (!hidden) {
+               PlaylistCreated (this); /* EMIT SIGNAL */
+       }
+}
+
+MidiPlaylist::MidiPlaylist (const MidiPlaylist& other, jack_nframes_t start, jack_nframes_t cnt, string name, bool hidden)
+               : Playlist (other, start, cnt, name, hidden)
+{
+       save_state (_("initial state"));
+
+       /* this constructor does NOT notify others (session) */
+}
+
+MidiPlaylist::~MidiPlaylist ()
+{
+       set <Region*> all_regions;
+
+       GoingAway (this);
+
+       /* find every region we've ever used, and add it to the set of
+          all regions.
+       */
+
+       for (RegionList::iterator x = regions.begin(); x != regions.end(); ++x) {
+               all_regions.insert (*x);
+       }
+
+       for (StateMap::iterator i = states.begin(); i != states.end(); ++i) {
+
+               MidiPlaylist::State* apstate = dynamic_cast<MidiPlaylist::State*> (*i);
+
+               for (RegionList::iterator r = apstate->regions.begin(); r != apstate->regions.end(); ++r) {
+                       all_regions.insert (*r);
+               }
+
+               delete apstate;
+       }
+
+       /* delete every region */
+
+       for (set<Region *>::iterator ar = all_regions.begin(); ar != all_regions.end(); ++ar) {
+               (*ar)->unlock_sources ();
+               delete *ar;
+       }
+
+}
+
+struct RegionSortByLayer
+{
+       bool operator() (Region *a, Region *b)
+       {
+               return a->layer() < b->layer();
+       }
+};
+
+jack_nframes_t
+MidiPlaylist::read (unsigned char *buf, unsigned char *mixdown_buffer, char * workbuf, jack_nframes_t start,
+                     jack_nframes_t cnt, unsigned chan_n)
+{
+       jack_nframes_t ret = cnt;
+       jack_nframes_t end;
+       jack_nframes_t read_frames;
+       jack_nframes_t skip_frames;
+
+       /* optimizing this memset() away involves a lot of conditionals
+          that may well cause more of a hit due to cache misses 
+          and related stuff than just doing this here.
+          
+          it would be great if someone could measure this
+          at some point.
+
+          one way or another, parts of the requested area
+          that are not written to by Region::region_at()
+          for all Regions that cover the area need to be
+          zeroed.
+       */
+
+       memset (buf, 0, sizeof (unsigned char) * cnt);
+
+       /* this function is never called from a realtime thread, so
+          its OK to block (for short intervals).
+       */
+
+       Glib::Mutex::Lock rm (region_lock);
+
+       end =  start + cnt - 1;
+
+       read_frames = 0;
+       skip_frames = 0;
+       _read_data_count = 0;
+
+       map<uint32_t,vector<Region*> > relevant_regions;
+       vector<uint32_t> relevant_layers;
+
+       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+               if ((*i)->coverage (start, end) != OverlapNone) {
+
+                       relevant_regions[(*i)->layer()].push_back (*i);
+                       relevant_layers.push_back ((*i)->layer());
+               }
+       }
+
+       //      RegionSortByLayer layer_cmp;
+       //      relevant_regions.sort (layer_cmp);
+
+
+       for (vector<uint32_t>::iterator l = relevant_layers.begin(); l != relevant_layers.end(); ++l) {
+
+               // FIXME: Should be vector<MidiRegion*>
+               vector<Region*>& r (relevant_regions[*l]);
+
+               for (vector<Region*>::iterator i = r.begin(); i != r.end(); ++i) {
+                       MidiRegion* const mr = dynamic_cast<MidiRegion*>(*i);
+                       assert(mr);
+                       mr->read_at (buf, mixdown_buffer, workbuf, start, cnt, chan_n, read_frames, skip_frames);
+                       _read_data_count += mr->read_data_count();
+               }
+
+       }
+
+       return ret;
+}
+
+
+void
+MidiPlaylist::remove_dependents (Region& region)
+{
+       MidiRegion* r = dynamic_cast<MidiRegion*> (&region);
+
+       if (r == 0) {
+               PBD::fatal << _("programming error: non-midi Region passed to remove_overlap in midi playlist")
+               << endmsg;
+               return;
+       }
+
+}
+
+
+void
+MidiPlaylist::flush_notifications ()
+{
+       Playlist::flush_notifications();
+
+       if (in_flush) {
+               return;
+       }
+
+       in_flush = true;
+
+       in_flush = false;
+}
+
+void
+MidiPlaylist::refresh_dependents (Region& r)
+{
+       MidiRegion* ar = dynamic_cast<MidiRegion*>(&r);
+
+       if (ar == 0) {
+               return;
+       }
+}
+
+void
+MidiPlaylist::finalize_split_region (Region *o, Region *l, Region *r)
+{
+       /*
+       MidiRegion *orig  = dynamic_cast<MidiRegion*>(o);
+       MidiRegion *left  = dynamic_cast<MidiRegion*>(l);
+       MidiRegion *right = dynamic_cast<MidiRegion*>(r);
+
+       for (Crossfades::iterator x = _crossfades.begin(); x != _crossfades.end();) {
+               Crossfades::iterator tmp;
+               tmp = x;
+               ++tmp;
+
+               Crossfade *fade = 0;
+
+               if ((*x)->_in == orig) {
+                       if (! (*x)->covers(right->position())) {
+                               fade = new Crossfade( *(*x), left, (*x)->_out);
+                       } else {
+                               // Overlap, the crossfade is copied on the left side of the right region instead
+                               fade = new Crossfade( *(*x), right, (*x)->_out);
+                       }
+               }
+
+               if ((*x)->_out == orig) {
+                       if (! (*x)->covers(right->position())) {
+                               fade = new Crossfade( *(*x), (*x)->_in, right);
+                       } else {
+                               // Overlap, the crossfade is copied on the right side of the left region instead
+                               fade = new Crossfade( *(*x), (*x)->_in, left);
+                       }
+               }
+
+               if (fade) {
+                       _crossfades.remove( (*x) );
+                       add_crossfade (*fade);
+               }
+               x = tmp;
+       }*/
+}
+
+void
+MidiPlaylist::check_dependents (Region& r, bool norefresh)
+{
+       MidiRegion* other;
+       MidiRegion* region;
+       MidiRegion* top;
+       MidiRegion* bottom;
+
+       if (in_set_state || in_partition) {
+               return;
+       }
+
+       if ((region = dynamic_cast<MidiRegion*> (&r)) == 0) {
+               PBD::fatal << _("programming error: non-midi Region tested for overlap in midi playlist")
+               << endmsg;
+               return;
+       }
+
+       if (!norefresh) {
+               refresh_dependents (r);
+       }
+
+       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+               other = dynamic_cast<MidiRegion*> (*i);
+
+               if (other == region) {
+                       continue;
+               }
+
+               if (other->muted() || region->muted()) {
+                       continue;
+               }
+
+               if (other->layer() < region->layer()) {
+                       top = region;
+                       bottom = other;
+               } else {
+                       top = other;
+                       bottom = region;
+               }
+
+       }
+}
+
+
+int
+MidiPlaylist::set_state (const XMLNode& node)
+{
+       /*
+       XMLNode *child;
+       XMLNodeList nlist;
+       XMLNodeConstIterator niter;
+
+       if (!in_set_state) {
+               Playlist::set_state (node);
+       }
+
+       nlist = node.children();
+
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+
+               child = *niter;
+
+       }*/
+
+       return 0;
+}
+
+void
+MidiPlaylist::drop_all_states ()
+{
+       set<Region*> all_regions;
+
+       /* find every region we've ever used, and add it to the set of
+          all regions. same for xfades;
+       */
+
+       for (StateMap::iterator i = states.begin(); i != states.end(); ++i) {
+
+               MidiPlaylist::State* apstate = dynamic_cast<MidiPlaylist::State*> (*i);
+
+               for (RegionList::iterator r = apstate->regions.begin(); r != apstate->regions.end(); ++r) {
+                       all_regions.insert (*r);
+               }
+       }
+
+       /* now remove from the "all" lists every region that is in the current list. */
+
+       for (list<Region*>::iterator i = regions.begin(); i != regions.end(); ++i) {
+               set
+                       <Region*>::iterator x = all_regions.find (*i);
+               if (x != all_regions.end()) {
+                       all_regions.erase (x);
+               }
+       }
+
+       /* delete every region that is left - these are all things that are part of our "history" */
+
+       for (set
+               <Region *>::iterator ar = all_regions.begin(); ar != all_regions.end(); ++ar) {
+               (*ar)->unlock_sources ();
+               delete *ar;
+       }
+
+       /* Now do the generic thing ... */
+
+       StateManager::drop_all_states ();
+}
+
+StateManager::State*
+MidiPlaylist::state_factory (std::string why) const
+{
+       State* state = new State (why);
+
+       state->regions = regions;
+       state->region_states.clear ();
+       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+               state->region_states.push_back ((*i)->get_memento());
+       }
+
+       return state;
+}
+
+Change
+MidiPlaylist::restore_state (StateManager::State& state)
+{
+       {
+               RegionLock rlock (this);
+               State* apstate = dynamic_cast<State*> (&state);
+
+               in_set_state = true;
+
+               regions = apstate->regions;
+
+               for (list<UndoAction>::iterator s = apstate->
+                                                   region_states.begin();
+                       s != apstate->region_states.end();
+                       ++s) {
+                       (*s) ();
+               }
+
+               in_set_state = false;
+       }
+
+       notify_length_changed ();
+       return Change (~0);
+}
+
+UndoAction
+MidiPlaylist::get_memento () const
+{
+       return sigc::bind (mem_fun (*(const_cast<MidiPlaylist*> (this)), &StateManager::use_state), _current_state_id);
+}
+
+
+XMLNode&
+MidiPlaylist::state (bool full_state)
+{
+       XMLNode& node = Playlist::state (full_state);
+
+       return node;
+}
+
+void
+MidiPlaylist::dump () const
+{
+       Region *r;
+
+       cerr << "Playlist \"" << _name << "\" " << endl
+       << regions.size() << " regions "
+       << endl;
+
+       for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) {
+               r = *i;
+               cerr << "  " << r->name() << " @ " << r << " ["
+               << r->start() << "+" << r->length()
+               << "] at "
+               << r->position()
+               << " on layer "
+               << r->layer ()
+               << endl;
+       }
+}
+
+bool
+MidiPlaylist::destroy_region (Region* region)
+{
+       MidiRegion* r = dynamic_cast<MidiRegion*> (region);
+       bool changed = false;
+
+       if (r == 0) {
+               PBD::fatal << _("programming error: non-midi Region passed to remove_overlap in midi playlist")
+               << endmsg;
+               /*NOTREACHED*/
+               return false;
+       }
+
+       {
+               RegionLock rlock (this);
+               RegionList::iterator i;
+               RegionList::iterator tmp;
+
+               for (i = regions.begin(); i != regions.end(); ) {
+
+                       tmp = i;
+                       ++tmp;
+
+                       if ((*i) == region) {
+                               (*i)->unlock_sources ();
+                               regions.erase (i);
+                               changed = true;
+                       }
+
+                       i = tmp;
+               }
+       }
+
+       for (StateMap::iterator s = states.begin(); s != states.end(); ) {
+               StateMap::iterator tmp;
+
+               tmp = s;
+               ++tmp;
+
+               State* astate = dynamic_cast<State*> (*s);
+
+               list<UndoAction>::iterator rsi, rsitmp;
+               RegionList::iterator ri, ritmp;
+
+               for (ri = astate->regions.begin(), rsi = astate->region_states.begin();
+                       ri != astate->regions.end() && rsi != astate->region_states.end();) {
+
+
+                       ritmp = ri;
+                       ++ritmp;
+
+                       rsitmp = rsi;
+                       ++rsitmp;
+
+                       if (region == (*ri)) {
+                               astate->regions.erase (ri);
+                               astate->region_states.erase (rsi);
+                       }
+
+                       ri = ritmp;
+                       rsi = rsitmp;
+               }
+
+               s = tmp;
+       }
+
+
+       if (changed) {
+               /* overload this, it normally means "removed", not destroyed */
+               notify_region_removed (region);
+       }
+
+       return changed;
+}
+
+
+void
+MidiPlaylist::get_equivalent_regions (const MidiRegion& other, vector<MidiRegion*>& results)
+{
+       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+               MidiRegion* ar = dynamic_cast<MidiRegion*> (*i);
+
+               if (ar) {
+                       if (Config->get_use_overlap_equivalency()) {
+                               if (ar->overlap_equivalent (other)) {
+                                       results.push_back (ar);
+                               } else if (ar->equivalent (other)) {
+                                       results.push_back (ar);
+                               }
+                       }
+               }
+       }
+}
+
+void
+MidiPlaylist::get_region_list_equivalent_regions (const MidiRegion& other, vector<MidiRegion*>& results)
+{
+       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+               MidiRegion* ar = dynamic_cast<MidiRegion*> (*i);
+
+               if (ar && ar->region_list_equivalent (other)) {
+                       results.push_back (ar);
+               }
+       }
+}
+
+bool
+MidiPlaylist::region_changed (Change what_changed, Region* region)
+{
+       if (in_flush || in_set_state) {
+               return false;
+       }
+
+       Change our_interests = Change (/*MidiRegion::FadeInChanged|
+                                      MidiRegion::FadeOutChanged|
+                                      MidiRegion::FadeInActiveChanged|
+                                      MidiRegion::FadeOutActiveChanged|
+                                      MidiRegion::EnvelopeActiveChanged|
+                                      MidiRegion::ScaleAmplitudeChanged|
+                                      MidiRegion::EnvelopeChanged*/);
+       bool parent_wants_notify;
+
+       parent_wants_notify = Playlist::region_changed (what_changed, region);
+
+       maybe_save_state (_("region modified"));
+
+       if ((parent_wants_notify || (what_changed & our_interests))) {
+               notify_modified ();
+       }
+
+       return true;
+}
+
diff --git a/libs/ardour/midi_region.cc b/libs/ardour/midi_region.cc
new file mode 100644 (file)
index 0000000..e9d75e3
--- /dev/null
@@ -0,0 +1,647 @@
+/*
+    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 <cmath>
+#include <climits>
+#include <cfloat>
+
+#include <set>
+
+#include <sigc++/bind.h>
+#include <sigc++/class_slot.h>
+
+#include <glibmm/thread.h>
+
+#include <pbd/basename.h>
+#include <pbd/xml++.h>
+
+#include <ardour/midi_region.h>
+#include <ardour/session.h>
+#include <ardour/gain.h>
+#include <ardour/dB.h>
+#include <ardour/playlist.h>
+#include <ardour/midi_source.h>
+
+#include "i18n.h"
+#include <locale.h>
+
+using namespace std;
+using namespace ARDOUR;
+
+MidiRegionState::MidiRegionState (string why)
+       : RegionState (why)
+{
+}
+
+MidiRegion::MidiRegion (MidiSource& src, jack_nframes_t start, jack_nframes_t length, bool announce)
+       : Region (start, length, PBD::basename_nosuffix(src.name()), 0,  Region::Flag(Region::DefaultFlags|Region::External))
+{
+       /* basic MidiRegion constructor */
+
+       sources.push_back (&src);
+       master_sources.push_back (&src);
+       src.GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+
+       save_state ("initial state");
+
+       if (announce) {
+                CheckNewRegion (this); /* EMIT SIGNAL */
+       }
+}
+
+MidiRegion::MidiRegion (MidiSource& src, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t layer, Flag flags, bool announce)
+       : Region (start, length, name, layer, flags)
+{
+       /* basic MidiRegion constructor */
+
+       sources.push_back (&src);
+       master_sources.push_back (&src);
+       src.GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+
+       save_state ("initial state");
+
+       if (announce) {
+                CheckNewRegion (this); /* EMIT SIGNAL */
+       }
+}
+
+MidiRegion::MidiRegion (SourceList& srcs, jack_nframes_t start, jack_nframes_t length, const string& name, layer_t layer, Flag flags, bool announce)
+       : Region (start, length, name, layer, flags)
+{
+       /* basic MidiRegion constructor */
+#if 0
+       for (SourceList::iterator i=srcs.begin(); i != srcs.end(); ++i) {
+               sources.push_back (*i);
+               master_sources.push_back (*i);
+               (*i)->GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+       }
+
+{
+       /* create a new MidiRegion, that is part of an existing one */
+       
+       set<MidiSource*> unique_srcs;
+
+       for (SourceList::const_iterator i= other.sources.begin(); i != other.sources.end(); ++i) {
+               sources.push_back (*i);
+               (*i)->GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+               unique_srcs.insert (*i);
+       }
+
+       for (SourceList::const_iterator i = other.master_sources.begin(); i != other.master_sources.end(); ++i) {
+               if (unique_srcs.find (*i) == unique_srcs.end()) {
+                       (*i)->GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+               }
+               master_sources.push_back (*i);
+       }
+
+       save_state ("initial state");
+
+       if (announce) {
+               CheckNewRegion (this); /* EMIT SIGNAL */
+       }
+#endif
+}
+
+MidiRegion::MidiRegion (const MidiRegion &other)
+       : Region (other)
+{
+       /* Pure copy constructor */
+
+       set<MidiSource*> unique_srcs;
+
+       for (SourceList::const_iterator i = other.sources.begin(); i != other.sources.end(); ++i) {
+               sources.push_back (*i);
+               (*i)->GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+               unique_srcs.insert (*i);
+       }
+
+       for (SourceList::const_iterator i = other.master_sources.begin(); i != other.master_sources.end(); ++i) {
+               master_sources.push_back (*i);
+               if (unique_srcs.find (*i) == unique_srcs.end()) {
+                       (*i)->GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+               }
+       }
+
+       save_state ("initial state");
+
+       /* NOTE: no CheckNewRegion signal emitted here. This is the copy constructor */
+}
+
+MidiRegion::MidiRegion (MidiSource& src, const XMLNode& node)
+       : Region (node)
+{
+       sources.push_back (&src);
+       master_sources.push_back (&src);
+       src.GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+
+       if (set_state (node)) {
+               throw failed_constructor();
+       }
+
+       save_state ("initial state");
+
+       CheckNewRegion (this); /* EMIT SIGNAL */
+}
+
+MidiRegion::MidiRegion (SourceList& srcs, const XMLNode& node)
+       : Region (node)
+{
+       /* basic MidiRegion constructor */
+
+       set<MidiSource*> unique_srcs;
+
+       for (SourceList::iterator i=srcs.begin(); i != srcs.end(); ++i) {
+               sources.push_back (*i);
+               (*i)->GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+               unique_srcs.insert (*i);
+       }
+
+       for (SourceList::iterator i = srcs.begin(); i != srcs.end(); ++i) {
+               master_sources.push_back (*i);
+               if (unique_srcs.find (*i) == unique_srcs.end()) {
+                       (*i)->GoingAway.connect (mem_fun (*this, &MidiRegion::source_deleted));
+               }
+       }
+
+       if (set_state (node)) {
+               throw failed_constructor();
+       }
+
+       save_state ("initial state");
+
+       CheckNewRegion (this); /* EMIT SIGNAL */
+}
+
+MidiRegion::~MidiRegion ()
+{
+       GoingAway (this);
+}
+
+StateManager::State*
+MidiRegion::state_factory (std::string why) const
+{
+       MidiRegionState* state = new MidiRegionState (why);
+
+       Region::store_state (*state);
+
+       return state;
+}      
+
+Change
+MidiRegion::restore_state (StateManager::State& sstate) 
+{
+       MidiRegionState* state = dynamic_cast<MidiRegionState*> (&sstate);
+
+       Change what_changed = Region::restore_and_return_flags (*state);
+       
+       if (_flags != Flag (state->_flags)) {
+               
+               //uint32_t old_flags = _flags;
+               
+               _flags = Flag (state->_flags);
+               
+       }
+               
+       /* XXX need a way to test stored state versus current for envelopes */
+
+       what_changed = Change (what_changed);
+
+       return what_changed;
+}
+
+UndoAction
+MidiRegion::get_memento() const
+{
+       return sigc::bind (mem_fun (*(const_cast<MidiRegion *> (this)), &StateManager::use_state), _current_state_id);
+}
+
+bool
+MidiRegion::verify_length (jack_nframes_t len)
+{
+       for (uint32_t n=0; n < sources.size(); ++n) {
+               if (_start > sources[n]->length() - len) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+bool
+MidiRegion::verify_start_and_length (jack_nframes_t new_start, jack_nframes_t new_length)
+{
+       for (uint32_t n=0; n < sources.size(); ++n) {
+               if (new_length > sources[n]->length() - new_start) {
+                       return false;
+               }
+       }
+       return true;
+}
+bool
+MidiRegion::verify_start (jack_nframes_t pos)
+{
+       for (uint32_t n=0; n < sources.size(); ++n) {
+               if (pos > sources[n]->length() - _length) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+bool
+MidiRegion::verify_start_mutable (jack_nframes_t& new_start)
+{
+       for (uint32_t n=0; n < sources.size(); ++n) {
+               if (new_start > sources[n]->length() - _length) {
+                       new_start = sources[n]->length() - _length;
+               }
+       }
+       return true;
+}
+
+jack_nframes_t
+MidiRegion::read_at (unsigned char *buf, unsigned char *mixdown_buffer, char * workbuf, jack_nframes_t position, 
+                     jack_nframes_t cnt, 
+                     uint32_t chan_n, jack_nframes_t read_frames, jack_nframes_t skip_frames) const
+{
+       return _read_at (sources, buf, mixdown_buffer, workbuf, position, cnt, chan_n, read_frames, skip_frames);
+}
+
+jack_nframes_t
+MidiRegion::master_read_at (unsigned char *buf, unsigned char *mixdown_buffer, char * workbuf, jack_nframes_t position, 
+                            jack_nframes_t cnt, uint32_t chan_n) const
+{
+       return _read_at (master_sources, buf, mixdown_buffer, workbuf, position, cnt, chan_n, 0, 0);
+}
+
+jack_nframes_t
+MidiRegion::_read_at (const SourceList& srcs, unsigned char *buf, unsigned char *mixdown_buffer, char * workbuf,
+                      jack_nframes_t position, jack_nframes_t cnt, 
+                      uint32_t chan_n, jack_nframes_t read_frames, jack_nframes_t skip_frames) const
+{
+       jack_nframes_t internal_offset;
+       jack_nframes_t buf_offset;
+       jack_nframes_t to_read;
+       
+       /* precondition: caller has verified that we cover the desired section */
+
+       if (chan_n >= sources.size()) {
+               return 0; /* read nothing */
+       }
+       
+       if (position < _position) {
+               internal_offset = 0;
+               buf_offset = _position - position;
+               cnt -= buf_offset;
+       } else {
+               internal_offset = position - _position;
+               buf_offset = 0;
+       }
+
+       if (internal_offset >= _length) {
+               return 0; /* read nothing */
+       }
+       
+
+       if ((to_read = min (cnt, _length - internal_offset)) == 0) {
+               return 0; /* read nothing */
+       }
+
+       if (opaque()) {
+               /* overwrite whatever is there */
+               mixdown_buffer = buf + buf_offset;
+       } else {
+               mixdown_buffer += buf_offset;
+       }
+
+       if (muted()) {
+               return 0; /* read nothing */
+       }
+
+       _read_data_count = 0;
+
+       if (srcs[chan_n]->read (mixdown_buffer, _start + internal_offset, to_read, workbuf) != to_read) {
+               return 0; /* "read nothing" */
+       }
+
+       _read_data_count += srcs[chan_n]->read_data_count();
+
+       if (!opaque()) {
+
+               /* gack. the things we do for users.
+                */
+
+               buf += buf_offset;
+
+               for (jack_nframes_t n = 0; n < to_read; ++n) {
+                       buf[n] += mixdown_buffer[n];
+               }
+       } 
+       
+       return to_read;
+}
+       
+XMLNode&
+MidiRegion::get_state ()
+{
+       return state (true);
+}
+
+XMLNode&
+MidiRegion::state (bool full)
+{
+       XMLNode& node (Region::state (full));
+       //XMLNode *child;
+       char buf[64];
+       char buf2[64];
+       LocaleGuard lg (X_("POSIX"));
+       
+       snprintf (buf, sizeof (buf), "0x%x", (int) _flags);
+       node.add_property ("flags", buf);
+
+       for (uint32_t n=0; n < sources.size(); ++n) {
+               snprintf (buf2, sizeof(buf2), "source-%d", n);
+               snprintf (buf, sizeof(buf), "%" PRIu64, sources[n]->id());
+               node.add_property (buf2, buf);
+       }
+
+       snprintf (buf, sizeof (buf), "%u", (uint32_t) sources.size());
+       node.add_property ("channels", buf);
+
+       if (full && _extra_xml) {
+               node.add_child_copy (*_extra_xml);
+       }
+
+       return node;
+}
+
+int
+MidiRegion::set_state (const XMLNode& node)
+{
+       const XMLNodeList& nlist = node.children();
+       const XMLProperty *prop;
+       LocaleGuard lg (X_("POSIX"));
+
+       Region::set_state (node);
+
+       if ((prop = node.property ("flags")) != 0) {
+               _flags = Flag (strtol (prop->value().c_str(), (char **) 0, 16));
+
+               _flags = Flag (_flags & ~Region::LeftOfSplit);
+               _flags = Flag (_flags & ~Region::RightOfSplit);
+       }
+
+       /* Now find envelope description and other misc child items */
+                               
+       for (XMLNodeConstIterator niter = nlist.begin(); niter != nlist.end(); ++niter) {
+               
+               XMLNode *child;
+               //XMLProperty *prop;
+               
+               child = (*niter);
+       }
+
+       return 0;
+}
+
+int
+MidiRegion::separate_by_channel (Session& session, vector<MidiRegion*>& v) const
+{
+       SourceList srcs;
+       string new_name;
+
+       for (SourceList::const_iterator i = master_sources.begin(); i != master_sources.end(); ++i) {
+
+               srcs.clear ();
+               srcs.push_back (*i);
+
+               /* generate a new name */
+               
+               if (session.region_name (new_name, _name)) {
+                       return -1;
+               }
+
+               /* create a copy with just one source */
+
+               v.push_back (new MidiRegion (srcs, _start, _length, new_name, _layer, _flags));
+       }
+
+       return 0;
+}
+
+void
+MidiRegion::source_deleted (Source* ignored)
+{
+       delete this;
+}
+
+void
+MidiRegion::lock_sources ()
+{
+       SourceList::iterator i;
+       set<MidiSource*> unique_srcs;
+
+       for (i = sources.begin(); i != sources.end(); ++i) {
+               unique_srcs.insert (*i);
+               (*i)->use ();
+       }
+
+       for (i = master_sources.begin(); i != master_sources.end(); ++i) {
+               if (unique_srcs.find (*i) == unique_srcs.end()) {
+                       (*i)->use ();
+               }
+       }
+}
+
+void
+MidiRegion::unlock_sources ()
+{
+       SourceList::iterator i;
+       set<MidiSource*> unique_srcs;
+
+       for (i = sources.begin(); i != sources.end(); ++i) {
+               unique_srcs.insert (*i);
+               (*i)->release ();
+       }
+
+       for (i = master_sources.begin(); i != master_sources.end(); ++i) {
+               if (unique_srcs.find (*i) == unique_srcs.end()) {
+                       (*i)->release ();
+               }
+       }
+}
+
+vector<string>
+MidiRegion::master_source_names ()
+{
+       SourceList::iterator i;
+
+       vector<string> names;
+       for (i = master_sources.begin(); i != master_sources.end(); ++i) {
+               names.push_back((*i)->name());
+       }
+
+       return names;
+}
+
+bool
+MidiRegion::region_list_equivalent (const MidiRegion& other) const
+{
+       return size_equivalent (other) && source_equivalent (other) && _name == other._name;
+}
+
+bool
+MidiRegion::source_equivalent (const MidiRegion& other) const
+{
+       SourceList::const_iterator i;
+       SourceList::const_iterator io;
+
+       for (i = sources.begin(), io = other.sources.begin(); i != sources.end() && io != other.sources.end(); ++i, ++io) {
+               if ((*i)->id() != (*io)->id()) {
+                       return false;
+               }
+       }
+
+       for (i = master_sources.begin(), io = other.master_sources.begin(); i != master_sources.end() && io != other.master_sources.end(); ++i, ++io) {
+               if ((*i)->id() != (*io)->id()) {
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+bool
+MidiRegion::overlap_equivalent (const MidiRegion& other) const
+{
+       return coverage (other.first_frame(), other.last_frame()) != OverlapNone;
+}
+
+bool
+MidiRegion::equivalent (const MidiRegion& other) const
+{
+       return _start == other._start &&
+               _position == other._position &&
+               _length == other._length;
+}
+
+bool
+MidiRegion::size_equivalent (const MidiRegion& other) const
+{
+       return _start == other._start &&
+               _length == other._length;
+}
+
+#if 0
+int
+MidiRegion::exportme (Session& session, AudioExportSpecification& spec)
+{
+       const jack_nframes_t blocksize = 4096;
+       jack_nframes_t to_read;
+       int status = -1;
+
+       spec.channels = sources.size();
+
+       if (spec.prepare (blocksize, session.frame_rate())) {
+               goto out;
+       }
+
+       spec.pos = 0;
+       spec.total_frames = _length;
+
+       while (spec.pos < _length && !spec.stop) {
+               
+               
+               /* step 1: interleave */
+               
+               to_read = min (_length - spec.pos, blocksize);
+               
+               if (spec.channels == 1) {
+
+                       if (sources.front()->read (spec.dataF, _start + spec.pos, to_read, 0) != to_read) {
+                               goto out;
+                       }
+
+               } else {
+
+                       Sample buf[blocksize];
+
+                       for (uint32_t chan = 0; chan < spec.channels; ++chan) {
+                               
+                               if (sources[chan]->read (buf, _start + spec.pos, to_read, 0) != to_read) {
+                                       goto out;
+                               }
+                               
+                               for (jack_nframes_t x = 0; x < to_read; ++x) {
+                                       spec.dataF[chan+(x*spec.channels)] = buf[x];
+                               }
+                       }
+               }
+               
+               if (spec.process (to_read)) {
+                       goto out;
+               }
+               
+               spec.pos += to_read;
+               spec.progress = (double) spec.pos /_length;
+               
+       }
+       
+       status = 0;
+
+  out: 
+       spec.running = false;
+       spec.status = status;
+       spec.clear();
+       
+       return status;
+}
+#endif
+
+Region*
+MidiRegion::get_parent()
+{
+#if 0
+       Region* r = 0;
+
+       if (_playlist) {
+               r = _playlist->session().find_whole_file_parent (*this);
+       }
+       
+       return r;
+#endif
+       return NULL;
+}
+
+
+bool
+MidiRegion::speed_mismatch (float sr) const
+{
+#if 0
+       if (sources.empty()) {
+               /* impossible, but ... */
+               return false;
+       }
+
+       float fsr = sources.front()->sample_rate();
+
+       return fsr == sr;
+#endif
+       return false;
+}
+
diff --git a/libs/ardour/midi_source.cc b/libs/ardour/midi_source.cc
new file mode 100644 (file)
index 0000000..f9fc8dd
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+    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 <ardour/midi_source.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+
+sigc::signal<void,MidiSource *> MidiSource::MidiSourceCreated;
+
+MidiSource::MidiSource (string name)
+       : Source (name)
+{
+       _read_data_count = 0;
+       _write_data_count = 0;
+}
+
+MidiSource::MidiSource (const XMLNode& node) 
+       : Source (node)
+{
+       _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;
+}
+
+jack_nframes_t
+MidiSource::read (unsigned char *dst, jack_nframes_t start, jack_nframes_t cnt, char * workbuf) const
+{
+       //Glib::Mutex::Lock lm (_lock);
+       //return read_unlocked (dst, start, cnt, workbuf);
+       return 0;
+}
+
+jack_nframes_t
+MidiSource::write (unsigned char *dst, jack_nframes_t cnt, char * workbuf)
+{
+       //Glib::Mutex::Lock lm (_lock);
+       //return write_unlocked (dst, cnt, workbuf);
+       return 0;
+}
+
+
+bool
+MidiSource::file_changed (string path)
+{
+       struct stat stat_file;
+       //struct stat stat_peak;
+
+       int e1 = stat (path.c_str(), &stat_file);
+       //int e2 = stat (peak_path(path).c_str(), &stat_peak);
+       
+       if (!e1){//&& !e2 && stat_file.st_mtime > stat_peak.st_mtime){
+               return true;
+       } else {
+               return false;
+       }
+}
+
+
+void
+MidiSource::update_length (jack_nframes_t pos, jack_nframes_t cnt)
+{
+       if (pos + cnt > _length) {
+               _length = pos+cnt;
+       }
+}
+
diff --git a/libs/ardour/midi_track.cc b/libs/ardour/midi_track.cc
new file mode 100644 (file)
index 0000000..d4f7be1
--- /dev/null
@@ -0,0 +1,1167 @@
+/*
+    Copyright (C) 2006 Paul Davis 
+       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 <pbd/error.h>
+#include <sigc++/retype.h>
+#include <sigc++/retype_return.h>
+#include <sigc++/bind.h>
+
+#include <ardour/midi_track.h>
+#include <ardour/midi_diskstream.h>
+#include <ardour/session.h>
+#include <ardour/redirect.h>
+#include <ardour/midi_region.h>
+#include <ardour/midi_source.h>
+#include <ardour/route_group_specialized.h>
+#include <ardour/insert.h>
+#include <ardour/midi_playlist.h>
+#include <ardour/panner.h>
+#include <ardour/utils.h>
+
+#include "i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+
+MidiTrack::MidiTrack (Session& sess, string name, Route::Flag flag, TrackMode mode)
+       : Route (sess, name, 1, -1, -1, -1, flag, Buffer::MIDI),
+         diskstream (0),
+         _midi_rec_enable_control (*this, _session.midi_port())
+{
+       MidiDiskstream::Flag dflags = MidiDiskstream::Flag (0);
+
+       if (_flags & Hidden) {
+               dflags = MidiDiskstream::Flag (dflags | MidiDiskstream::Hidden);
+       } else {
+               dflags = MidiDiskstream::Flag (dflags | MidiDiskstream::Recordable);
+       }
+
+       if (mode == Destructive) {
+               dflags = MidiDiskstream::Flag (dflags | MidiDiskstream::Destructive);
+       }
+
+       MidiDiskstream* ds = new MidiDiskstream (_session, name, dflags);
+       
+       _declickable = true;
+       _freeze_record.state = NoFreeze;
+       _saved_meter_point = _meter_point;
+       _mode = mode;
+
+       set_diskstream (*ds, this);
+
+       // session.SMPTEOffsetChanged.connect (mem_fun (*this, &MidiTrack::handle_smpte_offset_change));
+
+       // we do this even though Route already did it in it's init
+       reset_midi_control (_session.midi_port(), _session.get_midi_control());
+       
+}
+
+MidiTrack::MidiTrack (Session& sess, const XMLNode& node)
+       : Route (sess, "to be renamed", 0, 0, -1, -1),
+         diskstream (0),
+         _midi_rec_enable_control (*this, _session.midi_port())
+{
+       _freeze_record.state = NoFreeze;
+       set_state (node);
+       _declickable = true;
+       _saved_meter_point = _meter_point;
+
+       // we do this even though Route already did it in it's init
+       reset_midi_control (_session.midi_port(), _session.get_midi_control());
+}
+
+MidiTrack::~MidiTrack ()
+{
+       if (diskstream) {
+               diskstream->unref();
+       }
+}
+
+#if 0
+void
+MidiTrack::handle_smpte_offset_change ()
+{
+       diskstream
+}
+#endif
+
+int
+MidiTrack::deprecated_use_diskstream_connections ()
+{
+       if (diskstream->deprecated_io_node == 0) {
+               return 0;
+       }
+
+       const XMLProperty* prop;
+       XMLNode& node (*diskstream->deprecated_io_node);
+
+       /* don't do this more than once. */
+
+       diskstream->deprecated_io_node = 0;
+
+       set_input_minimum (-1);
+       set_input_maximum (-1);
+       set_output_minimum (-1);
+       set_output_maximum (-1);
+       
+       if ((prop = node.property ("gain")) != 0) {
+               set_gain (atof (prop->value().c_str()), this);
+               _gain = _desired_gain;
+       }
+
+       if ((prop = node.property ("input-connection")) != 0) {
+               Connection* c = _session.connection_by_name (prop->value());
+               
+               if (c == 0) {
+                       PBD::error << string_compose(_("Unknown connection \"%1\" listed for input of %2"), prop->value(), _name) << endmsg;
+                       
+                       if ((c = _session.connection_by_name (_("in 1"))) == 0) {
+                               PBD::error << _("No input connections available as a replacement")
+                               << endmsg;
+                               return -1;
+                       } else {
+                               PBD::info << string_compose (_("Connection %1 was not available - \"in 1\" used instead"), prop->value())
+                              << endmsg;
+                       }
+               }
+
+               use_input_connection (*c, this);
+
+       } else if ((prop = node.property ("inputs")) != 0) {
+               if (set_inputs (prop->value())) {
+                       PBD::error << string_compose(_("improper input channel list in XML node (%1)"), prop->value()) << endmsg;
+                       return -1;
+               }
+       }
+       
+       return 0;
+}
+
+int
+MidiTrack::set_diskstream (MidiDiskstream& ds, void *src)
+{
+       if (diskstream) {
+               diskstream->unref();
+       }
+
+       diskstream = &ds.ref();
+       diskstream->set_io (*this);
+       diskstream->set_destructive (_mode == Destructive);
+
+       if (diskstream->deprecated_io_node) {
+
+               if (!connecting_legal) {
+                       ConnectingLegal.connect (mem_fun (*this, &MidiTrack::deprecated_use_diskstream_connections));
+               } else {
+                       deprecated_use_diskstream_connections ();
+               }
+       }
+
+       diskstream->set_record_enabled (false, this);
+       //diskstream->monitor_input (false);
+
+       ic_connection.disconnect();
+       ic_connection = input_changed.connect (mem_fun (*diskstream, &MidiDiskstream::handle_input_change));
+
+       diskstream_changed (src); /* EMIT SIGNAL */
+
+       return 0;
+}      
+
+int 
+MidiTrack::use_diskstream (string name)
+{
+       /*
+       MidiDiskstream *dstream;
+
+       if ((dstream = _session.diskstream_by_name (name)) == 0) {
+         PBD::error << string_compose(_("MidiTrack: diskstream \"%1\" not known by session"), name) << endmsg;
+               return -1;
+       }
+
+       return set_diskstream (*dstream, this);
+       */
+       return 0;
+}
+
+int 
+MidiTrack::use_diskstream (id_t id)
+{
+       /*
+       MidiDiskstream *dstream;
+
+       if ((dstream = _session.diskstream_by_id (id)) == 0) {
+               PBD::error << string_compose(_("MidiTrack: diskstream \"%1\" not known by session"), id) << endmsg;
+               return -1;
+       }
+       
+       return set_diskstream (*dstream, this);
+       */
+       return 0;
+}
+
+bool
+MidiTrack::record_enabled () const
+{
+       return diskstream->record_enabled ();
+}
+
+void
+MidiTrack::set_record_enable (bool yn, void *src)
+{
+       if (_freeze_record.state == Frozen) {
+               return;
+       }
+#if 0
+       if (_mix_group && src != _mix_group && _mix_group->is_active()) {
+               _mix_group->apply (&MidiTrack::set_record_enable, yn, _mix_group);
+               return;
+       }
+
+       /* keep track of the meter point as it was before we rec-enabled */
+
+       if (!diskstream->record_enabled()) {
+               _saved_meter_point = _meter_point;
+       }
+       
+       diskstream->set_record_enabled (yn, src);
+
+       if (diskstream->record_enabled()) {
+               set_meter_point (MeterInput, this);
+       } else {
+               set_meter_point (_saved_meter_point, this);
+       }
+
+       if (_session.get_midi_feedback()) {
+               _midi_rec_enable_control.send_feedback (record_enabled());
+       }
+#endif
+}
+
+void
+MidiTrack::set_meter_point (MeterPoint p, void *src)
+{
+       Route::set_meter_point (p, src);
+}
+
+int
+MidiTrack::set_state (const XMLNode& node)
+{
+       const XMLProperty *prop;
+       XMLNodeConstIterator iter;
+       XMLNodeList midi_kids;
+
+       if (Route::set_state (node)) {
+               return -1;
+       }
+
+       if ((prop = node.property (X_("mode"))) != 0) {
+               if (prop->value() == X_("normal")) {
+                       _mode = Normal;
+               } else if (prop->value() == X_("destructive")) {
+                       _mode = Destructive;
+               } else {
+                       PBD::warning << string_compose ("unknown midi track mode \"%1\" seen and ignored", prop->value()) << endmsg;
+                       _mode = Normal;
+               }
+       } else {
+               _mode = Normal;
+       }
+
+       midi_kids = node.children ("MIDI");
+       
+       for (iter = midi_kids.begin(); iter != midi_kids.end(); ++iter) {
+       
+               XMLNodeList kids;
+               XMLNodeConstIterator miter;
+               XMLNode*    child;
+
+               kids = (*iter)->children ();
+
+               for (miter = kids.begin(); miter != kids.end(); ++miter) {
+
+                       child =* miter;
+
+                       if (child->name() == "rec_enable") {
+                       
+                               MIDI::eventType ev = MIDI::on; /* initialize to keep gcc happy */
+                               MIDI::byte additional = 0;  /* ditto */
+                               MIDI::channel_t chn = 0;    /* ditto */
+
+                               if (get_midi_node_info (child, ev, chn, additional)) {
+                                       _midi_rec_enable_control.set_control_type (chn, ev, additional);
+                               } else {
+                                 PBD::error << string_compose(_("MIDI rec_enable control specification for %1 is incomplete, so it has been ignored"), _name) << endmsg;
+                               }
+                       }
+               }
+       }
+
+       
+       if ((prop = node.property ("diskstream-id")) == 0) {
+               
+               /* some old sessions use the diskstream name rather than the ID */
+
+               if ((prop = node.property ("diskstream")) == 0) {
+                       PBD::fatal << _("programming error: MidiTrack given state without diskstream!") << endmsg;
+                       /*NOTREACHED*/
+                       return -1;
+               }
+
+               if (use_diskstream (prop->value())) {
+                       return -1;
+               }
+
+       } else {
+               
+               id_t id = strtoull (prop->value().c_str(), 0, 10);
+               
+               if (use_diskstream (id)) {
+                       return -1;
+               }
+       }
+
+
+       XMLNodeList nlist;
+       XMLNodeConstIterator niter;
+       XMLNode *child;
+
+       nlist = node.children();
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter){
+               child = *niter;
+
+               if (child->name() == X_("remote_control")) {
+                       if ((prop = child->property (X_("id"))) != 0) {
+                               int32_t x;
+                               sscanf (prop->value().c_str(), "%d", &x);
+                               set_remote_control_id (x);
+                       }
+               }
+       }
+
+       pending_state = const_cast<XMLNode*> (&node);
+
+       _session.StateReady.connect (mem_fun (*this, &MidiTrack::set_state_part_two));
+
+       return 0;
+}
+
+XMLNode&
+MidiTrack::get_template ()
+{
+       return state (false);
+}
+
+XMLNode&
+MidiTrack::get_state ()
+{
+       return state (true);
+}
+
+XMLNode& 
+MidiTrack::state(bool full_state)
+{
+       XMLNode& root (Route::state(full_state));
+       XMLNode* freeze_node;
+       char buf[32];
+
+       if (_freeze_record.playlist) {
+               XMLNode* inode;
+
+               freeze_node = new XMLNode (X_("freeze-info"));
+               freeze_node->add_property ("playlist", _freeze_record.playlist->name());
+               snprintf (buf, sizeof (buf), "%d", (int) _freeze_record.state);
+               freeze_node->add_property ("state", buf);
+
+               for (vector<FreezeRecordInsertInfo*>::iterator i = _freeze_record.insert_info.begin(); i != _freeze_record.insert_info.end(); ++i) {
+                       inode = new XMLNode (X_("insert"));
+                       snprintf (buf, sizeof (buf), "%" PRIu64, (*i)->id);
+                       inode->add_property (X_("id"), buf);
+                       inode->add_child_copy ((*i)->state);
+               
+                       freeze_node->add_child_nocopy (*inode);
+               }
+
+               root.add_child_nocopy (*freeze_node);
+       }
+
+       /* Alignment: act as a proxy for the diskstream */
+       
+       XMLNode* align_node = new XMLNode (X_("alignment"));
+       switch (diskstream->alignment_style()) {
+       case ExistingMaterial:
+               snprintf (buf, sizeof (buf), X_("existing"));
+               break;
+       case CaptureTime:
+               snprintf (buf, sizeof (buf), X_("capture"));
+               break;
+       }
+       align_node->add_property (X_("style"), buf);
+       root.add_child_nocopy (*align_node);
+
+       /* MIDI control */
+
+       MIDI::channel_t chn;
+       MIDI::eventType ev;
+       MIDI::byte      additional;
+       XMLNode*        midi_node = 0;
+       XMLNode*        child;
+       XMLNodeList     midikids;
+
+       midikids = root.children ("MIDI");
+       if (!midikids.empty()) {
+               midi_node = midikids.front();
+       }
+       else {
+               midi_node = root.add_child ("MIDI");
+       }
+               
+       if (_midi_rec_enable_control.get_control_info (chn, ev, additional) && midi_node) {
+
+               child = midi_node->add_child ("rec_enable");
+               set_midi_node_info (child, ev, chn, additional);
+       }
+
+       XMLNode* remote_control_node = new XMLNode (X_("remote_control"));
+       snprintf (buf, sizeof (buf), "%d", _remote_control_id);
+       remote_control_node->add_property (X_("id"), buf);
+       root.add_child_nocopy (*remote_control_node);
+
+       switch (_mode) {
+       case Normal:
+               root.add_property (X_("mode"), X_("normal"));
+               break;
+       case Destructive:
+               root.add_property (X_("mode"), X_("destructive"));
+               break;
+       }
+
+       /* we don't return diskstream state because we don't
+          own the diskstream exclusively. control of the diskstream
+          state is ceded to the Session, even if we create the
+          diskstream.
+       */
+
+       snprintf (buf, sizeof (buf), "%" PRIu64, diskstream->id());
+       root.add_property ("diskstream-id", buf);
+
+       return root;
+}
+
+void
+MidiTrack::set_state_part_two ()
+{
+       XMLNode* fnode;
+       XMLProperty* prop;
+       LocaleGuard lg (X_("POSIX"));
+
+       /* This is called after all session state has been restored but before
+          have been made ports and connections are established.
+       */
+
+       if (pending_state == 0) {
+               return;
+       }
+
+       if ((fnode = find_named_node (*pending_state, X_("freeze-info"))) != 0) {
+
+               
+               _freeze_record.have_mementos = false;
+               _freeze_record.state = Frozen;
+               
+               for (vector<FreezeRecordInsertInfo*>::iterator i = _freeze_record.insert_info.begin(); i != _freeze_record.insert_info.end(); ++i) {
+                       delete *i;
+               }
+               _freeze_record.insert_info.clear ();
+               
+               if ((prop = fnode->property (X_("playlist"))) != 0) {
+                       Playlist* pl = _session.playlist_by_name (prop->value());
+                       if (pl) {
+                               _freeze_record.playlist = dynamic_cast<MidiPlaylist*> (pl);
+                       } else {
+                               _freeze_record.playlist = 0;
+                               _freeze_record.state = NoFreeze;
+                       return;
+                       }
+               }
+               
+               if ((prop = fnode->property (X_("state"))) != 0) {
+                       _freeze_record.state = (FreezeState) atoi (prop->value().c_str());
+               }
+               
+               XMLNodeConstIterator citer;
+               XMLNodeList clist = fnode->children();
+               
+               for (citer = clist.begin(); citer != clist.end(); ++citer) {
+                       if ((*citer)->name() != X_("insert")) {
+                               continue;
+                       }
+                       
+                       if ((prop = (*citer)->property (X_("id"))) == 0) {
+                               continue;
+                       }
+                       
+                       FreezeRecordInsertInfo* frii = new FreezeRecordInsertInfo (*((*citer)->children().front()));
+                       frii->insert = 0;
+                       sscanf (prop->value().c_str(), "%" PRIu64, &frii->id);
+                       _freeze_record.insert_info.push_back (frii);
+               }
+       }
+
+       /* Alignment: act as a proxy for the diskstream */
+
+       if ((fnode = find_named_node (*pending_state, X_("alignment"))) != 0) {
+
+               if ((prop = fnode->property (X_("style"))) != 0) {
+                       if (prop->value() == "existing") {
+                               diskstream->set_persistent_align_style (ExistingMaterial);
+                       } else if (prop->value() == "capture") {
+                               diskstream->set_persistent_align_style (CaptureTime);
+                       }
+               }
+       }
+       return;
+}      
+
+uint32_t
+MidiTrack::n_process_buffers ()
+{
+       return max ((uint32_t) diskstream->n_channels(), redirect_max_outs);
+}
+
+void
+MidiTrack::passthru_silence (jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t nframes, jack_nframes_t offset, int declick, bool meter)
+{
+       uint32_t nbufs = n_process_buffers ();
+       process_output_buffers (_session.get_silent_buffers (nbufs), nbufs, start_frame, end_frame, nframes, offset, true, declick, meter);
+}
+
+int 
+MidiTrack::no_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset, 
+                    bool session_state_changing, bool can_record, bool rec_monitors_input)
+{
+       if (n_outputs() == 0) {
+               return 0;
+       }
+
+       if (!_active) {
+               silence (nframes, offset);
+               return 0;
+       }
+
+       if (session_state_changing) {
+
+               /* XXX is this safe to do against transport state changes? */
+
+               passthru_silence (start_frame, end_frame, nframes, offset, 0, false);
+               return 0;
+       }
+
+       diskstream->check_record_status (start_frame, nframes, can_record);
+
+       bool send_silence;
+       
+       if (_have_internal_generator) {
+               /* since the instrument has no input streams,
+                  there is no reason to send any signal
+                  into the route.
+               */
+               send_silence = true;
+       } else {
+
+               if (_session.get_auto_input()) {
+                       if (Config->get_use_sw_monitoring()) {
+                               send_silence = false;
+                       } else {
+                               send_silence = true;
+                       }
+               } else {
+                       if (diskstream->record_enabled()) {
+                               if (Config->get_use_sw_monitoring()) {
+                                       send_silence = false;
+                               } else {
+                                       send_silence = true;
+                               }
+                       } else {
+                               send_silence = true;
+                       }
+               }
+       }
+
+       apply_gain_automation = false;
+
+       if (send_silence) {
+               
+               /* if we're sending silence, but we want the meters to show levels for the signal,
+                  meter right here.
+               */
+               
+               if (_have_internal_generator) {
+                       passthru_silence (start_frame, end_frame, nframes, offset, 0, true);
+               } else {
+                       if (_meter_point == MeterInput) {
+                               just_meter_input (start_frame, end_frame, nframes, offset);
+                       }
+                       passthru_silence (start_frame, end_frame, nframes, offset, 0, false);
+               }
+
+       } else {
+       
+               /* we're sending signal, but we may still want to meter the input. 
+                */
+
+               passthru (start_frame, end_frame, nframes, offset, 0, (_meter_point == MeterInput));
+       }
+
+       return 0;
+}
+
+int
+MidiTrack::roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset, int declick,
+                 bool can_record, bool rec_monitors_input)
+{
+#if 0
+       int dret;
+       Sample* b;
+       Sample* tmpb;
+       jack_nframes_t transport_frame;
+
+       {
+               Glib::RWLock::ReaderLock lm (redirect_lock, Glib::TRY_LOCK);
+               if (lm.locked()) {
+                       // automation snapshot can also be called from the non-rt context
+                       // and it uses the redirect list, so we take the lock out here
+                       automation_snapshot (start_frame);
+               }
+       }
+       
+       if (n_outputs() == 0 && _redirects.empty()) {
+               return 0;
+       }
+
+       if (!_active) {
+               silence (nframes, offset);
+               return 0;
+       }
+
+       transport_frame = _session.transport_frame();
+
+       if ((nframes = check_initial_delay (nframes, offset, transport_frame)) == 0) {
+               /* need to do this so that the diskstream sets its
+                  playback distance to zero, thus causing diskstream::commit
+                  to do nothing.
+               */
+               return diskstream->process (transport_frame, 0, 0, can_record, rec_monitors_input);
+       } 
+
+       _silent = false;
+       apply_gain_automation = false;
+
+       if ((dret = diskstream->process (transport_frame, nframes, offset, can_record, rec_monitors_input)) != 0) {
+               
+               silence (nframes, offset);
+
+               return dret;
+       }
+
+       /* special condition applies */
+       
+       if (_meter_point == MeterInput) {
+               just_meter_input (start_frame, end_frame, nframes, offset);
+       }
+
+       if (diskstream->record_enabled() && !can_record && !_session.get_auto_input()) {
+
+               /* not actually recording, but we want to hear the input material anyway,
+                  at least potentially (depending on monitoring options)
+                */
+
+               passthru (start_frame, end_frame, nframes, offset, 0, true);
+
+       } else if ((b = diskstream->playback_buffer(0)) != 0) {
+
+               /*
+                 XXX is it true that the earlier test on n_outputs()
+                 means that we can avoid checking it again here? i think
+                 so, because changing the i/o configuration of an IO
+                 requires holding the AudioEngine lock, which we hold
+                 while in the process() tree.
+               */
+
+               
+               /* copy the diskstream data to all output buffers */
+               
+               vector<Sample*>& bufs = _session.get_passthru_buffers ();
+               uint32_t limit = n_process_buffers ();
+               
+               uint32_t n;
+               uint32_t i;
+
+
+               for (i = 0, n = 1; i < limit; ++i, ++n) {
+                       memcpy (bufs[i], b, sizeof (Sample) * nframes); 
+                       if (n < diskstream->n_channels()) {
+                               tmpb = diskstream->playback_buffer(n);
+                               if (tmpb!=0) {
+                                       b = tmpb;
+                               }
+                       }
+               }
+
+               /* don't waste time with automation if we're recording or we've just stopped (yes it can happen) */
+
+               if (!diskstream->record_enabled() && _session.transport_rolling()) {
+                       Glib::Mutex::Lock am (automation_lock, Glib::TRY_LOCK);
+                       
+                       if (am.locked() && gain_automation_playback()) {
+                               apply_gain_automation = _gain_automation_curve.rt_safe_get_vector (start_frame, end_frame, _session.gain_automation_buffer(), nframes);
+                       }
+               }
+
+               process_output_buffers (bufs, limit, start_frame, end_frame, nframes, offset, (!_session.get_record_enabled() || !_session.get_do_not_record_plugins()), declick, (_meter_point != MeterInput));
+               
+       } else {
+               /* problem with the diskstream; just be quiet for a bit */
+               silence (nframes, offset);
+       }
+#endif
+       return 0;
+}
+
+int
+MidiTrack::silent_roll (jack_nframes_t nframes, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t offset, 
+                        bool can_record, bool rec_monitors_input)
+{
+       if (n_outputs() == 0 && _redirects.empty()) {
+               return 0;
+       }
+
+       if (!_active) {
+               silence (nframes, offset);
+               return 0;
+       }
+
+       _silent = true;
+       apply_gain_automation = false;
+
+       silence (nframes, offset);
+
+       return diskstream->process (_session.transport_frame() + offset, nframes, offset, can_record, rec_monitors_input);
+}
+
+void
+MidiTrack::toggle_monitor_input ()
+{
+       for (vector<Port*>::iterator i = _inputs.begin(); i != _inputs.end(); ++i) {
+               (*i)->request_monitor_input(!(*i)->monitoring_input());
+       }
+}
+
+int
+MidiTrack::set_name (string str, void *src)
+{
+       int ret;
+
+       if (record_enabled() && _session.actively_recording()) {
+               /* this messes things up if done while recording */
+               return -1;
+       }
+
+       if (diskstream->set_name (str, src)) {
+               return -1;
+       }
+
+       /* save state so that the statefile fully reflects any filename changes */
+
+       if ((ret = IO::set_name (str, src)) == 0) {
+               _session.save_state ("");
+       }
+       return ret;
+}
+
+int
+MidiTrack::export_stuff (vector<unsigned char*>& buffers, char * workbuf, uint32_t nbufs, jack_nframes_t start, jack_nframes_t nframes)
+{
+#if 0
+       gain_t  gain_automation[nframes];
+       gain_t  gain_buffer[nframes];
+       float   mix_buffer[nframes];
+       RedirectList::iterator i;
+       bool post_fader_work = false;
+       gain_t this_gain = _gain;
+       vector<Sample*>::iterator bi;
+       Sample * b;
+       
+       Glib::RWLock::ReaderLock rlock (redirect_lock);
+               
+       if (diskstream->playlist()->read (buffers[0], mix_buffer, gain_buffer, workbuf, start, nframes) != nframes) {
+               return -1;
+       }
+
+       uint32_t n=1;
+       bi = buffers.begin();
+       b = buffers[0];
+       ++bi;
+       for (; bi != buffers.end(); ++bi, ++n) {
+               if (n < diskstream->n_channels()) {
+                       if (diskstream->playlist()->read ((*bi), mix_buffer, gain_buffer, workbuf, start, nframes, n) != nframes) {
+                               return -1;
+                       }
+                       b = (*bi);
+               }
+               else {
+                       /* duplicate last across remaining buffers */
+                       memcpy ((*bi), b, sizeof (Sample) * nframes); 
+               }
+       }
+
+
+       /* note: only run inserts during export. other layers in the machinery
+          will already have checked that there are no external port inserts.
+       */
+       
+       for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+               Insert *insert;
+               
+               if ((insert = dynamic_cast<Insert*>(*i)) != 0) {
+                       switch (insert->placement()) {
+                       case PreFader:
+                               insert->run (buffers, nbufs, nframes, 0);
+                               break;
+                       case PostFader:
+                               post_fader_work = true;
+                               break;
+                       }
+               }
+       }
+       
+       if (_gain_automation_curve.automation_state() == Play) {
+               
+               _gain_automation_curve.get_vector (start, start + nframes, gain_automation, nframes);
+
+               for (bi = buffers.begin(); bi != buffers.end(); ++bi) {
+                       Sample *b = *bi;
+                       for (jack_nframes_t n = 0; n < nframes; ++n) {
+                               b[n] *= gain_automation[n];
+                       }
+               }
+
+       } else {
+
+               for (bi = buffers.begin(); bi != buffers.end(); ++bi) {
+                       Sample *b = *bi;
+                       for (jack_nframes_t n = 0; n < nframes; ++n) {
+                               b[n] *= this_gain;
+                       }
+               }
+       }
+
+       if (post_fader_work) {
+
+               for (i = _redirects.begin(); i != _redirects.end(); ++i) {
+                       PluginInsert *insert;
+                       
+                       if ((insert = dynamic_cast<PluginInsert*>(*i)) != 0) {
+                               switch ((*i)->placement()) {
+                               case PreFader:
+                                       break;
+                               case PostFader:
+                                       insert->run (buffers, nbufs, nframes, 0);
+                                       break;
+                               }
+                       }
+               }
+       } 
+#endif
+       return 0;
+}
+
+void
+MidiTrack::set_latency_delay (jack_nframes_t longest_session_latency)
+{
+       Route::set_latency_delay (longest_session_latency);
+       diskstream->set_roll_delay (_roll_delay);
+}
+
+jack_nframes_t
+MidiTrack::update_total_latency ()
+{
+       _own_latency = 0;
+
+       for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+               if ((*i)->active ()) {
+                       _own_latency += (*i)->latency ();
+               }
+       }
+
+       set_port_latency (_own_latency);
+
+       return _own_latency;
+}
+
+void
+MidiTrack::bounce (InterThreadInfo& itt)
+{
+       //vector<MidiSource*> srcs;
+       //_session.write_one_midi_track (*this, 0, _session.current_end_frame(), false, srcs, itt);
+}
+
+
+void
+MidiTrack::bounce_range (jack_nframes_t start, jack_nframes_t end, InterThreadInfo& itt)
+{
+       //vector<MidiSource*> srcs;
+       //_session.write_one_midi_track (*this, start, end, false, srcs, itt);
+}
+
+void
+MidiTrack::freeze (InterThreadInfo& itt)
+{
+#if 0
+       Insert* insert;
+       vector<MidiSource*> srcs;
+       string new_playlist_name;
+       Playlist* new_playlist;
+       string dir;
+       AudioRegion* region;
+       string region_name;
+       
+       if ((_freeze_record.playlist = diskstream->playlist()) == 0) {
+               return;
+       }
+
+       uint32_t n = 1;
+
+       while (n < (UINT_MAX-1)) {
+        
+               string candidate;
+               
+               candidate = string_compose ("<F%2>%1", _freeze_record.playlist->name(), n);
+
+               if (_session.playlist_by_name (candidate) == 0) {
+                       new_playlist_name = candidate;
+                       break;
+               }
+
+               ++n;
+
+       } 
+
+       if (n == (UINT_MAX-1)) {
+         PBD::error << string_compose (X_("There Are too many frozen versions of playlist \"%1\""
+                           " to create another one"), _freeze_record.playlist->name())
+              << endmsg;
+               return;
+       }
+
+       if (_session.write_one_midi_track (*this, 0, _session.current_end_frame(), true, srcs, itt)) {
+               return;
+       }
+
+       _freeze_record.insert_info.clear ();
+       _freeze_record.have_mementos = true;
+
+       {
+               Glib::RWLock::ReaderLock lm (redirect_lock);
+               
+               for (RedirectList::iterator r = _redirects.begin(); r != _redirects.end(); ++r) {
+                       
+                       if ((insert = dynamic_cast<Insert*>(*r)) != 0) {
+                               
+                               FreezeRecordInsertInfo* frii  = new FreezeRecordInsertInfo ((*r)->get_state());
+                               
+                               frii->insert = insert;
+                               frii->id = insert->id();
+                               frii->memento = (*r)->get_memento();
+                               
+                               _freeze_record.insert_info.push_back (frii);
+                               
+                               /* now deactivate the insert */
+                               
+                               insert->set_active (false, this);
+                       }
+               }
+       }
+
+       new_playlist = new MidiPlaylist (_session, new_playlist_name, false);
+       region_name = new_playlist_name;
+
+       /* create a new region from all filesources, keep it private */
+
+       region = new AudioRegion (srcs, 0, srcs[0]->length(), 
+                                 region_name, 0, 
+                                 (AudioRegion::Flag) (AudioRegion::WholeFile|AudioRegion::DefaultFlags),
+                                 false);
+
+       new_playlist->set_orig_diskstream_id (diskstream->id());
+       new_playlist->add_region (*region, 0);
+       new_playlist->set_frozen (true);
+       region->set_locked (true);
+
+       diskstream->use_playlist (dynamic_cast<MidiPlaylist*>(new_playlist));
+       diskstream->set_record_enabled (false, this);
+
+       _freeze_record.state = Frozen;
+       FreezeChange(); /* EMIT SIGNAL */
+}
+
+void
+MidiTrack::unfreeze ()
+{
+       if (_freeze_record.playlist) {
+               diskstream->use_playlist (_freeze_record.playlist);
+
+               if (_freeze_record.have_mementos) {
+
+                       for (vector<FreezeRecordInsertInfo*>::iterator i = _freeze_record.insert_info.begin(); i != _freeze_record.insert_info.end(); ++i) {
+                               (*i)->memento ();
+                       }
+
+               } else {
+
+                       Glib::RWLock::ReaderLock lm (redirect_lock); // should this be a write lock? jlc
+                       for (RedirectList::iterator i = _redirects.begin(); i != _redirects.end(); ++i) {
+                               for (vector<FreezeRecordInsertInfo*>::iterator ii = _freeze_record.insert_info.begin(); ii != _freeze_record.insert_info.end(); ++ii) {
+                                       if ((*ii)->id == (*i)->id()) {
+                                               (*i)->set_state (((*ii)->state));
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               
+               _freeze_record.playlist = 0;
+       }
+#endif
+       _freeze_record.state = UnFrozen;
+       FreezeChange (); /* EMIT SIGNAL */
+}
+
+MidiTrack::FreezeRecord::~FreezeRecord ()
+{
+       for (vector<FreezeRecordInsertInfo*>::iterator i = insert_info.begin(); i != insert_info.end(); ++i) {
+               delete *i;
+       }
+}
+
+MidiTrack::FreezeState
+MidiTrack::freeze_state() const
+{
+       return _freeze_record.state;
+}
+
+
+void
+MidiTrack::reset_midi_control (MIDI::Port* port, bool on)
+{
+       MIDI::channel_t chn;
+       MIDI::eventType ev;
+       MIDI::byte extra;
+
+       Route::reset_midi_control (port, on);
+       
+       _midi_rec_enable_control.get_control_info (chn, ev, extra);
+       if (!on) {
+               chn = -1;
+       }
+       _midi_rec_enable_control.midi_rebind (port, chn);
+}
+
+void
+MidiTrack::send_all_midi_feedback ()
+{
+       if (_session.get_midi_feedback()) {
+
+               Route::send_all_midi_feedback();
+
+               _midi_rec_enable_control.send_feedback (record_enabled());
+       }
+}
+
+
+MidiTrack::MIDIRecEnableControl::MIDIRecEnableControl (MidiTrack& s,  MIDI::Port* port)
+       : MIDI::Controllable (port, 0), track (s), setting(false)
+{
+       last_written = false; /* XXX need a good out of bound value */
+}
+
+void
+MidiTrack::MIDIRecEnableControl::set_value (float val)
+{
+       bool bval = ((val >= 0.5f) ? true: false);
+       
+       setting = true;
+       track.set_record_enable (bval, this);
+       setting = false;
+}
+
+void
+MidiTrack::MIDIRecEnableControl::send_feedback (bool value)
+{
+
+       if (!setting && get_midi_feedback()) {
+               MIDI::byte val = (MIDI::byte) (value ? 127: 0);
+               MIDI::channel_t ch = 0;
+               MIDI::eventType ev = MIDI::none;
+               MIDI::byte additional = 0;
+               MIDI::EventTwoBytes data;
+           
+               if (get_control_info (ch, ev, additional)) {
+                       data.controller_number = additional;
+                       data.value = val;
+
+                       track._session.send_midi_message (get_port(), ev, ch, data);
+               }
+       }
+       
+}
+
+MIDI::byte*
+MidiTrack::MIDIRecEnableControl::write_feedback (MIDI::byte* buf, int32_t& bufsize, bool val, bool force)
+{
+       if (get_midi_feedback()) {
+
+               MIDI::channel_t ch = 0;
+               MIDI::eventType ev = MIDI::none;
+               MIDI::byte additional = 0;
+
+               if (get_control_info (ch, ev, additional)) {
+                       if (val != last_written || force) {
+                               *buf++ = ev & ch;
+                               *buf++ = additional; /* controller number */
+                               *buf++ = (MIDI::byte) (val ? 127: 0);
+                               last_written = val;
+                               bufsize -= 3;
+                       }
+               }
+       }
+
+       return buf;
+}
+
+void
+MidiTrack::set_mode (TrackMode m)
+{
+       if (diskstream) {
+               if (_mode != m) {
+                       _mode = m;
+                       diskstream->set_destructive (m == Destructive);
+                       ModeChanged();
+               }
+       }
+}
diff --git a/libs/ardour/smf_source.cc b/libs/ardour/smf_source.cc
new file mode 100644 (file)
index 0000000..10cf7ab
--- /dev/null
@@ -0,0 +1,406 @@
+/*
+    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 <vector>
+
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <pbd/mountpoint.h>
+#include <pbd/pathscanner.h>
+#include <pbd/stl_delete.h>
+#include <pbd/strsplit.h>
+
+#include <glibmm/miscutils.h>
+
+#include <ardour/smf_source.h>
+#include <ardour/session.h>
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+string SMFSource::_search_path;
+
+/*sigc::signal<void,struct tm*, time_t> SMFSource::HeaderPositionOffsetChanged;
+bool                                  SMFSource::header_position_negative;
+uint64_t                              SMFSource::header_position_offset;
+*/
+
+SMFSource::SMFSource (std::string path, Flag flags)
+       : MidiSource (path), _flags (flags)
+{
+       /* constructor used for new internal-to-session files. file cannot exist */
+
+       if (init (path, false)) {
+               throw failed_constructor ();
+       }
+}
+
+SMFSource::SMFSource (const XMLNode& node)
+       : MidiSource (node), _flags (Flag (Writable|CanRename))
+{
+       /* constructor used for existing internal-to-session files. file must exist */
+
+       if (set_state (node)) {
+               throw failed_constructor ();
+       }
+       
+       if (init (_name, true)) {
+               throw failed_constructor ();
+       }
+}
+
+SMFSource::~SMFSource ()
+{
+       if (removable()) {
+               unlink (_path.c_str());
+       }
+}
+
+bool
+SMFSource::removable () const
+{
+       return (_flags & Removable) && ((_flags & RemoveAtDestroy) || 
+                                     ((_flags & RemovableIfEmpty) && is_empty (_path)));
+}
+
+int
+SMFSource::init (string pathstr, bool must_exist)
+{
+       bool is_new = false;
+
+       _length = 0;
+       
+       if (!find (pathstr, must_exist, is_new)) {
+               cerr << "cannot find " << pathstr << " with me = " << must_exist << endl;
+               return -1;
+       }
+
+       if (is_new && must_exist) {
+               return -1;
+       }
+
+       return 0;
+}
+
+
+XMLNode&
+SMFSource::get_state ()
+{
+       XMLNode& root (MidiSource::get_state());
+       char buf[16];
+       snprintf (buf, sizeof (buf), "0x%x", (int)_flags);
+       root.add_property ("flags", buf);
+       return root;
+}
+
+int
+SMFSource::set_state (const XMLNode& node)
+{
+       const XMLProperty* prop;
+
+       if (MidiSource::set_state (node)) {
+               return -1;
+       }
+
+       if ((prop = node.property (X_("flags"))) != 0) {
+
+               int ival;
+               sscanf (prop->value().c_str(), "0x%x", &ival);
+               _flags = Flag (ival);
+
+       } else {
+
+               _flags = Flag (0);
+
+       }
+
+       return 0;
+}
+
+void
+SMFSource::mark_for_remove ()
+{
+       if (!writable()) {
+               return;
+       }
+       _flags = Flag (_flags | RemoveAtDestroy);
+}
+
+void
+SMFSource::mark_streaming_write_completed ()
+{
+       if (!writable()) {
+               return;
+       }
+#if 0
+       Glib::Mutex::Lock lm (_lock);
+
+
+       next_peak_clear_should_notify = true;
+
+       if (_peaks_built || pending_peak_builds.empty()) {
+               _peaks_built = true;
+                PeaksReady (); /* EMIT SIGNAL */
+       }
+#endif
+}
+
+void
+SMFSource::mark_take (string id)
+{
+       if (writable()) {
+               _take_id = id;
+       }
+}
+
+int
+SMFSource::move_to_trash (const string trash_dir_name)
+{
+       string newpath;
+
+       if (!writable()) {
+               return -1;
+       }
+
+       /* don't move the file across filesystems, just
+          stick it in the `trash_dir_name' directory
+          on whichever filesystem it was already on.
+       */
+
+       newpath = Glib::path_get_dirname (_path);
+       newpath = Glib::path_get_dirname (newpath);
+
+       newpath += '/';
+       newpath += trash_dir_name;
+       newpath += '/';
+       newpath += Glib::path_get_basename (_path);
+
+       if (access (newpath.c_str(), F_OK) == 0) {
+
+               /* the new path already exists, try versioning */
+               
+               char buf[PATH_MAX+1];
+               int version = 1;
+               string newpath_v;
+
+               snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), version);
+               newpath_v = buf;
+
+               while (access (newpath_v.c_str(), F_OK) == 0 && version < 999) {
+                       snprintf (buf, sizeof (buf), "%s.%d", newpath.c_str(), ++version);
+                       newpath_v = buf;
+               }
+               
+               if (version == 999) {
+                       PBD::error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
+                                         newpath)
+                             << endmsg;
+               } else {
+                       newpath = newpath_v;
+               }
+
+       } else {
+
+               /* it doesn't exist, or we can't read it or something */
+
+       }
+
+       if (::rename (_path.c_str(), newpath.c_str()) != 0) {
+               PBD::error << string_compose (_("cannot rename audio file source from %1 to %2 (%3)"),
+                                 _path, newpath, strerror (errno))
+                     << endmsg;
+               return -1;
+       }
+#if 0
+       if (::unlink (peakpath.c_str()) != 0) {
+               PBD::error << string_compose (_("cannot remove peakfile %1 for %2 (%3)"),
+                                 peakpath, _path, strerror (errno))
+                     << endmsg;
+               /* try to back out */
+               rename (newpath.c_str(), _path.c_str());
+               return -1;
+       }
+           
+       _path = newpath;
+       peakpath = "";
+#endif 
+       /* file can not be removed twice, since the operation is not idempotent */
+
+       _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
+
+       return 0;
+}
+
+// FIXME: Merge this with audiofilesource somehow (make a generic filesource?)
+bool
+SMFSource::find (string pathstr, bool must_exist, bool& isnew)
+{
+       string::size_type pos;
+       bool ret = false;
+
+       isnew = false;
+
+       /* clean up PATH:CHANNEL notation so that we are looking for the correct path */
+
+       if ((pos = pathstr.find_last_of (':')) == string::npos) {
+               pathstr = pathstr;
+       } else {
+               pathstr = pathstr.substr (0, pos);
+       }
+
+       if (pathstr[0] != '/') {
+
+               /* non-absolute pathname: find pathstr in search path */
+
+               vector<string> dirs;
+               int cnt;
+               string fullpath;
+               string keeppath;
+
+               if (_search_path.length() == 0) {
+                       PBD::error << _("FileSource: search path not set") << endmsg;
+                       goto out;
+               }
+
+               split (_search_path, dirs, ':');
+
+               cnt = 0;
+               
+               for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
+
+                       fullpath = *i;
+                       if (fullpath[fullpath.length()-1] != '/') {
+                               fullpath += '/';
+                       }
+                       fullpath += pathstr;
+                       
+                       if (access (fullpath.c_str(), R_OK) == 0) {
+                               keeppath = fullpath;
+                               ++cnt;
+                       } 
+               }
+
+               if (cnt > 1) {
+
+                       PBD::error << string_compose (_("FileSource: \"%1\" is ambigous when searching %2\n\t"), pathstr, _search_path) << endmsg;
+                       goto out;
+
+               } else if (cnt == 0) {
+
+                       if (must_exist) {
+                               PBD::error << string_compose(_("Filesource: cannot find required file (%1): while searching %2"), pathstr, _search_path) << endmsg;
+                               goto out;
+                       } else {
+                               isnew = true;
+                       }
+               }
+               
+               _name = pathstr;
+               _path = keeppath;
+               ret = true;
+
+       } else {
+               
+               /* external files and/or very very old style sessions include full paths */
+               
+               _path = pathstr;
+               _name = pathstr.substr (pathstr.find_last_of ('/') + 1);
+               
+               if (access (_path.c_str(), R_OK) != 0) {
+
+                       /* file does not exist or we cannot read it */
+
+                       if (must_exist) {
+                               PBD::error << string_compose(_("Filesource: cannot find required file (%1): %2"), _path, strerror (errno)) << endmsg;
+                               goto out;
+                       }
+                       
+                       if (errno != ENOENT) {
+                               PBD::error << string_compose(_("Filesource: cannot check for existing file (%1): %2"), _path, strerror (errno)) << endmsg;
+                               goto out;
+                       }
+                       
+                       /* a new file */
+
+                       isnew = true;
+                       ret = true;
+
+               } else {
+                       
+                       /* already exists */
+
+                       ret = true;
+               }
+       }
+       
+  out:
+       return ret;
+}
+
+void
+SMFSource::set_search_path (string p)
+{
+       _search_path = p;
+}
+
+
+void
+SMFSource::set_allow_remove_if_empty (bool yn)
+{
+       if (writable()) {
+               _allow_remove_if_empty = yn;
+       }
+}
+
+int
+SMFSource::set_name (string newname, bool destructive)
+{
+       //Glib::Mutex::Lock lm (_lock); FIXME
+       string oldpath = _path;
+       string newpath = Session::change_audio_path_by_name (oldpath, _name, newname, destructive);
+
+       if (newpath.empty()) {
+               PBD::error << string_compose (_("programming error: %1"), "cannot generate a changed audio path") << endmsg;
+               return -1;
+       }
+
+       if (rename (oldpath.c_str(), newpath.c_str()) != 0) {
+               PBD::error << string_compose (_("cannot rename audio file for %1 to %2"), _name, newpath) << endmsg;
+               return -1;
+       }
+
+       _name = Glib::path_get_basename (newpath);
+       _path = newpath;
+
+       return 0;//rename_peakfile (peak_path (_path));
+}
+
+bool
+SMFSource::is_empty (string path)
+{
+       /* XXX fix me */
+
+       return false;
+}
+