Soundboard: Arbitrary samplerate support for wave files
Screenshots
@ -99,8 +99,9 @@ void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phas
|
|||||||
send_message(&message);
|
send_message(&message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_audiotx_data(const uint32_t bw) {
|
void set_audiotx_data(const uint32_t divider, const uint32_t bw) {
|
||||||
const AudioTXConfigMessage message {
|
const AudioTXConfigMessage message {
|
||||||
|
divider,
|
||||||
bw
|
bw
|
||||||
};
|
};
|
||||||
send_message(&message);
|
send_message(&message);
|
||||||
|
@ -53,7 +53,7 @@ struct WFMConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void set_ccir_data( const uint32_t samples_per_tone, const uint16_t tone_count);
|
void set_ccir_data( const uint32_t samples_per_tone, const uint16_t tone_count);
|
||||||
void set_audiotx_data(const uint32_t bw);
|
void set_audiotx_data(const uint32_t divider, const uint32_t bw);
|
||||||
void set_fifo_data(const int8_t * data);
|
void set_fifo_data(const int8_t * data);
|
||||||
void set_pwmrssi(int32_t avg, bool enabled);
|
void set_pwmrssi(int32_t avg, bool enabled);
|
||||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space,
|
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space,
|
||||||
|
@ -138,7 +138,7 @@ static std::string find_last_file_matching_pattern(const std::string& pattern) {
|
|||||||
return last_match;
|
return last_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string remove_filename_extension(const std::string& filename) {
|
std::string remove_filename_extension(const std::string& filename) {
|
||||||
const auto extension_index = filename.find_last_of('.');
|
const auto extension_index = filename.find_last_of('.');
|
||||||
return filename.substr(0, extension_index);
|
return filename.substr(0, extension_index);
|
||||||
}
|
}
|
||||||
@ -178,14 +178,14 @@ std::string next_filename_stem_matching_pattern(const std::string& filename_stem
|
|||||||
return filename_stem;
|
return filename_stem;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> scan_root_files(const std::string& extension) {
|
std::vector<std::string> scan_root_files(const std::string& directory, const std::string& extension) {
|
||||||
std::vector<std::string> file_list { };
|
std::vector<std::string> file_list { };
|
||||||
std::string fname;
|
std::string fname;
|
||||||
FRESULT res;
|
FRESULT res;
|
||||||
DIR dir;
|
DIR dir;
|
||||||
static FILINFO fno;
|
static FILINFO fno;
|
||||||
|
|
||||||
res = f_opendir(&dir, "/");
|
res = f_opendir(&dir, directory.c_str());
|
||||||
if (res == FR_OK) {
|
if (res == FR_OK) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
res = f_readdir(&dir, &fno);
|
res = f_readdir(&dir, &fno);
|
||||||
|
@ -35,8 +35,9 @@
|
|||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
std::string remove_filename_extension(const std::string& filename);
|
||||||
std::string next_filename_stem_matching_pattern(const std::string& filename_stem_pattern);
|
std::string next_filename_stem_matching_pattern(const std::string& filename_stem_pattern);
|
||||||
std::vector<std::string> scan_root_files(const std::string& extension);
|
std::vector<std::string> scan_root_files(const std::string& directory, const std::string& extension);
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
namespace filesystem {
|
namespace filesystem {
|
||||||
|
@ -59,7 +59,7 @@ void AboutView::on_show() {
|
|||||||
transmitter_model.set_baseband_bandwidth(1750000);
|
transmitter_model.set_baseband_bandwidth(1750000);
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
baseband::set_audiotx_data(15);
|
baseband::set_audiotx_data(32, 15);
|
||||||
|
|
||||||
//audio::headphone::set_volume(volume_t::decibel(0 - 99) + audio::headphone::volume_range().max);
|
//audio::headphone::set_volume(volume_t::decibel(0 - 99) + audio::headphone::volume_range().max);
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ private:
|
|||||||
|
|
||||||
// 0123456789A 0123456789A
|
// 0123456789A 0123456789A
|
||||||
const credits_t credits[10] = { {"GURUS", "J. BOONE", false},
|
const credits_t credits[10] = { {"GURUS", "J. BOONE", false},
|
||||||
{"GURYS", "M. OSSMANN", true},
|
{"GURUS", "M. OSSMANN", true},
|
||||||
{"BUGS", "FURRTEK", true},
|
{"BUGS", "FURRTEK", true},
|
||||||
{"RDS WAVE", "C. JACQUET", true},
|
{"RDS WAVE", "C. JACQUET", true},
|
||||||
{"POCSAG RX", "T. SAILER", false},
|
{"POCSAG RX", "T. SAILER", false},
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "ui_soundboard.hpp"
|
#include "ui_soundboard.hpp"
|
||||||
|
|
||||||
#include "ch.h"
|
#include "ch.h"
|
||||||
|
#include "file.hpp"
|
||||||
|
|
||||||
#include "ui_alphanum.hpp"
|
#include "ui_alphanum.hpp"
|
||||||
#include "ff.h"
|
#include "ff.h"
|
||||||
@ -108,7 +109,9 @@ void SoundBoardView::on_tuning_frequency_changed(rf::Frequency f) {
|
|||||||
|
|
||||||
void SoundBoardView::play_sound(uint16_t id) {
|
void SoundBoardView::play_sound(uint16_t id) {
|
||||||
|
|
||||||
auto error = file.open(sounds[id].filename);
|
if (sounds[id].size == 0) return;
|
||||||
|
|
||||||
|
auto error = file.open("/wav/" + sounds[id].filename);
|
||||||
if (error.is_valid()) return;
|
if (error.is_valid()) return;
|
||||||
|
|
||||||
sample_duration = sounds[id].sample_duration;
|
sample_duration = sounds[id].sample_duration;
|
||||||
@ -132,7 +135,7 @@ void SoundBoardView::play_sound(uint16_t id) {
|
|||||||
transmitter_model.set_baseband_bandwidth(1750000);
|
transmitter_model.set_baseband_bandwidth(1750000);
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
baseband::set_audiotx_data(number_bw.value());
|
baseband::set_audiotx_data(1536000 / sounds[id].sample_rate, number_bw.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundBoardView::show_infos(uint16_t id) {
|
void SoundBoardView::show_infos(uint16_t id) {
|
||||||
@ -174,7 +177,6 @@ void SoundBoardView::change_page(Button& button, const KeyEvent key) {
|
|||||||
refresh_buttons(button.id);
|
refresh_buttons(button.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (button.screen_pos().x > 120) {
|
if (button.screen_pos().x > 120) {
|
||||||
if ((key == KeyEvent::Right) && (page < max_page - 1)) {
|
if ((key == KeyEvent::Right) && (page < max_page - 1)) {
|
||||||
page++;
|
page++;
|
||||||
@ -204,12 +206,12 @@ SoundBoardView::SoundBoardView(
|
|||||||
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
||||||
|
|
||||||
file_list = scan_root_files(".WAV");
|
file_list = scan_root_files("/wav", ".WAV");
|
||||||
|
|
||||||
c = 0;
|
c = 0;
|
||||||
for (auto& file_name : file_list) {
|
for (auto& file_name : file_list) {
|
||||||
|
|
||||||
auto error = file.open(file_name);
|
auto error = file.open("/wav/" + file_name);
|
||||||
|
|
||||||
file.seek(40);
|
file.seek(40);
|
||||||
file.read(file_buffer, 4);
|
file.read(file_buffer, 4);
|
||||||
@ -240,7 +242,7 @@ SoundBoardView::SoundBoardView(
|
|||||||
sounds[c].sample_duration = size;
|
sounds[c].sample_duration = size;
|
||||||
|
|
||||||
sounds[c].filename = file_name;
|
sounds[c].filename = file_name;
|
||||||
sounds[c].shortname = file_name.substr(0, file_name.find_last_of("."));
|
sounds[c].shortname = remove_filename_extension(file_name);
|
||||||
|
|
||||||
c++;
|
c++;
|
||||||
if (c == 100) break;
|
if (c == 100) break;
|
||||||
@ -271,8 +273,6 @@ SoundBoardView::SoundBoardView(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const auto button_dir = [this](Button& button, const KeyEvent key) {
|
const auto button_dir = [this](Button& button, const KeyEvent key) {
|
||||||
(void)button;
|
|
||||||
|
|
||||||
this->change_page(button, key);
|
this->change_page(button, key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -52,14 +52,14 @@ private:
|
|||||||
tx_modes tx_mode = NORMAL;
|
tx_modes tx_mode = NORMAL;
|
||||||
|
|
||||||
struct sound {
|
struct sound {
|
||||||
std::string filename;
|
std::string filename = "";
|
||||||
std::string shortname;
|
std::string shortname = "";
|
||||||
bool stereo;
|
bool stereo = false;
|
||||||
bool sixteenbit;
|
bool sixteenbit = false;
|
||||||
uint32_t sample_rate;
|
uint32_t sample_rate = 0;
|
||||||
uint32_t size;
|
uint32_t size = 0;
|
||||||
uint32_t sample_duration;
|
uint32_t sample_duration = 0;
|
||||||
uint32_t ms_duration;
|
uint32_t ms_duration = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t cnt;
|
uint32_t cnt;
|
||||||
|
@ -34,12 +34,12 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer){
|
|||||||
|
|
||||||
if (!configured) return;
|
if (!configured) return;
|
||||||
|
|
||||||
ai = 0;
|
//ai = 0;
|
||||||
for (size_t i = 0; i<buffer.count; i++) {
|
for (size_t i = 0; i<buffer.count; i++) {
|
||||||
|
|
||||||
// Audio preview sample generation: 1536000/48000 = 32
|
// Audio preview sample generation: 1536000/divider = samplerate
|
||||||
if (!as) {
|
if (!as) {
|
||||||
as = 32;
|
as = divider;
|
||||||
audio_fifo.out(sample);
|
audio_fifo.out(sample);
|
||||||
//preview_audio_buffer.p[ai++] = sample << 8;
|
//preview_audio_buffer.p[ai++] = sample << 8;
|
||||||
|
|
||||||
@ -78,6 +78,7 @@ void AudioTXProcessor::on_message(const Message* const msg) {
|
|||||||
// m = (262144 * a) / 1536000
|
// m = (262144 * a) / 1536000
|
||||||
// a = 262144 / 1536000 (*1000 = 171)
|
// a = 262144 / 1536000 (*1000 = 171)
|
||||||
bw = 171 * (message->bw);
|
bw = 171 * (message->bw);
|
||||||
|
divider = message->divider;
|
||||||
as = 0;
|
as = 0;
|
||||||
|
|
||||||
configured = true;
|
configured = true;
|
||||||
|
@ -42,7 +42,9 @@ private:
|
|||||||
FIFO<int8_t> audio_fifo = { audio_fifo_data, 11 }; // 43ms @ 48000Hz
|
FIFO<int8_t> audio_fifo = { audio_fifo_data, 11 }; // 43ms @ 48000Hz
|
||||||
|
|
||||||
uint32_t bw;
|
uint32_t bw;
|
||||||
uint8_t as = 0, ai;
|
uint32_t divider;
|
||||||
|
uint8_t as = 0;
|
||||||
|
|
||||||
int8_t re, im;
|
int8_t re, im;
|
||||||
int8_t sample;
|
int8_t sample;
|
||||||
|
|
||||||
|
@ -561,12 +561,15 @@ public:
|
|||||||
class AudioTXConfigMessage : public Message {
|
class AudioTXConfigMessage : public Message {
|
||||||
public:
|
public:
|
||||||
constexpr AudioTXConfigMessage(
|
constexpr AudioTXConfigMessage(
|
||||||
|
const uint32_t divider,
|
||||||
const uint32_t bw
|
const uint32_t bw
|
||||||
) : Message { ID::AudioTXConfig },
|
) : Message { ID::AudioTXConfig },
|
||||||
|
divider(divider),
|
||||||
bw(bw)
|
bw(bw)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint32_t divider;
|
||||||
const uint32_t bw;
|
const uint32_t bw;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BIN
pictures/about.png
Normal file
After Width: | Height: | Size: 227 KiB |
BIN
pictures/afsk.png
Normal file
After Width: | Height: | Size: 227 KiB |
BIN
pictures/config.png
Normal file
After Width: | Height: | Size: 227 KiB |
BIN
pictures/lcr.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
pictures/ook_enc.png
Normal file
After Width: | Height: | Size: 227 KiB |
BIN
pictures/pocsag.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
pictures/rds.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
pictures/soundboard.png
Normal file
After Width: | Height: | Size: 227 KiB |
BIN
pictures/xylos.png
Normal file
After Width: | Height: | Size: 5.1 KiB |