forked from len0rd/rockbox
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
This commit is contained in:
parent
0403c2a572
commit
9f4bd8712f
16 changed files with 640 additions and 6 deletions
|
@ -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
|
||||
|
|
360
apps/cuesheet.c
Normal file
360
apps/cuesheet.c
Normal file
|
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <atoi.h>
|
||||
#include <string.h>
|
||||
#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
|
85
apps/cuesheet.h
Normal file
85
apps/cuesheet.h
Normal file
|
@ -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 <stdbool.h>
|
||||
#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
|
|
@ -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;
|
||||
|
|
|
@ -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,6 +2610,28 @@ 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);
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -10515,3 +10515,31 @@
|
|||
*: ""
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_CUESHEET
|
||||
desc:
|
||||
user:
|
||||
<source>
|
||||
*: "Cuesheet"
|
||||
</source>
|
||||
<dest>
|
||||
*: "Cuesheet"
|
||||
</dest>
|
||||
<voice>
|
||||
*: "Cuesheet"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_CUESHEET_ENABLE
|
||||
desc: cuesheet support option
|
||||
user:
|
||||
<source>
|
||||
*: "Cuesheet Support"
|
||||
</source>
|
||||
<dest>
|
||||
*: "Cuesheet Support"
|
||||
</dest>
|
||||
<voice>
|
||||
*: "Cuesheet Support"
|
||||
</voice>
|
||||
</phrase>
|
||||
|
|
|
@ -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,6 +276,7 @@ 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
|
||||
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -683,6 +683,7 @@ struct user_settings
|
|||
#endif
|
||||
/* Encoder Settings End */
|
||||
#endif /* CONFIG_CODEC == SWCODEC */
|
||||
bool cuesheet;
|
||||
};
|
||||
|
||||
/** global variables **/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue