mirror of
https://github.com/claunia/cuetools.net.git
synced 2025-12-16 18:14:25 +00:00
301 lines
11 KiB
ObjectPascal
301 lines
11 KiB
ObjectPascal
|
|
{ *************************************************************************** }
|
||
|
|
{ }
|
||
|
|
{ Audio Tools Library (Freeware) }
|
||
|
|
{ Class TMonkey - for manipulating with Monkey's Audio file information }
|
||
|
|
{ }
|
||
|
|
{ Uses: }
|
||
|
|
{ - Class TID3v1 }
|
||
|
|
{ - Class TID3v2 }
|
||
|
|
{ - Class TAPEtag }
|
||
|
|
{ }
|
||
|
|
{ Copyright (c) 2001,2002 by Jurgen Faul }
|
||
|
|
{ E-mail: jfaul@gmx.de }
|
||
|
|
{ http://jfaul.de/atl }
|
||
|
|
{ }
|
||
|
|
{ Version 1.2 (21 April 2002) }
|
||
|
|
{ - Class TID3v2: support for ID3v2 tags }
|
||
|
|
{ - Class TAPEtag: support for APE tags }
|
||
|
|
{ }
|
||
|
|
{ Version 1.1 (11 September 2001) }
|
||
|
|
{ - Added property Samples }
|
||
|
|
{ - Removed WAV header information }
|
||
|
|
{ }
|
||
|
|
{ Version 1.0 (7 September 2001) }
|
||
|
|
{ - Support for Monkey's Audio files }
|
||
|
|
{ - Class TID3v1: reading & writing support for ID3v1.x tags }
|
||
|
|
{ }
|
||
|
|
{ *************************************************************************** }
|
||
|
|
|
||
|
|
unit Monkey;
|
||
|
|
|
||
|
|
interface
|
||
|
|
|
||
|
|
uses
|
||
|
|
Classes, SysUtils, ID3v1, ID3v2, APEtag;
|
||
|
|
|
||
|
|
const
|
||
|
|
{ Compression level codes }
|
||
|
|
MONKEY_COMPRESSION_FAST = 1000; { Fast (poor) }
|
||
|
|
MONKEY_COMPRESSION_NORMAL = 2000; { Normal (good) }
|
||
|
|
MONKEY_COMPRESSION_HIGH = 3000; { High (very good) }
|
||
|
|
MONKEY_COMPRESSION_EXTRA_HIGH = 4000; { Extra high (best) }
|
||
|
|
|
||
|
|
{ Compression level names }
|
||
|
|
MONKEY_COMPRESSION: array [0..4] of string =
|
||
|
|
('Unknown', 'Fast', 'Normal', 'High', 'Extra High');
|
||
|
|
|
||
|
|
{ Format flags }
|
||
|
|
MONKEY_FLAG_8_BIT = 1; { Audio 8-bit }
|
||
|
|
MONKEY_FLAG_CRC = 2; { New CRC32 error detection }
|
||
|
|
MONKEY_FLAG_PEAK_LEVEL = 4; { Peak level stored }
|
||
|
|
MONKEY_FLAG_24_BIT = 8; { Audio 24-bit }
|
||
|
|
MONKEY_FLAG_SEEK_ELEMENTS = 16; { Number of seek elements stored }
|
||
|
|
MONKEY_FLAG_WAV_NOT_STORED = 32; { WAV header not stored }
|
||
|
|
|
||
|
|
{ Channel mode names }
|
||
|
|
MONKEY_MODE: array [0..2] of string =
|
||
|
|
('Unknown', 'Mono', 'Stereo');
|
||
|
|
|
||
|
|
type
|
||
|
|
{ Real structure of Monkey's Audio header }
|
||
|
|
MonkeyHeader = record
|
||
|
|
ID: array [1..4] of Char; { Always "MAC " }
|
||
|
|
VersionID: Word; { Version number * 1000 (3.91 = 3910) }
|
||
|
|
CompressionID: Word; { Compression level code }
|
||
|
|
Flags: Word; { Any format flags }
|
||
|
|
Channels: Word; { Number of channels }
|
||
|
|
SampleRate: Integer; { Sample rate (hz) }
|
||
|
|
HeaderBytes: Integer; { Header length (without header ID) }
|
||
|
|
TerminatingBytes: Integer; { Extended data }
|
||
|
|
Frames: Integer; { Number of frames in the file }
|
||
|
|
FinalSamples: Integer; { Number of samples in the final frame }
|
||
|
|
PeakLevel: Integer; { Peak level (if stored) }
|
||
|
|
SeekElements: Integer; { Number of seek elements (if stored) }
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ Class TMonkey }
|
||
|
|
TMonkey = class(TObject)
|
||
|
|
private
|
||
|
|
{ Private declarations }
|
||
|
|
FFileLength: Integer;
|
||
|
|
FHeader: MonkeyHeader;
|
||
|
|
FID3v1: TID3v1;
|
||
|
|
FID3v2: TID3v2;
|
||
|
|
FAPEtag: TAPEtag;
|
||
|
|
procedure FResetData;
|
||
|
|
function FGetValid: Boolean;
|
||
|
|
function FGetVersion: string;
|
||
|
|
function FGetCompression: string;
|
||
|
|
function FGetBits: Byte;
|
||
|
|
function FGetChannelMode: string;
|
||
|
|
function FGetPeak: Double;
|
||
|
|
function FGetSamplesPerFrame: Integer;
|
||
|
|
function FGetSamples: Integer;
|
||
|
|
function FGetDuration: Double;
|
||
|
|
function FGetRatio: Double;
|
||
|
|
public
|
||
|
|
{ Public declarations }
|
||
|
|
constructor Create; { Create object }
|
||
|
|
destructor Destroy; override; { Destroy object }
|
||
|
|
function ReadFromFile(const FileName: string): Boolean; { Load header }
|
||
|
|
property FileLength: Integer read FFileLength; { File length (bytes) }
|
||
|
|
property Header: MonkeyHeader read FHeader; { Monkey's Audio header }
|
||
|
|
property ID3v1: TID3v1 read FID3v1; { ID3v1 tag data }
|
||
|
|
property ID3v2: TID3v2 read FID3v2; { ID3v2 tag data }
|
||
|
|
property APEtag: TAPEtag read FAPEtag; { APE tag data }
|
||
|
|
property Valid: Boolean read FGetValid; { True if header valid }
|
||
|
|
property Version: string read FGetVersion; { Encoder version }
|
||
|
|
property Compression: string read FGetCompression; { Compression level }
|
||
|
|
property Bits: Byte read FGetBits; { Bits per sample }
|
||
|
|
property ChannelMode: string read FGetChannelMode; { Channel mode }
|
||
|
|
property Peak: Double read FGetPeak; { Peak level ratio (%) }
|
||
|
|
property Samples: Integer read FGetSamples; { Number of samples }
|
||
|
|
property Duration: Double read FGetDuration; { Duration (seconds) }
|
||
|
|
property Ratio: Double read FGetRatio; { Compression ratio (%) }
|
||
|
|
end;
|
||
|
|
|
||
|
|
implementation
|
||
|
|
|
||
|
|
{ ********************** Private functions & procedures ********************* }
|
||
|
|
|
||
|
|
procedure TMonkey.FResetData;
|
||
|
|
begin
|
||
|
|
{ Reset data }
|
||
|
|
FFileLength := 0;
|
||
|
|
FillChar(FHeader, SizeOf(FHeader), 0);
|
||
|
|
FID3v1.ResetData;
|
||
|
|
FID3v2.ResetData;
|
||
|
|
FAPEtag.ResetData;
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetValid: Boolean;
|
||
|
|
begin
|
||
|
|
{ Check for right Monkey's Audio file data }
|
||
|
|
Result :=
|
||
|
|
(FHeader.ID = 'MAC ') and
|
||
|
|
(FHeader.SampleRate > 0) and
|
||
|
|
(FHeader.Channels > 0);
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetVersion: string;
|
||
|
|
begin
|
||
|
|
{ Get encoder version }
|
||
|
|
if FHeader.VersionID = 0 then Result := ''
|
||
|
|
else Str(FHeader.VersionID / 1000 : 4 : 2, Result);
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetCompression: string;
|
||
|
|
begin
|
||
|
|
{ Get compression level }
|
||
|
|
Result := MONKEY_COMPRESSION[FHeader.CompressionID div 1000];
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetBits: Byte;
|
||
|
|
begin
|
||
|
|
{ Get number of bits per sample }
|
||
|
|
if FGetValid then
|
||
|
|
begin
|
||
|
|
Result := 16;
|
||
|
|
if FHeader.Flags and MONKEY_FLAG_8_BIT > 0 then Result := 8;
|
||
|
|
if FHeader.Flags and MONKEY_FLAG_24_BIT > 0 then Result := 24;
|
||
|
|
end
|
||
|
|
else
|
||
|
|
Result := 0;
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetChannelMode: string;
|
||
|
|
begin
|
||
|
|
{ Get channel mode }
|
||
|
|
Result := MONKEY_MODE[FHeader.Channels];
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetPeak: Double;
|
||
|
|
begin
|
||
|
|
{ Get peak level ratio }
|
||
|
|
if (FGetValid) and (FHeader.Flags and MONKEY_FLAG_PEAK_LEVEL > 0) then
|
||
|
|
case FGetBits of
|
||
|
|
16: Result := FHeader.PeakLevel / 32768 * 100;
|
||
|
|
24: Result := FHeader.PeakLevel / 8388608 * 100;
|
||
|
|
else Result := FHeader.PeakLevel / 128 * 100;
|
||
|
|
end
|
||
|
|
else
|
||
|
|
Result := 0;
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetSamplesPerFrame: Integer;
|
||
|
|
begin
|
||
|
|
{ Get number of samples in a frame }
|
||
|
|
if FGetValid then
|
||
|
|
if (FHeader.VersionID >= 3900) or
|
||
|
|
((FHeader.VersionID >= 3800) and
|
||
|
|
(FHeader.CompressionID = MONKEY_COMPRESSION_EXTRA_HIGH)) then
|
||
|
|
Result := 73728
|
||
|
|
else
|
||
|
|
Result := 9216
|
||
|
|
else
|
||
|
|
Result := 0;
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetSamples: Integer;
|
||
|
|
begin
|
||
|
|
{ Get number of samples }
|
||
|
|
if FGetValid then
|
||
|
|
Result := (FHeader.Frames - 1) * FGetSamplesPerFrame + FHeader.FinalSamples
|
||
|
|
else
|
||
|
|
Result := 0;
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetDuration: Double;
|
||
|
|
begin
|
||
|
|
{ Get song duration }
|
||
|
|
if FGetValid then Result := FGetSamples / FHeader.SampleRate
|
||
|
|
else Result := 0;
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.FGetRatio: Double;
|
||
|
|
begin
|
||
|
|
{ Get compression ratio }
|
||
|
|
if FGetValid then
|
||
|
|
Result := FFileLength /
|
||
|
|
(FGetSamples * FHeader.Channels * FGetBits / 8 + 44) * 100
|
||
|
|
else
|
||
|
|
Result := 0;
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ ********************** Public functions & procedures ********************** }
|
||
|
|
|
||
|
|
constructor TMonkey.Create;
|
||
|
|
begin
|
||
|
|
{ Create object }
|
||
|
|
inherited;
|
||
|
|
FID3v1 := TID3v1.Create;
|
||
|
|
FID3v2 := TID3v2.Create;
|
||
|
|
FAPEtag := TAPEtag.Create;
|
||
|
|
FResetData;
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
destructor TMonkey.Destroy;
|
||
|
|
begin
|
||
|
|
{ Destroy object }
|
||
|
|
FID3v1.Free;
|
||
|
|
FID3v2.Free;
|
||
|
|
FAPEtag.Free;
|
||
|
|
inherited;
|
||
|
|
end;
|
||
|
|
|
||
|
|
{ --------------------------------------------------------------------------- }
|
||
|
|
|
||
|
|
function TMonkey.ReadFromFile(const FileName: string): Boolean;
|
||
|
|
var
|
||
|
|
SourceFile: file;
|
||
|
|
begin
|
||
|
|
try
|
||
|
|
{ Reset data and search for file tag }
|
||
|
|
FResetData;
|
||
|
|
if (not FID3v1.ReadFromFile(FileName)) or
|
||
|
|
(not FID3v2.ReadFromFile(FileName)) or
|
||
|
|
(not FAPEtag.ReadFromFile(FileName)) then raise Exception.Create('');
|
||
|
|
{ Set read-access, open file and get file length }
|
||
|
|
AssignFile(SourceFile, FileName);
|
||
|
|
FileMode := 0;
|
||
|
|
Reset(SourceFile, 1);
|
||
|
|
FFileLength := FileSize(SourceFile);
|
||
|
|
{ Read Monkey's Audio header data }
|
||
|
|
Seek(SourceFile, ID3v2.Size);
|
||
|
|
BlockRead(SourceFile, FHeader, SizeOf(FHeader));
|
||
|
|
if FHeader.Flags and MONKEY_FLAG_PEAK_LEVEL = 0 then
|
||
|
|
FHeader.PeakLevel := 0;
|
||
|
|
if FHeader.Flags and MONKEY_FLAG_SEEK_ELEMENTS = 0 then
|
||
|
|
FHeader.SeekElements := 0;
|
||
|
|
CloseFile(SourceFile);
|
||
|
|
Result := true;
|
||
|
|
except
|
||
|
|
FResetData;
|
||
|
|
Result := false;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
end.
|