mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-13 13:09:49 +04:00
add wav player & music player icon
This commit is contained in:
@@ -21,6 +21,7 @@ extern int32_t accessor_app(void* p);
|
||||
extern int32_t archive_app(void* p);
|
||||
extern int32_t bad_usb_app(void* p);
|
||||
extern int32_t u2f_app(void* p);
|
||||
extern int32_t wav_player_app(void* p);
|
||||
extern int32_t uart_echo_app(void* p);
|
||||
extern int32_t blink_test_app(void* p);
|
||||
extern int32_t bt_debug_app(void* p);
|
||||
@@ -339,7 +340,7 @@ const FlipperApplication FLIPPER_PLUGINS[] = {
|
||||
{.app = music_player_app,
|
||||
.name = "Music Player",
|
||||
.stack_size = 2048,
|
||||
.icon = &A_Plugins_14,
|
||||
.icon = &A_MusicPlayer_14,
|
||||
.flags = FlipperApplicationFlagDefault},
|
||||
#endif
|
||||
|
||||
@@ -350,6 +351,15 @@ const FlipperApplication FLIPPER_PLUGINS[] = {
|
||||
.icon = &A_Plugins_14,
|
||||
.flags = FlipperApplicationFlagDefault},
|
||||
#endif
|
||||
|
||||
#ifdef APP_WAV_PLAYER
|
||||
{.app = wav_player_app,
|
||||
.name = "WAV Player",
|
||||
.stack_size = 4096,
|
||||
.icon = &A_MusicPlayer_14,
|
||||
.flags = FlipperApplicationFlagDefault},
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
const size_t FLIPPER_PLUGINS_COUNT = COUNT_OF(FLIPPER_PLUGINS);
|
||||
|
||||
@@ -47,6 +47,7 @@ APP_UPDATER = 1
|
||||
# Plugins
|
||||
APP_MUSIC_PLAYER = 1
|
||||
APP_SNAKE_GAME = 1
|
||||
APP_WAV_PLAYER = 1
|
||||
|
||||
# Debug
|
||||
APP_ACCESSOR = 1
|
||||
@@ -240,6 +241,13 @@ CFLAGS += -DAPP_SNAKE_GAME
|
||||
SRV_GUI = 1
|
||||
endif
|
||||
|
||||
APP_WAV_PLAYER ?= 0
|
||||
ifeq ($(APP_WAV_PLAYER), 1)
|
||||
CFLAGS += -DAPP_WAV_PLAYER
|
||||
SRV_GUI = 1
|
||||
endif
|
||||
|
||||
|
||||
APP_IBUTTON ?= 0
|
||||
ifeq ($(APP_IBUTTON), 1)
|
||||
CFLAGS += -DAPP_IBUTTON
|
||||
|
||||
84
applications/wav_player/wav_parser.c
Normal file
84
applications/wav_player/wav_parser.c
Normal file
@@ -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;
|
||||
}
|
||||
51
applications/wav_player/wav_parser.h
Normal file
51
applications/wav_player/wav_parser.h
Normal file
@@ -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
|
||||
301
applications/wav_player/wav_player.c
Normal file
301
applications/wav_player/wav_player.c
Normal file
@@ -0,0 +1,301 @@
|
||||
#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"
|
||||
|
||||
#define TAG "WavPlayer"
|
||||
|
||||
static bool open_wav_stream(Storage* storage, Stream* stream) {
|
||||
DialogsApp* dialogs = furi_record_open("dialogs");
|
||||
bool result = false;
|
||||
string_t path;
|
||||
string_init(path);
|
||||
string_set_str(path, "/ext/wav_player");
|
||||
bool ret =
|
||||
dialog_file_browser_show(dialogs, path, path, ".wav",
|
||||
true,
|
||||
&I_music_10px,
|
||||
false);
|
||||
|
||||
furi_record_close("dialogs");
|
||||
if(ret) {
|
||||
if(!file_stream_open(stream, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Cannot open file \"%s\"", string_get_cstr(path));
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
string_clear(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) {
|
||||
osMessageQueueId_t 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};
|
||||
osMessageQueuePut(event_queue, &event, 0, 0);
|
||||
}
|
||||
|
||||
// transfer complete
|
||||
if(LL_DMA_IsActiveFlag_TC1(DMA1)) {
|
||||
LL_DMA_ClearFlag_TC1(DMA1);
|
||||
// fill second half of buffer
|
||||
WavPlayerEvent event = {.type = WavPlayerEventFullTransfer};
|
||||
osMessageQueuePut(event_queue, &event, 0, 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;
|
||||
|
||||
osMessageQueueId_t 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("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 = osMessageQueueNew(10, sizeof(WavPlayerEvent), NULL);
|
||||
|
||||
app->volume = 10.0f;
|
||||
app->play = true;
|
||||
|
||||
app->gui = furi_record_open("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("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("gui");
|
||||
|
||||
osMessageQueueDelete(app->queue);
|
||||
free(app->tmp_buffer);
|
||||
free(app->sample_buffer);
|
||||
wav_parser_free(app->parser);
|
||||
stream_free(app->stream);
|
||||
furi_record_close("storage");
|
||||
|
||||
notification_message(app->notification, &sequence_display_backlight_enforce_auto);
|
||||
furi_record_close("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) {
|
||||
osMessageQueueId_t event_queue = ctx;
|
||||
WavPlayerEvent event;
|
||||
|
||||
switch(ctrl) {
|
||||
case WavPlayerCtrlVolUp:
|
||||
event.type = WavPlayerEventCtrlVolUp;
|
||||
osMessageQueuePut(event_queue, &event, 0, 0);
|
||||
break;
|
||||
case WavPlayerCtrlVolDn:
|
||||
event.type = WavPlayerEventCtrlVolDn;
|
||||
osMessageQueuePut(event_queue, &event, 0, 0);
|
||||
break;
|
||||
case WavPlayerCtrlMoveL:
|
||||
event.type = WavPlayerEventCtrlMoveL;
|
||||
osMessageQueuePut(event_queue, &event, 0, 0);
|
||||
break;
|
||||
case WavPlayerCtrlMoveR:
|
||||
event.type = WavPlayerEventCtrlMoveR;
|
||||
osMessageQueuePut(event_queue, &event, 0, 0);
|
||||
break;
|
||||
case WavPlayerCtrlOk:
|
||||
event.type = WavPlayerEventCtrlOk;
|
||||
osMessageQueuePut(event_queue, &event, 0, 0);
|
||||
break;
|
||||
case WavPlayerCtrlBack:
|
||||
event.type = WavPlayerEventCtrlBack;
|
||||
osMessageQueuePut(event_queue, &event, 0, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void app_run(WavPlayerApp* app) {
|
||||
if(!open_wav_stream(app->storage, 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);
|
||||
|
||||
wav_player_dma_start();
|
||||
wav_player_speaker_start();
|
||||
|
||||
WavPlayerEvent event;
|
||||
|
||||
while(1) {
|
||||
if(osMessageQueueGet(app->queue, &event, NULL, osWaitForever) == osOK) {
|
||||
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.2;
|
||||
wav_player_view_set_volume(app->view, app->volume);
|
||||
} else if(event.type == WavPlayerEventCtrlVolDn) {
|
||||
if(app->volume > 0.01) app->volume -= 0.2;
|
||||
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, wav_parser_get_data_len(app->parser) / 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, wav_parser_get_data_len(app->parser) / 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_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL);
|
||||
}
|
||||
|
||||
int32_t wav_player_app(void* p) {
|
||||
WavPlayerApp* app = app_alloc();
|
||||
app_run(app);
|
||||
app_free(app);
|
||||
return 0;
|
||||
}
|
||||
58
applications/wav_player/wav_player_hal.c
Normal file
58
applications/wav_player/wav_player_hal.c
Normal file
@@ -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);
|
||||
}
|
||||
23
applications/wav_player/wav_player_hal.h
Normal file
23
applications/wav_player/wav_player_hal.h
Normal file
@@ -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
|
||||
214
applications/wav_player/wav_player_view.c
Normal file
214
applications/wav_player/wav_player_view.c
Normal file
@@ -0,0 +1,214 @@
|
||||
#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;
|
||||
return 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;
|
||||
return 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;
|
||||
return 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;
|
||||
return 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;
|
||||
return 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;
|
||||
}
|
||||
return 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;
|
||||
}
|
||||
45
applications/wav_player/wav_player_view.h
Normal file
45
applications/wav_player/wav_player_view.h
Normal file
@@ -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
|
||||
@@ -372,6 +372,13 @@ const uint8_t _A_Infrared_14_4[] = {0x01,0x00,0x0e,0x00,0x00,0x5f,0x82,0x02,0x05
|
||||
const uint8_t _A_Infrared_14_5[] = {0x01,0x00,0x15,0x00,0x00,0x2f,0xc2,0x07,0x08,0x82,0x01,0x47,0xc1,0x01,0x05,0x98,0x14,0x41,0xa3,0xf8,0x83,0x80,0x47,0xff,0x3f,};
|
||||
const uint8_t* const _A_Infrared_14[] = {_A_Infrared_14_0,_A_Infrared_14_1,_A_Infrared_14_2,_A_Infrared_14_3,_A_Infrared_14_4,_A_Infrared_14_5};
|
||||
|
||||
const uint8_t _A_MusicPlayer_14_0[] = {0x01,0x00,0x17,0x00,0x00,0x1e,0x02,0x01,0xc0,0x80,0xf0,0x20,0x74,0x08,0x15,0x02,0x00,0x01,0x3b,0x84,0x02,0xf0,0x01,0x29,0x80,0x5c,0x80,};
|
||||
const uint8_t _A_MusicPlayer_14_1[] = {0x01,0x00,0x16,0x00,0x80,0x41,0x20,0x10,0xe8,0x04,0x7a,0x01,0x12,0x80,0x40,0x80,0x27,0x80,0x81,0xf0,0x00,0x25,0x80,0x80,0x86,0x94,};
|
||||
const uint8_t _A_MusicPlayer_14_2[] = {0x01,0x00,0x13,0x00,0x00,0x34,0x82,0x03,0x30,0x81,0xcc,0x20,0x51,0x08,0x00,0x05,0x63,0x90,0x08,0xf0,0x04,0xa1,0x80,};
|
||||
const uint8_t _A_MusicPlayer_14_3[] = {0x01,0x00,0x16,0x00,0x82,0x40,0x21,0xd0,0x08,0xf4,0x02,0x25,0x00,0x81,0x00,0x52,0x07,0x20,0x81,0xcc,0x00,0x23,0x01,0x90,0x06,0xd4,};
|
||||
const uint8_t _A_MusicPlayer_14_4[] = {0x01,0x00,0x15,0x00,0x00,0x2c,0x82,0x01,0x70,0x80,0x7c,0x20,0x19,0x08,0x04,0x40,0x02,0x91,0xc8,0x04,0x78,0x02,0x50,0xc8,0x00,};
|
||||
const uint8_t* const _A_MusicPlayer_14[] = {_A_MusicPlayer_14_0,_A_MusicPlayer_14_1,_A_MusicPlayer_14_2,_A_MusicPlayer_14_3,_A_MusicPlayer_14_4};
|
||||
|
||||
const uint8_t _A_NFC_14_0[] = {0x00,0x00,0x08,0x00,0x10,0x00,0x12,0x00,0x22,0x42,0x24,0x87,0x24,0x8D,0x24,0x99,0x24,0xF1,0x24,0x62,0x24,0x00,0x22,0x00,0x12,0x00,0x10,0x00,0x08,};
|
||||
const uint8_t _A_NFC_14_1[] = {0x01,0x00,0x1a,0x00,0x80,0x42,0x20,0x11,0x00,0x09,0x48,0x28,0x52,0x0c,0x3c,0x83,0x1b,0x20,0xcc,0xc8,0x3e,0x32,0x0b,0x14,0x80,0x1a,0x21,0x34,0x84,0x00,};
|
||||
const uint8_t _A_NFC_14_2[] = {0x01,0x00,0x10,0x00,0x00,0x3d,0x0a,0x01,0x87,0x80,0x63,0x60,0x19,0x98,0x07,0xc6,0x01,0x62,0x09,0xc0,};
|
||||
|
||||
@@ -99,6 +99,7 @@ extern const Icon A_FileManager_14;
|
||||
extern const Icon A_GPIO_14;
|
||||
extern const Icon A_Games_14;
|
||||
extern const Icon A_Infrared_14;
|
||||
extern const Icon A_MusicPlayer_14;
|
||||
extern const Icon A_NFC_14;
|
||||
extern const Icon A_Passport_14;
|
||||
extern const Icon A_Plugins_14;
|
||||
|
||||
Reference in New Issue
Block a user