rtaudio hello world
This commit is contained in:
@@ -5,6 +5,33 @@ project(sonobulus LANGUAGES CXX)
|
|||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
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
|
find_package(Qt6 REQUIRED COMPONENTS
|
||||||
Core
|
Core
|
||||||
Quick
|
Quick
|
||||||
@@ -13,11 +40,20 @@ find_package(Qt6 REQUIRED COMPONENTS
|
|||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
|
|
||||||
add_library(sonobulus_core STATIC
|
add_library(sonobulus_core STATIC
|
||||||
|
src/synth/AudioEngine.cpp
|
||||||
src/TimerComponent.cpp
|
src/TimerComponent.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(sonobulus_core PRIVATE
|
target_link_libraries(sonobulus_core PRIVATE
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
Qt6::Quick
|
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
|
qt_add_executable(sonobulus
|
||||||
@@ -30,6 +66,10 @@ qt_add_qml_module(sonobulus
|
|||||||
QML_FILES ui/Main.qml
|
QML_FILES ui/Main.qml
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_include_directories(sonobulus PRIVATE
|
||||||
|
${rtaudio_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(sonobulus PRIVATE
|
target_link_libraries(sonobulus PRIVATE
|
||||||
sonobulus_core
|
sonobulus_core
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
@@ -45,6 +85,12 @@ if (WIN32)
|
|||||||
COMMENT "windeployqt..."
|
COMMENT "windeployqt..."
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET sonobulus POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
$<TARGET_FILE:rtaudio>
|
||||||
|
$<TARGET_FILE_DIR:sonobulus>
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# add_subdirectory(src)
|
# add_subdirectory(src)
|
||||||
|
|||||||
@@ -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.
|
to produce somewhat well-sounding instruments and music performance.
|
||||||
|
|
||||||
## Development plan:
|
## Development plan:
|
||||||
- [ ] Build & project setup, get working hello-world program.
|
- [x] Build & project setup, get working hello-world program.
|
||||||
- [ ] QML hello-world program: basic increment/reset counter
|
- [x] QML hello-world program: basic increment/reset counter
|
||||||
- [ ] RtAudio hello-world: basic sine output
|
- [x] RtAudio hello-world: basic sine output
|
||||||
- [ ] Connect UI control to sound output, add a slider for frequency control
|
- [ ] Connect UI control to sound output, add a slider for frequency control
|
||||||
- [ ] Add note control a keyboard. Coordinate on-off events to
|
- [ ] Add note control a keyboard. Coordinate on-off events to
|
||||||
start and stop tone generation
|
start and stop tone generation
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
|
|
||||||
#include "TimerComponent.hpp"
|
#include "TimerComponent.hpp"
|
||||||
|
#include "synth/AudioEngine.hpp"
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
@@ -27,6 +28,10 @@ int main(int argc, char* argv[]) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// audio synthesizer doohickey
|
||||||
|
AudioEngine audioEngine = AudioEngine();
|
||||||
|
audioEngine.start();
|
||||||
|
|
||||||
// execute app
|
// execute app
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,85 @@
|
|||||||
|
|
||||||
#include "AudioEngine.hpp"
|
#include "AudioEngine.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <numbers>
|
||||||
|
|
||||||
|
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<AudioEngine*>(userData)->process(static_cast<float*>(outputBuffer), static_cast<size_t>(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<float>;
|
||||||
|
if(phase_ > twoPi) {
|
||||||
|
phase_ -= twoPi;
|
||||||
|
}
|
||||||
|
float outSample = std::sin(phase_) / 4.0f;
|
||||||
|
|
||||||
|
out[2*i] = outSample;
|
||||||
|
out[2*i+1] = outSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
// 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 <RtAudio.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user