#define _POSIX_C_SOURCE 200809L #include "wave_internal.h" #include #include #include #include #include #include struct xtc_recorder { float *ring; size_t capacity; uint64_t write_position, read_position, written; unsigned int error, rate; FILE *file; char *path, *temporary; int finished, replace, committed, preserve; }; /** * @brief Allocates a bounded recording ring and opens a temporary mono float32 WAV file. * * @param path NUL-terminated file or asset path. * @param rate Sample rate in hertz. * @param capacity Recording ring capacity in frames, from 1 to 2097152. * @param out Receives the newly allocated object; caller owns it on success. * @return XTC_OK on success, or an error code describing the failure. */ xtc_result xtc_recorder_create_replace(const char *path, unsigned int rate, size_t capacity, int replace, xtc_recorder **out) { xtc_recorder *r; size_t length = 0; int fd; if (!out) return XTC_ERR_INVALID_ARGUMENT; *out = NULL; if ((replace != 0 && replace != 1) || !path || !rate || rate > 768000 || !capacity || capacity > 2097152U) return XTC_ERR_INVALID_ARGUMENT; while (length < 4096 && path[length]) ++length; if (!length || length == 4096) return XTC_ERR_INVALID_ARGUMENT; if (sizeof(float) != 4 || FLT_RADIX != 2 || FLT_MANT_DIG != 24 || FLT_MAX_EXP != 128) return XTC_ERR_UNSUPPORTED; if (!__atomic_always_lock_free(sizeof(uint64_t), 0) || !__atomic_always_lock_free(sizeof(unsigned int), 0)) return XTC_ERR_UNSUPPORTED; if (!replace) { struct stat info; if (lstat(path, &info) == 0 || errno != ENOENT) return XTC_ERR_IO; } r = calloc(1, sizeof(*r)); if (!r) return XTC_ERR_OUT_OF_MEMORY; r->replace = replace; r->capacity = capacity; r->rate = rate; r->ring = malloc(capacity * sizeof(float)); r->path = malloc(length + 1); r->temporary = calloc(length + 12, 1); if (!r->ring || !r->path || !r->temporary) { xtc_recorder_destroy(r); return XTC_ERR_OUT_OF_MEMORY; } memcpy(r->path, path, length + 1); memcpy(r->temporary, path, length); memcpy(r->temporary + length, ".tmp.XXXXXX", 12); fd = mkstemp(r->temporary); if (fd < 0) { xtc_recorder_destroy(r); return XTC_ERR_IO; } r->file = fdopen(fd, "w+b"); if (!r->file) { close(fd); xtc_recorder_destroy(r); return XTC_ERR_IO; } if (!xtc_wave_header(r->file, rate, 0)) { xtc_recorder_destroy(r); return XTC_ERR_IO; } *out = r; return XTC_OK; } /** * @brief Enqueues an entire audio block without blocking; overflow sets a sticky error. * * @param r Recorder handle. * @param input Borrowed input sample buffer. * @param frames Number of audio frames. */ void xtc_recorder_push(xtc_recorder *r, const float *input, size_t frames) { uint64_t write, read; size_t i; if (!r || !frames || __atomic_load_n(&r->error, __ATOMIC_ACQUIRE)) return; write = __atomic_load_n(&r->write_position, __ATOMIC_RELAXED); read = __atomic_load_n(&r->read_position, __ATOMIC_ACQUIRE); if (!input || frames > r->capacity || frames > r->capacity - (size_t)(write - read) || write > UINT64_C(1073741814) - frames) { __atomic_store_n(&r->error, (unsigned int)(input ? XTC_ERR_CAPACITY : XTC_ERR_IO), __ATOMIC_RELEASE); return; } for (i = 0; i < frames; ++i) r->ring[(size_t)((write + i) % r->capacity)] = input[i]; __atomic_store_n(&r->write_position, write + frames, __ATOMIC_RELEASE); } /** * @brief Writes queued samples to the temporary WAV file on the control thread. * * @param r Recorder handle. * @return XTC_OK on success, or an error code describing the failure. */ xtc_result xtc_recorder_drain(xtc_recorder *r) { uint64_t read, write; if (!r || !r->file || r->finished) return XTC_ERR_INVALID_STATE; read = __atomic_load_n(&r->read_position, __ATOMIC_RELAXED); write = __atomic_load_n(&r->write_position, __ATOMIC_ACQUIRE); while (read < write) { size_t offset = (size_t)(read % r->capacity); size_t count = r->capacity - offset; if (count > write - read) count = (size_t)(write - read); if (!xtc_wave_write(r->file, r->ring + offset, count)) { __atomic_store_n(&r->error, (unsigned int)XTC_ERR_IO, __ATOMIC_RELEASE); return XTC_ERR_IO; } read += count; r->written += count; __atomic_store_n(&r->read_position, read, __ATOMIC_RELEASE); } return (xtc_result)__atomic_load_n(&r->error, __ATOMIC_ACQUIRE); } /** * @brief Copies the number of accepted frames and returns the sticky recording status. * * @param r Recorder handle. * @param frames Receives the accepted frame count. * @return XTC_OK on success, or an error code describing the failure. */ xtc_result xtc_recorder_status(const xtc_recorder *r, uint64_t *frames) { if (!r || !frames) return XTC_ERR_INVALID_ARGUMENT; *frames = __atomic_load_n(&r->write_position, __ATOMIC_ACQUIRE); return (xtc_result)__atomic_load_n(&r->error, __ATOMIC_ACQUIRE); } /** * @brief Drains and finalizes the WAV file, then renames it after producer quiescence. * * @param r Recorder handle. * @return XTC_OK on success, or an error code describing the failure. */ xtc_result xtc_recorder_finish(xtc_recorder *r) { xtc_result result; int failed = 0; if (!r || !r->file || r->finished) return XTC_ERR_INVALID_STATE; result = xtc_recorder_drain(r); if (result == XTC_ERR_IO) failed = 1; if (fseek(r->file, 0, SEEK_SET) || !xtc_wave_header(r->file, r->rate, (uint32_t)r->written) || fflush(r->file) || fsync(fileno(r->file))) failed = 1; if (fclose(r->file)) failed = 1; r->file = NULL; if (!failed) { if (r->replace ? rename(r->temporary, r->path) : link(r->temporary, r->path)) failed = 1; else { r->committed = 1; if (!r->replace) (void)unlink(r->temporary); } } r->preserve = failed && (__atomic_load_n(&r->write_position, __ATOMIC_ACQUIRE) != 0 || r->written != 0); r->finished = 1; return failed ? XTC_ERR_IO : result; } /** * @brief Releases recorder storage and removes any unfinished temporary file; accepts NULL. * * @param r Recorder handle. */ void xtc_recorder_destroy(xtc_recorder *r) { if (!r) return; if (r->file) { if (__atomic_load_n(&r->write_position, __ATOMIC_ACQUIRE)) (void)xtc_recorder_finish(r); /* Producer must already be quiescent. */ else { fclose(r->file); r->file = NULL; } } if (r->temporary && !r->preserve) unlink(r->temporary); free(r->temporary); free(r->path); free(r->ring); free(r); } xtc_result xtc_recorder_create(const char *path, unsigned int rate, size_t capacity, xtc_recorder **out) { return xtc_recorder_create_replace(path, rate, capacity, 0, out); } const char *xtc_recorder_recovery_path(const xtc_recorder *r) { if (!r) return ""; return r->committed ? r->path : r->temporary ? r->temporary : ""; } /* Internal cancellation for a failed recovery transaction; never removes the * destination, which may have been created by another process. */ void xtc_recorder_abort(xtc_recorder *r) { if (!r) return; if (r->file) fclose(r->file); r->file = NULL; r->finished = 1; r->preserve = 0; }