/* * 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 #include #include #include #include #include #include #include #include #include #include #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; }