diff options
| author | Volker Fischer <corrados@users.noreply.github.com> | 2021-02-09 20:32:10 +0100 | 
|---|---|---|
| committer | Bent Bisballe Nyeng <deva@aasimon.org> | 2021-02-09 20:32:10 +0100 | 
| commit | 3dd3c332414bfbebd69bfd71a4a3198198525eb6 (patch) | |
| tree | 9dff61eab647703581c80b1e1b276571586696d7 | |
| parent | 9310ffe5959ce4de02204b6cd251d4b4a58c69b9 (diff) | |
Add ALSA midi input support.
| -rw-r--r-- | configure.ac | 26 | ||||
| -rw-r--r-- | drumgizmo/Makefile.am | 5 | ||||
| -rw-r--r-- | drumgizmo/drumgizmoc.cc | 6 | ||||
| -rw-r--r-- | drumgizmo/enginefactory.cc | 9 | ||||
| -rw-r--r-- | drumgizmo/enginefactory.h | 9 | ||||
| -rw-r--r-- | drumgizmo/input/alsamidi.cc | 196 | ||||
| -rw-r--r-- | drumgizmo/input/alsamidi.h | 57 | 
7 files changed, 301 insertions, 7 deletions
| diff --git a/configure.ac b/configure.ac index ee53a72..ac65c69 100644 --- a/configure.ac +++ b/configure.ac @@ -18,6 +18,7 @@ AC_SYS_LARGEFILE  AM_SILENT_RULES([yes])  need_jack=no +need_alsa=no  dnl ===========================  dnl Compile with C++11 support. @@ -417,6 +418,20 @@ AS_IF(      have_input_jackmidi=no]    ) +  dnl *** alsamidi +  AC_ARG_ENABLE([input_alsamidi], +    AS_HELP_STRING([--disable-input-alsamidi], [Disable input alsamidi plugin [default=enabled]]),, +           [enable_input_alsamidi="yes"]) + +  AS_IF( +    [test "x$enable_input_alsamidi" = "xyes"], +    [have_input_alsamidi=yes +     need_alsa=yes], + +    [AC_MSG_RESULT([*** input alsamidi plugin disabled per user request ***]) +    have_input_alsamidi=no] +  ) +    dnl *** Midifile    AC_ARG_ENABLE([input_midifile],  		AS_HELP_STRING([--disable-input-midifile], [Disable input midifile plugin [default=enabled]]),, @@ -513,10 +528,7 @@ AS_IF(    AS_IF(      [test "x$enable_output_alsa" = "xyes"],      [have_output_alsa=yes -     dnl ====================== -     dnl Check for alsa library -     dnl ====================== -     PKG_CHECK_MODULES(ALSA, alsa >= 1.0.18)], +     need_alsa=yes],      [AC_MSG_RESULT([*** output alsa plugin disabled per user request ***])       have_output_alsa=no] @@ -593,6 +605,7 @@ AM_CONDITIONAL([ENABLE_CLI], [test "x$enable_cli" == "xyes"])  AM_CONDITIONAL([HAVE_INPUT_DUMMY], [test "x$have_input_dummy" = "xyes"])  AM_CONDITIONAL([HAVE_INPUT_TEST], [test "x$have_input_test" = "xyes"])  AM_CONDITIONAL([HAVE_INPUT_JACKMIDI], [test "x$have_input_jackmidi" = "xyes"]) +AM_CONDITIONAL([HAVE_INPUT_ALSAMIDI], [test "x$have_input_alsamidi" = "xyes"])  AM_CONDITIONAL([HAVE_INPUT_OSSMIDI], [test "x$have_input_ossmidi" = "xyes"])  AM_CONDITIONAL([HAVE_INPUT_MIDIFILE], [test "x$have_input_midifile" = "xyes"])  AM_CONDITIONAL([HAVE_OUTPUT_DUMMY], [test "x$have_output_dummy" = "xyes"]) @@ -708,6 +721,11 @@ dnl Check for jack  dnl ======================  AS_IF([test "x$need_jack" = "xyes"], [PKG_CHECK_MODULES(JACK, jack >= 0.120.1)]) +dnl ====================== +dnl Check for alsa library +dnl ====================== +AS_IF([test "x$need_alsa" = "xyes"], [PKG_CHECK_MODULES(ALSA, alsa >= 1.0.18)]) +  AC_SUBST(CFLAGS)  AC_SUBST(CPPFLAGS)  AC_SUBST(CXXFLAGS) diff --git a/drumgizmo/Makefile.am b/drumgizmo/Makefile.am index b3bca32..7702b2e 100644 --- a/drumgizmo/Makefile.am +++ b/drumgizmo/Makefile.am @@ -77,6 +77,10 @@ drumgizmo_SOURCES += input/ossmidi.cc  drumgizmo_CXXFLAGS += -DHAVE_INPUT_OSSMIDI  endif # HAVE_INPUT_OSSMIDI +if HAVE_INPUT_ALSAMIDI +drumgizmo_SOURCES += input/alsamidi.cc +drumgizmo_CXXFLAGS += -DHAVE_INPUT_ALSAMIDI +endif # HAVE_INPUT_ALSAMIDI  # Only compile jackclient.cc if at least one of the jack modules are included.  if HAVE_OUTPUT_JACKAUDIO @@ -93,6 +97,7 @@ EXTRA_DIST = \  	input/inputdummy.h \  	input/test.h \  	input/jackmidi.h \ +	input/alsamidi.h \  	input/midifile.h \  	input/ossmidi.h \  	output/alsa.h \ diff --git a/drumgizmo/drumgizmoc.cc b/drumgizmo/drumgizmoc.cc index 159c533..c6b43f0 100644 --- a/drumgizmo/drumgizmoc.cc +++ b/drumgizmo/drumgizmoc.cc @@ -94,6 +94,7 @@ static std::string arguments()  	output <<  		"Input engine parameters:\n"  		"  jackmidi:  midimap=<midimapfile>\n" +		"  alsamidi:  midimap=<midimapfile>\n"  		"  midifile:  file=<midifile>, speed=<tempo> (default 1.0),\n"  		"             track=<miditrack> (default -1, all tracks)\n"  		"             midimap=<midimapfile>, loop=<true|false>\n" @@ -105,7 +106,8 @@ static std::string arguments()  		"\n"  		"Output engine parameters:\n"  		"  alsa:      dev=<device> (default 'default'), frames=<frames> (default " -		"32)\n" +		"32),\n" +		"             periods=<periods> (default 3)\n"  		"             srate=<samplerate> (default 441000)\n"  		"  oss:       dev=<device> (default '/dev/dsp'), srate=<samplerate>,\n"  		"             max_fragments=<number> (default 4, see man page for more info),\n" @@ -261,7 +263,7 @@ int main(int argc, char* argv[])  	        });  	opt.add("inputengine", required_argument, 'i', -	        "dummy|test|jackmidi|midifile  Use said event input engine.", +	        "dummy|test|jackmidi|alsamidi|midifile  Use said event input engine.",  	        [&]()  	        {  		        std::string engine = optarg; diff --git a/drumgizmo/enginefactory.cc b/drumgizmo/enginefactory.cc index c93607e..6d267c3 100644 --- a/drumgizmo/enginefactory.cc +++ b/drumgizmo/enginefactory.cc @@ -49,6 +49,9 @@ EngineFactory::EngineFactory()  #ifdef HAVE_INPUT_JACKMIDI  	input.push_back("jackmidi");  #endif +#ifdef HAVE_INPUT_ALSAMIDI +	input.push_back("alsamidi"); +#endif  #ifdef HAVE_INPUT_OSS  	input.push_back("oss");  #endif @@ -118,6 +121,12 @@ std::unique_ptr<AudioInputEngine> EngineFactory::createInput(const std::string&  		return std::make_unique<JackMidiInputEngine>(*jack);  	}  #endif +#ifdef HAVE_INPUT_ALSAMIDI +	if(name == "alsamidi") +	{ +		return std::make_unique<AlsaMidiInputEngine>(); +	} +#endif  #ifdef HAVE_INPUT_OSSMIDI  	if(name == "ossmidi")  	{ diff --git a/drumgizmo/enginefactory.h b/drumgizmo/enginefactory.h index 0b37c6e..7dbc9b3 100644 --- a/drumgizmo/enginefactory.h +++ b/drumgizmo/enginefactory.h @@ -53,6 +53,10 @@  #include "input/jackmidi.h"  #endif +#ifdef HAVE_INPUT_ALSAMIDI +#include "input/alsamidi.h" +#endif +  #ifdef HAVE_INPUT_OSS  #include "input/ossmidi.h"  #endif @@ -77,11 +81,14 @@  #include "output/oss.h"  #endif -  #ifdef HAVE_INPUT_OSSMIDI  #include "input/ossmidi.h"  #endif +#ifdef HAVE_INPUT_ALSAMIDI +#include "input/alsamidi.h" +#endif +  //! Factory for various input- and output engines  class EngineFactory diff --git a/drumgizmo/input/alsamidi.cc b/drumgizmo/input/alsamidi.cc new file mode 100644 index 0000000..068ea2b --- /dev/null +++ b/drumgizmo/input/alsamidi.cc @@ -0,0 +1,196 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + *            alsamidi.cc + * + *  Copyright 2021 Volker Fischer (github.com/corrados) + ****************************************************************************/ + +/* + *  This file is part of DrumGizmo. + * + *  DrumGizmo is free software; you can redistribute it and/or modify + *  it under the terms of the GNU Lesser General Public License as published by + *  the Free Software Foundation; either version 3 of the License, or + *  (at your option) any later version. + * + *  DrumGizmo is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU Lesser General Public License for more details. + * + *  You should have received a copy of the GNU Lesser General Public License + *  along with DrumGizmo; if not, write to the Free Software + *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. + */ +#include <iostream> +#include <cassert> + +#include "cpp11fix.h" // required for c++11 +#include "alsamidi.h" + +struct AlsaMidiInitError +{ +	int const code; +	const std::string msg; + +	AlsaMidiInitError(int op_code, const std::string& msg) +		: code{op_code} +		, msg{msg} +	{ +	} + +	static inline void test(int code, const std::string& msg) +	{ +		if(code < 0) +		{ +			throw AlsaMidiInitError(code, msg); +		} +	} +}; + +AlsaMidiInputEngine::AlsaMidiInputEngine() +	: AudioInputEngineMidi{} +	, in_port(0) +	, seq_handle{nullptr} +	, pos{0u} +	, events{} +{ +} + +AlsaMidiInputEngine::~AlsaMidiInputEngine() +{ +	if(seq_handle != nullptr) +	{ +		snd_seq_close(seq_handle); +	} +} + +bool AlsaMidiInputEngine::init(const Instruments& instruments) +{ +	if(!loadMidiMap(midimap_file, instruments)) +	{ +		std::cerr << "[AlsaMidiInputEngine] Failed to parse midimap '" +		          << midimap_file << "'\n"; +		return false; +	} + +	// try to initialize alsa MIDI +	try +	{ +		// it is not allowed to block in the run() function, therefore we +		// have to use a non-blocking mode +		int value = snd_seq_open(&seq_handle, "default", +		                         SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK); +		AlsaMidiInitError::test(value, "snd_seq_open"); + +		value = snd_seq_set_client_name(seq_handle, "drumgizmo"); +		AlsaMidiInitError::test(value, "snd_seq_set_client_name"); + +		in_port = +			snd_seq_create_simple_port(seq_handle, "listen:in", +			                           SND_SEQ_PORT_CAP_WRITE | +			                           SND_SEQ_PORT_CAP_SUBS_WRITE, +			                           SND_SEQ_PORT_TYPE_APPLICATION); +		AlsaMidiInitError::test(in_port, "snd_seq_create_simple_port"); +	} +	catch(AlsaMidiInitError const& error) +	{ +		std::cerr << "[AlsaMidiInputEngine] " << error.msg +		          << " failed: " << snd_strerror(error.code) << std::endl; +		return false; +	} + +	return true; +} + +void AlsaMidiInputEngine::setParm(const std::string& parm, +                                  const std::string& value) +{ +	if(parm == "midimap") +	{ +		// apply midimap filename +		midimap_file = value; +	} +	else +	{ +		std::cerr << "[AlsaMidiInputEngine] Unsupported parameter '" << parm +		          << "'\n"; +	} +} + +bool AlsaMidiInputEngine::start() +{ +	return true; +} + +void AlsaMidiInputEngine::stop() +{ +} + +void AlsaMidiInputEngine::pre() +{ +} + +void AlsaMidiInputEngine::run(size_t pos, size_t len, +                              std::vector<event_t>& events) +{ +	assert(events.empty()); +	snd_seq_event_t* ev = NULL; +	if ( snd_seq_event_input(seq_handle, &ev) >= 0 ) +	{ +		// TODO Better solution needed: The ALSA MIDI event structure does +		// not seem to contain the raw MIDI data, therefore we have to re-create +		// the raw MIDI data based on the ALSA sequence event information. +		std::vector<uint8_t> midi_buffer(3, 0); +		bool message_type_handled = true; + +		switch(ev->type) +		{ +		case SND_SEQ_EVENT_NOTEON: +			midi_buffer[0] = ev->data.note.channel + 0x90; // NoteOn +			midi_buffer[1] = ev->data.note.note; +			midi_buffer[2] = ev->data.note.velocity; +			break; + +		case SND_SEQ_EVENT_NOTEOFF: +			midi_buffer[0] = ev->data.note.channel + 0x80; // NoteOff +			midi_buffer[1] = ev->data.note.note; +			midi_buffer[2] = ev->data.note.off_velocity; +			break; + +		case SND_SEQ_EVENT_KEYPRESS: +			midi_buffer[0] = ev->data.note.channel + 0xA0; // NoteAftertouch +			midi_buffer[1] = ev->data.note.note; +			midi_buffer[2] = ev->data.note.velocity; +			break; + +		case SND_SEQ_EVENT_CONTROLLER: +			midi_buffer[0] = ev->data.control.channel + 0xB0; // ControlChange +			midi_buffer[1] = ev->data.control.param; +			midi_buffer[2] = ev->data.control.value; +			break; + +		default: +			// unkown message type, ignore the message +			message_type_handled = false; +			break; +		} + +		if(message_type_handled) +		{ +			// since we do not want to introduce any additional delay for the +			// MIDI processing, we set the offset to zero +			processNote(midi_buffer.data(), midi_buffer.size(), 0, events); +		} +	} +	snd_seq_free_event(ev); +} + +void AlsaMidiInputEngine::post() +{ +} + +bool AlsaMidiInputEngine::isFreewheeling() const +{ +	return true; +} diff --git a/drumgizmo/input/alsamidi.h b/drumgizmo/input/alsamidi.h new file mode 100644 index 0000000..73e0fc8 --- /dev/null +++ b/drumgizmo/input/alsamidi.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + *            alsamidi.h + * + *  Copyright 2021 Volker Fischer (github.com/corrados) + ****************************************************************************/ + +/* + *  This file is part of DrumGizmo. + * + *  DrumGizmo is free software; you can redistribute it and/or modify + *  it under the terms of the GNU Lesser General Public License as published by + *  the Free Software Foundation; either version 3 of the License, or + *  (at your option) any later version. + * + *  DrumGizmo is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU Lesser General Public License for more details. + * + *  You should have received a copy of the GNU Lesser General Public License + *  along with DrumGizmo; if not, write to the Free Software + *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. + */ +#pragma once +#include <memory> +#include <alsa/asoundlib.h> + +#include "audioinputenginemidi.h" +#include "midimapper.h" +#include "midimapparser.h" + +class AlsaMidiInputEngine +	: public AudioInputEngineMidi +{ +public: +	AlsaMidiInputEngine(); +	~AlsaMidiInputEngine(); + +	// based on AudioInputEngineMidi +	bool init(const Instruments& instruments) override; +	void setParm(const std::string& parm, const std::string& value) override; +	bool start() override; +	void stop() override; +	void pre() override; +	void run(size_t pos, size_t len, std::vector<event_t>& events) override; +	void post() override; +	bool isFreewheeling() const override; + +private: +	int in_port; +	snd_seq_t* seq_handle; + +	std::string midimap_file; +	std::size_t pos; +	std::vector<event_t> events; +}; | 
