giuliomoro@181: /*
giuliomoro@181:  * Midi.h
giuliomoro@181:  *
giuliomoro@181:  *  Created on: 15 Jan 2016
giuliomoro@181:  *      Author: giulio
giuliomoro@181:  */
giuliomoro@181: 
giuliomoro@181: #ifndef MIDI_H_
giuliomoro@181: #define MIDI_H_
giuliomoro@181: 
giuliomoro@301: #include <Bela.h>
giuliomoro@181: #include <vector>
giuliomoro@181: 
giuliomoro@181: typedef unsigned char midi_byte_t;
giuliomoro@181: 
giuliomoro@197: 
giuliomoro@197: typedef enum midiMessageType{
giuliomoro@197: 	kmmNoteOff = 0,
giuliomoro@197: 	kmmNoteOn,
giuliomoro@197: 	kmmPolyphonicKeyPressure,
giuliomoro@197: 	kmmControlChange,
giuliomoro@197: 	kmmProgramChange,
giuliomoro@197: 	kmmChannelPressure,
giuliomoro@197: 	kmmPitchBend,
giuliomoro@197: 	kmmNone,
giuliomoro@197: 	kmmAny,
giuliomoro@197: } MidiMessageType;
giuliomoro@197: #define midiMessageStatusBytesLength 7+2 //2 being kmmNone and kmmAny
giuliomoro@197: 
giuliomoro@197: extern midi_byte_t midiMessageStatusBytes[midiMessageStatusBytesLength];
giuliomoro@197: extern unsigned int midiMessageNumDataBytes[midiMessageStatusBytesLength];
giuliomoro@197: 
giuliomoro@197: class MidiChannelMessage{
giuliomoro@197: public:
giuliomoro@197: 	MidiChannelMessage();
giuliomoro@197: 	MidiChannelMessage(MidiMessageType type);
giuliomoro@197: 	virtual ~MidiChannelMessage();
giuliomoro@197: 	MidiMessageType getType();
giuliomoro@197: 	int getChannel();
giuliomoro@197: 	unsigned int getNumDataBytes(){
giuliomoro@197: 		return midiMessageNumDataBytes[(unsigned int)_type];
giuliomoro@197: 	}
giuliomoro@197: 	void setDataByte(unsigned int dataByteIndex, midi_byte_t input){
giuliomoro@197: 		_dataBytes[dataByteIndex] = input;
giuliomoro@197: 	}
giuliomoro@197: 	void setType(MidiMessageType type){
giuliomoro@197: 		_type = type;
giuliomoro@197: 		_statusByte = midiMessageStatusBytes[_type];
giuliomoro@197: 	}
giuliomoro@197: 	void setChannel(midi_byte_t channel){
giuliomoro@197: 		_channel = channel;
giuliomoro@197: 	}
giuliomoro@197: 	midi_byte_t getDataByte(unsigned int index){
giuliomoro@197: 		return _dataBytes[index];
giuliomoro@197: 	}
giuliomoro@197: 	void clear(){
giuliomoro@197: 		for(int n = 0; n<maxDataBytes; n++){
giuliomoro@197: 			_dataBytes[n] = 0;
giuliomoro@197: 		}
giuliomoro@197: 		_type = kmmNone;
giuliomoro@197: 		_statusByte = 0;
giuliomoro@197: 	}
giuliomoro@197: 	void prettyPrint(){
giuliomoro@197: 		rt_printf("MessageType: %x,  ", this->getType());
giuliomoro@197: 		rt_printf("channel: %u, ", this->getChannel());
giuliomoro@216: 		for(unsigned int n = 0; n < this->getNumDataBytes(); n++){
giuliomoro@197: 			rt_printf("data%d: %d, ", n + 1, this->getDataByte(n));
giuliomoro@197: 		}
giuliomoro@197: 		rt_printf("\n");
giuliomoro@197: 	}
giuliomoro@197: 
giuliomoro@197: private:
giuliomoro@197: 	const static int maxDataBytes = 2;
giuliomoro@197: protected:
giuliomoro@197: 	midi_byte_t _statusByte;
giuliomoro@197: 	midi_byte_t _dataBytes[maxDataBytes]; // where 2 is the maximum number of data bytes for a channel message
giuliomoro@197: 	MidiMessageType _type;
giuliomoro@197: 	midi_byte_t _channel;
giuliomoro@197: };
giuliomoro@197: /*
giuliomoro@197: class MidiControlChangeMessage : public MidiChannelMessage{
giuliomoro@197: 	int number;
giuliomoro@197: 	int value;
giuliomoro@197: public:
giuliomoro@197: 	int getNumber();
giuliomoro@197: 	int getNumDataBytes();
giuliomoro@197: 	int setDataByte(midi_byte_t input, unsigned int dataByteIndex);
giuliomoro@197: 	int getValue();
giuliomoro@197: 	int set(midi_byte_t* input);
giuliomoro@197: };
giuliomoro@197: 
giuliomoro@197: class MidiNoteMessage : public MidiChannelMessage{
giuliomoro@197: 	int note;
giuliomoro@197: 	int velocity;
giuliomoro@197: public:
giuliomoro@197: 	int getNote();
giuliomoro@197: 	int getVelocity();
giuliomoro@197: 	int getNumDataBytes();
giuliomoro@197: 	int setDataByte(midi_byte_t input, unsigned int dataByteIndex);
giuliomoro@197: };
giuliomoro@197: 
giuliomoro@197: class MidiProgramChangeMessage : public MidiChannelMessage{
giuliomoro@197: 	midi_byte_t program;
giuliomoro@197: public:
giuliomoro@197: 	int getNumDataBytes();
giuliomoro@197: 	int setDataByte(midi_byte_t input, unsigned int dataByteIndex);
giuliomoro@197: 	midi_byte_t getProgram();
giuliomoro@197: };
giuliomoro@197: */
giuliomoro@197: 
giuliomoro@197: class MidiParser{
giuliomoro@197: private:
giuliomoro@197: 	std::vector<MidiChannelMessage> messages;
giuliomoro@197: 	unsigned int writePointer;
giuliomoro@197: 	unsigned int readPointer;
giuliomoro@197: 	unsigned int elapsedDataBytes;
giuliomoro@197: 	bool waitingForStatus;
giuliomoro@226: 	void (*messageReadyCallback)(MidiChannelMessage,void*);
giuliomoro@224: 	bool callbackEnabled;
giuliomoro@226: 	void* callbackArg;
giuliomoro@197: public:
giuliomoro@197: 	MidiParser(){
giuliomoro@197: 		waitingForStatus = true;
giuliomoro@197: 		elapsedDataBytes= 0;
giuliomoro@197: 		messages.resize(100); // 100 is the number of messages that can be buffered
giuliomoro@197: 		writePointer = 0;
giuliomoro@197: 		readPointer = 0;
giuliomoro@224: 		callbackEnabled = false;
giuliomoro@224: 		messageReadyCallback = NULL;
giuliomoro@226: 		callbackArg = NULL;
giuliomoro@197: 	}
giuliomoro@197: 
giuliomoro@197: 	/**
giuliomoro@197: 	 * Parses some midi messages.
giuliomoro@197: 	 *
giuliomoro@197: 	 * @param input the array to read from
giuliomoro@197: 	 * @param length the maximum number of values available at the array
giuliomoro@197: 	 *
giuliomoro@197: 	 * @return the number of bytes parsed
giuliomoro@197: 	 */
giuliomoro@197: 	int parse(midi_byte_t* input, unsigned int length);
giuliomoro@224: 
giuliomoro@225: 	/**
giuliomoro@225: 	 * Sets the callback to call when a new MidiChannelMessage is available
giuliomoro@225: 	 * from the input port.
giuliomoro@225: 	 *
giuliomoro@226: 	 * The callback will be called with two arguments:
giuliomoro@226: 	 *   callback(MidiChannelMessage newMessage, void* arg)
giuliomoro@226: 	 *
giuliomoro@226: 	 * In order to deactivate the callback, call this method with NULL as the
giuliomoro@226: 	 * first argument.
giuliomoro@225: 	 * While the callback is enabled, calling numAvailableMessages() and
giuliomoro@225: 	 * getNextChannelMessage() is still possible, but it will probably always
giuliomoro@225: 	 * return 0 as the callback is called as soon as a new message is available.
giuliomoro@225: 	 *
giuliomoro@226: 	 * @param newCallback the callback function.
giuliomoro@226: 	 * @param arg the second argument to be passed to the callback function.
giuliomoro@225: 	 *
giuliomoro@225: 	 */
giuliomoro@226: 	void setCallback(void (*newCallback)(MidiChannelMessage, void*), void* arg=NULL){
giuliomoro@226: 		callbackArg = arg;
giuliomoro@224: 		messageReadyCallback = newCallback;
giuliomoro@224: 		if(newCallback != NULL){
giuliomoro@224: 			callbackEnabled = true;
giuliomoro@224: 		} else {
giuliomoro@224: 			callbackEnabled = false;
giuliomoro@224: 		}
giuliomoro@197: 	};
giuliomoro@224: 
giuliomoro@225: 	/**
giuliomoro@225: 	 * Checks whether there is a callback currently set to be called
giuliomoro@225: 	 * every time a new input MidiChannelMessage is available from the
giuliomoro@225: 	 * input port.
giuliomoro@225: 	 */
giuliomoro@224: 	bool isCallbackEnabled(){
giuliomoro@224: 		return callbackEnabled;
giuliomoro@224: 	};
giuliomoro@224: 
giuliomoro@225: 	/**
giuliomoro@225: 	 * Returns the number of unread MidiChannelMessage available from the
giuliomoro@225: 	 * input port.
giuliomoro@225: 	 *
giuliomoro@225: 	 */
giuliomoro@225: 
giuliomoro@197: 	int numAvailableMessages(){
giuliomoro@197: 		int num = (writePointer - readPointer + messages.size() ) % messages.size();
giuliomoro@197: 		return num;
giuliomoro@197: 	}
giuliomoro@225: 
giuliomoro@197: 	/**
giuliomoro@197: 	 * Get the oldest channel message in the buffer.
giuliomoro@197: 	 *
giuliomoro@199: 	 * If this method is called when numAvailableMessages()==0, then
giuliomoro@199: 	 * a message with all fields set to zero is returned.
giuliomoro@225: 	 *
giuliomoro@197: 	 * @param type the type of the message to retrieve
giuliomoro@225: 	 *
giuliomoro@197: 	 * @return a copy of the oldest message of the give type in the buffer
giuliomoro@197: 	 */
giuliomoro@197: 	MidiChannelMessage getNextChannelMessage(/*MidiMessageType type*/){
giuliomoro@197: 		MidiChannelMessage message;
giuliomoro@197: 		message = messages[readPointer];
giuliomoro@197: 		if(message.getType() == kmmNone){
giuliomoro@197: 			message.clear();
giuliomoro@197: 		}
giuliomoro@197: 		messages[readPointer].setType(kmmNone); // do not use it again
giuliomoro@197: 		readPointer++;
giuliomoro@197: 		if(readPointer == messages.size()){
giuliomoro@197: 			readPointer = 0;
giuliomoro@197: 		}
giuliomoro@197: 		return message;
giuliomoro@197: 	};
giuliomoro@197: 
giuliomoro@197: //	MidiChannelMessage getNextChannelMessage(){
giuliomoro@197: //		getNextChannelMessage(kmmAny);
giuliomoro@197: //	}
giuliomoro@197: //	MidiControlChangeMessage* getNextControlChangeMessage(){
giuliomoro@197: //		return (MidiControlChangeMessage*)getNextChannelMessage(kmmControlChange);
giuliomoro@197: //	};
giuliomoro@197: //	MidiProgramChangeMessage* getNextProgramChangeMessage(){
giuliomoro@197: //		return (MidiProgramChangeMessage*)getNextChannelMessage(kmmProgramChange);
giuliomoro@197: //	};
giuliomoro@197: //	MidiNoteMessage* getNextNoteOnMessage(){
giuliomoro@197: //		return (MidiNoteMessage*)getNextChannelMessage(kmmNoteOn);
giuliomoro@197: //	};
giuliomoro@197: };
giuliomoro@197: 
giuliomoro@197: 
giuliomoro@181: class Midi {
giuliomoro@181: public:
giuliomoro@181: 	Midi();
giuliomoro@197: 
giuliomoro@197: 	/**
giuliomoro@197: 	 * Enable the input MidiParser.
giuliomoro@197: 	 *
giuliomoro@199: 	 * If the parser is enabled, getInput() will return an error code.
giuliomoro@197: 	 * Midi messages should instead be retrieved via, e.g.: getMidiParser()->getNextChannelMessage();
giuliomoro@197: 	 *
giuliomoro@197: 	 * @param enable true to enable the input MidiParser, false to disable it.
giuliomoro@197: 	 */
giuliomoro@197: 	void enableParser(bool enable);
giuliomoro@197: 
giuliomoro@197: 	/**
giuliomoro@197: 	 * Get access to the input parser in use, if any.
giuliomoro@197: 	 *
giuliomoro@197: 	 * @return a pointer to the instance of MidiParser, if currently enabled, zero otherwise.
giuliomoro@197: 	 */
giuliomoro@197: 	MidiParser* getParser();
giuliomoro@197: 
giuliomoro@225: 	/**
giuliomoro@225: 	 * Sets the callback to call when a new MidiChannelMessage is available
giuliomoro@225: 	 * from the input port.
giuliomoro@225: 	 *
giuliomoro@225: 	 * Internally, it calls enableParser() and the MidiParser::setCallback();
giuliomoro@225: 	 *
giuliomoro@226: 	 * @param newCallback the callback function.
giuliomoro@226: 	 * @param arg the second argument to be passed to the callback function.
giuliomoro@225: 	 */
giuliomoro@226: 	void setParserCallback(void (*callback)(MidiChannelMessage, void*), void* arg=NULL){
giuliomoro@224: 		// if callback is not NULL, also enable the parser
giuliomoro@224: 		enableParser(callback != NULL); //this needs to be first, as it deletes the parser(if exists)
giuliomoro@226: 		getParser()->setCallback(callback, arg);
giuliomoro@224: 	}
giuliomoro@224: 
giuliomoro@181: 	/**
giuliomoro@181: 	 * Open the specified input Midi port and start reading from it.
giuliomoro@181: 	 * @param port Midi port to open
giuliomoro@181: 	 * @return 1 on success, -1 on failure
giuliomoro@181: 	 */
giuliomoro@181: 	int readFrom(int port);
giuliomoro@325: 
giuliomoro@181: 	/**
giuliomoro@181: 	 * Open the specified output Midi port and prepares to write to it.
giuliomoro@181: 	 * @param port Midi port to open
giuliomoro@181: 	 * @return 1 on success, -1 on failure
giuliomoro@181: 	 */
giuliomoro@181: 	int writeTo(int port);
giuliomoro@181: 
giuliomoro@181: 	/**
giuliomoro@191: 	 * Get received midi bytes, one at a time.
giuliomoro@181: 	 * @return  -1 if no new byte is available, -2 on error,
giuliomoro@181: 	 * the oldest not yet retrieved midi byte otherwise
giuliomoro@181: 	*/
giuliomoro@181: 	int getInput();
giuliomoro@181: 
giuliomoro@181: 	/**
giuliomoro@181: 	 * Writes a Midi byte to the output port
giuliomoro@181: 	 * @param byte the Midi byte to write
giuliomoro@181: 	 * @return 1 on success, -1 on error
giuliomoro@181: 	 */
giuliomoro@181: 	int writeOutput(midi_byte_t byte);
giuliomoro@181: 
giuliomoro@181: 	/**
giuliomoro@181: 	 * Writes Midi bytes to the output port
giuliomoro@181: 	 * @param bytes an array of bytes to be written
giuliomoro@181: 	 * @param length number of bytes to write
giuliomoro@181: 	 * @return 1 on success, -1 on error
giuliomoro@181: 	 */
giuliomoro@191: 	int writeOutput(midi_byte_t* bytes, unsigned int length);
giuliomoro@197: 	/**
giuliomoro@197: 	 * Gives access to the midi parser, if it has been activated.
giuliomoro@197: 	 *
giuliomoro@197: 	 * @return a pointer to the midi parser if active, zero otherwise
giuliomoro@197: 	 */
giuliomoro@197: 	MidiParser* getMidiParser();
giuliomoro@181: 	virtual ~Midi();
giuliomoro@181: 	static void midiInputLoop();
giuliomoro@191: 	static void midiOutputLoop();
giuliomoro@181:     static bool staticConstructed;
giuliomoro@181: 	static void staticConstructor();
giuliomoro@181: private:
giuliomoro@199: 	int _getInput();
giuliomoro@181: 	void readInputLoop();
giuliomoro@191: 	void writeOutputLoop();
giuliomoro@181: 	int outputPort;
giuliomoro@181: 	int inputPort;
giuliomoro@181: 	std::vector<midi_byte_t> inputBytes;
giuliomoro@181: 	unsigned int inputBytesWritePointer;
giuliomoro@181: 	unsigned int inputBytesReadPointer;
giuliomoro@181: 	std::vector<midi_byte_t> outputBytes;
giuliomoro@191: 	unsigned int outputBytesWritePointer;
giuliomoro@191: 	unsigned int outputBytesReadPointer;
giuliomoro@197: 	MidiParser* inputParser;
giuliomoro@197: 	bool parserEnabled;
giuliomoro@191: 	static std::vector<Midi*> objAddrs[2];
giuliomoro@181: 	static AuxiliaryTask midiInputTask;
giuliomoro@181: 	static AuxiliaryTask midiOutputTask;
giuliomoro@181: };
giuliomoro@181: 
giuliomoro@181: 
giuliomoro@181: #endif /* MIDI_H_ */