| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 | /* * Simple CUE sheet parser suitable for embedded systems. * *  Copyright (c) 2023 Rabbit Hole Computing *   *  This program is free software: you can redistribute it and/or modify *   it under the terms of the GNU General Public License as published by *   the Free Software Foundation, either version 3 of the License, or *   (at your option) any later version. * *   This program is distributed in the hope that it will be useful, *   but WITHOUT ANY WARRANTY; without even the implied warranty of *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the *   GNU General Public License for more details. * *   You should have received a copy of the GNU General Public License *   along with this program.  If not, see <https://www.gnu.org/licenses/>. */// Refer to e.g. https://www.gnu.org/software/ccd2cue/manual/html_node/CUE-sheet-format.html#CUE-sheet-format//// Example of a CUE file:// FILE "foo bar.bin" BINARY//   TRACK 01 MODE1/2048//     INDEX 01 00:00:00//   TRACK 02 AUDIO//     PREGAP 00:02:00//     INDEX 01 02:47:20//   TRACK 03 AUDIO//     INDEX 00 07:55:58//     INDEX 01 07:55:65#include "CUEParser.h"#include <ctype.h>#include <stdlib.h>#include <string.h>#include <strings.h>CUEParser::CUEParser(const char *cue_sheet):    m_cue_sheet(cue_sheet){    restart();}void CUEParser::restart(){    m_parse_pos = m_cue_sheet;    memset(&m_track_info, 0, sizeof(m_track_info));}const CUETrackInfo *CUEParser::next_track(){    bool got_track = false;    bool got_data = false;    while(start_line() && !(got_track && got_data))    {        if (strncasecmp(m_parse_pos, "FILE ", 5) == 0)        {            const char *p = read_quoted(m_parse_pos + 5, m_track_info.filename, sizeof(m_track_info.filename));            m_track_info.file_mode = parse_file_mode(skip_space(p));        }        else if (strncasecmp(m_parse_pos, "TRACK ", 6) == 0)        {            const char *track_num = skip_space(m_parse_pos + 6);            char *endptr;            m_track_info.track_number = strtoul(track_num, &endptr, 10);            m_track_info.track_mode = parse_track_mode(skip_space(endptr));            m_track_info.pregap_start = 0;            m_track_info.unstored_pregap_length = 0;            m_track_info.data_start = 0;            got_track = true;            got_data = false;        }        else if (strncasecmp(m_parse_pos, "PREGAP ", 7) == 0)        {            const char *time_str = skip_space(m_parse_pos + 7);            m_track_info.unstored_pregap_length = parse_time(time_str);        }        else if (strncasecmp(m_parse_pos, "INDEX ", 6) == 0)        {            const char *index_str = skip_space(m_parse_pos + 6);            char *endptr;            int index = strtoul(index_str, &endptr, 10);            const char *time_str = skip_space(endptr);            uint32_t time = parse_time(time_str);            if (index == 0)            {                m_track_info.pregap_start = time;            }            else if (index == 1)            {                m_track_info.data_start = time;                got_data = true;            }        }        next_line();    }    if (got_track && got_data)        return &m_track_info;    else        return nullptr;}bool CUEParser::start_line(){    // Skip initial whitespace    while (isspace(*m_parse_pos))    {        m_parse_pos++;    }    return *m_parse_pos != '\0';}void CUEParser::next_line(){    // Find end of current line    const char *p = m_parse_pos;    while (*p != '\n' && *p != '\0')    {        p++;    }    // Skip any linefeeds    while (*p == '\n' || *p == '\r')    {        p++;    }    m_parse_pos = p;}const char *CUEParser::skip_space(const char *p) const{    while (isspace(*p)) p++;    return p;}const char *CUEParser::read_quoted(const char *src, char *dest, int dest_size){    // Search for starting quote    while (*src != '"')    {        if (*src == '\0' || *src == '\n')        {            // Unexpected end of line / file            dest[0] = '\0';            return src;        }        src++;    }        src++;    // Copy text until ending quote    int len = 0;    while (*src != '"' && *src != '\0' && *src != '\n')    {        if (len < dest_size - 1)        {            dest[len++] = *src;        }        src++;    }    dest[len] = '\0';    if (*src == '"') src++;    return src;}uint32_t CUEParser::parse_time(const char *src){    char *endptr;    uint32_t minutes = strtoul(src, &endptr, 10);    if (*endptr == ':') endptr++;    uint32_t seconds = strtoul(endptr, &endptr, 10);    if (*endptr == ':') endptr++;    uint32_t frames = strtoul(endptr, &endptr, 10);    return frames + 75 * (seconds + 60 * minutes);}CUEFileMode CUEParser::parse_file_mode(const char *src){    if (strncasecmp(src, "BIN", 3) == 0)        return CUEFile_BINARY;    else if (strncasecmp(src, "MOTOROLA", 8) == 0)        return CUEFile_MOTOROLA;    else if (strncasecmp(src, "MP3", 3) == 0)        return CUEFile_MP3;    else if (strncasecmp(src, "WAV", 3) == 0)        return CUEFile_WAVE;    else if (strncasecmp(src, "AIFF", 4) == 0)        return CUEFile_AIFF;    else        return CUEFile_BINARY; // Default to binary mode}CUETrackMode CUEParser::parse_track_mode(const char *src){    if (strncasecmp(src, "AUDIO", 5) == 0)        return CUETrack_AUDIO;    else if (strncasecmp(src, "CDG", 3) == 0)        return CUETrack_CDG;    else if (strncasecmp(src, "MODE1/2048", 10) == 0)        return CUETrack_MODE1_2048;    else if (strncasecmp(src, "MODE1/2352", 10) == 0)        return CUETrack_MODE1_2352;    else if (strncasecmp(src, "MODE2/2048", 10) == 0)        return CUETrack_MODE2_2048;    else if (strncasecmp(src, "MODE2/2324", 10) == 0)        return CUETrack_MODE2_2324;    else if (strncasecmp(src, "MODE2/2336", 10) == 0)        return CUETrack_MODE2_2336;    else if (strncasecmp(src, "MODE2/2352", 10) == 0)        return CUETrack_MODE2_2352;    else if (strncasecmp(src, "CDI/2336", 8) == 0)        return CUETrack_CDI_2336;    else if (strncasecmp(src, "CDI/2352", 8) == 0)        return CUETrack_CDI_2352;    else        return CUETrack_MODE1_2048; // Default to data track}
 |