Sound Byte Libs 1ee2ca6
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
Sound Byte Libs Style Guide

How we write C++ for embedded audio applications.

  • Write clear, readable code
  • No dynamic allocation or exceptions
  • Use templates for compile-time optimization
  • Platform independence through compile-time configuration
  • Design for testability with template-based mocks

Style Philosophy

  • Match the existing style unless there is a compelling reason
  • Be explicit as a rule, verbose if necessary
    • Names should give a clear indication of what something is.
    • We don't have to worry about file size or length anymore, so let's be descriptive.
  • Don't be clever, be readable and correct.

File Organization

File conventions:

  • C++ headers: .hpp
  • Implementation: .cpp
  • Headers: snake_case.hpp
  • Tests: test_component_name.hpp
  • Directories: snake_case or dash-case

Examples:

src/sbl/hal/gpio_pin.hpp // HAL interface
src/sbl/components/display/led.hpp // Component header
src/sbl/patterns/timing/non_blocking_delay.hpp // Pattern header
tests/unit/test_blinker.hpp // Unit test
Root namespace for all Sound Byte Libs functionality.
Definition aliases.hpp:24

Include Guards

Use path-based include guards. Do NOT use #pragma once.

#ifndef SBL_HAL_GPIO_HPP_
#define SBL_HAL_GPIO_HPP_
// Header content
#endif // SBL_HAL_GPIO_HPP_

Pattern: SBL_PATH_FILENAME_HPP_

Why not #pragma once? While widely supported, it's non-standard and can have edge cases with symlinks and build systems. Traditional guards are explicit and portable.

NEVER use ../ includes or relative include paths as they are a code smell.

Naming Conventions

Naming Philosophy

Every word must add meaning.

Examples:

// GOOD
class Led { };
class Button { };
class CalibrationTable { };
// BAD
class StatusLed { }; // "Status" adds nothing
class ControlButton { }; // "Control" is redundant
class Manager { }; // Manager of what?
// Instance names show purpose
Led power_indicator;
Led activity_led;
Button encoder_button;

Naming conventions:

// Classes, enums, type aliases: PascalCase
class GpioController { };
enum class PinMode { Input, Output };
using GpioPin = uint8_t;
// Functions and methods: snake_case
bool set_tempo(uint32_t bpm);
void enable();
void delay_ms(uint32_t ms);
// Variables (local and member): snake_case
// Members use trailing underscore
class Timer {
private:
uint32_t frequency_;
bool is_running_;
};
uint32_t sample_rate = 44100;
// Constants: UPPER_CASE
static constexpr uint32_t BUFFER_SIZE = 1024;
// Namespaces: lowercase
namespace sbl::driver { }
MCU driver implementations (from sbl-hardware)
driver::Timer Timer
Definition aliases.hpp:28

Code Formatting

// 4 spaces indentation, braces on same line
class Timer {
public:
void start() {
if (condition) {
do_something();
}
}
private:
uint32_t frequency_;
};
// Space around operators
int result = a + b * c;
// Always use braces
if (simple) {
return;
}
// Break long lines at 80-120 characters
if (very_long_condition &&
another_condition) {
// ...
}

Documentation

/**
* @brief Configure GPIO pin mode and pull resistors
* @param pin GPIO pin number
* @param mode Desired pin configuration
*/
void set_mode(uint8_t pin, PinMode mode);
// Explain WHY, not WHAT
// Use Timer0 to preserve Timer1/Timer2 for PWM
timer0_init();
// Explain complex decisions
// Debounce for 50ms to handle switch bounce
if (pressed_time_ > DEBOUNCE_THRESHOLD_MS) {
// ...
}

File headers:

/**
* @file blinker.hpp
* @brief LED blinking controller with configurable patterns
*/
#ifndef SBL_WIDGET_BLINKER_HPP_
#define SBL_WIDGET_BLINKER_HPP_

Template Usage

// Template parameters use clear names
template<typename GpioImpl, typename TimerImpl>
class Blinker {
public:
Blinker(GpioImpl& gpio, TimerImpl& timer, uint8_t pin)
: gpio_(gpio), timer_(timer), pin_(pin) {}
void start() {
gpio_.set_mode(pin_, PinMode::Output);
is_running_ = true;
}
private:
GpioImpl& gpio_;
TimerImpl& timer_;
uint8_t pin_;
bool is_running_ = false;
};
// Type aliases for convenience
using PlatformBlinker = Blinker<sbl::driver::Gpio, sbl::driver::Timer>;

Memory Management

  • Static allocation of memory only
// Use references when ownership is clear
class Sequencer {
public:
Sequencer(Timer& timer, Gpio& gpio)
: timer_(timer), gpio_(gpio) {}
private:
Timer& timer_;
Gpio& gpio_;
};
// RAII for resource management
class I2cTransaction {
public:
I2cTransaction(I2c& i2c, uint8_t address)
: i2c_(i2c), address_(address) {
i2c_.begin_transaction(address_);
}
~I2cTransaction() {
i2c_.end_transaction();
}
};

Error Handling

// No exceptions - use return codes
enum class Result {
Success,
InvalidParameter,
BufferFull,
HardwareError,
Timeout
};
Result configure_timer(uint32_t frequency) {
if (frequency == 0 || frequency > MAX_FREQUENCY) {
return Result::InvalidParameter;
}
// Configure hardware...
return Result::Success;
}

Summary

We are pre-alpha with 0 users. This gives us an opportunity to break things and rebuild them correctly without regard for how things were or backwards compat.

Do:

  • Use templates for compile-time optimization
  • Static allocation only
  • Write clear, testable code
  • Follow RAII patterns
  • Use meaningful names

Don't:

  • Use dynamic allocation or exceptions
  • Use virtual functions in HAL
  • Hardcode platform values in application code
  • Write overly clever code
  • Deprecate code when making changes
  • Make code backwards compatible

ARM Cortex-M Constraints and Optimizations

Fixed-Point Arithmetic:

// Use fixed-point for ARM Cortex-M0+ compatibility (no FPU)
static constexpr int32_t VOLTAGE_SCALE_Q16 = 65536; // Q16.16 format
int32_t millivolts = (rawValue * VOLTAGE_SCALE_Q16) >> 16;
// Avoid floating-point in core library
float voltage = rawValue * 0.001f; // DSP library only

Memory Constraints:

// Fixed-size arrays with compile-time limits
template<size_t MaxSize>
class Buffer {
static_assert(MaxSize <= 1024, "Buffer exceeds typical ARM Cortex-M RAM limits");
private:
uint8_t data_[MaxSize];
};

Real-Time Requirements:

  • No division or modulo in performance-critical paths
  • Use bit shifts for power-of-2 operations
  • Bounded execution time for ISR-called functions
  • Critical sections must be minimal (< 1μs typical)

Performance:

  • Prefer constexpr for compile-time computation
  • Keep interrupt handlers minimal
  • Choose algorithms optimized for ARM Cortex-M architecture
  • Use lookup tables instead of complex mathematical functions