Kuidas ma magnetlindilt tundmatus vormingus andmeid taastasin

eelajalugu

Kuna olen retro riistvara armastaja, ostsin kunagi Ühendkuningriigi müüjalt ZX Spectrum+. Arvuti endaga kaasas sain mitu helikassetti mängudega (originaalpakendis koos juhendiga), samuti kassettidele salvestatud programme ilma erimärgistuseta. Üllataval kombel olid andmed 40-aastastelt kassettidelt hästi loetavad ja sealt sain peaaegu kõik mängud ja programmid alla laadida.

Kuidas ma magnetlindilt tundmatus vormingus andmeid taastasin

Mõnelt kassetilt aga leidsin salvestisi, mis ilmselgelt polnud ZX Spectrum arvutiga tehtud. Need kõlasid täiesti erinevalt ja erinevalt mainitud arvutist tehtud salvestustest ei alustanud nad lühikese BASIC bootloaderiga, mis tavaliselt on kõigi programmide ja mängude salvestustel.

Mõnda aega jäi see mind kummitama – tahtsin väga teada saada, mis neis peidus on. Kui saaksite lugeda helisignaali baitide jadana, võiksite otsida märke või midagi, mis näitab signaali päritolu. Omamoodi retro-arheoloogia.

Nüüd, kui olen kogu tee läbi käinud ja kassettide endi silte vaadanud, naeratan, sest

vastus oli kogu aeg silme ees
Vasakpoolse kasseti sildil on TRS-80 arvuti nimi ja vahetult selle all tootja nimi: “Tootja Radio Shack in USA”

(Kui tahad hoida intriigi lõpuni, ära mine spoileri alla)

Helisignaalide võrdlus

Kõigepealt digitaliseerime helisalvestised. Saate kuulata, kuidas see kõlab:


Ja nagu tavaliselt, kõlab ZX Spectrum arvuti salvestis:


Mõlemal juhul on salvestuse alguses nn piloottoon - sama sagedusega heli (esimesel salvestusel on see väga lühike <1 sekund, kuid on eristatav). Piloottoon annab arvutile signaali valmistuda andmete vastuvõtmiseks. Reeglina tunneb iga arvuti signaali kuju ja sageduse järgi ära ainult oma “oma” piloottooni.

Midagi on vaja öelda signaali kuju enda kohta. Näiteks ZX Spectrumil on selle kuju ristkülikukujuline:

Kuidas ma magnetlindilt tundmatus vormingus andmeid taastasin

Piloottooni tuvastamisel kuvab ZX Spectrum ekraani serval vaheldumisi punaseid ja siniseid ribasid, mis näitavad, et signaal on tuvastatud. Piloottoon lõpeb sünkroonimpulss, mis annab arvutile signaali hakata andmeid vastu võtma. Seda iseloomustab lühem kestus (võrreldes pilootheli ja sellele järgnevate andmetega) (vt joonist)

Pärast sünkroonimisimpulsi vastuvõtmist registreerib arvuti iga signaali tõusu/languse, mõõtes selle kestust. Kui kestus on teatud piirist väiksem, kirjutatakse mällu bitt 1, vastasel juhul 0. Bitid kogutakse baitideks ja protsessi korratakse seni, kuni saadakse N baiti. Number N võetakse tavaliselt allalaaditud faili päisest. Laadimisjärjekord on järgmine:

  1. piloottoon
  2. päis (fikseeritud pikkus), sisaldab allalaaditud andmete suurust (N), faili nime ja tüüpi
  3. piloottoon
  4. andmed ise

Andmete korrektses laadimises veendumiseks loeb ZX Spectrum nn paarsusbait (paarsusbait), mis arvutatakse faili salvestamisel kõigi kirjutatud andmete baitide XOR-i teel. Faili lugemisel arvutab arvuti saadud andmete põhjal paarsusbaidi ja kui tulemus erineb salvestatust, kuvab veateate “R Tape loading error”. Rangelt võttes võib arvuti selle teate varem välja anda, kui ta lugemisel impulssi ära ei tunne (jäänud vahele või selle kestus ei vasta teatud piiridele)

Niisiis, vaatame nüüd, kuidas tundmatu signaal välja näeb:

Kuidas ma magnetlindilt tundmatus vormingus andmeid taastasin

See on piloottoon. Signaali kuju on oluliselt erinev, kuid on selge, et signaal koosneb teatud sagedusega lühikeste impulsside kordumisest. Diskreetimissagedusel 44100 Hz on "tippude" vaheline kaugus ligikaudu 48 valimit (mis vastab ~918 Hz sagedusele). Meenutagem seda joonist.

Vaatame nüüd andmefragmenti:

Kuidas ma magnetlindilt tundmatus vormingus andmeid taastasin

Kui mõõta üksikute impulsside kaugust, siis selgub, et “pikkade” impulsside vaheline kaugus on ikkagi ~48 ja lühikeste vahel ~24. Natuke edasi vaadates ütlen, et lõpuks selgus, et “referents” impulsid sagedusega 918 Hz järgnevad pidevalt, faili algusest lõpuni. Võib eeldada, et andmete edastamisel, kui võrdlusimpulsside vahel tekib lisaimpulss, loeme seda bitiks 1, muidu 0-ks.

