changeset 2298:9d68799f945b

Added basic FLAC seek implementation and added support for FLAC tracks in CUE sheets
author Michael Pavone <pavone@retrodev.com>
date Thu, 09 Mar 2023 22:49:42 -0800
parents e6b2b2341c68
children a1c9edf44c7e
files cdimage.c flac.c flac.h system.h
diffstat 4 files changed, 185 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/cdimage.c	Sun Feb 19 22:00:29 2023 -0800
+++ b/cdimage.c	Thu Mar 09 22:49:42 2023 -0800
@@ -111,17 +111,21 @@
 			if (track) {
 				lba -= media->tracks[track - 1].end_lba;
 			}
-			if (media->tracks[track].has_subcodes) {
-				if (!media->tmp_buffer) {
-					media->tmp_buffer = calloc(1, 96);
+			if (media->tracks[track].flac) {
+				flac_seek(media->tracks[track].flac, (media->tracks[track].file_offset + lba * media->tracks[track].sector_bytes) / 4);
+			} else {
+				if (media->tracks[track].has_subcodes) {
+					if (!media->tmp_buffer) {
+						media->tmp_buffer = calloc(1, 96);
+					}
+					fseek(media->tracks[track].f, media->tracks[track].file_offset + (lba + 1) * media->tracks[track].sector_bytes - 96, SEEK_SET);
+					int bytes = fread(media->tmp_buffer, 1, 96, media->tracks[track].f);
+					if (bytes != 96) {
+						fprintf(stderr, "Only read %d subcode bytes\n", bytes);
+					}
 				}
-				fseek(media->tracks[track].f, media->tracks[track].file_offset + (lba + 1) * media->tracks[track].sector_bytes - 96, SEEK_SET);
-				int bytes = fread(media->tmp_buffer, 1, 96, media->tracks[track].f);
-				if (bytes != 96) {
-					fprintf(stderr, "Only read %d subcode bytes\n", bytes);
-				}
+				fseek(media->tracks[track].f, media->tracks[track].file_offset + lba * media->tracks[track].sector_bytes, SEEK_SET);
 			}
-			fseek(media->tracks[track].f, media->tracks[track].file_offset + lba * media->tracks[track].sector_bytes, SEEK_SET);
 		}
 	}
 	return track;
@@ -156,12 +160,23 @@
 		return 0;
 	} else if ((media->tracks[media->cur_track].sector_bytes < 2352 && offset < 16) || offset > (media->tracks[media->cur_track].sector_bytes + 16)) {
 		return fake_read(media->cur_sector, offset);
+	} else if (media->tracks[media->cur_track].flac) {
+		if (offset & 3) {
+			return media->byte_storage[(offset & 3) - 1];
+		} else {
+			int16_t samples[2];
+			flac_get_sample(media->tracks[media->cur_track].flac, samples, 2);
+			media->byte_storage[0] = samples[0] >> 8;
+			media->byte_storage[1] = samples[1];
+			media->byte_storage[2] = samples[1] >> 8;
+			return samples[0];
+		}
 	} else {
 		if (media->tracks[media->cur_track].need_swap) {
 			if (offset & 1) {
-				return media->byte_storage;
+				return media->byte_storage[0];
 			}
-			media->byte_storage = fgetc(media->tracks[media->cur_track].f);
+			media->byte_storage[0] = fgetc(media->tracks[media->cur_track].f);
 		}
 		return fgetc(media->tracks[media->cur_track].f);
 	}
@@ -198,6 +213,7 @@
 	int track = -1;
 	uint8_t audio_byte_swap = 0;
 	FILE *f = NULL;
+	flac_file *flac = NULL;
 	int track_of_file = -1;
 	uint8_t has_index_0 = 0;
 	uint32_t extra_offset = 0;
@@ -215,6 +231,7 @@
 					warning("Expected track %d, but found track %d in CUE sheet\n", track + 1, file_track);
 				}
 				tracks[track].f = f;
+				tracks[track].flac = flac;
 
 
 				cmd = cmd_start(end);
@@ -259,11 +276,12 @@
 							memcpy(fname + dirlen + 1, cmd, end-cmd);
 							fname[dirlen + 1 + (end-cmd)] = 0;
 						}
+						flac = NULL;
 						f = fopen(fname, "rb");
 						if (!f) {
 							fatal_error("Failed to open %s specified by FILE command in CUE sheet %s.%s\n", fname, media->name, media->extension);
 						}
