Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 49 additions & 93 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,95 +1,51 @@
name: ci-pr
on: [pull_request, workflow_dispatch]
name: Build
on: [push, pull_request]
defaults:
run:
shell: pwsh
jobs:
x64-linux-gcc:
runs-on: ubuntu-latest
build:
name: ${{ matrix.platform.name }} ${{ matrix.config.name }}
runs-on: ${{ matrix.platform.os }}
env:
CMAKE_BUILD_PARALLEL_LEVEL: 4
strategy:
fail-fast: false
matrix:
platform:
- { name: "Windows VS2022", os: windows-2022, flags: "" }
- { name: "Linux GCC", os: ubuntu-latest, flags: "" }
- { name: "Linux Clang", os: ubuntu-latest, flags: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++" }
- { name: "macOS AppleClang", os: macos-latest, flags: "" }
config:
- { name: "Static", flags: "-DBUILD_SHARED_LIBS=FALSE" }
steps:
- uses: actions/checkout@v4
- name: init
run: uname -m; sudo apt update -yqq && sudo apt install -yqq ninja-build mesa-common-dev libwayland-dev libxkbcommon-dev wayland-protocols extra-cmake-modules
- name: configure
run: cmake -S . --preset=ninja-gcc -B build -DGLFW_BUILD_X11=OFF -DJUKE_USE_LIBXMP=OFF -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14
- name: build debug
run: cmake --build build --config=Debug -- -v
- name: build release
run: cmake --build build --config=Release -- -v
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
run: cd build && ctest -V -C Release
x64-linux-clang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: init
run: uname -m; sudo apt update -yqq && sudo apt install -yqq ninja-build clang-19 mesa-common-dev libwayland-dev libxkbcommon-dev wayland-protocols extra-cmake-modules
- name: configure
run: cmake -S . --preset=ninja-clang -B build -DGLFW_BUILD_X11=OFF -DJUKE_USE_LIBXMP=OFF -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19
- name: build debug
run: cmake --build build --config=Debug -- -v
- name: build release
run: cmake --build build --config=Release -- -v
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
run: cd build && ctest -V -C Release
arm64-linux-gcc:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: init
run: uname -m; sudo apt update -yqq && sudo apt install -yqq ninja-build mesa-common-dev libwayland-dev libxkbcommon-dev wayland-protocols extra-cmake-modules
- name: configure
run: cmake -S . --preset=ninja-gcc -B build -DGLFW_BUILD_X11=OFF -DJUKE_USE_LIBXMP=OFF -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14
- name: build debug
run: cmake --build build --config=Debug -- -v
- name: build release
run: cmake --build build --config=Release -- -v
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
run: cd build && ctest -V -C Release
arm64-linux-clang:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: init
run: uname -m; sudo apt update -yqq && sudo apt install -yqq ninja-build clang-19 mesa-common-dev libwayland-dev libxkbcommon-dev wayland-protocols extra-cmake-modules
- name: configure
run: cmake -S . --preset=ninja-clang -B build -DGLFW_BUILD_X11=OFF -DJUKE_USE_LIBXMP=OFF -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19
- name: build debug
run: cmake --build build --config=Debug -- -v
- name: build release
run: cmake --build build --config=Release -- -v
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
run: cd build && ctest -V -C Release
x64-windows-vs22:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: configure
run: cmake -S . --preset=vs22 -B build -DJUKE_USE_LIBXMP=OFF
- name: build debug
run: cmake --build build --config=Debug --parallel
- name: build release
run: cmake --build build --config=Release --parallel
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
run: cd build && ctest -V -C Release
x64-windows-clang:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: configure
run: cmake -S . --preset=ninja-clang -B build -DJUKE_USE_LIBXMP=OFF
- name: build debug
run: cmake --build build --config=Debug -- -v
- name: build release
run: cmake --build build --config=Release -- -v
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
run: cd build && ctest -V -C Release
- name: Checkout
uses: actions/checkout@v4
- name: Install Dependencies
run: |
if ($env:RUNNER_OS -eq 'Windows') {
choco install ninja -y
} elseif ($env:RUNNER_OS -eq 'Linux') {
sudo apt-get update
sudo apt-get install -y ninja-build libxrandr-dev libxcursor-dev libxi-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libfreetype-dev
} elseif ($env:RUNNER_OS -eq 'macOS') {
brew install ninja
} else {
Write-Error "Unsupported OS: $env:RUNNER_OS"
exit 1
}
- name: Configure
run: |
if ($env:RUNNER_OS -eq 'Windows') {
. "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64 -HostArch amd64
Set-Location $env:GITHUB_WORKSPACE
}
cmake -B build -G "Ninja Multi-Config" ${{ matrix.platform.flags }} ${{ matrix.config.flags }}
- name: Build
run: |
if ($env:RUNNER_OS -eq 'Windows') {
. "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64 -HostArch amd64
Set-Location $env:GITHUB_WORKSPACE
}
cmake --build build --config Release
7 changes: 7 additions & 0 deletions app/src/MediaPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ void MediaPlayer::handle_input() {
static auto gain{1.f};
static auto pan{0.f};
static auto pitch{1.f};
static auto lo_cutoff{sample_rate_v};
static auto hi_cutoff{0.f};
static float pos[] = {0.f, 0.f, 0.f};

/* Metadata & Info */
Expand Down Expand Up @@ -67,6 +69,11 @@ void MediaPlayer::handle_input() {
m_jukebox.set_position(capo::Vec3f{pos[0], pos[1], pos[2]});
if (ImGui::Button("reset")) { pos[0] = pos[1] = pos[2] = 0.f; }
}

ImGui::SliderFloat("Low Pass", &lo_cutoff, 0.f, sample_rate_v, "%.1f", ImGuiSliderFlags_Logarithmic);
m_jukebox.set_cutoff(FilterType::low, lo_cutoff, sample_rate_v);
ImGui::SliderFloat("High Pass:", &hi_cutoff, 0.f, sample_rate_v, "%.1f", ImGuiSliderFlags_Logarithmic);
m_jukebox.set_cutoff(FilterType::high, hi_cutoff, sample_rate_v);
ImGui::TreePop();
}
}
Expand Down
2 changes: 2 additions & 0 deletions library/include/juke/core/AudioFile.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <capo/buffer.hpp>
#include <juke/core/Common.hpp>
#include <juke/core/MediaFile.hpp>

