-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
77869bd
commit 176fb85
Showing
7 changed files
with
272 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
retroload-lib/src/decoding/writer/z1013/Z1013BlockProcessor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import {type BufferAccess} from '../../../common/BufferAccess.js'; | ||
import {type Position} from '../../../common/Positioning.js'; | ||
import {type BlockDecodingResult, BlockDecodingResultStatus, type Z1013BlockProvider} from './Z1013BlockProvider.js'; | ||
|
||
/** | ||
* Minimal expected gap between files in seconds (from end of previous block to begin of next block) | ||
*/ | ||
const maximalIntraFileBlockGap = 1; | ||
|
||
export class Z1013BlockProcessor { | ||
private blocks: BlockDecodingResult[] = []; | ||
constructor( | ||
private readonly blockProvider: Z1013BlockProvider, | ||
) {} | ||
|
||
* files(): Generator<FileDecodingResult> { | ||
for (const bdr of this.blockProvider.blocks()) { | ||
if (this.belongsToCurrentFile(bdr)) { | ||
this.blocks.push(bdr); | ||
} else { | ||
yield new FileDecodingResult( | ||
this.blocks.map((b) => b.data), | ||
this.currentFileWasSuccessful() ? FileDecodingResultStatus.Success : FileDecodingResultStatus.Error, | ||
this.blocks[0].blockBegin, | ||
this.blocks[this.blocks.length - 1].blockEnd, | ||
); | ||
this.blocks = []; | ||
} | ||
} | ||
} | ||
|
||
private belongsToCurrentFile(bdr: BlockDecodingResult): boolean { | ||
if (this.blocks.length === 0) { | ||
return true; | ||
} | ||
if (bdr.blockEnd.seconds - this.blocks[this.blocks.length - 1].blockEnd.seconds < maximalIntraFileBlockGap) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private currentFileWasSuccessful(): boolean { | ||
for (const bdr of this.blocks) { | ||
if (bdr.status !== BlockDecodingResultStatus.Complete) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
|
||
export class FileDecodingResult { | ||
constructor( | ||
readonly blocks: BufferAccess[], | ||
readonly status: FileDecodingResultStatus, | ||
readonly begin: Position, | ||
readonly end: Position, | ||
) {} | ||
} | ||
|
||
export enum FileDecodingResultStatus { | ||
Success, | ||
Error, | ||
} |
30 changes: 30 additions & 0 deletions
30
retroload-lib/src/decoding/writer/z1013/Z1013BlockProvider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import {type BufferAccess} from '../../../common/BufferAccess.js'; | ||
import {type Position} from '../../../common/Positioning.js'; | ||
|
||
export type Z1013BlockProvider = { | ||
blocks(): Generator<BlockDecodingResult>; | ||
}; | ||
|
||
export class BlockDecodingResult { | ||
constructor( | ||
readonly data: BufferAccess, | ||
readonly status: BlockDecodingResultStatus, | ||
readonly blockBegin: Position, | ||
readonly blockEnd: Position, | ||
) {} | ||
} | ||
|
||
export enum BlockDecodingResultStatus { | ||
/** | ||
* A complete block has successfully been read. | ||
*/ | ||
Complete, | ||
/** | ||
* A complete block has been read, but it's checksum was incorrect. | ||
*/ | ||
InvalidChecksum, | ||
/** | ||
* Reading of a block was partial because of an encoding error. | ||
*/ | ||
Partial, | ||
} |
37 changes: 37 additions & 0 deletions
37
retroload-lib/src/decoding/writer/z1013/Z1013GenericWriter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import {BufferAccess} from '../../../common/BufferAccess.js'; | ||
import {type ConverterSettings, type OutputFile, type WriterDefinition} from '../../ConverterManager.js'; | ||
import {StreamingSampleToHalfPeriodConverter} from '../../half_period_provider/StreamingSampleToHalfPeriodConverter.js'; | ||
import {type SampleProvider} from '../../sample_provider/SampleProvider.js'; | ||
import {type FileDecodingResult, FileDecodingResultStatus, Z1013BlockProcessor} from './Z1013BlockProcessor.js'; | ||
import {Z1013HalfPeriodProcessor} from './Z1013HalfPeriodProcessor.js'; | ||
|
||
const definition: WriterDefinition = { | ||
to: 'z1013generic', | ||
convert, | ||
}; | ||
export default definition; | ||
|
||
function * convert(sampleProvider: SampleProvider, settings: ConverterSettings): Generator<OutputFile> { | ||
const streamingHalfPeriodProvider = new StreamingSampleToHalfPeriodConverter(sampleProvider); | ||
const bp = new Z1013BlockProcessor(new Z1013HalfPeriodProcessor(streamingHalfPeriodProvider)); | ||
for (const fdr of bp.files()) { | ||
if (fdr.status !== FileDecodingResultStatus.Success || settings.onError !== 'skipfile') { | ||
yield mapFileDecodingResult(fdr); | ||
} | ||
} | ||
} | ||
|
||
function mapFileDecodingResult(fdr: FileDecodingResult): OutputFile { | ||
const data = BufferAccess.create(fdr.blocks.length * 32); | ||
for (const blockBa of fdr.blocks) { | ||
data.writeBa(blockBa.slice(2, 32)); | ||
} | ||
|
||
return { | ||
proposedName: undefined, | ||
data, | ||
proposedExtension: 'bin', | ||
begin: fdr.begin, | ||
end: fdr.end, | ||
}; | ||
} |
109 changes: 109 additions & 0 deletions
109
retroload-lib/src/decoding/writer/z1013/Z1013HalfPeriodProcessor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import {BufferAccess} from '../../../common/BufferAccess.js'; | ||
import {formatPosition} from '../../../common/Positioning.js'; | ||
import {Logger} from '../../../common/logging/Logger.js'; | ||
import {BlockStartNotFound, DecodingError, EndOfInput} from '../../ConverterExceptions.js'; | ||
import {type FrequencyRange, bitByFrequency, oscillationIs} from '../../Frequency.js'; | ||
import {SyncFinder} from '../../SyncFinder.js'; | ||
import {type HalfPeriodProvider} from '../../half_period_provider/HalfPeriodProvider.js'; | ||
import {BlockDecodingResult, BlockDecodingResultStatus, type Z1013BlockProvider} from './Z1013BlockProvider.js'; | ||
|
||
const fOne: FrequencyRange = [1000, 1500]; | ||
const fZero: FrequencyRange = [2200, 2800]; | ||
const fSync: FrequencyRange = [300, 900]; | ||
const minIntroSyncPeriods = 5; | ||
const rawBlockLength = 2 + 32 + 2; | ||
|
||
export class Z1013HalfPeriodProcessor implements Z1013BlockProvider { | ||
private readonly syncFinder: SyncFinder; | ||
constructor(private readonly halfPeriodProvider: HalfPeriodProvider) { | ||
this.syncFinder = new SyncFinder(this.halfPeriodProvider, fSync, minIntroSyncPeriods); | ||
} | ||
|
||
* blocks(): Generator<BlockDecodingResult> { | ||
let keepGoing = true; | ||
do { | ||
try { | ||
yield this.decodeFile(); | ||
} catch (e) { | ||
if (e instanceof BlockStartNotFound) { | ||
continue; | ||
} else if (e instanceof EndOfInput) { | ||
keepGoing = false; | ||
} else { | ||
throw e; | ||
} | ||
} | ||
} while (keepGoing); | ||
} | ||
|
||
private decodeFile(): BlockDecodingResult { | ||
if (!this.syncFinder.findSync()) { | ||
throw new EndOfInput(); | ||
} | ||
|
||
const blockBa = BufferAccess.create(rawBlockLength); | ||
const fileBegin = this.halfPeriodProvider.getPosition(); | ||
if (!oscillationIs(this.halfPeriodProvider.getNext(), this.halfPeriodProvider.getNext(), fOne)) { | ||
throw new BlockStartNotFound(); | ||
} | ||
for (let i = 0; i < rawBlockLength; i++) { | ||
blockBa.writeUint8(this.readByte()); | ||
} | ||
const fileEnd = this.halfPeriodProvider.getPosition(); | ||
|
||
const readChecksum = blockBa.getUint16Le(34); | ||
const calculatedChecksum = calculateChecksum(blockBa.slice(0, blockBa.length() - 2)); | ||
const checksumCorrect = calculatedChecksum === readChecksum; | ||
if (!checksumCorrect) { | ||
Logger.error(`${formatPosition(fileEnd)} Warning: Invalid checksum! Read checksum: ${readChecksum}, Calculated checksum: ${calculatedChecksum}.`); | ||
} | ||
|
||
return new BlockDecodingResult( | ||
blockBa, | ||
checksumCorrect ? BlockDecodingResultStatus.Complete : BlockDecodingResultStatus.InvalidChecksum, | ||
fileBegin, | ||
fileEnd, | ||
); | ||
} | ||
|
||
private readByte(): number { | ||
let byte = 0; | ||
for (let i = 0; i < 8; i++) { | ||
const bit = this.readBit(); | ||
if (bit === undefined) { | ||
throw new DecodingError(`${formatPosition(this.halfPeriodProvider.getPosition())} Unable to detect bit.`); | ||
} | ||
byte |= ((bit ? 1 : 0) << i); | ||
} | ||
|
||
return byte; | ||
} | ||
|
||
private readBit(): boolean | undefined { | ||
const halfPeriod = this.halfPeriodProvider.getNext(); | ||
const bitValue = bitByFrequency(halfPeriod, fZero, fOne); | ||
if (bitValue === undefined) { | ||
return undefined; | ||
} | ||
if (bitValue) { | ||
// one bit consists of only one half period | ||
return true; | ||
} | ||
// zero - read next half period | ||
if (bitByFrequency(this.halfPeriodProvider.getNext(), fZero, fOne) !== false) { | ||
return undefined; // next half period doesn't match | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
|
||
function calculateChecksum(ba: BufferAccess) { | ||
let checkSum = 0; | ||
for (let i = 0; i < ba.length(); i += 2) { | ||
const word = ba.getUint16Le(i); | ||
checkSum = (checkSum + word) & 0xffff; | ||
} | ||
|
||
return checkSum; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters