diff --git a/firmware/application/app_settings.hpp b/firmware/application/app_settings.hpp index b06a5921..0e36ffab 100644 --- a/firmware/application/app_settings.hpp +++ b/firmware/application/app_settings.hpp @@ -113,6 +113,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings) namespace app_settings { enum class Mode : uint8_t { + NO_RF = 0x00, RX = 0x01, TX = 0x02, RX_TX = 0x03, // Both TX/RX diff --git a/firmware/application/apps/soundboard_app.cpp b/firmware/application/apps/soundboard_app.cpp index 5d23dff8..e35ba91b 100644 --- a/firmware/application/apps/soundboard_app.cpp +++ b/firmware/application/apps/soundboard_app.cpp @@ -116,15 +116,16 @@ void SoundBoardView::start_tx(const uint32_t id) { // TODO: Delete all this and use tx model. baseband::set_audiotx_config( - TONES_SAMPLERATE / 20, // Update vu-meter at 20Hz + 1536000 / 20, // Update vu-meter at 20Hz transmitter_model.channel_bandwidth(), 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), - 0, // AM - 0, // DSB - 0, // USB - 0 // LSB + false, // AM + false, // DSB + false, // USB + false // LSB ); baseband::set_sample_rate(sample_rate); diff --git a/firmware/application/apps/ui_mictx.cpp b/firmware/application/apps/ui_mictx.cpp index 2c7fa5f6..6a1a7acb 100644 --- a/firmware/application/apps/ui_mictx.cpp +++ b/firmware/application/apps/ui_mictx.cpp @@ -105,6 +105,7 @@ void MicTXView::configure_baseband() { transmitting ? transmitter_model.channel_bandwidth() : 0, mic_gain_x10 / 10.0, shift_bits(), // to be used in dsp_modulate + 8, // bits per sample TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate), (mic_mod_index == MIC_MOD_AM), (mic_mod_index == MIC_MOD_DSB), diff --git a/firmware/application/apps/ui_view_wav.cpp b/firmware/application/apps/ui_view_wav.cpp index 8cdc4231..f2793659 100644 --- a/firmware/application/apps/ui_view_wav.cpp +++ b/firmware/application/apps/ui_view_wav.cpp @@ -22,11 +22,12 @@ #include "ui_view_wav.hpp" #include "ui_fileman.hpp" +#include "audio.hpp" +#include "baseband_api.hpp" +#include "string_format.hpp" using namespace portapack; -#include "string_format.hpp" - namespace ui { void ViewWavView::update_scale(int32_t new_scale) { @@ -81,6 +82,8 @@ void ViewWavView::load_wav(std::filesystem::path file_path) { int16_t sample; uint32_t average; + wav_file_path = file_path; + text_filename.set(file_path.filename().string()); auto ms_duration = wav_reader->ms_duration(); 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); } +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(); + + 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( + 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( NavigationView& nav) : nav_(nav) { + baseband::run_image(portapack::spi_flash::image_tag_audio_tx); wav_reader = std::make_unique(); add_children({&labels, @@ -128,21 +212,26 @@ ViewWavView::ViewWavView( &text_title, &text_duration, &button_open, + &button_play, &waveform, + &progressbar, &field_pos_seconds, &field_pos_samples, &field_scale, &field_cursor_a, &field_cursor_b, - &text_delta}); + &text_delta, + &field_volume}); + reset_controls(); + button_open.on_select = [this, &nav](Button&) { auto open_view = nav.push(".WAV"); open_view->on_changed = [this, &nav](std::filesystem::path file_path) { // Can't show new dialogs in an on_changed handler, so use continuation. nav.set_on_pop([this, &nav, file_path]() { if (!wav_reader->open(file_path)) { - nav_.display_modal("Error", "Couldn't open file."); + file_error(); return; } 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) { update_scale(value); }; @@ -179,4 +277,9 @@ void ViewWavView::focus() { button_open.focus(); } +ViewWavView::~ViewWavView() { + stop(); + baseband::shutdown(); +} + } /* namespace ui */ diff --git a/firmware/application/apps/ui_view_wav.hpp b/firmware/application/apps/ui_view_wav.hpp index 57ec1e91..1958bfcb 100644 --- a/firmware/application/apps/ui_view_wav.hpp +++ b/firmware/application/apps/ui_view_wav.hpp @@ -24,12 +24,15 @@ #include "ui_navigation.hpp" #include "io_wave.hpp" #include "spectrum_color_lut.hpp" +#include "ui_receiver.hpp" +#include "replay_thread.hpp" namespace ui { class ViewWavView : public View { public: ViewWavView(NavigationView& nav); + ~ViewWavView(); void focus() override; void paint(Painter&) override; @@ -37,6 +40,9 @@ class ViewWavView : public View { std::string title() const override { return "WAV viewer"; }; private: + app_settings::SettingsManager settings_{ + "wav_viewer", app_settings::Mode::NO_RF}; + NavigationView& nav_; static constexpr uint32_t subsampling_factor = 8; @@ -46,6 +52,20 @@ class ViewWavView : public View { void on_pos_changed(); void load_wav(std::filesystem::path file_path); 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 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 wav_reader{}; @@ -60,10 +80,11 @@ class ViewWavView : public View { {{0 * 8, 1 * 16}, "Samplerate:", Color::light_grey()}, {{0 * 8, 2 * 16}, "Title:", 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}, "Cursor A:", Color::dark_cyan()}, - {{0 * 8, 13 * 16}, "Cursor B:", Color::dark_magenta()}, - {{0 * 8, 14 * 16}, "Delta:", 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()}, + {{24 * 8, 18 * 16}, "Vol:", Color::light_grey()}}; Text text_filename{ {5 * 8, 0 * 16, 12 * 8, 16}, @@ -80,6 +101,13 @@ class ViewWavView : public View { Button button_open{ {24 * 8, 8, 6 * 8, 2 * 16}, "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{ {0, 5 * 16, 240, 64}, @@ -89,34 +117,29 @@ class ViewWavView : public View { false, Color::white()}; + ProgressBar progressbar{ + {0 * 8, 11 * 16, 30 * 8, 8}}; + NumberField field_pos_seconds{ - {9 * 8, 11 * 16}, + {9 * 8, 12 * 16}, 3, {0, 999}, 1, ' '}; NumberField field_pos_samples{ - {14 * 8, 11 * 16}, + {14 * 8, 12 * 16}, 6, {0, 999999}, 1, '0'}; NumberField field_scale{ - {28 * 8, 11 * 16}, + {28 * 8, 12 * 16}, 2, {1, 40}, 1, ' '}; NumberField field_cursor_a{ - {9 * 8, 12 * 16}, - 3, - {0, 239}, - 1, - ' ', - true}; - - NumberField field_cursor_b{ {9 * 8, 13 * 16}, 3, {0, 239}, @@ -124,9 +147,40 @@ class ViewWavView : public View { ' ', true}; + NumberField field_cursor_b{ + {9 * 8, 14 * 16}, + 3, + {0, 239}, + 1, + ' ', + true}; + 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(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(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(p); + this->on_playback_progress(message.progress); + }}; }; } /* namespace ui */ diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index af98d90e..45289125 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -211,6 +211,7 @@ void set_audiotx_config( 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, @@ -221,6 +222,7 @@ void set_audiotx_config( deviation_hz, audio_gain, audio_shift_bits_s16, + bits_per_sample, tone_key_delta, (float)persistent_memory::tone_mix() / 100.0f, am_enabled, diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index 90590b62..48428f7e 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -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 kill_tone(); 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_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); diff --git a/firmware/baseband/proc_audiotx.cpp b/firmware/baseband/proc_audiotx.cpp index 4e84977a..e79ffdc8 100644 --- a/firmware/baseband/proc_audiotx.cpp +++ b/firmware/baseband/proc_audiotx.cpp @@ -30,23 +30,31 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) { if (!configured) return; + int32_t audio_sample_m; + // Zero-order hold (poop) for (size_t i = 0; i < buffer.count; i++) { resample_acc += resample_inc; if (resample_acc >= 0x10000) { resample_acc -= 0x10000; if (stream) { - stream->read(&audio_sample, 1); - bytes_read++; + audio_sample = 0; + 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 if (!tone_key_enabled) { 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)) audio_output.write_unprocessed(audio_buffer); } @@ -69,7 +77,7 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) { if (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; shared_memory.application_queue.push(txprogress_message); } @@ -83,7 +91,7 @@ void AudioTXProcessor::on_message(const Message* const message) { case Message::ID::ReplayConfig: configured = false; - bytes_read = 0; + samples_read = 0; replay_config(*reinterpret_cast(message)); break; @@ -105,6 +113,7 @@ void AudioTXProcessor::audio_config(const AudioTXConfigMessage& message) { tone_gen.configure(message.tone_key_delta, message.tone_key_mix_weight); progress_interval_samples = message.divider; resample_acc = 0; + bytes_per_sample = message.bits_per_sample / 8; audio_output.configure(false); tone_key_enabled = (message.tone_key_delta != 0); diff --git a/firmware/baseband/proc_audiotx.hpp b/firmware/baseband/proc_audiotx.hpp index 0791cd41..2a111e6b 100644 --- a/firmware/baseband/proc_audiotx.hpp +++ b/firmware/baseband/proc_audiotx.hpp @@ -48,9 +48,11 @@ class AudioTXProcessor : public BasebandProcessor { uint32_t resample_inc{}, resample_acc{}; uint32_t fm_delta{0}; uint32_t phase{0}, sphase{0}; - uint8_t audio_sample{}; + 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{}; int16_t audio_data[AUDIO_OUTPUT_BUFFER_SIZE]; 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; bool configured{false}; - uint32_t bytes_read{0}; + uint32_t samples_read{0}; bool tone_key_enabled{false}; void sample_rate_config(const SampleRateConfigMessage& message); diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index b8924abe..aae2239a 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -931,6 +931,7 @@ class AudioTXConfigMessage : public Message { const float deviation_hz, const float audio_gain, const uint8_t audio_shift_bits_s16, + const uint8_t bits_per_sample, const uint32_t tone_key_delta, const float tone_key_mix_weight, const bool am_enabled, @@ -942,6 +943,7 @@ class AudioTXConfigMessage : public Message { deviation_hz(deviation_hz), audio_gain(audio_gain), audio_shift_bits_s16(audio_shift_bits_s16), + bits_per_sample(bits_per_sample), tone_key_delta(tone_key_delta), tone_key_mix_weight(tone_key_mix_weight), am_enabled(am_enabled), @@ -954,6 +956,7 @@ class AudioTXConfigMessage : public Message { const float deviation_hz; const float audio_gain; const uint8_t audio_shift_bits_s16; + const uint8_t bits_per_sample; const uint32_t tone_key_delta; const float tone_key_mix_weight; const bool am_enabled;