Add audio playback ability to WAV Viewer app (#1829)

This commit is contained in:
Mark Thompson 2024-01-30 13:55:32 -06:00 committed by GitHub
parent 8068517808
commit 125184f300
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 211 additions and 35 deletions

View File

@ -113,6 +113,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
namespace app_settings { namespace app_settings {
enum class Mode : uint8_t { enum class Mode : uint8_t {
NO_RF = 0x00,
RX = 0x01, RX = 0x01,
TX = 0x02, TX = 0x02,
RX_TX = 0x03, // Both TX/RX RX_TX = 0x03, // Both TX/RX

View File

@ -116,15 +116,16 @@ void SoundBoardView::start_tx(const uint32_t id) {
// TODO: Delete all this and use tx model. // TODO: Delete all this and use tx model.
baseband::set_audiotx_config( baseband::set_audiotx_config(
TONES_SAMPLERATE / 20, // Update vu-meter at 20Hz 1536000 / 20, // Update vu-meter at 20Hz
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
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE), TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
0, // AM false, // AM
0, // DSB false, // DSB
0, // USB false, // USB
0 // LSB false // LSB
); );
baseband::set_sample_rate(sample_rate); baseband::set_sample_rate(sample_rate);

View File

@ -105,6 +105,7 @@ void MicTXView::configure_baseband() {
transmitting ? transmitter_model.channel_bandwidth() : 0, transmitting ? transmitter_model.channel_bandwidth() : 0,
mic_gain_x10 / 10.0, mic_gain_x10 / 10.0,
shift_bits(), // to be used in dsp_modulate shift_bits(), // to be used in dsp_modulate
8, // bits per sample
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate), TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
(mic_mod_index == MIC_MOD_AM), (mic_mod_index == MIC_MOD_AM),
(mic_mod_index == MIC_MOD_DSB), (mic_mod_index == MIC_MOD_DSB),

View File

@ -22,11 +22,12 @@
#include "ui_view_wav.hpp" #include "ui_view_wav.hpp"
#include "ui_fileman.hpp" #include "ui_fileman.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
using namespace portapack; using namespace portapack;
#include "string_format.hpp"
namespace ui { namespace ui {
void ViewWavView::update_scale(int32_t new_scale) { void ViewWavView::update_scale(int32_t new_scale) {
@ -81,6 +82,8 @@ void ViewWavView::load_wav(std::filesystem::path file_path) {
int16_t sample; int16_t sample;
uint32_t average; uint32_t average;
wav_file_path = file_path;
text_filename.set(file_path.filename().string()); text_filename.set(file_path.filename().string());
auto ms_duration = wav_reader->ms_duration(); auto ms_duration = wav_reader->ms_duration();
text_duration.set(unit_auto_scale(ms_duration, 2, 3) + "s"); text_duration.set(unit_auto_scale(ms_duration, 2, 3) + "s");
@ -117,9 +120,90 @@ void ViewWavView::reset_controls() {
field_cursor_b.set_value(0); field_cursor_b.set_value(0);
} }
bool ViewWavView::is_active() {
return (bool)replay_thread;
}
void ViewWavView::stop() {
if (is_active())
replay_thread.reset();
audio::output::stop();
button_play.set_bitmap(&bitmap_play);
ready_signal = false;
}
void ViewWavView::handle_replay_thread_done(const uint32_t return_code) {
(void)return_code;
stop();
progressbar.set_value(0);
if (return_code == ReplayThread::READ_ERROR)
file_error();
}
void ViewWavView::set_ready() {
ready_signal = true;
}
void ViewWavView::file_error() {
nav_.display_modal("Error", "File read error.");
}
void ViewWavView::start_playback() {
uint32_t sample_rate;
auto reader = std::make_unique<WAVFileReader>();
stop();
if (!reader->open(wav_file_path)) {
file_error();
return;
}
button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
progressbar.set_max(reader->sample_count());
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
read_size, buffer_count,
&ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
baseband::set_audiotx_config(
1536000 / 20, // Rate of sending progress updates
0, // Transmit BW = 0 = not transmitting
0, // Gain - unused
8, // shift_bits_s16, default 8 bits - unused
16, // bits per sample
0, // tone key disabled
false, // AM
false, // DSB
false, // USB
false // LSB
);
baseband::set_sample_rate(sample_rate);
audio::output::start();
}
void ViewWavView::on_playback_progress(const uint32_t progress) {
progressbar.set_value(progress);
}
ViewWavView::ViewWavView( ViewWavView::ViewWavView(
NavigationView& nav) NavigationView& nav)
: nav_(nav) { : nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
wav_reader = std::make_unique<WAVFileReader>(); wav_reader = std::make_unique<WAVFileReader>();
add_children({&labels, add_children({&labels,
@ -128,21 +212,26 @@ ViewWavView::ViewWavView(
&text_title, &text_title,
&text_duration, &text_duration,
&button_open, &button_open,
&button_play,
&waveform, &waveform,
&progressbar,
&field_pos_seconds, &field_pos_seconds,
&field_pos_samples, &field_pos_samples,
&field_scale, &field_scale,
&field_cursor_a, &field_cursor_a,
&field_cursor_b, &field_cursor_b,
&text_delta}); &text_delta,
&field_volume});
reset_controls(); reset_controls();
button_open.on_select = [this, &nav](Button&) { button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".WAV"); auto open_view = nav.push<FileLoadView>(".WAV");
open_view->on_changed = [this, &nav](std::filesystem::path file_path) { open_view->on_changed = [this, &nav](std::filesystem::path file_path) {
// Can't show new dialogs in an on_changed handler, so use continuation. // Can't show new dialogs in an on_changed handler, so use continuation.
nav.set_on_pop([this, &nav, file_path]() { nav.set_on_pop([this, &nav, file_path]() {
if (!wav_reader->open(file_path)) { if (!wav_reader->open(file_path)) {
nav_.display_modal("Error", "Couldn't open file."); 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() != 16)) {
@ -155,6 +244,15 @@ ViewWavView::ViewWavView(
}; };
}; };
field_volume.set_value(field_volume.value());
button_play.on_select = [this, &nav](ImageButton&) {
if (this->is_active())
stop();
else
start_playback();
};
field_scale.on_change = [this](int32_t value) { field_scale.on_change = [this](int32_t value) {
update_scale(value); update_scale(value);
}; };
@ -179,4 +277,9 @@ void ViewWavView::focus() {
button_open.focus(); button_open.focus();
} }
ViewWavView::~ViewWavView() {
stop();
baseband::shutdown();
}
} /* namespace ui */ } /* namespace ui */

View File

@ -24,12 +24,15 @@
#include "ui_navigation.hpp" #include "ui_navigation.hpp"
#include "io_wave.hpp" #include "io_wave.hpp"
#include "spectrum_color_lut.hpp" #include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "replay_thread.hpp"
namespace ui { namespace ui {
class ViewWavView : public View { class ViewWavView : public View {
public: public:
ViewWavView(NavigationView& nav); ViewWavView(NavigationView& nav);
~ViewWavView();
void focus() override; void focus() override;
void paint(Painter&) override; void paint(Painter&) override;
@ -37,6 +40,9 @@ class ViewWavView : public View {
std::string title() const override { return "WAV viewer"; }; std::string title() const override { return "WAV viewer"; };
private: private:
app_settings::SettingsManager settings_{
"wav_viewer", app_settings::Mode::NO_RF};
NavigationView& nav_; NavigationView& nav_;
static constexpr uint32_t subsampling_factor = 8; static constexpr uint32_t subsampling_factor = 8;
@ -46,6 +52,20 @@ class ViewWavView : public View {
void on_pos_changed(); void on_pos_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();
void stop();
void handle_replay_thread_done(const uint32_t return_code);
void set_ready();
void file_error();
void start_playback();
void on_playback_progress(const uint32_t progress);
std::filesystem::path wav_file_path{};
std::unique_ptr<ReplayThread> replay_thread{};
bool ready_signal{false};
const size_t read_size{2048};
const size_t buffer_count{3};
const uint32_t progress_interval_samples{1536000 / 20};
std::unique_ptr<WAVFileReader> wav_reader{}; std::unique_ptr<WAVFileReader> wav_reader{};
@ -60,10 +80,11 @@ class ViewWavView : public View {
{{0 * 8, 1 * 16}, "Samplerate:", Color::light_grey()}, {{0 * 8, 1 * 16}, "Samplerate:", 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, 11 * 16}, "Position: s Scale:", Color::light_grey()}, {{0 * 8, 12 * 16}, "Position: s Scale:", Color::light_grey()},
{{0 * 8, 12 * 16}, "Cursor A:", Color::dark_cyan()}, {{0 * 8, 13 * 16}, "Cursor A:", Color::dark_cyan()},
{{0 * 8, 13 * 16}, "Cursor B:", Color::dark_magenta()}, {{0 * 8, 14 * 16}, "Cursor B:", Color::dark_magenta()},
{{0 * 8, 14 * 16}, "Delta:", Color::light_grey()}}; {{0 * 8, 15 * 16}, "Delta:", 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, 12 * 8, 16},
@ -80,6 +101,13 @@ class ViewWavView : public View {
Button button_open{ Button button_open{
{24 * 8, 8, 6 * 8, 2 * 16}, {24 * 8, 8, 6 * 8, 2 * 16},
"Open"}; "Open"};
ImageButton button_play{
{24 * 8, 17 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Color::green(),
Color::black()};
AudioVolumeField field_volume{
{28 * 8, 18 * 16}};
Waveform waveform{ Waveform waveform{
{0, 5 * 16, 240, 64}, {0, 5 * 16, 240, 64},
@ -89,34 +117,29 @@ class ViewWavView : public View {
false, false,
Color::white()}; Color::white()};
ProgressBar progressbar{
{0 * 8, 11 * 16, 30 * 8, 8}};
NumberField field_pos_seconds{ NumberField field_pos_seconds{
{9 * 8, 11 * 16}, {9 * 8, 12 * 16},
3, 3,
{0, 999}, {0, 999},
1, 1,
' '}; ' '};
NumberField field_pos_samples{ NumberField field_pos_samples{
{14 * 8, 11 * 16}, {14 * 8, 12 * 16},
6, 6,
{0, 999999}, {0, 999999},
1, 1,
'0'}; '0'};
NumberField field_scale{ NumberField field_scale{
{28 * 8, 11 * 16}, {28 * 8, 12 * 16},
2, 2,
{1, 40}, {1, 40},
1, 1,
' '}; ' '};
NumberField field_cursor_a{ NumberField field_cursor_a{
{9 * 8, 12 * 16},
3,
{0, 239},
1,
' ',
true};
NumberField field_cursor_b{
{9 * 8, 13 * 16}, {9 * 8, 13 * 16},
3, 3,
{0, 239}, {0, 239},
@ -124,9 +147,40 @@ class ViewWavView : public View {
' ', ' ',
true}; true};
NumberField field_cursor_b{
{9 * 8, 14 * 16},
3,
{0, 239},
1,
' ',
true};
Text text_delta{ Text text_delta{
{6 * 8, 14 * 16, 30 * 8, 16}, {7 * 8, 15 * 16, 30 * 8, 16},
"-"}; "-"};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
this->handle_replay_thread_done(message.return_code);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* const p) {
const auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
this->set_ready();
}
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_playback_progress(message.progress);
}};
}; };
} /* namespace ui */ } /* namespace ui */

View File

@ -211,6 +211,7 @@ void set_audiotx_config(
const float deviation_hz, const float deviation_hz,
const float audio_gain, const float audio_gain,
uint8_t audio_shift_bits_s16, uint8_t audio_shift_bits_s16,
uint8_t bits_per_sample,
const uint32_t tone_key_delta, const uint32_t tone_key_delta,
const bool am_enabled, const bool am_enabled,
const bool dsb_enabled, const bool dsb_enabled,
@ -221,6 +222,7 @@ void set_audiotx_config(
deviation_hz, deviation_hz,
audio_gain, audio_gain,
audio_shift_bits_s16, audio_shift_bits_s16,
bits_per_sample,
tone_key_delta, tone_key_delta,
(float)persistent_memory::tone_mix() / 100.0f, (float)persistent_memory::tone_mix() / 100.0f,
am_enabled, am_enabled,

View File

@ -63,7 +63,7 @@ void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duratio
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out); void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
void kill_tone(); void kill_tone();
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration); void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled); void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, uint8_t bits_per_sample, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
void set_fifo_data(const int8_t* data); void set_fifo_data(const int8_t* data);
void set_pitch_rssi(int32_t avg, bool enabled); void set_pitch_rssi(int32_t avg, bool enabled);
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count); void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);

View File

@ -30,23 +30,31 @@
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;
// 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++) {
resample_acc += resample_inc; resample_acc += resample_inc;
if (resample_acc >= 0x10000) { if (resample_acc >= 0x10000) {
resample_acc -= 0x10000; resample_acc -= 0x10000;
if (stream) { if (stream) {
stream->read(&audio_sample, 1); audio_sample = 0;
bytes_read++; stream->read(&audio_sample, bytes_per_sample); // assumes little endian when reading 1 byte
samples_read++;
} }
} }
sample = audio_sample - 0x80; if (bytes_per_sample == 1) {
sample = audio_sample - 0x80;
audio_sample_m = sample * 256;
} else {
audio_sample_m = audio_sample;
}
// 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] = sample * 256; audio_data[imod32] = audio_sample_m;
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);
} }
@ -69,7 +77,7 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
if (progress_samples >= progress_interval_samples) { if (progress_samples >= progress_interval_samples) {
progress_samples -= progress_interval_samples; progress_samples -= progress_interval_samples;
txprogress_message.progress = bytes_read; // Inform UI about progress txprogress_message.progress = samples_read; // Inform UI about progress
txprogress_message.done = false; txprogress_message.done = false;
shared_memory.application_queue.push(txprogress_message); shared_memory.application_queue.push(txprogress_message);
} }
@ -83,7 +91,7 @@ void AudioTXProcessor::on_message(const Message* const message) {
case Message::ID::ReplayConfig: case Message::ID::ReplayConfig:
configured = false; configured = false;
bytes_read = 0; samples_read = 0;
replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message)); replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message));
break; break;
@ -105,6 +113,7 @@ void AudioTXProcessor::audio_config(const AudioTXConfigMessage& message) {
tone_gen.configure(message.tone_key_delta, message.tone_key_mix_weight); tone_gen.configure(message.tone_key_delta, message.tone_key_mix_weight);
progress_interval_samples = message.divider; progress_interval_samples = message.divider;
resample_acc = 0; resample_acc = 0;
bytes_per_sample = message.bits_per_sample / 8;
audio_output.configure(false); audio_output.configure(false);
tone_key_enabled = (message.tone_key_delta != 0); tone_key_enabled = (message.tone_key_delta != 0);

View File

@ -48,9 +48,11 @@ class AudioTXProcessor : public BasebandProcessor {
uint32_t resample_inc{}, resample_acc{}; uint32_t resample_inc{}, resample_acc{};
uint32_t fm_delta{0}; uint32_t fm_delta{0};
uint32_t phase{0}, sphase{0}; uint32_t phase{0}, sphase{0};
uint8_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};
int16_t audio_sample_s16{};
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}; buffer_s16_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, 48000};
@ -59,7 +61,7 @@ class AudioTXProcessor : public BasebandProcessor {
size_t progress_interval_samples = 0, progress_samples = 0; size_t progress_interval_samples = 0, progress_samples = 0;
bool configured{false}; bool configured{false};
uint32_t bytes_read{0}; uint32_t samples_read{0};
bool tone_key_enabled{false}; bool tone_key_enabled{false};
void sample_rate_config(const SampleRateConfigMessage& message); void sample_rate_config(const SampleRateConfigMessage& message);

View File

@ -931,6 +931,7 @@ class AudioTXConfigMessage : public Message {
const float deviation_hz, const float deviation_hz,
const float audio_gain, const float audio_gain,
const uint8_t audio_shift_bits_s16, const uint8_t audio_shift_bits_s16,
const uint8_t bits_per_sample,
const uint32_t tone_key_delta, const uint32_t tone_key_delta,
const float tone_key_mix_weight, const float tone_key_mix_weight,
const bool am_enabled, const bool am_enabled,
@ -942,6 +943,7 @@ class AudioTXConfigMessage : public Message {
deviation_hz(deviation_hz), deviation_hz(deviation_hz),
audio_gain(audio_gain), audio_gain(audio_gain),
audio_shift_bits_s16(audio_shift_bits_s16), audio_shift_bits_s16(audio_shift_bits_s16),
bits_per_sample(bits_per_sample),
tone_key_delta(tone_key_delta), tone_key_delta(tone_key_delta),
tone_key_mix_weight(tone_key_mix_weight), tone_key_mix_weight(tone_key_mix_weight),
am_enabled(am_enabled), am_enabled(am_enabled),
@ -954,6 +956,7 @@ class AudioTXConfigMessage : public Message {
const float deviation_hz; const float deviation_hz;
const float audio_gain; const float audio_gain;
const uint8_t audio_shift_bits_s16; const uint8_t audio_shift_bits_s16;
const uint8_t bits_per_sample;
const uint32_t tone_key_delta; const uint32_t tone_key_delta;
const float tone_key_mix_weight; const float tone_key_mix_weight;
const bool am_enabled; const bool am_enabled;