eb83d4164511958e954ed07ad875aed398a259bc
[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 this 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                         assert (ev_size == sizeof (framepos_t));
100                         nframes_t loop_start;
101                         read_contents (ev_size, (uint8_t *) &loop_start);
102
103                         loop_offset = ev_time - loop_start;
104                         continue;
105                 }
106
107                 ev_time += loop_offset;
108
109                 uint8_t status;
110                 success = this->full_peek(sizeof(uint8_t), &status);
111                 assert(success); // If this failed, buffer is corrupt, all hope is lost
112
113                 // Ignore event if it doesn't match channel filter
114                 if (is_channel_event(status) && get_channel_mode() == FilterChannels) {
115                         const uint8_t channel = status & 0x0F;
116                         if (!(get_channel_mask() & (1L << channel))) {
117                                 // cerr << "MRB skipping event due to channel mask" << endl;
118                                 this->skip(ev_size); // Advance read pointer to next event
119                                 continue;
120                         }
121                 }
122
123                 assert(ev_time >= start);
124                 
125                 ev_time -= start;
126                 ev_time += offset;
127
128                 // write the timestamp to address (write_loc - 1)
129                 uint8_t* write_loc = dst.reserve(ev_time, ev_size);
130                 if (write_loc == NULL) {
131                         cerr << "MRB: Unable to reserve space in buffer, event skipped";
132                         this->skip (ev_size); // Advance read pointer to next event
133                         continue;
134                 }
135
136                 // write MIDI buffer contents
137                 success = read_contents (ev_size, write_loc);
138
139 #ifndef NDEBUG
140                 DEBUG_STR_DECL(a);
141                 DEBUG_STR_APPEND(a, string_compose ("wrote MidiEvent to Buffer (time=%1, start=%2 offset=%3)", ev_time, start, offset));
142                 for (size_t i=0; i < ev_size; ++i) {
143                         DEBUG_STR_APPEND(a,hex);
144                         DEBUG_STR_APPEND(a,"0x");
145                         DEBUG_STR_APPEND(a,(int)write_loc[i]);
146                         DEBUG_STR_APPEND(a,' ');
147                 }
148                 DEBUG_STR_APPEND(a,'\n');
149                 DEBUG_TRACE (DEBUG::MidiDiskstreamIO, DEBUG_STR(a).str());
150 #endif
151
152                 if (success) {
153                         if (is_channel_event(status) && get_channel_mode() == ForceChannel) {
154                                 write_loc[0] = (write_loc[0] & 0xF0) | (get_channel_mask() & 0x0F);
155                         }
156                         ++count;
157                 } else {
158                         cerr << "WARNING: error reading event contents from MIDI ring" << endl;
159                 }
160         }
161
162         return count;
163 }
164 template<typename T>
165 void
166 MidiRingBuffer<T>::dump(ostream& str)
167 {
168         size_t rspace;
169
170         if ((rspace = this->read_space()) == 0) {
171                 str << "MRB::dump: empty\n";
172                 return;
173         }
174
175         T                 ev_time;
176         Evoral::EventType ev_type;
177         uint32_t          ev_size;
178         size_t read_ptr = g_atomic_int_get (&this->_read_ptr);
179
180         str << "Dump @ " << read_ptr << endl;
181
182         while (1) {
183                 uint8_t* wp;
184                 uint8_t* data;
185                 size_t write_ptr;
186
187 #define space(r,w) ((w > r) ? (w - r) : ((w - r + this->_size) % this->_size))
188
189                 write_ptr  = g_atomic_int_get (&this->_write_ptr);
190                 if (space (read_ptr, write_ptr) < sizeof (T)) {
191                         break;
192                 }
193
194                 wp = &this->_buf[read_ptr];
195                 memcpy (&ev_time, wp, sizeof (T));
196                 read_ptr = (read_ptr + sizeof (T)) % this->_size;
197                 str << "time " << ev_time;
198
199                 write_ptr  = g_atomic_int_get (&this->_write_ptr);
200                 if (space (read_ptr, write_ptr) < sizeof (ev_type)) {
201                         break;
202                 }
203
204                 wp = &this->_buf[read_ptr];
205                 memcpy (&ev_type, wp, sizeof (ev_type));
206                 read_ptr = (read_ptr + sizeof (ev_type)) % this->_size;
207                 str << " type " << ev_type;
208
209                 write_ptr  = g_atomic_int_get (&this->_write_ptr);
210                 if (space (read_ptr, write_ptr) < sizeof (ev_size)) {
211                         str << "!OUT!\n";
212                         break;
213                 }
214
215                 wp = &this->_buf[read_ptr];
216                 memcpy (&ev_size, wp, sizeof (ev_size));
217                 read_ptr = (read_ptr + sizeof (ev_size)) % this->_size;
218                 str << " size " << ev_size;
219
220                 write_ptr  = g_atomic_int_get (&this->_write_ptr);
221                 if (space (read_ptr, write_ptr) < ev_size) {
222                         str << "!OUT!\n";
223                         break;
224                 }
225
226                 data = new uint8_t[ev_size];
227
228                 wp = &this->_buf[read_ptr];
229                 memcpy (data, wp, ev_size);
230                 read_ptr = (read_ptr + ev_size) % this->_size;
231
232                 for (uint32_t i = 0; i != ev_size; ++i) {
233                         str << ' ' << hex << (int) data[i] << dec;
234                 }
235
236                 str << endl;
237
238                 delete [] data;
239         }
240 }
241
242
243 template class MidiRingBuffer<nframes_t>;
244