Aga sünkroonimisimpulss? Vaatame andmete algust:

Kuidas ma magnetlindilt tundmatus vormingus andmeid taastasin

Piloottoon lõpeb ja andmed algavad kohe. Veidi hiljem, pärast mitme erineva helisalvestise analüüsi, suutsime avastada, et andmete esimene bait on alati sama (10100101b, A5h). Arvuti võib hakata andmeid lugema pärast nende vastuvõtmist.

Tähelepanu saab pöörata ka esimese võrdlusimpulsi nihkele vahetult pärast viimast 1. sünkroonimisbaidis. See avastati palju hiljem andmetuvastusprogrammi väljatöötamise käigus, kui faili alguses olevaid andmeid ei suudetud stabiilselt lugeda.

Nüüd proovime kirjeldada algoritmi, mis töötleb helifaili ja laadib andmeid.

Andmete laadimine

Kõigepealt vaatame mõnda eeldust, et algoritm oleks lihtne:

  1. Arvestame ainult WAV-vormingus failidega;
  2. Helifail peab algama piloottooniga ja ei tohi sisaldada alguses vaikust
  3. Lähtefaili diskreetimissagedus peab olema 44100 Hz. Sel juhul on 48 proovi võrdlusimpulsside vaheline kaugus juba määratud ja me ei pea seda programmiliselt arvutama;
  4. Näidisvorming võib olla mis tahes (8/16 bitti/ujukoma) - kuna lugemisel saame selle soovitud vorminguks teisendada;
  5. Eeldame, et lähtefail on normaliseeritud amplituudiga, mis peaks tulemuse stabiliseerima;

Lugemisalgoritm on järgmine:

  1. Loeme faili mällu, teisendades samal ajal näidisvormingu 8-bitiseks;
  2. Määrake esimese impulsi asukoht heliandmetes. Selleks peate arvutama maksimaalse amplituudiga proovi numbri. Lihtsuse huvides arvutame selle üks kord käsitsi. Salvestame selle muutujasse prev_pos;
  3. Lisage 48 viimase impulsi positsioonile (pos := prev_pos + 48)
  4. Kuna positsiooni suurendamine 48 võrra ei garanteeri, et jõuame järgmise võrdlusimpulsi positsioonini (lindi defektid, lindiajami mehhanismi ebastabiilne töö jne), siis peame pos-impulsi asendit reguleerima. Selleks võtke väike osa andmeid (pos-8;pos+8) ja leidke sellelt maksimaalne amplituudi väärtus. Maksimumile vastav positsioon salvestatakse pos. Siin on 8 = 48/6 eksperimentaalselt saadud konstant, mis garanteerib, et määrame õige maksimumi ega mõjuta teisi läheduses olevaid impulsse. Väga halbadel juhtudel, kui impulsside vaheline kaugus on palju väiksem või suurem kui 48, saate rakendada impulsi sunnitud otsimist, kuid artikli raames ma seda algoritmis ei kirjelda;
  5. Eelmise sammu juures oleks vaja ka kontrollida, kas referentsimpulss üldse leiti. See tähendab, et kui otsite lihtsalt maksimumi, ei garanteeri see impulsi olemasolu selles segmendis. Lugemisprogrammi viimases teostuses kontrollin segmendi maksimaalse ja minimaalse amplituudi väärtuste erinevust ja kui see ületab teatud piiri, loendan impulsi olemasolu. Küsimus on ka selles, mida teha, kui võrdlusimpulssi ei leita. On 2 võimalust: kas andmed on lõppenud ja järgneb vaikus või tuleb seda lugeda lugemisveaks. Kuid me jätame selle algoritmi lihtsustamiseks välja;
  6. Järgmises etapis peame kindlaks määrama andmeimpulsi olemasolu (bitt 0 või 1), selleks võtame segmendi keskosa (prev_pos;pos) middle_pos, mis võrdub middle_pos := (prev_pos+pos)/2 ja segmendi (keskmine_positsioon-8;keskmine_positsioon +8) naabruses arvutame maksimaalse ja minimaalse amplituudi. Kui nende vahe on suurem kui 10, kirjutame tulemusesse biti 1, vastasel juhul 0. 10 on eksperimentaalselt saadud konstant;
  7. Salvestage praegune asukoht kaustas prev_pos (prev_pos := pos)
  8. Korrake alates 3. sammust, kuni loeme kogu faili;
  9. Saadud bitimassiiv tuleb salvestada baitide komplektina. Kuna me lugemisel ei arvestanud sünkroonimisbaiti, siis ei pruugi bittide arv olla 8-kordne ja teadmata on ka vajalik bitinihe. Algoritmi esimeses teostuses ei teadnud ma sünkroonimisbaidi olemasolust ja seetõttu salvestasin lihtsalt 8 erineva arvu nihkebittide arvuga faili. Üks neist sisaldas õigeid andmeid. Viimases algoritmis eemaldan lihtsalt kõik bitid kuni A5h-ni, mis võimaldab mul kohe õige väljundfaili hankida

