cosmopolitan/dsp/audio/cosmoaudio/cosmoaudio.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

245 lines
8.1 KiB
C
Raw Normal View History

#define COSMOAUDIO_BUILD
#include "cosmoaudio.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MA_STATIC
#define MA_NO_DECODING
#define MA_NO_ENCODING
#ifdef NDEBUG
#define MA_DR_MP3_NO_STDIO
#endif
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#ifndef NDEBUG
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#else
#define LOG(...) (void)0
#endif
struct CosmoAudio {
ma_device device;
ma_pcm_rb input;
ma_pcm_rb output;
ma_uint32 sampleRate;
ma_uint32 channels;
};
static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
ma_uint32 channels) {
ma_result result;
ma_uint32 framesRead;
ma_uint32 framesToRead;
for (framesRead = 0; framesRead < frameCount; framesRead += framesToRead) {
framesToRead = frameCount - framesRead;
void* pMappedBuffer;
result = ma_pcm_rb_acquire_read(rb, &framesToRead, &pMappedBuffer);
if (result != MA_SUCCESS) {
LOG("ma_pcm_rb_acquire_read failed: %s\n", ma_result_description(result));
return COSMOAUDIO_ERROR;
}
if (!framesToRead)
break;
memcpy(pOutput + framesRead * channels, pMappedBuffer,
framesToRead * channels * sizeof(float));
result = ma_pcm_rb_commit_read(rb, framesToRead);
if (result != MA_SUCCESS) {
if (result == MA_AT_END)
break;
LOG("ma_pcm_rb_commit_read failed: %s\n", ma_result_description(result));
return COSMOAUDIO_ERROR;
}
}
return framesRead;
}
static int write_ring_buffer(ma_pcm_rb* rb, const float* pInput,
ma_uint32 frameCount, ma_uint32 channels) {
ma_result result;
ma_uint32 framesWritten;
ma_uint32 framesToWrite;
for (framesWritten = 0; framesWritten < frameCount;
framesWritten += framesToWrite) {
framesToWrite = frameCount - framesWritten;
void* pMappedBuffer;
result = ma_pcm_rb_acquire_write(rb, &framesToWrite, &pMappedBuffer);
if (result != MA_SUCCESS) {
LOG("ma_pcm_rb_acquire_write failed: %s\n",
ma_result_description(result));
return COSMOAUDIO_ERROR;
}
if (!framesToWrite)
break;
memcpy(pMappedBuffer, pInput + framesWritten * channels,
framesToWrite * channels * sizeof(float));
result = ma_pcm_rb_commit_write(rb, framesToWrite);
if (result != MA_SUCCESS) {
if (result == MA_AT_END)
break;
LOG("ma_pcm_rb_commit_write failed: %s\n", ma_result_description(result));
return COSMOAUDIO_ERROR;
}
}
return framesWritten;
}
static void data_callback_f32(ma_device* pDevice, float* pOutput,
const float* pInput, ma_uint32 frameCount) {
struct CosmoAudio* ca = (struct CosmoAudio*)pDevice->pUserData;
read_ring_buffer(&ca->output, pOutput, frameCount, ca->channels);
write_ring_buffer(&ca->input, pInput, frameCount, ca->channels);
}
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
ma_uint32 frameCount) {
data_callback_f32(pDevice, (float*)pOutput, (const float*)pInput, frameCount);
}
/**
* Opens access to speaker and microphone.
*
* @param cap will receive pointer to allocated CosmoAudio object on success,
* which must be freed by caller with cosmoaudio_close()
* @param sampleRate is sample rate in Hz, e.g. 44100
* @param channels is number of channels (1 for mono, 2 for stereo)
* @return 0 on success, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_open(struct CosmoAudio** cap, int sampleRate,
int channels) {
// Allocate cosmo audio object.
struct CosmoAudio* ca;
if (!(ca = (struct CosmoAudio*)malloc(sizeof(struct CosmoAudio))))
return COSMOAUDIO_ERROR;
ca->channels = channels;
ca->sampleRate = sampleRate;
// Initialize device.
ma_result result;
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex);
deviceConfig.sampleRate = sampleRate;
deviceConfig.capture.channels = channels;
deviceConfig.capture.format = ma_format_f32;
deviceConfig.capture.shareMode = ma_share_mode_shared;
deviceConfig.playback.channels = channels;
deviceConfig.playback.format = ma_format_f32;
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = ca;
result = ma_device_init(NULL, &deviceConfig, &ca->device);
if (result != MA_SUCCESS) {
free(ca);
return COSMOAUDIO_ERROR;
}
// Initialize the speaker ring buffer.
result = ma_pcm_rb_init(ma_format_f32, channels,
ca->device.playback.internalPeriodSizeInFrames * 10,
NULL, NULL, &ca->output);
if (result != MA_SUCCESS) {
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
}
ma_pcm_rb_set_sample_rate(&ca->output, sampleRate);
// Initialize the microphone ring buffer.
result = ma_pcm_rb_init(ma_format_f32, channels,
ca->device.capture.internalPeriodSizeInFrames * 10,
NULL, NULL, &ca->input);
if (result != MA_SUCCESS) {
ma_pcm_rb_uninit(&ca->output);
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
}
ma_pcm_rb_set_sample_rate(&ca->output, sampleRate);
// Start audio playback.
if (ma_device_start(&ca->device) != MA_SUCCESS) {
ma_pcm_rb_uninit(&ca->input);
ma_pcm_rb_uninit(&ca->output);
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
}
*cap = ca;
return COSMOAUDIO_SUCCESS;
}
/**
* Closes audio device and frees all associated resources.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @return 0 on success, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
ma_device_uninit(&ca->device);
ma_pcm_rb_uninit(&ca->output);
ma_pcm_rb_uninit(&ca->input);
free(ca);
return COSMOAUDIO_SUCCESS;
}
/**
* Writes raw audio data to speaker.
*
* The data is written to a ring buffer in real-time, which is then
* played back very soon on the audio device. This has tolerence for
* a certain amount of buffering, but expects that this function is
* repeatedly called at a regular time interval. The caller should
* have its own sleep loop for this purpose.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @param data is pointer to raw audio samples, expected to be in the range
* -1.0 to 1.0, where channels are interleaved
* @param frames is the number of frames (i.e. number of samples divided by
* number of channels) from `data` to write to audio device
* @return number of frames written, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio* ca, const float* data,
int frames) {
return write_ring_buffer(&ca->output, data, frames, ca->channels);
}
/**
* Reads raw audio data from microphone.
*
* The data is read from a ring buffer in real-time, which is then
* played back very soon on the audio device. This has tolerence for
* a certain amount of buffering, but expects that this function is
* repeatedly called at a regular time interval. The caller should
* have its own sleep loop for this purpose.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @param data is pointer to raw audio samples, expected to be in the range
* -1.0 to 1.0, where channels are interleaved
* @param frames is the number of frames (i.e. number of samples divided by
* number of channels) from `data` to read from microphone
* @return number of frames read, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio* ca, float* data,
int frames) {
return read_ring_buffer(&ca->input, data, frames, ca->channels);
}
#ifdef _MSC_VER
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
(void)hModule;
(void)lpReserved;
(void)ul_reason_for_call;
}
#endif