Move all beats <-> frames time conversion into a single object that can be passed...
authorDavid Robillard <d@drobilla.net>
Mon, 16 Feb 2009 02:51:16 +0000 (02:51 +0000)
committerDavid Robillard <d@drobilla.net>
Mon, 16 Feb 2009 02:51:16 +0000 (02:51 +0000)
This has 3 main benefits:
 - All conversion code is in one place (less duplication, potential bugs)
 - The conversion method can be passed to things that are ignorant
   of the actual time units involved, information required, etc.
   (In the future it would be nice to have user selectable tempo/frame time)
 - It should be relatively simple now to support tempo changes part-way
   through a MIDI region (at least architecturally speaking)

git-svn-id: svn://localhost/ardour2/branches/3.0@4594 d708f5d6-7413-0410-9779-e7cbd77b26cf

13 files changed:
gtk2_ardour/editor.h
gtk2_ardour/midi_region_view.cc
libs/ardour/SConscript
libs/ardour/ardour/beats_frames_converter.h [new file with mode: 0644]
libs/ardour/ardour/midi_source.h
libs/ardour/ardour/session.h
libs/ardour/audio_region_importer.cc
libs/ardour/beats_frames_converter.cc [new file with mode: 0644]
libs/ardour/import.cc
libs/ardour/midi_source.cc
libs/ardour/quantize.cc
libs/ardour/smf_source.cc
libs/evoral/evoral/TimeConverter.hpp [new file with mode: 0644]

index 379716ed368bd143beaa0ca32ef20408c66d4854..f0e28677f7d591da5ec704e59bb4cd80cb48d9ab 100644 (file)
@@ -1305,7 +1305,7 @@ class Editor : public PublicEditor
 
        /* import specific info */
 
-       struct EditorImportStatus : public ARDOUR::Session::import_status {
+       struct EditorImportStatus : public ARDOUR::Session::ImportStatus {
            Editing::ImportMode mode;
            nframes64_t pos;
            int target_tracks;
index 5339131c8ec2ee10dc4852107ac89a46f5469cee..7a4acb560d4efc914ac300eab1e8c646f921a832 100644 (file)
@@ -641,9 +641,6 @@ MidiRegionView::display_program_change_flags()
                                }
                        }
                        break;
-               } else if (control->first.type() == MidiCCAutomation) {
-                       //cerr << " found CC Automation of channel " << int(control->first.channel())
-                       //              << " and id " << control->first.id() << endl;
                }
        }       
 }
@@ -1391,17 +1388,13 @@ MidiRegionView::get_position_pixels()
 nframes64_t
 MidiRegionView::beats_to_frames(double beats) const
 {
-       const Meter& m = trackview.session().tempo_map().meter_at(_region->position());
-       const Tempo& t = trackview.session().tempo_map().tempo_at(_region->position());
-       return lrint(beats * m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar());
+       return midi_region()->midi_source()->converter().to(beats);
 }
 
 double
 MidiRegionView::frames_to_beats(nframes64_t frames) const
 {
-       const Meter& m = trackview.session().tempo_map().meter_at(_region->position());
-       const Tempo& t = trackview.session().tempo_map().tempo_at(_region->position());
-       return frames / m.frames_per_bar(t, trackview.session().frame_rate()) * m.beats_per_bar();
+       return midi_region()->midi_source()->converter().from(frames);
 }
 
 void
index 11a5b36566320837d13221655874bcadf8c8f539..83b7d659b5676604cf5bf3bcb8920e1f60973a33 100644 (file)
@@ -38,6 +38,7 @@ audio_library.cc
 audio_playlist.cc
 audio_playlist_importer.cc
 audio_port.cc
+audio_region_importer.cc
 audio_track.cc
 audio_track_importer.cc
 audioanalyser.cc
@@ -45,13 +46,13 @@ audioengine.cc
 audiofile_tagger.cc
 audiofilesource.cc
 audioregion.cc
-audio_region_importer.cc
 audiosource.cc
 auditioner.cc
 automatable.cc
 automation.cc
 automation_control.cc
 automation_list.cc
+beats_frames_converter.cc
 broadcast_info.cc
 buffer.cc
 buffer_set.cc
@@ -65,8 +66,8 @@ cycle_timer.cc
 default_click.cc
 directory_names.cc
 diskstream.cc
-element_importer.cc
 element_import_handler.cc
+element_importer.cc
 enums.cc
 event_type_map.cc
 export_channel.cc
