Line data Source code
1 : #pragma once
2 :
3 : #include <AH/Containers/BitArray.hpp>
4 : #include <AH/STL/utility>
5 : #include <AH/Settings/Warnings.hpp>
6 : #include <MIDI_Parsers/MIDI_MessageTypes.hpp>
7 : #include <Settings/NamespaceSettings.hpp>
8 :
9 : AH_DIAGNOSTIC_WERROR()
10 :
11 : BEGIN_CS_NAMESPACE
12 :
13 : /// Data type for cable numbers.
14 : using cn_t = uint8_t;
15 :
16 : /**
17 : * @addtogroup MIDI_Routing MIDI Routing
18 : * @brief Operators and utilities for MIDI routing.
19 : *
20 : * Allows you to use syntax like:
21 : *
22 : * ~~~cpp
23 : * HardwareSerialMIDI_Interface
24 : * midiA = Serial1, midiB = Serial2, midiC = Serial3;
25 : * MIDI_PipeFactory<3> pipes; // Factory that can produce 3 pipes
26 : *
27 : * midiA >> pipes >> midiB;
28 : * midiC >> pipes >> midiB;
29 : * midiC << pipes << midiB;
30 : * ~~~
31 : *
32 : * Or for bidirectional connections:
33 : *
34 : * ~~~cpp
35 : * HardwareSerialMIDI_Interface
36 : * midiA = Serial1, midiB = Serial2, midiC = Serial3;
37 : * BidirectionalMIDI_PipeFactory<2> pipes; // Factory that can produce 2 pipes
38 : *
39 : * midiA | pipes | midiB;
40 : * midiA | pipes | midiC;
41 : * ~~~
42 : *
43 : * @{
44 : */
45 :
46 : class MIDI_Pipe;
47 : struct TrueMIDI_Sink;
48 : struct TrueMIDI_Source;
49 :
50 : /// Class that can receive MIDI messages from a MIDI pipe.
51 : class MIDI_Sink {
52 : public:
53 : /// Default constructor.
54 315 : MIDI_Sink() = default;
55 :
56 : /// Copy constructor (copying not allowed).
57 : MIDI_Sink(const MIDI_Sink &) = delete;
58 : /// Copy assignment (copying not allowed).
59 : MIDI_Sink &operator=(const MIDI_Sink &) = delete;
60 :
61 : /// Move constructor.
62 : MIDI_Sink(MIDI_Sink &&other);
63 : /// Move assignment.
64 : MIDI_Sink &operator=(MIDI_Sink &&other);
65 :
66 : /// Destructor.
67 : virtual ~MIDI_Sink();
68 :
69 : /// @name Sending data over a MIDI Pipe
70 : /// @{
71 :
72 : /// Accept an incoming MIDI Channel message.
73 : virtual void sinkMIDIfromPipe(ChannelMessage) = 0;
74 : /// Accept an incoming MIDI System Exclusive message.
75 : virtual void sinkMIDIfromPipe(SysExMessage) = 0;
76 : /// Accept an incoming MIDI Real-Time message.
77 : virtual void sinkMIDIfromPipe(RealTimeMessage) = 0;
78 :
79 : /// @}
80 :
81 : /// @name Connecting and disconnecting MIDI Pipes
82 : /// @{
83 :
84 : /// Fully connect a source pipe to this sink.
85 : void connectSourcePipe(MIDI_Pipe *source);
86 : /// Disconnect all source pipes that sink to this sink (recursively).
87 : void disconnectSourcePipes();
88 : /// Disconnect the given source from this sink. Leaves other sources
89 : /// connected.
90 : /// Returns true if the source was found and disconnected, false if the
91 : /// given source was not a direct or indirect source to this sink.
92 : bool disconnect(TrueMIDI_Source &source);
93 : /// Check if this sink is connected to a source pipe.
94 105 : bool hasSourcePipe() const { return sourcePipe != nullptr; }
95 :
96 : #ifndef ARDUINO
97 60 : MIDI_Pipe *getSourcePipe() { return sourcePipe; }
98 : #endif
99 :
100 : /// @}
101 :
102 : private:
103 : /// Base case for recursive lock function.
104 : /// @see MIDI_Pipe::lockDownstream
105 14 : virtual void lockDownstream(cn_t cn, bool lock) { (void)cn, (void)lock; }
106 : /// Base case for recursive function.
107 : /// @see MIDI_Pipe::getFinalSink
108 63 : virtual MIDI_Sink *getFinalSink() { return this; }
109 : /// Disconnect only the first pipe connected to this sink. Leaves the
110 : /// other pipes connected to the original pipe, which doesn't have a sink
111 : /// anymore when this function finishes.
112 : /// Used to disconnect a MIDI_Pipe while preserving the connections of its
113 : /// "through" inputs.
114 : void disconnectSourcePipesShallow();
115 :
116 : protected:
117 315 : MIDI_Pipe *sourcePipe = nullptr;
118 :
119 : friend class MIDI_Pipe;
120 : };
121 :
122 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
123 :
124 : /// Class that can send MIDI messages to a MIDI pipe.
125 : class MIDI_Source {
126 : public:
127 : /// @name Sending data over a MIDI Pipe
128 : /// @{
129 :
130 : /// Send a MIDI Channel Message.
131 : void sourceMIDItoPipe(ChannelMessage);
132 : /// Send a MIDI System Exclusive message.
133 : void sourceMIDItoPipe(SysExMessage);
134 : /// Send a MIDI Real-Time message.
135 : void sourceMIDItoPipe(RealTimeMessage);
136 :
137 : /**
138 : * @brief Enter or exit exclusive mode for the given cable number.
139 : * This means that this becomes the only source that can sink to
140 : * the sinks connected to this source. Other sources have to wait
141 : * until this source exits exclusive mode until they can send
142 : * again.
143 : * @param cn
144 : * Cable number to set the exclusive mode for [0, 15].
145 : * @param exclusive
146 : * True to enable exclusive mode, false to disable.
147 : */
148 : void exclusive(cn_t cn, bool exclusive = true);
149 : /**
150 : * @brief Check if this source can write to the sinks it connects to.
151 : * Returns false if any of the sinks have another source that is
152 : * in exclusive mode.
153 : * @param cn
154 : * Cable number to check [0, 15].
155 : */
156 : bool canWrite(cn_t cn) const;
157 :
158 : /// @}
159 :
160 : /// Default constructor.
161 312 : MIDI_Source() = default;
162 :
163 : /// Copy constructor (copying not allowed).
164 : MIDI_Source(const MIDI_Source &) = delete;
165 : /// Copy assignment (copying not allowed).
166 : MIDI_Source &operator=(const MIDI_Source &) = delete;
167 :
168 : /// Move constructor.
169 : MIDI_Source(MIDI_Source &&other);
170 : /// Move assignment.
171 : MIDI_Source &operator=(MIDI_Source &&other);
172 :
173 : /// Destructor.
174 : virtual ~MIDI_Source();
175 :
176 : /// @name Connecting and disconnecting MIDI Pipes
177 : /// @{
178 :
179 : /// Fully connect a sink pipe to this source.
180 : void connectSinkPipe(MIDI_Pipe *sink);
181 : /// Disconnect all sink pipes that this source sinks to (recursively).
182 : void disconnectSinkPipes();
183 : /// Disconnect the given sink from this source. Leaves other sinks
184 : /// connected.
185 : /// Returns true if the sink was found and disconnected, false if the
186 : /// given sink was not a direct or indirect sink of this source.
187 : bool disconnect(TrueMIDI_Sink &sink);
188 : /// Check if this source is connected to a sink pipe.
189 179 : bool hasSinkPipe() const { return sinkPipe != nullptr; }
190 :
191 : #ifndef ARDUINO
192 60 : MIDI_Pipe *getSinkPipe() { return sinkPipe; }
193 : #endif
194 :
195 : /// @}
196 :
197 : private:
198 : /// Base case for recursive function.
199 : /// @see MIDI_Pipe::getInitialSource
200 63 : virtual MIDI_Source *getInitialSource() { return this; }
201 : /// Disconnect only the first pipe connected to this source. Leaves the
202 : /// other pipes connected to the original pipe, which doesn't have a source
203 : /// anymore when this function finishes.
204 : /// Used to disconnect a MIDI_Pipe while preserving the connections of its
205 : /// "through" outputs.
206 : void disconnectSinkPipesShallow();
207 :
208 : protected:
209 312 : MIDI_Pipe *sinkPipe = nullptr;
210 :
211 : friend class MIDI_Pipe;
212 : };
213 :
214 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
215 :
216 : /// A MIDI_Sink that is not a MIDI_Pipe.
217 362 : struct TrueMIDI_Sink : MIDI_Sink {};
218 : /// A MIDI_Source that is not a MIDI_Pipe.
219 356 : struct TrueMIDI_Source : MIDI_Source {};
220 :
221 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
222 :
223 : /**
224 : * @brief Class that routes MIDI messages from a MIDI_Source to a MIDI_Sink.
225 : *
226 : * A pipe has at least two, at most four connections. In the simplest case, a
227 : * pipe just has a sourca and a sink. The source sends data down the pipe, and
228 : * the pipe sends it to the sink. A mapping or filter can be applied to the
229 : * data traveling down the pipe.
230 : *
231 : * To be able to connect multiple pipes to a single sink or source, a pipe also
232 : * has a "through" output and a "through" input, both are pipes, not sinks or
233 : * sources. All data that comes from the source is sent to the "through" output
234 : * as well, and all input that comes in from the "through" input is sent to the
235 : * sink as well. The mapping or filter is not applied to the data going from/to
236 : * the "through" connections.
237 : *
238 : * Merging data from multiple sources into a single sink can cause problems
239 : * because messages can be interleaved (e.g. RPN/NRPN messages). To circumvent
240 : * this issue, a source can request exclusive access to the pipe it's connected
241 : * to. This locks all other pipes that sink into the same sinks as the exclusive
242 : * source.
243 : * Other sources must then query its pipe before sending, to make sure it's not
244 : * locked by another source that has exclusive access.
245 : *
246 : * **Pipe model**
247 : *
248 : * ~~~
249 : * ╭────────────────> through out
250 : * │ ┌────────┐
251 : * source >━━━━┷━┥ filter ┝━┯━━━> sink
252 : * └────────┘ │
253 : * through in >─────────────────╯
254 : * ~~~
255 : *
256 : * For example, if you have one source that should connect to two sinks, the
257 : * configuration is as follows:
258 : *
259 : * ~~~
260 : * through out × ┌────────┐
261 : * ╭────────────────> >━━━━┷━┥ pipe 2 ┝━┯━━━> sink 2
262 : * │ ┌────────┐ └────────┘ ×
263 : * source >━━━━┷━┥ pipe 1 ┝━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━> sink 1
264 : * └────────┘ ×
265 : * ~~~
266 : *
267 : * Each connection between a source and a sink has its own pipe, and no two
268 : * pipes are connected in series.
269 : */
270 : class MIDI_Pipe : private MIDI_Sink, private MIDI_Source {
271 : public:
272 : /// Default constructor
273 140 : MIDI_Pipe() = default;
274 :
275 : /// Copy constructor (copying not allowed).
276 : MIDI_Pipe(const MIDI_Pipe &) = delete;
277 : /// Copy assignment (copying not allowed).
278 : MIDI_Pipe &operator=(const MIDI_Pipe &) = delete;
279 :
280 : /// Move constructor.
281 : /// @todo Add move constructor.
282 : MIDI_Pipe(MIDI_Pipe &&) = delete;
283 : /// Move assignment.
284 : /// @todo Add move assignment.
285 : MIDI_Pipe &operator=(MIDI_Pipe &&) = delete;
286 :
287 : /// Destructor.
288 : virtual ~MIDI_Pipe();
289 :
290 : private:
291 : /// Set the sink pointer to point to the given sink. Does not connect this
292 : /// pipe to the sink. Initiate the connection from the sink.
293 : void connectSink(MIDI_Sink *sink);
294 : /// Set the sink pointer to null. Does not disconnect this pipe from the
295 : /// sink. Initiate the disconnection from the sink.
296 : void disconnectSink();
297 : /// Set the source pointer to point to the given source. Does not connect
298 : /// this pipe to the source. Initiate the connection from the source.
299 : void connectSource(MIDI_Source *source);
300 : /// Set the source pointer to null. Does not disconnect this pipe from the
301 : /// source. Initiate the disconnection from the source.
302 : void disconnectSource();
303 :
304 : public:
305 : /// Check if this pipe is connected to a sink.
306 961 : bool hasSink() const { return sink != nullptr; }
307 : /// Check if this pipe is connected to a source.
308 785 : bool hasSource() const { return source != nullptr; }
309 : /// Check if this pipe has a "through" output that sends all incoming
310 : /// messages from the input (source) to another pipe.
311 768 : bool hasThroughOut() const { return throughOut != nullptr; }
312 : /// Check if this pipe has a "through" input that merges all messages from
313 : /// another pipe into the output (sink).
314 604 : bool hasThroughIn() const { return throughIn != nullptr; }
315 :
316 : protected:
317 : /// Accept a MIDI message from the source, forward it to the "through"
318 : /// output if necessary, map or filter the MIDI message if necessary,
319 : /// and send it to the sink.
320 115 : void pipeMIDI(ChannelMessage msg) {
321 : // Forward
322 115 : if (hasThroughOut())
323 5 : throughOut->pipeMIDI(msg);
324 115 : mapForwardMIDI(msg);
325 115 : }
326 : /// @copydoc pipeMIDI
327 12 : void pipeMIDI(SysExMessage msg) {
328 12 : if (hasThroughOut())
329 5 : throughOut->pipeMIDI(msg);
330 12 : mapForwardMIDI(msg);
331 12 : }
332 : /// @copydoc pipeMIDI
333 21 : void pipeMIDI(RealTimeMessage msg) {
334 21 : if (hasThroughOut())
335 6 : throughOut->pipeMIDI(msg);
336 21 : mapForwardMIDI(msg);
337 21 : }
338 :
339 : protected:
340 : /// Send the given MIDI message to the sink of this pipe.
341 115 : void sourceMIDItoSink(ChannelMessage msg) {
342 115 : if (hasSink())
343 115 : sink->sinkMIDIfromPipe(msg);
344 115 : }
345 :
346 : /// @copydoc sourceMIDItoSink
347 14 : void sourceMIDItoSink(SysExMessage msg) {
348 14 : if (hasSink())
349 14 : sink->sinkMIDIfromPipe(msg);
350 14 : }
351 :
352 : /// @copydoc sourceMIDItoSink
353 25 : void sourceMIDItoSink(RealTimeMessage msg) {
354 25 : if (hasSink())
355 25 : sink->sinkMIDIfromPipe(msg);
356 25 : }
357 :
358 : private:
359 : /// Function that maps, edits or filters MIDI messages, and then forwards
360 : /// them to the sink of the pipe.
361 112 : virtual void mapForwardMIDI(ChannelMessage msg) {
362 : // Optionally edit the message before passing it on
363 112 : sourceMIDItoSink(msg);
364 112 : }
365 :
366 : /// @copydoc mapForwardMIDI
367 12 : virtual void mapForwardMIDI(SysExMessage msg) {
368 : // Optionally edit the message before passing it on
369 12 : sourceMIDItoSink(msg);
370 12 : }
371 :
372 : /// @copydoc mapForwardMIDI
373 21 : virtual void mapForwardMIDI(RealTimeMessage msg) {
374 : // Optionally edit the message before passing it on
375 21 : sourceMIDItoSink(msg);
376 21 : }
377 :
378 : private:
379 1 : void sinkMIDIfromPipe(ChannelMessage msg) override {
380 : // Called when data from Through In arrives, forward it to the sink
381 1 : sourceMIDItoSink(msg);
382 1 : }
383 2 : void sinkMIDIfromPipe(SysExMessage msg) override {
384 : // Called when data from Through In arrives, forward it to the sink
385 2 : sourceMIDItoSink(msg);
386 2 : }
387 4 : void sinkMIDIfromPipe(RealTimeMessage msg) override {
388 : // Called when data from Through In arrives, forward it to the sink
389 4 : sourceMIDItoSink(msg);
390 4 : }
391 :
392 : private:
393 : /// Lock this pipe and all other pipes further downstream (following the
394 : /// path of the sink). Operates recursively until the end of the
395 : /// chain is reached.
396 8 : void lockDownstream(cn_t cn, bool lock) override {
397 8 : lockSelf(cn, lock);
398 8 : if (hasSink())
399 8 : sink->lockDownstream(cn, lock);
400 8 : }
401 :
402 : /// Lock this pipe and all other pipes further upstream (following the
403 : /// path of the "trough" input). Operates recursively until the end of the
404 : /// chain is reached.
405 12 : void lockUpstream(cn_t cn, bool lock) {
406 12 : lockSelf(cn, lock);
407 12 : if (hasThroughIn())
408 2 : throughIn->lockUpstream(cn, lock);
409 12 : }
410 :
411 : /// Lock this pipe, so sources cannot send messages through it.
412 20 : void lockSelf(cn_t cn, bool lock) { locks.set(cn, lock); }
413 :
414 : public:
415 : /// Disconnect this pipe from all other pipes, sources and sinks. If the
416 : /// "through" input and/or output were in use, they are reconnected to the
417 : /// original sink and/or source respectively.
418 : void disconnect();
419 :
420 : /// Get the sink this pipe eventually sinks to, following the chain
421 : /// recursively.
422 93 : MIDI_Sink *getFinalSink() override {
423 93 : return hasSink() ? sink->getFinalSink() : nullptr;
424 : }
425 : /// Get the original source that sources to this pipe, following the chain
426 : /// recursively.
427 93 : MIDI_Source *getInitialSource() override {
428 93 : return hasSource() ? source->getInitialSource() : nullptr;
429 : }
430 :
431 : /// Disconnect the given sink from this pipe. The sink can be connected
432 : /// directly, or via the "through" output.
433 : /// Returns true if the sink was found and disconnected, false if the given
434 : /// sink was not a direct or indirect sink of this pipe.
435 11 : bool disconnect(TrueMIDI_Sink &sink) {
436 11 : if (getFinalSink() == &sink) {
437 5 : disconnect();
438 5 : return true;
439 : }
440 6 : if (hasThroughOut()) {
441 1 : return throughOut->disconnect(sink);
442 : }
443 5 : return false;
444 11 : }
445 :
446 : /// Disconnect the given source from this pipe. The source can be connected
447 : /// directly, or via the "through" input.
448 : /// Returns true if the source was found and disconnected, false if the
449 : /// given source was not a direct or indirect source to this pipe.
450 11 : bool disconnect(TrueMIDI_Source &source) {
451 11 : if (getInitialSource() == &source) {
452 5 : disconnect();
453 5 : return true;
454 : }
455 6 : if (hasThroughIn()) {
456 1 : return throughIn->disconnect(source);
457 : }
458 5 : return false;
459 11 : }
460 :
461 : #ifdef ARDUINO
462 : protected:
463 : #endif
464 8 : MIDI_Pipe *getThroughOut() { return throughOut; }
465 8 : MIDI_Pipe *getThroughIn() { return throughIn; }
466 4 : MIDI_Source *getSource() { return source; }
467 4 : MIDI_Sink *getSink() { return sink; }
468 :
469 : public:
470 : /// @copydoc MIDI_Source::exclusive
471 : void exclusive(cn_t cn, bool exclusive = true);
472 :
473 : /// Check if this pipe is locked for a given cable number.
474 53 : bool isLocked(cn_t cn) const { return locks.get(cn); }
475 :
476 : /**
477 : * @brief Check if any of the sinks or outputs of this chain of pipes are
478 : * locked for the given cable number.
479 : * @param cn
480 : * The cable number to check.
481 : */
482 53 : bool isAvailableForWrite(cn_t cn) const {
483 97 : return !isLocked(cn) &&
484 44 : (!hasThroughOut() || throughOut->isAvailableForWrite(cn));
485 : }
486 :
487 : private:
488 140 : MIDI_Sink *sink = nullptr;
489 140 : MIDI_Source *source = nullptr;
490 140 : MIDI_Pipe *&throughOut = MIDI_Source::sinkPipe;
491 140 : MIDI_Pipe *&throughIn = MIDI_Sink::sourcePipe;
492 : AH::BitArray<16> locks;
493 :
494 : friend class MIDI_Sink;
495 : friend class MIDI_Source;
496 : };
497 :
498 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
499 :
500 : /// A struct that is both a TrueMIDI_Sink and a TrueMIDI_Source.
501 198 : struct TrueMIDI_SinkSource : TrueMIDI_Sink, TrueMIDI_Source {};
502 :
503 : /// A bidirectional pipe consists of two unidirectional pipes.
504 : using BidirectionalMIDI_Pipe = std::pair<MIDI_Pipe, MIDI_Pipe>;
505 :
506 : /// Connect a source to a pipe (`source >> pipe`).
507 125 : inline MIDI_Pipe &operator>>(TrueMIDI_Source &source, MIDI_Pipe &pipe) {
508 125 : source.connectSinkPipe(&pipe);
509 125 : return pipe;
510 : }
511 :
512 : /// Connect a pipe to a sink (`pipe >> sink`).
513 127 : inline TrueMIDI_Sink &operator>>(MIDI_Pipe &pipe, TrueMIDI_Sink &sink) {
514 127 : sink.connectSourcePipe(&pipe);
515 127 : return sink;
516 : }
517 :
518 : /// Connect a sink to a pipe (`sink << pipe`).
519 68 : inline MIDI_Pipe &operator<<(TrueMIDI_Sink &sink, MIDI_Pipe &pipe) {
520 68 : sink.connectSourcePipe(&pipe);
521 68 : return pipe;
522 : }
523 :
524 : /// Connect a pipe to a source (`pipe << source`).
525 68 : inline TrueMIDI_Source &operator<<(MIDI_Pipe &pipe, TrueMIDI_Source &source) {
526 68 : source.connectSinkPipe(&pipe);
527 68 : return source;
528 : }
529 :
530 : /// Connect a pipe to a sink+source (`pipe | source+sink`).
531 5 : inline TrueMIDI_SinkSource &operator|(BidirectionalMIDI_Pipe &pipe,
532 : TrueMIDI_SinkSource &sinksource) {
533 5 : sinksource.connectSinkPipe(&pipe.first);
534 5 : sinksource.connectSourcePipe(&pipe.second);
535 5 : return sinksource;
536 : }
537 :
538 : /// Connect a sink+source to a pipe (`source+sink | pipe`).
539 5 : inline BidirectionalMIDI_Pipe &operator|(TrueMIDI_SinkSource &sinksource,
540 : BidirectionalMIDI_Pipe &pipe) {
541 5 : sinksource.connectSinkPipe(&pipe.second);
542 5 : sinksource.connectSourcePipe(&pipe.first);
543 5 : return pipe;
544 : }
545 :
546 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
547 :
548 : /**
549 : * @brief Class that produces multiple MIDI_Pipe%s.
550 : *
551 : * @tparam N
552 : * The maximum number of pipes it can produce.
553 : * @tparam Pipe
554 : * The type of pipes to produce.
555 : */
556 : template <size_t N, class Pipe = MIDI_Pipe>
557 116 : struct MIDI_PipeFactory {
558 : Pipe pipes[N];
559 12 : size_t index = 0;
560 :
561 58 : Pipe &getNext() {
562 58 : if (index >= N)
563 1 : FATAL_ERROR(F("Not enough pipes available"), 0x2459);
564 57 : return pipes[index++];
565 1 : }
566 10 : Pipe &operator[](size_t i) { return pipes[i]; }
567 : const Pipe &operator[](size_t i) const { return pipes[i]; }
568 : };
569 :
570 : template <size_t N>
571 : using BidirectionalMIDI_PipeFactory =
572 : MIDI_PipeFactory<N, BidirectionalMIDI_Pipe>;
573 :
574 : template <size_t N, class Pipe>
575 30 : inline MIDI_Pipe &operator>>(TrueMIDI_Source &source,
576 : MIDI_PipeFactory<N, Pipe> &pipe_fact) {
577 30 : return source >> pipe_fact.getNext();
578 : }
579 :
580 : template <size_t N, class Pipe>
581 3 : inline TrueMIDI_Sink &operator>>(MIDI_PipeFactory<N, Pipe> &pipe_fact,
582 : TrueMIDI_Sink &sink) {
583 3 : return pipe_fact.getNext() >> sink;
584 : }
585 :
586 : template <size_t N, class Pipe>
587 25 : inline MIDI_Pipe &operator<<(TrueMIDI_Sink &sink,
588 : MIDI_PipeFactory<N, Pipe> &pipe_fact) {
589 25 : return sink << pipe_fact.getNext();
590 : }
591 :
592 : template <size_t N, class Pipe>
593 : inline TrueMIDI_Source &operator<<(MIDI_PipeFactory<N, Pipe> &pipe_fact,
594 : TrueMIDI_Source &source) {
595 : return pipe_fact.getNext() << source;
596 : }
597 :
598 : template <size_t N>
599 : inline TrueMIDI_SinkSource &
600 : operator|(BidirectionalMIDI_PipeFactory<N> &pipe_fact,
601 : TrueMIDI_SinkSource &sinksource) {
602 : return pipe_fact.getNext() | sinksource;
603 : }
604 :
605 : template <size_t N>
606 : inline BidirectionalMIDI_Pipe &
607 : operator|(TrueMIDI_SinkSource &sinksource,
608 : BidirectionalMIDI_PipeFactory<N> &pipe_fact) {
609 : return sinksource | pipe_fact.getNext();
610 : }
611 :
612 : /// @}
613 :
614 : END_CS_NAMESPACE
615 :
616 : AH_DIAGNOSTIC_POP()
|