Menu

#521 audio2tape -k stores preserved audio 79× oversampled, and past 38 s writes an invalid TZX

v1.9.2
closed-accepted
nobody
None
5
4 days ago
4 days ago
AJ B
No

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.

1. The output is 79× larger than it needs to be

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.

2. Past 38.3 seconds the tzx file is corrupt

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

Cause

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:

  • the search for the start of the section compares accumulated doubles with !=
    (tstates != start_tstates), so a value it steps over runs the iterator to end();
  • the write loop then dereferences that iterator without an i != pulses.end() test.

Proposed fix

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.

1 Attachments

Discussion

  • Fredrick Meunier

    • status: open --> closed-accepted
    • Group: future --> v1.9.2
     
  • Fredrick Meunier

    Thanks, fixed in [fec3ae].

     

    Related

    Commit: [fec3ae]


Log in to post a comment.