WAV Viewer & Soundboard enhancements (8 or 16-bit WAV files) (#1849)

* WAV Viewer & Soundboard enhancements
* Reduced width of sample rate field
This commit is contained in:
Mark Thompson 2024-02-06 04:33:00 -06:00 committed by GitHub
parent bc035cff6a
commit 5ea1bff1e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 129 additions and 58 deletions

View File

@ -89,6 +89,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
uint32_t tone_key_index = options_tone_key.selected_index(); uint32_t tone_key_index = options_tone_key.selected_index();
uint32_t sample_rate; uint32_t sample_rate;
uint8_t bits_per_sample;
stop(); stop();
@ -104,6 +105,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
// button_play.set_bitmap(&bitmap_stop); // button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate(); sample_rate = reader->sample_rate();
bits_per_sample = reader->bits_per_sample();
replay_thread = std::make_unique<ReplayThread>( replay_thread = std::make_unique<ReplayThread>(
std::move(reader), std::move(reader),
@ -120,7 +122,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
transmitter_model.channel_bandwidth(), transmitter_model.channel_bandwidth(),
0, // Gain is unused 0, // Gain is unused
8, // shift_bits_s16, default 8 bits, but also unused 8, // shift_bits_s16, default 8 bits, but also unused
8, // bits per sample bits_per_sample,
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE), TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
false, // AM false, // AM
false, // DSB false, // DSB
@ -172,7 +174,7 @@ void SoundBoardView::refresh_list() {
if (entry_extension == ".WAV") { if (entry_extension == ".WAV") {
if (reader->open(u"/WAV/" + entry.path().native())) { if (reader->open(u"/WAV/" + entry.path().native())) {
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) { if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
// sounds[c].ms_duration = reader->ms_duration(); // sounds[c].ms_duration = reader->ms_duration();
// sounds[c].path = u"WAV/" + entry.path().native(); // sounds[c].path = u"WAV/" + entry.path().native();
if (count >= (page - 1) * 100 && count < page * 100) { if (count >= (page - 1) * 100 && count < page * 100) {

View File

@ -38,9 +38,17 @@ void ViewWavView::update_scale(int32_t new_scale) {
} }
void ViewWavView::refresh_waveform() { void ViewWavView::refresh_waveform() {
uint8_t bits_per_sample = wav_reader->bits_per_sample();
for (size_t i = 0; i < 240; i++) { for (size_t i = 0; i < 240; i++) {
wav_reader->data_seek(position + (i * scale)); wav_reader->data_seek(position + (i * scale));
wav_reader->read(&waveform_buffer[i], sizeof(int16_t)); if (bits_per_sample == 8) {
uint8_t sample;
wav_reader->read(&sample, 1);
waveform_buffer[i] = (sample - 0x80) * 256;
} else {
wav_reader->read(&waveform_buffer[i], 2);
}
} }
waveform.set_dirty(); waveform.set_dirty();
@ -73,13 +81,29 @@ void ViewWavView::paint(Painter& painter) {
painter.draw_vline({(Coord)i, 11 * 16}, 8, spectrum_rgb2_lut[amplitude_buffer[i] << 1]); painter.draw_vline({(Coord)i, 11 * 16}, 8, spectrum_rgb2_lut[amplitude_buffer[i] << 1]);
} }
void ViewWavView::on_pos_changed() { void ViewWavView::on_pos_time_changed() {
position = (field_pos_seconds.value() * wav_reader->sample_rate()) + field_pos_samples.value(); position = (uint64_t)((field_pos_seconds.value() * 1000) + field_pos_milliseconds.value()) * wav_reader->sample_rate() / 1000;
field_pos_milliseconds.set_range(0, ((uint32_t)field_pos_seconds.value() == wav_reader->ms_duration() / 1000) ? wav_reader->ms_duration() % 1000 : 999);
if (!updating_position) {
updating_position = true; // prevent recursion
field_pos_samples.set_value(position);
updating_position = false;
}
refresh_waveform();
}
void ViewWavView::on_pos_sample_changed() {
position = field_pos_samples.value();
if (!updating_position) {
updating_position = true; // prevent recursion
field_pos_seconds.set_value(field_pos_samples.value() / wav_reader->sample_rate());
field_pos_milliseconds.set_value((field_pos_samples.value() * 1000ull / wav_reader->sample_rate()) % 1000);
updating_position = false;
}
refresh_waveform(); refresh_waveform();
} }
void ViewWavView::load_wav(std::filesystem::path file_path) { void ViewWavView::load_wav(std::filesystem::path file_path) {
int16_t sample;
uint32_t average; uint32_t average;
wav_file_path = file_path; wav_file_path = file_path;
@ -91,18 +115,27 @@ void ViewWavView::load_wav(std::filesystem::path file_path) {
wav_reader->rewind(); wav_reader->rewind();
text_samplerate.set(to_string_dec_uint(wav_reader->sample_rate()) + "Hz"); text_samplerate.set(to_string_dec_uint(wav_reader->sample_rate()) + "Hz");
text_bits_per_sample.set(to_string_dec_uint(wav_reader->bits_per_sample(), 2));
text_title.set(wav_reader->title()); text_title.set(wav_reader->title());
// Fill amplitude buffer, world's worst downsampling // Fill amplitude buffer, world's worst downsampling
uint64_t skip = wav_reader->sample_count() / (240 * subsampling_factor); uint64_t skip = wav_reader->sample_count() / (240 * subsampling_factor);
uint8_t bits_per_sample = wav_reader->bits_per_sample();
for (size_t i = 0; i < 240; i++) { for (size_t i = 0; i < 240; i++) {
average = 0; average = 0;
for (size_t s = 0; s < subsampling_factor; s++) { for (size_t s = 0; s < subsampling_factor; s++) {
wav_reader->data_seek(((i * subsampling_factor) + s) * skip); wav_reader->data_seek(((i * subsampling_factor) + s) * skip);
wav_reader->read(&sample, 2); if (bits_per_sample == 8) {
average += (abs(sample) >> 8); uint8_t sample;
wav_reader->read(&sample, 1);
average += sample / 2;
} else {
int16_t sample;
wav_reader->read(&sample, 2);
average += (abs(sample) >> 8);
}
} }
amplitude_buffer[i] = average / subsampling_factor; amplitude_buffer[i] = average / subsampling_factor;
@ -154,6 +187,7 @@ void ViewWavView::file_error() {
void ViewWavView::start_playback() { void ViewWavView::start_playback() {
uint32_t sample_rate; uint32_t sample_rate;
uint8_t bits_per_sample;
auto reader = std::make_unique<WAVFileReader>(); auto reader = std::make_unique<WAVFileReader>();
@ -167,6 +201,7 @@ void ViewWavView::start_playback() {
button_play.set_bitmap(&bitmap_stop); button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate(); sample_rate = reader->sample_rate();
bits_per_sample = reader->bits_per_sample();
progressbar.set_max(reader->sample_count()); progressbar.set_max(reader->sample_count());
@ -180,18 +215,19 @@ void ViewWavView::start_playback() {
}); });
baseband::set_audiotx_config( baseband::set_audiotx_config(
1536000 / 20, // Rate of sending progress updates 1536000 / 20, // Rate of sending progress updates
0, // Transmit BW = 0 = not transmitting 0, // Transmit BW = 0 = not transmitting
0, // Gain - unused 0, // Gain - unused
8, // shift_bits_s16, default 8 bits - unused 8, // shift_bits_s16, default 8 bits - unused
16, // bits per sample bits_per_sample, // bits_per_sample
0, // tone key disabled 0, // tone key disabled
false, // AM false, // AM
false, // DSB false, // DSB
false, // USB false, // USB
false // LSB false // LSB
); );
baseband::set_sample_rate(sample_rate); baseband::set_sample_rate(sample_rate);
transmitter_model.set_sampling_rate(1536000);
audio::output::start(); audio::output::start();
} }
@ -211,11 +247,13 @@ ViewWavView::ViewWavView(
&text_samplerate, &text_samplerate,
&text_title, &text_title,
&text_duration, &text_duration,
&text_bits_per_sample,
&button_open, &button_open,
&button_play, &button_play,
&waveform, &waveform,
&progressbar, &progressbar,
&field_pos_seconds, &field_pos_seconds,
&field_pos_milliseconds,
&field_pos_samples, &field_pos_samples,
&field_scale, &field_scale,
&field_cursor_a, &field_cursor_a,
@ -234,12 +272,16 @@ ViewWavView::ViewWavView(
file_error(); file_error();
return; return;
} }
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) { if ((wav_reader->channels() != 1) || ((wav_reader->bits_per_sample() != 8) && (wav_reader->bits_per_sample() != 16))) {
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files."); nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n8 or 16-bit mono files.");
return; return;
} }
load_wav(file_path); load_wav(file_path);
field_pos_seconds.focus(); field_pos_seconds.focus();
field_pos_seconds.set_range(0, wav_reader->ms_duration() / 1000);
field_pos_milliseconds.set_range(0, (wav_reader->ms_duration() < 1000) ? wav_reader->ms_duration() % 1000 : 999);
field_pos_samples.set_range(0, wav_reader->sample_count() - 1);
field_scale.set_range(1, wav_reader->sample_count() / 240);
}); });
}; };
}; };
@ -257,10 +299,13 @@ ViewWavView::ViewWavView(
update_scale(value); update_scale(value);
}; };
field_pos_seconds.on_change = [this](int32_t) { field_pos_seconds.on_change = [this](int32_t) {
on_pos_changed(); on_pos_time_changed();
};
field_pos_milliseconds.on_change = [this](int32_t) {
on_pos_time_changed();
}; };
field_pos_samples.on_change = [this](int32_t) { field_pos_samples.on_change = [this](int32_t) {
on_pos_changed(); on_pos_sample_changed();
}; };
field_cursor_a.on_change = [this](int32_t v) { field_cursor_a.on_change = [this](int32_t v) {

View File

@ -49,7 +49,8 @@ class ViewWavView : public View {
void update_scale(int32_t new_scale); void update_scale(int32_t new_scale);
void refresh_waveform(); void refresh_waveform();
void refresh_measurements(); void refresh_measurements();
void on_pos_changed(); void on_pos_time_changed();
void on_pos_sample_changed();
void load_wav(std::filesystem::path file_path); void load_wav(std::filesystem::path file_path);
void reset_controls(); void reset_controls();
bool is_active(); bool is_active();
@ -74,30 +75,35 @@ class ViewWavView : public View {
int32_t scale{1}; int32_t scale{1};
uint64_t ns_per_pixel{}; uint64_t ns_per_pixel{};
uint64_t position{}; uint64_t position{};
bool updating_position{false};
Labels labels{ Labels labels{
{{0 * 8, 0 * 16}, "File:", Color::light_grey()}, {{0 * 8, 0 * 16}, "File:", Color::light_grey()},
{{0 * 8, 1 * 16}, "Samplerate:", Color::light_grey()}, {{2 * 8, 1 * 16}, "-bit mono", Color::light_grey()},
{{0 * 8, 2 * 16}, "Title:", Color::light_grey()}, {{0 * 8, 2 * 16}, "Title:", Color::light_grey()},
{{0 * 8, 3 * 16}, "Duration:", Color::light_grey()}, {{0 * 8, 3 * 16}, "Duration:", Color::light_grey()},
{{0 * 8, 12 * 16}, "Position: s Scale:", Color::light_grey()}, {{0 * 8, 12 * 16}, "Position: . s Scale:", Color::light_grey()},
{{0 * 8, 13 * 16}, "Cursor A:", Color::dark_cyan()}, {{0 * 8, 13 * 16}, " Sample:", Color::light_grey()},
{{0 * 8, 14 * 16}, "Cursor B:", Color::dark_magenta()}, {{0 * 8, 14 * 16}, "Cursor A:", Color::dark_cyan()},
{{0 * 8, 15 * 16}, "Delta:", Color::light_grey()}, {{0 * 8, 15 * 16}, "Cursor B:", Color::dark_magenta()},
{{0 * 8, 16 * 16}, "Delta:", Color::light_grey()},
{{24 * 8, 18 * 16}, "Vol:", Color::light_grey()}}; {{24 * 8, 18 * 16}, "Vol:", Color::light_grey()}};
Text text_filename{ Text text_filename{
{5 * 8, 0 * 16, 12 * 8, 16}, {5 * 8, 0 * 16, 18 * 8, 16},
""}; ""};
Text text_samplerate{ Text text_samplerate{
{11 * 8, 1 * 16, 8 * 8, 16}, {12 * 8, 1 * 16, 10 * 8, 16},
""}; ""};
Text text_title{ Text text_title{
{6 * 8, 2 * 16, 18 * 8, 16}, {6 * 8, 2 * 16, 17 * 8, 16},
""}; ""};
Text text_duration{ Text text_duration{
{9 * 8, 3 * 16, 18 * 8, 16}, {9 * 8, 3 * 16, 20 * 8, 16},
""}; ""};
Text text_bits_per_sample{
{0 * 8, 1 * 16, 2 * 8, 16},
"16"};
Button button_open{ Button button_open{
{24 * 8, 8, 6 * 8, 2 * 16}, {24 * 8, 8, 6 * 8, 2 * 16},
"Open"}; "Open"};
@ -122,32 +128,34 @@ class ViewWavView : public View {
NumberField field_pos_seconds{ NumberField field_pos_seconds{
{9 * 8, 12 * 16}, {9 * 8, 12 * 16},
4,
{0, 0},
1,
' ',
true};
NumberField field_pos_milliseconds{
{14 * 8, 12 * 16},
3, 3,
{0, 999}, {0, 999},
1, 1,
' '}; '0',
true};
NumberField field_pos_samples{ NumberField field_pos_samples{
{14 * 8, 12 * 16},
6,
{0, 999999},
1,
'0'};
NumberField field_scale{
{28 * 8, 12 * 16},
2,
{1, 40},
1,
' '};
NumberField field_cursor_a{
{9 * 8, 13 * 16}, {9 * 8, 13 * 16},
3, 9,
{0, 239}, {0, 0},
1,
'0',
true};
NumberField field_scale{
{26 * 8, 12 * 16},
4,
{1, 9999},
1, 1,
' ', ' ',
true}; true};
NumberField field_cursor_b{ NumberField field_cursor_a{
{9 * 8, 14 * 16}, {9 * 8, 14 * 16},
3, 3,
{0, 239}, {0, 239},
@ -155,8 +163,16 @@ class ViewWavView : public View {
' ', ' ',
true}; true};
NumberField field_cursor_b{
{9 * 8, 15 * 16},
3,
{0, 239},
1,
' ',
true};
Text text_delta{ Text text_delta{
{7 * 8, 15 * 16, 30 * 8, 16}, {7 * 8, 16 * 16, 30 * 8, 16},
"-"}; "-"};
MessageHandlerRegistration message_handler_replay_thread_error{ MessageHandlerRegistration message_handler_replay_thread_error{

View File

@ -27,7 +27,7 @@ bool WAVFileReader::open(const std::filesystem::path& path) {
size_t i = 0; size_t i = 0;
char ch; char ch;
const uint8_t tag_INAM[4] = {'I', 'N', 'A', 'M'}; const uint8_t tag_INAM[4] = {'I', 'N', 'A', 'M'};
char title_buffer[32]; char title_buffer[32]{0};
uint32_t riff_size, data_end, title_size; uint32_t riff_size, data_end, title_size;
size_t search_limit = 0; size_t search_limit = 0;
@ -37,11 +37,17 @@ bool WAVFileReader::open(const std::filesystem::path& path) {
return true; return true;
} }
// Reinitialize to avoid old data when switching files
title_string = "";
sample_rate_ = 0;
bytes_per_sample = 0;
auto error = file_.open(path); auto error = file_.open(path);
if (!error.is_valid()) { if (!error.is_valid()) {
file_.read((void*)&header, sizeof(header)); // Read header (RIFF and WAVE) file_.read((void*)&header, sizeof(header)); // Read header (RIFF and WAVE)
// TODO: Work needed here to process RIFF file format correctly, i.e. properly skip over LIST & INFO chunks
riff_size = header.cksize + 8; riff_size = header.cksize + 8;
data_start = header.fmt.cksize + 28; data_start = header.fmt.cksize + 28;
data_size_ = header.data.cksize; data_size_ = header.data.cksize;

View File

@ -30,7 +30,8 @@
void AudioTXProcessor::execute(const buffer_c8_t& buffer) { void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
if (!configured) return; if (!configured) return;
int32_t audio_sample_m; buffer_s16_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, sampling_rate};
int16_t audio_sample_s16;
// Zero-order hold (poop) // Zero-order hold (poop)
for (size_t i = 0; i < buffer.count; i++) { for (size_t i = 0; i < buffer.count; i++) {
@ -46,15 +47,16 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
if (bytes_per_sample == 1) { if (bytes_per_sample == 1) {
sample = audio_sample - 0x80; sample = audio_sample - 0x80;
audio_sample_m = sample * 256; audio_sample_s16 = sample * 256;
} else { } else {
audio_sample_m = audio_sample; audio_sample_s16 = (int16_t)audio_sample;
sample = audio_sample_s16 / 256;
} }
// Output to speaker too // Output to speaker too
if (!tone_key_enabled) { if (!tone_key_enabled) {
uint32_t imod32 = i & (AUDIO_OUTPUT_BUFFER_SIZE - 1); uint32_t imod32 = i & (AUDIO_OUTPUT_BUFFER_SIZE - 1);
audio_data[imod32] = audio_sample_m; audio_data[imod32] = audio_sample_s16;
if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1)) if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1))
audio_output.write_unprocessed(audio_buffer); audio_output.write_unprocessed(audio_buffer);
} }
@ -133,6 +135,7 @@ void AudioTXProcessor::replay_config(const ReplayConfigMessage& message) {
void AudioTXProcessor::sample_rate_config(const SampleRateConfigMessage& message) { void AudioTXProcessor::sample_rate_config(const SampleRateConfigMessage& message) {
resample_inc = (((uint64_t)message.sample_rate) << 16) / baseband_fs; // 16.16 fixed point message.sample_rate resample_inc = (((uint64_t)message.sample_rate) << 16) / baseband_fs; // 16.16 fixed point message.sample_rate
sampling_rate = message.sample_rate;
} }
int main() { int main() {

View File

@ -51,11 +51,10 @@ class AudioTXProcessor : public BasebandProcessor {
uint32_t audio_sample{}; uint32_t audio_sample{};
int32_t sample{0}, delta{}; int32_t sample{0}, delta{};
int8_t re{0}, im{0}; int8_t re{0}, im{0};
int8_t bytes_per_sample{1}; uint8_t bytes_per_sample{1};
int16_t audio_sample_s16{}; uint32_t sampling_rate{48000};
int16_t audio_data[AUDIO_OUTPUT_BUFFER_SIZE]; int16_t audio_data[AUDIO_OUTPUT_BUFFER_SIZE];
buffer_s16_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, 48000};
AudioOutput audio_output{}; AudioOutput audio_output{};
size_t progress_interval_samples = 0, progress_samples = 0; size_t progress_interval_samples = 0, progress_samples = 0;