Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Z1013 decoder #41

Merged
merged 4 commits into from
Nov 13, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor KC decoder
stefanschramm committed Nov 13, 2023
commit a17b48c66531e8eafa08a60d1221edbf7c5f73d1
24 changes: 13 additions & 11 deletions retroload-lib/src/decoding/writer/kc/KcBlockProcessor.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {BufferAccess} from '../../../common/BufferAccess.js';
import {DecodingError} from '../../ConverterExceptions.js';
import {FileDecodingResultStatus, KcBlockProcessor} from './KcBlockProcessor.js';
import {BlockDecodingResult, BlockDecodingResultStatus, type KcBlockProvider} from './KcBlockProvider.js';
import {BlockDecodingResult, BlockDecodingResultStatus} from '../BlockDecodingResult.js';
import {FileDecodingResultStatus} from '../FileDecodingResult.js';
import {KcBlockProcessor} from './KcBlockProcessor.js';
import {type KcBlockProvider} from './KcBlockProvider.js';

describe('KcBlockProcessor', () => {
test('Generates a single file from a single file dump', () => {
@@ -15,9 +17,9 @@ describe('KcBlockProcessor', () => {
expect(files.length).toBe(1);
expect(files[0].blocks.length).toBe(3);
expect(files[0].status).toBe(FileDecodingResultStatus.Success);
expect(files[0].blocks[0].getUint8(0)).toBe(1);
expect(files[0].blocks[1].getUint8(0)).toBe(2);
expect(files[0].blocks[2].getUint8(0)).toBe(0xff);
expect(files[0].blocks[0].data.getUint8(0)).toBe(1);
expect(files[0].blocks[1].data.getUint8(0)).toBe(2);
expect(files[0].blocks[2].data.getUint8(0)).toBe(0xff);
});

test('Generates two files from a multiple file dump', () => {
@@ -50,9 +52,9 @@ describe('KcBlockProcessor', () => {
expect(files.length).toBe(1);
expect(files[0].blocks.length).toBe(3);
expect(files[0].status).toBe(FileDecodingResultStatus.Error);
expect(files[0].blocks[0].getUint8(0)).toBe(1);
expect(files[0].blocks[1].getUint8(0)).toBe(2);
expect(files[0].blocks[2].getUint8(0)).toBe(0xff);
expect(files[0].blocks[0].data.getUint8(0)).toBe(1);
expect(files[0].blocks[1].data.getUint8(0)).toBe(2);
expect(files[0].blocks[2].data.getUint8(0)).toBe(0xff);
});

test('Marks a file as broken if one block had an invalid checksum', () => {
@@ -67,9 +69,9 @@ describe('KcBlockProcessor', () => {
expect(files.length).toBe(1);
expect(files[0].blocks.length).toBe(3);
expect(files[0].status).toBe(FileDecodingResultStatus.Error);
expect(files[0].blocks[0].getUint8(0)).toBe(1);
expect(files[0].blocks[1].getUint8(0)).toBe(2);
expect(files[0].blocks[2].getUint8(0)).toBe(0xff);
expect(files[0].blocks[0].data.getUint8(0)).toBe(1);
expect(files[0].blocks[1].data.getUint8(0)).toBe(2);
expect(files[0].blocks[2].data.getUint8(0)).toBe(0xff);
});

test('Stops if one block was only partialy loaded and stopping was requested', () => {
29 changes: 8 additions & 21 deletions retroload-lib/src/decoding/writer/kc/KcBlockProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {type BufferAccess} from '../../../common/BufferAccess.js';
import {hex8} from '../../../common/Utils.js';
import {Logger} from '../../../common/logging/Logger.js';
import {DecodingError} from '../../ConverterExceptions.js';
import {formatPosition, type Position} from '../../../common/Positioning.js';
import {type BlockDecodingResult, BlockDecodingResultStatus, type KcBlockProvider} from './KcBlockProvider.js';
import {type KcBlockProvider} from './KcBlockProvider.js';
import {FileDecodingResult, FileDecodingResultStatus} from '../FileDecodingResult.js';
import {type BlockDecodingResult, BlockDecodingResultStatus} from '../BlockDecodingResult.js';

/**
* Minimal expected gap between files in seconds (from end of previous block to begin of next block)
@@ -41,13 +42,13 @@ export class KcBlockProcessor {
}

const blockNumber = decodingResult.data.getUint8(0);
if (this.blockBelongsToNextFile(blockNumber, decodingResult.blockBegin)) {
if (this.blockBelongsToNextFile(blockNumber, decodingResult.begin)) {
yield this.finishFile();
}
this.errorOccured = this.errorOccured || errorInCurrentBlock;
this.blocks.push(decodingResult);
this.previousBlockNumber = blockNumber;
this.previousBlockEnd = decodingResult.blockEnd;
this.previousBlockEnd = decodingResult.end;
}

yield this.finishFile();
@@ -59,10 +60,10 @@ export class KcBlockProcessor {

private finishFile(): FileDecodingResult {
const result = new FileDecodingResult(
this.blocks.map((bdr) => bdr.data),
this.blocks,
this.errorOccured ? FileDecodingResultStatus.Error : FileDecodingResultStatus.Success,
this.blocks[0].blockBegin,
this.blocks[this.blocks.length - 1].blockEnd,
this.blocks[0].begin,
this.blocks[this.blocks.length - 1].end,
);
this.blocks = [];
this.errorOccured = false;
@@ -93,17 +94,3 @@ export class KcBlockProcessor {
}
}
}

export class FileDecodingResult {
constructor(
readonly blocks: BufferAccess[],
readonly status: FileDecodingResultStatus,
readonly begin: Position,
readonly end: Position,
) {}
}

export enum FileDecodingResultStatus {
Success,
Error,
}
27 changes: 2 additions & 25 deletions retroload-lib/src/decoding/writer/kc/KcBlockProvider.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
import {type BufferAccess} from '../../../common/BufferAccess.js';
import {type Position} from '../../../common/Positioning.js';
import {type BlockDecodingResult} from '../BlockDecodingResult.js';

export type KcBlockProvider = {
blocks(): Generator<BlockDecodingResult>;
};
export {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,
}
Original file line number Diff line number Diff line change
@@ -4,9 +4,10 @@ import {Logger} from '../../../common/logging/Logger.js';
import {calculateChecksum8, hex8} from '../../../common/Utils.js';
import {BlockStartNotFound, DecodingError, EndOfInput} from '../../ConverterExceptions.js';
import {formatPosition} from '../../../common/Positioning.js';
import {BlockDecodingResult, BlockDecodingResultStatus, type KcBlockProvider} from './KcBlockProvider.js';
import {is, type FrequencyRange, isNot} from '../../Frequency.js';
import {SyncFinder} from '../../SyncFinder.js';
import {type KcBlockProvider} from './KcBlockProvider.js';
import {BlockDecodingResult, BlockDecodingResultStatus} from '../BlockDecodingResult.js';

const one: FrequencyRange = [770, 1300];
const delimiter: FrequencyRange = [500, 670];
10 changes: 6 additions & 4 deletions retroload-lib/src/decoding/writer/kc/KcTapWriter.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {BufferAccess} from '../../../common/BufferAccess.js';
import {KcHalfPeriodProcessor} from './KcHalfPeriodProcessor.js';
import {type FileDecodingResult, FileDecodingResultStatus, KcBlockProcessor} from './KcBlockProcessor.js';
import {LowPassFilter} from '../../sample_provider/LowPassFilter.js';
import {Logger} from '../../../common/logging/Logger.js';
import {type SampleProvider} from '../../sample_provider/SampleProvider.js';
import {StreamingSampleToHalfPeriodConverter} from '../../half_period_provider/StreamingSampleToHalfPeriodConverter.js';
import {type WriterDefinition, type ConverterSettings, type OutputFile} from '../../ConverterManager.js';
import {KcBlockProcessor} from './KcBlockProcessor.js';
import {type FileDecodingResult, FileDecodingResultStatus} from '../FileDecodingResult.js';

const definition: WriterDefinition = {
to: 'kctap',
@@ -36,10 +37,11 @@ function bufferAccessListToOutputFile(fdr: FileDecodingResult): OutputFile {
const data = BufferAccess.create(fileHeader.length + 129 * blocks.length);
data.writeAsciiString(fileHeader);
for (const block of fdr.blocks) {
data.writeBa(block);
data.writeBa(block.data);
}
const isBasicProgram = blocks[0].containsDataAt(0, '\x01\xd3\xd3\xd3') || blocks[0].containsDataAt(0, '\x01\xd7\xd7\xd7');
const filename = isBasicProgram ? blocks[0].slice(4, 8).asAsciiString().trim() : blocks[0].slice(1, 8).asAsciiString().trim();
const firstBlockData = blocks[0].data;
const isBasicProgram = firstBlockData.containsDataAt(0, '\x01\xd3\xd3\xd3') || firstBlockData.containsDataAt(0, '\x01\xd7\xd7\xd7');
const filename = isBasicProgram ? firstBlockData.slice(4, 8).asAsciiString().trim() : firstBlockData.slice(1, 8).asAsciiString().trim();
const proposedName = restrictCharacters(filename);

return {proposedName, proposedExtension: 'tap', data, begin: fdr.begin, end: fdr.end};