diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 0cb80e31..18d1823b 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -176,7 +176,7 @@ set(CPPSRC ui_touch_calibration.cpp ui_whipcalc.cpp ui_xylos.cpp - # ui_closecall.cpp + ui_closecall.cpp # ui_loadmodule.cpp recent_entries.cpp receiver_model.cpp @@ -196,6 +196,8 @@ set(CPPSRC sd_card.cpp time.cpp file.cpp + filewriter.cpp + wavfile.cpp log_file.cpp ${COMMON}/png_writer.cpp capture_thread.cpp diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 069a2a24..aeac8d74 100644 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -2886,6 +2886,30 @@ file.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/file.cpp.s .PHONY : file.cpp.s +filewriter.obj: filewriter.cpp.obj +.PHONY : filewriter.obj + +# target to build an object file +filewriter.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/filewriter.cpp.obj +.PHONY : filewriter.cpp.obj + +filewriter.i: filewriter.cpp.i +.PHONY : filewriter.i + +# target to preprocess a source file +filewriter.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/filewriter.cpp.i +.PHONY : filewriter.cpp.i + +filewriter.s: filewriter.cpp.s +.PHONY : filewriter.s + +# target to generate assembly for a file +filewriter.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/filewriter.cpp.s +.PHONY : filewriter.cpp.s + hackrf_cpld_data.obj: hackrf_cpld_data.cpp.obj .PHONY : hackrf_cpld_data.obj @@ -3774,6 +3798,30 @@ ui_channel.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_channel.cpp.s .PHONY : ui_channel.cpp.s +ui_closecall.obj: ui_closecall.cpp.obj +.PHONY : ui_closecall.obj + +# target to build an object file +ui_closecall.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_closecall.cpp.obj +.PHONY : ui_closecall.cpp.obj + +ui_closecall.i: ui_closecall.cpp.i +.PHONY : ui_closecall.i + +# target to preprocess a source file +ui_closecall.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_closecall.cpp.i +.PHONY : ui_closecall.cpp.i + +ui_closecall.s: ui_closecall.cpp.s +.PHONY : ui_closecall.s + +# target to generate assembly for a file +ui_closecall.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_closecall.cpp.s +.PHONY : ui_closecall.cpp.s + ui_debug.obj: ui_debug.cpp.obj .PHONY : ui_debug.obj @@ -4350,6 +4398,30 @@ ui_xylos.cpp.s: cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/ui_xylos.cpp.s .PHONY : ui_xylos.cpp.s +wavfile.obj: wavfile.cpp.obj +.PHONY : wavfile.obj + +# target to build an object file +wavfile.cpp.obj: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/wavfile.cpp.obj +.PHONY : wavfile.cpp.obj + +wavfile.i: wavfile.cpp.i +.PHONY : wavfile.i + +# target to preprocess a source file +wavfile.cpp.i: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/wavfile.cpp.i +.PHONY : wavfile.cpp.i + +wavfile.s: wavfile.cpp.s +.PHONY : wavfile.s + +# target to generate assembly for a file +wavfile.cpp.s: + cd /home/furrtek/portapack-hackrf && $(MAKE) -f firmware/application/CMakeFiles/application.elf.dir/build.make firmware/application/CMakeFiles/application.elf.dir/wavfile.cpp.s +.PHONY : wavfile.cpp.s + # Help Target help: @echo "The following are some of the valid targets for this Makefile:" @@ -4705,6 +4777,9 @@ help: @echo "... file.obj" @echo "... file.i" @echo "... file.s" + @echo "... filewriter.obj" + @echo "... filewriter.i" + @echo "... filewriter.s" @echo "... hackrf_cpld_data.obj" @echo "... hackrf_cpld_data.i" @echo "... hackrf_cpld_data.s" @@ -4816,6 +4891,9 @@ help: @echo "... ui_channel.obj" @echo "... ui_channel.i" @echo "... ui_channel.s" + @echo "... ui_closecall.obj" + @echo "... ui_closecall.i" + @echo "... ui_closecall.s" @echo "... ui_debug.obj" @echo "... ui_debug.i" @echo "... ui_debug.s" @@ -4888,6 +4966,9 @@ help: @echo "... ui_xylos.obj" @echo "... ui_xylos.i" @echo "... ui_xylos.s" + @echo "... wavfile.obj" + @echo "... wavfile.i" + @echo "... wavfile.s" .PHONY : help diff --git a/firmware/application/filewriter.cpp b/firmware/application/filewriter.cpp new file mode 100644 index 00000000..791b3247 --- /dev/null +++ b/firmware/application/filewriter.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "filewriter.hpp" + +#include "portapack.hpp" +using namespace portapack; + +#include + +namespace ui { + +Optional FileWriter::create(const std::string& filename) { + return file.create(filename); +} + +File::Result FileWriter::write(const void* const buffer, const size_t bytes) { + auto write_result = file.write(buffer, bytes) ; + if( write_result.is_ok() ) { + bytes_written += write_result.value(); + } + return write_result; +} + +} /* namespace ui */ diff --git a/firmware/application/filewriter.hpp b/firmware/application/filewriter.hpp new file mode 100644 index 00000000..32dc278f --- /dev/null +++ b/firmware/application/filewriter.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __FILEWRITER_H__ +#define __FILEWRITER_H__ + +#include "ui_widget.hpp" + +#include "capture_thread.hpp" +#include "signal.hpp" +#include "file.hpp" + +#include +#include +#include + +namespace ui { + +class FileWriter : public Writer { +public: + FileWriter() = default; + + FileWriter(const FileWriter&) = delete; + FileWriter& operator=(const FileWriter&) = delete; + FileWriter(FileWriter&& file) = delete; + FileWriter& operator=(FileWriter&&) = delete; + + Optional create(const std::string& filename); + + File::Result write(const void* const buffer, const size_t bytes) override; + +protected: + File file; + uint64_t bytes_written { 0 }; +}; + +} /* namespace ui */ + +#endif/*__FILEWRITER_H__*/ diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index aca5c944..83976b52 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -23,10 +23,12 @@ // Bitmaps generated with: // Gimp image > indexed colors (16), then "xxd -i *.bmp" +//TODO: CTCSS file/module //TEST: Imperial in whipcalc //TEST: Numbers //TEST: Jammer //TEST: RDS +//TEST: Morse coder/beacon //BUG (fixed ?): Soundboard crashes on exit if no wav files on sd card //BUG (fixed ?): No audio in about when shown second time diff --git a/firmware/application/ui_about.cpp b/firmware/application/ui_about.cpp index dfb6e272..4c8afe79 100644 --- a/firmware/application/ui_about.cpp +++ b/firmware/application/ui_about.cpp @@ -59,7 +59,7 @@ void AboutView::on_show() { transmitter_model.set_baseband_bandwidth(1750000); transmitter_model.enable(); - baseband::set_audiotx_data(32, 15, false, 0); + baseband::set_audiotx_data(32, 50, false, 0); //audio::headphone::set_volume(volume_t::decibel(0 - 99) + audio::headphone::volume_range().max); } diff --git a/firmware/application/ui_closecall.cpp b/firmware/application/ui_closecall.cpp index 8596e657..1563f354 100644 --- a/firmware/application/ui_closecall.cpp +++ b/firmware/application/ui_closecall.cpp @@ -33,8 +33,6 @@ #include "baseband_api.hpp" #include "string_format.hpp" -#include "hackrf_hal.hpp" - #include #include #include @@ -428,14 +426,8 @@ CloseCallView::CloseCallView( signal_token_tick_second = time::signal_tick_second += [this]() { this->on_tick_second(); }; - - receiver_model.set_baseband_configuration({ - .mode = toUType(ReceiverModel::Mode::CloseCall), - .sampling_rate = CC_SLICE_WIDTH, - .decimation_factor = 1, - }); receiver_model.set_baseband_bandwidth(CC_SLICE_WIDTH); - //receiver_model.enable(); + receiver_model.enable(); } } /* namespace ui */ diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index a7bb5b2a..0ec2e9a5 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -37,7 +37,7 @@ #include "ui_numbers.hpp" #include "ui_whipcalc.hpp" -//#include "ui_closecall.hpp" // DEBUG +#include "ui_closecall.hpp" #include "ui_freqman.hpp" #include "ui_nuoptix.hpp" #include "ui_soundboard.hpp" @@ -271,10 +271,11 @@ ReceiverMenuView::ReceiverMenuView(NavigationView& nav) { /* TransmitterCodedMenuView ******************************************************/ TransmitterCodedMenuView::TransmitterCodedMenuView(NavigationView& nav) { - add_items<7>({ { + add_items<8>({ { { "ADS-B Mode S", ui::Color::yellow(), [&nav](){ nav.push(); } }, { "BHT Epar", ui::Color::grey(), [&nav](){ nav.push(); } }, - { "BHT Xylos", ui::Color::yellow(), [&nav](){ nav.push(); } }, + { "BHT Xylos", ui::Color::green(), [&nav](){ nav.push(); } }, + { "Morse beacon", ui::Color::grey(), [&nav](){ nav.push(); } }, { "Nuoptix DTMF timecode", ui::Color::green(), [&nav](){ nav.push(); } }, { "OOK remote encoders", ui::Color::green(), [&nav](){ nav.push(); } }, { "RDS", ui::Color::orange(), [&nav](){ nav.push(); } }, @@ -288,7 +289,7 @@ TransmitterCodedMenuView::TransmitterCodedMenuView(NavigationView& nav) { TransmitterAudioMenuView::TransmitterAudioMenuView(NavigationView& nav) { add_items<4>({ { { "Soundboard", ui::Color::green(), [&nav](){ nav.push(); } }, - { "Numbers station", ui::Color::yellow(), [&nav](){ nav.push(); } }, + { "Numbers station", ui::Color::green(), [&nav](){ nav.push(); } }, { "Microphone", ui::Color::grey(), [&nav](){ nav.push(); } }, { "Whistle", ui::Color::grey(), [&nav](){ nav.push(); } }, } }); @@ -298,9 +299,10 @@ TransmitterAudioMenuView::TransmitterAudioMenuView(NavigationView& nav) { /* UtilitiesView *****************************************************************/ UtilitiesView::UtilitiesView(NavigationView& nav) { - add_items<2>({ { - { "Whip antenna calculator", ui::Color::green(), [&nav](){ nav.push(); } }, - { "Notepad", ui::Color::grey(), [&nav](){ nav.push(); } }, + add_items<3>({ { + { "Frequency manager", ui::Color::white(), [&nav](){ nav.push(); } }, + { "Whip antenna length", ui::Color::green(), [&nav](){ nav.push(); } }, + { "Notepad", ui::Color::grey(), [&nav](){ nav.push(); } }, } }); on_left = [&nav](){ nav.pop(); }; } @@ -313,14 +315,13 @@ SystemMenuView::SystemMenuView(NavigationView& nav) { { "Capture", ui::Color::cyan(), [&nav](){ nav.push(); } }, { "Code transmitters", ui::Color::green(), [&nav](){ nav.push(); } }, { "Audio transmitters", ui::Color::green(), [&nav](){ nav.push(); } }, - //{ "Close Call RX", ui::Color::cyan(), [&nav](){ nav.push(); } }, + { "Close Call", ui::Color::orange(), [&nav](){ nav.push(); } }, { "Jammer", ui::Color::orange(), [&nav](){ nav.push(); } }, - { "Frequency manager", ui::Color::white(), [&nav](){ nav.push(); } }, { "Utilities", ui::Color::purple(), [&nav](){ nav.push(); } }, //{ "Analyze", ui::Color::white(), [&nav](){ nav.push(); } }, { "Setup", ui::Color::white(), [&nav](){ nav.push(); } }, //{ "Debug", ui::Color::white(), [&nav](){ nav.push(); } }, - { "HackRF", ui::Color::white(), [&nav](){ nav.push(); } }, + { "HackRF mode", ui::Color::white(), [&nav](){ nav.push(); } }, { "About", ui::Color::white(), [&nav](){ nav.push(); } } } }); } diff --git a/firmware/application/ui_numbers.cpp b/firmware/application/ui_numbers.cpp index 72e41be0..4e27bc93 100644 --- a/firmware/application/ui_numbers.cpp +++ b/firmware/application/ui_numbers.cpp @@ -22,8 +22,6 @@ #include "ui_numbers.hpp" -#include "ch.h" -#include "ff.h" #include "portapack.hpp" #include "hackrf_hal.hpp" #include "portapack_shared_memory.hpp" @@ -34,16 +32,14 @@ // TODO: Total transmission time (all durations / 44100) using namespace portapack; -using namespace hackrf::one; namespace ui { void NumbersStationView::focus() { button_exit.focus(); - if (file_error) { + if (file_error) nav_.display_modal("No files", "Missing files in /numbers/", ABORT, nullptr); - } } NumbersStationView::~NumbersStationView() { @@ -58,62 +54,56 @@ void NumbersStationView::on_tuning_frequency_changed(rf::Frequency f) { void NumbersStationView::prepare_audio() { uint8_t code; - if (cnt >= sample_duration) { + if (sample_counter >= sample_duration) { if (segment == ANNOUNCE) { if (!announce_loop) { segment = MESSAGE; } else { - auto error = file.open("/numbers/announce.wav"); - if (error.is_valid()) return; - + reader->open("/numbers/announce.wav"); sample_duration = sound_sizes[10]; - - file.seek(44); // Skip header - announce_loop--; } } if (segment == MESSAGE) { - if (id_test == 10) + code = symfield_code.value(code_index); + + if (code_index == 25) transmitter_model.disable(); - code = symfield_code.value(id_test); - - if (code == 10) { - pause = 11025; // p: 0.25s @ 44100Hz - memset(audio_buffer, 0, 1024); - } else if (code == 11) { - pause = 33075; // P: 0.75s @ 44100Hz + if (code >= 10) { memset(audio_buffer, 0, 1024); + if (code == 10) { + pause = 11025; // p: 0.25s @ 44100Hz + } else if (code == 11) { + pause = 33075; // P: 0.75s @ 44100Hz + } else if (code == 12) { + transmitter_model.disable(); + } } else { - auto error = file.open("/numbers/" + file_names[code] + ".wav"); - if (error.is_valid()) return; - + reader->open("/numbers/" + file_names[code] + ".wav"); sample_duration = sound_sizes[code]; - - file.seek(44); // Skip header } - - id_test++; - } - - cnt = 0; + code_index++; + } + sample_counter = 0; } if (!pause) { - size_t bytes_read = file.read(audio_buffer, 1024).value(); + size_t bytes_read = reader->read(audio_buffer, 1024); // 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 < 1024; n++) + audio_buffer[n] = 0; - cnt += 1024; + sample_counter += 1024; } else { if (pause >= 1024) { pause -= 1024; } else { - cnt = sample_duration; + sample_counter = sample_duration; pause = 0; } } @@ -122,12 +112,10 @@ void NumbersStationView::prepare_audio() { } void NumbersStationView::start_tx() { - uint32_t divider; - - sample_duration = sound_sizes[0]; - cnt = sample_duration; + sample_duration = sound_sizes[10]; // Announce + sample_counter = sample_duration; - id_test = 0; + code_index = 0; announce_loop = 2; segment = ANNOUNCE; @@ -144,23 +132,25 @@ void NumbersStationView::start_tx() { transmitter_model.set_baseband_bandwidth(1750000); transmitter_model.enable(); - divider = (1536000 / 44100) - 1; - baseband::set_audiotx_data( - divider, + (1536000 / 44100) - 1, number_bw.value(), false, 0 ); } -// TODO: Copied from soundboard, make globally available -uint16_t NumbersStationView::fb_to_uint16(const std::string& fb) { - return (fb[1] << 8) + fb[0]; -} - -uint32_t NumbersStationView::fb_to_uint32(const std::string& fb) { - return (fb[3] << 24) + (fb[2] << 16) + (fb[1] << 8) + fb[0]; +void NumbersStationView::on_tick_second() { + if (check_armed.value()) { + armed_blink = not armed_blink; + + if (armed_blink) + check_armed.set_style(&style_red); + else + check_armed.set_style(&style()); + + check_armed.set_dirty(); + } } NumbersStationView::NumbersStationView( @@ -168,31 +158,17 @@ NumbersStationView::NumbersStationView( ) : nav_ (nav) { uint8_t c; - uint8_t y, m, d, dayofweek; - size_t size; + //uint8_t y, m, d, dayofweek; - char file_buffer[32]; + reader = std::make_unique(); c = 0; for (auto& file_name : file_names) { - auto error = file.open("/numbers/" + file_name + ".wav"); - if (!error.is_valid()) { - file.seek(22); - file.read(file_buffer, 2); - - // Is file mono ? - if (fb_to_uint16(file_buffer) == 1) { - file.seek(40); - file.read(file_buffer, 4); - size = fb_to_uint32(file_buffer); - if (!size) break; - sound_sizes[c] = size; + if (reader->open("/numbers/" + file_name + ".wav")) { + if ((reader->channels() == 1) && (reader->sample_rate() == 44100) && (reader->bits_per_sample() == 8)) { + sound_sizes[c] = reader->data_size(); c++; - } else { - break; } - } else { - break; } } @@ -204,12 +180,15 @@ NumbersStationView::NumbersStationView( &text_title, &field_frequency, &number_bw, + &text_code, &symfield_code, - &button_tx, + &check_armed, + &button_tx_now, &button_exit } }); - number_bw.set_value(120); + number_bw.set_value(75); + check_armed.set_value(false); field_frequency.set_value(transmitter_model.tuning_frequency()); field_frequency.set_step(50000); @@ -225,6 +204,18 @@ NumbersStationView::NumbersStationView( }; }; + check_armed.on_select = [this](Checkbox&) { + if (check_armed.value()) { + armed_blink = false; + signal_token_tick_second = time::signal_tick_second += [this]() { + this->on_tick_second(); + }; + } else { + check_armed.set_style(&style()); + time::signal_tick_second -= signal_token_tick_second; + } + }; + // DEBUG symfield_code.set_value(0, 10); symfield_code.set_value(1, 3); @@ -236,13 +227,12 @@ NumbersStationView::NumbersStationView( symfield_code.set_value(7, 7); symfield_code.set_value(8, 8); symfield_code.set_value(9, 0); - transmitter_model.set_tuning_frequency(103300000); // 103.3MHz - - for (c = 0; c < 10; c++) - symfield_code.set_symbol_list(c, "0123456789pP"); - + symfield_code.set_value(10, 12); // End + for (c = 0; c < 25; c++) + symfield_code.set_symbol_list(c, "0123456789pPE"); +/* rtc::RTC datetime; rtcGetTime(&RTCD1, &datetime); @@ -254,8 +244,9 @@ NumbersStationView::NumbersStationView( dayofweek = (y + y/4 - y/100 + y/400 + month_table[m-1] + d) % 7; text_title.set(day_of_week[dayofweek]); +*/ - button_tx.on_select = [this, &nav](Button&){ + button_tx_now.on_select = [this, &nav](Button&){ this->start_tx(); }; diff --git a/firmware/application/ui_numbers.hpp b/firmware/application/ui_numbers.hpp index a399da38..f0624d7a 100644 --- a/firmware/application/ui_numbers.hpp +++ b/firmware/application/ui_numbers.hpp @@ -28,10 +28,12 @@ #include "ui_receiver.hpp" #include "ui_navigation.hpp" #include "ui_font_fixed_8x16.hpp" +#include "time.hpp" #include "clock_manager.hpp" -#include "message.hpp" #include "baseband_api.hpp" -#include "file.hpp" +#include "utility.hpp" +#include "message.hpp" +#include "wavfile.hpp" namespace ui { @@ -61,6 +63,7 @@ private: NavigationView& nav_; segments segment; + bool armed = false; bool file_error = false; uint32_t sound_sizes[11]; @@ -77,22 +80,24 @@ private: { "9" }, { "announce" } }; - - const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; - const char * day_of_week[7] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; - File file; - uint8_t id_test, announce_loop; - uint32_t cnt; + // const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; + // const char * day_of_week[7] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; + + std::unique_ptr reader; + + uint8_t code_index, announce_loop; + uint32_t sample_counter; uint32_t sample_duration; int8_t audio_buffer[1024]; uint32_t pause = 0; - + bool armed_blink; + SignalToken signal_token_tick_second; + + void on_tick_second(); void on_tuning_frequency_changed(rf::Frequency f); void prepare_audio(); void start_tx(); - uint16_t fb_to_uint16(const std::string& fb); - uint32_t fb_to_uint32(const std::string& fb); // Schedule: save on sd card // For each day of the week, max 8 messages ? @@ -105,7 +110,7 @@ private: // Frequency list and sequence Text text_title { - { 1 * 8, 1 * 16, 11, 16 }, + { 1 * 8, 8 * 16, 11, 16 }, "Schedule:" }; @@ -113,22 +118,31 @@ private: { 1 * 8, 4 }, }; NumberField number_bw { - { 12 * 8, 2 * 16 }, + { 12 * 8, 4 }, 3, {1, 150}, 1, ' ' }; + Text text_code { + { 20, 4 * 16, 5 * 8, 16 }, + "Code:" + }; SymField symfield_code { - { 1*8, 3 * 16 }, - 10, + { 20, 5 * 16 }, + 25, false }; - Button button_tx { - { 21 * 8, 13 * 16, 64, 32 }, - "TX !" + Checkbox check_armed { + { 2 * 8, 13 * 16 }, + 5, + "Armed" + }; + Button button_tx_now { + { 18 * 8, 13 * 16, 10 * 8, 32 }, + "TX now" }; Button button_exit { { 21 * 8, 16 * 16, 64, 32 }, diff --git a/firmware/application/ui_record_view.cpp b/firmware/application/ui_record_view.cpp index 186c014b..97ff98ce 100644 --- a/firmware/application/ui_record_view.cpp +++ b/firmware/application/ui_record_view.cpp @@ -26,7 +26,6 @@ #include "portapack_shared_memory.hpp" using namespace portapack; -#include "file.hpp" #include "time.hpp" #include "string_format.hpp" @@ -34,134 +33,6 @@ using namespace portapack; #include -class FileWriter : public Writer { -public: - FileWriter() = default; - - FileWriter(const FileWriter&) = delete; - FileWriter& operator=(const FileWriter&) = delete; - FileWriter(FileWriter&& file) = delete; - FileWriter& operator=(FileWriter&&) = delete; - - Optional create(const std::string& filename) { - return file.create(filename); - } - - File::Result write(const void* const buffer, const size_t bytes) override { - auto write_result = file.write(buffer, bytes) ; - if( write_result.is_ok() ) { - bytes_written += write_result.value(); - } - return write_result; - } - -protected: - File file; - uint64_t bytes_written { 0 }; -}; - -using RawFileWriter = FileWriter; - -class WAVFileWriter : public FileWriter { -public: - WAVFileWriter( - size_t sampling_rate - ) : header { sampling_rate } - { - } - - - WAVFileWriter(const WAVFileWriter&) = delete; - WAVFileWriter& operator=(const WAVFileWriter&) = delete; - WAVFileWriter(WAVFileWriter&&) = delete; - WAVFileWriter& operator=(WAVFileWriter&&) = delete; - - ~WAVFileWriter() { - update_header(); - } - - Optional create( - const std::string& filename - ) { - const auto create_error = FileWriter::create(filename); - if( create_error.is_valid() ) { - return create_error; - } else { - return update_header(); - } - } - -private: - struct fmt_pcm_t { - constexpr fmt_pcm_t( - const uint32_t sampling_rate - ) : nSamplesPerSec { sampling_rate }, - nAvgBytesPerSec { nSamplesPerSec * nBlockAlign } - { - } - - private: - const uint8_t ckID[4] { 'f', 'm', 't', ' ' }; - const uint32_t cksize { 16 }; - const uint16_t wFormatTag { 0x0001 }; - const uint16_t nChannels { 1 }; - const uint32_t nSamplesPerSec; - const uint32_t nAvgBytesPerSec; - const uint16_t nBlockAlign { 2 }; - const uint16_t wBitsPerSample { 16 }; - }; - - struct data_t { - void set_size(const uint32_t value) { - cksize = value; - } - - private: - const uint8_t ckID[4] { 'd', 'a', 't', 'a' }; - uint32_t cksize { 0 }; - }; - - struct header_t { - constexpr header_t( - const uint32_t sampling_rate - ) : fmt { sampling_rate } - { - } - - void set_data_size(const uint32_t value) { - data.set_size(value); - cksize = sizeof(header_t) + value - 8; - } - - private: - const uint8_t riff_id[4] { 'R', 'I', 'F', 'F' }; - uint32_t cksize { 0 }; - const uint8_t wave_id[4] { 'W', 'A', 'V', 'E' }; - fmt_pcm_t fmt; - data_t data; - }; - - header_t header; - - Optional update_header() { - header.set_data_size(bytes_written); - const auto seek_0_result = file.seek(0); - if( seek_0_result.is_error() ) { - return seek_0_result.error(); - } - const auto old_position = seek_0_result.value(); - const auto write_result = file.write(&header, sizeof(header)); - if( write_result.is_error() ) { - return write_result.error(); - } - const auto seek_old_result = file.seek(old_position); - if( seek_old_result.is_error() ) { - return seek_old_result.error(); - } - return { }; - } -}; - namespace ui { RecordView::RecordView( @@ -294,7 +165,7 @@ void RecordView::start() { return; } - auto p = std::make_unique(); + auto p = std::make_unique(); auto create_error = p->create( filename_stem + ".C16" ); diff --git a/firmware/application/ui_record_view.hpp b/firmware/application/ui_record_view.hpp index 985e5eee..becca773 100644 --- a/firmware/application/ui_record_view.hpp +++ b/firmware/application/ui_record_view.hpp @@ -26,7 +26,7 @@ #include "capture_thread.hpp" #include "signal.hpp" - +#include "wavfile.hpp" #include "bitmap.hpp" #include diff --git a/firmware/application/ui_soundboard.cpp b/firmware/application/ui_soundboard.cpp index 13fa4892..c58d4ad1 100644 --- a/firmware/application/ui_soundboard.cpp +++ b/firmware/application/ui_soundboard.cpp @@ -24,7 +24,6 @@ #include "ui_soundboard.hpp" -#include "ch.h" #include "lfsr_random.hpp" #include "ui_alphanum.hpp" #include "portapack.hpp" @@ -56,15 +55,15 @@ void SoundBoardView::do_random() { void SoundBoardView::prepare_audio() { - if (cnt >= sample_duration) { + if (sample_counter >= sample_duration) { if (tx_mode == NORMAL) { if (!check_loop.value()) { pbar.set_value(0); transmitter_model.disable(); return; } else { - file.seek(44); - cnt = 0; + reader->rewind(); + sample_counter = 0; } } else { pbar.set_value(0); @@ -73,15 +72,17 @@ void SoundBoardView::prepare_audio() { } } - pbar.set_value(cnt); + pbar.set_value(sample_counter); - size_t bytes_read = file.read(audio_buffer, 1024).value(); + size_t bytes_read = reader->read(audio_buffer, 1024); // 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 < 1024; n++) + audio_buffer[n] = 0; - cnt += 1024; + sample_counter += 1024; baseband::set_fifo_data(audio_buffer); } @@ -105,16 +106,14 @@ void SoundBoardView::play_sound(uint16_t id) { if (sounds[id].size == 0) return; - auto error = file.open("/wav/" + sounds[id].filename); - if (error.is_valid()) return; + if (!reader->open("/wav/" + sounds[id].filename)) return; sample_duration = sounds[id].sample_duration; pbar.set_max(sample_duration); pbar.set_value(0); - cnt = 0; - file.seek(44); // Skip header + sample_counter = 0; prepare_audio(); @@ -177,7 +176,6 @@ void SoundBoardView::refresh_buttons(uint16_t id) { } void SoundBoardView::change_page(Button& button, const KeyEvent key) { - // Stupid way to find out if the button is on the sides if (button.screen_pos().x < 32) { if ((key == KeyEvent::Left) && (page > 0)) { @@ -193,14 +191,6 @@ void SoundBoardView::change_page(Button& button, const KeyEvent key) { } } -uint16_t SoundBoardView::fb_to_uint16(const std::string& fb) { - return (fb[1] << 8) + fb[0]; -} - -uint32_t SoundBoardView::fb_to_uint32(const std::string& fb) { - return (fb[3] << 24) + (fb[2] << 16) + (fb[1] << 8) + fb[0]; -} - void SoundBoardView::on_ctcss_changed(uint32_t v) { _ctcss_freq = v; } @@ -210,59 +200,36 @@ SoundBoardView::SoundBoardView( ) : nav_ (nav) { std::vector file_list; - std::string file_name; - uint32_t size; uint8_t c; - char file_buffer[32]; + reader = std::make_unique(); - baseband::run_image(portapack::spi_flash::image_tag_audio_tx); - file_list = scan_root_files("/wav", ".WAV"); - + c = 0; for (auto& file_name : file_list) { - - auto error = file.open("/wav/" + file_name); - - if (!error.is_valid()) { - - file.seek(22); - file.read(file_buffer, 2); - - // Is file mono ? - if (fb_to_uint16(file_buffer) == 1) { - file.seek(40); - file.read(file_buffer, 4); - size = fb_to_uint32(file_buffer); - sounds[c].size = size; - - file.seek(24); - file.read(file_buffer, 4); - sounds[c].sample_rate = fb_to_uint32(file_buffer); - - file.seek(34); - file.read(file_buffer, 2); - if (fb_to_uint16(file_buffer) > 8) { + if (reader->open("/wav/" + file_name)) { + if (reader->channels() == 1) { + 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; - size /= 2; - } else + else sounds[c].sixteenbit = false; - - sounds[c].ms_duration = (size * 1000) / sounds[c].sample_rate; - sounds[c].sample_duration = size; - + sounds[c].ms_duration = reader->ms_duration(); sounds[c].filename = file_name; sounds[c].shortname = remove_filename_extension(file_name); - c++; - if (c == 100) break; // Limit to 100 files + if (c == 105) break; // Limit to 105 files (5 pages) } } } + baseband::run_image(portapack::spi_flash::image_tag_audio_tx); + max_sound = c; - max_page = max_sound / 21; // 21 buttons per page + max_page = max_sound / 21; // 3 * 7 = 21 buttons per page add_children({ { &field_frequency, diff --git a/firmware/application/ui_soundboard.hpp b/firmware/application/ui_soundboard.hpp index 0df8e72b..67936cc7 100644 --- a/firmware/application/ui_soundboard.hpp +++ b/firmware/application/ui_soundboard.hpp @@ -29,8 +29,9 @@ #include "baseband_api.hpp" #include "ui_navigation.hpp" #include "ui_receiver.hpp" +#include "utility.hpp" #include "message.hpp" -#include "file.hpp" +#include "wavfile.hpp" namespace ui { @@ -63,15 +64,15 @@ private: uint32_t ms_duration = 0; }; - uint32_t cnt; + uint32_t sample_counter; uint32_t sample_duration; uint8_t page = 0; - File file; - uint16_t lfsr_v = 0x1337; - sound sounds[100]; + std::unique_ptr reader; + + sound sounds[105]; uint8_t max_sound; uint8_t max_page; @@ -112,8 +113,6 @@ private: void play_sound(uint16_t id); void prepare_audio(); void on_ctcss_changed(uint32_t v); - uint16_t fb_to_uint16(const std::string& fb); - uint32_t fb_to_uint32(const std::string& fb); Text text_duration { { 16, 236, 5 * 8, 16 } diff --git a/firmware/application/wavfile.cpp b/firmware/application/wavfile.cpp new file mode 100644 index 00000000..9cae58ac --- /dev/null +++ b/firmware/application/wavfile.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "wavfile.hpp" + +#include "portapack.hpp" +using namespace portapack; + +#include "file.hpp" +#include "time.hpp" +#include "utility.hpp" + +#include + +namespace ui { + +int WAVFileReader::open(const std::string& filename) { + if (filename == last_filename) { + rewind(); + return 1; // Already open + } + + auto error = file.open(filename); + + if (!error.is_valid()) { + file.read((void*)&header, sizeof(header)); + + // TODO: Check validity here + + last_filename = filename; + data_start = header.fmt.cksize + 28; // Skip "data" and cksize + + data_size_ = header.data.cksize; + sample_rate_ = header.fmt.nSamplesPerSec; + bytes_per_sample = header.fmt.wBitsPerSample / 8; + + rewind(); + + return 1; + } else { + return 0; + } +} + +size_t WAVFileReader::read(void * const data, const size_t bytes_to_read) { + return file.read(data, bytes_to_read).value(); +} + +void WAVFileReader::rewind() { + file.seek(data_start); +} + +uint32_t WAVFileReader::ms_duration() { + return ((data_size_ * 1000) / sample_rate_) / bytes_per_sample; +} + +int WAVFileReader::seek_mss(const uint16_t minutes, const uint8_t seconds, const uint32_t samples) { + + auto result = file.seek(data_start + ((((minutes * 60) + seconds) * sample_rate_) + samples) * bytes_per_sample); + + if (result.is_error()) + return 0; + + return 1; +} + +uint16_t WAVFileReader::channels() { + return header.fmt.nChannels; +} + +uint32_t WAVFileReader::sample_rate() { + return sample_rate_; +} + +uint32_t WAVFileReader::data_size() { + return data_size_; +} + +uint16_t WAVFileReader::bits_per_sample() { + return header.fmt.wBitsPerSample; +} + +WAVFileWriter::~WAVFileWriter() { + update_header(); +} + +Optional WAVFileWriter::create( + const std::string& filename +) { + const auto create_error = FileWriter::create(filename); + if( create_error.is_valid() ) { + return create_error; + } else { + return update_header(); + } +} + +Optional WAVFileWriter::update_header() { + header.set_data_size(bytes_written); + const auto seek_0_result = file.seek(0); + if( seek_0_result.is_error() ) { + return seek_0_result.error(); + } + const auto old_position = seek_0_result.value(); + const auto write_result = file.write(&header, sizeof(header)); + if( write_result.is_error() ) { + return write_result.error(); + } + const auto seek_old_result = file.seek(old_position); + if( seek_old_result.is_error() ) { + return seek_old_result.error(); + } + return { }; +} + +} /* namespace ui */ diff --git a/firmware/application/wavfile.hpp b/firmware/application/wavfile.hpp new file mode 100644 index 00000000..7a2e18a8 --- /dev/null +++ b/firmware/application/wavfile.hpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __WAVFILE_H__ +#define __WAVFILE_H__ + +#include "filewriter.hpp" + +#include +#include +#include + +namespace ui { + +class WAVFileReader { +public: + WAVFileReader() = default; + + WAVFileReader(const WAVFileReader&) = delete; + WAVFileReader& operator=(const WAVFileReader&) = delete; + WAVFileReader(WAVFileReader&&) = delete; + WAVFileReader& operator=(WAVFileReader&&) = delete; + + virtual ~WAVFileReader() = default; + + int open(const std::string& filename); + void rewind(); + uint32_t ms_duration(); + size_t read(void * const data, const size_t bytes_to_read); + int seek_mss(const uint16_t minutes, const uint8_t seconds, const uint32_t samples); + uint16_t channels(); + uint32_t sample_rate(); + uint32_t data_size(); + uint16_t bits_per_sample(); + +private: + File file; + uint32_t data_start; + uint32_t bytes_per_sample; + uint32_t data_size_; + uint32_t sample_rate_; + std::string last_filename = ""; + + struct fmt_pcm_t { + uint8_t ckID[4]; // fmt + uint32_t cksize; + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + }; + + struct data_t { + uint8_t ckID[4]; // data + uint32_t cksize; + }; + + struct header_t { + uint8_t riff_id[4]; // RIFF + uint32_t cksize; + uint8_t wave_id[4]; // WAVE + fmt_pcm_t fmt; + data_t data; + }; + + header_t header; +}; + + +class WAVFileWriter : public FileWriter { +public: + WAVFileWriter( + size_t sampling_rate + ) : header { sampling_rate } + { + } + + WAVFileWriter(const WAVFileWriter&) = delete; + WAVFileWriter& operator=(const WAVFileWriter&) = delete; + WAVFileWriter(WAVFileWriter&&) = delete; + WAVFileWriter& operator=(WAVFileWriter&&) = delete; + + ~WAVFileWriter(); + + Optional create(const std::string& filename); + +private: + struct fmt_pcm_t { + constexpr fmt_pcm_t( + const uint32_t sampling_rate + ) : nSamplesPerSec { sampling_rate }, + nAvgBytesPerSec { nSamplesPerSec * nBlockAlign } + { + } + + private: + const uint8_t ckID[4] { 'f', 'm', 't', ' ' }; + const uint32_t cksize { 16 }; + const uint16_t wFormatTag { 0x0001 }; + const uint16_t nChannels { 1 }; + const uint32_t nSamplesPerSec; + const uint32_t nAvgBytesPerSec; + const uint16_t nBlockAlign { 2 }; + const uint16_t wBitsPerSample { 16 }; + }; + + struct data_t { + void set_size(const uint32_t value) { + cksize = value; + } + + private: + const uint8_t ckID[4] { 'd', 'a', 't', 'a' }; + uint32_t cksize { 0 }; + }; + + struct header_t { + constexpr header_t( + const uint32_t sampling_rate + ) : fmt { sampling_rate } + { + } + + void set_data_size(const uint32_t value) { + data.set_size(value); + cksize = sizeof(header_t) + value - 8; + } + + private: + const uint8_t riff_id[4] { 'R', 'I', 'F', 'F' }; + uint32_t cksize { 0 }; + const uint8_t wave_id[4] { 'W', 'A', 'V', 'E' }; + fmt_pcm_t fmt; + data_t data; + }; + + header_t header; + + Optional update_header(); +}; + +} /* namespace ui */ + +#endif/*__WAVFILE_H__*/ diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 178d9f90..df9ff773 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -564,9 +564,9 @@ void Checkbox::set_text(const std::string value) { set_dirty(); } -std::string Checkbox::text() const { +/*std::string Checkbox::text() const { return text_; -} +}*/ void Checkbox::set_value(const bool value) { value_ = value; diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 7360d88a..49e3b66f 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -263,7 +263,7 @@ public: } void set_text(const std::string value); - std::string text() const; + // std::string text() const; void set_value(const bool value); bool value() const; diff --git a/firmware/common/utility.hpp b/firmware/common/utility.hpp index c6cfab94..97aff3dd 100644 --- a/firmware/common/utility.hpp +++ b/firmware/common/utility.hpp @@ -31,6 +31,14 @@ #define LOCATE_IN_RAM __attribute__((section(".ramtext"))) +inline uint16_t fb_to_uint16(const std::string& fb) { + return (fb[1] << 8) + fb[0]; +} + +inline uint32_t fb_to_uint32(const std::string& fb) { + return (fb[3] << 24) + (fb[2] << 16) + (fb[1] << 8) + fb[0]; +} + constexpr size_t operator "" _KiB(unsigned long long v) { return v * 1024; } diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index 466adb64..92f8c9bf 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