1 /*
2 * midi_in.cc
3 * DIN Is Noise MIDI Input is copyright (c) 2006-2021 Jagannathan Sampath
4 * DIN Is Noise is released under GNU Public License 2.0
5 * For more information, please visit https://dinisnoise.org/
6 */
7 
8 #include "midi_in.h"
9 #include "console.h"
10 #include "tcl_interp.h"
11 #include "keyboard_keyboard.h"
12 #include "chrono.h"
13 #include "log.h"
14 #include <fstream>
15 #include <string>
16 using namespace std;
17 
18 extern double MIDI_BPM;
19 extern char BUFFER [];
20 
21 #define DIN_IS_NOISE_MIDI_INPUT "DIN Is Noise MIDI Input"
22 
23 #if defined __WINDOWS_MM__
midi_in()24 midi_in::midi_in () : rt (RtMidi::WINDOWS_MM, DIN_IS_NOISE_MIDI_INPUT)
25 #elif defined __MACOSX_CORE__
26 midi_in::midi_in () : rt (RtMidi::MACOSX_CORE, DIN_IS_NOISE_MIDI_INPUT)
27 #elif defined __UNIX_JACK__
28 midi_in::midi_in () : rt (RtMidi::UNIX_JACK, DIN_IS_NOISE_MIDI_INPUT)
29 #elif defined __LINUX_ALSA__
30 midi_in::midi_in () : rt (RtMidi::LINUX_ALSA, DIN_IS_NOISE_MIDI_INPUT)
31 #endif
32 
33 {
34 	available = 0;
35 	input_port = 0;
36 	channel = -1;
37 	probe ();
38 }
39 
probe()40 void midi_in::probe () {
41 	num_ports = rt.getPortCount ();
42 	int api = rt.getCurrentApi ();
43 	if (api == RtMidi::WINDOWS_MM) dlog << "*** current MIDI API = Windows Multimedia ***" << endl;
44 	else if (api == RtMidi::MACOSX_CORE) dlog << "*** current MIDI API = Mac OS X Core ***" << endl;
45 	else if (api == RtMidi::UNIX_JACK) dlog << "*** current MIDI API = JACK ***" << endl;
46   else if (api == RtMidi::LINUX_ALSA) dlog << "*** current MIDI API = ALSA ***" << endl;
47 	else dlog << "!!! MIDI API not found. No MIDI input is possible !!!" << endl;
48 	if (num_ports == 0) {
49 		dlog << "!!! found no MIDI input ports !!!" << endl;
50 	}
51 	else {
52 		dlog  << "+++ found " << num_ports << " MIDI input ports +++" << endl;
53 		names.clear ();
54 		for (int i = 0; i < num_ports; ++i) {
55 			string name = rt.getPortName (i);
56 			names.push_back (name);
57 		}
58 		input_port = 0;
59 	}
60 }
61 
open()62 void midi_in::open () {
63 	if (input_port >= num_ports) {
64 		dlog << "!!! couldnt open MIDI port " << input_port << " !!!" << endl;
65 	} else {
66 		if (available) rt.closePort ();
67 		rt.openPort (input_port);
68 		rt.ignoreTypes (0, 0, 0); // dont ignore sysex, timing or active sensing messages
69 		available = 1;
70 		dlog  << "+++ opened MIDI input port " << input_port << " ["<< rt.getPortName (input_port) << "] +++" << endl;
71 	}
72 }
73 
open(int port)74 void midi_in::open (int port) {
75 	input_port = port;
76 	open ();
77 }
78 
run_midi_cmd(const string & c,std::vector<unsigned char> & args)79 void run_midi_cmd (const string& c, std::vector<unsigned char>& args) {
80 	string cmd (c);
81 	for (size_t i = 0, j = args.size (); i < j; ++i) {
82 		sprintf (BUFFER, " %u", args[i]);
83 		cmd += BUFFER;
84 	}
85 	cons (cmd);
86 }
87 
handle_input()88 void midi_in::handle_input () {
89 
90 	if (available == 0) return;
91 
92 	std::vector<unsigned char> args;
93 	rt.getMessage (&args);
94 	int args_size = args.size ();
95 
96 	static int num_clocks = 0;
97 	static double start_time = ui_clk();
98 
99 	channel = cc = val = 0xff;
100 
101 	if (args_size) {
102 
103 		unsigned char type = args[0];
104 		unsigned char type_f0 = type & 0xf0;
105 		channel = type & 0x0f;
106 
107     args.push_back (channel);
108 		if (type_f0 == 0xb0) { // control change
109 			cc = args[1];
110 			val = args[2];
111 			run_midi_cmd ("midi-cc", args);
112 		} else if (type_f0 == 0x90) { // note on
113 			run_midi_cmd ("midi-note-on", args);
114 			keybd2.note_on (args[1], args[2]);
115 		} else if (type_f0 == 0x80) { // note off
116 			run_midi_cmd ("midi-note-off", args);
117 			keybd2.note_off (args[1]);
118 		} else if (type_f0 == 0xc0) { // program change
119 			run_midi_cmd ("midi-program-change", args);
120 		} else if (type_f0 == 0xe0) { // pitch bend
121 
122 			char status = args[0];
123 			char lsb = args[1];
124 			char msb = args[2];
125 
126 			// normalise pitchbend range to -1.0...1.0
127 			short value = (lsb | (msb << 7)) - 0x2000;
128 			float fvalue = (float)value / ((value < 0) ? 0x2000 : 0x1FFF);
129 
130 			keybd2.pitch_bend (fvalue);
131 
132 			ostringstream msg;
133 			msg << "midi-pitch-bend " << status << " " << value << " " << fvalue;
134 			cons (msg.str());
135 
136 		} else if (type == 0xf8) { // midi clock tick
137 			++num_clocks;
138 			double time_now = ui_clk();
139 			double elapsed_time = time_now - start_time;
140 			if (elapsed_time >= 1) {
141 				// from MIDI specification - see https://en.wikipedia.org/wiki/MIDI_beat_clock
142 				float quarter_notes_per_sec = num_clocks * 1.0f / (elapsed_time * 24);
143 				MIDI_BPM = quarter_notes_per_sec * 60; // a beat is a quarter note
144 				Tcl_UpdateLinkedVar (interpreter.interp, "midibpm"); // accessible as Tcl variable on DIN's command line
145 				cons ("midi-clock");
146 				num_clocks = 0;
147 				start_time = time_now;
148 			}
149 
150 		} else if (type == 0xfa) { // midi start
151 			cons ("midi-start");
152 			num_clocks = 0;
153 			start_time = ui_clk();
154 		}
155 	}
156 }
157