From 6c3e703adf31dc40a9607c0bd62051f9d1e3c2f4 Mon Sep 17 00:00:00 2001 From: Bliblank Date: Sat, 6 Jun 2026 16:40:30 -0500 Subject: [PATCH] rtaudio hello world --- CMakeLists.txt | 46 ++++++++++++++++++++++ README.md | 6 +-- src/main.cpp | 5 +++ src/synth/AudioEngine.cpp | 83 +++++++++++++++++++++++++++++++++++++++ src/synth/AudioEngine.hpp | 37 +++++++++++++++++ 5 files changed, 174 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d3f24c..034c287 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,33 @@ project(sonobulus LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# get dependencies +include(FetchContent) +set(RTAUDIO_BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(RTMIDI_BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(RTAUDIO_TARGETNAME_UNINSTALL rtaudio-uninstall CACHE STRING "" FORCE) +set(RTMIDI_TARGETNAME_UNINSTALL rtmidi-uninstall CACHE STRING "" FORCE) +FetchContent_Declare( + rtaudio + GIT_REPOSITORY https://github.com/thestk/rtaudio.git + GIT_TAG 6.0.1 +) +FetchContent_Declare( + rtmidi + GIT_REPOSITORY https://github.com/thestk/rtmidi.git + GIT_TAG 6.0.0 +) +# FetchContent_Declare( +# libconfig +# GIT_REPOSITORY https://github.com/hyperrealm/libconfig.git +# GIT_TAG v1.8.2 +# ) +FetchContent_MakeAvailable(rtaudio) +FetchContent_MakeAvailable(rtmidi) +# FetchContent_MakeAvailable(libconfig) + +# needs to be preinstalled find_package(Qt6 REQUIRED COMPONENTS Core Quick @@ -13,11 +40,20 @@ find_package(Qt6 REQUIRED COMPONENTS qt_standard_project_setup() add_library(sonobulus_core STATIC + src/synth/AudioEngine.cpp src/TimerComponent.cpp ) target_link_libraries(sonobulus_core PRIVATE Qt6::Core Qt6::Quick + rtaudio + rtmidi +) + +message(STATUS "Looking for compiler dependencies: ${rtaudio_SOURCE_DIR}...") + +target_include_directories(sonobulus_core PRIVATE + ${rtaudio_SOURCE_DIR} ) qt_add_executable(sonobulus @@ -30,6 +66,10 @@ qt_add_qml_module(sonobulus QML_FILES ui/Main.qml ) +target_include_directories(sonobulus PRIVATE + ${rtaudio_SOURCE_DIR} +) + target_link_libraries(sonobulus PRIVATE sonobulus_core Qt6::Core @@ -45,6 +85,12 @@ if (WIN32) COMMENT "windeployqt..." VERBATIM ) + add_custom_command( + TARGET sonobulus POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + ) endif() # add_subdirectory(src) diff --git a/README.md b/README.md index a227a1c..e0ab3bb 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ requirements (hundreds or even thousands of calculations for a single sample at to produce somewhat well-sounding instruments and music performance. ## Development plan: -- [ ] Build & project setup, get working hello-world program. -- [ ] QML hello-world program: basic increment/reset counter -- [ ] RtAudio hello-world: basic sine output +- [x] Build & project setup, get working hello-world program. +- [x] QML hello-world program: basic increment/reset counter +- [x] RtAudio hello-world: basic sine output - [ ] Connect UI control to sound output, add a slider for frequency control - [ ] Add note control a keyboard. Coordinate on-off events to start and stop tone generation diff --git a/src/main.cpp b/src/main.cpp index ca0e934..c65c613 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include #include "TimerComponent.hpp" +#include "synth/AudioEngine.hpp" int main(int argc, char* argv[]) { @@ -27,6 +28,10 @@ int main(int argc, char* argv[]) { return -1; } + // audio synthesizer doohickey + AudioEngine audioEngine = AudioEngine(); + audioEngine.start(); + // execute app return app.exec(); } diff --git a/src/synth/AudioEngine.cpp b/src/synth/AudioEngine.cpp index b017b09..093a109 100644 --- a/src/synth/AudioEngine.cpp +++ b/src/synth/AudioEngine.cpp @@ -1,2 +1,85 @@ #include "AudioEngine.hpp" + +#include +#include +#include + +AudioEngine::AudioEngine() { + + if(audioDevice_.getDeviceCount() < 1) { + std::cout << "No audio devices found" << std::endl; + } + +} + +AudioEngine::~AudioEngine() { + (void)stop(); +} + +bool AudioEngine::start() { + + // initialize the audio engine + RtAudio::StreamParameters params; + params.deviceId = audioDevice_.getDefaultOutputDevice(); + params.nChannels = channels_; // we're doing two duplicate channels for pseudo-mono + params.firstChannel = 0; + + RtAudio::StreamOptions options; + options.flags = RTAUDIO_MINIMIZE_LATENCY; + + RtAudioErrorType status = audioDevice_.openStream(¶ms, nullptr, RTAUDIO_FLOAT32, sampleRate_, &bufferFrames_, &AudioEngine::audioCallback, this, &options); + if(status != RTAUDIO_NO_ERROR) { + std::cout << "Error opening RtAudio stream" << std::endl; + return false; + } + + status = audioDevice_.startStream(); + if(status != RTAUDIO_NO_ERROR) { + std::cout << "Error starting RtAudio stream" << std::endl; + return false; + } + + // sanity check + std::cout << "sample rate: " << sampleRate_ << " buffer frames: " << bufferFrames_ << std::endl; + return true; + +} + +bool AudioEngine::stop() { + + if(audioDevice_.isStreamRunning()) audioDevice_.stopStream(); + if(audioDevice_.isStreamOpen()) audioDevice_.closeStream(); + + return true; + +} + +int32_t AudioEngine::audioCallback(void* outputBuffer, void* inputBuffer, uint32_t nFrames, double, RtAudioStreamStatus status, void* userData) { + + // error if the callback is late + if(status) std::cout << "stream underflow" << std::endl; + + return static_cast(userData)->process(static_cast(outputBuffer), static_cast(nFrames)); + +} + +int32_t AudioEngine::process(float* out, size_t nFrames) { + + for(size_t i = 0; i < nFrames; i++) { + + // simulate a sine wave + phase_ += 0.04f; + const float twoPi = 2.0f*std::numbers::pi_v; + if(phase_ > twoPi) { + phase_ -= twoPi; + } + float outSample = std::sin(phase_) / 4.0f; + + out[2*i] = outSample; + out[2*i+1] = outSample; + } + + return 0; + +} diff --git a/src/synth/AudioEngine.hpp b/src/synth/AudioEngine.hpp index eea431f..83b3bce 100644 --- a/src/synth/AudioEngine.hpp +++ b/src/synth/AudioEngine.hpp @@ -1,2 +1,39 @@ // The audio engine handles the RtAudio implementation of outputing samples to audio. the audio callback fills a buffer of audio samples and submits it for playback + +#pragma once + +#include + +#if defined(_WIN32) + #define AUDIO_API RtAudio::WINDOWS_WASAPI +#else + #define AUDIO_API RtAudio::LINUX_ALSA +#endif + +class AudioEngine { + +public: + + AudioEngine(); + ~AudioEngine(); + + bool start(); + bool stop(); + +private: + + static int32_t audioCallback(void* outputBuffer, void* inputBuffer, uint32_t nFrames, double streamtime, RtAudioStreamStatus status, void* userData); + + int32_t process(float* out, size_t nFrames); + + RtAudio audioDevice_ { AUDIO_API }; + + // TODO: make these configurable + uint32_t sampleRate_ = 44100; + uint32_t bufferFrames_ = 512; + uint32_t channels_ = 2; + + float phase_ = 0.0f; + +};