Skip to content

Instantly share code, notes, and snippets.

@haruki7049
Created June 9, 2026 11:18
Show Gist options
  • Select an option

  • Save haruki7049/04777cd2888615a5f0f5b1b6c74c44ad to your computer and use it in GitHub Desktop.

Select an option

Save haruki7049/04777cd2888615a5f0f5b1b6c74c44ad to your computer and use it in GitHub Desktop.
diff --git a/.deps.nix b/.deps.nix
new file mode 100644
index 0000000..942885e
--- /dev/null
+++ b/.deps.nix
@@ -0,0 +1,38 @@
+# generated by zon2nix (https://github.com/nix-community/zon2nix)
+
+{
+ linkFarm,
+ fetchzip,
+ fetchgit,
+}:
+
+linkFarm "zig-packages" [
+ {
+ name = "riff_zig-1.0.0-J7H0A7E6TAAFStkejtsMRsIhfgLMLzf5iYxWgeSFBLJu";
+ path = fetchzip {
+ url = "https://github.com/haruki7049/RIFF.zig/archive/refs/tags/1.0.0.tar.gz";
+ hash = "sha256-ELlLnVXNOfI44m9EbnKAsaWMjDKEAP1i0VnIdv1Tq14=";
+ };
+ }
+ {
+ name = "system_sdk-0.3.0-dev-alwUNnYaaAJAtIdE2fg4NQfDqEKs7QCXy_qYukAOBfmF";
+ path = fetchzip {
+ url = "https://github.com/zig-gamedev/system_sdk/archive/c0dbf11cdc17da5904ea8a17eadc54dee26567ec.tar.gz";
+ hash = "sha256-O3+Z4F58yESes3AMR3yfgqzJjM35BjwxGULgjKgJ6cE=";
+ };
+ }
+ {
+ name = "zaudio-0.11.0-dev-_M-91kHvPwAlW8MCRI4XiTbTeihqB8Zspgiuw-6Gqgdz";
+ path = fetchzip {
+ url = "https://github.com/zig-gamedev/zaudio/archive/bb93ad665b89e302d4515a36b44cb8e73eaf6766.tar.gz";
+ hash = "sha256-iMGK3w/SQ6IxD2vW1x0nwKshhdzNLAsbngx0Vo1Svgc=";
+ };
+ }
+ {
+ name = "zigggwavvv-1.0.0-iR5xWiDEAAD6QVEQLTO2j1OfcisaJgUfwo0hLAJnb6hS";
+ path = fetchzip {
+ url = "https://github.com/haruki7049/zigggwavvv/archive/refs/tags/1.0.0.tar.gz";
+ hash = "sha256-XBhbXfS7KJlIuchKeTPYoMZrZvaCXuWNX+eWLxkdV0s=";
+ };
+ }
+]
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index dd174e0..3b8f7fe 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,5 +14,5 @@ jobs:
- uses: actions/checkout@v5.0.0
- uses: mlugg/setup-zig@v2.0.5
with:
- version: 0.15.1
+ version: 0.15.2
- run: zig build test
diff --git a/.github/workflows/deploy-api-docs.yml b/.github/workflows/deploy-api-docs.yml
new file mode 100644
index 0000000..ffe802a
--- /dev/null
+++ b/.github/workflows/deploy-api-docs.yml
@@ -0,0 +1,45 @@
+on:
+ push:
+ branches: ["main"]
+ workflow_dispatch:
+
+permissions:
+ pages: write
+ contents: read
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: false
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v6.0.1
+ - name: Setup Ziglang
+ uses: mlugg/setup-zig@v2.0.5
+ with:
+ version: 0.15.2
+ - name: Generate docs
+ run: |
+ zig build docs
+ - name: Setup Pages
+ uses: actions/configure-pages@v5.0.0
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v4.0.0
+ with:
+ path: zig-out/share/lightmix/docs/
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4.0.5
diff --git a/.github/workflows/nix-checker.yml b/.github/workflows/nix-checker.yml
new file mode 100644
index 0000000..76aa493
--- /dev/null
+++ b/.github/workflows/nix-checker.yml
@@ -0,0 +1,37 @@
+name: nix-checker
+
+on:
+ push:
+
+permissions: {}
+
+jobs:
+ run-nix-check:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ - name: Install Nix
+ uses: cachix/install-nix-action@v31.6.0
+ with:
+ nix_path: nixpkgs=channel:nixos-unstable
+ - name: Run nix flake check
+ run: nix flake check --all-systems
+
+ run-nix-build:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ - name: Install Nix
+ uses: cachix/install-nix-action@v31.6.0
+ with:
+ nix_path: nixpkgs=channel:nixos-unstable
+ - name: Run nix-build with flakes
+ run: nix build .#default
diff --git a/.github/workflows/pr-conventional-commits-validation.yml b/.github/workflows/pr-conventional-commits-validation.yml
new file mode 100644
index 0000000..3239625
--- /dev/null
+++ b/.github/workflows/pr-conventional-commits-validation.yml
@@ -0,0 +1,15 @@
+name: PR Conventional Commit Validation
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, edited]
+
+jobs:
+ validate-pr-title:
+ runs-on: ubuntu-latest
+ steps:
+ - name: PR Conventional Commit Validation
+ uses: ytanikin/pr-conventional-commits@1.4.0
+ with:
+ task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]'
+ add_scope_label: "true"
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..da1beda
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+tontonkirikiri@gmail.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
+
+[homepage]: https://www.contributor-covenant.org
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..e9dc3ee
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,377 @@
+# Contributing to lightmix
+
+Thank you for your interest in contributing to lightmix! This document provides guidelines and information for contributors.
+
+## Table of Contents
+
+- [Code of Conduct](#code-of-conduct)
+- [Getting Started](#getting-started)
+- [Development Environment](#development-environment)
+- [How to Contribute](#how-to-contribute)
+- [Coding Guidelines](#coding-guidelines)
+- [Testing](#testing)
+- [Submitting Changes](#submitting-changes)
+- [Documentation](#documentation)
+- [Audio-Specific Guidelines](#audio-specific-guidelines)
+
+## Code of Conduct
+
+See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). We use [Contributor Covenant](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
+
+## Getting Started
+
+1. **Fork the repository** on GitHub
+1. **Clone your fork** locally:
+ ```bash
+ git clone https://github.com/YOUR_USERNAME/lightmix.git
+ cd lightmix
+ ```
+1. **Set up the development environment** (see below)
+1. **Create a new branch** for your work:
+ ```bash
+ git checkout -b feature/your-feature-name
+ ```
+
+## Development Environment
+
+### Required Tools
+
+- **Zig 0.15.2** - This project tracks Zig's minor version
+- **Git** for version control
+
+### Optional but Recommended
+
+- **Nix** (with flakes enabled) for reproducible development environment
+- **direnv** for automatic environment loading
+- **Audio player** (`sox`, VLC, or similar) for testing audio output
+
+### Setup with Nix
+
+If you have Nix with flakes:
+
+```bash
+# Automatic setup with direnv
+echo "use flake" > .envrc
+direnv allow
+
+# Or manually enter the dev shell
+nix develop
+```
+
+### Setup without Nix
+
+1. Install Zig 0.15.2 from [ziglang.org](https://ziglang.org/download/)
+1. Verify installation:
+ ```bash
+ zig version # Should show 0.15.2
+ ```
+
+### Building and Testing
+
+```bash
+# Run all tests
+zig build test
+
+# Build the library
+zig build
+
+# Generate documentation
+zig build docs
+
+# Run examples
+cd examples/01-getting-started/hello-wave
+zig build run
+```
+
+## How to Contribute
+
+### Types of Contributions
+
+We welcome:
+
+- **Bug fixes** - Fix issues in the core library or examples
+- **New features** - Add new audio processing capabilities
+- **Examples** - Create educational examples demonstrating library features
+- **Documentation** - Improve docs, comments, or README files
+- **Tests** - Add test coverage for existing functionality
+- **Performance improvements** - Optimize audio processing algorithms
+
+### Finding Something to Work On
+
+- Check the [issue tracker](https://github.com/haruki7049/lightmix/issues) for open issues
+- Look for issues labeled `good first issue` or `help wanted`
+- Review the examples directory for areas that need more coverage
+- Propose new features by opening an issue first
+
+## Coding Guidelines
+
+### General Principles
+
+1. **Clarity over cleverness** - Write code that's easy to understand
+1. **Type safety** - Leverage Zig's type system for compile-time safety
+1. **Memory safety** - Properly manage allocations and prevent leaks
+1. **Documentation** - Document public APIs and complex algorithms
+
+### Code Style
+
+- **Comments**: Write comments in **English**
+ - Documentation comments (`///` and `//!`) must be in English
+ - Regular inline comments (`//`) should also be in English
+- **Formatting**: Use `zig fmt` before committing
+ - Run `zig fmt .` in the project root
+- **Naming**:
+ - `camelCase` for functions and variables
+ - `PascalCase` for types
+ - `SCREAMING_SNAKE_CASE` for constants
+- **Indentation**: 4 spaces (handled by `zig fmt`)
+
+### Documentation Comments
+
+Use documentation comments for all public APIs:
+
+```zig
+/// Creates a new Wave instance from sample data.
+///
+/// The function creates a deep copy of the sample data, so the caller
+/// retains ownership of the original samples slice.
+///
+/// ## Parameters
+/// - `samples`: Slice of sample data to copy
+/// - `allocator`: Memory allocator for internal allocations
+/// - `options`: Initialization options (sample rate and channel count)
+///
+/// ## Returns
+/// A new Wave instance containing a copy of the sample data
+pub fn init(
+ samples: []const T,
+ allocator: std.mem.Allocator,
+ options: InitOptions,
+) Self {
+ // Implementation
+}
+```
+
+### Generic Programming
+
+When creating generic functions:
+
+- Use `comptime` parameters for type flexibility
+- Document type constraints clearly
+- Provide usage examples
+
+````zig
+/// Wave type function: Creates a Wave type for the specified sample type.
+///
+/// ## Type Parameter
+/// - `T`: The sample data type (typically f64, f80, or f128)
+///
+/// ## Usage
+/// ```zig
+/// const Wave = lightmix.Wave;
+/// const wave = Wave(f64).init(samples, allocator, .{
+/// .sample_rate = 44100,
+/// .channels = 1,
+/// });
+/// defer wave.deinit();
+/// ```
+pub fn inner(comptime T: type) type {
+ return struct {
+ // Implementation
+ };
+}
+````
+
+### Memory Management
+
+- **Always provide `deinit` functions** for types that allocate memory
+- **Document ownership** clearly in function comments
+- **Use `defer` appropriately** in examples and tests
+- **Test for memory leaks** using `std.testing.allocator`
+
+```zig
+test "no memory leaks" {
+ const allocator = std.testing.allocator;
+ const wave = Wave(f64).init(samples, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
+ // Test code
+}
+```
+
+## Testing
+
+### Writing Tests
+
+- **Unit tests**: Place in the same file as the code being tested
+- **Integration tests**: Place in `tests/` directory
+- **Example tests**: Each example should run without errors
+
+### Test Organization
+
+```zig
+// In src/wave.zig
+test "init creates deep copy of samples" {
+ const allocator = testing.allocator;
+ var original_samples = [_]T{ 1.0, 2.0, 3.0 };
+ const wave = Self.init(&original_samples, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
+
+ // Modify original samples
+ original_samples[0] = 999.0;
+
+ // Wave samples should be unchanged
+ try testing.expectEqual(wave.samples[0], 1.0);
+}
+```
+
+### Running Tests
+
+```bash
+# Run all tests
+zig build test
+
+# Run specific test file
+zig test src/wave.zig
+
+# Run with memory leak detection
+zig test src/wave.zig --test-filter "no memory leaks"
+```
+
+## Submitting Changes
+
+### Before Submitting
+
+1. **Run tests**: `zig build test`
+1. **Format code**: `zig fmt .`
+1. **Update documentation** if needed
+1. **Add tests** for new functionality
+1. **Test examples** if they're affected
+
+### Commit Messages
+
+Write clear, descriptive commit messages:
+
+```
+Add filter chaining example
+
+- Create new example in examples/03-wave-operations/
+- Demonstrate using multiple filters in sequence
+- Include documentation about filter composition
+```
+
+### Pull Request Process
+
+1. **Push your branch** to your fork
+1. **Open a Pull Request** against `main`
+1. **Describe your changes**:
+ - What problem does this solve?
+ - How did you test it?
+ - Any breaking changes?
+1. **Respond to review feedback**
+1. **Keep your PR up to date** with main
+
+### PR Title Format
+
+- `feat: Add new feature`
+- `fix: Fix bug description`
+- `docs: Update documentation`
+- `test: Add tests for X`
+- `refactor: Improve code structure`
+- `perf: Optimize performance of X`
+
+## Documentation
+
+### Types of Documentation
+
+1. **API Documentation** - Document all public functions, types, and constants
+1. **Examples** - Create runnable examples with detailed comments
+1. **README** - Keep README.md up to date with new features
+1. **Build-time Docs** - Run `zig build docs` to generate API docs
+
+### Example Documentation
+
+When creating examples:
+
+- **Add top-level documentation** (`//!`) explaining the concept
+- **Include inline comments** for complex code sections
+- **Provide context** about what the example demonstrates
+- **Show expected output** or results
+
+```zig
+//! # Filter Chaining Example
+//!
+//! This example demonstrates how to chain multiple filters together.
+//! We apply decay and volume reduction to create a fade-out effect.
+//!
+//! ## What you'll learn:
+//! - Chaining filter functions
+//! - Creating composite effects
+//! - Managing filter ownership
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+
+pub fn main() !void {
+ // Example implementation with comments
+}
+```
+
+## Audio-Specific Guidelines
+
+### Sample Rates
+
+- **Default to 44100 Hz** in examples (CD quality)
+- **Support common rates**: 22050, 44100, 48000, 96000 Hz
+- **Document rate requirements** clearly
+
+### Audio Formats
+
+- **Prefer PCM formats** for examples
+- **Support standard bit depths**: 16, 24, 32 bits
+- **Document format limitations**
+
+### Testing Audio Output
+
+When contributing audio generation code:
+
+1. **Listen to the output** - Does it sound correct?
+1. **Check for clipping** - Ensure samples stay within [-1.0, 1.0]
+1. **Verify silence** - Silent sections should be exactly 0.0
+1. **Test edge cases** - Empty waves, single samples, etc.
+
+### Performance Considerations
+
+- **Avoid unnecessary allocations** in tight loops
+- **Use `comptime` for static computations**
+- **Profile performance-critical code**
+- **Document time complexity** for algorithms
+
+### Example Audio Files
+
+When adding test audio files:
+
+- **Keep files small** (< 1 MB preferred)
+- **Use standard formats** (WAV, 16-bit PCM)
+- **Document file contents** in comments
+- **Add to `.gitignore` if generated**
+
+## Questions?
+
+If you have questions:
+
+- **Open an issue** for discussion
+- **Check existing issues** and PRs
+- **Review examples** for patterns and conventions
+
+## License
+
+By contributing to lightmix, you agree that your contributions will be licensed under the [MIT License](./LICENSE).
+
+______________________________________________________________________
+
+Thank you for contributing to lightmix! 🎵
diff --git a/README.md b/README.md
index f92397e..d3f34e4 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,17 @@
`lightmix` is an audio processing library written by Zig-lang.
+## Why I create this
+
+I created this project because I felt a disconnect between existing audio synthesis environments and the standard software development workflow I use every day.
+
+- **From "Recording" to "Building"**:
+ In many existing tools, exporting audio feels like a manual task. I often had to click a record button or write specific code to manage recording buffers, essentially capturing the output in real-time. I wanted a workflow where audio is treated as a build artifact—where running `zig build run` (or `zig build`) instantly produces a WAV file, just as it would a binary executable.
+- **Integration with the Modern Toolchain**:
+ I found it cumbersome to set up dedicated runtimes or specialized IDEs just to generate sound. I wanted to use my preferred editor and the standard Zig toolchain without any external dependencies or complex server setups.
+
+**lightmix** is my attempt to bridge these two worlds. It allows me to "build" sound with the same precision, automation, and simplicity that I expect from any other software project.
+
## How to use
In `build.zig`, import lightmix from `build.zig.zon` using `b.dependency()`:
@@ -17,65 +28,175 @@ const lib_mod = b.createModule(.{
lib_mod.addImport("lightmix", lightmix.module("lightmix")); // Add lightmix to your library or executable module.
```
-You can find some examples in [./examples](./examples) directory. If you want to copy a example, edit `.lightmix = .{ .path = "../../.." }` on it's `build.zig.zon`.
+You can find some examples in [./examples](./examples) directory. If you want to copy an example, edit `.lightmix = .{ .path = "../../.." }` in its `build.zig.zon`.
+
+## Build-time Wave file generation
+
+lightmix provides a helper function `addWave` in `build.zig` that allows you to generate and install Wave files during the build process.
+
+### `addWave` function in `build.zig`
+
+The `addWave` function is a build-time helper that allows you to generate Wave files as part of your build process. This means you can write a Zig function that generates audio, and the build system will automatically create the WAV file when you run `zig build`.
+
+#### How to use `addWave`
+
+1. First, create a module with a function that generates a Wave:
+
+```zig
+// In your src/root.zig or similar file
+const std = @import("std");
+const lightmix = @import("lightmix");
+
+pub fn generate(allocator: std.mem.Allocator) !lightmix.Wave(f64) {
+ const allocator = std.heap.page_allocator;
+
+ // Generate your audio data (example: 1 second of silence)
+ const data: [44100]f64 = [_]f64{0.0} ** 44100;
+
+ // Wave.init() creates a deep copy of the data
+ // The original data array can be safely discarded after this call
+ const wave: lightmix.Wave(f64) = try lightmix.Wave(f64).init(data[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+
+ return wave;
+}
+```
+
+2. In your `build.zig`, use the `addWave` function:
+
+```zig
+const std = @import("std");
+const l = @import("lightmix");
+
+pub fn build(b: *std.Build) !void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ // Create your module that contains the wave generation function
+ const mod = b.createModule(.{
+ .root_source_file = b.path("src/root.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ // Use addWave to generate the wave file during build
+ const wave = try l.addWave(b, mod, .{
+ .func_name = "generate", // Name of your wave generation function (optional, defaults to "gen")
+ .format = .{ .wav = .{
+ .name = "result.wav", // Output filename (optional, defaults to "result.wav")
+ .format_code = .pcm, // Wave format code (e.g., .pcm, .ieee_float)
+ .bits = 16, // The bits depth for this wave (e.g., 8, 16, 24, 32)
+ } },
+ .path = .{ .custom = "share" }, // Install directory (optional, defaults to "share")
+ });
+
+ // Add to the install step so it runs during `zig build`
+ // You can use installWave helper or access wave.step directly
+ l.installWave(b, wave);
+ // Or: b.getInstallStep().dependOn(wave.step);
+}
+```
+
+3. Run `zig build` to generate the wave file. The file will be created in `zig-out/share/result.wav` (or your configured path).
+
+#### `addWave` Options
+
+- **`func_name`**: The name of the function in your module that generates the Wave. The function must have the signature `pub fn name() !lightmix.Wave(T)` where T is your chosen sample type (e.g., f64) (default: `"gen"`)
+- **`path`**: The installation directory relative to the install prefix (default: `.{ .custom = "share" }`)
+- **`wave.name`**: The output filename for the wave file (default: `"result.wav"`)
+- **`wave.bits`**: The bit depth for the wave file, which is typed u16
+- **`wave.format_code`**: Audio encoding format (e.g., .pcm, .ieee_float)
+
+You can find a complete example in [./examples/06-advanced/build-time-generation](./examples/06-advanced/build-time-generation).
## lightmix's types
### `Wave`
-Contains a PCM audio source.
+`Wave` is a generic type function that accepts a sample type parameter. It contains PCM audio source with samples of the specified floating-point type.
+
+Both waves must have identical `sample_rate`, `channels`, and sample length, or the program will panic.
+
+Supported sample types: `f64`, `f80`, `f128`.
```zig
const allocator = std.heap.page_allocator; // Use your allocator
-const data: []const f32 = &[_]{ 0.0, 0.0, 0.0 }; // This array contains 3 float number, then this wave will make from 3 samples.
+const data: []const f64 = &[_]f64{ 0.0, 0.0, 0.0 }; // This array contains 3 float numbers, then this wave will be made from 3 samples.
-const wave: lightmix.Wave = Wave.init(data, allocator, .{
+const wave: lightmix.Wave(f64) = try lightmix.Wave(f64).init(data, allocator, .{
.sample_rate = 44100, // Samples per second.
- .channels = 1, // Channels for this Wave. If this wave have two channels, it means this wave is stereo.
- .bits = 16, // Bits for this wave.
+ .channels = 1, // Channels for this Wave. If this wave has two channels, it means this wave is stereo.
});
-defer wave.deinit(); // Wave.data is owned data by passed allocator, then you must `free` this wave.
+defer wave.deinit(); // Wave samples are owned by the passed allocator, so you must free this wave.
```
-You can write your `Wave` to your wave file, such as `result.wav`.
+You can write your `Wave` to a wave file, such as `result.wav`.
```zig
-// First, create your `Wave`.
-const wave: lightmix.Wave = generate_wave();
+// First, create your Wave with a specific sample type
+const wave = generate_wave(); // Returns a Wave(f64)
// Second, you must create a file, typed as `std.fs.File`.
-var file = try std.fs.cwd().createFile("result.wav", .{});
+const file = try std.fs.cwd().createFile("result.wav", .{});
defer file.close();
+// Wav file size calculation
+const bits = 16;
+const bytes_per_sample = (bits + 7) / 8;
+const header_size = 44;
+const total_size = header_size + (wave.samples.len * wave.channels * bytes_per_sample);
+
+// Create a buffer for file writer
+const buf = try allocator.alloc(u8, total_size);
+defer allocator.free(buf);
+
+// Create a std.fs.File.Writer variable from the file.
+// It has an `interface` variable typed `std.Io.Writer`.
+var writer = file.writer(buf);
+
// Then, write down your wave!!
-try wave.write(file);
+try wave.write(.wav, &writer.interface, .{
+ .allocator = allocator,
+ .bits = bits, // Bit depth for the output file
+ .format_code = .pcm, // Format code (e.g., .pcm or .ieee_float)
+});
```
### `Composer`
-Contains a `Composer.WaveInfo` array, which contains a `Wave` and the timing when it plays.
+`Composer` is a generic type function that accepts a sample type parameter (same as Wave). It contains a `Composer(T).WaveInfo` array, which contains a `Wave(T)` and the timing when it plays.
```zig
const allocator = std.heap.page_allocator; // Use your allocator
-const wave: lightmix.Wave = generate_wave();
+const wave = generate_wave(); // Returns a Wave(f64)
-const info: []const lightmix.Composer.WaveInfo = &[_]WaveInfo{
+const info: []const lightmix.Composer(f64).WaveInfo = &.{
.{ .wave = wave, .start_point = 0 },
.{ .wave = wave, .start_point = 44100 },
};
-const composer: lightmix.Composer = Composer.init_with(info, allocator, .
+const composer: lightmix.Composer(f64) = try lightmix.Composer(f64).init_with(info, allocator, .{
.sample_rate = 44100, // Samples per second.
- .channels = 1, // Channels for the Wave. If this composer have two channels, it means the wave is stereo.
- .bits = 16, // Bits for the wave.
+ .channels = 1, // Channels for the Wave. If this composer has two channels, it means the wave is stereo.
});
-defer composer.deinit(); // Composer.info is also owned data by passed allocator, then you must `free` this wave.
+defer composer.deinit(); // Composer.info is also owned by the passed allocator, so you must free this composer.
-const wave: lightmix.Wave = composer.finalize(); // Let's finalize to create a `Wave`!!
-defer wave.deinit(); // Don't forget to free the `Wave` data.
+const result: lightmix.Wave(f64) = composer.finalize(.{}); // Let's finalize to create a Wave(f64)!!
+defer result.deinit(); // Don't forget to free the Wave data.
```
## Zig version
-0.15.1
+0.15.2
This project will follows Ziglang's minor version.
+
+## API Documentations
+
+https://haruki7049.github.io/lightmix
diff --git a/build.zig b/build.zig
index 011d4b1..0dfb07b 100644
--- a/build.zig
+++ b/build.zig
@@ -1,28 +1,49 @@
const std = @import("std");
+const z_wav = @import("zigggwavvv");
-pub fn build(b: *std.Build) void {
+pub const Wave = @import("./src/wave.zig");
+pub const Composer = @import("./src/composer.zig");
+
+pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
- const with_debug_features = b.option(bool, "with_debug_features", "Enable debug features implemented by PortAudio") orelse false;
-
- const options = b.addOptions();
- options.addOption(bool, "with_debug_features", with_debug_features);
-
// Dependencies
- const lightmix_wav = b.dependency("lightmix_wav", .{});
- const known_folders = b.dependency("known_folders", .{});
+ const zigggwavvv = b.dependency("zigggwavvv", .{});
+ const zaudio = b.dependency("zaudio", .{});
// Library module declaration
const lib_mod = b.addModule("lightmix", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
+ .imports = &.{
+ .{ .name = "zigggwavvv", .module = zigggwavvv.module("zigggwavvv") },
+ .{ .name = "zaudio", .module = zaudio.module("root") },
+ },
});
- lib_mod.addImport("lightmix_wav", lightmix_wav.module("lightmix_wav"));
- lib_mod.addImport("known-folders", known_folders.module("known-folders"));
- lib_mod.addOptions("build_options", options);
- lib_mod.addOptions("with_debug_features", options);
+
+ // miniaudio linking
+ lib_mod.linkLibrary(zaudio.artifact("miniaudio"));
+
+ // # macOS
+ // apple-sdk framework linking is needed if your machine runs macOS.
+ // This needs SDKROOT environment variable.
+ // Your SDKROOT should be a string as "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" (If you don't set SDKROOT, lightmix uses "xcrun --show-sdk-path" command to get SDKROOT).
+ // You can use pkgs.apple-sdk on nixpkgs with "pkgs.mkShell". You should have SDKROOT environment variable by pkgs.apple-sdk's hook when you use "pkgs.mkShell".
+ //
+ // I must write below programs, because "miniaudio" linking needs macOS SDK on macOS.
+ if (target.result.os.tag == .macos) {
+ const sdkroot_envvar: []const u8 = b.graph.env_map.get("SDKROOT") orelse inner: {
+ // These processes need "xcrun" command
+ const argv = &.{ "xcrun", "--show-sdk-path" };
+ const result = b.run(argv); // The stdout of "xcrun --show-sdk-path"
+
+ break :inner result;
+ };
+ const sdkroot: []const u8 = try std.mem.concat(b.allocator, u8, &.{ sdkroot_envvar, "/System/Library/Frameworks" });
+ lib_mod.addFrameworkPath(.{ .cwd_relative = sdkroot });
+ }
// Library installation
const lib = b.addLibrary(.{
@@ -30,10 +51,6 @@ pub fn build(b: *std.Build) void {
.name = "lightmix",
.root_module = lib_mod,
});
-
- if (with_debug_features)
- lib.linkLibC();
-
b.installArtifact(lib);
// Library unit tests
@@ -46,6 +63,35 @@ pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
+ // Integration tests
+ // Wave
+ const wave_integration_test = b.addTest(.{
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("tests/wave.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lib_mod },
+ },
+ }),
+ });
+ const run_wave_integration_tests = b.addRunArtifact(wave_integration_test);
+ test_step.dependOn(&run_wave_integration_tests.step);
+
+ // Composer
+ const composer_integration_test = b.addTest(.{
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("tests/composer.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lib_mod },
+ },
+ }),
+ });
+ const run_composer_integration_tests = b.addRunArtifact(composer_integration_test);
+ test_step.dependOn(&run_composer_integration_tests.step);
+
// Docs
const docs_step = b.step("docs", "Emit docs");
const docs_install = b.addInstallDirectory(.{
@@ -55,3 +101,348 @@ pub fn build(b: *std.Build) void {
});
docs_step.dependOn(&docs_install.step);
}
+
+/// Creates a build step that generates a WAV file at compile time.
+///
+/// This function enables compile-time audio generation by calling a user-defined
+/// function that returns a Wave instance, then writing it to a WAV file during
+/// the build process.
+///
+/// ## Parameters
+/// - `b`: The build context
+/// - `mod`: The module containing the wave generation function
+/// - `options`: Configuration options for wave generation
+///
+/// ## Returns
+/// A pointer to a value typed CompileWave
+///
+/// ## Errors
+/// Returns errors from:
+/// - File system operations (creating cache directory, writing files)
+/// - Memory allocation failures
+/// - The user-provided wave generation function (if it returns an error)
+///
+/// ## Usage
+/// ```zig
+/// const std = @import("std");
+/// const l = @import("lightmix");
+///
+/// pub fn build(b: *std.Build) !void {
+/// const target = b.standardTargetOptions(.{});
+/// const optimize = b.standardOptimizeOption(.{});
+///
+/// // Dependencies
+/// const lightmix = b.dependency("lightmix", .{});
+///
+/// // Module
+/// const mod = b.createModule(.{
+/// .root_source_file = b.path("src/main.zig"),
+/// .target = target,
+/// .optimize = optimize,
+/// .imports = &.{
+/// .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+/// },
+/// });
+///
+/// // Install Wave file into `zig-out` as `result.wav` (default Wave name)
+/// const wave = try l.addWave(b, mod, .{
+/// .func_name = "gen",
+/// .format = .{ .wav = .{ .bits = 16, .format_code = .pcm } },
+/// });
+/// b.getInstallStep().dependOn(wave.step);
+/// }
+/// ```
+///
+/// The user module must export a function matching the signature specified in
+/// `options.func_name` (default: "gen") that returns `!lightmix.Wave(T)`, and receives an argument `std.mem.Allocator`.
+pub fn addWave(
+ b: *std.Build,
+ mod: *std.Build.Module,
+ options: CreateWaveOptions,
+) anyerror!*CompileWave {
+ return switch (options.format) {
+ .wav => Generator.Wav.gen(b, mod, options),
+ };
+}
+
+const Generator = struct {
+ const Wav = struct {
+ fn gen(
+ b: *std.Build,
+ mod: *std.Build.Module,
+ options: CreateWaveOptions,
+ ) anyerror!*CompileWave {
+ // Create .zig-cache/lightmix directory
+ b.cache_root.handle.access("lightmix", .{}) catch {
+ try b.cache_root.handle.makeDir("lightmix");
+ };
+
+ // Create a wave file in .zig-cache/lightmix
+ const tmp_path: []const u8 = try std.fs.path.join(b.allocator, &[_][]const u8{
+ try b.build_root.handle.realpathAlloc(b.allocator, "."),
+ ".zig-cache",
+ "lightmix",
+ options.format.wav.name,
+ });
+ const tmp_path_in_zig: []const u8 = try std.mem.replaceOwned(
+ u8,
+ b.allocator,
+ tmp_path,
+ "\\",
+ "/",
+ );
+ // Generate temporary Zig code that calls the user's function
+ const gen_source = try std.fmt.allocPrint(b.allocator,
+ \\const std = @import("std");
+ \\const user_module = @import("user_module");
+ \\
+ \\var gpa = std.heap.GeneralPurposeAllocator(.{{}}){{}};
+ \\const allocator = gpa.allocator();
+ \\
+ \\pub fn main() !void {{
+ \\ defer {{
+ \\ const leaked = gpa.deinit();
+ \\ if (leaked == .leak)
+ \\ @panic("Memory leak happened");
+ \\ }}
+ \\
+ \\ const wave = try user_module.{s}(allocator);
+ \\ defer wave.deinit();
+ \\
+ \\ const bits = {d};
+ \\ const bytes_per_sample = (bits + 7) / 8;
+ \\
+ \\ const header_size = 44;
+ \\ const total_size = header_size + (wave.samples.len * wave.channels * bytes_per_sample);
+ \\
+ \\ const file = try std.fs.cwd().createFile("{s}", .{{}});
+ \\ defer file.close();
+ \\ const buf = try allocator.alloc(u8, total_size);
+ \\ defer allocator.free(buf);
+ \\ var writer = file.writer(buf);
+ \\
+ \\ try wave.write(.wav, &writer.interface, .{{
+ \\ .format_code = .{s},
+ \\ .bits = bits,
+ \\ }});
+ \\
+ \\ try writer.interface.flush();
+ \\}}
+ , .{
+ options.func_name,
+ options.format.wav.bits,
+ tmp_path_in_zig,
+ @tagName(options.format.wav.format_code),
+ });
+
+ // Create a write files step to generate the temporary source
+ const write_files = b.addWriteFiles();
+ const gen_file = write_files.add("wave_gen.zig", gen_source);
+
+ // Create executable that generates the wave
+ const gen_exe = b.addExecutable(.{
+ .name = "wave_generator",
+ .root_module = b.createModule(.{
+ .root_source_file = gen_file,
+ .target = b.graph.host,
+ .optimize = .Debug,
+ .imports = &.{
+ .{ .name = "user_module", .module = mod },
+ },
+ }),
+ });
+
+ // Run the generator during build
+ const run_gen = b.addRunArtifact(gen_exe);
+
+ const src_path: []const u8 = try std.fs.path.join(b.allocator, &[_][]const u8{ ".zig-cache", "lightmix", options.format.wav.name });
+ // Install the generated wave file
+ const install_wave = b.addInstallFileWithDir(
+ b.path(src_path),
+ options.path,
+ options.format.wav.name,
+ );
+ install_wave.step.dependOn(&run_gen.step);
+
+ const result = try b.allocator.create(CompileWave);
+ result.* = CompileWave{
+ .step = &install_wave.step,
+ .root_module = mod,
+ .name = options.format.wav.name,
+ .create_wave_options = options,
+ };
+ return result;
+ }
+ };
+};
+
+/// A return type for addWave function.
+pub const CompileWave = struct {
+ step: *std.Build.Step,
+ root_module: *std.Build.Module,
+ name: []const u8,
+ create_wave_options: CreateWaveOptions,
+};
+
+/// Options for configuring compile-time wave generation.
+///
+/// These options control how the wave generation function is called and
+/// where the resulting WAV file is installed.
+pub const CreateWaveOptions = struct {
+ /// Name of the function in the module that generates the Wave.
+ /// The function must have signature: `pub fn name() !lightmix.Wave(T)`
+ /// where T is typically f64, f80, or f128.
+ func_name: []const u8 = "gen",
+
+ /// Destination path relative to the install prefix where the WAV file will be installed.
+ /// Defaults to the "share" directory.
+ path: std.Build.InstallDir = .{ .custom = "share" },
+
+ /// Output format and codec-specific options for the wave file to generate.
+ format: FormatOptions,
+};
+
+/// Tagged union that selects the output format and carries its format-specific options.
+pub const FormatOptions = union(enum) {
+ wav: WavOptions,
+};
+
+/// Options for configuring a WAV file's output properties.
+///
+/// This struct specifies the output filename, bit depth, and audio format
+/// for the generated WAV file.
+pub const WavOptions = struct {
+ /// The output filename for the wave file (e.g., "result.wav", "audio.wav").
+ name: []const u8 = "result.wav",
+
+ /// The bit depth for the wave file (e.g., 16, 24, or 32 bits per sample).
+ bits: u16,
+
+ /// Audio encoding format such as .pcm (PCM integer) or .ieee_float (floating-point).
+ format_code: z_wav.FormatCode,
+};
+
+/// A helper function to install Wave file from a pointer of a value typed CompileWave.
+///
+/// This function does as the following:
+///
+/// ```
+/// b.getInstallStep().dependOn(wave.step);
+/// ```
+///
+/// ## Usage
+/// ```
+/// // Install Wave file into `zig-out` as `result.wav` (default Wave name)
+/// const wave = try l.addWave(b, mod, .{
+/// .func_name = "gen",
+/// .format = .{ .wav = .{ .bits = 16, .format_code = .pcm } },
+/// });
+/// l.installWave(b, wave);
+/// ```
+pub fn installWave(b: *std.Build, wave: *CompileWave) void {
+ b.getInstallStep().dependOn(wave.step);
+}
+
+/// Create a run step that plays the wave generated by CompileWave.
+///
+/// This function creates a build step that:
+/// 1. Generates an executable that calls the wave generation function
+/// 2. Calls the play() method on the resulting Wave
+/// 3. Returns a Run step that can be added as a dependency
+///
+/// ## Parameters
+/// - `b`: Build context
+/// - `wave`: Pointer to CompileWave containing the wave generation function info
+/// - `options`: Options for creating the play executable
+///
+/// ## Returns
+/// A Run step that executes the play functionality
+///
+/// ## Usage
+/// ```zig
+/// const wave = try l.addWave(b, mod, .{
+/// .format = .{ .wav = .{ .bits = 16, .format_code = .pcm } },
+/// });
+/// const play = try l.addPlay(b, wave, .{});
+/// l.installPlay(b, play);
+/// ```
+pub fn addPlay(
+ b: *std.Build,
+ wave: *CompileWave,
+ options: PlayOptions,
+) anyerror!*std.Build.Step.Run {
+ // Generate temporary Zig code that calls the user's function and plays it
+ const play_source = try std.fmt.allocPrint(b.allocator,
+ \\const std = @import("std");
+ \\const user_module = @import("user_module");
+ \\
+ \\var gpa = std.heap.GeneralPurposeAllocator(.{{}}){{}};
+ \\const allocator = gpa.allocator();
+ \\
+ \\pub fn main() !void {{
+ \\ defer {{
+ \\ const leaked = gpa.deinit();
+ \\ if (leaked == .leak)
+ \\ @panic("Memory leak happened");
+ \\ }}
+ \\
+ \\ const wave = try user_module.{s}(allocator);
+ \\ defer wave.deinit();
+ \\ try wave.play();
+ \\}}
+ \\
+ , .{wave.create_wave_options.func_name});
+
+ // Create a write files step to generate the temporary source
+ const write_files = b.addWriteFiles();
+ const play_source_file = write_files.add("play_wave.zig", play_source);
+
+ // Create executable that plays the wave
+ const play_exe = b.addExecutable(.{
+ .name = options.exe_name,
+ .root_module = b.createModule(.{
+ .root_source_file = play_source_file,
+ .target = b.graph.host,
+ .optimize = options.optimize,
+ .imports = &.{
+ .{ .name = "user_module", .module = wave.root_module },
+ },
+ }),
+ });
+
+ // Create and return a run step
+ const run_play = b.addRunArtifact(play_exe);
+
+ return run_play;
+}
+
+/// A helper function to play Wave at build time from a Run step.
+///
+/// This function does as the following:
+///
+/// ```
+/// b.getInstallStep().dependOn(&play.step);
+/// ```
+///
+/// ## Usage
+/// ```zig
+/// // Play Wave at build time
+/// const wave = try l.addWave(b, mod, .{
+/// .func_name = "gen",
+/// .format = .{ .wav = .{ .bits = 16, .format_code = .pcm } },
+/// });
+/// const play = try l.addPlay(b, wave, .{});
+/// l.installPlay(b, play);
+/// ```
+pub fn installPlay(b: *std.Build, play: *std.Build.Step.Run) void {
+ b.getInstallStep().dependOn(&play.step);
+}
+
+/// Options for creating a play executable
+pub const PlayOptions = struct {
+ /// Name of the generated executable
+ exe_name: []const u8 = "play_wave",
+
+ /// Optimization mode for the executable
+ optimize: std.builtin.OptimizeMode = .Debug,
+};
diff --git a/build.zig.zon b/build.zig.zon
index 8664401..4edcf9f 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -1,17 +1,17 @@
.{
.name = .lightmix,
- .version = "0.5.0",
+ .version = "0.20.4",
.fingerprint = 0xe48269dbca8904a,
- .minimum_zig_version = "0.15.1",
+ .minimum_zig_version = "0.15.2",
.dependencies = .{
- .lightmix_wav = .{
- .url = "https://github.com/haruki7049/lightmix_wav/archive/refs/tags/0.2.2.tar.gz",
- .hash = "lightmix_wav-0.1.0-IIrulkoMVQDAWgmrgqWgHvtyMFKpinx5YmrvxjA0QUqu",
+ .zigggwavvv = .{
+ .url = "https://github.com/haruki7049/zigggwavvv/archive/refs/tags/1.0.0.tar.gz",
+ .hash = "zigggwavvv-1.0.0-iR5xWiDEAAD6QVEQLTO2j1OfcisaJgUfwo0hLAJnb6hS",
},
- .known_folders = .{
- .url = "https://github.com/ziglibs/known-folders/archive/ab5cf5feb936fa3b72c95d3ad0c0c67791937ba1.tar.gz",
- .hash = "known_folders-0.0.0-Fy-PJtTTAADUOhGKM0sxzG4eMkNQxRvx9e5dfHVyaeA3",
+ .zaudio = .{
+ .url = "https://github.com/zig-gamedev/zaudio/archive/bb93ad665b89e302d4515a36b44cb8e73eaf6766.tar.gz",
+ .hash = "zaudio-0.11.0-dev-_M-91kHvPwAlW8MCRI4XiTbTeihqB8Zspgiuw-6Gqgdz",
},
},
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/examples/Composer/compose_multipul_sinewave/.gitignore b/examples/01-getting-started/.gitignore
similarity index 100%
rename from examples/Composer/compose_multipul_sinewave/.gitignore
rename to examples/01-getting-started/.gitignore
diff --git a/examples/Composer/compose_multipul_sinewave/build.zig b/examples/01-getting-started/hello-wave/build.zig
similarity index 87%
rename from examples/Composer/compose_multipul_sinewave/build.zig
rename to examples/01-getting-started/hello-wave/build.zig
index 735a448..81ce6fe 100644
--- a/examples/Composer/compose_multipul_sinewave/build.zig
+++ b/examples/01-getting-started/hello-wave/build.zig
@@ -12,17 +12,19 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
});
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
// Executable
const exe = b.addExecutable(.{
- .name = "compose_multipul_soundless",
+ .name = "hello-wave",
.root_module = exe_mod,
});
b.installArtifact(exe);
- // Rum cmd
+ // Run cmd
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
diff --git a/examples/01-getting-started/hello-wave/build.zig.zon b/examples/01-getting-started/hello-wave/build.zig.zon
new file mode 100644
index 0000000..79c2910
--- /dev/null
+++ b/examples/01-getting-started/hello-wave/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .hello_wave,
+ .version = "0.1.0",
+ .fingerprint = 0x44bdec7cce7d03ec,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/01-getting-started/hello-wave/src/main.zig b/examples/01-getting-started/hello-wave/src/main.zig
new file mode 100644
index 0000000..0ee97ea
--- /dev/null
+++ b/examples/01-getting-started/hello-wave/src/main.zig
@@ -0,0 +1,66 @@
+//! # Hello Wave - Your First Audio with lightmix
+//!
+//! This is the simplest possible example using lightmix.
+//! It creates a 1-second sine wave at 440Hz (musical note A4) and saves it to a WAV file.
+//!
+//! ## What you'll learn:
+//! - How to generate audio samples
+//! - How to create a Wave from samples
+//! - How to save audio to a WAV file
+//!
+//! ## Run this example:
+//! ```
+//! zig build run
+//! ```
+//! This will create `result.wav` in the current directory.
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ // Use page allocator for simplicity
+ // In production, consider using a more sophisticated allocator
+ const allocator = std.heap.page_allocator;
+
+ // Generate a 440Hz sine wave (A4 note)
+ const frequency: f64 = 440.0;
+ const sample_rate: f64 = 44100.0;
+
+ // Calculate radians per second for sine wave generation
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ // Generate the audio samples
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ samples[i] = 0.5 * @sin(radians_per_sec * t); // 0.5 = volume control
+ }
+
+ // Create a Wave object from our samples
+ const wave = try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1, // Mono audio
+ });
+ defer wave.deinit();
+
+ // Save the wave to a WAV file
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ // Write as 16-bit integer PCM (most common format)
+ try wave.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Created result.wav - A 440Hz sine wave!\n", .{});
+ std.debug.print(" Duration: 1 second\n", .{});
+ std.debug.print(" Sample rate: 44100 Hz\n", .{});
+ std.debug.print(" Format: 16-bit mono\n", .{});
+}
diff --git a/examples/guitar/acoustic_guitar/build.zig b/examples/01-getting-started/using-filters/build.zig
similarity index 81%
rename from examples/guitar/acoustic_guitar/build.zig
rename to examples/01-getting-started/using-filters/build.zig
index eab9e81..c24971f 100644
--- a/examples/guitar/acoustic_guitar/build.zig
+++ b/examples/01-getting-started/using-filters/build.zig
@@ -4,42 +4,37 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
- // Dependencies
const lightmix = b.dependency("lightmix", .{});
- // Executable Module
const exe_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
});
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
- // Executable
const exe = b.addExecutable(.{
- .name = "acoustic_guitar",
+ .name = "using-filters",
.root_module = exe_mod,
});
b.installArtifact(exe);
- // Rum cmd
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
- // Run step
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
- // Unit tests
const exe_unit_tests = b.addTest(.{
.root_module = exe_mod,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
- // Test step
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}
diff --git a/examples/01-getting-started/using-filters/build.zig.zon b/examples/01-getting-started/using-filters/build.zig.zon
new file mode 100644
index 0000000..baf0591
--- /dev/null
+++ b/examples/01-getting-started/using-filters/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .using_filters,
+ .version = "0.1.0",
+ .fingerprint = 0x4ed3f39bf2fcbf80,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/01-getting-started/using-filters/src/main.zig b/examples/01-getting-started/using-filters/src/main.zig
new file mode 100644
index 0000000..b82a67a
--- /dev/null
+++ b/examples/01-getting-started/using-filters/src/main.zig
@@ -0,0 +1,84 @@
+//! # Using Filters - Transform Your Audio
+//!
+//! This example demonstrates how to use filters to transform audio waves.
+//! We'll create a sine wave and apply a decay filter to create a fade-out effect.
+//!
+//! ## What you'll learn:
+//! - How to create custom filter functions
+//! - How to apply filters to waves
+//! - How filter chaining works
+//!
+//! ## Run this example:
+//! ```
+//! zig build run
+//! ```
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate a 440Hz sine wave
+ const frequency: f64 = 440.0;
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ samples[i] = 0.5 * @sin(radians_per_sec * t);
+ }
+
+ var wave = try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ try wave.filter(decayFilter);
+ defer wave.deinit();
+
+ // Save the result
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try wave.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Created result.wav with fade-out effect!\n", .{});
+}
+
+/// Decay filter: Creates a linear fade-out effect
+/// The volume decreases from 100% to 0% over the duration of the wave
+fn decayFilter(comptime T: type, original_wave: Wave(T)) !Wave(T) {
+ var result_list: std.array_list.Aligned(T, null) = .empty;
+
+ // Process each sample, applying a decay factor
+ for (original_wave.samples, 0..) |sample, n| {
+ // Calculate how far from the end we are
+ const remaining_samples = original_wave.samples.len - n;
+
+ // Decay factor: 1.0 at start, 0.0 at end
+ const decay_factor = @as(T, @floatFromInt(remaining_samples)) /
+ @as(T, @floatFromInt(original_wave.samples.len));
+
+ // Apply the decay to the sample
+ const decayed_sample = sample * decay_factor;
+ try result_list.append(original_wave.allocator, decayed_sample);
+ }
+
+ // Return a new Wave with the filtered samples
+ return Wave(T){
+ .samples = try result_list.toOwnedSlice(original_wave.allocator),
+ .allocator = original_wave.allocator,
+ .sample_rate = original_wave.sample_rate,
+ .channels = original_wave.channels,
+ };
+}
diff --git a/examples/02-wave-basics/.gitignore b/examples/02-wave-basics/.gitignore
new file mode 100644
index 0000000..3fa78f0
--- /dev/null
+++ b/examples/02-wave-basics/.gitignore
@@ -0,0 +1,4 @@
+brown_noise.wav
+pink_noise.wav
+white_noise.wav
+result.wav
diff --git a/examples/Wave/generate_sinewave/build.zig b/examples/02-wave-basics/noise/build.zig
similarity index 80%
rename from examples/Wave/generate_sinewave/build.zig
rename to examples/02-wave-basics/noise/build.zig
index 88023bd..dc81f22 100644
--- a/examples/Wave/generate_sinewave/build.zig
+++ b/examples/02-wave-basics/noise/build.zig
@@ -4,42 +4,37 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
- // Dependencies
const lightmix = b.dependency("lightmix", .{});
- // Executable Module
const exe_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
});
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
- // Executable
const exe = b.addExecutable(.{
- .name = "generate_sinewave",
+ .name = "noise",
.root_module = exe_mod,
});
b.installArtifact(exe);
- // Rum cmd
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
- // Run step
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
- // Unit tests
const exe_unit_tests = b.addTest(.{
.root_module = exe_mod,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
- // Test step
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}
diff --git a/examples/02-wave-basics/noise/build.zig.zon b/examples/02-wave-basics/noise/build.zig.zon
new file mode 100644
index 0000000..abe3465
--- /dev/null
+++ b/examples/02-wave-basics/noise/build.zig.zon
@@ -0,0 +1,17 @@
+.{
+ .name = .noise,
+ .version = "0.1.0",
+ .fingerprint = 0xfda99629c4965348,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/02-wave-basics/noise/src/main.zig b/examples/02-wave-basics/noise/src/main.zig
new file mode 100644
index 0000000..5b6f2a5
--- /dev/null
+++ b/examples/02-wave-basics/noise/src/main.zig
@@ -0,0 +1,124 @@
+//! # Noise - Random Audio Signals
+//!
+//! This example demonstrates three types of noise commonly used in audio:
+//! - White Noise: Equal energy across all frequencies (like TV static)
+//! - Pink Noise: Equal energy per octave (more natural, like rain)
+//! - Brown Noise: Even more bass-heavy (like ocean waves)
+//!
+//! ## What you'll learn:
+//! - Using random number generation for audio
+//! - Different types of noise and their characteristics
+//! - Filtering techniques for colored noise
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate different types of noise
+ const white_noise = try generateWhiteNoise(allocator);
+ defer white_noise.deinit();
+
+ const pink_noise = try generatePinkNoise(allocator);
+ defer pink_noise.deinit();
+
+ const brown_noise = try generateBrownNoise(allocator);
+ defer brown_noise.deinit();
+
+ // Save each to a file
+ try saveWave(white_noise, "white_noise.wav", allocator);
+ try saveWave(pink_noise, "pink_noise.wav", allocator);
+ try saveWave(brown_noise, "brown_noise.wav", allocator);
+
+ std.debug.print("✓ Generated noise samples:\n", .{});
+ std.debug.print(" white_noise.wav - Equal energy across frequencies\n", .{});
+ std.debug.print(" pink_noise.wav - Equal energy per octave\n", .{});
+ std.debug.print(" brown_noise.wav - Bass-heavy noise\n", .{});
+}
+
+/// White noise: completely random values
+fn generateWhiteNoise(allocator: std.mem.Allocator) !Wave(f64) {
+ var prng = std.Random.DefaultPrng.init(0);
+ const rand = prng.random();
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ // Random value between -0.5 and +0.5
+ samples[i] = (rand.float(f64) * 2.0 - 1.0) * 0.5;
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
+
+/// Pink noise: filtered white noise with 1/f spectrum
+fn generatePinkNoise(allocator: std.mem.Allocator) !Wave(f64) {
+ var prng = std.Random.DefaultPrng.init(0);
+ const rand = prng.random();
+
+ // Paul Kellett's refined method for pink noise
+ var b0: f64 = 0.0;
+ var b1: f64 = 0.0;
+ var b2: f64 = 0.0;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const white = rand.float(f64) * 2.0 - 1.0;
+
+ b0 = 0.99765 * b0 + white * 0.0990460;
+ b1 = 0.96300 * b1 + white * 0.2965164;
+ b2 = 0.57000 * b2 + white * 1.0526913;
+
+ samples[i] = (b0 + b1 + b2 + white * 0.1848) * 0.15;
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
+
+/// Brown noise: integrated white noise (random walk)
+fn generateBrownNoise(allocator: std.mem.Allocator) !Wave(f64) {
+ var prng = std.Random.DefaultPrng.init(0);
+ const rand = prng.random();
+
+ var last_value: f64 = 0.0;
+ var samples: [44100]f64 = undefined;
+
+ for (0..samples.len) |i| {
+ // Add small random step to previous value
+ const step = (rand.float(f64) * 2.0 - 1.0) * 0.02;
+ last_value += step;
+
+ // Clamp to prevent drift
+ last_value = @max(-1.0, @min(1.0, last_value));
+
+ samples[i] = last_value * 0.5;
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
+
+fn saveWave(wave: Wave(f64), filename: []const u8, allocator: std.mem.Allocator) !void {
+ const file = try std.fs.cwd().createFile(filename, .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ // Write as 16-bit integer PCM (most common format)
+ try wave.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+}
diff --git a/examples/Wave/generate_soundless/build.zig b/examples/02-wave-basics/sawtooth-wave/build.zig
similarity index 80%
rename from examples/Wave/generate_soundless/build.zig
rename to examples/02-wave-basics/sawtooth-wave/build.zig
index 88023bd..6dc2a16 100644
--- a/examples/Wave/generate_soundless/build.zig
+++ b/examples/02-wave-basics/sawtooth-wave/build.zig
@@ -4,42 +4,37 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
- // Dependencies
const lightmix = b.dependency("lightmix", .{});
- // Executable Module
const exe_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
});
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
- // Executable
const exe = b.addExecutable(.{
- .name = "generate_sinewave",
+ .name = "sawtooth-wave",
.root_module = exe_mod,
});
b.installArtifact(exe);
- // Rum cmd
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
- // Run step
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
- // Unit tests
const exe_unit_tests = b.addTest(.{
.root_module = exe_mod,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
- // Test step
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}
diff --git a/examples/02-wave-basics/sawtooth-wave/build.zig.zon b/examples/02-wave-basics/sawtooth-wave/build.zig.zon
new file mode 100644
index 0000000..e4cdfb9
--- /dev/null
+++ b/examples/02-wave-basics/sawtooth-wave/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .sawtooth_wave,
+ .version = "0.1.0",
+ .fingerprint = 0xa494bbdff0765ae5,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/02-wave-basics/sawtooth-wave/src/main.zig b/examples/02-wave-basics/sawtooth-wave/src/main.zig
new file mode 100644
index 0000000..36fa34e
--- /dev/null
+++ b/examples/02-wave-basics/sawtooth-wave/src/main.zig
@@ -0,0 +1,51 @@
+//! # Sawtooth Wave - The Bright Sound
+//!
+//! A sawtooth wave has a linear rise and sharp fall, creating a bright, harsh sound.
+//! It's rich in both odd and even harmonics, making it useful for brass and string synthesis.
+//!
+//! ## What you'll learn:
+//! - How to generate a sawtooth wave using modulo operation
+//! - Understanding harmonic richness
+//! - Applications in synthesis
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate a 440Hz sawtooth wave
+ const frequency: f64 = 440.0;
+ const sample_rate: f64 = 44100.0;
+ const period: f64 = sample_rate / frequency;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ // Calculate position within the current period (0.0 to 1.0)
+ const phase = @mod(@as(f64, @floatFromInt(i)), period) / period;
+
+ // Sawtooth wave: Linear ramp from -1 to +1
+ samples[i] = (phase * 2.0) - 1.0;
+ }
+
+ const wave = try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
+
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ var writer = file.writer(buf);
+
+ try wave.write(.wav, &writer.interface, .{
+ .bits = 16,
+ .format_code = .pcm,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Generated sawtooth wave at 440 Hz\n", .{});
+}
diff --git a/examples/02-wave-basics/sine-wave/build.zig b/examples/02-wave-basics/sine-wave/build.zig
new file mode 100644
index 0000000..0814bc0
--- /dev/null
+++ b/examples/02-wave-basics/sine-wave/build.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "sine-wave",
+ .root_module = exe_mod,
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_module = exe_mod,
+ });
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/examples/02-wave-basics/sine-wave/build.zig.zon b/examples/02-wave-basics/sine-wave/build.zig.zon
new file mode 100644
index 0000000..a91b3ce
--- /dev/null
+++ b/examples/02-wave-basics/sine-wave/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .sine_wave,
+ .version = "0.1.0",
+ .fingerprint = 0x2230d3f0bce50a14,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/02-wave-basics/sine-wave/src/main.zig b/examples/02-wave-basics/sine-wave/src/main.zig
new file mode 100644
index 0000000..07fe272
--- /dev/null
+++ b/examples/02-wave-basics/sine-wave/src/main.zig
@@ -0,0 +1,55 @@
+//! # Sine Wave - The Pure Tone
+//!
+//! A sine wave is the most fundamental waveform in audio synthesis.
+//! It produces a pure tone with no harmonics, sounding very smooth and "electronic".
+//!
+//! ## What you'll learn:
+//! - Mathematical formula for sine wave generation
+//! - Relationship between frequency and pitch
+//! - How sample rate affects audio quality
+//!
+//! ## Musical note in this example:
+//! - A4: 440.0 Hz (standard tuning reference)
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate a 440Hz sine wave (A4 note)
+ const frequency: f64 = 440.0;
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+ const volume: f64 = 0.5;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ // Sine wave formula: amplitude * sin(2π * frequency * time)
+ samples[i] = volume * @sin(radians_per_sec * t);
+ }
+
+ const wave = try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
+
+ // Save to file
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try wave.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Generated sine wave at 440 Hz\n", .{});
+}
diff --git a/examples/02-wave-basics/square-wave/build.zig b/examples/02-wave-basics/square-wave/build.zig
new file mode 100644
index 0000000..2d6c65d
--- /dev/null
+++ b/examples/02-wave-basics/square-wave/build.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "square-wave",
+ .root_module = exe_mod,
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_module = exe_mod,
+ });
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/examples/02-wave-basics/square-wave/build.zig.zon b/examples/02-wave-basics/square-wave/build.zig.zon
new file mode 100644
index 0000000..3b5ec63
--- /dev/null
+++ b/examples/02-wave-basics/square-wave/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .square_wave,
+ .version = "0.1.0",
+ .fingerprint = 0xd1743863eb9f5d29,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/02-wave-basics/square-wave/src/main.zig b/examples/02-wave-basics/square-wave/src/main.zig
new file mode 100644
index 0000000..c634cb7
--- /dev/null
+++ b/examples/02-wave-basics/square-wave/src/main.zig
@@ -0,0 +1,56 @@
+//! # Square Wave - The Digital Sound
+//!
+//! A square wave alternates between two values (high and low) with sharp transitions.
+//! It produces a rich, buzzy sound with many odd harmonics, commonly used in retro video game music.
+//!
+//! ## What you'll learn:
+//! - How to generate a square wave
+//! - Understanding duty cycle (50% in this example)
+//! - The characteristic "buzzy" sound of square waves
+//!
+//! ## Sound characteristics:
+//! - Contains fundamental frequency + odd harmonics (3rd, 5th, 7th, etc.)
+//! - Sharp, electronic, "8-bit" sound
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate a 440Hz square wave
+ const frequency: f64 = 440.0;
+ const sample_rate: f64 = 44100.0;
+ const volume: f64 = 0.5;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ const phase = t * frequency;
+
+ // Square wave: +1 for first half of cycle, -1 for second half
+ // We use @mod to get the fractional part of the phase
+ samples[i] = if (@mod(phase, 1.0) < 0.5) volume else -volume;
+ }
+
+ const wave = try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
+
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ var writer = file.writer(buf);
+
+ try wave.write(.wav, &writer.interface, .{
+ .bits = 16,
+ .format_code = .pcm,
+ });
+
+ try writer.interface.flush();
+ std.debug.print("✓ Generated square wave at 440 Hz\n", .{});
+ std.debug.print(" Duty cycle: 50%\n", .{});
+}
diff --git a/examples/02-wave-basics/triangle-wave/build.zig b/examples/02-wave-basics/triangle-wave/build.zig
new file mode 100644
index 0000000..c6837be
--- /dev/null
+++ b/examples/02-wave-basics/triangle-wave/build.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "triangle-wave",
+ .root_module = exe_mod,
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_module = exe_mod,
+ });
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/examples/02-wave-basics/triangle-wave/build.zig.zon b/examples/02-wave-basics/triangle-wave/build.zig.zon
new file mode 100644
index 0000000..0da389e
--- /dev/null
+++ b/examples/02-wave-basics/triangle-wave/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .triangle_wave,
+ .version = "0.1.0",
+ .fingerprint = 0xb357e96b64517423,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/02-wave-basics/triangle-wave/src/main.zig b/examples/02-wave-basics/triangle-wave/src/main.zig
new file mode 100644
index 0000000..76b920c
--- /dev/null
+++ b/examples/02-wave-basics/triangle-wave/src/main.zig
@@ -0,0 +1,56 @@
+//! # Triangle Wave - The Mellow Sound
+//!
+//! A triangle wave rises and falls linearly, producing a mellow sound.
+//! It contains only odd harmonics (like a square wave), but they fall off faster,
+//! making it softer and rounder than a square wave.
+//!
+//! ## What you'll learn:
+//! - How to generate a triangle wave
+//! - Piecewise linear function implementation
+//! - Balancing between sine and square wave characteristics
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate a 440Hz triangle wave
+ const frequency: f64 = 440.0;
+ const sample_rate: f64 = 44100.0;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ const phase = @mod(t * frequency, 1.0);
+
+ // Triangle wave: rises from 0 to 1 in first half, falls from 1 to 0 in second half
+ // Transform to -1 to +1 range
+ samples[i] = if (phase < 0.5)
+ 4.0 * phase - 1.0 // Rising: -1 to +1
+ else
+ 3.0 - 4.0 * phase; // Falling: +1 to -1
+ }
+
+ const wave = try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
+
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try wave.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Generated triangle wave at 440 Hz\n", .{});
+}
diff --git a/examples/03-wave-operations/.gitignore b/examples/03-wave-operations/.gitignore
new file mode 100644
index 0000000..a4beede
--- /dev/null
+++ b/examples/03-wave-operations/.gitignore
@@ -0,0 +1,4 @@
+result.wav
+a3_octave_down.wav
+a4_original.wav
+a5_octave_up.wav
diff --git a/examples/03-wave-operations/filtering/build.zig b/examples/03-wave-operations/filtering/build.zig
new file mode 100644
index 0000000..8019df1
--- /dev/null
+++ b/examples/03-wave-operations/filtering/build.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "filtering",
+ .root_module = exe_mod,
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_module = exe_mod,
+ });
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/examples/03-wave-operations/filtering/build.zig.zon b/examples/03-wave-operations/filtering/build.zig.zon
new file mode 100644
index 0000000..df03954
--- /dev/null
+++ b/examples/03-wave-operations/filtering/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .filtering,
+ .version = "0.1.0",
+ .fingerprint = 0x253bec01838b0c59,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/03-wave-operations/filtering/src/main.zig b/examples/03-wave-operations/filtering/src/main.zig
new file mode 100644
index 0000000..2668377
--- /dev/null
+++ b/examples/03-wave-operations/filtering/src/main.zig
@@ -0,0 +1,88 @@
+//! # Filtering - Apply Multiple Transformations
+//!
+//! This example shows how to chain multiple filters together to transform audio.
+//! We'll create a sine wave and apply decay, volume reduction, and distortion filters.
+//!
+//! ## What you'll learn:
+//! - How to chain multiple filters
+//! - Creating different types of audio effects
+//! - Understanding filter composition
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate a sine wave
+ const frequency: f64 = 440.0;
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ samples[i] = 0.8 * @sin(radians_per_sec * t);
+ }
+
+ var wave = try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ try wave.filter(decayFilter); // Apply fade-out
+ try wave.filter(halveSampleValuesFilter); // Reduce volume
+ defer wave.deinit();
+
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try wave.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Applied multiple filters:\n", .{});
+ std.debug.print(" 1. Decay (fade-out)\n", .{});
+ std.debug.print(" 2. Volume reduction (50%)\n", .{});
+}
+
+/// Decay filter: Linear fade-out effect
+fn decayFilter(comptime T: type, original_wave: Wave(T)) !Wave(T) {
+ var result_list: std.array_list.Aligned(T, null) = .empty;
+
+ for (original_wave.samples, 0..) |sample, n| {
+ const remaining = original_wave.samples.len - n;
+ const decay_factor = @as(T, @floatFromInt(remaining)) /
+ @as(T, @floatFromInt(original_wave.samples.len));
+ try result_list.append(original_wave.allocator, sample * decay_factor);
+ }
+
+ return Wave(T){
+ .samples = try result_list.toOwnedSlice(original_wave.allocator),
+ .allocator = original_wave.allocator,
+ .sample_rate = original_wave.sample_rate,
+ .channels = original_wave.channels,
+ };
+}
+
+/// Volume reduction filter: Halve all sample values
+fn halveSampleValuesFilter(comptime T: type, original_wave: Wave(T)) !Wave(T) {
+ var result_list: std.array_list.Aligned(T, null) = .empty;
+
+ for (original_wave.samples) |sample| {
+ try result_list.append(original_wave.allocator, sample * 0.5);
+ }
+
+ return Wave(T){
+ .samples = try result_list.toOwnedSlice(original_wave.allocator),
+ .allocator = original_wave.allocator,
+ .sample_rate = original_wave.sample_rate,
+ .channels = original_wave.channels,
+ };
+}
diff --git a/examples/03-wave-operations/frequency-changes/build.zig b/examples/03-wave-operations/frequency-changes/build.zig
new file mode 100644
index 0000000..d1b538d
--- /dev/null
+++ b/examples/03-wave-operations/frequency-changes/build.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "frequency-changes",
+ .root_module = exe_mod,
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_module = exe_mod,
+ });
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/examples/03-wave-operations/frequency-changes/build.zig.zon b/examples/03-wave-operations/frequency-changes/build.zig.zon
new file mode 100644
index 0000000..165fb55
--- /dev/null
+++ b/examples/03-wave-operations/frequency-changes/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .frequency_changes,
+ .version = "0.1.0",
+ .fingerprint = 0x1330bff611602aa,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/03-wave-operations/frequency-changes/src/main.zig b/examples/03-wave-operations/frequency-changes/src/main.zig
new file mode 100644
index 0000000..a2fa561
--- /dev/null
+++ b/examples/03-wave-operations/frequency-changes/src/main.zig
@@ -0,0 +1,70 @@
+//! # Frequency Changes - Pitch Shifting
+//!
+//! This example demonstrates techniques for changing the pitch of audio:
+//! - Doubling frequency (one octave up)
+//! - Halving frequency (one octave down)
+//!
+//! ## What you'll learn:
+//! - How frequency relates to pitch
+//! - Simple pitch shifting techniques
+//! - Octave relationships in music
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate original 440Hz sine wave (A4)
+ const original = try generateSineWave(440.0, allocator);
+ defer original.deinit();
+
+ // One octave higher (880Hz - A5)
+ const octave_up = try generateSineWave(880.0, allocator);
+ defer octave_up.deinit();
+
+ // One octave lower (220Hz - A3)
+ const octave_down = try generateSineWave(220.0, allocator);
+ defer octave_down.deinit();
+
+ try saveWave(original, "a4_original.wav", allocator);
+ try saveWave(octave_up, "a5_octave_up.wav", allocator);
+ try saveWave(octave_down, "a3_octave_down.wav", allocator);
+
+ std.debug.print("✓ Generated frequency variations:\n", .{});
+ std.debug.print(" a4_original.wav - 440 Hz (A4)\n", .{});
+ std.debug.print(" a5_octave_up.wav - 880 Hz (A5, +1 octave)\n", .{});
+ std.debug.print(" a3_octave_down.wav - 220 Hz (A3, -1 octave)\n", .{});
+}
+
+fn generateSineWave(frequency: f64, allocator: std.mem.Allocator) !Wave(f64) {
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ samples[i] = 0.5 * @sin(radians_per_sec * t);
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
+
+fn saveWave(wave: Wave(f64), filename: []const u8, allocator: std.mem.Allocator) !void {
+ const file = try std.fs.cwd().createFile(filename, .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try wave.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+}
diff --git a/examples/03-wave-operations/mixing-waves/build.zig b/examples/03-wave-operations/mixing-waves/build.zig
new file mode 100644
index 0000000..4d29c8a
--- /dev/null
+++ b/examples/03-wave-operations/mixing-waves/build.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "mixing-waves",
+ .root_module = exe_mod,
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_module = exe_mod,
+ });
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/examples/03-wave-operations/mixing-waves/build.zig.zon b/examples/03-wave-operations/mixing-waves/build.zig.zon
new file mode 100644
index 0000000..719460c
--- /dev/null
+++ b/examples/03-wave-operations/mixing-waves/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .mixing_waves,
+ .version = "0.1.0",
+ .fingerprint = 0xccd58c58e459b785,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/03-wave-operations/mixing-waves/src/main.zig b/examples/03-wave-operations/mixing-waves/src/main.zig
new file mode 100644
index 0000000..74a2892
--- /dev/null
+++ b/examples/03-wave-operations/mixing-waves/src/main.zig
@@ -0,0 +1,74 @@
+//! # Mixing Waves - Combining Multiple Audio Sources
+//!
+//! This example demonstrates how to mix (combine) multiple waves together.
+//! We'll create a simple musical chord by mixing three sine waves at different frequencies.
+//!
+//! ## What you'll learn:
+//! - How to use Wave.mix() to combine waves
+//! - Creating musical chords from multiple frequencies
+//! - Understanding additive synthesis
+//!
+//! ## Musical theory:
+//! We'll create a C major chord (C4-E4-G4):
+//! - C4: 261.63 Hz (root)
+//! - E4: 329.63 Hz (major third)
+//! - G4: 392.00 Hz (perfect fifth)
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate three notes of a C major chord
+ const c4 = try generateSineWave(261.63, 0.3, allocator); // C4 - root
+ defer c4.deinit();
+ const e4 = try generateSineWave(329.63, 0.3, allocator); // E4 - major third
+ defer e4.deinit();
+ const g4 = try generateSineWave(392.00, 0.3, allocator); // G4 - perfect fifth
+ defer g4.deinit();
+
+ // Mix the first two notes
+ const c_e_mix = try c4.mix(e4, .{});
+ defer c_e_mix.deinit();
+
+ // Mix in the third note to complete the chord
+ const chord = try c_e_mix.mix(g4, .{});
+ defer chord.deinit();
+
+ // Save the result
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try chord.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Created C major chord (C4-E4-G4)\n", .{});
+ std.debug.print(" C4: 261.63 Hz\n", .{});
+ std.debug.print(" E4: 329.63 Hz\n", .{});
+ std.debug.print(" G4: 392.00 Hz\n", .{});
+}
+
+fn generateSineWave(frequency: f64, volume: f64, allocator: std.mem.Allocator) !Wave(f64) {
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ samples[i] = volume * @sin(radians_per_sec * t);
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
diff --git a/examples/Composer/compose_multipul_soundless/.gitignore b/examples/04-composer/.gitignore
similarity index 100%
rename from examples/Composer/compose_multipul_soundless/.gitignore
rename to examples/04-composer/.gitignore
diff --git a/examples/04-composer/overlapping-sounds/build.zig b/examples/04-composer/overlapping-sounds/build.zig
new file mode 100644
index 0000000..9bd713d
--- /dev/null
+++ b/examples/04-composer/overlapping-sounds/build.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "overlapping-sounds",
+ .root_module = exe_mod,
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_module = exe_mod,
+ });
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/examples/04-composer/overlapping-sounds/build.zig.zon b/examples/04-composer/overlapping-sounds/build.zig.zon
new file mode 100644
index 0000000..3a10065
--- /dev/null
+++ b/examples/04-composer/overlapping-sounds/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .overlapping_sounds,
+ .version = "0.1.0",
+ .fingerprint = 0x1c9cd58a6b23253,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/04-composer/overlapping-sounds/src/main.zig b/examples/04-composer/overlapping-sounds/src/main.zig
new file mode 100644
index 0000000..108fa98
--- /dev/null
+++ b/examples/04-composer/overlapping-sounds/src/main.zig
@@ -0,0 +1,98 @@
+//! # Overlapping Sounds - Polyphony with Composer
+//!
+//! This example shows how to use Composer to create overlapping sounds,
+//! simulating polyphonic music where multiple notes play simultaneously.
+//!
+//! ## What you'll learn:
+//! - Creating overlapping audio with Composer
+//! - Understanding polyphony vs monophony
+//! - Building more complex arrangements
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+const Composer = lightmix.Composer;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ var composer = Composer(f64).init(allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer composer.deinit();
+
+ // Create a longer sustaining note
+ const long_c = try generateLongNote(261.63, allocator);
+ defer long_c.deinit();
+
+ // Create shorter melody notes
+ const e4 = try generateNote(329.63, allocator);
+ defer e4.deinit();
+ const g4 = try generateNote(392.00, allocator);
+ defer g4.deinit();
+
+ // Arrange with overlaps: bass note holds while melody plays
+ var arrangement: std.array_list.Aligned(Composer(f64).WaveInfo, null) = .empty;
+ defer arrangement.deinit(allocator);
+
+ try arrangement.append(allocator, .{ .wave = long_c, .start_point = 0 }); // Bass starts at 0
+ try arrangement.append(allocator, .{ .wave = e4, .start_point = 0 }); // Melody starts with bass
+ try arrangement.append(allocator, .{ .wave = g4, .start_point = 22050 }); // Second melody note at 0.5s
+
+ try composer.appendSlice(arrangement.items);
+
+ const result = try composer.finalize(.{});
+ defer result.deinit();
+
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try result.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Created overlapping arrangement\n", .{});
+}
+
+fn generateNote(frequency: f64, allocator: std.mem.Allocator) !Wave(f64) {
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [22050]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ const envelope = 1.0 - (t / 0.5);
+ samples[i] = 0.2 * @sin(radians_per_sec * t) * envelope;
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
+
+fn generateLongNote(frequency: f64, allocator: std.mem.Allocator) !Wave(f64) {
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [66150]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ const envelope = 1.0 - (t / 1.5);
+ samples[i] = 0.15 * @sin(radians_per_sec * t) * envelope;
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
diff --git a/examples/04-composer/simple-sequence/build.zig b/examples/04-composer/simple-sequence/build.zig
new file mode 100644
index 0000000..2b97d4c
--- /dev/null
+++ b/examples/04-composer/simple-sequence/build.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "simple-sequence",
+ .root_module = exe_mod,
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_module = exe_mod,
+ });
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/examples/04-composer/simple-sequence/build.zig.zon b/examples/04-composer/simple-sequence/build.zig.zon
new file mode 100644
index 0000000..0e8c253
--- /dev/null
+++ b/examples/04-composer/simple-sequence/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .simple_sequence,
+ .version = "0.1.0",
+ .fingerprint = 0x78a3b5a1fd29c606,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/04-composer/simple-sequence/src/main.zig b/examples/04-composer/simple-sequence/src/main.zig
new file mode 100644
index 0000000..bb38176
--- /dev/null
+++ b/examples/04-composer/simple-sequence/src/main.zig
@@ -0,0 +1,84 @@
+//! # Simple Sequence - Using Composer
+//!
+//! This example introduces the Composer, which allows you to arrange multiple
+//! waves in time. We'll create a simple melody by sequencing sine waves.
+//!
+//! ## What you'll learn:
+//! - How to use the Composer API
+//! - Sequencing waves with start_point
+//! - Creating simple melodies
+//!
+//! ## The melody:
+//! We'll play C-D-E-C in sequence (first 4 notes of "Frère Jacques")
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+const Composer = lightmix.Composer;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ // Create composer
+ var composer = Composer(f64).init(allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer composer.deinit();
+
+ // Generate notes (quarter notes at 120 BPM = 0.5 seconds each)
+ const c4 = try generateNote(261.63, allocator); // C4
+ defer c4.deinit();
+ const d4 = try generateNote(293.66, allocator); // D4
+ defer d4.deinit();
+ const e4 = try generateNote(329.63, allocator); // E4
+ defer e4.deinit();
+
+ // Arrange notes in sequence
+ var notes_list: std.array_list.Aligned(Composer(f64).WaveInfo, null) = .empty;
+ defer notes_list.deinit(allocator);
+
+ try notes_list.append(allocator, .{ .wave = c4, .start_point = 0 }); // 0.0s
+ try notes_list.append(allocator, .{ .wave = d4, .start_point = 22050 }); // 0.5s
+ try notes_list.append(allocator, .{ .wave = e4, .start_point = 44100 }); // 1.0s
+ try notes_list.append(allocator, .{ .wave = c4, .start_point = 66150 }); // 1.5s
+
+ try composer.appendSlice(notes_list.items);
+
+ const result = try composer.finalize(.{});
+ defer result.deinit();
+
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try result.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Created simple melody: C-D-E-C\n", .{});
+}
+
+fn generateNote(frequency: f64, allocator: std.mem.Allocator) !Wave(f64) {
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [22050]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ const envelope = 1.0 - (t / 0.5); // Simple decay
+ samples[i] = 0.3 * @sin(radians_per_sec * t) * envelope;
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
diff --git a/examples/05-practical-examples/.gitignore b/examples/05-practical-examples/.gitignore
new file mode 100644
index 0000000..6fddcdb
--- /dev/null
+++ b/examples/05-practical-examples/.gitignore
@@ -0,0 +1,3 @@
+result.wav
+guitar_a2.wav
+guitar_e2.wav
diff --git a/examples/05-practical-examples/drum/build.zig b/examples/05-practical-examples/drum/build.zig
new file mode 100644
index 0000000..bf7ed42
--- /dev/null
+++ b/examples/05-practical-examples/drum/build.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "drum",
+ .root_module = exe_mod,
+ });
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_module = exe_mod,
+ });
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/examples/05-practical-examples/drum/build.zig.zon b/examples/05-practical-examples/drum/build.zig.zon
new file mode 100644
index 0000000..b84a698
--- /dev/null
+++ b/examples/05-practical-examples/drum/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .drum,
+ .version = "0.1.0",
+ .fingerprint = 0xa33e93205174b58f,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/05-practical-examples/drum/src/main.zig b/examples/05-practical-examples/drum/src/main.zig
new file mode 100644
index 0000000..6f354ea
--- /dev/null
+++ b/examples/05-practical-examples/drum/src/main.zig
@@ -0,0 +1,130 @@
+//! # Drum Synthesis - Snare Drum
+//!
+//! This example creates a snare drum sound by combining:
+//! - Pink noise (for the "snare" wire rattle)
+//! - Sine wave (for the drum shell resonance)
+//! - Decay envelopes (for the percussive attack)
+//!
+//! ## What you'll learn:
+//! - Combining different sound sources for synthesis
+//! - Using noise for percussion sounds
+//! - Envelope shaping for realistic drums
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ const snare_wave = try generateSnare(allocator);
+ defer snare_wave.deinit();
+
+ const file = try std.fs.cwd().createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try snare_wave.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ std.debug.print("✓ Generated snare drum sound\n", .{});
+}
+
+fn generateSnare(allocator: std.mem.Allocator) !Wave(f64) {
+ // Generate pink noise for snare wires
+ var noise: Wave(f64) = try generatePinkNoise(allocator);
+ // Apply aggressive decay to noise
+ try noise.filter(fastDecayFilter);
+ try noise.filter(fastDecayFilter);
+ try noise.filter(fastDecayFilter);
+ defer noise.deinit();
+
+ // Generate low sine for drum body
+ var tone = try generateDrumTone(allocator);
+ // Apply decay to tone
+ try tone.filter(fastDecayFilter);
+ try tone.filter(halveSampleValuesFilter);
+ defer tone.deinit();
+
+ // Mix together
+ return noise.mix(tone, .{});
+}
+
+fn generatePinkNoise(allocator: std.mem.Allocator) !Wave(f64) {
+ var prng = std.Random.DefaultPrng.init(0);
+ const rand = prng.random();
+
+ var b0: f64 = 0.0;
+ var b1: f64 = 0.0;
+ var b2: f64 = 0.0;
+
+ var samples: [22050]f64 = undefined; // 0.5 seconds
+ for (0..samples.len) |i| {
+ const white = rand.float(f64) * 2.0 - 1.0;
+ b0 = 0.99765 * b0 + white * 0.0990460;
+ b1 = 0.96300 * b1 + white * 0.2965164;
+ b2 = 0.57000 * b2 + white * 1.0526913;
+ samples[i] = (b0 + b1 + b2 + white * 0.1848) * 0.3;
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
+
+fn generateDrumTone(allocator: std.mem.Allocator) !Wave(f64) {
+ const frequency: f64 = 200.0; // Low frequency for drum body
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [22050]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ samples[i] = 0.4 * @sin(radians_per_sec * t);
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
+
+fn fastDecayFilter(comptime T: type, original_wave: Wave(T)) !Wave(T) {
+ var result_list: std.array_list.Aligned(T, null) = .empty;
+
+ for (original_wave.samples, 0..) |sample, n| {
+ const remaining = original_wave.samples.len - n;
+ const decay_factor = @as(T, @floatFromInt(remaining)) /
+ @as(T, @floatFromInt(original_wave.samples.len));
+ try result_list.append(original_wave.allocator, sample * decay_factor);
+ }
+
+ return Wave(T){
+ .samples = try result_list.toOwnedSlice(original_wave.allocator),
+ .allocator = original_wave.allocator,
+ .sample_rate = original_wave.sample_rate,
+ .channels = original_wave.channels,
+ };
+}
+
+fn halveSampleValuesFilter(comptime T: type, original_wave: Wave(T)) !Wave(T) {
+ var result_list: std.array_list.Aligned(T, null) = .empty;
+
+ for (original_wave.samples) |sample| {
+ try result_list.append(original_wave.allocator, sample * 0.5);
+ }
+
+ return Wave(T){
+ .samples = try result_list.toOwnedSlice(original_wave.allocator),
+ .allocator = original_wave.allocator,
+ .sample_rate = original_wave.sample_rate,
+ .channels = original_wave.channels,
+ };
+}
diff --git a/examples/drum/snare_drum/build.zig b/examples/05-practical-examples/guitar/build.zig
similarity index 86%
rename from examples/drum/snare_drum/build.zig
rename to examples/05-practical-examples/guitar/build.zig
index e6fbabc..36ae2bb 100644
--- a/examples/drum/snare_drum/build.zig
+++ b/examples/05-practical-examples/guitar/build.zig
@@ -4,10 +4,8 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
- // Dependencies
const lightmix = b.dependency("lightmix", .{});
- // Executable Module
const exe_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
@@ -15,31 +13,26 @@ pub fn build(b: *std.Build) void {
});
exe_mod.addImport("lightmix", lightmix.module("lightmix"));
- // Executable
const exe = b.addExecutable(.{
- .name = "snare_drum",
+ .name = "guitar",
.root_module = exe_mod,
});
b.installArtifact(exe);
- // Rum cmd
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
- // Run step
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
- // Unit tests
const exe_unit_tests = b.addTest(.{
.root_module = exe_mod,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
- // Test step
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}
diff --git a/examples/05-practical-examples/guitar/build.zig.zon b/examples/05-practical-examples/guitar/build.zig.zon
new file mode 100644
index 0000000..c0d8782
--- /dev/null
+++ b/examples/05-practical-examples/guitar/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .guitar,
+ .version = "0.1.0",
+ .fingerprint = 0x423ac30d2a62da7e,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/05-practical-examples/guitar/src/main.zig b/examples/05-practical-examples/guitar/src/main.zig
new file mode 100644
index 0000000..c24d3d0
--- /dev/null
+++ b/examples/05-practical-examples/guitar/src/main.zig
@@ -0,0 +1,87 @@
+//! # Guitar Synthesis - Karplus-Strong Algorithm
+//!
+//! This example demonstrates the Karplus-Strong algorithm for plucked string synthesis.
+//! It simulates a guitar-like sound by using noise excitation and feedback delay.
+//!
+//! ## What you'll learn:
+//! - Physical modeling synthesis
+//! - The Karplus-Strong algorithm
+//! - Creating realistic instrument sounds
+//!
+//! ## How it works:
+//! 1. Start with noise in a delay buffer
+//! 2. Average adjacent samples and apply decay
+//! 3. Feed back into the buffer
+//! This creates a pitched, decaying sound like a plucked string!
+
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate guitar notes at different frequencies
+ const e2 = try generateGuitarNote(82.41, allocator); // Low E string
+ defer e2.deinit();
+
+ const a2 = try generateGuitarNote(110.00, allocator); // A string
+ defer a2.deinit();
+
+ try saveWave(e2, "guitar_e2.wav", allocator);
+ try saveWave(a2, "guitar_a2.wav", allocator);
+
+ std.debug.print("✓ Generated guitar sounds:\n", .{});
+ std.debug.print(" guitar_e2.wav - Low E string (82.41 Hz)\n", .{});
+ std.debug.print(" guitar_a2.wav - A string (110.00 Hz)\n", .{});
+}
+
+/// Karplus-Strong guitar synthesis
+fn generateGuitarNote(frequency: f64, allocator: std.mem.Allocator) !Wave(f64) {
+ const sample_rate: f64 = 44100.0;
+ const decay: f64 = 0.996; // Decay factor (close to 1 = longer sustain)
+
+ var result: [88200]f64 = undefined;
+
+ // Calculate period in samples
+ const period = @as(usize, @intFromFloat(sample_rate / frequency));
+
+ // Initialize delay buffer with noise
+ var buffer: [2000]f64 = undefined;
+ var prng = std.Random.DefaultPrng.init(0);
+ const rand = prng.random();
+
+ for (buffer[0..period]) |*val| {
+ val.* = rand.float(f64) * 2.0 - 1.0;
+ }
+
+ // Karplus-Strong loop: average adjacent samples with decay
+ var idx: usize = 0;
+ for (0..result.len) |i| {
+ const next_idx = (idx + 1) % period;
+ const avg = (buffer[idx] + buffer[next_idx]) * 0.5 * decay;
+ buffer[idx] = avg;
+ result[i] = avg * 0.8; // Volume adjustment
+ idx = next_idx;
+ }
+
+ return try Wave(f64).init(result[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
+
+fn saveWave(wave: Wave(f64), filename: []const u8, allocator: std.mem.Allocator) !void {
+ const file = try std.fs.cwd().createFile(filename, .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ try wave.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+}
diff --git a/examples/06-advanced/build-time-generation/build.zig b/examples/06-advanced/build-time-generation/build.zig
new file mode 100644
index 0000000..3b725a8
--- /dev/null
+++ b/examples/06-advanced/build-time-generation/build.zig
@@ -0,0 +1,38 @@
+const std = @import("std");
+const l = @import("lightmix");
+
+pub fn build(b: *std.Build) !void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ // Wave Installation
+ const wave = try l.addWave(b, mod, .{
+ // .func_name = "gen", // The default value of func_name is "gen"
+ .format = .{ .wav = .{
+ .bits = 16,
+ .format_code = .pcm,
+ } },
+ });
+ l.installWave(b, wave);
+
+ // Unit tests
+ const unit_tests = b.addTest(.{
+ .root_module = mod,
+ });
+ const run_unit_tests = b.addRunArtifact(unit_tests);
+
+ // Test step
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_unit_tests.step);
+}
diff --git a/examples/06-advanced/build-time-generation/build.zig.zon b/examples/06-advanced/build-time-generation/build.zig.zon
new file mode 100644
index 0000000..40a40aa
--- /dev/null
+++ b/examples/06-advanced/build-time-generation/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .build_time_generation,
+ .version = "0.1.0",
+ .fingerprint = 0xa66d0dedc977e4d5,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/06-advanced/build-time-generation/src/main.zig b/examples/06-advanced/build-time-generation/src/main.zig
new file mode 100644
index 0000000..215911e
--- /dev/null
+++ b/examples/06-advanced/build-time-generation/src/main.zig
@@ -0,0 +1,37 @@
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn gen(allocator: std.mem.Allocator) !Wave(f64) {
+ // Generate a chord using const-compatible patterns
+ const c5 = try generateSineWave(523.25, allocator); // C5
+ defer c5.deinit();
+ const e5 = try generateSineWave(659.25, allocator); // E5
+ defer e5.deinit();
+ const g5 = try generateSineWave(783.99, allocator); // G5
+ defer g5.deinit();
+
+ // Mix the three notes
+ const ce_mix = try c5.mix(e5, .{});
+ defer ce_mix.deinit();
+
+ const chord = try ce_mix.mix(g5, .{});
+
+ return chord;
+}
+
+fn generateSineWave(frequency: f64, allocator: std.mem.Allocator) !Wave(f64) {
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ samples[i] = 0.25 * @sin(radians_per_sec * t);
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
diff --git a/examples/06-advanced/build-time-play/build.zig b/examples/06-advanced/build-time-play/build.zig
new file mode 100644
index 0000000..3c5e1f5
--- /dev/null
+++ b/examples/06-advanced/build-time-play/build.zig
@@ -0,0 +1,47 @@
+const std = @import("std");
+const l = @import("lightmix");
+
+pub fn build(b: *std.Build) !void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+
+ const mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
+ });
+
+ if (target.result.os.tag == .linux) {
+ mod.linkSystemLibrary("alsa", .{});
+ mod.linkSystemLibrary("libpulse", .{});
+ mod.linkSystemLibrary("libpipewire-0.3", .{});
+ }
+
+ const wave = try l.addWave(b, mod, .{
+ // .func_name = "gen", // The default value of func_name is "gen"
+ .format = .{ .wav = .{
+ .bits = 16,
+ .format_code = .pcm,
+ } },
+ });
+ l.installWave(b, wave);
+
+ const play = try l.addPlay(b, wave, .{});
+ const play_step = b.step("play", "Play produced Wave file");
+ play_step.dependOn(&play.step);
+
+ // Unit tests
+ const unit_tests = b.addTest(.{
+ .root_module = mod,
+ });
+ const run_unit_tests = b.addRunArtifact(unit_tests);
+
+ // Test step
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_unit_tests.step);
+}
diff --git a/examples/06-advanced/build-time-play/build.zig.zon b/examples/06-advanced/build-time-play/build.zig.zon
new file mode 100644
index 0000000..ec82c9a
--- /dev/null
+++ b/examples/06-advanced/build-time-play/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .build_time_play,
+ .version = "0.1.0",
+ .fingerprint = 0x4ab620c4c90e0f63,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{
+ .path = "../../..",
+ },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/06-advanced/build-time-play/src/main.zig b/examples/06-advanced/build-time-play/src/main.zig
new file mode 100644
index 0000000..215911e
--- /dev/null
+++ b/examples/06-advanced/build-time-play/src/main.zig
@@ -0,0 +1,37 @@
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn gen(allocator: std.mem.Allocator) !Wave(f64) {
+ // Generate a chord using const-compatible patterns
+ const c5 = try generateSineWave(523.25, allocator); // C5
+ defer c5.deinit();
+ const e5 = try generateSineWave(659.25, allocator); // E5
+ defer e5.deinit();
+ const g5 = try generateSineWave(783.99, allocator); // G5
+ defer g5.deinit();
+
+ // Mix the three notes
+ const ce_mix = try c5.mix(e5, .{});
+ defer ce_mix.deinit();
+
+ const chord = try ce_mix.mix(g5, .{});
+
+ return chord;
+}
+
+fn generateSineWave(frequency: f64, allocator: std.mem.Allocator) !Wave(f64) {
+ const sample_rate: f64 = 44100.0;
+ const radians_per_sec: f64 = frequency * 2.0 * std.math.pi;
+
+ var samples: [44100]f64 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ samples[i] = 0.25 * @sin(radians_per_sec * t);
+ }
+
+ return try Wave(f64).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+}
diff --git a/examples/06-advanced/modular-composing/README.md b/examples/06-advanced/modular-composing/README.md
new file mode 100644
index 0000000..b5c2ef9
--- /dev/null
+++ b/examples/06-advanced/modular-composing/README.md
@@ -0,0 +1,91 @@
+# Modular Composing Example
+
+This example demonstrates how to generate audio using a modular architecture with lightmix.
+
+## Overview
+
+This project shows how to separate music theory and synthesis concepts into independent packages and combine them to generate audio.
+
+- **temperaments**: Package defining tuning systems
+- **synths**: Package providing audio synthesis engines
+
+By combining these packages, you can build a flexible and maintainable audio generation system.
+
+## Project Structure
+
+```
+modular-composing/
+├── build.zig # Build configuration
+├── build.zig.zon # Dependency definitions
+├── src/
+│ └── root.zig # Main entry point
+└── packages/
+ ├── temperaments/ # Tuning system package
+ │ ├── src/
+ │ │ ├── root.zig
+ │ │ └── twelve_equal_temperament.zig
+ │ ├── build.zig
+ │ └── build.zig.zon
+ └── synths/ # Synthesizer package
+ ├── src/
+ │ ├── synths.zig
+ │ └── synths/
+ │ └── sine.zig
+ ├── build.zig
+ └── build.zig.zon
+```
+
+## Package Descriptions
+
+### temperaments
+
+Provides tuning systems (scale tuning systems). Currently implements Twelve Equal Temperament.
+
+Key features:
+
+- Convert MIDI numbers to frequencies
+- Manipulate pitches by semitones
+- Manage note names and octaves
+
+### synths
+
+Provides synthesizer engines. Currently implements a sine wave oscillator.
+
+Key features:
+
+- Generate sine waves at specified pitches
+- Customize sample rate and channel count
+
+## Usage
+
+Run the following command in this directory to generate a `result.wav` file:
+
+```bash
+zig build
+```
+
+The generated file is an audio file containing a sine wave playing C4 (middle C) for 1 second.
+
+## Extension Methods
+
+This example can be extended in the following ways:
+
+1. **Add new tuning systems**: Add new tuning systems to the `temperaments` package (e.g., just intonation, Pythagorean tuning)
+1. **Add new synthesizers**: Add new waveforms to the `synths` package (e.g., square wave, sawtooth wave, triangle wave)
+1. **Create complex music**: Combine multiple notes to create chords and melodies
+
+## Technical Features
+
+- **Modular design**: Each feature is implemented as an independent package
+- **Leverage Zig build system**: Manage package dependencies with `build.zig`
+- **Type safety**: Safe implementation leveraging Zig's powerful type system
+- **Build-time generation**: Generate WAV files at compile time
+
+## Requirements
+
+- Zig 0.15.2 or later
+- lightmix library
+
+## License
+
+This project is part of the lightmix project and is subject to the same license.
diff --git a/examples/06-advanced/modular-composing/build.zig b/examples/06-advanced/modular-composing/build.zig
new file mode 100644
index 0000000..17fe2bb
--- /dev/null
+++ b/examples/06-advanced/modular-composing/build.zig
@@ -0,0 +1,50 @@
+const std = @import("std");
+const l = @import("lightmix");
+
+pub fn build(b: *std.Build) !void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+ const synths = b.dependency("synths", .{});
+ const temperaments = b.dependency("temperaments", .{});
+
+ const mod = b.addModule("modular-composing", .{
+ .root_source_file = b.path("src/root.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ .{ .name = "synths", .module = synths.module("synths") },
+ .{ .name = "temperaments", .module = temperaments.module("temperaments") },
+ },
+ });
+
+ // Library linking on Linux
+ if (target.result.os.tag == .linux) {
+ mod.linkSystemLibrary("alsa", .{});
+ mod.linkSystemLibrary("libpulse", .{});
+ mod.linkSystemLibrary("libpipewire-0.3", .{});
+ }
+
+ const wave = try l.addWave(b, mod, .{
+ .func_name = "gen",
+ .format = .{ .wav = .{
+ .bits = 16,
+ .format_code = .pcm,
+ } },
+ });
+ l.installWave(b, wave);
+
+ const play_step = b.step("play", "Play the produced Wave file");
+ const play = try l.addPlay(b, wave, .{});
+ play_step.dependOn(&play.step);
+
+ const mod_tests = b.addTest(.{
+ .root_module = mod,
+ });
+ const run_mod_tests = b.addRunArtifact(mod_tests);
+
+ const test_step = b.step("test", "Run tests");
+ test_step.dependOn(&run_mod_tests.step);
+}
diff --git a/examples/06-advanced/modular-composing/build.zig.zon b/examples/06-advanced/modular-composing/build.zig.zon
new file mode 100644
index 0000000..35dc993
--- /dev/null
+++ b/examples/06-advanced/modular-composing/build.zig.zon
@@ -0,0 +1,18 @@
+.{
+ .name = .modular_composing,
+ .version = "0.0.0",
+ .fingerprint = 0xf05ee580c12b42d4,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{ .path = "../../.." },
+ .synths = .{ .path = "packages/synths" },
+ .temperaments = .{ .path = "packages/temperaments" },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/06-advanced/modular-composing/packages/synths/build.zig b/examples/06-advanced/modular-composing/packages/synths/build.zig
new file mode 100644
index 0000000..5d041dd
--- /dev/null
+++ b/examples/06-advanced/modular-composing/packages/synths/build.zig
@@ -0,0 +1,35 @@
+const std = @import("std");
+const l = @import("lightmix");
+
+pub fn build(b: *std.Build) !void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lightmix = b.dependency("lightmix", .{});
+ const temperaments = b.dependency("temperaments", .{});
+
+ const mod = b.addModule("synths", .{
+ .root_source_file = b.path("src/synths.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ .{ .name = "temperaments", .module = temperaments.module("temperaments") },
+ },
+ });
+
+ // Install lib as a static library
+ const lib = b.addLibrary(.{
+ .name = "synths",
+ .root_module = mod,
+ });
+ b.installArtifact(lib);
+
+ const mod_tests = b.addTest(.{
+ .root_module = mod,
+ });
+ const run_mod_tests = b.addRunArtifact(mod_tests);
+
+ const test_step = b.step("test", "Run tests");
+ test_step.dependOn(&run_mod_tests.step);
+}
diff --git a/examples/06-advanced/modular-composing/packages/synths/build.zig.zon b/examples/06-advanced/modular-composing/packages/synths/build.zig.zon
new file mode 100644
index 0000000..262ad24
--- /dev/null
+++ b/examples/06-advanced/modular-composing/packages/synths/build.zig.zon
@@ -0,0 +1,17 @@
+.{
+ .name = .synths,
+ .version = "0.0.0",
+ .fingerprint = 0x249fdde76d58985d,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{
+ .lightmix = .{ .path = "../../../../.." },
+ .temperaments = .{ .path = "../temperaments" },
+ },
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/06-advanced/modular-composing/packages/synths/src/synths.zig b/examples/06-advanced/modular-composing/packages/synths/src/synths.zig
new file mode 100644
index 0000000..7e40dfd
--- /dev/null
+++ b/examples/06-advanced/modular-composing/packages/synths/src/synths.zig
@@ -0,0 +1,13 @@
+//! Synths Package
+//!
+//! This package provides audio synthesis engines (synthesizers).
+//! Each synthesizer generates audio waveforms at specified pitches and durations.
+//!
+//! Currently, only a sine wave oscillator is provided, but other waveforms
+//! such as square wave, sawtooth wave, and triangle wave can be added in the future.
+
+/// Sine Wave Synthesizer
+///
+/// Generates pure sine waves at specified pitches.
+/// A sine wave is the most basic waveform and is a pure tone without harmonics.
+pub const Sine = @import("./synths/sine.zig");
diff --git a/examples/06-advanced/modular-composing/packages/synths/src/synths/sine.zig b/examples/06-advanced/modular-composing/packages/synths/src/synths/sine.zig
new file mode 100644
index 0000000..e910ae8
--- /dev/null
+++ b/examples/06-advanced/modular-composing/packages/synths/src/synths/sine.zig
@@ -0,0 +1,37 @@
+const std = @import("std");
+const lightmix = @import("lightmix");
+const temperaments = @import("temperaments");
+
+const Wave = lightmix.Wave;
+const Scale = temperaments.TwelveEqualTemperament;
+
+pub fn gen(
+ allocator: std.mem.Allocator,
+ length: usize,
+ sample_rate: u32,
+ channels: u16,
+ scale: Scale,
+) !Wave(f64) {
+ // Allocate sample data for the specified length
+ var samples = try allocator.alloc(f64, length);
+
+ // Calculate sine wave values at each sample point
+ for (0..samples.len) |i| {
+ // Calculate time t (in seconds)
+ // t = sample index / sampling rate
+ const t = @as(f64, @floatFromInt(i)) / @as(f64, @floatFromInt(sample_rate));
+
+ // Calculate sine wave: sin(2πft)
+ // scale.gen() gets the frequency f
+ // 2πft is the phase in radians
+ samples[i] = @sin(t * scale.gen() * 2.0 * std.math.pi);
+ }
+
+ // Initialize and return the Wave object
+ return Wave(f64){
+ .samples = samples,
+ .allocator = allocator,
+ .sample_rate = sample_rate,
+ .channels = channels,
+ };
+}
diff --git a/examples/06-advanced/modular-composing/packages/temperaments/build.zig b/examples/06-advanced/modular-composing/packages/temperaments/build.zig
new file mode 100644
index 0000000..53692ab
--- /dev/null
+++ b/examples/06-advanced/modular-composing/packages/temperaments/build.zig
@@ -0,0 +1,29 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ // Declare a temperaments module
+ const mod = b.addModule("temperaments", .{
+ .root_source_file = b.path("src/root.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ // Install lib as a static library
+ const lib = b.addLibrary(.{
+ .name = "temperaments",
+ .root_module = mod,
+ });
+ b.installArtifact(lib);
+
+ // Tests
+ const mod_tests = b.addTest(.{
+ .root_module = mod,
+ });
+ const run_mod_tests = b.addRunArtifact(mod_tests);
+
+ const test_step = b.step("test", "Run tests");
+ test_step.dependOn(&run_mod_tests.step);
+}
diff --git a/examples/06-advanced/modular-composing/packages/temperaments/build.zig.zon b/examples/06-advanced/modular-composing/packages/temperaments/build.zig.zon
new file mode 100644
index 0000000..f7f305a
--- /dev/null
+++ b/examples/06-advanced/modular-composing/packages/temperaments/build.zig.zon
@@ -0,0 +1,14 @@
+.{
+ .name = .temperaments,
+ .version = "0.0.0",
+ .fingerprint = 0x24b8773a7d72da8c,
+ .minimum_zig_version = "0.15.2",
+
+ .dependencies = .{},
+
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/examples/06-advanced/modular-composing/packages/temperaments/src/root.zig b/examples/06-advanced/modular-composing/packages/temperaments/src/root.zig
new file mode 100644
index 0000000..6cd3522
--- /dev/null
+++ b/examples/06-advanced/modular-composing/packages/temperaments/src/root.zig
@@ -0,0 +1,14 @@
+//! Temperaments Package
+//!
+//! This package provides tuning systems (scale tuning systems).
+//! A tuning system is a system that defines the relative frequency relationships
+//! of pitches used in music.
+//!
+//! Currently, only Twelve Equal Temperament is provided, but other tuning systems
+//! such as just intonation and Pythagorean tuning can be added in the future.
+
+/// Twelve Equal Temperament
+///
+/// A tuning system that divides one octave into 12 equal semitones.
+/// This is the most commonly used tuning system in Western music.
+pub const TwelveEqualTemperament = @import("./twelve_equal_temperament.zig");
diff --git a/examples/06-advanced/modular-composing/packages/temperaments/src/twelve_equal_temperament.zig b/examples/06-advanced/modular-composing/packages/temperaments/src/twelve_equal_temperament.zig
new file mode 100644
index 0000000..821916f
--- /dev/null
+++ b/examples/06-advanced/modular-composing/packages/temperaments/src/twelve_equal_temperament.zig
@@ -0,0 +1,120 @@
+//! Twelve Equal Temperament
+//!
+//! This module implements twelve equal temperament.
+//! Twelve equal temperament is a tuning system that divides one octave into 12 equal semitones,
+//! with each semitone having a frequency ratio of 2^(1/12). This allows the same pitch relationships
+//! to be maintained in any key (allowing free transposition).
+//!
+//! ## Usage Example
+//! ```zig
+//! const scale = TwelveEqualTemperament{ .code = .c, .octave = 4 };
+//! const freq = scale.gen(); // Get the frequency of C4 (approximately 261.63 Hz)
+//!
+//! const d4 = scale.add(2); // Move to D4 (2 semitones up)
+//! ```
+
+const std = @import("std");
+const testing = std.testing;
+
+const Self = @This();
+
+/// Note code (C, C#, D, etc.)
+code: Code,
+
+/// Octave number (4 for C4)
+octave: usize,
+
+/// Move the pitch by the specified number of semitones
+///
+/// ## Arguments
+/// - `self`: Current pitch
+/// - `semitones`: Number of semitones to move (positive for up, negative for down)
+///
+/// ## Returns
+/// A new `Self` instance representing the moved pitch
+///
+/// ## Example
+/// ```zig
+/// const c4 = TwelveEqualTemperament{ .code = .c, .octave = 4 };
+/// const e4 = c4.add(4); // E4, 4 semitones up from C4
+/// const a3 = c4.add(-3); // A3, 3 semitones down from C4
+/// ```
+pub fn add(self: Self, semitones: isize) Self {
+ // Convert current pitch to MIDI number (C-1 = 0, A4 = 69)
+ const self_midi_number: isize = @intCast(12 * (self.octave + 1) + @intFromEnum(self.code));
+
+ // Add semitones
+ const result_midi_number: isize = self_midi_number + semitones;
+
+ // Decompose MIDI number into note code and octave
+ // Remainder of division by 12 is the note, quotient gives the octave
+ const result_code: Code = @enumFromInt(@as(u8, @intCast(@mod(result_midi_number, 12))));
+ const result_octave: usize = @intCast(@divTrunc(result_midi_number, 12) - 1);
+
+ return Self{
+ .code = result_code,
+ .octave = result_octave,
+ };
+}
+
+/// Calculate frequency (Hz) from pitch
+///
+/// Uses the frequency calculation formula for twelve equal temperament:
+/// f = 440 * 2^((midi_number-69)/12)
+/// where midi_number is the MIDI number, and 440Hz is the reference frequency for A4 (MIDI number 69).
+///
+/// ## Arguments
+/// - `scale`: Pitch to calculate frequency for
+///
+/// ## Returns
+/// Frequency of the pitch (Hz)
+///
+/// ## Example
+/// ```zig
+/// const a4 = TwelveEqualTemperament{ .code = .a, .octave = 4 };
+/// const freq = a4.gen(); // 440.0 Hz
+/// ```
+pub fn gen(scale: Self) f32 {
+ // Convert pitch to MIDI number
+ const midi_number: isize = @intCast(12 * (scale.octave + 1) + @intFromEnum(scale.code));
+
+ // Calculate semitone difference from A4 (MIDI number 69, 440Hz)
+ const exp: f32 = @floatFromInt(midi_number - 69);
+
+ // Twelve equal temperament formula: f = 440 * 2^((midi_number-69)/12)
+ const result: f32 = 440.0 * std.math.pow(f32, 2.0, exp / 12.0);
+ return result;
+}
+
+/// Note code
+///
+/// Codes with `~s` represent sharp (#).
+/// Example: `cs` is C# (C sharp)
+///
+/// ## Note names and MIDI number mapping
+/// - c (0): C
+/// - cs (1): C♯
+/// - d (2): D
+/// - ds (3): D♯
+/// - e (4): E
+/// - f (5): F
+/// - fs (6): F♯
+/// - g (7): G
+/// - gs (8): G♯
+/// - a (9): A
+/// - as (10): A♯
+/// - b (11): B
+pub const Code = enum(u8) {
+ c = 0,
+ cs = 1,
+ d = 2,
+ ds = 3,
+ e = 4,
+ f = 5,
+ fs = 6,
+ g = 7,
+ gs = 8,
+ a = 9,
+ as = 10,
+ b = 11,
+};
diff --git a/examples/06-advanced/modular-composing/src/root.zig b/examples/06-advanced/modular-composing/src/root.zig
new file mode 100644
index 0000000..b2d3b92
--- /dev/null
+++ b/examples/06-advanced/modular-composing/src/root.zig
@@ -0,0 +1,9 @@
+const std = @import("std");
+const lightmix = @import("lightmix");
+const synths = @import("synths");
+
+const Wave = lightmix.Wave;
+
+pub fn gen(allocator: std.mem.Allocator) !Wave(f64) {
+ return synths.Sine.gen(allocator, 44100, 44100, 1, .{ .code = .c, .octave = 4 });
+}
diff --git a/examples/Wave/generate_double_frequency_wave/build.zig b/examples/06-advanced/runtime-play/build.zig
similarity index 65%
rename from examples/Wave/generate_double_frequency_wave/build.zig
rename to examples/06-advanced/runtime-play/build.zig
index 8eccc1c..d6b2b49 100644
--- a/examples/Wave/generate_double_frequency_wave/build.zig
+++ b/examples/06-advanced/runtime-play/build.zig
@@ -1,45 +1,47 @@
const std = @import("std");
+const l = @import("lightmix");
-pub fn build(b: *std.Build) void {
+pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
- // Dependencies
const lightmix = b.dependency("lightmix", .{});
- // Executable Module
const exe_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
+ .imports = &.{
+ .{ .name = "lightmix", .module = lightmix.module("lightmix") },
+ },
});
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
- // Executable
+ if (target.result.os.tag == .linux) {
+ exe_mod.linkSystemLibrary("alsa", .{});
+ exe_mod.linkSystemLibrary("libpulse", .{});
+ exe_mod.linkSystemLibrary("libpipewire-0.3", .{});
+ }
+
const exe = b.addExecutable(.{
- .name = "generate_double_frequency_wave",
+ .name = "noise",
.root_module = exe_mod,
});
b.installArtifact(exe);
- // Rum cmd
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
- // Run step
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
- // Unit tests
const exe_unit_tests = b.addTest(.{
.root_module = exe_mod,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
- // Test step
- const test_step = b.step("test", "Run unit tests");
+ const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_exe_unit_tests.step);
}
diff --git a/examples/Composer/compose_multipul_soundless/build.zig.zon b/examples/06-advanced/runtime-play/build.zig.zon
similarity index 54%
rename from examples/Composer/compose_multipul_soundless/build.zig.zon
rename to examples/06-advanced/runtime-play/build.zig.zon
index df58b43..fdc9c03 100644
--- a/examples/Composer/compose_multipul_soundless/build.zig.zon
+++ b/examples/06-advanced/runtime-play/build.zig.zon
@@ -1,8 +1,8 @@
.{
- .name = .compose_multipul_soundless,
+ .name = .runtime_play,
.version = "0.0.0",
- .fingerprint = 0xbd23ff1b97235887,
- .minimum_zig_version = "0.15.1",
+ .fingerprint = 0xe6c2d04f8c10927a, // Changing this has security and trust implications.
+ .minimum_zig_version = "0.15.2",
.dependencies = .{
.lightmix = .{ .path = "../../.." },
diff --git a/examples/06-advanced/runtime-play/src/main.zig b/examples/06-advanced/runtime-play/src/main.zig
new file mode 100644
index 0000000..1901ba7
--- /dev/null
+++ b/examples/06-advanced/runtime-play/src/main.zig
@@ -0,0 +1,27 @@
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+pub fn main() !void {
+ const allocator = std.heap.page_allocator;
+
+ // Generate a 440Hz sine wave (A4 note)
+ const frequency: f128 = 440.0;
+ const sample_rate: f128 = 44100.0;
+ const radians_per_sec: f128 = frequency * 2.0 * std.math.pi;
+ const volume: f128 = 0.5;
+
+ var samples: [44100]f128 = undefined;
+ for (0..samples.len) |i| {
+ const t = @as(f128, @floatFromInt(i)) / sample_rate;
+ // Sine wave formula: amplitude * sin(2π * frequency * time)
+ samples[i] = volume * @sin(radians_per_sec * t);
+ }
+
+ const wave = try Wave(f128).init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+
+ try wave.play();
+}
diff --git a/examples/Composer/compose_multipul_sinewave/build.zig.zon b/examples/Composer/compose_multipul_sinewave/build.zig.zon
deleted file mode 100644
index df58b43..0000000
--- a/examples/Composer/compose_multipul_sinewave/build.zig.zon
+++ /dev/null
@@ -1,16 +0,0 @@
-.{
- .name = .compose_multipul_soundless,
- .version = "0.0.0",
- .fingerprint = 0xbd23ff1b97235887,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{
- .lightmix = .{ .path = "../../.." },
- },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Composer/compose_multipul_sinewave/src/main.zig b/examples/Composer/compose_multipul_sinewave/src/main.zig
deleted file mode 100644
index 04218fb..0000000
--- a/examples/Composer/compose_multipul_sinewave/src/main.zig
+++ /dev/null
@@ -1,80 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const Composer = lightmix.Composer;
-const WaveInfo = Composer.WaveInfo;
-
-var gpa = std.heap.GeneralPurposeAllocator(.{}){};
-const allocator = gpa.allocator();
-
-pub fn main() !void {
- defer _ = gpa.detectLeaks();
-
- const composer = Composer.init(allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer composer.deinit();
-
- const data: [44100]f32 = generate_sinewave_data();
- const wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const decayed_wave: Wave = wave.filter(decay).filter(decay).filter(decay);
- defer decayed_wave.deinit();
-
- var append_list: std.array_list.Aligned(WaveInfo, null) = .empty;
- defer append_list.deinit(allocator);
- try append_list.append(allocator, .{ .wave = decayed_wave, .start_point = 0 });
- try append_list.append(allocator, .{ .wave = decayed_wave, .start_point = 44100 });
-
- const appended_composer = composer.appendSlice(append_list.items);
- defer appended_composer.deinit();
-
- const result: Wave = appended_composer.finalize();
- defer result.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try result.write(file);
-}
-
-fn generate_sinewave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = 440.0 * 2.0 * std.math.pi;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- result[i] = 0.5 * std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate);
- }
-
- return result;
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Composer/compose_multipul_soundless/build.zig b/examples/Composer/compose_multipul_soundless/build.zig
deleted file mode 100644
index 735a448..0000000
--- a/examples/Composer/compose_multipul_soundless/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "compose_multipul_soundless",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Composer/compose_multipul_soundless/src/main.zig b/examples/Composer/compose_multipul_soundless/src/main.zig
deleted file mode 100644
index 2bee249..0000000
--- a/examples/Composer/compose_multipul_soundless/src/main.zig
+++ /dev/null
@@ -1,53 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const Composer = lightmix.Composer;
-const WaveInfo = Composer.WaveInfo;
-
-pub fn main() !void {
- const allocator = std.heap.page_allocator;
- const composer: Composer = Composer.init(allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer composer.deinit();
-
- const data: []const f32 = generate_soundless_data(44100, allocator);
- defer allocator.free(data);
-
- const wave = Wave.init(data, allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer wave.deinit();
-
- var append_list: std.array_list.Aligned(WaveInfo, null) = .empty;
- defer append_list.deinit(allocator);
- try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
- try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
-
- const appended_composer = composer.appendSlice(append_list.items);
- defer appended_composer.deinit();
-
- const result: Wave = appended_composer.finalize();
- defer result.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try result.write(file);
-}
-
-fn generate_soundless_data(length: usize, allocator: std.mem.Allocator) []const f32 {
- var list: std.array_list.Aligned(f32, null) = .empty;
-
- // Append empty wave
- for (0..length) |_|
- list.append(allocator, 0.0) catch @panic("Out of memory");
-
- const result: []const f32 = list.toOwnedSlice(allocator) catch @panic("Out of memory");
-
- return result;
-}
diff --git a/examples/QUICKSTART.md b/examples/QUICKSTART.md
new file mode 100644
index 0000000..2b08834
--- /dev/null
+++ b/examples/QUICKSTART.md
@@ -0,0 +1,100 @@
+# Examples Directory - Quick Start Guide
+
+## 🚀 Quick Start
+
+**Complete beginners? Start here:**
+
+```bash
+cd examples/01-getting-started/hello-wave
+zig build run
+# Play the generated result.wav file
+```
+
+## 📚 Learning Path
+
+Follow this recommended order:
+
+1. **01-getting-started/** - Your first sounds (2 examples)
+
+ - Start with `hello-wave` - creates a simple sine wave
+ - Then try `using-filters` - learn audio transformations
+
+1. **02-wave-basics/** - Understanding different sounds (5 examples)
+
+ - Try all five: sine, square, sawtooth, triangle, noise
+ - Listen to how each one sounds different!
+
+1. **03-wave-operations/** - Combining sounds (3 examples)
+
+ - `mixing-waves` - create a musical chord
+ - `filtering` - chain effects
+ - `frequency-changes` - change pitch
+
+1. **04-composer/** - Making music (2 examples)
+
+ - `simple-sequence` - create a melody
+ - `overlapping-sounds` - layer sounds
+
+1. **05-practical-examples/** - Real instruments (2 examples)
+
+ - `guitar` - realistic string sound
+ - `drum` - percussion synthesis
+
+1. **06-advanced/** - Advanced techniques (4 examples)
+
+ - `build-time-generation` - compile-time audio
+ - `build-time-play`
+ - `modular-composing` - organize complex projects
+ - `runtime-play`
+
+## 🎵 Example Categories
+
+```
+📦 examples/
+│
+├── 🌱 01-getting-started - Absolute beginner friendly
+├── 🎼 02-wave-basics - Basic waveforms & sounds
+├── 🔧 03-wave-operations - Transform & combine audio
+├── 🎹 04-composer - Sequence & layer sounds
+├── 🎸 05-practical-examples - Real synthesis techniques
+└── 🚀 06-advanced - Advanced features
+
+📚 Legacy examples (Wave/, Composer/, etc.) preserved for reference
+```
+
+## ⚠️ Important
+
+- **CHECK YOUR VOLUME** before playing generated audio files!
+- All examples create `result.wav` in their directory
+- Format: 16-bit mono PCM @ 44.1kHz
+- Each example focuses on ONE concept
+
+## 🎧 Playing Audio
+
+After running an example:
+
+- **Linux:** `aplay result.wav`
+- **macOS:** `afplay result.wav`
+- **Windows:** `start result.wav`
+- **All:** VLC, Audacity, or any audio player
+
+## 📖 Full Documentation
+
+See `examples/README.md` for complete details.
+
+## 🐛 Issues?
+
+If an example doesn't work:
+
+1. Check you're in the example's directory
+1. Try `zig build clean` then `zig build run`
+1. Make sure you have a compatible Zig version
+
+## 🎓 What Each Example Teaches
+
+Every example includes:
+
+- ✅ Detailed comments explaining the code
+- ✅ Top-level documentation describing the concept
+- ✅ Clear, readable variable names
+- ✅ Complete, runnable code
diff --git a/examples/README.md b/examples/README.md
index 65413e2..b1bc0e1 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,12 +1,169 @@
-# Examples
+# lightmix Examples
-- Composer
-- Wave
-- drum
-- guitar
+Welcome to the lightmix examples! This directory contains a comprehensive set of examples organized by difficulty and concept, designed to help you learn audio programming with lightmix step by step.
-## How to run examples
+## 📚 Example Categories
-Go to the example's directory you want to check. Then, run this command, `zig build run`. You will see the `result.wav` file in current directory.
+### 01-getting-started/
-**Please check the volume!! The wave files' volume are not checked!!**
+**Perfect for first-time users**
+
+Start here if you're new to lightmix! These examples introduce the basics with clear, simple code.
+
+- **hello-wave** - Create your first sine wave and save it to a WAV file
+- **using-filters** - Learn how to apply filters to transform audio
+
+**What you'll learn:** Basic Wave creation, saving audio files, applying simple filters
+
+### 02-wave-basics/
+
+**Understanding fundamental waveforms**
+
+Learn about the building blocks of sound synthesis. Each example generates a different basic waveform.
+
+- **sine-wave** - Pure tone with no harmonics (smooth, clean sound)
+- **square-wave** - Rich in odd harmonics (buzzy, 8-bit sound)
+- **sawtooth-wave** - Bright with all harmonics (brass, string-like)
+- **triangle-wave** - Mellow with odd harmonics (softer than square)
+- **noise** - White, pink, and brown noise generation
+
+**What you'll learn:** Wave generation algorithms, harmonics, audio characteristics of different waveforms
+
+### 03-wave-operations/
+
+**Manipulating and combining waves**
+
+Once you can generate waves, learn how to transform and combine them.
+
+- **mixing-waves** - Combine multiple waves to create chords and complex sounds
+- **filtering** - Chain multiple filters for audio effects
+- **frequency-changes** - Change pitch by altering frequency
+
+**What you'll learn:** Wave mixing, filter chaining, additive synthesis, pitch relationships
+
+### 04-composer/
+
+**Arranging sounds in time**
+
+The Composer lets you sequence and layer multiple audio sources.
+
+- **simple-sequence** - Create a simple melody by sequencing notes
+- **overlapping-sounds** - Layer sounds on top of each other (polyphony)
+
+**What you'll learn:** Using the Composer API, timing control, creating musical arrangements
+
+### 05-practical-examples/
+
+**Real-world instrument synthesis**
+
+See how to combine techniques to create realistic instrument sounds.
+
+- **guitar** - Karplus-Strong plucked string synthesis
+- **drum** - Snare drum using noise + tone synthesis
+
+**What you'll learn:** Physical modeling, combining noise and tones, percussion synthesis
+
+### 06-advanced/
+
+**Advanced techniques**
+
+Ready for more? These examples show advanced lightmix features.
+
+- **build-time-generation** - Generate audio files at compile-time
+- **build-time-play**
+- **runtime-play**
+- **modular-composing** - Organize complex audio projects with modules
+
+**What you'll learn:** Build system integration, code organization, reusable components
+
+## 🚀 How to Run Examples
+
+Each example is a complete Zig project with its own `build.zig`. To run any example:
+
+```bash
+cd examples/01-getting-started/hello-wave
+zig build run
+```
+
+This will create a `result.wav` file in the example directory.
+
+### Build-Time Generation
+
+The `06-advanced/build-time-generation` example is special - it generates audio during the build:
+
+```bash
+cd examples/06-advanced/build-time-generation
+zig build # Note: just 'build', not 'build run'
+```
+
+## ⚠️ Important Notes
+
+### Volume Warning
+
+**Please check your volume before playing the generated WAV files!** The examples do not normalize or limit audio levels. Start with low volume and adjust as needed.
+
+### Sample Rate
+
+Most examples use 44100 Hz (CD quality), which is standard for audio files.
+
+### File Formats
+
+Examples typically output 16-bit PCM WAV files (`.i16`), which is widely compatible. Some examples demonstrate other formats like `.f32`.
+
+## 📖 Learning Path
+
+We recommend following this order:
+
+1. **Start with 01-getting-started/** - Get comfortable with the basics
+1. **Explore 02-wave-basics/** - Understand different waveforms and their sounds
+1. **Try 03-wave-operations/** - Learn to combine and transform waves
+1. **Move to 04-composer/** - Start creating musical sequences
+1. **Study 05-practical-examples/** - See real synthesis techniques in action
+1. **Experiment with 06-advanced/** - Explore advanced features
+
+## 🎵 Understanding the Code
+
+Each example includes:
+
+- **Detailed comments** explaining what the code does
+- **Top-level documentation** (`//!`) describing the concept
+- **Clear variable names** to make the code readable
+- **Complete, runnable code** - no dependencies on other examples
+
+## 🔊 Listening to Results
+
+After generating WAV files, you can play them with:
+
+- **Linux:** `aplay result.wav` or `ffplay result.wav`
+- **macOS:** `afplay result.wav`
+- **Windows:** `start result.wav` or use Windows Media Player
+- **All platforms:** VLC, Audacity, or any audio player
+
+## 🐛 Troubleshooting
+
+**"Out of memory" errors:**
+
+- The examples use `page_allocator` for simplicity
+- For production code, consider using `GeneralPurposeAllocator` or an arena
+
+**"File not found" errors:**
+
+- Make sure you're in the example's directory when running `zig build run`
+- The `result.wav` is created in the current working directory
+
+**Build errors:**
+
+- Ensure you're using a compatible Zig version (check the main README.md)
+- Try `zig build clean` and then `zig build run` again
+
+## 📚 Further Learning
+
+After working through these examples, check out:
+
+- The main lightmix documentation in `src/root.zig`
+- API documentation for Wave and Composer modules
+- The test files in `tests/` for more usage patterns
+
+## 🤝 Contributing
+
+Found a bug in an example? Have an idea for a new example? Contributions are welcome! Please open an issue or pull request on the GitHub repository.
diff --git a/examples/Wave/generate_brown_noise/.gitignore b/examples/Wave/generate_brown_noise/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_brown_noise/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_brown_noise/build.zig b/examples/Wave/generate_brown_noise/build.zig
deleted file mode 100644
index bea5676..0000000
--- a/examples/Wave/generate_brown_noise/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_brown_noise",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_brown_noise/build.zig.zon b/examples/Wave/generate_brown_noise/build.zig.zon
deleted file mode 100644
index f5a2738..0000000
--- a/examples/Wave/generate_brown_noise/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_brown_noise,
- .version = "0.0.0",
- .fingerprint = 0x225c63cf08b1f45a,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_brown_noise/src/main.zig b/examples/Wave/generate_brown_noise/src/main.zig
deleted file mode 100644
index 4ce1484..0000000
--- a/examples/Wave/generate_brown_noise/src/main.zig
+++ /dev/null
@@ -1,66 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_brown_noise();
- const brown_noise: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const decayed_brown_noise: Wave = brown_noise.filter(decay).filter(decay).filter(decay);
- defer decayed_brown_noise.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try decayed_brown_noise.write(file);
-}
-
-fn generate_brown_noise() [44100]f32 {
- var result: [44100]f32 = undefined;
-
- var prng = std.Random.DefaultPrng.init(0);
- const rand = prng.random();
-
- var last: f32 = 0.0;
- var i: usize = 0;
- while (i < result.len) : (i += 1) {
- const white: f32 = rand.float(f32) * 2.0 - 1.0;
- last += white;
-
- if (last > 1.0)
- last = 1.0;
-
- if (last < -1.0)
- last = -1.0;
-
- result[i] = last;
- }
-
- return result;
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Wave/generate_double_frequency_wave/.gitignore b/examples/Wave/generate_double_frequency_wave/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_double_frequency_wave/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_double_frequency_wave/build.zig.zon b/examples/Wave/generate_double_frequency_wave/build.zig.zon
deleted file mode 100644
index cc7a6a5..0000000
--- a/examples/Wave/generate_double_frequency_wave/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_double_frequency_wave,
- .version = "0.0.0",
- .fingerprint = 0xe103efd538644454,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_double_frequency_wave/src/main.zig b/examples/Wave/generate_double_frequency_wave/src/main.zig
deleted file mode 100644
index cbbd083..0000000
--- a/examples/Wave/generate_double_frequency_wave/src/main.zig
+++ /dev/null
@@ -1,76 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_sinewave_data();
- const sinewave: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const double_freq_wave: Wave = sinewave.filter(to_double_freq).filter(decay);
- defer double_freq_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try double_freq_wave.write(file);
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
-
-fn to_double_freq(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, i| {
- if (i % 2 == 0)
- try result.append(original_wave.allocator, data);
- }
-
- std.debug.print("{d}\n", .{result.items.len});
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
-
-fn generate_sinewave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = 440.0 * 4.0 * std.math.pi;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- result[i] = 0.5 * std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate);
- }
-
- return result;
-}
diff --git a/examples/Wave/generate_function/.gitignore b/examples/Wave/generate_function/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_function/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_function/build.zig b/examples/Wave/generate_function/build.zig
deleted file mode 100644
index 3553b8f..0000000
--- a/examples/Wave/generate_function/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_function",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_function/build.zig.zon b/examples/Wave/generate_function/build.zig.zon
deleted file mode 100644
index 3341b6d..0000000
--- a/examples/Wave/generate_function/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_function,
- .version = "0.0.0",
- .fingerprint = 0x6cbe76a58f61502c,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_function/src/main.zig b/examples/Wave/generate_function/src/main.zig
deleted file mode 100644
index 0bce3d9..0000000
--- a/examples/Wave/generate_function/src/main.zig
+++ /dev/null
@@ -1,34 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const sinewave: Wave = Wave.from_file_content(@embedFile("./assets/sine.wav"), allocator);
-
- const overtone_wave: Wave = sinewave.filter(generate_function).filter(generate_function).filter(generate_function);
- defer overtone_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try overtone_wave.write(file);
-}
-
-fn generate_function(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data) |data| {
- const new_data = data * 2;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Wave/generate_half_frequency_wave/.gitignore b/examples/Wave/generate_half_frequency_wave/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_half_frequency_wave/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_half_frequency_wave/build.zig b/examples/Wave/generate_half_frequency_wave/build.zig
deleted file mode 100644
index 44a4341..0000000
--- a/examples/Wave/generate_half_frequency_wave/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_half_frequency_wave",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_half_frequency_wave/build.zig.zon b/examples/Wave/generate_half_frequency_wave/build.zig.zon
deleted file mode 100644
index baa3851..0000000
--- a/examples/Wave/generate_half_frequency_wave/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_half_frequency_wave,
- .version = "0.0.0",
- .fingerprint = 0xa13ef7d4c5c0e072,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_half_frequency_wave/src/main.zig b/examples/Wave/generate_half_frequency_wave/src/main.zig
deleted file mode 100644
index 1bb465c..0000000
--- a/examples/Wave/generate_half_frequency_wave/src/main.zig
+++ /dev/null
@@ -1,53 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_sinewave_data();
- const sinewave: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const half_freq_wave: Wave = sinewave.filter(to_half_freq);
- defer half_freq_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try half_freq_wave.write(file);
-}
-
-fn to_half_freq(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data) |data| {
- try result.append(original_wave.allocator, data);
- try result.append(original_wave.allocator, data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
-
-fn generate_sinewave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = 440.0 * 4.0 * std.math.pi;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- result[i] = 0.5 * std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate);
- }
-
- return result;
-}
diff --git a/examples/Wave/generate_mix_wave/.gitignore b/examples/Wave/generate_mix_wave/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_mix_wave/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_mix_wave/build.zig b/examples/Wave/generate_mix_wave/build.zig
deleted file mode 100644
index dfebd8e..0000000
--- a/examples/Wave/generate_mix_wave/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_mix_wave",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_mix_wave/build.zig.zon b/examples/Wave/generate_mix_wave/build.zig.zon
deleted file mode 100644
index b5f9fbd..0000000
--- a/examples/Wave/generate_mix_wave/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_mix_wave,
- .version = "0.0.0",
- .fingerprint = 0x3080a68e671504f0,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_mix_wave/src/main.zig b/examples/Wave/generate_mix_wave/src/main.zig
deleted file mode 100644
index abbd034..0000000
--- a/examples/Wave/generate_mix_wave/src/main.zig
+++ /dev/null
@@ -1,86 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const sine_data: [44100]f32 = generate_sinewave_data();
- const sinewave: Wave = Wave.init(sine_data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const sawtooth_data: [44100]f32 = generate_sawtooth_wave_data();
- const sawtooth_wave: Wave = Wave.init(sawtooth_data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const decayed_sawtooth_wave: Wave = sawtooth_wave
- .filter(decay)
- .filter(decay)
- .filter(decay)
- .filter(decay)
- .filter(decay);
-
- const mixed_wave: Wave = sinewave.mix(decayed_sawtooth_wave);
- defer mixed_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try mixed_wave.write(file);
-}
-
-fn generate_sawtooth_wave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const freq: f32 = 440.0;
- const period: f32 = sample_rate / freq;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- const phase = @as(f32, @floatFromInt(i % @as(usize, @intFromFloat(period)))) / period;
- result[i] = (phase * 2.0) - 1.0;
- }
-
- return result;
-}
-
-fn generate_sinewave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = 440.0 * 2.0 * std.math.pi;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- result[i] = 0.5 * std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate);
- }
-
- return result;
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Wave/generate_pinknoise/.gitignore b/examples/Wave/generate_pinknoise/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_pinknoise/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_pinknoise/build.zig b/examples/Wave/generate_pinknoise/build.zig
deleted file mode 100644
index 2db7c6e..0000000
--- a/examples/Wave/generate_pinknoise/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_pinknoise",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_pinknoise/build.zig.zon b/examples/Wave/generate_pinknoise/build.zig.zon
deleted file mode 100644
index 9a594ec..0000000
--- a/examples/Wave/generate_pinknoise/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_pinknoise,
- .version = "0.0.0",
- .fingerprint = 0xa54dcee860679070,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_pinknoise/src/main.zig b/examples/Wave/generate_pinknoise/src/main.zig
deleted file mode 100644
index eb98195..0000000
--- a/examples/Wave/generate_pinknoise/src/main.zig
+++ /dev/null
@@ -1,68 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_pink_noise();
- const pinknoise: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const decayed_pinknoise: Wave = pinknoise.filter(decay).filter(decay).filter(decay);
- defer decayed_pinknoise.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try decayed_pinknoise.write(file);
-}
-
-fn generate_pink_noise() [44100]f32 {
- var result: [44100]f32 = undefined;
-
- var prng = std.Random.DefaultPrng.init(0);
- const rand = prng.random();
-
- var b0: f32 = 0.0;
- var b1: f32 = 0.0;
- var b2: f32 = 0.0;
-
- var i: usize = 0;
- while (i < result.len) : (i += 1) {
- const white: f32 = rand.float(f32) * 2.0 - 1.0;
-
- b0 = 0.99765 * b0 + white * 0.0990460;
- b1 = 0.96300 * b1 + white * 0.2965164;
- b2 = 0.57000 * b2 + white * 1.0526913;
-
- const pink: f32 = b0 + b1 + b2 + white * 0.1848;
-
- result[i] = pink;
- }
-
- return result;
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Wave/generate_sawtooth_wave/.gitignore b/examples/Wave/generate_sawtooth_wave/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_sawtooth_wave/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_sawtooth_wave/build.zig b/examples/Wave/generate_sawtooth_wave/build.zig
deleted file mode 100644
index ace9741..0000000
--- a/examples/Wave/generate_sawtooth_wave/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_sawtooth_wave",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_sawtooth_wave/build.zig.zon b/examples/Wave/generate_sawtooth_wave/build.zig.zon
deleted file mode 100644
index b14879f..0000000
--- a/examples/Wave/generate_sawtooth_wave/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_sawtooth_wave,
- .version = "0.0.0",
- .fingerprint = 0x9abf981241297e1a,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_sawtooth_wave/src/main.zig b/examples/Wave/generate_sawtooth_wave/src/main.zig
deleted file mode 100644
index c481fc0..0000000
--- a/examples/Wave/generate_sawtooth_wave/src/main.zig
+++ /dev/null
@@ -1,58 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_sawtooth_wave_data();
- const sawtooth_wave: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const decayed_wave: Wave = sawtooth_wave.filter(decay);
- defer decayed_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try decayed_wave.write(file);
-}
-
-fn generate_sawtooth_wave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const freq: f32 = 440.0;
- const period: f32 = sample_rate / freq;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- const phase = @as(f32, @floatFromInt(i % @as(usize, @intFromFloat(period)))) / period;
- result[i] = (phase * 2.0) - 1.0;
- }
-
- return result;
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Wave/generate_sinewave/.gitignore b/examples/Wave/generate_sinewave/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_sinewave/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_sinewave/build.zig.zon b/examples/Wave/generate_sinewave/build.zig.zon
deleted file mode 100644
index f079a1e..0000000
--- a/examples/Wave/generate_sinewave/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_sinewave,
- .version = "0.0.0",
- .fingerprint = 0x67afc43683928479,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_sinewave/src/main.zig b/examples/Wave/generate_sinewave/src/main.zig
deleted file mode 100644
index abddaa2..0000000
--- a/examples/Wave/generate_sinewave/src/main.zig
+++ /dev/null
@@ -1,36 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_sinewave_data();
- const sinewave: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer sinewave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try sinewave.write(file);
-}
-
-const c_5: f32 = 523.251;
-const volume: f32 = 1.0;
-
-fn generate_sinewave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = c_5 * 2.0 * std.math.pi;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- result[i] = std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate) * volume;
- }
-
- return result;
-}
diff --git a/examples/Wave/generate_soundless/.gitignore b/examples/Wave/generate_soundless/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_soundless/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_soundless/build.zig.zon b/examples/Wave/generate_soundless/build.zig.zon
deleted file mode 100644
index f079a1e..0000000
--- a/examples/Wave/generate_soundless/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_sinewave,
- .version = "0.0.0",
- .fingerprint = 0x67afc43683928479,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_soundless/src/main.zig b/examples/Wave/generate_soundless/src/main.zig
deleted file mode 100644
index 8d584aa..0000000
--- a/examples/Wave/generate_soundless/src/main.zig
+++ /dev/null
@@ -1,30 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-
-pub fn main() !void {
- const allocator = std.heap.page_allocator;
- const data: []const f32 = generate_soundless_data(44100, allocator);
- defer allocator.free(data);
-
- const soundless_wave: Wave = Wave.init(data, allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer soundless_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try soundless_wave.write(file);
-}
-
-fn generate_soundless_data(length: usize, allocator: std.mem.Allocator) []const f32 {
- var result: []f32 = allocator.alloc(f32, length) catch @panic("Out of memory");
-
- for (0..result.len) |i|
- result[i] = 0.0;
-
- return result;
-}
diff --git a/examples/Wave/generate_square_wave/.gitignore b/examples/Wave/generate_square_wave/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_square_wave/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_square_wave/build.zig b/examples/Wave/generate_square_wave/build.zig
deleted file mode 100644
index b868e22..0000000
--- a/examples/Wave/generate_square_wave/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_square_wave",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_square_wave/build.zig.zon b/examples/Wave/generate_square_wave/build.zig.zon
deleted file mode 100644
index 04b4565..0000000
--- a/examples/Wave/generate_square_wave/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_square_wave,
- .version = "0.0.0",
- .fingerprint = 0x5a3eaa4ef07db985,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_square_wave/src/main.zig b/examples/Wave/generate_square_wave/src/main.zig
deleted file mode 100644
index 0003365..0000000
--- a/examples/Wave/generate_square_wave/src/main.zig
+++ /dev/null
@@ -1,59 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_square_wave_data();
- const square_wave: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const decayed_wave: Wave = square_wave.filter(decay);
- defer decayed_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try decayed_wave.write(file);
-}
-
-fn generate_square_wave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = 440.0 * 2.0 * std.math.pi;
- const sharpness: f32 = 7.0;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- const sine_wave = std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate);
- const square_wave = std.math.tanh(sharpness * sine_wave);
- result[i] = square_wave;
- }
-
- return result;
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Wave/generate_triangle_wave/.gitignore b/examples/Wave/generate_triangle_wave/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_triangle_wave/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_triangle_wave/build.zig b/examples/Wave/generate_triangle_wave/build.zig
deleted file mode 100644
index 965736c..0000000
--- a/examples/Wave/generate_triangle_wave/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_triangle_wave",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_triangle_wave/build.zig.zon b/examples/Wave/generate_triangle_wave/build.zig.zon
deleted file mode 100644
index ab49995..0000000
--- a/examples/Wave/generate_triangle_wave/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_triangle_wave,
- .version = "0.0.0",
- .fingerprint = 0x8d7ccaa6407e2b94,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_triangle_wave/src/main.zig b/examples/Wave/generate_triangle_wave/src/main.zig
deleted file mode 100644
index 6c9f050..0000000
--- a/examples/Wave/generate_triangle_wave/src/main.zig
+++ /dev/null
@@ -1,63 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_triangle_wave_data();
- const triangle_wave: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const decayed_wave: Wave = triangle_wave.filter(decay);
- defer decayed_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try decayed_wave.write(file);
-}
-
-fn generate_triangle_wave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const freq: f32 = 440.0;
- const period: f32 = sample_rate / freq;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- const phase = @as(f32, @floatFromInt(i % @as(usize, @intFromFloat(period)))) / period;
- const triangle_value = if (phase < 0.5)
- (phase * 4.0) - 1.0 // 前半
- else
- 3.0 - (phase * 4.0); // 後半
-
- result[i] = triangle_value;
- }
-
- return result;
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Wave/generate_whitenoise/.gitignore b/examples/Wave/generate_whitenoise/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_whitenoise/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_whitenoise/build.zig b/examples/Wave/generate_whitenoise/build.zig
deleted file mode 100644
index 9ebad25..0000000
--- a/examples/Wave/generate_whitenoise/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_whitenoise",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_whitenoise/build.zig.zon b/examples/Wave/generate_whitenoise/build.zig.zon
deleted file mode 100644
index c53d461..0000000
--- a/examples/Wave/generate_whitenoise/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_whitenoise,
- .version = "0.0.0",
- .fingerprint = 0x50bfa204b59a6196,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_whitenoise/src/main.zig b/examples/Wave/generate_whitenoise/src/main.zig
deleted file mode 100644
index f7d69f4..0000000
--- a/examples/Wave/generate_whitenoise/src/main.zig
+++ /dev/null
@@ -1,57 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_white_noise();
- const whitenoise: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const decayed_whitenoise: Wave = whitenoise.filter(decay).filter(decay).filter(decay);
- defer decayed_whitenoise.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try decayed_whitenoise.write(file);
-}
-
-fn generate_white_noise() [44100]f32 {
- var result: [44100]f32 = undefined;
-
- var prng = std.Random.DefaultPrng.init(0);
- const rand = prng.random();
-
- var i: usize = 0;
- while (i < result.len) : (i += 1) {
- const r: f32 = rand.float(f32) * 2.0 - 1.0;
- result[i] = r;
- }
-
- return result;
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Wave/generate_with_debug_play/.gitignore b/examples/Wave/generate_with_debug_play/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_with_debug_play/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_with_debug_play/build.zig b/examples/Wave/generate_with_debug_play/build.zig
deleted file mode 100644
index 3b4fe70..0000000
--- a/examples/Wave/generate_with_debug_play/build.zig
+++ /dev/null
@@ -1,49 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{
- .with_debug_features = true,
- });
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_with_debug_play",
- .root_module = exe_mod,
- });
- exe.linkSystemLibrary2("portaudio-2.0", .{});
- exe.linkSystemLibrary2("sndfile", .{});
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_with_debug_play/build.zig.zon b/examples/Wave/generate_with_debug_play/build.zig.zon
deleted file mode 100644
index 1066a26..0000000
--- a/examples/Wave/generate_with_debug_play/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_with_debug_play,
- .version = "0.0.0",
- .fingerprint = 0x397a3b91015a3229,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_with_debug_play/src/assets/sine.wav b/examples/Wave/generate_with_debug_play/src/assets/sine.wav
deleted file mode 100644
index 13cb2fb..0000000
Binary files a/examples/Wave/generate_with_debug_play/src/assets/sine.wav and /dev/null differ
diff --git a/examples/Wave/generate_with_debug_play/src/main.zig b/examples/Wave/generate_with_debug_play/src/main.zig
deleted file mode 100644
index f8e7322..0000000
--- a/examples/Wave/generate_with_debug_play/src/main.zig
+++ /dev/null
@@ -1,59 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const sinewave: Wave = Wave.from_file_content(@embedFile("./assets/sine.wav"), allocator);
-
- const decayed_wave: Wave = sinewave.filter(decay).filter(amp);
- defer decayed_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try decayed_wave.write(file);
-
- try decayed_wave.debug_play();
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
-
-fn amp(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, i| {
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/Wave/generate_with_decay/.gitignore b/examples/Wave/generate_with_decay/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/Wave/generate_with_decay/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/Wave/generate_with_decay/build.zig b/examples/Wave/generate_with_decay/build.zig
deleted file mode 100644
index 3553b8f..0000000
--- a/examples/Wave/generate_with_decay/build.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- // Dependencies
- const lightmix = b.dependency("lightmix", .{});
-
- // Executable Module
- const exe_mod = b.createModule(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
- });
- exe_mod.addImport("lightmix", lightmix.module("lightmix"));
-
- // Executable
- const exe = b.addExecutable(.{
- .name = "generate_function",
- .root_module = exe_mod,
- });
- b.installArtifact(exe);
-
- // Rum cmd
- const run_cmd = b.addRunArtifact(exe);
- run_cmd.step.dependOn(b.getInstallStep());
- if (b.args) |args| {
- run_cmd.addArgs(args);
- }
-
- // Run step
- const run_step = b.step("run", "Run the app");
- run_step.dependOn(&run_cmd.step);
-
- // Unit tests
- const exe_unit_tests = b.addTest(.{
- .root_module = exe_mod,
- });
- const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
-
- // Test step
- const test_step = b.step("test", "Run unit tests");
- test_step.dependOn(&run_exe_unit_tests.step);
-}
diff --git a/examples/Wave/generate_with_decay/build.zig.zon b/examples/Wave/generate_with_decay/build.zig.zon
deleted file mode 100644
index 3341b6d..0000000
--- a/examples/Wave/generate_with_decay/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_function,
- .version = "0.0.0",
- .fingerprint = 0x6cbe76a58f61502c,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/Wave/generate_with_decay/src/assets/sine.wav b/examples/Wave/generate_with_decay/src/assets/sine.wav
deleted file mode 100644
index 13cb2fb..0000000
Binary files a/examples/Wave/generate_with_decay/src/assets/sine.wav and /dev/null differ
diff --git a/examples/Wave/generate_with_decay/src/main.zig b/examples/Wave/generate_with_decay/src/main.zig
deleted file mode 100644
index d6bfaf6..0000000
--- a/examples/Wave/generate_with_decay/src/main.zig
+++ /dev/null
@@ -1,60 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-
-var gpa = std.heap.GeneralPurposeAllocator(.{}){};
-const allocator = gpa.allocator();
-
-pub fn main() !void {
- defer _ = gpa.detectLeaks();
-
- const sinewave: Wave = Wave.from_file_content(@embedFile("./assets/sine.wav"), allocator);
- const decayed_wave: Wave = sinewave.filter(decay).filter(amp);
- defer decayed_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try decayed_wave.write(file);
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
-
-fn amp(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, i| {
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/drum/snare_drum/.gitignore b/examples/drum/snare_drum/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/drum/snare_drum/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/drum/snare_drum/build.zig.zon b/examples/drum/snare_drum/build.zig.zon
deleted file mode 100644
index e3c335e..0000000
--- a/examples/drum/snare_drum/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_snare_drum,
- .version = "0.0.0",
- .fingerprint = 0x99c92069736f5bdb,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/drum/snare_drum/src/main.zig b/examples/drum/snare_drum/src/main.zig
deleted file mode 100644
index 77bd773..0000000
--- a/examples/drum/snare_drum/src/main.zig
+++ /dev/null
@@ -1,127 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const snare_wave = generate_snare_wave();
- defer snare_wave.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try snare_wave.write(file);
-}
-
-fn generate_snare_wave() Wave {
- const pinknoise_data: [44100]f32 = generate_pink_noise();
- const pinknoise: Wave = Wave.init(pinknoise_data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const sine_data: [44100]f32 = generate_sinewave_data();
- const sinewave: Wave = Wave.init(sine_data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
-
- const decayed_pinknoise = pinknoise
- .filter(decay)
- .filter(decay)
- .filter(decay)
- .filter(decay)
- .filter(decay)
- .filter(decay);
- defer decayed_pinknoise.deinit();
-
- const decayed_sinewave = sinewave
- .filter(decay)
- .filter(decay)
- .filter(half_volume)
- .filter(half_volume);
- defer decayed_sinewave.deinit();
-
- const result = decayed_pinknoise.mix(decayed_sinewave);
- return result;
-}
-
-fn generate_pink_noise() [44100]f32 {
- var result: [44100]f32 = undefined;
-
- var prng = std.Random.DefaultPrng.init(0);
- const rand = prng.random();
-
- var b0: f32 = 0.0;
- var b1: f32 = 0.0;
- var b2: f32 = 0.0;
-
- var i: usize = 0;
- while (i < result.len) : (i += 1) {
- const white: f32 = rand.float(f32) * 2.0 - 1.0;
-
- b0 = 0.99765 * b0 + white * 0.0990460;
- b1 = 0.96300 * b1 + white * 0.2965164;
- b2 = 0.57000 * b2 + white * 1.0526913;
-
- const pink: f32 = b0 + b1 + b2 + white * 0.1848;
-
- result[i] = pink;
- }
-
- return result;
-}
-
-fn generate_sinewave_data() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = 440.0 * 2.0 * std.math.pi;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
-
- while (i < result.len) : (i += 1) {
- result[i] = 0.5 * std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate);
- }
-
- return result;
-}
-
-fn decay(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data, 0..) |data, n| {
- const i = original_wave.data.len - n;
- const volume: f32 = @as(f32, @floatFromInt(i)) * (1.0 / @as(f32, @floatFromInt(original_wave.data.len)));
-
- const new_data = data * volume;
- try result.append(original_wave.allocator, new_data);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
-
-fn half_volume(original_wave: Wave) !Wave {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (original_wave.data) |data| {
- try result.append(original_wave.allocator, data / 2.0);
- }
-
- return Wave{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
diff --git a/examples/guitar/acoustic_guitar/.gitignore b/examples/guitar/acoustic_guitar/.gitignore
deleted file mode 100644
index 374ca18..0000000
--- a/examples/guitar/acoustic_guitar/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-result.wav
diff --git a/examples/guitar/acoustic_guitar/build.zig.zon b/examples/guitar/acoustic_guitar/build.zig.zon
deleted file mode 100644
index de2156d..0000000
--- a/examples/guitar/acoustic_guitar/build.zig.zon
+++ /dev/null
@@ -1,14 +0,0 @@
-.{
- .name = .generate_acoustic_guitar,
- .version = "0.0.0",
- .fingerprint = 0x8550528c13906134,
- .minimum_zig_version = "0.15.1",
-
- .dependencies = .{ .lightmix = .{ .path = "../../.." } },
-
- .paths = .{
- "build.zig",
- "build.zig.zon",
- "src",
- },
-}
diff --git a/examples/guitar/acoustic_guitar/src/main.zig b/examples/guitar/acoustic_guitar/src/main.zig
deleted file mode 100644
index 0d80cc7..0000000
--- a/examples/guitar/acoustic_guitar/src/main.zig
+++ /dev/null
@@ -1,51 +0,0 @@
-const std = @import("std");
-const lightmix = @import("lightmix");
-const Wave = lightmix.Wave;
-const allocator = std.heap.page_allocator;
-
-pub fn main() !void {
- const data: [44100]f32 = generate_guitar_note();
- const guitar: Wave = Wave.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer guitar.deinit();
-
- var file = try std.fs.cwd().createFile("result.wav", .{});
- defer file.close();
-
- try guitar.write(file);
-}
-
-// By ChatGPT...
-// This function will create Guitar data
-fn generate_guitar_note() [44100]f32 {
- const freq: f32 = 440.0;
- const sample_rate: f32 = 44100.0;
- const decay: f32 = 0.996;
-
- var result: [44100]f32 = undefined;
-
- const period = @as(usize, @intFromFloat(sample_rate / freq));
- var buffer: [2000]f32 = undefined;
- var prng = std.Random.DefaultPrng.init(0);
- const rand = prng.random();
-
- for (buffer[0..period]) |*val| {
- val.* = rand.float(f32) * 2.0 - 1.0;
- }
-
- // Karplus–Strong loop
- var idx: usize = 0;
- var i: usize = 0;
- while (i < result.len) : (i += 1) {
- const next_idx = (idx + 1) % period;
- const avg = (buffer[idx] + buffer[next_idx]) * 0.5 * decay;
- buffer[idx] = avg;
- result[i] = avg;
- idx = next_idx;
- }
-
- return result;
-}
diff --git a/flake.lock b/flake.lock
index 31a3766..99c6deb 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"flake-compat": {
"locked": {
- "lastModified": 1747046372,
- "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
+ "lastModified": 1766929376,
+ "narHash": "sha256-Eyg78VjLzKNG2XkqLqylSvHGAwsP8gsvmUHtZxQwPq4=",
"owner": "edolstra",
"repo": "flake-compat",
- "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
+ "rev": "236f248441a986331cda53b039e7f9fd96e03635",
"type": "github"
},
"original": {
@@ -22,11 +22,11 @@
]
},
"locked": {
- "lastModified": 1754487366,
- "narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
+ "lastModified": 1765835352,
+ "narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=",
"owner": "hercules-ci",
"repo": "flake-parts",
- "rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
+ "rev": "a34fae9c08a15ad73f295041fec82323541400a9",
"type": "github"
},
"original": {
@@ -37,11 +37,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1756636162,
- "narHash": "sha256-mBecwgUTWRgClJYqcF+y4O1bY8PQHqeDpB+zsAn+/zA=",
+ "lastModified": 1766870016,
+ "narHash": "sha256-fHmxAesa6XNqnIkcS6+nIHuEmgd/iZSP/VXxweiEuQw=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "37ff64b7108517f8b6ba5705ee5085eac636a249",
+ "rev": "5c2bc52fb9f8c264ed6c93bd20afa2ff5e763dce",
"type": "github"
},
"original": {
@@ -82,11 +82,11 @@
]
},
"locked": {
- "lastModified": 1756662192,
- "narHash": "sha256-F1oFfV51AE259I85av+MAia221XwMHCOtZCMcZLK2Jk=",
+ "lastModified": 1766000401,
+ "narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=",
"owner": "numtide",
"repo": "treefmt-nix",
- "rev": "1aabc6c05ccbcbf4a635fb7a90400e44282f61c4",
+ "rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index aaaafb7..823ab86 100644
--- a/flake.nix
+++ b/flake.nix
@@ -23,6 +23,24 @@
perSystem =
{ pkgs, lib, ... }:
+ let
+ lightmix = pkgs.stdenv.mkDerivation {
+ name = "lightmix";
+ src = lib.cleanSource ./.;
+ doCheck = true;
+
+ nativeBuildInputs = [
+ pkgs.zig_0_15.hook
+ ];
+
+ postPatch = ''
+ ln -s ${pkgs.callPackage ./.deps.nix { }} $ZIG_GLOBAL_CACHE_DIR/p
+
+ # Remove NIX_CFLAGS_COMPILE because zig cannot understand it
+ unset NIX_CFLAGS_COMPILE
+ '';
+ };
+ in
{
treefmt = {
projectRootFile = ".git/config";
@@ -39,14 +57,22 @@
# Markdown
programs.mdformat.enable = true;
+ settings.formatter.mdformat.excludes = [ "CODE_OF_CONDUCT.md" ];
+ };
+
+ packages = {
+ inherit lightmix;
+ default = lightmix;
+ };
+
+ checks = {
+ inherit lightmix;
};
devShells.default = pkgs.mkShell {
nativeBuildInputs = [
# Compiler
pkgs.zig_0_15
-
- # C library manager
pkgs.pkg-config
# LSP
@@ -55,12 +81,21 @@
# Music Player
pkgs.sox # Use this command as: `play result.wav`
+
+ # zon2nix
+ pkgs.zon2nix
];
- buildInputs = [
- pkgs.portaudio
- pkgs.libsndfile
+ buildInputs = lib.optionals pkgs.stdenv.isLinux [
+ pkgs.alsa-lib
+ pkgs.pulseaudio
+ pkgs.pipewire
];
+
+ shellHook = ''
+ # Remove NIX_CFLAGS_COMPILE because zig cannot understand it
+ unset NIX_CFLAGS_COMPILE
+ '';
};
};
};
diff --git a/src/composer.zig b/src/composer.zig
index efaf6dd..1e8cc39 100644
--- a/src/composer.zig
+++ b/src/composer.zig
@@ -1,340 +1,437 @@
-//! # Composer
-//!
-//! Composer is used to combine multipul `Wave`.
-
const std = @import("std");
const testing = std.testing;
const Wave = @import("./root.zig").Wave;
-const Self = @This();
-
-pub const WaveInfo = struct {
- wave: Wave,
- start_point: usize,
+/// Composer type function: Creates a Composer type for the specified sample type.
+///
+/// Composer allows sequencing and overlaying multiple Wave instances in time to create
+/// complex audio arrangements.
+///
+/// ## Type Parameter
+/// - `T`: The sample data type (typically f64, f80, or f128 for floating-point audio)
+///
+/// ## Usage
+/// ```zig
+/// const Composer = lightmix.Composer;
+/// const composer = Composer(f64).init(allocator, .{
+/// .sample_rate = 44100,
+/// .channels = 1,
+/// });
+/// defer composer.deinit();
+///
+/// // Append waves at specific time points
+/// const composed = composer
+/// .append(.{ .wave = wave1, .start_point = 0 })
+/// .append(.{ .wave = wave2, .start_point = 22050 });
+/// defer composed.deinit();
+///
+/// // Finalize to create the mixed result
+/// const result = composed.finalize(.{});
+/// defer result.deinit();
+/// ```
+pub fn inner(comptime T: type) type {
+ return struct {
+ info: []const WaveInfo,
+ allocator: std.mem.Allocator,
+ sample_rate: u32,
+ channels: u16,
+
+ const Self = @This();
+
+ /// Information about a wave to be placed at a specific time point.
+ pub const WaveInfo = struct {
+ wave: Wave(T),
+ start_point: usize,
+
+ fn to_wave(self: WaveInfo, allocator: std.mem.Allocator) std.mem.Allocator.Error!Wave(T) {
+ var padding_samples: []T = try allocator.alloc(T, self.start_point);
+
+ for (0..padding_samples.len) |i| {
+ padding_samples[i] = 0.0;
+ }
+
+ const slices: []const []const T = &[_][]const T{ padding_samples, self.wave.samples };
+ const samples = std.mem.concat(allocator, T, slices);
+
+ const result: Wave(T) = try Wave(T).init(samples, allocator, .{
+ .sample_rate = self.wave.sample_rate,
+ .channels = self.wave.channels,
+ });
+
+ return result;
+ }
+ };
- fn to_wave(self: WaveInfo, allocator: std.mem.Allocator) Wave {
- var padding_data: []f32 = allocator.alloc(f32, self.start_point) catch @panic("Out of memory");
+ /// Options for initializing a Composer instance.
+ pub const InitOptions = struct {
+ sample_rate: u32,
+ channels: u16,
+ };
- for (0..padding_data.len) |i| {
- padding_data[i] = 0.0;
+ /// Creates a new empty Composer instance.
+ ///
+ /// ## Parameters
+ /// - `allocator`: Memory allocator for internal allocations
+ /// - `options`: Initialization options (sample rate and channel count)
+ ///
+ /// ## Returns
+ /// A new Composer instance with no waves
+ pub fn init(
+ allocator: std.mem.Allocator,
+ options: InitOptions,
+ ) Self {
+ return Self{
+ .allocator = allocator,
+ .info = &[_]WaveInfo{},
+
+ .sample_rate = options.sample_rate,
+ .channels = options.channels,
+ };
}
- const slices: []const []const f32 = &[_][]const f32{ padding_data, self.wave.data };
- const data = std.mem.concat(allocator, f32, slices);
-
- const result: Wave = Wave.init(data, allocator, .{
- .sample_rate = self.wave.sample_rate,
- .channels = self.wave.channels,
- .bits = self.wave.bits,
- });
-
- return result;
- }
-};
-
-info: []const WaveInfo,
-allocator: std.mem.Allocator,
-
-sample_rate: usize,
-channels: usize,
-bits: usize,
-
-pub const initOptions = struct {
- sample_rate: usize,
- channels: usize,
- bits: usize,
-};
-
-pub fn init(allocator: std.mem.Allocator, options: initOptions) Self {
- return Self{
- .allocator = allocator,
- .info = &[_]WaveInfo{},
-
- .sample_rate = options.sample_rate,
- .channels = options.channels,
- .bits = options.bits,
- };
-}
-
-pub fn deinit(self: Self) void {
- self.allocator.free(self.info);
-}
-
-pub fn init_with(info: []const WaveInfo, allocator: std.mem.Allocator, options: initOptions) Self {
- var list: std.array_list.Aligned(WaveInfo, null) = .empty;
- list.appendSlice(allocator, info) catch @panic("Out of memory");
-
- return Self{
- .allocator = allocator,
- .info = list.toOwnedSlice(allocator) catch @panic("Out of memory"),
-
- .sample_rate = options.sample_rate,
- .channels = options.channels,
- .bits = options.bits,
- };
-}
-
-pub fn append(self: Self, waveinfo: WaveInfo) Self {
- var d: std.array_list.Aligned(WaveInfo, null) = .empty;
- d.appendSlice(self.allocator, self.info) catch @panic("Out of memory");
- d.append(self.allocator, waveinfo) catch @panic("Out of memory");
-
- const result: []const WaveInfo = d.toOwnedSlice(self.allocator) catch @panic("Out of memory");
-
- return Self{
- .allocator = self.allocator,
- .info = result,
-
- .sample_rate = self.sample_rate,
- .channels = self.channels,
- .bits = self.bits,
- };
-}
-
-pub fn appendSlice(self: Self, append_list: []const WaveInfo) Self {
- var d: std.array_list.Aligned(WaveInfo, null) = .empty;
- d.appendSlice(self.allocator, self.info) catch @panic("Out of memory");
- d.appendSlice(self.allocator, append_list) catch @panic("Out of memory");
-
- const result: []const WaveInfo = d.toOwnedSlice(self.allocator) catch @panic("Out of memory");
-
- return Self{
- .allocator = self.allocator,
- .info = result,
-
- .sample_rate = self.sample_rate,
- .channels = self.channels,
- .bits = self.bits,
- };
-}
-
-pub fn finalize(self: Self) Wave {
- var end_point: usize = 0;
-
- // Calculate the length for emitted wave
- for (self.info) |waveinfo| {
- const ep = waveinfo.start_point + waveinfo.wave.data.len;
+ /// Frees the memory allocated for the composer's internal data.
+ ///
+ /// Note: This does not free the individual Wave instances stored in WaveInfo.
+ /// Those must be freed separately by the caller.
+ pub fn deinit(self: Self) void {
+ self.allocator.free(self.info);
+ }
- if (end_point < ep)
- end_point = ep;
- }
+ /// Creates a new Composer instance initialized with the provided wave information.
+ ///
+ /// ## Parameters
+ /// - `info`: Slice of WaveInfo structures describing waves and their start points
+ /// - `allocator`: Memory allocator for internal allocations
+ /// - `options`: Initialization options (sample rate and channel count)
+ ///
+ /// ## Returns
+ /// A new Composer instance containing the provided waves
+ pub fn init_with(
+ info: []const WaveInfo,
+ allocator: std.mem.Allocator,
+ options: InitOptions,
+ ) std.mem.Allocator.Error!Self {
+ var list: std.array_list.Aligned(WaveInfo, null) = .empty;
+ try list.appendSlice(allocator, info);
+
+ return Self{
+ .allocator = allocator,
+ .info = try list.toOwnedSlice(allocator),
+
+ .sample_rate = options.sample_rate,
+ .channels = options.channels,
+ };
+ }
- var padded_waveinfo_list: std.array_list.Aligned(WaveInfo, null) = .empty;
- defer padded_waveinfo_list.deinit(self.allocator);
+ /// Appends a single wave to the composition. This method modifies the composer in-place.
+ ///
+ /// ## Parameters
+ /// - `self`: Pointer to the composer to modify (will be updated in-place)
+ /// - `waveinfo`: Information about the wave and when it should start
+ ///
+ /// ## Memory Management
+ /// The old internal array is freed, and a new one is allocated with the
+ /// appended wave. The composer pointer is updated to reference the new data.
+ ///
+ /// ## Example
+ /// ```
+ /// var composer: Composer(f64) = Composer(f64).init(allocator, .{
+ /// .sample_rate = 44100,
+ /// .channels = 1,
+ /// });
+ /// defer composer.deinit();
+ ///
+ /// // Append modifies composer in-place
+ /// try composer.append(.{ .wave = wave1, .start_point = 0 });
+ /// try composer.append(.{ .wave = wave2, .start_point = 44100 });
+ /// ```
+ pub fn append(self: *Self, waveinfo: WaveInfo) std.mem.Allocator.Error!void {
+ var d: std.array_list.Aligned(WaveInfo, null) = .empty;
+ try d.appendSlice(self.allocator, self.info);
+ try d.append(self.allocator, waveinfo);
+
+ const result: Self = Self{
+ .allocator = self.allocator,
+ .info = try d.toOwnedSlice(self.allocator),
+
+ .sample_rate = self.sample_rate,
+ .channels = self.channels,
+ };
+
+ self.deinit(); // Free the old one now
+ self.* = result; // Then copy the new one (result variable)
+ }
- // Filter each WaveInfo to append padding both of start and last
- for (self.info) |waveinfo| {
- const padded_at_start: []const f32 = padding_for_start(waveinfo.wave.data, waveinfo.start_point, self.allocator);
- defer self.allocator.free(padded_at_start);
+ /// Appends multiple waves to the composition. This method modifies the composer in-place.
+ ///
+ /// ## Parameters
+ /// - `self`: Pointer to the composer to modify (will be updated in-place)
+ /// - `append_list`: Slice of WaveInfo structures to append
+ ///
+ /// ## Memory Management
+ /// The old internal array is freed, and a new one is allocated with the
+ /// appended wave. The composer pointer is updated to reference the new data.
+ pub fn appendSlice(self: *Self, append_list: []const WaveInfo) std.mem.Allocator.Error!void {
+ var d: std.array_list.Aligned(WaveInfo, null) = .empty;
+ try d.appendSlice(self.allocator, self.info);
+ try d.appendSlice(self.allocator, append_list);
+
+ const result: Self = Self{
+ .allocator = self.allocator,
+ .info = try d.toOwnedSlice(self.allocator),
+
+ .sample_rate = self.sample_rate,
+ .channels = self.channels,
+ };
+
+ self.deinit();
+ self.* = result;
+ }
- const padded_at_start_and_last: []const f32 = padding_for_last(padded_at_start, end_point, self.allocator);
- defer self.allocator.free(padded_at_start_and_last);
+ /// Finalizes the composition by mixing all waves together.
+ ///
+ /// This creates a single Wave by:
+ /// 1. Calculating the total length needed
+ /// 2. Padding each wave to align with its start_point
+ /// 3. Mixing all waves together using the provided mixer function
+ ///
+ /// ## Parameters
+ /// - `self`: The composer containing all the waves to mix
+ /// - `options`: Mixing options (includes the mixer function)
+ ///
+ /// ## Returns
+ /// A new Wave containing the final mixed composition
+ ///
+ /// ## Performance Notes
+ /// Memory usage is proportional to: `number_of_waves × total_length × sample_size`
+ /// Each wave is temporarily padded to the full composition length before mixing.
+ /// Consider using this for up to ~100 overlapping waves on typical systems.
+ pub fn finalize(self: Self, options: Wave(T).mixOptions) std.mem.Allocator.Error!Wave(T) {
+ var end_point: usize = 0;
+
+ // Calculate the length for emitted wave
+ for (self.info) |waveinfo| {
+ const ep = waveinfo.start_point + waveinfo.wave.samples.len;
+
+ if (end_point < ep)
+ end_point = ep;
+ }
+
+ var padded_waveinfo_list: std.array_list.Aligned(WaveInfo, null) = .empty;
+ defer padded_waveinfo_list.deinit(self.allocator);
+
+ // Filter each WaveInfo to append padding both of start and last
+ for (self.info) |waveinfo| {
+ const padded_at_start: []const T = try padding_for_start(waveinfo.wave.samples, waveinfo.start_point, self.allocator);
+ defer self.allocator.free(padded_at_start);
+
+ const padded_at_start_and_last: []const T = try padding_for_last(padded_at_start, end_point, self.allocator);
+ defer self.allocator.free(padded_at_start_and_last);
+
+ const wave = try Wave(T).init(padded_at_start_and_last, self.allocator, .{
+ .sample_rate = self.sample_rate,
+ .channels = self.channels,
+ });
+
+ const wi: WaveInfo = WaveInfo{
+ .wave = wave,
+ .start_point = waveinfo.start_point,
+ };
+
+ try padded_waveinfo_list.append(self.allocator, wi);
+ }
+
+ const padded_waveinfo_slice: []const WaveInfo = try padded_waveinfo_list.toOwnedSlice(self.allocator);
+ defer self.allocator.free(padded_waveinfo_slice);
+
+ const empty_samples: []const T = try generate_soundless_samples(end_point, self.allocator);
+ defer self.allocator.free(empty_samples);
+
+ var result = try Wave(T).init(empty_samples, self.allocator, .{
+ .sample_rate = self.sample_rate,
+ .channels = self.channels,
+ });
+
+ for (padded_waveinfo_slice) |waveinfo| {
+ const wave = try result.mix(waveinfo.wave, options);
+ result.deinit();
+ waveinfo.wave.deinit();
+ result = wave;
+ }
+
+ return result;
+ }
- const wave: Wave = Wave.init(padded_at_start_and_last, self.allocator, .{
- .sample_rate = self.sample_rate,
- .channels = self.channels,
- .bits = self.bits,
- });
+ fn padding_for_start(samples: []const T, start_point: usize, allocator: std.mem.Allocator) std.mem.Allocator.Error![]const T {
+ const padding_length: usize = start_point;
+ var padding: std.array_list.Aligned(T, null) = .empty;
+ defer padding.deinit(allocator);
- const wi: WaveInfo = WaveInfo{
- .wave = wave,
- .start_point = waveinfo.start_point,
- };
+ // Append padding
+ for (0..padding_length) |_|
+ try padding.append(allocator, 0.0);
- padded_waveinfo_list.append(self.allocator, wi) catch @panic("Out of memory");
- }
+ // Append samples slice
+ try padding.appendSlice(allocator, samples);
- const padded_waveinfo_slice: []const WaveInfo = padded_waveinfo_list.toOwnedSlice(self.allocator) catch @panic("Out of memory");
- defer self.allocator.free(padded_waveinfo_slice);
+ const result: []const T = try padding.toOwnedSlice(allocator);
- const empty_data: []const f32 = generate_soundless_data(end_point, self.allocator);
- defer self.allocator.free(empty_data);
+ return result;
+ }
- var result: Wave = Wave.init(empty_data, self.allocator, .{
- .sample_rate = self.sample_rate,
- .channels = self.channels,
- .bits = self.bits,
- });
+ fn padding_for_last(samples: []const T, end_point: usize, allocator: std.mem.Allocator) std.mem.Allocator.Error![]const T {
+ std.debug.assert(samples.len <= end_point);
- for (padded_waveinfo_slice) |waveinfo| {
- const wave = result.mix(waveinfo.wave);
- result.deinit();
- waveinfo.wave.deinit();
- result = wave;
- }
+ const padding_length: usize = end_point - samples.len;
+ var padding: std.array_list.Aligned(T, null) = .empty;
+ defer padding.deinit(allocator);
- return result;
-}
+ // Append samples slice
+ try padding.appendSlice(allocator, samples);
-fn padding_for_start(data: []const f32, start_point: usize, allocator: std.mem.Allocator) []const f32 {
- const padding_length: usize = start_point;
- var padding: std.array_list.Aligned(f32, null) = .empty;
- defer padding.deinit(allocator);
+ // Append padding
+ for (0..padding_length) |_|
+ try padding.append(allocator, 0.0);
- // Append padding
- for (0..padding_length) |_|
- padding.append(allocator, 0.0) catch @panic("Out of memory");
+ const result: []const T = try padding.toOwnedSlice(allocator);
- // Append data slice
- padding.appendSlice(allocator, data) catch @panic("Out of memory");
+ return result;
+ }
- const result: []const f32 = padding.toOwnedSlice(allocator) catch @panic("Out of memory");
+ fn generate_soundless_samples(length: usize, allocator: std.mem.Allocator) std.mem.Allocator.Error![]const T {
+ var list: std.array_list.Aligned(T, null) = .empty;
+ defer list.deinit(allocator);
- return result;
-}
+ // Append empty wave
+ for (0..length) |_|
+ try list.append(allocator, 0.0);
-fn padding_for_last(data: []const f32, end_point: usize, allocator: std.mem.Allocator) []const f32 {
- std.debug.assert(data.len <= end_point);
+ const result: []const T = try list.toOwnedSlice(allocator);
- const padding_length: usize = end_point - data.len;
- var padding: std.array_list.Aligned(f32, null) = .empty;
- defer padding.deinit(allocator);
+ return result;
+ }
- // Append data slice
- padding.appendSlice(allocator, data) catch @panic("Out of memory");
+ test "padding_for_start" {
+ const allocator = testing.allocator;
+ const samples: []const T = &[_]T{ 1.0, 1.0 };
+ const start_point: usize = 10;
- // Append padding
- for (0..padding_length) |_|
- padding.append(allocator, 0.0) catch @panic("Out of memory");
+ const result: []const T = try padding_for_start(samples, start_point, allocator);
+ defer allocator.free(result);
- const result: []const f32 = padding.toOwnedSlice(allocator) catch @panic("Out of memory");
+ try testing.expectEqual(samples.len + start_point, result.len);
- return result;
-}
+ const expected: []const T = &[_]T{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0 };
+ for (0..result.len) |i| {
+ try testing.expectApproxEqAbs(expected[i], result[i], 0.001);
+ }
+ }
-fn generate_soundless_data(length: usize, allocator: std.mem.Allocator) []const f32 {
- var list: std.array_list.Aligned(f32, null) = .empty;
- defer list.deinit(allocator);
+ test "init & deinit" {
+ const allocator = testing.allocator;
+ const composer = Self.init(allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer composer.deinit();
+ }
- // Append empty wave
- for (0..length) |_|
- list.append(allocator, 0.0) catch @panic("Out of memory");
+ test "init_with & deinit" {
+ const allocator = testing.allocator;
+ var reader = std.Io.Reader.fixed(@embedFile("./assets/sine.wav"));
- const result: []const f32 = list.toOwnedSlice(allocator) catch @panic("Out of memory");
+ const wave = try Wave(T).read(.wav, allocator, &reader);
+ defer wave.deinit();
- return result;
-}
+ const info: []const WaveInfo = &[_]WaveInfo{ .{ .wave = wave, .start_point = 0 }, .{ .wave = wave, .start_point = 0 } };
-test "padding_for_start" {
- const allocator = testing.allocator;
- const data: []const f32 = &[_]f32{ 1.0, 1.0 };
- const start_point: usize = 10;
+ const composer = try Self.init_with(info, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer composer.deinit();
+ }
- const result: []const f32 = padding_for_start(data, start_point, allocator);
- defer allocator.free(result);
+ test "append" {
+ const allocator = testing.allocator;
+ var reader = std.Io.Reader.fixed(@embedFile("./assets/sine.wav"));
+ var composer = Self.init(allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer composer.deinit();
- try testing.expectEqual(data.len + start_point, result.len);
+ const wave = try Wave(T).read(.wav, allocator, &reader);
+ defer wave.deinit();
- const expected: []const f32 = &[_]f32{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0 };
- for (0..result.len) |i| {
- try testing.expectApproxEqAbs(expected[i], result[i], 0.001);
- }
-}
+ try composer.append(.{ .wave = wave, .start_point = 0 });
-test "init & deinit" {
- const allocator = testing.allocator;
- const composer = Self.init(allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer composer.deinit();
-}
+ try testing.expectEqualSlices(WaveInfo, composer.info, &[_]WaveInfo{.{ .wave = wave, .start_point = 0 }});
+ }
-test "init_with & deinit" {
- const allocator = testing.allocator;
+ test "appendSlice" {
+ const allocator = testing.allocator;
+ var composer = Self.init(allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer composer.deinit();
- const wave = Wave.from_file_content(@embedFile("./assets/sine.wav"), allocator);
- defer wave.deinit();
+ var reader = std.Io.Reader.fixed(@embedFile("./assets/sine.wav"));
- const info: []const WaveInfo = &[_]WaveInfo{ .{ .wave = wave, .start_point = 0 }, .{ .wave = wave, .start_point = 0 } };
+ const wave = try Wave(T).read(.wav, allocator, &reader);
+ defer wave.deinit();
- const composer = Self.init_with(info, allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer composer.deinit();
-}
+ var append_list: std.array_list.Aligned(WaveInfo, null) = .empty;
+ defer append_list.deinit(allocator);
+ try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
+ try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
+ try composer.appendSlice(append_list.items);
-test "append" {
- const allocator = testing.allocator;
- const composer = Self.init(allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer composer.deinit();
+ try testing.expectEqualSlices(WaveInfo, composer.info, &[_]WaveInfo{ .{ .wave = wave, .start_point = 0 }, .{ .wave = wave, .start_point = 0 } });
+ }
- const wave = Wave.from_file_content(@embedFile("./assets/sine.wav"), allocator);
- defer wave.deinit();
+ test "finalize" {
+ const allocator = testing.allocator;
+ var composer = Self.init(allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer composer.deinit();
- const appended_composer = composer.append(.{ .wave = wave, .start_point = 0 });
- defer appended_composer.deinit();
+ var samples: []T = try allocator.alloc(T, 44100);
+ defer allocator.free(samples);
- try testing.expectEqualSlices(WaveInfo, appended_composer.info, &[_]WaveInfo{.{ .wave = wave, .start_point = 0 }});
-}
+ for (0..samples.len) |i| {
+ samples[i] = 1.0;
+ }
-test "appendSlice" {
- const allocator = testing.allocator;
- const composer = Self.init(allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer composer.deinit();
+ const wave = try Wave(T).init(samples, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
- const wave = Wave.from_file_content(@embedFile("./assets/sine.wav"), allocator);
- defer wave.deinit();
+ var append_list: std.array_list.Aligned(WaveInfo, null) = .empty;
+ defer append_list.deinit(allocator);
+ try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
+ try append_list.append(allocator, .{ .wave = wave, .start_point = 44100 });
+ try composer.appendSlice(append_list.items);
- var append_list: std.array_list.Aligned(WaveInfo, null) = .empty;
- defer append_list.deinit(allocator);
- try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
- try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
+ const result = try composer.finalize(.{});
+ defer result.deinit();
- const appended_composer = composer.appendSlice(append_list.items);
- defer appended_composer.deinit();
+ try testing.expectEqual(result.samples.len, 88200);
- try testing.expectEqualSlices(WaveInfo, appended_composer.info, &[_]WaveInfo{ .{ .wave = wave, .start_point = 0 }, .{ .wave = wave, .start_point = 0 } });
+ try testing.expectEqual(result.sample_rate, 44100);
+ try testing.expectEqual(result.channels, 1);
+ }
+ };
}
-test "finalize" {
- const allocator = testing.allocator;
- const composer = Self.init(allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer composer.deinit();
-
- var data: []f32 = try allocator.alloc(f32, 44100);
- defer allocator.free(data);
-
- for (0..data.len) |i| {
- data[i] = 1.0;
- }
-
- const wave = Wave.init(data, allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer wave.deinit();
-
- var append_list: std.array_list.Aligned(WaveInfo, null) = .empty;
- defer append_list.deinit(allocator);
- try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
- try append_list.append(allocator, .{ .wave = wave, .start_point = 44100 });
-
- const appended_composer = composer.appendSlice(append_list.items);
- defer appended_composer.deinit();
-
- const result: Wave = appended_composer.finalize();
- defer result.deinit();
-
- try testing.expectEqual(result.data.len, 88200);
-
- try testing.expectEqual(result.sample_rate, 44100);
- try testing.expectEqual(result.channels, 1);
- try testing.expectEqual(result.bits, 16);
+test "Run tests for each samples' type" {
+ _ = inner(f128);
+ _ = inner(f80);
+ _ = inner(f64);
+ // _ = inner(f32); zigggwavvv 0.2.1 cannot use f32 as samples' type
}
diff --git a/src/root.zig b/src/root.zig
index d6d5ee2..71aabd6 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -1,9 +1,60 @@
-//! # lightmix
+//! # lightmix - Audio Synthesis and Manipulation Library
//!
-//! `lightmix` is an audio processing library written by Zig-lang.
+//! lightmix is a Zig library for audio waveform generation, manipulation, and composition.
+//! It provides type-safe, generic interfaces for working with audio data.
+//!
+//! ## Core Types
+//!
+//! ### Wave
+//! The `Wave` type function creates audio waveform types for different sample formats.
+//! It supports operations like mixing, filtering, and reading/writing WAV files.
+//!
+//! ### Composer
+//! The `Composer` type function creates types for sequencing and overlaying multiple
+//! Wave instances in time to create complex audio arrangements.
+//!
+//! ## Example Usage
+//!
+//! ```zig
+//! const std = @import("std");
+//! const lightmix = @import("lightmix");
+//! const Wave = lightmix.Wave;
+//! const Composer = lightmix.Composer;
+//!
+//! pub fn main() !void {
+//! const allocator = std.heap.page_allocator;
+//!
+//! // Create a simple sine wave
+//! var samples: [44100]f64 = undefined;
+//! for (0..samples.len) |i| {
+//! const t = @as(f64, @floatFromInt(i)) / 44100.0;
+//! samples[i] = @sin(t * 440.0 * 2.0 * std.math.pi);
+//! }
+//!
+//! // Wave(T).init() creates a deep copy of samples
+//! const wave: Wave(f64) = Wave(f64).init(&samples, allocator, .{
+//! .sample_rate = 44100,
+//! .channels = 1,
+//! });
+//! defer wave.deinit();
+//!
+//! // Create a composition with multiple waves
+//! var composer: Composer(f64) = Composer(f64).init(allocator, .{
+//! .sample_rate = 44100,
+//! .channels = 1,
+//! });
+//! defer composer.deinit();
+//!
+//! // Composer(T).append() modifies the Composer in-place
+//! composer.append(.{ .wave = wave, .start_point = 0 });
+//!
+//! const result: Wave(f64) = try composer.finalize(.{});
+//! defer result.deinit();
+//! }
+//! ```
-pub const Wave = @import("./wave.zig");
-pub const Composer = @import("./composer.zig");
+pub const Wave = @import("./wave.zig").inner;
+pub const Composer = @import("./composer.zig").inner;
test "Import tests" {
_ = @import("./wave.zig");
diff --git a/src/wave.zig b/src/wave.zig
index 31d3521..4dca70e 100644
--- a/src/wave.zig
+++ b/src/wave.zig
@@ -1,537 +1,954 @@
-//! # Wave
-//!
-//! Contains a wave data.
-
const std = @import("std");
-const build_options = @import("build_options");
-const lightmix_wav = @import("lightmix_wav");
-const known_folders = @import("known-folders");
+const zigggwavvv = @import("zigggwavvv");
+const zaudio = @import("zaudio");
const testing = std.testing;
-const Self = @This();
-
-/// A wave data, expressed by array contains f32.
-data: []const f32,
-
-/// An allocator
-allocator: std.mem.Allocator,
-
-/// This wave's sample rate.
-sample_rate: usize,
-
-/// This wave's channels.
-channels: usize,
-
-/// This wave's bits.
-bits: usize,
-
-pub const initOptions = struct {
- sample_rate: usize,
- channels: usize,
- bits: usize,
-};
+/// Wave type function: Creates a Wave type for the specified sample type.
+///
+/// Wave represents audio waveform data with methods for manipulation, mixing, and I/O.
+///
+/// ## Type Parameter
+/// - `T`: The sample data type (typically f64, f80, or f128 for floating-point audio)
+///
+/// ## Usage
+/// ```zig
+/// const Wave = lightmix.Wave;
+/// const wave = Wave(f64).init(samples, allocator, .{
+/// .sample_rate = 44100,
+/// .channels = 1,
+/// });
+/// defer wave.deinit();
+/// ```
+pub fn inner(comptime T: type) type {
+ return struct {
+ const Self = @This();
+
+ samples: []const T,
+ allocator: std.mem.Allocator,
+ sample_rate: u32,
+ channels: u16,
+
+ /// Supported audio file formats for reading wave data.
+ pub const LowLevelInterfaces = enum {
+ wav,
+
+ /// Reads wave data using the specified file format.
+ ///
+ /// ## Parameters
+ /// - `self`: The file format to use for decoding
+ /// - `allocator`: Memory allocator for sample data
+ /// - `reader`: A reader interface providing the raw file bytes
+ ///
+ /// ## Returns
+ /// A `LowLevelWave` containing the decoded samples, sample rate, and channel count
+ ///
+ /// ## Errors
+ /// Returns errors from the underlying format decoder or allocation failures
+ pub fn read(self: LowLevelInterfaces, allocator: std.mem.Allocator, reader: anytype) anyerror!LowLevelWave {
+ return switch (self) {
+ .wav => {
+ const v = try zigggwavvv.Wave(T).read(allocator, reader);
+
+ return .{
+ .samples = v.samples,
+ .sample_rate = v.sample_rate,
+ .channels = v.channels,
+ };
+ },
+ };
+ }
-/// Initialize a Wave with wave data (`[]const f32`).
-pub fn init(data: []const f32, allocator: std.mem.Allocator, options: initOptions) Self {
- const owned_data = allocator.alloc(f32, data.len) catch @panic("Out of memory");
- @memcpy(owned_data, data);
+ /// Writes wave data using the specified file format.
+ ///
+ /// ## Parameters
+ /// - `self`: The file format to use for encoding
+ /// - `wave`: The wave instance to write
+ /// - `writer`: A writer interface for the output bytes
+ /// - `options`: Format-specific write options (see `writeOptions`)
+ ///
+ /// ## Errors
+ /// Returns errors from the underlying format encoder or I/O failures
+ pub fn write(self: LowLevelInterfaces, wave: Self, writer: anytype, options: writeOptions(self)) anyerror!void {
+ switch (self) {
+ .wav => {
+ const zigggwavvv_wave = zigggwavvv.Wave(T).init(.{
+ .format_code = options.format_code,
+ .sample_rate = wave.sample_rate,
+ .channels = wave.channels,
+ .bits = options.bits,
+ .samples = try wave.allocator.dupe(T, wave.samples),
+ });
+ defer zigggwavvv_wave.deinit(wave.allocator);
+
+ try zigggwavvv_wave.write(writer, .{
+ .allocator = wave.allocator,
+ .use_fact = options.use_fact,
+ .use_peak = options.use_peak,
+ .peak_timestamp = options.peak_timestamp,
+ });
+ },
+ }
+ }
- return Self{
- .data = owned_data,
- .allocator = allocator,
+ /// Returns the format-specific options type for `write`.
+ ///
+ /// ## Parameters
+ /// - `interface`: The file format whose options type to return
+ ///
+ /// ## Returns
+ /// The options struct type corresponding to the given format
+ pub fn writeOptions(interface: LowLevelInterfaces) type {
+ return switch (interface) {
+ .wav => writeWavOptions,
+ };
+ }
- .sample_rate = options.sample_rate,
- .channels = options.channels,
- .bits = options.bits,
- };
-}
+ /// Options for writing wave data to a WAV file.
+ pub const writeWavOptions = struct {
+ /// Whether to include a `fact` chunk in the output file
+ use_fact: bool = false,
+ /// Whether to include a `PEAK` chunk in the output file
+ use_peak: bool = false,
+ /// Timestamp value written into the `PEAK` chunk (only used when `use_peak` is true)
+ peak_timestamp: u32 = 0,
+
+ /// Bits per sample (e.g. 16 or 24)
+ bits: u16,
+ /// Audio format code (e.g. PCM or IEEE float)
+ format_code: zigggwavvv.FormatCode,
+ };
+
+ /// Raw wave data returned by low-level format decoders.
+ pub const LowLevelWave = struct {
+ samples: []const T,
+ sample_rate: u32,
+ channels: u16,
+ };
+ };
-/// Mix a wave and the other wave.
-/// The each wave data's length, sample_rate, channels, and bits must be same.
-/// That's because we cannot ajust the timing for every users which the each wave should be played.
-pub fn mix(self: Self, other: Self) Self {
- std.debug.assert(self.data.len == other.data.len);
- std.debug.assert(self.sample_rate == other.sample_rate);
- std.debug.assert(self.channels == other.channels);
- std.debug.assert(self.bits == other.bits);
-
- if (self.data.len == 0)
- return Self{
- .data = &[_]f32{},
- .allocator = self.allocator,
-
- .sample_rate = self.sample_rate,
- .channels = self.channels,
- .bits = self.bits,
+ /// Options for initializing a Wave instance.
+ pub const InitOptions = struct {
+ sample_rate: u32,
+ channels: u16,
};
- var data: std.array_list.Aligned(f32, null) = .empty;
+ /// Creates a new Wave instance from the provided sample data.
+ ///
+ /// The function creates a deep copy of the sample data, so the caller
+ /// retains ownership of the original samples slice.
+ ///
+ /// ## Parameters
+ /// - `samples`: Slice of sample data to copy
+ /// - `allocator`: Memory allocator for internal allocations
+ /// - `options`: Initialization options (sample rate and channel count)
+ ///
+ /// ## Returns
+ /// A new Wave instance containing a copy of the sample data
+ ///
+ /// ## Errors
+ /// - Allocator error (errors.OutOfMemory)
+ pub fn init(
+ samples: []const T,
+ allocator: std.mem.Allocator,
+ options: InitOptions,
+ ) std.mem.Allocator.Error!Self {
+ const owned_samples = try allocator.alloc(T, samples.len);
+ @memcpy(owned_samples, samples);
+
+ return Self{
+ .samples = owned_samples,
+ .allocator = allocator,
+
+ .sample_rate = options.sample_rate,
+ .channels = options.channels,
+ };
+ }
- for (0..self.data.len) |i| {
- data.append(self.allocator, self.data[i] + other.data[i]) catch @panic("Out of memory");
- }
+ /// Options for mixing two waves together.
+ pub const mixOptions = struct {
+ mixer: fn (T, T) T = default_mixing_expression,
+ };
- const result: []const f32 = data.toOwnedSlice(self.allocator) catch @panic("Out of memory");
+ /// Default mixing function that adds two samples together.
+ ///
+ /// ## Parameters
+ /// - `left`: Sample value from the first wave
+ /// - `right`: Sample value from the second wave
+ ///
+ /// ## Returns
+ /// The sum of the two sample values
+ pub fn default_mixing_expression(left: T, right: T) T {
+ const result: T = left + right;
+ return result;
+ }
- return Self{
- .data = result,
- .allocator = self.allocator,
+ /// Mixes this wave with another wave, combining their samples.
+ ///
+ /// Both waves must have the same length, sample rate, and channel count.
+ /// The mixing is performed sample-by-sample using the provided mixer function.
+ ///
+ /// ## Parameters
+ /// - `self`: The first wave to mix
+ /// - `other`: The second wave to mix
+ /// - `options`: Mixing options (includes the mixer function)
+ ///
+ /// ## Returns
+ /// A new Wave containing the mixed result
+ ///
+ /// ## Errors
+ /// - Allocator error (errors.OutOfMemory)
+ pub fn mix(self: Self, other: Self, options: mixOptions) std.mem.Allocator.Error!Self {
+ std.debug.assert(self.samples.len == other.samples.len);
+ std.debug.assert(self.sample_rate == other.sample_rate);
+ std.debug.assert(self.channels == other.channels);
+
+ if (self.samples.len == 0)
+ return Self{
+ .samples = &[_]T{},
+ .allocator = self.allocator,
+
+ .sample_rate = self.sample_rate,
+ .channels = self.channels,
+ };
+
+ var samples: std.array_list.Aligned(T, null) = .empty;
+
+ for (0..self.samples.len) |i| {
+ const left: T = self.samples[i];
+ const right: T = other.samples[i];
+ const result: T = options.mixer(left, right);
+
+ try samples.append(self.allocator, result);
+ }
- .sample_rate = self.sample_rate,
- .channels = self.channels,
- .bits = self.bits,
- };
-}
+ const result: []const T = try samples.toOwnedSlice(self.allocator);
-pub fn fill_zero_to_end(self: Self, start: usize, end: usize) !Self {
- // Initialization
- var result: std.array_list.Aligned(f32, null) = .empty;
- try result.appendSlice(self.allocator, self.data);
+ return Self{
+ .samples = result,
+ .allocator = self.allocator,
- const delete_count: usize = result.items.len - start;
+ .sample_rate = self.sample_rate,
+ .channels = self.channels,
+ };
+ }
- for (0..delete_count) |_| {
- _ = result.pop();
- }
+ /// Separates the wave into two parts at a specified point.
+ ///
+ /// This function splits the wave's samples into two new Wave instances:
+ /// one containing samples from the start to the separation point,
+ /// and another containing samples from the separation point to the end.
+ ///
+ /// ## Parameters
+ /// - `self`: The wave to separate
+ /// - `options`: Separation options (allocator and separation point)
+ ///
+ /// ## Returns
+ /// A SeparateResult containing two Wave instances (initial and terminal)
+ ///
+ /// ## Errors
+ /// - `SeparatingZeroLengthWave`: If the wave has no samples
+ /// - `TooBigSeparatePoint`: If the separation point exceeds the wave length
+ /// - `OutOfMemory`: Allocator error when memory allocation fails
+ pub fn separate(
+ self: Self,
+ options: SeparateOptions,
+ ) (SeparateErrors || std.mem.Allocator.Error)!SeparateResult {
+ if (self.samples.len == 0)
+ return error.SeparatingZeroLengthWave;
+
+ if (self.samples.len < options.separate_point)
+ return error.TooBigSeparatePoint;
+
+ const initial_len = options.separate_point;
+ const terminal_len = self.samples.len - options.separate_point;
+ var initial: []T = try options.allocator.alloc(T, initial_len);
+ var terminal: []T = try options.allocator.alloc(T, terminal_len);
+
+ for (0..initial.len) |i| {
+ initial[i] = self.samples[i];
+ }
+ for (0..terminal.len) |i| {
+ terminal[i] = self.samples[initial_len + i];
+ }
- std.debug.assert(start == result.items.len);
+ const result = SeparateResult{
+ .initial = Self{
+ .allocator = options.allocator,
+ .samples = initial,
+ .sample_rate = self.sample_rate,
+ .channels = self.channels,
+ },
+ .terminal = Self{
+ .allocator = options.allocator,
+ .samples = terminal,
+ .sample_rate = self.sample_rate,
+ .channels = self.channels,
+ },
+ };
- for (delete_count..end) |_| {
- try result.append(self.allocator, 0.0);
- }
+ return result;
+ }
- std.debug.assert(result.items.len == end);
+ /// Options for separating a wave into two parts.
+ pub const SeparateOptions = struct {
+ /// Memory allocator for the new wave instances
+ allocator: std.mem.Allocator,
+ /// Sample index at which to split the wave (exclusive for initial, inclusive for terminal)
+ separate_point: usize,
+ };
- return Self{
- .data = try result.toOwnedSlice(self.allocator),
- .allocator = self.allocator,
+ /// Result of separating a wave into two parts.
+ pub const SeparateResult = struct {
+ /// The first part of the wave (from start to separation point)
+ initial: Self,
+ /// The second part of the wave (from separation point to end)
+ terminal: Self,
+ };
- .sample_rate = self.sample_rate,
- .channels = self.channels,
- .bits = self.bits,
- };
-}
+ /// Errors that can occur when separating a wave.
+ pub const SeparateErrors = error{
+ /// Attempted to separate a wave with zero samples
+ SeparatingZeroLengthWave,
+ /// The separation point exceeds the wave's sample length
+ TooBigSeparatePoint,
+ };
-/// Free the Wave struct
-pub fn deinit(self: Self) void {
- self.allocator.free(self.data);
-}
+ /// Truncates the wave at a start point and fills with zeros to the end point.
+ ///
+ /// This is useful for creating silence or padding at the end of a wave.
+ ///
+ /// ## Parameters
+ /// - `self`: The wave to modify
+ /// - `start`: Sample index where truncation begins
+ /// - `end`: Sample index where the new wave ends (filled with zeros)
+ ///
+ /// ## Returns
+ /// A new Wave with samples from 0 to `start`, then zeros from `start` to `end`
+ ///
+ /// ## Errors
+ /// - Allocator error (errors.OutOfMemory)
+ pub fn fill_zero_to_end(self: Self, start: usize, end: usize) std.mem.Allocator.Error!Self {
+ // Initialization
+ var result: std.array_list.Aligned(T, null) = .empty;
+ try result.appendSlice(self.allocator, self.samples);
+
+ const delete_count: usize = result.items.len - start;
+
+ for (0..delete_count) |_| {
+ _ = result.pop();
+ }
-/// Create Wave from binary data
-/// The data argument can receive a binary data, as @embedFile("./assets/sine.wav")
-/// Therefore you can use this function as:
-/// const wave = Wave.from_file_content(@embedFile("./asset/sine.wav"), allocator);
-pub fn from_file_content(content: []const u8, allocator: std.mem.Allocator) Self {
- var stream = std.io.fixedBufferStream(content);
- var decoder = lightmix_wav.decoder(stream.reader()) catch |err| {
- std.debug.print("In lightmix_wav\n", .{});
- std.debug.print("{any}\n", .{err});
- @panic("Failed to create decoder");
- };
+ std.debug.assert(start == result.items.len);
- var buf: [64]f32 = undefined;
- var arraylist: std.array_list.Aligned(f32, null) = .empty;
+ for (delete_count..end) |_| {
+ try result.append(self.allocator, 0.0);
+ }
- while (true) {
- // Read samples as f32. Channels are interleaved.
- const samples_read = decoder.read(f32, &buf) catch |err| {
- std.debug.print("In lightmix_wav\n", .{});
- std.debug.print("{any}\n", .{err});
- @panic("Failed to read samples from decoder");
- };
+ std.debug.assert(result.items.len == end);
- // < ------ Do something with samples in buf. ------ >
- arraylist.appendSlice(allocator, &buf) catch @panic("Out of memory");
+ return Self{
+ .samples = try result.toOwnedSlice(self.allocator),
+ .allocator = self.allocator,
- if (samples_read < buf.len) {
- break;
+ .sample_rate = self.sample_rate,
+ .channels = self.channels,
+ };
+ }
+ /// Frees the memory allocated for the wave's sample data.
+ ///
+ /// This must be called when you're done with a Wave instance to avoid memory leaks.
+ pub fn deinit(self: Self) void {
+ self.allocator.free(self.samples);
}
- }
-
- const result: []const f32 = arraylist.toOwnedSlice(allocator) catch @panic("Out of memory");
-
- const sample_rate: usize = decoder.sampleRate();
- const channels: usize = decoder.channels();
- const bits: usize = decoder.bits();
-
- return Self{
- .data = result,
- .allocator = allocator,
- .sample_rate = sample_rate,
- .channels = channels,
- .bits = bits,
- };
-}
+ /// Reads wave data from a file using the specified format.
+ ///
+ /// ## Parameters
+ /// - `file_extension`: The file format to use for decoding (e.g. `.wav`)
+ /// - `allocator`: Memory allocator for sample data
+ /// - `reader`: A reader interface for reading the audio file data
+ ///
+ /// ## Returns
+ /// A new Wave instance containing the audio data from the file
+ ///
+ /// ## Errors
+ /// Returns errors from the underlying format decoder or allocation failures
+ pub fn read(
+ file_extension: LowLevelInterfaces,
+ allocator: std.mem.Allocator,
+ reader: anytype,
+ ) anyerror!Self {
+ const lowlevel_wave = try file_extension.read(allocator, reader);
+
+ return Self{
+ .samples = lowlevel_wave.samples,
+ .allocator = allocator,
+ .sample_rate = lowlevel_wave.sample_rate,
+ .channels = lowlevel_wave.channels,
+ };
+ }
+ /// Writes wave data to a file writer using the specified format.
+ ///
+ /// ## Parameters
+ /// - `self`: The wave to write
+ /// - `file_extension`: The file format to use for encoding (e.g. `.wav`)
+ /// - `writer`: A writer interface for writing the audio file data
+ /// - `options`: Format-specific write options (e.g. bit depth, format code)
+ ///
+ /// ## Errors
+ /// Returns errors from the underlying format encoder or I/O failures
+ pub fn write(self: Self, file_extension: LowLevelInterfaces, writer: anytype, options: LowLevelInterfaces.writeOptions(file_extension)) anyerror!void {
+ try file_extension.write(self, writer, options);
+ }
-/// Writes down the wave data to `std.fs.File`.
-pub fn write(self: Self, file: std.fs.File) !void {
- var encoder = try lightmix_wav.encoder(i16, file, self.sample_rate, self.channels);
- try encoder.write(f32, self.data);
- try encoder.finalize();
-}
+ /// Applies a filter function with custom arguments to the wave.
+ ///
+ /// The original wave is automatically freed after the filter is applied.
+ /// This enables chaining multiple filters together efficiently.
+ ///
+ /// ## Parameters
+ /// - `self`: The wave to filter (will be freed after filtering)
+ /// - `args_type`: The type of the arguments to pass to the filter function
+ /// - `filter_fn`: The filter function to apply
+ /// - `args`: Arguments to pass to the filter function
+ ///
+ /// ## Errors
+ /// Returns any error produced by `filter_fn` or allocation failures
+ ///
+ /// ## Example Usage
+ /// ```zig
+ /// const DecayWithDebugPrintArgs = struct {
+ /// string: []const u8,
+ /// };
+ ///
+ /// /// Decay filter: Creates a linear fade-out effect
+ /// /// The volume decreases from 100% to 0% over the duration of the wave
+ /// fn decay_with_debug_print(comptime T: type, original_wave: Wave(T), args: DecayWithDebugPrintArgs) !Wave(T) {
+ /// var result_list: std.array_list.Aligned(T, null) = .empty;
+ /// defer result_list.deinit(original_wave.allocator);
+ ///
+ /// // Process each sample, applying a decay factor
+ /// for (original_wave.samples, 0..) |sample, n| {
+ /// // Calculate how far from the end we are
+ /// const remaining_samples = original_wave.samples.len - n;
+ ///
+ /// // Decay factor: 1.0 at start, 0.0 at end
+ /// const decay_factor = @as(T, @floatFromInt(remaining_samples)) /
+ /// @as(T, @floatFromInt(original_wave.samples.len));
+ ///
+ /// // Apply the decay to the sample
+ /// const decayed_sample = sample * decay_factor;
+ /// try result_list.append(original_wave.allocator, decayed_sample);
+ /// }
+ ///
+ /// // A example usage of args
+ /// // This means that you can accept any arguments to change this filter's effects
+ /// std.debug.print("A message from args: {s}\n", .{args.string});
+ ///
+ /// // Return a new Wave with the filtered samples
+ /// return Wave(T).init(result_list.items, original_wave.allocator, .{
+ /// .sample_rate = original_wave.sample_rate,
+ /// .channels = original_wave.channels,
+ /// });
+ /// }
+ ///
+ /// // Sine wave generation
+ /// var samples: [44100]f64 = undefined;
+ /// for (0..samples.len) |i| {
+ /// const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ /// samples[i] = 0.5 * @sin(radians_per_sec * t);
+ /// }
+ ///
+ /// const wave: Wave(f64) = Wave(f64).init(samples[0..], allocator, .{
+ /// .sample_rate = 44100,
+ /// .channels = 1,
+ /// });
+ ///
+ /// const decayed_wave: Wave(f64) = wave.filter(decay);
+ /// defer decayed_wave.deinit();
+ /// ```
+ pub fn filter_with(
+ self: *Self,
+ comptime args_type: type,
+ comptime filter_fn: anytype,
+ args: args_type,
+ ) anyerror!void {
+ const result: Self = try filter_fn(T, self.*, args);
+
+ // To destroy original samples array
+ // If we don't do this, we may catch some memory leaks by not to free original samples array
+ self.deinit();
+
+ // Assign the result into self
+ self.* = result;
+ }
-/// Filters a zig function.
-/// Use this function as `wave.filter_with(Args, your_filter, .{ args = 0 });`
-/// This function uses self.deinit() to avoid the memory leaks by not free the data arrays
-pub fn filter_with(
- self: Self,
- comptime args_type: type,
- filter_fn: fn (self: Self, args: args_type) anyerror!Self,
- args: args_type,
-) Self {
- // To destroy original data array
- // If we don't do this, we may catch some memory leaks by not to free original data array
- defer self.deinit();
-
- const result: Self = filter_fn(self, args) catch |err| {
- std.debug.print("{any}\n", .{err});
- @panic("Error happened in filter_with function...");
- };
+ /// Applies a filter function to the wave.
+ ///
+ /// The original wave is automatically freed after the filter is applied.
+ /// This enables chaining multiple filters together efficiently.
+ ///
+ /// ## Parameters
+ /// - `self`: The wave to filter (will be freed after filtering)
+ /// - `filter_fn`: The filter function to apply
+ ///
+ /// ## Errors
+ /// Returns any error produced by `filter_fn` or allocation failures
+ ///
+ /// ## Example Usage
+ /// ```zig
+ /// /// Decay filter: Creates a linear fade-out effect
+ /// /// The volume decreases from 100% to 0% over the duration of the wave
+ /// fn decay(comptime T: type, original_wave: Wave(T)) !Wave(T) {
+ /// var result_list: std.array_list.Aligned(T, null) = .empty;
+ /// defer result_list.deinit(original_wave.allocator);
+ ///
+ /// // Process each sample, applying a decay factor
+ /// for (original_wave.samples, 0..) |sample, n| {
+ /// // Calculate how far from the end we are
+ /// const remaining_samples = original_wave.samples.len - n;
+ ///
+ /// // Decay factor: 1.0 at start, 0.0 at end
+ /// const decay_factor = @as(T, @floatFromInt(remaining_samples)) /
+ /// @as(T, @floatFromInt(original_wave.samples.len));
+ ///
+ /// // Apply the decay to the sample
+ /// const decayed_sample = sample * decay_factor;
+ /// try result_list.append(original_wave.allocator, decayed_sample);
+ /// }
+ ///
+ /// // Return a new Wave with the filtered samples
+ /// return Wave(T).init(result_list.items, original_wave.allocator, .{
+ /// .sample_rate = original_wave.sample_rate,
+ /// .channels = original_wave.channels,
+ /// });
+ /// }
+ ///
+ /// // Sine wave generation
+ /// var samples: [44100]f64 = undefined;
+ /// for (0..samples.len) |i| {
+ /// const t = @as(f64, @floatFromInt(i)) / sample_rate;
+ /// samples[i] = 0.5 * @sin(radians_per_sec * t);
+ /// }
+ ///
+ /// const wave: Wave(f64) = Wave(f64).init(samples[0..], allocator, .{
+ /// .sample_rate = 44100,
+ /// .channels = 1,
+ /// });
+ ///
+ /// const decayed_wave: Wave(f64) = wave.filter(decay);
+ /// defer decayed_wave.deinit();
+ /// ```
+ pub fn filter(
+ self: *Self,
+ comptime filter_fn: anytype,
+ ) anyerror!void {
+ const result: Self = try filter_fn(T, self.*);
+
+ // To destroy original samples array
+ // If we don't do this, we may catch some memory leaks by not to free original samples array
+ self.deinit();
+
+ // Assign the result into self
+ self.* = result;
+ }
- return result;
-}
+ /// Plays the wave audio through the system audio output.
+ ///
+ /// Initializes the audio engine, converts samples to f32, and blocks until
+ /// playback completes.
+ ///
+ /// ## Parameters
+ /// - `self`: The wave to play
+ ///
+ /// ## Errors
+ /// Returns errors from the audio engine initialization or playback
+ pub fn play(self: Self) anyerror!void {
+ const allocator = self.allocator;
+
+ zaudio.init(allocator);
+ defer zaudio.deinit();
+
+ var engine: *zaudio.Engine = try zaudio.Engine.create(null);
+ defer engine.destroy();
+
+ const samples = try allocator.alloc(f32, self.samples.len);
+ defer allocator.free(samples);
+
+ for (self.samples, 0..) |orig_sample, i| {
+ samples[i] = @as(f32, @floatCast(orig_sample));
+ }
-/// Filters a zig function.
-/// Use this function as `wave.filter(your_filter);`
-/// This function uses self.deinit() to avoid the memory leaks by not free the data arrays
-pub fn filter(
- self: Self,
- filter_fn: fn (self: Self) anyerror!Self,
-) Self {
- // To destroy original data array
- // If we don't do this, we may catch some memory leaks by not to free original data array
- defer self.deinit();
-
- const result: Self = filter_fn(self) catch |err| {
- std.debug.print("{any}\n", .{err});
- @panic("Error happened in filter function...");
- };
+ var buffer_config = zaudio.AudioBuffer.Config.init(.float32, self.channels, samples.len / self.channels, samples.ptr);
+ buffer_config.sample_rate = self.sample_rate;
+ const buffer = try zaudio.AudioBuffer.create(buffer_config);
+ defer buffer.destroy();
+ const sound = try engine.createSoundFromDataSource(buffer.asDataSourceMut(), .{}, null);
+ defer sound.destroy();
- return result;
-}
+ try sound.start();
-/// Plays the wave instantly.
-/// You must enable `with_debug_features` in `build.zig`.
-pub fn debug_play(self: Self) !void {
- if (!build_options.with_debug_features)
- @panic("Wave.debug_play called without 'with_debug_features' flag. Please turn on the flag.");
+ while (!sound.isAtEnd()) {
+ std.Thread.sleep(10 * std.time.ns_per_ms);
+ }
+ }
- const cache_dir: std.fs.Dir = try known_folders.open(self.allocator, .cache, .{}) orelse @panic("XDG cache dir is null");
- cache_dir.access("lightmix", .{}) catch {
- try cache_dir.makeDir("lightmix");
- };
+ fn tmp_file(allocator: std.mem.Allocator) !struct { std.fs.File, std.testing.TmpDir, []const u8 } {
+ const timestamp: i64 = std.time.timestamp();
+ const timestamp_str: []const u8 = try std.fmt.allocPrint(allocator, "{s}", .{timestamp});
- const path: []const u8 = blk: {
- const cache_dir_path: []const u8 = try known_folders.getPath(self.allocator, .cache) orelse @panic("XDG cache dir is null");
- const now: std.time.Instant = try std.time.Instant.now();
+ const tmp = std.testing.tmpDir(.{});
+ const file = try tmp.dir.createFile(timestamp_str ++ ".wav", .{});
- const result: []const u8 = try std.fmt.allocPrint(self.allocator, "{s}/lightmix/result-{d}.{d}.wav", .{ cache_dir_path, now.timestamp.sec, now.timestamp.nsec });
- break :blk result;
- };
- defer self.allocator.free(path);
+ return .{ file, tmp, timestamp_str };
+ }
- const file = try cache_dir.createFile(path, .{});
+ /// Options for the `play` function (reserved for future use).
+ pub const PlayOptions = struct {
+ do_cleanup: bool,
+ };
- try self.write(file);
- std.debug.print("Wave file saved to: {s}\n", .{path});
+ test "read & deinit" {
+ const allocator = testing.allocator;
+ var reader = std.Io.Reader.fixed(@embedFile("./assets/sine.wav"));
+ const wave = try Self.read(.wav, allocator, &reader);
+ defer wave.deinit();
- // Debug-play
+ try testing.expectApproxEqAbs(wave.samples[0], 0.0, 0.00001);
+ try testing.expectApproxEqAbs(wave.samples[1], 0.05011139255958739, 0.00001);
+ try testing.expectApproxEqAbs(wave.samples[2], 0.1000396740623188, 0.00001);
- std.debug.print("Debug-playing...\n", .{});
+ try testing.expectEqual(wave.sample_rate, 44100);
+ try testing.expectEqual(wave.channels, 1);
+ }
- const c_headers = @cImport({
- @cInclude("portaudio.h");
- @cInclude("sndfile.h");
- });
+ test "init & deinit" {
+ const allocator = testing.allocator;
- const c_path: [*c]const u8 = try self.allocator.dupeZ(u8, path);
+ const generator = struct {
+ fn sinewave() [44100]T {
+ const sample_rate: T = 44100.0;
+ const radins_per_sec: T = 440.0 * 2.0 * std.math.pi;
- var sfInfo: c_headers.SF_INFO = undefined;
- const sndFile: ?*c_headers.SNDFILE = c_headers.sf_open(c_path, c_headers.SFM_READ, &sfInfo);
- defer _ = c_headers.sf_close(sndFile);
+ var result: [44100]T = undefined;
+ var i: usize = 0;
- if (c_headers.Pa_Initialize() != c_headers.paNoError)
- return error.PortaudioInitFailed;
+ while (i < result.len) : (i += 1) {
+ result[i] = 0.5 * std.math.sin(@as(T, @floatFromInt(i)) * radins_per_sec / sample_rate);
+ }
- defer _ = c_headers.Pa_Terminate();
+ return result;
+ }
+ };
- var stream: ?*c_headers.PaStream = undefined;
+ const samples: [44100]T = generator.sinewave();
+ const wave = try Self.init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
- if (self.bits != 16)
- @panic("Debug-playing the others of 16 bits' Wave is not implemented yet.");
+ try testing.expectEqual(wave.sample_rate, 44100);
+ try testing.expectEqual(wave.channels, 1);
+ }
- if (c_headers.Pa_OpenDefaultStream(
- &stream,
- 0,
- @intCast(self.channels),
- c_headers.paFloat32,
- @as(f64, @floatFromInt(self.sample_rate)),
- 256,
- null,
- null,
- ) != c_headers.paNoError)
- return error.PortaudioFailedToOpenStream;
+ test "mix" {
+ const allocator = testing.allocator;
+ const generator = struct {
+ fn sinewave() [44100]T {
+ const sample_rate: T = 44100.0;
+ const radins_per_sec: T = 440.0 * 2.0 * std.math.pi;
- defer _ = c_headers.Pa_CloseStream(stream.?);
+ var result: [44100]T = undefined;
+ var i: usize = 0;
- if (c_headers.Pa_StartStream(stream) != c_headers.paNoError)
- return error.PortaudioStartStreamFailed;
+ while (i < result.len) : (i += 1) {
+ result[i] = 0.5 * std.math.sin(@as(T, @floatFromInt(i)) * radins_per_sec / sample_rate);
+ }
- defer _ = c_headers.Pa_StopStream(stream);
+ return result;
+ }
+ };
- var buffer: [256 * 2]f32 = undefined; // stereo
- var framesRead: i64 = undefined;
+ const samples: [44100]T = generator.sinewave();
+ const wave = try Self.init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
- while (true) {
- framesRead = c_headers.sf_readf_float(sndFile, &buffer, 256);
+ const result: Self = try wave.mix(wave, .{});
+ defer result.deinit();
- if (c_headers.Pa_WriteStream(stream, &buffer, @intCast(framesRead)) != c_headers.paNoError)
- break;
+ try testing.expectEqual(wave.sample_rate, 44100);
+ try testing.expectEqual(wave.channels, 1);
- if (framesRead <= 0)
- break;
- }
+ try testing.expectApproxEqAbs(result.samples[0], 0.0, 0.00001);
+ try testing.expectApproxEqAbs(result.samples[1], 0.06264832417874369, 0.00001);
+ try testing.expectApproxEqAbs(result.samples[2], 0.1250505236945281, 0.00001);
+ }
- std.debug.print("Debug-playing finished.\n", .{});
-}
+ test "fill_zero_to_end" {
+ const allocator = testing.allocator;
+ const generator = struct {
+ fn sinewave() [44100]T {
+ const sample_rate: T = 44100.0;
+ const radins_per_sec: T = 440.0 * 2.0 * std.math.pi;
+
+ var result: [44100]T = undefined;
+ var i: usize = 0;
+
+ while (i < result.len) : (i += 1) {
+ result[i] = 0.5 * std.math.sin(@as(T, @floatFromInt(i)) * radins_per_sec / sample_rate);
+ }
+
+ return result;
+ }
+ };
+
+ const samples: [44100]T = generator.sinewave();
+ const wave = try Self.init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
+
+ const filled_wave: Self = try wave.fill_zero_to_end(22050, 44100);
+ defer filled_wave.deinit();
+
+ try testing.expectEqual(filled_wave.sample_rate, 44100);
+ try testing.expectEqual(filled_wave.channels, 1);
+
+ try testing.expectApproxEqAbs(filled_wave.samples[0], 0.0, 0.00001);
+ try testing.expectApproxEqAbs(filled_wave.samples[1], 0.031324162089371846, 0.00001);
+ try testing.expectApproxEqAbs(filled_wave.samples[2], 0.06252526184726405, 0.00001);
+
+ try testing.expectApproxEqAbs(filled_wave.samples[22049], -0.03132416208941618, 0.00001);
+ try testing.expectApproxEqAbs(filled_wave.samples[22050], 0.0, 0.00001);
+ try testing.expectApproxEqAbs(filled_wave.samples[22051], 0.0, 0.00001);
+ try testing.expectApproxEqAbs(filled_wave.samples[44099], 0.0, 0.00001);
+ }
-const DebugPlayErrors = error{
- PortaudioInitFailed,
- PortaudioFailedToOpenStream,
- PortaudioStartStreamFailed,
-};
+ test "filter_with" {
+ const allocator = testing.allocator;
+ const samples: []const T = &[_]T{};
-test "from_file_content & deinit" {
- const allocator = testing.allocator;
- const wave = Self.from_file_content(@embedFile("./assets/sine.wav"), allocator);
- defer wave.deinit();
+ var wave = try Self.init(samples[0..], allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ try wave.filter_with(ArgsForTesting, test_filter_with_args, .{ .samples = 3 });
+ defer wave.deinit();
- try testing.expectEqual(wave.data[0], 0.0);
- try testing.expectEqual(wave.data[1], 5.0109863e-2);
- try testing.expectEqual(wave.data[2], 1.0003662e-1);
+ try testing.expectEqual(wave.sample_rate, 44100);
+ try testing.expectEqual(wave.channels, 1);
- try testing.expectEqual(wave.sample_rate, 44100);
- try testing.expectEqual(wave.channels, 1);
- try testing.expectEqual(wave.bits, 16);
-}
+ try testing.expectEqual(wave.samples.len, 3);
+ try testing.expectEqual(wave.samples[0], 0.0);
+ try testing.expectEqual(wave.samples[1], 0.0);
+ try testing.expectEqual(wave.samples[2], 0.0);
+ }
-test "init & deinit" {
- const allocator = testing.allocator;
+ test "filter" {
+ const allocator = testing.allocator;
+ const samples: []const T = &[_]T{};
+
+ var wave = try Self.init(samples, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ try wave.filter(test_filter_without_args);
+ defer wave.deinit();
+
+ try testing.expectEqual(wave.sample_rate, 44100);
+ try testing.expectEqual(wave.channels, 1);
+
+ try testing.expectEqual(wave.samples.len, 5);
+ try testing.expectEqual(wave.samples[0], 0.0);
+ try testing.expectEqual(wave.samples[1], 0.0);
+ try testing.expectEqual(wave.samples[2], 0.0);
+ try testing.expectEqual(wave.samples[3], 0.0);
+ try testing.expectEqual(wave.samples[4], 0.0);
+ }
- const generator = struct {
- fn sinewave() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = 440.0 * 2.0 * std.math.pi;
+ test "filter memory leaks' check" {
+ const allocator = testing.allocator;
+ const samples: []const T = &[_]T{};
+
+ var wave = try Self.init(samples, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ try wave.filter(test_filter_without_args);
+ try wave.filter(test_filter_without_args);
+ try wave.filter(test_filter_without_args);
+ try wave.filter(test_filter_without_args);
+ defer wave.deinit();
+
+ try testing.expectEqual(wave.sample_rate, 44100);
+ try testing.expectEqual(wave.channels, 1);
+
+ try testing.expectEqual(wave.samples.len, 5);
+ try testing.expectEqual(wave.samples[0], 0.0);
+ try testing.expectEqual(wave.samples[1], 0.0);
+ try testing.expectEqual(wave.samples[2], 0.0);
+ try testing.expectEqual(wave.samples[3], 0.0);
+ try testing.expectEqual(wave.samples[4], 0.0);
+ }
- var result: [44100]f32 = undefined;
- var i: usize = 0;
+ test "init with empty samples" {
+ const allocator = testing.allocator;
+ const samples: []const T = &[_]T{};
- while (i < result.len) : (i += 1) {
- result[i] = 0.5 * std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate);
- }
+ const wave = try Self.init(samples, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
- return result;
+ try testing.expectEqual(wave.samples.len, 0);
+ try testing.expectEqual(wave.sample_rate, 44100);
+ try testing.expectEqual(wave.channels, 1);
}
- };
- const data: [44100]f32 = generator.sinewave();
- const wave = Self.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer wave.deinit();
+ test "init creates deep copy of samples" {
+ const allocator = testing.allocator;
+ var original_samples = [_]T{ 1.0, 2.0, 3.0 };
+ const wave = try Self.init(&original_samples, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
+
+ // Modify original samples
+ original_samples[0] = 999.0;
+
+ // Wave samples should be unchanged (deep copy was made)
+ try testing.expectEqual(wave.samples[0], 1.0);
+ try testing.expectEqual(wave.samples[1], 2.0);
+ try testing.expectEqual(wave.samples[2], 3.0);
+ }
- try testing.expectEqual(wave.sample_rate, 44100);
- try testing.expectEqual(wave.channels, 1);
- try testing.expectEqual(wave.bits, 16);
-}
+ test "init with different channels" {
+ const allocator = testing.allocator;
+ const samples: []const T = &[_]T{ 1.0, 2.0, 3.0, 4.0 };
+
+ // Mono
+ const wave_mono = try Self.init(samples, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave_mono.deinit();
+ try testing.expectEqual(wave_mono.channels, 1);
+
+ // Stereo
+ const wave_stereo = try Self.init(samples, allocator, .{
+ .sample_rate = 44100,
+ .channels = 2,
+ });
+ defer wave_stereo.deinit();
+ try testing.expectEqual(wave_stereo.channels, 2);
+ }
-test "mix" {
- const allocator = testing.allocator;
- const generator = struct {
- fn sinewave() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = 440.0 * 2.0 * std.math.pi;
+ test "mix preserves wave properties" {
+ const allocator = testing.allocator;
+ const samples1: []const T = &[_]T{ 1.0, 2.0, 3.0 };
+ const samples2: []const T = &[_]T{ 0.5, 1.0, 1.5 };
+
+ const wave1 = try Self.init(samples1, allocator, .{
+ .sample_rate = 48000,
+ .channels = 2,
+ });
+ defer wave1.deinit();
+
+ const wave2 = try Self.init(samples2, allocator, .{
+ .sample_rate = 48000,
+ .channels = 2,
+ });
+ defer wave2.deinit();
+
+ const result: Self = try wave1.mix(wave2, .{});
+ defer result.deinit();
+
+ try testing.expectEqual(result.sample_rate, 48000);
+ try testing.expectEqual(result.channels, 2);
+ try testing.expectEqual(result.samples.len, 3);
+ try testing.expectEqual(result.samples[0], 1.5);
+ try testing.expectEqual(result.samples[1], 3.0);
+ try testing.expectEqual(result.samples[2], 4.5);
+ }
- var result: [44100]f32 = undefined;
- var i: usize = 0;
+ test "read with different sample rates" {
+ const allocator = testing.allocator;
+ var reader = std.Io.Reader.fixed(@embedFile("./assets/sine.wav"));
+ const wave = try Self.read(.wav, allocator, &reader);
+ defer wave.deinit();
- while (i < result.len) : (i += 1) {
- result[i] = 0.5 * std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate);
- }
+ // Verify the wave has valid properties
+ try testing.expect(wave.sample_rate > 0);
+ try testing.expect(wave.channels > 0);
+ try testing.expect(wave.samples.len > 0);
+ }
- return result;
+ test "separate func separates a Wave" {
+ const allocator = testing.allocator;
+ const samples: []const T = &[_]T{ 1.0, 2.0, 3.0, 4.0, 5.0 };
+ const original = try Self.init(samples, allocator, .{
+ .sample_rate = 41000,
+ .channels = 1,
+ });
+ defer original.deinit();
+
+ const result = try original.separate(.{
+ .allocator = allocator,
+ .separate_point = 3,
+ });
+ defer result.initial.deinit();
+ defer result.terminal.deinit();
+
+ try testing.expectEqualSlices(T, result.initial.samples, &.{ 1.0, 2.0, 3.0 });
+ try testing.expectEqualSlices(T, result.terminal.samples, &.{ 4.0, 5.0 });
}
};
-
- const data: [44100]f32 = generator.sinewave();
- const wave = Self.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- });
- defer wave.deinit();
-
- const result: Self = wave.mix(wave);
- defer result.deinit();
-
- try testing.expectEqual(wave.sample_rate, 44100);
- try testing.expectEqual(wave.channels, 1);
- try testing.expectEqual(wave.bits, 16);
-
- try testing.expectEqual(result.data[0], 0.0);
- try testing.expectEqual(result.data[1], 6.2648326e-2);
- try testing.expectEqual(result.data[2], 1.2505053e-1);
}
-test "fill_zero_to_end" {
- const allocator = testing.allocator;
- const generator = struct {
- fn sinewave() [44100]f32 {
- const sample_rate: f32 = 44100.0;
- const radins_per_sec: f32 = 440.0 * 2.0 * std.math.pi;
-
- var result: [44100]f32 = undefined;
- var i: usize = 0;
+test "Run tests for each samples' type" {
+ _ = inner(f128);
+ _ = inner(f80);
+ _ = inner(f64);
+ // _ = inner(f32); zigggwavvv 0.2.1 cannot use f32 as samples' type
+}
- while (i < result.len) : (i += 1) {
- result[i] = 0.5 * std.math.sin(@as(f32, @floatFromInt(i)) * radins_per_sec / sample_rate);
- }
+fn test_filter_without_args(comptime SampleType: type, original: inner(SampleType)) !inner(SampleType) {
+ var result: std.array_list.Aligned(SampleType, null) = .empty;
+ defer result.deinit(original.allocator);
- return result;
- }
- };
+ for (0..5) |_|
+ try result.append(original.allocator, 0.0);
- const data: [44100]f32 = generator.sinewave();
- const wave = Self.init(data[0..], allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
+ return inner(SampleType).init(result.items, original.allocator, .{
+ .sample_rate = original.sample_rate,
+ .channels = original.channels,
});
- defer wave.deinit();
-
- const filled_wave: Self = try wave.fill_zero_to_end(22050, 44100);
- defer filled_wave.deinit();
-
- try testing.expectEqual(filled_wave.sample_rate, 44100);
- try testing.expectEqual(filled_wave.channels, 1);
- try testing.expectEqual(filled_wave.bits, 16);
-
- try testing.expectEqual(filled_wave.data[0], 0.0);
- try testing.expectEqual(filled_wave.data[1], 3.1324163e-2);
- try testing.expectEqual(filled_wave.data[2], 6.2525265e-2);
-
- try testing.expectEqual(filled_wave.data[22049], -3.1344667e-2);
- try testing.expectEqual(filled_wave.data[22050], 0.0);
- try testing.expectEqual(filled_wave.data[22051], 0.0);
- try testing.expectEqual(filled_wave.data[44099], 0.0);
-}
-
-test "filter_with" {
- const allocator = testing.allocator;
- const data: []const f32 = &[_]f32{};
- const wave: Self = Self.init(data, allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- }).filter_with(ArgsForTesting, test_filter_with_args, .{ .samples = 3 });
- defer wave.deinit();
-
- try testing.expectEqual(wave.sample_rate, 44100);
- try testing.expectEqual(wave.channels, 1);
- try testing.expectEqual(wave.bits, 16);
-
- try testing.expectEqual(wave.data.len, 3);
- try testing.expectEqual(wave.data[0], 0.0);
- try testing.expectEqual(wave.data[1], 0.0);
- try testing.expectEqual(wave.data[2], 0.0);
}
fn test_filter_with_args(
- original_wave: Self,
+ comptime SampleType: type,
+ original: inner(SampleType),
args: ArgsForTesting,
-) !Self {
- var result: std.array_list.Aligned(f32, null) = .empty;
+) !inner(SampleType) {
+ var result: std.array_list.Aligned(SampleType, null) = .empty;
+ defer result.deinit(original.allocator);
for (0..args.samples) |_|
- try result.append(original_wave.allocator, 0.0);
+ try result.append(original.allocator, 0.0);
- return Self{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
+ return try inner(SampleType).init(result.items, original.allocator, .{
+ .sample_rate = original.sample_rate,
+ .channels = original.channels,
+ });
}
const ArgsForTesting = struct {
samples: usize,
};
-
-test "filter" {
- const allocator = testing.allocator;
- const data: []const f32 = &[_]f32{};
- const wave: Self = Self.init(data, allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- }).filter(test_filter_without_args);
- defer wave.deinit();
-
- try testing.expectEqual(wave.sample_rate, 44100);
- try testing.expectEqual(wave.channels, 1);
- try testing.expectEqual(wave.bits, 16);
-
- try testing.expectEqual(wave.data.len, 5);
- try testing.expectEqual(wave.data[0], 0.0);
- try testing.expectEqual(wave.data[1], 0.0);
- try testing.expectEqual(wave.data[2], 0.0);
- try testing.expectEqual(wave.data[3], 0.0);
- try testing.expectEqual(wave.data[4], 0.0);
-}
-
-fn test_filter_without_args(original_wave: Self) !Self {
- var result: std.array_list.Aligned(f32, null) = .empty;
-
- for (0..5) |_|
- try result.append(original_wave.allocator, 0.0);
-
- return Self{
- .data = try result.toOwnedSlice(original_wave.allocator),
- .allocator = original_wave.allocator,
-
- .sample_rate = original_wave.sample_rate,
- .channels = original_wave.channels,
- .bits = original_wave.bits,
- };
-}
-
-test "filter memory leaks' check" {
- const allocator = testing.allocator;
- const data: []const f32 = &[_]f32{};
- const wave: Self = Self.init(data, allocator, .{
- .sample_rate = 44100,
- .channels = 1,
- .bits = 16,
- })
- .filter(test_filter_without_args)
- .filter(test_filter_without_args)
- .filter(test_filter_without_args)
- .filter(test_filter_without_args);
- defer wave.deinit();
-
- try testing.expectEqual(wave.sample_rate, 44100);
- try testing.expectEqual(wave.channels, 1);
- try testing.expectEqual(wave.bits, 16);
-
- try testing.expectEqual(wave.data.len, 5);
- try testing.expectEqual(wave.data[0], 0.0);
- try testing.expectEqual(wave.data[1], 0.0);
- try testing.expectEqual(wave.data[2], 0.0);
- try testing.expectEqual(wave.data[3], 0.0);
- try testing.expectEqual(wave.data[4], 0.0);
-}
diff --git a/examples/Wave/generate_function/src/assets/sine.wav b/tests/assets/sine.wav
similarity index 100%
rename from examples/Wave/generate_function/src/assets/sine.wav
rename to tests/assets/sine.wav
diff --git a/tests/assets/soundless.wav b/tests/assets/soundless.wav
new file mode 100644
index 0000000..24d262a
Binary files /dev/null and b/tests/assets/soundless.wav differ
diff --git a/tests/composer.zig b/tests/composer.zig
new file mode 100644
index 0000000..e0675b5
--- /dev/null
+++ b/tests/composer.zig
@@ -0,0 +1,72 @@
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+const Composer = lightmix.Composer;
+
+test "Compose multiple soundless Wave" {
+ const allocator = std.testing.allocator;
+ var composer = Composer(f64).init(allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer composer.deinit();
+
+ const data: []const f64 = try generate_soundless_data(44100, allocator);
+ defer allocator.free(data);
+
+ const wave: Wave(f64) = try Wave(f64).init(data, allocator, .{
+ .sample_rate = 44100,
+ .channels = 1,
+ });
+ defer wave.deinit();
+
+ var append_list: std.array_list.Aligned(Composer(f64).WaveInfo, null) = .empty;
+ defer append_list.deinit(allocator);
+ try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
+ try append_list.append(allocator, .{ .wave = wave, .start_point = 0 });
+
+ try composer.appendSlice(append_list.items);
+
+ const result = try composer.finalize(.{});
+ defer result.deinit();
+
+ // Create TmpDir
+ var tmpDir = std.testing.tmpDir(.{});
+ defer tmpDir.cleanup();
+
+ var file = try tmpDir.dir.createFile("result.wav", .{});
+ defer file.close();
+ const buf = try allocator.alloc(u8, 10 * 1024 * 1024);
+ defer allocator.free(buf);
+ var writer = file.writer(buf);
+
+ // Write Wave into the file
+ try result.write(.wav, &writer.interface, .{
+ .format_code = .pcm,
+ .bits = 16,
+ });
+
+ try writer.interface.flush();
+
+ // Read the written wave file
+ const result_bytes = try tmpDir.dir.readFileAlloc(allocator, "result.wav", 10 * 1024 * 1024);
+ defer allocator.free(result_bytes);
+
+ // Read the actual file
+ const expected_bytes = try std.fs.cwd().readFileAlloc(allocator, "tests/assets/soundless.wav", 100 * 1024 * 1024);
+ defer allocator.free(expected_bytes);
+
+ try std.testing.expectEqualSlices(u8, expected_bytes, result_bytes);
+}
+
+fn generate_soundless_data(length: usize, allocator: std.mem.Allocator) ![]const f64 {
+ var list: std.array_list.Aligned(f64, null) = .empty;
+
+ // Append empty wave
+ for (0..length) |_|
+ try list.append(allocator, 0.0);
+
+ const result: []const f64 = try list.toOwnedSlice(allocator);
+
+ return result;
+}
diff --git a/tests/wave.zig b/tests/wave.zig
new file mode 100644
index 0000000..842bea2
--- /dev/null
+++ b/tests/wave.zig
@@ -0,0 +1,31 @@
+const std = @import("std");
+const lightmix = @import("lightmix");
+const Wave = lightmix.Wave;
+
+test "read sine.wav" {
+ const allocator = std.testing.allocator;
+ var reader = std.Io.Reader.fixed(@embedFile("./assets/sine.wav"));
+
+ const sine = try Wave(f64).read(.wav, allocator, &reader);
+ defer sine.deinit();
+
+ const expected_samples: []const f64 = &[_]f64{
+ 0,
+ 0.05011139255958739,
+ 0.1000396740623188,
+ 0.14960173345133823,
+ 0.19849238563188573,
+ 0.24665059358500932,
+ 0.293954283272805,
+ 0.33979308450575274,
+ 0.3847468489638966,
+ 0.42771691030610065,
+ 0.4693441572313608,
+ 0.5090182195501571,
+ 0.5465865047151097,
+ 0.5822626422925504,
+ 0.6153447065645314,
+ 0.6462599566637165,
+ };
+ try std.testing.expectEqualSlices(f64, expected_samples, sine.samples[0..16]);
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment