diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index 50198002..35951eff 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -175,6 +175,28 @@ static constexpr Bitmap bitmap_sd_card_unknown { { 16, 16 }, bitmap_sd_card_unknown_data }; +static constexpr uint8_t bitmap_play_data[] = { + 0x00, 0x00, + 0x00, 0x00, + 0x0C, 0x00, + 0x3C, 0x00, + 0xFC, 0x00, + 0xFC, 0x03, + 0xFC, 0x0F, + 0xFC, 0x3F, + 0xFC, 0x3F, + 0xFC, 0x0F, + 0xFC, 0x03, + 0xFC, 0x00, + 0x3C, 0x00, + 0x0C, 0x00, + 0x00, 0x00, + 0x00, 0x00, +}; +static constexpr Bitmap bitmap_play { + { 16, 16 }, bitmap_play_data +}; + static constexpr uint8_t bitmap_icon_stealth_data[] = { 0x00, 0x00, 0x00, 0x00, diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index f011252d..75d82490 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -319,6 +319,7 @@ public: Result write(const void* const data, const Size bytes_to_write); Result seek(const uint64_t Offset); + Size size(); template Result write(const std::array& data) { diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index 931e479f..09ca84cc 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -23,13 +23,13 @@ // Color bitmaps generated with: // Gimp image > indexed colors (16), then "xxd -i *.bmp" -//BUG: Length is wrong in soundboard for long files (> 1min ?) //BUG: RDS Radiotext is not recognized in Redsea (and car radio) //BUG: RDS doesn't stop baseband when stopping tx ? //BUG: Check AFSK transmit end, skips last bits ? //TEST: Imperial in whipcalc +//TODO: IQ replay //TODO: Optimize (and group ?) CTCSS tone gen code //TODO: Morse use prosigns //TODO: Morse live keying mode ? @@ -49,7 +49,6 @@ Continuous (Fox-oring) //TODO: Script engine ? //TODO: Close Call multiple slices (buggy) //TODO: Finish EPAR tx -//TODO: IQ replay //TODO: Wav visualizer //TODO: File browser view ? diff --git a/firmware/application/replay_app.cpp b/firmware/application/replay_app.cpp index 01f8edf1..c84ec945 100644 --- a/firmware/application/replay_app.cpp +++ b/firmware/application/replay_app.cpp @@ -21,6 +21,7 @@ */ #include "replay_app.hpp" +#include "string_format.hpp" #include "baseband_api.hpp" @@ -32,20 +33,31 @@ using namespace portapack; namespace ui { -ReplayAppView::ReplayAppView(NavigationView& nav) { - baseband::run_image(portapack::spi_flash::image_tag_replay); +ReplayAppView::ReplayAppView( + NavigationView& nav +) : nav_ (nav) +{ + std::vector file_list; + + // Search for files + file_list = scan_root_files(u"/", u"*.C16"); + if (!file_list.size()) { + file_error = true; + return; + } + + //baseband::run_image(portapack::spi_flash::image_tag_replay); add_children({ - &channel, &field_frequency, &field_frequency_step, &field_rf_amp, - &field_lna, - &field_vga, &replay_view, &waterfall, }); - + + replay_view.set_file_list(file_list); + field_frequency.set_value(target_frequency()); field_frequency.set_step(receiver_model.frequency_step()); field_frequency.on_change = [this](rf::Frequency f) { @@ -66,17 +78,6 @@ ReplayAppView::ReplayAppView(NavigationView& nav) { this->field_frequency.set_step(v); }; - radio::enable({ - target_frequency(), - sampling_rate, - baseband_bandwidth, - rf::Direction::Transmit, - receiver_model.rf_amp(), - static_cast(receiver_model.lna()), - static_cast(receiver_model.vga()) - }); - - replay_view.set_sampling_rate(sampling_rate / 8); replay_view.on_error = [&nav](std::string message) { nav.display_modal("Error", message); }; diff --git a/firmware/application/replay_app.hpp b/firmware/application/replay_app.hpp index 8d86e513..b5f7ebd6 100644 --- a/firmware/application/replay_app.hpp +++ b/firmware/application/replay_app.hpp @@ -45,9 +45,13 @@ public: void focus() override; - std::string title() const override { return "Capture"; }; + std::string title() const override { return "Replay (beta)"; }; private: + NavigationView& nav_; + + bool file_error { false }; + static constexpr ui::Dim header_height = 2 * 16; static constexpr uint32_t sampling_rate = 4000000; @@ -58,10 +62,6 @@ private: rf::Frequency target_frequency() const; void set_target_frequency(const rf::Frequency new_value); - Channel channel { - { 24 * 8, 5, 6 * 8, 4 }, - }; - FrequencyField field_frequency { { 0 * 8, 0 * 16 }, }; @@ -74,20 +74,12 @@ private: { 16 * 8, 0 * 16 } }; - LNAGainField field_lna { - { 18 * 8, 0 * 16 } - }; - - VGAGainField field_vga { - { 21 * 8, 0 * 16 } - }; - ReplayView replay_view { { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, - "BBD_????", ReplayView::FileType::RawS16, 16384, 3 + 16384, 3 }; - spectrum::WaterfallWidget waterfall; + spectrum::WaterfallWidget waterfall { }; }; } /* namespace ui */ diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 11d0be14..034cad0f 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -358,10 +358,10 @@ SystemMenuView::SystemMenuView(NavigationView& nav) { { "Play dead", ui::Color::red(), &bitmap_icon_playdead, [&nav](){ nav.push(); } }, { "Receivers", ui::Color::cyan(), &bitmap_icon_receivers, [&nav](){ nav.push(); } }, { "Capture", ui::Color::blue(), &bitmap_icon_capture, [&nav](){ nav.push(); } }, - { "Replay", ui::Color::grey(), &bitmap_icon_replay, [&nav](){ nav.push(); } }, + { "Replay", ui::Color::grey(), &bitmap_icon_replay, [&nav](){ nav.push(); } }, // ReplayAppView { "Audio transmitters", ui::Color::green(), &bitmap_icon_audiotx, [&nav](){ nav.push(); } }, { "Code transmitters", ui::Color::green(), &bitmap_icon_codetx, [&nav](){ nav.push(); } }, - { "SSTV transmitter", ui::Color::dark_orange(), &bitmap_icon_sstv, [&nav](){ nav.push(); } }, + { "SSTV transmitter", ui::Color::dark_green(), &bitmap_icon_sstv, [&nav](){ nav.push(); } }, { "Close Call", ui::Color::cyan(), &bitmap_icon_closecall, [&nav](){ nav.push(); } }, { "Jammer", ui::Color::orange(),&bitmap_icon_jammer, [&nav](){ nav.push(); } }, { "Utilities", ui::Color::purple(),&bitmap_icon_utilities, [&nav](){ nav.push(); } }, diff --git a/firmware/application/ui_replay_view.cpp b/firmware/application/ui_replay_view.cpp index f597e644..d31a6b9d 100644 --- a/firmware/application/ui_replay_view.cpp +++ b/firmware/application/ui_replay_view.cpp @@ -37,28 +37,38 @@ using namespace portapack; namespace ui { +void ReplayView::on_file_changed(const uint32_t duration) { + std::string str_duration = ""; + + if (duration >= 60) + str_duration = to_string_dec_uint(duration / 60) + "m"; + + text_duration.set(str_duration + to_string_dec_uint(duration % 60) + "s"); +} + ReplayView::ReplayView( const Rect parent_rect, - std::string filename, - const FileType file_type, const size_t read_size, const size_t buffer_count ) : View { parent_rect }, - filename { filename }, - file_type { file_type }, read_size { read_size }, buffer_count { buffer_count } { add_children({ &rect_background, - &button_record, - &text_replay_filename, - &text_time_seek, + &button_play, + &options_files, + &text_duration, + //&text_time_seek, }); rect_background.set_parent_rect({ { 0, 0 }, size() }); - button_record.on_select = [this](ImageButton&) { + options_files.on_change = [this](size_t, int32_t duration) { + this->on_file_changed(duration); + }; + + button_play.on_select = [this](ImageButton&) { this->toggle(); }; @@ -72,21 +82,22 @@ ReplayView::~ReplayView() { } void ReplayView::focus() { - button_record.focus(); + options_files.focus(); } -void ReplayView::set_sampling_rate(const size_t new_sampling_rate) { - if( new_sampling_rate != sampling_rate ) { - stop(); - sampling_rate = new_sampling_rate; - - button_record.hidden(sampling_rate == 0); - text_replay_filename.hidden(sampling_rate == 0); - text_time_seek.hidden(sampling_rate == 0); - rect_background.hidden(sampling_rate != 0); - - update_status_display(); +void ReplayView::set_file_list(const std::vector& file_list) { + File bbd_file; + uint32_t duration; + + for (const auto& file : file_list) { + bbd_file.open("/" + file.string()); + duration = bbd_file.size() / (2 * 2 * (sampling_rate / 8)); + file_options.emplace_back(file.string().substr(0, 8), duration); } + options_files.set_options(file_options); + options_files.set_selected_index(0); // First file + on_file_changed(file_options[0].second); + } bool ReplayView::is_active() const { @@ -104,17 +115,10 @@ void ReplayView::toggle() { void ReplayView::start() { stop(); - text_replay_filename.set(""); - - if( sampling_rate == 0 ) { - return; - } - auto reader = std::make_unique(); if( reader ) { - text_replay_filename.set(filename.string()); - button_record.set_bitmap(&bitmap_stop); + button_play.set_bitmap(&bitmap_stop); replay_thread = std::make_unique( std::move(reader), read_size, buffer_count, @@ -130,12 +134,23 @@ void ReplayView::start() { } update_status_display(); + + radio::enable({ + 460000000, //target_frequency(), + 4000000, //sampling_rate, + 2500000, //baseband_bandwidth, + rf::Direction::Transmit, + receiver_model.rf_amp(), + static_cast(receiver_model.lna()), + static_cast(receiver_model.vga()) + }); } void ReplayView::stop() { if( is_active() ) { replay_thread.reset(); - button_record.set_bitmap(&bitmap_record); + radio::disable(); + button_play.set_bitmap(&bitmap_play); } update_status_display(); diff --git a/firmware/application/ui_replay_view.hpp b/firmware/application/ui_replay_view.hpp index ad35f066..b0ff71a6 100644 --- a/firmware/application/ui_replay_view.hpp +++ b/firmware/application/ui_replay_view.hpp @@ -37,17 +37,10 @@ namespace ui { class ReplayView : public View { public: - std::function on_error; - - enum FileType { - RawS16 = 2, - WAV = 3, - }; + std::function on_error { }; ReplayView( const Rect parent_rect, - std::string filename, - FileType file_type, const size_t read_size, const size_t buffer_count ); @@ -55,7 +48,7 @@ public: void focus() override; - void set_sampling_rate(const size_t new_sampling_rate); + void set_file_list(const std::vector& file_list); void start(); void stop(); @@ -63,43 +56,53 @@ public: bool is_active() const; private: + using option_t = std::pair; + using options_t = std::vector; + + static constexpr uint32_t sampling_rate = 4000000; + void toggle(); + void on_file_changed(const uint32_t duration); void on_tick_second(); void update_status_display(); void handle_replay_thread_done(const File::Error error); void handle_error(const File::Error error); - const std::filesystem::path filename; - const FileType file_type; const size_t read_size; const size_t buffer_count; - size_t sampling_rate { 0 }; - SignalToken signal_token_tick_second; + SignalToken signal_token_tick_second { }; + options_t file_options { }; Rectangle rect_background { Color::black() }; - ImageButton button_record { - { 4 * 8, 0 * 16, 2 * 8, 1 * 16 }, - &bitmap_record, - Color::red(), + ImageButton button_play { + { 0 * 8, 0 * 16, 2 * 8, 1 * 16 }, + &bitmap_play, + Color::green(), Color::black() }; - Text text_replay_filename { - { 7 * 8, 0 * 16, 8 * 8, 16 }, - "", + OptionsField options_files { + { 2 * 8, 0 * 8 }, + 8, + { } + }; + + Text text_duration { + { 11 * 8, 0 * 8, 12 * 8, 16 }, + "-" }; - Text text_time_seek { - { 21 * 8, 0 * 16, 9 * 8, 16 }, + /*Text text_time_seek { + { 18 * 8, 0 * 16, 9 * 8, 16 }, "", - }; + };*/ - std::unique_ptr replay_thread; + std::unique_ptr replay_thread { }; MessageHandlerRegistration message_handler_capture_thread_error { Message::ID::CaptureThreadDone, diff --git a/firmware/application/ui_sstvtx.cpp b/firmware/application/ui_sstvtx.cpp index 9117e3c8..01bdb3c7 100644 --- a/firmware/application/ui_sstvtx.cpp +++ b/firmware/application/ui_sstvtx.cpp @@ -99,6 +99,7 @@ void SSTVTXView::on_tuning_frequency_changed(rf::Frequency f) { void SSTVTXView::prepare_scanline() { sstv_scanline scanline_buffer; uint32_t component, pixel_idx; + uint8_t offset; if (scanline_counter >= (256 * 3)) { progressbar.set_value(0); @@ -122,31 +123,33 @@ void SSTVTXView::prepare_scanline() { // Scanline time: 88.064ms (275.2us/pixel @ 320 pixels/line) component = scanline_counter % 3; - - if ((!scanline_counter) || (component == 2)) { + + if ((!scanline_counter && tx_sstv_mode->sync_on_first) || (component == tx_sstv_mode->sync_index)) { + // Sync scanline_buffer.start_tone.frequency = SSTV_F2D(1200); - scanline_buffer.start_tone.duration = SSTV_MS2S(9); - } else + scanline_buffer.start_tone.duration = tx_sstv_mode->samples_per_sync; + scanline_buffer.gap_tone.frequency = SSTV_F2D(1500); + scanline_buffer.gap_tone.duration = tx_sstv_mode->samples_per_gap; + } else { + // Regular scanline scanline_buffer.start_tone.duration = 0; + if (tx_sstv_mode->gaps) { + scanline_buffer.gap_tone.frequency = SSTV_F2D(1500); + scanline_buffer.gap_tone.duration = tx_sstv_mode->samples_per_gap; + } + } - scanline_buffer.gap_tone.frequency = SSTV_F2D(1500); - scanline_buffer.gap_tone.duration = SSTV_MS2S(1.5); - - if (component == 0) { + if (!component) { // Read a new line read_boundary(pixels_buffer, bmp_header.image_data + ((255 - (scanline_counter / 3)) * sizeof(pixels_buffer)), sizeof(pixels_buffer)); } + offset = component_map[component]; for (uint32_t bmp_px = 0; bmp_px < 320; bmp_px++) { pixel_idx = bmp_px * 3; - if (component == 0) - scanline_buffer.luma[bmp_px] = pixels_buffer[pixel_idx + 1]; // Green - else if (component == 1) - scanline_buffer.luma[bmp_px] = pixels_buffer[pixel_idx]; // Blue - else - scanline_buffer.luma[bmp_px] = pixels_buffer[pixel_idx + 2]; // Red + scanline_buffer.luma[bmp_px] = pixels_buffer[pixel_idx + offset]; } baseband::set_fifo_data((int8_t *)&scanline_buffer); @@ -155,15 +158,12 @@ void SSTVTXView::prepare_scanline() { } void SSTVTXView::start_tx() { - // Baseband SSTV TX code should have a 2 scanlines buffer, and ask - // for fill-up when there's 1 or less remaining. This should leave - // enough time for the code here to generate the scanline data - // before tx. See sstv.hpp: - - // Scottie 2 is 320x256 px + // The baseband SSTV TX code (proc_sstv) has a 2-scanline buffer. It is preloaded before + // TX start, and asks for fill-up when a new scanline starts being read. This should + // leave enough time for the code in prepare_scanline() before it ends. scanline_counter = 0; - prepare_scanline(); + prepare_scanline(); // Preload one scanline transmitter_model.set_sampling_rate(3072000U); transmitter_model.set_rf_amp(true); @@ -171,8 +171,8 @@ void SSTVTXView::start_tx() { transmitter_model.enable(); baseband::set_sstv_data( - 0b00011101, // Scottie 2, 275.2us/px - (uint32_t)(0.0002752 * 3072000.0) + tx_sstv_mode->vis_code, + tx_sstv_mode->samples_per_pixel ); // Todo: Find a better way to prevent user from changing bitmap during tx @@ -180,13 +180,31 @@ void SSTVTXView::start_tx() { tx_view.focus(); } -void SSTVTXView::on_bitmap_changed(size_t index) { +void SSTVTXView::on_bitmap_changed(const size_t index) { bmp_file.open("/sstv/" + bitmaps[index].string()); bmp_file.read(&bmp_header, sizeof(bmp_header)); - progressbar.set_max(256 * 3); set_dirty(); } +void SSTVTXView::on_mode_changed(const size_t index) { + sstv_color_seq tx_color_sequence; + + tx_sstv_mode = &sstv_modes[index]; + + tx_color_sequence = sstv_modes[index].color_sequence; + if (tx_color_sequence == SSTV_COLOR_RGB) { + component_map[0] = 2; + component_map[1] = 1; + component_map[2] = 0; + } else if (tx_color_sequence == SSTV_COLOR_GBR) { + component_map[0] = 1; + component_map[1] = 0; + component_map[2] = 2; + } + + progressbar.set_max(sstv_modes[index].lines * 3); +} + SSTVTXView::SSTVTXView( NavigationView& nav ) : nav_ (nav) @@ -195,7 +213,9 @@ SSTVTXView::SSTVTXView( using option_t = std::pair; using options_t = std::vector; options_t bitmap_options; - + options_t mode_options; + uint32_t c; + // Search for valid bitmaps file_list = scan_root_files(u"/sstv", u"*.bmp"); if (!file_list.size()) { @@ -205,11 +225,11 @@ SSTVTXView::SSTVTXView( for (const auto& file_name : file_list) { if (!bmp_file.open("/sstv/" + file_name.string()).is_valid()) { bmp_file.read(&bmp_header, sizeof(bmp_header)); - if ((bmp_header.signature == 0x4D42) && - (bmp_header.width == 320) && // Must be == 320x256 pixels for now + if ((bmp_header.signature == 0x4D42) && // "BM" + (bmp_header.width == 320) && // Must be exactly 320x256 pixels for now (bmp_header.height == 256) && (bmp_header.planes == 1) && - (bmp_header.bpp >= 24) && // 24 or 32 bpp + (bmp_header.bpp == 24) && // 24 bpp only (bmp_header.compression == 0)) { // No compression bitmaps.push_back(file_name); } @@ -227,21 +247,33 @@ SSTVTXView::SSTVTXView( add_children({ &labels, &options_bitmaps, - &text_mode, + &options_modes, &progressbar, &tx_view }); + // Populate file list for (const auto& bitmap : bitmaps) bitmap_options.emplace_back(bitmap.string().substr(0, 16), 0); - options_bitmaps.set_options(bitmap_options); + + // Populate mode list + for (c = 0; c < SSTV_MODES_NB; c++) + mode_options.emplace_back(sstv_modes[c].name, c); + options_modes.set_options(mode_options); + options_bitmaps.on_change = [this](size_t i, int32_t) { this->on_bitmap_changed(i); }; - options_bitmaps.set_selected_index(0); + options_bitmaps.set_selected_index(0); // First file on_bitmap_changed(0); + options_modes.on_change = [this](size_t i, int32_t) { + this->on_mode_changed(i); + }; + options_modes.set_selected_index(1); // Scottie 2 + on_mode_changed(1); + tx_view.on_edit_frequency = [this, &nav]() { auto new_view = nav.push(receiver_model.tuning_frequency()); new_view->on_changed = [this](rf::Frequency f) { diff --git a/firmware/application/ui_sstvtx.hpp b/firmware/application/ui_sstvtx.hpp index c83b8e49..8c320680 100644 --- a/firmware/application/ui_sstvtx.hpp +++ b/firmware/application/ui_sstvtx.hpp @@ -43,6 +43,11 @@ class SSTVTXView : public View { public: SSTVTXView(NavigationView& nav); ~SSTVTXView(); + + SSTVTXView(const SSTVTXView&) = delete; + SSTVTXView(SSTVTXView&&) = delete; + SSTVTXView& operator=(const SSTVTXView&) = delete; + SSTVTXView& operator=(SSTVTXView&&) = delete; void focus() override; void paint(Painter&) override; @@ -54,21 +59,26 @@ private: sstv_scanline scanline_buffer { }; + bool file_error { false }; File bmp_file { }; bmp_header_t bmp_header { }; std::vector bitmaps { }; - bool file_error { false }; uint32_t scanline_counter { 0 }; uint8_t pixels_buffer[320 * 3]; // 320 pixels @ 24bpp + const sstv_mode * tx_sstv_mode { }; + + uint8_t component_map[3] { }; void read_boundary(uint8_t * buffer, uint32_t position, uint32_t length); - void on_bitmap_changed(size_t index); + void on_bitmap_changed(const size_t index); + void on_mode_changed(const size_t index); void on_tuning_frequency_changed(rf::Frequency f); void start_tx(); void prepare_scanline(); Labels labels { - { { 1 * 8, 1 * 8 }, "File:", Color::light_grey() } + { { 1 * 8, 1 * 8 }, "File:", Color::light_grey() }, + { { 1 * 8, 3 * 8 }, "Mode:", Color::light_grey() } }; OptionsField options_bitmaps { @@ -76,9 +86,10 @@ private: 16, { } }; - Text text_mode { - { 2 * 8, 4 * 8, 16 * 8, 16 }, - "Scottie 2" + OptionsField options_modes { + { 6 * 8, 3 * 8 }, + 16, + { } }; ProgressBar progressbar { diff --git a/firmware/baseband/proc_replay.cpp b/firmware/baseband/proc_replay.cpp index 34e7b78e..9cf936a5 100644 --- a/firmware/baseband/proc_replay.cpp +++ b/firmware/baseband/proc_replay.cpp @@ -39,13 +39,17 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) { //const auto& decimator_out = decim_1_out; //const auto& channel = decimator_out; - //if( stream ) { - // const size_t bytes_to_write = sizeof(*decimator_out.p) * decimator_out.count; - // const auto result = stream->write(decimator_out.p, bytes_to_write); - //} + if( stream ) { + const size_t bytes_to_read = buffer.count; // ? + const auto result = stream->read(iq_buffer.p, bytes_to_read); + } //feed_channel_stats(channel); - + + for (size_t i = 0; i < buffer.count; i++) { + buffer.p[i] = { iq_buffer.p[i].real() >> 8, iq_buffer.p[i].imag() >> 8}; + } + /*spectrum_samples += channel.count; if( spectrum_samples >= spectrum_interval_samples ) { spectrum_samples -= spectrum_interval_samples; @@ -60,7 +64,7 @@ void ReplayProcessor::on_message(const Message* const message) { channel_spectrum.on_message(message); break;*/ - case Message::ID::ReplayConfig: + case Message::ID::CaptureConfig: replay_config(*reinterpret_cast(message)); break; diff --git a/firmware/baseband/proc_replay.hpp b/firmware/baseband/proc_replay.hpp index 34317845..2e15c089 100644 --- a/firmware/baseband/proc_replay.hpp +++ b/firmware/baseband/proc_replay.hpp @@ -52,13 +52,13 @@ private: BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit }; //RSSIThread rssi_thread { NORMALPRIO + 10 }; - std::array dst; - const buffer_c16_t dst_buffer { - dst.data(), - dst.size() + std::array iq { }; + const buffer_c16_t iq_buffer { + iq.data(), + iq.size() }; - std::unique_ptr stream; + std::unique_ptr stream { }; /*SpectrumCollector channel_spectrum; size_t spectrum_interval_samples = 0; diff --git a/firmware/baseband/proc_sstvtx.cpp b/firmware/baseband/proc_sstvtx.cpp index c1f5e111..e11ede10 100644 --- a/firmware/baseband/proc_sstvtx.cpp +++ b/firmware/baseband/proc_sstvtx.cpp @@ -40,7 +40,7 @@ void SSTVTXProcessor::execute(const buffer_c8_t& buffer) { // of the scanline. Todo: simplify ! if (state == STATE_CALIBRATION) { - // Once per scanline + // Once per picture tone_delta = calibration_sequence[substep].first; sample_count = calibration_sequence[substep].second; if (substep == 2) { @@ -49,17 +49,15 @@ void SSTVTXProcessor::execute(const buffer_c8_t& buffer) { } else substep++; } else if (state == STATE_VIS) { - // Once per scanline + // Once per picture if (substep == 10) { current_scanline = &scanline_buffer[buffer_flip]; buffer_flip ^= 1; - if (asked == false) { - // Ask application for a new scanline - shared_memory.application_queue.push(sig_message); - asked = true; - } + // Ask application for a new scanline + shared_memory.application_queue.push(sig_message); + // Do we have to transmit a start tone ? if (current_scanline->start_tone.duration) { - state = STATE_START; + state = STATE_SYNC; tone_delta = current_scanline->start_tone.frequency; sample_count = current_scanline->start_tone.duration; } else { @@ -69,23 +67,19 @@ void SSTVTXProcessor::execute(const buffer_c8_t& buffer) { } } else { tone_delta = vis_code_sequence[substep]; - sample_count = SSTV_MS2S(30); // VIS code bit is 30ms + sample_count = SSTV_MS2S(30); // A VIS code bit is 30ms substep++; } - } else if (state == STATE_START) { + } else if (state == STATE_SYNC) { // Once per scanline, optional state = STATE_PIXELS; tone_delta = current_scanline->gap_tone.frequency; sample_count = current_scanline->gap_tone.duration; } else if (state == STATE_PIXELS) { // Many times per scanline - pixel_luma = current_scanline->luma[pixel_index]; - - pixel_index++; - - tone_delta = SSTV_F2D(1500 + ((pixel_luma * 800) / 256)); - + tone_delta = SSTV_F2D(1500 + ((current_scanline->luma[pixel_index] * 800) / 256)); sample_count = pixel_duration; + pixel_index++; if (pixel_index >= 320) { // Scanline done, (dirty) state jump @@ -98,6 +92,7 @@ void SSTVTXProcessor::execute(const buffer_c8_t& buffer) { sample_count--; } + // Tone synth tone_sample = (sine_table_i8[(tone_phase & 0xFF000000U) >> 24]); tone_phase += tone_delta; @@ -116,29 +111,27 @@ void SSTVTXProcessor::execute(const buffer_c8_t& buffer) { void SSTVTXProcessor::on_message(const Message* const msg) { const auto message = *reinterpret_cast(msg); + uint8_t vis_code; switch(msg->id) { case Message::ID::SSTVConfigure: pixel_duration = message.pixel_duration; if (!pixel_duration) { - configured = false; + configured = false; // Shutdown return; } vis_code = message.vis_code; // VIS code: - // 1200 - // 00011101 (0=1300, 1=1100) - // 1200 + // 1200, (0=1300, 1=1100), 1200 vis_code_sequence[0] = SSTV_VIS_SS; - for (uint32_t c = 0; c < 8; c++) { - vis_code_sequence[c + 1] = ((vis_code << c) & 0x80) ? SSTV_VIS_ONE : SSTV_VIS_ZERO; - } + for (uint32_t c = 0; c < 8; c++) + vis_code_sequence[c + 1] = ((vis_code >> c) & 1) ? SSTV_VIS_ONE : SSTV_VIS_ZERO; vis_code_sequence[9] = SSTV_VIS_SS; - fm_delta = 9000 * (0xFFFFFFULL / 3072000); // Fixed for now + fm_delta = 9000 * (0xFFFFFFULL / 3072000); // Fixed bw for now pixel_index = 0; sample_count = 0; @@ -151,7 +144,6 @@ void SSTVTXProcessor::on_message(const Message* const msg) { case Message::ID::FIFOData: memcpy(&scanline_buffer[buffer_flip], static_cast(msg)->data, sizeof(sstv_scanline)); - asked = false; break; default: diff --git a/firmware/baseband/proc_sstvtx.hpp b/firmware/baseband/proc_sstvtx.hpp index c213d45c..9e217d6f 100644 --- a/firmware/baseband/proc_sstvtx.hpp +++ b/firmware/baseband/proc_sstvtx.hpp @@ -50,7 +50,7 @@ private: enum state_t { STATE_CALIBRATION = 0, STATE_VIS, - STATE_START, + STATE_SYNC, STATE_PIXELS }; @@ -63,8 +63,6 @@ private: uint32_t vis_code_sequence[10] { }; sstv_scanline scanline_buffer[2] { }; uint8_t buffer_flip { 0 }, substep { 0 }; - - uint8_t vis_code { }; uint32_t pixel_duration { }; sstv_scanline * current_scanline { }; @@ -77,9 +75,7 @@ private: uint32_t sample_count { 0 }; uint32_t phase { 0 }, sphase { 0 }; int32_t tone_sample { 0 }, delta { 0 }; - int8_t re { 0 }, im { 0 }; - - bool asked { false }; + int8_t re { }, im { }; RequestSignalMessage sig_message { RequestSignalMessage::Signal::FillRequest }; }; diff --git a/firmware/baseband/stream_output.cpp b/firmware/baseband/stream_output.cpp index e70337e8..4d1d8b28 100644 --- a/firmware/baseband/stream_output.cpp +++ b/firmware/baseband/stream_output.cpp @@ -40,7 +40,7 @@ StreamOutput::StreamOutput(ReplayConfig* const config) : } } -size_t StreamOutput::write(const void* const data, const size_t length) { +size_t StreamOutput::read(const void* const data, const size_t length) { const uint8_t* p = static_cast(data); size_t written = 0; diff --git a/firmware/baseband/stream_output.hpp b/firmware/baseband/stream_output.hpp index 55be916a..ae9de5d0 100644 --- a/firmware/baseband/stream_output.hpp +++ b/firmware/baseband/stream_output.hpp @@ -34,8 +34,13 @@ class StreamOutput { public: StreamOutput(ReplayConfig* const config); + + StreamOutput(const StreamOutput&) = delete; + StreamOutput(StreamOutput&&) = delete; + StreamOutput& operator=(const StreamOutput&) = delete; + StreamOutput& operator=(StreamOutput&&) = delete; - size_t write(const void* const data, const size_t length); + size_t read(const void* const data, const size_t length); private: static constexpr size_t buffer_count_max_log2 = 3; diff --git a/firmware/common/sstv.hpp b/firmware/common/sstv.hpp index 50ec09fd..c4d72ec0 100644 --- a/firmware/common/sstv.hpp +++ b/firmware/common/sstv.hpp @@ -29,12 +29,28 @@ namespace sstv { #define SSTV_DELTA_COEF ((1ULL << 32) / SSTV_SAMPLERATE) #define SSTV_F2D(f) (uint32_t)((f) * SSTV_DELTA_COEF) -#define SSTV_MS2S(f) (uint32_t)((f) / 1000.0 * (float)SSTV_SAMPLERATE) +#define SSTV_MS2S(d) (uint32_t)((d) / 1000.0 * (float)SSTV_SAMPLERATE) #define SSTV_VIS_SS SSTV_F2D(1200) #define SSTV_VIS_ZERO SSTV_F2D(1300) #define SSTV_VIS_ONE SSTV_F2D(1100) +enum sstv_color_seq { + SSTV_COLOR_RGB, + SSTV_COLOR_GBR, + SSTV_COLOR_YUV // Not supported for now +}; + +#define SSTV_MODES_NB 6 + +// From http://www.graphics.stanford.edu/~seander/bithacks.html, nice ! +inline uint8_t sstv_parity(uint8_t code) { + uint8_t out = code; + out ^= code >> 4; + out &= 0x0F; + return (((0b0110100110010110 >> out) & 1) << 7) | code; +} + struct sstv_tone { uint32_t frequency; uint32_t duration; @@ -46,6 +62,33 @@ struct sstv_scanline { uint8_t luma[320]; }; +struct sstv_mode { + std::string name; + uint8_t vis_code; + bool color; // Unused for now + sstv_color_seq color_sequence; + uint16_t pixels; + uint16_t lines; + uint32_t samples_per_pixel; + bool sync_on_first; + uint8_t sync_index; + bool gaps; + uint32_t samples_per_sync; + uint32_t samples_per_gap; + //std::pair luma_range; +}; + +const sstv_mode sstv_modes[SSTV_MODES_NB] = { + { "Scottie 1", sstv_parity(60), true, SSTV_COLOR_GBR, 320, 256, SSTV_MS2S(0.4320), true, 2, true, SSTV_MS2S(9), SSTV_MS2S(1.5) }, + { "Scottie 2", sstv_parity(56), true, SSTV_COLOR_GBR, 320, 256, SSTV_MS2S(0.2752), true, 2, true, SSTV_MS2S(9), SSTV_MS2S(1.5) }, + { "Scottie DX", sstv_parity(76), true, SSTV_COLOR_GBR, 320, 256, SSTV_MS2S(1.08), true, 2, true, SSTV_MS2S(9), SSTV_MS2S(1.5) }, + { "Martin 1", sstv_parity(44), true, SSTV_COLOR_GBR, 320, 256, SSTV_MS2S(0.4576), false, 0, true, SSTV_MS2S(4.862), SSTV_MS2S(0.572) }, + { "Martin 2", sstv_parity(40), true, SSTV_COLOR_GBR, 320, 256, SSTV_MS2S(0.2288), false, 0, true, SSTV_MS2S(4.862), SSTV_MS2S(0.572) }, + { "SC2-180", sstv_parity(55), true, SSTV_COLOR_RGB, 320, 256, SSTV_MS2S(0.7344), false, 0, false, SSTV_MS2S(5.5225), SSTV_MS2S(0.5) }, + //{ "PASOKON 3", sstv_parity(113), true, SSTV_COLOR_RGB, 640, 496, SSTV_MS2S(0.2083), { 1500, 2300 } }, + //{ "PASOKON 7", sstv_parity(115), true, SSTV_COLOR_RGB, 640, 496, SSTV_MS2S(0.4167), { 1500, 2300 } } +}; + } /* namespace sstv */ #endif/*__SSTV_H__*/ diff --git a/firmware/graphics/play.png b/firmware/graphics/play.png new file mode 100644 index 00000000..04b0b2d5 Binary files /dev/null and b/firmware/graphics/play.png differ diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index d14ce394..dcc0eaf1 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