diff options
Diffstat (limited to 'drumgizmo')
-rw-r--r-- | drumgizmo/Makefile.am | 15 | ||||
-rw-r--r-- | drumgizmo/dgvalidator.cc | 18 | ||||
-rw-r--r-- | drumgizmo/drumgizmoc.cc | 113 | ||||
-rw-r--r-- | drumgizmo/enginefactory.cc | 9 | ||||
-rw-r--r-- | drumgizmo/enginefactory.h | 9 | ||||
-rw-r--r-- | drumgizmo/input/alsamidi.cc | 196 | ||||
-rw-r--r-- | drumgizmo/input/alsamidi.h | 57 | ||||
-rw-r--r-- | drumgizmo/jackclient.cc | 48 | ||||
-rw-r--r-- | drumgizmo/jackclient.h | 12 | ||||
-rw-r--r-- | drumgizmo/output/alsa.cc | 40 | ||||
-rw-r--r-- | drumgizmo/output/alsa.h | 2 |
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; }; |