summaryrefslogtreecommitdiff
path: root/drumgizmo
diff options
context:
space:
mode:
Diffstat (limited to 'drumgizmo')
-rw-r--r--drumgizmo/Makefile.am15
-rw-r--r--drumgizmo/dgvalidator.cc18
-rw-r--r--drumgizmo/drumgizmoc.cc113
-rw-r--r--drumgizmo/enginefactory.cc9
-rw-r--r--drumgizmo/enginefactory.h9
-rw-r--r--drumgizmo/input/alsamidi.cc196
-rw-r--r--drumgizmo/input/alsamidi.h57
-rw-r--r--drumgizmo/jackclient.cc48
-rw-r--r--drumgizmo/jackclient.h12
-rw-r--r--drumgizmo/output/alsa.cc40
-rw-r--r--drumgizmo/output/alsa.h2
11 files changed, 486 insertions, 33 deletions
diff --git a/drumgizmo/Makefile.am b/drumgizmo/Makefile.am
index 2cb46bf..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 \
@@ -109,7 +114,7 @@ dgvalidator_CXXFLAGS = \
-I$(top_srcdir)/src -I$(top_srcdir)/getoptpp \
-I$(top_srcdir)/hugin -DWITH_HUG_MUTEX -DWITH_HUG_FILTER \
$(SSEFLAGS) \
- -I$(top_srcdir)/plugingui \
+ -I$(top_srcdir)/ \
-DLODEPNG_NO_COMPILE_ENCODER \
-DLODEPNG_NO_COMPILE_DISK \
-DLODEPNG_NO_COMPILE_ANCILLARY_CHUNKS \
@@ -122,9 +127,9 @@ dgvalidator_SOURCES = \
dgvalidator.cc \
$(top_srcdir)/hugin/hugin.c \
$(top_srcdir)/hugin/hugin_filter.c \
- $(top_srcdir)/plugingui/lodepng/lodepng.cpp \
- $(top_srcdir)/plugingui/image.cc \
- $(top_srcdir)/plugingui/resource.cc \
- $(top_srcdir)/plugingui/colour.cc
+ $(top_srcdir)/dggui/lodepng/lodepng.cpp \
+ $(top_srcdir)/dggui/image.cc \
+ $(top_srcdir)/dggui/resource.cc \
+ $(top_srcdir)/dggui/colour.cc
endif # ENABLE_CLI
diff --git a/drumgizmo/dgvalidator.cc b/drumgizmo/dgvalidator.cc
index 6a7c546..c4d4d46 100644
--- a/drumgizmo/dgvalidator.cc
+++ b/drumgizmo/dgvalidator.cc
@@ -37,8 +37,6 @@
#include <sstream>
#include <climits>
-#include <lodepng/lodepng.h>
-
#include <config.h>
#include <platform.h>
@@ -48,11 +46,13 @@
#include <unistd.h>
#endif
-#include <image.h>
+#include <dggui/image.h>
// Needed for Resource class
-#include <resource_data.h>
-const rc_data_t rc_data[] = {};
+#include <dggui/resource_data.h>
+
+const rc_data_t rc_dataX[] = {};
+const rc_data_t* rc_data = rc_dataX;
namespace
{
@@ -360,7 +360,7 @@ int main(int argc, char* argv[])
else
{
// Check if the image_map can be loaded (is a valid png file)
- GUI::Image img(image);
+ dggui::Image img(image);
if(!img.isValid())
{
logger(LogLevel::Error, "Drumkit image, '" + image +
@@ -392,7 +392,7 @@ int main(int argc, char* argv[])
else
{
// Check if the image_map can be loaded (is a valid png file)
- GUI::Image image(image_map);
+ dggui::Image image(image_map);
if(!image.isValid())
{
logger(LogLevel::Error, "Drumkit image_map, '" + image_map +
@@ -423,7 +423,7 @@ int main(int argc, char* argv[])
float red = (hex_colour >> 16 & 0xff) / 255.0f;
float green = (hex_colour >> 8 & 0xff) / 255.0f;
float blue = (hex_colour >> 0 & 0xff) / 255.0f;
- GUI::Colour colour(red, green, blue);
+ dggui::Colour colour(red, green, blue);
bool found{false};
for(int y = 0; y < image.height() && !found; ++y)
@@ -548,7 +548,7 @@ int main(int argc, char* argv[])
else
{
// Check if the logo can be loaded (is a valid png file)
- GUI::Image img(image);
+ dggui::Image img(image);
if(!img.isValid())
{
logger(LogLevel::Error, "Drumkit logo, '" + image +
diff --git a/drumgizmo/drumgizmoc.cc b/drumgizmo/drumgizmoc.cc
index 4853641..8eba4c9 100644
--- a/drumgizmo/drumgizmoc.cc
+++ b/drumgizmo/drumgizmoc.cc
@@ -34,7 +34,11 @@
#include <sstream>
#include <chrono>
#include <thread>
+#ifdef HAVE_WORDEXP
#include <wordexp.h>
+#else
+#include <glob.h>
+#endif
#include <hugin.hpp>
@@ -90,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"
@@ -101,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"
@@ -136,6 +142,12 @@ static std::string arguments()
" diverse: The importance given to choosing samples\n"
" which haven't been played recently. [0,1]\n"
" random: The amount of randomness added. [0,1]\n"
+ "\n"
+ "Voice limit parameters:\n"
+ " max: Maximum number of voices for each instrument before\n"
+ " old samples are ramped down. [1,30]\n"
+ " rampdown: Time it takes for an old sample to completely fall\n"
+ " silent. [0.01,2.0]\n"
"\n";
return output.str();
}
@@ -146,19 +158,31 @@ std::vector<ParmToken> parseParameters(std::string &parms)
std::string parm;
std::string val;
bool inval = false;
+#ifdef HAVE_WORDEXP
wordexp_t exp_result;
+#else
+ glob_t g;
+#endif
for(size_t i = 0; i < parms.size(); ++i)
{
if(parms[i] == ',')
{
+ #ifdef HAVE_WORDEXP
int error = wordexp(val.data(), &exp_result, 0);
+ #else
+ int error = glob(val.data(), 0, NULL, &g);
+ #endif
if(error)
{
std::cerr << "Wrong argument: ";
std::cerr << parm << " = " << val << std::endl;
exit(1);
}
+ #ifdef HAVE_WORDEXP
result.push_back({parm, exp_result.we_wordv[0]});
+ #else
+ result.push_back({parm, g.gl_pathv[0]});
+ #endif
parm = "";
val = "";
inval = false;
@@ -182,14 +206,22 @@ std::vector<ParmToken> parseParameters(std::string &parms)
}
if(parm != "")
{
+ #ifdef HAVE_WORDEXP
int error = wordexp(val.data(), &exp_result, 0);
+ #else
+ int error = glob(val.data(), 0, NULL, &g);
+ #endif
if(error)
{
std::cerr << "Wrong argument: ";
std::cerr << parm << " = " << val << std::endl;
exit(1);
}
+ #ifdef HAVE_WORDEXP
result.push_back({parm, exp_result.we_wordv[0]});
+ #else
+ result.push_back({parm, g.gl_pathv[0]});
+ #endif
}
return result;
}
@@ -237,7 +269,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;
@@ -300,6 +332,7 @@ int main(int argc, char* argv[])
}
oe = factory.createOutput(engine);
if(ie == NULL)
+ if(oe == NULL)
{
std::cerr << "Invalid output engine: " << engine << std::endl;
return 1;
@@ -518,6 +551,54 @@ int main(int argc, char* argv[])
return 0;
});
+ // Default is to disable voice limit
+ settings.enable_voice_limit.store(false);
+
+ opt.add("voice-limit", no_argument, 'l',
+ "Enable voice limit.",
+ [&]()
+ {
+ settings.enable_voice_limit.store(true);
+ return 0;
+ });
+
+ opt.add("voice-limitparms", required_argument, 'L',
+ "Voice limit options.",
+ [&]()
+ {
+ std::string parms = optarg;
+ auto tokens = parseParameters(parms);
+ for(auto& token : tokens)
+ {
+ if(token.key == "max")
+ {
+ auto val = atof_nol(token.value.data());
+ if(val < 1.0 || val > 30.0)
+ {
+ std::cerr << "max range is [1, 30].\n";
+ return 1;
+ }
+ settings.voice_limit_max.store(val);
+ }
+ else if(token.key == "rampdown")
+ {
+ auto val = atof_nol(token.value.data());
+ if(val < 0.01 || val > 2.0)
+ {
+ std::cerr << "rampdown range is [0.01, 2.0].\n";
+ return 1;
+ }
+ settings.voice_limit_rampdown.store(val);
+ }
+ else
+ {
+ std::cerr << "Unknown voice limitparms argument " << token.key << std::endl;
+ return 1;
+ }
+ }
+ return 0;
+ });
+
opt.add("parameters", required_argument, 'p',
"Parameters for sample selection algorithm.",
[&]()
@@ -674,6 +755,34 @@ int main(int argc, char* argv[])
}
std::cout << "\ndone" << std::endl;
}
+ else
+ {
+ // Async loading in progress
+
+ // Wait until the loader has passed the kit parsing step before proceeding.
+ bool parsing_done{false};
+ while(!parsing_done)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+
+ switch(settings.drumkit_load_status.load())
+ {
+ case LoadStatus::Idle:
+ case LoadStatus::Parsing:
+ // Not yet past the parsing step
+ break;
+ case LoadStatus::Loading:
+ case LoadStatus::Done:
+ // Past parsing step
+ parsing_done = true;
+ break;
+ case LoadStatus::Error:
+ // Kit parser error?
+ std::cout << "\nFailed to load " << kitfile << std::endl;
+ return 1;
+ }
+ }
+ }
gizmo.setSamplerate(oe->getSamplerate());
oe->onLatencyChange(gizmo.getLatency());
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;
+};
diff --git a/drumgizmo/jackclient.cc b/drumgizmo/jackclient.cc
index 8bf0939..0b05358 100644
--- a/drumgizmo/jackclient.cc
+++ b/drumgizmo/jackclient.cc
@@ -28,12 +28,6 @@
#include "jackclient.h"
-JackProcess::~JackProcess()
-{
-}
-
-// --------------------------------------------------------------------
-
JackPort::JackPort(JackClient& client, const std::string& name,
const char* type, JackPortFlags flags)
: client{client.client} // register jack port for given client
@@ -57,8 +51,7 @@ int JackClient::wrapJackProcess(jack_nframes_t nframes, void* arg)
return static_cast<JackClient*>(arg)->process(nframes);
}
-void JackClient::latencyCallback(jack_latency_callback_mode_t mode,
- void* arg)
+void JackClient::latencyCallback(jack_latency_callback_mode_t mode, void* arg)
{
static_cast<JackClient*>(arg)->jackLatencyCallback(mode);
}
@@ -92,12 +85,23 @@ JackClient::~JackClient()
void JackClient::add(JackProcess& process)
{
- processes.insert(&process);
+ JackProcessContainer c;
+ c.process = &process;
+ processes.push_back(std::move(c));
}
void JackClient::remove(JackProcess& process)
{
- processes.erase(&process);
+ // Do not erase here. Instead mark as disabled - it will be erased at next
+ // JackClient::process call.
+ for(auto& ptr : processes)
+ {
+ if(ptr.process == &process)
+ {
+ ptr.active = false;
+ }
+ }
+ dirty = true;
}
void JackClient::activate()
@@ -111,10 +115,30 @@ void JackClient::activate()
int JackClient::process(jack_nframes_t num_frames)
{
+ // Clear out any inactive processes before iterating
+ if(dirty)
+ {
+ auto it = processes.begin();
+ while(it != processes.end())
+ {
+ if(it->active == false)
+ {
+ it = processes.erase(it);
+ }
+ else
+ {
+ it++;
+ }
+ }
+
+ dirty = false;
+ }
+
for(auto& ptr : processes)
{
- ptr->process(num_frames);
+ ptr.process->process(num_frames);
}
+
return 0;
}
@@ -122,7 +146,7 @@ void JackClient::jackLatencyCallback(jack_latency_callback_mode_t mode)
{
for(auto& ptr : processes)
{
- ptr->jackLatencyCallback(mode);
+ ptr.process->jackLatencyCallback(mode);
}
}
diff --git a/drumgizmo/jackclient.h b/drumgizmo/jackclient.h
index f769ab4..04d6654 100644
--- a/drumgizmo/jackclient.h
+++ b/drumgizmo/jackclient.h
@@ -27,7 +27,7 @@
#pragma once
#include <vector>
#include <string>
-#include <set>
+#include <list>
#include <jack/jack.h>
@@ -38,7 +38,7 @@ class JackClient;
class JackProcess
{
public:
- virtual ~JackProcess();
+ virtual ~JackProcess() = default;
virtual void process(jack_nframes_t num_frames) = 0;
virtual void jackLatencyCallback(jack_latency_callback_mode_t mode) {}
};
@@ -76,7 +76,13 @@ public:
private:
jack_client_t* client;
- std::set<JackProcess*> processes;
+ bool dirty{false};
+ struct JackProcessContainer
+ {
+ JackProcess *process;
+ bool active{true};
+ };
+ std::list<JackProcessContainer> processes;
bool is_active;
bool is_freewheeling;
diff --git a/drumgizmo/output/alsa.cc b/drumgizmo/output/alsa.cc
index f340c30..db7932b 100644
--- a/drumgizmo/output/alsa.cc
+++ b/drumgizmo/output/alsa.cc
@@ -58,6 +58,7 @@ AlsaOutputEngine::AlsaOutputEngine()
, dev{"default"}
, srate{44100}
, frames{32}
+ , periods{3}
{
}
@@ -104,6 +105,9 @@ bool AlsaOutputEngine::init(const Channels& channels)
value =
snd_pcm_hw_params_set_period_size_near(handle, params, &frames, 0);
AlsaInitError::test(value, "snd_pcm_hw_params_set_period_size_near");
+ value =
+ snd_pcm_hw_params_set_periods_near(handle, params, &periods, 0);
+ AlsaInitError::test(value, "snd_pcm_hw_params_set_periods_near");
value = snd_pcm_hw_params(handle, params);
AlsaInitError::test(value, "snd_pcm_hw_params");
}
@@ -140,6 +144,19 @@ void AlsaOutputEngine::setParm(const std::string& parm, const std::string& value
<< "\n";
}
}
+ else if(parm == "periods")
+ {
+ // try to apply number of periods
+ try
+ {
+ periods = std::stoi(value);
+ }
+ catch(...)
+ {
+ std::cerr << "[AlsaOutputEngine] Invalid number of periods " << value
+ << "\n";
+ }
+ }
else if(parm == "srate")
{
try
@@ -184,7 +201,28 @@ void AlsaOutputEngine::run(int ch, sample_t* samples, size_t nsamples)
void AlsaOutputEngine::post(size_t nsamples)
{
// Write the interleaved buffer to the soundcard
- snd_pcm_writei(handle, data.data(), nsamples);
+ snd_pcm_sframes_t value = snd_pcm_writei(handle, data.data(), nsamples);
+
+ if(value == -EPIPE) // under-run
+ {
+ snd_pcm_prepare(handle);
+ }
+ else if(value == -ESTRPIPE)
+ {
+ while((value = snd_pcm_resume(handle)) == -EAGAIN)
+ {
+ sleep(1); // wait until the suspend flag is released
+ }
+ if(value < 0)
+ {
+ snd_pcm_prepare(handle);
+ }
+ }
+}
+
+size_t AlsaOutputEngine::getBufferSize() const
+{
+ return frames;
}
size_t AlsaOutputEngine::getSamplerate() const
diff --git a/drumgizmo/output/alsa.h b/drumgizmo/output/alsa.h
index 56011b6..96baf44 100644
--- a/drumgizmo/output/alsa.h
+++ b/drumgizmo/output/alsa.h
@@ -49,6 +49,7 @@ public:
void pre(size_t nsamples) override;
void run(int ch, sample_t* samples, size_t nsamples) override;
void post(size_t nsamples) override;
+ size_t getBufferSize() const override;
size_t getSamplerate() const override;
bool isFreewheeling() const override;
@@ -61,4 +62,5 @@ private:
std::string dev;
unsigned int srate; // samplerate
snd_pcm_uframes_t frames;
+ unsigned int periods;
};