-						free(fname);
+
 						track_of_file = -1;
 						for (end++; *end && *end != '\n' && *end != '\r'; end++)
 						{
@@ -276,19 +294,29 @@
 								} else if (startswith(end, "WAVE")) {
 									audio_byte_swap = 0;
 									wave_header wave;
-									if (!wave_read_header(f, &wave)) {
-										fatal_error("Wave file %s specified by cute sheet %s.%s is not valid\n", fname, media->name, media->extension);
+									if (wave_read_header(f, &wave)) {
+										if (wave.audio_format != 1 || wave.num_channels != 2 || wave.sample_rate != 44100 || wave.bits_per_sample != 16) {
+											warning("BlastEm only suports WAVE tracks in 16-bit stereo PCM format at 44100 hz, file %s does not match\n", fname);
+										}
+										extra_offset = wave.format_header.size + sizeof(wave.data_header) + sizeof(wave.chunk);
+									} else {
+										fseek(f, 0, SEEK_SET);
+										flac = flac_file_from_file(f);
+										if (!flac) {
+											fatal_error("WAVE file %s in cue sheet %s.%s is neither a valid WAVE nor a valid FLAC file\n", fname, media->name, media->extension);
+										}
+										if (flac->sample_rate != 44100 || flac->bits_per_sample != 16 || flac->channels != 2) {
+											warning("FLAC files in a CUE sheet should match CD audio specs, %s does not\n", fname);
+										}
+
 									}
-									if (wave.audio_format != 1 || wave.num_channels != 2 || wave.sample_rate != 44100 || wave.bits_per_sample != 16) {
-										warning("BlastEm only suports WAVE tracks in 16-bit stereo PCM format at 44100 hz, file %s does not match\n", fname);
-									}
-									extra_offset = wave.format_header.size + sizeof(wave.data_header) + sizeof(wave.chunk);
 								} else {
 									warning("Unsupported FILE type in CUE sheet. Only BINARY and MOTOROLA are supported\n");
 								}
 								break;
 							}
 						}
+						free(fname);
 					}
 				}
 			} else if (track >= 0) {
@@ -344,10 +372,22 @@
 		for (int track = 0; track < media->num_tracks; track++) {
 			if (track == media->num_tracks - 1 && tracks[track].f) {
 				uint32_t start_lba =tracks[track].fake_pregap ? tracks[track].start_lba : tracks[track].pregap_lba;
-				tracks[track].end_lba = start_lba + (file_size(tracks[track].f) - tracks[track].file_offset)/ tracks[track].sector_bytes;
+				uint32_t fsize;
+				if (tracks[track].flac) {
+					fsize = tracks[track].flac->total_samples * 4;
+				} else {
+					fsize = file_size(tracks[track].f);
+				}
+				tracks[track].end_lba = start_lba + (fsize - tracks[track].file_offset)/ tracks[track].sector_bytes;
 			} else if (tracks[track].f != f) {
 				uint32_t start_lba =tracks[track-1].fake_pregap ? tracks[track-1].start_lba : tracks[track-1].pregap_lba;
-				tracks[track-1].end_lba = start_lba + (file_size(tracks[track-1].f) - tracks[track-1].file_offset)/ tracks[track-1].sector_bytes;
+				uint32_t fsize;
+				if (tracks[track-1].flac) {
+					fsize = tracks[track-1].flac->total_samples * 4;
+				} else {
+					fsize = file_size(tracks[track-1].f);
+				}
+				tracks[track-1].end_lba = start_lba + (fsize - tracks[track-1].file_offset)/ tracks[track-1].sector_bytes;
 				offset = tracks[track-1].end_lba;
 			}
 			if (!tracks[track].fake_pregap) {
@@ -359,7 +399,7 @@
 		//replace cue sheet with first sector
 		free(media->buffer);
 		media->buffer = calloc(2048, 1);
-		if (tracks[0].type == TRACK_DATA && tracks[0].sector_bytes == 2352) {
+		if (tracks[0].type == TRACK_DATA && tracks[0].sector_bytes == 2352 && !tracks[0].flac) {
 			// if the first track is a data track, don't trust the CUE sheet and look at the MM:SS:FF from first sector
 			uint8_t msf[3];
 			fseek(tracks[0].f, 12, SEEK_SET);
@@ -588,10 +628,12 @@
 		save_int32(buf, 0);
 	}
 	save_int8(buf, media->in_fake_pregap);
-	save_int8(buf, media->byte_storage);
+	save_int8(buf, media->byte_storage[0]);
 	if (media->tmp_buffer) {
 		save_buffer8(buf, media->tmp_buffer, 96);
 	}
+	save_int8(buf, media->byte_storage[1]);
+	save_int8(buf, media->byte_storage[2]);
 }
 
 void cdimage_deserialize(deserialize_buffer *buf, void *vmedia)
@@ -607,8 +649,12 @@
 		fseek(media->tracks[media->cur_track].f, seekpos, SEEK_SET);
 	}
 	media->in_fake_pregap = load_int8(buf);
-	media->byte_storage = load_int8(buf);
+	media->byte_storage[0] = load_int8(buf);
 	if (media->tmp_buffer) {
 		load_buffer8(buf, media->tmp_buffer, 96);
 	}
+	if (buf->size - buf->cur_pos >= 2) {
+		media->byte_storage[1] = load_int8(buf);
+		media->byte_storage[2] = load_int8(buf);
+	}
 }
--- a/flac.c	Sun Feb 19 22:00:29 2023 -0800
+++ b/flac.c	Thu Mar 09 22:49:42 2023 -0800
@@ -17,6 +17,11 @@
 	f->offset = relative ? f->offset + offset : offset;
 }
 
