Keep track of MIDI region's start positions in beats, to
authorCarl Hetherington <carl@carlh.net>
Sun, 11 Dec 2011 14:50:36 +0000 (14:50 +0000)
committerCarl Hetherington <carl@carlh.net>
Sun, 11 Dec 2011 14:50:36 +0000 (14:50 +0000)
match the source, so that starts are not corrupted when
tempos change (#4494).

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

12 files changed:
libs/ardour/ardour/midi_region.h
libs/ardour/ardour/region.h
libs/ardour/ardour/tempo.h
libs/ardour/midi_region.cc
libs/ardour/playlist.cc
libs/ardour/region.cc
libs/ardour/tempo.cc
libs/ardour/test/framepos_minus_beats_test.cc [new file with mode: 0644]
libs/ardour/test/framepos_minus_beats_test.h [new file with mode: 0644]
libs/ardour/test/framepos_plus_beats_test.cc
libs/ardour/test/framewalk_to_beats_test.cc
libs/ardour/wscript

index 9e8c7441fc072de8bf08eb36cbfbce260f37e1a5..a5c9e23a0f47c9758b79b023a943dfe6db978230 100644 (file)
@@ -35,6 +35,7 @@ namespace ARDOUR {
                   MidiModel used by the MidiRegion
                */
                extern PBD::PropertyDescriptor<void*> midi_data;
+               extern PBD::PropertyDescriptor<Evoral::MusicalTime> start_beats;
                extern PBD::PropertyDescriptor<Evoral::MusicalTime> length_beats;
        }
 }
@@ -111,6 +112,7 @@ class MidiRegion : public Region
 
   private:
        friend class RegionFactory;
+       PBD::Property<Evoral::MusicalTime> _start_beats;
        PBD::Property<Evoral::MusicalTime> _length_beats;
 
        MidiRegion (const SourceList&);
@@ -138,6 +140,9 @@ class MidiRegion : public Region
        void model_automation_state_changed (Evoral::Parameter const &);
        void model_contents_changed ();
 
+       void set_start_beats_from_start_frames ();
+       void update_after_tempo_map_change ();
+
        std::set<Evoral::Parameter> _filtered_parameters; ///< parameters that we ask our source not to return when reading
        PBD::ScopedConnection _model_connection;
        PBD::ScopedConnection _source_connection;
index a2cc3f4cf76069b3ba42ca696757de8a59bad254..8be4709fbf227f772cef31b574c30552db23653f 100644 (file)
@@ -189,7 +189,7 @@ class Region
        void set_position (framepos_t);
        void set_position_on_top (framepos_t);
        void special_set_position (framepos_t);
-       void update_position_after_tempo_map_change ();
+       virtual void update_after_tempo_map_change ();
        void nudge_position (frameoffset_t);
 
        bool at_natural_position () const;
index a6f7a36541f5406bfc46c42eb437e7e5d36a5720..05d65602cf841fb71d2e20a163b14862f3210820 100644 (file)
@@ -249,6 +249,7 @@ class TempoMap : public PBD::StatefulDestructible
 
        framepos_t framepos_plus_bbt (framepos_t pos, Timecode::BBT_Time b) const;
        framepos_t framepos_plus_beats (framepos_t, Evoral::MusicalTime) const;
+       framepos_t framepos_minus_beats (framepos_t, Evoral::MusicalTime) const;
        Evoral::MusicalTime framewalk_to_beats (framepos_t pos, framecnt_t distance) const;
 
        void change_existing_tempo_at (framepos_t, double bpm, double note_type);
index f501390c2403275764cc10c9feac424d3e151fd6..3dd0e1e203d1d7ba11c655c9bc7e4dc3b0522cf5 100644 (file)
@@ -40,6 +40,7 @@
 #include "ardour/playlist.h"
 #include "ardour/region_factory.h"
 #include "ardour/session.h"
+#include "ardour/tempo.h"
 #include "ardour/types.h"
 
 #include "i18n.h"
