Come ho recuperato i dati in un formato sconosciuto dal nastro magnetico

Sfondo

Essendo un amante dell'hardware retrò, una volta ho acquistato uno ZX Spectrum+ da un venditore nel Regno Unito. In dotazione al computer stesso, ho ricevuto diverse cassette audio con giochi (nella confezione originale con istruzioni), nonché programmi registrati su cassette senza contrassegni speciali. Sorprendentemente, i dati delle cassette vecchie di 40 anni erano ben leggibili e ho potuto scaricare quasi tutti i giochi e i programmi da esse.

Come ho recuperato i dati in un formato sconosciuto dal nastro magnetico

Tuttavia, su alcune cassette ho trovato registrazioni che chiaramente non erano state effettuate dal computer ZX Spectrum. Suonavano in modo completamente diverso e, a differenza delle registrazioni del computer citato, non si avviavano con un breve bootloader BASIC, che di solito è presente nelle registrazioni di tutti i programmi e giochi.

Per qualche tempo questo mi ha perseguitato: volevo davvero scoprire cosa si nascondevano in loro. Se potessi leggere il segnale audio come una sequenza di byte, potresti cercare caratteri o qualsiasi cosa che indichi l'origine del segnale. Una sorta di retro-archeologia.

Ora che sono andato fino in fondo e ho guardato le etichette delle cassette stesse, sorrido perché

la risposta era proprio davanti ai miei occhi da sempre
Sull'etichetta della cassetta sinistra c'è il nome del computer TRS-80 e subito sotto il nome del produttore: "Manufacturing by Radio Shack in USA"