namespace juke {
Expand All @@ -10,6 +11,7 @@ class AudioFile : public IMediaFile {
explicit AudioFile(std::filesystem::path const& path);

bool bind_to(capo::ISource& source) final { return source.bind_to(m_buffer); }
void set_cutoff(FilterType type, float to_cutoff, float sample_rate) override {}

private:
std::shared_ptr<capo::Buffer> m_buffer{std::make_shared<capo::Buffer>()};
Expand Down
10 changes: 10 additions & 0 deletions library/include/juke/core/Common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

#pragma once

namespace juke {

enum class FilterType : std::uint8_t { high, low };

constexpr auto sample_rate_v = 44100.0f;

} // namespace juke
2 changes: 2 additions & 0 deletions library/include/juke/core/MediaFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#pragma once

#include <capo/source.hpp>
#include <juke/core/Common.hpp>
#include <filesystem>

namespace juke {
Expand All @@ -11,6 +12,7 @@ class IMediaFile : public capo::Polymorphic {
explicit IMediaFile(std::filesystem::path const& path) : m_filename(path.filename().string()) {}

virtual bool bind_to(capo::ISource& source) = 0;
virtual void set_cutoff(FilterType type, float to_cutoff, float sample_rate) = 0;

[[nodiscard]] std::string const& get_filename() const { return m_filename; }

Expand Down
2 changes: 2 additions & 0 deletions library/include/juke/core/XMFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class XMFile : public IMediaFile {

bool bind_to(capo::ISource& source) final { return source.bind_to(m_stream); }

void set_cutoff(FilterType type, float to_cutoff, float sample_rate) override { m_stream->set_cutoff(type, to_cutoff, sample_rate); }

private:
std::shared_ptr<XMStream> m_stream{};
};
Expand Down
8 changes: 8 additions & 0 deletions library/include/juke/core/XMStream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include <capo/buffer.hpp>
#include <capo/stream_pipe.hpp>
#include <juke/core/Common.hpp>
#include <juke/effect/HighPassFilter.hpp>
#include <juke/effect/LowPassFilter.hpp>
#include <filesystem>
#include <memory>

Expand All @@ -13,6 +16,8 @@ class XMStream : public capo::IStreamPipe {

explicit XMStream(std::filesystem::path const& path);

void set_cutoff(FilterType type, float to_cutoff, float sample_rate);

private:
struct Impl;
struct Deleter {
Expand All @@ -25,6 +30,9 @@ class XMStream : public capo::IStreamPipe {
auto set_looping(bool looping) -> bool final;

std::unique_ptr<Impl, Deleter> m_impl{};

LowPassFilter m_lowpass;
HighPassFilter m_highpass;
};

} // namespace juke
37 changes: 37 additions & 0 deletions library/include/juke/effect/HighPassFilter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

#pragma once

#include <numbers>

namespace juke {

class HighPassFilter {
public:
HighPassFilter(float cutoff_hz, float sample_rate) { set_cutoff(cutoff_hz, sample_rate); }

void set_cutoff(float cutoff_hz, float sample_rate) {
if (cutoff_hz <= 0.0f) {
m_alpha = 0.0f;
return;
}

auto const dt = 1.0f / sample_rate;
auto const rc = 1.0f / (2.0f * std::numbers::pi * cutoff_hz);
m_alpha = rc / (rc + dt);
}

float process(float input) {
if (m_alpha == 0.0f) return input;
auto output = m_alpha * (m_prev_out + input - m_prev_in);
m_prev_in = input;
m_prev_out = output;
return output;
}

private:
float m_prev_in{};
float m_prev_out{};
float m_alpha{};
};

} // namespace juke
28 changes: 28 additions & 0 deletions library/include/juke/effect/LowPassFilter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

#pragma once

#include <numbers>

namespace juke {

class LowPassFilter {
public:
LowPassFilter(float cutoff_hz, float sample_rate) { set_cutoff(cutoff_hz, sample_rate); }

void set_cutoff(float cutoff_hz, float sample_rate) {
auto const rc = 1.0f / (2.0f * std::numbers::pi * cutoff_hz);
auto const dt = 1.0f / sample_rate;
m_alpha = dt / (rc + dt);
}

float process(float in) {
m_prev_out = m_alpha * in + (1.0f - m_alpha) * m_prev_out;
return m_prev_out;
}

private:
float m_prev_out{};
float m_alpha{};
};

} // namespace juke
5 changes: 4 additions & 1 deletion library/include/juke/juke.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class Jukebox {
/// \brief Stop audio and set cursor to beginning. This does not work for XM format.
void stop();

// utility functions
void set_cutoff(FilterType type, float cutoff_hz, float sample_rate);

/* capo API wrapper functions */

// setters
Expand All @@ -39,7 +42,7 @@ class Jukebox {
void set_position(capo::Vec3f pos) { m_source->set_position(pos); }
void set_spatialized(bool spatialized) { m_source->set_spatialized(spatialized); }

//getters
// getters
[[nodiscard]] auto get_gain() const -> float { return m_source->get_gain(); }
[[nodiscard]] auto get_pitch() const -> float { return m_source->get_pitch(); }
[[nodiscard]] auto get_pan() const -> float { return m_source->get_pan(); }
Expand Down
18 changes: 16 additions & 2 deletions library/src/core/XMStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ void XMStream::Deleter::operator()(Impl* ptr) const noexcept {
std::default_delete<Impl>{}(ptr);
}

XMStream::XMStream(std::filesystem::path const& path) : m_impl(new Impl) {
XMStream::XMStream(std::filesystem::path const& path) : m_impl(new Impl), m_lowpass{sample_rate_v, sample_rate_v}, m_highpass{0.f, sample_rate_v} {
if (!m_impl->load_module(path)) { throw MediaError{"Failed to open XM file: " + path.generic_string()}; }
xmp_start_player(m_impl->context, static_cast<int>(capo::Buffer::sample_rate_v), 0);
}
Expand All @@ -43,15 +43,29 @@ void XMStream::push_samples(std::vector<float>& out) {
// wrap buffer into strongly-typed span for easy iteration and normalization.
auto const src = std::span{static_cast<std::int16_t const*>(frame_info.buffer), static_cast<std::size_t>(frame_info.buffer_size) / sizeof(std::int16_t)};
// need to normalize each u16 sample [-32k,32k] to f32 [-1,1].

static constexpr auto sample_max_v = static_cast<float>(std::numeric_limits<std::int16_t>::max());
for (std::int16_t const sample : src) { out.push_back(static_cast<float>(sample) / sample_max_v); }
for (std::int16_t const sample : src) {
auto normalized = static_cast<float>(sample) / sample_max_v;
auto high_passed = m_highpass.process(normalized);
auto band_passed = m_lowpass.process(high_passed);
out.push_back(band_passed);
}
}

auto XMStream::set_looping(bool const looping) -> bool {
m_impl->looping = looping;
return true;
}

void XMStream::set_cutoff(FilterType type, float to_cutoff, float sample_rate) {
switch (type) {
case FilterType::high: m_highpass.set_cutoff(to_cutoff, sample_rate); break;
case FilterType::low: m_lowpass.set_cutoff(to_cutoff, sample_rate); break;
default: break;
}
}

#else

void XMStream::Deleter::operator()(Impl* /*ptr*/) const noexcept {}
Expand Down
2 changes: 2 additions & 0 deletions library/src/juke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ void Jukebox::stop() {
m_source->set_cursor({}); // seek to start. this will do nothing for XM streams
}

void Jukebox::set_cutoff(FilterType type, float cutoff_hz, float sample_rate) { m_media_file->set_cutoff(type, cutoff_hz, sample_rate); }

} // namespace juke
Loading