Eellugu
Olles retro riistvara huviline, ostsin kord ühelt müüjalt Suurbritanniast ZX Spectrum+. Koos arvutiga sain mitu audikassetti mängudega (originaalpakendis koos juhistega) ning ka programme, mis olid salvestatud kassettidele ilma eriliste tähisteta. Üllataval kombel loeti 40 aastat vanad andmed hästi ja õnnestus mul laadida peaaegu kõik mängud ja programmid neist.

Kuid mõnel kassettidel avastasin ma salvestisi, mida selgelt ei olnud teinud ZX Spectrum. Need kõlasid täiesti teisiti ja, erinevalt eelmainitud arvutist, ei alustatud need lühikese BASIC laadijaga, mis tavaliselt on kohal kõikides programmides ja mängudes.
Mõnda aega ei andnud see mulle rahu — väga tahtsin teada, mis neis peidus on. Kui oleks saanud lugeda audiosignaali kui byte'ide jada, oleks saanud otsida seal sümboleid või midagi, mis viitaks signaali päritolule. Üks sort retro-arheoloogiat.
Nüüd, kui olen kogu tee läbinud ja vaatan kassettide etikette, naeran, sest
vastus oli kogu aeg silme ees
Vasakpoolse kasi etiketil on arvuti TRS-80 nimi ja veidi allpool tootja nimi: «Manufactured by Radio Shack in USA»
(Kui soovite hoida intriigi lõpuni, siis ärge avage spoilerit)
Audio signaalide võrdlemine
Esiteks digitaliseerime audisalvestised. Saame kuulata, kuidas see kõlab:
Ja kuidas tavaliselt kõlab salvestis ZX Spectrumilt:
Mõlemal juhul alguses salvestamisel on nn piloottoon — ühe sagedusega heli (esimeses salvestises on see väga lühike <1 sek, kuid siiski äratuntav). Piloottoon toimib signaalina arvutile, et see peaks valmistuma andmete vastuvõtmiseks. Reeglina tunneb iga arvuti ära ainult oma piloottooni signaali kuju ja sageduse järgi.
Peab rääkima ka signaali kujust. Näiteks ZX Spectrumi signaali kuju on ristkülikukujuline:

Piloottooni tuvastamisel näitab ZX Spectrum ekraani serval vahelduvaid punaseid ja siniseid triibusid, andes märku, et signaal on tuvastatud. Piloottoon lõpeb sünhroniseerimisimpulsiga, mis annab arvutile signaali, et alustada andmete vastuvõtmist. Sellel on lühem (võrreldes piloottooniga ja järgnevate andmetega) kestus (vt joonist)
Pärast sünkro-impulsi vastuvõtmist fikseerib arvuti iga signaali tõusu/laskumise, mõõtes selle kestust. Kui kestus on madalam kindlast piirist, salvestatakse mällu bit 1, vastasel juhul 0. Bitid kogutakse baitideks ja protsess kordub, kuni saadakse N baiti. N number põhineb tavaliselt laaditava faili pealkirjal. Laadimisjärjestus on järgmine:
- piloottoon
- pealkiri (fikseeritud pikkusega), sisaldab laaditavate andmete suurust (N), faili nime ja tüüpi
- piloottoon
- isegi andmed
Et veenduda, et andmed on õigesti laaditud, loeb ZX Spectrum viimase baitina nii kutsutud pariteedi baiti (parity byte), mis arvutatakse faili salvestamise ajal, tehes XOR-operatsiooni kõigi salvestatud andmete baitidega. Faili lugemisel arvutab arvuti pariteedi baidi saadud andmete põhjal ja kui tulemus erineb salvestatud väärtusest, kuvab see veateate 'R Tape loading error'. Täpsemalt öeldes võib arvuti seda teadet anda ka varem, kui see ei suuda lugemise käigus impulssi ära tunda (puudu või kestus ei vasta kindlatele piiridele)
Nii, vaatame nüüd, milline välja näeb tundmatu signaal:

See on piloottoon. Signaali kuju erineb oluliselt, kuid on näha, et signaal koosneb korduvatest lühikestest impulssidest kindla sagedusega. Discreteerimise sagedusel 44100 Hz on 'tippude' vahemaa umbes 48 näidist (mis vastab sagedusele ~918 Hz). Jätame selle numbri meelde.
Vaatame nüüd andmefragmenti:

Kui mõõta vahemaa eraldi impulside vahel, selgub, et 'pikkade' impulside vahel on vahe endiselt umbes 48 näidist, aga lühikeste vahel umbes 24. Natuke ette rutates, võin öelda, et lõpuks selgus, et 'toetavad' impulsid sagedusel 918 Hz järgnesid pidevalt, faili algusest lõpuni. Võime arvata, et andmete edastamisel, kui toetavate impulside vahel esineb täiendav impulss, loeme selle bitiks 1, vastasel juhul 0.
Kuidas on siiakohase sünkro-impulsiga? Vaatame andmete algust:

