soundfile::get_tape_block() sets the preserved block's scale to 1, so -k writes one
sample per T-state: 3,500,000 samples/s, 437,500 bytes/s, whatever the source rate. Against a
44.1 kHz recording that is 79× the detail there is to store.
curl -sSLO https://worldofspectrum.net/pub/sinclair/games/a/Arkanoid.tzx.zip
unzip -j Arkanoid.tzx.zip '*.tzx'
tape2wav Arkanoid.tzx ark.wav # 44.1 kHz, the default
audio2tape -k ark.wav ark-k.tzx
ls -l Arkanoid.tzx ark-k.tzx # 47,528 bytes in, 844,448 out
| Tape | -k output |
preserved | that is | patched | preserved becomes |
|---|---|---|---|---|---|
| Arkanoid | 844,448 | 796,360 | 1.82 s | 58,171 | 10,083, 79× |
Arkanoid is a Speedlock 2 tape, romloader does not claim block #25 in the source tape, that 1.8 seconds is 794,861 of the 844,448 bytes written.
The block ID 15 length field is three bytes (TZX 1.20 spec), so a block cannot exceed 16,777,215. Against
437,500 bytes per second:
16777215 bytes × 8 ÷ 3500000 = 38.3 seconds
Any preserved section longer than that is written with a truncated length, and the file is
unreadable from that block onward. Attached a WAV of 45 seconds of square wave, 8 kHz, 8-bit
mono:
audio2tape -k audio2tape-tone-8k-45s.wav tone.tzx # 19,687,519 bytes
tzxlist tone.tzx # unknown block type 0x00
libspectrum error: libspectrum_tzx_create: unknown block type 0x00
importer/soundfile.cc, soundfile::get_tape_block() builds the preserved section as an
RLE pulse block and sets its scale to 1:
libspectrum_tape_block_set_scale( block, 1 );
Two smaller things in the same function, found while fixing the above:
!=tstates != start_tstates), so a value it steps over runs the iterator to end();i != pulses.end() test.Scale to the source sample rate, convert the stored pulse lengths into those units carrying
the remainder so the section does not drift, and guard the walk:
--- a/importer/soundfile.cc
+++ b/importer/soundfile.cc
@@ get_tape_block
- libspectrum_tape_block_set_scale( block, 1 );
+ unsigned int tstates_per_sample = 1;
+ if( sample_rate > 0 && source_machine_hz > sample_rate )
+ tstates_per_sample = (unsigned int)( source_machine_hz / sample_rate );
+
+ libspectrum_tape_block_set_scale( block, tstates_per_sample );
// Find start of relevant section of pulse_list
double tstates = 0;
pulse_list::const_iterator i;
- for( i = pulses.begin(); i != pulses.end() && tstates != start_tstates;
+ for( i = pulses.begin(); i != pulses.end() && tstates < start_tstates;
i++ ) {
tstates += *i;
}
- while( tstates < end_tstates ) {
+ double balance = 0;
+ while( i != pulses.end() && tstates < end_tstates ) {
+ balance += *i;
+ unsigned int pulse = (unsigned int)( balance / tstates_per_sample );
+ if( !pulse ) pulse = 1; /* a stored zero is this encoding's escape marker */
+ balance -= (double)pulse * tstates_per_sample;
+
- if( *i <= 0xff ) {
- data[ data_used++ ] = *i;
+ if( pulse <= 0xff ) {
+ data[ data_used++ ] = pulse;
} else {
data[ data_used++ ] = 0;
- data[ data_used++ ] = ( *i & 0x000000ff ) ;
+ data[ data_used++ ] = ( pulse & 0x000000ff ) ;
...
}
tstates += *i++;
}
| Input | 1.4.7 | patched |
|---|---|---|
| Arkanoid | 844,448 bytes | 58,171 bytes |
| Manic Miner | 34,674 bytes | 33,354 bytes |
| 45 s tone | 19,687,519 bytes, unreadable | 45,071 bytes, reads back |
The preserved audio survives the round trip. Rendering the patched 45 s tone back through
tape2wav -r 8000 reproduces the input signal: 90,001 pulses against the original's 90,000,
99.54% of them identical to the sample and every one within ±1 sample of it — the
quantisation is the whole of the difference.
Thanks, fixed in [fec3ae].
Related
Commit: [fec3ae]