Pristoria
Essendu un amante di hardware retro, una volta aghju compru un ZX Spectrum + da un venditore in u Regnu Unitu. Inclusu cù l'urdinatore stessu, aghju ricivutu parechje cassette audio cù ghjochi (in l'imballu originale cù struzzioni), è ancu prugrammi arregistrati nantu à cassette senza marcatura speciale. Sorprendentemente, i dati da cassette di 40 anni sò leghjite bè è aghju pussutu scaricà quasi tutti i ghjochi è i prugrammi da elli.
In ogni casu, in certi cassette aghju trovu registrazioni chjaramente micca fattu da l'urdinatore ZX Spectrum. Sonavanu cumplettamente sfarente è, à u cuntrariu di e gravazioni da l'urdinatore mintuatu, ùn anu micca principiatu cù un cortu bootloader BASIC, chì hè di solitu presente in i registrazioni di tutti i prugrammi è di i ghjochi.
Per qualchì tempu questu m'hà perseguitatu - vulia veramente sapè ciò chì era ammucciatu in elli. Se pudete leghje u signale audio cum'è una sequenza di bytes, pudete cercà caratteri o qualcosa chì indica l'origine di u signale. Una spezia di retro-archeologia.
Avà ch'e aghju andatu in tuttu è fighjulà l'etichette di e cassette stesse, surrisu perchè
a risposta era ghjustu davanti à i mo ochji per tuttu
In l'etichetta di a cassetta sinistra hè u nome di l'urdinatore TRS-80, è ghjustu sottu u nome di u fabricatore: "Fabricatu da Radio Shack in USA"
(Se vulete mantene l'intriga finu à a fine, ùn andate micca sottu u spoiler)
Comparazione di signali audio
Prima di tuttu, digitalizemu e registrazioni audio. Pudete sente ciò chì sona cum'è:
E cum'è di solitu, a registrazione da l'urdinatore ZX Spectrum sona:
In i dui casi, à u principiu di l'arregistramentu ci hè un cusì chjamatu tonu di pilotu - un sonu di a listessa freccia (in a prima registrazione hè assai corta <1 secunna, ma hè distinguibile). U tonu di pilotu signala l'urdinatore per preparà per riceve dati. Comu regula, ogni computer ricunnosce solu u so "propiu" tonu pilotu da a forma di u signale è a so freccia.
Hè necessariu di dì qualcosa nantu à a forma di u signale stessu. Per esempiu, nantu à u ZX Spectrum, a so forma hè rettangulare:
Quandu un tonu pilotu hè rilevatu, u ZX Spectrum mostra alternate barre rosse è blu à u cunfini di u screnu per indicà chì u signale hè statu ricunnisciutu. U tonu di pilotu finisce impulsu di sincronia, chì signala l'urdinatore per cumincià à riceve dati. Hè carattarizatu da una durata più corta (paragunatu à u tonu pilotu è i dati sussegwenti) (vede a figura)
Dopu chì u pulsu di sincronia hè ricevutu, l'urdinatore registra ogni crescita / caduta di u segnu, misurandu a so durata. Se a durata hè menu di un certu limitu, u bit 1 hè scrittu in memoria, altrimente 0. I bits sò cullati in byte è u prucessu hè ripetutu finu à chì N byte sò ricevuti. U numeru N hè generalmente pigliatu da l'intestazione di u schedariu telecaricatu. A sequenza di carica hè a siguenti:
- tonu di pilotu
- header (lunghezza fissa), cuntene a dimensione di i dati scaricati (N), u nome di u schedariu è u tipu
- tonu di pilotu
- i dati stessi
Per assicurà chì i dati sò caricati currettamente, u ZX Spectrum leghje u cusì chjamatu byte di parità (byte di parità), chì hè calculatu quandu salvà un schedariu XORing tutti i bytes di e dati scritti. Quandu leghje un schedariu, l'urdinatore calcula u byte di parità da i dati ricevuti è, se u risultatu hè diversu da quellu salvatu, mostra u missaghju d'errore "R Tape loading error". In modu strettu, l'urdinatore pò emette stu missaghju prima se, durante a lettura, ùn pò micca ricunnosce un impulsu (miccatu o a so durata ùn currisponde à certi limiti)
Allora, vedemu avà ciò chì pare un signalu scunnisciutu:
Questu hè u tonu pilotu. A forma di u signale hè significativamente sfarente, ma hè chjaru chì u signale hè custituitu di ripetiri impulsi brevi di una certa frequenza. À una frequenza di campionamentu di 44100 Hz, a distanza trà i "picchi" hè di circa 48 campioni (chì currisponde à una freccia di ~ 918 Hz). Ricordemu sta figura.
Fighjemu avà u frammentu di dati:
Se misuramu a distanza trà i pulsati individuali, ci hè chì a distanza trà i pulsati "longu" hè sempre ~ 48 campioni, è trà i brevi - ~ 24. Fighjendu un pocu avanti, diceraghju chì à a fine hè stata chì i pulsazioni "di riferimentu" cù una freccia di 918 Hz seguitanu continuamente, da u principiu à a fine di u schedariu. Pò esse presumitu chì quandu trasmettenu dati, se un impulsu supplementu hè scontru trà l'impulsi di riferimentu, u cunsideremu cum'è bit 1, altrimenti 0.
Chì ci hè di u pulse di sincronia? Fighjemu u principiu di i dati:
U tonu pilotu finisce è i dati cumincianu immediatamente. Un pocu dopu, dopu l'analisi di parechje registrazioni audio diffirenti, pudemu scopre chì u primu byte di dati hè sempre u stessu (10100101b, A5h). L'urdinatore pò principià à leghje i dati dopu avè ricevutu.
Pudete ancu attentu à u cambiamentu di u primu impulsu di riferimentu immediatamente dopu à l'ultimu 1u in u byte di sincronia. Hè statu scupertu assai più tardi in u prucessu di sviluppà un prugramma di ricunniscenza di dati, quandu i dati à l'iniziu di u schedariu ùn puderanu micca leghje stabilmente.
Avà pruvemu di discrìviri un algoritmu chì processerà un schedariu audio è carica dati.
Caricà Dati
Prima, fighjemu uni pochi supposizioni per mantene l'algoritmu simplice:
- Avemu da cunsiderà solu i schedari in formatu WAV;
- U schedariu audio deve principià cù un tonu pilotu è ùn deve micca cuntene u silenziu à u principiu
- U schedariu fonte deve avè una freccia di campionamentu di 44100 Hz. In questu casu, a distanza trà i pulsati di riferimentu di 48 campioni hè digià determinata è ùn avemu micca bisognu di calculà in modu programmaticu;
- U formatu di mostra pò esse qualsiasi (8/16 bits / flottante) - postu chì quandu leghje pudemu cunvertisce à quellu desideratu;
- Assumimu chì u schedariu fonte hè nurmalizatu da amplitude, chì deve stabilizzà u risultatu;
L'algoritmu di lettura serà cusì:
- Avemu leghje u schedariu in memoria, à u listessu tempu cunvertisce u furmatu di mostra à 8 bits;
- Determina a pusizione di u primu impulsu in i dati audio. Per fà questu, avete bisognu di calculà u numeru di mostra cù l'amplitude massima. Per simplicità, calculeremu una volta manualmente. Salvemu à a variabile prev_pos;
- Aghjunghjite 48 à a pusizione di l'ultimu impulsu (pos := prev_pos + 48)
- Siccomu l'aumentu di a pusizione da 48 ùn guarantisci micca chì avemu da ghjunghje à a pusizione di u prossimu impulsu di riferimentu (difetti di cinta, funziunamentu inestabile di u mecanismu di l'unità di cinta, etc.), avemu bisognu di aghjustà a pusizione di u pulsu pos. Per fà questu, pigliate un picculu pezzu di dati (pos-8; pos + 8) è truvate u valore di l'amplitude massima nantu à questu. A pusizioni currispundenti à u massimu serà guardatu in pos. Quì 8 = 48/6 hè una constante ottenuta spirimintali, chì guarantisci chì determinà u massimu currettu è ùn affetterà micca altri impulsi chì ponu esse vicinu. In casi assai cattivi, quandu a distanza trà i pulsati hè assai menu o più grande di 48, pudete implementà una ricerca furzata per un pulse, ma in u scopu di l'articulu ùn aghju micca discrittu questu in l'algoritmu;
- À u passu precedente, saria ancu necessariu di verificà chì u pulsu di riferimentu hè statu trovu in tuttu. Vale à dì, sè solu cercà u massimu, questu ùn guarantisci micca chì l'impulsu hè presente in questu segmentu. In a mo ultima implementazione di u prugramma di lettura, aghju verificatu a diffarenza trà i valori di ampiezza massima è minima nantu à un segmentu, è s'ellu supera un certu limitu, cuntu a presenza di un impulsu. A quistione hè ancu ciò chì fà s'ellu ùn hè micca truvatu u pulsu di riferimentu. Ci sò 2 opzioni: o i dati sò finiti è u silenziu seguita, o questu deve esse cunsideratu un errore di lettura. In ogni casu, omettemu questu per simplificà l'algoritmu;
- À u prossimu passu, avemu bisognu di determinà a prisenza di un impulsu di dati (bit 0 o 1), per questu pigliemu a mità di u segmentu (prev_pos;pos) middle_pos uguale à middle_pos := (prev_pos+pos)/2 è in qualchì vicinatu di middle_pos nantu à u segmentu (middle_pos-8; middle_pos +8) calculemu l'amplitude massima è minima. Se a diffarenza trà elli hè più di 10, scrivimu u bit 1 in u risultatu, altrimente 0. 10 hè una constante ottenuta sperimentalmente;
- Salvà a pusizione attuale in prev_pos (prev_pos := pos)
- Repetite da u passu 3 finu à leghje u schedariu sanu;
- L'array di bit resultante deve esse salvatu cum'è un set di byte. Siccomu ùn avemu micca cunsideratu u byte di sincronia durante a lettura, u numeru di bit pò esse micca un multiplu di 8, è l'offset di bit necessariu hè ancu scunnisciutu. In a prima implementazione di l'algoritmu, ùn sapia micca di l'esistenza di u byte di sincronia è per quessa hà salvatu simpliciamente 8 schedarii cù parechji numeri di offset bits. Unu di elli cuntene dati curretti. In l'algoritmu finali, solu sguassate tutti i bits finu à A5h, chì mi permette di ottene immediatamente u schedariu di output currettu.
Algoritmu in Ruby, per quelli interessati
Aghju sceltu Ruby cum'è lingua per scrive u prugramma, perchè ... U prugramma nantu à a maiò parte di u tempu. L'opzione ùn hè micca d'alta prestazione, ma u compitu di fà a velocità di lettura u più veloce pussibule ùn vale a 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*")
risultatu
Dopu avè pruvatu parechje varianti di l'algoritmu è custanti, aghju avutu a furtuna di ottene qualcosa estremamente interessante:
Allora, à ghjudicà da e stringhe di caratteri, avemu un prugramma per tracciate gràfiche. Tuttavia, ùn ci sò micca parole chjave in u testu di u prugramma. Tutte e parolle chjave sò codificate cum'è byte (ogni valore> 80h). Avà avemu bisognu di sapè quale computer da l'anni 80 puderia salvà i prugrammi in stu formatu.
In fatti, hè assai simile à un prugramma BASIC. L'urdinatore ZX Spectrum guarda i prugrammi in circa u listessu formatu in memoria è salva i prugrammi in cinta. In casu, aghju verificatu e parolle chjave contru
Aghju verificatu ancu i chjavi BASIC di u populari Atari, Commodore 64 è parechji altri computer di quellu tempu, per quale aghju pussutu truvà documentazione, ma senza successu - a mo cunniscenza di i tipi di computer retro ùn sò micca cusì largu.
Allora decisu di andà
Computer Tandy/Radio Shack TRS-80
Hè assai prubabile chì l'arregistramentu audio in quistione, chì aghju datu cum'è un esempiu à u principiu di l'articulu, hè stata fatta in un computer cum'è questu:
Hè risultatu chì questu computer è e so varietà (Model I / Model III / Model IV, etc.) eranu assai populari in un tempu (di sicuru, micca in Russia). Hè nutate chì u processatore chì anu utilizatu era ancu Z80. Per questu computer pudete truvà nantu à Internet
Aghju scaricatu l'emulatore
Aghju trovu ancu
Dopu avè scupertu u furmatu di u schedariu CAS (chì hè diventatu solu una copia di e dati da a cinta chì aghju digià in manu, fora di l'intestazione cù a presenza di un byte di sincronia), aghju fattu un pochi cambiamenti à u mo prugramma è hà sappiutu pruduce un schedariu CAS di travagliu chì hà travagliatu in l'emulatore (TRS-80 Model III):
Aghju cuncepitu l'ultima versione di l'utilità di cunversione cù a determinazione automatica di u primu impulsu è a distanza trà i pulsati di riferimentu cum'è un pacchettu GEM, u codice fonte hè dispunibule à
cunchiusioni
U caminu chì avemu viaghjatu hè diventatu un viaghju fascinante in u passatu, è sò cuntentu chì à a fine aghju trovu a risposta. Frà altre cose, aghju:
- Aghju capitu u formatu per salvà dati in u ZX Spectrum è studiatu e rutine ROM integrate per salvà / leghje dati da cassette audio
- Aghju cunnisciutu l'urdinatore TRS-80 è e so variità, hà studiatu u sistema operatore, hà guardatu i prugrammi di mostra è ancu avutu l'uppurtunità di fà debugging in i codici di a macchina (dopu à tuttu, tutti i mnemotichi Z80 sò familiari per mè)
- Scrive una utilità cumpleta per cunvertisce registrazioni audio in u formatu CAS, chì pò leghje dati chì ùn sò micca ricunnisciuti da l'utilità "ufficiale".
Source: www.habr.com