sending and receiving MIDI messages through ALSA (the dirty way) v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sat, 19 Dec 2020 17:33:16 +0100
branchv_0
changeset 5 ef8f4023e32e
parent 4 4d777d6c8024
child 6 bddcf2bf29f2
sending and receiving MIDI messages through ALSA (the dirty way)
AlsaBridge.cpp
AlsaBridge.h
DJMFix.cpp
djm-fix.cpp
--- a/AlsaBridge.cpp	Fri Dec 18 23:58:03 2020 +0100
+++ b/AlsaBridge.cpp	Sat Dec 19 17:33:16 2020 +0100
@@ -15,6 +15,9 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #include <iostream>
+#include <stdexcept>
+#include <thread>
+#include <atomic>
 
 #include <alsa/asoundlib.h>
 
@@ -26,32 +29,63 @@
 class AlsaBridgeImpl : public AlsaBridge, private djmfix::MidiSender {
 private:
 	djmfix::DJMFix* djmFix;
+	snd_rawmidi_t* input;
+	snd_rawmidi_t* output;
+	std::thread receivingThread;
+	std::atomic<bool> stopped{false};
+
+	void run() {
+		while (!stopped) {
+			// TODO: poll
+			uint8_t buffer[256];
+			ssize_t length = snd_rawmidi_read(input, buffer, sizeof (buffer));
+			if (length > 0 && length <= sizeof (buffer)) {
+				// TODO: multiple messages combined together?
+				djmFix->receive(MidiMessage(buffer, buffer + length));
+			}
+
+			std::this_thread::sleep_for(std::chrono::milliseconds(100));
+		}
+	}
 public:
 
-	AlsaBridgeImpl(djmfix::DJMFix* djmFix) : djmFix(djmFix) {
+	AlsaBridgeImpl(djmfix::DJMFix* djmFix, const std::string& deviceName) : djmFix(djmFix) {
+		if (djmFix == nullptr) throw std::invalid_argument("need a djmFix for AlsaBridge");
+
+		int error = snd_rawmidi_open(&input, &output, deviceName.c_str(), SND_RAWMIDI_NONBLOCK);
+		if (error) throw std::invalid_argument("unable to open ALSA device");
+
+
 		djmFix->setMidiSender(this);
 	}
 
 	virtual ~AlsaBridgeImpl() {
+		// TODO: do not use raw/exclusive access to the device
+		snd_rawmidi_close(input);
+		snd_rawmidi_close(output);
 		std::cerr << "~AlsaBridgeImpl()" << std::endl; // TODO: do not mess STDIO
 	}
 
 	virtual void start() override {
 		djmFix->start();
+		receivingThread = std::thread(&AlsaBridgeImpl::run, this);
 	}
 
 	virtual void stop() override {
+		stopped = true;
+		receivingThread.join();
 		djmFix->stop();
 	}
 
 	virtual void send(MidiMessage midiMessage) override {
-		std::cerr << "AlsaBridgeImpl::send()" << std::endl; // TODO: do not mess STDIO
+		ssize_t length = snd_rawmidi_write(output, midiMessage.data(), midiMessage.size());
+		std::cerr << "AlsaBridgeImpl::send(): length = " << length << std::endl; // TODO: do not mess STDIO
 	}
 
 };
 
-AlsaBridge* create(djmfix::DJMFix* djmFix) {
-	return new AlsaBridgeImpl(djmFix);
+AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName) {
+	return new AlsaBridgeImpl(djmFix, deviceName);
 }
 
 }
--- a/AlsaBridge.h	Fri Dec 18 23:58:03 2020 +0100
+++ b/AlsaBridge.h	Sat Dec 19 17:33:16 2020 +0100
@@ -16,6 +16,8 @@
  */
 #pragma once
 
+#include <string>
+
 #include "DJMFix.h"
 
 namespace djmfix {
@@ -29,7 +31,7 @@
 
 };
 
-AlsaBridge* create(djmfix::DJMFix* djmFix);
+AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName);
 
 }
 }
