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 sample_rate;
uint8_t bits_per_sample;
stop();
@ -104,6 +105,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
// button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
bits_per_sample = reader->bits_per_sample();
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
@ -120,7 +122,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
transmitter_model.channel_bandwidth(),
0, // Gain is 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),
false, // AM
false, // DSB
@ -172,7 +174,7 @@ void SoundBoardView::refresh_list() {
if (entry_extension == ".WAV") {
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].path = u"WAV/" + entry.path().native();
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() {
uint8_t bits_per_sample = wav_reader->bits_per_sample();
for (size_t i = 0; i < 240; i++) {
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();
@ -73,13 +81,29 @@ void ViewWavView::paint(Painter& painter) {
painter.draw_vline({(Coord)i, 11 * 16}, 8, spectrum_rgb2_lut[amplitude_buffer[i] << 1]);
}
void ViewWavView::on_pos_changed() {
position = (field_pos_seconds.value() * wav_reader->sample_rate()) + field_pos_samples.value();
void ViewWavView::on_pos_time_changed() {
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();
}
void ViewWavView::load_wav(std::filesystem::path file_path) {
int16_t sample;
uint32_t average;
wav_file_path = file_path;
@ -91,19 +115,28 @@ void ViewWavView::load_wav(std::filesystem::path file_path) {
wav_reader->rewind();
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());
// Fill amplitude buffer, world's worst downsampling
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++) {
average = 0;
for (size_t s = 0; s < subsampling_factor; s++) {
wav_reader->data_seek(((i * subsampling_factor) + s) * skip);
if (bits_per_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;
}
@ -154,6 +187,7 @@ void ViewWavView::file_error() {
void ViewWavView::start_playback() {
uint32_t sample_rate;
uint8_t bits_per_sample;
auto reader = std::make_unique<WAVFileReader>();
@ -167,6 +201,7 @@ void ViewWavView::start_playback() {
button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
bits_per_sample = reader->bits_per_sample();
progressbar.set_max(reader->sample_count());
@ -184,7 +219,7 @@ void ViewWavView::start_playback() {
0, // Transmit BW = 0 = not transmitting
0, // Gain - unused
8, // shift_bits_s16, default 8 bits - unused
16, // bits per sample
bits_per_sample, // bits_per_sample
0, // tone key disabled
false, // AM
false, // DSB
@ -192,6 +227,7 @@ void ViewWavView::start_playback() {
false // LSB
);
baseband::set_sample_rate(sample_rate);
transmitter_model.set_sampling_rate(1536000);
audio::output::start();
}
@ -211,11 +247,13 @@ ViewWavView::ViewWavView(
&text_samplerate,
&text_title,
&text_duration,
&text_bits_per_sample,
&button_open,
&button_play,
&waveform,
&progressbar,
&field_pos_seconds,
&field_pos_milliseconds,
&field_pos_samples,
&field_scale,
&field_cursor_a,
@ -234,12 +272,16 @@ ViewWavView::ViewWavView(
file_error();
return;
}
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.");
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\n8 or 16-bit mono files.");
return;
}
load_wav(file_path);
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);
};
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) {
on_pos_changed();
on_pos_sample_changed();
};
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 refresh_waveform();
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 reset_controls();
bool is_active();
@ -74,30 +75,35 @@ class ViewWavView : public View {
int32_t scale{1};
uint64_t ns_per_pixel{};
uint64_t position{};
bool updating_position{false};
Labels labels{
{{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, 3 * 16}, "Duration:", Color::light_grey()},
{{0 * 8, 12 * 16}, "Position: s Scale:", Color::light_grey()},
{{0 * 8, 13 * 16}, "Cursor A:", Color::dark_cyan()},
{{0 * 8, 14 * 16}, "Cursor B:", Color::dark_magenta()},
{{0 * 8, 15 * 16}, "Delta:", Color::light_grey()},
{{0 * 8, 12 * 16}, "Position: . s Scale:", Color::light_grey()},
{{0 * 8, 13 * 16}, " Sample:", Color::light_grey()},
{{0 * 8, 14 * 16}, "Cursor A:", Color::dark_cyan()},
{{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()}};
Text text_filename{
{5 * 8, 0 * 16, 12 * 8, 16},
{5 * 8, 0 * 16, 18 * 8, 16},
""};
Text text_samplerate{
{11 * 8, 1 * 16, 8 * 8, 16},
{12 * 8, 1 * 16, 10 * 8, 16},
""};
Text text_title{
{6 * 8, 2 * 16, 18 * 8, 16},
{6 * 8, 2 * 16, 17 * 8, 16},
""};
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{
{24 * 8, 8, 6 * 8, 2 * 16},
"Open"};
@ -122,32 +128,34 @@ class ViewWavView : public View {
NumberField field_pos_seconds{
{9 * 8, 12 * 16},
4,
{0, 0},
1,
' ',
true};
NumberField field_pos_milliseconds{
{14 * 8, 12 * 16},
3,
{0, 999},
1,
' '};
'0',
true};
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},
3,
{0, 239},
9,
{0, 0},
1,
'0',
true};
NumberField field_scale{
{26 * 8, 12 * 16},
4,
{1, 9999},
1,
' ',
true};
NumberField field_cursor_b{
NumberField field_cursor_a{
{9 * 8, 14 * 16},
3,
{0, 239},
@ -155,8 +163,16 @@ class ViewWavView : public View {
' ',
true};
NumberField field_cursor_b{
{9 * 8, 15 * 16},
3,
{0, 239},
1,
' ',
true};
Text text_delta{
{7 * 8, 15 * 16, 30 * 8, 16},
{7 * 8, 16 * 16, 30 * 8, 16},
"-"};
MessageHandlerRegistration message_handler_replay_thread_error{

View File

@ -27,7 +27,7 @@ bool WAVFileReader::open(const std::filesystem::path& path) {
size_t i = 0;
char ch;
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;
size_t search_limit = 0;
@ -37,11 +37,17 @@ bool WAVFileReader::open(const std::filesystem::path& path) {
return true;
}
// Reinitialize to avoid old data when switching files
title_string = "";
sample_rate_ = 0;
bytes_per_sample = 0;
auto error = file_.open(path);
if (!error.is_valid()) {
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;
data_start = header.fmt.cksize + 28;
data_size_ = header.data.cksize;

View File

@ -30,7 +30,8 @@
void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
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)
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) {
sample = audio_sample - 0x80;
audio_sample_m = sample * 256;
audio_sample_s16 = sample * 256;
} else {
audio_sample_m = audio_sample;
audio_sample_s16 = (int16_t)audio_sample;
sample = audio_sample_s16 / 256;
}
// Output to speaker too
if (!tone_key_enabled) {
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))
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) {
resample_inc = (((uint64_t)message.sample_rate) << 16) / baseband_fs; // 16.16 fixed point message.sample_rate
sampling_rate = message.sample_rate;
}
int main() {

View File

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