integrated libmidi into Musique codebase

This commit is contained in:
Robert Bendun 2022-10-07 18:06:30 +02:00
parent b6b1209014
commit 6389535443
14 changed files with 377 additions and 21 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "lib/midi"]
path = lib/midi
url = https://git.wmi.amu.edu.pl/s416496/midi.git

View File

@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Added `scan` builtin, which computes prefix sum of passed values when provided with addition operator
### Changed
* Integrated libmidi library into Musique codebase
### Fixed
* Prevented accidental recursive construction of Arrays and Values by making convinience constructor `Value::Value(std::vector<Value>&&)` explicit

View File

@ -21,16 +21,6 @@ $ sudo apt install -y build-essential libasound2-dev
## Budowanie interpretera
Jeśli nie posiadasz zależności `lib/midi` to:
```console
$ git submodule init
$ git submodule update
$ (cd lib/midi; make)
```
A następnie zbuduj interpreter:
```console
$ make bin/musique
```

View File

@ -1,12 +1,12 @@
MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)"
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result -Wno-maybe-uninitialized
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/midi/include -I. -Ilib/bestline/
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -I. -Ilib/bestline/
RELEASE_FLAGS=-O2
DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined -DDebug
CXX=g++
LDFLAGS=-L./lib/midi/ -flto
LDLIBS=-lmidi-alsa -lasound -lpthread -static-libgcc -static-libstdc++
LDFLAGS=-flto
LDLIBS=-lasound -lpthread -static-libgcc -static-libstdc++

@ -1 +0,0 @@
Subproject commit f42b663f0d08fc629c7deb26cd32ee06fba76d83

View File

@ -1,7 +1,7 @@
#ifndef MUSIQUE_INTERPRETER_HH
#define MUSIQUE_INTERPRETER_HH
#include <midi.hh>
#include <musique/midi/midi.hh>
#include <musique/interpreter/context.hh>
#include <musique/value/value.hh>

View File

@ -6,11 +6,11 @@
#include <span>
#include <thread>
#include <midi.hh>
#include <musique/interpreter/env.hh>
#include <musique/format.hh>
#include <musique/interpreter/env.hh>
#include <musique/interpreter/interpreter.hh>
#include <musique/lexer/lines.hh>
#include <musique/midi/midi.hh>
#include <musique/parser/parser.hh>
#include <musique/pretty.hh>
#include <musique/try.hh>

5
musique/midi/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
example-usage-alsa
compile_commands.json
.ccls-cache
*.a
*.o

13
musique/midi/Makefile Normal file
View File

@ -0,0 +1,13 @@
all: libmidi-alsa.a
midi.o: midi.cc midi.hh
g++ -c -std=c++20 -Wall -Wextra $< -o $@ -I.
midi-alsa.o: alsa.cc midi.hh
g++ -c -std=c++20 -Wall -Wextra $< -o $@ -I.
libmidi-alsa.a: midi.o midi-alsa.o
ar rcs $@ $^
example-usage-alsa: example-usage-alsa.cc libmidi-alsa.a
g++ -std=c++20 -Wall -Wextra $< -o $@ -L. -lmidi-alsa -lasound -Iinclude -g

13
musique/midi/README.md Normal file
View File

@ -0,0 +1,13 @@
# `midi` sub library
Utility library for interfacing MIDI protocol in different environments.
## Available interfaces
### ALSA
To use with ALSA before including library define `MIDI_ENABLE_ALSA_SUPPORT` macro.
To compile static library use `make libmidi-alsa`.
When in doubt, see [`example-usage-alsa.cc`](./example-usage-alsa.cc).

237
musique/midi/alsa.cc Normal file
View File

