1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Rosegarden
5 A MIDI and audio sequencer and musical notation editor.
6 Copyright 2000-2011 the Rosegarden development team.
7
8 Other copyrights also apply to some parts of this work. Please
9 see the AUTHORS file and individual file headers for details.
10
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License as
13 published by the Free Software Foundation; either version 2 of the
14 License, or (at your option) any later version. See the file
15 COPYING included with this distribution for more information.
16 */
17
18 #define RG_MODULE_STRING "[AllocateChannels]"
19 #define RG_NO_DEBUG_PRINT 1
20
21 #include "AllocateChannels.h"
22 #include "Instrument.h"
23 #include "MidiDevice.h"
24 #include "misc/Debug.h"
25 #include "sound/ControlBlock.h"
26
27 #include <algorithm>
28
29 namespace Rosegarden
30 {
31
32 // Allocate a channel interval that encompasses startTime and endTime.
33 // @param startTime is the first instant sound is to be played on the channel.
34 // @param endTime is the last such instant.
35 // @author Tom Breton (Tehom)
36 ChannelInterval
37 FreeChannels::
allocateChannelInterval(RealTime startTime,RealTime endTime,Instrument * instrument,RealTime marginBefore,RealTime marginAfter)38 allocateChannelInterval(RealTime startTime, RealTime endTime,
39 Instrument *instrument,
40 RealTime marginBefore,
41 RealTime marginAfter)
42 {
43 RG_DEBUG << "allocateChannelInterval";
44 iterator bestMatch = end();
45 // Scoring just minimizes wasted space by choosing the smallest
46 // piece that fits.
47
48 // Initialize (leastOverflow, leastDuration) to longer than any
49 // interval can be.
50 RealTime leastDuration = ChannelInterval::m_afterLatestTime;
51 // leastDuration's overflow bit. See comments on thisOverflow and
52 // thisDuration.
53 bool leastOverflow = true;
54
55 // Scan segments backwards from the last one beginning at time
56 // `startTime'
57 if (!empty()) {
58 RG_DEBUG
59 << "Scanning for existing ChannelInterval";
60
61 ChannelInterval dummy(startTime);
62 for (reverse_iterator i(upper_bound(dummy));
63 i != rend();
64 ++i) {
65
66 const ChannelInterval &cs = (*i);
67 RG_DEBUG << "Considering" << cs;
68 cs.assertSane();
69
70 // Consider each end of the proposed interval. An end
71 // fits if either:
72 //
73 // * It is big enough to accomodate the respective margin.
74 //
75 // * It is big enough without the margin and the adjacent
76 // (allocated) channel interval sounds on the same
77 // instrument.
78
79 // Reject complete non-fits early.
80 if (cs.m_start > startTime) {
81 RG_DEBUG << " Rejecting due to free channel's available start time (" << cs.m_start << ") after needed start (" << startTime << ")";
82 continue;
83 }
84 if (cs.m_end < endTime) {
85 RG_DEBUG << " Rejecting due to free channel's available end time (" << cs.m_end << ") before needed end (" << endTime << ")";
86 continue;
87 }
88
89 // Reject if instrument changed and margin is
90 // insufficient. This considers both the given margins
91 // and the adjacent instruments' margins recorded in
92 // ChannelInterval. Its fields m_marginBefore and
93 // m_marginAfter refer to our own before/after
94 // orientation, not to the reversed orientation that the
95 // instruments playing before and after would have.
96 if (cs.m_instrumentBefore &&
97 (cs.m_instrumentBefore != instrument) &&
98 (((cs.m_start + marginBefore) > startTime) ||
99 ((cs.m_start + cs.m_marginBefore) > startTime)))
100 { continue; }
101
102 if (cs.m_instrumentAfter &&
103 (cs.m_instrumentAfter != instrument) &&
104 (((cs.m_end - marginAfter) < endTime) ||
105 ((cs.m_end - cs.m_marginAfter) < endTime)))
106 { continue; }
107
108 // We found an candidate, but is it the best so far? Only
109 // if it wastes less space than all others we've seen,
110 // which is true if it's smaller than them.
111
112 // Be careful of overflow. This calculation ranges from 0
113 // to twice the maximum RealTime can hold. Overflow can
114 // cause us to see huge waste as negative waste, which
115 // results in very inefficient allocation. To avoid this,
116 // we keep an overflow bit (thisOverflow and
117 // leastOverflow) and treat it as the most significant
118 // bit.
119 RealTime thisDuration = cs.m_end - cs.m_start;
120 bool thisOverflow = (thisDuration < RealTime::zeroTime);
121
122 RG_DEBUG << "Found a candidate that takes"
123 << (thisOverflow ? "the maximum plus" : "only")
124 << thisDuration;
125
126 if ((thisOverflow < leastOverflow) ||
127 ((thisOverflow == leastOverflow) &&
128 (thisDuration < leastDuration))) {
129
130 RG_DEBUG << "Best candidate so far";
131 // Decrement so forward iterator refers to the same
132 // element we examined.
133 bestMatch = --(i.base());
134 leastDuration = thisDuration;
135 leastOverflow = thisOverflow;
136 }
137 }
138 }
139 if (bestMatch != end()) {
140 RG_DEBUG << " FreeChannels::allocateChannelInterval() SUCCESS!!!!";
141 return allocateChannelIntervalFrom(bestMatch,
142 startTime, endTime,
143 instrument,
144 marginBefore, marginAfter);
145 } else {
146 RG_DEBUG << " FreeChannels::allocateChannelInterval() giving up. FAIL";
147 // If we found nothing usable, return an unplayable dummy
148 // channel
149 return ChannelInterval();
150 }
151 }
152
153 // Free the given channel interval, setting old's channel to unused.
154 // @author Tom Breton (Tehom)
155 void
156 FreeChannels::
freeChannelInterval(ChannelInterval & old)157 freeChannelInterval(ChannelInterval &old)
158 {
159 if (!old.validChannel()) { return; }
160 RG_DEBUG << "Freeing" << old;
161 // We are sometimes asked to free a zero-length interval. If so,
162 // do nothing.
163 if (old.m_start == old.m_end) { return; }
164 old.assertSane();
165
166 // The first element which is not considered to go before val
167 // (i.e., either it is equivalent or goes after).
168 iterator atOrAfter = lower_bound(ChannelInterval(old.m_start));
169 iterator prevIterator = end();
170 iterator nextIterator = end();
171
172 for (iterator i = begin(); i != atOrAfter; ++i) {
173 const ChannelInterval &cs = (*i);
174 if (cs.getChannelId() == old.getChannelId() &&
175 (cs.m_end == old.m_start)) {
176 prevIterator = i;
177 break;
178 }
179 }
180
181 for (iterator i = atOrAfter; i != end(); ++i) {
182 const ChannelInterval &cs = (*i);
183 if (cs.getChannelId() == old.getChannelId() &&
184 (cs.m_start == old.m_end)) {
185 nextIterator = i;
186 break;
187 }
188 }
189
190 // Figure out the actual endpoints.
191 const ChannelInterval &ciBefore =
192 (prevIterator == end()) ? old : *prevIterator;
193
194 const ChannelInterval &ciAfter =
195 (nextIterator == end()) ? old : *nextIterator;
196
197 const ChannelInterval
198 newChannelInterval(old.getChannelId(),
199 ciBefore.m_start, ciAfter.m_end,
200 ciBefore.m_instrumentBefore, ciAfter.m_instrumentAfter,
201 ciBefore.m_marginBefore, ciAfter.m_marginAfter);
202
203 // Physically remove the adjacent intervals that we are merging
204 // with.
205 if (prevIterator != end()) { erase(prevIterator); }
206 if (nextIterator != end()) { erase(nextIterator); }
207
208 newChannelInterval.assertSane();
209
210 // Add a channelsegment incorporating the whole contiguous time.
211 insert(newChannelInterval);
212
213 old.clearChannelId();
214 }
215
216
217
218 // Allocate a time interval
219 // @param i an iterator indexing a ChannelInterval that includes the
220 // interval from start to end.
221 // @param start is the first instant sound is to be played on the channel.
222 // @param end is the last such instant.
223 // @returns A ChannelInterval, either a suitable one or non-playing.
224 // @author Tom Breton (Tehom)
225 ChannelInterval
226 FreeChannels::
allocateChannelIntervalFrom(iterator i,RealTime start,RealTime end,Instrument * instrument,RealTime marginBefore,RealTime marginAfter)227 allocateChannelIntervalFrom(iterator i, RealTime start, RealTime end,
228 Instrument *instrument,
229 RealTime marginBefore,
230 RealTime marginAfter)
231 {
232 const ChannelInterval cs = (*i);
233
234 erase(i);
235 if (cs.m_start < start) {
236 // There's some length before `start'. Insert a new piece. (We
237 // can't alter it in place because the base class owns it and
238 // makes it const)
239 (void)insert(ChannelInterval(cs.getChannelId(),
240 cs.m_start, start,
241 cs.m_instrumentBefore, instrument,
242 cs.m_marginBefore, marginBefore));
243 } else { }
244
245 if (cs.m_end > end) {
246 // There's some length after `end'. Insert a new piece.
247 (void)insert(ChannelInterval(cs.getChannelId(),
248 end, cs.m_end,
249 instrument, cs.m_instrumentAfter,
250 marginAfter, cs.m_marginAfter));
251 } else {}
252
253 return ChannelInterval(cs.getChannelId(),
254 start, end,
255 nullptr, nullptr,
256 RealTime::zeroTime, RealTime::zeroTime);
257 }
258
259 // Add a channel that may be allocated from. It is caller's
260 // responsibility to not duplicate channel numbers.
261 // @author Tom Breton (Tehom)
262 void
263 FreeChannels::
addChannel(ChannelId channelNb)264 addChannel(ChannelId channelNb)
265 {
266 insert(begin(),
267 ChannelInterval(channelNb,
268 ChannelInterval::m_beforeEarliestTime,
269 ChannelInterval::m_afterLatestTime,
270 nullptr, nullptr,
271 RealTime::zeroTime, RealTime::zeroTime));
272 }
273
274 // Remove channel from being allocated. It is caller's
275 // responsibility to deal with objects currently holding channel
276 // intervals.
277 // @author Tom Breton (Tehom)
278 void
279 FreeChannels::
removeChannel(ChannelId channelNb)280 removeChannel(ChannelId channelNb)
281 {
282 // Filter the channels. We build a temporary container, put
283 // everything that stays into that container, then swap contents.
284 container els;
285 for (iterator i = begin(); i != end(); ++i)
286 if (i->getChannelId() != channelNb) {
287 els.insert(els.end(), *i);
288 }
289 swap(els);
290 }
291
292
293 // Re-allocate a ChannelInterval to encompass start and end.
294 // @author Tom Breton (Tehom)
295 void
296 FreeChannels::
reallocateToFit(ChannelInterval & ci,RealTime start,RealTime end,Instrument * instrument,RealTime marginBefore,RealTime marginAfter)297 reallocateToFit(ChannelInterval &ci, RealTime start, RealTime end,
298 Instrument *instrument,
299 RealTime marginBefore,
300 RealTime marginAfter)
301 {
302 // If it still fits, re-use it.
303 if ((ci.validChannel()) &&
304 (ci.m_start <= start) &&
305 (end <= ci.m_end))
306 { return; }
307
308 // Otherwise just replace it.
309 if (ci.validChannel())
310 { freeChannelInterval(ci); }
311 ci = allocateChannelInterval(start, end, instrument,
312 marginBefore, marginAfter);
313 }
314
315 void
dump()316 FreeChannels::dump()
317 {
318 RG_DEBUG << "FreeChannels::Dump()";
319 for (iterator I = begin(); I != end(); ++I) {
320 RG_DEBUG << " Channel:" << I->getChannelId();
321 RG_DEBUG << " Start:" << I->m_start;
322 RG_DEBUG << " End:" << I->m_end;
323 }
324 }
325
326
327 /*** ChannelSetup definitions ***/
328
329 // This is a stub in case we ever want AllocateChannels to set up
330 // differently for different devices.
331 const ChannelSetup ChannelSetup::MIDI = ChannelSetup();
332
333 /*** AllocateChannels definitions ***/
334
335 // Whether a channelId denotes a percussion channel
336 // @author Tom Breton (Tehom)
337 bool
338 AllocateChannels::
isPercussion(ChannelId channel)339 isPercussion(ChannelId channel)
340 {
341 return MidiDevice::isPercussionNumber(channel);
342 }
343
344 // Whether a ChannelInterval denotes a percussion channel
345 // @author Tom Breton (Tehom)
346 bool
347 AllocateChannels::
isPercussion(ChannelInterval & ci)348 isPercussion(ChannelInterval &ci)
349 {
350 return isPercussion(ci.getChannelId());
351 }
352
353 // Constructor for AllocateChannels
354 // @author Tom Breton (Tehom)
355 AllocateChannels::
AllocateChannels(ChannelSetup)356 AllocateChannels(ChannelSetup /*unused*/) :
357 m_freeChannels()
358 {
359 // Quick and dirty: assume ChannelSetup::MIDI.
360 for (int i = 0; i < 16; ++i) {
361 if (!isPercussion(i)) {
362 (void)m_freeChannels.addChannel(i);
363 }
364 }
365 }
366
367 AllocateChannels::
~AllocateChannels()368 ~AllocateChannels()
369 {
370 RG_DEBUG
371 << "~AllocateChannels";
372 }
373
374 // Re-allocate a ChannelInterval to encompass start and end,
375 // appropriately for Instrument
376 // @author Tom Breton (Tehom)
377 void
378 AllocateChannels::
reallocateToFit(Instrument & instrument,ChannelInterval & ci,RealTime start,RealTime end,RealTime marginBefore,RealTime marginAfter,bool changedInstrument)379 reallocateToFit(Instrument& instrument, ChannelInterval &ci,
380 RealTime start, RealTime end,
381 RealTime marginBefore, RealTime marginAfter,
382 bool changedInstrument)
383 {
384 RG_DEBUG
385 << "reallocateToFit: Reallocating"
386 << (instrument.isPercussion() ? "percussion" : "non-percussion")
387 << instrument.getName() << instrument.getId()
388 << "on bank"
389 << (int)instrument.getMSB() << ":" << (int)instrument.getLSB()
390 << "channel "
391 << ci.getChannelId();
392 // If we already have a channel but it's the wrong type or it
393 // changed instrument, always free it.
394 if (ci.validChannel() &&
395 ((changedInstrument && (end != ChannelInterval::m_latestTime)) ||
396 (instrument.isPercussion() != (isPercussion(ci)))))
397 { freeChannelInterval(ci); }
398
399 if (instrument.isPercussion()) {
400 // For single channel, this implicitly frees+reallocates
401 ci = ChannelInterval(getPercussionChannel(), start, end,
402 nullptr, nullptr,
403 RealTime::zeroTime, RealTime::zeroTime);
404 } else {
405 m_freeChannels.reallocateToFit(ci, start, end,
406 &instrument,
407 marginBefore, marginAfter);
408 }
409
410 RG_DEBUG
411 << "Now channel "
412 << ci.getChannelId();
413 }
414
415 // Free the given channel interval appropriately.
416 // @author Tom Breton (Tehom)
417 void
418 AllocateChannels::
freeChannelInterval(ChannelInterval & old)419 freeChannelInterval(ChannelInterval &old)
420 {
421 if (isPercussion(old)) {
422 old.clearChannelId();
423 }
424 else {
425 m_freeChannels.freeChannelInterval(old);
426 }
427 }
428
429 // Reserve a channel for a fixed-channel instrument.
430 // The signal connections this uses are made by ChannelManager.
431 // @author Tom Breton (Tehom)
432 void
433 AllocateChannels::
reserveFixedChannel(ChannelId channel)434 reserveFixedChannel(ChannelId channel)
435 {
436 reserveChannel(channel, m_fixedChannels);
437
438 // If m_thruChannels has it, release it from there too. Fixed
439 // channels have priority over thru channels because their channel
440 // numbers are chosen by the user. We do this last to be very
441 // sure we're in a nice clean state when ControlBlock looks for
442 // another thru channel.
443 FixedChannelSet::iterator i = m_thruChannels.find(channel);
444 if (i != m_thruChannels.end())
445 {
446 // This statement is effectively releaseThruChannel, since we
447 // already found the channel in m_thruChannels and we know
448 // it's not going into m_freeChannels.
449 m_thruChannels.erase(i);
450
451 // Kick ControlBlock off this channel. It will allocate
452 // another.
453 ControlBlock::getInstance()->vacateThruChannel(channel);
454 }
455 }
456
457 ChannelId
458 AllocateChannels::
reallocateThruChannel(Instrument & instrument,ChannelId channel)459 reallocateThruChannel(Instrument& instrument, ChannelId channel)
460 {
461 // If we already have a valid channel and it has the right
462 // percussion-ness, we're done.
463 if (channel >= 0) {
464 bool isInstrumentPercussion = instrument.isPercussion();
465 bool isChannelPercussion = isPercussion(channel);
466 if (isInstrumentPercussion == isChannelPercussion) { return channel; }
467 }
468
469 // Otherwise, out with the old, in with the new.
470 releaseThruChannel(channel);
471 return allocateThruChannel(instrument);
472 }
473
474
475 // Allocate a channel for MIDI playthru
476 // The signal connections this uses are made by ChannelManager.
477 // @author Tom Breton (Tehom)
478 ChannelId
479 AllocateChannels::
allocateThruChannel(Instrument & instrument)480 allocateThruChannel(Instrument& instrument)
481 {
482 if (instrument.isPercussion()) { return getPercussionChannel(); }
483
484 // Quick and dirty: assume ChannelSetup::MIDI. We inspect
485 // channels highest-first because they tend to be used
486 // lowest-first and we'd prefer not to collide.
487 for (int channel = 15; channel >= 0; --channel) {
488 // Avoid channels we've already reserved.
489 if (m_thruChannels.find(channel) != m_thruChannels.end())
490 { continue; }
491
492 // Avoid any channel in m_fixedChannels.
493 if (m_fixedChannels.find(channel) != m_fixedChannels.end())
494 { continue; }
495
496 // We don't try to find a good channel, we just accept the
497 // first candidate.
498 reserveChannel(channel, m_thruChannels);
499 return channel;
500 }
501
502 // Fixed channels has all the channels, so return invalid channel.
503 return -1;
504 }
505
506 // Release a reserved channel from channelSet
507 // @author Tom Breton (Tehom)
508 void
509 AllocateChannels::
releaseReservedChannel(ChannelId channel,FixedChannelSet & channelSet)510 releaseReservedChannel(ChannelId channel, FixedChannelSet& channelSet)
511 {
512 // Releasing an invalid channel does nothing.
513 if (channel < 0) { return; }
514 // Releasing the percussion channel does nothing.
515 if (isPercussion(channel)) { return; }
516
517 FixedChannelSet::iterator i = channelSet.find(channel);
518
519 if (i == channelSet.end()) { return; }
520 RG_DEBUG
521 << "AllocateChannels: releaseFixedChannel releasing"
522 << (int) channel;
523
524 // Remove from reserved channels.
525 channelSet.erase(i);
526
527 // Add channel to FreeChannels (if not percussion)
528 if (!isPercussion(channel)) {
529 m_freeChannels.addChannel(channel);
530 }
531 }
532
533 // Reserve a channel with regard to channelSet.
534 // @author Tom Breton (Tehom)
535 void
536 AllocateChannels::
reserveChannel(ChannelId channel,FixedChannelSet & channelSet)537 reserveChannel(ChannelId channel, FixedChannelSet& channelSet)
538 {
539 // Remove channel from FreeChannels (if not percussion) so we
540 // don't give anything an interval on this channel.
541 if (!isPercussion(channel)) {
542 RG_DEBUG
543 << "AllocateChannels: reserveFixedChannel reserving"
544 << (int) channel;
545 m_freeChannels.removeChannel(channel);
546 }
547 // Record that this channel is reserved.
548 channelSet.insert(channel);
549
550 // Kick ChannelManagers off the channel. They'll get a new
551 // channel; that's not a concern here.
552 emit sigVacateChannel(channel);
553 }
554
555 }
556
557