2
* Copyright (C) 2002-2010 The DOSBox Team
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation; either version 2 of the License, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, write to the Free Software
16
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
/* Geoffrey Brown 2010
20
* Includes ideas from dosbox src/dos/cdrom_image.cpp
22
* Limitations: 1) cue files must reference single bin file
23
* 2) only supports raw mode1 data and audio
24
* 3) no support for audio flags
25
* 4) requires SDL audio or OS X core audio
26
* 5) limited cue file keyword support
28
* Creating cue/bin files:
29
* cdrdao read-cd --read-raw --paranoia 3 foo.toc
45
#include "../MacOSX/MacOSX_sound_if.h"
46
static int bincue_core_audio_callback(void);
51
#include <SDL_audio.h>
54
#include "bincue_unix.h"
61
#define RAW_SECTOR_SIZE 2352
62
#define COOKED_SECTOR_SIZE 2048
64
// Bits of Track Control Field -- These are standard for scsi cd players
66
#define PREMPHASIS 0x1
72
// Audio status -- These are standard for scsi cd players
74
#define CDROM_AUDIO_INVALID 0x00
75
#define CDROM_AUDIO_PLAY 0x11
76
#define CDROM_AUDIO_PAUSED 0x12
77
#define CDROM_AUDIO_COMPLETED 0x13
78
#define CDROM_AUDIO_ERROR 0x14
79
#define CDROM_AUDIO_NO_STATUS 0x15
81
typedef unsigned char uint8;
83
// cuefiles can be challenging as some information is
84
// implied. For example, there may a pregap (also postgap)
85
// of silence that must be generated. Here we implement
90
unsigned int start; // Track start in frames
91
unsigned int length; // Track length in frames
92
loff_t fileoffset; // Track frame start within file
93
unsigned int pregap; // Silence in frames to generate
94
unsigned char tcf; // Track control field
98
char *binfile; // Binary file name
99
unsigned int length; // file length in frames
100
int binfh; // binary file handle
101
int tcnt; // number of tracks
102
Track tracks[MAXTRACK];
106
CueSheet *cs; // cue sheet to play from
107
int audiofh; // file handle for audio data
108
unsigned int audioposition; // current position from audiostart (bytes)
109
unsigned int audiostart; // start position if playing (frame)
110
unsigned int audioend; // end position if playing (frames)
111
unsigned int silence; // pregap (silence) bytes
112
unsigned char audiostatus; // See defines above for status
113
loff_t fileoffset; // offset from file beginning to audiostart
114
#ifdef OSX_CORE_AUDIO
115
OSXsoundOutput soundoutput;
119
// Minute,Second,Frame data type
122
int m, s, f; // note size matters since we scan for %d !
127
static unsigned int totalPregap;
128
static unsigned int prestart;
130
// Audio System State
132
static bool audio_enabled = false;
133
static uint8 silence_byte;
136
// CD Player state. Note only one player is supported !
138
static CDPlayer player;
140
static void FramesToMSF(unsigned int frames, MSF *msf)
142
msf->m = frames/(60 * CD_FRAMES);
143
frames = frames%(60 * CD_FRAMES);
144
msf->s = frames/CD_FRAMES;
145
msf->f = frames%CD_FRAMES;
148
static int MSFToFrames(MSF msf)
150
return (msf.m * 60 * CD_FRAMES) + (msf.s * CD_FRAMES) + msf.f;
154
static int PositionToTrack(CueSheet *cs, unsigned int position)
159
FramesToMSF(position, &msf);
161
for (i = 0; i < cs->tcnt; i++) {
162
if ((position >= cs->tracks[i].start) &&
163
(position <= (cs->tracks[i].start + cs->tracks[i].length)))
169
static bool AddTrack(CueSheet *cs)
173
Track *curr = &(cs->tracks[cs->tcnt]);
178
if (skip > curr->start) {
179
D(bug("AddTrack: prestart > start\n"));
184
curr->fileoffset = curr->start * RAW_SECTOR_SIZE;
186
// now we patch up the indicated time
188
curr->start += totalPregap;
190
// curr->pregap is supposed to be part of this track, but it
191
// must be generated as silence
193
totalPregap += curr->pregap;
196
if (curr->number != 1) {
197
D(bug("AddTrack: number != 1\n"));
204
prev = &(cs->tracks[cs->tcnt - 1]);
206
if (prev->start < skip)
207
prev->length = skip - prev->start - curr->pregap;
209
prev->length = curr->start - prev->start - curr->pregap;
213
if (curr->number <= 1) {
214
D(bug("Bad track number %d\n", curr->number));
217
if ((prev->number + 1 != curr->number) && (curr->number != 0xAA)) {
218
D(bug("Bad track number %d\n", curr->number));
221
if (curr->start < prev->start + prev->length) {
222
D(bug("unexpected start %d\n", curr->start));
230
static bool ParseCueSheet(FILE *fh, CueSheet *cs, const char *cuefile)
232
bool seen1st = false;
234
unsigned int i_line=0;
240
while (fgets(line, MAXLINE, fh) != NULL) {
241
Track *curr = &cs->tracks[cs->tcnt];
243
// check for CUE file
245
if (!i_line && (strncmp("FILE", line, 4) != 0)) {
252
if (NULL != (keyword = strtok(line, " \t\n\t"))) {
253
if (!strcmp("FILE", keyword)) {
258
D(bug("More than one FILE token\n"));
261
filename = strtok(NULL, "\"\t\n\r");
262
filetype = strtok(NULL, " \"\t\n\r");
263
if (strcmp("BINARY", filetype)) {
264
D(bug("Not binary file %s", filetype));
268
char *tmp = strdup(cuefile);
269
char *b = dirname(tmp);
270
cs->binfile = (char *) malloc(strlen(b) + strlen(filename) + 2);
271
sprintf(cs->binfile, "%s/%s", b, filename);
274
} else if (!strcmp("TRACK", keyword)) {
280
D(bug("AddTrack failed \n"));
283
curr = &cs->tracks[cs->tcnt];
288
// parse track number
290
field = strtok(NULL, " \t\n\r");
291
if (1 != sscanf(field, "%d", &i_track)) {
292
D(bug("Expected track number\n"));
295
curr->number = i_track;
299
field = strtok(NULL, " \t\n\r");
300
if (!strcmp("MODE1/2352", field)) {
302
} else if (!strcmp("AUDIO", field)) {
305
D(bug("Unexpected track type %s", field));
309
} else if (!strcmp("INDEX", keyword)) {
314
// parse INDEX number
316
field = strtok(NULL, " \t\n\r");
317
if (1 != sscanf(field, "%d", &i_index)) {
318
D(bug("Expected index number"));
324
field = strtok(NULL, " \t\n\r");
325
if (3 != sscanf(field, "%d:%d:%d",
326
&msf.m, &msf.s, &msf.f)) {
327
D(bug("Expected index start frame\n"));
332
curr->start = MSFToFrames(msf);
333
else if (i_index == 0)
334
prestart = MSFToFrames(msf);
335
} else if (!strcmp("PREGAP", keyword)) {
337
char *field = strtok(NULL, " \t\n\r");
338
if (3 != sscanf(field, "%d:%d:%d",
339
&msf.m, &msf.s, &msf.f)) {
340
D(bug("Expected pregap frame\n"));
343
curr->pregap = MSFToFrames(msf);
345
// Ignored directives
347
} else if (!strcmp("TITLE", keyword)) {
348
} else if (!strcmp("PERFORMER", keyword)) {
349
} else if (!strcmp("REM", keyword)) {
350
} else if (!strcmp("ISRC", keyword)) {
351
} else if (!strcmp("SONGWRITER", keyword)) {
353
D(bug("Unexpected keyword %s\n", keyword));
359
AddTrack(cs); // add final track
365
static bool LoadCueSheet(const char *cuefile, CueSheet *cs)
373
bzero(cs, sizeof(*cs));
374
if (!(fh = fopen(cuefile, "r")))
377
if (!ParseCueSheet(fh, cs, cuefile)) goto fail;
379
// Open bin file and find length
381
if ((binfh = open(cs->binfile,O_RDONLY)) < 0) {
382
D(bug("Can't read bin file %s\n", cs->binfile));
386
if (fstat(binfh, &buf)) {
387
D(bug("fstat returned error\n"));
391
// compute length of final track
394
tlast = &cs->tracks[cs->tcnt - 1];
395
tlast->length = buf.st_size/RAW_SECTOR_SIZE
396
- tlast->start + totalPregap;
398
if (tlast->length < 0) {
399
D(bug("Binary file too short \n"));
403
// save bin file length and pointer
405
cs->length = buf.st_size/RAW_SECTOR_SIZE;
424
void *open_bincue(const char *name)
428
if (player.cs == NULL) {
429
cs = (CueSheet *) malloc(sizeof(CueSheet));
431
D(bug("malloc failed\n"));
435
if (LoadCueSheet(name, cs)) {
437
#ifdef OSX_CORE_AUDIO
438
audio_enabled = true;
441
player.audiostatus = CDROM_AUDIO_NO_STATUS;
443
player.audiostatus = CDROM_AUDIO_INVALID;
444
player.audiofh = dup(cs->binfh);
453
void close_bincue(void *fh)
461
* Data are stored in raw sectors of which only COOKED_SECTOR_SIZE
462
* bytes are valid -- the remaining include 16 bytes at the beginning
463
* of each raw sector and RAW_SECTOR_SIZE - COOKED_SECTOR_SIZE - bytes
466
* We assume that a read request can land in the middle of
467
* sector. We compute the byte address of that sector (sec)
468
* and the offset of the first byte we want within that sector (secoff)
470
* Reading is performed one raw sector at a time, extracting as many
471
* valid bytes as possible from that raw sector (available)
474
size_t read_bincue(void *fh, void *b, loff_t offset, size_t len)
476
size_t bytes_read = 0; // bytes read so far
477
unsigned char *buf = (unsigned char *) b; // target buffer
478
unsigned char secbuf[RAW_SECTOR_SIZE]; // temporary buffer
480
off_t sec = ((offset/COOKED_SECTOR_SIZE) * RAW_SECTOR_SIZE);
481
off_t secoff = offset % COOKED_SECTOR_SIZE;
483
// sec contains location (in bytes) of next raw sector to read
484
// secoff contains offset within that sector at which to start
485
// reading since we can request a read that starts in the middle
488
CueSheet *cs = (CueSheet *) fh;
490
if (cs == NULL || lseek(cs->binfh, sec, SEEK_SET) < 0) {
495
// bytes available in next raw sector or len (bytes)
496
// we want whichever is less
498
size_t available = COOKED_SECTOR_SIZE - secoff;
499
available = (available > len) ? len : available;
501
// read the next raw sector
503
if (read(cs->binfh, secbuf, RAW_SECTOR_SIZE) != RAW_SECTOR_SIZE) {
507
// copy cooked sector bytes (skip first 16)
508
// we want out of those available
510
bcopy(&secbuf[16+secoff], &buf[bytes_read], available);
512
// next sector we start at the beginning
516
// increment running count decrement request
518
bytes_read += available;
524
loff_t size_bincue(void *fh)
527
return ((CueSheet *)fh)->length * COOKED_SECTOR_SIZE;
531
bool readtoc_bincue(void *fh, unsigned char *toc)
533
CueSheet *cs = (CueSheet *) fh;
537
unsigned char *p = toc + 2;
538
*p++ = cs->tracks[0].number;
539
*p++ = cs->tracks[cs->tcnt - 1].number;
540
for (int i = 0; i < cs->tcnt; i++) {
542
FramesToMSF(cs->tracks[i].start, &msf);
544
*p++ = 0x10 | cs->tracks[i].tcf;
545
*p++ = cs->tracks[i].number;
552
FramesToMSF(cs->length, &msf);
562
int toc_size = p - toc;
563
*toc++ = toc_size >> 8;
564
*toc++ = toc_size & 0xff;
569
bool GetPosition_bincue(void *fh, uint8 *pos)
571
CueSheet *cs = (CueSheet *) fh;
572
if (cs && player.cs == cs) {
574
int fpos = player.audioposition / RAW_SECTOR_SIZE + player.audiostart;
575
int trackno = PositionToTrack(cs, fpos);
580
FramesToMSF(fpos, &abs);
581
if (trackno < cs->tcnt) {
582
// compute position relative to start of frame
584
unsigned int position = player.audioposition/RAW_SECTOR_SIZE +
585
player.audiostart - player.cs->tracks[trackno].start;
587
FramesToMSF(position, &rel);
590
FramesToMSF(0, &rel);
593
*pos++ = player.audiostatus;
595
*pos++ = 12; // Sub-Q data length
597
if (trackno < cs->tcnt)
598
*pos++ = 0x10 | cs->tracks[trackno].tcf;
599
*pos++ = (trackno < cs->tcnt) ? cs->tracks[trackno].number : 0xAA;
600
*pos++ = 1; // track index
610
// D(bug("CDROM position %02d:%02d:%02d track %02d\n", abs.m, abs.s, abs.f, trackno));
617
bool CDPause_bincue(void *fh)
619
CueSheet *cs = (CueSheet *) fh;
620
if (cs && cs == player.cs) {
621
if (player.audiostatus == CDROM_AUDIO_PLAY) {
622
player.audiostatus = CDROM_AUDIO_PAUSED;
629
bool CDStop_bincue(void *fh)
631
CueSheet *cs = (CueSheet *) fh;
633
if (cs && cs == player.cs) {
634
#ifdef OSX_CORE_AUDIO
635
player.soundoutput.stop();
637
if (player.audiostatus != CDROM_AUDIO_INVALID)
638
player.audiostatus = CDROM_AUDIO_NO_STATUS;
644
bool CDResume_bincue(void *fh)
646
CueSheet *cs = (CueSheet *) fh;
647
if (cs && cs == player.cs) {
648
if (player.audiostatus == CDROM_AUDIO_PAUSED) {
649
player.audiostatus = CDROM_AUDIO_PLAY;
656
bool CDPlay_bincue(void *fh, uint8 start_m, uint8 start_s, uint8 start_f,
657
uint8 end_m, uint8 end_s, uint8 end_f)
659
CueSheet *cs = (CueSheet *)fh;
660
if (cs && cs == player.cs) {
668
player.audiostatus = CDROM_AUDIO_NO_STATUS;
670
player.audiostart = (start_m * 60 * CD_FRAMES) +
671
(start_s * CD_FRAMES) + start_f;
672
player.audioend = (end_m * 60 * CD_FRAMES) + (end_s * CD_FRAMES) + end_f;
674
track = PositionToTrack(player.cs, player.audiostart);
676
if (track < player.cs->tcnt) {
677
player.audioposition = 0;
679
// here we need to compute silence
681
if (player.audiostart - player.cs->tracks[track].start >
682
player.cs->tracks[track].pregap)
685
player.silence = (player.cs->tracks[track].pregap -
687
player.cs->tracks[track].start) * RAW_SECTOR_SIZE;
689
player.fileoffset = player.cs->tracks[track].fileoffset;
691
D(bug("file offset %d\n", (unsigned int) player.fileoffset));
693
// fix up file offset if beyond the silence bytes
695
if (!player.silence) // not at the beginning
696
player.fileoffset += (player.audiostart -
697
player.cs->tracks[track].start -
698
player.cs->tracks[track].pregap) * RAW_SECTOR_SIZE;
700
FramesToMSF(player.cs->tracks[track].start, &msf);
701
D(bug("CDPlay_bincue track %02d start %02d:%02d:%02d silence %d",
702
player.cs->tracks[track].number, msf.m, msf.s, msf.f,
703
player.silence/RAW_SECTOR_SIZE));
704
D(bug(" Stop %02u:%02u:%02u\n", end_m, end_s, end_f));
707
D(bug("CDPlay_bincue: play beyond last track !\n"));
714
player.audiostatus = CDROM_AUDIO_PLAY;
715
#ifdef OSX_CORE_AUDIO
716
D(bug("starting os x sound"));
717
player.soundoutput.setCallback(bincue_core_audio_callback);
718
// should be from current track !
719
player.soundoutput.start(16, 2, 44100);
727
static uint8 *fill_buffer(int stream_len)
729
static uint8 *buf = 0;
730
static int bufsize = 0;
733
if (bufsize < stream_len) {
735
buf = (uint8 *) malloc(stream_len);
737
bufsize = stream_len;
740
D(bug("malloc failed \n"));
745
memset(buf, silence_byte, stream_len);
747
if (player.audiostatus == CDROM_AUDIO_PLAY) {
748
int remaining_silence = player.silence - player.audioposition;
750
if (player.audiostart + player.audioposition/RAW_SECTOR_SIZE
751
>= player.audioend) {
752
player.audiostatus = CDROM_AUDIO_COMPLETED;
756
if (remaining_silence >= stream_len) {
757
player.audioposition += stream_len;
761
if (remaining_silence > 0) {
762
offset += remaining_silence;
763
player.audioposition += remaining_silence;
767
int available = ((player.audioend - player.audiostart) *
768
RAW_SECTOR_SIZE) - player.audioposition;
769
if (available > (stream_len - offset))
770
available = stream_len - offset;
772
if (lseek(player.audiofh,
773
player.fileoffset + player.audioposition - player.silence,
778
player.audioposition += available; // correct end !;
782
if ((ret = read(player.audiofh, &buf[offset], available)) >= 0) {
783
player.audioposition += ret;
788
while (offset < stream_len) {
789
buf[offset++] = silence_byte;
790
if (available-- > 0){
791
player.audioposition++;
800
void MixAudio_bincue(uint8 *stream, int stream_len)
803
if (audio_enabled && (player.audiostatus == CDROM_AUDIO_PLAY)) {
804
if (buf = fill_buffer(stream_len))
805
SDL_MixAudio(stream, buf, stream_len, SDL_MIX_MAXVOLUME);
809
void OpenAudio_bincue(int freq, int format, int channels, uint8 silence)
811
if (freq == 44100 && format == AUDIO_S16MSB && channels == 2) {
812
audio_enabled = true;
813
silence_byte = silence;
816
D(bug("unexpected frequency %d , format %d, or channels %d\n",
817
freq, format, channels));
822
#ifdef OSX_CORE_AUDIO
823
static int bincue_core_audio_callback(void)
825
int frames = player.soundoutput.bufferSizeFrames();
826
uint8 *buf = fill_buffer(frames*4);
828
// D(bug("Audio request %d\n", stream_len));
830
player.soundoutput.sendAudioBuffer((void *) buf, (buf ? frames : 0));