OreBolt-OS/modules/orebolt-scalpel/scalpel.c

216 lines
7.3 KiB
C
Executable File

/*
* scalpel.c -- Acoustic Scalpel Live Sound Synthesizer
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* POSIX-threaded audio synthesizer writing to /dev/dsp at 44.1kHz.
* Generates precise audio diagnostic tracking signals via
* continuous multi-waveform raw data matrices written to
* /dev/dsp through an independent POSIX threading loop.
* Supports SINE and SQUARE waveforms with real-time
* frequency tuning via the rotary encoder (20 Hz - 20 kHz).
*
* Manifest ref: Asset V -- Acoustic Scalpel Live Sound Synthesizer
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <linux/input.h>
#include "h2_ui.h"
#include "log_manager.h"
#define SAMPLE_RATE 44100
#define CHANNELS 1
#define AUDIO_FORMAT AFMT_S16_LE
#define BUFFER_SIZE 1024
#define MIN_FREQ 20
#define MAX_FREQ 20000
#define FREQ_STEP 10
static volatile int target_frequency = 440;
static volatile int wave_type = 0; /* 0 = SINE, 1 = SQUARE */
static volatile int keep_playing = 1;
/* UI labels */
static lv_obj_t *freq_label = NULL;
static lv_obj_t *wave_label = NULL;
static lv_obj_t *status_label = NULL;
static lv_obj_t *status_icon = NULL;
static void *audio_synthesis_thread(void *arg) {
(void)arg;
int audio_fd = open("/dev/dsp", O_WRONLY);
if (audio_fd == -1) {
LOG_ERR("cannot open /dev/dsp for audio output");
return NULL;
}
int format = AUDIO_FORMAT;
ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format);
int channels = CHANNELS;
ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &channels);
int speed = SAMPLE_RATE;
ioctl(audio_fd, SNDCTL_DSP_SPEED, &speed);
LOG_INF("audio synth thread started: %d Hz, %d ch, format=%d",
speed, channels, format);
int16_t buffer[BUFFER_SIZE];
uint32_t sample_index = 0;
while (keep_playing) {
int current_freq = target_frequency;
int current_type = wave_type;
for (int i = 0; i < BUFFER_SIZE; i++) {
double time_t = (double)sample_index / SAMPLE_RATE;
double angle = 2.0 * M_PI * current_freq * time_t;
if (current_type == 0) {
/* SINE wave */
buffer[i] = (int16_t)(20000.0 * sin(angle));
} else {
/* SQUARE wave */
buffer[i] = (sin(angle) >= 0) ? 15000 : -15000;
}
sample_index++;
}
write(audio_fd, buffer, sizeof(buffer));
}
close(audio_fd);
LOG_INF("audio synth thread stopped");
return NULL;
}
static void update_display(void) {
char buf[48];
snprintf(buf, sizeof(buf), "FREQUENCY: %d Hz", target_frequency);
lv_label_set_text(freq_label, buf);
const char *wave_name = (wave_type == 0) ? "SINE WAVE" : "SQUARE WAVE";
lv_label_set_text(wave_label, wave_name);
}
int main(void) {
init_h2_graphics_runtime("ACOUSTIC SCALPEL");
LOG_INF("scalpel module started");
lv_obj_t *scr = lv_scr_act();
/* Header bar */
lv_obj_t *header = lv_obj_create(scr);
lv_obj_set_size(header, 320, 32);
lv_obj_align(header, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_bg_color(header,
lv_color_make(211, 160, 0), LV_PART_MAIN);
lv_obj_set_style_border_width(header, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(header, 4, LV_PART_MAIN);
lv_obj_t *title = lv_label_create(header);
lv_label_set_text(title, "ACOUSTIC SCALPEL SIGNAL GEN");
lv_obj_set_style_text_color(title,
lv_color_make(0, 0, 0), LV_PART_MAIN);
lv_obj_set_style_text_font(title, &lv_font_montserrat_12, LV_PART_MAIN);
lv_obj_align(title, LV_ALIGN_CENTER, 0, 0);
/* Frequency display */
freq_label = lv_label_create(scr);
lv_obj_align(freq_label, LV_ALIGN_TOP_MID, 0, 50);
lv_obj_set_style_text_color(freq_label, COLOR_ACCENT, LV_PART_MAIN);
lv_obj_set_style_text_font(freq_label, &lv_font_montserrat_14, LV_PART_MAIN);
/* Waveform display */
wave_label = lv_label_create(scr);
lv_obj_align(wave_label, LV_ALIGN_TOP_MID, 0, 80);
lv_obj_set_style_text_color(wave_label, COLOR_PRIMARY, LV_PART_MAIN);
lv_obj_set_style_text_font(wave_label, &lv_font_montserrat_14, LV_PART_MAIN);
/* Status card */
lv_obj_t *card = lv_obj_create(scr);
lv_obj_set_size(card, 280, 80);
lv_obj_align(card, LV_ALIGN_CENTER, 0, 20);
lv_obj_set_style_bg_color(card,
lv_color_make(18, 22, 32), LV_PART_MAIN);
status_icon = lv_label_create(card);
lv_obj_align(status_icon, LV_ALIGN_TOP_LEFT, 10, 8);
lv_obj_set_style_text_color(status_icon, COLOR_ACCENT, LV_PART_MAIN);
lv_label_set_text(status_icon, LV_SYMBOL_AUDIO);
status_label = lv_label_create(card);
lv_label_set_text(status_label, "Audio engine: ACTIVE");
lv_obj_align(status_label, LV_ALIGN_TOP_LEFT, 30, 8);
lv_obj_set_style_text_color(status_label, COLOR_MUTED, LV_PART_MAIN);
lv_obj_set_style_text_font(status_label, &lv_font_montserrat_10, LV_PART_MAIN);
/* Help text */
lv_obj_t *help = lv_label_create(scr);
lv_label_set_text(help,
"[WHEEL] Tune Freq [PLAY] Toggle Wave [BACK] Exit");
lv_obj_align(help, LV_ALIGN_BOTTOM_MID, 0, -8);
lv_obj_set_style_text_color(help, COLOR_MUTED, LV_PART_MAIN);
lv_obj_set_style_text_font(help, &lv_font_montserrat_10, LV_PART_MAIN);
update_display();
/* Start audio synthesis thread */
pthread_t sound_worker;
if (pthread_create(&sound_worker, NULL, audio_synthesis_thread, NULL) != 0) {
LOG_ERR("failed to create audio thread");
lv_label_set_text(status_label, "Audio engine: FAILED");
lv_obj_set_style_text_color(status_label,
lv_color_make(255, 0, 0), LV_PART_MAIN);
/* Still allow UI to run for user to see the error */
}
/* Input loop */
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
struct input_event ev;
while (1) {
lv_timer_handler();
if (input_fd >= 0 &&
read(input_fd, &ev, sizeof(struct input_event)) > 0) {
if (ev.type == EV_REL && ev.code == H2_ROTARY_REL) {
if (ev.value > 0 && target_frequency < MAX_FREQ) {
target_frequency += FREQ_STEP;
if (target_frequency > MAX_FREQ)
target_frequency = MAX_FREQ;
update_display();
} else if (ev.value < 0 && target_frequency > MIN_FREQ) {
target_frequency -= FREQ_STEP;
if (target_frequency < MIN_FREQ)
target_frequency = MIN_FREQ;
update_display();
}
} else if (ev.type == EV_KEY && ev.value == 1) {
if (ev.code == H2_KEY_PLAY) {
wave_type = (wave_type + 1) % 2;
update_display();
LOG_INF("waveform toggled to: %s",
wave_type == 0 ? "SINE" : "SQUARE");
} else if (ev.code == H2_KEY_BACK) {
break;
}
}
}
usleep(5000);
}
/* Cleanup */
keep_playing = 0;
pthread_join(sound_worker, NULL);
if (input_fd >= 0) close(input_fd);
LOG_INF("scalpel module exited");
return 0;
}