@@ -162,8 +163,8 @@ svn_revision.cc
 tape_file_matcher.cc
 template_utils.cc
 tempo.cc
-ticker.cc
 tempo_map_importer.cc
+ticker.cc
 track.cc
 transient_detector.cc
 user_bundle.cc
diff --git a/libs/ardour/ardour/beats_frames_converter.h b/libs/ardour/ardour/beats_frames_converter.h
new file mode 100644 (file)
index 0000000..79972cb
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+    Copyright (C) 2009 Paul Davis 
+    Author: 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.
+
+    $Id: midiregion.h 733 2006-08-01 17:19:38Z drobilla $
+*/
+
+#include <evoral/TimeConverter.hpp>
+#include <ardour/types.h>
+
+#ifndef __ardour_beats_frames_converter_h__
+#define __ardour_beats_frames_converter_h__
+
+namespace ARDOUR {
+
+class Session;
+
+class BeatsFramesConverter : public Evoral::TimeConverter<double,nframes_t> {
+public:
+       BeatsFramesConverter(Session& session, nframes_t origin)
+               : _session(session)
+               , _origin(origin)
+       {}
+       
+       nframes_t to(double beats)       const;
+       double    from(nframes_t frames) const;
+
+       nframes_t origin() const              { return _origin; }
+       void     set_origin(nframes_t origin) { _origin = origin; }
+
+private:
+       Session&  _session;
+       nframes_t _origin;
+};
+
+} /* namespace ARDOUR */
+
+#endif /* __ardour_beats_frames_converter_h__ */
index 3f8ed82310e624baeaa0d0fa3dea99f072d8e506..3a28cde8c6f38df0386d033d6f506602083b11dd 100644 (file)
@@ -30,8 +30,7 @@
 #include <ardour/ardour.h>
 #include <ardour/buffer.h>
 #include <ardour/source.h>
-
-using std::string;
+#include <ardour/beats_frames_converter.h>
 
 namespace ARDOUR {
 
@@ -44,7 +43,7 @@ class MidiSource : public Source
   public:
        typedef double TimeType;
 
-       MidiSource (Session& session, string name);
+       MidiSource (Session& session, std::string name);
        MidiSource (Session& session, const XMLNode&);
        virtual ~MidiSource ();
        
@@ -66,13 +65,13 @@ class MidiSource : public Source
        virtual void mark_streaming_write_started ();
        virtual void mark_streaming_write_completed ();
        
-       uint64_t timeline_position ()                   { return _timeline_position; }
-       void     set_timeline_position (nframes_t when) { _timeline_position = when; }
+       uint64_t timeline_position () { return _timeline_position; }
+       void     set_timeline_position (nframes_t when);
        
        virtual void session_saved();
 
-       string captured_for() const { return _captured_for; }
-       void   set_captured_for (string str) { _captured_for = str; }
+       std::string captured_for() const               { return _captured_for; }
+       void        set_captured_for (std::string str) { _captured_for = str; }
 
        uint32_t read_data_count()  const { return _read_data_count; }
        uint32_t write_data_count() const { return _write_data_count; }
@@ -96,6 +95,8 @@ class MidiSource : public Source
        void set_model(boost::shared_ptr<MidiModel> m) { _model = m; }
        void drop_model() { _model.reset(); }
 
+       BeatsFramesConverter& converter() { return _converter; }
+
   protected:
        virtual void flush_midi() = 0;
        
@@ -104,10 +105,12 @@ class MidiSource : public Source
        virtual nframes_t write_unlocked (MidiRingBuffer<nframes_t>& dst, nframes_t cnt) = 0;
        
        mutable Glib::Mutex _lock;
-       string              _captured_for;
+       std::string         _captured_for;
        uint64_t            _timeline_position;
        mutable uint32_t    _read_data_count;  ///< modified in read()
        mutable uint32_t    _write_data_count; ///< modified in write()
+       
+       BeatsFramesConverter _converter;
 
        boost::shared_ptr<MidiModel> _model;
        bool                         _writing;
@@ -116,7 +119,7 @@ class MidiSource : public Source
        mutable nframes_t                                _last_read_end;
 
   private:
-       bool file_changed (string path);
+       bool file_changed (std::string path);
 };
 
 }
index 8de0e1c5234bff25163f3eada812aac2ff383227..ef25ebaea30f04cb6dc3e53fa18a2c0aa65d0065 100644 (file)
@@ -610,7 +610,7 @@ class Session : public PBD::StatefulDestructible
 
        /* source management */
 
