WIP Gleam bindings to ALSA

Initial Commit

Signed-off-by: Naomi Roberts <mia@naomieow.xyz>

+756
+1
.envrc
··· 1 + use flake
+7
.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump 5 + node_modules/ 6 + 7 + test_audio
+26
README.md
··· 1 + # fern 2 + 3 + WIP Gleam bindings to ALSA libasound2 using the Erlang [alsa](https://hex.pm/packages/alsa) package. 4 + 5 + [![Package Version](https://img.shields.io/hexpm/v/fern)](https://hex.pm/packages/fern) 6 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/fern/) 7 + 8 + ```sh 9 + gleam add fern@1 10 + ``` 11 + ```gleam 12 + import fern 13 + 14 + pub fn main() -> Nil { 15 + // TODO: An example of the project in use 16 + } 17 + ``` 18 + 19 + Further documentation can be found at <https://hexdocs.pm/fern>. 20 + 21 + ## Development 22 + 23 + ```sh 24 + gleam run # Run the project 25 + gleam test # Run the tests 26 + ```
+187
flake.lock
··· 1 + { 2 + "nodes": { 3 + "fenix": { 4 + "inputs": { 5 + "nixpkgs": [ 6 + "gleam2nix", 7 + "nixpkgs" 8 + ], 9 + "rust-analyzer-src": "rust-analyzer-src" 10 + }, 11 + "locked": { 12 + "lastModified": 1758004879, 13 + "narHash": "sha256-kV7tQzcNbmo58wg2uE2MQ/etaTx+PxBMHeNrLP8vOgk=", 14 + "owner": "nix-community", 15 + "repo": "fenix", 16 + "rev": "07e5ce53dd020e6b337fdddc934561bee0698fa2", 17 + "type": "github" 18 + }, 19 + "original": { 20 + "owner": "nix-community", 21 + "repo": "fenix", 22 + "type": "github" 23 + } 24 + }, 25 + "flake-compat": { 26 + "flake": false, 27 + "locked": { 28 + "lastModified": 1751685974, 29 + "narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=", 30 + "rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1", 31 + "type": "tarball", 32 + "url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1" 33 + }, 34 + "original": { 35 + "type": "tarball", 36 + "url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz" 37 + } 38 + }, 39 + "flake-compat_2": { 40 + "flake": false, 41 + "locked": { 42 + "lastModified": 1747046372, 43 + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", 44 + "owner": "edolstra", 45 + "repo": "flake-compat", 46 + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", 47 + "type": "github" 48 + }, 49 + "original": { 50 + "owner": "edolstra", 51 + "repo": "flake-compat", 52 + "type": "github" 53 + } 54 + }, 55 + "git-hooks": { 56 + "inputs": { 57 + "flake-compat": "flake-compat_2", 58 + "gitignore": "gitignore", 59 + "nixpkgs": [ 60 + "gleam2nix", 61 + "nixpkgs" 62 + ] 63 + }, 64 + "locked": { 65 + "lastModified": 1757974173, 66 + "narHash": "sha256-4DpXmct/2rcLgScT1CXOLr0TUeIlrBB1rnFqCOf5MUw=", 67 + "owner": "cachix", 68 + "repo": "git-hooks.nix", 69 + "rev": "302af509428169db34f268324162712d10559f74", 70 + "type": "github" 71 + }, 72 + "original": { 73 + "owner": "cachix", 74 + "repo": "git-hooks.nix", 75 + "type": "github" 76 + } 77 + }, 78 + "gitignore": { 79 + "inputs": { 80 + "nixpkgs": [ 81 + "gleam2nix", 82 + "git-hooks", 83 + "nixpkgs" 84 + ] 85 + }, 86 + "locked": { 87 + "lastModified": 1709087332, 88 + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 89 + "owner": "hercules-ci", 90 + "repo": "gitignore.nix", 91 + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 92 + "type": "github" 93 + }, 94 + "original": { 95 + "owner": "hercules-ci", 96 + "repo": "gitignore.nix", 97 + "type": "github" 98 + } 99 + }, 100 + "gleam2nix": { 101 + "inputs": { 102 + "fenix": "fenix", 103 + "flake-compat": "flake-compat", 104 + "git-hooks": "git-hooks", 105 + "nixpkgs": [ 106 + "nixpkgs" 107 + ], 108 + "treefmt-nix": "treefmt-nix" 109 + }, 110 + "locked": { 111 + "lastModified": 1760789750, 112 + "narHash": "sha256-qFNTfY5RGQ+ZKg2TbEVY+um1TpAWF+kp1IWln/qcqMA=", 113 + "ref": "refs/heads/main", 114 + "rev": "bab978942a79b09725a45ea00f700584e7d187c0", 115 + "revCount": 122, 116 + "type": "git", 117 + "url": "https://git.isincredibly.gay/srxl/gleam2nix" 118 + }, 119 + "original": { 120 + "type": "git", 121 + "url": "https://git.isincredibly.gay/srxl/gleam2nix" 122 + } 123 + }, 124 + "nixpkgs": { 125 + "locked": { 126 + "lastModified": 1768569498, 127 + "narHash": "sha256-bB6Nt99Cj8Nu5nIUq0GLmpiErIT5KFshMQJGMZwgqUo=", 128 + "owner": "NixOS", 129 + "repo": "nixpkgs", 130 + "rev": "be5afa0fcb31f0a96bf9ecba05a516c66fcd8114", 131 + "type": "github" 132 + }, 133 + "original": { 134 + "owner": "NixOS", 135 + "ref": "nixpkgs-unstable", 136 + "repo": "nixpkgs", 137 + "type": "github" 138 + } 139 + }, 140 + "root": { 141 + "inputs": { 142 + "gleam2nix": "gleam2nix", 143 + "nixpkgs": "nixpkgs" 144 + } 145 + }, 146 + "rust-analyzer-src": { 147 + "flake": false, 148 + "locked": { 149 + "lastModified": 1757362324, 150 + "narHash": "sha256-/PAhxheUq4WBrW5i/JHzcCqK5fGWwLKdH6/Lu1tyS18=", 151 + "owner": "rust-lang", 152 + "repo": "rust-analyzer", 153 + "rev": "9edc9cbe5d8e832b5864e09854fa94861697d2fd", 154 + "type": "github" 155 + }, 156 + "original": { 157 + "owner": "rust-lang", 158 + "ref": "nightly", 159 + "repo": "rust-analyzer", 160 + "type": "github" 161 + } 162 + }, 163 + "treefmt-nix": { 164 + "inputs": { 165 + "nixpkgs": [ 166 + "gleam2nix", 167 + "nixpkgs" 168 + ] 169 + }, 170 + "locked": { 171 + "lastModified": 1756662192, 172 + "narHash": "sha256-F1oFfV51AE259I85av+MAia221XwMHCOtZCMcZLK2Jk=", 173 + "owner": "numtide", 174 + "repo": "treefmt-nix", 175 + "rev": "1aabc6c05ccbcbf4a635fb7a90400e44282f61c4", 176 + "type": "github" 177 + }, 178 + "original": { 179 + "owner": "numtide", 180 + "repo": "treefmt-nix", 181 + "type": "github" 182 + } 183 + } 184 + }, 185 + "root": "root", 186 + "version": 7 187 + }
+95
flake.nix
··· 1 + { 2 + inputs = { 3 + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 + gleam2nix.url = "git+https://git.isincredibly.gay/srxl/gleam2nix"; 5 + gleam2nix.inputs.nixpkgs.follows = "nixpkgs"; 6 + }; 7 + 8 + outputs = { 9 + nixpkgs, 10 + gleam2nix, 11 + ... 12 + }: let 13 + lib = nixpkgs.lib; 14 + supportedSystems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]; 15 + forEachSupportedSystem = f: 16 + lib.genAttrs supportedSystems (system: 17 + f { 18 + pkgs = import nixpkgs {inherit system;}; 19 + g2n = gleam2nix.packages.${system}.gleam2nix; 20 + buildGleamApplication = gleam2nix.lib.${system}.buildGleamApplication; 21 + }); 22 + in { 23 + devShells = forEachSupportedSystem ({ 24 + pkgs, 25 + g2n, 26 + ... 27 + }: { 28 + default = pkgs.mkShell { 29 + packages = with pkgs; [ 30 + gleam 31 + beamMinimal28Packages.erlang 32 + beamMinimal28Packages.rebar3 33 + g2n 34 + ]; 35 + buildInputs = with pkgs; [ 36 + alsa-lib 37 + ]; 38 + nativeBuildInputs = with pkgs; [ 39 + pkg-config 40 + ]; 41 + }; 42 + }); 43 + apps = forEachSupportedSystem ({pkgs, ...}: let 44 + runtimeInputs = with pkgs; [ 45 + gleam 46 + beamMinimal28Packages.erlang 47 + beamMinimal28Packages.rebar3 48 + beamMinimal28Packages.elixir 49 + beamMinimal28Packages.hex 50 + ]; 51 + in { 52 + default = { 53 + type = "app"; 54 + program = "${(pkgs.writeShellApplication { 55 + inherit runtimeInputs; 56 + name = "app"; 57 + text = '' 58 + ${pkgs.gleam}/bin/gleam run 59 + ''; 60 + })}/bin/app"; 61 + }; 62 + }); 63 + packages = forEachSupportedSystem ({ 64 + pkgs, 65 + buildGleamApplication, 66 + ... 67 + }: let 68 + buildInputs = with pkgs; [ 69 + alsa-lib 70 + ]; 71 + nativeBuildInputs = with pkgs; [ 72 + pkg-config 73 + ]; 74 + in { 75 + default = buildGleamApplication { 76 + inherit buildInputs nativeBuildInputs; 77 + pname = "fern"; 78 + version = "1.0.0"; 79 + target = "erlang"; 80 + erlang = pkgs.beamMinimal28Packages.erlang; 81 + src = ./.; 82 + gleamNix = import ./gleam.nix; 83 + gleamNixOverrides = final: prev: { 84 + alsa = 85 + prev.alsa; 86 + # .override { 87 + # buildInputs = with pkgs; [ 88 + # alsa-lib.dev 89 + # ]; 90 + # }; 91 + }; 92 + }; 93 + }); 94 + }; 95 + }
+159
gleam.nix
··· 1 + { 2 + lib, 3 + newScope, 4 + beamPackages, 5 + buildGleam, 6 + fetchgit, 7 + }: 8 + 9 + let 10 + inherit (beamPackages) buildMix buildRebar3 fetchHex; 11 + in 12 + 13 + lib.makeScope newScope (self: { 14 + alsa = buildRebar3 { 15 + name = "alsa"; 16 + version = "0.2.3"; 17 + otpApplication = "alsa"; 18 + 19 + src = fetchHex { 20 + pkg = "alsa"; 21 + version = "0.2.3"; 22 + sha256 = "sha256-2neaWcJnD2wj4FUu+o8Ph8gRBagypGQmSj4zqPhKNgw="; 23 + }; 24 + 25 + beamDeps = with self; [ 26 + reg 27 + ]; 28 + }; 29 + 30 + decimal = buildMix { 31 + name = "decimal"; 32 + version = "2.3.0"; 33 + otpApplication = "decimal"; 34 + 35 + src = fetchHex { 36 + pkg = "decimal"; 37 + version = "2.3.0"; 38 + sha256 = "sha256-pNZjVcspy0fDzzDnEynlg2HPyzfDQjXvO/HXvzdzrqw="; 39 + }; 40 + }; 41 + 42 + ffmpex = buildMix { 43 + name = "ffmpex"; 44 + version = "0.11.0"; 45 + otpApplication = "ffmpex"; 46 + 47 + src = fetchHex { 48 + pkg = "ffmpex"; 49 + version = "0.11.0"; 50 + sha256 = "sha256-JCnWe63JGVes5XK5FpYVYZdAkEpYeRKJulTZnlehZOs="; 51 + }; 52 + 53 + beamDeps = with self; [ 54 + jason 55 + rambo 56 + ]; 57 + }; 58 + 59 + filepath = buildGleam { 60 + name = "filepath"; 61 + version = "1.1.2"; 62 + otpApplication = "filepath"; 63 + 64 + src = fetchHex { 65 + pkg = "filepath"; 66 + version = "1.1.2"; 67 + sha256 = "sha256-sGqa8L8Q5RQB1kuY5LYn8dLkjBVJZ9p69NCRR4Cm1Ao="; 68 + }; 69 + 70 + beamDeps = with self; [ 71 + gleam_stdlib 72 + ]; 73 + }; 74 + 75 + gleam_stdlib = buildGleam { 76 + name = "gleam_stdlib"; 77 + version = "0.68.1"; 78 + otpApplication = "gleam_stdlib"; 79 + 80 + src = fetchHex { 81 + pkg = "gleam_stdlib"; 82 + version = "0.68.1"; 83 + sha256 = "sha256-9/rr2O8mBmToakbI26I1CNHRG7O8xu4bibO8PlyD/x4="; 84 + }; 85 + }; 86 + 87 + gleeunit = buildGleam { 88 + name = "gleeunit"; 89 + version = "1.9.0"; 90 + otpApplication = "gleeunit"; 91 + 92 + src = fetchHex { 93 + pkg = "gleeunit"; 94 + version = "1.9.0"; 95 + sha256 = "sha256-2pVTzli2eSSzxjH5b+M3DEnrbW3Gs4TsSGLMSqpxjzw="; 96 + }; 97 + 98 + beamDeps = with self; [ 99 + gleam_stdlib 100 + ]; 101 + }; 102 + 103 + jason = buildMix { 104 + name = "jason"; 105 + version = "1.4.4"; 106 + otpApplication = "jason"; 107 + 108 + src = fetchHex { 109 + pkg = "jason"; 110 + version = "1.4.4"; 111 + sha256 = "sha256-xesMq5HwlFmflNVbxjQJI2qOxpohpngUUp6NX2zJCzs="; 112 + }; 113 + 114 + beamDeps = with self; [ 115 + decimal 116 + ]; 117 + }; 118 + 119 + rambo = buildMix { 120 + name = "rambo"; 121 + version = "0.3.4"; 122 + otpApplication = "rambo"; 123 + 124 + src = fetchHex { 125 + pkg = "rambo"; 126 + version = "0.3.4"; 127 + sha256 = "sha256-DMVO0In7vIS2X0uKd0Ik6/5g5cgBhvr8eRCz43mtWPE="; 128 + }; 129 + }; 130 + 131 + reg = buildRebar3 { 132 + name = "reg"; 133 + version = "0.1.0"; 134 + otpApplication = "reg"; 135 + 136 + src = fetchHex { 137 + pkg = "reg"; 138 + version = "0.1.0"; 139 + sha256 = "sha256-qFejOSvl5nNuYTghn2ub5FVSD1PtcfUUBpMXQ8ghbT4="; 140 + }; 141 + }; 142 + 143 + simplifile = buildGleam { 144 + name = "simplifile"; 145 + version = "2.3.2"; 146 + otpApplication = "simplifile"; 147 + 148 + src = fetchHex { 149 + pkg = "simplifile"; 150 + version = "2.3.2"; 151 + sha256 = "sha256-4Em02s1NIG2HhDvPTHdaUK4PUKUgMaL/tAye0H1uxwo="; 152 + }; 153 + 154 + beamDeps = with self; [ 155 + filepath 156 + gleam_stdlib 157 + ]; 158 + }; 159 + })
+27
gleam.toml
··· 1 + name = "fern" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 17 + alsa = ">= 0.2.3 and < 1.0.0" 18 + simplifile = ">= 2.3.2 and < 3.0.0" 19 + ffmpex = ">= 0.11.0 and < 1.0.0" 20 + jason = ">= 1.4.4 and < 2.0.0" 21 + decimal = ">= 2.3.0 and < 3.0.0" 22 + 23 + [dev-dependencies] 24 + gleeunit = ">= 1.0.0 and < 2.0.0" 25 + 26 + [erlang] 27 + extra_applications = ["alsa"]
+24
manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "alsa", version = "0.2.3", build_tools = ["rebar3"], requirements = ["reg"], otp_app = "alsa", source = "hex", outer_checksum = "DA779A59C2670F6C23E0552EFA8F0F87C81105A832A464264A3E33A8F84A360C" }, 6 + { name = "decimal", version = "2.3.0", build_tools = ["mix"], requirements = [], otp_app = "decimal", source = "hex", outer_checksum = "A4D66355CB29CB47C3CF30E71329E58361CFCB37C34235EF3BF1D7BF3773AEAC" }, 7 + { name = "ffmpex", version = "0.11.0", build_tools = ["mix"], requirements = ["jason", "rambo"], otp_app = "ffmpex", source = "hex", outer_checksum = "2429D67BADC91957ACE572B9169615619740904A58791289BA54D99E57A164EB" }, 8 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 9 + { name = "gleam_stdlib", version = "0.68.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F7FAEBD8EF260664E86A46C8DBA23508D1D11BB3BCC6EE1B89B3BC3E5C83FF1E" }, 10 + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 11 + { name = "jason", version = "1.4.4", build_tools = ["mix"], requirements = ["decimal"], otp_app = "jason", source = "hex", outer_checksum = "C5EB0CAB91F094599F94D55BC63409236A8EC69A21A67814529E8D5F6CC90B3B" }, 12 + { name = "rambo", version = "0.3.4", build_tools = ["mix"], requirements = [], otp_app = "rambo", source = "hex", outer_checksum = "0CC54ED089FBBC84B65F4B8A774224EBFE60E5C80186FAFC7910B3E379AD58F1" }, 13 + { name = "reg", version = "0.1.0", build_tools = ["rebar3"], requirements = [], otp_app = "reg", source = "hex", outer_checksum = "A857A3392BE5E6736E6138219F6B9BE455520F53ED71F51406931743C8216D3E" }, 14 + { name = "simplifile", version = "2.3.2", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "E049B4DACD4D206D87843BCF4C775A50AE0F50A52031A2FFB40C9ED07D6EC70A" }, 15 + ] 16 + 17 + [requirements] 18 + alsa = { version = ">= 0.2.3 and < 1.0.0" } 19 + decimal = { version = ">= 2.3.0 and < 3.0.0" } 20 + ffmpex = { version = ">= 0.11.0 and < 1.0.0" } 21 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 22 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 23 + jason = { version = ">= 1.4.4 and < 2.0.0" } 24 + simplifile = { version = ">= 2.3.2 and < 3.0.0" }
+56
src/fern.gleam
··· 1 + import fern/alsa_pcm 2 + import fern/alsa_result 3 + import gleam/io 4 + import gleam/result 5 + import gleam/string 6 + import simplifile 7 + 8 + pub fn play( 9 + device: String, 10 + data: BitArray, 11 + ) -> Result(Nil, alsa_result.AlsaPcmError) { 12 + use pcm <- result.try(alsa_pcm.open(device, alsa_pcm.Playback)) 13 + use _ <- result.try(alsa_pcm.set_params( 14 + pcm, 15 + alsa_pcm.params( 16 + access: alsa_pcm.rw_interleaved(), 17 + format: alsa_pcm.s16_le(), 18 + channels: 2, 19 + rate: 48_000, 20 + rate_resample: True, 21 + latency: 100_000, 22 + ), 23 + )) 24 + use _ <- result.try(play_i(pcm, data)) 25 + use _ <- result.try(alsa_pcm.close(pcm)) 26 + Ok(Nil) 27 + } 28 + 29 + pub fn play_i( 30 + pcm: alsa_pcm.Pcm, 31 + data: BitArray, 32 + ) -> Result(Nil, alsa_result.AlsaPcmError) { 33 + case alsa_pcm.write_i(pcm, data, alsa_pcm.Infinity) { 34 + Ok(_) -> Ok(Nil) 35 + Error(e) -> { 36 + io.println_error( 37 + "write_i failure: " <> string.inspect(e) <> " (trying recovery)", 38 + ) 39 + case alsa_pcm.recover(pcm, e) { 40 + Ok(_) -> play_i(pcm, data) 41 + Error(e) -> Error(e) 42 + } 43 + } 44 + } 45 + } 46 + 47 + pub fn main() { 48 + use contents <- result.try(simplifile.read_bits( 49 + // i wonder what i'm listening to 50 + // ../../Audiobooks/Arc of a Scythe/Scythe/0001_Scythe.mp3 51 + // ffmpeg -i "../../Audiobooks/Arc of a Scythe/Scythe/0001_Scythe.mp3" -f s16le -ac 2 -ar 48000 ./test_audio 52 + "./test_audio", 53 + )) 54 + let _ = play("default", contents) 55 + Ok(Nil) 56 + }
+109
src/fern/alsa_pcm.gleam
··· 1 + import fern/alsa_result 2 + 3 + pub type Direction { 4 + Playback 5 + Capture 6 + } 7 + 8 + @external(erlang, "alsa_pcm", "pcm") 9 + pub type Pcm 10 + 11 + @external(erlang, "alsa_pcm", "params") 12 + pub type Params 13 + 14 + pub type Timeout { 15 + Infinity 16 + Timeout(Int) 17 + NoWait 18 + } 19 + 20 + @external(erlang, "alsa_pcm", "open") 21 + pub fn open( 22 + device: String, 23 + direction: Direction, 24 + ) -> Result(Pcm, alsa_result.AlsaPcmError) 25 + 26 + pub fn close(pcm: Pcm) -> Result(Nil, alsa_result.AlsaPcmError) { 27 + case do_close(pcm) { 28 + alsa_result.Ok -> Ok(Nil) 29 + alsa_result.Error(e) -> Error(e) 30 + } 31 + } 32 + 33 + @external(erlang, "alsa_pcm", "close") 34 + fn do_close(pcm: Pcm) -> alsa_result.AlsaResult 35 + 36 + pub fn write_i( 37 + pcm pcm: Pcm, 38 + binary binary: BitArray, 39 + timeout timeout: Timeout, 40 + ) -> Result(Nil, alsa_result.AlsaPcmError) { 41 + case do_write_i(pcm, binary, timeout) { 42 + alsa_result.Ok -> Ok(Nil) 43 + alsa_result.Error(e) -> Error(e) 44 + } 45 + } 46 + 47 + @external(erlang, "alsa_pcm", "writei") 48 + fn do_write_i( 49 + pcm: Pcm, 50 + binary: BitArray, 51 + timeout: Timeout, 52 + ) -> alsa_result.AlsaResult 53 + 54 + pub fn recover( 55 + pcm pcm: Pcm, 56 + error error: alsa_result.AlsaPcmError, 57 + ) -> Result(Nil, alsa_result.AlsaPcmError) { 58 + case do_recover(pcm, error) { 59 + alsa_result.Ok -> Ok(Nil) 60 + alsa_result.Error(e) -> Error(e) 61 + } 62 + } 63 + 64 + @external(erlang, "alsa_pcm", "recover") 65 + fn do_recover( 66 + pcm: Pcm, 67 + error: alsa_result.AlsaPcmError, 68 + ) -> alsa_result.AlsaResult 69 + 70 + pub fn set_params( 71 + pcm pcm: Pcm, 72 + params params: Params, 73 + ) -> Result(Nil, alsa_result.AlsaPcmError) { 74 + case do_set_params(pcm, params) { 75 + alsa_result.Ok -> Ok(Nil) 76 + alsa_result.Error(e) -> Error(e) 77 + } 78 + } 79 + 80 + @external(erlang, "alsa_pcm", "set_params") 81 + fn do_set_params(pcm: Pcm, params: Params) -> alsa_result.AlsaResult 82 + 83 + @external(erlang, "alsa_pcm", "hwparam_access") 84 + pub type HwParamAccess 85 + 86 + @external(erlang, "alsa_pcm", "hwparam_format") 87 + pub type HwParamFormat 88 + 89 + @external(erlang, "fern@ffi", "make_params") 90 + pub fn params( 91 + access access: HwParamAccess, 92 + format format: HwParamFormat, 93 + channels channels: Int, 94 + rate rate: Int, 95 + rate_resample rate_resample: Bool, 96 + latency latency: Int, 97 + ) -> Params 98 + 99 + @external(erlang, "fern@ffi", "rw_interleaved") 100 + pub fn rw_interleaved() -> HwParamAccess 101 + 102 + @external(erlang, "fern@ffi", "s16_le") 103 + pub fn s16_le() -> HwParamFormat 104 + 105 + @external(erlang, "fern@ffi", "float") 106 + pub fn float() -> HwParamFormat 107 + 108 + @external(erlang, "fern@ffi", "float64") 109 + pub fn float64() -> HwParamFormat
+10
src/fern/alsa_result.gleam
··· 1 + // Fucked up result type in different file to not collide with Result from the 2 + // prelude - yippee 3 + @internal 4 + pub type AlsaResult { 5 + Ok 6 + Error(AlsaPcmError) 7 + } 8 + 9 + @external(erlang, "alsa_pcm", "error") 10 + pub type AlsaPcmError
+42
src/fern/fern@ffi.erl
··· 1 + -module(fern@ffi). 2 + -export([ 3 + rw_interleaved/0, 4 + make_params/6, 5 + s16_le/0, 6 + float/0, 7 + float64/0 8 + ]). 9 + 10 + -spec rw_interleaved() -> alsa_pcm:hwparam_access(). 11 + rw_interleaved() -> 12 + rw_interleaved. 13 + 14 + -spec s16_le() -> alsa_pcm:hwparam_format(). 15 + s16_le() -> 16 + s16_le. 17 + 18 + -spec float() -> alsa_pcm:hwparam_format(). 19 + float() -> 20 + float. 21 + 22 + -spec float64() -> alsa_pcm:hwparam_format(). 23 + float64() -> 24 + float64. 25 + 26 + -spec make_params( 27 + alsa_pcm:hwparam_access(), 28 + alsa_pcm:hwparam_format(), 29 + pos_integer(), 30 + pos_integer(), 31 + boolean(), 32 + pos_integer() 33 + ) -> alsa_pcm:params(). 34 + make_params(Access, Format, Channels, Rate, RateResample, Latency) -> 35 + #{ 36 + access => Access, 37 + format => Format, 38 + channels => Channels, 39 + rate => Rate, 40 + rate_resample => RateResample, 41 + latency => Latency 42 + }.
+13
test/fern_test.gleam
··· 1 + import gleeunit 2 + 3 + pub fn main() -> Nil { 4 + gleeunit.main() 5 + } 6 + 7 + // gleeunit test functions end in `_test` 8 + pub fn hello_world_test() { 9 + let name = "Joe" 10 + let greeting = "Hello, " <> name <> "!" 11 + 12 + assert greeting == "Hello, Joe!" 13 + }