cuticle/hi/microphone.c
2024-06-30 14:43:13 +03:00

231 lines
6.3 KiB
C

#include"node.h"
#include<portaudio.h>
#include"img.h"
#include<stdlib.h>
#include<stdio.h>
#include<math.h>
#include<stdatomic.h>
#include<string.h>
#include"microphone.h"
#include"minitrace.h"
#define pabufsize (48000*5)
typedef struct CHiMicrophoneNode {
CHiPubNode pub;
PaStream *paStream;
int16_t paBuffer[pabufsize];
atomic_size_t paBufferWriteIdx;
atomic_size_t paBufferReadIdx;
} CHiMicrophoneNode;
static int pacallback(const void *input_, void *output, unsigned long samples, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags flags, void *ud) {
CHiMicrophoneNode *node = ud;
const int16_t *input = input_;
size_t writeidx = node->paBufferWriteIdx;
while(writeidx + samples >= pabufsize) {
memcpy(node->paBuffer + writeidx, input, (pabufsize - writeidx) * sizeof(*node->paBuffer));
samples -= pabufsize - writeidx;
input += pabufsize - writeidx;
writeidx = 0;
}
memcpy(node->paBuffer + writeidx, input, samples * sizeof(*node->paBuffer));
writeidx = (writeidx + samples) % pabufsize;
node->paBufferWriteIdx = writeidx;
/*static size_t total = 0;
for(size_t i = 0; i < pabufsize; i++) {
paBuffer[paBufferWriteIdx] = sin(total++ * 440.0 / 24000 * 3.141592653) * 0.1;
paBufferWriteIdx = (paBufferWriteIdx + 1) % pabufsize;
}*/
return paContinue;
}
static int microphone_start(CHiPubNode *pubn) {
CHiMicrophoneNode *node = (void*) pubn;
int dev;
for(dev = 0; dev < CHi_Microphone_GetSourceCount(); dev++) {
if(!strcmp(CHi_Microphone_GetSourceName(dev), CHi_Crawl(&pubn->sinks[0])->data.text)) {
break;
}
}
if(dev == CHi_Microphone_GetSourceCount()) {
for(dev = 0; dev < CHi_Microphone_GetSourceCount(); dev++) {
if(!strcmp(CHi_Microphone_GetSourceName(dev), "default")) {
break;
}
}
}
PaStreamParameters params = {
.device = dev,
.channelCount = 1,
.sampleFormat = paInt16,
.suggestedLatency = Pa_GetDeviceInfo(dev)->defaultLowInputLatency,
};
Pa_OpenStream(&node->paStream, &params, NULL, 48000, 0, paNoFlag, pacallback, pubn);
Pa_StartStream(node->paStream);
return 1;
}
static int microphone_stop(CHiPubNode *pubn) {
CHiMicrophoneNode *node = (void*) pubn;
Pa_StopStream(node->paStream);
Pa_CloseStream(node->paStream);
return 1;
}
static int microphone_perform(CHiPubNode *pubn) {
CHiMicrophoneNode *node = (void*) pubn;
MTR_BEGIN("CHi", "microphone_perform");
if(pubn->sources[0].data.sample) {
CHi_Image_Free(pubn->sources[0].data.sample);
}
size_t width = roundf(CHi_Time_GetDelta(pubn->ng) * 48000.f);
do {
}while((node->paBufferWriteIdx - node->paBufferReadIdx + pabufsize) % pabufsize < width); // Wait until available
CHiImage *ret = CHi_Image_New(2, 1, 2 * width, width, 1, NULL);
if(node->paBufferReadIdx + width >= pabufsize) {
memcpy(ret->data16, node->paBuffer + node->paBufferReadIdx, sizeof(*node->paBuffer) * (pabufsize - node->paBufferReadIdx));
memcpy(ret->data16 + pabufsize - node->paBufferReadIdx, node->paBuffer, sizeof(*node->paBuffer) * (width - pabufsize + node->paBufferReadIdx));
node->paBufferReadIdx = node->paBufferReadIdx + width - pabufsize;
} else {
memcpy(ret->data16, node->paBuffer + node->paBufferReadIdx, sizeof(*node->paBuffer) * width);
node->paBufferReadIdx = (node->paBufferReadIdx + width) % pabufsize;
}
pubn->sources[0].type = CUTIHI_VAL_SAMPLE;
pubn->sources[0].data.sample = ret;
pubn->clean = 0;
MTR_END("CHi", "microphone_perform");
return 1;
}
CUTIVIS CHiPubNode *CHi_Microphone() {
static int inited = 0;
if(!inited) {
Pa_Initialize();
inited = 1;
}
CHiPubNode *n = calloc(1, sizeof(CHiMicrophoneNode));
n->type = CUTIHI_T('CInA','udio');
n->Start = microphone_start;
n->Perform = microphone_perform;
n->Stop = microphone_stop;
n->clean = 0;
n->sinkCount = 1;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), n->sourceCount);
return n;
}
struct CHiExportWavNode {
CHiPubNode pubn;
FILE *output;
};
CUTIVIS int CHi_ExportWav_Start(CHiPubNode *pubn) {
struct CHiExportWavNode *n = (struct CHiExportWavNode*) pubn;
n->output = fopen(CHi_Crawl(&pubn->sinks[0])->data.text, "wb");
struct __attribute__((packed)) {
uint32_t ckID;
uint32_t ckSize;
uint32_t waveID;
} header = {.ckID = 'FFIR', .ckSize = 0, .waveID = 'EVAW'};
fwrite(&header, sizeof(header), 1, n->output);
struct __attribute__((packed)) {
uint32_t ckID;
uint32_t ckSize;
uint16_t wFormatTag;
uint16_t nChannels;
uint32_t nSamplesPerSec;
uint32_t nAvgBytesPerSec;
uint16_t nBlockAlign;
uint16_t wBitsPerSample;
} chunk0 = {.ckID = ' tmf', .ckSize = 16, .wFormatTag = 1 /* float */, .nChannels = 1, .nSamplesPerSec = 48000, .nAvgBytesPerSec = 48000 * 2, .nBlockAlign = 4, .wBitsPerSample = 16};
fwrite(&chunk0, sizeof(chunk0), 1, n->output);
struct __attribute__((packed)) {
uint32_t ckID;
uint32_t ckSize;
} chunk1 = {.ckID = 'atad', .ckSize = 0};
fwrite(&chunk1, sizeof(chunk1), 1, n->output);
return 1;
}
static int exportwav_perform(CHiPubNode *pubn) {
struct CHiExportWavNode *n = (struct CHiExportWavNode*) pubn;
CHiImage *buf = CHi_Crawl(&pubn->sinks[1])->data.sample;
fwrite(buf->data16, 2, buf->width, n->output);
pubn->clean = 0;
return 1;
}
CUTIVIS int CHi_ExportWav_Stop(CHiPubNode *pubn) {
struct CHiExportWavNode *n = (struct CHiExportWavNode*) pubn;
/* Fill size info in headers. */
uint32_t sz = ftell(n->output) - 8;
fseek(n->output, 4, SEEK_SET);
fwrite(&sz, sizeof(sz), 1, n->output);
sz -= 36;
fseek(n->output, 32, SEEK_CUR);
fwrite(&sz, sizeof(sz), 1, n->output);
fclose(n->output);
return 1;
}
CUTIVIS CHiPubNode *CHi_ExportWav() {
struct CHiExportWavNode *n = calloc(1, sizeof(*n));
n->pubn.type = CUTIHI_T('CExp','Wave');
n->pubn.Start = CHi_ExportWav_Start;
n->pubn.Perform = exportwav_perform;
n->pubn.Stop = CHi_ExportWav_Stop;
n->pubn.clean = 0;
n->pubn.sinkCount = 2;
n->pubn.sinks = calloc(sizeof(*n->pubn.sinks), n->pubn.sinkCount);
n->pubn.sourceCount = 0;
n->pubn.sources = NULL;
return &n->pubn;
}
size_t CHi_Microphone_GetSourceCount() {
return Pa_GetDeviceCount();
}
const char *CHi_Microphone_GetSourceName(size_t i) {
return Pa_GetDeviceInfo(i)->name;
}
size_t CHi_Microphone_GetNextSource(size_t i) {
i++;
while(i < CHi_Microphone_GetSourceCount()) {
if(Pa_GetDeviceInfo(i)->maxInputChannels > 0) break;
i++;
}
return i;
}