Compare commits

...

4 Commits

Author SHA1 Message Date
sommermorgentraum
ee472e1ed2 waveform fix 3 and trivial change (#2540) 2025-03-05 17:53:41 +01:00
sommermorgentraum
e933c8b498 Trivial textual change about missing SD content (#2542)
* _

* _
2025-03-05 15:37:53 +01:00
RocketGod
18bebbfb6d Breakout - The Portapack remake game rises from the pirate's lair (#2541)
* Breakout - The Portapack remake game rises from the pirate's lair

* Fixes

* Added a signature
2025-03-05 14:43:30 +08:00
Brumi-2021
52c3760e90 Adding Wefax demodulation mode inside Audio App (#2539)
* Adding_new_WFAX_GUI_mode_Audio_App

* Wefax_APT_demodulation_structure

* Solving REC Apt signal.wav from WFAX

* clang format issues

* correcting comments
2025-03-05 11:27:18 +08:00
35 changed files with 1261 additions and 81 deletions

View File

@@ -102,6 +102,24 @@ WFMOptionsView::WFMOptionsView(
};
}
/* AMFMAptOptionsView *********************************************************/
AMFMAptOptionsView::AMFMAptOptionsView(
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
add_children({
&label_config,
&options_config,
});
freqman_set_bandwidth_option(AMFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_by_value(receiver_model.amfm_configuration());
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
}
/* SPECOptionsView *******************************************************/
SPECOptionsView::SPECOptionsView(
@@ -176,8 +194,8 @@ AnalogAudioView::AnalogAudioView(
auto modulation = receiver_model.modulation();
// This app doesn't handle "Capture" mode.
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
modulation = ReceiverModel::Mode::SpectrumAnalysis;
if (modulation > ReceiverModel::Mode::SpectrumAnalysis) // This two should be together in the last index position : SpectrumAnalysis = 4, and Capture = 5
modulation = ReceiverModel::Mode::SpectrumAnalysis; // For sw simplicity , Wefax_mode, should NOT be added between.
options_modulation.set_by_value(toUType(modulation));
options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
@@ -346,6 +364,12 @@ void AnalogAudioView::on_show_options_modulation() {
text_ctcss.hidden(true);
break;
case ReceiverModel::Mode::AMAudioFMApt:
widget = std::make_unique<AMFMAptOptionsView>(options_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(false);
text_ctcss.hidden(true);
break;
case ReceiverModel::Mode::SpectrumAnalysis:
widget = std::make_unique<SPECOptionsView>(this, nbfm_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(false);
@@ -387,6 +411,9 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
case ReceiverModel::Mode::WidebandFMAudio:
image_tag = portapack::spi_flash::image_tag_wfm_audio;
break;
case ReceiverModel::Mode::AMAudioFMApt: // TODO pending to update it.
image_tag = portapack::spi_flash::image_tag_am_audio;
break;
case ReceiverModel::Mode::SpectrumAnalysis:
image_tag = portapack::spi_flash::image_tag_wideband_spectrum;
break;
@@ -421,6 +448,9 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
case ReceiverModel::Mode::WidebandFMAudio:
sampling_rate = 48000;
break;
case ReceiverModel::Mode::AMAudioFMApt: // TODO Wefax mode.
sampling_rate = 12000;
break;
default:
break;
}

View File

@@ -53,6 +53,24 @@ class AMOptionsView : public View {
}};
};
class AMFMAptOptionsView : public View {
public:
AMFMAptOptionsView(Rect parent_rect, const Style* style);
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
6, // Max option length
{
// Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal.
}};
};
class NBFMOptionsView : public View {
public:
NBFMOptionsView(Rect parent_rect, const Style* style);
@@ -206,12 +224,11 @@ class AnalogAudioView : public View {
OptionsField options_modulation{
{0 * 8, 0 * 16},
4,
{
{" AM ", toUType(ReceiverModel::Mode::AMAudio)},
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
}};
{{" AM ", toUType(ReceiverModel::Mode::AMAudio)},
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
{"WFAX", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)}}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};

View File

@@ -175,7 +175,8 @@ void SoundBoardView::refresh_list() {
for (auto& c : entry_extension)
c = toupper(c);
if (entry_extension == ".WAV") {
if (entry_extension == ".WAV" && entry.path().string().find("shopping_cart") == std::string::npos) {
/* ^ because the shopping cart lock app using the speaker to send the LF signal, it's meaningless to be here */
if (reader->open(wav_dir / entry.path())) {
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
// sounds[c].ms_duration = reader->ms_duration();

View File

@@ -66,12 +66,12 @@ static void send_message(const Message* const message) {
void AMConfig::apply() const {
const AMConfigureMessage message{
taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 5 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW)
taps_6k0_decim_1, // common FIR filter taps pre-decim_1 to all 5 x AM mod. types.
decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k all rest AM modes)
channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW)
modulation, // var parameter .
audio_12k_hpf_300hz_config};
taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 6 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW, WFAX)
taps_6k0_decim_1, // common FIR filter taps pre-decim_1 to all 6 x AM mod. types. (")
decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k and all rest AM modes)
channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW,WFAX)
modulation, // var parameter . enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
audio_12k_iir_filter_config}; // var parameter , 300 Hz hpf all except Wefax (1.500Hz lpf)
send_message(&message);
audio::set_rate(audio::Rate::Hz_12000);
}

View File

@@ -36,9 +36,10 @@
namespace baseband {
struct AMConfig {
const fir_taps_real<32> decim_2; // added to handle two types decim_2 9k, 6k
const fir_taps_real<32> decim_2; // added to handle two var types decim_2 9k, 6k
const fir_taps_complex<64> channel;
const AMConfigureMessage::Modulation modulation;
const iir_biquad_config_t audio_12k_iir_filter_config; // added to handle two var IIR filter types : 300 hpf(as before) , 1500Hz lpf for Wefax.
void apply() const;
};

View File

@@ -0,0 +1,29 @@
/*
* 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.
*/
// dummy include file to avoid changing original source
#ifndef __UI_Arial12x12_H__
#define __UI_Arial12x12_H__
#define Arial12x12 (0)
#endif /*__UI_Arial12x12_H__*/

View File

@@ -0,0 +1,68 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_SPI_TFT_ILI9341_H__
#define __UI_SPI_TFT_ILI9341_H__
ui::Painter painter;
static int bg_color;
enum {
White,
Blue,
Yellow,
Purple,
Green,
Red,
Maroon,
Orange,
Black,
};
static const Color pp_colors[] = {
Color::white(),
Color::blue(),
Color::yellow(),
Color::purple(),
Color::green(),
Color::red(),
Color::magenta(),
Color::orange(),
Color::black(),
};
static void claim(__FILE* x) {
(void)x;
};
static void cls() {
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
};
static void background(int color) {
bg_color = color;
};
static void set_orientation(int x) {
(void)x;
};
static void set_font(unsigned char* x) {
(void)x;
};
static void fillrect(int x1, int y1, int x2, int y2, int color) {
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
};
static void rect(int x1, int y1, int x2, int y2, int color) {
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
};
#endif /*__UI_SPI_TFT_ILI9341_H__*/

View File

@@ -0,0 +1,429 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "mbed.h"
#include "SPI_TFT_ILI9341.h"
#include "Arial12x12.h"
#include "ui.hpp"
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define PADDLE_WIDTH 40
#define PADDLE_HEIGHT 10
#define BALL_SIZE 8
#define BRICK_WIDTH 20
#define BRICK_HEIGHT 10
#define BRICK_ROWS 5
#define BRICK_COLS 10
#define BRICK_GAP 2
#define GAME_AREA_TOP 50
#define GAME_AREA_BOTTOM 310
#define PADDLE_Y (GAME_AREA_BOTTOM - PADDLE_HEIGHT)
#define BALL_SPEED_INCREASE 0.1f
#define STATE_MENU 0
#define STATE_PLAYING 1
#define STATE_GAME_OVER 3
#define COLOR_BACKGROUND Black
#define COLOR_PADDLE Blue
#define COLOR_BALL White
#define COLOR_BORDER White
#define COLOR_BRICK_COLORS \
{ Red, Orange, Yellow, Green, Purple }
Ticker game_timer;
int paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) / 2;
float ball_x = SCREEN_WIDTH / 2;
float ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
float ball_dx = 1.5f;
float ball_dy = -2.0f;
int score = 0;
int lives = 3;
int level = 1;
int game_state = STATE_MENU;
bool initialized = false;
bool ball_attached = true;
unsigned int brick_count = 0;
bool bricks[BRICK_ROWS][BRICK_COLS];
int brick_colors[BRICK_ROWS];
extern ui::Painter painter;
void init_game();
void init_level();
void draw_screen();
void draw_bricks();
void draw_paddle();
void draw_ball();
void draw_score();
void draw_lives();
void draw_level();
void draw_borders();
void move_paddle_left();
void move_paddle_right();
void launch_ball();
void update_game();
void check_collisions();
bool check_brick_collision(int row, int col);
void handle_game_over();
void show_menu();
void show_game_over();
bool check_level_complete();
void next_level();
void reset_game();
void game_timer_check() {
if (game_state == STATE_PLAYING) {
update_game();
}
}
void init_game() {
claim(stdout);
set_orientation(2);
set_font((unsigned char*)Arial12x12);
paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) / 2;
score = 0;
lives = 3;
level = 1;
brick_colors[0] = Red;
brick_colors[1] = Orange;
brick_colors[2] = Yellow;
brick_colors[3] = Green;
brick_colors[4] = Purple;
init_level();
game_state = STATE_MENU;
show_menu();
}
void init_level() {
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
float speed_multiplier = (level == 1) ? 1.0f : 1.0f + ((level - 1) * BALL_SPEED_INCREASE);
ball_dx = (ball_dx > 0 ? 1.5f : -1.5f) * speed_multiplier;
ball_dy = -2.0f * speed_multiplier;
ball_attached = true;
brick_count = 0;
for (int row = 0; row < BRICK_ROWS; row++) {
for (int col = 0; col < BRICK_COLS; col++) {
bricks[row][col] = true;
brick_count++;
}
}
}
void draw_screen() {
cls();
background(COLOR_BACKGROUND);
draw_borders();
draw_bricks();
draw_paddle();
draw_ball();
draw_score();
draw_lives();
draw_level();
}
void draw_borders() {
rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
}
void draw_bricks() {
for (int row = 0; row < BRICK_ROWS; row++) {
for (int col = 0; col < BRICK_COLS; col++) {
if (bricks[row][col]) {
int x = col * (BRICK_WIDTH + BRICK_GAP);
int y = GAME_AREA_TOP + row * (BRICK_HEIGHT + BRICK_GAP) + 5;
fillrect(x, y, x + BRICK_WIDTH, y + BRICK_HEIGHT, brick_colors[row]);
rect(x, y, x + BRICK_WIDTH, y + BRICK_HEIGHT, Black);
}
}
}
}
void draw_paddle() {
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_PADDLE);
}
void draw_ball() {
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BALL);
}
void draw_score() {
auto style = *ui::Theme::getInstance()->fg_green;
painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
}
void draw_lives() {
auto style = *ui::Theme::getInstance()->fg_red;
painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
}
void draw_level() {
auto style = *ui::Theme::getInstance()->fg_yellow;
painter.draw_string({80, 30}, style, "Level: " + std::to_string(level));
}
void move_paddle_left() {
if (paddle_x > 0) {
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
if (ball_attached) {
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
}
paddle_x -= 10;
if (paddle_x < 0) paddle_x = 0;
if (ball_attached) {
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
}
draw_paddle();
if (ball_attached) {
draw_ball();
}
}
}
void move_paddle_right() {
if (paddle_x < SCREEN_WIDTH - PADDLE_WIDTH) {
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
if (ball_attached) {
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
}
paddle_x += 10;
if (paddle_x > SCREEN_WIDTH - PADDLE_WIDTH) paddle_x = SCREEN_WIDTH - PADDLE_WIDTH;
if (ball_attached) {
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
}
draw_paddle();
if (ball_attached) {
draw_ball();
}
}
}
void launch_ball() {
if (ball_attached) {
ball_attached = false;
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
float speed_multiplier = (level == 1) ? 1.0f : 1.0f + ((level - 1) * BALL_SPEED_INCREASE);
ball_dx = 1.5f * speed_multiplier;
ball_dy = -2.0f * speed_multiplier;
}
}
void update_game() {
if (ball_attached) {
return;
}
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
float next_ball_y = ball_y + ball_dy;
if (next_ball_y > GAME_AREA_BOTTOM) {
lives--;
draw_lives();
if (lives <= 0) {
handle_game_over();
} else {
ball_attached = true;
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
draw_ball();
}
return;
}
ball_x += ball_dx;
ball_y = next_ball_y;
if (ball_x < 0) {
ball_x = 0;
ball_dx = -ball_dx;
} else if (ball_x > SCREEN_WIDTH - BALL_SIZE) {
ball_x = SCREEN_WIDTH - BALL_SIZE;
ball_dx = -ball_dx;
}
if (ball_y < GAME_AREA_TOP) {
ball_y = GAME_AREA_TOP;
ball_dy = -ball_dy;
}
if (ball_y + BALL_SIZE >= PADDLE_Y && ball_y <= PADDLE_Y + PADDLE_HEIGHT) {
if (ball_x + BALL_SIZE >= paddle_x && ball_x <= paddle_x + PADDLE_WIDTH) {
ball_y = PADDLE_Y - BALL_SIZE;
float hit_position = (ball_x + (BALL_SIZE / 2)) - paddle_x;
float angle = (hit_position / PADDLE_WIDTH) - 0.5f;
ball_dx = angle * 4.0f;
if (ball_dx > -0.5f && ball_dx < 0.5f) {
ball_dx = (ball_dx > 0) ? 0.5f : -0.5f;
}
ball_dy = -ball_dy;
}
}
check_collisions();
draw_ball();
if (check_level_complete()) {
next_level();
}
}
void check_collisions() {
int grid_x = ball_x / (BRICK_WIDTH + BRICK_GAP);
int grid_y = (ball_y - GAME_AREA_TOP - 5) / (BRICK_HEIGHT + BRICK_GAP);
for (int row = grid_y - 1; row <= grid_y + 1; row++) {
for (int col = grid_x - 1; col <= grid_x + 1; col++) {
if (row >= 0 && row < BRICK_ROWS && col >= 0 && col < BRICK_COLS) {
if (bricks[row][col] && check_brick_collision(row, col)) {
return;
}
}
}
}
}
bool check_brick_collision(int row, int col) {
int brick_x = col * (BRICK_WIDTH + BRICK_GAP);
int brick_y = GAME_AREA_TOP + row * (BRICK_HEIGHT + BRICK_GAP) + 5;
if (ball_x + BALL_SIZE >= brick_x && ball_x <= brick_x + BRICK_WIDTH &&
ball_y + BALL_SIZE >= brick_y && ball_y <= brick_y + BRICK_HEIGHT) {
fillrect(brick_x, brick_y, brick_x + BRICK_WIDTH, brick_y + BRICK_HEIGHT, COLOR_BACKGROUND);
bricks[row][col] = false;
brick_count--;
score += (5 - row) * 10;
draw_score();
float center_x = brick_x + BRICK_WIDTH / 2;
float center_y = brick_y + BRICK_HEIGHT / 2;
float ball_center_x = ball_x + BALL_SIZE / 2;
float ball_center_y = ball_y + BALL_SIZE / 2;
float dx = std::abs(ball_center_x - center_x);
float dy = std::abs(ball_center_y - center_y);
if (dx * BRICK_HEIGHT > dy * BRICK_WIDTH) {
ball_dx = -ball_dx;
} else {
ball_dy = -ball_dy;
}
return true;
}
return false;
}
bool check_level_complete() {
return brick_count == 0;
}
void next_level() {
level++;
init_level();
draw_screen();
}
void handle_game_over() {
game_state = STATE_GAME_OVER;
show_game_over();
}
void show_menu() {
cls();
background(COLOR_BACKGROUND);
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
auto style_white = *ui::Theme::getInstance()->fg_light;
auto style_green = *ui::Theme::getInstance()->fg_green;
auto style_red = *ui::Theme::getInstance()->fg_red;
painter.draw_string({0, 40}, style_yellow, "* * * BREAKOUT * * *");
painter.draw_string({0, 70}, style_white, "========================");
painter.draw_string({0, 120}, style_green, "| ROTARY: MOVE PADDLE |");
painter.draw_string({0, 150}, style_green, "| SELECT: START/LAUNCH |");
painter.draw_string({0, 190}, style_white, "========================");
painter.draw_string({24, 230}, style_red, "* PRESS SELECT *");
}
void show_game_over() {
cls();
background(COLOR_BACKGROUND);
auto style_red = *ui::Theme::getInstance()->fg_red;
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
auto style_green = *ui::Theme::getInstance()->fg_green;
painter.draw_string({72, 120}, style_red, "GAME OVER");
painter.draw_string({12, 160}, style_yellow, "FINAL SCORE: " + std::to_string(score));
painter.draw_string({0, 200}, style_green, "PRESS SELECT TO RESTART");
wait(1);
}
void reset_game() {
level = 1;
score = 0;
lives = 3;
game_state = STATE_PLAYING;
init_level();
draw_screen();
}
int main() {
if (!initialized) {
initialized = true;
game_timer.attach(&game_timer_check, 1.0 / 60.0);
init_game();
}
while (1) {
if (but_SELECT && game_state == STATE_MENU) {
game_state = STATE_PLAYING;
reset_game();
}
if (but_SELECT && game_state == STATE_GAME_OVER) {
reset_game();
}
if (but_SELECT && game_state == STATE_PLAYING && ball_attached) {
launch_ball();
}
if (but_LEFT && game_state == STATE_PLAYING) {
move_paddle_left();
}
if (but_RIGHT && game_state == STATE_PLAYING) {
move_paddle_right();
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui.hpp"
#include "ui_breakout.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::breakout {
void initialize_app(ui::NavigationView& nav) {
nav.push<BreakoutView>();
}
} // namespace ui::external_app::breakout
extern "C" {
__attribute__((section(".external_app.app_breakout.application_information"), used)) application_information_t _application_information_breakout = {
/*.memory_location = */ (uint8_t*)0x00000000, // will be filled at compile time
/*.externalAppEntry = */ ui::external_app::breakout::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Breakout",
/*.bitmap_data = */ {
0xFF,
0xFF,
0x01,
0x01,
0x01,
0x01,
0x01,
0x01,
0xFF,
0xFF,
0x80,
0x80,
0x80,
0x80,
0x80,
0x80,
0xFF,
0xFF,
0x01,
0x01,
0x01,
0x01,
0x01,
0x01,
0xFF,
0xFF,
0x80,
0x80,
0x80,
0x80,
0x80,
0x80,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.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,105 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_mbed_H__
#define __UI_mbed_H__
using Callback = void (*)(void);
#define wait_us(x) (void)0
#define wait(x) chThdSleepMilliseconds(x * 1000)
#define PullUp 1
#include "ui_navigation.hpp"
enum {
dp0,
dp1,
dp2,
dp3,
dp4,
dp5,
dp6,
dp7,
dp8,
dp9,
dp10,
dp11,
dp12,
dp13,
dp14,
dp15,
dp16,
dp17,
dp18,
dp19,
dp20,
dp21,
dp22,
dp23,
dp24,
dp25,
};
static bool but_RIGHT;
static bool but_LEFT;
static bool but_SELECT;
class Timer {
public:
Timer() { (void)0; };
void reset() { (void)0; };
void start() { (void)0; }
uint32_t read_ms() { return 1000; };
private:
};
static Callback game_update_callback;
static uint32_t game_update_timeout;
static uint32_t game_update_counter;
static void check_game_timer() {
if (game_update_callback) {
if (++game_update_counter >= game_update_timeout) {
game_update_counter = 0;
game_update_callback();
}
}
}
class Ticker {
public:
Ticker() { (void)0; };
void attach(Callback func, double delay_sec) {
game_update_callback = func;
game_update_timeout = delay_sec * 60;
}
void detach() {
game_update_callback = nullptr;
}
private:
};
static Callback button_callback;
class InterruptIn {
public:
InterruptIn(int reg) {
(void)reg;
};
void fall(Callback func) { button_callback = func; };
void mode(int v) { (void)v; };
private:
};
#endif /*__UI_mbed_H__*/

View File

@@ -0,0 +1,79 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui_breakout.hpp"
namespace ui::external_app::breakout {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include "breakout.cpp"
#pragma GCC diagnostic pop
BreakoutView::BreakoutView(NavigationView& nav)
: nav_{nav} {
add_children({&dummy});
game_timer.attach(&game_timer_check, 1.0 / 60.0);
}
void BreakoutView::on_show() {
}
void BreakoutView::paint(Painter& painter) {
(void)painter;
if (!initialized) {
initialized = true;
std::srand(LPC_RTC->CTIME0);
init_game();
}
}
void BreakoutView::frame_sync() {
check_game_timer();
set_dirty();
}
bool BreakoutView::on_encoder(const EncoderEvent delta) {
if (game_state == STATE_PLAYING) {
if (delta > 0) {
move_paddle_right();
set_dirty();
} else if (delta < 0) {
move_paddle_left();
set_dirty();
}
}
return true;
}
bool BreakoutView::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (game_state == STATE_MENU) {
game_state = STATE_PLAYING;
reset_game();
} else if (game_state == STATE_PLAYING && ball_attached) {
launch_ball();
} else if (game_state == STATE_GAME_OVER) {
reset_game();
}
} else if (key == KeyEvent::Left) {
if (game_state == STATE_PLAYING) {
move_paddle_left();
}
} else if (key == KeyEvent::Right) {
if (game_state == STATE_PLAYING) {
move_paddle_right();
}
}
set_dirty();
return true;
}
} // namespace ui::external_app::breakout

View File

@@ -0,0 +1,53 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_BREAKOUT_H__
#define __UI_BREAKOUT_H__
#include "ui_navigation.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "irq_controls.hpp"
#include "random.hpp"
#include "lpc43xx_cpp.hpp"
#include "limits.h"
#include "ui_widget.hpp"
namespace ui::external_app::breakout {
class BreakoutView : public View {
public:
BreakoutView(NavigationView& nav);
void on_show() override;
std::string title() const override { return "Breakout"; };
void focus() override { dummy.focus(); };
void paint(Painter& painter) override;
void frame_sync();
bool on_encoder(const EncoderEvent event) override;
bool on_key(KeyEvent key) override;
private:
bool initialized = false;
NavigationView& nav_;
Button dummy{
{240, 0, 0, 0},
""};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->frame_sync();
}};
};
} // namespace ui::external_app::breakout
#endif /*__UI_BREAKOUT_H__*/

View File

@@ -4,6 +4,10 @@ set(EXTCPPSRC
external/tetris/main.cpp
external/tetris/ui_tetris.cpp
#tetris
external/breakout/main.cpp
external/breakout/ui_breakout.cpp
#afsk_rx
external/afsk_rx/main.cpp
external/afsk_rx/ui_afsk_rx.cpp
@@ -187,6 +191,7 @@ set(EXTAPPLIST
spainter
keyfob
tetris
breakout
extsensors
foxhunt_rx
audio_test

View File

@@ -38,6 +38,7 @@ MEMORY
ram_external_app_keyfob(rwx) : org = 0xADBD0000, len = 32k
ram_external_app_tetris(rwx) : org = 0xADBE0000, len = 32k
ram_external_app_extsensors(rwx) : org = 0xADBF0000, len = 32k
ram_external_app_breakout(rwx) : org = 0xADE00000, len = 32k
ram_external_app_foxhunt_rx(rwx) : org = 0xADC00000, len = 32k
ram_external_app_audio_test(rwx) : org = 0xADC10000, len = 32k
ram_external_app_wardrivemap(rwx) : org = 0xADC20000, len = 32k
@@ -152,6 +153,12 @@ SECTIONS
*(*ui*external_app*tetris*);
} > ram_external_app_tetris
.external_app_breakout : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_breakout.application_information));
*(*ui*external_app*breakout*);
} > ram_external_app_breakout
.external_app_extsensors : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_extsensors.application_information));

