216 lines
7.3 KiB
C
Executable File
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;
|
|
} |