Alsa Sequencer tests.
This commit is contained in:
Vendored
+1
-1
@@ -142,7 +142,7 @@
|
||||
//"[pipedal_alsa_test]"
|
||||
// "[wifi_channels_test]"
|
||||
// "[locale]"
|
||||
"[modFileTypes]"
|
||||
"[pipedal_alsa_seq_test]"
|
||||
],
|
||||
|
||||
"stopAtEntry": false,
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "AlsaSequencer.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "ss.hpp"
|
||||
|
||||
// enumerate alsa sequencer ports
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
std::vector<AlsaSequencerPort> AlsaSequencer::EnumeratePorts()
|
||||
{
|
||||
std::vector<AlsaSequencerPort> ports;
|
||||
|
||||
snd_seq_t *seq;
|
||||
if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_INPUT, 0) < 0)
|
||||
{
|
||||
return ports;
|
||||
}
|
||||
|
||||
snd_seq_client_info_t *client_info;
|
||||
snd_seq_port_info_t *port_info;
|
||||
|
||||
snd_seq_client_info_alloca(&client_info);
|
||||
snd_seq_port_info_alloca(&port_info);
|
||||
|
||||
snd_seq_client_info_set_client(client_info, -1);
|
||||
|
||||
while (snd_seq_query_next_client(seq, client_info) >= 0)
|
||||
{
|
||||
int client = snd_seq_client_info_get_client(client_info);
|
||||
|
||||
snd_seq_port_info_set_client(port_info, client);
|
||||
snd_seq_port_info_set_port(port_info, -1);
|
||||
|
||||
while (snd_seq_query_next_port(seq, port_info) >= 0)
|
||||
{
|
||||
unsigned int capability = snd_seq_port_info_get_capability(port_info);
|
||||
|
||||
// Skip system ports and ports without MIDI capability
|
||||
if (client == SND_SEQ_CLIENT_SYSTEM ||
|
||||
!(capability & (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ((capability & (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ)) != (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ))
|
||||
{
|
||||
continue; // Skip ports that are not readable AND subscribable
|
||||
}
|
||||
|
||||
AlsaSequencerPort port;
|
||||
port.client = client;
|
||||
port.isKernelDevice = snd_seq_client_info_get_type(client_info) == SND_SEQ_KERNEL_CLIENT;
|
||||
port.port = snd_seq_port_info_get_port(port_info);
|
||||
port.name = snd_seq_port_info_get_name(port_info);
|
||||
port.clientName = snd_seq_client_info_get_name(client_info);
|
||||
port.canRead = capability & SND_SEQ_PORT_CAP_READ;
|
||||
port.canWrite = capability & SND_SEQ_PORT_CAP_WRITE;
|
||||
port.canReadSubscribe = capability & SND_SEQ_PORT_CAP_SUBS_READ;
|
||||
port.canWriteSubscribe = capability & SND_SEQ_PORT_CAP_SUBS_WRITE;
|
||||
auto typeBits = snd_seq_port_info_get_type(port_info);
|
||||
|
||||
port.isUmp = (typeBits & SND_SEQ_PORT_TYPE_MIDI_UMP) != 0;
|
||||
port.isSystemAnnounce = (typeBits & SND_SEQ_PORT_SYSTEM_ANNOUNCE) != 0;
|
||||
port.isMidiSynth = (typeBits & SND_SEQ_PORT_TYPE_MIDI_GENERIC) != 0 ||
|
||||
(typeBits & SND_SEQ_PORT_TYPE_MIDI_GM) != 0 ||
|
||||
(typeBits & SND_SEQ_PORT_TYPE_MIDI_GS) != 0 ||
|
||||
(typeBits & SND_SEQ_PORT_TYPE_MIDI_XG) != 0;
|
||||
port.isApplication = (typeBits & SND_SEQ_PORT_TYPE_APPLICATION) != 0;
|
||||
port.isSpecific = (typeBits & SND_SEQ_PORT_TYPE_SPECIFIC) != 0;
|
||||
port.isSynth = (typeBits & SND_SEQ_PORT_TYPE_SYNTH) != 0;
|
||||
port.isHardware = (typeBits & SND_SEQ_PORT_TYPE_HARDWARE) != 0;
|
||||
port.isSoftware = (typeBits & SND_SEQ_PORT_TYPE_SOFTWARE) != 0;
|
||||
|
||||
port.cardNumber = snd_seq_client_info_get_card(client_info);
|
||||
port.port = snd_seq_port_info_get_port(port_info);
|
||||
|
||||
if (port.isKernelDevice & port.cardNumber >= 0 && port.port >= 0)
|
||||
{
|
||||
// For kernel devices, we can construct a raw MIDI device string
|
||||
port.rawMidiDevice = SS("hw:" << port.cardNumber << ",0," << port.port);
|
||||
}
|
||||
port.id = SS("seq:" << port.clientName << "/" << port.name);
|
||||
|
||||
ports.push_back(port);
|
||||
}
|
||||
}
|
||||
|
||||
snd_seq_close(seq);
|
||||
return ports;
|
||||
}
|
||||
|
||||
AlsaSequencer::AlsaSequencer()
|
||||
{
|
||||
seqHandle = nullptr;
|
||||
queueId = -1;
|
||||
// Open sequencer in input mode
|
||||
int rc;
|
||||
|
||||
rc = snd_seq_open(&seqHandle, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
||||
if (rc < 0)
|
||||
{
|
||||
// convert rc to message
|
||||
throw std::runtime_error(SS("Failed to open ALSA sequencer:" << snd_strerror(rc)));
|
||||
}
|
||||
snd_seq_set_client_name(seqHandle, "PiPedal");
|
||||
|
||||
inPort = snd_seq_create_simple_port(seqHandle, "PiPedal:in",
|
||||
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
|
||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
||||
SND_SEQ_PORT_TYPE_MIDI_GM | SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
if (inPort < 0)
|
||||
{
|
||||
// convert rc to message
|
||||
throw std::runtime_error(SS("Failed to open ALSA sequencer:" << snd_strerror(inPort)));
|
||||
}
|
||||
}
|
||||
AlsaSequencer::~AlsaSequencer()
|
||||
{
|
||||
for (const auto &connection : connections)
|
||||
{
|
||||
snd_seq_disconnect_from(seqHandle, inPort, connection.clientId, connection.portId);
|
||||
}
|
||||
if (queueId >= 0)
|
||||
{
|
||||
snd_seq_free_queue(seqHandle, queueId);
|
||||
queueId = -1;
|
||||
}
|
||||
if (inPort >= 0)
|
||||
{
|
||||
snd_seq_delete_port(seqHandle, inPort);
|
||||
inPort = -1;
|
||||
}
|
||||
if (seqHandle)
|
||||
{
|
||||
snd_seq_close(seqHandle);
|
||||
seqHandle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AlsaSequencer::ConnectPort(int clientId, int portId)
|
||||
{
|
||||
|
||||
CreateRealtimeInputQueue();
|
||||
// Connect this input port to the target client:port
|
||||
int rc = snd_seq_connect_from(seqHandle, inPort, clientId, portId);
|
||||
if (rc < 0)
|
||||
{
|
||||
throw std::runtime_error(SS("Failed to connect to ALSA port: " << snd_strerror(rc)));
|
||||
}
|
||||
this->connections.push_back({clientId, portId});
|
||||
}
|
||||
|
||||
void AlsaSequencer::ConnectPort(const std::string &name)
|
||||
{
|
||||
auto ports = EnumeratePorts();
|
||||
for (const auto &port : ports)
|
||||
{
|
||||
if (port.name == name)
|
||||
{
|
||||
ConnectPort(port.client, port.port);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("ALSA port not found");
|
||||
}
|
||||
|
||||
bool AlsaSequencer::ReadMessage(AlsaMidiMessage &message)
|
||||
{
|
||||
// Event loop
|
||||
snd_seq_event_t *event = nullptr;
|
||||
bool success = false;
|
||||
if (snd_seq_event_input(seqHandle, &event) >= 0 && event)
|
||||
{
|
||||
success = true;
|
||||
// Extract timestamp information
|
||||
message.timestamp = event->time.tick;
|
||||
message.realtime_sec = event->time.time.tv_sec;
|
||||
message.realtime_nsec = event->time.time.tv_nsec;
|
||||
|
||||
// Process MIDI event here, e.g. NOTEON, NOTEOFF, etc.
|
||||
switch (event->type)
|
||||
{
|
||||
case SND_SEQ_EVENT_NOTEON:
|
||||
message.cc0 = 0x90 | event->data.note.channel; // channel
|
||||
message.cc1 = event->data.note.note; // note
|
||||
message.cc2 = event->data.note.velocity; // velocity
|
||||
break;
|
||||
case SND_SEQ_EVENT_NOTEOFF:
|
||||
// handle note-off
|
||||
message.cc0 = 0x80 | event->data.note.channel; // channel
|
||||
message.cc1 = event->data.note.note; // note
|
||||
message.cc2 = event->data.note.off_velocity; // off velocity
|
||||
break;
|
||||
case SND_SEQ_EVENT_KEYPRESS:
|
||||
message.cc0 = 0xA0 | event->data.note.channel; // polyphonic key pressure
|
||||
message.cc1 = event->data.note.note; // note
|
||||
message.cc2 = event->data.note.velocity; // pressure
|
||||
break;
|
||||
case SND_SEQ_EVENT_CONTROLLER:
|
||||
message.cc0 = 0xB0 | event->data.control.channel; // control change
|
||||
message.cc1 = event->data.control.param; // controller number
|
||||
message.cc2 = event->data.control.value; // controller value
|
||||
break;
|
||||
case SND_SEQ_EVENT_PGMCHANGE:
|
||||
message.cc0 = 0xC0 | event->data.control.channel; // program change
|
||||
message.cc1 = event->data.control.value; // program number
|
||||
message.cc2 = 0; // unused
|
||||
break;
|
||||
case SND_SEQ_EVENT_CHANPRESS:
|
||||
message.cc0 = 0xD0 | event->data.control.channel; // channel pressure
|
||||
message.cc1 = event->data.control.value; // pressure value
|
||||
message.cc2 = 0; // unused
|
||||
break;
|
||||
case SND_SEQ_EVENT_PITCHBEND:
|
||||
message.cc0 = 0xE0 | event->data.control.channel; // pitch bend
|
||||
message.cc1 = (event->data.control.value >> 7) & 0x7F; // MSB
|
||||
message.cc2 = event->data.control.value & 0x7F; // LSB
|
||||
break;
|
||||
case SND_SEQ_EVENT_CONTROL14:
|
||||
case SND_SEQ_EVENT_NONREGPARAM:
|
||||
case SND_SEQ_EVENT_REGPARAM:
|
||||
case SND_SEQ_EVENT_SONGPOS:
|
||||
default:
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
snd_seq_free_event(event);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int AlsaSequencer::CreateRealtimeInputQueue()
|
||||
{
|
||||
if (!seqHandle)
|
||||
{
|
||||
throw std::runtime_error("ALSA sequencer not initialized");
|
||||
}
|
||||
|
||||
// Create a new queue if we don't have one yet
|
||||
if (queueId < 0)
|
||||
{
|
||||
queueId = snd_seq_alloc_named_queue(seqHandle, "PiPedal Realtime Queue");
|
||||
if (queueId < 0)
|
||||
{
|
||||
throw std::runtime_error(SS("Failed to create ALSA queue: " << snd_strerror(queueId)));
|
||||
}
|
||||
|
||||
// Set queue timing to real-time mode
|
||||
snd_seq_queue_tempo_t *tempo;
|
||||
snd_seq_queue_tempo_alloca(&tempo);
|
||||
snd_seq_queue_tempo_set_tempo(tempo, 120); // 120 BPM default
|
||||
snd_seq_queue_tempo_set_ppq(tempo, 96); // 96 ticks per quarter note
|
||||
|
||||
int rc = snd_seq_set_queue_tempo(seqHandle, queueId, tempo);
|
||||
if (rc < 0)
|
||||
{
|
||||
snd_seq_free_queue(seqHandle, queueId);
|
||||
queueId = -1;
|
||||
throw std::runtime_error(SS("Failed to set queue tempo: " << snd_strerror(rc)));
|
||||
}
|
||||
|
||||
// Start the queue
|
||||
rc = snd_seq_start_queue(seqHandle, queueId, nullptr);
|
||||
if (rc < 0)
|
||||
{
|
||||
snd_seq_free_queue(seqHandle, queueId);
|
||||
queueId = -1;
|
||||
throw std::runtime_error(SS("Failed to start queue: " << snd_strerror(rc)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Set the queue for input timestamping
|
||||
snd_seq_port_info_t *port_info;
|
||||
snd_seq_port_info_alloca(&port_info);
|
||||
|
||||
rc = snd_seq_get_port_info(seqHandle, inPort, port_info);
|
||||
if (rc < 0)
|
||||
{
|
||||
snd_seq_free_queue(seqHandle, queueId);
|
||||
queueId = -1;
|
||||
throw std::runtime_error(SS("Failed to get port info: " << snd_strerror(rc)));
|
||||
}
|
||||
|
||||
// Enable timestamping on the input port
|
||||
snd_seq_port_info_set_timestamping(port_info, 1);
|
||||
snd_seq_port_info_set_timestamp_real(port_info, 1);
|
||||
snd_seq_port_info_set_timestamp_queue(port_info, queueId);
|
||||
|
||||
rc = snd_seq_set_port_info(seqHandle, inPort, port_info);
|
||||
if (rc < 0)
|
||||
{
|
||||
snd_seq_free_queue(seqHandle, queueId);
|
||||
queueId = -1;
|
||||
throw std::runtime_error(SS("Failed to set port timestamping: " << snd_strerror(rc)));
|
||||
}
|
||||
}
|
||||
|
||||
return queueId;
|
||||
}
|
||||
bool AlsaSequencer::GetQueueRealtime(uint64_t *sec, uint32_t *nsec)
|
||||
{
|
||||
if (!seqHandle || queueId < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
snd_seq_queue_status_t *status;
|
||||
snd_seq_queue_status_alloca(&status);
|
||||
|
||||
int rc = snd_seq_get_queue_status(seqHandle, queueId, status);
|
||||
if (rc < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const snd_seq_real_time_t *realtime = snd_seq_queue_status_get_real_time(status);
|
||||
if (sec)
|
||||
*sec = realtime->tv_sec;
|
||||
if (nsec)
|
||||
*nsec = realtime->tv_nsec;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ add_library(PiPedalCommon STATIC
|
||||
include/dbus/org.freedesktop.NetworkManager.Device.Wireless.hpp
|
||||
include/dbus/org.freedesktop.NetworkManager.DHCP4Config.hpp
|
||||
|
||||
AlsaSequencer.cpp include/AlsaSequencer.hpp
|
||||
Utf8Utils.cpp include/Utf8Utils.hpp
|
||||
NetworkManagerInterfaces.cpp
|
||||
include/NetworkManagerInterfaces.hpp
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <alsa/seq.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
struct AlsaSequencerPort
|
||||
{
|
||||
std::string id;
|
||||
int client;
|
||||
int port;
|
||||
std::string name;
|
||||
std::string clientName;
|
||||
bool canRead = false;
|
||||
bool canWrite = false;
|
||||
bool canReadSubscribe = false;
|
||||
bool canWriteSubscribe = false;
|
||||
|
||||
bool isKernelDevice = false;
|
||||
bool isSystemAnnounce = false;
|
||||
bool isUmp = false;
|
||||
bool isMidi = false;
|
||||
bool isApplication = false;
|
||||
bool isMidiSynth = false;
|
||||
bool isSynth = false;
|
||||
bool isSpecific;
|
||||
bool isHardware = false;
|
||||
bool isSoftware = false;
|
||||
int cardNumber = -1;
|
||||
std::string rawMidiDevice; // e.g. "hw:0,0,0" for kernel devices
|
||||
};
|
||||
|
||||
struct AlsaMidiMessage
|
||||
{
|
||||
uint32_t timestamp; // in microseconds (tick time if using queue)
|
||||
uint64_t realtime_sec; // real-time timestamp seconds
|
||||
uint32_t realtime_nsec; // real-time timestamp nanoseconds
|
||||
uint8_t cc0;
|
||||
uint8_t cc1;
|
||||
uint8_t cc2;
|
||||
};
|
||||
|
||||
/*
|
||||
* AlsaSequencer - ALSA MIDI sequencer interface with real-time timestamp support
|
||||
*
|
||||
* Example usage with real-time timestamps:
|
||||
*
|
||||
* AlsaSequencer sequencer;
|
||||
* sequencer.connect(clientId, portId);
|
||||
*
|
||||
* AlsaMidiMessage message;
|
||||
* while (sequencer.ReadMessage(&message)) {
|
||||
* // message.realtime_sec and message.realtime_nsec contain precise real-time timestamps
|
||||
* // message.timestamp contains the queue tick time
|
||||
* printf("MIDI event at %lu.%09u seconds\n", message.realtime_sec, message.realtime_nsec);
|
||||
* }
|
||||
*/
|
||||
class AlsaSequencer
|
||||
{
|
||||
public:
|
||||
static std::vector<AlsaSequencerPort> EnumeratePorts();
|
||||
|
||||
AlsaSequencer();
|
||||
~AlsaSequencer();
|
||||
|
||||
void ConnectPort(int clientId, int portId);
|
||||
void ConnectPort(const std::string&name);
|
||||
|
||||
// Read a single MIDI message from the sequencer input port
|
||||
bool ReadMessage(AlsaMidiMessage &message);
|
||||
|
||||
|
||||
// Get current real-time from the queue (useful for calculating precise timing)
|
||||
bool GetQueueRealtime(uint64_t* sec, uint32_t* nsec);
|
||||
|
||||
// Get the current queue ID (returns -1 if no queue is active)
|
||||
int GetQueueId() const { return queueId; }
|
||||
|
||||
private:
|
||||
// Create an ALSA input queue with real-time timestamps for the given client/port
|
||||
int CreateRealtimeInputQueue();
|
||||
|
||||
struct Connection
|
||||
{
|
||||
int clientId;
|
||||
int portId;
|
||||
};
|
||||
|
||||
std::vector<Connection> connections;
|
||||
|
||||
snd_seq_t *seqHandle = nullptr;
|
||||
int inPort = -1;
|
||||
int queueId = -1; // Queue for real-time timestamps
|
||||
|
||||
// Example: open an ALSA sequencer input port and read MIDI events continuously
|
||||
void ReadMidiFromPort(int clientId, int portId);
|
||||
};
|
||||
|
||||
}
|
||||
+71
-8
@@ -23,6 +23,8 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "AlsaSequencer.hpp"
|
||||
#include <iomanip>
|
||||
|
||||
#include "PiPedalAlsa.hpp"
|
||||
|
||||
@@ -40,16 +42,77 @@ static void DiscoveryTest()
|
||||
std::cout << midiInputDevices.size() << " ALSA MIDI input devices found." << std::endl;
|
||||
auto midiOutputDevices = GetAlsaMidiOutputDevices();
|
||||
std::cout << midiOutputDevices.size() << " ALSA MIDI output devices found." << std::endl;
|
||||
|
||||
|
||||
}
|
||||
|
||||
void EnumerateSequencers()
|
||||
{
|
||||
cout << "--- Enumerating ALSA Sequencers" << endl;
|
||||
auto sequencers = AlsaSequencer::EnumeratePorts();
|
||||
for (const auto &sequencer : sequencers)
|
||||
{
|
||||
cout << sequencer.client << ": "<< sequencer.port << " - " << sequencer.clientName << "/" << sequencer.name;
|
||||
|
||||
cout << " [" << (sequencer.isKernelDevice ? "Kernel" : "User");
|
||||
if (!sequencer.rawMidiDevice.empty())
|
||||
{
|
||||
cout << " " << sequencer.rawMidiDevice;
|
||||
}
|
||||
cout << "]" << endl;
|
||||
|
||||
cout << " Read: " << (sequencer.canRead ? "Yes" : "No")
|
||||
<< ", Write: " << (sequencer.canWrite ? "Yes" : "No")
|
||||
<< ", Read Subscribe: " << (sequencer.canReadSubscribe ? "Yes" : "No")
|
||||
<< ", Write Subscribe: " << (sequencer.canWriteSubscribe ? "Yes" : "No") << std::endl;
|
||||
|
||||
cout
|
||||
<< " System Announce : " << (sequencer.isSystemAnnounce ? " Yes " : " No ")
|
||||
<< "UMP: "
|
||||
<< (sequencer.isUmp ? "Yes" : "No")
|
||||
<< "MIDI: "
|
||||
<< (sequencer.isMidi ? "Yes" : "No")
|
||||
<< "Application: "
|
||||
<< (sequencer.isApplication ? "Yes" : "No")
|
||||
<< " MIDI Synth: "
|
||||
<< (sequencer.isMidiSynth ? "Yes" : "No")
|
||||
<< "Synth: "
|
||||
<< (sequencer.isSynth ? "Yes" : "No")
|
||||
<< std::endl;
|
||||
cout << " Specific: " << (sequencer.isSpecific ? "Yes" : "No")
|
||||
<< "Hardware: " << (sequencer.isHardware ? "Yes" : "No")
|
||||
<< "Software: " << (sequencer.isSoftware ? "Yes" : "No") << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadFromsequencerTest()
|
||||
{
|
||||
cout << "--- Reading from ALSA Sequencer" << endl;
|
||||
AlsaSequencer sequencer;
|
||||
sequencer.ConnectPort("V25 V25 In");
|
||||
|
||||
AlsaMidiMessage message;
|
||||
while (true)
|
||||
{
|
||||
if (sequencer.ReadMessage(message))
|
||||
{
|
||||
cout << "Received MIDI message: "
|
||||
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc0)
|
||||
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc1)
|
||||
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc2)
|
||||
<< " Timestamp: " << setw(8) << dec << message.timestamp
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ALSA Seq Test", "[pipedal_alsa_seq_test][Build][Dev]")
|
||||
{
|
||||
EnumerateSequencers();
|
||||
|
||||
ReadFromsequencerTest();
|
||||
}
|
||||
|
||||
TEST_CASE("ALSA Test", "[pipedal_alsa_test][Build][Dev]")
|
||||
{
|
||||
|
||||
TEST_CASE( "ALSA Test", "[pipedal_alsa_test][Build][Dev]" ) {
|
||||
DiscoveryTest();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user