View File

@@ -74,7 +74,7 @@ __attribute__((section(".external_app.app_random_password.application_informatio
0x01,
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},

View File

@@ -41,6 +41,7 @@ enum freqman_entry_modulation : uint8_t {
AM_MODULATION = 0,
NFM_MODULATION,
WFM_MODULATION,
AMFM_MODULATION, // Added for Wefax.
SPEC_MODULATION
};

View File

@@ -51,7 +51,7 @@ options_t freqman_modulations = {
{"SPEC", 3},
};
options_t freqman_bandwidths[4] = {
options_t freqman_bandwidths[5] = {
{
// AM
{"DSB 9k", 0},
@@ -72,6 +72,10 @@ options_t freqman_bandwidths[4] = {
{"180k", 1},
{"200k", 0},
},
{
// AMFM for Wefax-
{"USB+FM", 5}, // Fixed RX demodul AM config Index 5 : USB+FM for Audio Weather fax (Wfax) tones.
},
{
// SPEC -- TODO: these should be indexes.
{"12k5", 12500},
@@ -99,8 +103,7 @@ options_t freqman_bandwidths[4] = {
{"4500k", 4500000},
{"5000k", 5500000},
{"5500k", 5500000}, // Max capture, needs /4 decimation, (22Mhz sampling ADC).
},
};
}};
// TODO: these should be indexes.
options_t freqman_steps = {

View File

@@ -39,13 +39,14 @@ using namespace portapack;
namespace {
static constexpr std::array<baseband::AMConfig, 5> am_configs{{
static constexpr std::array<baseband::AMConfig, 6> am_configs{{
// we config here all the non COMMON parameters to each AM modulation type in RX.
{taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
{taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
{taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 2K8 (+ 2K8)
{taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB}, // SSB LSB BW 2K8 (- 2K8)
{taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 0K7 (+ 0K7) used to get audio tone from CW Morse, assuming tx shifted +700hz aprox
{taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
{taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
{taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments.
{taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments.
{taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox
{taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, audio_12k_lpf_1500hz_config}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax.
}};
static constexpr std::array<baseband::NBFMConfig, 3> nbfm_configs{{
@@ -145,6 +146,17 @@ void ReceiverModel::set_am_configuration(uint8_t n) {
}
}
uint8_t ReceiverModel::amfm_configuration() const {
return settings_.amfm_config_index;
}
void ReceiverModel::set_amfm_configuration(uint8_t n) {
if (n < am_configs.size()) {
settings_.amfm_config_index = n;
update_modulation();
}
}
uint8_t ReceiverModel::nbfm_configuration() const {
return settings_.nbfm_config_index;
}
@@ -303,6 +315,10 @@ void ReceiverModel::update_modulation() {
update_am_configuration();
break;
case Mode::AMAudioFMApt: // Wefax , first step , USB demodulation from the AMAudio group, index 2 (USB+3K), TODO +FM subcarrier demod ?
update_amfm_configuration();
break;
case Mode::NarrowbandFMAudio:
update_nbfm_configuration();
break;
@@ -321,6 +337,10 @@ void ReceiverModel::update_am_configuration() {
am_configs[am_configuration()].apply();
}
void ReceiverModel::update_amfm_configuration() {
am_configs[amfm_configuration()].apply(); // update with different index for Wefax.
}
void ReceiverModel::update_nbfm_configuration() {
nbfm_configs[nbfm_configuration()].apply(squelch_level());
}

View File

@@ -40,8 +40,9 @@ class ReceiverModel {
AMAudio = 0,
NarrowbandFMAudio = 1,
WidebandFMAudio = 2,
SpectrumAnalysis = 3,
Capture = 4
AMAudioFMApt = 3, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
SpectrumAnalysis = 4,
Capture = 5
};
struct settings_t {
@@ -54,6 +55,7 @@ class ReceiverModel {
bool rf_amp = false;
Mode mode = Mode::NarrowbandFMAudio;
uint8_t am_config_index = 0;
uint8_t amfm_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t squelch_level = 80;
@@ -87,6 +89,9 @@ class ReceiverModel {
uint8_t am_configuration() const;
void set_am_configuration(uint8_t n);
uint8_t amfm_configuration() const;
void set_amfm_configuration(uint8_t n);
uint8_t nbfm_configuration() const;
void set_nbfm_configuration(uint8_t n);
@@ -140,6 +145,7 @@ class ReceiverModel {
void update_modulation();
void update_am_configuration();
void update_amfm_configuration();
void update_nbfm_configuration();
void update_wfm_configuration();

View File

@@ -158,13 +158,13 @@ const NavigationView::AppList NavigationView::appList = {
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
{"soundbrd", "Soundbrd", TX, ui::Color::green(), &bitmap_icon_soundboard, new ViewFactory<SoundBoardView>()},
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
{"signalgen", "Signal Gen", TX, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
/* UTILITIES *************************************************************/
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
{nullptr, "SD Over USB", UTILITIES, Color::yellow(), &bitmap_icon_hackrf, new ViewFactory<SdOverUsbView>()},
{"signalgen", "Signal Gen", UTILITIES, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
// Dangerous apps.
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
@@ -793,9 +793,9 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
[&nav]() {
nav.display_modal(
"Notice",
"External app directory empty;\n"
"see Mayhem wiki and copy apps\n"
"to " + apps_dir.string() + " folder of SD card.");
"Can't read external apps\n"
"Check SD card\n"
"Update SD card content\n");
}},
error_tile_pos);
} else {
@@ -821,7 +821,7 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
bool verify_sdcard_format() {
FATFS* fs = &sd_card::fs;
return (fs->fs_type == FS_FAT32 || fs->fs_type == FS_EXFAT) || !(sd_card::status() == sd_card::Status::Mounted);
/* ^ to satisfy those users that not use an sd*/
/* ^ to satisfy those users that not use an sd*/
}
/* ReceiversMenuView *****************************************************/
@@ -891,7 +891,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav)
void SystemMenuView::on_populate() {
if (!verify_sdcard_format()) {
add_item({"SDCard Error", Theme::getInstance()->error_dark->foreground, nullptr, [this]() {
nav_.display_modal("Error", "SD Card is not exFAT/FAT32,\nformat to exFAT or FAT32 on PC");
nav_.display_modal("Error", "SD Card is not exFAT/FAT32");
}});
}
add_apps(nav_, *this, HOME);

View File

@@ -72,7 +72,7 @@ void AudioOutput::on_block(const buffer_f32_t& audio) {
if (do_processing) {
const auto audio_present_now = squelch.execute(audio);
hpf.execute_in_place(audio);
hpf.execute_in_place(audio); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except WFAX with "lpf-coef".
deemph.execute_in_place(audio);
audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0);

View File

@@ -24,6 +24,8 @@
#include "complex.hpp"
#include "fxpt_atan2.hpp"
#include "utility_m4.hpp"
#include "dsp_hilbert.hpp"
#include "dsp_modulate.hpp"
#include <hal.h>
@@ -63,12 +65,7 @@ buffer_f32_t SSB::execute(
return {dst.p, src.count, src.sampling_rate};
}
/*
static inline float angle_approx_4deg0(const complex32_t t) {
const auto x = static_cast<float>(t.imag()) / static_cast<float>(t.real());
return 16384.0f * x;
}
*/
static inline float angle_approx_0deg27(const complex32_t t) {
if (t.real()) {
const auto x = static_cast<float>(t.imag()) / static_cast<float>(t.real());
@@ -82,6 +79,32 @@ static inline float angle_precise(const complex32_t t) {
return atan2f(t.imag(), t.real());
}
buffer_f32_t SSB_FM::execute( // Added to handle WFAX (HF weather map )-
const buffer_c16_t& src, // input arg , pointer Complex c16 i,q buffer.
const buffer_f32_t& dst) { // input arg , pointer f32 buffer audio demodulated
complex16_t* src_p = src.p; // removed const ; init src_p pointer with the mem address pointed by src.p.
const auto src_end = &src.p[src.count];
auto dst_p = dst.p;
float mag_sq_lpf_norm;
while (src_p < src_end) {
// FM APT audio tone demod: real part (USB-differentiator) and AM tone demodulation + lpf (to remove the subcarrier.)
real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm);
*(dst_p++) = mag_sq_lpf_norm; // already normalized/32.768f and clipped to +1.0f for the wav file.
real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm);
*(dst_p++) = mag_sq_lpf_norm;
real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm);
*(dst_p++) = mag_sq_lpf_norm;
real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm);
*(dst_p++) = mag_sq_lpf_norm;
}
return {dst.p, src.count, src.sampling_rate};
}
buffer_f32_t FM::execute(
const buffer_c16_t& src,
const buffer_f32_t& dst) {

View File

@@ -23,6 +23,7 @@
#define __DSP_DEMODULATE_H__
#include "dsp_types.hpp"
#include "dsp_hilbert.hpp"
namespace dsp {
namespace demodulate {
@@ -47,6 +48,17 @@ class SSB {
static constexpr float k = 1.0f / 32768.0f;
};
class SSB_FM { // Added to handle WFAX-
public:
buffer_f32_t execute(
const buffer_c16_t& src,
const buffer_f32_t& dst);
private:
static constexpr float k = 1.0f / 32768.0f;
dsp::Real_to_Complex real_to_complex; // It is a member variable of SSB_FM.
};
class FM {
public:
buffer_f32_t execute(

View File

@@ -21,6 +21,7 @@
#include "dsp_hilbert.hpp"
#include "dsp_sos_config.hpp"
#include "utility_m4.hpp"
namespace dsp {
@@ -83,4 +84,88 @@ void HilbertTransform::execute(float in, float& out_i, float& out_q) {
n = (n + 1) % 4;
}
Real_to_Complex::Real_to_Complex() {
// No need to call a separate configuration method like "Real_to_Complex()" externally before using the execute() method
// This is the constructor for the Real_to_Complex class.
// It initializes the member variables and calls the configure function for the sos_input, sos_i, and sos_q filters.
// to ensure the object is ready to use right after instantiation.
n = 0;
sos_input.configure(full_band_lpf_config);
sos_i.configure(full_band_lpf_config);
sos_q.configure(full_band_lpf_config);
sos_mag_sq.configure(quarter_band_lpf_config); // for APT LPF subcarrier filter. (1/4 Nyquist fs/2 = 1/4 * 12Khz/2 = 1.5khz)
}
void Real_to_Complex::execute(float in, float& out_mag_sq_lpf) {
// Full_band LPF means a LP filter with f_cut_off = fs/2; Full band = Full max band = 1/2 * fs_max = 1.0 x f_Nyquist = 1 * fs/2 = fs/2
float a = 0, b = 0;
float out_i = 0, out_q = 0, out_mag_sq = 0;
// int32_t packed;
float in_filtered = sos_input.execute(in) * 1.0f; // Anti-aliasing full band LPF, fc = fs/2= 6k, audio filter front-end.
switch (n) {
case 0:
a = in_filtered;
b = 0;
break;
case 1:
a = 0;
b = -in_filtered;
break;
case 2:
a = -in_filtered;
b = 0;
break;
case 3:
a = 0;
b = in_filtered;
break;
}
float i = sos_i.execute(a) * 1.0f; // better keep <1.0f to minimize recorded APT(t) black level artifacts.-
float q = sos_q.execute(b) * 1.0f;
switch (n) { // shifting down -fs4 (fs = 12khz , fs/4 = 3khz)
case 0:
out_i = i;
out_q = q;
break;
case 1:
out_i = -q;
out_q = i;
break;
case 2:
out_i = -i;
out_q = -q;
break;
case 3:
out_i = q;
out_q = -i;
break;
}
n = (n + 1) % 4;
/* res = __smuad(val1,val2); p1 = val1[15:0] × val2[15:0]
p2 = val1[31:16] × val2[31:16]
res[31:0] = p1 + p2
return res; */
// Not strict Magnitude complex calculation, it is a cross multiplication (lower 16 bit real x lower 16 imag) + 0 (higher 16 bits comp),
// but better visual results comparing real magnitude calculation, (better map diagonal lines reproduction, and less artifacts in APT signal(t)
out_mag_sq = __SMUAD(out_i, out_q); // "cross-magnitude" of the complex (out_i + j out_q)
out_mag_sq_lpf = sos_mag_sq.execute((out_mag_sq)) * 2.0f; // LPF quater band = 1.5khz APT signal
out_mag_sq_lpf /= 32768.0f; // normalize ;
// Compress clipping positive APT signal [-1.5 ..1.5] input , converted to [-1.0 ...1.0] with "S" compressor gain shape.
if (out_mag_sq_lpf > 1.0f) {
out_mag_sq_lpf = 1.0f; // clipped signal at +1.0f, APT signal is positive, no need to clip -1.0
} else {
out_mag_sq_lpf = out_mag_sq_lpf * (1.5f - ((out_mag_sq_lpf * out_mag_sq_lpf) / 2.0f));
}
}
} /* namespace dsp */

View File

@@ -39,6 +39,19 @@ class HilbertTransform {
SOSFilter<5> sos_q = {};
};
class Real_to_Complex {
public:
Real_to_Complex(); // Additional initialization
void execute(float in, float& out_mag_sq_lpf);
private:
uint8_t n = 0;
SOSFilter<5> sos_input = {};
SOSFilter<5> sos_i = {};
SOSFilter<5> sos_q = {};
SOSFilter<5> sos_mag_sq = {};
};
} /* namespace dsp */
#endif /*__DSP_HILBERT_H__*/

View File

@@ -27,6 +27,7 @@
#include "event_m4.hpp"
#include <array>
#include "dsp_hilbert.hpp"
void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
if (!configured) {
@@ -44,16 +45,28 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
// TODO: Feed channel_stats post-decimation data?
feed_channel_stats(channel_out);
auto audio = demodulate(channel_out);
auto audio = demodulate(channel_out); // now 3 AM demodulation types : demod_am, demod_ssb, demod_ssb_fm (for Wefax)
audio_compressor.execute_in_place(audio);
audio_output.write(audio);
}
buffer_f32_t NarrowbandAMAudio::demodulate(const buffer_c16_t& channel) {
if (modulation_ssb) {
return demod_ssb.execute(channel, audio_buffer);
} else {
return demod_am.execute(channel, audio_buffer);
switch (modulation_ssb) { // enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
case (int)(AMConfigureMessage::Modulation::DSB):
return demod_am.execute(channel, audio_buffer);
break;
case (int)(AMConfigureMessage::Modulation::SSB):
return demod_ssb.execute(channel, audio_buffer);
break;
case (int)(AMConfigureMessage::Modulation::SSB_FM): // Added to handle Weather Fax mode.
// chDbgPanic("case SSB_FM demodulation"); // Debug.
return demod_ssb_fm.execute(channel, audio_buffer); // Calling a derivative of demod_ssb (USB) , but with different FIR taps + FM audio tones demod.
break;
default:
break;
}
}
@@ -98,8 +111,9 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
channel_filter_high_f = message.channel_filter.high_frequency_normalized * channel_filter_input_fs;
channel_filter_transition = message.channel_filter.transition_normalized * channel_filter_input_fs;
channel_spectrum.set_decimation_factor(1.0f);
modulation_ssb = (message.modulation == AMConfigureMessage::Modulation::SSB);
audio_output.configure(message.audio_hpf_config);
// modulation_ssb = (message.modulation == AMConfigureMessage::Modulation::SSB); // originally we had just 2 AM types of demod. (DSB , SSB)
modulation_ssb = (int)message.modulation; // now sending by message , 3 types of AM demod : enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
audio_output.configure(message.audio_hpf_lpf_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there).
configured = true;
}

View File

@@ -63,9 +63,11 @@ class NarrowbandAMAudio : public BasebandProcessor {
int32_t channel_filter_transition = 0;
bool configured{false};
bool modulation_ssb = false;
// bool modulation_ssb = false; // Origianlly we only had 2 AM demod types {DSB = 0, SSB = 1} , and we could handle it with bool var , 1 bit.
int8_t modulation_ssb = 0; // Now we have 3 AM demod types we will send now index integer {DSB = 0, SSB = 1, SSB_FM = 2}
dsp::demodulate::AM demod_am{};
dsp::demodulate::SSB demod_ssb{};
dsp::demodulate::SSB_FM demod_ssb_fm{}; // added for Wfax mode.
FeedForwardCompressor audio_compressor{};
AudioOutput audio_output{};

View File

@@ -553,14 +553,14 @@ constexpr fir_taps_real<32> taps_6k0_decim_2{
}},
};
// IFIR prototype filter fs=48000 ; pass=4500 (cutt off -3dBs) , stop=8000 (<-60dBs), decim=4, fout=12000
// IFIR prototype filter fs=48000 ; pass=4500 (cutoff -3dBs) , stop=8000 (<-60dBs), decim=4, fout=12000
// For Europe AM commercial broadcasting stations in LF/MF/HF, Emissions Designator 9K00A3E Bandwidth: 9.00 kHz (derivated from taps_6k0_decim_2 )
// Pre-decimate LPF FIR filter design Created with SciPy Python with the "window method", num_taps = 32, cut_off = 5150. sample_rate = 48000 # Hz,
// Created with h = signal.firwin(num_taps, cut_off, nyq=sample_rate/2, window=('chebwin',50)) , achieving good STOP band plot < -60 dB's with some ripple.
// Pre-decimate LPF FIR filter design Created with SciPy Python with the "window method", num_taps = 32, cutoff = 5150. sample_rate = 48000 # Hz,
// Created with h = signal.firwin(num_taps, cutoff, nyq=sample_rate/2, window=('chebwin',50)) , achieving good STOP band plot < -60 dB's with some ripple.
// post-scaled h taps to avoid decimals , targeting <= similar int values as previous taps_6k0_dsb_channel peak < 32.767 (2 exp 15) and similar H(f)gain
constexpr fir_taps_real<32> taps_9k0_decim_2{
.low_frequency_normalized = -4500.0f / 48000.0f, // Negative -cutt off freq -3dB (real achieved data ,in the plot and measurements)
.high_frequency_normalized = 4500.0f / 48000.0f, // Positive +cutt off freq -3dB (idem)
.low_frequency_normalized = -4500.0f / 48000.0f, // Negative -cutoff freq -3dB (real achieved data ,in the plot and measurements)
.high_frequency_normalized = 4500.0f / 48000.0f, // Positive +cutoff freq -3dB (idem)
.transition_normalized = 3500.0f / 48000.0f, // 3500 Hz = (8000 Hz - 4500 Hz) (both from plot H(f) curve plot)
.taps = {{-53, -30, 47, 198, 355, 372, 89, -535,
-1307, -1771, -1353, 370, 3384, 7109, 10535, 12591,
@@ -644,14 +644,14 @@ constexpr fir_taps_complex<64> taps_6k0_dsb_channel{
}},
};
// Channel filter: fs=12000, pass=4500 (cutt off -3dBs), stop=4940 (<-60dBs), decim=1, fout=12000 (*1) real frec pass / stop , based on plotted H(f) curve)
// Channel filter: fs=12000, pass=4500 (cutoff -3dBs), stop=4940 (<-60dBs), decim=1, fout=12000 (*1) real frec pass / stop , based on plotted H(f) curve)
// For Europe AM commercial broadcasting stations in LF/MF/HF, Emissions Designator 9K00A3E Bandwidth: 9.00 kHz (derivative from taps_6k0_dsb_channel)
// FIR filter design created with SciPy Python using "window method"; selected design parameters: num_taps = 64, cut_off = 4575. sample_rate = 12000 # Hz,
// Created with : h = signal.firwin(num_taps, cut_off, nyq=sample_rate/2, window=('chebwin',50)) , achieving real plot curve (*1) with peak stop band ripple -60dBs.
// FIR filter design created with SciPy Python using "window method"; selected design parameters: num_taps = 64, cutoff = 4575. sample_rate = 12000 # Hz,
// Created with : h = signal.firwin(num_taps, cutoff, nyq=sample_rate/2, window=('chebwin',50)) , achieving real plot curve (*1) with peak stop band ripple -60dBs.
// post-scaled h taps to avoid decimals , targeting <= similar int values as previous taps_6k0_dsb_channel peak < 32.767 (2 exp 15), (29625) and similar H(f)gain
constexpr fir_taps_complex<64> taps_9k0_dsb_channel{
.low_frequency_normalized = -4500.0f / 12000.0f, // Negative -cutt off freq -3dB (in the H(f) curve plot)
.high_frequency_normalized = 4500.0f / 12000.0f, // Positive +cutt off freq -3dB (in the H(f) curve plot)
.low_frequency_normalized = -4500.0f / 12000.0f, // Negative -cutoff freq -3dB (in the H(f) curve plot)
.high_frequency_normalized = 4500.0f / 12000.0f, // Positive +cutoff freq -3dB (in the H(f) curve plot)
.transition_normalized = 440.0f / 12000.0f, // 440Hz = (4940 Hz -4500 Hz) cut-3dB's (both data comes from H(f) curve plot and confirmed by measurements )
.taps = {{
{2, 0},
@@ -945,6 +945,82 @@ constexpr fir_taps_complex<64> taps_0k7_usb_channel{
}},
};
// USB AM+FM for Wefax (Weather fax RX) , based USB AM with truncated Differentiator band limmited cuttoff 2.400Hz for Audio Tones FM dem. ///////////////////
// IFIR prototype filter: fs=12000, pass=2600, stop=3200, decim=1, fout=12000 // stop band minimum att < -48 dB's (+3300 Hz min atten peak) , rest <50 to -60dB's
constexpr fir_taps_complex<64> taps_2k6_usb_wefax_channel{
.low_frequency_normalized = 0,
.high_frequency_normalized = 2600.0f / 12000.0f,
.transition_normalized = 600.0f / 12000.0f,
.taps = {{{-14 + 2},
{-11 - 5},
{-2 - 8},
{6 - 5},
{13 + 1},
{15 + 14},
{0 + 26},
{-22 + 13},
{-13 - 11},
{7 - 1},
{-20 + 17},
{-47 - 37},
{33 - 89},
{122 + 8},
{19 + 131},
{-124 + 26},
{1 - 123},
{158 + 52},
{-94 + 245},
{-363 - 91},
{36 - 468},
{524 - 37},
{67 + 531},
{-552 + 5},
{136 - 686},
{1013 + 258},
{-204 + 1527},
{-2104 + 168},
{-900 - 2529},
{2577 - 1881},
{2868 + 2122},
{-1209 + 3570},
{-3768 - 52},
{-1043 - 3412},
{2634 - 1801},
{2083 + 1693},
{-861 + 1927},
{-1507 - 318},
{95 - 1041},
{692 + 100},
{-189 + 519},
{-478 - 241},
{210 - 481},
{454 + 122},
{-35 + 372},
{-262 + 7},
{4 - 166},
{116 + 40},
{-66 + 108},
{-117 - 62},
{33 - 117},
{95 - 2},
{19 + 57},
{-23 + 13},
{3 - 7},
{6 + 16},
{-20 + 16},
{-25 - 9},
{-8 - 19},
{4 - 12},
{7 - 4},
{7 + 4},
{2 + 12},
{-7 + 13}
}}
};
// WFM 200KF8E emission type //////////////////////////////////////////////
// IFIR image-reject filter: fs=3072000, pass=100000, stop=484000, decim=4, fout=768000

View File

@@ -55,6 +55,12 @@ constexpr iir_biquad_config_t audio_12k_hpf_300hz_config{
{0.89485861f, -1.78971721f, 0.89485861f},
{1.00000000f, -1.77863178f, 0.80080265f}};
// scipy.signal.butter(2, 1500 / 6000.0, 'low', analog=False)
constexpr iir_biquad_config_t audio_12k_lpf_1500hz_config{
// Added to lpf the audio in wefax mode , before sending to SD card or spk.
{0.09763107f, 0.19526215f, 0.09763107f},
{1.00000000f, -0.94280904f, 0.33333333f}};
// scipy.signal.butter(2, 300 / 4000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_8k_hpf_300hz_config{
{0.84645925f, -1.69291851f, 0.84645925f},

View File

@@ -25,7 +25,7 @@
#include "dsp_iir.hpp"
// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5, rs = 60.0, Wn = 0.5, btype = 'lowpass', output="sos")
// 3khz cutofff @fs:12Khz , used in Hilbert
constexpr iir_biquad_df2_config_t half_band_lpf_config[5] = {
{0.02339042f, 0.0411599f, 0.02339042f, 1.0f, -0.95317621f, 0.33446485f},
{1.0f, 0.82196114f, 1.0f, 1.0f, -0.50327735f, 0.63611027f},
@@ -33,4 +33,22 @@ constexpr iir_biquad_df2_config_t half_band_lpf_config[5] = {
{1.0f, 0.14394122f, 1.0f, 1.0f, -0.04368236f, 0.94798064f},
{1.0f, 0.08720754, 1.0f, 1.0f, 0.00220944f, 0.98743139f}};
// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5, rs = 60.0, Wn = 0.99, btype = 'lowpass', output="sos")
// 6khz cutofff @fs:12Khz , used in WFAX demod.
constexpr iir_biquad_df2_config_t full_band_lpf_config[5] = {
{0.88095275f, 1.76184993f, 0.88095275f, 1.0f, 1.89055677f, 0.89616378f},
{1.0f, 1.99958798f, 1.0f, 1.0f, 1.9781807f, 0.98002549f},
{1.0f, 1.99928911f, 1.0f, 1.0f, 1.99328036f, 0.99447816f},
{1.0f, 1.99914562f, 1.0f, 1.0f, 1.997254f, 0.99828526f},
{1.0f, 1.99909558f, 1.0f, 1.0f, 1.9986187f, 0.99960319f}};
// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5 , rs = 60.0, Wn = 0.25, btype = 'lowpass', output="sos")
// 1.5khz cutofff @fs:12Khz, used in WFAX demod.
constexpr iir_biquad_df2_config_t quarter_band_lpf_config[5] = {
{0.00349312f, 0.00319397f, 0.00349312f, 1.0f, -1.53025211f, 0.6203438f},
{1.0f, -0.83483341f, 1.0f, 1.0f, -1.47619047f, 0.77120659f},
{1.0f, -1.23050154f, 1.0f, 1.0f, -1.43058949f, 0.9000896f},
{1.0f, -1.33837384f, 1.0f, 1.0f, -1.41007744f, 0.96349953f},
{1.0f, -1.36921549f, 1.0f, 1.0f, -1.40680439f, 0.9910884f}};
#endif /*__DSP_SOS_CONFIG_H__*/

View File

@@ -585,6 +585,7 @@ class AMConfigureMessage : public Message {
enum class Modulation : int32_t {
DSB = 0,
SSB = 1,
SSB_FM = 2, // Added new for RX Wefax mode, to demodulate APT signal ,FM modulated inside audio subcarrier tones, and then up broadcasted in SSB USB .
};
constexpr AMConfigureMessage(
@@ -593,14 +594,14 @@ class AMConfigureMessage : public Message {
const fir_taps_real<32> decim_2_filter,
const fir_taps_complex<64> channel_filter,
const Modulation modulation,
const iir_biquad_config_t audio_hpf_config)
const iir_biquad_config_t audio_hpf_lpf_config)
: Message{ID::AMConfigure},
decim_0_filter(decim_0_filter),
decim_1_filter(decim_1_filter),
decim_2_filter(decim_2_filter),
channel_filter(channel_filter),
modulation{modulation},
audio_hpf_config(audio_hpf_config) {
audio_hpf_lpf_config(audio_hpf_lpf_config) {
}
const fir_taps_real<24> decim_0_filter;
@@ -608,7 +609,7 @@ class AMConfigureMessage : public Message {
const fir_taps_real<32> decim_2_filter;
const fir_taps_complex<64> channel_filter;
const Modulation modulation;
const iir_biquad_config_t audio_hpf_config;
const iir_biquad_config_t audio_hpf_lpf_config;
};
// TODO: Put this somewhere else, or at least the implementation part.

View File

@@ -2651,6 +2651,9 @@ bool Waveform::is_paused() const {
void Waveform::set_paused(bool paused) {
paused_ = paused;
if (!paused) {
if_ever_painted_pause = false;
}
set_dirty();
}
@@ -2722,26 +2725,28 @@ void Waveform::paint(Painter& painter) {
// TODO: this is bad: that it still enter this func and still consume resources.
// even do a if(paused_) return; comsume too, but not that much.
// if (dirty()) {
// clear
// painter.fill_rectangle_unrolled8(screen_rect(), Theme::getInstance()->bg_darkest->background);
if (dirty() && !if_ever_painted_pause) {
// clear
painter.fill_rectangle_unrolled8(screen_rect(), Theme::getInstance()->bg_darkest->background);
// // draw "PAUSED" text
// const auto r = screen_rect();
// painter.draw_string(
// {r.center().x() - 24, r.center().y() - 8},
// style(),
// "PAUSED");
// draw "WF HIDDEN" text
const auto r = screen_rect();
painter.draw_string(
{r.center().x() - 24, r.center().y() - 8},
style(),
"WF HIDDEN");
if_ever_painted_pause = true;
}
if (show_cursors) {
for (uint32_t n = 0; n < 2; n++) {
painter.draw_vline(
Point(std::min(screen_rect().size().width(), (int)cursors[n]), screen_rect().location().y()),
screen_rect().size().height(),
cursor_colors[n]);
}
}
// if (show_cursors) {
// for (uint32_t n = 0; n < 2; n++) {
// painter.draw_vline(
// Point(std::min(screen_rect().size().width(), (int)cursors[n]), screen_rect().location().y()),
// screen_rect().size().height(),
// cursor_colors[n]);
// }
// }
// }
return;
}

View File

@@ -1011,6 +1011,7 @@ class Waveform : public Widget {
bool show_cursors{false};
bool paused_{false};
bool clickable_{false};
bool if_ever_painted_pause{false}; // for prevent the "hidden" label keeps painting and being expensive
};
class VuMeter : public Widget {

Binary file not shown.

Binary file not shown.