+static uint32_t tell_buffer(flac_file *f)
+{
+	return f->offset;
+}
+
 static uint8_t read_byte_file(flac_file *f)
 {
 	int result = fgetc(f->read_data);
@@ -31,6 +36,11 @@
 	fseek(f->read_data, offset, relative ? SEEK_CUR : SEEK_SET);
 }
 
+static uint32_t tell_file(flac_file *f)
+{
+	return ftell(f->read_data);
+}
+
 static void read_chars(flac_file *f, char *dest, uint32_t count)
 {
 	for (; count > 0; --count)
@@ -46,6 +56,19 @@
 	return ret;
 }
 
+static uint64_t read64(flac_file *f)
+{
+	uint64_t value = ((uint64_t)f->read_byte(f)) << 56;
+	value |= ((uint64_t)f->read_byte(f)) << 48;
+	value |= ((uint64_t)f->read_byte(f)) << 40;
+	value |= ((uint64_t)f->read_byte(f)) << 32;
+	value |= ((uint64_t)f->read_byte(f)) << 24;
+	value |= f->read_byte(f) << 16;
+	value |= f->read_byte(f) << 8;
+	value |= f->read_byte(f);
+	return value;
+}
+
 static uint32_t read_bits(flac_file *f, uint32_t num_bits)
 {
 	uint32_t ret = 0;
@@ -127,6 +150,18 @@
 	f->seek(f, 16, 1);//MD5
 }
 
+static void parse_seektable(flac_file *f, uint32_t size)
+{
+	f->num_seekpoints = size / 18;
+	f->seekpoints = calloc(f->num_seekpoints, sizeof(flac_seekpoint));
+	for (uint32_t i = 0; i < f->num_seekpoints; i++)
+	{
+		f->seekpoints[i].sample_number = read64(f);
+		f->seekpoints[i].offset = read64(f);
+		f->seekpoints[i].sample_count = read16(f);
+	}
+}
+
 static uint8_t parse_header(flac_file *f)
 {
 	char id[4];
@@ -139,10 +174,13 @@
 		read_meta_block_header(f, &header);
 		if (header.type == STREAMINFO) {
 			parse_streaminfo(f);
+		} else if (header.type == SEEKTABLE) {
+			parse_seektable(f, header.size);
 		} else {
 			f->seek(f, header.size, 1);
 		}
 	} while (!header.is_last);
+	f->first_frame_offset = f->tell(f);
 	return 1;
 }
 
@@ -152,6 +190,7 @@
 	f->read_data = buffer;
 	f->read_byte = read_byte_buffer;
 	f->seek = seek_buffer;
+	f->tell = tell_buffer;
 	f->buffer_size = size;
 	if (parse_header(f)) {
 		return f;
@@ -166,6 +205,7 @@
 	f->read_data = file;
 	f->read_byte = read_byte_file;
 	f->seek = seek_file;
+	f->tell = tell_file;
 	if (parse_header(f)) {
 		return f;
 	}
@@ -186,7 +226,7 @@
 		mask >>= 1;
 		length++;
 	}
-	uint64_t value = byte + (mask - 1);
+	uint64_t value = byte & (mask - 1);
 	for (uint8_t i = 0; i < length; i++)
 	{
 		value <<= 6;
@@ -208,7 +248,7 @@
 		mask >>= 1;
 		length++;
 	}
-	uint32_t value = byte + (mask - 1);
+	uint32_t value = byte & (mask - 1);
 	for (uint8_t i = 0; i < length; i++)
 	{
 		value <<= 6;
@@ -580,3 +620,38 @@
 
 	return 1;
 }
