From dbf395c00b85e554f2c32d672a5d6975dc2e74e5 Mon Sep 17 00:00:00 2001 From: Daniel Leonov Date: Sat, 22 Nov 2025 00:47:51 +0300 Subject: [PATCH 1/3] Added support for HART audio buffers --- dave/server/languages/c_cpp/__init__.py | 1 + dave/server/languages/c_cpp/hart.py | 57 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 dave/server/languages/c_cpp/hart.py diff --git a/dave/server/languages/c_cpp/__init__.py b/dave/server/languages/c_cpp/__init__.py index 2757acc..457060a 100644 --- a/dave/server/languages/c_cpp/__init__.py +++ b/dave/server/languages/c_cpp/__init__.py @@ -2,3 +2,4 @@ from .std_2D import * from .juce import * from .choc import * +from .hart import * diff --git a/dave/server/languages/c_cpp/hart.py b/dave/server/languages/c_cpp/hart.py new file mode 100644 index 0000000..410d8d9 --- /dev/null +++ b/dave/server/languages/c_cpp/hart.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +import re +from typing import Tuple + +from ...container import SampleType, Container2D +from ...debuggers.value import AbstractValue, DebuggerMemoryError + +class HartAudioBuffer(Container2D): + __REGEX = rf"^(?:const\s+)?hart::AudioBuffer<{SampleType.regex()}>\s*$" + + def __init__(self, dbg_value: AbstractValue, name: str, _=[]): + typename = dbg_value.typename() + sample_type, *_ = self._parse_typename(typename) + self._value = dbg_value + super().__init__(dbg_value, name, sample_type) + + @classmethod + def typename_matcher(cls) -> re.Pattern: + return re.compile(cls.__REGEX) + + @classmethod + def _parse_typename(cls, typename: str, **_) -> Tuple[SampleType, None, None]: + re_match = cls.typename_matcher().match(typename) + if re_match is None: + raise TypeError(f"Could not parse {typename} as a valid hart::AudioBuffer type") + return (SampleType.parse(re_match.group(1)), None, None) + + @property + def num_channels(self) -> int: + return int(self._value.attr("m_numChannels")) + + @property + def block_size(self) -> int: + return int(self._value.attr("m_numFrames")) + + def shape(self) -> Tuple[int, int]: + return (self.num_channels, self.block_size) + + def __channel_data_ptr(self, channel: int) -> int: + wrapped_vec = self._value.attr("m_channelPointers") + raw_vec = wrapped_vec._GdbValue__value + start = raw_vec["_M_impl"]["_M_start"] + return int(start[channel]) + + def read_from_debugger(self) -> bytearray: + if self.num_channels <= 0: + raise DebuggerMemoryError("m_numChannels is <= 0") + return b"".join( + self._value.readmemory( + self.__channel_data_ptr(channel), + self.sample_type.byte_size() * self.block_size, + ) + for channel in range(self.num_channels) + ) + +HartAudioBuffer.register() From 938d83940630a3d31f0f7ab9212c0769e94179a5 Mon Sep 17 00:00:00 2001 From: Daniel Leonov Date: Sun, 23 Nov 2025 19:21:16 +0300 Subject: [PATCH 2/3] Improved implementation for HART audio buffers More robust, due to use of an existing template for a vector --- dave/server/languages/c_cpp/hart.py | 48 ++++++++++++----------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/dave/server/languages/c_cpp/hart.py b/dave/server/languages/c_cpp/hart.py index 410d8d9..ab6e49c 100644 --- a/dave/server/languages/c_cpp/hart.py +++ b/dave/server/languages/c_cpp/hart.py @@ -4,17 +4,21 @@ from typing import Tuple from ...container import SampleType, Container2D -from ...debuggers.value import AbstractValue, DebuggerMemoryError +from ...debuggers.value import AbstractValue +from dave.server.languages import c_cpp class HartAudioBuffer(Container2D): __REGEX = rf"^(?:const\s+)?hart::AudioBuffer<{SampleType.regex()}>\s*$" - def __init__(self, dbg_value: AbstractValue, name: str, _=[]): + def __init__(self, dbg_value: AbstractValue, name: str, _): typename = dbg_value.typename() sample_type, *_ = self._parse_typename(typename) - self._value = dbg_value super().__init__(dbg_value, name, sample_type) + @property + def __inner(self) -> c_cpp.StdVector1D: + return c_cpp.StdVector1D(self._value.attr("m_frames"), self.name + ".m_frames") + @classmethod def typename_matcher(cls) -> re.Pattern: return re.compile(cls.__REGEX) @@ -23,35 +27,23 @@ def typename_matcher(cls) -> re.Pattern: def _parse_typename(cls, typename: str, **_) -> Tuple[SampleType, None, None]: re_match = cls.typename_matcher().match(typename) if re_match is None: - raise TypeError(f"Could not parse {typename} as a valid hart::AudioBuffer type") - return (SampleType.parse(re_match.group(1)), None, None) - - @property - def num_channels(self) -> int: - return int(self._value.attr("m_numChannels")) + raise TypeError( + f"HartAudioBuffer could not parse {typename} as a valid type" + ) - @property - def block_size(self) -> int: - return int(self._value.attr("m_numFrames")) + return (SampleType.parse(re_match.group(1)), None, None) def shape(self) -> Tuple[int, int]: - return (self.num_channels, self.block_size) - - def __channel_data_ptr(self, channel: int) -> int: - wrapped_vec = self._value.attr("m_channelPointers") - raw_vec = wrapped_vec._GdbValue__value - start = raw_vec["_M_impl"]["_M_start"] - return int(start[channel]) + assert isinstance(self._value, AbstractValue) + try: + return ( + int(self._value.attr("m_numChannels")), + int(self._value.attr("m_numFrames")) + ) + except: + raise RuntimeError(f"Failed to retrieve shape of {self._value.typename()}") def read_from_debugger(self) -> bytearray: - if self.num_channels <= 0: - raise DebuggerMemoryError("m_numChannels is <= 0") - return b"".join( - self._value.readmemory( - self.__channel_data_ptr(channel), - self.sample_type.byte_size() * self.block_size, - ) - for channel in range(self.num_channels) - ) + return self.__inner.read_from_debugger() HartAudioBuffer.register() From 8cafdefba5e8da29cadeb7ee290ac2f9d3ae517d Mon Sep 17 00:00:00 2001 From: Daniel Leonov Date: Sun, 23 Nov 2025 22:00:29 +0300 Subject: [PATCH 3/3] Added example for HART AudioBuffer --- examples/CMakeLists.txt | 21 +++++++++++++++++ examples/hart.cpp | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 examples/hart.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9be8d1b..ec6e1f9 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -26,6 +26,16 @@ FetchContent_Declare( FetchContent_MakeAvailable(GSL) +set(HART_BUILD_TESTS OFF) +FetchContent_Declare( + HART + GIT_REPOSITORY "https://github.com/daleonov/HART.git" + GIT_TAG 0.1.0 + GIT_SHALLOW ON +) + +FetchContent_MakeAvailable(HART) + ################################################################################ ### stdlib ################################################################################ @@ -89,6 +99,17 @@ add_executable( ) set_target_properties(choc PROPERTIES CXX_STANDARD 17) +################################################################################ +### HART +################################################################################ + +add_executable( + hart + ${CMAKE_CURRENT_SOURCE_DIR}/hart.cpp +) +set_target_properties(hart PROPERTIES CXX_STANDARD 11) +target_link_libraries(hart PRIVATE HART::HART) + ################################################################################ ### Custom containers example ################################################################################ diff --git a/examples/hart.cpp b/examples/hart.cpp new file mode 100644 index 0000000..74f7d5f --- /dev/null +++ b/examples/hart.cpp @@ -0,0 +1,52 @@ +#define HART_IMPLEMENTATION +#include "hart.hpp" + +HART_DECLARE_ALIASES_FOR_FLOAT; +using AudioBuffer = hart::AudioBuffer; +using hart::roundToSizeT; + +int main() +{ + constexpr double sampleRateHz = 44100.0; + + // Sine sweep + const size_t sineSweepDurationFrames = roundToSizeT (sampleRateHz * 1.0_s); + AudioBuffer bufferA (1, sineSweepDurationFrames); + auto sineSweepSignalA = SineSweep(); + sineSweepSignalA.prepare ( + sampleRateHz, + 1, // numOutputChannels + sineSweepDurationFrames // maxBlockSizeFrames + ); + sineSweepSignalA.renderNextBlock (bufferA); + + // A different sweep, overwrites the same buffer + auto sineSweepSignalB = SineSweep() + .withType (SineSweep::SweepType::linear) + .withStartFrequency (100_Hz) + .withEndFrequency (1_Hz) + .withDuration (500_ms) + .withLoop (SineSweep::Loop::yes); + sineSweepSignalB.prepare ( + sampleRateHz, + 1, // numOutputChannels + sineSweepDurationFrames // maxBlockSizeFrames + ); + sineSweepSignalB.renderNextBlock (bufferA); + + // Multi-channel noise + constexpr size_t multiChannelNoiseNumChannels = 5; + const size_t multiChannelNoiseDurationFrames = hart::roundToSizeT (sampleRateHz * 10_ms); + AudioBuffer bufferB (multiChannelNoiseNumChannels, multiChannelNoiseDurationFrames); + auto multiChannelNoiseSignal = WhiteNoise(); + multiChannelNoiseSignal.prepare ( + sampleRateHz, + multiChannelNoiseNumChannels, // numOutputChannels + multiChannelNoiseDurationFrames // maxBlockSizeFrames + ); + multiChannelNoiseSignal.renderNextBlock (bufferB); + multiChannelNoiseSignal.renderNextBlock (bufferB); // Some more noise... + multiChannelNoiseSignal.renderNextBlock (bufferB); // ...and more noise + + return 0; +}