├── assets ├── demo.gif └── index.html ├── .github ├── FUNDING.yml ├── dependabot.yml ├── CODEOWNERS ├── bors.toml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── pages.yml │ ├── ci.yml │ ├── docker.yml │ └── cd.yml └── PULL_REQUEST_TEMPLATE.md ├── .dockerignore ├── Dockerfile ├── SECURITY.md ├── .gitignore ├── RELEASE.md ├── src ├── defaults.zig ├── file.zig ├── args.zig ├── gen.zig ├── main.zig └── wav.zig ├── release.sh ├── LICENSE ├── man └── linuxwave.1 ├── cliff.toml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── README.md /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/linuxwave/HEAD/assets/demo.gif -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: orhun 2 | patreon: orhunp 3 | custom: ["https://www.buymeacoffee.com/orhun"] 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Directories 2 | .github/ 3 | assets/ 4 | kcov-output/ 5 | zig-cache/ 6 | zig-out/ 7 | man/ 8 | docs/ 9 | 10 | # Files 11 | .gitignore 12 | output.wav 13 | cliff.toml 14 | release.sh 15 | *.md 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/about-codeowners/ 2 | # for more info about CODEOWNERS file 3 | 4 | # It uses the same pattern rule for gitignore file 5 | # https://git-scm.com/docs/gitignore#_pattern_format 6 | 7 | # Core 8 | * @orhun 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eloitor/zig:0.14.0 as builder 2 | RUN apk update 3 | RUN apk add --no-cache git 4 | WORKDIR /app 5 | COPY . . 6 | RUN zig build --release=safe 7 | 8 | FROM alpine:3.8 9 | WORKDIR /app 10 | COPY --from=builder /app/zig-out/bin/linuxwave /usr/local/bin 11 | RUN touch output.wav && \ 12 | chown 1000:1000 output.wav 13 | USER 1000:1000 14 | ENTRYPOINT [ "linuxwave" ] 15 | -------------------------------------------------------------------------------- /.github/bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "Build with args: ''", 3 | "Build with args: '--release=safe'", 4 | "Build with args: '--release=fast'", 5 | "Build with args: '--release=small'", 6 | "Test", 7 | "Check formatting", 8 | ] 9 | delete_merged_branches = true 10 | update_base_for_deletes = true 11 | cut_body_after = "
" 12 | commit_title = "chore(pull): merge ${PR_REFS}" 13 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 🐧🎵 2 | 3 | ## Supported Versions 4 | 5 | The following versions are supported with security updates: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 0.1.x | :white_check_mark: | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Please use the [GitHub Security Advisories](https://github.com/orhun/linuxwave/security/advisories/new) feature to report vulnerabilities. 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/zig 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=zig 3 | 4 | ### zig ### 5 | # Zig programming language 6 | 7 | zig-cache/ 8 | .zig-cache/ 9 | zig-out/ 10 | build/ 11 | build-*/ 12 | docgen_tmp/ 13 | docs/ 14 | 15 | # End of https://www.toptal.com/developers/gitignore/api/zig 16 | 17 | # Default output file 18 | output.wav 19 | 20 | # Test coverage files 21 | kcov-output 22 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Creating a Release 🐧🎵 2 | 3 | [GitHub](https://github.com/orhun/linuxwave/releases) releases are automated via [GitHub actions](.github/workflows/cd.yml) and triggered by pushing a tag. 4 | 5 | 1. Run the [release script](./release.sh): `./release.sh v[X.Y.Z]` (requires [git-cliff](https://github.com/orhun/git-cliff) for changelog generation) 6 | 2. Push the changes: `git push` 7 | 3. Check if [Continuous Integration](https://github.com/orhun/linuxwave/actions) workflow is completed successfully. 8 | 4. Push the tags: `git push --tags` 9 | 5. Wait for [Continuous Deployment](https://github.com/orhun/linuxwave/actions) workflow to finish. 10 | -------------------------------------------------------------------------------- /src/defaults.zig: -------------------------------------------------------------------------------- 1 | const wav = @import("wav.zig"); 2 | 3 | // Default input file. 4 | pub const input = "/dev/urandom"; 5 | // Default output file. 6 | pub const output = "output.wav"; 7 | // Semitones from the base note in a major musical scale. 8 | pub const scale = "0,2,3,5,7,8,10,12"; 9 | // Default sample rate. 10 | pub const sample_rate: usize = 24000; 11 | // Frequency of A4. () 12 | pub const note: f32 = 440; 13 | // Default number of channels. 14 | pub const channels: usize = 1; 15 | // Default sample format. 16 | pub const format = wav.Format.S16_LE; 17 | // Default volume control. 18 | pub const volume: u8 = 50; 19 | // Default duration. 20 | pub const duration: usize = 20; 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 🐧🎵 3 | about: Make a suggestion for the project 4 | title: "" 5 | labels: "enhancement" 6 | assignees: "orhun" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | 17 | **Describe alternatives you've considered** 18 | 19 | 20 | 21 | **Additional context** 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 🐛 3 | about: Report a bug to help us improve 4 | title: "" 5 | labels: "bug" 6 | assignees: "orhun" 7 | --- 8 | 9 | **Describe the bug** 10 | 11 | 12 | 13 | **To reproduce** 14 | 15 | 21 | 22 | **Expected behavior** 23 | 24 | 25 | 26 | **Screenshots / Logs** 27 | 28 | 29 | 30 | **Software information** 31 | 32 | 33 | 34 | - Operating system: 35 | - Zig version: 36 | - Project version: 37 | 38 | **Additional context** 39 | 40 | 41 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ -z "$1" ]; then 6 | echo "Please provide a tag." 7 | echo "Usage: ./release.sh v[X.Y.Z]" 8 | exit 9 | fi 10 | 11 | echo "Preparing $1..." 12 | # update the version 13 | msg="\/\/ managed by release.sh" 14 | sed -E -i "s/^(const version = ).* $msg$/\1\"${1#v}\"; $msg/" build.zig 15 | # zig build test 16 | # update the changelog 17 | git cliff --tag "$1" >CHANGELOG.md 18 | git add -A 19 | git commit -m "chore(release): prepare for $1" 20 | git show 21 | # generate a changelog for the tag message 22 | changelog=$(git cliff --tag "$1" --unreleased --strip all | sed -e '/^#/d' -e '/^$/d') 23 | # create a signed tag 24 | # https://keyserver.ubuntu.com/pks/lookup?search=0xC0701E98290D90B8&op=vindex 25 | git -c user.name="linuxwave" \ 26 | -c user.email="linuxwave@protonmail.com" \ 27 | -c user.signingkey="21B6926360C8A0C82C48155DC0701E98290D90B8" \ 28 | tag -f -s -a "$1" -m "Release $1" -m "$changelog" 29 | git tag -v "$1" 30 | echo "Done!" 31 | echo "Now push the commit (git push) and the tag (git push --tags)." 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023-2024 Orhun Parmaksız 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/file.zig: -------------------------------------------------------------------------------- 1 | //! File operations helper. 2 | 3 | const std = @import("std"); 4 | 5 | /// Reads the given file and returns a byte array with the length of `len`. 6 | pub fn readBytes( 7 | allocator: std.mem.Allocator, 8 | path: []const u8, 9 | len: usize, 10 | ) ![]u8 { 11 | const file = try std.fs.cwd().openFile(path, .{}); 12 | defer file.close(); 13 | var list = try std.ArrayList(u8).initCapacity(allocator, len); 14 | var buffer = list.allocatedSlice(); 15 | const bytes_read = try file.read(buffer); 16 | return buffer[0..bytes_read]; 17 | } 18 | 19 | test "read bytes from the file" { 20 | // Get the current directory. 21 | var cwd_buffer: [std.fs.max_path_bytes]u8 = undefined; 22 | const cwd = try std.posix.getcwd(&cwd_buffer); 23 | 24 | // Concatenate the current directory with the builder file. 25 | const allocator = std.testing.allocator; 26 | const path = try std.fs.path.join(allocator, &.{ cwd, "build.zig" }); 27 | defer allocator.free(path); 28 | 29 | // Read the contents of the file and compare. 30 | const bytes = try readBytes(allocator, path, 9); 31 | try std.testing.expectEqualStrings("const std", bytes); 32 | defer allocator.free(bytes); 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | concurrency: 16 | group: "pages" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | deploy: 21 | name: Deploy website 22 | runs-on: ubuntu-latest 23 | environment: 24 | name: github-pages 25 | url: ${{ steps.deployment.outputs.page_url }} 26 | steps: 27 | - name: Checkout the repository 28 | uses: actions/checkout@v6 29 | 30 | - name: Install Zig 31 | uses: mlugg/setup-zig@v2 32 | with: 33 | version: 0.14.0 34 | 35 | - name: Show Zig version 36 | run: | 37 | zig version 38 | zig env 39 | 40 | - name: Build 41 | run: | 42 | zig build -Ddocs=true 43 | mkdir website 44 | cp assets/index.html website 45 | cp -r docs website 46 | 47 | - name: Setup Pages 48 | uses: actions/configure-pages@v5 49 | - name: Upload artifact 50 | uses: actions/upload-pages-artifact@v4 51 | with: 52 | path: website 53 | 54 | - name: Deploy to GitHub Pages 55 | id: deployment 56 | uses: actions/deploy-pages@v2 57 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Motivation and Context 8 | 9 | 10 | 11 | 12 | ## How Has This Been Tested? 13 | 14 | 15 | 16 | 17 | 18 | ## Screenshots / Logs (if applicable) 19 | 20 | ## Types of Changes 21 | 22 | 23 | 24 | - [ ] Bug fix (non-breaking change which fixes an issue) 25 | - [ ] New feature (non-breaking change which adds functionality) 26 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 27 | - [ ] Documentation (no code change) 28 | - [ ] Refactor (refactoring production code) 29 | - [ ] Other 30 | 31 | ## Checklist: 32 | 33 | 34 | 35 | - [ ] My code follows the code style of this project. 36 | - [ ] I have updated the documentation accordingly. 37 | - [ ] I have added tests to cover my changes. 38 | - [ ] All new and existing tests passed. 39 | -------------------------------------------------------------------------------- /man/linuxwave.1: -------------------------------------------------------------------------------- 1 | .\" Manpage for linuxwave. 2 | .TH linuxwave "1" "April 2023" "linuxwave" "User Commands" 3 | .SH NAME 4 | linuxwave \- generate music from the entropy of Linux 5 | .SH SYNOPSIS 6 | .B linuxwave 7 | [options] 8 | .SH DESCRIPTION 9 | linuxwave generates music compositions from the input data. The default input is /dev/urandom 10 | .SH OPTIONS 11 | .TP 12 | \fB\-s\fR, \fB\-\-scale\fR 13 | Sets the musical scale [default: 0,2,3,5,7,8,10,12] 14 | .TP 15 | \fB\-n\fR, \fB\-\-note\fR 16 | Sets the frequency of the note [default: 440 (A4)] 17 | .TP 18 | \fB\-r\fR, \fB\-\-rate\fR 19 | Sets the sample rate [default: 24000] 20 | .TP 21 | \fB\-c\fR, \fB\-\-channels\fR 22 | Sets the number of channels [default: 1] 23 | .TP 24 | \fB\-f\fR, \fB\-\-format\fR 25 | Sets the sample format [default: S16_LE] 26 | .TP 27 | \fB\-v\fR, \fB\-\-volume\fR 28 | Sets the volume (0\-100) [default: 50] 29 | .TP 30 | \fB\-d\fR, \fB\-\-duration\fR 31 | Sets the duration [default: 20] 32 | .TP 33 | \fB\-i\fR, \fB\-\-input\fR 34 | Sets the input file [default: /dev/urandom] 35 | .TP 36 | \fB\-o\fR, \fB\-\-output\fR 37 | Sets the output file [default: output.wav] 38 | .TP 39 | \fB\-V\fR, \fB\-\-version\fR 40 | Display version information. 41 | .TP 42 | \fB\-h\fR, \fB\-\-help\fR 43 | Display this help and exit. 44 | .SH BUGS 45 | No known bugs. 46 | Use "Issues" page for reporting bugs: 47 | .SH AUTHOR 48 | Written by Orhun Parmaksız 49 | .SH COPYRIGHT 50 | Copyright © 2023-2024 Orhun Parmaksız 51 | .P 52 | The MIT License 53 | -------------------------------------------------------------------------------- /src/args.zig: -------------------------------------------------------------------------------- 1 | const wav = @import("wav.zig"); 2 | const clap = @import("clap"); 3 | 4 | // Banner text. 5 | pub const banner = 6 | \\ 7 | \\ ▜ ▘ 8 | \\ ▐ ▌▛▌▌▌▚▘▌▌▌▀▌▌▌█▌ — Generate music from the entropy of Linux 🐧🎵 9 | \\ ▐▖▌▌▌▙▌▞▖▚▚▘█▌▚▘▙▖ https://github.com/orhun/linuxwave 10 | \\ 11 | \\Options: 12 | ; 13 | 14 | // Parameters that the program can take. 15 | pub const params = clap.parseParamsComptime( 16 | \\-s, --scale Sets the musical scale [default: 0,2,3,5,7,8,10,12] 17 | \\-n, --note Sets the frequency of the note [default: 440 (A4)] 18 | \\-r, --rate Sets the sample rate [default: 24000] 19 | \\-c, --channels Sets the number of channels [default: 1] 20 | \\-f, --format Sets the sample format [default: S16_LE] 21 | \\-v, --volume Sets the volume (0-100) [default: 50] 22 | \\-d, --duration Sets the duration [default: 20] 23 | \\-i, --input Sets the input file [default: /dev/urandom] 24 | \\-o, --output Sets the output file [default: output.wav] 25 | \\-V, --version Display version information. 26 | \\-h, --help Display this help and exit. 27 | ); 28 | 29 | // Style options for the help text. 30 | pub const help_options = clap.HelpOptions{ .spacing_between_parameters = 0, .indent = 2, .description_on_new_line = false }; 31 | 32 | /// Argument parsers. 33 | pub const parsers = .{ 34 | .NUM = clap.parsers.int(usize, 0), 35 | .SCALE = clap.parsers.string, 36 | .HZ = clap.parsers.float(f32), 37 | .VOL = clap.parsers.int(u8, 0), 38 | .SECS = clap.parsers.int(usize, 0), 39 | .FILE = clap.parsers.string, 40 | .FORMAT = clap.parsers.enumeration(wav.Format), 41 | }; 42 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - staging # for bors 9 | - trying # for bors 10 | schedule: 11 | - cron: "0 0 * * 0" 12 | 13 | jobs: 14 | build: 15 | name: "Build with args: '${{ matrix.OPTIMIZE }}'" 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | OPTIMIZE: 21 | [ 22 | "", 23 | "--release=safe", 24 | "--release=fast", 25 | "--release=small" 26 | ] 27 | steps: 28 | - name: Checkout the repository 29 | uses: actions/checkout@v6 30 | 31 | - name: Install Zig 32 | uses: mlugg/setup-zig@v2 33 | with: 34 | version: 0.14.0 35 | 36 | - name: Show Zig version 37 | run: | 38 | zig version 39 | zig env 40 | 41 | - name: Build 42 | run: zig build ${{ matrix.OPTIMIZE }} 43 | 44 | - name: Test 45 | run: zig build test ${{ matrix.OPTIMIZE }} 46 | 47 | test: 48 | name: Test 49 | runs-on: ubuntu-22.04 50 | steps: 51 | - name: Checkout the repository 52 | uses: actions/checkout@v6 53 | 54 | - name: Install Zig 55 | uses: mlugg/setup-zig@v2 56 | with: 57 | version: 0.14.0 58 | 59 | - name: Install kcov 60 | run: | 61 | sudo apt-get update 62 | sudo apt-get install -y \ 63 | --no-install-recommends \ 64 | --allow-unauthenticated \ 65 | kcov 66 | 67 | - name: Test 68 | run: zig build test -Dtest-coverage 69 | 70 | - name: Upload coverage to Codecov 71 | uses: codecov/codecov-action@v5 72 | with: 73 | name: code-coverage-report 74 | directory: kcov-output 75 | fail_ci_if_error: true 76 | verbose: true 77 | token: ${{ secrets.CODECOV_TOKEN }} 78 | 79 | fmt: 80 | name: Check formatting 81 | runs-on: ubuntu-latest 82 | steps: 83 | - name: Checkout the repository 84 | uses: actions/checkout@v6 85 | 86 | - name: Install Zig 87 | uses: mlugg/setup-zig@v2 88 | with: 89 | version: 0.14.0 90 | 91 | - name: Check formatting 92 | run: zig fmt --check . 93 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Automated Builds 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v*.*.*" 9 | pull_request: 10 | branches: 11 | - main 12 | schedule: 13 | - cron: "0 0 * * 0" 14 | 15 | jobs: 16 | docker: 17 | name: Docker Build and Push 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v6 22 | 23 | - name: Docker meta 24 | id: meta 25 | uses: docker/metadata-action@v5 26 | with: 27 | images: | 28 | orhunp/linuxwave 29 | ghcr.io/${{ github.repository_owner }}/linuxwave 30 | tags: | 31 | type=schedule 32 | type=ref,event=branch 33 | type=ref,event=pr 34 | type=sha 35 | type=raw,value=latest 36 | type=semver,pattern={{version}} 37 | 38 | - name: Set up QEMU 39 | uses: docker/setup-qemu-action@v3 40 | with: 41 | platforms: arm64 42 | 43 | - name: Set up Docker Buildx 44 | id: buildx 45 | uses: docker/setup-buildx-action@v3 46 | 47 | - name: Cache Docker layers 48 | uses: actions/cache@v5 49 | with: 50 | path: /tmp/.buildx-cache 51 | key: ${{ runner.os }}-buildx-${{ github.sha }} 52 | restore-keys: | 53 | ${{ runner.os }}-buildx- 54 | 55 | - name: Login to Docker Hub 56 | if: github.event_name != 'pull_request' 57 | uses: docker/login-action@v3 58 | with: 59 | username: orhunp 60 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 61 | 62 | - name: Login to GHCR 63 | if: github.event_name != 'pull_request' 64 | uses: docker/login-action@v3 65 | with: 66 | registry: ghcr.io 67 | username: ${{ github.repository_owner }} 68 | password: ${{ secrets.GITHUB_TOKEN }} 69 | 70 | - name: Build and push 71 | id: docker_build 72 | uses: docker/build-push-action@v6 73 | with: 74 | context: ./ 75 | file: ./Dockerfile 76 | platforms: linux/amd64,linux/arm64 77 | builder: ${{ steps.buildx.outputs.name }} 78 | push: ${{ github.event_name != 'pull_request' }} 79 | tags: ${{ steps.meta.outputs.tags }} 80 | sbom: true 81 | provenance: true 82 | labels: ${{ steps.meta.outputs.labels }} 83 | cache-from: type=local,src=/tmp/.buildx-cache 84 | cache-to: type=local,dest=/tmp/.buildx-cache 85 | 86 | - name: Scan the image 87 | uses: anchore/sbom-action@v0 88 | with: 89 | image: ghcr.io/${{ github.repository_owner }}/linuxwave 90 | 91 | - name: Image digest 92 | run: echo ${{ steps.docker_build.outputs.digest }} 93 | -------------------------------------------------------------------------------- /src/gen.zig: -------------------------------------------------------------------------------- 1 | //! Music generator. 2 | 3 | const std = @import("std"); 4 | 5 | /// Generator configuration. 6 | pub const GeneratorConfig = struct { 7 | /// Semitones from the base note in a major musical scale. 8 | scale: []const u8, 9 | /// Frequency of the note. 10 | /// 11 | /// 12 | note: f32, 13 | /// Volume control. 14 | volume: u8, 15 | }; 16 | 17 | /// Generator implementation. 18 | pub const Generator = struct { 19 | /// Number of calculated samples per sine curve (affects perceived frequency). 20 | pub const sample_count: usize = 10000; 21 | 22 | /// Configuration. 23 | config: GeneratorConfig, 24 | 25 | /// Creates a new instance. 26 | pub fn init(config: GeneratorConfig) Generator { 27 | return Generator{ .config = config }; 28 | } 29 | 30 | /// Generates a sound from the given sample. 31 | /// 32 | /// Returns an array that contains the amplitudes of the sound wave at a given point in time. 33 | pub fn generate(self: Generator, allocator: std.mem.Allocator, sample: u8) ![]u8 { 34 | var buffer = std.ArrayList(u8).init(allocator); 35 | var i: usize = 0; 36 | while (i < sample_count) : (i += 1) { 37 | // Calculate the frequency according to the equal temperament. 38 | // Hertz = 440 * 2^(semitone distance / 12) 39 | // () 40 | const tone_distance: f32 = @floatFromInt(self.config.scale[sample % self.config.scale.len]); 41 | const increment: f32 = @floatFromInt(i); 42 | var amp = @sin(self.config.note * std.math.pi * std.math.pow(f32, 2, tone_distance / 12) * (increment * 0.0001)); 43 | // Scale the amplitude between 0 and 256. 44 | amp = (amp * std.math.maxInt(u8) / 2) + (std.math.maxInt(u8) / 2); 45 | // Apply the volume control. 46 | const volume: f32 = @floatFromInt(self.config.volume); 47 | amp = amp * volume / 100; 48 | try buffer.append(@intFromFloat(amp)); 49 | } 50 | return buffer.toOwnedSlice(); 51 | } 52 | }; 53 | 54 | test "generate music" { 55 | const config = GeneratorConfig{ 56 | .scale = &[_]u8{ 0, 1 }, 57 | .note = 440, 58 | .volume = 100, 59 | }; 60 | const generator = Generator.init(config); 61 | const allocator = std.testing.allocator; 62 | 63 | var data1 = try generator.generate(allocator, 'a'); 64 | defer allocator.free(data1); 65 | try std.testing.expectEqualSlices(u8, &[_]u8{ 127, 145, 163, 181, 197, 212, 225, 235 }, data1[0..8]); 66 | try std.testing.expect(data1.len == 10000); 67 | 68 | var data2 = try generator.generate(allocator, 'b'); 69 | defer allocator.free(data2); 70 | try std.testing.expectEqualSlices(u8, &[_]u8{ 127, 144, 161, 178, 193, 208, 221, 232 }, data2[0..8]); 71 | try std.testing.expect(data2.len == 10000); 72 | } 73 | -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 18 | 19 | 24 | 🐧 linuxwave 25 | 81 | 82 | 83 | 84 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # configuration for https://github.com/orhun/git-cliff 2 | 3 | [changelog] 4 | # changelog header 5 | header = """ 6 | # Changelog 🐧🎵\n 7 | All notable changes to this project will be documented in this file.\n 8 | """ 9 | # template for the changelog body 10 | # https://tera.netlify.app/docs/#introduction 11 | body = """ 12 | {% if version %}\ 13 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 14 | {% else %}\ 15 | ## [unreleased] 16 | {% endif %}\ 17 | {% for group, commits in commits | group_by(attribute="group") %} 18 | ### {{ group | striptags | trim | upper_first }} 19 | {% for commit in commits 20 | | filter(attribute="scope") 21 | | sort(attribute="scope") %} 22 | - *({{commit.scope}})*{% if commit.breaking %} [**breaking**]{% endif %} \ 23 | {{ commit.message | upper_first }} 24 | {%- endfor -%} 25 | {% raw %}\n{% endraw %}\ 26 | {%- for commit in commits %} 27 | {%- if commit.scope -%} 28 | {% else -%} 29 | - *(uncategorized)*{% if commit.breaking %} [**breaking**]{% endif %} \ 30 | {{ commit.message | upper_first }} 31 | {% endif -%} 32 | {% endfor -%} 33 | {% endfor %}\n 34 | """ 35 | # remove the leading and trailing whitespace from the template 36 | trim = true 37 | # changelog footer 38 | footer = """ 39 | 40 | """ 41 | 42 | [git] 43 | # parse the commits based on https://www.conventionalcommits.org 44 | conventional_commits = true 45 | # filter out the commits that are not conventional 46 | filter_unconventional = true 47 | # process each line of a commit as an individual commit 48 | split_commits = false 49 | # regex for preprocessing the commit messages 50 | commit_preprocessors = [ 51 | { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/linuxwave/issues/${2}))" }, 52 | ] 53 | # regex for parsing and grouping commits 54 | commit_parsers = [ 55 | { message = "^feat", group = "🎵 Features" }, 56 | { message = "^fix", group = "🐛 Bug Fixes" }, 57 | { message = "^doc", group = "📚 Documentation" }, 58 | { message = "^perf", group = "⚡ Performance" }, 59 | { message = "^refactor", group = "🚜 Refactor" }, 60 | { message = "^style", group = "🎨 Styling" }, 61 | { message = "^test", group = "🧪 Testing" }, 62 | { message = "^chore\\(release\\): prepare for", skip = true }, 63 | { message = "^chore\\(deps\\):", skip = true }, 64 | { message = "^chore\\(pr\\)", skip = true }, 65 | { message = "^chore\\(pull\\)", skip = true }, 66 | { message = "^chore", group = "⚙️ Miscellaneous Tasks" }, 67 | { body = ".*security", group = "🛡️ Security" }, 68 | ] 69 | # protect breaking changes from being skipped due to matching a skipping commit_parser 70 | protect_breaking_commits = false 71 | # filter out the commits that are not matched by commit parsers 72 | filter_commits = false 73 | # glob pattern for matching git tags 74 | tag_pattern = "v[0-9]*" 75 | # regex for skipping tags 76 | skip_tags = "v0.1.0-rc.1" 77 | # regex for ignoring tags 78 | ignore_tags = "" 79 | # sort the tags topologically 80 | topo_order = false 81 | # sort the commits inside sections by oldest/newest order 82 | sort_commits = "newest" 83 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 🐧🎵 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [0.3.0] - 2025-03-07 6 | 7 | ### 🎵 Features 8 | 9 | - *(uncategorized)* Zig 0.14 ([#40](https://github.com/orhun/linuxwave/issues/40)) 10 | 11 | ### 📚 Documentation 12 | 13 | - *(license)* Update license copyright years 14 | 15 | ## [0.2.0] - 2024-10-20 16 | 17 | ### 🎵 Features 18 | 19 | - *(uncategorized)* [**breaking**] Rewrite for Zig 0.12/0.13 ([#28](https://github.com/orhun/linuxwave/issues/28)) 20 | 21 | ### 🐛 Bug Fixes 22 | 23 | - *(test)* Add back `main` test ([#30](https://github.com/orhun/linuxwave/issues/30)) 24 | 25 | ### ⚙️ Miscellaneous Tasks 26 | 27 | - *(website)* Update tracking link 28 | 29 | ## [0.1.5] - 2023-07-21 30 | 31 | ### 📚 Documentation 32 | 33 | - *(readme)* Add Spotify link 34 | - *(readme)* Update table of contents 35 | 36 | ## [0.1.4] - 2023-06-29 37 | 38 | ### 🚜 Refactor 39 | 40 | - *(build)* Use Builder's fmt function to avoid using allocator 41 | - *(build)* Use built-in allocator 42 | 43 | ### 📚 Documentation 44 | 45 | - *(readme)* Add installation instruction for Void Linux 46 | - *(readme)* Simplify the Docker command 47 | 48 | ## [0.1.3] - 2023-04-19 49 | 50 | ### 🎵 Features 51 | 52 | - *(cd)* Publish the signed source code which includes submodules 53 | 54 | ### 📚 Documentation 55 | 56 | - *(readme)* Add stdout playback example 57 | 58 | ### ⚙️ Miscellaneous Tasks 59 | 60 | - *(ci)* Use Zig 0.10.1 for CI/CD 61 | 62 | ## [0.1.2] - 2023-04-17 63 | 64 | ### 🐛 Bug Fixes 65 | 66 | - *(docker)* Scan the correct docker image 67 | 68 | ### 📚 Documentation 69 | 70 | - *(preset)* Add the "spooky_manor" preset 71 | 72 | ## [0.1.1] - 2023-04-17 73 | 74 | ### 📚 Documentation 75 | 76 | - *(readme)* Add submodule update step for building from source 77 | - *(readme)* Add installation instructions for Arch Linux 78 | 79 | ## [0.1.0] - 2023-04-17 80 | 81 | ### 🎵 Features 82 | 83 | - *(docs)* Add a man page 84 | 85 | ### 🚜 Refactor 86 | 87 | - *(ci)* Remove unnecessary node options 88 | 89 | ### 📚 Documentation 90 | 91 | - *(readme)* Add demo link 92 | - *(readme)* Add motivation section 93 | - *(website)* Add demo link to website 94 | 95 | ### 🎨 Styling 96 | 97 | - *(readme)* Set the alignment to center for demo link 98 | - *(readme)* Center the demo text 99 | 100 | ## [0.1.0-rc.4] - 2023-04-15 101 | 102 | ### 🎵 Features 103 | 104 | - *(website)* Add a landing page 105 | 106 | ### 🚜 Refactor 107 | 108 | - *(ci)* Remove unnecessary OS from build matrixes 109 | - *(lib)* Extract reusable modules as packages 110 | 111 | ### 📚 Documentation 112 | 113 | - *(lib)* Support auto-generation of documentation with a build flag 114 | - *(readme)* Mention the API documentation 115 | - *(readme)* Add documentation build badge 116 | 117 | ### 🎨 Styling 118 | 119 | - *(readme)* Use HTML for the badges 120 | - *(readme)* Fix centering the badges 121 | - *(readme)* Center the badges 122 | 123 | ### ⚙️ Miscellaneous Tasks 124 | 125 | - *(ci)* Fix the grammar for the build job 126 | - *(pages)* Rename deploy step 127 | - *(pages)* Add workflow for deploying documentation 128 | 129 | ## [0.1.0-rc.3] - 2023-04-15 130 | 131 | ### 🐛 Bug Fixes 132 | 133 | - *(cd)* Use windows specific commands 134 | 135 | ### ⚙️ Miscellaneous Tasks 136 | 137 | - *(changelog)* Remove author names from changelog 138 | 139 | ## [0.1.0-rc.2] - 2023-04-15 140 | 141 | ### 🎵 Features 142 | 143 | - *(cd)* Add different build targets 144 | 145 | 146 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 🐧🎵 2 | 3 | A big welcome and thank you for considering contributing to `linuxwave`! It is people like you that make it a reality for users in the open source community. 4 | 5 | Reading and following these guidelines will help us make the contribution process easy and effective for everyone involved. It also communicates that you agree to respect the time of the developers managing and developing this project. In return, we will reciprocate that respect by addressing your issue, assessing changes, and helping you finalize your pull requests. 6 | 7 | ## Quicklinks 8 | 9 | - [Code of Conduct](#code-of-conduct) 10 | - [Getting Started](#getting-started) 11 | - [Issues](#issues) 12 | - [Pull Requests](#pull-requests) 13 | - [License](#license) 14 | 15 | ## Code of Conduct 16 | 17 | We take our open source community seriously and hold ourselves and other contributors to high standards of communication. By participating and contributing to this project, you agree to uphold our [Code of Conduct](./CODE_OF_CONDUCT.md). 18 | 19 | ## Getting Started 20 | 21 | Contributions are made to this repo via Issues and Pull Requests (PRs). A few general guidelines that cover both: 22 | 23 | - First, discuss the change you wish to make via creating an [issue](https://github.com/orhun/linuxwave/issues/new/choose), [email](mailto:orhunparmaksiz@gmail.com), or any other method with the owners of this repository before making a change. 24 | - Search for existing issues and PRs before creating your own. 25 | - We work hard to make sure issues are handled on time but, depending on the impact, it could take a while to investigate the root cause. A friendly ping in the comment thread to the submitter or a contributor can help draw attention if your issue is blocking. 26 | 27 | ### Issues 28 | 29 | Issues should be used to report problems with the project, request a new feature, or discuss potential changes before a PR is created. When you create a new issue, a template will be loaded that will guide you through collecting and providing the information we need to investigate. 30 | 31 | If you find an issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help be indicating to our maintainers that a particular problem is affecting more than just the reporter. 32 | 33 | ### Pull Requests 34 | 35 | PRs are always welcome and can be a quick way to get your fix or improvement slated for the next release. In general, PRs should: 36 | 37 | - Only fix/add the functionality in question **or** address widespread whitespace/style issues, not both. 38 | - Add unit or integration tests for fixed or changed functionality (if a test suite already exists). 39 | - Address a single concern in the least number of changed lines as possible. 40 | - Include documentation. 41 | - Be accompanied by a complete Pull Request template (loaded automatically when a PR is created). 42 | 43 | For changes that address core functionality or would require breaking changes (e.g. a major release), it's best to open an issue to discuss your proposal first. This is not required but can save time in creating and reviewing changes. 44 | 45 | In general, we follow the "[fork-and-pull](https://github.com/susam/gitpr)" Git workflow: 46 | 47 | 1. Fork the repository to your own GitHub account. 48 | 49 | 2. Clone the project to your local environment. 50 | 51 | ```sh 52 | git clone https://github.com//linuxwave && cd linuxwave/ 53 | ``` 54 | 55 | 3. Create a branch locally with a succinct but descriptive name. 56 | 57 | ```sh 58 | git checkout -b 59 | ``` 60 | 61 | 3. Make sure you have everything listed in the [prerequisites](./README.md#prerequisites) section installed. If so, build the project: 62 | 63 | ```sh 64 | zig build 65 | ``` 66 | 67 | 4. Test the functionality by running the binary: 68 | 69 | ```sh 70 | zig build run 71 | ``` 72 | 73 | 5. Start committing changes to the branch. 74 | 75 | 6. Add your tests or update the existing tests according to the changes and check if the tests are passed. 76 | 77 | ```sh 78 | zig build --summary all test 79 | ``` 80 | 81 | 8. Push changes to your fork. 82 | 83 | 9. Open a PR in our repository and follow the [PR template](./.github/PULL_REQUEST_TEMPLATE.md) so that we can efficiently review the changes. 84 | 85 | 10. Wait for approval from the repository owners. Discuss the possible changes and update your PR if necessary. 86 | 87 | 11. The PR will be merged once you have the sign-off of the repository owners. 88 | 89 | ## License 90 | 91 | By contributing, you agree that your contributions will be licensed under [The MIT License](./LICENSE). 92 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const wav = @import("wav.zig"); 3 | const gen = @import("gen.zig"); 4 | const file = @import("file.zig"); 5 | const args = @import("args.zig"); 6 | const defaults = @import("defaults.zig"); 7 | const build_options = @import("build_options"); 8 | const clap = @import("clap"); 9 | 10 | /// Runs `linuxwave`. 11 | fn run(allocator: std.mem.Allocator, output: anytype) !void { 12 | // Parse command-line arguments. 13 | const cli = try clap.parse(clap.Help, &args.params, args.parsers, .{ .allocator = allocator }); 14 | defer cli.deinit(); 15 | if (cli.args.help != 0) { 16 | try output.print("{s}\n", .{args.banner}); 17 | return clap.help(output, clap.Help, &args.params, args.help_options); 18 | } else if (cli.args.version != 0) { 19 | try output.print("{s} {s}\n", .{ build_options.exe_name, build_options.version }); 20 | return; 21 | } 22 | 23 | // Create encoder configuration. 24 | const encoder_config = wav.EncoderConfig{ 25 | .num_channels = if (cli.args.channels) |channels| channels else defaults.channels, 26 | .sample_rate = if (cli.args.rate) |rate| @intFromFloat(rate) else defaults.sample_rate, 27 | .format = if (cli.args.format) |format| format else defaults.format, 28 | }; 29 | 30 | // Create generator configuration. 31 | const scale = s: { 32 | var scale = std.ArrayList(u8).init(allocator); 33 | var splits = std.mem.splitAny(u8, if (cli.args.scale) |s| s else defaults.scale, ","); 34 | while (splits.next()) |chunk| { 35 | try scale.append(try std.fmt.parseInt(u8, chunk, 0)); 36 | } 37 | break :s try scale.toOwnedSlice(); 38 | }; 39 | defer allocator.free(scale); 40 | const generator_config = gen.GeneratorConfig{ 41 | .scale = scale, 42 | .note = if (cli.args.note) |note| note else defaults.note, 43 | .volume = if (cli.args.volume) |volume| volume else defaults.volume, 44 | }; 45 | const duration = if (cli.args.duration) |duration| duration else defaults.duration; 46 | const data_len = encoder_config.getDataLength(duration) / (gen.Generator.sample_count - 2); 47 | 48 | // Read data from a file or stdin. 49 | const input_file = if (cli.args.input) |input| input else defaults.input; 50 | const buffer = b: { 51 | if (std.mem.eql(u8, input_file, "-")) { 52 | try output.print("Reading {d} bytes from stdin\n", .{data_len}); 53 | var list = try std.ArrayList(u8).initCapacity(allocator, data_len); 54 | const buffer = list.allocatedSlice(); 55 | const stdin = std.io.getStdIn().reader(); 56 | try stdin.readNoEof(buffer); 57 | break :b buffer; 58 | } else { 59 | try output.print("Reading {d} bytes from {s}\n", .{ data_len, input_file }); 60 | break :b try file.readBytes(allocator, input_file, data_len); 61 | } 62 | }; 63 | defer allocator.free(buffer); 64 | 65 | // Generate music. 66 | const generator = gen.Generator.init(generator_config); 67 | var data = std.ArrayList(u8).init(allocator); 68 | for (buffer) |v| { 69 | const gen_data = try generator.generate(allocator, v); 70 | defer allocator.free(gen_data); 71 | try data.appendSlice(gen_data); 72 | } 73 | 74 | // Encode WAV. 75 | const out = if (cli.args.output) |out| out else defaults.output; 76 | const writer = w: { 77 | if (std.mem.eql(u8, out, "-")) { 78 | try output.print("Writing to stdout\n", .{}); 79 | break :w std.io.getStdOut().writer(); 80 | } else { 81 | try output.print("Saving to {s}\n", .{out}); 82 | const out_file = try std.fs.cwd().createFile(out, .{}); 83 | break :w out_file.writer(); 84 | } 85 | }; 86 | const wav_data = try data.toOwnedSlice(); 87 | defer allocator.free(wav_data); 88 | try wav.Encoder(@TypeOf(writer)).encode(writer, wav_data, encoder_config); 89 | } 90 | 91 | /// Entry-point. 92 | pub fn main() !void { 93 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 94 | const allocator = gpa.allocator(); 95 | const stderr = std.io.getStdErr().writer(); 96 | run(allocator, stderr) catch |err| { 97 | try stderr.print("Error occurred: {}\n", .{err}); 98 | }; 99 | } 100 | 101 | test "run" { 102 | const allocator = std.testing.allocator; 103 | var buffer = std.ArrayList(u8).init(allocator); 104 | const output = buffer.writer(); 105 | run(allocator, output) catch |err| { 106 | std.debug.print("Error occurred: {s}\n", .{@errorName(err)}); 107 | return; 108 | }; 109 | const result = buffer.toOwnedSlice() catch |err| { 110 | std.debug.print("Error occurred: {s}\n", .{@errorName(err)}); 111 | return; 112 | }; 113 | defer allocator.free(result); 114 | try std.testing.expectEqualStrings( 115 | \\Reading 96 bytes from /dev/urandom 116 | \\Saving to output.wav 117 | \\ 118 | , result); 119 | } 120 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | changelog: 10 | name: Generate changelog 11 | runs-on: ubuntu-latest 12 | outputs: 13 | release_body: ${{ steps.git-cliff.outputs.content }} 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v6 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Generate a changelog 21 | uses: orhun/git-cliff-action@v4 22 | id: git-cliff 23 | with: 24 | config: cliff.toml 25 | args: --latest --strip header 26 | 27 | publish-binaries: 28 | name: Publish binaries 29 | needs: changelog 30 | runs-on: ubuntu-latest 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | TARGET: 35 | [ 36 | x86_64-linux, 37 | x86_64-macos, 38 | x86_64-windows, 39 | aarch64-linux, 40 | aarch64-macos, 41 | aarch64-windows, 42 | arm-linux, 43 | riscv64-linux, 44 | i386-linux 45 | ] 46 | 47 | steps: 48 | - name: Checkout the repository 49 | uses: actions/checkout@v6 50 | 51 | - name: Set the release version 52 | run: echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV 53 | 54 | - name: Install Zig 55 | uses: mlugg/setup-zig@v2 56 | with: 57 | version: 0.14.0 58 | 59 | - name: Show Zig version 60 | run: | 61 | zig version 62 | zig env 63 | 64 | - name: Build 65 | run: zig build --release=safe -Dtarget=${{ matrix.TARGET }} 66 | 67 | - name: Prepare release assets 68 | shell: bash 69 | run: | 70 | mkdir -p release/man 71 | cp {LICENSE,README.md,CHANGELOG.md} release/ 72 | cp man/* release/man 73 | if [[ "${{ matrix.TARGET }}" = *"windows" ]]; then 74 | cp zig-out/bin/linuxwave.exe release/ 75 | else 76 | cp zig-out/bin/linuxwave release/ 77 | fi 78 | mv release/ linuxwave-${{ env.RELEASE_VERSION }}/ 79 | 80 | - name: Create release artifacts 81 | shell: bash 82 | run: | 83 | if [[ "${{ matrix.TARGET }}" = *"windows" ]]; then 84 | 7z a -tzip linuxwave-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}.zip \ 85 | linuxwave-${{ env.RELEASE_VERSION }}/ 86 | else 87 | tar -czvf linuxwave-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}.tar.gz \ 88 | linuxwave-${{ env.RELEASE_VERSION }}/ 89 | shasum -a 512 linuxwave-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}.tar.gz \ 90 | > linuxwave-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}.tar.gz.sha512 91 | fi 92 | 93 | - name: Sign the release 94 | shell: bash 95 | run: | 96 | if [[ "${{ matrix.TARGET }}" != *"windows" ]]; then 97 | echo "${{ secrets.GPG_RELEASE_KEY }}" | base64 --decode > private.key 98 | echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --pinentry-mode=loopback \ 99 | --passphrase-fd 0 --import private.key 100 | echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --pinentry-mode=loopback \ 101 | --passphrase-fd 0 --detach-sign \ 102 | linuxwave-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}.tar.gz 103 | fi 104 | 105 | - name: Upload the binary releases 106 | uses: svenstaro/upload-release-action@v2 107 | with: 108 | file: linuxwave-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}* 109 | file_glob: true 110 | overwrite: true 111 | tag: ${{ github.ref }} 112 | release_name: "Release v${{ env.RELEASE_VERSION }}" 113 | body: ${{ needs.changelog.outputs.release_body }} 114 | repo_token: ${{ secrets.GITHUB_TOKEN }} 115 | 116 | publish-source: 117 | name: Publish the source code 118 | needs: changelog 119 | runs-on: ubuntu-latest 120 | steps: 121 | - name: Checkout the repository 122 | uses: actions/checkout@v6 123 | with: 124 | fetch-depth: 0 125 | 126 | - name: Set the release version 127 | run: echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV 128 | 129 | - name: Prepare source code 130 | run: | 131 | cd .. 132 | zip -r v${{ env.RELEASE_VERSION }}.zip ${{ github.event.repository.name }} 133 | tar -czvf v${{ env.RELEASE_VERSION }}.tar.gz ${{ github.event.repository.name }} 134 | mv v${{ env.RELEASE_VERSION }}* ${{ github.event.repository.name }} 135 | 136 | - name: Sign 137 | shell: bash 138 | run: | 139 | echo "${{ secrets.GPG_RELEASE_KEY }}" | base64 --decode > private.key 140 | echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --pinentry-mode=loopback \ 141 | --passphrase-fd 0 --import private.key 142 | for ext in 'zip' 'tar.gz'; do 143 | echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --pinentry-mode=loopback \ 144 | --passphrase-fd 0 --detach-sign \ 145 | "v${{ env.RELEASE_VERSION }}.${ext}" 146 | done 147 | 148 | - name: Upload the source code 149 | uses: svenstaro/upload-release-action@v2 150 | with: 151 | file: v${{ env.RELEASE_VERSION }}* 152 | file_glob: true 153 | overwrite: true 154 | tag: ${{ github.ref }} 155 | release_name: "Release v${{ env.RELEASE_VERSION }}" 156 | body: ${{ needs.changelog.outputs.release_body }} 157 | repo_token: ${{ secrets.GITHUB_TOKEN }} 158 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 🐧🎵 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | linuxwave@protonmail.com 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/wav.zig: -------------------------------------------------------------------------------- 1 | //! Waveform Audio File Format encoder. 2 | //! 3 | //! 4 | 5 | const std = @import("std"); 6 | 7 | /// File header. 8 | const RIFF = [4]u8{ 'R', 'I', 'F', 'F' }; 9 | /// RIFF type. 10 | const WAVE = [4]u8{ 'W', 'A', 'V', 'E' }; 11 | /// Chunk name for the information about how the waveform data is stored. 12 | const FMT_ = [4]u8{ 'f', 'm', 't', ' ' }; 13 | /// Chunk name for the digital audio sample data. 14 | const DATA = [4]u8{ 'd', 'a', 't', 'a' }; 15 | // Total length of the header. 16 | const header_len: u32 = 44; 17 | 18 | /// Format of the waveform data. 19 | pub const Format = enum { 20 | /// Unsigned 8-bit. 21 | U8, 22 | /// Signed 16-bit little-endian. 23 | S16_LE, 24 | /// Signed 24-bit little-endian. 25 | S24_LE, 26 | /// Signed 32-bit little-endian. 27 | S32_LE, 28 | 29 | /// Returns the bytes per sample. 30 | pub fn getNumBytes(self: Format) u16 { 31 | return switch (self) { 32 | .U8 => 1, 33 | .S16_LE => 2, 34 | .S24_LE => 3, 35 | .S32_LE => 4, 36 | }; 37 | } 38 | }; 39 | 40 | /// Encoder configuration. 41 | pub const EncoderConfig = struct { 42 | /// Number of channels. 43 | num_channels: usize, 44 | /// Sample rate. 45 | sample_rate: usize, 46 | /// Sample format. 47 | format: Format, 48 | 49 | /// Returns the data length needed for given duration. 50 | pub fn getDataLength(self: EncoderConfig, duration: usize) usize { 51 | return duration * (self.sample_rate * self.num_channels * self.format.getNumBytes()) - header_len; 52 | } 53 | }; 54 | 55 | /// WAV encoder implementation. 56 | pub fn Encoder(comptime Writer: type) type { 57 | // Position of the data chunk. 58 | const data_chunk_pos: u32 = 36; 59 | 60 | return struct { 61 | // Encode WAV. 62 | pub fn encode(writer: Writer, data: []const u8, config: EncoderConfig) !void { 63 | try writeChunks(writer, config, data); 64 | } 65 | 66 | /// Writes the headers with placeholder values for length. 67 | /// 68 | /// This can be used while streaming the WAV file i.e. when the total length is unknown. 69 | pub fn writeHeader(writer: Writer, config: EncoderConfig) !void { 70 | try writeChunks(writer, config, null); 71 | } 72 | 73 | /// Patches the headers to seek back and patch the headers for length values. 74 | pub fn patchHeader(writer: Writer, seeker: anytype, data_len: u32) !void { 75 | const endian = std.builtin.Endian.little; 76 | try seeker.seekTo(4); 77 | try writer.writeInt(u32, data_chunk_pos + 8 + data_len - 8, endian); 78 | try seeker.seekTo(data_chunk_pos + 4); 79 | try writer.writeInt(u32, data_len, endian); 80 | } 81 | 82 | /// Writes the WAV chunks with optional data. 83 | /// 84 | /// → RIFF('WAVE' 85 | /// // Format 86 | /// [] // Fact chunk 87 | /// [] // Cue points 88 | /// [] // Playlist 89 | /// [] // Associated data list 90 | /// ) // Wave data 91 | fn writeChunks(writer: Writer, config: EncoderConfig, opt_data: ?[]const u8) !void { 92 | // Chunk configuration. 93 | const bytes_per_sample = config.format.getNumBytes(); 94 | const num_channels: u16 = @intCast(config.num_channels); 95 | const sample_rate: u32 = @intCast(config.sample_rate); 96 | const byte_rate = sample_rate * @as(u32, num_channels) * bytes_per_sample; 97 | const block_align: u16 = num_channels * bytes_per_sample; 98 | const bits_per_sample: u16 = bytes_per_sample * 8; 99 | const data_len: u32 = if (opt_data) |data| @intCast(data.len) else 0; 100 | const endian = std.builtin.Endian.little; 101 | // Write the file header. 102 | try writer.writeAll(&RIFF); 103 | if (opt_data != null) { 104 | try writer.writeInt(u32, data_chunk_pos + 8 + data_len - 8, endian); 105 | } else { 106 | try writer.writeInt(u32, 0, endian); 107 | } 108 | try writer.writeAll(&WAVE); 109 | // Write the format chunk. 110 | try writer.writeAll(&FMT_); 111 | // Encode with pulse-code modulation (LPCM). 112 | try writer.writeInt(u32, 16, endian); 113 | // Uncompressed. 114 | try writer.writeInt(u16, 1, endian); 115 | try writer.writeInt(u16, num_channels, endian); 116 | try writer.writeInt(u32, sample_rate, endian); 117 | try writer.writeInt(u32, byte_rate, endian); 118 | try writer.writeInt(u16, block_align, endian); 119 | try writer.writeInt(u16, bits_per_sample, endian); 120 | // Write the data chunk. 121 | try writer.writeAll(&DATA); 122 | if (opt_data) |data| { 123 | try writer.writeInt(u32, data_len, endian); 124 | try writer.writeAll(data); 125 | } else { 126 | try writer.writeInt(u32, 0, endian); 127 | } 128 | } 129 | }; 130 | } 131 | 132 | test "encode WAV" { 133 | var buffer: [1000]u8 = undefined; 134 | var stream = std.io.fixedBufferStream(&buffer); 135 | const writer = stream.writer(); 136 | try Encoder(@TypeOf(writer)).encode(writer, &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, .{ 137 | .num_channels = 1, 138 | .sample_rate = 44100, 139 | .format = .S16_LE, 140 | }); 141 | try std.testing.expectEqualSlices(u8, "RIFF", buffer[0..4]); 142 | } 143 | 144 | test "stream out WAV" { 145 | var buffer: [1000]u8 = undefined; 146 | var fbs = std.io.fixedBufferStream(&buffer); 147 | const endian = std.builtin.Endian.little; 148 | const WavEncoder = Encoder(@TypeOf(fbs).Writer); 149 | try WavEncoder.writeHeader(fbs.writer(), .{ 150 | .num_channels = 1, 151 | .sample_rate = 44100, 152 | .format = .S16_LE, 153 | }); 154 | try std.testing.expectEqual(@as(u64, 44), try fbs.getPos()); 155 | try std.testing.expectEqual(@as(u32, 0), std.mem.readInt(u32, buffer[4..8], endian)); 156 | try std.testing.expectEqual(@as(u32, 0), std.mem.readInt(u32, buffer[40..44], endian)); 157 | 158 | const data = &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }; 159 | try fbs.writer().writeAll(data); 160 | try std.testing.expectEqual(@as(u64, 52), try fbs.getPos()); 161 | try WavEncoder.patchHeader(fbs.writer(), fbs.seekableStream(), data.len); 162 | try std.testing.expectEqual(@as(u32, 44), std.mem.readInt(u32, buffer[4..8], endian)); 163 | try std.testing.expectEqual(@as(u32, 8), std.mem.readInt(u32, buffer[40..44], endian)); 164 | } 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `linuxwave` 🐧🎵 2 | 3 |

