diff --git a/res/Examples/MIDI_SimpleSynth/MIDI_SimpleSynth.ino b/res/Examples/MIDI_SimpleSynth/MIDI_SimpleSynth.ino new file mode 100644 index 0000000..c44c441 --- /dev/null +++ b/res/Examples/MIDI_SimpleSynth/MIDI_SimpleSynth.ino @@ -0,0 +1,81 @@ +#include +#include "noteList.h" +#include "pitches.h" + +#ifdef ARDUINO_SAM_DUE // Due has no tone function (yet), overriden to prevent build errors. +#define tone(...) +#define noTone(...) +#endif + +// This example shows how to make a simple synth out of an Arduino, using the +// tone() function. It also outputs a gate signal for controlling external +// analog synth components (like envelopes). + +static const unsigned sGatePin = 13; +static const unsigned sAudioOutPin = 10; +static const unsigned sMaxNumNotes = 16; +MidiNoteList midiNotes; + +// ----------------------------------------------------------------------------- + +void handleGateChanged(bool inGateActive) +{ + digitalWrite(sGatePin, inGateActive ? HIGH : LOW); +} + +void pulseGate() +{ + handleGateChanged(false); + delay(1); + handleGateChanged(true); +} + +// ----------------------------------------------------------------------------- + +void handleNotesChanged() +{ + if (midiNotes.empty()) + { + handleGateChanged(false); + noTone(sAudioOutPin); + } + else + { + byte currentNote = 0; + if (midiNotes.getTail(currentNote)) + { + tone(sAudioOutPin, sNotePitches[currentNote]); + pulseGate(); // Retrigger envelopes. Remove for legato effect. + } + } +} + +// ----------------------------------------------------------------------------- + +void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) +{ + midiNotes.add(MidiNote(inNote, inVelocity)); + handleNotesChanged(); +} + +void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) +{ + midiNotes.remove(inNote); + handleNotesChanged(); +} + +// ----------------------------------------------------------------------------- + +void setup() +{ + pinMode(sGatePin, OUTPUT); + pinMode(sAudioOutPin, OUTPUT); + MIDI.setHandleNoteOn(handleNoteOn); + MIDI.setHandleNoteOff(handleNoteOff); + MIDI.begin(); +} + +void loop() +{ + MIDI.read(); +} diff --git a/res/Examples/MIDI_SimpleSynth/noteList.cpp b/res/Examples/MIDI_SimpleSynth/noteList.cpp new file mode 100644 index 0000000..7f1afe3 --- /dev/null +++ b/res/Examples/MIDI_SimpleSynth/noteList.cpp @@ -0,0 +1,21 @@ +/*! + * \file synth-core_NoteList.h + * \author Francois Best + * \date 24/05/2013 + * \license GPL v3.0 - Copyright Forty Seven Effects 2013 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "noteList.h" diff --git a/res/Examples/MIDI_SimpleSynth/noteList.h b/res/Examples/MIDI_SimpleSynth/noteList.h new file mode 100644 index 0000000..559edc8 --- /dev/null +++ b/res/Examples/MIDI_SimpleSynth/noteList.h @@ -0,0 +1,317 @@ +/*! + * \file synth-core_NoteList.h + * \author Francois Best + * \date 24/05/2013 + * \license GPL v3.0 - Copyright Forty Seven Effects 2013 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +typedef uint8_t byte; + +// ----------------------------------------------------------------------------- + +struct MidiNote +{ + inline MidiNote(); + inline MidiNote(byte inPitch, byte inVelocity); + inline MidiNote(const MidiNote& inOther); + inline MidiNote& operator= (const MidiNote& inOther); + + byte pitch; + byte velocity; +}; + +// ----------------------------------------------------------------------------- + +template +class MidiNoteList +{ +private: + struct Cell + { + inline Cell(); + inline Cell(const Cell& inOther); + inline Cell& operator= (const Cell& inOther); + + MidiNote note; + bool active; + Cell* next; + Cell* prev; + }; + +public: + inline MidiNoteList(); + inline ~MidiNoteList(); + +public: + inline void add(const MidiNote& inNote); + inline void remove(byte inPitch); + +public: + inline bool get(byte inIndex, byte& outPitch) const; + inline bool getTail(byte& outPitch) const; + +public: + inline bool empty() const; + inline byte size() const; + +private: + inline Cell* getFirstEmptyCell(); + inline void print() const; + +private: + Cell mArray[Size]; + Cell* mHead; + Cell* mTail; + byte mSize; +}; + +// ########################################################################## // +// Inline implementation + +inline MidiNote::MidiNote() + : pitch(0) + , velocity(0) +{ +} + +inline MidiNote::MidiNote(byte inPitch, byte inVelocity) + : pitch(inPitch) + , velocity(inVelocity) +{ +} + +inline MidiNote::MidiNote(const MidiNote& inOther) + : pitch(inOther.pitch) + , velocity(inOther.velocity) +{ +} + +inline MidiNote& MidiNote::operator= (const MidiNote& inOther) +{ + pitch = inOther.pitch; + velocity = inOther.velocity; + return *this; +} + +// ########################################################################## // + +template +inline MidiNoteList::Cell::Cell() + : note() + , active(false) + , next(0) + , prev(0) +{ +} + +template +inline MidiNoteList::Cell::Cell(const Cell& inOther) + : note(inOther.note) + , active(inOther.active) + , next(inOther.next) + , prev(inOther.prev) +{ +} + +template +inline typename MidiNoteList::Cell& MidiNoteList::Cell::operator= (const Cell& inOther) +{ + note = inOther.note; + active = inOther.active; + next = inOther.next; + prev = inOther.prev; + return *this; +} + +// ########################################################################## // + +template +inline MidiNoteList::MidiNoteList() +{ + // Check that size is a power of two + //AVR_STATIC_ASSERT(Size != 0 && !(Size & (Size - 1))); +} + +template +inline MidiNoteList::~MidiNoteList() +{ +} + +// ----------------------------------------------------------------------------- + +template +inline void MidiNoteList::add(const MidiNote& inNote) +{ + if (mHead == 0) + { + mArray[0].note = inNote; + mArray[0].active = true; + mArray[0].next = 0; + mArray[0].prev = 0; + mHead = mArray; + mTail = mArray; + } + else + { + // Find the first inactive cell, and use it as tail. + Cell* const oldTail = mTail; + Cell* const newTail = getFirstEmptyCell(); + + newTail->active = true; + newTail->note = inNote; + + oldTail->next = newTail; + newTail->prev = oldTail; + newTail->next = 0; + mTail = newTail; + } + mSize++; + print(); +} + +template +inline void MidiNoteList::remove(byte inPitch) +{ + if (mHead != 0) + { + for (Cell* it = mHead; it != 0; it = it->next) + { + if (it->note.pitch == inPitch) + { + Cell* const prev = it->prev; + Cell* const next = it->next; + + it->active = false; + it->next = 0; + it->prev = 0; + + // Reconnect both ends + if (it == mHead) + { + //AVR_ASSERT(prev == 0); + mHead = next; + } + else + { + //AVR_ASSERT(prev != 0); + prev->next = next; + } + + if (it == mTail) + { + //AVR_ASSERT(next == 0); + mTail = prev; + } + else + { + //AVR_ASSERT(next != 0); + next->prev = prev; + } + + mSize--; + break; + } + } + } + print(); +} + +// ----------------------------------------------------------------------------- + +template +inline bool MidiNoteList::get(byte inIndex, byte& outPitch) const +{ + if (mTail) + { + const Cell* it = mTail; + for (byte i = 0; i < inIndex; ++i) + { + if (it->prev) + { + it = it->prev; + } + } + + print(); + //AVR_LOG("Index " << inIndex << ": " << it->note.pitch); + + outPitch = it->note.pitch; + return true; + } + return false; +} + +template +inline bool MidiNoteList::getTail(byte& outPitch) const +{ + if (mTail) + { + outPitch = mTail->note.pitch; + return true; + } + return false; +} + +// ----------------------------------------------------------------------------- + +template +inline bool MidiNoteList::empty() const +{ + return mSize == 0; +} + +template +inline byte MidiNoteList::size() const +{ + return mSize; +} + +// ----------------------------------------------------------------------------- + +template +inline typename MidiNoteList::Cell* MidiNoteList::getFirstEmptyCell() +{ + for (byte i = 0; i < Size; ++i) + { + if (mArray[i].active == false) + { + return mArray + i; + } + } + return 0; +} + +template +inline void MidiNoteList::print() const +{ +//#ifndef NDEBUG +// AVR_DBG("Note List: [ "); +// if (mHead) +// { +// for (const Cell* it = mHead; it != 0; it = it->next) +// { +// AVR_DBG(it->note.pitch); +// if (it->next) +// AVR_DBG(" -> "); +// } +// } +// AVR_LOG(" ]"); +//#endif +} diff --git a/res/Examples/MIDI_SimpleSynth/pitches.h b/res/Examples/MIDI_SimpleSynth/pitches.h new file mode 100644 index 0000000..284c14d --- /dev/null +++ b/res/Examples/MIDI_SimpleSynth/pitches.h @@ -0,0 +1,108 @@ +/************************************************* + * Public Constants + *************************************************/ +#include + +#define NOTE_B0 31 +#define NOTE_C1 33 +#define NOTE_CS1 35 +#define NOTE_D1 37 +#define NOTE_DS1 39 +#define NOTE_E1 41 +#define NOTE_F1 44 +#define NOTE_FS1 46 +#define NOTE_G1 49 +#define NOTE_GS1 52 +#define NOTE_A1 55 +#define NOTE_AS1 58 +#define NOTE_B1 62 +#define NOTE_C2 65 +#define NOTE_CS2 69 +#define NOTE_D2 73 +#define NOTE_DS2 78 +#define NOTE_E2 82 +#define NOTE_F2 87 +#define NOTE_FS2 93 +#define NOTE_G2 98 +#define NOTE_GS2 104 +#define NOTE_A2 110 +#define NOTE_AS2 117 +#define NOTE_B2 123 +#define NOTE_C3 131 +#define NOTE_CS3 139 +#define NOTE_D3 147 +#define NOTE_DS3 156 +#define NOTE_E3 165 +#define NOTE_F3 175 +#define NOTE_FS3 185 +#define NOTE_G3 196 +#define NOTE_GS3 208 +#define NOTE_A3 220 +#define NOTE_AS3 233 +#define NOTE_B3 247 +#define NOTE_C4 262 +#define NOTE_CS4 277 +#define NOTE_D4 294 +#define NOTE_DS4 311 +#define NOTE_E4 330 +#define NOTE_F4 349 +#define NOTE_FS4 370 +#define NOTE_G4 392 +#define NOTE_GS4 415 +#define NOTE_A4 440 +#define NOTE_AS4 466 +#define NOTE_B4 494 +#define NOTE_C5 523 +#define NOTE_CS5 554 +#define NOTE_D5 587 +#define NOTE_DS5 622 +#define NOTE_E5 659 +#define NOTE_F5 698 +#define NOTE_FS5 740 +#define NOTE_G5 784 +#define NOTE_GS5 831 +#define NOTE_A5 880 +#define NOTE_AS5 932 +#define NOTE_B5 988 +#define NOTE_C6 1047 +#define NOTE_CS6 1109 +#define NOTE_D6 1175 +#define NOTE_DS6 1245 +#define NOTE_E6 1319 +#define NOTE_F6 1397 +#define NOTE_FS6 1480 +#define NOTE_G6 1568 +#define NOTE_GS6 1661 +#define NOTE_A6 1760 +#define NOTE_AS6 1865 +#define NOTE_B6 1976 +#define NOTE_C7 2093 +#define NOTE_CS7 2217 +#define NOTE_D7 2349 +#define NOTE_DS7 2489 +#define NOTE_E7 2637 +#define NOTE_F7 2794 +#define NOTE_FS7 2960 +#define NOTE_G7 3136 +#define NOTE_GS7 3322 +#define NOTE_A7 3520 +#define NOTE_AS7 3729 +#define NOTE_B7 3951 +#define NOTE_C8 4186 +#define NOTE_CS8 4435 +#define NOTE_D8 4699 +#define NOTE_DS8 4978 + +static const uint16_t sNotePitches[] = { + NOTE_B0, NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, + NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, NOTE_C2, NOTE_CS2, NOTE_D2, + NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, + NOTE_B2, NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, + NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, NOTE_C4, NOTE_CS4, NOTE_D4, + NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, + NOTE_B4, NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, + NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, NOTE_C6, NOTE_CS6, NOTE_D6, + NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, + NOTE_B6, NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, + NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7, NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8, +}; diff --git a/src/MIDI.hpp b/src/MIDI.hpp index 7a154c2..789f524 100644 --- a/src/MIDI.hpp +++ b/src/MIDI.hpp @@ -483,24 +483,22 @@ inline bool MidiInterface::read(Channel inChannel) if (inChannel >= MIDI_CHANNEL_OFF) return false; // MIDI Input disabled. - if (parse()) + if (!parse()) + return false; + + handleNullVelocityNoteOnAsNoteOff(); + const bool channelMatch = inputFilter(inChannel); + + if (MIDI_USE_CALLBACKS && channelMatch) { - handleNullVelocityNoteOnAsNoteOff(); - if (inputFilter(inChannel)) - { - -#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) - thruFilter(inChannel); -#endif - -#if MIDI_USE_CALLBACKS - launchCallback(); -#endif - return true; - } + launchCallback(); } - return false; +#if MIDI_BUILD_THRU + thruFilter(inChannel); +#endif + + return channelMatch; } // ----------------------------------------------------------------------------- diff --git a/src/midi_Defs.h b/src/midi_Defs.h index 324f361..6da132d 100644 --- a/src/midi_Defs.h +++ b/src/midi_Defs.h @@ -203,6 +203,8 @@ struct Message /*! \brief Create an instance of the library attached to a serial port. You can use HardwareSerial or SoftwareSerial for the serial port. + Example: MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, midi2); + Then call midi2.begin(), midi2.read() etc.. */ #define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \ midi::MidiInterface Name((Type&)SerialPort);