Files
Arjun Patel 8870741c3e ai conversation (#49)
* capture audio with alsa

* add brainstorm thoughts

* use basecamp project for managing this project

* add libcurl basic example

* write pcm data to file

* transcribe audio file with deepgram

* transcribe raw audio

* hit anthropic API for ai question

* create full flow with stt, anthropic, tts, and playback

This organizes some components like deepgram into own module, and also
allows gets the full flow to work every time we run the program.

* organize audio and intelligence modules

* organize header files into include directory

* docs: add readme

* docs: explain future work

* docs: add disclaimer about hardcoded audio params
2025-07-27 15:07:30 -07:00

169 lines
5.1 KiB
C

#include "deepgram_client.h"
#include <string.h>
// Returns string that caller must free
static char *dg_find_transcript(char *json_response) {
printf("Parsing response: %s \n", json_response);
char *start = strstr(json_response, "\"transcript\":\"");
if (!start)
return NULL;
start += 14; // Skip past "transcript":"
char *end = strchr(start, '"'); // Find closing quote
// Copy substring between start and end
size_t len = end - start;
char *result = malloc(len + 1);
strncpy(result, start, len);
result[len] = '\0';
return result;
}
char *dg_transcribe(FILE *audio_file) {
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
// Initialize response buffer
struct curl_response_data response = {0};
response.data = malloc(1);
response.size = 0;
// Get file size
fseek(audio_file, 0, SEEK_END);
long file_size = ftell(audio_file);
fseek(audio_file, 0, SEEK_SET);
struct curl_file_data data;
data.file = audio_file;
data.size = file_size;
curl = curl_easy_init();
if (curl) {
// Set headers for Deepgram API
char *deepgram_api_key = getenv("DEEPGRAM_API_KEY");
if (!deepgram_api_key) {
fprintf(stderr, "DEEPGRAM_API_KEY environment variable not set\n");
free(response.data);
curl_easy_cleanup(curl);
return NULL;
}
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "Authorization: Token %s", deepgram_api_key);
headers = curl_slist_append(headers, auth_header);
curl_easy_setopt(curl, CURLOPT_URL,
"https://api.deepgram.com/v1/"
"listen?encoding=linear16&sample_rate=48000&channels=1");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, curl_read_callback_file);
curl_easy_setopt(curl, CURLOPT_READDATA, &data);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, file_size);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_callback_response);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl failed: %s\n", curl_easy_strerror(res));
free(response.data);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return NULL;
}
printf("done performing curl\n");
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
// Parse transcript from complete response
char *transcript = dg_find_transcript(response.data);
free(response.data);
return transcript;
}
struct curl_response_data *dg_text_to_speech(char *text) {
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
// Create JSON payload - need to escape the text properly
// Worst case: every char needs escaping, so allocate 2x + overhead
size_t text_len = strlen(text);
size_t payload_size = text_len * 2 + 100;
char *json_payload = malloc(payload_size);
// Simple approach: replace problematic chars with spaces
char *escaped_text = malloc(text_len + 1);
strcpy(escaped_text, text);
for (int i = 0; escaped_text[i]; i++) {
if (escaped_text[i] == '"' || escaped_text[i] == '\n' ||
escaped_text[i] == '\r' || escaped_text[i] == '\t') {
escaped_text[i] = ' ';
}
}
snprintf(json_payload, payload_size,
"{"
"\"text\":\"%s\""
"}",
escaped_text);
free(escaped_text);
// Initialize response buffer
struct curl_response_data *response =
malloc(sizeof(struct curl_response_data));
response->data = malloc(1);
response->size = 0;
curl = curl_easy_init();
if (curl) {
char *deepgram_api_key = getenv("DEEPGRAM_API_KEY");
if (!deepgram_api_key) {
fprintf(stderr, "DEEPGRAM_API_KEY environment variable not set\n");
free(response->data);
curl_easy_cleanup(curl);
return NULL;
}
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "Authorization: Token %s", deepgram_api_key);
headers = curl_slist_append(headers, auth_header);
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL,
"https://api.deepgram.com/v1/"
"speak?encoding=linear16&sample_rate=48000");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_callback_response);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl failed: %s\n", curl_easy_strerror(res));
free(response->data);
free(response);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return NULL;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
printf("received audio of %d length for tts\n", (int)response->size);
// Debug: print first 100 chars to see if it's audio or error message
printf("First 100 chars: %.100s\n", response->data);
free(json_payload);
return response;
}