4 | 5 | demo 6 | GitHub Release 7 | Coverage 8 | Continuous Integration 9 | Continuous Deployment 10 | Docker Builds 11 | Documentation 12 | 13 |

14 | Click here to watch the demo!
15 | Listen to "linuxwave" on Spotify! 16 |

17 | 18 |

19 | 20 |
21 | Table of Contents 22 | 23 | 24 | 25 | - [Motivation ✨](#motivation-) 26 | - [Installation 🤖](#installation-) 27 | - [Build from source](#build-from-source) 28 | - [Prerequisites](#prerequisites) 29 | - [Instructions](#instructions) 30 | - [Binary releases](#binary-releases) 31 | - [Arch Linux](#arch-linux) 32 | - [Void Linux](#void-linux) 33 | - [Docker](#docker) 34 | - [Images](#images) 35 | - [Usage](#usage) 36 | - [Building](#building) 37 | - [Examples 🎵](#examples-) 38 | - [Presets 🎹](#presets-) 39 | - [Usage 📚](#usage-) 40 | - [`scale`](#scale) 41 | - [`note`](#note) 42 | - [`rate`](#rate) 43 | - [`channels`](#channels) 44 | - [`format`](#format) 45 | - [`volume`](#volume) 46 | - [`duration`](#duration) 47 | - [`input`](#input) 48 | - [`output`](#output) 49 | - [Related Projects](#related-projects) 50 | - [Funding 💖](#funding-) 51 | - [Contributing 🌱](#contributing-) 52 | - [License ⚖️](#license-) 53 | - [Copyright ⛓️](#copyright-) 54 | 55 | 56 | 57 |
58 | 59 | ## Motivation ✨ 60 | 61 | - [Bash One Liner - Compose Music From Entropy in /dev/urandom](https://web.archive.org/web/20230122184930/https://blog.robertelder.org/bash-one-liner-compose-music/) 62 | - ['Music' from /dev/urandom](https://news.ycombinator.com/item?id=11238247) 63 | 64 | ## Installation 🤖 65 | 66 | ### Build from source 67 | 68 | #### Prerequisites 69 | 70 | - [Zig](https://ziglang.org/download/) (`0.14`) 71 | 72 | #### Instructions 73 | 74 | 1. Clone the repository. 75 | 76 | ```sh 77 | git clone https://github.com/orhun/linuxwave && cd linuxwave/ 78 | ``` 79 | 80 | 2. Build. 81 | 82 | ```sh 83 | zig build --release=safe 84 | ``` 85 | 86 | Binary will be located at `zig-out/bin/linuxwave`. You can also run the binary directly via `zig build run`. 87 | 88 | If you want to use `linuxwave` in your Zig project as a package, the API documentation is available [here](https://orhun.dev/linuxwave/docs). 89 | 90 | ### Binary releases 91 | 92 | See the available binaries for different targets from the [releases page](https://github.com/orhun/linuxwave/releases). They are automated via [Continuous Deployment](.github/workflows/cd.yml) workflow. 93 | 94 | Release tarballs are signed with the following PGP key: [0xC0701E98290D90B8](https://keyserver.ubuntu.com/pks/lookup?search=0xC0701E98290D90B8&op=vindex) 95 | 96 | ### Arch Linux 97 | 98 | `linuxwave` can be installed from the [community repository](https://archlinux.org/packages/community/x86_64/linuxwave/) using [pacman](https://wiki.archlinux.org/title/Pacman): 99 | 100 | ```sh 101 | pacman -S linuxwave 102 | ``` 103 | 104 | ### Void Linux 105 | 106 | `linuxwave` can be installed from official Void Linux package repository: 107 | 108 | ```sh 109 | xbps-install linuxwave 110 | ``` 111 | 112 | ### Docker 113 | 114 | #### Images 115 | 116 | Docker builds are [automated](./.github/workflows/docker.yml) and images are available in the following registries: 117 | 118 | - [Docker Hub](https://hub.docker.com/r/orhunp/linuxwave) 119 | - [GitHub Container Registry](https://github.com/orhun/linuxwave/pkgs/container/linuxwave) 120 | 121 | #### Usage 122 | 123 | The following command can be used to generate `output.wav` in the current working directory: 124 | 125 | ```sh 126 | docker run --rm -v "$(pwd)":/app "orhunp/linuxwave:${TAG:-latest}" 127 | ``` 128 | 129 | #### Building 130 | 131 | Custom Docker images can be built from the [Dockerfile](./Dockerfile): 132 | 133 | ```sh 134 | docker build -t linuxwave . 135 | ``` 136 | 137 | ## Examples 🎵 138 | 139 | **Default**: Read random data from `/dev/urandom` to generate a 20-second music composition in the A4 scale and save it to `output.wav`: 140 | 141 | ```sh 142 | linuxwave 143 | ``` 144 | 145 | Or play it directly with [mpv](https://mpv.io/) without saving: 146 | 147 | ```sh 148 | linuxwave -o - | mpv - 149 | ``` 150 | 151 | To use the A minor blues scale: 152 | 153 | ```sh 154 | linuxwave -s 0,3,5,6,7,10 -n 220 -o blues.wav 155 | ``` 156 | 157 | Read from an arbitrary file and turn it into a 10-second music composition in the C major scale: 158 | 159 | ```sh 160 | linuxwave -i build.zig -n 261.63 -d 10 -o music.wav 161 | ``` 162 | 163 | Read from stdin via giving `-` as input: 164 | 165 | ```sh 166 | cat README.md | linuxwave -i - 167 | ``` 168 | 169 | Write to stdout via giving `-` as output: 170 | 171 | ``` 172 | linuxwave -o - > output.wav 173 | ``` 174 | 175 | ## Presets 🎹 176 | 177 | Generate a **calming music** with a sample rate of 2000 Hz and a 32-bit little-endian signed integer format: 178 | 179 | ```sh 180 | linuxwave -r 2000 -f S32_LE -o calm.wav 181 | ``` 182 | 183 | Generate a **chiptune music** with a sample rate of 44100 Hz, stereo (2-channel) output and 8-bit unsigned integer format: 184 | 185 | ```sh 186 | linuxwave -r 44100 -f U8 -c 2 -o chiptune.wav 187 | ``` 188 | 189 | Generate a **boss stage music** with the volume of 65: 190 | 191 | ```sh 192 | linuxwave -s 0,7,1 -n 60 -v 65 -o boss.wav 193 | ``` 194 | 195 | Generate a **spooky low-fidelity music** with a sample rate of 1000 Hz, 4-channel output: 196 | 197 | ```sh 198 | linuxwave -s 0,1,5,3 -n 100 -r 1000 -v 55 -c 4 -o spooky_manor.wav 199 | ``` 200 | 201 | Feel free to [submit a pull request](CONTRIBUTING.md) to show off your preset here! 202 | 203 | Also, see [this discussion](https://github.com/orhun/linuxwave/discussions/1) for browsing the music generated by our community. 204 | 205 | ## Usage 📚 206 | 207 | ``` 208 | Options: 209 | -s, --scale Sets the musical scale [default: 0,2,3,5,7,8,10,12] 210 | -n, --note Sets the frequency of the note [default: 440 (A4)] 211 | -r, --rate Sets the sample rate [default: 24000] 212 | -c, --channels Sets the number of channels [default: 1] 213 | -f, --format Sets the sample format [default: S16_LE] 214 | -v, --volume Sets the volume (0-100) [default: 50] 215 | -d, --duration Sets the duration [default: 20] 216 | -i, --input Sets the input file [default: /dev/urandom] 217 | -o, --output Sets the output file [default: output.wav] 218 | -V, --version Display version information. 219 | -h, --help Display this help and exit. 220 | ``` 221 | 222 | ### `scale` 223 | 224 | Sets the musical scale for the output. It takes a list of [semitones](https://en.wikipedia.org/wiki/Semitone) separated by commas as its argument. 225 | 226 | The default value is `0,2,3,5,7,8,10,12`, which represents a major scale starting from C. 227 | 228 | Here are other examples: 229 | 230 | - A natural minor scale: `0,2,3,5,7,8,10` 231 | - A pentatonic scale starting from G: `7,9,10,12,14` 232 | - A blues scale starting from D: `2,3,4,6,7,10` 233 | - An octatonic scale starting from F#: `6,7,9,10,12,13,15,16` 234 | - Ryukyuan (Okinawa) Japanese scale: `4,5,7,11` 235 | 236 | ### `note` 237 | 238 | The `note` option sets the frequency of the note played. It takes a frequency in Hz as its argument. 239 | 240 | The default value is `440`, which represents A4. You can see the frequencies of musical notes [here](https://pages.mtu.edu/~suits/notefreqs.html). 241 | 242 | Other examples would be: 243 | 244 | - A3 (220 Hz) 245 | - C4 (261.63 Hz) 246 | - G4 (392 Hz) 247 | - A4 (440 Hz) (default) 248 | - E5 (659.26 Hz) 249 | 250 | ### `rate` 251 | 252 | Sets the sample rate for the output in Hertz (Hz). 253 | 254 | The default value is `24000`. 255 | 256 | ### `channels` 257 | 258 | Sets the number of audio channels in the output file. It takes an integer as its argument, representing the number of audio channels to generate. The default value is `1`, indicating mono audio. 259 | 260 | For stereo audio, set the value to `2`. For multi-channel audio, specify the desired number of channels. 261 | 262 | Note that the more audio channels you use, the larger the resulting file size will be. 263 | 264 | ### `format` 265 | 266 | Sets the sample format for the output file. It takes a string representation of the format as its argument. 267 | 268 | The default value is `S16_LE`, which represents 16-bit little-endian signed integer. 269 | 270 | Possible values are: 271 | 272 | - `U8`: Unsigned 8-bit. 273 | - `S16_LE`: Signed 16-bit little-endian. 274 | - `S24_LE`: Signed 24-bit little-endian. 275 | - `S32_LE`: Signed 32-bit little-endian. 276 | 277 | ### `volume` 278 | 279 | Sets the volume of the output file as a percentage from 0 to 100. 280 | 281 | The default value is `50`. 282 | 283 | ### `duration` 284 | 285 | Sets the duration of the output file in seconds. It takes a float as its argument. 286 | 287 | The default value is `20` seconds. 288 | 289 | ### `input` 290 | 291 | Sets the input file for the music generation. It takes a filename as its argument. 292 | 293 | The default value is `/dev/urandom`, which generates random data. 294 | 295 | You can provide _any_ type of file for this argument and it will generate music based on the contents of that file. 296 | 297 | ### `output` 298 | 299 | Sets the output file. It takes a filename as its argument. 300 | 301 | The default value is `output.wav`. 302 | 303 | ## Related Projects 304 | 305 | - [linuxwavegui](https://github.com/Arcmyx/linuxwavegui) – A graphical interface for linuxwave with tempo shifting, pitch control, flawed (but still cool-sounding) MIDI generation, and an interactive piano for scale selection. 306 | 307 | ## Funding 💖 308 | 309 | If you find `linuxwave` and/or other projects on my [GitHub profile](https://github.com/orhun) useful, consider supporting me on [GitHub Sponsors](https://github.com/sponsors/orhun) or [becoming a patron](https://www.patreon.com/join/orhunp)! 310 | 311 | [![Support me on GitHub Sponsors](https://img.shields.io/github/sponsors/orhun?style=flat&logo=GitHub&labelColor=424242&color=1d1d1d&logoColor=white)](https://github.com/sponsors/orhun) 312 | [![Support me on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dorhunp%26type%3Dpatrons&style=flat&logo=Patreon&labelColor=424242&color=1d1d1d&logoColor=white)](https://patreon.com/join/orhunp) 313 | [![Support me on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dorhunp%26type%3Dpledges&style=flat&logo=Patreon&labelColor=424242&color=1d1d1d&logoColor=white&label=)](https://patreon.com/join/orhunp) 314 | 315 | ## Contributing 🌱 316 | 317 | See our [Contribution Guide](./CONTRIBUTING.md) and please follow the [Code of Conduct](./CODE_OF_CONDUCT.md) in all your interactions with the project. 318 | 319 | ## License ⚖️ 320 | 321 | Licensed under [The MIT License](./LICENSE). 322 | 323 | ## Copyright ⛓️ 324 | 325 | Copyright © 2023-2024, [Orhun Parmaksız](mailto:orhunparmaksiz@gmail.com) 326 | --------------------------------------------------------------------------------