Externalize antenna calc and wav view (#2498)

* externalize antenna calc and wav view
* Added a tool to check if all the pictures in graphics are used in internal apps
This commit is contained in:
gullradriel
2025-01-26 21:36:21 +01:00
committed by GitHub
parent b2bb37af74
commit a65ef3ce2e
12 changed files with 191 additions and 93 deletions

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_whipcalc.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::antenna_length {
void initialize_app(ui::NavigationView& nav) {
nav.push<WhipCalcView>();
}
} // namespace ui::external_app::antenna_length
extern "C" {
__attribute__((section(".external_app.app_antenna_length.application_information"), used)) application_information_t _application_information_antenna_length = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::antenna_length::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Antenna Length",
/*.bitmap_data = */ {0x38, 0x3E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x10, 0x2E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x26, 0x38, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x3E, 0x00, 0x00},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::SETTINGS,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}

View File

@@ -0,0 +1,180 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_whipcalc.hpp"
#include "ch.h"
#include "convert.hpp"
#include "event_m0.hpp"
#include "file_reader.hpp"
#include "portapack.hpp"
#include "file_path.hpp"
#include <cstring>
using namespace portapack;
using namespace ui;
namespace ui::external_app::antenna_length {
void WhipCalcView::focus() {
field_frequency.focus();
}
void WhipCalcView::update_result() {
double length, calclength, divider;
console.clear(true);
divider = ((double)options_type.selected_index_value() / 8.0);
// Antenna lengths fields
if (field_frequency.value() > 0) {
// Metric
length = (speed_of_light_mps / (double)field_frequency.value()) * divider;
auto m = to_string_dec_int((int)length, 0);
// auto cm = to_string_dec_int(int(length * 100.0) % 100, 2);
// auto mm = to_string_dec_int(int(length * 1000.0) % 10, 1);
calclength = get_decimals(length, 100); // cm
auto cm = to_string_dec_int(int(calclength), 0);
auto mm = to_string_dec_int(int(get_decimals(calclength, 10, true)), 0);
text_result_metric.set(m + "m " + cm + "." + mm + "cm");
// Imperial
calclength = (speed_of_light_fps / (double)field_frequency.value()) * divider;
auto feet = to_string_dec_int(int(calclength), 0);
calclength = get_decimals(calclength, 12); // inches
auto inch = to_string_dec_int(int(calclength), 0);
auto inch_c = to_string_dec_int(int(get_decimals(calclength, 10, true)), 0);
text_result_imperial.set(feet + "ft " + inch + "." + inch_c + "in");
} else {
text_result_metric.set("infinity+");
text_result_imperial.set("infinity+");
return;
}
uint8_t ant_count = 9; // Shown antennas counter
length *= 1000; // Get length in mm needed to extend the antenna
for (antenna_entry antenna : antenna_db) { // go thru all antennas available
uint16_t element, refined_quarter = 0;
for (element = 0; element < antenna.elements.size(); element++) {
if (length == antenna.elements[element]) // Exact element in length
{
element++; // Real element is +1 (zero based vector)
break; // Done with this ant
} else if (length < antenna.elements[element]) {
double remain, this_element, quarter = 0;
remain = length - antenna.elements[element - 1]; // mm needed from this element to reach length
this_element = antenna.elements[element] - antenna.elements[element - 1]; // total mm on this element
quarter = (remain * 4) / this_element; // havoc & portack ended on this int(quarter) resolution.
if (quarter - int(quarter) > 0.5) { // rounding gave a measure closer to next quarter
refined_quarter = int(quarter) + 1;
if (refined_quarter == 4) { // rounding gave a measure closer to next element
refined_quarter = 0;
element++;
}
} else {
refined_quarter = int(quarter);
}
break; // Done with this ant
}
}
/*if (!ant_count)
{
console.write(" and more ...");
break;
}*/
console.write(antenna.label + ": " + to_string_dec_int(element, 1) + frac_str[refined_quarter] + " elements\n");
ant_count--; // For now, just showing all.
}
}
WhipCalcView::WhipCalcView(NavigationView& nav)
: nav_{nav} {
add_children({&labels,
//&antennas_on_memory,
&field_frequency,
&options_type,
&text_result_metric,
&text_result_imperial,
&console,
&button_exit});
// Try loading antennas from file.
load_antenna_db();
if (!antenna_db.size())
add_default_antenna();
// antennas_on_memory.set(to_string_dec_int(antenna_db.size(),0) + " antennas"); //tell user
options_type.set_selected_index(2); // Quarter wave
options_type.on_change = [this](size_t, OptionsField::value_t) {
this->update_result();
};
field_frequency.set_step(1000000); // 1MHz step
field_frequency.updated = [this](rf::Frequency) {
update_result();
};
button_exit.on_select = [this, &nav](Button&) {
nav.pop();
};
update_result();
}
void WhipCalcView::load_antenna_db() {
File antennas_file;
auto error = antennas_file.open(whipcalc_dir / u"ANTENNAS.TXT");
if (error)
return;
auto reader = FileLineReader(antennas_file);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue; // Empty or comment line.
auto cols = split_string(line, ',');
if (cols.size() < 2)
continue; // Line doesn't have enough columns.
antenna_entry new_antenna{
std::string{cols[0]}};
// Add antenna elements.
for (auto i = 1ul; i < cols.size(); ++i) {
uint16_t length = 0;
if (parse_int(cols[i], length)) {
new_antenna.elements.push_back(length);
}
}
if (!new_antenna.elements.empty())
antenna_db.push_back(std::move(new_antenna));
}
}
void WhipCalcView::add_default_antenna() {
antenna_db.push_back({"ANT500", {185, 315, 450, 586, 724, 862}}); // store a default ant500
}
} // namespace ui::external_app::antenna_length

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_WHIPCALC_H__
#define __UI_WHIPCALC_H__
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_navigation.hpp"
#include "string_format.hpp"
#include <vector>
using namespace ui;
namespace ui::external_app::antenna_length {
class WhipCalcView : public View {
public:
WhipCalcView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Ant. length"; };
private:
const double speed_of_light_mps = 299792458.0; // m/s
const double speed_of_light_fps = 983571087.90472; // feet/s
const std::string frac_str[4] = {"", " 1/4", " 1/2", " 3/4"};
struct antenna_entry {
std::string label{};
std::vector<uint16_t> elements{};
};
NavigationView& nav_;
std::vector<antenna_entry> antenna_db{};
void update_result();
void load_antenna_db();
void add_default_antenna();
Labels labels{
{{2 * 8, 1 * 16}, "Frequency:", Theme::getInstance()->fg_light->foreground},
{{7 * 8, 2 * 16}, "Wave:", Theme::getInstance()->fg_light->foreground},
{{5 * 8, 3 * 16}, "Metric:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 4 * 16}, "Imperial:", Theme::getInstance()->fg_light->foreground}};
TxFrequencyField field_frequency{
{13 * 8, 1 * 16},
nav_};
OptionsField options_type{
{13 * 8, 2 * 16},
7,
{{"Full", 8},
{"Half", 4},
{"Quarter", 2},
{"3/4", 6},
{"1/8", 1},
{"3/8", 3},
{"5/8", 5},
{"7/8", 7}}};
Text text_result_metric{
{13 * 8, 3 * 16, 10 * 16, 16},
"-"};
Text text_result_imperial{
{13 * 8, 4 * 16, 10 * 16, 16},
"-"};
Console console{
{0, 6 * 16, 240, 160}};
Button button_exit{
{72, 17 * 16, 96, 32},
"Back"};
};
} // namespace ui::external_app::antenna_length
#endif /*__UI_WHIPCALC__*/

View File

@@ -150,6 +150,14 @@ set(EXTCPPSRC
#app_manager
external/app_manager/main.cpp
external/app_manager/ui_app_manager.cpp
# whip calculator
external/antenna_length/main.cpp
external/antenna_length/ui_whipcalc.cpp
# wav viewer
external/wav_view/main.cpp
external/wav_view/ui_view_wav.cpp
)
set(EXTAPPLIST
@@ -189,4 +197,6 @@ set(EXTAPPLIST
tuner
metronome
app_manager
)
antenna_length
view_wav
)

View File

@@ -59,6 +59,8 @@ MEMORY
ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k
ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k
ram_external_app_app_manager(rwx) : org = 0xADD40000, len = 32k
ram_external_app_antenna_length(rwx) : org = 0xADD50000, len = 32k
ram_external_app_view_wav(rwx) : org = 0xADD60000, len = 32k
}
SECTIONS
@@ -279,4 +281,17 @@ SECTIONS
KEEP(*(.external_app.app_app_manager.application_information));
*(*ui*external_app*app_manager*);
} > ram_external_app_app_manager
.external_app_antenna_length : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_antenna_length.application_information));
*(*ui*external_app*antenna_length*);
} > ram_external_app_antenna_length
.external_app_view_wav : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_view_wav.application_information));
*(*ui*external_app*view_wav*);
} > ram_external_app_view_wav
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_view_wav.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::view_wav {
void initialize_app(ui::NavigationView& nav) {
nav.push<ViewWavView>();
}
} // namespace ui::external_app::view_wav
extern "C" {
__attribute__((section(".external_app.app_view_wav.application_information"), used)) application_information_t _application_information_view_wav = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::view_wav::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "WAV Viewer",
/*.bitmap_data = */ {0xF0, 0x0F, 0x1C, 0x18, 0x17, 0x38, 0x15, 0x78, 0x15, 0xF8, 0x15, 0x82, 0x15, 0x8B, 0xD5, 0x83, 0xD5, 0xBB, 0xD5, 0x83, 0x15, 0x8B, 0x15, 0x92, 0x15, 0xA0, 0x17, 0x80, 0x1C, 0x80, 0xF0, 0xFF},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::SETTINGS,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}