@@ -52,6 +53,7 @@ using namespace PBD;
 namespace ARDOUR {
        namespace Properties {
                PBD::PropertyDescriptor<void*>                midi_data;
+               PBD::PropertyDescriptor<Evoral::MusicalTime>  start_beats;
                PBD::PropertyDescriptor<Evoral::MusicalTime>  length_beats;
        }
 }
@@ -61,6 +63,8 @@ MidiRegion::make_property_quarks ()
 {
        Properties::midi_data.property_id = g_quark_from_static_string (X_("midi-data"));
        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for midi-data = %1\n", Properties::midi_data.property_id));
+       Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
+       DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
        Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
        DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
 }
@@ -68,12 +72,14 @@ MidiRegion::make_property_quarks ()
 void
 MidiRegion::register_properties ()
 {
+       add_property (_start_beats);
        add_property (_length_beats);
 }
 
 /* Basic MidiRegion constructor (many channels) */
 MidiRegion::MidiRegion (const SourceList& srcs)
        : Region (srcs)
+       , _start_beats (Properties::start_beats, 0)
        , _length_beats (Properties::length_beats, midi_source(0)->length_beats())
 {
        register_properties ();
@@ -86,6 +92,7 @@ MidiRegion::MidiRegion (const SourceList& srcs)
 
 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
        : Region (other)
+       , _start_beats (Properties::start_beats, other->_start_beats)
        , _length_beats (Properties::length_beats, (Evoral::MusicalTime) 0)
 {
        update_length_beats ();
@@ -96,14 +103,16 @@ MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
        model_changed ();
 }
 
-/** Create a new MidiRegion, that is part of an existing one */
+/** Create a new MidiRegion that is part of an existing one */
 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, frameoffset_t offset)
        : Region (other, offset)
+       , _start_beats (Properties::start_beats, (Evoral::MusicalTime) 0)
        , _length_beats (Properties::length_beats, (Evoral::MusicalTime) 0)
 {
        BeatsFramesConverter bfc (_session.tempo_map(), _position);
        Evoral::MusicalTime const offset_beats = bfc.from (offset);
 
+       _start_beats = other->_start_beats + offset_beats;
        _length_beats = other->_length_beats - offset_beats;
 
        register_properties ();
@@ -133,6 +142,7 @@ MidiRegion::clone () const
        plist.add (Properties::name, ms->name());
        plist.add (Properties::whole_file, true);
        plist.add (Properties::start, _start);
+       plist.add (Properties::start_beats, _start_beats);
        plist.add (Properties::length, _length);
        plist.add (Properties::length_beats, _length_beats);
        plist.add (Properties::layer, 0);
@@ -145,9 +155,18 @@ MidiRegion::post_set (const PropertyChange& pc)
 {
        if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
                update_length_beats ();
+       } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
+               set_start_beats_from_start_frames ();
        }
 }
 
+void
+MidiRegion::set_start_beats_from_start_frames ()
+{
+       BeatsFramesConverter c (_session.tempo_map(), _position - _start);
+       _start_beats = c.from (_start);
+}
+
 void
 MidiRegion::set_length_internal (framecnt_t len)
 {
@@ -155,6 +174,17 @@ MidiRegion::set_length_internal (framecnt_t len)
        update_length_beats ();
 }
 
+void
+MidiRegion::update_after_tempo_map_change ()
+{
+       Region::update_after_tempo_map_change ();
+
+       /* _position has now been updated for the new tempo map */
+       _start = _position - _session.tempo_map().framepos_minus_beats (_position, _start_beats);
+
+       send_change (Properties::start);
+}
+
 void
 MidiRegion::update_length_beats ()
 {
index 4aae95ea761e1e81772379efbd4a8db4a294e516..2586a49fc46c6d7cf8b68397b81f21522951fa24 100644 (file)
@@ -2979,7 +2979,7 @@ Playlist::update_after_tempo_map_change ()
        freeze ();
 
        for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) {
-               (*i)->update_position_after_tempo_map_change ();
+               (*i)->update_after_tempo_map_change ();
        }
 
        thaw ();
