Alsa Sequencer Tests
This commit is contained in:
@@ -11,6 +11,7 @@ set (DISPLAY_VERSION "PiPedal v1.4.76-Release")
|
||||
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
|
||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||
|
||||
set (PIPEDAL_EXCLUDE_TESTS false)
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "AlsaSequencer.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include "ss.hpp"
|
||||
|
||||
// enumerate alsa sequencer ports
|
||||
@@ -35,7 +36,7 @@ namespace pipedal
|
||||
std::vector<AlsaSequencerPort> ports;
|
||||
|
||||
snd_seq_t *seq;
|
||||
if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_INPUT, 0) < 0)
|
||||
if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0)
|
||||
{
|
||||
return ports;
|
||||
}
|
||||
@@ -83,7 +84,7 @@ namespace pipedal
|
||||
auto typeBits = snd_seq_port_info_get_type(port_info);
|
||||
#ifdef SND_SEQ_PORT_TYPE_MIDI_UMP
|
||||
port.isUmp = (typeBits & SND_SEQ_PORT_TYPE_MIDI_UMP) != 0;
|
||||
#else
|
||||
#else
|
||||
port.isUmp = false; // UMP support is not available in all versions of ALSA
|
||||
#endif
|
||||
port.isSystemAnnounce = (typeBits & SND_SEQ_PORT_SYSTEM_ANNOUNCE) != 0;
|
||||
@@ -95,19 +96,52 @@ namespace pipedal
|
||||
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.isPort = (typeBits & SND_SEQ_PORT_TYPE_PORT) != 0;
|
||||
port.isSoftware = (typeBits & SND_SEQ_PORT_TYPE_SOFTWARE) != 0;
|
||||
|
||||
port.isVirtual = port.name.starts_with("VirMIDI");
|
||||
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)
|
||||
if (port.isKernelDevice && port.isPort && 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);
|
||||
std::string rawMidiDevice;
|
||||
if (port.isVirtual)
|
||||
{
|
||||
// "VirMidI 2-1"
|
||||
std::regex virtualDeviceRegex{"^(VirMIDI) (\\d+)-(\\d+)$"};
|
||||
{
|
||||
// Extract the card and device numbers from the match
|
||||
std::smatch match;
|
||||
std::regex_search(port.name, match, virtualDeviceRegex);
|
||||
if (match.size() == 4)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string devName = match[1];
|
||||
int card = std::stoi(match[2]);
|
||||
int device = std::stoi(match[3]);
|
||||
rawMidiDevice = SS("hw:CARD=" << devName << ",DEV=" << device);
|
||||
}
|
||||
catch (const std::exception &ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rawMidiDevice = SS("hw:CARD=" << port.clientName << ",DEV=" << 0);
|
||||
if (port.port > 0)
|
||||
{
|
||||
rawMidiDevice += SS("," << port.port);
|
||||
}
|
||||
}
|
||||
port.rawMidiDevice = std::move(rawMidiDevice);
|
||||
}
|
||||
port.id = SS("seq:" << port.clientName << "/" << port.name);
|
||||
|
||||
ports.push_back(port);
|
||||
ports.push_back(std::move(port));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +156,7 @@ namespace pipedal
|
||||
// Open sequencer in input mode
|
||||
int rc;
|
||||
|
||||
rc = snd_seq_open(&seqHandle, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
||||
rc = snd_seq_open(&seqHandle, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
||||
if (rc < 0)
|
||||
{
|
||||
// convert rc to message
|
||||
@@ -131,14 +165,15 @@ namespace pipedal
|
||||
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);
|
||||
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)));
|
||||
}
|
||||
snd_seq_nonblock(seqHandle, 1); // Set sequencer to non-blocking mode
|
||||
}
|
||||
AlsaSequencer::~AlsaSequencer()
|
||||
{
|
||||
@@ -190,69 +225,93 @@ namespace pipedal
|
||||
throw std::runtime_error("ALSA port not found");
|
||||
}
|
||||
|
||||
bool AlsaSequencer::ReadMessage(AlsaMidiMessage &message)
|
||||
void AlsaSequencer::WaitForMessage() {
|
||||
auto fdCount = snd_seq_poll_descriptors_count(seqHandle, POLLIN);
|
||||
if (fdCount == 0) return;
|
||||
this->pollFds.resize(fdCount);
|
||||
|
||||
snd_seq_poll_descriptors(seqHandle, pollFds.data(), fdCount, POLLIN);
|
||||
|
||||
poll(pollFds.data(), fdCount, -1); // Wait indefinitely for input
|
||||
}
|
||||
bool AlsaSequencer::ReadMessage(AlsaMidiMessage &message, bool block)
|
||||
{
|
||||
// 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)
|
||||
while (true) {
|
||||
bool success = false;
|
||||
int rc = snd_seq_event_input(seqHandle, &event);
|
||||
if (rc < 0) {
|
||||
if (rc == -EAGAIN) {
|
||||
if (!block) {
|
||||
return false;
|
||||
}
|
||||
WaitForMessage();
|
||||
} else {
|
||||
// Handle other errors
|
||||
throw std::runtime_error(SS("ALSA sequencer input error: " << snd_strerror(rc)));
|
||||
}
|
||||
} else if (event)
|
||||
{
|
||||
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;
|
||||
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);
|
||||
}
|
||||
if (success) {
|
||||
return true;
|
||||
}
|
||||
snd_seq_free_event(event);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int AlsaSequencer::CreateRealtimeInputQueue()
|
||||
@@ -294,8 +353,6 @@ namespace pipedal
|
||||
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);
|
||||
@@ -348,4 +405,17 @@ namespace pipedal
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string RawMidiIdToSequencerId(const std::vector<AlsaSequencerPort> &seqDevices, const std::string &rawMidiId)
|
||||
{
|
||||
for (const auto &device : seqDevices)
|
||||
{
|
||||
if (device.rawMidiDevice == rawMidiId)
|
||||
{
|
||||
return device.id;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace pipedal
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace pipedal
|
||||
bool isSpecific;
|
||||
bool isHardware = false;
|
||||
bool isSoftware = false;
|
||||
bool isPort = false;
|
||||
bool isVirtual = false;
|
||||
int cardNumber = -1;
|
||||
std::string rawMidiDevice; // e.g. "hw:0,0,0" for kernel devices
|
||||
};
|
||||
@@ -94,7 +96,7 @@ namespace pipedal
|
||||
void ConnectPort(const std::string&name);
|
||||
|
||||
// Read a single MIDI message from the sequencer input port
|
||||
bool ReadMessage(AlsaMidiMessage &message);
|
||||
bool ReadMessage(AlsaMidiMessage &message, bool block = true);
|
||||
|
||||
|
||||
// Get current real-time from the queue (useful for calculating precise timing)
|
||||
@@ -104,6 +106,7 @@ namespace pipedal
|
||||
int GetQueueId() const { return queueId; }
|
||||
|
||||
private:
|
||||
void WaitForMessage();
|
||||
// Create an ALSA input queue with real-time timestamps for the given client/port
|
||||
int CreateRealtimeInputQueue();
|
||||
|
||||
@@ -114,7 +117,7 @@ namespace pipedal
|
||||
};
|
||||
|
||||
std::vector<Connection> connections;
|
||||
|
||||
std::vector<struct pollfd> pollFds; // For polling input events
|
||||
snd_seq_t *seqHandle = nullptr;
|
||||
int inPort = -1;
|
||||
int queueId = -1; // Queue for real-time timestamps
|
||||
@@ -123,4 +126,6 @@ namespace pipedal
|
||||
void ReadMidiFromPort(int clientId, int portId);
|
||||
};
|
||||
|
||||
std::string RawMidiIdToSequencerId(const std::vector<AlsaSequencerPort> &seqDevices,const std::string &rawMidiId);
|
||||
|
||||
}
|
||||
@@ -131,17 +131,28 @@ tone stack used in Polytone and HiWatt amps.
|
||||
rdfs:label "Baxandall" ;
|
||||
rdf:value 2.0
|
||||
];
|
||||
],[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 4 ;
|
||||
lv2:symbol "gain" ;
|
||||
lv2:name "Gain";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -40.0 ;
|
||||
lv2:maximum 30.0;
|
||||
units:unit units:db;
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort ;
|
||||
lv2:index 4 ;
|
||||
lv2:index 5 ;
|
||||
lv2:symbol "in" ;
|
||||
lv2:name "In"
|
||||
], [
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort ;
|
||||
lv2:index 5 ;
|
||||
lv2:index 6 ;
|
||||
lv2:symbol "out" ;
|
||||
lv2:name "Out"
|
||||
],[
|
||||
@@ -150,7 +161,7 @@ tone stack used in Polytone and HiWatt amps.
|
||||
atom:bufferType atom:Sequence ;
|
||||
# atom:supports patch:Message;
|
||||
lv2:designation lv2:control ;
|
||||
lv2:index 6 ;
|
||||
lv2:index 7 ;
|
||||
lv2:symbol "control" ;
|
||||
lv2:name "Control" ;
|
||||
rdfs:comment "Plugin to GUI communication" ;
|
||||
@@ -160,7 +171,7 @@ tone stack used in Polytone and HiWatt amps.
|
||||
atom:bufferType atom:Sequence ;
|
||||
# atom:supports patch:Message;
|
||||
lv2:designation lv2:control ;
|
||||
lv2:index 7 ;
|
||||
lv2:index 8 ;
|
||||
lv2:symbol "notify" ;
|
||||
lv2:name "Notify" ;
|
||||
rdfs:comment "Plugin to GUI communication" ;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -57,7 +57,7 @@ Remix a stereo input signal.
|
||||
|
||||
lv2:index 0 ;
|
||||
lv2:symbol "trimL" ;
|
||||
lv2:name "Trim L";
|
||||
lv2:name "Vol L";
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -60.0 ;
|
||||
@@ -89,7 +89,7 @@ Remix a stereo input signal.
|
||||
|
||||
lv2:index 2 ;
|
||||
lv2:symbol "trimR" ;
|
||||
lv2:name "Trim R";
|
||||
lv2:name "Vol R";
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -60.0 ;
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
@prefix doap: <http://usefulinc.com/ns/doap#> .
|
||||
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
|
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
|
||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
|
||||
@prefix epp: <http://lv2plug.in/ns/ext/port-props#> .
|
||||
@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> .
|
||||
@prefix idpy: <http://harrisonconsoles.com/lv2/inlinedisplay#> .
|
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
||||
@prefix mod: <http://moddevices.com/ns/mod#> .
|
||||
@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
|
||||
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
|
||||
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
|
||||
|
||||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix state: <http://lv2plug.in/ns/ext/state#> .
|
||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||
@prefix toobPlayer: <http://two-play.com/plugins/toob-player#> .
|
||||
|
||||
toobPlayer:mixGroup
|
||||
a param:ControlGroup ,
|
||||
pg:InputGroup ;
|
||||
lv2:name "Mix" ;
|
||||
lv2:symbol "mixGroup" .
|
||||
|
||||
|
||||
<http://two-play.com/rerdavies#me>
|
||||
a foaf:Person ;
|
||||
foaf:name "Robin Davies" ;
|
||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||
|
||||
|
||||
toobPlayer:audioFile
|
||||
a lv2:Parameter;
|
||||
rdfs:label "File";
|
||||
mod:fileTypes "audiotrack,wav,flac,mp3";
|
||||
rdfs:range atom:Path;
|
||||
lv2:index 4
|
||||
.
|
||||
toobPlayer:seek
|
||||
a lv2:Parameter;
|
||||
rdfs:label "Seek";
|
||||
rdfs:range atom:Float;
|
||||
.
|
||||
|
||||
|
||||
<http://two-play.com/plugins/toob-player>
|
||||
a lv2:Plugin ,
|
||||
lv2:GeneratorPlugin ;
|
||||
doap:name "TooB File Player" ,
|
||||
"TooB File Player"@en-gb-gb
|
||||
;
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 62 ;
|
||||
rdfs:comment """
|
||||
Play audio files. Audio files can optionally be loooped by pressing the "Set loop" button.
|
||||
|
||||
To skip the start of a file, press the "Set Loop" button, set the "Start" value in the seek dialog, and leave the "Loop Enabled"
|
||||
checkbox unchecked.
|
||||
|
||||
Note that loop information is stored as per-file metadata, and is not stored in the preset. If you need different loop settings for the
|
||||
same file, create a copy of the file. Files are copied using hard links, so the copy will not take up significant disk space.
|
||||
|
||||
|
||||
TooB Player will display audio metadata for a file (track number, title, album, artist) if available.
|
||||
If the file's metadata contains album art, that will be displayed as well. If not, the player will look for a folder artwork file in the same directory. The
|
||||
folder artwork file can be called any of "Folder.jpg", "Cover.jpg", "AlbumArt.jpg" or a number of other common names used by media players and taggers.
|
||||
You can upload album artwork files using the Upload button in the file browser. The Upload button will allow you to upload audio files, or
|
||||
folder artwork files that have a .jpg file extension.
|
||||
|
||||
|
||||
""" ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "File Player";
|
||||
|
||||
# ONLY works in PiPedal
|
||||
lv2:requiredFeature "http://github.com/rerdavies/pipedal#host" ;
|
||||
|
||||
lv2:optionalFeature lv2:hardRTCapable,state::freePath, state:makePath, state:mapPath, work:schedule ;
|
||||
lv2:extensionData state:interface, work:interface;
|
||||
|
||||
patch:readable
|
||||
toobPlayer:audioFile,toobPlayer:seek;
|
||||
patch:writable
|
||||
toobPlayer:audioFile;
|
||||
|
||||
lv2:port
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort ;
|
||||
lv2:index 0 ;
|
||||
lv2:symbol "inl" ;
|
||||
lv2:name "In L"
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort ;
|
||||
lv2:index 1 ;
|
||||
lv2:symbol "inr" ;
|
||||
lv2:name "In R"
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort ;
|
||||
lv2:index 2 ;
|
||||
lv2:symbol "outl" ;
|
||||
lv2:name "Out L"
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort ;
|
||||
lv2:index 3 ;
|
||||
lv2:symbol "outr" ;
|
||||
lv2:name "OutR"
|
||||
],
|
||||
######################
|
||||
[
|
||||
a atom:AtomPort ,
|
||||
lv2:InputPort ;
|
||||
lv2:index 4 ;
|
||||
atom:bufferType atom:Sequence ;
|
||||
atom:supports patch:Message;
|
||||
lv2:designation lv2:control ;
|
||||
lv2:symbol "controlIn" ;
|
||||
lv2:name "ControlIn"
|
||||
],
|
||||
[
|
||||
a atom:AtomPort ,
|
||||
lv2:OutputPort ;
|
||||
lv2:index 5 ;
|
||||
atom:bufferType atom:Sequence ;
|
||||
atom:supports patch:Message;
|
||||
lv2:designation lv2:control ;
|
||||
|
||||
lv2:symbol "controlOut" ;
|
||||
lv2:name "ControlOut"
|
||||
],
|
||||
|
||||
######################
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 6;
|
||||
lv2:symbol "stop" ;
|
||||
lv2:name "Stop";
|
||||
rdf:comment "Stop playback.";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 1.0;
|
||||
lv2:portProperty lv2:toggled,epp:trigger;
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 7;
|
||||
lv2:symbol "pause" ;
|
||||
lv2:name "Pause";
|
||||
rdf:comment "Pause playback.";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 1.0;
|
||||
lv2:portProperty lv2:toggled,epp:trigger;
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 8;
|
||||
lv2:symbol "play" ;
|
||||
lv2:name "Play";
|
||||
rdf:comment "Play selected file.";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 1.0;
|
||||
lv2:portProperty lv2:toggled,epp:trigger;
|
||||
],
|
||||
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 9 ;
|
||||
lv2:symbol "volIn" ;
|
||||
lv2:name "Input Vol";
|
||||
rdfs:comment "Volume audio passed through from the input. ";
|
||||
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -40.0 ;
|
||||
lv2:maximum 30.0 ;
|
||||
units:unit units:db ;
|
||||
lv2:scalePoint
|
||||
[
|
||||
rdfs:label "-INF" ;
|
||||
rdf:value -40.0
|
||||
];
|
||||
pg:group toobPlayer:mixGroup ;
|
||||
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 10;
|
||||
lv2:symbol "panIn" ;
|
||||
lv2:name "Input Pan";
|
||||
rdfs:comment "Pan for passed-through audio.";
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -1.0;
|
||||
lv2:maximum 1.0;
|
||||
pg:group toobPlayer:mixGroup ;
|
||||
rdfs:comment "Input Pan";
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 11 ;
|
||||
lv2:symbol "volFile" ;
|
||||
lv2:name "File Vol";
|
||||
rdfs:comment "Volume of the audio file being played.";
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -40.0 ;
|
||||
lv2:maximum 30.0 ;
|
||||
units:unit units:db ;
|
||||
lv2:scalePoint
|
||||
[
|
||||
rdfs:label "-INF" ;
|
||||
rdf:value -40.0
|
||||
];
|
||||
pg:group toobPlayer:mixGroup ;
|
||||
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 12;
|
||||
lv2:symbol "panFile" ;
|
||||
lv2:name "File Pan";
|
||||
rdfs:comment "Pan position of the file being played.";
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -1.0;
|
||||
lv2:maximum 1.0;
|
||||
pg:group toobPlayer:mixGroup ;
|
||||
],
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
[
|
||||
a lv2:OutputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 13;
|
||||
lv2:symbol "state" ;
|
||||
lv2:name "Play State";
|
||||
lv2:portProperty epp:notOnGUI;
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 10.0;
|
||||
],
|
||||
[
|
||||
a lv2:OutputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 14;
|
||||
lv2:symbol "position" ;
|
||||
lv2:name "Play Position";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 1.0E+38;
|
||||
units:unit units:s ;
|
||||
],
|
||||
[
|
||||
a lv2:OutputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 15;
|
||||
lv2:symbol "duration" ;
|
||||
lv2:name "Duration";
|
||||
rdfs:comment "Length of the selected audio file.";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 1.0E+38;
|
||||
units:unit units:s ;
|
||||
]
|
||||
|
||||
.
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
|
||||
@prefix pprop: <http://lv2plug.in/ns/ext/port-props#> .
|
||||
@prefix pipedal_ui: <http://github.com/rerdavies/pipedal/ui#> .
|
||||
|
||||
|
||||
@@ -61,7 +60,7 @@ recordPrefix:audioFile
|
||||
patch:writable
|
||||
recordPrefix:audioFile;
|
||||
|
||||
lv2:extensionData state:interface ;
|
||||
lv2:extensionData state:interface, work:interface ;
|
||||
|
||||
rdfs:comment """
|
||||
Record the plugin's audio input signal to a monophonic file. See also: TooB Record Input (stereo), which records a stereo file.
|
||||
@@ -83,7 +82,7 @@ Record the plugin's audio input signal to a monophonic file. See also: TooB Reco
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 1.0;
|
||||
lv2:portProperty lv2:toggled,pprop:trigger;
|
||||
lv2:portProperty lv2:toggled,epp:trigger;
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
@@ -96,7 +95,7 @@ Record the plugin's audio input signal to a monophonic file. See also: TooB Reco
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 1.0;
|
||||
lv2:portProperty lv2:toggled,pprop:trigger;
|
||||
lv2:portProperty lv2:toggled,epp:trigger;
|
||||
],
|
||||
[
|
||||
a lv2:OutputPort ,
|
||||
@@ -117,11 +116,11 @@ Record the plugin's audio input signal to a monophonic file. See also: TooB Reco
|
||||
lv2:index 3;
|
||||
lv2:symbol "play" ;
|
||||
lv2:name "⏵";
|
||||
rdfs:comment "Preview recorded file. Click again to stop.";
|
||||
rdfs:comment "Preview recorded file.";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 1.0;
|
||||
lv2:portProperty lv2:toggled,pprop:trigger;
|
||||
lv2:portProperty lv2:toggled,epp:trigger;
|
||||
],
|
||||
[
|
||||
a lv2:OutputPort ,
|
||||
@@ -186,7 +185,7 @@ Record the plugin's audio input signal to a monophonic file. See also: TooB Reco
|
||||
|
||||
lv2:index 7;
|
||||
lv2:symbol "level" ;
|
||||
lv2:name "Level";
|
||||
lv2:name "Rec Lvl";
|
||||
rdfs:comment "Input trim level for recording";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -60.0;
|
||||
|
||||
@@ -97,7 +97,7 @@ myprefix:loop3_group
|
||||
patch:writable
|
||||
recordPrefix:audioFile;
|
||||
|
||||
lv2:extensionData state:interface ;
|
||||
lv2:extensionData state:interface, work:interface ;
|
||||
|
||||
|
||||
rdfs:comment """
|
||||
@@ -154,7 +154,7 @@ Record the plugin's audio input signal to a stereophonic file. See also: TooB Re
|
||||
lv2:index 3;
|
||||
lv2:symbol "play" ;
|
||||
lv2:name "⏵";
|
||||
rdfs:comment "Preview the recorded file. Click again to stop.";
|
||||
rdfs:comment "Preview the recorded file.";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0;
|
||||
lv2:maximum 1.0;
|
||||
@@ -223,8 +223,8 @@ Record the plugin's audio input signal to a stereophonic file. See also: TooB Re
|
||||
|
||||
lv2:index 7;
|
||||
lv2:symbol "level" ;
|
||||
lv2:name "Level";
|
||||
lv2:name "Input trim level for recording";
|
||||
lv2:name "Rec Lvl";
|
||||
rdfs:comment "Input trim level for recording";
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum -60.0;
|
||||
lv2:maximum 30.0;
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<http://two-play.com/plugins/toob-volume>
|
||||
a lv2:Plugin ,
|
||||
lv2:UtilityPlugin ;
|
||||
lv2:MixerPlugin ;
|
||||
doap:name "TooB Volume" ,
|
||||
"TooB Volume"@en-gb-gb
|
||||
;
|
||||
|
||||
@@ -97,6 +97,10 @@
|
||||
lv2:binary <ToobAmp.so> ;
|
||||
rdfs:seeAlso <ToobMix.ttl> .
|
||||
|
||||
<http://two-play.com/plugins/toob-player> a lv2:Plugin ;
|
||||
lv2:binary <ToobAmp.so> ;
|
||||
rdfs:seeAlso <ToobPlayer.ttl> .
|
||||
|
||||
<http://two-play.com/plugins/toob-graphiceq> a lv2:Plugin ;
|
||||
lv2:binary <ToobAmp.so> ;
|
||||
rdfs:seeAlso <ToobGraphicEq.ttl> .
|
||||
|
||||
+1
-1
@@ -2286,7 +2286,7 @@ namespace pipedal
|
||||
}
|
||||
|
||||
#ifdef JUNK
|
||||
static void ExpectEvent(AlsaDriverImpl::AlsaMidiDeviceImpl &m, int event, const std::vector<uint8_t> message)
|
||||
static void ExpectEvent(AlsaDriverImpl::AlsaMidiDeviceImpl &m, int event, const std::vector<uint8_t> message)
|
||||
{
|
||||
MidiEvent e;
|
||||
m.GetMidiInputEvent(&e, event);
|
||||
|
||||
+5
-3
@@ -323,6 +323,7 @@ set(PIPEDAL_LIBS libpipedald zip
|
||||
pthread atomic stdc++fs asound avahi-common avahi-client systemd
|
||||
${VST3_LIBRARIES}
|
||||
${LILV_0_LIBRARIES}
|
||||
asound
|
||||
# ${JACK_LIBRARIES} - pending delete for JACK support.
|
||||
)
|
||||
|
||||
@@ -360,6 +361,7 @@ target_link_libraries(pipedal_kconfig
|
||||
PRIVATE ftxui::dom
|
||||
PRIVATE ftxui::component # Not needed for this example.
|
||||
PRIVATE PiPedalCommon
|
||||
asound
|
||||
systemd
|
||||
)
|
||||
|
||||
@@ -385,7 +387,7 @@ add_executable(hotspotManagerTest
|
||||
|
||||
target_link_libraries(hotspotManagerTest PRIVATE ${PIPEDAL_LIBS})
|
||||
|
||||
set_target_properties(hotspotManagerTest PROPERTIES EXCLUDE_FROM_ALL true)
|
||||
set_target_properties(hotspotManagerTest PROPERTIES EXCLUDE_FROM_ALL ${PIPEDAL_EXCLUDE_TESTS})
|
||||
|
||||
|
||||
add_executable(AuxInTest
|
||||
@@ -426,7 +428,7 @@ target_link_libraries(pipedaltest PRIVATE ${PIPEDAL_LIBS} ${ICU_LIBRARIES})
|
||||
target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES}
|
||||
)
|
||||
|
||||
set_target_properties(pipedaltest PROPERTIES EXCLUDE_FROM_ALL true)
|
||||
set_target_properties(pipedaltest PROPERTIES EXCLUDE_FROM_ALL ${PIPEDAL_EXCLUDE_TESTS})
|
||||
|
||||
if (NOT GITHUB_ACTIONS) # Google perftools are not available on Github action servers.
|
||||
|
||||
@@ -462,7 +464,7 @@ target_link_libraries(jsonTest PRIVATE PiPedalCommon)
|
||||
target_include_directories(jsonTest PRIVATE ${PIPEDAL_INCLUDES}
|
||||
)
|
||||
|
||||
set_target_properties(jsonTest PROPERTIES EXCLUDE_FROM_ALL true)
|
||||
set_target_properties(jsonTest PROPERTIES EXCLUDE_FROM_ALL ${PIPEDAL_EXCLUDE_TESTS})
|
||||
|
||||
|
||||
target_link_libraries(jsonTest PRIVATE pthread)
|
||||
|
||||
+215
-3
@@ -47,6 +47,7 @@ void PiPedalAlsaDevices::cacheDevice(const std::string &name, const AlsaDeviceIn
|
||||
|
||||
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
{
|
||||
|
||||
std::lock_guard guard{alsaMutex};
|
||||
|
||||
std::vector<AlsaDeviceInfo> result;
|
||||
@@ -118,7 +119,7 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
if (err == 0)
|
||||
{
|
||||
unsigned int minRate = 0, maxRate = 0;
|
||||
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
||||
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
||||
int dir;
|
||||
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
||||
if (err == 0)
|
||||
@@ -143,7 +144,8 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
info.sampleRates_.push_back(rate);
|
||||
}
|
||||
}
|
||||
if (minBufferSize < 16) {
|
||||
if (minBufferSize < 16)
|
||||
{
|
||||
minBufferSize = 16;
|
||||
}
|
||||
|
||||
@@ -180,7 +182,207 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<AlsaMidiDeviceInfo> GetAlsaDevices(const char *devname, const char *direction)
|
||||
static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaMidiDeviceInfo::Direction direction, std::vector<AlsaMidiDeviceInfo> *result)
|
||||
{
|
||||
snd_rawmidi_info_t *info = nullptr;
|
||||
const char *name = nullptr;
|
||||
const char *sub_name = nullptr;
|
||||
int subs = 0, subs_in = 0, subs_out = 0;
|
||||
int sub = 0;
|
||||
int err = 0;
|
||||
;
|
||||
|
||||
snd_rawmidi_info_alloca(&info);
|
||||
snd_rawmidi_info_set_device(info, device);
|
||||
|
||||
snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);
|
||||
err = snd_ctl_rawmidi_info(ctl, info);
|
||||
if (err >= 0)
|
||||
subs_in = snd_rawmidi_info_get_subdevices_count(info);
|
||||
else
|
||||
subs_in = 0;
|
||||
|
||||
snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);
|
||||
err = snd_ctl_rawmidi_info(ctl, info);
|
||||
if (err >= 0)
|
||||
subs_out = snd_rawmidi_info_get_subdevices_count(info);
|
||||
else
|
||||
subs_out = 0;
|
||||
|
||||
subs = subs_in > subs_out ? subs_in : subs_out;
|
||||
if (!subs)
|
||||
return;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case AlsaMidiDeviceInfo::In:
|
||||
if (subs_out == 0) // out for the device, in for us.
|
||||
{
|
||||
return; // no input devices to add
|
||||
}
|
||||
break;
|
||||
case AlsaMidiDeviceInfo::Out: // in for the device, out for us.
|
||||
if (subs_in == 0)
|
||||
{
|
||||
return; // no output devices to add
|
||||
}
|
||||
break;
|
||||
case AlsaMidiDeviceInfo::InOut:
|
||||
if (subs_in == 0 || subs_out == 0)
|
||||
{
|
||||
return; // no input or output devices to add
|
||||
}
|
||||
break;
|
||||
case AlsaMidiDeviceInfo::None:
|
||||
return; // no devices to add
|
||||
}
|
||||
|
||||
for (sub = 0; sub < subs; ++sub)
|
||||
{
|
||||
snd_rawmidi_info_set_stream(info, direction == AlsaMidiDeviceInfo::Direction::Out ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT);
|
||||
snd_rawmidi_info_set_subdevice(info, sub);
|
||||
err = snd_ctl_rawmidi_info(ctl, info);
|
||||
if (err < 0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
SS("snd_ctl_rawmidi_info failed for card " << card << ", device " << device << ", subdevice " << sub << ": " << snd_strerror(err)));
|
||||
}
|
||||
name = snd_rawmidi_info_get_name(info);
|
||||
sub_name = snd_rawmidi_info_get_subdevice_name(info);
|
||||
// get card name.
|
||||
|
||||
std::string cardName;
|
||||
snd_ctl_card_info_t *card_info = nullptr;
|
||||
snd_ctl_card_info_malloc(&card_info);
|
||||
if (snd_ctl_card_info(ctl, card_info) == 0) {
|
||||
cardName = snd_ctl_card_info_get_name(card_info);
|
||||
}
|
||||
snd_ctl_card_info_free(card_info);
|
||||
if (cardName.length() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub == 0 && sub_name[0] == '\0')
|
||||
{
|
||||
AlsaMidiDeviceInfo info;
|
||||
info.name_ = SS("hw:CARD=" << cardName << ",DEV=" << device);
|
||||
info.description_ = SS("Virtual MIDI " << card << "-" << device);
|
||||
info.card_ = card;
|
||||
info.device_ = device;
|
||||
info.subdevice_ = 0;
|
||||
info.isVirtual_ = true;
|
||||
info.subDevices_ = subs;
|
||||
if (subs_in > 0 && subs_out > 0)
|
||||
{
|
||||
info.direction_ = AlsaMidiDeviceInfo::InOut;
|
||||
}
|
||||
else if (subs_in > 0)
|
||||
{
|
||||
info.direction_ = AlsaMidiDeviceInfo::In;
|
||||
}
|
||||
else if (subs_out > 0)
|
||||
{
|
||||
info.direction_ = AlsaMidiDeviceInfo::Out;
|
||||
}
|
||||
else
|
||||
{
|
||||
info.direction_ = AlsaMidiDeviceInfo::None;
|
||||
}
|
||||
result->push_back(info);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
AlsaMidiDeviceInfo info;
|
||||
info.name_ = SS("hw:CARD=" << cardName << ",DEV=" << device);
|
||||
if (sub != 0) {
|
||||
info.name_ = SS(info.name_ << "," << sub);
|
||||
}
|
||||
info.description_ = sub_name;
|
||||
info.card_ = card;
|
||||
info.device_ = device;
|
||||
info.subdevice_ = sub;
|
||||
info.isVirtual_ = false;
|
||||
info.subDevices_ = 1;
|
||||
result->push_back(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
static void AddMidiCardToList(int card, std::vector<AlsaMidiDeviceInfo> *result, AlsaMidiDeviceInfo::Direction direction)
|
||||
{
|
||||
snd_ctl_t *ctl = nullptr;
|
||||
char name[32];
|
||||
int device;
|
||||
int err;
|
||||
|
||||
sprintf(name, "hw:%d", card);
|
||||
if ((err = snd_ctl_open(&ctl, name, 0)) < 0)
|
||||
{
|
||||
throw std::runtime_error(SS("cannot open control for card " << card << ": " << snd_strerror(err)));
|
||||
}
|
||||
device = -1;
|
||||
for (;;)
|
||||
{
|
||||
if ((err = snd_ctl_rawmidi_next_device(ctl, &device)) < 0)
|
||||
{
|
||||
snd_ctl_close(ctl);
|
||||
throw std::runtime_error(SS("cannot get next rawmidi device for card " << card << ": " << snd_strerror(err)));
|
||||
}
|
||||
if (device < 0)
|
||||
break;
|
||||
AddMidiCardDevicesToList(ctl, card, device, direction, result);
|
||||
}
|
||||
snd_ctl_close(ctl);
|
||||
}
|
||||
|
||||
static std::vector<AlsaMidiDeviceInfo> GetAlsaDevices(const char *devname, const char *direction_)
|
||||
{
|
||||
std::vector<AlsaMidiDeviceInfo> result;
|
||||
|
||||
int card, err;
|
||||
|
||||
AlsaMidiDeviceInfo::Direction direction;
|
||||
if (strcmp(direction_, "Input") == 0)
|
||||
{
|
||||
direction = AlsaMidiDeviceInfo::In;
|
||||
}
|
||||
else if (strcmp(direction_, "Output") == 0)
|
||||
{
|
||||
direction = AlsaMidiDeviceInfo::Out;
|
||||
}
|
||||
else if (strcmp(direction_, "InOut") == 0)
|
||||
{
|
||||
direction = AlsaMidiDeviceInfo::InOut;
|
||||
}
|
||||
else
|
||||
{
|
||||
direction = AlsaMidiDeviceInfo::None;
|
||||
return result;
|
||||
}
|
||||
|
||||
card = -1;
|
||||
if ((err = snd_card_next(&card)) < 0)
|
||||
{
|
||||
throw std::runtime_error(SS("snd_card_next failed.: " << snd_strerror(err)));
|
||||
}
|
||||
if (card < 0)
|
||||
{
|
||||
// no devices!
|
||||
return result;
|
||||
}
|
||||
do
|
||||
{
|
||||
AddMidiCardToList(card, &result, direction);
|
||||
if ((err = snd_card_next(&card)) < 0)
|
||||
{
|
||||
throw std::runtime_error(SS("snd_card_next failed: " << snd_strerror(err)));
|
||||
}
|
||||
} while (card >= 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
static std::vector<AlsaMidiDeviceInfo> OldGetAlsaDevices(const char *devname, const char *direction)
|
||||
{
|
||||
std::vector<AlsaMidiDeviceInfo> result;
|
||||
{
|
||||
@@ -214,6 +416,8 @@ static std::vector<AlsaMidiDeviceInfo> GetAlsaDevices(const char *devname, const
|
||||
result.push_back(AlsaMidiDeviceInfo(name, desc));
|
||||
}
|
||||
}
|
||||
// translate rawmidi device name to device number.
|
||||
|
||||
if (name && strcmp("null", name) != 0)
|
||||
free(name);
|
||||
if (desc && strcmp("null", desc) != 0)
|
||||
@@ -260,6 +464,14 @@ AlsaMidiDeviceInfo::AlsaMidiDeviceInfo(const char *name, const char *description
|
||||
}
|
||||
}
|
||||
|
||||
AlsaMidiDeviceInfo::AlsaMidiDeviceInfo(const char *name, const char *description, int card, int device, int subdevice)
|
||||
: AlsaMidiDeviceInfo(name, description)
|
||||
{
|
||||
this->card_ = card;
|
||||
this->device_ = device;
|
||||
this->subdevice_ = subdevice;
|
||||
}
|
||||
|
||||
JSON_MAP_BEGIN(AlsaDeviceInfo)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, cardId)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, id)
|
||||
|
||||
@@ -41,11 +41,25 @@ namespace pipedal {
|
||||
};
|
||||
class AlsaMidiDeviceInfo {
|
||||
public:
|
||||
enum Direction {
|
||||
None = 0,
|
||||
In = 1,
|
||||
Out = 2,
|
||||
InOut = 3
|
||||
};
|
||||
AlsaMidiDeviceInfo() { }
|
||||
AlsaMidiDeviceInfo(const char*name, const char*description);
|
||||
AlsaMidiDeviceInfo(const char*name, const char*description, int card, int device, int subdevice);
|
||||
std::string name_;
|
||||
std::string description_;
|
||||
|
||||
int card_ = -1;
|
||||
int device_ = -1;
|
||||
int subdevice_ = -1;
|
||||
bool isVirtual_ = false;
|
||||
int subDevices_ = -1;
|
||||
Direction direction_ = Direction::None;
|
||||
|
||||
DECLARE_JSON_MAP(AlsaMidiDeviceInfo);
|
||||
|
||||
};
|
||||
@@ -62,4 +76,5 @@ namespace pipedal {
|
||||
};
|
||||
std::vector<AlsaMidiDeviceInfo> GetAlsaMidiInputDevices();
|
||||
std::vector<AlsaMidiDeviceInfo> GetAlsaMidiOutputDevices();
|
||||
|
||||
}
|
||||
+31
-1
@@ -92,7 +92,7 @@ void ReadFromsequencerTest()
|
||||
AlsaMidiMessage message;
|
||||
while (true)
|
||||
{
|
||||
if (sequencer.ReadMessage(message))
|
||||
if (sequencer.ReadMessage(message,true))
|
||||
{
|
||||
cout << "Received MIDI message: "
|
||||
<< " " << hex << setfill('0') << setw(2) << static_cast<int>(message.cc0)
|
||||
@@ -104,9 +104,39 @@ void ReadFromsequencerTest()
|
||||
}
|
||||
}
|
||||
|
||||
void TestConfigMigration()
|
||||
{
|
||||
cout << "--- Testing ALSA Config Migration" << endl;
|
||||
// older versions of PiPedal uses ALSA rawmidi.
|
||||
// this code test coversion of rawmidi device IDs to ALSA Sequencer IDs.
|
||||
auto rawDevices = GetAlsaMidiInputDevices();
|
||||
auto seqDevices = AlsaSequencer::EnumeratePorts();
|
||||
|
||||
for (const auto&seqDevice: seqDevices) {
|
||||
cout << seqDevice.id << " -> " << seqDevice.rawMidiDevice << endl;
|
||||
}
|
||||
for (const auto&rawDevice: rawDevices) {
|
||||
std::string sequencerId = RawMidiIdToSequencerId(seqDevices,rawDevice.name_);
|
||||
REQUIRE(!sequencerId.empty());
|
||||
cout << rawDevice.name_ << "->" << sequencerId << std::endl;
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("ALSA Seq Test", "[pipedal_alsa_seq_test][Build][Dev]")
|
||||
{
|
||||
|
||||
// hw:CARD=VirMIDI,DEV=0 VirMIDI
|
||||
// hw:CARD=VirMIDI,DEV=1 VirMIDI
|
||||
// hw:CARD=VirMIDI,DEV=2 VirMIDI
|
||||
// hw:CARD=VirMIDI,DEV=3 VirMIDI
|
||||
// hw:CARD=M2,DEV=0 M2
|
||||
// hw:CARD=V25,DEV=0 V25
|
||||
EnumerateSequencers();
|
||||
TestConfigMigration();
|
||||
|
||||
|
||||
ReadFromsequencerTest();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user