View File

@@ -0,0 +1,349 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_view_wav.hpp"
#include "ui_fileman.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
using namespace portapack;
using namespace ui;
namespace ui::external_app::view_wav {
void ViewWavView::update_scale(int32_t new_scale) {
scale = new_scale;
ns_per_pixel = (1000000000UL / wav_reader->sample_rate()) * scale;
refresh_waveform();
refresh_measurements();
}
void ViewWavView::refresh_waveform() {
// NB: We can't read from the file to update the waveform when playback is in progress, so defer til playback done.
// (This only happens if the user messes with position or scale fields while playback is occurring)
if (playback_in_progress) {
waveform_update_needed = true;
return;
}
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));
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();
// Window
uint64_t w_start = (position * 240) / wav_reader->sample_count();
uint64_t w_width = (scale * 240) / (wav_reader->sample_count() / 240);
display.fill_rectangle({0, 10 * 16 + 1, 240, 16}, Theme::getInstance()->bg_darkest->background);
display.fill_rectangle({(Coord)w_start, 21 * 8, (Dim)w_width + 1, 8}, Theme::getInstance()->bg_darkest->foreground);
display.draw_line({0, 10 * 16 + 1}, {(Coord)w_start, 21 * 8}, Theme::getInstance()->bg_darkest->foreground);
display.draw_line({239, 10 * 16 + 1}, {(Coord)(w_start + w_width), 21 * 8}, Theme::getInstance()->bg_darkest->foreground);
}
void ViewWavView::refresh_measurements() {
uint64_t span_ns = ns_per_pixel * abs(field_cursor_b.value() - field_cursor_a.value());
if (span_ns)
text_delta.set(unit_auto_scale(span_ns, 0, 3) + "s (" + to_string_dec_uint(1000000000UL / span_ns) + "Hz)");
else
text_delta.set("0us ?Hz");
}
void ViewWavView::paint(Painter& painter) {
// Waveform limits
painter.draw_hline({0, 6 * 16 - 1}, 240, Theme::getInstance()->bg_medium->background);
painter.draw_hline({0, 10 * 16}, 240, Theme::getInstance()->bg_medium->background);
// Overall amplitude view, 0~127 to 0~255 color index
for (size_t i = 0; i < 240; i++)
painter.draw_vline({(Coord)i, 11 * 16}, 8, spectrum_rgb2_lut[amplitude_buffer[i] << 1]);
}
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) {
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");
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;
}
reset_controls();
update_scale(1);
}
void ViewWavView::reset_controls() {
field_scale.set_value(1);
field_pos_seconds.set_value(0);
field_pos_samples.set_value(0);
field_cursor_a.set_value(0);
field_cursor_b.set_value(0);
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, std::min(99999ul, wav_reader->sample_count() / 240));
}
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();
// Playback complete - now it's safe to update waveform view
playback_in_progress = false;
if (waveform_update_needed) {
waveform_update_needed = false;
refresh_waveform();
}
}
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;
uint8_t bits_per_sample;
auto reader = std::make_unique<WAVFileReader>();
stop();
if (!reader->open(wav_file_path)) {
file_error();
return;
}
playback_in_progress = true;
button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
bits_per_sample = reader->bits_per_sample();
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
bits_per_sample, // bits_per_sample
0, // tone key disabled
false, // AM
false, // DSB
false, // USB
false // LSB
);
baseband::set_sample_rate(sample_rate);
transmitter_model.set_sampling_rate(1536000);
audio::output::start();
}
void ViewWavView::on_playback_progress(const uint32_t progress) {
progressbar.set_value(progress);
field_pos_samples.set_value(progress);
}
ViewWavView::ViewWavView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
wav_reader = std::make_unique<WAVFileReader>();
add_children({&labels,
&text_filename,
&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,
&field_cursor_b,
&text_delta,
&field_volume});
reset_controls();
button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".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)) {
file_error();
return;
}
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_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);
};
field_pos_seconds.on_change = [this](int32_t) {
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_sample_changed();
};
field_cursor_a.on_change = [this](int32_t v) {
waveform.set_cursor(0, v);
refresh_measurements();
};
field_cursor_b.on_change = [this](int32_t v) {
waveform.set_cursor(1, v);
refresh_measurements();
};
}
void ViewWavView::focus() {
button_open.focus();
}
ViewWavView::~ViewWavView() {
stop();
baseband::shutdown();
}
} /* namespace ui::external_app::view_wav */

