2008-06-02 17:41:35 -04:00
/*
2009-10-14 12:10:01 -04:00
Copyright ( C ) 2006 Paul Davis
2011-03-14 21:16:24 -04:00
Author : David Robillard
2008-06-02 17:41:35 -04:00
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 .
2009-10-14 12:10:01 -04:00
2008-06-02 17:41:35 -04:00
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 .
2009-10-14 12:10:01 -04:00
2008-06-02 17:41:35 -04:00
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 0213 9 , USA .
*/
# include <cassert>
# include <algorithm>
2009-10-24 09:26:56 -04:00
# include <iostream>
2008-06-02 17:41:35 -04:00
# include <stdlib.h>
2011-04-04 17:48:17 -04:00
# include "pbd/error.h"
2009-10-24 09:26:56 -04:00
# include "evoral/EventList.hpp"
2009-10-24 09:51:33 -04:00
# include "ardour/debug.h"
2009-02-25 13:26:51 -05:00
# include "ardour/types.h"
# include "ardour/configuration.h"
# include "ardour/midi_playlist.h"
# include "ardour/midi_region.h"
# include "ardour/session.h"
# include "ardour/midi_ring_buffer.h"
2008-06-02 17:41:35 -04:00
# include "i18n.h"
using namespace ARDOUR ;
2010-03-01 19:00:00 -05:00
using namespace PBD ;
2008-06-02 17:41:35 -04:00
using namespace std ;
MidiPlaylist : : MidiPlaylist ( Session & session , const XMLNode & node , bool hidden )
2009-10-24 18:39:11 -04:00
: Playlist ( session , node , DataType : : MIDI , hidden )
, _note_mode ( Sustained )
2008-06-02 17:41:35 -04:00
{
2010-06-21 11:26:03 -04:00
# ifndef NDEBUG
2008-06-02 17:41:35 -04:00
const XMLProperty * prop = node . property ( " type " ) ;
assert ( prop & & DataType ( prop - > value ( ) ) = = DataType : : MIDI ) ;
2010-06-21 11:26:03 -04:00
# endif
2008-06-02 17:41:35 -04:00
in_set_state + + ;
2009-10-15 14:56:11 -04:00
set_state ( node , Stateful : : loading_state_version ) ;
2008-06-02 17:41:35 -04:00
in_set_state - - ;
}
MidiPlaylist : : MidiPlaylist ( Session & session , string name , bool hidden )
2009-10-24 18:39:11 -04:00
: Playlist ( session , name , DataType : : MIDI , hidden )
2010-02-23 15:25:53 -05:00
, _note_mode ( Sustained )
2008-06-02 17:41:35 -04:00
{
}
MidiPlaylist : : MidiPlaylist ( boost : : shared_ptr < const MidiPlaylist > other , string name , bool hidden )
2009-10-24 18:39:11 -04:00
: Playlist ( other , name , hidden )
2010-02-23 15:25:53 -05:00
, _note_mode ( other - > _note_mode )
2008-06-02 17:41:35 -04:00
{
}
2010-12-03 17:26:29 -05:00
MidiPlaylist : : MidiPlaylist ( boost : : shared_ptr < const MidiPlaylist > other , framepos_t start , framecnt_t dur , string name , bool hidden )
2010-02-23 15:25:53 -05:00
: Playlist ( other , start , dur , name , hidden )
, _note_mode ( other - > _note_mode )
2008-06-02 17:41:35 -04:00
{
/* this constructor does NOT notify others (session) */
}
MidiPlaylist : : ~ MidiPlaylist ( )
{
}
2009-10-24 09:26:56 -04:00
template < typename Time >
struct EventsSortByTime {
bool operator ( ) ( Evoral : : Event < Time > * a , Evoral : : Event < Time > * b ) {
return a - > time ( ) < b - > time ( ) ;
}
} ;
2008-06-02 17:41:35 -04:00
/** Returns the number of frames in time duration read (eg could be large when 0 events are read) */
2010-12-03 17:26:29 -05:00
framecnt_t
MidiPlaylist : : read ( MidiRingBuffer < framepos_t > & dst , framepos_t start , framecnt_t dur , unsigned chan_n )
2008-06-02 17:41:35 -04:00
{
/* this function is never called from a realtime thread, so
its OK to block ( for short intervals ) .
*/
2009-04-20 17:02:46 -04:00
Glib : : RecMutex : : Lock rm ( region_lock ) ;
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " ++++++ %1 .. %2 +++++++++++++++++++++++++++++++++++++++++++++++ \n " , start , start + dur ) ) ;
2008-06-02 17:41:35 -04:00
2010-12-03 17:26:29 -05:00
framepos_t end = start + dur - 1 ;
2008-06-02 17:41:35 -04:00
_read_data_count = 0 ;
// relevent regions overlapping start <--> end
2009-10-22 10:46:47 -04:00
vector < boost : : shared_ptr < Region > > regs ;
2010-09-17 14:20:37 -04:00
typedef pair < MidiStateTracker * , framepos_t > TrackerInfo ;
2009-10-24 09:26:56 -04:00
vector < TrackerInfo > tracker_info ;
uint32_t note_cnt = 0 ;
2008-06-02 17:41:35 -04:00
for ( RegionList : : iterator i = regions . begin ( ) ; i ! = regions . end ( ) ; + + i ) {
if ( ( * i ) - > coverage ( start , end ) ! = OverlapNone ) {
regs . push_back ( * i ) ;
2009-10-19 13:05:22 -04:00
} else {
NoteTrackers : : iterator t = _note_trackers . find ( ( * i ) . get ( ) ) ;
if ( t ! = _note_trackers . end ( ) ) {
2009-10-24 09:26:56 -04:00
/* add it the set of trackers we will do note resolution
2009-10-24 18:41:27 -04:00
on , and remove it from the list we are keeping
2009-10-24 09:26:56 -04:00
around , because we don ' t need it anymore .
2009-10-25 10:42:46 -04:00
2009-10-26 13:24:08 -04:00
if the end of the region ( where we want to theoretically resolve notes )
2009-10-25 10:42:46 -04:00
is outside the current read range , then just do it at the start
of this read range .
2009-10-24 09:26:56 -04:00
*/
2009-10-24 18:41:27 -04:00
2010-09-17 14:20:37 -04:00
framepos_t resolve_at = ( * i ) - > last_frame ( ) ;
2011-04-21 09:59:32 -04:00
if ( resolve_at < start | | resolve_at > = end ) {
2009-10-25 10:42:46 -04:00
resolve_at = start ;
}
2009-10-26 13:24:08 -04:00
2009-10-25 10:42:46 -04:00
tracker_info . push_back ( TrackerInfo ( t - > second , resolve_at ) ) ;
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " time to resolve & remove tracker for %1 @ %2 \n " , ( * i ) - > name ( ) , resolve_at ) ) ;
2009-10-24 09:26:56 -04:00
note_cnt + = ( t - > second - > on ( ) ) ;
2009-10-19 13:05:22 -04:00
_note_trackers . erase ( t ) ;
}
2008-06-02 17:41:35 -04:00
}
}
2009-10-24 09:26:56 -04:00
if ( note_cnt = = 0 & & ! tracker_info . empty ( ) ) {
/* trackers to dispose of, but they have no notes in them */
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " Clearing %1 empty trackers \n " , tracker_info . size ( ) ) ) ;
2009-10-24 09:26:56 -04:00
for ( vector < TrackerInfo > : : iterator t = tracker_info . begin ( ) ; t ! = tracker_info . end ( ) ; + + t ) {
delete ( * t ) . first ;
}
tracker_info . clear ( ) ;
}
2009-10-24 18:41:27 -04:00
2009-10-24 09:26:56 -04:00
if ( regs . size ( ) = = 1 & & tracker_info . empty ( ) ) {
2009-10-24 18:41:27 -04:00
2009-10-24 09:26:56 -04:00
/* just a single region - read directly into dst */
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " Single region (%1) read, no out-of-bound region tracking info \n " , regs . front ( ) - > name ( ) ) ) ;
2009-10-24 18:41:27 -04:00
2009-10-24 09:26:56 -04:00
boost : : shared_ptr < MidiRegion > mr = boost : : dynamic_pointer_cast < MidiRegion > ( regs . front ( ) ) ;
2008-06-02 17:41:35 -04:00
if ( mr ) {
2009-10-19 13:05:22 -04:00
2009-10-24 09:26:56 -04:00
NoteTrackers : : iterator t = _note_trackers . find ( mr . get ( ) ) ;
2009-10-19 13:05:22 -04:00
MidiStateTracker * tracker ;
2009-10-24 09:26:56 -04:00
bool new_tracker = false ;
2009-10-22 10:46:47 -04:00
2009-10-19 13:05:22 -04:00
if ( t = = _note_trackers . end ( ) ) {
2009-10-24 09:26:56 -04:00
tracker = new MidiStateTracker ;
new_tracker = true ;
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , " \t BEFORE: new tracker \n " ) ;
2009-10-24 09:26:56 -04:00
} else {
tracker = t - > second ;
2010-06-09 09:00:54 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " \t BEFORE: tracker says there are %1 on notes \n " , tracker - > on ( ) ) ) ;
2009-10-24 09:26:56 -04:00
}
2009-10-24 18:41:27 -04:00
2009-10-24 09:26:56 -04:00
mr - > read_at ( dst , start , dur , chan_n , _note_mode , tracker ) ;
2010-06-09 09:00:54 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " \t AFTER: tracker says there are %1 on notes \n " , tracker - > on ( ) ) ) ;
2009-10-24 18:41:27 -04:00
2009-10-24 09:26:56 -04:00
if ( new_tracker ) {
2009-10-19 13:05:22 -04:00
pair < Region * , MidiStateTracker * > newpair ;
2009-10-24 09:26:56 -04:00
newpair . first = mr . get ( ) ;
newpair . second = tracker ;
2009-10-19 13:05:22 -04:00
_note_trackers . insert ( newpair ) ;
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , " \t added tracker to trackers \n " ) ;
2009-10-24 09:26:56 -04:00
}
_read_data_count + = mr - > read_data_count ( ) ;
}
2009-10-24 18:41:27 -04:00
2009-10-24 09:26:56 -04:00
} else {
2009-10-24 18:41:27 -04:00
/* multiple regions and/or note resolution: sort by layer, read into a temporary non-monotonically
2009-10-24 09:26:56 -04:00
sorted EventSink , sort and then insert into dst .
*/
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " %1 regions to read, plus %2 trackers \n " , regs . size ( ) , tracker_info . size ( ) ) ) ;
2009-10-24 18:41:27 -04:00
2010-12-03 17:26:29 -05:00
Evoral : : EventList < framepos_t > evlist ;
2009-10-24 09:26:56 -04:00
for ( vector < TrackerInfo > : : iterator t = tracker_info . begin ( ) ; t ! = tracker_info . end ( ) ; + + t ) {
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " Resolve %1 notes \n " , ( * t ) . first - > on ( ) ) ) ;
2009-10-24 09:26:56 -04:00
( * t ) . first - > resolve_notes ( evlist , ( * t ) . second ) ;
delete ( * t ) . first ;
}
2009-10-24 18:41:27 -04:00
2009-10-25 10:42:46 -04:00
# ifndef NDEBUG
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " After resolution we now have %1 events \n " , evlist . size ( ) ) ) ;
2010-12-03 17:26:29 -05:00
for ( Evoral : : EventList < framepos_t > : : iterator x = evlist . begin ( ) ; x ! = evlist . end ( ) ; + + x ) {
2009-10-25 10:42:46 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " \t %1 \n " , * * x ) ) ;
2009-10-24 18:41:27 -04:00
}
2009-10-25 10:42:46 -04:00
# endif
2009-10-24 18:41:27 -04:00
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " for %1 .. %2 we have %3 to consider \n " , start , start + dur - 1 , regs . size ( ) ) ) ;
2009-10-24 18:41:27 -04:00
2009-10-24 09:26:56 -04:00
for ( vector < boost : : shared_ptr < Region > > : : iterator i = regs . begin ( ) ; i ! = regs . end ( ) ; + + i ) {
boost : : shared_ptr < MidiRegion > mr = boost : : dynamic_pointer_cast < MidiRegion > ( * i ) ;
if ( ! mr ) {
continue ;
}
NoteTrackers : : iterator t = _note_trackers . find ( mr . get ( ) ) ;
MidiStateTracker * tracker ;
bool new_tracker = false ;
2009-10-24 18:41:27 -04:00
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " Before %1 (%2 .. %3) we now have %4 events \n " , mr - > name ( ) , mr - > position ( ) , mr - > last_frame ( ) , evlist . size ( ) ) ) ;
2009-10-24 09:26:56 -04:00
if ( t = = _note_trackers . end ( ) ) {
tracker = new MidiStateTracker ;
new_tracker = true ;
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , " \t BEFORE: new tracker \n " ) ;
2009-10-19 13:05:22 -04:00
} else {
tracker = t - > second ;
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " \t BEFORE: tracker says there are %1 on notes \n " , tracker - > on ( ) ) ) ;
2009-10-19 13:05:22 -04:00
}
2009-10-22 10:46:47 -04:00
2009-10-24 18:41:27 -04:00
2009-10-24 09:26:56 -04:00
mr - > read_at ( evlist , start , dur , chan_n , _note_mode , tracker ) ;
2008-06-02 17:41:35 -04:00
_read_data_count + = mr - > read_data_count ( ) ;
2009-10-24 09:26:56 -04:00
2009-10-25 10:42:46 -04:00
# ifndef NDEBUG
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " After %1 (%2 .. %3) we now have %4 \n " , mr - > name ( ) , mr - > position ( ) , mr - > last_frame ( ) , evlist . size ( ) ) ) ;
2010-12-03 17:26:29 -05:00
for ( Evoral : : EventList < framepos_t > : : iterator x = evlist . begin ( ) ; x ! = evlist . end ( ) ; + + x ) {
2009-10-25 10:42:46 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " \t %1 \n " , * * x ) ) ;
2009-10-24 18:41:27 -04:00
}
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " \t AFTER: tracker says there are %1 on notes \n " , tracker - > on ( ) ) ) ;
2009-10-25 10:42:46 -04:00
# endif
2009-10-24 09:26:56 -04:00
if ( new_tracker ) {
pair < Region * , MidiStateTracker * > newpair ;
newpair . first = mr . get ( ) ;
newpair . second = tracker ;
_note_trackers . insert ( newpair ) ;
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , " \t added tracker to trackers \n " ) ;
2009-10-24 18:41:27 -04:00
}
2009-10-24 09:26:56 -04:00
}
if ( ! evlist . empty ( ) ) {
2009-10-24 18:41:27 -04:00
2009-10-24 09:26:56 -04:00
/* sort the event list */
2010-12-03 17:26:29 -05:00
EventsSortByTime < framepos_t > time_cmp ;
2009-10-24 09:26:56 -04:00
evlist . sort ( time_cmp ) ;
2009-10-24 18:41:27 -04:00
2009-10-25 10:42:46 -04:00
# ifndef NDEBUG
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " Final we now have %1 events \n " , evlist . size ( ) ) ) ;
2010-12-03 17:26:29 -05:00
for ( Evoral : : EventList < framepos_t > : : iterator x = evlist . begin ( ) ; x ! = evlist . end ( ) ; + + x ) {
2009-10-25 10:42:46 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , string_compose ( " \t %1 \n " , * * x ) ) ;
}
# endif
2009-10-24 09:26:56 -04:00
/* write into dst */
2010-12-03 17:26:29 -05:00
for ( Evoral : : EventList < framepos_t > : : iterator e = evlist . begin ( ) ; e ! = evlist . end ( ) ; + + e ) {
Evoral : : Event < framepos_t > * ev ( * e ) ;
2009-10-24 09:26:56 -04:00
dst . write ( ev - > time ( ) , ev - > event_type ( ) , ev - > size ( ) , ev - > buffer ( ) ) ;
delete ev ;
}
2009-10-25 10:42:46 -04:00
2008-06-02 17:41:35 -04:00
}
}
2009-10-24 09:51:33 -04:00
DEBUG_TRACE ( DEBUG : : MidiPlaylistIO , " ------------------------------------------------------------- \n " ) ;
2008-06-02 17:41:35 -04:00
return dur ;
}
2009-10-25 10:42:46 -04:00
void
MidiPlaylist : : clear_note_trackers ( )
{
Glib : : RecMutex : : Lock rm ( region_lock ) ;
for ( NoteTrackers : : iterator n = _note_trackers . begin ( ) ; n ! = _note_trackers . end ( ) ; + + n ) {
delete n - > second ;
}
_note_trackers . clear ( ) ;
}
2008-06-02 17:41:35 -04:00
void
2009-10-19 13:05:22 -04:00
MidiPlaylist : : remove_dependents ( boost : : shared_ptr < Region > region )
2008-06-02 17:41:35 -04:00
{
2009-10-19 13:05:22 -04:00
/* MIDI regions have no dependents (crossfades) but we might be tracking notes */
NoteTrackers : : iterator t = _note_trackers . find ( region . get ( ) ) ;
/* GACK! THREAD SAFETY! */
if ( t ! = _note_trackers . end ( ) ) {
delete t - > second ;
_note_trackers . erase ( t ) ;
}
2008-06-02 17:41:35 -04:00
}
void
2009-07-21 11:55:17 -04:00
MidiPlaylist : : refresh_dependents ( boost : : shared_ptr < Region > /*r*/ )
2008-06-02 17:41:35 -04:00
{
/* MIDI regions have no dependents (crossfades) */
}
void
2009-07-21 11:55:17 -04:00
MidiPlaylist : : finalize_split_region ( boost : : shared_ptr < Region > /*original*/ , boost : : shared_ptr < Region > /*left*/ , boost : : shared_ptr < Region > /*right*/ )
2008-06-02 17:41:35 -04:00
{
/* No MIDI crossfading (yet?), so nothing to do here */
}
void
2009-07-21 11:55:17 -04:00
MidiPlaylist : : check_dependents ( boost : : shared_ptr < Region > /*r*/ , bool /*norefresh*/ )
2008-06-02 17:41:35 -04:00
{
/* MIDI regions have no dependents (crossfades) */
}
int
2009-10-14 20:57:55 -04:00
MidiPlaylist : : set_state ( const XMLNode & node , int version )
2008-06-02 17:41:35 -04:00
{
in_set_state + + ;
freeze ( ) ;
2009-10-15 14:56:11 -04:00
Playlist : : set_state ( node , version ) ;
2008-06-02 17:41:35 -04:00
thaw ( ) ;
in_set_state - - ;
return 0 ;
}
void
MidiPlaylist : : dump ( ) const
{
boost : : shared_ptr < 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 ( boost : : shared_ptr < Region > region )
{
boost : : shared_ptr < MidiRegion > r = boost : : dynamic_pointer_cast < MidiRegion > ( region ) ;
2010-06-23 16:14:07 -04:00
if ( ! r ) {
2008-06-02 17:41:35 -04:00
return false ;
}
2010-06-23 16:14:07 -04:00
bool changed = false ;
2008-06-02 17:41:35 -04:00
{
RegionLock rlock ( this ) ;
RegionList : : iterator i ;
RegionList : : iterator tmp ;
for ( i = regions . begin ( ) ; i ! = regions . end ( ) ; ) {
tmp = i ;
+ + tmp ;
if ( ( * i ) = = region ) {
regions . erase ( i ) ;
changed = true ;
}
i = tmp ;
}
}
if ( changed ) {
/* overload this, it normally means "removed", not destroyed */
notify_region_removed ( region ) ;
}
return changed ;
}
2008-09-29 18:47:40 -04:00
set < Evoral : : Parameter >
2008-06-02 17:41:35 -04:00
MidiPlaylist : : contained_automation ( )
{
/* this function is never called from a realtime thread, so
its OK to block ( for short intervals ) .
*/
2009-04-20 17:02:46 -04:00
Glib : : RecMutex : : Lock rm ( region_lock ) ;
2008-06-02 17:41:35 -04:00
2008-09-29 18:47:40 -04:00
set < Evoral : : Parameter > ret ;
2008-06-02 17:41:35 -04:00
for ( RegionList : : const_iterator r = regions . begin ( ) ; r ! = regions . end ( ) ; + + r ) {
boost : : shared_ptr < MidiRegion > mr = boost : : dynamic_pointer_cast < MidiRegion > ( * r ) ;
2008-09-21 12:17:02 -04:00
for ( Automatable : : Controls : : iterator c = mr - > model ( ) - > controls ( ) . begin ( ) ;
c ! = mr - > model ( ) - > controls ( ) . end ( ) ; + + c ) {
2008-06-02 17:41:35 -04:00
ret . insert ( c - > first ) ;
}
}
return ret ;
}
bool
2010-02-19 13:09:08 -05:00
MidiPlaylist : : region_changed ( const PBD : : PropertyChange & what_changed , boost : : shared_ptr < Region > region )
2008-06-02 17:41:35 -04:00
{
if ( in_flush | | in_set_state ) {
return false ;
}
2010-02-19 13:09:08 -05:00
PBD : : PropertyChange our_interests ;
2011-03-14 21:16:24 -04:00
our_interests . add ( Properties : : midi_data ) ;
2010-02-19 13:09:08 -05:00
2010-09-17 12:24:22 -04:00
bool parent_wants_notify = Playlist : : region_changed ( what_changed , region ) ;
2008-06-02 17:41:35 -04:00
2010-02-19 13:09:08 -05:00
if ( parent_wants_notify | | what_changed . contains ( our_interests ) ) {
2010-01-25 15:34:09 -05:00
notify_contents_changed ( ) ;
2008-06-02 17:41:35 -04:00
}
return true ;
}