Split MidiModel::Note out to ARDOUR::Note in it's own file (midi_model.h was getting fat).

Initial work on MidiModel iterator.


git-svn-id: svn://localhost/ardour2/trunk@2355 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard 2007-08-28 17:48:37 +00:00
parent 23949886e6
commit 056b2a59d5
17 changed files with 375 additions and 120 deletions

View File

@ -29,7 +29,7 @@ namespace Canvas {
class CanvasHit : public Diamond, public CanvasMidiEvent {
public:
CanvasHit(MidiRegionView& region, Group& group, double size, const ARDOUR::MidiModel::Note* note=NULL)
CanvasHit(MidiRegionView& region, Group& group, double size, const ARDOUR::Note* note=NULL)
: Diamond(group, size), CanvasMidiEvent(region, this, note) {}
// FIXME

View File

@ -31,7 +31,7 @@ namespace Gnome {
namespace Canvas {
CanvasMidiEvent::CanvasMidiEvent(MidiRegionView& region, Item* item, const ARDOUR::MidiModel::Note* note)
CanvasMidiEvent::CanvasMidiEvent(MidiRegionView& region, Item* item, const ARDOUR::Note* note)
: _region(region)
, _item(item)
, _state(None)

View File

@ -43,7 +43,7 @@ namespace Canvas {
*/
class CanvasMidiEvent {
public:
CanvasMidiEvent(MidiRegionView& region, Item* item, const ARDOUR::MidiModel::Note* note = NULL);
CanvasMidiEvent(MidiRegionView& region, Item* item, const ARDOUR::Note* note = NULL);
virtual ~CanvasMidiEvent() {}
bool on_event(GdkEvent* ev);
@ -62,16 +62,16 @@ public:
const Item* item() const { return _item; }
Item* item() { return _item; }
const ARDOUR::MidiModel::Note* note() { return _note; }
const ARDOUR::Note* note() { return _note; }
protected:
enum State { None, Pressed, Dragging };
MidiRegionView& _region;
Item* const _item;
State _state;
const ARDOUR::MidiModel::Note* _note;
bool _selected;
MidiRegionView& _region;
Item* const _item;
State _state;
const ARDOUR::Note* _note;
bool _selected;
};
} // namespace Gnome

View File

@ -30,7 +30,7 @@ namespace Canvas {
class CanvasNote : public SimpleRect, public CanvasMidiEvent {
public:
CanvasNote(MidiRegionView& region, Group& group, const ARDOUR::MidiModel::Note* note=NULL)
CanvasNote(MidiRegionView& region, Group& group, const ARDOUR::Note* note=NULL)
: SimpleRect(group), CanvasMidiEvent(region, this, note)
{
}

View File

@ -359,7 +359,7 @@ MidiRegionView::create_note_at(double x, double y, double dur)
//double dur = m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar();
// Add a 1 beat long note (for now)
const MidiModel::Note new_note(stamp, dur, (uint8_t)note, 0x40);
const Note new_note(stamp, dur, (uint8_t)note, 0x40);
view->update_bounds(new_note.note());
@ -629,7 +629,7 @@ MidiRegionView::extend_active_notes()
* duration so they can be drawn in full immediately.
*/
void
MidiRegionView::add_note (const MidiModel::Note& note)
MidiRegionView::add_note (const Note& note)
{
assert(note.time() >= 0);
//assert(note.time() < _region->length());
@ -777,7 +777,7 @@ MidiRegionView::note_dropped(CanvasMidiEvent* ev, double dt, uint8_t dnote)
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
command_remove_note(*i);
MidiModel::Note copy(*(*i)->note());
Note copy(*(*i)->note());
copy.set_time((*i)->note()->time() + dt);
copy.set_note((*i)->note()->note() + dnote);

View File

@ -77,7 +77,7 @@ class MidiRegionView : public RegionView
GhostRegion* add_ghost (AutomationTimeAxisView&);
void add_event(const ARDOUR::MidiEvent& ev);
void add_note(const ARDOUR::MidiModel::Note& note);
void add_note(const ARDOUR::Note& note);
void begin_write();
void end_write();
@ -99,7 +99,7 @@ class MidiRegionView : public RegionView
_delta_command = _model->new_delta_command();
}
void command_add_note(ARDOUR::MidiModel::Note& note) {
void command_add_note(ARDOUR::Note& note) {
if (_delta_command)
_delta_command->add(note);
}

View File

@ -149,7 +149,7 @@ MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
if (source->model()) {
// Find our note range
for (size_t i=0; i < source->model()->n_notes(); ++i) {
const MidiModel::Note& note = source->model()->note_at(i);
const Note& note = source->model()->note_at(i);
update_bounds(note.note());
}
}
@ -469,7 +469,7 @@ MidiStreamView::update_rec_regions (boost::shared_ptr<MidiModel> data, nframes_t
MidiRegionView* mrv = (MidiRegionView*)iter->second;
// FIXME: slow
for (size_t i=0; i < data->n_notes(); ++i) {
const MidiModel::Note& note = data->note_at(i);
const Note& note = data->note_at(i);
if (note.time() > start + dur)
break;

View File

@ -62,6 +62,7 @@ midi_playlist.cc
midi_track.cc
midi_region.cc
midi_model.cc
note.cc
smf_source.cc
auditioner.cc
automation.cc

View File

@ -84,8 +84,8 @@ class AutomationList : public PBD::StatefulDestructible
AutomationList& operator= (const AutomationList&);
bool operator== (const AutomationList&);
Parameter parameter() const { return _parameter; }
void set_parameter(Parameter p) { _parameter = p; }
const Parameter& parameter() const { return _parameter; }
void set_parameter(Parameter p) { _parameter = p; }
void freeze();
void thaw ();

View File

@ -21,7 +21,9 @@
#ifndef __ardour_midi_event_h__
#define __ardour_midi_event_h__
#include <ardour/types.h>
#include <ardour/midi_events.h>
#include <stdint.h>
/** If this is not defined, all methods of MidiEvent are RT safe
* but MidiEvent will never deep copy and (depending on the scenario)
@ -38,7 +40,7 @@ namespace ARDOUR {
*/
struct MidiEvent {
#ifdef MIDI_EVENT_ALLOW_ALLOC
MidiEvent(double t=0, size_t s=0, Byte* b=NULL, bool owns_buffer=false)
MidiEvent(double t=0, uint32_t s=0, Byte* b=NULL, bool owns_buffer=false)
: _time(t)
, _size(s)
, _buffer(b)
@ -96,7 +98,15 @@ struct MidiEvent {
}
inline bool owns_buffer() const { return _owns_buffer; }
inline void set_buffer(Byte* buf) { assert(!_owns_buffer); _buffer = buf; }
inline void set_buffer(Byte* buf) {
if (_owns_buffer) {
free(_buffer);
_buffer = NULL;
}
_buffer = buf;
_owns_buffer = false;
}
#else

View File

@ -22,6 +22,7 @@
#define __ardour_midi_model_h__
#include <queue>
#include <utility>
#include <boost/utility.hpp>
#include <glibmm/thread.h>
#include <pbd/command.h>
@ -29,11 +30,16 @@
#include <ardour/midi_buffer.h>
#include <ardour/midi_ring_buffer.h>
#include <ardour/automatable.h>
#include <ardour/note.h>
namespace ARDOUR {
class Session;
class MidiSource;
// x , y
typedef std::pair<boost::shared_ptr<const AutomationList>, std::pair<double,double> >
MidiControlIterator;
/** This is a slightly higher level (than MidiBuffer) model of MIDI note data.
@ -44,45 +50,13 @@ class MidiSource;
*/
class MidiModel : public boost::noncopyable, public Automatable {
public:
struct Note {
Note(double time=0, double dur=0, uint8_t note=0, uint8_t vel=0x40);
Note(const Note& copy);
const MidiModel::Note& operator=(const MidiModel::Note& copy);
inline bool operator==(const Note& other)
{ return time() == other.time() && note() == other.note(); }
inline double time() const { return _on_event.time(); }
inline double end_time() const { return _off_event.time(); }
inline uint8_t note() const { return _on_event.note(); }
inline uint8_t velocity() const { return _on_event.velocity(); }
inline double duration() const { return _off_event.time() - _on_event.time(); }
inline void set_time(double t) { _off_event.time() = t + duration(); _on_event.time() = t; }
inline void set_note(uint8_t n) { _on_event.buffer()[1] = n; _off_event.buffer()[1] = n; }
inline void set_velocity(uint8_t n) { _on_event.buffer()[2] = n; }
inline void set_duration(double d) { _off_event.time() = _on_event.time() + d; }
inline MidiEvent& on_event() { return _on_event; }
inline MidiEvent& off_event() { return _off_event; }
inline const MidiEvent& on_event() const { return _on_event; }
inline const MidiEvent& off_event() const { return _off_event; }
private:
// Event buffers are self-contained
MidiEvent _on_event;
MidiEvent _off_event;
};
MidiModel(Session& s, size_t size=0);
// This is crap.
void write_lock() { _lock.writer_lock(); _automation_lock.lock(); }
void write_unlock() { _lock.writer_unlock(); _automation_lock.unlock(); }
void read_lock() { _lock.reader_lock(); _automation_lock.lock(); }
void read_unlock() { _lock.reader_unlock(); _automation_lock.unlock(); }
void write_lock() { _lock.writer_lock(); _automation_lock.lock(); }
void write_unlock() { _lock.writer_unlock(); _automation_lock.unlock(); }
void read_lock() const { _lock.reader_lock(); _automation_lock.lock(); }
void read_unlock() const { _lock.reader_unlock(); _automation_lock.unlock(); }
void clear() { _notes.clear(); }
@ -90,7 +64,7 @@ public:
void set_note_mode(NoteMode mode) { _note_mode = mode; }
void start_write();
bool currently_writing() const { return _writing; }
bool writing() const { return _writing; }
void end_write(bool delete_stuck=false);
size_t read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframes_t stamp_offset) const;
@ -99,7 +73,6 @@ public:
void append(const MidiBuffer& data);
/** Resizes vector if necessary (NOT realtime safe) */
//void append(double time, size_t size, const Byte* in_buffer);
void append(const MidiEvent& ev);
inline const Note& note_at(unsigned i) const { return _notes[i]; }
@ -164,11 +137,37 @@ public:
sigc::signal<void> ContentsChanged;
/** Read iterator */
class const_iterator {
public:
const_iterator(MidiModel& model, double t);
~const_iterator();
const MidiEvent& operator*() const { return _event; }
const const_iterator& operator++(); // prefix only
private:
const MidiModel& _model;
MidiEvent _event;
typedef std::priority_queue<const Note*,std::vector<const Note*>, LaterNoteEndComparator>
ActiveNotes;
mutable ActiveNotes _active_notes;
Notes::iterator _note_iter;
std::vector<MidiControlIterator> _control_iters;
};
private:
friend class DeltaCommand;
void add_note_unlocked(const Note& note);
void remove_note_unlocked(const Note& note);
friend class const_iterator;
bool control_to_midi_event(MidiEvent& ev, const MidiControlIterator& iter);
#ifndef NDEBUG
bool is_sorted() const;
#endif
@ -177,7 +176,7 @@ private:
void append_note_off_unlocked(double time, uint8_t note);
void append_cc_unlocked(double time, uint8_t number, uint8_t value);
Glib::RWLock _lock;
mutable Glib::RWLock _lock;
Notes _notes;
NoteMode _note_mode;
@ -188,6 +187,7 @@ private:
bool _edited;
// note state for read():
// (TODO: Remove and replace with iterator)
typedef std::priority_queue<const Note*,std::vector<const Note*>,
LaterNoteEndComparator> ActiveNotes;

71
libs/ardour/ardour/note.h Normal file
View File

@ -0,0 +1,71 @@
/*
Copyright (C) 2007 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.
*/
#ifndef __ardour_note_h__
#define __ardour_note_h__
#include <stdint.h>
#include <ardour/midi_event.h>
namespace ARDOUR {
/** A MIDI Note.
*
* A note is (unfortunately) special and not just another MidiEvent as it
* has a duration and two separate MIDI events (on and off).
*/
class Note {
public:
Note(double time=0, double dur=0, uint8_t note=0, uint8_t vel=0x40);
Note(const Note& copy);
const Note& operator=(const Note& copy);
inline bool operator==(const Note& other)
{ return time() == other.time() && note() == other.note(); }
inline double time() const { return _on_event.time(); }
inline double end_time() const { return _off_event.time(); }
inline uint8_t note() const { return _on_event.note(); }
inline uint8_t velocity() const { return _on_event.velocity(); }
inline double duration() const { return _off_event.time() - _on_event.time(); }
inline void set_time(double t) { _off_event.time() = t + duration(); _on_event.time() = t; }
inline void set_note(uint8_t n) { _on_event.buffer()[1] = n; _off_event.buffer()[1] = n; }
inline void set_velocity(uint8_t n) { _on_event.buffer()[2] = n; }
inline void set_duration(double d) { _off_event.time() = _on_event.time() + d; }
inline MidiEvent& on_event() { return _on_event; }
inline MidiEvent& off_event() { return _off_event; }
inline const MidiEvent& on_event() const { return _on_event; }
inline const MidiEvent& off_event() const { return _off_event; }
private:
// Event buffers are self-contained
MidiEvent _on_event;
MidiEvent _off_event;
};
} // namespace ARDOUR
#endif /* __ardour_note_h__ */

View File

@ -90,9 +90,10 @@ public:
/** Arbitrary but fixed ordering, so we're comparable (usable in std::map) */
inline bool operator<(const Parameter& id) const {
// FIXME: branch a performance problem? #ifdef DEBUG?
#ifndef NDEBUG
if (_type == NullAutomation)
PBD::warning << "Uninitialized Parameter compared." << endmsg;
#endif
return (_type < id._type || _id < id._id);
}

View File

@ -1077,7 +1077,7 @@ AutomationList::rt_safe_earliest_event_discrete (double start, double end, doubl
build_search_cache_if_necessary(start, end);
pair<const_iterator,const_iterator> range = _search_cache.range;
const pair<const_iterator,const_iterator>& range = _search_cache.range;
if (range.first != _events.end()) {
const ControlEvent* const first = *range.first;

View File

@ -18,8 +18,11 @@
*/
#define __STDC_LIMIT_MACROS 1
#include <iostream>
#include <algorithm>
#include <stdint.h>
#include <pbd/enumwriter.h>
#include <ardour/midi_model.h>
#include <ardour/midi_events.h>
@ -30,72 +33,53 @@
using namespace std;
using namespace ARDOUR;
// Note
MidiModel::Note::Note(double t, double d, uint8_t n, uint8_t v)
: _on_event(t, 3, NULL, true)
, _off_event(t + d, 3, NULL, true)
// Read iterator (const_iterator)
MidiModel::const_iterator::const_iterator(MidiModel& model, double t)
: _model(model)
{
_on_event.buffer()[0] = MIDI_CMD_NOTE_ON;
_on_event.buffer()[1] = n;
_on_event.buffer()[2] = v;
_off_event.buffer()[0] = MIDI_CMD_NOTE_OFF;
_off_event.buffer()[1] = n;
_off_event.buffer()[2] = 0x40;
assert(time() == t);
assert(duration() == d);
assert(note() == n);
assert(velocity() == v);
model.read_lock();
_note_iter = model.notes().end();
for (MidiModel::Notes::iterator i = model.notes().begin(); i != model.notes().end(); ++i) {
if ((*i).time() >= t) {
_note_iter = i;
break;
}
}
MidiControlIterator earliest_control = make_pair(boost::shared_ptr<AutomationList>(), make_pair(DBL_MAX, 0.0));
_control_iters.reserve(model.controls().size());
for (Automatable::Controls::const_iterator i = model.controls().begin();
i != model.controls().end(); ++i) {
double x, y;
i->second->list()->rt_safe_earliest_event(t, DBL_MAX, x, y);
const MidiControlIterator new_iter = make_pair(i->second->list(), make_pair(x, y));
if (x < earliest_control.second.first)
earliest_control = new_iter;
_control_iters.push_back(new_iter);
}
if (_note_iter != model.notes().end())
_event = MidiEvent(_note_iter->on_event(), false);
if (earliest_control.first != 0 && earliest_control.second.first < _event.time())
model.control_to_midi_event(_event, earliest_control);
}
MidiModel::Note::Note(const Note& copy)
: _on_event(copy._on_event, true)
, _off_event(copy._off_event, true)
MidiModel::const_iterator::~const_iterator()
{
/*
assert(copy._on_event.size == 3);
_on_event.buffer = _on_event_buffer;
memcpy(_on_event_buffer, copy._on_event_buffer, 3);
assert(copy._off_event.size == 3);
_off_event.buffer = _off_event_buffer;
memcpy(_off_event_buffer, copy._off_event_buffer, 3);
*/
assert(time() == copy.time());
assert(end_time() == copy.end_time());
assert(note() == copy.note());
assert(velocity() == copy.velocity());
assert(duration() == copy.duration());
_model.read_unlock();
}
const MidiModel::Note&
MidiModel::Note::operator=(const Note& copy)
{
_on_event = copy._on_event;
_off_event = copy._off_event;
/*_on_event.time = copy._on_event.time;
assert(copy._on_event.size == 3);
memcpy(_on_event_buffer, copy._on_event_buffer, 3);
_off_event.time = copy._off_event.time;
assert(copy._off_event.size == 3);
memcpy(_off_event_buffer, copy._off_event_buffer, 3);
*/
assert(time() == copy.time());
assert(end_time() == copy.end_time());
assert(note() == copy.note());
assert(velocity() == copy.velocity());
assert(duration() == copy.duration());
return *this;
}
// MidiModel
MidiModel::MidiModel(Session& s, size_t size)
@ -180,7 +164,106 @@ MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframe
return read_events;
}
bool
MidiModel::control_to_midi_event(MidiEvent& ev, const MidiControlIterator& iter)
{
if (iter.first->parameter().type() == MidiCCAutomation) {
if (!ev.owns_buffer() || ev.size() < 3)
ev = MidiEvent(iter.second.first, 3, (Byte*)malloc(3), true);
assert(iter.first);
assert(iter.first->parameter().id() <= INT8_MAX);
assert(iter.second.second <= INT8_MAX);
ev.buffer()[0] = MIDI_CMD_CONTROL;
ev.buffer()[1] = (Byte)iter.first->parameter().id();
ev.buffer()[2] = (Byte)iter.second.second;
ev.time() = iter.second.first; // x
ev.size() = 3;
return true;
} else {
return false;
}
}
/** Return the earliest MIDI event in the given range.
*
* \return true if \a output has been set to the earliest event in the given range.
*/
#if 0
bool
MidiModel::earliest_note_event(MidiEvent& output, nframes_t start, nframes_t nframes) const
{
/* FIXME: cache last lookup value to avoid O(n) search every time */
const Note* const earliest_on = NULL;
const Note* const earliest_off = NULL;
const MidiEvent* const earliest_cc = NULL;
/* Notes */
if (_note_mode == Sustained) {
for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
if ( ! _active_notes.empty() ) {
const Note* const earliest_off = _active_notes.top();
const MidiEvent& off_ev = earliest_off->off_event();
if (off_ev.time() < start + nframes && off_ev.time() <= n->time()) {
output = off_ev;
//dst.write(off_ev.time() + stamp_offset, off_ev.size(), off_ev.buffer());
_active_notes.pop();
return true;
}
}
if (n->time() >= start + nframes)
break;
// Note on
if (n->time() >= start) {
earliest_on = &n->on_event();
//dst.write(on_ev.time() + stamp_offset, on_ev.size(), on_ev.buffer());
_active_notes.push(&(*n));
return true;
}
}
// Write any trailing note offs
while ( ! _active_notes.empty() ) {
const Note* const earliest_off = _active_notes.top();
const MidiEvent& off_ev = earliest_off->off_event();
if (off_ev.time() < start + nframes) {
dst.write(off_ev.time() + stamp_offset, off_ev.size(), off_ev.buffer());
_active_notes.pop();
++read_events;
} else {
break;
}
}
// Percussive
} else {
for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
// Note on
if (n->time() >= start) {
if (n->time() < start + nframes) {
const MidiEvent& ev = n->on_event();
dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
++read_events;
} else {
break;
}
}
}
}
return read_events;
}
#endif
/** Begin a write of events to the model.
*

89
libs/ardour/note.cc Normal file
View File

@ -0,0 +1,89 @@
/*
Copyright (C) 2007 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.
*/
#include <ardour/note.h>
namespace ARDOUR {
Note::Note(double t, double d, uint8_t n, uint8_t v)
: _on_event(t, 3, NULL, true)
, _off_event(t + d, 3, NULL, true)
{
_on_event.buffer()[0] = MIDI_CMD_NOTE_ON;
_on_event.buffer()[1] = n;
_on_event.buffer()[2] = v;
_off_event.buffer()[0] = MIDI_CMD_NOTE_OFF;
_off_event.buffer()[1] = n;
_off_event.buffer()[2] = 0x40;
assert(time() == t);
assert(duration() == d);
assert(note() == n);
assert(velocity() == v);
}
Note::Note(const Note& copy)
: _on_event(copy._on_event, true)
, _off_event(copy._off_event, true)
{
/*
assert(copy._on_event.size == 3);
_on_event.buffer = _on_event_buffer;
memcpy(_on_event_buffer, copy._on_event_buffer, 3);
assert(copy._off_event.size == 3);
_off_event.buffer = _off_event_buffer;
memcpy(_off_event_buffer, copy._off_event_buffer, 3);
*/
assert(time() == copy.time());
assert(end_time() == copy.end_time());
assert(note() == copy.note());
assert(velocity() == copy.velocity());
assert(duration() == copy.duration());
}
const Note&
Note::operator=(const Note& copy)
{
_on_event = copy._on_event;
_off_event = copy._off_event;
/*_on_event.time = copy._on_event.time;
assert(copy._on_event.size == 3);
memcpy(_on_event_buffer, copy._on_event_buffer, 3);
_off_event.time = copy._off_event.time;
assert(copy._off_event.size == 3);
memcpy(_off_event_buffer, copy._off_event_buffer, 3);
*/
assert(time() == copy.time());
assert(end_time() == copy.end_time());
assert(note() == copy.note());
assert(velocity() == copy.velocity());
assert(duration() == copy.duration());
return *this;
}
} // namespace ARDOUR

View File

@ -398,7 +398,7 @@ SMFSource::write_unlocked (MidiRingBuffer& src, nframes_t cnt)
size_t buf_capacity = 4;
Byte* buf = (Byte*)malloc(buf_capacity);
if (_model && ! _model->currently_writing())
if (_model && ! _model->writing())
_model->start_write();
while (true) {