View File

@@ -0,0 +1,207 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "io_wave.hpp"
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "replay_thread.hpp"
using namespace ui;
namespace ui::external_app::view_wav {
class ViewWavView : public View {
public:
ViewWavView(NavigationView& nav);
~ViewWavView();
void focus() override;
void paint(Painter&) override;
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;
void update_scale(int32_t new_scale);
void refresh_waveform();
void refresh_measurements();
void on_pos_time_changed();
void on_pos_sample_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<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{};
int16_t waveform_buffer[240]{};
uint8_t amplitude_buffer[240]{};
int32_t scale{1};
uint64_t ns_per_pixel{};
uint64_t position{};
bool updating_position{false};
bool playback_in_progress{false};
bool waveform_update_needed{false};
Labels labels{
{{0 * 8, 0 * 16}, "File:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 1 * 16}, "-bit mono", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Title:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Duration:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 12 * 16}, "Position: . s Scale:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 13 * 16}, " Sample:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 14 * 16}, "Cursor A:", Theme::getInstance()->fg_darkcyan->foreground},
{{0 * 8, 15 * 16}, "Cursor B:", Theme::getInstance()->fg_magenta->foreground},
{{0 * 8, 16 * 16}, "Delta:", Theme::getInstance()->fg_light->foreground},
{{24 * 8, 18 * 16}, "Vol:", Theme::getInstance()->fg_light->foreground}};
Text text_filename{
{5 * 8, 0 * 16, 18 * 8, 16},
""};
Text text_samplerate{
{12 * 8, 1 * 16, 10 * 8, 16},
""};
Text text_title{
{6 * 8, 2 * 16, 17 * 8, 16},
""};
Text text_duration{
{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"};
ImageButton button_play{
{24 * 8, 17 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
AudioVolumeField field_volume{
{28 * 8, 18 * 16}};
Waveform waveform{
{0, 5 * 16, 240, 64},
waveform_buffer,
240,
0,
false,
Theme::getInstance()->bg_darkest->foreground};
ProgressBar progressbar{
{0 * 8, 11 * 16, 30 * 8, 4}};
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{
{9 * 8, 13 * 16},
9,
{0, 0},
1,
'0',
true};
NumberField field_scale{
{25 * 8, 12 * 16},
5,
{1, 1},
1,
' ',
true};
NumberField field_cursor_a{
{9 * 8, 14 * 16},
3,
{0, 239},
1,
' ',
true};
NumberField field_cursor_b{
{9 * 8, 15 * 16},
3,
{0, 239},
1,
' ',
true};
Text text_delta{
{7 * 8, 16 * 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::external_app::view_wav */