Piloottone lõppeb ja kohe algavad andmed. Hiljem, analüüsides mitmeid erinevaid helisalvestusi, õnnestus avastada, et esimene andmebitt on alati sama (10100101b, A5h). Võib-olla hakkab arvuti andmeid lugema pärast selle saamist.
Samuti tasub tähele panna esimese referentsimpulsi nihkumist kohe pärast viimast 1-sümbolit sünkrobaidis. Selle suudeti avastada oluliselt hiljem andmete tuvastamise programmi arendamise käigus, kui faili alguses andmed ei saanud stabiilselt loetud.
Nüüd püüame kirjeldada algoritmi, mis töötleb helifaili ja laadib andmed.
Andmete laadimine
Esmalt vaatame mõningaid eeldusi, et mitte algoritmi keerulisemaks teha:
- Vaatame faile ainult WAV formaadis;
- Helifail peab algama piloottone ja ei tohi sisaldada alguses vaikust.
- Algne fail peab olema salvestatud sagedusega 44100 Hz. Sellisel juhul on 48 näidise vahel olev kaugus juba määratud ja me ei pea seda programmeerimisel arvutama;
- Näidiste formaat võib olla ükskõik milline (8/16 bitti/käigu float) — kuna lugemise ajal saame selle vajaliku vormi konverteerida;
- Eeldame, et algne fail on amplituudinäidust normaliseeritud, mis peaks tulemust stabiliseerima;
Lugemise algoritm on järgmine:
- Loeme faili mäosse, samal ajal konverteerides näidisformaat 8 bitiseks;
- Määrame esimese impulsi positsiooni helidatas. Selleks on vaja arvutada näidise number, millel on maksimaalne amplituud. Lihtsuse huvides arvutame selle ühe korra käsitsi. Salvestame muutuja prev_pos;
- Lisame viimase impulsi positsioonile 48 (pos := prev_pos + 48)
- Kuna positsiooni tõstmine 48 võrra ei taga, et jõuame järgmise tugimpulsi positsiooni (lintide defektid, lintide mehhanismi ebastabiilne töö jne), tuleb korrektsioon teha impulsi positsioonile pos. Selle jaoks võtame väikese andmeosa (pos-8; pos+8) ja leiame sellel maksimaalse amplituudi väärtuse. Positsioon, mis vastab maksimumile, salvestatakse pos-iks. Siin 8 = 48/6 — eksperimentaalselt saadud konstant, mis tagab, et leiame õige maksimumi ja ei häiri teisi impulsi, mis võivad lähedal asuda. Väga halvates olukordades, kus impulsside vahel on kaugus oluliselt väiksem või suurem kui 48, võib rakendada sundotsingut, kuid artikli raames ma seda algoritmis ei kirjuta;
- Eelmisel sammul tuleb ka kontrollida, kas tugimpuls on üldse leitud. See tähendab, et lihtsalt maksimumi otsimine ei garanteeri, et antud andmepaljus impuls olemas on. Oma viimases lugemisprogrammi teostuses kontrollin maksimumi ja miinimumi vahelise erinevuse amplituudist lõigus ja kui see ületab teatud piiri, arvestan impulsi olemasolu. Küsimus on ka, mida teha, kui tugimpuls ei ole leitud. Siin on kaks varianti: kas andmed on lihtsalt lõppenud ja edasi järgneb vaikus, või tuleb seda käsitleda lugemise veana. Kuid lihtsustamiseks jätame selle välja;
- Järgmises etapis tuleb määrata andmimpulsi (bit 0 või 1) olemasolu, selleks võtame lõigu keskosa (prev_pos; pos) middle_pos, kus middle_pos := (prev_pos+pos)/2 ja mingis läheduses middle_pos lõikus (middle_pos-8; middle_pos+8) arvutame maksimumi ja miinimumi amplituudi. Kui nende vahe on suurem kui 10, kirjutame tulemusse bit 1, vastasel juhul 0. 10 on eksperimentaalselt saadud konstant;
- Salvestame hetke positsiooni prev_pos-sse (prev_pos := pos)
- Korrame alates sammust 3, kuni fail on täielikult läbi loetud;
- Saadud bitimassi tuleb salvestada baitide kogumikuna. Kuna me ei arvestanud sünkrobaite lugemisel, võib bitite arv olla mitme 8-st ning vajalik bitti nihke kogus on teadmata. Algse algoritmi rakendusest ei teadnud ma sünkrobaidist ning seetõttu salvestasin lihtsalt 8 faili erineva bitinihkega. Üks neist sisaldas õigeid andmeid. Lõppalgoritmis eemaldan lihtsalt kõik bitid kuni A5h, mis võimaldab kohe saada õige faili väljundis.
Algoritm Ruby's, kellele huvi pakub.
Programmi kirjutamiseks valisin Ruby, kuna programmeerin sellega enamikku ajast. Variant pole kõrge jõudlusega, kuid eesmärk mitte nii väga lugemise kiirus ei ole prioriteet.
# Используем 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*")
Tulemus
Proovides erinevaid algoritmi ja konstande, õnnestus mul leida midagi äärmiselt huvitavat:

