mirror of
https://github.com/UberGuidoZ/Flipper.git
synced 2026-09-18 18:41:31 +00:00
Updating many apps
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="WAV_Player",
|
||||
name="WAV Player",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="wav_player_app",
|
||||
cdefines=["APP_WAV_PLAYER"],
|
||||
stack_size=4 * 1024,
|
||||
order=60,
|
||||
fap_icon="wav_10px.png",
|
||||
fap_category="Music",
|
||||
fap_icon_assets="images",
|
||||
)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 142 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,84 @@
|
||||
#include "wav_parser.h"
|
||||
|
||||
#define TAG "WavParser"
|
||||
|
||||
const char* format_text(FormatTag tag) {
|
||||
switch(tag) {
|
||||
case FormatTagPCM:
|
||||
return "PCM";
|
||||
case FormatTagIEEE_FLOAT:
|
||||
return "IEEE FLOAT";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
};
|
||||
|
||||
struct WavParser {
|
||||
WavHeaderChunk header;
|
||||
WavFormatChunk format;
|
||||
WavDataChunk data;
|
||||
size_t wav_data_start;
|
||||
size_t wav_data_end;
|
||||
};
|
||||
|
||||
WavParser* wav_parser_alloc() {
|
||||
return malloc(sizeof(WavParser));
|
||||
}
|
||||
|
||||
void wav_parser_free(WavParser* parser) {
|
||||
free(parser);
|
||||
}
|
||||
|
||||
bool wav_parser_parse(WavParser* parser, Stream* stream) {
|
||||
stream_read(stream, (uint8_t*)&parser->header, sizeof(WavHeaderChunk));
|
||||
stream_read(stream, (uint8_t*)&parser->format, sizeof(WavFormatChunk));
|
||||
stream_read(stream, (uint8_t*)&parser->data, sizeof(WavDataChunk));
|
||||
|
||||
if(memcmp(parser->header.riff, "RIFF", 4) != 0 ||
|
||||
memcmp(parser->header.wave, "WAVE", 4) != 0) {
|
||||
FURI_LOG_E(TAG, "WAV: wrong header");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(memcmp(parser->format.fmt, "fmt ", 4) != 0) {
|
||||
FURI_LOG_E(TAG, "WAV: wrong format");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(parser->format.tag != FormatTagPCM || memcmp(parser->data.data, "data", 4) != 0) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"WAV: non-PCM format %u, next '%lu'",
|
||||
parser->format.tag,
|
||||
(uint32_t)parser->data.data);
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Format tag: %s, ch: %u, smplrate: %lu, bps: %lu, bits: %u",
|
||||
format_text(parser->format.tag),
|
||||
parser->format.channels,
|
||||
parser->format.sample_rate,
|
||||
parser->format.byte_per_sec,
|
||||
parser->format.bits_per_sample);
|
||||
|
||||
parser->wav_data_start = stream_tell(stream);
|
||||
parser->wav_data_end = parser->wav_data_start + parser->data.size;
|
||||
|
||||
FURI_LOG_I(TAG, "data: %u - %u", parser->wav_data_start, parser->wav_data_end);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t wav_parser_get_data_start(WavParser* parser) {
|
||||
return parser->wav_data_start;
|
||||
}
|
||||
|
||||
size_t wav_parser_get_data_end(WavParser* parser) {
|
||||
return parser->wav_data_end;
|
||||
}
|
||||
|
||||
size_t wav_parser_get_data_len(WavParser* parser) {
|
||||
return parser->wav_data_end - parser->wav_data_start;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
#include <toolbox/stream/stream.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
FormatTagPCM = 0x0001,
|
||||
FormatTagIEEE_FLOAT = 0x0003,
|
||||
} FormatTag;
|
||||
|
||||
typedef struct {
|
||||
uint8_t riff[4];
|
||||
uint32_t size;
|
||||
uint8_t wave[4];
|
||||
} WavHeaderChunk;
|
||||
|
||||
typedef struct {
|
||||
uint8_t fmt[4];
|
||||
uint32_t size;
|
||||
uint16_t tag;
|
||||
uint16_t channels;
|
||||
uint32_t sample_rate;
|
||||
uint32_t byte_per_sec;
|
||||
uint16_t block_align;
|
||||
uint16_t bits_per_sample;
|
||||
} WavFormatChunk;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[4];
|
||||
uint32_t size;
|
||||
} WavDataChunk;
|
||||
|
||||
typedef struct WavParser WavParser;
|
||||
|
||||
WavParser* wav_parser_alloc();
|
||||
|
||||
void wav_parser_free(WavParser* parser);
|
||||
|
||||
bool wav_parser_parse(WavParser* parser, Stream* stream);
|
||||
|
||||
size_t wav_parser_get_data_start(WavParser* parser);
|
||||
|
||||
size_t wav_parser_get_data_end(WavParser* parser);
|
||||
|
||||
size_t wav_parser_get_data_len(WavParser* parser);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,323 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <cli/cli.h>
|
||||
#include <gui/gui.h>
|
||||
#include <stm32wbxx_ll_dma.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include "wav_player_hal.h"
|
||||
#include "wav_parser.h"
|
||||
#include "wav_player_view.h"
|
||||
#include <math.h>
|
||||
#include <WAV_Player_icons.h>
|
||||
|
||||
#include <WAV_Player_icons.h>
|
||||
|
||||
#define TAG "WavPlayer"
|
||||
|
||||
#define WAVPLAYER_FOLDER "/ext/wav_player"
|
||||
|
||||
static bool open_wav_stream(Stream* stream) {
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
bool result = false;
|
||||
FuriString* path;
|
||||
path = furi_string_alloc();
|
||||
furi_string_set(path, WAVPLAYER_FOLDER);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, ".wav", &I_music_10px);
|
||||
browser_options.base_path = WAVPLAYER_FOLDER;
|
||||
browser_options.hide_ext = false;
|
||||
|
||||
bool ret = dialog_file_browser_show(dialogs, path, path, &browser_options);
|
||||
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
if(ret) {
|
||||
if(!file_stream_open(stream, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Cannot open file \"%s\"", furi_string_get_cstr(path));
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
furi_string_free(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
WavPlayerEventHalfTransfer,
|
||||
WavPlayerEventFullTransfer,
|
||||
WavPlayerEventCtrlVolUp,
|
||||
WavPlayerEventCtrlVolDn,
|
||||
WavPlayerEventCtrlMoveL,
|
||||
WavPlayerEventCtrlMoveR,
|
||||
WavPlayerEventCtrlOk,
|
||||
WavPlayerEventCtrlBack,
|
||||
} WavPlayerEventType;
|
||||
|
||||
typedef struct {
|
||||
WavPlayerEventType type;
|
||||
} WavPlayerEvent;
|
||||
|
||||
static void wav_player_dma_isr(void* ctx) {
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
|
||||
// half of transfer
|
||||
if(LL_DMA_IsActiveFlag_HT1(DMA1)) {
|
||||
LL_DMA_ClearFlag_HT1(DMA1);
|
||||
// fill first half of buffer
|
||||
WavPlayerEvent event = {.type = WavPlayerEventHalfTransfer};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
// transfer complete
|
||||
if(LL_DMA_IsActiveFlag_TC1(DMA1)) {
|
||||
LL_DMA_ClearFlag_TC1(DMA1);
|
||||
// fill second half of buffer
|
||||
WavPlayerEvent event = {.type = WavPlayerEventFullTransfer};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
Storage* storage;
|
||||
Stream* stream;
|
||||
WavParser* parser;
|
||||
uint16_t* sample_buffer;
|
||||
uint8_t* tmp_buffer;
|
||||
|
||||
size_t samples_count_half;
|
||||
size_t samples_count;
|
||||
|
||||
FuriMessageQueue* queue;
|
||||
|
||||
float volume;
|
||||
bool play;
|
||||
|
||||
WavPlayerView* view;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Gui* gui;
|
||||
NotificationApp* notification;
|
||||
} WavPlayerApp;
|
||||
|
||||
static WavPlayerApp* app_alloc() {
|
||||
WavPlayerApp* app = malloc(sizeof(WavPlayerApp));
|
||||
app->samples_count_half = 1024 * 4;
|
||||
app->samples_count = app->samples_count_half * 2;
|
||||
app->storage = furi_record_open(RECORD_STORAGE);
|
||||
app->stream = file_stream_alloc(app->storage);
|
||||
app->parser = wav_parser_alloc();
|
||||
app->sample_buffer = malloc(sizeof(uint16_t) * app->samples_count);
|
||||
app->tmp_buffer = malloc(sizeof(uint8_t) * app->samples_count);
|
||||
app->queue = furi_message_queue_alloc(10, sizeof(WavPlayerEvent));
|
||||
|
||||
app->volume = 10.0f;
|
||||
app->play = true;
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->view = wav_player_view_alloc();
|
||||
|
||||
view_dispatcher_add_view(app->view_dispatcher, 0, wav_player_view_get_view(app->view));
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
|
||||
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(app->notification, &sequence_display_backlight_enforce_on);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void app_free(WavPlayerApp* app) {
|
||||
view_dispatcher_remove_view(app->view_dispatcher, 0);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
wav_player_view_free(app->view);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
furi_message_queue_free(app->queue);
|
||||
free(app->tmp_buffer);
|
||||
free(app->sample_buffer);
|
||||
wav_parser_free(app->parser);
|
||||
stream_free(app->stream);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
notification_message(app->notification, &sequence_display_backlight_enforce_auto);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
free(app);
|
||||
}
|
||||
|
||||
// TODO: that works only with 8-bit 2ch audio
|
||||
static bool fill_data(WavPlayerApp* app, size_t index) {
|
||||
uint16_t* sample_buffer_start = &app->sample_buffer[index];
|
||||
size_t count = stream_read(app->stream, app->tmp_buffer, app->samples_count);
|
||||
|
||||
for(size_t i = count; i < app->samples_count; i++) {
|
||||
app->tmp_buffer[i] = 0;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < app->samples_count; i += 2) {
|
||||
float data = app->tmp_buffer[i];
|
||||
data -= UINT8_MAX / 2; // to signed
|
||||
data /= UINT8_MAX / 2; // scale -1..1
|
||||
|
||||
data *= app->volume; // volume
|
||||
data = tanhf(data); // hyperbolic tangent limiter
|
||||
|
||||
data *= UINT8_MAX / 2; // scale -128..127
|
||||
data += UINT8_MAX / 2; // to unsigned
|
||||
|
||||
if(data < 0) {
|
||||
data = 0;
|
||||
}
|
||||
|
||||
if(data > 255) {
|
||||
data = 255;
|
||||
}
|
||||
|
||||
sample_buffer_start[i / 2] = data;
|
||||
}
|
||||
|
||||
wav_player_view_set_data(app->view, sample_buffer_start, app->samples_count_half);
|
||||
|
||||
return count != app->samples_count;
|
||||
}
|
||||
|
||||
static void ctrl_callback(WavPlayerCtrl ctrl, void* ctx) {
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
WavPlayerEvent event;
|
||||
|
||||
switch(ctrl) {
|
||||
case WavPlayerCtrlVolUp:
|
||||
event.type = WavPlayerEventCtrlVolUp;
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
break;
|
||||
case WavPlayerCtrlVolDn:
|
||||
event.type = WavPlayerEventCtrlVolDn;
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
break;
|
||||
case WavPlayerCtrlMoveL:
|
||||
event.type = WavPlayerEventCtrlMoveL;
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
break;
|
||||
case WavPlayerCtrlMoveR:
|
||||
event.type = WavPlayerEventCtrlMoveR;
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
break;
|
||||
case WavPlayerCtrlOk:
|
||||
event.type = WavPlayerEventCtrlOk;
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
break;
|
||||
case WavPlayerCtrlBack:
|
||||
event.type = WavPlayerEventCtrlBack;
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void app_run(WavPlayerApp* app) {
|
||||
if(!open_wav_stream(app->stream)) return;
|
||||
if(!wav_parser_parse(app->parser, app->stream)) return;
|
||||
|
||||
wav_player_view_set_volume(app->view, app->volume);
|
||||
wav_player_view_set_start(app->view, wav_parser_get_data_start(app->parser));
|
||||
wav_player_view_set_current(app->view, stream_tell(app->stream));
|
||||
wav_player_view_set_end(app->view, wav_parser_get_data_end(app->parser));
|
||||
wav_player_view_set_play(app->view, app->play);
|
||||
|
||||
wav_player_view_set_context(app->view, app->queue);
|
||||
wav_player_view_set_ctrl_callback(app->view, ctrl_callback);
|
||||
|
||||
bool eof = fill_data(app, 0);
|
||||
eof = fill_data(app, app->samples_count_half);
|
||||
|
||||
wav_player_speaker_init();
|
||||
wav_player_dma_init((uint32_t)app->sample_buffer, app->samples_count);
|
||||
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, wav_player_dma_isr, app->queue);
|
||||
|
||||
if(furi_hal_speaker_acquire(1000)) {
|
||||
wav_player_dma_start();
|
||||
wav_player_speaker_start();
|
||||
|
||||
WavPlayerEvent event;
|
||||
|
||||
while(1) {
|
||||
if(furi_message_queue_get(app->queue, &event, FuriWaitForever) == FuriStatusOk) {
|
||||
if(event.type == WavPlayerEventHalfTransfer) {
|
||||
eof = fill_data(app, 0);
|
||||
wav_player_view_set_current(app->view, stream_tell(app->stream));
|
||||
if(eof) {
|
||||
stream_seek(
|
||||
app->stream,
|
||||
wav_parser_get_data_start(app->parser),
|
||||
StreamOffsetFromStart);
|
||||
}
|
||||
|
||||
} else if(event.type == WavPlayerEventFullTransfer) {
|
||||
eof = fill_data(app, app->samples_count_half);
|
||||
wav_player_view_set_current(app->view, stream_tell(app->stream));
|
||||
if(eof) {
|
||||
stream_seek(
|
||||
app->stream,
|
||||
wav_parser_get_data_start(app->parser),
|
||||
StreamOffsetFromStart);
|
||||
}
|
||||
} else if(event.type == WavPlayerEventCtrlVolUp) {
|
||||
if(app->volume < 9.9) app->volume += 0.4;
|
||||
wav_player_view_set_volume(app->view, app->volume);
|
||||
} else if(event.type == WavPlayerEventCtrlVolDn) {
|
||||
if(app->volume > 0.01) app->volume -= 0.4;
|
||||
wav_player_view_set_volume(app->view, app->volume);
|
||||
} else if(event.type == WavPlayerEventCtrlMoveL) {
|
||||
int32_t seek =
|
||||
stream_tell(app->stream) - wav_parser_get_data_start(app->parser);
|
||||
seek =
|
||||
MIN(seek, (int32_t)(wav_parser_get_data_len(app->parser) / (size_t)100));
|
||||
stream_seek(app->stream, -seek, StreamOffsetFromCurrent);
|
||||
wav_player_view_set_current(app->view, stream_tell(app->stream));
|
||||
} else if(event.type == WavPlayerEventCtrlMoveR) {
|
||||
int32_t seek = wav_parser_get_data_end(app->parser) - stream_tell(app->stream);
|
||||
seek =
|
||||
MIN(seek, (int32_t)(wav_parser_get_data_len(app->parser) / (size_t)100));
|
||||
stream_seek(app->stream, seek, StreamOffsetFromCurrent);
|
||||
wav_player_view_set_current(app->view, stream_tell(app->stream));
|
||||
} else if(event.type == WavPlayerEventCtrlOk) {
|
||||
app->play = !app->play;
|
||||
wav_player_view_set_play(app->view, app->play);
|
||||
|
||||
if(!app->play) {
|
||||
wav_player_speaker_stop();
|
||||
} else {
|
||||
wav_player_speaker_start();
|
||||
}
|
||||
} else if(event.type == WavPlayerEventCtrlBack) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wav_player_speaker_stop();
|
||||
wav_player_dma_stop();
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL);
|
||||
}
|
||||
|
||||
int32_t wav_player_app(void* p) {
|
||||
UNUSED(p);
|
||||
WavPlayerApp* app = app_alloc();
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(!storage_simply_mkdir(storage, WAVPLAYER_FOLDER)) {
|
||||
FURI_LOG_E(TAG, "Could not create folder %s", WAVPLAYER_FOLDER);
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
app_run(app);
|
||||
app_free(app);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "wav_player_hal.h"
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
#include <stm32wbxx_ll_dma.h>
|
||||
|
||||
#define FURI_HAL_SPEAKER_TIMER TIM16
|
||||
#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1
|
||||
#define DMA_INSTANCE DMA1, LL_DMA_CHANNEL_1
|
||||
|
||||
void wav_player_speaker_init() {
|
||||
LL_TIM_InitTypeDef TIM_InitStruct = {0};
|
||||
TIM_InitStruct.Prescaler = 4;
|
||||
TIM_InitStruct.Autoreload = 255;
|
||||
LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct);
|
||||
|
||||
LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
|
||||
TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
|
||||
TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
|
||||
TIM_OC_InitStruct.CompareValue = 127;
|
||||
LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct);
|
||||
}
|
||||
|
||||
void wav_player_speaker_start() {
|
||||
LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER);
|
||||
LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER);
|
||||
}
|
||||
|
||||
void wav_player_speaker_stop() {
|
||||
LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER);
|
||||
LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER);
|
||||
}
|
||||
|
||||
void wav_player_dma_init(uint32_t address, size_t size) {
|
||||
uint32_t dma_dst = (uint32_t) & (FURI_HAL_SPEAKER_TIMER->CCR1);
|
||||
|
||||
LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
|
||||
LL_DMA_SetDataLength(DMA_INSTANCE, size);
|
||||
|
||||
LL_DMA_SetPeriphRequest(DMA_INSTANCE, LL_DMAMUX_REQ_TIM16_UP);
|
||||
LL_DMA_SetDataTransferDirection(DMA_INSTANCE, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
|
||||
LL_DMA_SetChannelPriorityLevel(DMA_INSTANCE, LL_DMA_PRIORITY_VERYHIGH);
|
||||
LL_DMA_SetMode(DMA_INSTANCE, LL_DMA_MODE_CIRCULAR);
|
||||
LL_DMA_SetPeriphIncMode(DMA_INSTANCE, LL_DMA_PERIPH_NOINCREMENT);
|
||||
LL_DMA_SetMemoryIncMode(DMA_INSTANCE, LL_DMA_MEMORY_INCREMENT);
|
||||
LL_DMA_SetPeriphSize(DMA_INSTANCE, LL_DMA_PDATAALIGN_HALFWORD);
|
||||
LL_DMA_SetMemorySize(DMA_INSTANCE, LL_DMA_MDATAALIGN_HALFWORD);
|
||||
|
||||
LL_DMA_EnableIT_TC(DMA_INSTANCE);
|
||||
LL_DMA_EnableIT_HT(DMA_INSTANCE);
|
||||
}
|
||||
|
||||
void wav_player_dma_start() {
|
||||
LL_DMA_EnableChannel(DMA_INSTANCE);
|
||||
LL_TIM_EnableDMAReq_UPDATE(FURI_HAL_SPEAKER_TIMER);
|
||||
}
|
||||
|
||||
void wav_player_dma_stop() {
|
||||
LL_DMA_DisableChannel(DMA_INSTANCE);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void wav_player_speaker_init();
|
||||
|
||||
void wav_player_speaker_start();
|
||||
|
||||
void wav_player_speaker_stop();
|
||||
|
||||
void wav_player_dma_init(uint32_t address, size_t size);
|
||||
|
||||
void wav_player_dma_start();
|
||||
|
||||
void wav_player_dma_stop();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,201 @@
|
||||
#include "wav_player_view.h"
|
||||
|
||||
#define DATA_COUNT 116
|
||||
|
||||
struct WavPlayerView {
|
||||
View* view;
|
||||
WavPlayerCtrlCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool play;
|
||||
float volume;
|
||||
size_t start;
|
||||
size_t end;
|
||||
size_t current;
|
||||
uint8_t data[DATA_COUNT];
|
||||
} WavPlayerViewModel;
|
||||
|
||||
float map(float x, float in_min, float in_max, float out_min, float out_max) {
|
||||
return (x - in_min) * (out_max - out_min + 1) / (in_max - in_min + 1) + out_min;
|
||||
}
|
||||
|
||||
static void wav_player_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
WavPlayerViewModel* model = _model;
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
uint8_t x_pos = 0;
|
||||
uint8_t y_pos = 0;
|
||||
|
||||
// volume
|
||||
x_pos = 124;
|
||||
y_pos = 0;
|
||||
const float volume = (64 / 10.0f) * model->volume;
|
||||
canvas_draw_frame(canvas, x_pos, y_pos, 4, 64);
|
||||
canvas_draw_box(canvas, x_pos, y_pos + (64 - volume), 4, volume);
|
||||
|
||||
// play / pause
|
||||
x_pos = 58;
|
||||
y_pos = 55;
|
||||
if(!model->play) {
|
||||
canvas_draw_line(canvas, x_pos, y_pos, x_pos + 8, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos + 8, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos);
|
||||
} else {
|
||||
canvas_draw_box(canvas, x_pos, y_pos, 3, 9);
|
||||
canvas_draw_box(canvas, x_pos + 4, y_pos, 3, 9);
|
||||
}
|
||||
|
||||
x_pos = 78;
|
||||
y_pos = 55;
|
||||
canvas_draw_line(canvas, x_pos, y_pos, x_pos + 4, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos + 4, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos);
|
||||
|
||||
x_pos = 82;
|
||||
y_pos = 55;
|
||||
canvas_draw_line(canvas, x_pos, y_pos, x_pos + 4, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos + 4, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos);
|
||||
|
||||
x_pos = 40;
|
||||
y_pos = 55;
|
||||
canvas_draw_line(canvas, x_pos, y_pos, x_pos - 4, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos - 4, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos);
|
||||
|
||||
x_pos = 44;
|
||||
y_pos = 55;
|
||||
canvas_draw_line(canvas, x_pos, y_pos, x_pos - 4, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos - 4, y_pos + 4);
|
||||
canvas_draw_line(canvas, x_pos, y_pos + 8, x_pos, y_pos);
|
||||
|
||||
// len
|
||||
x_pos = 4;
|
||||
y_pos = 47;
|
||||
const uint8_t play_len = 116;
|
||||
uint8_t play_pos = map(model->current, model->start, model->end, 0, play_len - 4);
|
||||
|
||||
canvas_draw_frame(canvas, x_pos, y_pos, play_len, 4);
|
||||
canvas_draw_box(canvas, x_pos + play_pos, y_pos - 2, 4, 8);
|
||||
canvas_draw_box(canvas, x_pos, y_pos, play_pos, 4);
|
||||
|
||||
// osc
|
||||
x_pos = 4;
|
||||
y_pos = 0;
|
||||
for(size_t i = 1; i < DATA_COUNT; i++) {
|
||||
canvas_draw_line(canvas, x_pos + i - 1, model->data[i - 1], x_pos + i, model->data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool wav_player_view_input_callback(InputEvent* event, void* context) {
|
||||
WavPlayerView* wav_player_view = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(wav_player_view->callback) {
|
||||
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
|
||||
if(event->key == InputKeyUp) {
|
||||
wav_player_view->callback(WavPlayerCtrlVolUp, wav_player_view->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
wav_player_view->callback(WavPlayerCtrlVolDn, wav_player_view->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
wav_player_view->callback(WavPlayerCtrlMoveL, wav_player_view->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyRight) {
|
||||
wav_player_view->callback(WavPlayerCtrlMoveR, wav_player_view->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyOk) {
|
||||
wav_player_view->callback(WavPlayerCtrlOk, wav_player_view->context);
|
||||
consumed = true;
|
||||
} else if(event->key == InputKeyBack) {
|
||||
wav_player_view->callback(WavPlayerCtrlBack, wav_player_view->context);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
WavPlayerView* wav_player_view_alloc() {
|
||||
WavPlayerView* wav_view = malloc(sizeof(WavPlayerView));
|
||||
wav_view->view = view_alloc();
|
||||
view_set_context(wav_view->view, wav_view);
|
||||
view_allocate_model(wav_view->view, ViewModelTypeLocking, sizeof(WavPlayerViewModel));
|
||||
view_set_draw_callback(wav_view->view, wav_player_view_draw_callback);
|
||||
view_set_input_callback(wav_view->view, wav_player_view_input_callback);
|
||||
|
||||
return wav_view;
|
||||
}
|
||||
|
||||
void wav_player_view_free(WavPlayerView* wav_view) {
|
||||
furi_assert(wav_view);
|
||||
view_free(wav_view->view);
|
||||
free(wav_view);
|
||||
}
|
||||
|
||||
View* wav_player_view_get_view(WavPlayerView* wav_view) {
|
||||
furi_assert(wav_view);
|
||||
return wav_view->view;
|
||||
}
|
||||
|
||||
void wav_player_view_set_volume(WavPlayerView* wav_view, float volume) {
|
||||
furi_assert(wav_view);
|
||||
with_view_model(
|
||||
wav_view->view, WavPlayerViewModel * model, { model->volume = volume; }, true);
|
||||
}
|
||||
|
||||
void wav_player_view_set_start(WavPlayerView* wav_view, size_t start) {
|
||||
furi_assert(wav_view);
|
||||
with_view_model(
|
||||
wav_view->view, WavPlayerViewModel * model, { model->start = start; }, true);
|
||||
}
|
||||
|
||||
void wav_player_view_set_end(WavPlayerView* wav_view, size_t end) {
|
||||
furi_assert(wav_view);
|
||||
with_view_model(
|
||||
wav_view->view, WavPlayerViewModel * model, { model->end = end; }, true);
|
||||
}
|
||||
|
||||
void wav_player_view_set_current(WavPlayerView* wav_view, size_t current) {
|
||||
furi_assert(wav_view);
|
||||
with_view_model(
|
||||
wav_view->view, WavPlayerViewModel * model, { model->current = current; }, true);
|
||||
}
|
||||
|
||||
void wav_player_view_set_play(WavPlayerView* wav_view, bool play) {
|
||||
furi_assert(wav_view);
|
||||
with_view_model(
|
||||
wav_view->view, WavPlayerViewModel * model, { model->play = play; }, true);
|
||||
}
|
||||
|
||||
void wav_player_view_set_data(WavPlayerView* wav_view, uint16_t* data, size_t data_count) {
|
||||
furi_assert(wav_view);
|
||||
with_view_model(
|
||||
wav_view->view,
|
||||
WavPlayerViewModel * model,
|
||||
{
|
||||
size_t inc = (data_count / DATA_COUNT) - 1;
|
||||
|
||||
for(size_t i = 0; i < DATA_COUNT; i++) {
|
||||
model->data[i] = *data / 6;
|
||||
if(model->data[i] > 42) model->data[i] = 42;
|
||||
data += inc;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void wav_player_view_set_ctrl_callback(WavPlayerView* wav_view, WavPlayerCtrlCallback callback) {
|
||||
furi_assert(wav_view);
|
||||
wav_view->callback = callback;
|
||||
}
|
||||
|
||||
void wav_player_view_set_context(WavPlayerView* wav_view, void* context) {
|
||||
furi_assert(wav_view);
|
||||
wav_view->context = context;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct WavPlayerView WavPlayerView;
|
||||
|
||||
typedef enum {
|
||||
WavPlayerCtrlVolUp,
|
||||
WavPlayerCtrlVolDn,
|
||||
WavPlayerCtrlMoveL,
|
||||
WavPlayerCtrlMoveR,
|
||||
WavPlayerCtrlOk,
|
||||
WavPlayerCtrlBack,
|
||||
} WavPlayerCtrl;
|
||||
|
||||
typedef void (*WavPlayerCtrlCallback)(WavPlayerCtrl ctrl, void* context);
|
||||
|
||||
WavPlayerView* wav_player_view_alloc();
|
||||
|
||||
void wav_player_view_free(WavPlayerView* wav_view);
|
||||
|
||||
View* wav_player_view_get_view(WavPlayerView* wav_view);
|
||||
|
||||
void wav_player_view_set_volume(WavPlayerView* wav_view, float volume);
|
||||
|
||||
void wav_player_view_set_start(WavPlayerView* wav_view, size_t start);
|
||||
|
||||
void wav_player_view_set_end(WavPlayerView* wav_view, size_t end);
|
||||
|
||||
void wav_player_view_set_current(WavPlayerView* wav_view, size_t current);
|
||||
|
||||
void wav_player_view_set_play(WavPlayerView* wav_view, bool play);
|
||||
|
||||
void wav_player_view_set_data(WavPlayerView* wav_view, uint16_t* data, size_t data_count);
|
||||
|
||||
void wav_player_view_set_ctrl_callback(WavPlayerView* wav_view, WavPlayerCtrlCallback callback);
|
||||
|
||||
void wav_player_view_set_context(WavPlayerView* wav_view, void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user