diff --git a/.vscode/launch.json b/.vscode/launch.json index 89fa735..3a3272a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -112,7 +112,7 @@ //"[json_variants]" // subtest of your choice, or none to run all of the tests. //"[inverting_mutex_test]" // "[utf8_to_utf32]" - "[wifi_channels_test]" + "[pipedal_alsa_test]" ], "stopAtEntry": false, @@ -297,7 +297,7 @@ //"--get-current-port" // "--uninstall" //"--enable-p2p" , "CA", "PiPedalTest","12345678","14" - "--list-p2p-channels" + "--alsa-devices" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", @@ -326,6 +326,33 @@ } ] }, + { + "name": "(gdb) pipedal_alsa_check", + "type": "cppdbg", + "request": "launch", + // Resolved by CMake Tools: + "program": "${command:cmake.launchTargetPath}", + "args": [ + //"hw:M2" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "PATH", + "value": "$PATH:${command:cmake.launchTargetDirectory}" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, { "name": "(gdb) pipedal_p2pd", diff --git a/CMakeLists.txt b/CMakeLists.txt index aa0506e..93922b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 3.16.0) project(pipedal - VERSION 1.3.62 + VERSION 1.3.66 DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" HOMEPAGE_URL "https://rerdavies.github.io/pipedal" ) -set (DISPLAY_VERSION "PiPedal v1.3.62-Beta") +set (DISPLAY_VERSION "PiPedal v1.3.66-Release") set (PACKAGE_ARCHITECTURE "arm64") set (CMAKE_INSTALL_PREFIX "/usr/") diff --git a/README.md b/README.md index adee28e..4ed6b75 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,18 @@ -Download: v1.3.62 +Download: v1.3.66 Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).   -#### NEW version 1.3.62 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. +#### PiPedal has a submitted a request to support sharing of Pipedal presets at [PatchStorage.com](https:///patchstorage.com). We need your support! Please vote for PiPedal [here](https://patchstorage.com/requests/pipedal/). + +  + + +#### NEW version 1.3.66 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.   diff --git a/docs/Installing.md b/docs/Installing.md index 104b917..f036a80 100644 --- a/docs/Installing.md +++ b/docs/Installing.md @@ -13,17 +13,17 @@ page_icon: img/Install4.jpg Download the most recent Debian (.deb) package for your platform: -- [Raspberry Pi OS bookworm (64-bit) v1.3.62](https://github.com/rerdavies/pipedal/releases/download/) +- [Raspberry Pi OS bookworm (64-bit) v1.3.66](https://github.com/rerdavies/pipedal/releases/download/) - [Ubuntu/Raspberry Pi OS bullseyeye (64-bit) v1.2.31](https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb) -Version 1.3.62 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31. +Version 1.3.66 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31. Install the package by running ``` sudo apt update cd ~/Downloads - sudo apt-get install pipedal_1.3.62_arm64.deb + sudo apt-get install pipedal_1.3.66_arm64.deb ``` Adjust accordingly if you have downloaded v1.1.31. diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 5fcde28..d3e1703 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -1,5 +1,28 @@ # Release Notes +## PiPedal 1.3.66 Release + +Bug fixes: +- Choose 24-bit or 32-bit audio formats instead of 16-bit audio formats on devices that support both. +- Force selection of stereo channel configuration for legacy I2C audio drivers without support for channel maps. +- Avoid selection of hardware-downmixed Surround Sound channel configurations. + +Minor features: +- Improved diagnostic tools for ALSA devices via `pipedalconfig --alsa-devices` + +## PiPedal 1.3.64 Beta + +Bug fixes: +- Always choose the largest available ALSA sample format. +- With ALSA devices for which maximum and minimum channels are not equal, prefer stereo channel configuration. + + +## PiPedal 1.3.63 Experimental + +Bug fixes: +- With ALSA devices for which maximum and minimum channels are not equal, prefer stereo channel configuration. + + ## PiPedal 1.3.62 Beta Features: @@ -19,7 +42,7 @@ Known issues: Sharing file types poses a problem with previous versions of PiPedal which did not share uploads by file type, but instead placed uploads in a private directory for each plugin. In order to accomodate this, Pipedal provides legacy support for this situation. If a private upload directory for a plugin exists, the file property selection dialog will show both the new shared directories, and the old private directory. The private directory has a name that reflects the name of the plugin (e.g. "Ratatoille.lv2"). This directory only -shows up if you have used the plugin on previous versiouns of Pipedal. If you have nothing of particular value in the old private upload directory, you can delete the plugin's private directory (which can be found in `/var/pipedal/audio_uploads`), and the private directory will no longer be displayed in the PiPedal UI. +shows up if you have used the plugin on previous versions of Pipedal. If you have nothingof particular value in the old private upload directory, you can delete the plugin's private directory (which can be found in `/var/pipedal/audio_uploads`), and the private directory will no longer be displayed in the PiPedal UI. I anticipate providing a migration utility in the near future which will clean up and migrate legacy upload directories to the new directory structure (automatically uploading presets which reference uploaded files that have moved). In the meantime, I think you will find the current occomodation for legacy upload directories perfectly functional. diff --git a/docs/download.md b/docs/download.md index c8e3e97..719359e 100644 --- a/docs/download.md +++ b/docs/download.md @@ -4,7 +4,7 @@ Download the most recent Debian (.deb) package for your platform: -- Raspberry Pi OS Bookworm (64-bit) v1.3.62 +- Raspberry Pi OS Bookworm (64-bit) v1.3.66 Install the package by running @@ -12,7 +12,7 @@ Install the package by running ``` sudo apt update cd ~/Downloads - sudo apt-get install ./pipedal_1.3.62_arm64.deb + sudo apt-get install ./pipedal_1.3.66_arm64.deb ``` Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation. diff --git a/docs/index.md b/docs/index.md index 707999a..cd88660 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,15 +1,18 @@ -v1.3.62 +v1.3.66   To download PiPedal, click [here](download.md). To view PiPedal documentation, click [here](Documentation.md). +#### PiPedal has a submitted a request to support sharing of Pipedal presets at [PatchStorage.com](https:///patchstorage.com). We need your support! Please vote for PiPedal [here](https://patchstorage.com/requests/pipedal/). -#### NEW version 1.3.62 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. +  + +#### NEW version 1.3.66 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.   diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 11b2889..e92382a 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2022 Robin E. R. Davies + * Copyright (c) 2024 Robin E. R. 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 @@ -24,6 +24,7 @@ #include "pch.h" #include "util.hpp" +#include "Finally.hpp" #include #include #include "ss.hpp" @@ -53,6 +54,97 @@ using namespace pipedal; namespace pipedal { + static bool ShouldForceStereoChannels(snd_pcm_t *pcmHandle, snd_pcm_hw_params_t *hwParams, unsigned int channelsMin, unsigned int channelsMax) + { + // The problem: old IC2 drivers seem to return 1-8 channels, but 8 channels is non-functinal. The assumption is that legacy drivers + // (I2C drivers, particularl, but also the Rpi headphones device, as an interesting example) that don't support channel maps do this. + // Hypothetically, devices could allow slection of hardware-downmixed surround channels. So deal with this case defensively. + // The approach: check the channel map and do our best to interpret what we find. + // No channel map, or any part of the channel map is unknown? Probably the legalcy case we're interested in. Return TRUE + // If the channel map is a surround format, return true in that case as well. + // If the channel map is pairwise, return false! (legitimately multi-channel devices should not be forced to stereo). + // If the channel map is all FL/FR/MONO return false (a hypothetical configuration for a multi-channel device) + // If the channel map is not all FL/FR/MONO, assume that it's an upmixed/downmixed surround format, and return TRUE. + // This is high-risk code, because it attempts to anticipate hypothetical device configurations with no actual testing. + + if (channelsMax <= 2) + return false; + if (channelsMin == channelsMax) + return false; + if (channelsMin > 2) + return false; // can't imagine what sort of device this is. + + snd_pcm_hw_params_t *test_params; + + snd_pcm_hw_params_alloca(&test_params); + snd_pcm_hw_params_copy(test_params, hwParams); + + // can we select 2 channels? + if (snd_pcm_hw_params_set_channels(pcmHandle, test_params, (unsigned int)2) >= 0) + { + snd_pcm_chmap_query_t **chmaps = snd_pcm_query_chmaps(pcmHandle); + + if (chmaps == nullptr) + { + return true; // probably an old driver. Do it. + } + Finally ff([chmaps]() + { snd_pcm_free_chmaps(chmaps); }); + for (size_t i = 0; chmaps[i] != nullptr; ++i) + { + snd_pcm_chmap_query_t *chmap = chmaps[i]; + if (chmap->map.channels == channelsMax) + { + switch (chmap->type) + { + case SND_CHMAP_TYPE_NONE: + default: + return true; // weird legacy case? Do it. + case SND_CHMAP_TYPE_PAIRED: + return false; // A legitimate multi-channel device. definitely don't do it. + + case SND_CHMAP_TYPE_VAR: + case SND_CHMAP_TYPE_FIXED: + { + // we should do it for surround formats. guard against other hypothetical mappings for legitimately multi-channel devices. + + snd_pcm_chmap_position pos0 = (snd_pcm_chmap_position)(chmap->map.pos[0]); + if (pos0 == snd_pcm_chmap_position::SND_CHMAP_MONO) // hypothetical channel map of all mono channesl. + { + return false; // don't do it. + } + if (pos0 != snd_pcm_chmap_position::SND_CHMAP_FL && pos0 != snd_pcm_chmap_position::SND_CHMAP_FL) // surround formats always start with FL. Hypothetical quad formats could start with FC. + { + return false; // don't do it. + } + // accept a hypothetical channel map of mixed FL's and FR's, FC's and MONOs. (Multi-channel with mixed mono and stereo pairs). + // But otherwise assume it's a surround map, and use a stereo channel configuration instead. + for (size_t i = 0; i < chmap->map.channels; ++i) + { + snd_pcm_chmap_position pos = (snd_pcm_chmap_position)(chmap->map.pos[i]); + switch (pos) + { + case snd_pcm_chmap_position::SND_CHMAP_MONO: + case snd_pcm_chmap_position::SND_CHMAP_FL: + case snd_pcm_chmap_position::SND_CHMAP_FR: + case snd_pcm_chmap_position::SND_CHMAP_FC: + break; // keep going. + default: + return true; // probably a surround sound map. + } + } + return false; + }; + } + } + } + return true; // no matching channel map(!??). nonsensical case. may as well use the stereo config, which might be more sensible. + } + return false; + } + + + struct AudioFormat { char name[40]; @@ -66,20 +158,27 @@ namespace pipedal AudioFormat *formats, size_t nItems) { + snd_pcm_hw_params_t *test_params; + snd_pcm_hw_params_alloca(&test_params); + for (size_t i = 0; i < nItems; ++i) { - int err = snd_pcm_hw_params_set_format(handle, hwParams, formats[i].pcm_format); + snd_pcm_hw_params_copy(test_params, hwParams); + + int err = snd_pcm_hw_params_set_format(handle, test_params, formats[i].pcm_format); if (err == 0) { - return true; + int err = snd_pcm_hw_params_set_format(handle, hwParams, formats[i].pcm_format); + if (err == 0) + { + return true; + } } } return false; } static AudioFormat leFormats[]{ - {"16-bit little-endian", SND_PCM_FORMAT_S16_LE}, - {"32-bit float little-endian", SND_PCM_FORMAT_FLOAT_LE}, {"32-bit integer little-endian", SND_PCM_FORMAT_S32_LE}, {"24-bit little-endian", SND_PCM_FORMAT_S24_LE}, @@ -190,15 +289,16 @@ namespace pipedal AudioDriverHost *driverHost = nullptr; - - void validate_capture_handle() { // leftover debugging for a buffer overrun :-/ - #ifdef DEBUG + void validate_capture_handle() + { // leftover debugging for a buffer overrun :-/ +#ifdef DEBUG if (snd_pcm_type(captureHandle) != SND_PCM_TYPE_HW) { throw std::runtime_error("Capture handle has been overwritten"); } - #endif +#endif } + public: AlsaDriverImpl(AudioDriverHost *driverHost) : driverHost(driverHost) @@ -366,12 +466,30 @@ namespace pipedal { /*if not user-specified, try to find the maximum * number of channels */ - unsigned int channels_max; + unsigned int channels_max = 0; + unsigned int channels_min = 0; err = snd_pcm_hw_params_get_channels_max(hwParams, &channels_max); + if (err < 0) + { + AlsaError(SS("Can't get channels_max.")); + } + + err = snd_pcm_hw_params_get_channels_min(hwParams, + &channels_min); + if (err < 0) + { + AlsaError(SS("Can't get channels_min.")); + } + *channels = channels_max; - if (*channels > 1024) + if (ShouldForceStereoChannels(handle, hwParams, channels_min, channels_max)) + { + *channels = 2; + } + + if (*channels >= 1024) { // The default PCM device has unlimited channels. // report 2 channels @@ -1278,7 +1396,6 @@ namespace pipedal } } validate_capture_handle(); - } void recover_from_output_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err) { @@ -1364,7 +1481,6 @@ namespace pipedal } audioRunning = true; validate_capture_handle(); - } else { @@ -1393,7 +1509,6 @@ namespace pipedal // buffer boundaries (but discard them) snd_pcm_sframes_t framesRead; - auto state = snd_pcm_state(handle); auto frame_bytes = this->captureFrameSize; do @@ -1937,12 +2052,14 @@ namespace pipedal for (size_t i = 0; i < devices.size(); ++i) { - try { + try + { const auto &device = devices[i]; auto midiDevice = std::make_unique(); midiDevice->Open(device); midiDevices.push_back(std::move(midiDevice)); - } catch (const std::exception &e) + } + catch (const std::exception &e) { Lv2Log::error(e.what()); } @@ -2024,7 +2141,7 @@ namespace pipedal if (jackServerSettings.IsDummyAudioDevice()) { auto nChannels = GetDummyAudioChannels(jackServerSettings.GetAlsaInputDevice()); - + inputAudioPorts.clear(); outputAudioPorts.clear(); for (uint32_t i = 0; i < nChannels; ++i) @@ -2109,11 +2226,30 @@ namespace pipedal { throw PiPedalLogicException("No outut channels."); } + unsigned int channelsMin; + err = snd_pcm_hw_params_get_channels_min(playbackHwParams, &channelsMin); + if (err < 0) + { + throw PiPedalLogicException("No outut channels."); + } + if (ShouldForceStereoChannels(playbackHandle, playbackHwParams, channelsMin, playbackChannels)) + { + playbackChannels = 2; + } + err = snd_pcm_hw_params_get_channels_max(captureHwParams, &captureChannels); if (err < 0) { throw PiPedalLogicException("No input channels."); } + err = snd_pcm_hw_params_get_channels_min(captureHwParams, &channelsMin); + if (err >= 0) + { + if (ShouldForceStereoChannels(captureHandle, captureHwParams, channelsMin, captureChannels)) + { + captureChannels = 2; + } + } inputAudioPorts.clear(); for (unsigned int i = 0; i < captureChannels; ++i) diff --git a/src/AlsaDriverTest.cpp b/src/AlsaDriverTest.cpp index 827b6b6..8c3e4c2 100644 --- a/src/AlsaDriverTest.cpp +++ b/src/AlsaDriverTest.cpp @@ -223,11 +223,11 @@ public: for (size_t i = 0; i < inputs; ++i) { - inputBuffers[i] = audioDriver->GetInputBuffer(i,nFrames); + inputBuffers[i] = audioDriver->GetInputBuffer(i); } for (size_t i = 0; i < outputs; ++i) { - outputBuffers[i] = audioDriver->GetOutputBuffer(i,nFrames); + outputBuffers[i] = audioDriver->GetOutputBuffer(i); } if (this->testType == TestType::Oscillator) { diff --git a/src/AvahiServiceTest.cpp b/src/AvahiServiceTest.cpp index 6cec4da..be76579 100644 --- a/src/AvahiServiceTest.cpp +++ b/src/AvahiServiceTest.cpp @@ -30,18 +30,18 @@ using namespace pipedal; -TEST_CASE("Avahi Service Test", "[avahi_service][dev]") -{ +// TEST_CASE("Avahi Service Test", "[avahi_service][dev]") +// { - { - AvahiService service; +// { +// AvahiService service; - service.Announce(81, "Test Announcement", "0a6045b0-1753-4104-b3e4-b9713b9cc358","pipedal"); +// service.Announce(81, "Test Announcement", "0a6045b0-1753-4104-b3e4-b9713b9cc358","pipedal"); - sleep(10); +// sleep(10); - service.Unannounce(); - service.Announce(81, "Test Announcement 2", "0a6045b0-1753-4104-b3e4-b9713b9cc358","pipedal"); - sleep(10); - } -} \ No newline at end of file +// service.Unannounce(); +// service.Announce(81, "Test Announcement 2", "0a6045b0-1753-4104-b3e4-b9713b9cc358","pipedal"); +// sleep(10); +// } +// } \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6fdd18d..8f45399 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -638,6 +638,8 @@ add_executable(pipedalconfig CommandLineParser.hpp PiPedalException.hpp ConfigMain.cpp + alsaCheck.cpp + alsaCheck.hpp ModFileTypes.cpp ModFileTypes.hpp PiPedalConfiguration.hpp PiPedalConfiguration.cpp JackServerSettings.hpp JackServerSettings.cpp @@ -647,7 +649,7 @@ add_executable(pipedalconfig ) -target_link_libraries(pipedalconfig PRIVATE PiPedalCommon pthread atomic uuid stdc++fs +target_link_libraries(pipedalconfig PRIVATE PiPedalCommon pthread atomic uuid stdc++fs asound ) add_executable(pipedal_latency_test @@ -668,6 +670,12 @@ target_link_libraries(pipedal_latency_test PRIVATE pthread asound PiPedalCommon ) +add_executable(pipedal_alsa_info + alsaCheck.cpp alsaCheck.hpp + alsaCheckMain.cpp +) +target_link_libraries(pipedal_alsa_info PRIVATE asound) + add_executable(capturepresets CapturePresetsMain.cpp Storage.cpp diff --git a/src/ConfigMain.cpp b/src/ConfigMain.cpp index 4738203..84e8834 100644 --- a/src/ConfigMain.cpp +++ b/src/ConfigMain.cpp @@ -24,6 +24,7 @@ #include "CommandLineParser.hpp" #include "SystemConfigFile.hpp" #include "ModFileTypes.hpp" +#include "alsaCheck.hpp" #include #include @@ -92,18 +93,19 @@ namespace fs = std::filesystem; #define REMOVE_OLD_SERVICE 0 // Grandfathering: whether to remove the old shutdown service (now pipedaladmind) #define OLD_SHUTDOWN_SERVICE "pipedalshutdownd" - - -void changeUserShell(const char* username, const char* newShell) { - struct passwd* pw; +void changeUserShell(const char *username, const char *newShell) +{ + struct passwd *pw; struct passwd p; char buf[1024]; pw = getpwnam(username); - if (pw == nullptr) { + if (pw == nullptr) + { throw std::runtime_error("User not found"); } - if (strcmp(pw->pw_shell,newShell) == 0) { + if (strcmp(pw->pw_shell, newShell) == 0) + { return; } std::string args = SS("/usr/sbin/usermod -s " << newShell << " " << username); @@ -114,7 +116,6 @@ void changeUserShell(const char* username, const char* newShell) { } } - fs::path GetServiceFileName(const std::string &serviceName) { return fs::path(SERVICE_PATH) / (serviceName + ".service"); @@ -218,7 +219,7 @@ bool disableAvahiConfigLine(SystemConfigFile &avahi, const std::string §ion, ++line; } } - avahi.UndoableAddLine(line,lineStart+"no"); + avahi.UndoableAddLine(line, lineStart + "no"); changed = true; } } @@ -242,7 +243,7 @@ static void AvahiInstall() changed |= disableAvahiConfigLine(avahi, "[server]", "use-ipv6="); changed |= disableAvahiConfigLine(avahi, "[publish]", "publish-aaaa-on-ipv4="); - + if (changed) { avahi.Save(); @@ -1035,12 +1036,13 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress) // add to netdev group sysExec(USERMOD_BIN " -a -G " NETDEV_GROUP_NAME " " SERVICE_ACCOUNT_NAME); - try { + try + { changeUserShell(SERVICE_ACCOUNT_NAME, "/usr/sbin/nologon"); - } catch (const std::exception&e) + } + catch (const std::exception &e) { cout << "Error: Can't set user shell for pipedal_d. " << e.what() << std::endl; - } // create and configure /var directory. @@ -1162,7 +1164,6 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress) FixPermissions(); ModFileTypes::CreateDefaultDirectories("/var/pipedal/audio_uploads"); - StopService(false); AvahiInstall(); InstallPgpKey(); @@ -1281,6 +1282,10 @@ static void PrintHelp() << HangingIndent() << " --disable-hotspot\tDisabled the Wi-Fi hotspot." << "\n\n" + << HangingIndent() << " --alsa-devices\tList available ALSA hw devices." + << "\n\n" + << HangingIndent() << " --alsa-device [device_id]\tPrint info for the specified ALSA device." + << "\n\n" << HangingIndent() << " --list-wifi-channels [] \tList valid Wifi channels for the current/specified country." << "\n\n" @@ -1364,6 +1369,8 @@ int main(int argc, char **argv) bool fix_permissions = false; bool nosudo = false; bool excludeShutdownService = false; + bool alsaDevices = false; + std::string alsaDevice; std::string prefixOption; std::string portOption; std::string homeNetwork; @@ -1387,12 +1394,14 @@ int main(int argc, char **argv) parser.AddOption("--home-network", &homeNetwork); parser.AddOption("--no-ethernet", &noEthernet); parser.AddOption("--no-wifi", &noWifi); + parser.AddOption("--alsa-devices", &alsaDevices); + parser.AddOption("--alsa-device", &alsaDevice); try { parser.Parse(argc, (const char **)argv); int actionCount = - help + get_current_port + install + uninstall + stop + start + enable + disable + enable_hotspot + disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels + fix_permissions; + (!alsaDevice.empty()) + alsaDevices + help + get_current_port + install + uninstall + stop + start + enable + disable + enable_hotspot + disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels + fix_permissions; if (actionCount > 1) { throw std::runtime_error("Please provide only one action."); @@ -1454,6 +1463,32 @@ int main(int argc, char **argv) std::cout << "current port: " << GetCurrentWebServicePort() << std::endl; return EXIT_SUCCESS; } + if (alsaDevices) + { + try + { + list_alsa_devices(); + } + catch (const std::exception &e) + { + cerr << "Error: " << e.what() << endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } + if (alsaDevice.length() != 0) + { + try + { + check_alsa_channel_configs(alsaDevice.c_str()); + } + catch (const std::exception &e) + { + cerr << "Error: " << e.what() << endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } if (list_p2p_channels) { return ListP2PChannels(parser.Arguments()); diff --git a/src/PiPedalAlsaTest.cpp b/src/PiPedalAlsaTest.cpp index 17f2db9..e7cf5d9 100644 --- a/src/PiPedalAlsaTest.cpp +++ b/src/PiPedalAlsaTest.cpp @@ -27,9 +27,11 @@ #include "PiPedalAlsa.hpp" using namespace pipedal; +using namespace std; -TEST_CASE( "ALSA Test", "[pipedal_alsa_test][Build][Dev]" ) { - +static void DiscoveryTest() +{ + cout << "--- Discovery" << endl; PiPedalAlsaDevices devices; auto result = devices.GetAlsaDevices(); std::cout << result.size() << " ALSA devices found." << std::endl; @@ -42,5 +44,18 @@ TEST_CASE( "ALSA Test", "[pipedal_alsa_test][Build][Dev]" ) { } +void ChannelConfigtest() +{ + cout << "--- Channel Config Test" << endl; + +} + +TEST_CASE( "ALSA Test", "[pipedal_alsa_test][Build][Dev]" ) { + DiscoveryTest(); + + ChannelConfigTest(); + +} + diff --git a/src/ReleaseChecklist.md b/src/ReleaseChecklist.md new file mode 100644 index 0000000..adf5714 --- /dev/null +++ b/src/ReleaseChecklist.md @@ -0,0 +1,41 @@ +## Release Checklist + +Steps to publishing a release. Chances should be waiting on a dev branch. + +### prepare the commit. + +- Update versions. In vscode, do a global replace of the previous version with the next. Close ReleaseNotes.md to ignore. + There should be 13 replacements (excluding ReleaseNotes.md). +- Make sure the verision display name has the correct suffix (-Release, -Beta or -Experimental). +- Write release notes for the new version in ReleaseNotes.md +- On the dev machine do a full relWithDebugInfo build. (symbols will be stripped in the package) +- On the dev machine, run `./buildPackage` +- On the dev machine, run `./signPackage` +- Commit all changes on the dev branch. +- Wait for the Github build action for the dev branch to complete. There should be no build errors. + +### preparing DRAFT release on github. + +- On git hub, create a DRAFT release. +- Release Title format is "PiPedal v1.3.36 Release" (or "Beta" or "Experimental"). +- Copy release notes from ReleaseNotes.md into the draft release. +- Copy footnotes (link to full release notes, and PGP info) from the previous release. +- Set tag to "create a new tag" with appropriate version name. e.g. "v1.3.26" (no tag suffix) +- UPLAOD THE DEBIAN PACKAGE AND ITS .asc filtering +- Check "Create a discussion group". +- Set "Set as current release" ONLY for Release and Beta builds. +- Do NOT COMMIT the DRAFT yet. Wait until the dev branch has been merged in following steps. + +### Merging the changes. +- Check that the dev build action completed without errors. +- MERGE the dev branch onto the main branch. +- Without waiting for build actions to complete, commit the github release, wich will now pick up the corect source bundles. The release is now live at this point. +- Docs should update 30 seconds after the merge, when Github docs action completes. +- Verify that the main build and docs actions complete without errors. + +### Post release verification. +- Verify that README.md links to the correct verion. +- Verify that online docs link to the correct version. +- Proofread the Release and associated notes. + +Experimental releases should be built on the dev branch (and therefore do not show up in readme.md and docs). diff --git a/src/alsaCheck.cpp b/src/alsaCheck.cpp new file mode 100644 index 0000000..e2147e9 --- /dev/null +++ b/src/alsaCheck.cpp @@ -0,0 +1,306 @@ +/* + * MIT License + * + * Copyright (c) 2024 Robin E. R. 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 "alsaCheck.hpp" + +#include +#include +#include +#include +#include "ss.hpp" +#include +#include "Finally.hpp" + +using namespace pipedal; + +static std::string GetChannelDescription( + snd_pcm_t* handle, + snd_pcm_hw_params_t*hwParams, + unsigned int channels) +{ + snd_pcm_chmap_query_t **chmaps = snd_pcm_query_chmaps(handle); + + if (chmaps) { + Finally ff([chmaps] { + snd_pcm_free_chmaps(chmaps); + }); + std::stringstream ss; + bool firstMap = true; + for (size_t i = 0; chmaps[i] != nullptr; ++i) + { + auto chmap = chmaps[i]; + if (chmap->map.channels == channels) + { + if (!firstMap) { + ss << "/"; + } + ss << channels << " "; + firstMap = false; + switch (chmap->type) + { + case SND_CHMAP_TYPE_NONE: + default: + ss << "Unknown"; + break; + case SND_CHMAP_TYPE_VAR: + case SND_CHMAP_TYPE_FIXED: + for (size_t ch = 0; ch < chmap->map.channels; ++ch) + { + if (ch != 0) + { + ss << " "; + } + snd_pcm_chmap_position pos =(snd_pcm_chmap_position)(chmap->map.pos[ch]); + if (pos == snd_pcm_chmap_position::SND_CHMAP_MONO) + { + ss << "ch" << (i+1); + } + ss << snd_pcm_chmap_name((snd_pcm_chmap_position)(chmap->map.pos[ch])); + } + break; + case SND_CHMAP_TYPE_PAIRED: + for (size_t pair = 0; pair < chmap->map.channels/2; ++pair) + { + if (pair != 0) + { + ss << " "; + } + ss << "L" << (pair+1) << " R" << (pair+1); + } + break; + } + if (chmap->type == SND_CHMAP_TYPE_VAR) + { + ss << " (var)"; + } + } + } + return ss.str(); + } + std::string config_name; + switch (channels) { + case 1: config_name = "Mono"; break; + case 2: config_name = "Stereo"; break; + case 4: config_name = "Quadraphonic"; break; + case 6: config_name = "5.1 Surround"; break; + case 8: config_name = "7.1 Surround"; break; + default: config_name = std::to_string(channels) + " channels"; + } + return config_name; +} + + +void pipedal::check_alsa_channel_configs(const char* device_name) { + snd_pcm_t* handle; + int err; + + // Open PCM device for playback + if ((err = snd_pcm_open(&handle, device_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + throw std::runtime_error(SS("Cannot open audio device " << device_name << ": " << snd_strerror(err))); + return; + } + + Finally ff([handle] { + snd_pcm_close(handle); + }); + // Get hardware parameters + snd_pcm_hw_params_t* hw_params; + snd_pcm_hw_params_alloca(&hw_params); + + if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) { + const std::string errorMessage = snd_strerror(err); + throw std::runtime_error(SS("Cannot initialize hardware parameter structure: " << errorMessage)); + } + + // Get channel count range + unsigned int min_channels, max_channels; + if ((err = snd_pcm_hw_params_get_channels_min(hw_params, &min_channels)) < 0) { + const std::string errorMessage = snd_strerror(err); + throw std::runtime_error(SS("Cannot get minimum channels: " << errorMessage)); + } + + if ((err = snd_pcm_hw_params_get_channels_max(hw_params, &max_channels)) < 0) { + std::cerr << "Cannot get maximum channels: " << snd_strerror(err) << std::endl; + return; + } + + std::cout << "Device: " << device_name << std::endl; + std::cout << "Channel range: " << min_channels << " to " << max_channels << std::endl; + std::cout << "\nSupported channel configurations:" << std::endl; + + // Test specific channel counts + std::vector common_configs = {1, 2, 4, 6, 8}; + for (unsigned int channels : common_configs) { + if (channels >= min_channels && channels <= max_channels) { + snd_pcm_hw_params_t* test_params; + snd_pcm_hw_params_alloca(&test_params); + snd_pcm_hw_params_copy(test_params, hw_params); + + if (snd_pcm_hw_params_set_channels(handle, test_params, channels) == 0) { + std::string channel_description = GetChannelDescription(handle,test_params,channels); + + std::cout << " " << channels << " channels (" << channel_description << ")" << std::endl; + } + } + } + +} + + +static bool hasChannelMaps(snd_pcm_t* pcm_handle) { + snd_pcm_chmap_query_t **chmaps = snd_pcm_query_chmaps(pcm_handle); + if (chmaps) { + snd_pcm_free_chmaps(chmaps); + return true; + } + return false; +} + +static void print_device_info(snd_ctl_t* handle, int card, int device) { + snd_pcm_info_t* pcminfo; + snd_pcm_info_alloca(&pcminfo); + snd_pcm_info_set_device(pcminfo, device); + snd_pcm_info_set_subdevice(pcminfo, 0); + + // Check playback + snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); + bool has_playback = snd_ctl_pcm_info(handle, pcminfo) >= 0; + + // Check capture + snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE); + bool has_capture = snd_ctl_pcm_info(handle, pcminfo) >= 0; + + if (has_playback || has_capture) { + char name[32]; + snprintf(name, sizeof(name), "hw:%d,%d", card, device); + + // Get device name + snd_pcm_t* pcm; + std::string device_name = "Unknown"; + if (snd_pcm_open(&pcm, name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) >= 0) { + device_name = snd_pcm_name(pcm); + snd_pcm_close(pcm); + } + + // Print basic info + std::cout << "\n Subdevice: " << name << " - " << device_name << std::endl; + std::cout << " ID: " << snd_pcm_info_get_id(pcminfo) << std::endl; + std::cout << " Capabilities:"; + if (has_playback) std::cout << " PLAYBACK"; + if (has_capture) std::cout << " CAPTURE"; + std::cout << std::endl; + + // Get hardware parameters for more detailed info + if (has_playback) { + snd_pcm_t* pcm_handle; + if (snd_pcm_open(&pcm_handle, name, SND_PCM_STREAM_PLAYBACK, 0) >= 0) { + snd_pcm_hw_params_t* hw_params; + snd_pcm_hw_params_alloca(&hw_params); + + if (snd_pcm_hw_params_any(pcm_handle, hw_params) >= 0) { + unsigned int min_channels = 1024, max_channels = 1024; + unsigned int min_rate = 1, max_rate = 1; + + snd_pcm_hw_params_get_channels_min(hw_params, &min_channels); + snd_pcm_hw_params_get_channels_max(hw_params, &max_channels); + snd_pcm_hw_params_get_rate_min(hw_params, &min_rate, 0); + snd_pcm_hw_params_get_rate_max(hw_params, &max_rate, 0); + + std::cout << " Channels: " << min_channels << " - " << max_channels << std::endl; + std::cout << " Sample rates: " << min_rate << " - " << max_rate << " Hz" << std::endl; + + // Get supported formats + std::cout << " Supported formats:"; + for (snd_pcm_format_t fmt = SND_PCM_FORMAT_S8; fmt <= SND_PCM_FORMAT_LAST; fmt = snd_pcm_format_t(fmt + 1)) { + if (snd_pcm_hw_params_test_format(pcm_handle, hw_params, fmt) == 0) { + std::cout << " " << snd_pcm_format_name(fmt); + } + } + std::cout << std::endl; + std::cout << " Channel maps:" << std::endl; + snd_pcm_hw_params_t* test_params; + snd_pcm_hw_params_alloca(&test_params); + + if (!hasChannelMaps(pcm_handle)) + { + std::cout << " No channel maps." << std::endl; + } else { + for (size_t i = min_channels; i <= max_channels; ++i) + { + snd_pcm_hw_params_copy(test_params, hw_params); + + if (snd_pcm_hw_params_set_channels(pcm_handle, test_params, i) == 0) { + + std::cout << " " << GetChannelDescription(pcm_handle,hw_params,i) << std::endl; + } + } + } + } + snd_pcm_close(pcm_handle); + } else { + std::cout << " (device not available)" << std::endl; + } + } + } +} + +void pipedal::list_alsa_devices() { + int card = -1; + int err; + + std::cout << "=== ALSA Hardware Devices ===" << std::endl; + + // Iterate through cards + while ((err = snd_card_next(&card)) >= 0 && card >= 0) { + snd_ctl_t* handle; + char name[32]; + snprintf(name, sizeof(name), "hw:%d", card); + + if ((err = snd_ctl_open(&handle, name, 0)) < 0) { + std::cerr << "Cannot open control for card " << card << ": " << snd_strerror(err) << std::endl; + continue; + } + + // Get card info + snd_ctl_card_info_t* info; + snd_ctl_card_info_alloca(&info); + if ((err = snd_ctl_card_info(handle, info)) < 0) { + std::cerr << "Cannot get card info: " << snd_strerror(err) << std::endl; + snd_ctl_close(handle); + continue; + } + + std::cout << "\nSound Card hw:" << snd_ctl_card_info_get_id(info) << std::endl; + std::cout << " Name: " << snd_ctl_card_info_get_name(info) << std::endl; + std::cout << " Driver: " << snd_ctl_card_info_get_driver(info) << std::endl; + + // Iterate through devices on this card + int device = -1; + while ((err = snd_ctl_pcm_next_device(handle, &device)) >= 0 && device >= 0) { + print_device_info(handle, card, device); + } + + snd_ctl_close(handle); + } +} diff --git a/src/alsaCheck.hpp b/src/alsaCheck.hpp new file mode 100644 index 0000000..90efff5 --- /dev/null +++ b/src/alsaCheck.hpp @@ -0,0 +1,29 @@ +/* + * MIT License + * + * Copyright (c) 2024 Robin E. R. 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 + +namespace pipedal { + void check_alsa_channel_configs(const char* device_name = "default"); + void list_alsa_devices(); +} diff --git a/src/alsaCheckMain.cpp b/src/alsaCheckMain.cpp new file mode 100644 index 0000000..a682863 --- /dev/null +++ b/src/alsaCheckMain.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include "CommandLineParser.hpp" +#include "alsaCheck.hpp" + +using namespace std; +using namespace pipedal; + + +int main(int argc, char* argv[]) { + + bool listDevices = false; + + try { + CommandLineParser commandLineParser; + + std::string device = ""; + + commandLineParser.AddOption("--list",&listDevices); + + commandLineParser.Parse(argc,(const char**)argv); + + if (commandLineParser.Arguments().size() >= 1) { + device = commandLineParser.Arguments()[0]; + } + if (listDevices || device.empty()) { + list_alsa_devices(); + } else { + check_alsa_channel_configs(device.c_str()); + } + return EXIT_SUCCESS; + } catch (const std::exception &e) + { + cerr << "Error: " << e.what() << endl; + return EXIT_FAILURE; + } +} \ No newline at end of file