@ -0,0 +1,237 @@
#define MIDI_ENABLE_ALSA_SUPPORT
#include <musique/midi/midi.hh>
#include <cassert>
#include <iomanip>
#include <iostream>
/// Ensures that operation performed by ALSA was successfull
static int ensure_snd(int maybe_error, std::string_view message)
{
if (maybe_error < 0) {
std::cerr << "ALSA ERROR: " << message << ": " << snd_strerror(maybe_error) << std::endl;
std::exit(1);
}
return maybe_error;
}
static void create_source_port(midi::ALSA &alsa);
static void create_queue(midi::ALSA &alsa);
static void connect_port(midi::ALSA &alsa);
static void send_note_event(midi::ALSA &alsa, snd_seq_event_type type, uint8_t channel, uint8_t note, uint8_t velocity);
midi::ALSA::ALSA(std::string connection_name)
: connection_name(connection_name)
{
}
void midi::ALSA::init_sequencer()
{
ensure_snd(snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0), "opening sequencer");
ensure_snd(snd_seq_set_client_name(seq, connection_name.c_str()), "setting client name");
ensure_snd(client = snd_seq_client_id(seq), "getting client id");
}
void midi::ALSA::connect_output(std::string const& target)
{
assert(!connected);
ensure_snd(snd_seq_parse_address(seq, &output_port_addr, target.c_str()), "Invalid port specification");
create_source_port(*this);
create_queue(*this);
connect_port(*this);
connected = true;
}
void midi::ALSA::connect_input(std::string const& target)
{
ensure_snd(snd_seq_parse_address(seq, &input_port_addr, target.c_str()), "Invalid port specification");
input_port = ensure_snd(snd_seq_create_simple_port(seq, connection_name.c_str(),
SND_SEQ_PORT_CAP_WRITE |
SND_SEQ_PORT_CAP_SUBS_WRITE,
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
SND_SEQ_PORT_TYPE_APPLICATION), "create simple port");
ensure_snd(snd_seq_connect_from(seq, input_port, input_port_addr.client, input_port_addr.port),
"connect from");
}
void midi::ALSA::list_ports(std::ostream& out) const
{
snd_seq_client_info_t *c;
snd_seq_port_info_t *p;
// Wonders of C based API <3
snd_seq_client_info_alloca(&c);
snd_seq_port_info_alloca(&p);
out << " Port Client name Port name\n";
snd_seq_client_info_set_client(c, -1);
while (snd_seq_query_next_client(seq, c) >= 0) {
int client = snd_seq_client_info_get_client(c);
snd_seq_port_info_set_client(p, client);
snd_seq_port_info_set_port(p, -1);
while (snd_seq_query_next_port(seq, p) >= 0) {
// Port must support MIDI messages
if (!(snd_seq_port_info_get_type(p) & SND_SEQ_PORT_TYPE_MIDI_GENERIC)) continue;
// Both WRITE and SUBS_WRITE are required
// TODO Aquire knowladge why
auto const exp = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
if ((snd_seq_port_info_get_capability(p) & exp) != exp) continue;
out <<
std::setw(3) << std::right << snd_seq_port_info_get_client(p) << ':' <<
std::setw(3) << std::left << snd_seq_port_info_get_port(p) << " " <<
std::setw(32) << std::left << snd_seq_client_info_get_name(c) << " " <<
snd_seq_port_info_get_name(p) << '\n';
}
}
out << std::flush;
}
void midi::ALSA::send_note_on(uint8_t channel, uint8_t note_number, uint8_t velocity)
{
send_note_event(*this, SND_SEQ_EVENT_NOTEON, channel, note_number, velocity);
}
void midi::ALSA::send_note_off(uint8_t channel, uint8_t note_number, uint8_t velocity)
{
send_note_event(*this, SND_SEQ_EVENT_NOTEOFF, channel, note_number, velocity);
}
void midi::ALSA::send_program_change(uint8_t channel, uint8_t program)
{
snd_seq_event_t ev;
snd_seq_ev_clear(&ev);
ev.queue = queue;
ev.source.port = 0;
ev.flags = SND_SEQ_TIME_STAMP_TICK;
ev.type = SND_SEQ_EVENT_PGMCHANGE;
ev.time.tick = 0;
ev.dest = output_port_addr;
snd_seq_ev_set_fixed(&ev);
ev.data.control.channel = channel;
ev.data.control.value = program;
ensure_snd(snd_seq_event_output(seq, &ev), "output event");
ensure_snd(snd_seq_drain_output(seq), "drain output queue");
}
void midi::ALSA::send_controller_change(uint8_t channel, uint8_t controller_number, uint8_t value)
{
snd_seq_event_t ev;
snd_seq_ev_clear(&ev);
ev.queue = queue;
ev.source.port = 0;
ev.flags = SND_SEQ_TIME_STAMP_TICK;
ev.type = SND_SEQ_EVENT_CONTROLLER;
ev.time.tick = 0;
ev.dest = output_port_addr;
snd_seq_ev_set_fixed(&ev);
ev.data.control.channel = channel;
ev.data.control.param = controller_number;
ev.data.control.value = value;
ensure_snd(snd_seq_event_output(seq, &ev), "output event");
ensure_snd(snd_seq_drain_output(seq), "drain output queue");
}
void midi::ALSA::input_event_loop(std::stop_token stop_token)
{
int const poll_fd_count = snd_seq_poll_descriptors_count(seq, POLLIN);
std::vector<pollfd> poll_fds(poll_fd_count);
ensure_snd(snd_seq_poll_descriptors(seq, poll_fds.data(), poll_fds.size(), POLLIN), "poll descriptors");
while (!stop_token.stop_requested()) {
if (poll(poll_fds.data(), poll_fds.size(), -1) < 0) {
return;
}
while (!stop_token.stop_requested()) {
snd_seq_event_t *event;
if (snd_seq_event_input(seq, &event) < 0) { break; }
if (!event) { continue; }
switch (event->type) {
case SND_SEQ_EVENT_NOTEON:
if (!event->data.note.velocity)
goto noteoff;
if (note_on_callback)
note_on_callback(event->data.note.channel, event->data.note.note, event->data.note.velocity);
break;
case SND_SEQ_EVENT_NOTEOFF:
noteoff:
if (note_off_callback)
note_off_callback(event->data.note.channel, event->data.note.note);
break;
default:
// TODO Add all events as callbacks
break;
}
}
}
}
bool midi::ALSA::supports_output() const
{
return output_port < 0;
}
bool midi::ALSA::supports_input () const
{
return input_port < 0;
}
static void create_source_port(midi::ALSA &alsa)
{
snd_seq_port_info_t *pinfo;
snd_seq_port_info_alloca(&pinfo);
snd_seq_port_info_set_port(pinfo, alsa.output_port);
snd_seq_port_info_set_port_specified(pinfo, 1);
snd_seq_port_info_set_name(pinfo, alsa.connection_name.c_str());
snd_seq_port_info_set_capability(pinfo, 0);
snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
ensure_snd(snd_seq_create_port(alsa.seq, pinfo), "create port");
}
static void create_queue(midi::ALSA &alsa)
{
ensure_snd(alsa.queue = snd_seq_alloc_named_queue(alsa.seq, alsa.connection_name.c_str()), "create queue");
snd_seq_queue_tempo_t *queue_tempo;
snd_seq_queue_tempo_alloca(&queue_tempo);
// We assume time division as ticks per quarter
snd_seq_queue_tempo_set_tempo(queue_tempo, 500'000); // 120 BPM
snd_seq_queue_tempo_set_ppq(queue_tempo, 96);
ensure_snd(snd_seq_set_queue_tempo(alsa.seq, alsa.queue, queue_tempo), "set queue tempo");
}
static void connect_port(midi::ALSA &alsa)
{
ensure_snd(snd_seq_connect_to(alsa.seq, alsa.output_port, alsa.output_port_addr.client, alsa.output_port_addr.port), "connect to port");
}
static void send_note_event(midi::ALSA &alsa, snd_seq_event_type type, uint8_t channel, uint8_t note, uint8_t velocity)
{
snd_seq_event_t ev;
snd_seq_ev_clear(&ev);
ev.queue = alsa.queue;
ev.source.port = alsa.output_port;
ev.flags = SND_SEQ_TIME_STAMP_TICK;
ev.type = type;
ev.time.tick = 0;
ev.dest = alsa.output_port_addr;
snd_seq_ev_set_fixed(&ev);
ev.data.note.channel = channel;
ev.data.note.note = note;
ev.data.note.velocity = velocity;
ensure_snd(snd_seq_event_output(alsa.seq, &ev), "output event");
ensure_snd(snd_seq_drain_output(alsa.seq), "drain output queue");
}

18
musique/midi/midi.cc Normal file
View File

@ -0,0 +1,18 @@
#include <musique/midi/midi.hh>
#include <concepts>
#include <type_traits>
template<typename T>
concept Enum = std::is_enum_v<T>;
/// Converts enum to underlying type
static constexpr auto u(Enum auto e)
{
return static_cast<std::underlying_type_t<decltype(e)>>(e);
}
void midi::Connection::send_all_sounds_off(uint8_t channel)
{
send_controller_change(channel, u(Controller::All_Notes_Off), 0);
}

80
musique/midi/midi.hh Normal file
View File

@ -0,0 +1,80 @@
#pragma once
#include <cstdint>
#include <functional>
#ifdef MIDI_ENABLE_ALSA_SUPPORT
#include <alsa/asoundlib.h>
#include <ostream>
#include <stop_token>
#ifdef assert
#undef assert
#endif
#endif
// Documentation of midi messages available at http://midi.teragonaudio.com/tech/midispec.htm
namespace midi
{
struct Connection
{
virtual bool supports_output() const = 0;
virtual bool supports_input () const = 0;
virtual void send_note_on (uint8_t channel, uint8_t note_number, uint8_t velocity) = 0;
virtual void send_note_off(uint8_t channel, uint8_t note_number, uint8_t velocity) = 0;
virtual void send_program_change(uint8_t channel, uint8_t program) = 0;
virtual void send_controller_change(uint8_t channel, uint8_t controller_number, uint8_t value) = 0;
void send_all_sounds_off(uint8_t channel);
std::function<void(uint8_t, uint8_t, uint8_t)> note_on_callback = nullptr;
std::function<void(uint8_t, uint8_t)> note_off_callback = nullptr;
};
#ifdef MIDI_ENABLE_ALSA_SUPPORT
struct ALSA : Connection
{
explicit ALSA(std::string connection_name);
/// Initialize connection with ALSA
void init_sequencer();
/// Connect with specific ALSA port for outputing MIDI messages
void connect_output(std::string const& target);
/// Connect with specific ALSA port for reading MIDI messages
void connect_input(std::string const& target);
/// List available ports
void list_ports(std::ostream &out) const;
bool supports_output() const override;
bool supports_input () const override;
void send_note_on (uint8_t channel, uint8_t note_number, uint8_t velocity) override;
void send_note_off(uint8_t channel, uint8_t note_number, uint8_t velocity) override;
void send_program_change(uint8_t channel, uint8_t program) override;
void send_controller_change(uint8_t channel, uint8_t controller_number, uint8_t value) override;
void input_event_loop(std::stop_token);
/// Name that is used by ALSA to describe our connection
std::string connection_name = {};
bool connected = false;
snd_seq_t *seq = nullptr;
long client = 0;
int queue = 0;
snd_seq_addr_t input_port_addr{};
snd_seq_addr_t output_port_addr{};
int input_port = -1;
int output_port = -1;
};
#endif
/// All defined controllers for controller change message
enum class Controller : uint8_t
{
All_Notes_Off = 120,
};
}

View File

@ -4,6 +4,6 @@ bin/%.o: musique/%.cc
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
bin/musique: $(Release_Obj) bin/main.o bin/bestline.o lib/midi/libmidi-alsa.a
bin/musique: $(Release_Obj) bin/main.o bin/bestline.o
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/bestline.o $(LDFLAGS) $(LDLIBS)