diff --git a/components/audio_mp3/audio_mp3.c b/components/audio_mp3/audio_mp3.c index 33b962eafea43cf79dc43df2cff3270c45492374..73758ad054bc7ba2b3dc9d03f6e3fce327d4b87e 100644 --- a/components/audio_mp3/audio_mp3.c +++ b/components/audio_mp3/audio_mp3.c @@ -17,18 +17,40 @@ #define MINIMP3_IMPLEMENTATION #include "minimp3.h" +#define BUFFER_SIZE (32 * 1024) + typedef struct { st3m_media control; mp3dec_t mp3d; char *path; + char *artist; + char *title; + int year; int started; int samplerate; int channels; uint8_t *data; size_t size; + size_t count; int pos; + int offset; + + int file_size; + FILE *file; } mp3_state; +static void mp3_fetch_data(mp3_state *mp3) { + if (mp3->pos + (16 * 1024) >= mp3->count) { + memmove(mp3->data, &mp3->data[mp3->pos], mp3->count - mp3->pos); + mp3->offset += mp3->pos; + mp3->count -= mp3->pos; + mp3->pos = 0; + } + int read = + fread(mp3->data + mp3->count, 1, mp3->size - mp3->count, mp3->file); + mp3->count += read; +} + static void mp3_draw(st3m_media *media, Ctx *ctx) { mp3_state *self = (void *)media; @@ -36,163 +58,161 @@ static void mp3_draw(st3m_media *media, Ctx *ctx) { ctx_gray(ctx, 0); ctx_fill(ctx); ctx_rgb(ctx, 1.0, 1.0, 1.0); - ctx_font_size(ctx, 120); - ctx_rectangle (ctx,-120 + self->pos * 240.0 / self->size, 0, 10, 10); + ctx_rectangle(ctx, -120, 0, 240, 1); + ctx_rectangle(ctx, -120 + self->offset * 240.0 / self->file_size, -32, 2, + 64); ctx_fill(ctx); + ctx_font_size(ctx, 24); + ctx_text_align(ctx, CTX_TEXT_ALIGN_CENTER); + ctx_move_to(ctx, 0, -40); + ctx_text(ctx, self->artist); + ctx_move_to(ctx, 0, 64); + ctx_text(ctx, self->title); } -static int over_render = 0; - static void mp3_think(st3m_media *media, float ms_elapsed) { mp3_state *self = (void *)media; + mp3_fetch_data(self); if (!self->started) { self->started = 1; mp3_think(media, 100); } - int samples_needed = ((AUDIO_BUF_SIZE - st3m_media_samples_queued ()) / 2) - 2400; - //if (samples_needed > 7000) samples_needed = 7000; + int samples_needed = + ((AUDIO_BUF_SIZE - st3m_media_samples_queued()) / 2) - 2400; int samples; mp3dec_frame_info_t info; - if (samples_needed>0) do { - int16_t rendered[MINIMP3_MAX_SAMPLES_PER_FRAME]; - samples = mp3dec_decode_frame(&self->mp3d, self->data + self->pos, - self->size - self->pos, rendered, &info); - self->samplerate = info.hz; - self->channels = info.channels; - self->pos += info.frame_bytes; - //printf (".. %i\n", samples); -#if 1 - if (self->samplerate == 44100) { - int phase = 0; - if (info.channels == 1) - for (int i = 0; i < samples; i++) { - again1: - self->control.audio_buffer[self->control.audio_w++] = - rendered[i]; - if (self->control.audio_w >= AUDIO_BUF_SIZE) - self->control.audio_w = 0; - phase += ((48000 / 44100.0) - 1.0) * 65536; - if (phase > 65536) { - phase -= 65536; - phase -= ((48000 / 44100.0) - 1.0) * 65536; - goto again1; - } - } - else if (info.channels == 2) { + if (samples_needed > 0 && self->offset + 8192 < self->file_size) do { + int16_t rendered[MINIMP3_MAX_SAMPLES_PER_FRAME]; + samples = + mp3dec_decode_frame(&self->mp3d, self->data + self->pos, + self->count - self->pos, rendered, &info); + self->samplerate = info.hz; + self->channels = info.channels; + self->pos += info.frame_bytes; + + if (self->samplerate != 48000) { int phase = 0; - for (int i = 0; i < samples ; i++) { - again2: - self->control.audio_buffer[self->control.audio_w++] = - rendered[i*2]; - if (self->control.audio_w >= AUDIO_BUF_SIZE) - self->control.audio_w = 0; - self->control.audio_buffer[self->control.audio_w++] = - rendered[i*2+1]; - if (self->control.audio_w >= AUDIO_BUF_SIZE) - self->control.audio_w = 0; - - phase += ((48000 / 44100.0) - 1.0) * 65536; - if (phase > 65536) { - phase -= 65536; - phase -= ((48000 / 44100.0) - 1.0) * 65536; - goto again2; + int fraction = ((48000.0 / self->samplerate) - 1.0) * 65536; + if (info.channels == 1) + for (int i = 0; i < samples; i++) { + again1: + self->control.audio_buffer[self->control.audio_w++] = + rendered[i]; + if (self->control.audio_w >= AUDIO_BUF_SIZE) + self->control.audio_w = 0; + phase += fraction; + if (phase > 65536) { + phase -= 65536; + phase -= fraction; + goto again1; + } + } + else if (info.channels == 2) { + int phase = 0; + for (int i = 0; i < samples; i++) { + again2: + self->control.audio_buffer[self->control.audio_w++] = + rendered[i * 2]; + if (self->control.audio_w >= AUDIO_BUF_SIZE) + self->control.audio_w = 0; + self->control.audio_buffer[self->control.audio_w++] = + rendered[i * 2 + 1]; + if (self->control.audio_w >= AUDIO_BUF_SIZE) + self->control.audio_w = 0; + + phase += fraction; + if (phase > 65536) { + phase -= 65536; + phase -= fraction; + goto again2; + } } } - } - } else -#endif - { - if (info.channels == 1) - for (int i = 0; i < samples; i++) { - self->control.audio_buffer[self->control.audio_w++] = - rendered[i]; - if (self->control.audio_w >= AUDIO_BUF_SIZE) - self->control.audio_w = 0; - self->control.audio_buffer[self->control.audio_w++] = - rendered[i]; - if (self->control.audio_w >= AUDIO_BUF_SIZE) - self->control.audio_w = 0; - } - else if (info.channels == 2) { - for (int i = 0; i < samples; i++) { - self->control.audio_buffer[self->control.audio_w++] = - rendered[i*2]; - if (self->control.audio_w >= AUDIO_BUF_SIZE) - self->control.audio_w = 0; - self->control.audio_buffer[self->control.audio_w++] = - rendered[i*2+1]; - if (self->control.audio_w >= AUDIO_BUF_SIZE) - self->control.audio_w = 0; + } else { + if (info.channels == 1) + for (int i = 0; i < samples; i++) { + self->control.audio_buffer[self->control.audio_w++] = + rendered[i]; + if (self->control.audio_w >= AUDIO_BUF_SIZE) + self->control.audio_w = 0; + self->control.audio_buffer[self->control.audio_w++] = + rendered[i]; + if (self->control.audio_w >= AUDIO_BUF_SIZE) + self->control.audio_w = 0; + } + else if (info.channels == 2) { + for (int i = 0; i < samples; i++) { + self->control.audio_buffer[self->control.audio_w++] = + rendered[i * 2]; + if (self->control.audio_w >= AUDIO_BUF_SIZE) + self->control.audio_w = 0; + self->control.audio_buffer[self->control.audio_w++] = + rendered[i * 2 + 1]; + if (self->control.audio_w >= AUDIO_BUF_SIZE) + self->control.audio_w = 0; + } } } - } - samples_needed -= (samples); - } while (samples_needed > 0); + samples_needed -= (samples); + } while (samples_needed > 0); } static void mp3_destroy(st3m_media *media) { mp3_state *self = (void *)media; if (self->data) free(self->data); + if (self->file) fclose(self->file); + if (self->path) free(self->path); + if (self->title) free(self->title); + if (self->artist) free(self->artist); free(self); } -static int file_get_contents(const char *path, uint8_t **contents, - size_t *length) { - FILE *file; - long size; - long remaining; - uint8_t *buffer; - file = fopen(path, "rb"); - if (!file) { - return -1; - } - // fseek(file, 0, SEEK_END); - size = remaining = 1024 * 1024; // ftell(file); - - if (length) { - *length = size; - } - // rewind(file); - buffer = malloc(size + 2); - if (!buffer) { - fclose(file); - return -1; - } - int pos = 0; - do { - int to_read = 1024; - if (remaining < to_read) to_read = remaining; - int read = fread(buffer + pos, 1, to_read, file); - remaining -= read; - pos += read; - } while (remaining); - if (remaining) { - fclose(file); - free(buffer); - return -1; - } - fclose(file); - *contents = (unsigned char *)buffer; - buffer[size] = 0; - return 0; -} +typedef struct { + char tag[3]; + char artist[30]; + char title[30]; + char year[4]; + char pad[128]; +} id3tag_t; st3m_media *st3m_media_load_mp3(const char *path) { mp3_state *self = (mp3_state *)malloc(sizeof(mp3_state)); + id3tag_t id3; memset(self, 0, sizeof(mp3_state)); self->control.draw = mp3_draw; self->control.think = mp3_think; self->control.destroy = mp3_destroy; self->samplerate = 44100; - file_get_contents(path, &self->data, &self->size); - if (!self->data) { + self->file = fopen(path, "r"); + fseek(self->file, 0, SEEK_END); + self->file_size = ftell(self->file); + fseek(self->file, self->file_size - 128, SEEK_SET); + fread(&id3, 128, 1, self->file); + if (id3.tag[0] == 'T' && id3.tag[1] == 'A' && id3.tag[2] == 'G') { + self->title = strndup(id3.title, 30); + while (self->title[strlen(self->title) - 1] == ' ') + self->title[strlen(self->title) - 1] = 0; + self->artist = strndup(id3.artist, 30); + while (self->artist[strlen(self->artist) - 1] == ' ') + self->artist[strlen(self->artist) - 1] = 0; + self->year = atoi(id3.year); + } else { + self->artist = "Anonymous"; + self->title = strdup(strrchr(path, '/') + 1); + } + self->path = strdup(path); + rewind(self->file); + + self->data = malloc(BUFFER_SIZE); + self->size = BUFFER_SIZE; + if (!self->file) { printf("!!!!bo\n"); free(self); return NULL; } mp3dec_init(&self->mp3d); - + self->control.duration = 1200.0; return (st3m_media *)self; } diff --git a/components/audio_mp3/minimp3.h b/components/audio_mp3/minimp3.h index 93ca4c6e8018b40f059a7f705726d3b2b9c15de8..721e098eccbba66dbe850a74b75af601f0e9bdfd 100644 --- a/components/audio_mp3/minimp3.h +++ b/components/audio_mp3/minimp3.h @@ -40,7 +40,7 @@ typedef struct { #define MAX_L3_FRAME_PAYLOAD_BYTES \ MAX_FREE_FORMAT_FRAME_SIZE /* MUST be >= 320000/8/32000*1152 = 1440 */ -struct _mp3dec_scratch_t{ +struct _mp3dec_scratch_t { bs_t bs; uint8_t maindata[MAX_BITRESERVOIR_BYTES + MAX_L3_FRAME_PAYLOAD_BYTES]; L3_gr_info_t gr_info[4]; @@ -53,7 +53,7 @@ typedef struct { int reserv, free_format_bytes; unsigned char header[4], reserv_buf[511]; mp3dec_scratch_t scratch; - L12_scale_info sci; + L12_scale_info sci; } mp3dec_t; #ifdef __cplusplus @@ -85,7 +85,6 @@ int mp3dec_decode_frame(mp3dec_t *dec, const uint8_t *mp3, int mp3_bytes, #define MAX_FRAME_SYNC_MATCHES 10 #endif /* MAX_FRAME_SYNC_MATCHES */ - #define SHORT_BLOCK_TYPE 2 #define STOP_BLOCK_TYPE 3 #define MODE_MONO 3 @@ -235,14 +234,10 @@ static __inline__ __attribute__((always_inline)) int32_t minimp3_clip_int16_arm( #define HAVE_ARMV6 0 #endif - - typedef struct { uint8_t tab_offset, code_tab_width, band_count; } L12_subband_alloc_t; - - static void bs_init(bs_t *bs, const uint8_t *data, int bytes) { bs->buf = data; bs->pos = 0; @@ -1817,7 +1812,8 @@ static void mp3d_synth(float *xl, mp3d_sample_t *dstl, int nch, float *lins) { V0(0) V2(1) - V1(2) V2(3) V1(4) V2(5) V1(6) V2(7) + V1(2) + V2(3) V1(4) V2(5) V1(6) V2(7) { #ifndef MINIMP3_FLOAT_OUTPUT @@ -1940,7 +1936,8 @@ static void mp3d_synth(float *xl, mp3d_sample_t *dstl, int nch, float *lins) { S0(0) S2(1) - S1(2) S2(3) S1(4) S2(5) S1(6) S2(7) + S1(2) + S2(3) S1(4) S2(5) S1(6) S2(7) dstr[(15 - i) * nch] = mp3d_scale_pcm(a[1]); dstr[(17 + i) * nch] = mp3d_scale_pcm(b[1]); @@ -2026,8 +2023,6 @@ static int mp3d_find_frame(const uint8_t *mp3, int mp3_bytes, void mp3dec_init(mp3dec_t *dec) { dec->header[0] = 0; } -static L12_scale_info *sci = NULL; - int mp3dec_decode_frame(mp3dec_t *dec, const uint8_t *mp3, int mp3_bytes, mp3d_sample_t *pcm, mp3dec_frame_info_t *info) { int i = 0, igr, frame_size = 0, success = 1; @@ -2077,12 +2072,14 @@ int mp3dec_decode_frame(mp3dec_t *dec, const uint8_t *mp3, int mp3_bytes, mp3dec_init(dec); return 0; } - success = L3_restore_reservoir(dec, bs_frame, &dec->scratch, main_data_begin); + success = + L3_restore_reservoir(dec, bs_frame, &dec->scratch, main_data_begin); if (success) { for (igr = 0; igr < (HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm += 576 * info->channels) { memset(dec->scratch.grbuf[0], 0, 576 * 2 * sizeof(float)); - L3_decode(dec, &dec->scratch, dec->scratch.gr_info + igr * info->channels, + L3_decode(dec, &dec->scratch, + dec->scratch.gr_info + igr * info->channels, info->channels); mp3d_synth_granule(dec->qmf_state, dec->scratch.grbuf[0], 18, info->channels, pcm, dec->scratch.syn[0]); @@ -2097,11 +2094,12 @@ int mp3dec_decode_frame(mp3dec_t *dec, const uint8_t *mp3, int mp3_bytes, memset(dec->scratch.grbuf[0], 0, 576 * 2 * sizeof(float)); for (i = 0, igr = 0; igr < 3; igr++) { - if (12 == - (i += L12_dequantize_granule(dec->scratch.grbuf[0] + i, bs_frame, - &dec->sci, info->layer | 1))) { + if (12 == (i += L12_dequantize_granule(dec->scratch.grbuf[0] + i, + bs_frame, &dec->sci, + info->layer | 1))) { i = 0; - L12_apply_scf_384(&dec->sci, dec->sci.scf + igr, dec->scratch.grbuf[0]); + L12_apply_scf_384(&dec->sci, dec->sci.scf + igr, + dec->scratch.grbuf[0]); mp3d_synth_granule(dec->qmf_state, dec->scratch.grbuf[0], 12, info->channels, pcm, dec->scratch.syn[0]); memset(dec->scratch.grbuf[0], 0, 576 * 2 * sizeof(float)); diff --git a/components/micropython/usermodule/mp_media.c b/components/micropython/usermodule/mp_media.c index f9f30e7f66dc66606882e8c2e3375cf600f88715..aa813913f6683e8384b89ff202b439278d043f36 100644 --- a/components/micropython/usermodule/mp_media.c +++ b/components/micropython/usermodule/mp_media.c @@ -9,10 +9,10 @@ typedef struct _mp_ctx_obj_t { mp_obj_t user_data; } mp_ctx_obj_t; -STATIC mp_obj_t mp_set_path(mp_obj_t path) { - return mp_obj_new_int(st3m_media_set_path(mp_obj_str_get_str(path))); +STATIC mp_obj_t mp_load(mp_obj_t path) { + return mp_obj_new_int(st3m_media_load(mp_obj_str_get_str(path))); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_set_path_obj, mp_set_path); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_load_obj, mp_load); STATIC mp_obj_t mp_draw(mp_obj_t uctx_mp) { mp_ctx_obj_t *uctx = MP_OBJ_TO_PTR(uctx_mp); @@ -37,7 +37,7 @@ STATIC const mp_rom_map_elem_t globals_table[] = { { MP_ROM_QSTR(MP_QSTR_draw), MP_ROM_PTR(&mp_draw_obj) }, { MP_ROM_QSTR(MP_QSTR_think), MP_ROM_PTR(&mp_think_obj) }, { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&mp_stop_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_path), MP_ROM_PTR(&mp_set_path_obj) }, + { MP_ROM_QSTR(MP_QSTR_load), MP_ROM_PTR(&mp_load_obj) }, }; STATIC MP_DEFINE_CONST_DICT(globals, globals_table); diff --git a/components/st3m/st3m_media.c b/components/st3m/st3m_media.c index 074facb1324ac81b116399bd1c614b905d3eea18..7f3301ff0639da93d983d31ca7cbb69a5ebe8d68 100644 --- a/components/st3m/st3m_media.c +++ b/components/st3m/st3m_media.c @@ -8,125 +8,417 @@ #include "esp_log.h" #include "freertos/FreeRTOS.h" -//static const char *TAG = "st3m-media"; +// static const char *TAG = "st3m-media"; #ifdef CONFIG_FLOW3R_CTX_FLAVOUR_FULL -static st3m_media *media = NULL; +static st3m_media *audio_media = NULL; +static st3m_media *visual_media = NULL; +// XXX should be refactored to either be temporary SPIRAM allocation +// or a static shared global pcm queuing API +// static int16_t audio_buffer[AUDIO_BUF_SIZE]; +static st3m_media_playmode playmode = st3m_media_playmode_queue; +static float current_audio_in = 0.0; +static float current_audio_out = -1.0; +static float current_visual_in = 0.0; +static float current_visual_out = -1.0; + +static int file_is_visual(const char *path) { + if (!path) return 0; + if (strstr(path, ".png") || strstr(path, ".PNG") || strstr(path, ".jpg") || + strstr(path, ".JPG") || strstr(path, ".gif") || strstr(path, ".GIF") || + strstr(path, ".txt") || strstr(path, ".TXT")) { + return 1; + } + return 0; +} + +/////////////////////////////// + void st3m_media_audio_out(int16_t *rx, int16_t *tx, uint16_t len) { - if (!media) return; + if (!audio_media) return; for (int i = 0; i < len; i++) { - if ((media->audio_r + 1 != media->audio_w) && - (media->audio_r + 1 - AUDIO_BUF_SIZE != media->audio_w)) { - tx[i] = media->audio_buffer[media->audio_r++]; - if (media->audio_r >= AUDIO_BUF_SIZE) media->audio_r = 0; + if ((audio_media->audio_r + 1 != audio_media->audio_w) && + (audio_media->audio_r + 1 - AUDIO_BUF_SIZE != + audio_media->audio_w)) { + tx[i] = audio_media->audio_buffer[audio_media->audio_r++]; + if (audio_media->audio_r >= AUDIO_BUF_SIZE) + audio_media->audio_r = 0; } else tx[i] = 0; } } -int st3m_media_samples_queued (void) -{ - if (!media) return 0; - if (media->audio_r > media->audio_w) - return (AUDIO_BUF_SIZE-media->audio_r) + media->audio_w; - return media->audio_w - media->audio_r; +int st3m_media_samples_queued(void) { + if (!audio_media) return 0; + if (audio_media->audio_r > audio_media->audio_w) + return (AUDIO_BUF_SIZE - audio_media->audio_r) + audio_media->audio_w; + return audio_media->audio_w - audio_media->audio_r; } -st3m_media *st3m_media_load_mpg1(const char *path); -st3m_media *st3m_media_load_mod(const char *path); -st3m_media *st3m_media_load_mp3(const char *path); - void st3m_media_stop(void) { - if (media && media->destroy) media->destroy(media); - media = NULL; + if (audio_media && audio_media->destroy) audio_media->destroy(audio_media); + if (visual_media == audio_media) visual_media = NULL; + audio_media = NULL; st3m_audio_set_player_function(st3m_audio_player_function_dummy); -} - -int st3m_media_set_path(const char *path) { - st3m_media_stop(); - if (strstr(path, ".mpg")) - media = st3m_media_load_mpg1(path); - else if (strstr(path, ".mod")) - media = st3m_media_load_mod(path); - else if (strstr(path, ".mp3")) - media = st3m_media_load_mp3(path); - - if (!media) return 0; - - st3m_audio_set_player_function(st3m_media_audio_out); - media->audio_buffer = audio_buffer; - media->audio_r = 0; - media->audio_w = 1; - - return 1; + if (visual_media) { + visual_media->destroy(visual_media); + visual_media = NULL; + } } void st3m_media_pause(void) { - if (!media) return; - media->paused = 1; + if (!audio_media) return; + audio_media->paused = 1; } void st3m_media_play(void) { - if (!media) return; - media->paused = 0; + if (!audio_media) return; + audio_media->paused = 0; } int st3m_media_is_playing(void) { - if (!media) return 0; - return !media->paused; + if (!audio_media) return 0; + return !audio_media->paused; } float st3m_media_get_duration(void) { - if (!media) return 0; - return media->duration; + if (!audio_media) return 0; + return audio_media->duration; +} + +float st3m_media_get_visual_duration(void) { + if (!visual_media) return 0; + return visual_media->duration; } float st3m_media_get_position(void) { - if (!media) return 0; - return media->position; + if (!audio_media) return 0; + return audio_media->position; } float st3m_media_get_time(void) { - if (!media) return 0; - return media->position * media->duration; + if (!audio_media) return 0; + return audio_media->time; +} + +float st3m_media_get_visual_time(void) { + if (!visual_media) return 0; + return visual_media->time; } void st3m_media_seek(float position) { - if (!media) return; - media->seek = position; + if (!audio_media) return; + audio_media->seek = position; } void st3m_media_seek_relative(float time) { - if (!media) return; - st3m_media_seek((media->position * media->duration) + time); + if (!audio_media) return; + st3m_media_seek((audio_media->position * audio_media->duration) + time); } void st3m_media_draw(Ctx *ctx) { - if (!media) return; - if (media->draw) media->draw(media, ctx); + if (!audio_media) return; + if (visual_media && visual_media->draw) + visual_media->draw(visual_media, ctx); + else if (audio_media->draw) + audio_media->draw(audio_media, ctx); +} + +typedef struct st3m_playlist_entry { + char *path_or_uri; + float in; + float out; +} st3m_playlist_entry; + +static int st3m_playlist_cursor = 0; +static int st3m_playlist_visual_cursor = 0; +static st3m_playlist_entry *st3m_playlist = NULL; +static size_t st3m_playlist_count = 0; +static size_t st3m_playlist_capacity = 0; + +static int st3m_media_current_visual_is_done(void) { + if (visual_media == audio_media) return 0; + if ((current_visual_out <= 0.0f)) return visual_media->position >= 1.0; + float out = + current_visual_out <= 0.0 ? st3m_media_get_visual_duration() : 0; + return st3m_media_get_visual_time() >= out; +} + +static int st3m_media_current_is_done(void) { + if (!audio_media) return 1; + if ((current_audio_out <= 0.0f)) return st3m_media_get_position() >= 1.0; + float out = current_audio_out <= 0.0 ? st3m_media_get_duration() : 0; + return st3m_media_get_time() >= out; } void st3m_media_think(float ms) { - if (!media) return; - if (media->think) media->think(media, ms); + if (st3m_media_current_is_done()) { + // find next item to play if any - depending on play mode + switch (playmode) { + case st3m_media_playmode_queue_consume: + st3m_media_load( + st3m_playlist[st3m_playlist_cursor].path_or_uri); + current_audio_in = st3m_playlist[st3m_playlist_cursor].in; + current_audio_out = st3m_playlist[st3m_playlist_cursor].out; + st3m_playlist_visual_cursor = st3m_playlist_cursor; + if (file_is_visual( + st3m_playlist_get(st3m_playlist_visual_cursor + 1))) { + st3m_playlist_visual_cursor++; + st3m_media_load( + st3m_playlist[st3m_playlist_visual_cursor].path_or_uri); + } + memmove(&st3m_playlist[st3m_playlist_cursor], + &st3m_playlist[st3m_playlist_cursor + 1], + sizeof(st3m_playlist_entry) * + (--st3m_playlist_count - st3m_playlist_cursor)); + break; + + case st3m_media_playmode_queue: + case st3m_media_playmode_shuffle: + if (st3m_playlist) { + st3m_media_load( + st3m_playlist[st3m_playlist_cursor].path_or_uri); + current_audio_in = st3m_playlist[st3m_playlist_cursor].in; + current_audio_out = st3m_playlist[st3m_playlist_cursor].out; + st3m_playlist_visual_cursor = st3m_playlist_cursor; + if (file_is_visual(st3m_playlist_get( + st3m_playlist_visual_cursor + 1))) { + st3m_playlist_visual_cursor++; + st3m_media_load( + st3m_playlist[st3m_playlist_visual_cursor] + .path_or_uri); + } + + if (playmode == st3m_media_playmode_shuffle) + st3m_playlist_cursor = random() % st3m_playlist_count; + else + st3m_playlist_cursor = + (st3m_playlist_cursor + 1) % st3m_playlist_count; + } + break; + + case st3m_media_playmode_single: + st3m_media_pause(); + break; + case st3m_media_playmode_loop: + st3m_media_seek(0); + st3m_playlist_visual_cursor = st3m_playlist_cursor; + if (file_is_visual( + st3m_playlist_get(st3m_playlist_visual_cursor + 1))) { + st3m_playlist_visual_cursor++; + st3m_media_load( + st3m_playlist[st3m_playlist_visual_cursor].path_or_uri); + } + break; + } + + // fast-forward until in-point + if (audio_media && current_audio_in > 0.0) switch (playmode) { + case st3m_media_playmode_queue_consume: + case st3m_media_playmode_queue: + case st3m_media_playmode_shuffle: + case st3m_media_playmode_loop: + audio_media->think(audio_media, current_audio_in * 1000); + break; + + case st3m_media_playmode_single: + audio_media->think(audio_media, current_audio_in * 1000); + return; + } + } + + if (!audio_media) return; + + if (audio_media->think) audio_media->think(audio_media, ms); + + if (visual_media != audio_media) { + if (st3m_media_current_visual_is_done()) { + // find next item to play if any - depending on play mode + switch (playmode) { + case st3m_media_playmode_queue_consume: + if (file_is_visual(st3m_playlist_get( + st3m_playlist_visual_cursor + 1))) { + st3m_playlist_visual_cursor++; + st3m_media_load( + st3m_playlist[st3m_playlist_visual_cursor] + .path_or_uri); + current_visual_in = + st3m_playlist[st3m_playlist_visual_cursor].in; + current_visual_out = + st3m_playlist[st3m_playlist_visual_cursor].out; + memmove(&st3m_playlist[st3m_playlist_visual_cursor], + &st3m_playlist[st3m_playlist_cursor + 1], + sizeof(st3m_playlist_entry) * + (--st3m_playlist_count - + st3m_playlist_visual_cursor)); + } + break; + + case st3m_media_playmode_queue: + case st3m_media_playmode_shuffle: + if (st3m_playlist) { + if (file_is_visual(st3m_playlist_get( + st3m_playlist_visual_cursor + 1))) { + st3m_playlist_visual_cursor++; + st3m_media_load( + st3m_playlist[st3m_playlist_visual_cursor] + .path_or_uri); + } else { + st3m_playlist_visual_cursor = + st3m_playlist_cursor + 1; + if (st3m_playlist_visual_cursor >= + st3m_playlist_count) + st3m_playlist_visual_cursor = + st3m_playlist_cursor; + st3m_media_load( + st3m_playlist[st3m_playlist_visual_cursor] + .path_or_uri); + } + } + break; + + case st3m_media_playmode_single: + break; + case st3m_media_playmode_loop: + st3m_playlist_visual_cursor = st3m_playlist_cursor + 1; + if (st3m_playlist_visual_cursor >= st3m_playlist_count) + st3m_playlist_visual_cursor = st3m_playlist_cursor; + st3m_media_load( + st3m_playlist[st3m_playlist_visual_cursor].path_or_uri); + break; + } + + // fast-forward until in-point + if (visual_media != audio_media && current_visual_in > 0.0) + switch (playmode) { + case st3m_media_playmode_queue_consume: + case st3m_media_playmode_queue: + visual_media->think(visual_media, + current_visual_in * 1000); + case st3m_media_playmode_shuffle: + visual_media->think(visual_media, + current_visual_in * 1000); + case st3m_media_playmode_loop: + break; + + case st3m_media_playmode_single: + visual_media->think(visual_media, + current_visual_in * 1000); + return; + } + } + } } char *st3m_media_get_string(const char *key) { - if (!media) return NULL; - if (!media->get_string) return NULL; - return media->get_string(media, key); + if (!audio_media) return NULL; + if (!audio_media->get_string) return NULL; + return audio_media->get_string(audio_media, key); } float st3m_media_get(const char *key) { - if (!media || !media->get_string) return -1.0f; - return media->get(media, key); + if (!audio_media || !audio_media->get_string) return -1.0f; + return audio_media->get(audio_media, key); } void st3m_media_set(const char *key, float value) { - if (!media || !media->set) return; - return media->set(media, key, value); + if (!audio_media || !audio_media->set) return; + return audio_media->set(audio_media, key, value); +} + +st3m_media_playmode st3m_media_get_playmode(void) { return playmode; } + +void st3m_media_set_playmode(st3m_media_playmode new_mode) { + playmode = new_mode; +} + +void st3m_playlist_add_with_in_out(int index, const char *path_or_uri, float in, + float out) { + if (index > st3m_playlist_count) return; + if (index < 0) index = st3m_playlist_count; + if (index >= st3m_playlist_capacity) { + st3m_playlist = + realloc(st3m_playlist, + sizeof(st3m_playlist_entry) * (st3m_playlist_capacity + 8)); + st3m_playlist_capacity += 8; + } +} + +void st3m_playlist_add(int index, const char *path_or_uri) { + return st3m_playlist_add_with_in_out(index, path_or_uri, 0.0f, -1.0f); +} + +// remove the media item at queued position +// -1 to clear queue +void st3m_playlist_remove(int index) { + if (index < 0) { + for (int i = 0; i < st3m_playlist_count; i++) + free(st3m_playlist[i].path_or_uri); + st3m_playlist_count = 0; + } else if (index < st3m_playlist_count) { + free(st3m_playlist[index].path_or_uri); + memmove(&st3m_playlist[index], &st3m_playlist[index + 1], + (st3m_playlist_count - (index + 1))); + st3m_playlist_count--; + } +} + +// get media at given position +const char *st3m_playlist_get(int index) { + if (!(index >= 0 && index < st3m_playlist_count)) return NULL; + return st3m_playlist[index].path_or_uri; +} + +// set in and out value for a clip in queue +void st3m_media_set_in_out(int index, float in, float out) { + if (!(index >= 0 && index < st3m_playlist_count)) return; + st3m_playlist[index].in = in; + st3m_playlist[index].out = out; +} + +// get in and out values for a clip in queue +void st3m_media_get_in_out(int index, float *in, float *out) { + if (!(index >= 0 && index < st3m_playlist_count)) { + if (in) *in = 0.0; + if (out) *out = -1.0; + return; + } + if (in) *in = st3m_playlist[index].in; + if (out) *out = st3m_playlist[index].out; +} + +/////////////////////////////////////////////////////////////////// +// loading dispatch - entirely based on substring matching. +/////////////////////////////////////////////////////////////////// + +st3m_media *st3m_media_load_mpg1(const char *path); +st3m_media *st3m_media_load_mod(const char *path); +st3m_media *st3m_media_load_mp3(const char *path); + +int st3m_media_load(const char *path) { + if (strstr(path, ".mpg")) { + st3m_media_stop(); + audio_media = visual_media = st3m_media_load_mpg1(path); + } else if (strstr(path, ".mod")) { + st3m_media_stop(); + audio_media = visual_media = st3m_media_load_mod(path); + } else if (strstr(path, ".mp3")) { + st3m_media_stop(); + audio_media = visual_media = st3m_media_load_mp3(path); + } + + if (!audio_media) return 0; + + st3m_audio_set_player_function(st3m_media_audio_out); + audio_media->audio_buffer = audio_buffer; + audio_media->audio_r = 0; + audio_media->audio_w = 1; + + current_audio_in = 0.0; + current_audio_out = -1.0; + return 1; } #endif diff --git a/components/st3m/st3m_media.h b/components/st3m/st3m_media.h index 66ea9d9bd89a1f45d1ed4e8b32b2cef6495db73c..681dd66b8a27cc9adaf6886588479e9773f9bc7f 100644 --- a/components/st3m/st3m_media.h +++ b/components/st3m/st3m_media.h @@ -6,6 +6,14 @@ #define AUDIO_BUF_SIZE (8192) +typedef enum { + st3m_media_playmode_queue, + st3m_media_playmode_queue_consume, + st3m_media_playmode_shuffle, + st3m_media_playmode_single, + st3m_media_playmode_loop +} st3m_media_playmode; + typedef struct _st3m_media st3m_media; struct _st3m_media { @@ -20,7 +28,7 @@ struct _st3m_media { // free resources used by this media instance void (*destroy)(st3m_media *media); - + // draw the current frame / visualization / metadata screen void (*draw)(st3m_media *media, Ctx *ctx); @@ -36,12 +44,17 @@ struct _st3m_media { // Duration of media in seconds or -1 for infinite/streaming media // at worst approximation of some unit, set by decoder. - float duration; + float duration; // currently played back position - set by decoder float position; - // decoder should seek to this position if not -1, and set it to -1 + // currently played back position, in seconds - set by decoder + float time; // time might be precise even when duration is not + // until first play through of some media, when time + // duration should also be set exact. + + // decoder should seek to this relative if not -1, and set it to -1 float seek; // if set to 1 playback is momentarily stopped but can be resumed, @@ -49,12 +62,26 @@ struct _st3m_media { int paused; }; -int st3m_media_samples_queued (void); -int st3m_media_set_path(const char *path); -void st3m_media_pause(void); -void st3m_media_play(void); -void st3m_media_stop(void); -int st3m_media_is_playing(void); +st3m_media_playmode st3m_media_get_playmode(void); +void st3m_media_set_playmode(st3m_media_playmode); + +// stops the currently playing media item +void st3m_media_stop(void); +// set a new media item +int st3m_media_load(const char *path_or_uri); + +// decode current media item ms ahead (unless paused) +void st3m_media_think(float ms); + +// draw the codecs own view of itself / its meta data - progress +// for video or animations formats this should draw the media-content +// other codecs can find mixes of debug visualizations. +void st3m_media_draw(Ctx *ctx); + +// controls whether we are playing +void st3m_media_pause(void); +void st3m_media_play(void); +int st3m_media_is_playing(void); // get duration in seconds float st3m_media_get_duration(void); @@ -63,13 +90,9 @@ float st3m_media_get_time(void); // get current playback position relative to overall duration float st3m_media_get_position(void); // seek to a position relative to overall duration -void st3m_media_seek(float position); +void st3m_media_seek(float position); // seek a relative amount of seconds forward or with negative values back -void st3m_media_seek_relative(float seconds_jump); -// draw the codecs own view of itself / its meta data - progress -void st3m_media_draw(Ctx *ctx); -// iterate time forwards this many milliseconds -void st3m_media_think(float ms); +void st3m_media_seek_relative(float seconds_jump); // get decoder specific string or NULL if not existing, free returned value // common values: @@ -78,8 +101,37 @@ char *st3m_media_get_string(const char *key); // get a decoder specific numeric value, defaulting to -1 for nonexisting values float st3m_media_get(const char *key); // set a decoder specific floating point value -// common values: -// scale 0.0 - 1.0 - how large part of the screen to take up -// grayscale 0 or 1 -void st3m_media_set(const char *key, float value); +// example posible/or already used values: +// "scale" 0.0 - 1.0 - how large part of the screen to take up +// "grayscale" 0 or 1 - drop color bits for performance +// "smoothing" 0 or 1 - enable smooth texture scaling +void st3m_media_set(const char *key, float value); + +// API for use in implementations +// query how manu audio samples have been queued in the pcm output buffer +int st3m_media_samples_queued(void); + +// add a path or uri to the media queue +// 0 to insert at front of queue +// 1 to queue after upcoming song +// -1 to queue at end +// -2 to replace whole queue +void st3m_playlist_add(int index, const char *path_or_uri); + +// adds a media item with in and out point, in point is the time of a timed +// media to start playing at defaults to 0.0 out is the time to stop playing +// defaulting to -1 which means play to end. +void st3m_playlist_add_with_in_out(int index, const char *path_or_uri, float in, + float out); + +// remove the media item at queued position +// -1 to clear queue +void st3m_playlist_remove(int index); + +// get media at given position +const char *st3m_playlist_get(int index); +// set in and out value for a clip in queue +void st3m_playlist_set_in_out(int index, float in, float out); +// get in and out values for a clip in queue +void st3m_playlist_get_in_out(int index, float *in, float *out); diff --git a/python_payload/apps/mod/__init__.py b/python_payload/apps/mod/__init__.py deleted file mode 100644 index 940003c9a48d7560bdcd322943f9a4a0f37aebd7..0000000000000000000000000000000000000000 --- a/python_payload/apps/mod/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from st3m.application import Application, ApplicationContext -from ctx import Context - -import media - - -class ModApp(Application): - def __init__(self, app_ctx: ApplicationContext) -> None: - super().__init__(app_ctx) - self._filename = "/sd/elysium.mod" - - def draw(self, ctx: Context) -> None: - media.draw(ctx) - - def think(self, ins: InputState, delta_ms: int) -> None: - super().think(ins, delta_ms) - media.think(delta_ms) - - def on_enter(self, vm: Optional[ViewManager]) -> None: - super().on_enter(vm) - media.set_path(self._filename) - - def on_exit(self) -> None: - media.stop() - self._started = False diff --git a/python_payload/apps/mod/flow3r.toml b/python_payload/apps/mod/flow3r.toml deleted file mode 100644 index 7af1e9ec00f4d08f9f9a4232885e910c3e3289ff..0000000000000000000000000000000000000000 --- a/python_payload/apps/mod/flow3r.toml +++ /dev/null @@ -1,11 +0,0 @@ -[app] -name = "MOD" -menu = "Apps" - -[entry] -class = "ModApp" - -[metadata] -author = "Flow3r Badge Authors" -license = "LGPL-3.0-only" -url = "https://git.flow3r.garden/flow3r/flow3r-firmware"