commit 0c918cb45c23088aff1233f43910a54fdcc619c1 Author: Hisham Aburaqibah Date: Sat Jun 28 21:26:28 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb64745 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +!*.odin diff --git a/main.odin b/main.odin new file mode 100644 index 0000000..26bb201 --- /dev/null +++ b/main.odin @@ -0,0 +1,119 @@ +package main + +import "base:runtime" +import "base:intrinsics" +import "core:fmt" +import "core:slice" +import "core:time" +import "core:encoding/endian" +import "core:sys/posix" +import "core:os/os2" + +import "shared:alsa" + +DEVICE :: "default" +OUTPUT_FILE :: "capture" +FRAMES :: 128 +FORMAT: alsa.pcm_format_t : .S16_LE +BITS_PER_SAMPLE :: 16 +CHANNELS :: 1 +SAMPLE_RATE :: 22050 + +WAV_HEADER_SIZE :: 44 + +interrupted := false + +main :: proc() { + handle: ^alsa.pcm_t + + alsa_err := alsa.pcm_open(&handle, DEVICE, .CAPTURE, .BLOCK) + if alsa_err < 0 { + fmt.eprintln("Failed to open default capture device.") + os2.exit(1) + } + defer alsa.pcm_close(handle) + + now := time.now() + year, month, day := time.date(now) + hour, minute, second := time.clock_from_time(now) + + out, err := os2.open(fmt.tprintf("%v_%v-%02d-%02dT%02d:%02d:%02d.wav", OUTPUT_FILE, year, int(month), day, hour, minute, second), {.Write, .Create, .Trunc}, 0o644) + if err != nil { + fmt.eprintln("Failed to open output file:", err) + os2.exit(1) + } + defer os2.close(out) + + alsa_err = alsa.pcm_set_params(handle, FORMAT, .RW_INTERLEAVED, CHANNELS, SAMPLE_RATE, 1, 500000) + if alsa_err < 0 { + fmt.eprintln("Failed to set the HW params") + os2.exit(1) + } + + alsa_err = alsa.pcm_prepare(handle) + if alsa_err < 0 { + fmt.eprintln("Failed to prepare audio device for capture.") + os2.exit(1) + } + + samples := make([]u8, FRAMES * CHANNELS * alsa.pcm_format_width(FORMAT) / 8) + + fmt.println("Capturing audio") + + action: posix.sigaction_t + action.sa_handler = proc "c" (signal: posix.Signal) { + context = runtime.default_context() + fmt.println("\nCaught SIGINT. Exiting gracefully") + intrinsics.atomic_store(&interrupted, true) + } + posix.sigaction(.SIGINT, &action, nil) + + four_byte_slice := make([]byte, 4) + two_byte_slice := make([]byte, 2) + defer delete(four_byte_slice) + defer delete(two_byte_slice) + + os2.write(out, []byte{'R', 'I', 'F', 'F'}) // Riff file + os2.write(out, []byte{0,0,0,0}) // File size - 8 bytes. To be filled in later + os2.write(out, []byte{'W', 'A', 'V', 'E'}) // WAVE data + os2.write(out, []byte{'f', 'm', 't', ' '}) // Format chunk marker with NUL byte + endian.put_i32(four_byte_slice, .Little, 16) + os2.write(out, four_byte_slice) // Chunk size: 16 for PCM + endian.put_i16(two_byte_slice, .Little, 1) + os2.write(out, two_byte_slice) // Audio format: 1 for PCM + os2.write(out, two_byte_slice) // Number of channels + endian.put_i32(four_byte_slice, .Little, SAMPLE_RATE) + os2.write(out, four_byte_slice) // Sample rate + endian.put_i32(four_byte_slice, .Little, SAMPLE_RATE * CHANNELS * BITS_PER_SAMPLE / 8) + os2.write(out, four_byte_slice) // Byte rate + endian.put_i16(two_byte_slice, .Little, CHANNELS * BITS_PER_SAMPLE / 8) + os2.write(out, two_byte_slice) // Block align + endian.put_i16(two_byte_slice, .Little, BITS_PER_SAMPLE) + os2.write(out, two_byte_slice) // Bits per sample + os2.write(out, []byte{'d', 'a', 't', 'a'}) // Data chunk header + + size_written := 0 + + os2.write(out, []byte{0,0,0,0}) // Length of data. To be filled in later + + for !intrinsics.atomic_load(&interrupted) { + read := alsa.pcm_readi(handle, raw_data(samples), FRAMES) + if read != FRAMES { + fmt.eprintfln("Didn't read %v frames", FRAMES) + os2.exit(1) + } + + n, err := os2.write(out, samples) // Write the data + if err != nil { + fmt.eprintln("Failed to write sample data to file:", err) + os2.exit(1) + } + size_written += n + } + + endian.put_i32(four_byte_slice, .Little, i32(size_written + WAV_HEADER_SIZE - 8)) + os2.write_at(out, four_byte_slice, 4) // Overall size - 8 bytes + + endian.put_i32(four_byte_slice, .Little, i32(size_written)) + os2.write_at(out, four_byte_slice, 40) // Data size +}