Come ho recuperato dati in un formato sconosciuto da nastri magnetici

Antefatti

Essendo un amante dell'hardware retro, un giorno ho acquistato da un venditore britannico un ZX Spectrum+. Insieme al computer ho ricevuto anche alcune audiocassette con giochi (nella confezione originale con istruzioni), e programmi registrati su cassette senza etichette particolari. Con mia sorpresa, i dati delle cassette di 40 anni fa si leggevano bene e sono riuscito a caricare quasi tutti i giochi e programmi da esse.

Come ho recuperato dati in un formato sconosciuto da nastri magnetici

Tuttavia, su alcune cassette ho trovato registrazioni che evidentemente non erano fatte dal computer ZX Spectrum. Suonavano completamente in modo diverso e, a differenza delle registrazioni del computer menzionato, non iniziavano con un breve caricamento BASIC, che è solitamente presente in tutte le registrazioni di programmi e giochi.

Per un certo periodo questa cosa mi ha disturbato — ero molto curioso di sapere cosa nascondessero. Se fossi riuscito a leggere il segnale audio come una sequenza di byte, avrei potuto cercare simboli o qualcosa che indicasse l'origine del segnale. Una sorta di retro-archeologia.

Adesso, dopo aver compiuto l'intero percorso e guardando le etichette delle cassette stesse, sorrido, perché

la risposta era davanti ai miei occhi tutto il tempo
Sull'etichetta della cassetta di sinistra c'è il nome del computer TRS-80, e poco sotto il nome del produttore: «Manufactured by Radio Shack in USA»

(Se vuoi mantenere il mistero fino alla fine, non entrare nello spoiler)

Confronto dei segnali audio

Per prima cosa digitalizziamo le registrazioni audio. Possiamo ascoltare come suona:


E come suona normalmente una registrazione del computer ZX Spectrum:


In entrambi i casi all'inizio della registrazione c'è quello che si chiama tonalità pilota — un suono a una frequenza (nella prima registrazione è molto breve <1 sec, ma è distinguibile). La tonalità pilota serve a segnalare al computer di prepararsi a ricevere dati. In genere, ogni computer riconosce solo la sua «propria» tonalità pilota in base alla forma del segnale e alla sua frequenza.

È importante parlare della forma del segnale stesso. Ad esempio, sul ZX Spectrum la sua forma è rettangolare:

Come ho recuperato dati in un formato sconosciuto da nastri magnetici

Quando rileva la tonalità pilota, lo ZX Spectrum visualizza strisce alternati rosso-blu sulla parte bordata dello schermo, mostrando che il segnale è stato riconosciuto. La tonalità pilota termina con un impulso di sincronizzazione, che segnala al computer di iniziare a ricevere dati. È caratterizzato da una durata inferiore (rispetto al tono pilota e ai dati successivi) (vedi figura)

Dopo che l'impulso di sincronizzazione è stato ricevuto, il computer registra ogni salita/discesa del segnale, misurando la sua durata. Se la durata è inferiore a una certa soglia, viene memorizzato un bit 1, altrimenti 0. I bit vengono raccolti in byte e il processo viene ripetuto fino a quando non vengono ricevuti N byte. Il numero N è generalmente preso dall'intestazione del file da caricare. La sequenza di caricamento è la seguente:

  1. tonalità pilota
  2. intestazione (di lunghezza fissa), contiene la dimensione dei dati da caricare (N), nome e tipo di file
  3. tonalità pilota
  4. i dati stessi

Per assicurarsi che i dati siano stati caricati correttamente, ZX Spectrum legge come ultimo byte il cosiddetto byte di parità che viene calcolato al momento del salvataggio del file mediante l'operazione XOR su tutti i byte dei dati memorizzati. Durante la lettura del file, il computer calcola il byte di parità dai dati ricevuti e, se il risultato è diverso da quello memorizzato, mostra un messaggio di errore "R Tape loading error". In effetti, il computer può emettere questo messaggio anche prima, se durante la lettura non riesce a riconoscere l'impulso (perso o la sua durata non corrisponde a determinati limiti)

Quindi, vediamo ora come appare il segnale sconosciuto:

Come ho recuperato dati in un formato sconosciuto da nastri magnetici

