Move all beats <-> frames time conversion into a single object that can be passed around.

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
This commit is contained in:
David Robillard 2009-02-16 02:51:16 +00:00
parent beb3eea62b
commit 3963d2b0b2
13 changed files with 206 additions and 80 deletions

View 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;

View 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

View 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

View file

@ -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__ */

View 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);
};
}

View 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 ();

View 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) {

View file

@ -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 */

View 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;

View 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)
{

View 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);

View 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));
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()));
uint32_t delta_time = (uint32_t)((ev.time() - _last_ev_time_frames)
/ frames_per_beat * (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);
}
}

View file

@ -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