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 5ef8f4023e32e
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
     1.1 --- a/AlsaBridge.cpp	Fri Dec 18 23:58:03 2020 +0100
     1.2 +++ b/AlsaBridge.cpp	Sat Dec 19 17:33:16 2020 +0100
     1.3 @@ -15,6 +15,9 @@
     1.4   * along with this program. If not, see <http://www.gnu.org/licenses/>.
     1.5   */
     1.6  #include <iostream>
     1.7 +#include <stdexcept>
     1.8 +#include <thread>
     1.9 +#include <atomic>
    1.10  
    1.11  #include <alsa/asoundlib.h>
    1.12  
    1.13 @@ -26,32 +29,63 @@
    1.14  class AlsaBridgeImpl : public AlsaBridge, private djmfix::MidiSender {
    1.15  private:
    1.16  	djmfix::DJMFix* djmFix;
    1.17 +	snd_rawmidi_t* input;
    1.18 +	snd_rawmidi_t* output;
    1.19 +	std::thread receivingThread;
    1.20 +	std::atomic<bool> stopped{false};
    1.21 +
    1.22 +	void run() {
    1.23 +		while (!stopped) {
    1.24 +			// TODO: poll
    1.25 +			uint8_t buffer[256];
    1.26 +			ssize_t length = snd_rawmidi_read(input, buffer, sizeof (buffer));
    1.27 +			if (length > 0 && length <= sizeof (buffer)) {
    1.28 +				// TODO: multiple messages combined together?
    1.29 +				djmFix->receive(MidiMessage(buffer, buffer + length));
    1.30 +			}
    1.31 +
    1.32 +			std::this_thread::sleep_for(std::chrono::milliseconds(100));
    1.33 +		}
    1.34 +	}
    1.35  public:
    1.36  
    1.37 -	AlsaBridgeImpl(djmfix::DJMFix* djmFix) : djmFix(djmFix) {
    1.38 +	AlsaBridgeImpl(djmfix::DJMFix* djmFix, const std::string& deviceName) : djmFix(djmFix) {
    1.39 +		if (djmFix == nullptr) throw std::invalid_argument("need a djmFix for AlsaBridge");
    1.40 +
    1.41 +		int error = snd_rawmidi_open(&input, &output, deviceName.c_str(), SND_RAWMIDI_NONBLOCK);
    1.42 +		if (error) throw std::invalid_argument("unable to open ALSA device");
    1.43 +
    1.44 +
    1.45  		djmFix->setMidiSender(this);
    1.46  	}
    1.47  
    1.48  	virtual ~AlsaBridgeImpl() {
    1.49 +		// TODO: do not use raw/exclusive access to the device
    1.50 +		snd_rawmidi_close(input);
    1.51 +		snd_rawmidi_close(output);
    1.52  		std::cerr << "~AlsaBridgeImpl()" << std::endl; // TODO: do not mess STDIO
    1.53  	}
    1.54  
    1.55  	virtual void start() override {
    1.56  		djmFix->start();
    1.57 +		receivingThread = std::thread(&AlsaBridgeImpl::run, this);
    1.58  	}
    1.59  
    1.60  	virtual void stop() override {
    1.61 +		stopped = true;
    1.62 +		receivingThread.join();
    1.63  		djmFix->stop();
    1.64  	}
    1.65  
    1.66  	virtual void send(MidiMessage midiMessage) override {
    1.67 -		std::cerr << "AlsaBridgeImpl::send()" << std::endl; // TODO: do not mess STDIO
    1.68 +		ssize_t length = snd_rawmidi_write(output, midiMessage.data(), midiMessage.size());
    1.69 +		std::cerr << "AlsaBridgeImpl::send(): length = " << length << std::endl; // TODO: do not mess STDIO
    1.70  	}
    1.71  
    1.72  };
    1.73  
    1.74 -AlsaBridge* create(djmfix::DJMFix* djmFix) {
    1.75 -	return new AlsaBridgeImpl(djmFix);
    1.76 +AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName) {
    1.77 +	return new AlsaBridgeImpl(djmFix, deviceName);
    1.78  }
    1.79  
    1.80  }
     2.1 --- a/AlsaBridge.h	Fri Dec 18 23:58:03 2020 +0100
     2.2 +++ b/AlsaBridge.h	Sat Dec 19 17:33:16 2020 +0100
     2.3 @@ -16,6 +16,8 @@
     2.4   */
     2.5  #pragma once
     2.6  
     2.7 +#include <string>
     2.8 +
     2.9  #include "DJMFix.h"
    2.10  
    2.11  namespace djmfix {
    2.12 @@ -29,7 +31,7 @@
    2.13  
    2.14  };
    2.15  
    2.16 -AlsaBridge* create(djmfix::DJMFix* djmFix);
    2.17 +AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName);
    2.18  
    2.19  }
    2.20  }
     3.1 --- a/DJMFix.cpp	Fri Dec 18 23:58:03 2020 +0100
     3.2 +++ b/DJMFix.cpp	Sat Dec 19 17:33:16 2020 +0100
     3.3 @@ -15,7 +15,9 @@
     3.4   * along with this program. If not, see <http://www.gnu.org/licenses/>.
     3.5   */
     3.6  #include <iostream>
     3.7 +#include <iomanip>
     3.8  #include <thread>
     3.9 +#include <atomic>
    3.10  #include <chrono>
    3.11  #include <stdexcept>
    3.12  
    3.13 @@ -27,8 +29,8 @@
    3.14  private:
    3.15  	MidiSender* midiSender;
    3.16  	std::thread keepAliveThread;
    3.17 -	bool running = false;
    3.18 -	bool stopped = false;
    3.19 +	std::atomic<bool> running{false};
    3.20 +	std::atomic<bool> stopped{false};
    3.21  
    3.22  	void run() {
    3.23  		while (!stopped) {
    3.24 @@ -38,6 +40,13 @@
    3.25  		}
    3.26  	}
    3.27  
    3.28 +	// TODO: remove
    3.29 +	std::string toString(const MidiMessage& midiMessage) {
    3.30 +		std::stringstream result;
    3.31 +		for (uint8_t b : midiMessage) result << std::hex << std::setw(2) << std::setfill('0') << (int) b;
    3.32 +		return result.str();
    3.33 +	}
    3.34 +
    3.35  public:
    3.36  
    3.37  	virtual ~DJMFixImpl() override {
    3.38 @@ -51,13 +60,22 @@
    3.39  	}
    3.40  
    3.41  	virtual void receive(MidiMessage midiMessage) override {
    3.42 -		std::cerr << "DJMFixImpl::receive()" << std::endl; // TODO: do not mess STDIO
    3.43 +		std::cerr << "DJMFixImpl::receive(): size = " << midiMessage.size() << " data = " << toString(midiMessage) << std::endl; // TODO: do not mess STDIO
    3.44 +
    3.45 +		if (midiMessage.size() == 54 && midiMessage[9] == 0x13) {
    3.46 +			std::cerr << "DJMFixImpl::receive(): got message with HashA and SeedE" << std::endl; // TODO: do not mess STDIO
    3.47 +		}
    3.48 +
    3.49  	}
    3.50  
    3.51  	void start() override {
    3.52  		std::cerr << "DJMFixImpl::start()" << std::endl; // TODO: do not mess STDIO
    3.53 -		if (midiSender == nullptr) throw std::logic_error("need a midiSender when starting");
    3.54 -		midiSender->send({0xf0, 0xf7});
    3.55 +		if (midiSender == nullptr) throw std::logic_error("need a midiSender when starting DJMFix");
    3.56 +
    3.57 +		// TODO: methods for parsing and constructing messages from parts (TLV)
    3.58 +		midiSender->send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7});
    3.59 +		std::this_thread::sleep_for(std::chrono::milliseconds(10));
    3.60 +		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});
    3.61  
    3.62  		keepAliveThread = std::thread(&DJMFixImpl::run, this);
    3.63  		running = true;
     4.1 --- a/djm-fix.cpp	Fri Dec 18 23:58:03 2020 +0100
     4.2 +++ b/djm-fix.cpp	Sat Dec 19 17:33:16 2020 +0100
     4.3 @@ -20,11 +20,12 @@
     4.4  #include <chrono>
     4.5  #include <thread>
     4.6  #include <csignal>
     4.7 +#include <atomic>
     4.8  
     4.9  #include "DJMFix.h"
    4.10  #include "AlsaBridge.h"
    4.11  
    4.12 -volatile static bool run = true;
    4.13 +static std::atomic<bool> run{true};
    4.14  
    4.15  void interrupt(int signal) {
    4.16  	run = false;
    4.17 @@ -32,9 +33,11 @@
    4.18  }
    4.19  
    4.20  int main(int argc, char**argv) {
    4.21 +	std::string deviceName = argc == 2 ? argv[1] : "hw:1"; // FIXME: parse CLI options + automatic device search
    4.22 +	
    4.23  	signal(SIGINT, interrupt);
    4.24  	std::unique_ptr<djmfix::DJMFix> djmFix(djmfix::create());
    4.25 -	std::unique_ptr<djmfix::alsa::AlsaBridge> alsaBridge(djmfix::alsa::create(djmFix.get()));
    4.26 +	std::unique_ptr<djmfix::alsa::AlsaBridge> alsaBridge(djmfix::alsa::create(djmFix.get(), deviceName));
    4.27  
    4.28  	alsaBridge->start();
    4.29  	while (run) std::this_thread::sleep_for(std::chrono::milliseconds(100));