FastLED 3.9.7
Loading...
Searching...
No Matches
MIDI.hpp
Go to the documentation of this file.
1
28#pragma once
29
30BEGIN_MIDI_NAMESPACE
31
33template<class Transport, class Settings, class Platform>
35 : mTransport(inTransport)
36 , mInputChannel(0)
37 , mRunningStatus_RX(InvalidType)
38 , mRunningStatus_TX(InvalidType)
39 , mPendingMessageExpectedLength(0)
40 , mPendingMessageIndex(0)
41 , mCurrentRpnNumber(0xffff)
42 , mCurrentNrpnNumber(0xffff)
43 , mThruActivated(true)
44 , mThruFilterMode(Thru::Full)
45 , mLastMessageSentTime(0)
46 , mLastMessageReceivedTime(0)
47 , mSenderActiveSensingPeriodicity(0)
48 , mReceiverActiveSensingActivated(false)
49 , mLastError(0)
50{
51 mSenderActiveSensingPeriodicity = Settings::SenderActiveSensingPeriodicity;
52}
53
58template<class Transport, class Settings, class Platform>
62
63// -----------------------------------------------------------------------------
64
71template<class Transport, class Settings, class Platform>
73{
74 // Initialise the Transport layer
75 mTransport.begin();
76
77 mInputChannel = inChannel;
78 mRunningStatus_TX = InvalidType;
79 mRunningStatus_RX = InvalidType;
80
81 mPendingMessageIndex = 0;
82 mPendingMessageExpectedLength = 0;
83
84 mCurrentRpnNumber = 0xffff;
85 mCurrentNrpnNumber = 0xffff;
86
87 mLastMessageSentTime = Platform::now();
88
89 mMessage.valid = false;
90 mMessage.type = InvalidType;
91 mMessage.channel = 0;
92 mMessage.data1 = 0;
93 mMessage.data2 = 0;
94 mMessage.length = 0;
95
96 mThruFilterMode = Thru::Full;
97 mThruActivated = mTransport.thruActivated;
98}
99
100// -----------------------------------------------------------------------------
101// Output
102// -----------------------------------------------------------------------------
103
117template<class Transport, class Settings, class Platform>
119{
120 if (!inMessage.valid)
121 return;
122
123 if (mTransport.beginTransmission(inMessage.type))
124 {
125 const StatusByte status = getStatus(inMessage.type, inMessage.channel);
126 mTransport.write(status);
127
128 if (inMessage.type != MidiType::SystemExclusive)
129 {
130 if (inMessage.length > 1) mTransport.write(inMessage.data1);
131 if (inMessage.length > 2) mTransport.write(inMessage.data2);
132 } else
133 {
134 // sysexArray does not contain the start and end tags
135 mTransport.write(MidiType::SystemExclusiveStart);
136
137 for (size_t i = 0; i < inMessage.getSysExSize(); i++)
138 mTransport.write(inMessage.sysexArray[i]);
139
140 mTransport.write(MidiType::SystemExclusiveEnd);
141 }
142 }
143 mTransport.endTransmission();
144 UpdateLastSentTime();
145}
146
147
159template<class Transport, class Settings, class Platform>
161 DataByte inData1,
162 DataByte inData2,
163 Channel inChannel)
164{
165 if (inType <= PitchBend) // Channel messages
166 {
167 // Then test if channel is valid
168 if (inChannel >= MIDI_CHANNEL_OFF ||
169 inChannel == MIDI_CHANNEL_OMNI ||
170 inType < 0x80)
171 {
172 return; // Don't send anything
173 }
174 // Protection: remove MSBs on data
175 inData1 &= 0x7f;
176 inData2 &= 0x7f;
177
178 const StatusByte status = getStatus(inType, inChannel);
179
180 if (mTransport.beginTransmission(inType))
181 {
182 if (Settings::UseRunningStatus)
183 {
184 if (mRunningStatus_TX != status)
185 {
186 // New message, memorise and send header
187 mRunningStatus_TX = status;
188 mTransport.write(mRunningStatus_TX);
189 }
190 }
191 else
192 {
193 // Don't care about running status, send the status byte.
194 mTransport.write(status);
195 }
196
197 // Then send data
198 mTransport.write(inData1);
199 if (inType != ProgramChange && inType != AfterTouchChannel)
200 {
201 mTransport.write(inData2);
202 }
203
204 mTransport.endTransmission();
205 UpdateLastSentTime();
206 }
207 }
208 else if (inType >= Clock && inType <= SystemReset)
209 {
210 sendRealTime(inType); // System Real-time and 1 byte.
211 }
212}
213
214// -----------------------------------------------------------------------------
215
225template<class Transport, class Settings, class Platform>
227 DataByte inVelocity,
228 Channel inChannel)
229{
230 send(NoteOn, inNoteNumber, inVelocity, inChannel);
231}
232
244template<class Transport, class Settings, class Platform>
246 DataByte inVelocity,
247 Channel inChannel)
248{
249 send(NoteOff, inNoteNumber, inVelocity, inChannel);
250}
251
256template<class Transport, class Settings, class Platform>
258 Channel inChannel)
259{
260 send(ProgramChange, inProgramNumber, 0, inChannel);
261}
262
269template<class Transport, class Settings, class Platform>
271 DataByte inControlValue,
272 Channel inChannel)
273{
274 send(ControlChange, inControlNumber, inControlValue, inChannel);
275}
276
284template<class Transport, class Settings, class Platform>
286 DataByte inPressure,
287 Channel inChannel)
288{
289 send(AfterTouchPoly, inNoteNumber, inPressure, inChannel);
290}
291
296template<class Transport, class Settings, class Platform>
298 Channel inChannel)
299{
300 send(AfterTouchChannel, inPressure, 0, inChannel);
301}
302
309template<class Transport, class Settings, class Platform>
311 DataByte inPressure,
312 Channel inChannel)
313{
314 send(AfterTouchPoly, inNoteNumber, inPressure, inChannel);
315}
316
323template<class Transport, class Settings, class Platform>
325 Channel inChannel)
326{
327 const unsigned bend = unsigned(inPitchValue - int(MIDI_PITCHBEND_MIN));
328 send(PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, inChannel);
329}
330
331
338template<class Transport, class Settings, class Platform>
340 Channel inChannel)
341{
342 const int scale = inPitchValue > 0.0 ? MIDI_PITCHBEND_MAX : MIDI_PITCHBEND_MIN;
343 const int value = int(inPitchValue * double(scale));
344 sendPitchBend(value, inChannel);
345}
346
356template<class Transport, class Settings, class Platform>
358 const byte* inArray,
359 bool inArrayContainsBoundaries)
360{
361 const bool writeBeginEndBytes = !inArrayContainsBoundaries;
362
363 if (mTransport.beginTransmission(MidiType::SystemExclusiveStart))
364 {
365 if (writeBeginEndBytes)
366 mTransport.write(MidiType::SystemExclusiveStart);
367
368 for (unsigned i = 0; i < inLength; ++i)
369 mTransport.write(inArray[i]);
370
371 if (writeBeginEndBytes)
372 mTransport.write(MidiType::SystemExclusiveEnd);
373
374 mTransport.endTransmission();
375 UpdateLastSentTime();
376 }
377
378 if (Settings::UseRunningStatus)
379 mRunningStatus_TX = InvalidType;
380}
381
387template<class Transport, class Settings, class Platform>
392
399template<class Transport, class Settings, class Platform>
401 DataByte inValuesNibble)
402{
403 const byte data = byte((((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0f)));
404 sendTimeCodeQuarterFrame(data);
405}
406
413template<class Transport, class Settings, class Platform>
418
422template<class Transport, class Settings, class Platform>
424{
425 sendCommon(SongPosition, inBeats);
426}
427
429template<class Transport, class Settings, class Platform>
431{
432 sendCommon(SongSelect, inSongNumber);
433}
434
442template<class Transport, class Settings, class Platform>
444{
445 switch (inType)
446 {
448 case SongPosition:
449 case SongSelect:
450 case TuneRequest:
451 break;
452 default:
453 // Invalid Common marker
454 return;
455 }
456
457 if (mTransport.beginTransmission(inType))
458 {
459 mTransport.write((byte)inType);
460 switch (inType)
461 {
463 mTransport.write(inData1);
464 break;
465 case SongPosition:
466 mTransport.write(inData1 & 0x7f);
467 mTransport.write((inData1 >> 7) & 0x7f);
468 break;
469 case SongSelect:
470 mTransport.write(inData1 & 0x7f);
471 break;
472 case TuneRequest:
473 break;
474 default:
475 break; // LCOV_EXCL_LINE - Coverage blind spot
476 }
477 mTransport.endTransmission();
478 UpdateLastSentTime();
479 }
480
481 if (Settings::UseRunningStatus)
482 mRunningStatus_TX = InvalidType;
483}
484
491template<class Transport, class Settings, class Platform>
493{
494 // Do not invalidate Running Status for real-time messages
495 // as they can be interleaved within any message.
496
497 switch (inType)
498 {
499 case Clock:
500 case Start:
501 case Stop:
502 case Continue:
503 case ActiveSensing:
504 case SystemReset:
505 if (mTransport.beginTransmission(inType))
506 {
507 mTransport.write((byte)inType);
508 mTransport.endTransmission();
509 UpdateLastSentTime();
510 }
511 break;
512 default:
513 // Invalid Real Time marker
514 break;
515 }
516}
517
522template<class Transport, class Settings, class Platform>
524 Channel inChannel)
525{
526 if (mCurrentRpnNumber != inNumber)
527 {
528 const byte numMsb = 0x7f & (inNumber >> 7);
529 const byte numLsb = 0x7f & inNumber;
530 sendControlChange(RPNLSB, numLsb, inChannel);
531 sendControlChange(RPNMSB, numMsb, inChannel);
532 mCurrentRpnNumber = inNumber;
533 }
534}
535
540template<class Transport, class Settings, class Platform>
542 Channel inChannel)
543{;
544 const byte valMsb = 0x7f & (inValue >> 7);
545 const byte valLsb = 0x7f & inValue;
546 sendControlChange(DataEntryMSB, valMsb, inChannel);
547 sendControlChange(DataEntryLSB, valLsb, inChannel);
548}
549
555template<class Transport, class Settings, class Platform>
557 byte inLsb,
558 Channel inChannel)
559{
560 sendControlChange(DataEntryMSB, inMsb, inChannel);
561 sendControlChange(DataEntryLSB, inLsb, inChannel);
562}
563
564/* \brief Increment the value of the currently selected RPN number by the specified amount.
565 \param inAmount The amount to add to the currently selected RPN value.
566*/
567template<class Transport, class Settings, class Platform>
569 Channel inChannel)
570{
571 sendControlChange(DataIncrement, inAmount, inChannel);
572}
573
574/* \brief Decrement the value of the currently selected RPN number by the specified amount.
575 \param inAmount The amount to subtract to the currently selected RPN value.
576*/
577template<class Transport, class Settings, class Platform>
579 Channel inChannel)
580{
581 sendControlChange(DataDecrement, inAmount, inChannel);
582}
583
588template<class Transport, class Settings, class Platform>
590{
591 sendControlChange(RPNLSB, 0x7f, inChannel);
592 sendControlChange(RPNMSB, 0x7f, inChannel);
593 mCurrentRpnNumber = 0xffff;
594}
595
596
597
602template<class Transport, class Settings, class Platform>
604 Channel inChannel)
605{
606 if (mCurrentNrpnNumber != inNumber)
607 {
608 const byte numMsb = 0x7f & (inNumber >> 7);
609 const byte numLsb = 0x7f & inNumber;
610 sendControlChange(NRPNLSB, numLsb, inChannel);
611 sendControlChange(NRPNMSB, numMsb, inChannel);
612 mCurrentNrpnNumber = inNumber;
613 }
614}
615
620template<class Transport, class Settings, class Platform>
622 Channel inChannel)
623{;
624 const byte valMsb = 0x7f & (inValue >> 7);
625 const byte valLsb = 0x7f & inValue;
626 sendControlChange(DataEntryMSB, valMsb, inChannel);
627 sendControlChange(DataEntryLSB, valLsb, inChannel);
628}
629
635template<class Transport, class Settings, class Platform>
637 byte inLsb,
638 Channel inChannel)
639{
640 sendControlChange(DataEntryMSB, inMsb, inChannel);
641 sendControlChange(DataEntryLSB, inLsb, inChannel);
642}
643
644/* \brief Increment the value of the currently selected NRPN number by the specified amount.
645 \param inAmount The amount to add to the currently selected NRPN value.
646*/
647template<class Transport, class Settings, class Platform>
649 Channel inChannel)
650{
651 sendControlChange(DataIncrement, inAmount, inChannel);
652}
653
654/* \brief Decrement the value of the currently selected NRPN number by the specified amount.
655 \param inAmount The amount to subtract to the currently selected NRPN value.
656*/
657template<class Transport, class Settings, class Platform>
659 Channel inChannel)
660{
661 sendControlChange(DataDecrement, inAmount, inChannel);
662}
663
668template<class Transport, class Settings, class Platform>
670{
671 sendControlChange(NRPNLSB, 0x7f, inChannel);
672 sendControlChange(NRPNMSB, 0x7f, inChannel);
673 mCurrentNrpnNumber = 0xffff;
674}
675
676 // End of doc group MIDI Output
677
678// -----------------------------------------------------------------------------
679
680template<class Transport, class Settings, class Platform>
682 Channel inChannel) const
683{
684 return StatusByte(((byte)inType | ((inChannel - 1) & 0x0f)));
685}
686
687// -----------------------------------------------------------------------------
688// Input
689// -----------------------------------------------------------------------------
690
703template<class Transport, class Settings, class Platform>
705{
706 return read(mInputChannel);
707}
708
711template<class Transport, class Settings, class Platform>
713{
714 #ifndef RegionActiveSending
715 // Active Sensing. This message is intended to be sent
716 // repeatedly to tell the receiver that a connection is alive. Use
717 // of this message is optional. When initially received, the
718 // receiver will expect to receive another Active Sensing
719 // message each 300ms (max), and if it does not then it will
720 // assume that the connection has been terminated. At
721 // termination, the receiver will turn off all voices and return to
722 // normal (non- active sensing) operation.
723 if (Settings::UseSenderActiveSensing && (mSenderActiveSensingPeriodicity > 0) && (Platform::now() - mLastMessageSentTime) > mSenderActiveSensingPeriodicity)
724 {
725 sendActiveSensing();
726 mLastMessageSentTime = Platform::now();
727 }
728
729 if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated && (mLastMessageReceivedTime + ActiveSensingTimeout < Platform::now()))
730 {
731 mReceiverActiveSensingActivated = false;
732
733 mLastError |= 1UL << ErrorActiveSensingTimeout; // set the ErrorActiveSensingTimeout bit
734 if (mErrorCallback)
735 mErrorCallback(mLastError);
736 }
737 #endif
738
739 if (inChannel >= MIDI_CHANNEL_OFF)
740 return false; // MIDI Input disabled.
741
742 if (!parse())
743 return false;
744
745 #ifndef RegionActiveSending
746
747 if (Settings::UseReceiverActiveSensing && mMessage.type == ActiveSensing)
748 {
749 // When an ActiveSensing message is received, the time keeping is activated.
750 // When a timeout occurs, an error message is send and time keeping ends.
751 mReceiverActiveSensingActivated = true;
752
753 // is ErrorActiveSensingTimeout bit in mLastError on
754 if (mLastError & (1 << (ErrorActiveSensingTimeout - 1)))
755 {
756 mLastError &= ~(1UL << ErrorActiveSensingTimeout); // clear the ErrorActiveSensingTimeout bit
757 if (mErrorCallback)
758 mErrorCallback(mLastError);
759 }
760 }
761
762 // Keep the time of the last received message, so we can check for the timeout
763 if (Settings::UseReceiverActiveSensing && mReceiverActiveSensingActivated)
764 mLastMessageReceivedTime = Platform::now();
765
766 #endif
767
768 handleNullVelocityNoteOnAsNoteOff();
769
770 const bool channelMatch = inputFilter(inChannel);
771 if (channelMatch)
772 launchCallback();
773
774 thruFilter(inChannel);
775
776 return channelMatch;
777}
778
779// -----------------------------------------------------------------------------
780
781// Private method: MIDI parser
782template<class Transport, class Settings, class Platform>
784{
785 if (mTransport.available() == 0)
786 return false; // No data available.
787
788 // clear the ErrorParse bit
789 mLastError &= ~(1UL << ErrorParse);
790
791 // Parsing algorithm:
792 // Get a byte from the serial buffer.
793 // If there is no pending message to be recomposed, start a new one.
794 // - Find type and channel (if pertinent)
795 // - Look for other bytes in buffer, call parser recursively,
796 // until the message is assembled or the buffer is empty.
797 // Else, add the extracted byte to the pending message, and check validity.
798 // When the message is done, store it.
799
800 const byte extracted = mTransport.read();
801
802 // Ignore Undefined
803 if (extracted == Undefined_FD)
804 return (Settings::Use1ByteParsing) ? false : parse();
805
806 if (mPendingMessageIndex == 0)
807 {
808 // Start a new pending message
809 mPendingMessage[0] = extracted;
810
811 // Check for running status first
812 if (isChannelMessage(getTypeFromStatusByte(mRunningStatus_RX)))
813 {
814 // Only these types allow Running Status
815
816 // If the status byte is not received, prepend it
817 // to the pending message
818 if (extracted < 0x80)
819 {
820 mPendingMessage[0] = mRunningStatus_RX;
821 mPendingMessage[1] = extracted;
822 mPendingMessageIndex = 1;
823 }
824 // Else: well, we received another status byte,
825 // so the running status does not apply here.
826 // It will be updated upon completion of this message.
827 }
828
829 const MidiType pendingType = getTypeFromStatusByte(mPendingMessage[0]);
830
831 switch (pendingType)
832 {
833 // 1 byte messages
834 case Start:
835 case Continue:
836 case Stop:
837 case Clock:
838 case Tick:
839 case ActiveSensing:
840 case SystemReset:
841 case TuneRequest:
842 // Handle the message type directly here.
843 mMessage.type = pendingType;
844 mMessage.channel = 0;
845 mMessage.data1 = 0;
846 mMessage.data2 = 0;
847 mMessage.valid = true;
848
849 // Do not reset all input attributes, Running Status must remain unchanged.
850 // We still need to reset these
851 mPendingMessageIndex = 0;
852 mPendingMessageExpectedLength = 0;
853
854 return true;
855 break;
856
857 // 2 bytes messages
858 case ProgramChange:
861 case SongSelect:
862 mPendingMessageExpectedLength = 2;
863 break;
864
865 // 3 bytes messages
866 case NoteOn:
867 case NoteOff:
868 case ControlChange:
869 case PitchBend:
870 case AfterTouchPoly:
871 case SongPosition:
872 mPendingMessageExpectedLength = 3;
873 break;
874
877 // The message can be any length
878 // between 3 and MidiMessage::sSysExMaxSize bytes
879 mPendingMessageExpectedLength = MidiMessage::sSysExMaxSize;
880 mRunningStatus_RX = InvalidType;
881 mMessage.sysexArray[0] = pendingType;
882 break;
883
884 case InvalidType:
885 default:
886 // This is obviously wrong. Let's get the hell out'a here.
887 mLastError |= 1UL << ErrorParse; // set the ErrorParse bit
888 if (mErrorCallback)
889 mErrorCallback(mLastError); // LCOV_EXCL_LINE
890
891 resetInput();
892 return false;
893 break;
894 }
895
896 if (mPendingMessageIndex >= (mPendingMessageExpectedLength - 1))
897 {
898 // Reception complete
899 mMessage.type = pendingType;
900 mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]);
901 mMessage.data1 = mPendingMessage[1];
902 mMessage.data2 = 0; // Completed new message has 1 data byte
903 mMessage.length = 1;
904
905 mPendingMessageIndex = 0;
906 mPendingMessageExpectedLength = 0;
907 mMessage.valid = true;
908
909 return true;
910 }
911 else
912 {
913 // Waiting for more data
914 mPendingMessageIndex++;
915 }
916
917 return (Settings::Use1ByteParsing) ? false : parse();
918 }
919 else
920 {
921 // First, test if this is a status byte
922 if (extracted >= 0x80)
923 {
924 // Reception of status bytes in the middle of an uncompleted message
925 // are allowed only for interleaved Real Time message or EOX
926 switch (extracted)
927 {
928 case Clock:
929 case Start:
930 case Tick:
931 case Continue:
932 case Stop:
933 case ActiveSensing:
934 case SystemReset:
935
936 // Here we will have to extract the one-byte message,
937 // pass it to the structure for being read outside
938 // the MIDI class, and recompose the message it was
939 // interleaved into. Oh, and without killing the running status..
940 // This is done by leaving the pending message as is,
941 // it will be completed on next calls.
942
943 mMessage.type = (MidiType)extracted;
944 mMessage.data1 = 0;
945 mMessage.data2 = 0;
946 mMessage.channel = 0;
947 mMessage.length = 1;
948 mMessage.valid = true;
949
950 return true;
951
952 // Exclusive
955 if ((mMessage.sysexArray[0] == SystemExclusiveStart)
956 || (mMessage.sysexArray[0] == SystemExclusiveEnd))
957 {
958 // Store the last byte (EOX)
959 mMessage.sysexArray[mPendingMessageIndex++] = extracted;
960 mMessage.type = SystemExclusive;
961
962 // Get length
963 mMessage.data1 = mPendingMessageIndex & 0xff; // LSB
964 mMessage.data2 = byte(mPendingMessageIndex >> 8); // MSB
965 mMessage.channel = 0;
966 mMessage.length = mPendingMessageIndex;
967 mMessage.valid = true;
968
969 resetInput();
970
971 return true;
972 }
973 else
974 {
975 // Well well well.. error.
976 mLastError |= 1UL << ErrorParse; // set the error bits
977 if (mErrorCallback)
978 mErrorCallback(mLastError); // LCOV_EXCL_LINE
979
980 resetInput();
981 return false;
982 }
983
984 default:
985 break; // LCOV_EXCL_LINE - Coverage blind spot
986 }
987 }
988
989 // Add extracted data byte to pending message
990 if ((mPendingMessage[0] == SystemExclusiveStart)
991 || (mPendingMessage[0] == SystemExclusiveEnd))
992 mMessage.sysexArray[mPendingMessageIndex] = extracted;
993 else
994 mPendingMessage[mPendingMessageIndex] = extracted;
995
996 // Now we are going to check if we have reached the end of the message
997 if (mPendingMessageIndex >= (mPendingMessageExpectedLength - 1))
998 {
999 // SysEx larger than the allocated buffer size,
1000 // Split SysEx like so:
1001 // first: 0xF0 .... 0xF0
1002 // midlle: 0xF7 .... 0xF0
1003 // last: 0xF7 .... 0xF7
1004 if ((mPendingMessage[0] == SystemExclusiveStart)
1005 || (mPendingMessage[0] == SystemExclusiveEnd))
1006 {
1007 auto lastByte = mMessage.sysexArray[Settings::SysExMaxSize - 1];
1008 mMessage.sysexArray[Settings::SysExMaxSize - 1] = SystemExclusiveStart;
1009 mMessage.type = SystemExclusive;
1010
1011 // Get length
1012 mMessage.data1 = Settings::SysExMaxSize & 0xff; // LSB
1013 mMessage.data2 = byte(Settings::SysExMaxSize >> 8); // MSB
1014 mMessage.channel = 0;
1015 mMessage.length = Settings::SysExMaxSize;
1016 mMessage.valid = true;
1017
1018 // No need to check against the inputChannel,
1019 // SysEx ignores input channel
1020 launchCallback();
1021
1022 mMessage.sysexArray[0] = SystemExclusiveEnd;
1023 mMessage.sysexArray[1] = lastByte;
1024
1025 mPendingMessageIndex = 2;
1026
1027 return false;
1028 }
1029
1030 mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
1031
1032 if (isChannelMessage(mMessage.type))
1033 mMessage.channel = getChannelFromStatusByte(mPendingMessage[0]);
1034 else
1035 mMessage.channel = 0;
1036
1037 mMessage.data1 = mPendingMessage[1];
1038 // Save data2 only if applicable
1039 mMessage.data2 = mPendingMessageExpectedLength == 3 ? mPendingMessage[2] : 0;
1040
1041 // Reset local variables
1042 mPendingMessageIndex = 0;
1043 mPendingMessageExpectedLength = 0;
1044
1045 mMessage.valid = true;
1046
1047 // Activate running status (if enabled for the received type)
1048 switch (mMessage.type)
1049 {
1050 case NoteOff:
1051 case NoteOn:
1052 case AfterTouchPoly:
1053 case ControlChange:
1054 case ProgramChange:
1055 case AfterTouchChannel:
1056 case PitchBend:
1057 // Running status enabled: store it from received message
1058 mRunningStatus_RX = mPendingMessage[0];
1059 break;
1060
1061 default:
1062 // No running status
1063 mRunningStatus_RX = InvalidType;
1064 break;
1065 }
1066 return true;
1067 }
1068 else
1069 {
1070 // Then update the index of the pending message.
1071 mPendingMessageIndex++;
1072
1073 return (Settings::Use1ByteParsing) ? false : parse();
1074 }
1075 }
1076}
1077
1078// Private method, see midi_Settings.h for documentation
1079template<class Transport, class Settings, class Platform>
1081{
1082 if (Settings::HandleNullVelocityNoteOnAsNoteOff &&
1083 getType() == NoteOn && getData2() == 0)
1084 {
1085 mMessage.type = NoteOff;
1086 }
1087}
1088
1089// Private method: check if the received message is on the listened channel
1090template<class Transport, class Settings, class Platform>
1092{
1093 // This method handles recognition of channel
1094 // (to know if the message is destinated to the Arduino)
1095
1096 // First, check if the received message is Channel
1097 if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
1098 {
1099 // Then we need to know if we listen to it
1100 if ((mMessage.channel == inChannel) ||
1101 (inChannel == MIDI_CHANNEL_OMNI))
1102 {
1103 return true;
1104 }
1105 else
1106 {
1107 // We don't listen to this channel
1108 return false;
1109 }
1110 }
1111 else
1112 {
1113 // System messages are always received
1114 return true;
1115 }
1116}
1117
1118// Private method: reset input attributes
1119template<class Transport, class Settings, class Platform>
1121{
1122 mPendingMessageIndex = 0;
1123 mPendingMessageExpectedLength = 0;
1124 mRunningStatus_RX = InvalidType;
1125}
1126
1127// -----------------------------------------------------------------------------
1128
1133template<class Transport, class Settings, class Platform>
1135{
1136 return mMessage.type;
1137}
1138
1144template<class Transport, class Settings, class Platform>
1146{
1147 return mMessage.channel;
1148}
1149
1151template<class Transport, class Settings, class Platform>
1153{
1154 return mMessage.data1;
1155}
1156
1158template<class Transport, class Settings, class Platform>
1160{
1161 return mMessage.data2;
1162}
1163
1168template<class Transport, class Settings, class Platform>
1170{
1171 return mMessage.sysexArray;
1172}
1173
1179template<class Transport, class Settings, class Platform>
1181{
1182 return mMessage.getSysExSize();
1183}
1184
1186template<class Transport, class Settings, class Platform>
1188{
1189 return mMessage.valid;
1190}
1191
1192// -----------------------------------------------------------------------------
1193
1194template<class Transport, class Settings, class Platform>
1196{
1197 return mInputChannel;
1198}
1199
1204template<class Transport, class Settings, class Platform>
1206{
1207 mInputChannel = inChannel;
1208}
1209
1210// -----------------------------------------------------------------------------
1211
1217template<class Transport, class Settings, class Platform>
1219{
1220 if ((inStatus < 0x80) ||
1221 (inStatus == Undefined_F4) ||
1222 (inStatus == Undefined_F5) ||
1223 (inStatus == Undefined_FD))
1224 return InvalidType; // Data bytes and undefined.
1225
1226 if (inStatus < 0xf0)
1227 // Channel message, remove channel nibble.
1228 return MidiType(inStatus & 0xf0);
1229
1230 return MidiType(inStatus);
1231}
1232
1235template<class Transport, class Settings, class Platform>
1237{
1238 return Channel((inStatus & 0x0f) + 1);
1239}
1240
1241template<class Transport, class Settings, class Platform>
1243{
1244 return (inType == NoteOff ||
1245 inType == NoteOn ||
1246 inType == ControlChange ||
1247 inType == AfterTouchPoly ||
1248 inType == AfterTouchChannel ||
1249 inType == PitchBend ||
1250 inType == ProgramChange);
1251}
1252
1253// -----------------------------------------------------------------------------
1254
1261template<class Transport, class Settings, class Platform>
1263{
1264 switch (inType)
1265 {
1266 case NoteOff: mNoteOffCallback = nullptr; break;
1267 case NoteOn: mNoteOnCallback = nullptr; break;
1268 case AfterTouchPoly: mAfterTouchPolyCallback = nullptr; break;
1269 case ControlChange: mControlChangeCallback = nullptr; break;
1270 case ProgramChange: mProgramChangeCallback = nullptr; break;
1271 case AfterTouchChannel: mAfterTouchChannelCallback = nullptr; break;
1272 case PitchBend: mPitchBendCallback = nullptr; break;
1273 case SystemExclusive: mSystemExclusiveCallback = nullptr; break;
1274 case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = nullptr; break;
1275 case SongPosition: mSongPositionCallback = nullptr; break;
1276 case SongSelect: mSongSelectCallback = nullptr; break;
1277 case TuneRequest: mTuneRequestCallback = nullptr; break;
1278 case Clock: mClockCallback = nullptr; break;
1279 case Start: mStartCallback = nullptr; break;
1280 case Tick: mTickCallback = nullptr; break;
1281 case Continue: mContinueCallback = nullptr; break;
1282 case Stop: mStopCallback = nullptr; break;
1283 case ActiveSensing: mActiveSensingCallback = nullptr; break;
1284 case SystemReset: mSystemResetCallback = nullptr; break;
1285 default:
1286 break;
1287 }
1288}
1289
1290 // End of doc group MIDI Callbacks
1291
1292// Private - launch callback function based on received type.
1293template<class Transport, class Settings, class Platform>
1295{
1296 if (mMessageCallback != 0) mMessageCallback(mMessage);
1297
1298 // The order is mixed to allow frequent messages to trigger their callback faster.
1299 switch (mMessage.type)
1300 {
1301 // Notes
1302 case NoteOff: if (mNoteOffCallback != nullptr) mNoteOffCallback(mMessage.channel, mMessage.data1, mMessage.data2); break;
1303 case NoteOn: if (mNoteOnCallback != nullptr) mNoteOnCallback(mMessage.channel, mMessage.data1, mMessage.data2); break;
1304
1305 // Real-time messages
1306 case Clock: if (mClockCallback != nullptr) mClockCallback(); break;
1307 case Start: if (mStartCallback != nullptr) mStartCallback(); break;
1308 case Tick: if (mTickCallback != nullptr) mTickCallback(); break;
1309 case Continue: if (mContinueCallback != nullptr) mContinueCallback(); break;
1310 case Stop: if (mStopCallback != nullptr) mStopCallback(); break;
1311 case ActiveSensing: if (mActiveSensingCallback != nullptr) mActiveSensingCallback(); break;
1312
1313 // Continuous controllers
1314 case ControlChange: if (mControlChangeCallback != nullptr) mControlChangeCallback(mMessage.channel, mMessage.data1, mMessage.data2); break;
1315 case PitchBend: if (mPitchBendCallback != nullptr) mPitchBendCallback(mMessage.channel, (int)((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7)) + MIDI_PITCHBEND_MIN); break;
1316 case AfterTouchPoly: if (mAfterTouchPolyCallback != nullptr) mAfterTouchPolyCallback(mMessage.channel, mMessage.data1, mMessage.data2); break;
1317 case AfterTouchChannel: if (mAfterTouchChannelCallback != nullptr) mAfterTouchChannelCallback(mMessage.channel, mMessage.data1); break;
1318
1319 case ProgramChange: if (mProgramChangeCallback != nullptr) mProgramChangeCallback(mMessage.channel, mMessage.data1); break;
1320 case SystemExclusive: if (mSystemExclusiveCallback != nullptr) mSystemExclusiveCallback(mMessage.sysexArray, mMessage.getSysExSize()); break;
1321
1322 // Occasional messages
1323 case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != nullptr) mTimeCodeQuarterFrameCallback(mMessage.data1); break;
1324 case SongPosition: if (mSongPositionCallback != nullptr) mSongPositionCallback(unsigned((mMessage.data1 & 0x7f) | ((mMessage.data2 & 0x7f) << 7))); break;
1325 case SongSelect: if (mSongSelectCallback != nullptr) mSongSelectCallback(mMessage.data1); break;
1326 case TuneRequest: if (mTuneRequestCallback != nullptr) mTuneRequestCallback(); break;
1327
1328 case SystemReset: if (mSystemResetCallback != nullptr) mSystemResetCallback(); break;
1329
1330 case InvalidType:
1331 default:
1332 break; // LCOV_EXCL_LINE - Unreacheable code, but prevents unhandled case warning.
1333 }
1334}
1335
1336 // End of doc group MIDI Input
1337
1338// -----------------------------------------------------------------------------
1339// Thru
1340// -----------------------------------------------------------------------------
1341
1351template<class Transport, class Settings, class Platform>
1353{
1354 mThruFilterMode = inThruFilterMode;
1355 mThruActivated = mThruFilterMode != Thru::Off;
1356}
1357
1358template<class Transport, class Settings, class Platform>
1360{
1361 return mThruFilterMode;
1362}
1363
1364template<class Transport, class Settings, class Platform>
1366{
1367 return mThruActivated;
1368}
1369
1370template<class Transport, class Settings, class Platform>
1372{
1373 mThruActivated = true;
1374 mThruFilterMode = inThruFilterMode;
1375}
1376
1377template<class Transport, class Settings, class Platform>
1379{
1380 mThruActivated = false;
1381 mThruFilterMode = Thru::Off;
1382}
1383
1384template<class Transport, class Settings, class Platform>
1386{
1387 if (Settings::UseSenderActiveSensing && mSenderActiveSensingPeriodicity)
1388 mLastMessageSentTime = Platform::now();
1389}
1390
1391 // End of doc group MIDI Thru
1392
1393// This method is called upon reception of a message
1394// and takes care of Thru filtering and sending.
1395// - All system messages (System Exclusive, Common and Real Time) are passed
1396// to output unless filter is set to Off.
1397// - Channel messages are passed to the output whether their channel
1398// is matching the input channel and the filter setting
1399template<class Transport, class Settings, class Platform>
1401{
1402 // If the feature is disabled, don't do anything.
1403 if (!mThruActivated || (mThruFilterMode == Thru::Off))
1404 return;
1405
1406 // First, check if the received message is Channel
1407 if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
1408 {
1409 const bool filter_condition = ((mMessage.channel == inChannel) ||
1410 (inChannel == MIDI_CHANNEL_OMNI));
1411
1412 // Now let's pass it to the output
1413 switch (mThruFilterMode)
1414 {
1415 case Thru::Full:
1416 send(mMessage.type,
1417 mMessage.data1,
1418 mMessage.data2,
1419 mMessage.channel);
1420 break;
1421
1422 case Thru::SameChannel:
1423 if (filter_condition)
1424 {
1425 send(mMessage.type,
1426 mMessage.data1,
1427 mMessage.data2,
1428 mMessage.channel);
1429 }
1430 break;
1431
1433 if (!filter_condition)
1434 {
1435 send(mMessage.type,
1436 mMessage.data1,
1437 mMessage.data2,
1438 mMessage.channel);
1439 }
1440 break;
1441
1442 default:
1443 break;
1444 }
1445 }
1446 else
1447 {
1448 // Send the message to the output
1449 switch (mMessage.type)
1450 {
1451 // Real Time and 1 byte
1452 case Clock:
1453 case Start:
1454 case Stop:
1455 case Continue:
1456 case ActiveSensing:
1457 case SystemReset:
1458 case TuneRequest:
1459 sendRealTime(mMessage.type);
1460 break;
1461
1462 case SystemExclusive:
1463 // Send SysEx (0xf0 and 0xf7 are included in the buffer)
1464 sendSysEx(getSysExArrayLength(), getSysExArray(), true);
1465 break;
1466
1467 case SongSelect:
1468 sendSongSelect(mMessage.data1);
1469 break;
1470
1471 case SongPosition:
1472 sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2 << 7));
1473 break;
1474
1476 sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2);
1477 break;
1478
1479 default:
1480 break; // LCOV_EXCL_LINE - Unreacheable code, but prevents unhandled case warning.
1481 }
1482 }
1483}
1484
1485END_MIDI_NAMESPACE
The main class for MIDI handling. It is templated over the type of serial port to provide abstraction...
Definition MIDI.h:55
~MidiInterface()
Destructor for MidiInterface.
Definition MIDI.hpp:59
MidiInterface(Transport &)
Constructor for MidiInterface.
Definition MIDI.hpp:34
void begin(Channel inChannel=1)
Call the begin method in the setup() function of the Arduino.
Definition MIDI.hpp:72
bool check() const
Check if a valid message is stored in the structure.
Definition MIDI.hpp:1187
unsigned getSysExArrayLength() const
Get the length of the System Exclusive array.
Definition MIDI.hpp:1180
Channel getChannel() const
Get the channel of the message stored in the structure.
Definition MIDI.hpp:1145
static Channel getChannelFromStatusByte(byte inStatus)
Returns channel in the range 1-16.
Definition MIDI.hpp:1236
static MidiType getTypeFromStatusByte(byte inStatus)
Extract an enumerated MIDI type from a status byte.
Definition MIDI.hpp:1218
DataByte getData1() const
Get the first data byte of the last received message.
Definition MIDI.hpp:1152
MidiType getType() const
Get the last received message's type.
Definition MIDI.hpp:1134
DataByte getData2() const
Get the second data byte of the last received message.
Definition MIDI.hpp:1159
const byte * getSysExArray() const
Get the System Exclusive byte array.
Definition MIDI.hpp:1169
void disconnectCallbackFromType(MidiType inType)
Detach an external function from the given type.
Definition MIDI.hpp:1262
bool read()
Read messages from the serial port using the main input channel.
Definition MIDI.hpp:704
void setInputChannel(Channel inChannel)
Set the value for the input MIDI channel.
Definition MIDI.hpp:1205
void sendAfterTouch(DataByte inPressure, Channel inChannel)
Send a MonoPhonic AfterTouch message (applies to all notes)
Definition MIDI.hpp:297
void endRpn(Channel inChannel)
Terminate an RPN frame. This will send a Null Function to deselect the currently selected RPN.
Definition MIDI.hpp:589
void sendPitchBend(int inPitchValue, Channel inChannel)
Send a Pitch Bend message using a signed integer value.
Definition MIDI.hpp:324
void sendTimeCodeQuarterFrame(DataByte inTypeNibble, DataByte inValuesNibble)
Send a MIDI Time Code Quarter Frame.
Definition MIDI.hpp:400
void endNrpn(Channel inChannel)
Terminate an NRPN frame. This will send a Null Function to deselect the currently selected NRPN.
Definition MIDI.hpp:669
void sendNoteOn(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel)
Send a Note On message.
Definition MIDI.hpp:226
void sendTuneRequest()
Send a Tune Request message.
Definition MIDI.hpp:388
void sendControlChange(DataByte inControlNumber, DataByte inControlValue, Channel inChannel)
Send a Control Change message.
Definition MIDI.hpp:270
void send(const MidiMessage &)
Send a MIDI message.
Definition MIDI.hpp:118
void sendProgramChange(DataByte inProgramNumber, Channel inChannel)
Send a Program Change message.
Definition MIDI.hpp:257
void beginNrpn(unsigned inNumber, Channel inChannel)
Start a Non-Registered Parameter Number frame.
Definition MIDI.hpp:603
void beginRpn(unsigned inNumber, Channel inChannel)
Start a Registered Parameter Number frame.
Definition MIDI.hpp:523
void sendSongPosition(unsigned inBeats)
Send a Song Position Pointer message.
Definition MIDI.hpp:423
void sendPolyPressure(DataByte inNoteNumber, DataByte inPressure, Channel inChannel)
Send a Polyphonic AfterTouch message (applies to a specified note)
Definition MIDI.hpp:285
void sendSongSelect(DataByte inSongNumber)
Send a Song Select message.
Definition MIDI.hpp:430
void sendCommon(MidiType inType, unsigned=0)
Send a Common message. Common messages reset the running status.
Definition MIDI.hpp:443
void sendRpnValue(unsigned inValue, Channel inChannel)
Send a 14-bit value for the currently selected RPN number.
Definition MIDI.hpp:541
void sendRealTime(MidiType inType)
Send a Real Time (one byte) message.
Definition MIDI.hpp:492
void sendSysEx(unsigned inLength, const byte *inArray, bool inArrayContainsBoundaries=false)
Generate and send a System Exclusive frame.
Definition MIDI.hpp:357
void sendNoteOff(DataByte inNoteNumber, DataByte inVelocity, Channel inChannel)
Send a Note Off message.
Definition MIDI.hpp:245
void sendNrpnValue(unsigned inValue, Channel inChannel)
Send a 14-bit value for the currently selected NRPN number.
Definition MIDI.hpp:621
void setThruFilterMode(Thru::Mode inThruFilterMode)
Set the filter for thru mirroring.
Definition MIDI.hpp:1352
@ RPNLSB
Registered Parameter Number (LSB)
Definition midi_Defs.h:204
@ RPNMSB
Registered Parameter Number (MSB)
Definition midi_Defs.h:205
@ NRPNLSB
Non-Registered Parameter Number (LSB)
Definition midi_Defs.h:202
@ NRPNMSB
Non-Registered Parameter Number (MSB)
Definition midi_Defs.h:203
MidiType
Definition midi_Defs.h:95
@ AfterTouchChannel
Channel Message - Channel (monophonic) AfterTouch.
Definition midi_Defs.h:102
@ ProgramChange
Channel Message - Program Change.
Definition midi_Defs.h:101
@ SystemExclusiveStart
System Exclusive Start.
Definition midi_Defs.h:105
@ Continue
System Real Time - Continue.
Definition midi_Defs.h:117
@ Clock
System Real Time - Timing Clock.
Definition midi_Defs.h:113
@ ActiveSensing
System Real Time - Active Sensing.
Definition midi_Defs.h:120
@ SystemReset
System Real Time - System Reset.
Definition midi_Defs.h:121
@ SystemExclusive
System Exclusive.
Definition midi_Defs.h:104
@ InvalidType
For notifying errors.
Definition midi_Defs.h:96
@ SongPosition
System Common - Song Position Pointer.
Definition midi_Defs.h:107
@ Tick
System Real Time - Timing Tick (1 tick = 10 milliseconds)
Definition midi_Defs.h:115
@ TuneRequest
System Common - Tune Request.
Definition midi_Defs.h:111
@ Start
System Real Time - Start.
Definition midi_Defs.h:116
@ ControlChange
Channel Message - Control Change / Channel Mode.
Definition midi_Defs.h:100
@ PitchBend
Channel Message - Pitch Bend.
Definition midi_Defs.h:103
@ NoteOff
Channel Message - Note Off.
Definition midi_Defs.h:97
@ SongSelect
System Common - Song Select.
Definition midi_Defs.h:108
@ SystemExclusiveEnd
System Exclusive End.
Definition midi_Defs.h:112
@ AfterTouchPoly
Channel Message - Polyphonic AfterTouch.
Definition midi_Defs.h:99
@ Stop
System Real Time - Stop.
Definition midi_Defs.h:118
@ TimeCodeQuarterFrame
System Common - MIDI Time Code Quarter Frame.
Definition midi_Defs.h:106
@ NoteOn
Channel Message - Note On.
Definition midi_Defs.h:98
Channel channel
DataByte sysexArray[sSysExMaxSize]
bool valid
DataByte data1
MidiType type
unsigned length
DataByte data2
@ DifferentChannel
All the messages but the ones on the Input Channel will be sent back.
Definition midi_Defs.h:134
@ Full
Fully enabled Thru (every incoming message is sent back).
Definition midi_Defs.h:132
@ Off
Thru disabled (nothing passes through).
Definition midi_Defs.h:131
@ SameChannel
Only the messages on the Input Channel will be sent back.
Definition midi_Defs.h:133