├── 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 |
85 |

89 |
100 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
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 | [](https://github.com/sponsors/orhun)
312 | [](https://patreon.com/join/orhunp)
313 | [](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 |
--------------------------------------------------------------------------------