(Se vuoi mantenere l'intrigo fino alla fine, non andare sotto lo spoiler)

Confronto di segnali audio

Prima di tutto digitalizziamo le registrazioni audio. Puoi ascoltare come suona:


E come al solito la registrazione dal computer ZX Spectrum suona:


In entrambi i casi, all'inizio della registrazione c'è un cosiddetto tono pilota - un suono della stessa frequenza (nella prima registrazione è molto breve <1 secondo, ma è distinguibile). Il tono pilota segnala al computer di prepararsi a ricevere i dati. Di norma, ogni computer riconosce solo il “proprio” tono pilota dalla forma del segnale e dalla sua frequenza.

È necessario dire qualcosa sulla forma del segnale stesso. Ad esempio, sullo ZX Spectrum la sua forma è rettangolare:

Come ho recuperato i dati in un formato sconosciuto dal nastro magnetico

Quando viene rilevato un tono pilota, lo ZX Spectrum visualizza barre rosse e blu alternate sul bordo dello schermo per indicare che il segnale è stato riconosciuto. Il tono pilota termina impulso sincro, che segnala al computer di iniziare a ricevere i dati. È caratterizzato da una durata più breve (rispetto al tono pilota e ai dati successivi) (vedi figura)

Dopo aver ricevuto l'impulso di sincronizzazione, il computer registra ogni aumento/diminuzione del segnale, misurandone la durata. Se la durata è inferiore ad un certo limite, viene scritto in memoria il bit 1, altrimenti 0. I bit vengono raccolti in byte e il processo viene ripetuto finché non vengono ricevuti N byte. Il numero N viene solitamente preso dall'intestazione del file scaricato. La sequenza di caricamento è la seguente:

  1. tono pilota
  2. intestazione (lunghezza fissa), contiene la dimensione dei dati scaricati (N), il nome e il tipo del file
  3. tono pilota
  4. i dati stessi

Per assicurarsi che i dati vengano caricati correttamente, lo ZX Spectrum legge i cosiddetti byte di parità (byte di parità), che viene calcolato quando si salva un file eseguendo XOR su tutti i byte dei dati scritti. Durante la lettura di un file, il computer calcola il byte di parità dai dati ricevuti e, se il risultato è diverso da quello salvato, visualizza il messaggio di errore “R Errore caricamento nastro”. A rigor di termini, il computer può emettere questo messaggio prima se, durante la lettura, non riesce a riconoscere un impulso (mancato o la sua durata non corrisponde a determinati limiti)

Quindi, vediamo ora come appare un segnale sconosciuto:

Come ho recuperato i dati in un formato sconosciuto dal nastro magnetico

Questo è il tono pilota. La forma del segnale è significativamente diversa, ma è chiaro che il segnale consiste nel ripetere brevi impulsi di una certa frequenza. Ad una frequenza di campionamento di 44100 Hz, la distanza tra i “picchi” è di circa 48 campioni (che corrisponde ad una frequenza di ~918 Hz). Ricordiamo questa cifra.

Diamo ora un'occhiata al frammento di dati:

Come ho recuperato i dati in un formato sconosciuto dal nastro magnetico

Se misuriamo la distanza tra i singoli impulsi, risulta che la distanza tra gli impulsi "lunghi" è ancora di ~48 campioni e tra quelli brevi - ~24. Guardando un po' avanti, dirò che alla fine si è scoperto che gli impulsi “di riferimento” con una frequenza di 918 Hz seguono continuamente, dall'inizio alla fine del file. Si può presumere che durante la trasmissione dei dati, se si incontra un impulso aggiuntivo tra gli impulsi di riferimento, lo consideriamo come bit 1, altrimenti 0.

E l'impulso di sincronizzazione? Diamo un'occhiata all'inizio dei dati:

Come ho recuperato i dati in un formato sconosciuto dal nastro magnetico

Il tono pilota termina e i dati iniziano immediatamente. Poco dopo, dopo aver analizzato diverse registrazioni audio, abbiamo potuto scoprire che il primo byte di dati è sempre lo stesso (10100101b, A5h). Il computer potrebbe iniziare a leggere i dati dopo averli ricevuti.

È inoltre possibile prestare attenzione allo spostamento del primo impulso di riferimento immediatamente dopo l'ultimo 1° nel byte di sincronizzazione. È stato scoperto molto più tardi nel processo di sviluppo di un programma di riconoscimento dei dati, quando i dati all'inizio del file non potevano essere letti in modo stabile.

Ora proviamo a descrivere un algoritmo che elaborerà un file audio e caricherà i dati.

Caricamento dati

Innanzitutto, diamo un'occhiata ad alcune ipotesi per mantenere semplice l'algoritmo:

  1. Considereremo solo file in 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à determinata e non è necessario calcolarla a livello di programmazione;
  4. Il formato del campione può essere qualsiasi (8/16 bit/virgola mobile) - poiché durante la lettura possiamo convertirlo in quello desiderato;
  5. Assumiamo che il file sorgente sia normalizzato in base all'ampiezza, il che dovrebbe stabilizzare il risultato;

L’algoritmo di lettura sarà il seguente:

  1. Leggiamo il file in memoria, convertendo contemporaneamente il formato campione a 8 bit;
  2. Determinare la posizione del primo impulso nei dati audio. Per fare ciò, è necessario calcolare il numero del campione con l'ampiezza massima. Per semplicità, lo calcoleremo una volta manualmente. Salviamolo nella variabile prev_pos;
  3. Aggiungi 48 alla posizione dell'ultimo impulso (pos := prev_pos + 48)
  4. Poiché l'aumento della posizione di 48 non garantisce il raggiungimento della posizione del successivo impulso di riferimento (difetti del nastro, funzionamento instabile del meccanismo di azionamento del nastro, ecc.), è necessario regolare la posizione dell'impulso pos. Per fare ciò, prendi un piccolo pezzo di dati (pos-8;pos+8) e trova su di esso il valore di ampiezza massima. La posizione corrispondente al massimo verrà memorizzata in pos. Qui 8 = 48/6 è una costante ottenuta sperimentalmente, che garantisce che determineremo il massimo corretto e non influenzerà altri impulsi che potrebbero trovarsi nelle vicinanze. In casi molto gravi, quando la distanza tra gli impulsi è molto inferiore o superiore a 48, è possibile implementare una ricerca forzata di un impulso, ma nell'ambito dell'articolo non lo descriverò nell'algoritmo;
  5. Nella fase precedente sarebbe anche necessario verificare che l'impulso di riferimento sia stato trovato. Cioè, se cerchi semplicemente il massimo, questo non garantisce che l'impulso sia presente in questo segmento. Nella mia ultima implementazione del programma di lettura, controllo la differenza tra i valori di ampiezza massimo e minimo su un segmento e, se supera un certo limite, conto la presenza di un impulso. La domanda è anche cosa fare se l'impulso di riferimento non viene trovato. Ci sono 2 opzioni: o i dati sono finiti e segue il silenzio, oppure questo è da considerarsi un errore di lettura. Tuttavia, lo ometteremo per semplificare l'algoritmo;
  6. Nel passaggio successivo dobbiamo determinare la presenza di un impulso di dati (bit 0 o 1), per questo prendiamo il centro del segmento (prev_pos;pos) middle_pos uguale a middle_pos := (prev_pos+pos)/2 e in qualche intorno di middle_pos sul segmento (middle_pos-8;middle_pos +8) calcoliamo l'ampiezza massima e minima. Se la differenza tra loro è maggiore di 10, scriviamo nel risultato il bit 1, altrimenti 0. 10 è una costante ottenuta sperimentalmente;
  7. Salva la posizione corrente in prev_pos (prev_pos := pos)
  8. Ripetere partendo dal passaggio 3 fino a leggere l'intero file;
  9. La matrice di bit risultante deve essere salvata come set di byte. Poiché durante la lettura non abbiamo tenuto conto del byte di sincronizzazione, il numero di bit potrebbe non essere un multiplo di 8 e anche l'offset di bit richiesto è sconosciuto. Nella prima implementazione dell'algoritmo non sapevo dell'esistenza del byte di sincronizzazione e quindi ho semplicemente salvato 8 file con numeri diversi di bit di offset. Uno di loro conteneva dati corretti. Nell'algoritmo finale rimuovo semplicemente tutti i bit fino a A5h, il che mi consente di ottenere immediatamente il file di output corretto

Algoritmo in Ruby, per chi è interessato
Ho scelto Ruby come linguaggio per scrivere il programma, perché... Lo programmo per la maggior parte del tempo. L'opzione non è performante, ma il compito di aumentare la velocità di lettura il più velocemente possibile non ne vale la pena.

# Используем 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 i dati in un formato sconosciuto dal nastro magnetico

Quindi, a giudicare dalle stringhe di caratteri, abbiamo un programma per tracciare grafici. Tuttavia, non ci sono parole chiave nel testo del programma. Tutte le parole chiave sono codificate come byte (ciascun valore > 80h). Ora dobbiamo scoprire quale computer degli anni '80 potrebbe salvare programmi in questo formato.

In effetti è molto simile ad un programma BASIC. Il computer ZX Spectrum memorizza i programmi approssimativamente nello stesso formato in memoria e li salva su nastro. Per ogni evenienza, ho controllato le parole chiave un tavolo. Il risultato però è stato ovviamente negativo.

Ho anche controllato le parole chiave BASIC del popolare Atari, Commodore 64 e molti altri computer dell'epoca, per i quali sono riuscito a trovare la documentazione, ma senza successo: la mia conoscenza dei tipi di computer retrò si è rivelata non così ampia.

Poi ho deciso di andare la lista, e poi il mio sguardo è caduto sul nome del produttore Radio Shack e sul computer TRS-80. Questi sono i nomi scritti sulle etichette delle cassette che giacevano sul mio tavolo! Non conoscevo questi nomi prima e non avevo familiarità con il computer TRS-80, quindi mi sembrava che Radio Shack fosse un produttore di audiocassette come BASF, Sony o TDK, e TRS-80 fosse il tempo di riproduzione. Perché no?

Computer Tandy/Radio Shack TRS-80

È molto probabile che la registrazione audio in questione, che ho riportato come esempio all'inizio dell'articolo, sia stata effettuata su un computer come questo:

Come ho recuperato i dati in un formato sconosciuto dal nastro magnetico

Si è scoperto che questo computer e le sue varietà (Modello I/Modello III/Modello IV, ecc.) erano molto popolari un tempo (ovviamente, non in Russia). È interessante notare che anche il processore utilizzato era Z80. Per questo computer puoi trovare su Internet molte informazioni. Negli anni '80 le informazioni informatiche venivano distribuite riviste. Al momento ce ne sono diversi emulatori computer per piattaforme diverse.

Ho scaricato l'emulatore trs80gp e per la prima volta ho potuto vedere come funzionava questo computer. Naturalmente il computer non supportava l'output a colori; la risoluzione dello schermo era di soli 128x48 pixel, ma c'erano molte estensioni e modifiche che potevano aumentare la risoluzione dello schermo. C'erano anche molte opzioni per i sistemi operativi per questo computer e opzioni per l'implementazione del linguaggio BASIC (che, a differenza dello ZX Spectrum, in alcuni modelli non veniva nemmeno "flashato" nella ROM e qualsiasi opzione poteva essere caricata da un floppy disk, proprio come il sistema operativo stesso)

Ho anche trovato utilità per convertire le registrazioni audio nel formato CAS, che è supportato dagli emulatori, ma per qualche motivo non è stato possibile leggere le registrazioni dalle mie cassette utilizzandole.

Dopo aver individuato il formato del file CAS (che si è rivelato essere solo una copia bit per bit dei dati del nastro che avevo già a portata di mano, ad eccezione dell'intestazione con la presenza di un byte di sincronizzazione), ho creato un alcune modifiche al mio programma e sono riuscito a generare un file CAS funzionante che funzionava nell'emulatore (TRS-80 Modello III):

Come ho recuperato i dati in un formato sconosciuto dal nastro magnetico

Ho progettato l'ultima versione dell'utilità di conversione con determinazione automatica del primo impulso e della distanza tra gli impulsi di riferimento come pacchetto GEM, il codice sorgente è disponibile su Github.

conclusione

Il percorso che abbiamo percorso si è rivelato un affascinante viaggio nel passato e sono felice di aver trovato la risposta. Tra l'altro io:

  • Ho capito il formato per il salvataggio dei dati nello ZX Spectrum e ho studiato le routine integrate nella ROM per il salvataggio/lettura dei dati dalle cassette audio
  • Ho conosciuto il computer TRS-80 e le sue varietà, ho studiato il sistema operativo, ho guardato programmi di esempio e ho anche avuto l'opportunità di eseguire il debug nei codici macchina (dopo tutto, tutti i mnemonici dello Z80 mi sono familiari)
  • Ha scritto un'utilità completa per convertire le registrazioni audio in formato CAS, in grado di leggere dati non riconosciuti dall'utilità "ufficiale"

Fonte: habr.com

Aggiungi un commento