index 71aca0cce6d08d2e8ad364f982b1e5bcbe8632fa..43a42705011021ea33ff048daf5044c75a9aaebd 100644 (file)
@@ -559,7 +559,7 @@ Region::set_position_lock_style (PositionLockStyle ps)
 }
 
 void
-Region::update_position_after_tempo_map_change ()
+Region::update_after_tempo_map_change ()
 {
        boost::shared_ptr<Playlist> pl (playlist());
 
index f0880fed913c1cebafc39999c9ed088a59c22084..351b5979e6219927704e983e0c848e6f7bdad6b8 100644 (file)
@@ -1935,19 +1935,30 @@ TempoMap::bbt_subtract (const BBT_Time& start, const BBT_Time& decrement) const
        return result;
 }
 
-/** Add some (fractional) beats to a frame position, and return the result in frames */
+/** Add some (fractional) beats to a session frame position, and return the result in frames.
+ *  pos can be -ve, if required.
+ */
 framepos_t
 TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const
 {
        Metrics::const_iterator i;
        const TempoSection* tempo;
        const MeterSection* meter;
-       
+
        /* Find the starting metrics for tempo & meter */
 
        for (i = metrics->begin(); i != metrics->end(); ++i) {
 
-               if ((*i)->frame() > pos) {
+               /* This is a bit of a hack, but pos could be -ve, and if it is,
+                  we consider the initial metric changes (at time 0) to actually
+                  be in effect at pos.
+               */
+               framepos_t f = (*i)->frame ();
+               if (pos < 0 && f == 0) {
+                       f = pos;
+               }
+
+               if (f > pos) {
                        break;
                }
 
@@ -1970,11 +1981,11 @@ TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const
 
        while (beats) {
 
-               /* End of this section */
-               framepos_t end = i == metrics->end() ? max_framepos : (*i)->frame ();
+               /* Distance to the end of this section in frames */
+               framecnt_t distance_frames = i == metrics->end() ? max_framepos : ((*i)->frame() - pos);
 
                /* Distance to the end in beats */
-               Evoral::MusicalTime distance_beats = (end - pos) / tempo->frames_per_beat (_frame_rate, *meter);
+               Evoral::MusicalTime distance_beats = distance_frames / tempo->frames_per_beat (_frame_rate, *meter);
 
                /* Amount to subtract this time */
                double const sub = min (distance_beats, beats);
@@ -2001,6 +2012,112 @@ TempoMap::framepos_plus_beats (framepos_t pos, Evoral::MusicalTime beats) const
        return pos;
 }
 
+/** Subtract some (fractional) beats to a frame position, and return the result in frames */
+framepos_t
+TempoMap::framepos_minus_beats (framepos_t pos, Evoral::MusicalTime beats) const
+{
+       Metrics::const_iterator i;
+       const TempoSection* tempo = 0;
+       const MeterSection* meter = 0;
+       
+       /* Find the starting metrics for tempo & meter */
+
+       for (i = metrics->begin(); i != metrics->end(); ++i) {
+
+               if ((*i)->frame() > pos) {
+                       break;
+               }
+
+               const TempoSection* t;
+               const MeterSection* m;
+
+               if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+                       tempo = t;
+               } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+                       meter = m;
+               }
+       }
+
+       /* Move i back to the metric before "pos" */
+       if (i != metrics->begin ()) {
+               --i;
+       }
+
+       /* We now have:
+
+          meter -> the Meter for "pos"
+          tempo -> the Tempo for "pos"
+          i     -> the first metric before "pos", possibly metrics->end()
+       */
+
+       while (beats) {
+
+               /* End of this section (looking backwards) */
+               framepos_t end = i == metrics->end() ? max_framepos : (*i)->frame ();
+
+               /* Distance to the end in beats */
+               Evoral::MusicalTime distance_beats = (pos - end) / tempo->frames_per_beat (_frame_rate, *meter);
+
+               /* Amount to subtract this time */
+               double const sub = min (distance_beats, beats);
+
+               /* Update */
+               beats -= sub;
+               pos -= sub * tempo->frames_per_beat (_frame_rate, *meter);
+
+               /* Move i, tempo and meter back, if there's anything to move to.
+                  This is more complicated than the forward case, as we have to
+                  a) move back to the previous change in tempo or metric
+                  then b) scan back further to the last change in the opposite thing
+                  so that tempo/meter are both set up correctly.
+
+                  e.g. if we have (where M is a meter change and T a tempo change):
+                  M1  T1  T2  T3  M2
+
+                  and we move i back to M2, we must also move tempo back to T3 so
+                  that tempo/meter continue to reflect the current state.
+
+                  Moving further back we'd move i to T3, and meter to M1, then
+                  i to T2 and meter (still) to M1, etc.
+
+                  XXX: this is slightly farcical.
+               */
+
+               if (i != metrics->begin ()) {
+
+                       --i;
+
+                       bool found_tempo = false;
+                       bool found_meter = false;
+
+                       const TempoSection* t;
+                       const MeterSection* m;
+
+                       if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
+                               tempo = t;
+                               found_tempo = true;
+                       } else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
+                               meter = m;
+                               found_meter = true;
+                       }
+
+                       Metrics::const_iterator j = i;
+                       while (j != metrics->begin ()) {
+                               --j;
+                               if (found_tempo && ((m = dynamic_cast<const MeterSection*> (*j)) != 0)) {
+                                       meter = m;
+                                       break;
+                               } else if (found_meter && ((t = dynamic_cast<const TempoSection*> (*j)) != 0)) {
+                                       tempo = t;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       return pos;
+}
+
 /** Add the BBT interval op to pos and return the result */
 framepos_t
 TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
@@ -2135,7 +2252,9 @@ TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
        return pos;
 }
 
-/** Count the number of beats that are equivalent to distance when starting at pos */
+/** Count the number of beats that are equivalent to distance when going forward,
+    starting at pos.
+*/
 Evoral::MusicalTime
 TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 {
@@ -2165,7 +2284,7 @@ TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
 
           meter -> the Meter for "pos"
           tempo -> the Tempo for "pos"
-          i     -> for first new metric after "pos", possibly metrics->end()
+          i     -> the first metric after "pos", possibly metrics->end()
        */
 
        Evoral::MusicalTime beats = 0;
diff --git a/libs/ardour/test/framepos_minus_beats_test.cc b/libs/ardour/test/framepos_minus_beats_test.cc
new file mode 100644 (file)
index 0000000..f80f57d
--- /dev/null
@@ -0,0 +1,134 @@
+#include "framepos_minus_beats_test.h"
+#include "ardour/tempo.h"
+#include "timecode/bbt_time.h"
+
+CPPUNIT_TEST_SUITE_REGISTRATION (FrameposMinusBeatsTest);
+
+using namespace std;
+using namespace ARDOUR;
+using namespace Timecode;
+
+/* Basic tests with no tempo / meter changes */
+void
+FrameposMinusBeatsTest::singleTempoTest ()
+{
+       int const sampling_rate = 48000;
+       int const bpm = 120;
+
+       double const frames_per_beat = (60 / double (bpm)) * double (sampling_rate);
+       
+       TempoMap map (sampling_rate);
+       Tempo tempo (bpm);
+       Meter meter (4, 4);
+
+       map.add_meter (meter, BBT_Time (1, 1, 0));
+       map.add_tempo (tempo, BBT_Time (1, 1, 0));
+
+       /* Subtract 1 beat from beat 3 of the first bar */
+       framepos_t r = map.framepos_minus_beats (frames_per_beat * 2, 1);
+       CPPUNIT_ASSERT_EQUAL (r, framepos_t (frames_per_beat * 1));
+}
+
+/* Test adding things that overlap a tempo change */
+void
+FrameposMinusBeatsTest::doubleTempoTest ()
+{
+       int const sampling_rate = 48000;
+
+       TempoMap map (sampling_rate);
+       Meter meter (4, 4);
+       map.add_meter (meter, BBT_Time (1, 1, 0));
+
+       /*
+         120bpm at bar 1, 240bpm at bar 4
+         
+         120bpm = 24e3 samples per beat
+         240bpm = 12e3 samples per beat
+       */
+       
+
+       /*
+         
+         120bpm                                                240bpm
+         0 beats                                               12 beats
+         0 frames                                              288e3 frames
+         |                 |                 |                 |                 |
+         | 1.1 1.2 1.3 1.4 | 2.1 2.2 2.3.2.4 | 3.1 3.2 3.3 3.4 | 4.1 4.2 4.3 4.4 |
+
+       */
+
+       Tempo tempoA (120);
+       map.add_tempo (tempoA, BBT_Time (1, 1, 0));
+       Tempo tempoB (240);
+       map.add_tempo (tempoB, BBT_Time (4, 1, 0));
+
+       /* Now some tests */
+
+       /* Subtract 1 beat from 1|2 */
+       framepos_t r = map.framepos_minus_beats (24e3, 1);
+       CPPUNIT_ASSERT_EQUAL (r, framepos_t (0));
+
+       /* Subtract 2 beats from 4|2 (over the tempo change) */
+       r = map.framepos_minus_beats (288e3 + 12e3, 2);
+       CPPUNIT_ASSERT_EQUAL (r, framepos_t (288e3 - 24e3));
+
+       /* Subtract 2.5 beats from 4|2 (over the tempo change) */
+       r = map.framepos_minus_beats (288e3 + 12e3, 2.5);
+       CPPUNIT_ASSERT_EQUAL (r, framepos_t (288e3 - 24e3 - 12e3));
+}
+
+/* Same as doubleTempoTest () except put a meter change at the same time as the
+   tempo change (which shouldn't affect anything, since we are just dealing with
+   beats)
+*/
+   
+void
+FrameposMinusBeatsTest::doubleTempoWithMeterTest ()
+{
+       int const sampling_rate = 48000;
+
+       TempoMap map (sampling_rate);
+       Meter meterA (4, 4);
+       map.add_meter (meterA, BBT_Time (1, 1, 0));
+
+       /*
+         120bpm at bar 1, 240bpm at bar 4
+         
+         120bpm = 24e3 samples per beat
+         240bpm = 12e3 samples per beat
+       */
+       
+
+       /*
+         
+         120bpm                                                240bpm
+         0 beats                                               12 beats
+         0 frames                                              288e3 frames
+         |                 |                 |                 |             |
+         | 1.1 1.2 1.3 1.4 | 2.1 2.2 2.3.2.4 | 3.1 3.2 3.3 3.4 | 4.1 4.2 4.3 |
+
+       */
+
+       Tempo tempoA (120);
+       map.add_tempo (tempoA, BBT_Time (1, 1, 0));
+       Tempo tempoB (240);
+       map.add_tempo (tempoB, BBT_Time (4, 1, 0));
+       Meter meterB (3, 4);
+       map.add_meter (meterB, BBT_Time (4, 1, 0));
+
+       /* Now some tests */
+
+       /* Subtract 1 beat from 1|2 */
+       framepos_t r = map.framepos_minus_beats (24e3, 1);
+       CPPUNIT_ASSERT_EQUAL (r, framepos_t (0));
+
+       /* Subtract 2 beats from 4|2 (over the tempo change) */
+       r = map.framepos_minus_beats (288e3 + 12e3, 2);
+       CPPUNIT_ASSERT_EQUAL (r, framepos_t (288e3 - 24e3));
+
+       /* Subtract 2.5 beats from 4|2 (over the tempo change) */
+       r = map.framepos_minus_beats (288e3 + 12e3, 2.5);
+       CPPUNIT_ASSERT_EQUAL (r, framepos_t (288e3 - 24e3 - 12e3));
+}
+
+
diff --git a/libs/ardour/test/framepos_minus_beats_test.h b/libs/ardour/test/framepos_minus_beats_test.h
new file mode 100644 (file)
index 0000000..648ef71
--- /dev/null
@@ -0,0 +1,21 @@
+#include <sigc++/sigc++.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class FrameposMinusBeatsTest : public CppUnit::TestFixture
+{
+       CPPUNIT_TEST_SUITE (FrameposMinusBeatsTest);
+       CPPUNIT_TEST (singleTempoTest);
+       CPPUNIT_TEST (doubleTempoTest);
+       CPPUNIT_TEST (doubleTempoWithMeterTest);
+       CPPUNIT_TEST_SUITE_END ();
+
+public:
+       void setUp () {}
+       void tearDown () {}
+
+       void singleTempoTest ();
+       void doubleTempoTest ();
+       void doubleTempoWithMeterTest ();
+};
+
index 19aa7f29c6af2ae8b2624be67814047d729882e5..882b6b57212b4fbc2f2c4dfecd2c3adf0a2da89b 100644 (file)
@@ -27,6 +27,10 @@ FrameposPlusBeatsTest::singleTempoTest ()
        /* Add 1 beat to beat 3 of the first bar */
        framepos_t r = map.framepos_plus_beats (frames_per_beat * 2, 1);
        CPPUNIT_ASSERT_EQUAL (r, framepos_t (frames_per_beat * 3));
+
+       /* Add 4 beats to a -ve frame of 1 beat before zero */
+       r = map.framepos_plus_beats (-frames_per_beat * 1, 4);
+       CPPUNIT_ASSERT_EQUAL (r, framepos_t (frames_per_beat * 3));
 }
 
 /* Test adding things that overlap a tempo change */
index 7e50aedcb54bb0f8b616be3b30c38a920d851123..2b2a5a782ce2d015d8d93a9fffd0ff47bd71c8ca 100644 (file)
@@ -74,23 +74,23 @@ FramewalkToBeatsTest::doubleTempoTest ()
 
        /* Now some tests */
 
-       /* Walk 1 beat from 1.2 */
+       /* Walk 1 beat from 1|2 */
        double r = map.framewalk_to_beats (24e3, 24e3);
        CPPUNIT_ASSERT_EQUAL (r, 1.0);
 
-       /* Walk 2 beats from 3.3 to 4.1 (over the tempo change) */
+       /* Walk 2 beats from 3|3 to 4|1 (over the tempo change) */
        r = map.framewalk_to_beats (264e3, (24e3 + 12e3));
        CPPUNIT_ASSERT_EQUAL (r, 2.0);
 
-       /* Walk 2.5 beats from 3.3-and-a-half to 4.2 (over the tempo change) */
+       /* Walk 2.5 beats from 3|3.5 to 4.2 (over the tempo change) */
        r = map.framewalk_to_beats (264e3 - 12e3, (24e3 + 12e3 + 12e3));
        CPPUNIT_ASSERT_EQUAL (r, 2.5);
 
-       /* Walk 3 beats from 3.4-and-a-half to 4.3-and-a-half (over the tempo change) */
+       /* Walk 3 beats from 3|4.5 to 4|3.5 (over the tempo change) */
        r = map.framewalk_to_beats (264e3 - 12e3, (24e3 + 12e3 + 12e3 + 6e3));
        CPPUNIT_ASSERT_EQUAL (r, 3.0);
 
-       /* Walk 3.5 beats from 3.4-and-a-half to 4.4 (over the tempo change) */
+       /* Walk 3.5 beats from 3|4.5 to 4.4 (over the tempo change) */
        r = map.framewalk_to_beats (264e3 - 12e3, (24e3 + 12e3 + 12e3 + 12e3));
        CPPUNIT_ASSERT_EQUAL (r, 3.5);
 }
index 0eb4f0ceb677799c06ffd258b1a426006bdb0a7d..1c26a16fd04f9622d6a6d9a87ec86b4f3fc402a4 100644 (file)
@@ -429,6 +429,7 @@ def build(bld):
                 test/resampled_source_test.cc
                 test/framewalk_to_beats_test.cc
                 test/framepos_plus_beats_test.cc
+                test/framepos_minus_beats_test.cc
                 test/testrunner.cc
         '''.split()