Hopefully fix dropped MIDI notes on loop. Fixes #3133.
[ardour.git] / libs / ardour / midi_ring_buffer.cc
1 /*
2     Copyright (C) 2006-2008 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include "pbd/compose.h"
20
21 #include "ardour/debug.h"
22 #include "ardour/midi_ring_buffer.h"
23 #include "ardour/midi_buffer.h"
24 #include "ardour/event_type_map.h"
25
26 using namespace std;
27 using namespace ARDOUR;
28 using namespace PBD;
29
30 /** Read a block of MIDI events from buffer into a MidiBuffer.
31  *
32  * Timestamps of events returned are relative to start (i.e. event with stamp 0
33  * occurred at start), with offset added.
34  */
35 template<typename T>
36 size_t
37 MidiRingBuffer<T>::read(MidiBuffer& dst, nframes_t start, nframes_t end, nframes_t offset)
38 {
39         if (this->read_space() == 0) {
40                 return 0;
41         }
42
43         T                 ev_time;
44         Evoral::EventType ev_type;
45         uint32_t          ev_size;
46
47         /* If we see the end of a loop during this read, we must write the events after it
48            to the MidiBuffer with adjusted times.  The situation is as follows:
49
50            session frames----------------------------->
51            
52                      |                            |                    |
53                 start_of_loop                   start              end_of_loop
54
55            The MidiDiskstream::read method which will have happened before this checks for
56            loops ending, and helpfully inserts a magic LoopEvent into the ringbuffer.  After this,
57            the MidiDiskstream continues to write events with their proper session frame times,
58            so after the LoopEvent event times will go backwards (ie non-monotonically).
59
60            Once we hit end_of_loop, we need to fake it to make it look as though the loop has been
61            immediately repeated.  Say that an event E after the end_of_loop in the ringbuffer
62            has time E_t, which is a time in session frames.  Its offset from the start
63            of the loop will be E_t - start_of_loop.  Its `faked' time will therefore be
64            end_of_loop + E_t - start_of_loop.  And so its port-buffer-relative time (for
65            writing to the MidiBuffer) will be end_of_loop + E_t - start_of_loop - start.
66
67            The subtraction of start is already taken care of, so if we see a LoopEvent, we'll
68            set up loop_offset to equal end_of_loop - start_of_loop, so that given an event
69            time E_t in the ringbuffer we can get the port-buffer-relative time as
70            E_t + offset - start.
71         */
72
73         frameoffset_t loop_offset = 0;
74
75         size_t count = 0;
76
77         while (this->read_space() >= sizeof(T) + sizeof(Evoral::EventType) + sizeof(uint32_t)) {
78
79                 this->full_peek(sizeof(T), (uint8_t*)&ev_time);
80
81                 if (ev_time + loop_offset >= end) {
82                         DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MRB event @ %1 past end @ %2\n", ev_time, end));
83                         break;
84                 } else if (ev_time + loop_offset < start) {
85                         DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MRB event @ %1 before start @ %2\n", ev_time, start));
86                         break;
87                 } else {
88                         DEBUG_TRACE (DEBUG::MidiDiskstreamIO, string_compose ("MRB event @ %1 in range %2 .. %3\n", ev_time, start, end));
89                 }
90
91                 bool success = read_prefix(&ev_time, &ev_type, &ev_size);
92                 if (!success) {
93                         cerr << "WARNING: error reading event prefix from MIDI ring" << endl;
94                         continue;
95                 }
96
97                 // This event marks a loop end (i.e. the next event's timestamp will be non-monotonic)
98                 if (ev_type == LoopEventType) {
99                         cerr << "MRB loop boundary @ " << ev_time << endl;
100                         
101                         assert (ev_size == sizeof (nframes_t));
102                         nframes_t loop_start;
103                         read_contents (ev_size, (uint8_t *) &loop_start);
104
105                         loop_offset = ev_time - loop_start;
106                         continue;
107                 }
108
109                 ev_time += loop_offset;
110
111                 uint8_t status;
112                 success = this->full_peek(sizeof(uint8_t), &status);
113                 assert(success); // If this failed, buffer is corrupt, all hope is lost
114
115                 // Ignore event if it doesn't match channel filter
116                 if (is_channel_event(status) && get_channel_mode() == FilterChannels) {
117                         const uint8_t channel = status & 0x0F;
118                         if (!(get_channel_mask() & (1L << channel))) {
119                                 // cerr << "MRB skipping event due to channel mask" << endl;
120                                 this->skip(ev_size); // Advance read pointer to next event
121                                 continue;
122                         }
123                 }
124
125                 assert(ev_time >= start);
126                 
127                 ev_time -= start;
128                 ev_time += offset;
129
130                 // write the timestamp to address (write_loc - 1)
131                 uint8_t* write_loc = dst.reserve(ev_time, ev_size);
132                 if (write_loc == NULL) {
133                         cerr << "MRB: Unable to reserve space in buffer, event skipped";
134                         this->skip (ev_size); // Advance read pointer to next event
135                         continue;
136                 }
137
138                 // write MIDI buffer contents
139                 success = read_contents (ev_size, write_loc);
140
141 #ifndef NDEBUG
142                 DEBUG_STR_DECL(a);
143                 DEBUG_STR_APPEND(a, string_compose ("wrote MidiEvent to Buffer (time=%1, start=%2 offset=%3)", ev_time, start, offset));
144                 for (size_t i=0; i < ev_size; ++i) {
145                         DEBUG_STR_APPEND(a,hex);
146                         DEBUG_STR_APPEND(a,"0x");
147                         DEBUG_STR_APPEND(a,(int)write_loc[i]);
148                         DEBUG_STR_APPEND(a,' ');
149                 }
150                 DEBUG_STR_APPEND(a,'\n');
151                 DEBUG_TRACE (DEBUG::MidiDiskstreamIO, DEBUG_STR(a).str());
152 #endif
153
154                 if (success) {
155                         if (is_channel_event(status) && get_channel_mode() == ForceChannel) {
156                                 write_loc[0] = (write_loc[0] & 0xF0) | (get_channel_mask() & 0x0F);
157                         }
158                         ++count;
159                 } else {
160                         cerr << "WARNING: error reading event contents from MIDI ring" << endl;
161                 }
162         }
163
164         return count;
165 }
166 template<typename T>
167 void
168 MidiRingBuffer<T>::dump(ostream& str)
169 {
170         size_t rspace;
171
172         if ((rspace = this->read_space()) == 0) {
173                 str << "MRB::dump: empty\n";
174                 return;
175         }
176
177         T                 ev_time;
178         Evoral::EventType ev_type;
179         uint32_t          ev_size;
180         size_t read_ptr = g_atomic_int_get (&this->_read_ptr);
181
182         str << "Dump @ " << read_ptr << endl;
183
184         while (1) {
185                 uint8_t* wp;
186                 uint8_t* data;
187                 size_t write_ptr;
188
189 #define space(r,w) ((w > r) ? (w - r) : ((w - r + this->_size) % this->_size))
190
191                 write_ptr  = g_atomic_int_get (&this->_write_ptr);
192                 if (space (read_ptr, write_ptr) < sizeof (T)) {
193                         break;
194                 }
195
196                 wp = &this->_buf[read_ptr];
197                 memcpy (&ev_time, wp, sizeof (T));
198                 read_ptr = (read_ptr + sizeof (T)) % this->_size;
199                 str << "time " << ev_time;
200
201                 write_ptr  = g_atomic_int_get (&this->_write_ptr);
202                 if (space (read_ptr, write_ptr) < sizeof (ev_type)) {
203                         break;
204                 }
205
206                 wp = &this->_buf[read_ptr];
207                 memcpy (&ev_type, wp, sizeof (ev_type));
208                 read_ptr = (read_ptr + sizeof (ev_type)) % this->_size;
209                 str << " type " << ev_type;
210
211                 write_ptr  = g_atomic_int_get (&this->_write_ptr);
212                 if (space (read_ptr, write_ptr) < sizeof (ev_size)) {
213                         str << "!OUT!\n";
214                         break;
215                 }
216
217                 wp = &this->_buf[read_ptr];
218                 memcpy (&ev_size, wp, sizeof (ev_size));
219                 read_ptr = (read_ptr + sizeof (ev_size)) % this->_size;
220                 str << " size " << ev_size;
221
222                 write_ptr  = g_atomic_int_get (&this->_write_ptr);
223                 if (space (read_ptr, write_ptr) < ev_size) {
224                         str << "!OUT!\n";
225                         break;
226                 }
227
228                 data = new uint8_t[ev_size];
229
230                 wp = &this->_buf[read_ptr];
231                 memcpy (data, wp, ev_size);
232                 read_ptr = (read_ptr + ev_size) % this->_size;
233
234                 for (uint32_t i = 0; i != ev_size; ++i) {
235                         str << ' ' << hex << (int) data[i] << dec;
236                 }
237
238                 str << endl;
239
240                 delete [] data;
241         }
242 }
243
244
245 template class MidiRingBuffer<nframes_t>;
246