diff options
-rw-r--r-- | libs/ardour/ardour/buffer.h | 143 | ||||
-rw-r--r-- | libs/ardour/ardour/diskstream.h | 341 | ||||
-rw-r--r-- | libs/ardour/ardour/midi_diskstream.h | 194 | ||||
-rw-r--r-- | libs/ardour/ardour/midi_playlist.h | 109 | ||||
-rw-r--r-- | libs/ardour/ardour/midi_region.h | 152 | ||||
-rw-r--r-- | libs/ardour/ardour/midi_source.h | 93 | ||||
-rw-r--r-- | libs/ardour/ardour/midi_track.h | 180 | ||||
-rw-r--r-- | libs/ardour/ardour/smf_source.h | 96 | ||||
-rw-r--r-- | libs/ardour/diskstream.cc | 362 | ||||
-rw-r--r-- | libs/ardour/midi_diskstream.cc | 624 | ||||
-rw-r--r-- | libs/ardour/midi_playlist.cc | 634 | ||||
-rw-r--r-- | libs/ardour/midi_region.cc | 647 | ||||
-rw-r--r-- | libs/ardour/midi_source.cc | 133 | ||||
-rw-r--r-- | libs/ardour/midi_track.cc | 1167 | ||||
-rw-r--r-- | libs/ardour/smf_source.cc | 406 |
15 files changed, 5281 insertions, 0 deletions
diff --git a/libs/ardour/ardour/buffer.h b/libs/ardour/ardour/buffer.h new file mode 100644 index 0000000000..5171f50697 --- /dev/null +++ b/libs/ardour/ardour/buffer.h @@ -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 index 0000000000..f43e8273a7 --- /dev/null +++ b/libs/ardour/ardour/diskstream.h @@ -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 index 0000000000..b6121d1176 --- /dev/null +++ b/libs/ardour/ardour/midi_diskstream.h @@ -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 index 0000000000..da3a72a3fd --- /dev/null +++ b/libs/ardour/ardour/midi_playlist.h @@ -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 index 0000000000..e4d8f48c6e --- /dev/null +++ b/libs/ardour/ardour/midi_region.h @@ -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 index 0000000000..735ebba447 --- /dev/null +++ b/libs/ardour/ardour/midi_source.h @@ -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 index 0000000000..1ef9bedf8d --- /dev/null +++ b/libs/ardour/ardour/midi_track.h @@ -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 index 0000000000..00f8cb26de --- /dev/null +++ b/libs/ardour/ardour/smf_source.h @@ -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 index 0000000000..050f23497b --- /dev/null +++ b/libs/ardour/diskstream.cc @@ -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 index 0000000000..0af4af4f7c --- /dev/null +++ b/libs/ardour/midi_diskstream.cc @@ -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 index 0000000000..007856e3a7 --- /dev/null +++ b/libs/ardour/midi_playlist.cc @@ -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*> (®ion); + + 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 index 0000000000..e9d75e3d06 --- /dev/null +++ b/libs/ardour/midi_region.cc @@ -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 index 0000000000..f9fc8dd8ec --- /dev/null +++ b/libs/ardour/midi_source.cc @@ -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 index 0000000000..d4f7be1e6f --- /dev/null +++ b/libs/ardour/midi_track.cc @@ -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 index 0000000000..10cf7ab335 --- /dev/null +++ b/libs/ardour/smf_source.cc @@ -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; +} + |