-       struct import_status : public InterThreadInfo {
+       struct ImportStatus : public InterThreadInfo {
                string doing_what;
 
                /* control info */
@@ -624,8 +624,8 @@ class Session : public PBD::StatefulDestructible
                SourceList sources;
        };
 
-       void import_audiofiles (import_status&);
-       bool sample_rate_convert (import_status&, string infile, string& outfile);
+       void import_audiofiles (ImportStatus&);
+       bool sample_rate_convert (ImportStatus&, string infile, string& outfile);
        string build_tmp_convert_name (string file);
 
        boost::shared_ptr<ExportHandler> get_export_handler ();
index 714446bfe2fc718a3fff76b5313ac2d0d5d79b1b..23f3bfd6c6060639483827b746441638dd03b2e2 100644 (file)
@@ -336,7 +336,7 @@ AudioRegionImporter::prepare_sources ()
                return;
        }
        
-       Session::import_status status;
+       Session::ImportStatus status;
        
        // Get sources that still need to be imported
        for (std::list<string>::iterator it = filenames.begin(); it != filenames.end(); ++it) {
diff --git a/libs/ardour/beats_frames_converter.cc b/libs/ardour/beats_frames_converter.cc
new file mode 100644 (file)
index 0000000..736068e
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+    Copyright (C) 2009 Paul Davis 
+    Author: 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.
+
+    $Id: midiregion.h 733 2006-08-01 17:19:38Z drobilla $
+*/
+
+#include <ardour/audioengine.h>
+#include <ardour/beats_frames_converter.h>
+#include <ardour/session.h>
+#include <ardour/tempo.h>
+
+namespace ARDOUR {
+
+nframes_t
+BeatsFramesConverter::to(double beats) const
+{
+       // FIXME: assumes tempo never changes after origin
+       const Tempo& tempo = _session.tempo_map().tempo_at(_origin);
+       const double frames_per_beat = tempo.frames_per_beat(
+                       _session.engine().frame_rate(),
+                       _session.tempo_map().meter_at(_origin));
+
+       return lrint(beats * frames_per_beat);
+}
+
+double
+BeatsFramesConverter::from(nframes_t frames) const
+{
+       // FIXME: assumes tempo never changes after origin
+       const Tempo& tempo = _session.tempo_map().tempo_at(_origin);
+       const double frames_per_beat = tempo.frames_per_beat(
+                       _session.engine().frame_rate(),
+                       _session.tempo_map().meter_at(_origin));
+
+       return frames / frames_per_beat;
+}
+
+} /* namespace ARDOUR */
+
index 824cdb43f713fb24999861a28e86fe38def7fbbe..0e2cfb9988499d9a4b29d8c95643f2f7aa6fc448 100644 (file)
@@ -257,7 +257,7 @@ compose_status_message (const string& path,
 }
 
 static void
-write_audio_data_to_new_files (ImportableSource* source, Session::import_status& status,
+write_audio_data_to_new_files (ImportableSource* source, Session::ImportStatus& status,
                               vector<boost::shared_ptr<Source> >& newfiles)
 {
        const nframes_t nframes = ResampledImportableSource::blocksize;
@@ -309,8 +309,8 @@ write_audio_data_to_new_files (ImportableSource* source, Session::import_status&
 }
 
 static void
-write_midi_data_to_new_files (Evoral::SMF* source, Session::import_status& status,
-                              vector<boost::shared_ptr<Source> >& newfiles)
+write_midi_data_to_new_files (Evoral::SMF* source, Session::ImportStatus& status,
+               vector<boost::shared_ptr<Source> >& newfiles)
 {
        uint32_t buf_size = 4;
        uint8_t* buf      = (uint8_t*)malloc(buf_size);
@@ -354,20 +354,14 @@ write_midi_data_to_new_files (Evoral::SMF* source, Session::import_status& statu
                        if (status.progress < 0.99)
                                status.progress += 0.01;
                }
-                       
-               nframes_t timeline_position = 0; // FIXME: ?
 
-               // FIXME: kluuuuudge: assumes tempo never changes after start
-               const double frames_per_beat = smfs->session().tempo_map().tempo_at(
-                               timeline_position).frames_per_beat(
-                                       smfs->session().engine().frame_rate(),
-                                       smfs->session().tempo_map().meter_at(timeline_position));
-
-               smfs->update_length(0, (nframes_t) ceil ((t / (double)source->ppqn()) * frames_per_beat));
+               const double length_beats = ceil(t / (double)source->ppqn());
+               smfs->update_length(0, smfs->converter().to(length_beats));
                smfs->end_write();
 
-               if (status.cancel)
+               if (status.cancel) {
                        break;
+               }
        }
 
        } catch (...) {
@@ -382,11 +376,11 @@ remove_file_source (boost::shared_ptr<Source> source)
 }
 
 // This function is still unable to cleanly update an existing source, even though
-// it is possible to set the import_status flag accordingly. The functinality
+// it is possible to set the ImportStatus flag accordingly. The functinality
 // is disabled at the GUI until the Source implementations are able to provide
 // the necessary API.
 void
-Session::import_audiofiles (import_status& status)
+Session::import_audiofiles (ImportStatus& status)
 {
        uint32_t cnt = 1;
        typedef vector<boost::shared_ptr<Source> > Sources;
index 70abb99e567f3dd6f72b312aed7f97d8189871be..ece0d9b104d413acb4f186352fc9b156831e6d0c 100644 (file)
@@ -52,16 +52,20 @@ sigc::signal<void,MidiSource *> MidiSource::MidiSourceCreated;
 MidiSource::MidiSource (Session& s, string name)
        : Source (s, name, DataType::MIDI)
        , _timeline_position(0)
+       , _read_data_count(0)
+       , _write_data_count(0)
+       , _converter(s, _timeline_position)
        , _writing (false)
        , _last_read_end(0)
 {
-       _read_data_count = 0;
-       _write_data_count = 0;
 }
 
 MidiSource::MidiSource (Session& s, const XMLNode& node) 
        : Source (s, node)
        , _timeline_position(0)
+       , _read_data_count(0)
+       , _write_data_count(0)
+       , _converter(s, _timeline_position)
        , _writing (false)
        , _last_read_end(0)
 {
@@ -110,12 +114,7 @@ MidiSource::midi_read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_
 
        Glib::Mutex::Lock lm (_lock);
        if (_model) {
-               // FIXME: assumes tempo never changes after start
-               const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position);
-               const double frames_per_beat = tempo.frames_per_beat(
-                               _session.engine().frame_rate(),
-                               _session.tempo_map().meter_at(_timeline_position));
-#define BEATS_TO_FRAMES(t) (((t) * frames_per_beat) + stamp_offset - negative_stamp_offset)
+#define BEATS_TO_FRAMES(t) (_converter.to(t) + stamp_offset - negative_stamp_offset)
 
                Evoral::Sequence<double>::const_iterator& i = _model_iter;
                
@@ -161,6 +160,13 @@ MidiSource::file_changed (string path)
        return !e1;
 }
 
+void
+MidiSource::set_timeline_position (nframes_t when)
+{
+       _timeline_position = when;
+       _converter.set_origin(when);
+}
+
 void
 MidiSource::mark_streaming_midi_write_started (NoteMode mode, nframes_t start_frame)
 {
index e4a28092771ca39e6370ed81004881e5995b3c30..76eb924612e49082bb01bda0348d5de1b1784ba1 100644 (file)
@@ -62,19 +62,12 @@ Quantize::run (boost::shared_ptr<Region> r)
 
        boost::shared_ptr<MidiModel> model = src->model();
        
-       // FIXME: Model really needs to be switched to beat time (double) ASAP
-       
-       const Tempo& t = session.tempo_map().tempo_at(r->start());
-       const Meter& m = session.tempo_map().meter_at(r->start());
-
-       double q_frames = _q * (m.frames_per_bar(t, session.frame_rate()) / (double)m.beats_per_bar());
-
        for (Evoral::Sequence<MidiModel::TimeType>::Notes::iterator i = model->notes().begin();
                        i != model->notes().end(); ++i) {
-               const double new_time = lrint((*i)->time() / q_frames) * q_frames;
-               double new_dur = lrint((*i)->length() / q_frames) * q_frames;
+               const double new_time = lrint((*i)->time() / _q) * _q;
+               double new_dur = lrint((*i)->length() / _q) * _q;
                if (new_dur == 0.0)
-                       new_dur = q_frames;
+                       new_dur = _q;
                
                (*i)->set_time(new_time);
                (*i)->set_length(new_dur);
index 5fd09ef1bb3f81da51e5f6f32b77697c122fa39a..ab0c290191bbfa94f2fb20a5e3301652d777c8d6 100644 (file)
@@ -41,7 +41,6 @@
 #include <ardour/midi_ring_buffer.h>
 #include <ardour/session.h>
 #include <ardour/smf_source.h>
-#include <ardour/tempo.h>
 
 #include "i18n.h"
 
@@ -145,13 +144,7 @@ SMFSource::read_unlocked (MidiRingBuffer<nframes_t>& dst, nframes_t start, nfram
 
        size_t scratch_size = 0; // keep track of scratch to minimize reallocs
 
-       // FIXME: assumes tempo never changes after start
-       const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position);
-       const double frames_per_beat = tempo.frames_per_beat(
-                       _session.engine().frame_rate(),
-                       _session.tempo_map().meter_at(_timeline_position));
-       
-       const uint64_t start_ticks = (uint64_t)((start / frames_per_beat) * ppqn());
+       const uint64_t start_ticks = (uint64_t)(_converter.from(start) * ppqn());
 
        if (_last_read_end == 0 || start != _last_read_end) {
                cerr << "SMFSource::read_unlocked seeking to " << start << endl;
@@ -183,8 +176,7 @@ SMFSource::read_unlocked (MidiRingBuffer<nframes_t>& dst, nframes_t start, nfram
                ev_type = EventTypeMap::instance().midi_event_type(ev_buffer[0]);
 
                assert(time >= start_ticks);
-               const nframes_t ev_frame_time = (nframes_t)(
-                               ((time / (double)ppqn()) * frames_per_beat)) + stamp_offset;
+               const nframes_t ev_frame_time = _converter.to(time / (double)ppqn()) + stamp_offset;
 
                if (ev_frame_time < start + dur) {
                        dst.write(ev_frame_time - negative_stamp_offset, ev_type, ev_size, ev_buffer);
@@ -323,24 +315,19 @@ SMFSource::append_event_unlocked_frames(const Evoral::Event<nframes_t>& ev)
                return;
        }
        
-       // FIXME: assumes tempo never changes after start
-       const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position);
-       const double frames_per_beat = tempo.frames_per_beat(
-                       _session.engine().frame_rate(),
-                       _session.tempo_map().meter_at(_timeline_position));
-
-       uint32_t delta_time = (uint32_t)((ev.time() - _last_ev_time_frames)
-                       / frames_per_beat * (double)ppqn());
+       const nframes_t delta_time_frames = ev.time() - _last_ev_time_frames;
+       const double    delta_time_beats  = _converter.from(delta_time_frames);
+       const uint32_t  delta_time_ticks  = (uint32_t)(lrint(delta_time_beats * (double)ppqn()));
 
-       Evoral::SMF::append_event_delta(delta_time, ev.size(), ev.buffer());
+       Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer());
        _last_ev_time_frames = ev.time();
 
        _write_data_count += ev.size();
 
        if (_model) {
-               double beat_time = ev.time() / frames_per_beat;
+               const double ev_time_beats = _converter.from(ev.time());
                const Evoral::Event<double> beat_ev(
-                               ev.event_type(), beat_time, ev.size(), (uint8_t*)ev.buffer());
+                               ev.event_type(), ev_time_beats, ev.size(), (uint8_t*)ev.buffer());
                _model->append(beat_ev);
        }
 }
diff --git a/libs/evoral/evoral/TimeConverter.hpp b/libs/evoral/evoral/TimeConverter.hpp
new file mode 100644 (file)
index 0000000..3f434d9
--- /dev/null
@@ -0,0 +1,43 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2009 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2009 Paul Davis
+ * 
+ * Evoral 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.
+ * 
+ * Evoral 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 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.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_TIME_CONVERTER_HPP
+#define EVORAL_TIME_CONVERTER_HPP
+
+namespace Evoral {
+
+/** A bidirectional converter between two different time units.
+ *
+ * Think of the conversion method names as if they are written in-between
+ * the two template parameters (i.e. "A <name> B").
+ */
+template<typename A, typename B>
+class TimeConverter {
+public:
+       virtual ~TimeConverter() {}
+
+       /** Convert A time to B time (A to B) */
+       virtual B to(A a) const = 0;
+       
+       /** Convert B time to A time (A from B) */
+       virtual A from(B b) const = 0;
+};
+
+} // namespace Evoral
+
+#endif // EVORAL_TIME_CONVERTER_HPP