From 9f4bd8712fc122f61ec162c544d613a95c3ca66e Mon Sep 17 00:00:00 2001 From: Nicolas Pennequin Date: Wed, 14 Feb 2007 14:40:24 +0000 Subject: Cuesheet support by Jonathan Gordon and me (FS #6460). Everytime an audio file is loaded, a cue file with the same name is searched for. A setting allows to disable this (default is off). Cuesheet files can also be viewed in the file browser. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12304 a1c6a512-1295-4272-9138-f99709370657 --- apps/SOURCES | 1 + apps/cuesheet.c | 360 +++++++++++++++++++++++++++++++++++++++++++++ apps/cuesheet.h | 85 +++++++++++ apps/filetree.c | 5 + apps/gui/gwps-common.c | 64 +++++++- apps/gui/gwps.c | 48 +++++- apps/lang/english.lang | 28 ++++ apps/main.c | 6 +- apps/menus/playback_menu.c | 18 ++- apps/metadata.c | 6 + apps/playback.c | 18 +++ apps/settings.h | 1 + apps/settings_list.c | 1 + apps/tree.c | 1 + apps/tree.h | 1 + firmware/export/id3.h | 3 + 16 files changed, 640 insertions(+), 6 deletions(-) create mode 100644 apps/cuesheet.c create mode 100644 apps/cuesheet.h diff --git a/apps/SOURCES b/apps/SOURCES index e57c882a8b..1793724665 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -32,6 +32,7 @@ settings_list.c settings_menu.c sound_menu.c status.c +cuesheet.c #if !defined(SIMULATOR) || CONFIG_CODEC == SWCODEC talk.c #endif diff --git a/apps/cuesheet.c b/apps/cuesheet.c new file mode 100644 index 0000000000..6db3528cad --- /dev/null +++ b/apps/cuesheet.c @@ -0,0 +1,360 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $$ + * + * Copyright (C) 2007 Nicolas Pennequin, Jonathan Gordon + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include "system.h" +#include "audio.h" +#include "kernel.h" +#include "logf.h" +#include "sprintf.h" +#include "misc.h" +#include "screens.h" +#include "splash.h" +#include "list.h" +#include "action.h" +#include "lang.h" +#include "debug.h" +#include "settings.h" +#include "buffer.h" +#include "plugin.h" +#include "playback.h" +#include "cuesheet.h" + + +void cuesheet_init(void) +{ + if (global_settings.cuesheet) { + curr_cue = (struct cuesheet *)buffer_alloc(MAX_TRACKS * sizeof(struct cuesheet)); + temp_cue = (struct cuesheet *)buffer_alloc(MAX_TRACKS * sizeof(struct cuesheet)); + } else { + curr_cue = NULL; + temp_cue = NULL; + } +} + +bool cuesheet_is_enabled(void) +{ + return (curr_cue != NULL); +} + +bool look_for_cuesheet_file(const char *trackpath) +{ + char cuepath[MAX_PATH]; + strncpy(cuepath, trackpath, MAX_PATH); + char *dot = strrchr(cuepath, '.'); + strcpy(dot, ".cue"); + + int fd = open(cuepath,O_RDONLY); + if (fd < 0) + { + return false; + } + else + { + close(fd); + return true; + } +} + +char *skip_whitespace(char* buf) +{ + char *r = buf; + while (*r && (*r < 33)) + r++; + return r; +} + +/* parse cuesheet "file" and store the information in "cue" */ +bool parse_cuesheet(char *file, struct cuesheet *cue) +{ + char line[MAX_PATH]; + char *s, *start, *end; + int fd = open(file,O_RDONLY); + if (fd < 0) + { + /* couln't open the file */ + return false; + } + + memset(cue, 0, sizeof(struct cuesheet)); + + strcpy(cue->path, file); + + cue->curr_track_idx = 0; + cue->curr_track = cue->tracks; + + cue->track_count = 0; + while (read_line(fd,line,MAX_PATH)) + { + s = skip_whitespace(line); + if (!strncmp(s, "TITLE", 5)) + { + start = strchr(s,'"'); + if (!start) + break; + end = strchr(++start,'"'); + if (!end) + break; + *end = '\0'; + if (cue->track_count <= 0) + strncpy(cue->title,start,MAX_NAME); + else strncpy(cue->tracks[cue->track_count-1].title, + start,MAX_NAME); + } + else if (!strncmp(s, "PERFORMER", 9)) + { + start = strchr(s,'"'); + if (!start) + break; + end = strchr(++start,'"'); + if (!end) + break; + *end = '\0'; + if (cue->track_count <= 0) + strncpy(cue->performer,start,MAX_NAME); + else strncpy(cue->tracks[cue->track_count-1].performer, + start,MAX_NAME); + } + else if (!strncmp(s, "TRACK", 5)) + { + if (cue->track_count >= MAX_TRACKS) + break; /* out of memeory! stop parsing */ + cue->track_count++; + } + else if (!strncmp(s, "INDEX", 5)) + { + s = strchr(s,' '); + s = skip_whitespace(s); + s = strchr(s,' '); + s = skip_whitespace(s); + cue->tracks[cue->track_count-1].offset = 60*1000 * atoi(s); + s = strchr(s,':') + 1; + cue->tracks[cue->track_count-1].offset += 1000 * atoi(s); + s = strchr(s,':') + 1; + cue->tracks[cue->track_count-1].offset += 13 * atoi(s); + } + } + close(fd); + + /* If some songs don't have performer info, we copy the cuesheet performer */ + int i; + for (i = 0; i < cue->track_count; i++) + { + if (*(cue->tracks[i].performer) == '\0') + { + strncpy(cue->tracks[i].performer, cue->performer, MAX_NAME); + } + } + + return true; +} + +/* takes care of seeking to a track in a playlist + * returns false if audio isn't playing */ +bool seek(unsigned long pos) +{ + if (!(audio_status() & AUDIO_STATUS_PLAY)) + { + return false; + } + else + { +#if (CONFIG_CODEC == SWCODEC) + audio_pre_ff_rewind(); +#else + audio_pause(); +#endif + audio_ff_rewind(pos); + return true; + } +} + +/* returns the index of the track currently being played + and updates the information about the current track. */ +int cue_find_current_track(struct cuesheet *cue, unsigned long curpos) +{ + int i=0; + while (i < cue->track_count-1 && cue->tracks[i+1].offset < curpos) + { + i++; + } + cue->curr_track_idx = i; + cue->curr_track = cue->tracks + i; + return i; +} + +/* callback that gives list item titles for the cuesheet browser */ +char *list_get_name_cb(int selected_item, + void *data, + char *buffer) +{ + struct cuesheet *cue = (struct cuesheet *)data; + + if (selected_item & 1) + { + snprintf(buffer, MAX_PATH, + (selected_item+1)/2 > 9 ? " %s" : " %s", + cue->tracks[selected_item/2].title); + } + else + { + snprintf(buffer, MAX_PATH, "%d %s", selected_item/2+1, + cue->tracks[selected_item/2].performer); + } + return buffer; +} + +void browse_cuesheet(struct cuesheet *cue) +{ + struct gui_synclist lists; + int action; + bool done = false; + int sel; + char title[MAX_PATH]; + char cuepath[MAX_PATH]; + char *dot; + struct mp3entry *id3 = audio_current_track(); + + snprintf(title, MAX_PATH, "%s: %s", cue->performer, cue->title); + gui_synclist_init(&lists, list_get_name_cb, cue, false, 2); + gui_synclist_set_nb_items(&lists, 2*cue->track_count); + gui_synclist_set_title(&lists, title, 0); + + if (strcmp(id3->path, "No file!")) + { + strncpy(cuepath, id3->path, MAX_PATH); + dot = strrchr(cuepath, '.'); + strcpy(dot, ".cue"); + } + + if (id3->cuesheet_type && !strcmp(cue->path, cuepath)) + { + gui_synclist_select_item(&lists, + 2*cue_find_current_track(cue, id3->elapsed)); + } + + while (!done) + { + gui_synclist_draw(&lists); + action = get_action(CONTEXT_LIST,TIMEOUT_BLOCK); + if (gui_synclist_do_button(&lists,action,LIST_WRAP_UNLESS_HELD)) + continue; + switch (action) + { + case ACTION_STD_OK: + id3 = audio_current_track(); + if (strcmp(id3->path, "No file!")) + { + strncpy(cuepath, id3->path, MAX_PATH); + dot = strrchr(cuepath, '.'); + strcpy(dot, ".cue"); + if (id3->cuesheet_type && !strcmp(cue->path, cuepath)) + { + sel = gui_synclist_get_sel_pos(&lists); + seek(cue->tracks[sel/2].offset); + } + } + break; + case ACTION_STD_CANCEL: + done = true; + } + } +} + +bool display_cuesheet_content(char* filename) +{ + int bufsize = 0; + struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize); + if (!cue) + return false; + + if (!parse_cuesheet(filename, cue)) + return false; + + browse_cuesheet(cue); + return true; +} + +/* skips backwards or forward in the current cuesheet + * the return value indicates whether we're still in a cusheet after skipping + * it also returns false if we weren't in a cuesheet. + * direction should be 1 or -1. + */ +bool curr_cuesheet_skip(int direction, unsigned long curr_pos) +{ + int track = cue_find_current_track(curr_cue, curr_pos); + + if (direction >= 0 && track == curr_cue->track_count - 1) + { + /* we want to get out of the cuesheet */ + return false; + } + else + { + if (!(direction <= 0 && track == 0)) + track += direction; + + seek(curr_cue->tracks[track].offset); + return true; + } + +} + +void cue_spoof_id3(struct cuesheet *cue, struct mp3entry *id3) +{ + if (!cue) + return; + + int i = cue->curr_track_idx; + + id3->title = cue->tracks[i].title; + id3->artist = cue->tracks[i].performer; + id3->tracknum = i+1; + id3->album = cue->title; + id3->composer = cue->performer; + if (id3->track_string) + snprintf(id3->track_string, 10, "%d/%d", i+1, cue->track_count); +} + +#ifdef HAVE_LCD_BITMAP +static inline void draw_veritcal_line_mark(struct screen * screen, + int x, int y, int h) +{ + screen->set_drawmode(DRMODE_COMPLEMENT); + screen->vline(x, y, y+h-1); +} + +/* draw the cuesheet markers for a track of length "tracklen", + between (x1,y) and (x2,y) */ +void cue_draw_markers(struct screen *screen, unsigned long tracklen, + int x1, int x2, int y, int h) +{ + int i,xi; + int w = x2 - x1; + for (i=1; i < curr_cue->track_count; i++) + { + xi = x1 + (w * curr_cue->tracks[i].offset)/tracklen; + draw_veritcal_line_mark(screen, xi, y, h); + } +} +#endif diff --git a/apps/cuesheet.h b/apps/cuesheet.h new file mode 100644 index 0000000000..9dff370e09 --- /dev/null +++ b/apps/cuesheet.h @@ -0,0 +1,85 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $$ + * + * Copyright (C) 2007 Nicolas Pennequin, Jonathan Gordon + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef _CUESHEET_H_ +#define _CUESHEET_H_ + +#include +#include "screens.h" + +#define MAX_NAME 64 /* Max length of information strings */ +#define MAX_TRACKS 99 /* Max number of tracks in a cuesheet */ + +struct cue_track_info { + char title[MAX_NAME]; + char performer[MAX_NAME]; + unsigned long offset; /* ms from start of track */ +}; + +struct cuesheet { + char path[MAX_PATH]; + char audio_filename[MAX_PATH]; + + char title[MAX_NAME]; + char performer[MAX_NAME]; + + int track_count; + struct cue_track_info tracks[MAX_TRACKS]; + + int curr_track_idx; + struct cue_track_info *curr_track; +}; + +struct cuesheet *curr_cue; +struct cuesheet *temp_cue; + +/* returns true if cuesheet support is initialised */ +bool cuesheet_is_enabled(void); + +/* allocates the cuesheet buffer */ +void cuesheet_init(void); + +/* looks if there is a cuesheet file that has a name matching "trackpath" */ +bool look_for_cuesheet_file(const char *trackpath); + +/* parse cuesheet "file" and store the information in "cue" */ +bool parse_cuesheet(char *file, struct cuesheet *cue); + +/* reads a cuesheet to find the audio track associated to it */ +bool get_trackname_from_cuesheet(char *filename, char *buf); + +/* displays a cuesheet to the screen (it is stored in the plugin buffer) */ +bool display_cuesheet_content(char* filename); + +/* finds the index of the current track played within a cuesheet */ +int cue_find_current_track(struct cuesheet *cue, unsigned long curpos); + +/* update the id3 info to that of the currently playing track in the cuesheet */ +void cue_spoof_id3(struct cuesheet *cue, struct mp3entry *id3); + +/* skip to next track in the cuesheet towards "direction" (which is 1 or -1) */ +bool curr_cuesheet_skip(int direction, unsigned long curr_pos); + +#ifdef HAVE_LCD_BITMAP +/* draw track markers on the progressbar */ +void cue_draw_markers(struct screen *screen, unsigned long tracklen, + int x1, int x2, int y, int h); +#endif + +#endif diff --git a/apps/filetree.c b/apps/filetree.c index 10174dbb31..df4065c91b 100644 --- a/apps/filetree.c +++ b/apps/filetree.c @@ -41,6 +41,7 @@ #include "dircache.h" #include "splash.h" #include "yesno.h" +#include "cuesheet.h" #ifdef HAVE_LCD_BITMAP #include "keyboard.h" #endif @@ -550,6 +551,10 @@ int ft_enter(struct tree_context* c) } break; + case TREE_ATTR_CUE: + display_cuesheet_content(buf); + break; + default: { char* plugin; diff --git a/apps/gui/gwps-common.c b/apps/gui/gwps-common.c index 53b12238e4..b6e64d2fcc 100644 --- a/apps/gui/gwps-common.c +++ b/apps/gui/gwps-common.c @@ -53,6 +53,7 @@ #endif #include "dsp.h" #include "action.h" +#include "cuesheet.h" #ifdef HAVE_LCD_CHARCELLS static bool draw_player_progress(struct gui_wps *gwps); @@ -1850,6 +1851,14 @@ bool gui_wps_refresh(struct gui_wps *gwps, int ffwd_offset, data->progress_start, data->progress_end, sb_y, data->progress_height); #endif + + if (cuesheet_is_enabled() && state->id3->cuesheet_type) + { + cue_draw_markers(display, state->id3->length, + data->progress_start, data->progress_end, + sb_y+1, data->progress_height-2); + } + update_line = true; } if (flags & refresh_mode & WPS_REFRESH_PEAK_METER) { @@ -2561,6 +2570,35 @@ bool update(struct gui_wps *gwps) { gwps->display->stop_scroll(); gwps->state->id3 = audio_current_track(); + + if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type + && strcmp(gwps->state->id3->path, curr_cue->audio_filename)) + { + /* the current cuesheet isn't the right one any more */ + + if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) { + /* We have the new cuesheet in memory (temp_cue), + let's make it the current one ! */ + memcpy(curr_cue, temp_cue, sizeof(struct cuesheet)); + } + else { + /* We need to parse the new cuesheet */ + + char cuepath[MAX_PATH]; + strncpy(cuepath, gwps->state->id3->path, MAX_PATH); + char *dot = strrchr(cuepath, '.'); + strcpy(dot, ".cue"); + + if (parse_cuesheet(cuepath, curr_cue)) + { + gwps->state->id3->cuesheet_type = 1; + strcpy(curr_cue->audio_filename, gwps->state->id3->path); + } + } + + cue_spoof_id3(curr_cue, gwps->state->id3); + } + if (gui_wps_display()) retcode = true; else{ @@ -2572,12 +2610,34 @@ bool update(struct gui_wps *gwps) sizeof(gwps->state->current_track_path)); } + if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type + && (gwps->state->id3->elapsed < curr_cue->curr_track->offset + || (curr_cue->curr_track_idx < curr_cue->track_count - 1 + && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset))) + { + /* We've changed tracks within the cuesheet : + we need to update the ID3 info and refresh the WPS */ + + cue_find_current_track(curr_cue, gwps->state->id3->elapsed); + cue_spoof_id3(curr_cue, gwps->state->id3); + + gwps->display->stop_scroll(); + if (gui_wps_display()) + retcode = true; + else{ + gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL); + } + gui_wps_statusbar_draw(gwps, false); + + return retcode; + } + if (gwps->state->id3) gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC); gui_wps_statusbar_draw(gwps, false); - - return retcode; + + return retcode; } diff --git a/apps/gui/gwps.c b/apps/gui/gwps.c index 7128a958f7..00290a8871 100644 --- a/apps/gui/gwps.c +++ b/apps/gui/gwps.c @@ -54,6 +54,7 @@ #include "abrepeat.h" #include "playback.h" #include "splash.h" +#include "cuesheet.h" #if LCD_DEPTH > 1 #include "backdrop.h" #endif @@ -333,7 +334,16 @@ long gui_wps_show(void) if (global_settings.party_mode) break; if (current_tick -last_right < HZ) - audio_next_dir(); + { + if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type) + { + audio_next(); + } + else + { + audio_next_dir(); + } + } else ffwd_rew(ACTION_WPS_SEEKFWD); last_right = 0; break; @@ -343,7 +353,22 @@ long gui_wps_show(void) if (global_settings.party_mode) break; if (current_tick -last_left < HZ) - audio_prev_dir(); + { + if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type) + { + if (!wps_state.paused) +#if (CONFIG_CODEC == SWCODEC) + audio_pre_ff_rewind(); +#else + audio_pause(); +#endif + audio_ff_rewind(0); + } + else + { + audio_prev_dir(); + } + } else ffwd_rew(ACTION_WPS_SEEKBACK); last_left = 0; break; @@ -377,6 +402,13 @@ long gui_wps_show(void) audio_prev(); } else { + + if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type) + { + curr_cuesheet_skip(-1, wps_state.id3->elapsed); + break; + } + if (!wps_state.paused) #if (CONFIG_CODEC == SWCODEC) audio_pre_ff_rewind(); @@ -417,6 +449,18 @@ long gui_wps_show(void) } /* ...otherwise, do it normally */ #endif + + /* take care of if we're playing a cuesheet */ + if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type) + { + if (curr_cuesheet_skip(1, wps_state.id3->elapsed)) + { + /* if the result was false, then we really want + to skip to the next track */ + break; + } + } + audio_next(); break; /* next / prev directories */ diff --git a/apps/lang/english.lang b/apps/lang/english.lang index f663e855ed..3085f839cf 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -10515,3 +10515,31 @@ *: "" + + id: LANG_CUESHEET + desc: + user: + + *: "Cuesheet" + + + *: "Cuesheet" + + + *: "Cuesheet" + + + + id: LANG_CUESHEET_ENABLE + desc: cuesheet support option + user: + + *: "Cuesheet Support" + + + *: "Cuesheet Support" + + + *: "Cuesheet Support" + + diff --git a/apps/main.c b/apps/main.c index dac7ac755d..4c643c4130 100644 --- a/apps/main.c +++ b/apps/main.c @@ -103,6 +103,8 @@ #include "m5636.h" #endif +#include "cuesheet.h" + /*#define AUTOROCK*/ /* define this to check for "autostart.rock" on boot */ const char appsversion[]=APPSVERSION; @@ -274,7 +276,8 @@ static void init(void) global_settings.superbass); scrobbler_init(); - + cuesheet_init(); + /* audio_init must to know the size of voice buffer so init voice first */ #if CONFIG_CODEC == SWCODEC talk_init(); @@ -489,6 +492,7 @@ static void init(void) playlist_init(); tree_init(); scrobbler_init(); + cuesheet_init(); /* No buffer allocation (see buffer.c) may take place after the call to audio_init() since the mpeg thread takes the rest of the buffer space */ diff --git a/apps/menus/playback_menu.c b/apps/menus/playback_menu.c index 7c0f75198d..4dbfb6c40d 100644 --- a/apps/menus/playback_menu.c +++ b/apps/menus/playback_menu.c @@ -33,6 +33,7 @@ #include "dsp.h" #include "scrobbler.h" #include "audio.h" +#include "cuesheet.h" #if CONFIG_CODEC == SWCODEC int setcrossfadeonexit_callback(int action,const struct menu_item_ex *this_item) @@ -146,6 +147,21 @@ int audioscrobbler_callback(int action,const struct menu_item_ex *this_item) } MENUITEM_SETTING(audioscrobbler, &global_settings.audioscrobbler, audioscrobbler_callback); + +int cuesheet_callback(int action,const struct menu_item_ex *this_item) +{ + (void)this_item; + switch (action) + { + case ACTION_EXIT_MENUITEM: /* on exit */ + if (!cuesheet_is_enabled() && global_settings.cuesheet) + gui_syncsplash(HZ*2, true, str(LANG_PLEASE_REBOOT)); + break; + } + return action; +} +MENUITEM_SETTING(cuesheet, &global_settings.cuesheet, cuesheet_callback); + #ifdef HAVE_HEADPHONE_DETECTION MENUITEM_SETTING(unplug_mode, &global_settings.unplug_mode, NULL); MENUITEM_SETTING(unplug_rw, &global_settings.unplug_rw, NULL); @@ -167,7 +183,7 @@ MAKE_MENU(playback_menu_item,ID2P(LANG_PLAYBACK),0, #ifdef HAVE_SPDIF_POWER &spdif_enable, #endif - &id3_v1_first, &next_folder, &audioscrobbler + &id3_v1_first, &next_folder, &audioscrobbler, &cuesheet #ifdef HAVE_HEADPHONE_DETECTION ,&unplug_menu #endif diff --git a/apps/metadata.c b/apps/metadata.c index fbe7bfc233..ae2a8ecda8 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -30,6 +30,7 @@ #include "replaygain.h" #include "debug.h" #include "system.h" +#include "cuesheet.h" enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS }; @@ -2272,6 +2273,11 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname, /* We have successfully read the metadata from the file */ + if (cuesheet_is_enabled() && look_for_cuesheet_file(trackname)) + { + track->id3.cuesheet_type = 1; + } + lseek(fd, 0, SEEK_SET); strncpy(track->id3.path, trackname, sizeof(track->id3.path)); track->taginfo_ready = true; diff --git a/apps/playback.c b/apps/playback.c index d256f5a4f0..b80c449c47 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -57,6 +57,7 @@ #include "buffer.h" #include "dsp.h" #include "abrepeat.h" +#include "cuesheet.h" #ifdef HAVE_TAGCACHE #include "tagcache.h" #endif @@ -2742,6 +2743,23 @@ static bool audio_load_track(int offset, bool start_play, bool rebuffer) } + if (cuesheet_is_enabled() && tracks[track_widx].id3.cuesheet_type == 1) + { + char cuepath[MAX_PATH]; + strncpy(cuepath, trackname, MAX_PATH); + char *dot = strrchr(cuepath, '.'); + strcpy(dot, ".cue"); + + struct cuesheet *cue = start_play ? curr_cue : temp_cue; + + if (parse_cuesheet(cuepath, cue)) + { + strcpy((cue)->audio_filename, trackname); + if (start_play) + cue_spoof_id3(curr_cue, &tracks[track_widx].id3); + } + } + /* Load the codec. */ tracks[track_widx].codecbuf = &filebuf[buf_widx]; if (!audio_loadcodec(start_play)) diff --git a/apps/settings.h b/apps/settings.h index 30872ac04f..3605e7e777 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -683,6 +683,7 @@ struct user_settings #endif /* Encoder Settings End */ #endif /* CONFIG_CODEC == SWCODEC */ + bool cuesheet; }; /** global variables **/ diff --git a/apps/settings_list.c b/apps/settings_list.c index c40cf09b2e..e847cfa23e 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -919,6 +919,7 @@ const struct settings_list settings[] = { OFFON_SETTING(0,usb_charging,LANG_USB_CHARGING,false,"usb charging",NULL), #endif #endif + OFFON_SETTING(0,cuesheet,LANG_CUESHEET_ENABLE,false,"cuesheet support", NULL), }; const int nb_settings = sizeof(settings)/sizeof(*settings); diff --git a/apps/tree.c b/apps/tree.c index 88492f4b65..806d1de616 100644 --- a/apps/tree.c +++ b/apps/tree.c @@ -133,6 +133,7 @@ const struct filetype filetypes[] = { { "kbd", TREE_ATTR_KBD, Icon_Keyboard, VOICE_EXT_KBD }, #endif { "bmark",TREE_ATTR_BMARK, Icon_Bookmark, VOICE_EXT_BMARK }, + { "cue", TREE_ATTR_CUE, Icon_Bookmark, LANG_CUESHEET }, #ifdef BOOTFILE_EXT { BOOTFILE_EXT, TREE_ATTR_MOD, Icon_Firmware, VOICE_EXT_AJZ }, #endif /* #ifndef SIMULATOR */ diff --git a/apps/tree.h b/apps/tree.h index 003714252e..110fc09f0c 100644 --- a/apps/tree.h +++ b/apps/tree.h @@ -94,6 +94,7 @@ struct tree_context { #define TREE_ATTR_BMP 0x1100 /* backdrop bmp file */ #define TREE_ATTR_KBD 0x1200 /* keyboard file */ #define TREE_ATTR_FMR 0x1300 /* preset file */ +#define TREE_ATTR_CUE 0x1400 /* cuesheet file */ #define TREE_ATTR_MASK 0xFF00 /* which bits tree.c uses for file types */ void tree_get_filetypes(const struct filetype**, int*); diff --git a/firmware/export/id3.h b/firmware/export/id3.h index 980d3b4d94..721de9030c 100644 --- a/firmware/export/id3.h +++ b/firmware/export/id3.h @@ -213,6 +213,9 @@ struct mp3entry { long track_peak; /* 7.24 signed fixed point. 0 for no peak. */ long album_peak; #endif + + /* Cuesheet support */ + int cuesheet_type; /* 0: none, 1: external, 2: embedded */ }; enum { -- cgit v1.2.3