Questo è il tono pilota. La forma del segnale è notevolmente diversa, ma è evidente che il segnale è composto da impulsi brevi ripetuti a una certa frequenza. Con una frequenza di campionamento di 44100 Hz, la distanza tra i "picchi" è di circa 48 campioni (corrispondente a una frequenza ~918 Hz) Ricordiamo questo numero.

Diamo ora un'occhiata a un frammento con i dati:

Come ho recuperato dati in un formato sconosciuto da nastri magnetici

Se misuriamo la distanza tra gli impulsi singoli, si scoprirà che tra gli impulsi "lunghi" la distanza è ancora di ~48 campioni, mentre tra i brevi è di ~24. Andando un po' avanti, dirò che alla fine si è scoperto che gli impulsi "di riferimento" con frequenza di 918 Hz seguono continuamente, dall'inizio alla fine del file. Si può supporre che durante la trasmissione dei dati, se tra gli impulsi di riferimento si incontra un impulso aggiuntivo, lo consideriamo un bit 1, altrimenti 0.

Che dire dell'impulso di sincronizzazione? Diamo un'occhiata all'inizio dei dati:

Come ho recuperato dati in un formato sconosciuto da nastri magnetici

Il tono pilota termina e subito iniziano i dati. Poco dopo, analizzando diversi registrazioni audio, si è riusciti a scoprire che il primo byte dei dati è sempre lo stesso (10100101b, A5h). È possibile che il computer inizi a leggere i dati dopo averli ricevuti.

Si può anche prestare attenzione allo spostamento del primo impulso di riferimento subito dopo l'ultima '1' nel byte di sincronizzazione. È stato scoperto successivamente nel processo di sviluppo del programma per il riconoscimento dei dati, quando i dati all'inizio del file non potevano essere letti stabilmente.

Ora proviamo a descrivere l'algoritmo che elaborerà il file audio e caricherà i dati.

Caricamento dati

Iniziamo considerando alcune assunzioni, per non complicare l'algoritmo:

  1. Considereremo solo file nel formato WAV;
  2. Il file audio deve iniziare con un tono pilota e non deve contenere silenzio all'inizio;
  3. Il file sorgente deve avere una frequenza di campionamento di 44100 Hz. In questo caso la distanza tra gli impulsi di riferimento di 48 campioni è già definita e non è necessario calcolarla programmaticamente;
  4. Il formato dei campioni può essere qualsiasi (8/16 bit/con punto mobile) — poiché durante la lettura possiamo convertirlo nel formato necessario;
  5. Presumiamo che il file sorgente sia normalizzato in ampiezza, il che dovrebbe stabilizzare il risultato;

