Jack configuration (alpha)

This commit is contained in:
Robin Davies
2021-08-29 14:04:59 -04:00
parent 6e1b7346f6
commit 94c7d406f7
32 changed files with 1061 additions and 293 deletions
+4 -2
View File
@@ -118,6 +118,7 @@ set (PIPEDAL_SOURCES
WifiChannels.hpp
WifiChannels.cpp
RegDb.cpp RegDb.hpp
PiPedalAlsa.hpp PiPedalAlsa.cpp
)
configure_file(config.hpp.in config.hpp)
@@ -136,7 +137,7 @@ target_include_directories(pipedald PRIVATE
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS} ${LVDEV_INCLUDE_DIRS}
)
target_link_libraries(pipedald PRIVATE pthread atomic stdc++fs
target_link_libraries(pipedald PRIVATE pthread atomic stdc++fs asound
${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} systemd
)
@@ -144,6 +145,7 @@ target_link_libraries(pipedald PRIVATE pthread atomic stdc++fs
add_executable(pipedaltest ${PIPEDAL_SOURCES} testMain.cpp
jsonTest.cpp
WifiChannelsTest.cpp
PiPedalAlsaTest.cpp
SystemConfigFile.hpp SystemConfigFile.cpp
SystemConfigFileTest.cpp
@@ -160,7 +162,7 @@ target_include_directories(pipedaltest PRIVATE
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS}
)
target_link_libraries(pipedaltest PRIVATE pthread atomic stdc++fs
target_link_libraries(pipedaltest PRIVATE pthread atomic stdc++fs asound
${LILV_0_LIBRARIES} ${JACK_LIBRARIES} ${LIBNL3_LIBRARIES} systemd
)
+128 -26
View File
@@ -18,6 +18,8 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include "CommandLineParser.hpp"
#include "PiPedalException.hpp"
#include <filesystem>
@@ -27,27 +29,29 @@
#include "SetWifiConfig.hpp"
#include "SysExec.hpp"
#include <sys/wait.h>
#include <pwd.h>
using namespace std;
using namespace pipedal;
#define SERVICE_ACCOUNT_NAME "pipedal_d"
#define SERVICE_GROUP_NAME "pipedal_d"
#define JACK_SERVICE_ACCOUNT_NAME "jack"
#define JACK_SERVICE_GROUP_NAME "jack"
#define AUDIO_SERVICE_GROUP_NAME "audio"
#define SYSTEMCTL_BIN "/usr/bin/systemctl"
#define GROUPADD_BIN "/usr/sbin/groupadd"
#define USERADD_BIN "/usr/sbin/useradd"
#define USERADD_BIN "/usr/sbin/useradd"
#define USERMOD_BIN "/usr/sbin/usermod"
#define CHGRP_BIN "/usr/bin/chgrp"
#define CHOWN_BIN "/usr/bin/chown"
#define CHMOD_BIN "/usr/bin/chmod"
#define SERVICE_PATH "/usr/lib/systemd/system"
#define NATIVE_SERVICE "pipedald"
#define SHUTDOWN_SERVICE "pipedalshutdownd"
// #define REACT_SERVICE "pipedal_react"
#define JACK_SERVICE "jack"
std::filesystem::path GetServiceFileName(const std::string &serviceName)
{
@@ -113,6 +117,14 @@ void StopService()
cout << "Error: Failed to stop the " SHUTDOWN_SERVICE " service.";
}
}
void Uninstall() {
StopService();
DisableService();
system(SYSTEMCTL_BIN " stop jack");
system(SYSTEMCTL_BIN " disable jack");
}
void StartService()
{
@@ -132,8 +144,98 @@ void RestartService()
StartService();
}
static bool userExists(const char *userName)
{
struct passwd pwd;
struct passwd *result;
char *buf;
size_t bufsize;
int s;
bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
if (bufsize == -1) /* Value was indeterminate */
bufsize = 16384; /* Should be more than enough */
buf = (char *)malloc(bufsize);
if (buf == NULL)
{
perror("malloc");
exit(EXIT_FAILURE);
}
s = getpwnam_r(userName, &pwd, buf, bufsize, &result);
if (result == NULL)
{
return false;
}
return true;
}
void InstallLimits()
{
if (SysExec(GROUPADD_BIN " -f " AUDIO_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
{
throw PiPedalException("Failed to create audio service group.");
}
std::filesystem::path limitsConfig = "/usr/security/audio.conf";
if (!std::filesystem::exists(limitsConfig))
{
ofstream output(limitsConfig);
output << "# Realtime priority group used by pipedal/jack daemon" << endl;
output << "@audio - rtprio 95" << endl;
output << "@audio - memlock unlimited" << endl;
}
}
void MaybeStartJackService() {
std::filesystem::path drcFile = "/etc/jackdrc";
if (std::filesystem::exists(drcFile) && std::filesystem::file_size(drcFile) != 0) {
system(SYSTEMCTL_BIN " start jack");
} else {
system(SYSTEMCTL_BIN " mask jack");
}
}
void InstallJackService()
{
InstallLimits();
if (SysExec(GROUPADD_BIN " -f " JACK_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
{
throw PiPedalException("Failed to create jack service group.");
}
if (!userExists(JACK_SERVICE_ACCOUNT_NAME))
{
if (SysExec(USERADD_BIN " " JACK_SERVICE_ACCOUNT_NAME " -g " JACK_SERVICE_GROUP_NAME " -M -N -r") != EXIT_SUCCESS)
{
// throw PiPedalException("Failed to create service account.");
}
// lock account for login.
SysExec("passwd -l " JACK_SERVICE_ACCOUNT_NAME);
}
// Add to audio groups.
SysExec(USERMOD_BIN " -a -G " JACK_SERVICE_GROUP_NAME " " JACK_SERVICE_ACCOUNT_NAME);
SysExec(USERMOD_BIN " -a -G" AUDIO_SERVICE_GROUP_NAME " " JACK_SERVICE_ACCOUNT_NAME);
// deploy the systemd service file
std::map<std::string,std::string> map; // nothing to customize.
WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/templateJack.service"), GetServiceFileName(JACK_SERVICE));
MaybeStartJackService();
}
void Install(const std::filesystem::path &programPrefix, const std::string endpointAddress)
{
InstallJackService();
auto endpos = endpointAddress.find_last_of(':');
if (endpos == string::npos)
{
@@ -168,13 +270,15 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo
{
throw PiPedalException("Failed to create service group.");
}
if (SysExec(USERADD_BIN " " SERVICE_ACCOUNT_NAME " -g " SERVICE_GROUP_NAME " -M -N -r") != EXIT_SUCCESS)
if (!userExists(SERVICE_ACCOUNT_NAME))
{
// throw PiPedalException("Failed to create service account.");
if (SysExec(USERADD_BIN " " SERVICE_ACCOUNT_NAME " -g " SERVICE_GROUP_NAME " -M -N -r") != EXIT_SUCCESS)
{
// throw PiPedalException("Failed to create service account.");
}
// lock account for login.
SysExec("passwd -l " SERVICE_ACCOUNT_NAME);
}
// lock account for login.
SysExec("passwd -l " SERVICE_ACCOUNT_NAME);
// Add to audio groups.
SysExec(USERMOD_BIN " -a -G jack " SERVICE_ACCOUNT_NAME);
@@ -278,8 +382,9 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo
cout << "Complete" << endl;
}
int SudoExec(char**argv) {
int pbPid;
int SudoExec(char **argv)
{
int pbPid;
int returnValue = 0;
if ((pbPid = fork()) == 0)
@@ -295,7 +400,6 @@ int SudoExec(char**argv) {
}
}
int main(int argc, char **argv)
{
CommandLineParser parser;
@@ -328,7 +432,7 @@ int main(int argc, char **argv)
{
parser.Parse(argc, (const char **)argv);
int actionCount = install + uninstall + stop + start + enable + disable + enable_ap + disable_ap;
int actionCount = install + uninstall + stop + start + enable + disable + enable_ap + disable_ap + restart;
if (actionCount > 1)
{
throw PiPedalException("Please provide only one action.");
@@ -364,7 +468,8 @@ int main(int argc, char **argv)
if (help || helpError)
{
if (helpError) cout << endl;
if (helpError)
cout << endl;
cout << "pipedalconfig - Post-install configuration for pipdeal" << endl
<< "Copyright (c) 2021 Robin Davies. All rights reserved." << endl
@@ -409,7 +514,7 @@ int main(int argc, char **argv)
<< "Description:" << endl
<< " pipedalconfig performs post-install configuration of pipedal." << endl
<< endl;
exit(helpError? EXIT_FAILURE: EXIT_SUCCESS);
exit(helpError ? EXIT_FAILURE : EXIT_SUCCESS);
}
if (portOption.size() != 0 && !install)
{
@@ -421,16 +526,16 @@ int main(int argc, char **argv)
if (uid != 0 && !nopkexec)
{
// re-execute with PKEXEC in order to prompt form SUDO credentials.
std::vector<char*> args;
std::vector<char *> args;
std::string pkexec = "/usr/bin/pkexec"; // staged because "ISO C++ forbids converting a string constant to std::vector<char*>::value_type"(!)
args.push_back((char*)(pkexec.c_str()));
args.push_back((char *)(pkexec.c_str()));
std::filesystem::path path = std::filesystem::absolute(argv[0]);
std::string sPath = path;
args.push_back((char*)path.c_str());
args.push_back((char *)path.c_str());
for (int arg = 1; arg < argc; ++arg)
{
args.push_back((char*)argv[arg]);
args.push_back((char *)argv[arg]);
}
std::string prefixArg; // lifetime management for the prefix arguments
@@ -440,18 +545,14 @@ int main(int argc, char **argv)
{
std::filesystem::path prefix = std::filesystem::path(argv[0]).parent_path().parent_path();
prefixOptionArg = "-prefix";
args.push_back((char*)(prefixOptionArg.c_str()));
args.push_back((char *)(prefixOptionArg.c_str()));
prefixArg = prefix;
args.push_back((char*)prefixArg.c_str());
args.push_back((char *)prefixArg.c_str());
}
args.push_back(nullptr);
char**rawArgv = &args[0];
char **rawArgv = &args[0];
return SudoExec(rawArgv);
}
try
@@ -488,6 +589,7 @@ int main(int argc, char **argv)
}
else if (uninstall)
{
Uninstall();
}
else if (stop)
{
+1
View File
@@ -55,6 +55,7 @@ namespace pipedal
size_t GetBlockLength() const { return blockLength_; }
size_t GetMidiBufferSize() const { return midiBufferSize_;}
double GetMaxAllowedMidiDelta() const { return maxAllowedMidiDelta_; }
void SetErrorStatus(const std::string&message) { this->errorStatus_ = message; }
const std::vector<std::string> &GetInputAudioPorts() const { return inputAudioPorts_; }
const std::vector<std::string> &GetOutputAudioPorts() const { return outputAudioPorts_; }
+15 -6
View File
@@ -68,6 +68,7 @@ namespace pipedal
class JackHostImpl : public JackHost
{
private:
std::recursive_mutex mutex;
int64_t overrunGracePeriodSamples = 0;
@@ -145,7 +146,7 @@ private:
auto port = outputPorts[i];
if (port != nullptr)
{
jack_disconnect(client, channelSelection.getOutputAudioPorts()[i].c_str(), jack_port_name(port));
jack_disconnect(client, jack_port_name(port),channelSelection.getOutputAudioPorts()[i].c_str());
}
}
for (size_t i = 0; i < midiInputPorts.size(); ++i)
@@ -906,6 +907,7 @@ public:
virtual void Open(const JackChannelSelection &channelSelection)
{
std::lock_guard guard(mutex);
this->currentSample = 0;
@@ -1262,11 +1264,18 @@ private:
{
this_->restarting = true;
this_->Close();
ShutdownClient::SetJackServerConfiguration(jackServerSettings);
this_->Open(this_->channelSelection);
this_->restarting = false;
onComplete(true, "");
isComplete = true;
try {
ShutdownClient::SetJackServerConfiguration(jackServerSettings);
this_->Open(this_->channelSelection);
this_->restarting = false;
onComplete(true, "");
isComplete = true;
} catch (const std::exception &e)
{
onComplete(false,e.what());
this_->restarting = false;
isComplete = true;
}
}
static void ThreadProc_(RestartThread *this_)
{
+1 -1
View File
@@ -27,6 +27,7 @@
#include "json.hpp"
#include "JackServerSettings.hpp"
#include <functional>
#include "PiPedalAlsa.hpp"
namespace pipedal {
@@ -118,7 +119,6 @@ public:
class IHost;
class JackHost {
protected:
JackHost() { }
public:
+110 -79
View File
@@ -23,6 +23,9 @@
#include "JackServerSettings.hpp"
#include <fstream>
#include "PiPedalException.hpp"
#include <string>
#include "unistd.h"
#include <filesystem>
#define DRC_FILENAME "/etc/jackdrc"
@@ -55,24 +58,46 @@ static std::vector<std::string> SplitArgs(const char *szBuff)
return result;
}
static uint64_t GetJackArg(const std::vector<std::string> &args, const char *shortOption, const char *longOption)
static std::string GetJackStringArg(const std::vector<std::string> &args, const std::string&shortOption, const std::string&longOption)
{
int pos = -1;
std::string strVal;
for (size_t i = 1; i < args.size(); ++i)
{
if (args[i] == shortOption || args[i] == longOption)
{
pos = i;
auto pos = args[i].rfind(longOption,0);
if (pos != std::string::npos) {
if (args[i].length() == longOption.length())
{
if (i == args.size()-1) {
throw PiPedalException("Can't read Jack configuration.");
}
strVal = args[i+1];
} else {
strVal = args[i].substr(longOption.length());
}
break;
}
pos = args[i].rfind(shortOption,0);
if (pos != std::string::npos) {
if (args[i].length() == shortOption.length())
{
if (i == args.size()-1) {
throw PiPedalException("Can't read Jack configuration.");
}
strVal = args[i+1];
} else {
strVal = args[i].substr(shortOption.length());
}
break;
}
}
if (pos == -1 || pos == args.size() - 1)
{
throw PiPedalException("Can't read Jack configuration.");
}
return strVal;
}
static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::string&shortOption, const std::string&longOption)
{
std::string strVal = GetJackStringArg(args,shortOption,longOption);
try
{
unsigned long value = std::stoul(args[pos + 1]);
unsigned long value = std::stoul(strVal);
return value;
}
catch (const std::exception &)
@@ -80,35 +105,13 @@ static uint64_t GetJackArg(const std::vector<std::string> &args, const char *sho
throw PiPedalException("Can't read Jack configuration.");
}
}
static void SetJackArg(std::vector<std::string> &args, const char *shortOption, const char *longOption,uint64_t value)
{
int pos = -1;
for (size_t i = 1; i < args.size(); ++i)
{
if (args[i] == shortOption || args[i] == longOption)
{
pos = i;
break;
}
}
if (pos == -1 || pos == args.size() - 1)
{
throw PiPedalException("Can't read Jack configuration.");
}
stringstream s;
s << value;
args[pos+1] = s.str();
return;
}
void JackServerSettings::ReadJackConfiguration()
{
this->valid_ = false;
char firstLine[1024];
std::string lastLine;
{
ifstream input(DRC_FILENAME);
@@ -120,22 +123,32 @@ void JackServerSettings::ReadJackConfiguration()
while (true)
{
if (input.eof())
{
Lv2Log::error("Premature end of file in " DRC_FILENAME);
return;
std::string line;
std::getline(input,line);
if (line.length() != 0) {
lastLine = line;
}
input.getline(firstLine, sizeof(firstLine));
if (firstLine[0] != '#')
if (input.eof()) {
break;
}
}
try {
std::vector < std::string> argv = SplitArgs(firstLine);
this->bufferSize_ = GetJackArg(argv, "-p", "-period");
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "-nperiods");
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "-rate");
try
{
std::vector<std::string> argv = SplitArgs(lastLine.c_str());
for (auto i = argv.begin(); i != argv.end(); ++i)
{
if ((*i) == "-dalsa") {
argv.erase(i);
break;
}
}
this->bufferSize_ = GetJackArg(argv, "-p", "--period");
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods");
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "--rate");
this->alsaDevice_ = GetJackStringArg(argv,"-d", "--device");
valid_ = true;
} catch (std::exception &)
}
catch (std::exception &)
{
Lv2Log::error("Can't parse " DRC_FILENAME);
}
@@ -147,10 +160,10 @@ void JackServerSettings::Write()
this->valid_ = false;
std::vector<std::string> precedingLines;
std::vector < std::string> argv;
std::vector<std::string> argv;
std::string lastLine;
{
char firstLine[1024];
if (std::filesystem::exists(DRC_FILENAME)) {
ifstream input(DRC_FILENAME);
if (!input.is_open())
@@ -164,46 +177,64 @@ void JackServerSettings::Write()
if (input.eof())
{
Lv2Log::error("Premature end of file in " DRC_FILENAME);
return;
}
input.getline(firstLine, sizeof(firstLine));
if (firstLine[0] != '#')
break;
precedingLines.push_back(firstLine);
}
std::getline(input,lastLine);
precedingLines.push_back(lastLine);
if (input.eof())
{
break;
}
}
// set new values for arguments.
argv = SplitArgs(firstLine);
try {
SetJackArg(argv, "-p", "-period", this->bufferSize_);
SetJackArg(argv, "-n", "-nperiods", this->numberOfBuffers_);
SetJackArg(argv, "-r", "-rate",this->sampleRate_);
} catch (std::exception &)
// erase blank lines at the end.
while (precedingLines.size() != 0 && precedingLines[precedingLines.size()-1] == "")
{
Lv2Log::error("Can't parse " DRC_FILENAME);
return;
precedingLines.erase(precedingLines.begin()+precedingLines.size()-1);
}
}
// erase the last line, which should contain the command invocation.
if (precedingLines.size() != 0)
{
precedingLines.erase(precedingLines.begin()+precedingLines.size()-1);
}
}
// write to the output.
try {
try
{
ofstream output(DRC_FILENAME);
if (!output.is_open())
{
throw PiPedalException("Can't write " DRC_FILENAME);
}
for (auto line: precedingLines)
if (precedingLines.size() == 0)
{
// jack1 incantation for promiscuous servers.
output << "#!/bin/sh" <<endl;
output << "export JACK_PROMISCUOUS_SERVER=" << endl;
output << "export JACK_NO_AUDIO_RESERVATION=" << endl;
output << "umask 0" << endl;
}
for (auto line : precedingLines)
{
output << line << endl;
}
for (size_t i = 0; i < argv.size(); ++i)
// the style used by qjackctl. :-/
output << "/usr/bin/jackd "
<< "-R -P90"
<< " -driver alsa -d" << this->alsaDevice_
<< " -r" << this->sampleRate_
<< " -p" << this->bufferSize_
<< " -n" << this->numberOfBuffers_ << " -Xseq"
<< endl;
system("/usr/bin/chmod 755 " DRC_FILENAME);
system("pulseaudio --kill");
system("/usr/bin/systemctl enable jack");
if (system("/usr/bin/systemctl restart jack") != 0)
{
if (i != 0) {
output << " ";
}
output << argv[i];
throw PiPedalException("Failed to start jack audio service.");
}
output << endl;
} catch (const std::exception &e) {
}
catch (const std::exception &e)
{
stringstream s;
s << "jack - " << e.what();
Lv2Log::error(s.str());
@@ -211,10 +242,10 @@ void JackServerSettings::Write()
}
JSON_MAP_BEGIN(JackServerSettings)
JSON_MAP_REFERENCE(JackServerSettings,valid)
JSON_MAP_REFERENCE(JackServerSettings,rebootRequired)
JSON_MAP_REFERENCE(JackServerSettings,sampleRate)
JSON_MAP_REFERENCE(JackServerSettings,bufferSize)
JSON_MAP_REFERENCE(JackServerSettings,numberOfBuffers)
JSON_MAP_REFERENCE(JackServerSettings, valid)
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice)
JSON_MAP_REFERENCE(JackServerSettings, sampleRate)
JSON_MAP_REFERENCE(JackServerSettings, bufferSize)
JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers)
JSON_MAP_END()
+2
View File
@@ -26,6 +26,7 @@ namespace pipedal {
class JackServerSettings {
bool valid_ = false;
bool rebootRequired_ = false;
std::string alsaDevice_;
uint64_t sampleRate_ = 0;
uint32_t bufferSize_ = 0;
uint32_t numberOfBuffers_ = 0;
@@ -36,6 +37,7 @@ namespace pipedal {
uint64_t GetSampleRate() const { return sampleRate_; }
uint32_t GetBufferSize() const { return bufferSize_; }
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
const std::string&GetAlsaDevice() const { return alsaDevice_; }
void ReadJackConfiguration();
+20 -2
View File
@@ -22,6 +22,7 @@
#include "Locale.hpp"
#include <stdlib.h>
#include "Lv2Log.hpp"
// Must be UNICODE. Should reflect system locale. (.e.g en-US.UTF8, de-de.UTF8)
@@ -36,12 +37,29 @@ static const char*getLocale(const char*localeEnvironmentVariable)
{
result = getenv("LC_ALL");
}
if (result == nullptr) {
result = "en_US.UTF-8";
}
std::stringstream s;
s << "Locale: " << result;
Lv2Log::error(s.str());
return result;
}
static std::locale collationLocale(getLocale("LC_COLLATION"));
const std::collate<char>& Locale::collation = std::use_facet<std::collate<char> >(collationLocale);
void Locale::setDefaultLocale() {
const char* locale = getLocale("LC_ALL");
try {
setlocale(LC_ALL,locale);
} catch (const std::exception&)
{
std::stringstream s;
s << "Failed to set default locale (" << locale << "). Defaulting to en_US.";
Lv2Log::error(s.str());
std::setlocale(LC_ALL,"en_US.UTF-8");
}
}
const std::collate<char>& Locale::collation() { return std::use_facet<std::collate<char> >(std::locale()); }
+3 -1
View File
@@ -25,6 +25,8 @@ namespace pipedal {
class Locale {
public:
static const std::collate<char>& collation;
static void setDefaultLocale();
static const std::collate<char>& collation();
};
}
+7 -4
View File
@@ -394,13 +394,14 @@ void Lv2Host::Load(const char *lv2Path)
}
}
auto compare = [](
const auto &collation = std::use_facet<std::collate<char> >(std::locale());
auto compare = [&collation](
const std::shared_ptr<Lv2PluginInfo> &left,
const std::shared_ptr<Lv2PluginInfo> &right)
{
const char *pb1 = left->name().c_str();
const char *pb2 = right->name().c_str();
return Locale::collation.compare(
return collation.compare(
pb1, pb1 + left->name().size(),
pb2, pb2 + right->name().size()) < 0;
};
@@ -1005,9 +1006,11 @@ std::vector<Lv2PluginPreset> Lv2Host::GetPluginPresets(const std::string &plugin
}
lilv_nodes_free(presets);
auto compare = [] (const Lv2PluginPreset&left, const Lv2PluginPreset&right)
auto& collation = std::use_facet<std::collate<char> >(std::locale());
auto compare = [&collation] (const Lv2PluginPreset&left, const Lv2PluginPreset&right)
{
return Locale::collation.compare(
return collation.compare(
left.name_.c_str(),
left.name_.c_str()+left.name_.size(),
right.name_.c_str(),
+200
View File
@@ -0,0 +1,200 @@
// Copyright (c) 2021 Robin Davies
//
// 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 "pch.h"
#include "PiPedalAlsa.hpp"
#include "alsa/asoundlib.h"
#include "Lv2Log.hpp"
#include <mutex>
using namespace pipedal;
static uint32_t RATES[] = {
44100, 48000, 44100 * 2, 48000 * 2, 44100 * 4, 48000 * 4};
std::mutex alsaMutex;
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAvailableAlsaDevices()
{
std::lock_guard guard{alsaMutex};
std::vector<AlsaDeviceInfo> result;
int cardNum = -1; // Start with first card
int err;
for (;;)
{
if ((err = snd_card_next(&cardNum)) < 0)
{
Lv2Log::error("Unexpected error enumerating ALSA devices.");
break;
}
if (cardNum < 0)
// No more cards
break;
{
std::stringstream ss;
ss << "hw:" << cardNum;
std::string cardId = ss.str();
snd_ctl_t *hDevice = nullptr;
if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0)
{
continue;
}
snd_ctl_card_info_t *alsaInfo;
if (snd_ctl_card_info_malloc(&alsaInfo) != 0)
{
Lv2Log::error("Failed to allocate ALSA card info");
snd_ctl_close(hDevice);
continue;
}
err = snd_ctl_card_info(hDevice, alsaInfo);
if (err == 0)
{
AlsaDeviceInfo info;
info.cardId_ = cardNum;
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
const char* driver = snd_ctl_card_info_get_driver(alsaInfo);
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
snd_pcm_t *hDevice = nullptr;
// must support capture AND playback
err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, 0);
if (err == 0) {
snd_pcm_close(hDevice);
}
if (err == 0)
{
err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
}
if (err == 0)
{
snd_pcm_hw_params_t *params = nullptr;
err = snd_pcm_hw_params_malloc(&params);
if (err == 0)
{
err = snd_pcm_hw_params_any(hDevice, params);
if (err == 0)
{
unsigned int minRate = 0, maxRate = 0;
snd_pcm_uframes_t minBufferSize,maxBufferSize;
int dir;
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
if (err == 0)
{
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
}
if (err == 0)
{
err = snd_pcm_hw_params_get_buffer_size_min(params,&minBufferSize);
}
if (err == 0)
{
err = snd_pcm_hw_params_get_buffer_size_max(params,&maxBufferSize);
}
if (err == 0)
{
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
{
uint32_t rate = RATES[i];
if (rate >= minRate && rate <= maxRate)
{
info.sampleRates_.push_back(rate);
}
}
info.minBufferSize_ = (uint32_t)minBufferSize;
info.maxBufferSize_ = (uint32_t)maxBufferSize;
result.push_back(std::move(info));
}
}
}
if (params != nullptr)
snd_pcm_hw_params_free(params);
snd_pcm_close(hDevice);
}
}
snd_ctl_card_info_free(alsaInfo);
snd_ctl_close(hDevice);
}
}
snd_config_update_free_global();
return result;
}
void PiPedalAlsaDevices::PreLoadJackDevice(const std::string&deviceName)
{
// save the device info before we start the jack server, because
// we won't be able to do it later.
std::vector<AlsaDeviceInfo> devices = GetAvailableAlsaDevices();
this->hasJackDevice = false;
for (auto &device : devices)
{
if (device.id_ == deviceName) {
this->currentJackDevice = device;
this->hasJackDevice = true;
break;
}
}
}
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
{
std::vector<AlsaDeviceInfo> devices = GetAvailableAlsaDevices();
if (this->hasJackDevice)
{
bool found = false;
for (auto &device: devices)
{
if (device.id_ == this->currentJackDevice.id_)
{
found = true;
break;
}
}
if (!found)
{
devices.push_back(this->currentJackDevice);
}
}
return devices;
}
JSON_MAP_BEGIN(AlsaDeviceInfo)
JSON_MAP_REFERENCE(AlsaDeviceInfo, cardId)
JSON_MAP_REFERENCE(AlsaDeviceInfo, id)
JSON_MAP_REFERENCE(AlsaDeviceInfo, name)
JSON_MAP_REFERENCE(AlsaDeviceInfo, longName)
JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates)
JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
JSON_MAP_END()
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) 2021 Robin Davies
//
// 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 "json.hpp"
namespace pipedal {
class AlsaDeviceInfo {
public:
int cardId_ = -1;
std::string id_;
std::string name_;
std::string longName_;
std::vector<uint32_t> sampleRates_;
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
DECLARE_JSON_MAP(AlsaDeviceInfo);
};
class PiPedalAlsaDevices {
bool hasJackDevice = false;
AlsaDeviceInfo currentJackDevice;
std::vector<AlsaDeviceInfo> GetAvailableAlsaDevices();
public:
void PreLoadJackDevice(const std::string&deviceName);
std::vector<AlsaDeviceInfo> GetAlsaDevices();
};
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) 2021 Robin Davies
//
// 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 "pch.h"
#include "catch.hpp"
#include <sstream>
#include <cstdint>
#include <string>
#include "PiPedalAlsa.hpp"
using namespace pipedal;
TEST_CASE( "ALSA Test", "[pipedal_alsa_test]" ) {
PiPedalAlsaDevices devices;
auto result = devices.GetAlsaDevices();
REQUIRE(result.size() >= 1);
}
+46 -25
View File
@@ -87,6 +87,8 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration)
this->jackServerSettings.ReadJackConfiguration();
alsaDevices.PreLoadJackDevice(this->jackServerSettings.GetAlsaDevice());
storage.SetDataRoot(configuration.GetLocalStoragePath().c_str());
storage.Initialize();
@@ -332,7 +334,7 @@ PedalBoard PiPedalModel::getPreset(int64_t instanceId)
std::lock_guard guard(mutex);
return this->storage.GetPreset(instanceId);
}
void PiPedalModel::getBank(int64_t instanceId, BankFile*pResult)
void PiPedalModel::getBank(int64_t instanceId, BankFile *pResult)
{
std::lock_guard guard(mutex);
this->storage.GetBankFile(instanceId, pResult);
@@ -855,13 +857,19 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
{
std::lock_guard guard(mutex);
this->jackServerSettings = jackServerSettings;
if (!ShutdownClient::CanUseShutdownClient())
{
throw PiPedalException("Can't change server settings when running interactively.");
throw PiPedalException("Can't change server settings when running a debug server.");
}
if (this->jackServerSettings.GetAlsaDevice() != jackServerSettings.GetAlsaDevice())
{
this->alsaDevices.PreLoadJackDevice(jackServerSettings.GetAlsaDevice());
}
this->jackServerSettings = jackServerSettings;
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
@@ -879,29 +887,38 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
{
this->jackConfiguration.SetIsRestarting(true);
fireJackConfigurationChanged(this->jackConfiguration);
this->jackHost->UpdateServerConfiguration(jackServerSettings,
[this](bool success, const std::string &errorMessage)
{
std::lock_guard guard(mutex);
if (!success)
{
std::stringstream s;
s << "UpdateServerconfiguration failed: " << errorMessage;
Lv2Log::error(s.str().c_str());
}
// Update jack server status.
this->jackConfiguration.SetIsRestarting(false);
fireJackConfigurationChanged(this->jackConfiguration);
this->jackHost->UpdateServerConfiguration(
jackServerSettings,
[this](bool success, const std::string &errorMessage)
{
std::lock_guard guard(mutex);
if (!success)
{
std::stringstream s;
s << "UpdateServerconfiguration failed: " << errorMessage;
Lv2Log::error(s.str().c_str());
}
// Update jack server status.
this->jackConfiguration.SetIsRestarting(false);
if (!success)
{
this->jackConfiguration.SetErrorStatus(errorMessage);
}
else
{
this->jackConfiguration.SetErrorStatus("");
fireJackConfigurationChanged(this->jackConfiguration);
// restart the pedalboard on a new instance.
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
// restart the pedalboard on a new instance.
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard{this->lv2Host.CreateLv2PedalBoard(this->pedalBoard)};
this->lv2PedalBoard = lv2PedalBoard;
jackHost->SetPedalBoard(lv2PedalBoard);
updateRealtimeVuSubscriptions();
updateRealtimeMonitorPortSubscriptions();
});
}
jackHost->SetPedalBoard(lv2PedalBoard);
updateRealtimeVuSubscriptions();
updateRealtimeMonitorPortSubscriptions();
}
});
}
}
void PiPedalModel::updateDefaults(PedalBoardItem *pedalBoardItem)
@@ -1040,3 +1057,7 @@ void PiPedalModel::cancelListenForMidiEvent(int64_t clientId, int64_t clientHand
jackHost->SetListenForMidiEvent(false);
}
}
std::vector<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices()
{
return this->alsaDevices.GetAlsaDevices();
}
+3
View File
@@ -60,6 +60,8 @@ public:
class PiPedalModel: private IJackHostCallbacks {
private:
PiPedalAlsaDevices alsaDevices;
class MidiListener {
public:
@@ -202,6 +204,7 @@ public:
void listenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly);
void cancelListenForMidiEvent(int64_t clientId, int64_t clientHandled);
std::vector<AlsaDeviceInfo> GetAlsaDevices();
};
} // namespace pipedal.
+7
View File
@@ -33,6 +33,7 @@
#include "WifiConfigSettings.hpp"
#include "WifiChannels.hpp"
#include "SysExec.hpp"
#include "PiPedalAlsa.hpp"
using namespace std;
using namespace pipedal;
@@ -689,6 +690,12 @@ public:
{
JackHostStatus status = model.getJackStatus();
this->Reply(replyTo,"getJackStatus",status);
} else if (message == "getAlsaDevices")
{
std::vector<AlsaDeviceInfo> devices = model.GetAlsaDevices();
this->Reply(replyTo,"getAlsaDevices",devices);
} else if (message == "getWifiChannels")
{
std::string country;
+7 -7
View File
@@ -109,7 +109,8 @@ bool ShutdownClient::WriteMessage(const char*message) {
while (!eolFound)
{
ssize_t nRead = read(sock,pWrite,available);
if (nRead == 0) {
if (nRead == -1)
{
*pWrite = 0;
break;
}
@@ -156,12 +157,11 @@ bool ShutdownClient::WriteMessage(const char*message) {
bool ShutdownClient::SetJackServerConfiguration(const JackServerSettings & jackServerSettings)
{
std::stringstream s;
s << "setJackConfiguration "
<< jackServerSettings.GetSampleRate()
<< " " << jackServerSettings.GetBufferSize()
<< " " << jackServerSettings.GetNumberOfBuffers()
<< "\n";
s << "setJackConfiguration ";
json_writer writer(s,true);
writer.write(jackServerSettings);
s << std::endl;
return WriteMessage(s.str().c_str());
}
+13 -24
View File
@@ -80,27 +80,12 @@ static void ReleaseAccessPoint(const std::string gatewayAddress)
}
bool setJackConfiguration(uint32_t sampleRate,uint32_t bufferSize,uint32_t numberOfBuffers)
bool setJackConfiguration(JackServerSettings serverSettings)
{
bool success = true;
JackServerSettings serverSettings(sampleRate,bufferSize,numberOfBuffers);
try {
serverSettings.Write();
} catch (const std::exception &e) {
std::stringstream s;
s << "Failed to write jackdrc settings. " << e.what();
Lv2Log::error(s.str());
success = false;
}
::system("pulseaudio --kill");
if (::system("systemctl restart jack") != 0)
{
Lv2Log::error("Failed to restart jack server.");
success = false;
}
return false;
serverSettings.Write();
return true;
}
@@ -245,24 +230,28 @@ private:
} else if (startsWith(s,"setJackConfiguration "))
{
auto remainder = s.substr(strlen("setJackConfiguration "));
// xxx delete me
Lv2Log::error("setJackConfiguration: " + remainder);
std::stringstream input(remainder);
uint32_t sampleRate;
uint32_t bufferSize;
uint32_t numberOfBuffers;
input >> sampleRate >> bufferSize >> numberOfBuffers;
JackServerSettings serverSettings;
json_reader reader(input);
reader.read(&serverSettings);
if (input.fail())
{
result = -1;
} else {
result = setJackConfiguration(sampleRate,bufferSize,numberOfBuffers) ? 0: -1;
result = setJackConfiguration(serverSettings) ? 0: -1;
}
}
} catch (const std::exception &e)
{
std::stringstream t;
t << "-2 " << e.what();
t << "-2 " << e.what() << "\n";
this->response = t.str();
this->writePosition = 0;
WriteSome();
+2
View File
@@ -418,6 +418,8 @@ uint16_t g_ShutdownPort = 0;
int main(int argc, char *argv[])
{
sem_init(&signalSemaphore, 0, 0);
signal(SIGINT, sig_handler);
+4 -3
View File
@@ -5,6 +5,7 @@ After=network.target
BindsTo=jack.service
[Service]
LimitMEMLOCK=infinity
LimitRTPRIO=95
@@ -12,11 +13,11 @@ ExecStart=${COMMAND}
User=pipedal_d
Group=pipedal_d
Restart=always
RestartSec=15
RestartSec=25
TimeoutStopSec=10
WorkingDirectory=/var/pipedal
Environment=JACK_PROMISCUOUS_SERVER=jack
Environment=JACK_START_SERVER=1
Environment=JACK_PROMISCUOUS_SERVER=
Environment=JACK_STJACK_NO_START_SERVER=
[Install]
+24
View File
@@ -0,0 +1,24 @@
[Unit]
Description=Jack Audio Daemon
After=sound.target
After=local-fs.target
After=dbus.socket
[Service]
LimitMEMLOCK=infinity
LimitRTTIME=infinity
LimitRTPRIO=95
ExecStartPre=/bin/sleep 3
ExecStart=/etc/jackdrc
User=jack
Group=jack
Restart=always
RestartSec=15
Environment=JACK_PROMISCUOUS_SERVER=jack
Environment=JACK_NO_AUDIO_RESERVATION=1
[Install]
WantedBy=multi-user.target