2024-06-26 20:47:26 +03:00
|
|
|
#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"
|
|
|
|
|
2024-06-30 14:43:13 +03:00
|
|
|
#include"minitrace.h"
|
|
|
|
|
2024-06-26 20:47:26 +03:00
|
|
|
#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;
|
|
|
|
|
2024-06-30 14:43:13 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-26 20:47:26 +03:00
|
|
|
PaStreamParameters params = {
|
2024-06-30 14:43:13 +03:00
|
|
|
.device = dev,
|
2024-06-26 20:47:26 +03:00
|
|
|
.channelCount = 1,
|
|
|
|
.sampleFormat = paInt16,
|
2024-06-30 14:43:13 +03:00
|
|
|
.suggestedLatency = Pa_GetDeviceInfo(dev)->defaultLowInputLatency,
|
2024-06-26 20:47:26 +03:00
|
|
|
};
|
|
|
|
Pa_OpenStream(&node->paStream, ¶ms, 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;
|
|
|
|
|
2024-06-30 14:43:13 +03:00
|
|
|
MTR_BEGIN("CHi", "microphone_perform");
|
|
|
|
|
2024-06-26 20:47:26 +03:00
|
|
|
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;
|
|
|
|
|
2024-06-30 14:43:13 +03:00
|
|
|
MTR_END("CHi", "microphone_perform");
|
|
|
|
|
2024-06-26 20:47:26 +03:00
|
|
|
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() {
|
2024-06-30 14:43:13 +03:00
|
|
|
struct CHiExportWavNode *n = calloc(1, sizeof(*n));
|
2024-06-26 20:47:26 +03:00
|
|
|
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;
|
|
|
|
}
|