diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 03d2629f..18d4c45e 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -217,7 +217,7 @@ set(CPPSRC apps/ui_aprs_tx.cpp apps/ui_bht_tx.cpp apps/ui_coasterp.cpp - apps/ui_debug.cpp + # apps/ui_debug.cpp apps/ui_encoders.cpp apps/ui_fileman.cpp apps/ui_freqman.cpp diff --git a/firmware/application/apps/pocsag_app.cpp b/firmware/application/apps/pocsag_app.cpp index b1932188..6808e631 100644 --- a/firmware/application/apps/pocsag_app.cpp +++ b/firmware/application/apps/pocsag_app.cpp @@ -142,7 +142,7 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage * message) { std::string alphanum_text = ""; if (message->packet.flag() != NORMAL) - console.writeln("\n\x1B\x0CRX ERROR: " + pocsag::flag_str(message->packet.flag())); + console.writeln("\n\x1B\x0CRC ERROR: " + pocsag::flag_str(message->packet.flag())); else { pocsag_decode_batch(message->packet, &pocsag_state); diff --git a/firmware/application/apps/replay_app.cpp b/firmware/application/apps/replay_app.cpp index be34e619..8bde0c2f 100644 --- a/firmware/application/apps/replay_app.cpp +++ b/firmware/application/apps/replay_app.cpp @@ -161,6 +161,8 @@ void ReplayAppView::stop(const bool do_loop) { radio::disable(); button_play.set_bitmap(&bitmap_play); } + + ready_signal = false; } void ReplayAppView::handle_replay_thread_done(const uint32_t return_code) { diff --git a/firmware/application/apps/ui_about.cpp b/firmware/application/apps/ui_about.cpp index faecaa0a..ce09961b 100644 --- a/firmware/application/apps/ui_about.cpp +++ b/firmware/application/apps/ui_about.cpp @@ -62,10 +62,11 @@ void CreditsWidget::on_hide() { void CreditsWidget::new_row( const std::array& pixel_row ) { + // Glitch be here (see comment in main.cpp) const auto draw_y = display.scroll(-1); display.draw_pixels( - { { 0, draw_y }, { 240, 1 } }, + { { 0, draw_y - 1 }, { 240, 1 } }, pixel_row ); } diff --git a/firmware/application/apps/ui_encoders.cpp b/firmware/application/apps/ui_encoders.cpp index 9ac1e3b5..e15defd1 100644 --- a/firmware/application/apps/ui_encoders.cpp +++ b/firmware/application/apps/ui_encoders.cpp @@ -102,7 +102,7 @@ void EncodersConfigView::on_type_change(size_t index) { symfield_word.set_length(word_length); size_t n = 0, i = 0; while (n < word_length) { - symbol_type = encoder_def->word_format.at(i++); + symbol_type = encoder_def->word_format[i++]; if (symbol_type == 'A') { symfield_word.set_symbol_list(n++, encoder_def->address_symbols); format_string += 'A'; diff --git a/firmware/application/apps/ui_mictx.cpp b/firmware/application/apps/ui_mictx.cpp index 54dca6b0..fff65098 100644 --- a/firmware/application/apps/ui_mictx.cpp +++ b/firmware/application/apps/ui_mictx.cpp @@ -51,11 +51,11 @@ void MicTXView::on_tx_progress(const bool done) { } void MicTXView::configure_baseband() { - baseband::set_audiotx_data( + baseband::set_audiotx_config( sampling_rate / 20, // Update vu-meter at 20Hz transmitting ? transmitter_model.channel_bandwidth() : 0, mic_gain, - TONES_F2D(tone_key_frequency(tone_key_index)), + TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate), 0.2 // 20% mix ); } diff --git a/firmware/application/apps/ui_soundboard.cpp b/firmware/application/apps/ui_soundboard.cpp index 9dc3f864..5902bfad 100644 --- a/firmware/application/apps/ui_soundboard.cpp +++ b/firmware/application/apps/ui_soundboard.cpp @@ -23,7 +23,6 @@ // To prepare samples: for f in ./*.wav; do sox "$f" -r 48000 -c 1 -b8 --norm "conv/$f"; done #include "ui_soundboard.hpp" - #include "lfsr_random.hpp" #include "string_format.hpp" #include "tonesets.hpp" @@ -33,6 +32,7 @@ using namespace portapack; namespace ui { +// TODO: Use Sharebrained's PRNG void SoundBoardView::do_random() { uint32_t id; @@ -43,45 +43,42 @@ void SoundBoardView::do_random() { play_sound(id); - buttons[id % 18].focus(); - page = id / 18; + buttons[id % 15].focus(); + page = id / 15; refresh_buttons(id); } -void SoundBoardView::prepare_audio() { +bool SoundBoardView::is_active() const { + return (bool)replay_thread; +} + +void SoundBoardView::stop(const bool do_loop) { + if( is_active() ) + replay_thread.reset(); - if (sample_counter >= sample_duration) { - if (tx_mode == NORMAL) { - if (!check_loop.value()) { - pbar.set_value(0); - transmitter_model.disable(); - return; - } else { - reader->rewind(); - sample_counter = 0; - } - } else { - pbar.set_value(0); - transmitter_model.disable(); - if (check_loop.value()) - do_random(); - } + if (do_loop && check_loop.value()) { + play_sound(playing_id); + } else { + radio::disable(); + //button_play.set_bitmap(&bitmap_play); + } + ready_signal = false; +} + +void SoundBoardView::handle_replay_thread_done(const uint32_t return_code) { + if (return_code == ReplayThread::END_OF_FILE) { + stop(true); + } else if (return_code == ReplayThread::READ_ERROR) { + stop(false); + file_error(); } - pbar.set_value(sample_counter); + progressbar.set_value(0); +} - auto bytes_read = reader->read(audio_buffer, 512).value(); - - // Unsigned to signed, pretty stupid :/ - for (size_t n = 0; n < bytes_read; n++) - audio_buffer[n] -= 0x80; - for (size_t n = bytes_read; n < 512; n++) - audio_buffer[n] = 0; - - sample_counter += 512; - - baseband::set_fifo_data(audio_buffer); +void SoundBoardView::set_ready() { + ready_signal = true; } void SoundBoardView::focus() { @@ -95,41 +92,60 @@ void SoundBoardView::on_tuning_frequency_changed(rf::Frequency f) { transmitter_model.set_tuning_frequency(f); } +void SoundBoardView::file_error() { + nav_.display_modal("Error", "File read error."); +} + void SoundBoardView::play_sound(uint16_t id) { - uint32_t tone_key_index; - uint32_t divider; + uint32_t sample_rate = 0; + + auto reader = std::make_unique(); + uint32_t tone_key_index = options_tone_key.selected_index(); + + stop(false); - if (sounds[id].size == 0) return; - - if (!reader->open(sounds[id].path)) return; + if(!reader->open(sounds[id].path)) { + file_error(); + return; + } - sample_duration = sounds[id].sample_duration; + playing_id = id; - pbar.set_max(sample_duration); - pbar.set_value(0); + progressbar.set_max(reader->sample_count()); + sample_rate = reader->sample_rate() * 32; - sample_counter = 0; + if( reader ) { + //button_play.set_bitmap(&bitmap_stop); + baseband::set_sample_rate(sample_rate); + + replay_thread = std::make_unique( + std::move(reader), + read_size, buffer_count, + &ready_signal, + [](uint32_t return_code) { + ReplayThreadDoneMessage message { return_code }; + EventDispatcher::send_message(message); + } + ); + } - prepare_audio(); - - transmitter_model.set_sampling_rate(1536000U); - transmitter_model.set_rf_amp(true); - transmitter_model.set_lna(40); - transmitter_model.set_vga(40); - transmitter_model.set_baseband_bandwidth(1750000); - transmitter_model.enable(); - - tone_key_index = 0; //options_tone_key.selected_index(); - - divider = (1536000 / sounds[id].sample_rate) - 1; - - baseband::set_audiotx_data( - divider, + baseband::set_audiotx_config( + 0, number_bw.value() * 1000, 10, - TONES_F2D(tone_key_frequency(tone_key_index)), + TONES_F2D(tone_key_frequency(tone_key_index), sample_rate), 0.2 // 20% mix ); + + radio::enable({ + receiver_model.tuning_frequency(), + sample_rate, + 1750000, + rf::Direction::Transmit, + receiver_model.rf_amp(), + static_cast(receiver_model.lna()), + static_cast(receiver_model.vga()) + }); } void SoundBoardView::show_infos(uint16_t id) { @@ -144,7 +160,7 @@ void SoundBoardView::refresh_buttons(uint16_t id) { text_page.set("Page " + to_string_dec_uint(page + 1) + "/" + to_string_dec_uint(max_page)); for (auto& button : buttons) { - n_sound = (page * 18) + n; + n_sound = (page * 15) + n; button.id = n_sound; @@ -180,64 +196,57 @@ bool SoundBoardView::change_page(Button& button, const KeyEvent key) { return false; } +void SoundBoardView::on_tx_progress(const uint32_t progress) { + progressbar.set_value(progress); +} + SoundBoardView::SoundBoardView( NavigationView& nav ) : nav_ (nav) { - std::vector file_list; - std::string title; - uint8_t c; + auto reader = std::make_unique(); + uint8_t c = 0; - reader = std::make_unique(); - - file_list = scan_root_files(u"WAV", u"*.WAV"); - - c = 0; - for (auto& path : file_list) { - if (reader->open(u"WAV/" + path.native())) { - if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) { - sounds[c].size = reader->data_size(); - sounds[c].sample_duration = reader->data_size(); // / (reader->bits_per_sample() / 8); - sounds[c].sample_rate = reader->sample_rate(); - //if (reader->bits_per_sample() > 8) - // sounds[c].sixteenbit = true; - //else - // sounds[c].sixteenbit = false; - sounds[c].ms_duration = reader->ms_duration(); - sounds[c].path = u"WAV/" + path.native(); - title = reader->title().substr(0, 20); - if (title != "") - sounds[c].title = title; - else - sounds[c].title = "-"; - c++; - if (c == 54) break; // Limit to 54 files (3 pages) + for(const auto& entry : std::filesystem::directory_iterator(u"WAV", u"*.WAV")) { + if( std::filesystem::is_regular_file(entry.status()) ) { + if (reader->open(u"WAV/" + entry.path().native())) { + if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) { + sounds[c].ms_duration = reader->ms_duration(); + sounds[c].path = u"WAV/" + entry.path().native(); + std::string title = reader->title().substr(0, 20); + if (title != "") + sounds[c].title = title; + else + sounds[c].title = "-"; + c++; + if (c == 60) break; // Limit to 60 files (4 pages) + } } } } baseband::run_image(portapack::spi_flash::image_tag_audio_tx); - + max_sound = c; - max_page = (max_sound + 18 - 1) / 18; // 3 * 6 = 18 buttons per page + max_page = (max_sound + 15 - 1) / 15; // 3 * 5 = 15 buttons per page add_children({ &labels, &field_frequency, &number_bw, - //&options_tone_key, + &options_tone_key, &text_title, &text_page, &text_duration, - &pbar, + &progressbar, &check_loop, &button_random, &button_exit }); - - //tone_keys_populate(options_tone_key); - //options_tone_key.set_selected_index(0); - + + tone_keys_populate(options_tone_key); + options_tone_key.set_selected_index(0); + const auto button_fn = [this](Button& button) { tx_mode = NORMAL; this->play_sound(button.id); @@ -250,7 +259,7 @@ SoundBoardView::SoundBoardView( const auto button_dir = [this](Button& button, const KeyEvent key) { return change_page(button, key); }; - + // Generate buttons size_t n = 0; for(auto& button : buttons) { @@ -260,8 +269,8 @@ SoundBoardView::SoundBoardView( button.on_dir = button_dir; button.set_parent_rect({ static_cast((n % 3) * 78 + 3), - static_cast((n / 3) * 30 + 24), - 78, 30 + static_cast((n / 3) * 38 + 24), + 78, 38 }); n++; } diff --git a/firmware/application/apps/ui_soundboard.hpp b/firmware/application/apps/ui_soundboard.hpp index 5dd9fc88..5f9e65b0 100644 --- a/firmware/application/apps/ui_soundboard.hpp +++ b/firmware/application/apps/ui_soundboard.hpp @@ -26,6 +26,7 @@ #include "ui.hpp" #include "ui_widget.hpp" #include "ui_font_fixed_8x16.hpp" +#include "replay_thread.hpp" #include "baseband_api.hpp" #include "ui_receiver.hpp" #include "io_wave.hpp" @@ -59,10 +60,6 @@ private: struct sound { std::filesystem::path path { }; - bool sixteenbit = false; - uint32_t sample_rate = 0; - uint32_t size = 0; - uint32_t sample_duration = 0; uint32_t ms_duration = 0; std::string title { }; }; @@ -73,13 +70,15 @@ private: uint32_t lfsr_v = 0x13377331; - std::unique_ptr reader { }; - - sound sounds[54]; // 3 pages * 18 buttons + sound sounds[60]; // 5 pages * 12 buttons uint32_t max_sound { }; uint8_t max_page { }; + uint32_t playing_id { }; - int8_t audio_buffer[512]; + const size_t read_size { 2048 }; // Less ? + const size_t buffer_count { 3 }; + std::unique_ptr replay_thread { }; + bool ready_signal { false }; Style style_a { .font = font::fixed_8x16, @@ -102,7 +101,7 @@ private: .foreground = { 153, 102, 255 } }; - std::array buttons { }; + std::array buttons { }; const Style * styles[4] = { &style_a, &style_b, &style_c, &style_d }; void on_tuning_frequency_changed(rf::Frequency f); @@ -112,8 +111,13 @@ private: bool change_page(Button& button, const KeyEvent key); void refresh_buttons(uint16_t id); void play_sound(uint16_t id); - void prepare_audio(); void on_ctcss_changed(uint32_t v); + void stop(const bool do_loop); + bool is_active() const; + void set_ready(); + void handle_replay_thread_done(const uint32_t return_code); + void file_error(); + void on_tx_progress(const uint32_t progress); Labels labels { { { 10 * 8, 4 }, "BW: kHz", Color::light_grey() } @@ -138,21 +142,21 @@ private: }; Text text_title { - { 1 * 8, 26 * 8, 20 * 8, 16 }, + { 1 * 8, 28 * 8, 20 * 8, 16 }, "-" }; Text text_page { - { 22 * 8 - 4, 26 * 8, 8 * 8, 16 }, + { 22 * 8 - 4, 28 * 8, 8 * 8, 16 }, "Page -/-" }; Text text_duration { - { 1 * 8, 30 * 8, 5 * 8, 16 } + { 1 * 8, 31 * 8, 5 * 8, 16 } }; - ProgressBar pbar { - { 9 * 8, 30 * 8, 20 * 8, 16 } + ProgressBar progressbar { + { 9 * 8, 31 * 8, 20 * 8, 16 } }; Checkbox check_loop { @@ -171,15 +175,31 @@ private: "Exit" }; + MessageHandlerRegistration message_handler_replay_thread_error { + Message::ID::ReplayThreadDone, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->handle_replay_thread_done(message.return_code); + } + }; + MessageHandlerRegistration message_handler_fifo_signal { Message::ID::RequestSignal, [this](const Message* const p) { const auto message = static_cast(p); if (message->signal == RequestSignalMessage::Signal::FillRequest) { - this->prepare_audio(); + this->set_ready(); } } }; + + MessageHandlerRegistration message_handler_tx_progress { + Message::ID::TXProgress, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->on_tx_progress(message.progress); + } + }; }; } /* namespace ui */ diff --git a/firmware/application/apps/ui_tone_search.cpp b/firmware/application/apps/ui_tone_search.cpp index c7c2e0fc..0f190d55 100644 --- a/firmware/application/apps/ui_tone_search.cpp +++ b/firmware/application/apps/ui_tone_search.cpp @@ -45,7 +45,10 @@ ToneSearchView::ToneSearchView( //baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum); add_children({ - &labels + &labels, + &field_lna, + &field_vga, + &field_rf_amp, }); } diff --git a/firmware/application/apps/ui_tone_search.hpp b/firmware/application/apps/ui_tone_search.hpp index b32c9bff..1d121138 100644 --- a/firmware/application/apps/ui_tone_search.hpp +++ b/firmware/application/apps/ui_tone_search.hpp @@ -40,7 +40,19 @@ private: NavigationView& nav_; Labels labels { - { { 1 * 8, 0 }, "Min: Max: LNA VGA", Color::light_grey() } + { { 0 * 8, 0 * 8 }, "LNA: VGA: AMP:", Color::light_grey() } + }; + + LNAGainField field_lna { + { 4 * 8, 0 * 16 } + }; + + VGAGainField field_vga { + { 11 * 8, 0 * 16 } + }; + + RFAmpField field_rf_amp { + { 18 * 8, 0 * 16 } }; /* diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index ef03ac9d..2416a655 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -152,7 +152,7 @@ void kill_afsk() { send_message(&message); } -void set_audiotx_data(const uint32_t divider, const float deviation_hz, const float audio_gain, +void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, const uint32_t tone_key_delta, const float tone_key_mix_weight) { const AudioTXConfigMessage message { divider, @@ -240,7 +240,7 @@ void set_spectrum(const size_t sampling_rate, const size_t trigger) { void set_siggen_tone(const uint32_t tone) { const SigGenToneMessage message { - TONES_F2D(tone) + TONES_F2D(tone, TONES_SAMPLERATE) }; send_message(&message); } diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index 22f6ead6..c665ff6e 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -60,7 +60,7 @@ void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint1 const bool dual_tone, const bool audio_out); void kill_tone(); void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration); -void set_audiotx_data(const uint32_t divider, const float deviation_hz, const float audio_gain, +void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, const uint32_t tone_key_delta, const float tone_key_mix_weight); void set_fifo_data(const int8_t * data); void set_pitch_rssi(int32_t avg, bool enabled); diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index 63d7c163..1b007486 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -23,6 +23,10 @@ // Color bitmaps generated with: // Gimp image > indexed colors (16), then "xxd -i *.bmp" +// Note about available RAM: +// Check what ends up in the BSS section by looking at the map files ! +// Use constexpr where possible or make sure const are in .cpp files, not headers ! + //TEST: Goertzel //TEST: Menuview refresh, seems to blink a lot //TEST: Check AFSK transmit end, skips last bits ? @@ -31,8 +35,8 @@ //BUG: (Workaround ok) CPLD-related rx ok, tx bad, see portapack.cpp lines 214+ to disable CPLD overlay //BUG: SCANNER Lock on frequency, if frequency jump, still locked on first one //BUG: SCANNER Multiple slices +//GLITCH: The about view scroller sometimes misses lines because of a race condition between the display scrolling and drawing the line -//TODO: Disable Nuoptix timecode TX, useless (almost) //TODO: Move Touchtunes remote to Custom remote //TODO: Use escapes \x1B to set colors in text, it works ! //TODO: Open files in File Manager diff --git a/firmware/application/protocols/encoders.hpp b/firmware/application/protocols/encoders.hpp index b30b5deb..d23b38f4 100644 --- a/firmware/application/protocols/encoders.hpp +++ b/firmware/application/protocols/encoders.hpp @@ -38,22 +38,22 @@ namespace encoders { void bitstream_append(size_t& bitstream_length, uint32_t bit_count, uint32_t bits); struct encoder_def_t { - std::string name; // Encoder chip ref/name - std::string address_symbols; // List of possible symbols like "01", "01F"... - std::string data_symbols; // Same + char name[16]; // Encoder chip ref/name + char address_symbols[8]; // List of possible symbols like "01", "01F"... + char data_symbols[8]; // Same uint16_t clk_per_symbol; // Oscillator periods per symbol uint16_t clk_per_fragment; // Oscillator periods per symbol fragment (state) - std::vector bit_format; // List of fragments for each symbol in previous *_symbols list order + char bit_format[4][20]; // List of fragments for each symbol in previous *_symbols list order uint8_t word_length; // Total # of symbols (not counting sync) - std::string word_format; // A for Address, D for Data, S for sync - std::string sync; // Like bit_format + char word_format[32]; // A for Address, D for Data, S for sync + char sync[64]; // Like bit_format uint32_t default_speed; // Default encoder clk frequency (often set by shitty resistor) uint8_t repeat_min; // Minimum repeat count uint16_t pause_symbols; // Length of pause between repeats in symbols }; // Warning ! If this is changed, make sure that ENCODER_UM3750 is still valid ! - const encoder_def_t encoder_defs[ENC_TYPES_COUNT] = { + constexpr encoder_def_t encoder_defs[ENC_TYPES_COUNT] = { // PT2260-R2 { "2260-R2", diff --git a/firmware/application/protocols/modems.hpp b/firmware/application/protocols/modems.hpp index 3582dfe7..48dea3ee 100644 --- a/firmware/application/protocols/modems.hpp +++ b/firmware/application/protocols/modems.hpp @@ -40,14 +40,14 @@ enum ModemModulation { }; struct modem_def_t { - std::string name; + char name[16]; ModemModulation modulation; uint16_t mark_freq; uint16_t space_freq; uint16_t baudrate; }; -const modem_def_t modem_defs[MODEM_DEF_COUNT] = { +constexpr modem_def_t modem_defs[MODEM_DEF_COUNT] = { { "Bell202", AFSK, 1200, 2200, 1200 }, { "Bell103", AFSK, 1270, 1070, 300 }, { "V21", AFSK, 980, 1180, 300 }, diff --git a/firmware/application/replay_thread.cpp b/firmware/application/replay_thread.cpp index 29f75cdd..c303f75d 100644 --- a/firmware/application/replay_thread.cpp +++ b/firmware/application/replay_thread.cpp @@ -88,7 +88,7 @@ uint32_t ReplayThread::run() { if (prefill_buffer == nullptr) { buffers.put_app(prefill_buffer); } else { - size_t blocks = 16384 / 512; + size_t blocks = config.read_size / 512; for (size_t c = 0; c < blocks; c++) { auto read_result = reader->read(&((uint8_t*)prefill_buffer->data())[c * 512], 512); @@ -97,7 +97,7 @@ uint32_t ReplayThread::run() { } } - prefill_buffer->set_size(16384); + prefill_buffer->set_size(config.read_size); buffers.put(prefill_buffer); } diff --git a/firmware/application/string_format.hpp b/firmware/application/string_format.hpp index 278088ba..c0164a0d 100644 --- a/firmware/application/string_format.hpp +++ b/firmware/application/string_format.hpp @@ -41,7 +41,7 @@ const char unit_prefix[7] { 'n', 'u', 'm', 0, 'k', 'M', 'G' }; // TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp... std::string to_string_bin(const uint32_t n, const uint8_t l = 0); -std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = '0'); +std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = ' '); std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0); std::string to_string_hex(const uint64_t n, const int32_t l = 0); std::string to_string_hex_array(uint8_t * const array, const int32_t l = 0); diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 935d9245..e4aae62c 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -37,7 +37,7 @@ #include "ui_aprs_tx.hpp" #include "ui_bht_tx.hpp" #include "ui_coasterp.hpp" -#include "ui_debug.hpp" +//#include "ui_debug.hpp" #include "ui_encoders.hpp" #include "ui_fileman.hpp" #include "ui_freqman.hpp" @@ -329,20 +329,20 @@ void NavigationView::focus() { ReceiversMenuView::ReceiversMenuView(NavigationView& nav) { add_items({ - { "ADS-B: Planes", ui::Color::green(), &bitmap_icon_adsb, [&nav](){ nav.push(); }, }, - { "AIS: Boats", ui::Color::green(), &bitmap_icon_ais, [&nav](){ nav.push(); } }, - { "AFSK", ui::Color::yellow(),&bitmap_icon_receivers, [&nav](){ nav.push(); } }, - { "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.push(); } }, - { "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.push(false); } }, - { "DMR framing", ui::Color::grey(), &bitmap_icon_dmr, [&nav](){ nav.push(); } }, - { "ERT: Utility Meters", ui::Color::green(), &bitmap_icon_ert, [&nav](){ nav.push(); } }, - { "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.push(); } }, - { "SIGFOX", ui::Color::grey(), &bitmap_icon_fox, [&nav](){ nav.push(); } }, // SIGFRXView - { "LoRa", ui::Color::grey(), &bitmap_icon_lora, [&nav](){ nav.push(); } }, - { "Radiosondes", ui::Color::yellow(),&bitmap_icon_sonde, [&nav](){ nav.push(); } }, - { "SSTV", ui::Color::grey(), &bitmap_icon_sstv, [&nav](){ nav.push(); } }, - { "TETRA framing", ui::Color::grey(), &bitmap_icon_tetra, [&nav](){ nav.push(); } }, - { "TPMS: Cars", ui::Color::green(), &bitmap_icon_tpms, [&nav](){ nav.push(); } }, + { "ADS-B: Planes", ui::Color::green(), &bitmap_icon_adsb, [&nav](){ nav.replace(); }, }, + { "AIS: Boats", ui::Color::green(), &bitmap_icon_ais, [&nav](){ nav.replace(); } }, + { "AFSK", ui::Color::yellow(),&bitmap_icon_receivers, [&nav](){ nav.replace(); } }, + { "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.replace(); } }, + { "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.replace(false); } }, + { "DMR framing", ui::Color::grey(), &bitmap_icon_dmr, [&nav](){ nav.replace(); } }, + { "ERT: Utility Meters", ui::Color::green(), &bitmap_icon_ert, [&nav](){ nav.replace(); } }, + { "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.replace(); } }, + { "SIGFOX", ui::Color::grey(), &bitmap_icon_fox, [&nav](){ nav.replace(); } }, // SIGFRXView + { "LoRa", ui::Color::grey(), &bitmap_icon_lora, [&nav](){ nav.replace(); } }, + { "Radiosondes", ui::Color::yellow(),&bitmap_icon_sonde, [&nav](){ nav.replace(); } }, + { "SSTV", ui::Color::grey(), &bitmap_icon_sstv, [&nav](){ nav.replace(); } }, + { "TETRA framing", ui::Color::grey(), &bitmap_icon_tetra, [&nav](){ nav.replace(); } }, + { "TPMS: Cars", ui::Color::green(), &bitmap_icon_tpms, [&nav](){ nav.replace(); } }, }); on_left = [&nav](){ nav.pop(); }; @@ -383,7 +383,7 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) { { "File manager", ui::Color::yellow(), &bitmap_icon_file, [&nav](){ nav.push(); } }, { "Notepad", ui::Color::grey(), &bitmap_icon_notepad, [&nav](){ nav.push(); } }, { "Signal generator", ui::Color::green(), &bitmap_icon_cwgen, [&nav](){ nav.push(); } }, - { "Tone search", ui::Color::grey(), nullptr, [&nav](){ nav.push(); } }, // ToneSearchView + { "Tone search", ui::Color::grey(), nullptr, [&nav](){ nav.push(); } }, { "Whip antenna length", ui::Color::yellow(), nullptr, [&nav](){ nav.push(); } }, { "Wipe SD card", ui::Color::red(), nullptr, [&nav](){ nav.push(); } }, }); @@ -466,10 +466,12 @@ SystemView::SystemView( (portapack::persistent_memory::ui_config() & 16)) { // Login option navigation_view.push(); } else {*/ + if (portapack::persistent_memory::config_splash()) navigation_view.push(); else navigation_view.push(); + //} } diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index ee8f49e2..925f0de6 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -70,8 +70,14 @@ public: T* push(Args&&... args) { return reinterpret_cast(push_view(std::unique_ptr(new T(*this, std::forward(args)...)))); } + template + T* replace(Args&&... args) { + pop(); + return reinterpret_cast(push_view(std::unique_ptr(new T(*this, std::forward(args)...)))); + } void push(View* v); + void replace(View* v); void pop(); void pop_modal(); diff --git a/firmware/baseband/proc_audiotx.cpp b/firmware/baseband/proc_audiotx.cpp index de78d836..2dfbdf55 100644 --- a/firmware/baseband/proc_audiotx.cpp +++ b/firmware/baseband/proc_audiotx.cpp @@ -29,70 +29,94 @@ #include void AudioTXProcessor::execute(const buffer_c8_t& buffer){ - - // This is called at 1536000/2048 = 750Hz if (!configured) return; - - for (size_t i = 0; iread(audio_buffer.p, bytes_to_read); + } + + // Fill and "stretch" + for (size_t i = 0; i < buffer.count; i++) { + if (!(i & 31)) + audio_sample = audio_buffer.p[i >> 5] - 0x80; - // Audio preview sample generation @ 1536000/divider - if (!as) { - as = divider; - audio_fifo.out(out_sample); - sample = (int32_t)out_sample; - //preview_audio_buffer.p[ai++] = sample << 8; - - if ((audio_fifo.len() < 512) && (asked == false)) { - // Ask application to fill up fifo - shared_memory.application_queue.push(sig_message); - asked = true; - } - } else { - as--; - } - - sample = tone_gen.process(sample); + sample = tone_gen.process(audio_sample); // FM delta = sample * fm_delta; phase += delta; sphase = phase + (64 << 24); - + re = (sine_table_i8[(sphase & 0xFF000000U) >> 24]); im = (sine_table_i8[(phase & 0xFF000000U) >> 24]); - buffer.p[i] = {re, im}; + buffer.p[i] = { (int8_t)re, (int8_t)im }; + } + + spectrum_samples += buffer.count; + if( spectrum_samples >= spectrum_interval_samples ) { + spectrum_samples -= spectrum_interval_samples; + + txprogress_message.progress = bytes_read; // Inform UI about progress + txprogress_message.done = false; + shared_memory.application_queue.push(txprogress_message); } //AudioOutput::fill_audio_buffer(preview_audio_buffer, true); } -void AudioTXProcessor::on_message(const Message* const msg) { - const auto message = *reinterpret_cast(msg); - - switch(msg->id) { +void AudioTXProcessor::on_message(const Message* const message) { + switch(message->id) { case Message::ID::AudioTXConfig: - fm_delta = message.deviation_hz * (0xFFFFFFULL / baseband_fs); - divider = message.divider; - as = 0; - - tone_gen.configure(message.tone_key_delta, message.tone_key_mix_weight); + audio_config(*reinterpret_cast(message)); + break; - configured = true; + case Message::ID::ReplayConfig: + configured = false; + bytes_read = 0; + replay_config(*reinterpret_cast(message)); + break; + + case Message::ID::SamplerateConfig: + samplerate_config(*reinterpret_cast(message)); break; case Message::ID::FIFOData: - audio_fifo.in(static_cast(msg)->data, 512); - asked = false; + configured = true; break; - + default: break; } } +void AudioTXProcessor::audio_config(const AudioTXConfigMessage& message) { + fm_delta = message.deviation_hz * (0xFFFFFFULL / baseband_fs); + + tone_gen.configure(message.tone_key_delta, message.tone_key_mix_weight); +} + +void AudioTXProcessor::samplerate_config(const SamplerateConfigMessage& message) { + baseband_fs = message.sample_rate; + baseband_thread.set_sampling_rate(baseband_fs); + spectrum_interval_samples = baseband_fs / 20; +} + +void AudioTXProcessor::replay_config(const ReplayConfigMessage& message) { + if( message.config ) { + + stream = std::make_unique(message.config); + + // Tell application that the buffers and FIFO pointers are ready, prefill + shared_memory.application_queue.push(sig_message); + } else { + stream.reset(); + } +} + int main() { EventDispatcher event_dispatcher { std::make_unique() }; event_dispatcher.run(); diff --git a/firmware/baseband/proc_audiotx.hpp b/firmware/baseband/proc_audiotx.hpp index 711b6378..b15f8524 100644 --- a/firmware/baseband/proc_audiotx.hpp +++ b/firmware/baseband/proc_audiotx.hpp @@ -23,10 +23,10 @@ #ifndef __PROC_AUDIOTX_H__ #define __PROC_AUDIOTX_H__ -#include "fifo.hpp" #include "baseband_processor.hpp" #include "baseband_thread.hpp" #include "tone_gen.hpp" +#include "stream_output.hpp" class AudioTXProcessor : public BasebandProcessor { public: @@ -35,27 +35,29 @@ public: void on_message(const Message* const msg) override; private: - static constexpr size_t baseband_fs = 1536000U; - - bool configured = false; + size_t baseband_fs = 0; BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Transmit }; - int8_t audio_fifo_data[2048] { }; - FIFO audio_fifo = { audio_fifo_data, 11 }; // 43ms @ 48000Hz + std::array audio { }; + const buffer_t audio_buffer { + audio.data(), + audio.size(), + baseband_fs / 8 + }; + + std::unique_ptr stream { }; ToneGen tone_gen { }; uint32_t fm_delta { 0 }; - uint32_t divider { }; - uint32_t as { 0 }; uint32_t phase { 0 }, sphase { 0 }; int8_t out_sample { }; - int32_t sample { 0 }, delta { }; - + int32_t sample { 0 }, audio_sample { 0 }, delta { }; int8_t re { 0 }, im { 0 }; - bool asked { false }; + size_t spectrum_interval_samples = 0; + size_t spectrum_samples = 0; //int16_t audio_data[64]; /*const buffer_s16_t preview_audio_buffer { @@ -63,6 +65,14 @@ private: sizeof(int16_t)*64 };*/ + bool configured { false }; + uint32_t bytes_read { 0 }; + + void samplerate_config(const SamplerateConfigMessage& message); + void audio_config(const AudioTXConfigMessage& message); + void replay_config(const ReplayConfigMessage& message); + + TXProgressMessage txprogress_message { }; RequestSignalMessage sig_message { RequestSignalMessage::Signal::FillRequest }; }; diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index be1d7e21..37ba5af3 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -96,16 +96,16 @@ public: SigGenConfig = 43, SigGenTone = 44, - POCSAGPacket = 50, - ADSBFrame = 51, - AFSKData = 52, - TestAppPacket = 53, + POCSAGPacket = 45, + ADSBFrame = 46, + AFSKData = 47, + TestAppPacket = 48, - RequestSignal = 60, - FIFOData = 61, + RequestSignal = 49, + FIFOData = 50, - AudioLevelReport = 70, - CodedSquelch = 71, + AudioLevelReport = 51, + CodedSquelch = 52, MAX }; diff --git a/firmware/common/morse.cpp b/firmware/common/morse.cpp index 6b7c14af..bc8ae318 100644 --- a/firmware/common/morse.cpp +++ b/firmware/common/morse.cpp @@ -78,7 +78,7 @@ size_t morse_encode(std::string& message, const uint32_t time_unit_ms, // Setup tone "symbols" for (c = 0; c < 5; c++) { if (c < 2) - delta = TONES_F2D(tone); // Dot and dash + delta = TONES_F2D(tone, TONES_SAMPLERATE); // Dot and dash else delta = 0; // Pause diff --git a/firmware/common/morse.hpp b/firmware/common/morse.hpp index 00384a08..7522a097 100644 --- a/firmware/common/morse.hpp +++ b/firmware/common/morse.hpp @@ -45,7 +45,7 @@ const uint32_t morse_symbols[5] = { size_t morse_encode(std::string& message, const uint32_t time_unit_ms, const uint32_t tone, uint32_t * const time_units); -const std::string foxhunt_codes[11] = { +constexpr char foxhunt_codes[11][4] = { { "MOE" }, // -----. { "MOI" }, // -----.. { "MOS" }, // -----... @@ -60,7 +60,7 @@ const std::string foxhunt_codes[11] = { }; // 0=dot 1=dash -const uint16_t morse_ITU[63] = { +constexpr uint16_t morse_ITU[63] = { // Code Size 0b1010110000000110, // !: 101011- 110 0b0100100000000110, // ": 010010- 110 @@ -127,7 +127,7 @@ const uint16_t morse_ITU[63] = { 0b0011010000000110 // _: 001101- 110 }; -const uint16_t prosigns[12] = { +constexpr uint16_t prosigns[12] = { // Code Size 0b0001110000001001, // : 000111000 1001 0b0101000000000100, // : 0101----- 0100 diff --git a/firmware/common/sstv.hpp b/firmware/common/sstv.hpp index c4d72ec0..6ab35532 100644 --- a/firmware/common/sstv.hpp +++ b/firmware/common/sstv.hpp @@ -44,7 +44,7 @@ enum sstv_color_seq { #define SSTV_MODES_NB 6 // From http://www.graphics.stanford.edu/~seander/bithacks.html, nice ! -inline uint8_t sstv_parity(uint8_t code) { +constexpr inline uint8_t sstv_parity(uint8_t code) { uint8_t out = code; out ^= code >> 4; out &= 0x0F; @@ -63,7 +63,7 @@ struct sstv_scanline { }; struct sstv_mode { - std::string name; + char name[16]; uint8_t vis_code; bool color; // Unused for now sstv_color_seq color_sequence; @@ -78,7 +78,7 @@ struct sstv_mode { //std::pair luma_range; }; -const sstv_mode sstv_modes[SSTV_MODES_NB] = { +constexpr 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) }, diff --git a/firmware/common/tonesets.hpp b/firmware/common/tonesets.hpp index d1d9851f..876ef7e6 100644 --- a/firmware/common/tonesets.hpp +++ b/firmware/common/tonesets.hpp @@ -26,38 +26,37 @@ #include #define TONES_SAMPLERATE 1536000 -#define TONES_DELTA_COEF ((1ULL << 32) / TONES_SAMPLERATE) - -#define TONES_F2D(f) (uint32_t)(f * TONES_DELTA_COEF) +#define TONES_DELTA_COEF(sr) ((1ULL << 32) / sr) +#define TONES_F2D(f, sr) (uint32_t)(f * TONES_DELTA_COEF(sr)) #define BEEP_TONES_NB 6 -#define DTMF_C0 TONES_F2D(1209) -#define DTMF_C1 TONES_F2D(1336) -#define DTMF_C2 TONES_F2D(1477) -#define DTMF_C3 TONES_F2D(1633) -#define DTMF_R0 TONES_F2D(697) -#define DTMF_R1 TONES_F2D(770) -#define DTMF_R2 TONES_F2D(852) -#define DTMF_R3 TONES_F2D(941) +#define DTMF_C0 TONES_F2D(1209, TONES_SAMPLERATE) +#define DTMF_C1 TONES_F2D(1336, TONES_SAMPLERATE) +#define DTMF_C2 TONES_F2D(1477, TONES_SAMPLERATE) +#define DTMF_C3 TONES_F2D(1633, TONES_SAMPLERATE) +#define DTMF_R0 TONES_F2D(697, TONES_SAMPLERATE) +#define DTMF_R1 TONES_F2D(770, TONES_SAMPLERATE) +#define DTMF_R2 TONES_F2D(852, TONES_SAMPLERATE) +#define DTMF_R3 TONES_F2D(941, TONES_SAMPLERATE) const std::array ccir_deltas = { - TONES_F2D(1981), - TONES_F2D(1124), - TONES_F2D(1197), - TONES_F2D(1275), - TONES_F2D(1358), - TONES_F2D(1446), - TONES_F2D(1540), - TONES_F2D(1640), - TONES_F2D(1747), - TONES_F2D(1860), - TONES_F2D(2400), - TONES_F2D(930), - TONES_F2D(2247), - TONES_F2D(991), - TONES_F2D(2110), - TONES_F2D(1055) + TONES_F2D(1981, TONES_SAMPLERATE), + TONES_F2D(1124, TONES_SAMPLERATE), + TONES_F2D(1197, TONES_SAMPLERATE), + TONES_F2D(1275, TONES_SAMPLERATE), + TONES_F2D(1358, TONES_SAMPLERATE), + TONES_F2D(1446, TONES_SAMPLERATE), + TONES_F2D(1540, TONES_SAMPLERATE), + TONES_F2D(1640, TONES_SAMPLERATE), + TONES_F2D(1747, TONES_SAMPLERATE), + TONES_F2D(1860, TONES_SAMPLERATE), + TONES_F2D(2400, TONES_SAMPLERATE), + TONES_F2D(930, TONES_SAMPLERATE), + TONES_F2D(2247, TONES_SAMPLERATE), + TONES_F2D(991, TONES_SAMPLERATE), + TONES_F2D(2110, TONES_SAMPLERATE), + TONES_F2D(1055, TONES_SAMPLERATE) }; // 0123456789ABCD#* @@ -81,50 +80,50 @@ const uint32_t dtmf_deltas[16][2] = { }; const std::array eia_deltas = { - TONES_F2D(600), - TONES_F2D(741), - TONES_F2D(882), - TONES_F2D(1023), - TONES_F2D(1164), - TONES_F2D(1305), - TONES_F2D(1446), - TONES_F2D(1587), - TONES_F2D(1728), - TONES_F2D(1869), - TONES_F2D(2151), - TONES_F2D(2433), - TONES_F2D(2010), - TONES_F2D(2292), - TONES_F2D(459), - TONES_F2D(1091) + TONES_F2D(600, TONES_SAMPLERATE), + TONES_F2D(741, TONES_SAMPLERATE), + TONES_F2D(882, TONES_SAMPLERATE), + TONES_F2D(1023, TONES_SAMPLERATE), + TONES_F2D(1164, TONES_SAMPLERATE), + TONES_F2D(1305, TONES_SAMPLERATE), + TONES_F2D(1446, TONES_SAMPLERATE), + TONES_F2D(1587, TONES_SAMPLERATE), + TONES_F2D(1728, TONES_SAMPLERATE), + TONES_F2D(1869, TONES_SAMPLERATE), + TONES_F2D(2151, TONES_SAMPLERATE), + TONES_F2D(2433, TONES_SAMPLERATE), + TONES_F2D(2010, TONES_SAMPLERATE), + TONES_F2D(2292, TONES_SAMPLERATE), + TONES_F2D(459, TONES_SAMPLERATE), + TONES_F2D(1091, TONES_SAMPLERATE) }; const std::array zvei_deltas = { - TONES_F2D(2400), - TONES_F2D(1060), - TONES_F2D(1160), - TONES_F2D(1270), - TONES_F2D(1400), - TONES_F2D(1530), - TONES_F2D(1670), - TONES_F2D(1830), - TONES_F2D(2000), - TONES_F2D(2200), - TONES_F2D(2800), - TONES_F2D(810), - TONES_F2D(970), - TONES_F2D(885), - TONES_F2D(2600), - TONES_F2D(680) + TONES_F2D(2400, TONES_SAMPLERATE), + TONES_F2D(1060, TONES_SAMPLERATE), + TONES_F2D(1160, TONES_SAMPLERATE), + TONES_F2D(1270, TONES_SAMPLERATE), + TONES_F2D(1400, TONES_SAMPLERATE), + TONES_F2D(1530, TONES_SAMPLERATE), + TONES_F2D(1670, TONES_SAMPLERATE), + TONES_F2D(1830, TONES_SAMPLERATE), + TONES_F2D(2000, TONES_SAMPLERATE), + TONES_F2D(2200, TONES_SAMPLERATE), + TONES_F2D(2800, TONES_SAMPLERATE), + TONES_F2D(810, TONES_SAMPLERATE), + TONES_F2D(970, TONES_SAMPLERATE), + TONES_F2D(885, TONES_SAMPLERATE), + TONES_F2D(2600, TONES_SAMPLERATE), + TONES_F2D(680, TONES_SAMPLERATE) }; const uint32_t beep_deltas[BEEP_TONES_NB] = { - TONES_F2D(1475), - TONES_F2D(740), - TONES_F2D(587), - TONES_F2D(1109), - TONES_F2D(831), - TONES_F2D(740) + TONES_F2D(1475, TONES_SAMPLERATE), + TONES_F2D(740, TONES_SAMPLERATE), + TONES_F2D(587, TONES_SAMPLERATE), + TONES_F2D(1109, TONES_SAMPLERATE), + TONES_F2D(831, TONES_SAMPLERATE), + TONES_F2D(740, TONES_SAMPLERATE) }; #endif/*__TONESETS_H__*/ diff --git a/firmware/tools/world_map.py b/firmware/tools/world_map.py new file mode 100644 index 00000000..9066664d --- /dev/null +++ b/firmware/tools/world_map.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +# Copyright (C) 2017 Furrtek +# +# 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. +# + +from __future__ import print_function +import sys +import struct +from PIL import Image + +outfile = open('../../sdcard/world_map.bin', 'wb') + +im = Image.open("../../sdcard/world_map.jpg") +pix = im.load() + +outfile.write(struct.pack('H', im.size[0])) +outfile.write(struct.pack('H', im.size[1])) + +for y in range (0, im.size[1]): + line = '' + for x in range (0, im.size[0]): + # RRRRRGGGGGGBBBBB + #pixel_lcd = (pix[x, y][0] >> 3) << 11 + #pixel_lcd |= (pix[x, y][1] >> 2) << 5 + #pixel_lcd |= (pix[x, y][2] >> 3) + # RRRGGGBB to + # RRR00GGG000BB000 + pixel_lcd = (pix[x, y][0] >> 5) << 5 + pixel_lcd |= (pix[x, y][1] >> 5) << 2 + pixel_lcd |= (pix[x, y][2] >> 6) + line += struct.pack('B', pixel_lcd) + outfile.write(line) + print(str(y) + '/' + str(im.size[1]) + '\r', end="") diff --git a/sdcard/world_map.jpg b/sdcard/world_map.jpg new file mode 100644 index 00000000..a57b8291 Binary files /dev/null and b/sdcard/world_map.jpg differ