Ruby algoritm huvilistele
Valisin programmi kirjutamise keeleks Ruby, kuna... Programmeerin selle peale enamuse ajast. Valik ei ole suure jõudlusega, kuid lugemiskiiruse võimalikult kiireks muutmine pole seda väärt.

# Используем 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

Olles proovinud mitut algoritmi ja konstanti varianti, sain õnneks midagi äärmiselt huvitavat:

Kuidas ma magnetlindilt tundmatus vormingus andmeid taastasin

Niisiis, märgistringide järgi otsustades on meil programm graafikute joonistamiseks. Programmi tekstis pole aga märksõnu. Kõik märksõnad on kodeeritud baitidena (iga väärtus > 80h). Nüüd peame välja selgitama, milline 80ndate arvuti võiks selles vormingus programme salvestada.

Tegelikult on see väga sarnane BASIC programmiga. ZX Spectrum arvuti salvestab umbes samas formaadis programme mällu ja salvestab programmid lindile. Igaks juhuks kontrollisin märksõnade vastu laud. Tulemus oli aga ilmselgelt negatiivne.

Vaatasin üle ka populaarse Atari, Commodore 64 ja mitme teise tolle aja arvuti BASIC märksõnad, mille kohta õnnestus leida dokumentatsiooni, kuid edutult - minu teadmised retroarvutite tüüpidest osutusid mitte nii laiaks.

Siis otsustasin minna nimekiri, ja siis sattus mu pilk tootja Radio Shacki nimele ja arvutile TRS-80. Need on nimed, mis olid kirjutatud nende kassettide siltidele, mis minu laual lebasid! Ma ei teadnud neid nimesid varem ega tundnud TRS-80 arvutit, mistõttu mulle tundus, et Radio Shack on helikassettide tootja nagu BASF, Sony või TDK ja TRS-80 oli taasesituse aeg. Miks mitte?

Arvuti Tandy/Radio Shack TRS-80

Suure tõenäosusega kõnealune helisalvestis, mille artikli alguses näitena tõin, on tehtud sellises arvutis:

Kuidas ma magnetlindilt tundmatus vormingus andmeid taastasin

Selgus, et see arvuti ja selle sordid (mudel I/mudel III/mudel IV jne) olid omal ajal väga populaarsed (loomulikult mitte Venemaal). Tähelepanuväärne on, et protsessor, mida nad kasutasid, oli samuti Z80. Selle arvuti jaoks leiate Internetist palju infot. 80ndatel levitati arvutiteavet ajakirjad. Hetkel on neid mitu emulaatorid arvutid erinevatele platvormidele.

Laadisin emulaatori alla trs80gp ja esimest korda sain näha, kuidas see arvuti töötab. Loomulikult ei toetanud arvuti värviväljundit, ekraani eraldusvõime oli vaid 128x48 pikslit, kuid laiendusi ja modifikatsioone, mis võisid ekraani eraldusvõimet suurendada, oli palju. Selle arvuti jaoks oli ka palju opsüsteemide võimalusi ja BASIC-keele juurutamise võimalusi (mida erinevalt ZX Spectrumist mõnel mudelil isegi ROM-i ei “vilkutud” ja mis tahes valikuid sai laadida disketilt, nagu ka OS ise)

leidsin ka kasulikkust helisalvestisi konverteerimiseks CAS-vormingusse, mida emulaatorid toetavad, kuid millegipärast ei olnud nende abil võimalik oma kassettidest salvestisi lugeda.

Olles välja selgitanud CAS-failivormingu (mis osutus lihtsalt bittihaaval koopiaks lindilt, mis mul juba käes oli, välja arvatud päis koos sünkroonimisbaidiga), tegin mõned muudatused minu programmis ja suutsin väljastada toimiva CAS-faili, mis töötas emulaatoris (TRS-80 mudel III):

Kuidas ma magnetlindilt tundmatus vormingus andmeid taastasin

Konversiooniutiliidi uusima versiooni koos esimese impulsi ja võrdlusimpulsside vahelise kauguse automaatse määramisega kujundasin GEM-paketina, lähtekood on saadaval aadressil Github.

Järeldus

Meie läbitud tee osutus põnevaks rännakuks minevikku ja mul on hea meel, et lõpuks leidsin vastuse. Muuhulgas ma:

  • Arvasin välja andmete salvestamise vormingu ZX Spectrumis ja uurisin sisseehitatud ROM-i rutiine helikassettidelt andmete salvestamiseks/lugemiseks.
  • Tutvusin TRS-80 arvuti ja selle sortidega, uurisin operatsioonisüsteemi, vaatasin näidisprogramme ja sain isegi masinkoodides silumist teha (kõik Z80 mnemoonika on mulle ju tuttavad)
  • Kirjutas täieõigusliku utiliidi helisalvestiste CAS-vormingusse teisendamiseks, mis suudab lugeda andmeid, mida “ametlik” utiliit ei tunne ära

Allikas: www.habr.com

Lisa kommentaar