Initial working version
This commit is contained in:
+86
@@ -0,0 +1,86 @@
|
||||
import { IGetToken } from 'strtok3/lib/core';
|
||||
/**
|
||||
* Common interface for the common chunk DSD header
|
||||
*/
|
||||
export interface IChunkHeader {
|
||||
/**
|
||||
* Chunk ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Chunk size
|
||||
*/
|
||||
size: bigint;
|
||||
}
|
||||
/**
|
||||
* Common chunk DSD header: the 'chunk name (Four-CC)' & chunk size
|
||||
*/
|
||||
export declare const ChunkHeader: IGetToken<IChunkHeader>;
|
||||
/**
|
||||
* Interface to DSD payload chunk
|
||||
*/
|
||||
export interface IDsdChunk {
|
||||
/**
|
||||
* Total file size
|
||||
*/
|
||||
fileSize: bigint;
|
||||
/**
|
||||
* If Metadata doesn’t exist, set 0. If the file has ID3v2 tag, then set the pointer to it.
|
||||
* ID3v2 tag should be located in the end of the file.
|
||||
*/
|
||||
metadataPointer: bigint;
|
||||
}
|
||||
/**
|
||||
* Common chunk DSD header: the 'chunk name (Four-CC)' & chunk size
|
||||
*/
|
||||
export declare const DsdChunk: IGetToken<IDsdChunk>;
|
||||
export declare enum ChannelType {
|
||||
mono = 1,
|
||||
stereo = 2,
|
||||
channels = 3,
|
||||
quad = 4,
|
||||
'4 channels' = 5,
|
||||
'5 channels' = 6,
|
||||
'5.1 channels' = 7
|
||||
}
|
||||
/**
|
||||
* Interface to format chunk payload chunk
|
||||
*/
|
||||
export interface IFormatChunk {
|
||||
/**
|
||||
* Version of this file format
|
||||
*/
|
||||
formatVersion: number;
|
||||
/**
|
||||
* Format ID
|
||||
*/
|
||||
formatID: number;
|
||||
/**
|
||||
* Channel Type
|
||||
*/
|
||||
channelType: ChannelType;
|
||||
/**
|
||||
* Channel num
|
||||
*/
|
||||
channelNum: number;
|
||||
/**
|
||||
* Sampling frequency
|
||||
*/
|
||||
samplingFrequency: number;
|
||||
/**
|
||||
* Bits per sample
|
||||
*/
|
||||
bitsPerSample: number;
|
||||
/**
|
||||
* Sample count
|
||||
*/
|
||||
sampleCount: bigint;
|
||||
/**
|
||||
* Block size per channel
|
||||
*/
|
||||
blockSizePerChannel: number;
|
||||
}
|
||||
/**
|
||||
* Common chunk DSD header: the 'chunk name (Four-CC)' & chunk size
|
||||
*/
|
||||
export declare const FormatChunk: IGetToken<IFormatChunk>;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FormatChunk = exports.ChannelType = exports.DsdChunk = exports.ChunkHeader = void 0;
|
||||
const Token = require("token-types");
|
||||
const FourCC_1 = require("../common/FourCC");
|
||||
/**
|
||||
* Common chunk DSD header: the 'chunk name (Four-CC)' & chunk size
|
||||
*/
|
||||
exports.ChunkHeader = {
|
||||
len: 12,
|
||||
get: (buf, off) => {
|
||||
return { id: FourCC_1.FourCcToken.get(buf, off), size: Token.UINT64_LE.get(buf, off + 4) };
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Common chunk DSD header: the 'chunk name (Four-CC)' & chunk size
|
||||
*/
|
||||
exports.DsdChunk = {
|
||||
len: 16,
|
||||
get: (buf, off) => {
|
||||
return {
|
||||
fileSize: Token.INT64_LE.get(buf, off),
|
||||
metadataPointer: Token.INT64_LE.get(buf, off + 8)
|
||||
};
|
||||
}
|
||||
};
|
||||
var ChannelType;
|
||||
(function (ChannelType) {
|
||||
ChannelType[ChannelType["mono"] = 1] = "mono";
|
||||
ChannelType[ChannelType["stereo"] = 2] = "stereo";
|
||||
ChannelType[ChannelType["channels"] = 3] = "channels";
|
||||
ChannelType[ChannelType["quad"] = 4] = "quad";
|
||||
ChannelType[ChannelType["4 channels"] = 5] = "4 channels";
|
||||
ChannelType[ChannelType["5 channels"] = 6] = "5 channels";
|
||||
ChannelType[ChannelType["5.1 channels"] = 7] = "5.1 channels";
|
||||
})(ChannelType = exports.ChannelType || (exports.ChannelType = {}));
|
||||
/**
|
||||
* Common chunk DSD header: the 'chunk name (Four-CC)' & chunk size
|
||||
*/
|
||||
exports.FormatChunk = {
|
||||
len: 40,
|
||||
get: (buf, off) => {
|
||||
return {
|
||||
formatVersion: Token.INT32_LE.get(buf, off),
|
||||
formatID: Token.INT32_LE.get(buf, off + 4),
|
||||
channelType: Token.INT32_LE.get(buf, off + 8),
|
||||
channelNum: Token.INT32_LE.get(buf, off + 12),
|
||||
samplingFrequency: Token.INT32_LE.get(buf, off + 16),
|
||||
bitsPerSample: Token.INT32_LE.get(buf, off + 20),
|
||||
sampleCount: Token.INT64_LE.get(buf, off + 24),
|
||||
blockSizePerChannel: Token.INT32_LE.get(buf, off + 32)
|
||||
};
|
||||
}
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { AbstractID3Parser } from '../id3v2/AbstractID3Parser';
|
||||
/**
|
||||
* DSF (dsd stream file) File Parser
|
||||
* Ref: https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
|
||||
*/
|
||||
export declare class DsfParser extends AbstractID3Parser {
|
||||
_parse(): Promise<void>;
|
||||
private parseChunks;
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DsfParser = void 0;
|
||||
const AbstractID3Parser_1 = require("../id3v2/AbstractID3Parser");
|
||||
const _debug = require("debug");
|
||||
const DsfChunk_1 = require("./DsfChunk");
|
||||
const ID3v2Parser_1 = require("../id3v2/ID3v2Parser");
|
||||
const debug = _debug('music-metadata:parser:DSF');
|
||||
/**
|
||||
* DSF (dsd stream file) File Parser
|
||||
* Ref: https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
|
||||
*/
|
||||
class DsfParser extends AbstractID3Parser_1.AbstractID3Parser {
|
||||
async _parse() {
|
||||
const p0 = this.tokenizer.position; // mark start position, normally 0
|
||||
const chunkHeader = await this.tokenizer.readToken(DsfChunk_1.ChunkHeader);
|
||||
if (chunkHeader.id !== 'DSD ')
|
||||
throw new Error('Invalid chunk signature');
|
||||
this.metadata.setFormat('container', 'DSF');
|
||||
this.metadata.setFormat('lossless', true);
|
||||
const dsdChunk = await this.tokenizer.readToken(DsfChunk_1.DsdChunk);
|
||||
if (dsdChunk.metadataPointer === BigInt(0)) {
|
||||
debug(`No ID3v2 tag present`);
|
||||
}
|
||||
else {
|
||||
debug(`expect ID3v2 at offset=${dsdChunk.metadataPointer}`);
|
||||
await this.parseChunks(dsdChunk.fileSize - chunkHeader.size);
|
||||
// Jump to ID3 header
|
||||
await this.tokenizer.ignore(Number(dsdChunk.metadataPointer) - this.tokenizer.position - p0);
|
||||
return new ID3v2Parser_1.ID3v2Parser().parse(this.metadata, this.tokenizer, this.options);
|
||||
}
|
||||
}
|
||||
async parseChunks(bytesRemaining) {
|
||||
while (bytesRemaining >= DsfChunk_1.ChunkHeader.len) {
|
||||
const chunkHeader = await this.tokenizer.readToken(DsfChunk_1.ChunkHeader);
|
||||
debug(`Parsing chunk name=${chunkHeader.id} size=${chunkHeader.size}`);
|
||||
switch (chunkHeader.id) {
|
||||
case 'fmt ':
|
||||
const formatChunk = await this.tokenizer.readToken(DsfChunk_1.FormatChunk);
|
||||
this.metadata.setFormat('numberOfChannels', formatChunk.channelNum);
|
||||
this.metadata.setFormat('sampleRate', formatChunk.samplingFrequency);
|
||||
this.metadata.setFormat('bitsPerSample', formatChunk.bitsPerSample);
|
||||
this.metadata.setFormat('numberOfSamples', formatChunk.sampleCount);
|
||||
this.metadata.setFormat('duration', Number(formatChunk.sampleCount) / formatChunk.samplingFrequency);
|
||||
const bitrate = formatChunk.bitsPerSample * formatChunk.samplingFrequency * formatChunk.channelNum;
|
||||
this.metadata.setFormat('bitrate', bitrate);
|
||||
return; // We got what we want, stop further processing of chunks
|
||||
default:
|
||||
this.tokenizer.ignore(Number(chunkHeader.size) - DsfChunk_1.ChunkHeader.len);
|
||||
break;
|
||||
}
|
||||
bytesRemaining -= chunkHeader.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.DsfParser = DsfParser;
|
||||
Reference in New Issue
Block a user