+
+void flac_seek(flac_file *f, uint64_t sample_number)
+{
+	if (sample_number >= f->frame_start_sample && sample_number < f->frame_start_sample + f->frame_block_size) {
+		f->frame_sample_pos = sample_number - f->frame_start_sample;
+		return;
+	}
+	uint32_t best_seekpoint = f->num_seekpoints + 1;
+	if (f->num_seekpoints) {
+		uint64_t best_diff;
+		for (uint32_t i = 0; i < f->num_seekpoints; i++)
+		{
+			if (f->seekpoints[i].sample_number > sample_number) {
+				continue;
+			}
+			uint64_t diff = sample_number - f->seekpoints[i].sample_number;
+			if (best_seekpoint > f->num_seekpoints || diff < best_diff) {
+				best_seekpoint = i;
+				best_diff = diff;
+			}
+		}
+	}
+	//TODO: more efficient seeking
+	if (best_seekpoint > f->num_seekpoints) {
+		f->seek(f, f->first_frame_offset, 0);
+	} else if (f->seekpoints[best_seekpoint].sample_number > f->frame_start_sample || f->frame_start_sample > sample_number){
+		f->seek(f, f->seekpoints[best_seekpoint].offset + f->first_frame_offset, 0);
+	}
+	do {
+		if (!decode_frame(f)) {
+			return;
+		}
+	} while ((f->frame_start_sample + f->frame_block_size) <= sample_number);
+	f->frame_sample_pos = sample_number - f->frame_start_sample;
+}
--- a/flac.h	Sun Feb 19 22:00:29 2023 -0800
+++ b/flac.h	Thu Mar 09 22:49:42 2023 -0800
@@ -6,43 +6,54 @@
 
 typedef struct flac_file flac_file;
 
-typedef uint8_t (*flac_read)(flac_file *f);
-typedef void (*flac_seek)(flac_file *f, uint32_t offset, uint8_t relative);
+typedef uint8_t (*flac_read_fun)(flac_file *f);
+typedef void (*flac_seek_fun)(flac_file *f, uint32_t offset, uint8_t relative);
+typedef uint32_t (*flac_tell_fun)(flac_file *f);
 
 typedef struct {
 	uint32_t allocated_samples;
 	int32_t *decoded;
 } flac_subframe;
 
-struct flac_file {
-	uint64_t      total_samples;
-	uint64_t      frame_start_sample;
-	void          *read_data;
-	flac_read     read_byte;
-	flac_seek     seek;
-	flac_subframe *subframes;
-	uint32_t      offset;
-	uint32_t      buffer_size;
-
-	uint32_t      frame_sample_pos;
-	uint32_t      remaining_frame_samples;
+typedef struct {
+	uint64_t sample_number;
+	uint64_t offset;
+	uint16_t sample_count;
+} flac_seekpoint;
 
-	uint32_t      sample_rate;
-	uint32_t      frame_sample_rate;
-	uint32_t      frame_block_size;
-	uint8_t       bits_per_sample;
-	uint8_t       frame_bits_per_sample;
-	uint8_t       channels;
-	uint8_t       frame_channels;
-	uint8_t       frame_joint_stereo;
-	uint8_t       subframe_alloc;
+struct flac_file {
+	uint64_t       total_samples;
+	uint64_t       frame_start_sample;
+	void           *read_data;
+	flac_read_fun  read_byte;
+	flac_seek_fun  seek;
+	flac_tell_fun  tell;
+	flac_subframe  *subframes;
+	flac_seekpoint *seekpoints;
+	uint32_t       num_seekpoints;
+	uint32_t       offset;
+	uint32_t       buffer_size;
+	uint32_t       first_frame_offset;
 
-	uint8_t       cur_byte;
-	uint8_t       bits;
+	uint32_t       frame_sample_pos;
+
+	uint32_t       sample_rate;
+	uint32_t       frame_sample_rate;
+	uint32_t       frame_block_size;
+	uint8_t        bits_per_sample;
+	uint8_t        frame_bits_per_sample;
+	uint8_t        channels;
+	uint8_t        frame_channels;
+	uint8_t        frame_joint_stereo;
+	uint8_t        subframe_alloc;
+
+	uint8_t        cur_byte;
+	uint8_t        bits;
 };
 
 flac_file *flac_file_from_buffer(void *buffer, uint32_t size);
 flac_file *flac_file_from_file(FILE *file);
 uint8_t flac_get_sample(flac_file *f, int16_t *out, uint8_t desired_channels);
+void flac_seek(flac_file *f, uint64_t sample_number);
 
 #endif //FLAC_H_
--- a/system.h	Sun Feb 19 22:00:29 2023 -0800
+++ b/system.h	Thu Mar 09 22:49:42 2023 -0800
@@ -3,6 +3,7 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
+#include "flac.h"
 
 typedef struct system_header system_header;
 typedef struct system_media system_media;
@@ -111,6 +112,7 @@
 
 typedef struct {
 	FILE       *f;
+	flac_file  *flac;
 	uint32_t   file_offset;
 	uint32_t   fake_pregap;
 	uint32_t   pregap_lba;
@@ -142,7 +144,7 @@
 	uint32_t     cur_sector;
 	media_type   type;
 	uint8_t      in_fake_pregap;
-	uint8_t      byte_storage;
+	uint8_t      byte_storage[3];
 };
 
 #define OPT_ADDRESS_LOG (1U << 31U)