--- a/DJMFix.cpp	Fri Dec 18 23:58:03 2020 +0100
+++ b/DJMFix.cpp	Sat Dec 19 17:33:16 2020 +0100
@@ -15,7 +15,9 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 #include <iostream>
+#include <iomanip>
 #include <thread>
+#include <atomic>
 #include <chrono>
 #include <stdexcept>
 
@@ -27,8 +29,8 @@
 private:
 	MidiSender* midiSender;
 	std::thread keepAliveThread;
-	bool running = false;
-	bool stopped = false;
+	std::atomic<bool> running{false};
+	std::atomic<bool> stopped{false};
 
 	void run() {
 		while (!stopped) {
@@ -38,6 +40,13 @@
 		}
 	}
 
+	// TODO: remove
+	std::string toString(const MidiMessage& midiMessage) {
+		std::stringstream result;
+		for (uint8_t b : midiMessage) result << std::hex << std::setw(2) << std::setfill('0') << (int) b;
+		return result.str();
+	}
+
 public:
 
 	virtual ~DJMFixImpl() override {
@@ -51,13 +60,22 @@
 	}
 
 	virtual void receive(MidiMessage midiMessage) override {
-		std::cerr << "DJMFixImpl::receive()" << std::endl; // TODO: do not mess STDIO
+		std::cerr << "DJMFixImpl::receive(): size = " << midiMessage.size() << " data = " << toString(midiMessage) << std::endl; // TODO: do not mess STDIO
+
+		if (midiMessage.size() == 54 && midiMessage[9] == 0x13) {
+			std::cerr << "DJMFixImpl::receive(): got message with HashA and SeedE" << std::endl; // TODO: do not mess STDIO
+		}
+
 	}
 
 	void start() override {
 		std::cerr << "DJMFixImpl::start()" << std::endl; // TODO: do not mess STDIO
-		if (midiSender == nullptr) throw std::logic_error("need a midiSender when starting");
-		midiSender->send({0xf0, 0xf7});
+		if (midiSender == nullptr) throw std::logic_error("need a midiSender when starting DJMFix");
+
+		// TODO: methods for parsing and constructing messages from parts (TLV)
+		midiSender->send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7});
+		std::this_thread::sleep_for(std::chrono::milliseconds(10));
+		midiSender->send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x12, 0x2a, 0x01, 0x0b, 0x50, 0x69, 0x6f, 0x6e, 0x65, 0x65, 0x72, 0x44, 0x4a, 0x02, 0x0b, 0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78, 0x03, 0x12, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0xf7});
 
 		keepAliveThread = std::thread(&DJMFixImpl::run, this);
 		running = true;
--- a/djm-fix.cpp	Fri Dec 18 23:58:03 2020 +0100
+++ b/djm-fix.cpp	Sat Dec 19 17:33:16 2020 +0100
@@ -20,11 +20,12 @@
 #include <chrono>
 #include <thread>
 #include <csignal>
+#include <atomic>
 
 #include "DJMFix.h"
 #include "AlsaBridge.h"
 
-volatile static bool run = true;
+static std::atomic<bool> run{true};
 
 void interrupt(int signal) {
 	run = false;
@@ -32,9 +33,11 @@
 }
 
 int main(int argc, char**argv) {
+	std::string deviceName = argc == 2 ? argv[1] : "hw:1"; // FIXME: parse CLI options + automatic device search
+	
 	signal(SIGINT, interrupt);
 	std::unique_ptr<djmfix::DJMFix> djmFix(djmfix::create());
-	std::unique_ptr<djmfix::alsa::AlsaBridge> alsaBridge(djmfix::alsa::create(djmFix.get()));
+	std::unique_ptr<djmfix::alsa::AlsaBridge> alsaBridge(djmfix::alsa::create(djmFix.get(), deviceName));
 
 	alsaBridge->start();
 	while (run) std::this_thread::sleep_for(std::chrono::milliseconds(100));