Nii et, hinnates sümbolstringe, omame me programmi graafikute koostamiseks. Kuid programmi tekstis puuduvad võtmesõnad. Kõik võtmesõnad on kodeeritud baitidena (igaühe väärtus > 80h). Nüüd on vaja välja selgitada, milline arvuti 80-ndatest võis programme sellises formaadis salvestada.
Tegelikult sarnaneb see väga BASICi keeles kirjutatud programmile. Umbes sellises formaadis salvestab ja hoiab arvuti ZX Spectrum mäles programme lintide peale. Igaks juhuks kontrollisin võtmesõnu vastavust. Kuid tulemus oli ilmselgelt negatiivne.
Samuti kontrollisin populaarse ajaloo BASICi võtmesõnu Atarilt, Commodore 64-st ja veel mõnest muust, mille dokumentatsiooni leidsin, kuid edutult - minu teadmised retroarvutite sortidest ei olnud nii laiad.
Siis otsustasin minna , ja siis hakkas mu pilk peatuma tootja Radio Shack ja arvuti TRS-80 nimetusel. Just need nimed olid kirjutatud mu laual olevate kassettide siltidele! Ma ei teadnud varem neid nimesid ja ei olnud tuttav arvuti TRS-80-ga, seega arvasin, et Radio Shack on audiokassettide tootja, nagu BASF, Sony või TDK, ja TRS-80 - mängimise kestus. Miks mitte?
Computer Tandy/Radio Shack TRS-80.
On väga tõenäoline, et arutletud audio salvestus, mille tõin välja artikli alguses, on tehtud sellise arvutiga:

Selgus, et see arvuti ja selle variandid (Model I/Model III/Model IV jne) olid oma ajal väga populaarsed (loomulikult mitte Venemaal). Huvitav on see, et protsessor, mida neis kasutati, oli samuti Z80. Selle arvuti kohta on Internetis võimalik leida . 80ndatel levis arvuti kohta teave . Praegu on olemas mitu arvuti jaoks erinevatele platvormidele.
Laadisin alla emulaatori ja mul õnnestus esmakordselt näha, kuidas see arvuti töötas. Loomulikult ei toetanud arvuti värvide väljundit, ekraani eraldusvõime oli vaid 128x48 punkti, kuid oli palju laiendusi ja modifikatsioone, mis suudavad ekraani eraldusvõimet suurendada. Samuti oli selle arvuti jaoks olemas palju erinevaid operatsioonisüsteeme ja versioone BASICi keelest (mis, erinevalt ZX Spectrumist, ei olnud mõnel mudelil isegi BIOSi
Samuti leidsin helifailide konverteerimiseks CAS-vormingusse, mida emulaatorid toetavad, kuid minu kassettide salvestiste lugemine nende abil ei õnnestunud mingil põhjusel.
Pärast CAS-faili formaadi mõistmist (mis osutus lihtsalt bitijärjestuseks andmetest lint, mis mul juba oli, välja arvatud pealkiri sünkro-aktiivsete baitide olemasolu tõttu), tegin oma programmis mitu muudatust ja sain välja töötada töötava CAS-faili, mis töötas emulaatoris (TRS-80 Model III):

Viimase versiooni utiliidist, mis automaatselt tuvastab esimese impulsi ja vahemaa tugipulsidena, korraldasin GEM-paketi vormis, allikas on saadaval .
Kokkuvõte
Läbitud teekond osutus põnevaks reisi minevikku, ja mul on hea meel, et lõpuks leidsin selle mõistatuse lahenduse. Pealegi, ma:
- Mõistsin ära ZX Spectrumi andmete salvestamise formaadi ja uurisin BIOSis sisseehitatud salvestamis-/lugemisprotseduure helikassettidest
- Sain tuttavaks TRS-80 arvuti ja selle varianti, uurisin operatsioonisüsteemi, vaatasin näiteprogramme ja isegi sain tegeleda masinkoodi silumisega (lõppude-lõpuks on kõik Z80 mnemoonikud mulle hästi tuttavad)
- Loodud on täieõiguslik utiliit audiofailide konverteerimiseks CAS-formaati, mis suudab lugeda andmeid, mida "ametlik" utiliit ei tunne.
Allikas: habr.com