L'algoritmo di lettura sarà il seguente:

  1. Leggiamo il file in memoria, convertendo contemporaneamente il formato dei campioni in 8 bit;
  2. Determiniamo la posizione del primo impulso nei dati audio. A tal fine, è necessario calcolare il numero del campione con l'ampiezza massima. Per semplificare, lo calcoleremo una sola volta manualmente. Salviamo nella variabile prev_pos;
  3. Aggiungiamo alla posizione dell'ultimo impulso 48 (pos := prev_pos + 48)
  4. Poiché l'aumento della posizione di 48 non garantisce che raggiungeremo la posizione dell'impulso di riferimento successivo (difetti del nastro, funzionamento instabile del meccanismo di trasporto del nastro, ecc.), è necessario correggere la posizione dell'impulso pos. Per fare ciò, prenderemo un piccolo intervallo di dati (pos-8;pos+8) e troveremo il massimo del valore dell'ampiezza. Salveremo la posizione corrispondente al massimo in pos. Qui 8 = 48/6 — una costante ottenuta sperimentalmente, che garantisce che identificheremo il massimo corretto e non interferiremo con altri impulsi che potrebbero essere vicini. In casi molto gravi, quando la distanza tra gli impulsi è molto inferiore o superiore a 48, si potrebbe implementare una ricerca forzata dell'impulso, ma nel contesto di questo articolo non descriverò tale algoritmo;
  5. Nella fase precedente è anche necessario verificare che l'impulso di riferimento sia stato effettivamente trovato. Cioè, se si cerca semplicemente il massimo, questo non garantisce che l'impulso sia presente in questo intervallo. Nella mia ultima implementazione del programma di lettura, controllo la differenza tra il valore massimo e minino dell'ampiezza nell'intervallo e, se supera una certa soglia, considero che l'impulso è presente. Il problema è anche cosa fare se l'impulso di riferimento non è trovato. Qui ci sono 2 possibilità: o i dati sono terminati e segue un silenzio, oppure questo deve essere considerato come un errore di lettura. Tuttavia, tralasciamo questo per semplificare l'algoritmo;
  6. Nella successiva fase è necessario determinare la presenza di un impulso di dati (bit 0 o 1), per fare ciò prenderemo il centro dell'intervallo (prev_pos;pos) middle_pos pari a middle_pos := (prev_pos+pos)/2 e in una certa vicinanza di middle_pos nell'intervallo (middle_pos-8;middle_pos+8) calcoleremo il massimo e il minimo dell'ampiezza. Se la differenza tra di essi è maggiore di 10, registriamo nel risultato il bit 1 altrimenti 0. 10 è una costante ottenuta empiricamente;
  7. Salviamo la posizione attuale in prev_pos (prev_pos := pos)
  8. Ripetiamo iniziando dal passo 3, finché non leggiamo l'intero file;
  9. L'array di bit ottenuto deve essere salvato come un insieme di byte. Poiché non abbiamo considerato il byte di sincronizzazione durante la lettura, il numero di bit potrebbe non essere un multiplo di 8, e inoltre non si conosce l'offset necessario in bit. Nella prima implementazione dell'algoritmo non sapevo dell'esistenza del byte di sincronizzazione e quindi salvavo semplicemente 8 file con diversi offset di bit. Uno di essi conteneva dati corretti. Nell'algoritmo finale rimuovo semplicemente tutti i bit fino a A5h, il che consente di ottenere immediatamente un file corretto in output.

Algoritmo in Ruby, per chi è interessato.
Ho scelto Ruby come linguaggio di programmazione, poiché trascorro gran parte del mio tempo a programmare in esso. L'opzione non è ad alta efficienza, tuttavia non è necessario che la velocità di lettura sia massima.

# Используем gem 'wavefile'
require 'wavefile'

reader = WaveFile::Reader.new('input.wav')
samples = []
format = WaveFile::Format.new(:mono, :pcm_8, 44100)

# Читаем WAV файл, конвертируем в формат Mono, 8 bit 
# Массив samples будет состоять из байт со значениями 0-255
reader.each_buffer(10000) do |buffer|
  samples += buffer.convert(format).samples
end

# Позиция первого импульса (вместо 0)
prev_pos = 0
# Расстояние между импульсами
distance = 48
# Значение расстояния для окрестности поиска локального максимума
delta = (distance / 6).floor
# Биты будем сохранять в виде строки из "0" и "1"
bits = ""

loop do
  # Рассчитываем позицию следующего импульса
  pos = prev_pos + distance
  
  # Выходим из цикла если данные закончились 
  break if pos + delta >= samples.size

  # Корректируем позицию pos обнаружением максимума на отрезке [pos - delta;pos + delta]
  (pos - delta..pos + delta).each { |p| pos = p if samples[p] > samples[pos] }

  # Находим середину отрезка [prev_pos;pos]
  middle_pos = ((prev_pos + pos) / 2).floor

  # Берем окрестность в середине 
  sample = samples[middle_pos - delta..middle_pos + delta]

  # Определяем бит как "1" если разница между максимальным и минимальным значением на отрезке превышает 10
  bit = sample.max - sample.min > 10
  bits += bit ? "1" : "0"
end

# Определяем синхро-байт и заменяем все предшествующие биты на 256 бит нулей (согласно спецификации формата) 
bits.gsub! /^[01]*?10100101/, ("0" * 256) + "10100101"

# Сохраняем выходной файл, упаковывая биты в байты
File.write "output.cas", [bits].pack("B*")

Risultato

Dopo aver provato diverse varianti dell'algoritmo e delle costanti, ho avuto la fortuna di ottenere qualcosa di estremamente interessante:

Come ho recuperato dati in un formato sconosciuto da nastri magnetici

Quindi, a giudicare dalle stringhe, abbiamo un programma per la generazione di grafici. Tuttavia, nel testo del programma mancano le parole chiave. Tutte le parole chiave sono codificate in forma di byte (valore di ciascuna > 80h). Ora bisogna scoprire quale computer degli anni '80 potesse salvare programmi in questo formato.

