jamp3
Version:
mp3, id3v1, id3v2 - reader & writer
124 lines (115 loc) • 4.87 kB
text/typescript
import {IMP3} from './mp3.types';
import {rawHeaderLayerIdx, rawHeaderOffSet, rawHeaderSize, rawHeaderVersionIdx} from './mp3.mpeg.frame';
// function findIndexOfOffset(start: number, list: Array<IMP3.RawFrame>, offset: number): number {
// for (let i = start; i < list.length; i++) {
// if (list[i].header.offset === offset) {
// return i;
// } else if (list[i].header.offset > offset) {
// return -1;
// }
// }
// return -1;
// }
function followChain(frame: IMP3.FrameRawHeaderArray, pos: number, frames: Array<IMP3.FrameRawHeaderArray>): Array<IMP3.FrameRawHeaderArray> {
const result: Array<IMP3.FrameRawHeaderArray> = [];
// console.log('include start frame:', 'current:', {size: frame.header.size, offset: frame.header.offset});
result.push(frame);
frames = frames.filter(f => rawHeaderSize(f) > 0);
for (let i = pos + 1; i < frames.length; i++) {
const nextpos = rawHeaderOffSet(frame) + rawHeaderSize(frame);
const direct = getNextMatch(nextpos, i - 1, frames);
if (direct >= 0) {
const nextframe = frames[direct];
result.push(nextframe);
// console.log('include direct frame:', 'current:', {size: nextframe.header.size, offset: nextframe.header.offset});
frame = nextframe;
i = direct;
} else {
const nextframe = frames[i];
const diff = rawHeaderOffSet(nextframe) - nextpos;
if (diff === 0) {
result.push(nextframe);
// console.log('include direct diff frame:', 'current:', {size: nextframe.header.size, offset: nextframe.header.offset});
frame = nextframe;
} else if (diff > 0) {
// TODO resync chain if nextframe.header.offset is larger
if ((rawHeaderVersionIdx(nextframe) === rawHeaderVersionIdx(frame) &&
rawHeaderLayerIdx(nextframe) === rawHeaderLayerIdx(frame))
) {
result.push(nextframe);
// console.log('include frame after gap:', 'diff:', diff, 'current:', {size: nextframe.header.size, offset: nextframe.header.offset}, 'last:', {size: frame.header.size, offset: frame.header.offset});
frame = nextframe;
} else {
// console.log('skipped frame after gap:', 'diff:', diff, 'current:', {size: nextframe.header.size, offset: nextframe.header.offset}, 'last:', {size: frame.header.size, offset: frame.header.offset});
}
} else {
// console.log('skipped frame too soon:', 'diff:', diff, 'current:', {size: nextframe.header.size, offset: nextframe.header.offset}, 'last:', {size: frame.header.size, offset: frame.header.offset});
}
}
}
return result;
}
function getNextMatch(offset: number, pos: number, frames: Array<IMP3.FrameRawHeaderArray>): number {
for (let j = pos + 1; j < frames.length; j++) {
if (rawHeaderOffSet(frames[j]) === offset) {
return j;
} else if (rawHeaderOffSet(frames[j]) > offset) {
return -1;
}
}
return -1;
}
export interface IMPEGFrameChain {
frame: Array<number>;
pos: number;
count: number;
}
function buildMPEGChains(frames: Array<IMP3.FrameRawHeaderArray>, maxCheckFrames: number, followMaxChain: number): Array<IMPEGFrameChain> {
const done: Array<Array<number>> = [];
const chains: Array<{ frame: Array<number>; pos: number; count: number }> = [];
const count = Math.min(maxCheckFrames, frames.length);
for (let i = 0; i < count; i++) {
const frame = frames[i];
if (done.indexOf(frame) < 0) {
const chain = {frame, count: 0, pos: i};
chains.push(chain);
done.push(frame);
let next = getNextMatch(rawHeaderOffSet(frame) + rawHeaderSize(frame), i, frames);
while (next >= 0 && chain.count < followMaxChain) {
chain.count++;
const nextframe = frames[next];
done.push(nextframe);
next = getNextMatch(rawHeaderOffSet(nextframe) + rawHeaderSize(nextframe), next, frames);
}
}
}
return chains;
}
function findBestMPEGChain(frames: Array<IMP3.FrameRawHeaderArray>, maxCheckFrames: number, followMaxChain: number): IMPEGFrameChain | undefined {
const chains = buildMPEGChains(frames, maxCheckFrames, followMaxChain);
const bestChains = chains.filter(chain => chain.count > 0).sort((a, b) => b.count - a.count);
return bestChains[0];
}
export function getBestMPEGChain(frames: Array<IMP3.FrameRawHeaderArray>, followMaxChain: number): IMPEGFrameChain | undefined {
if (frames.length === 0) {
return;
}
let select: IMPEGFrameChain | undefined;
let checkMaxFrames = 50;
// try finding a chain in the first {checkMaxFrames} frames
while (!select && checkMaxFrames < 500) {
select = findBestMPEGChain(frames, checkMaxFrames, followMaxChain);
if (checkMaxFrames > frames.length) {
break;
}
checkMaxFrames += 50;
}
return select;
}
export function filterBestMPEGChain(frames: Array<IMP3.FrameRawHeaderArray>, followMaxChain: number): Array<IMP3.FrameRawHeaderArray> {
const chain = getBestMPEGChain(frames, followMaxChain);
if (!chain) {
return frames;
}
return followChain(chain.frame, chain.pos, frames);
}