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