In realtà, questo è molto simile a un programma scritto in linguaggio BASIC. Circa nello stesso formato, il computer ZX Spectrum memorizza e salva i programmi su nastro. Per precauzione, ho verificato le parole chiave in conformità con la tabella. Tuttavia, il risultato si è rivelato chiaramente negativo.

Ho anche controllato le parole chiave BASIC di computer popolari dell'epoca come Atari, Commodore 64 e alcuni altri per cui sono riuscito a trovare documentazione, ma senza successo: le mie conoscenze sulle varietà di computer retro non erano così ampie.

Allora ho deciso di seguire il l'elenco, e a questo punto il mio sguardo è caduto sul nome del produttore Radio Shack e sul computer TRS-80. Proprio questi nomi erano scritti sulle etichette delle cassette che avevo sulla mia scrivania! Non conoscevo prima questi nomi e non ero familiare con il computer TRS-80, quindi pensavo che Radio Shack fosse un produttore di cassette audio, come BASF, Sony o TDK, e che il TRS-80 fosse la durata di riproduzione. Perché no?

Computer Tandy/Radio Shack TRS-80

È molto probabile che la registrazione audio considerata, che ho citato come esempio all'inizio dell'articolo, sia stata realizzata su un tale computer:

Come ho recuperato dati in un formato sconosciuto da nastri magnetici

Si è scoperto che questo computer e le sue varianti (Model I/Model III/Model IV, ecc.) erano molto popolari nel loro periodo (certo, non in Russia). È degno di nota che il processore utilizzato in essi fosse anch'esso uno Z80. Su questo computer si possono trovare molte informazioni. Negli anni '80, le informazioni sul computer si diffondevano attraverso riviste. Attualmente esistono diversi emulatori del computer per diverse piattaforme.

Ho caricato l'emulatore trs80gp e ho potuto finalmente vedere come funzionava questo computer. Certo, il computer non supportava l'uscita a colori, la risoluzione dello schermo era di sole 128x48 pixel, ma esistevano numerose estensioni e modifiche che potevano aumentare la risoluzione dello schermo. Esistevano anche molte varianti di sistemi operativi per questo computer e diverse realizzazioni del linguaggio BASIC (che, a differenza dello ZX Spectrum, in alcuni modelli non era nemmeno 'flashato' nella ROM e qualsiasi variante poteva essere caricata da un floppy disk, così come il sistema operativo stesso)

Ho anche trovato utilità un programma per convertire registrazioni audio nel formato CAS, supportato dagli emulatori, tuttavia non sono riuscito a leggere le registrazioni dalle mie cassette per qualche motivo.

Dopo aver compreso il formato del file CAS (che si è rivelato essere semplicemente una copia bit per bit dei dati della cassetta che avevo già, ad eccezione dell'intestazione con la presenza di un byte di sincronizzazione), ho apportato alcune modifiche al mio programma e sono riuscito a ottenere un file CAS funzionante che ha funzionato nell'emulatore (TRS-80 Model III):

Come ho recuperato dati in un formato sconosciuto da nastri magnetici

L'ultima versione dell'utilità per la conversione con rilevamento automatico del primo impulso e della distanza tra gli impulsi di riferimento l'ho presentata sotto forma di pacchetto GEM, il codice sorgente è disponibile su Github.

Conclusione

Il percorso che ho seguito si è rivelato un affascinante viaggio nel passato e sono felice di aver finalmente trovato la soluzione. Inoltre, ho:

  • Compreso il formato di salvataggio dei dati nello ZX Spectrum e studiato le sottoprogrammi incorporati nella ROM per il salvataggio/la lettura dei dati dalle cassette audio.
  • Familiarizzato con il computer TRS-80 e le sue varianti, studiato il sistema operativo, dato un'occhiata a esempi di programmi e ho persino avuto occasione di occuparmi del debug nei codici macchina (tuttavia, tutte le mnemoniche Z80 mi sono ben note)
  • Ho sviluppato un'utilità completa per la conversione di registrazioni audio nel formato CAS, in grado di leggere dati non riconosciuti dall'utilità "ufficiale".

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster