ares-openbsd/nall/decode/cue.hpp

239 行
6.1 KiB
C++

#pragma once
#include <nall/file.hpp>
#include <nall/maybe.hpp>
#include <nall/string.hpp>
#include <nall/decode/wav.hpp>
namespace nall::Decode {
struct CUE {
struct Index {
auto sectorCount() const -> u32;
u8 number = 0xff; //00-99
s32 lba = -1;
s32 end = -1;
};
struct Track {
auto sectorCount() const -> u32;
auto sectorSize() const -> u32;
u8 number = 0xff; //01-99
string type;
vector<Index> indices;
maybe<s32> pregap;
maybe<s32> postgap;
};
struct File {
auto sectorCount() const -> u32;
auto scan(const string& pathname) -> bool;
string name;
string type;
vector<Track> tracks;
};
auto load(const string& location) -> bool;
auto sectorCount() const -> u32;
vector<File> files;
private:
auto loadFile(vector<string>& lines, u32& offset) -> File;
auto loadTrack(vector<string>& lines, u32& offset) -> Track;
auto loadIndex(vector<string>& lines, u32& offset) -> Index;
auto toLBA(const string& msf) -> u32;
};
inline auto CUE::load(const string& location) -> bool {
auto lines = string::read(location).replace("\r", "").split("\n");
u32 offset = 0;
while(offset < lines.size()) {
lines[offset].strip();
if(lines[offset].ibeginsWith("FILE ")) {
auto file = loadFile(lines, offset);
if(!file.tracks) continue;
files.append(file);
continue;
}
offset++;
}
if(!files) return false;
if(!files.first().tracks) return false;
if(!files.first().tracks.first().indices) return false;
// calculate index ends for all but the last index
for(auto& file : files) {
maybe<Index&> previous;
for(auto& track : file.tracks) {
for(auto& index : track.indices) {
if(index.lba < 0) continue; // ignore gaps (not in file)
if(previous) previous->end = index.lba - 1;
previous = index;
}
}
}
for(auto& file : files) {
if(!file.scan(Location::path(location))) return false;
}
return true;
}
inline auto CUE::loadFile(vector<string>& lines, u32& offset) -> File {
File file;
lines[offset].itrimLeft("FILE ", 1L).strip();
file.type = lines[offset].split(" ").last().strip().downcase();
lines[offset].itrimRight(file.type, 1L).strip();
file.name = lines[offset].trim("\"", "\"", 1L);
offset++;
while(offset < lines.size()) {
lines[offset].strip();
if(lines[offset].ibeginsWith("FILE ")) break;
if(lines[offset].ibeginsWith("TRACK ")) {
auto track = loadTrack(lines, offset);
if(!track.indices) continue;
file.tracks.append(track);
continue;
}
offset++;
}
return file;
}
inline auto CUE::loadTrack(vector<string>& lines, u32& offset) -> Track {
Track track;
lines[offset].itrimLeft("TRACK ", 1L).strip();
track.type = lines[offset].split(" ").last().strip().downcase();
lines[offset].itrimRight(track.type, 1L).strip();
track.number = lines[offset].natural();
offset++;
while(offset < lines.size()) {
lines[offset].strip();
if(lines[offset].ibeginsWith("FILE ")) break;
if(lines[offset].ibeginsWith("TRACK ")) break;
if(lines[offset].ibeginsWith("INDEX ")) {
auto index = loadIndex(lines, offset);
if(index.number == 0 && track.number == 1)
index.lba = 0; // ignore track 1 index 0 (assume 1st pregap always starts at origin)
track.indices.append(index);
continue;
}
if(lines[offset].ibeginsWith("PREGAP ")) {
track.pregap = toLBA(lines[offset++].itrimLeft("PREGAP ", 1L));
Index index; index.number = 0; index.lba = -1;
track.indices.append(index); // placeholder
continue;
}
if(lines[offset].ibeginsWith("POSTGAP ")) {
track.postgap = toLBA(lines[offset++].itrimLeft("POSTGAP ", 1L));
Index index; index.number = track.indices.last().number + 1; index.lba = -1;
track.indices.append(index); // placeholder
continue;
}
offset++;
}
if(track.number == 0 || track.number > 99) return {};
return track;
}
inline auto CUE::loadIndex(vector<string>& lines, u32& offset) -> Index {
Index index;
lines[offset].itrimLeft("INDEX ", 1L);
string sector = lines[offset].split(" ").last().strip();
lines[offset].itrimRight(sector, 1L).strip();
index.number = lines[offset].natural();
index.lba = toLBA(sector);
offset++;
if(index.number > 99) return {};
return index;
}
inline auto CUE::toLBA(const string& msf) -> u32 {
u32 m = msf.split(":")(0).natural();
u32 s = msf.split(":")(1).natural();
u32 f = msf.split(":")(2).natural();
return m * 60 * 75 + s * 75 + f;
}
inline auto CUE::sectorCount() const -> u32 {
u32 count = 0;
for(auto& file : files) count += file.sectorCount();
return count;
}
inline auto CUE::File::scan(const string& pathname) -> bool {
string location = {Location::path(pathname), name};
if(!file::exists(location)) return false;
u64 size = 0;
if(type == "binary") {
size = file::size(location);
} else if(type == "wave") {
Decode::WAV wav;
if(!wav.open(location)) return false;
if(wav.channels != 2) return false;
if(wav.frequency != 44100) return false;
if(wav.bitrate != 16) return false;
size = wav.size();
} else {
return false;
}
// calculate last index end for the file
for(auto& track : tracks) {
for(auto& index : track.indices) {
if(index.lba < 0) continue; // ignore gaps (not in file)
if(index.end >= 0) {
size -= track.sectorSize() * index.sectorCount();
} else {
index.end = index.lba + size / track.sectorSize() - 1;
}
}
}
return true;
}
inline auto CUE::File::sectorCount() const -> u32 {
u32 count = 0;
for(auto& track : tracks) count += track.sectorCount();
return count;
}
inline auto CUE::Track::sectorCount() const -> u32 {
u32 count = 0;
for(auto& index : indices) count += index.sectorCount();
return count;
}
inline auto CUE::Track::sectorSize() const -> u32 {
if(type == "mode1/2048") return 2048;
if(type == "mode1/2352") return 2352;
if(type == "mode2/2352") return 2352;
if(type == "audio" ) return 2352;
return 0;
}
inline auto CUE::Index::sectorCount() const -> u32 {
if(end < 0) return 0;
return end - lba + 1;
}
}