// intellectual property is bullshit bgdc

#ifndef MIDI_H
#define MIDI_H

#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>

class midix;

// == MIDI COMMANDS ===========================================================

// All midi commands use values >= 0x80 as the high bit marks the start of a
// packet. We use values < 0x80 as internal events so all may share the same
// queue.

struct midi_message {
	midix * source;
	uint8_t command;
	union {
		uint8_t   arguments[2];
		uint8_t * content;
	};
};

#define MIDI_CMD_HAS_CHAN(cmd) ((cmd) >= 0x80 && (cmd) <= 0xEF)
#define MIDI_CMD_CHAN(cmd)     ((cmd) & 0x0F)
#define MIDI_CMD(cmd)          (MIDI_CMD_HAS_CHAN(cmd) ? ((cmd) & 0xF0) : (cmd))

// Internal command events

#define MIDI_ICMD_NOP            0x00
#define MIDI_ICMD_SYSEX          0x01 // sysex text via midi_message.content,
                                      // NUL-terminated
#define MIDI_ICMD_SYSEX_OVERFLOW 0x02

// MIDI events

#define MIDI_CMD_NOTE_OFF   0x80  // key   velocity
#define MIDI_CMD_NOTE_ON    0x90  // key   velocity
#define MIDI_CMD_AFTERTOUCH 0xA0  // key   touch
#define MIDI_CMD_CC         0xB0  // cc#   value
#define MIDI_CMD_PATCH      0xC0  // inst# ?
#define MIDI_CMD_PRESSURE   0xD0  // pres  -n/a-
#define MIDI_CMD_PITCHBEND  0xE0  // lsb   msb


#define MIDI_CMD_START_SYSEX   0xF0
#define MIDI_CMD_TIMECODE_QFRM 0xF1
#define MIDI_CMD_SONG_POS_PTR  0xF2
#define MIDI_CMD_SONG_SELECT   0xF3
#define MIDI_CMD_TUNE_REQ      0xF6
#define MIDI_CMD_END_SYSEX     0xF7
#define MIDI_CMD_TIMING_CLK    0xF8
#define MIDI_CMD_START         0xFA
#define MIDI_CMD_CONTINUE      0xFB
#define MIDI_CMD_STOP          0xFC
#define MIDI_CMD_ACTIVESENSE   0xFE
#define MIDI_CMD_SYSRESET      0xFF

// Convert a single MIDI argument to a signed int
static inline int midi_signed(uint8_t arg)
{
	uint16_t v = arg;
	if (arg & 0x40)
		v |= 0xFF80;
	return (int16_t) v;
}

// Convert two MIDI arguments to a signed int
static inline int midi_double_signed(uint8_t lsb, uint8_t msb)
{
	uint16_t v = lsb | (msb << 7);
	if (v & 0x2000)
		v |= 0xC000;
	return (int16_t) v;
}

// Return the number of arguments expected for a command
static inline int midi_num_args(uint8_t cmd)
{
	cmd = MIDI_CMD(cmd);
	switch (cmd)
	{
	case MIDI_CMD_NOTE_OFF:   return 2;
	case MIDI_CMD_NOTE_ON:    return 2;
	case MIDI_CMD_AFTERTOUCH: return 2;
	case MIDI_CMD_CC:         return 2;
	case MIDI_CMD_PATCH:      return 2;
	case MIDI_CMD_PRESSURE:   return 1;
	case MIDI_CMD_PITCHBEND:  return 2;
	default: return 0;
	}
}

#endif // !defined(MIDI_H)
