Kaip atkūriau duomenis nežinomu formatu iš magnetinės juostos

priešistorė

Būdamas retro aparatūros mėgėjas, kartą įsigijau ZX Spectrum+ iš pardavėjo JK. Kartu su pačiu kompiuteriu gavau keletą garso kasečių su žaidimais (originalioje pakuotėje su instrukcija), taip pat programas įrašytas į kasetes be specialių žymėjimų. Keista, bet duomenis iš 40 metų senumo kasečių buvo gerai skaitomi ir iš jų galėjau parsisiųsti beveik visus žaidimus ir programas.

Kaip atkūriau duomenis nežinomu formatu iš magnetinės juostos

Tačiau kai kuriose kasetėse radau įrašų, kurie aiškiai nebuvo padaryti ZX Spectrum kompiuteriu. Jie skambėjo visiškai kitaip ir, skirtingai nei įrašai iš minėto kompiuterio, neprasidėjo nuo trumpo BASIC bootloaderio, kuris dažniausiai būna visų programų ir žaidimų įrašuose.

Kurį laiką tai mane persekiojo – labai norėjau sužinoti, kas juose slypi. Jei galėtumėte nuskaityti garso signalą kaip baitų seką, galėtumėte ieškoti simbolių ar bet ko, kas nurodo signalo kilmę. Savotiška retro archeologija.

Dabar, kai nuėjau visą kelią ir pažiūrėjau į pačių kasečių etiketes, šypsausi, nes

atsakymas visą laiką buvo man prieš akis
Ant kairiosios kasetės etiketės yra kompiuterio TRS-80 pavadinimas, o tiesiai po gamintojo pavadinimu: „Pagaminta Radio Shack in USA“

(Jei norite išlaikyti intrigą iki galo, nesileiskite po spoileriu)

Garso signalų palyginimas

Pirmiausia suskaitmeninkime garso įrašus. Galite klausytis, kaip tai skamba:


Ir kaip įprasta skamba įrašas iš ZX Spectrum kompiuterio:


Abiem atvejais įrašo pradžioje yra vadinamasis piloto tonas - to paties dažnio garsas (pirmame įraše labai trumpas <1 sekundė, bet yra atskiriamas). Pilotinis signalas praneša kompiuteriui pasiruošti priimti duomenis. Paprastai kiekvienas kompiuteris atpažįsta tik savo „savo“ pilotinį toną pagal signalo formą ir jo dažnį.

Būtina ką nors pasakyti apie pačią signalo formą. Pavyzdžiui, ZX Spectrum jo forma yra stačiakampė:

Kaip atkūriau duomenis nežinomu formatu iš magnetinės juostos

Kai aptinkamas bandomasis tonas, ZX Spectrum ekrano pakraštyje rodo raudonas ir mėlynas juostas, rodančias, kad signalas buvo atpažintas. Piloto tonas baigiasi sinchroninis impulsas, kuris signalizuoja kompiuteriui pradėti priimti duomenis. Jam būdinga trumpesnė trukmė (palyginti su piloto tonu ir vėlesniais duomenimis) (žr. pav.)

Gavęs sinchronizavimo impulsą, kompiuteris registruoja kiekvieną signalo kilimą/smūgį, matuodamas jo trukmę. Jei trukmė mažesnė už tam tikrą ribą, į atmintį įrašomas bitas 1, kitu atveju 0. Bitai surenkami į baitus ir procesas kartojamas tol, kol gaunama N baitų. Skaičius N paprastai paimamas iš atsisiųsto failo antraštės. Įkrovimo seka yra tokia:

  1. piloto tonas
  2. antraštėje (fiksuoto ilgio), yra atsisiųstų duomenų dydis (N), failo pavadinimas ir tipas
  3. piloto tonas
  4. pačius duomenis

Kad įsitikintumėte, jog duomenys įkelti teisingai, ZX Spectrum nuskaito vadinamąjį pariteto baitas (pariteto baitas), kuris apskaičiuojamas išsaugant failą XOR surenkant visus įrašytų duomenų baitus. Skaitydamas failą kompiuteris iš gautų duomenų apskaičiuoja pariteto baitą ir, jei rezultatas skiriasi nuo įrašytojo, parodo klaidos pranešimą „R Tape loading error“. Griežtai tariant, kompiuteris gali pateikti šį pranešimą anksčiau, jei skaitydamas negali atpažinti impulso (praleistas arba jo trukmė neatitinka tam tikrų ribų).

Taigi, pažiūrėkime, kaip atrodo nežinomas signalas:

Kaip atkūriau duomenis nežinomu formatu iš magnetinės juostos

Tai yra bandomasis tonas. Signalo forma labai skiriasi, tačiau aišku, kad signalas susideda iš pasikartojančių trumpų tam tikro dažnio impulsų. Esant 44100 Hz diskretizavimo dažniui, atstumas tarp "smailių" yra maždaug 48 mėginiai (tai atitinka ~918 Hz dažnį). Prisiminkime šį skaičių.

Dabar pažiūrėkime į duomenų fragmentą:

Kaip atkūriau duomenis nežinomu formatu iš magnetinės juostos

Jei išmatuotume atstumą tarp atskirų impulsų, paaiškėtų, kad atstumas tarp „ilgų“ impulsų vis tiek yra ~48 mėginiai, o tarp trumpų – ~24. Žvelgdamas į priekį, pasakysiu, kad galiausiai paaiškėjo, kad „atskaitos“ impulsai, kurių dažnis yra 918 Hz, eina nuolat, nuo failo pradžios iki pabaigos. Galima daryti prielaidą, kad perduodant duomenis, jei tarp atskaitos impulsų atsiranda papildomas impulsas, mes jį laikome bitu 1, kitu atveju 0.

O kaip su sinchronizavimo impulsu? Pažiūrėkime į duomenų pradžią:

Kaip atkūriau duomenis nežinomu formatu iš magnetinės juostos

Bandomasis tonas baigiasi, o duomenys iškart prasideda. Kiek vėliau, išanalizavę kelis skirtingus garso įrašus, galėjome atrasti, kad pirmasis duomenų baitas visada yra tas pats (10100101b, A5h). Kompiuteris gali pradėti skaityti duomenis po to, kai juos gauna.

Taip pat galite atkreipti dėmesį į pirmojo atskaitos impulso poslinkį iškart po paskutinio 1-ojo sinchronizavimo baito. Jis buvo atrastas daug vėliau, kuriant duomenų atpažinimo programą, kai failo pradžioje nebuvo galima stabiliai nuskaityti duomenų.

Dabar pabandykime apibūdinti algoritmą, kuris apdoros garso failą ir įkels duomenis.

Įkeliami duomenys

Pirmiausia pažvelkime į keletą prielaidų, kad algoritmas būtų paprastas:

  1. Будем рассматривать файлы только в формате WAV;
  2. Garso failas turi prasidėti bandomuoju tonu ir jo pradžioje neturi būti tylos
  3. Šaltinio failo diskretizavimo dažnis turi būti 44100 Hz. Šiuo atveju atstumas tarp 48 imčių atskaitos impulsų jau yra nustatytas ir mums jo nereikia programiškai skaičiuoti;
  4. Pavyzdžio formatas gali būti bet koks (8/16 bitų/slankiojo kablelio) – kadangi skaitydami galime jį konvertuoti į norimą;
  5. Darome prielaidą, kad šaltinio failas yra normalizuotas pagal amplitudę, o tai turėtų stabilizuoti rezultatą;

Skaitymo algoritmas bus toks:

  1. Perskaitome failą į atmintį, tuo pačiu konvertuodami pavyzdžio formatą į 8 bitus;
  2. Nustatykite pirmojo impulso vietą garso duomenyse. Norėdami tai padaryti, turite apskaičiuoti didžiausios amplitudės mėginio skaičių. Paprastumo dėlei apskaičiuosime vieną kartą rankiniu būdu. Išsaugokime jį kintamajame prev_pos;
  3. Pridėkite 48 prie paskutinio impulso vietos (poz := prev_pos + 48)
  4. Kadangi pozicijos padidinimas 48 negarantuoja, kad pateksime į kito atskaitos impulso padėtį (juostos defektai, nestabilus juostos pavaros mechanizmo veikimas ir pan.), reikia pakoreguoti pozicinio impulso padėtį. Norėdami tai padaryti, paimkite nedidelį duomenų fragmentą (pos-8; poz + 8) ir suraskite joje didžiausią amplitudės reikšmę. Padėtis, atitinkanti maksimumą, bus išsaugota pozicijoje. Čia 8 = 48/6 yra eksperimentiniu būdu gauta konstanta, kuri garantuoja, kad nustatysime teisingą maksimumą ir neturėsime įtakos kitiems impulsams, kurie gali būti šalia. Labai blogais atvejais, kai atstumas tarp impulsų yra daug mažesnis arba didesnis nei 48, galima įgyvendinti priverstinę impulso paiešką, tačiau straipsnio apimtyje algoritme to neaprašysiu;
  5. Ankstesniame žingsnyje taip pat reikėtų patikrinti, ar atskaitos impulsas apskritai buvo rastas. Tai yra, jei jūs tiesiog ieškote maksimumo, tai negarantuoja, kad impulsas yra šiame segmente. Paskutiniame skaitymo programos įgyvendinime aš tikrinu skirtumą tarp didžiausios ir minimalios amplitudės reikšmių segmente ir, jei jis viršija tam tikrą ribą, skaičiuoju impulso buvimą. Taip pat kyla klausimas, ką daryti, jei atskaitos impulsas nerastas. Yra 2 parinktys: arba baigiasi duomenys ir tyla, arba tai turėtų būti laikoma skaitymo klaida. Tačiau mes to praleisime, kad supaprastintume algoritmą;
  6. Kitame žingsnyje turime nustatyti duomenų impulso buvimą (bitas 0 arba 1), tam paimame segmento vidurį (prev_pos;pos) middle_pos lygus middle_pos := (prev_pos+pos)/2 ir kai kuriose segmento Middle_pos apylinkėse (vidurinė_pozicija-8;vidurinė_pozicija +8) apskaičiuokime didžiausią ir mažiausią amplitudę. Jei skirtumas tarp jų didesnis nei 10, į rezultatą įrašome bitą 1, kitu atveju 0. 10 yra eksperimentiniu būdu gauta konstanta;
  7. Išsaugoti dabartinę poziciją prev_pos (prev_pos := poz)
  8. Kartokite nuo 3 veiksmo, kol perskaitysime visą failą;
  9. Gautas bitų masyvas turi būti išsaugotas kaip baitų rinkinys. Kadangi skaitydami neatsižvelgėme į sinchronizavimo baitą, bitų skaičius gali būti ne 8 kartotinis, o reikalingas bitų poslinkis taip pat nežinomas. Pirmą kartą įgyvendindamas algoritmą, aš nežinojau apie sinchronizavimo baito egzistavimą, todėl tiesiog išsaugojau 8 failus su skirtingu poslinkio bitų skaičiumi. Viename iš jų buvo pateikti teisingi duomenys. Paskutiniame algoritme aš tiesiog pašalinu visus bitus iki A5h, o tai leidžia iš karto gauti teisingą išvesties failą

Ruby algoritmas tiems, kurie domisi
Pasirinkau Ruby kaip kalbą programai rašyti, nes... Aš jį programuoju dažniausiai. Pasirinkimas nėra didelio našumo, tačiau užduotis, kad skaitymo greitis būtų kuo greitesnis, nėra verta.

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

Rezultatas

Išbandžius kelis algoritmo ir konstantų variantus, man pasisekė gauti kažką nepaprastai įdomaus:

Kaip atkūriau duomenis nežinomu formatu iš magnetinės juostos

Taigi, sprendžiant pagal simbolių eilutes, turime grafikų braižymo programą. Tačiau programos tekste nėra raktinių žodžių. Visi raktiniai žodžiai užkoduoti kaip baitai (kiekviena reikšmė > 80h). Dabar turime išsiaiškinti, kuris 80-ųjų kompiuteris galėjo išsaugoti programas tokiu formatu.

Tiesą sakant, tai labai panašu į BASIC programą. Kompiuteris ZX Spectrum atmintyje saugo programas maždaug tokiu pat formatu ir išsaugo jas į juostelę. Tik tuo atveju patikrinau raktinius žodžius stalo. Tačiau rezultatas buvo akivaizdžiai neigiamas.

Taip pat patikrinau populiariųjų „Atari“, „Commodore 64“ ir kelių kitų to meto kompiuterių BASIC raktažodžius, kuriems pavyko rasti dokumentaciją, tačiau nesėkmingai - mano žinios apie retro kompiuterių tipus pasirodė ne tokios plačios.

Tada nusprendžiau eiti sąrašas, o tada žvilgsnis užkliuvo už gamintojo Radio Shack pavadinimo ir kompiuterio TRS-80. Tai tie pavadinimai, kurie buvo užrašyti ant kasečių, kurios gulėjo ant mano stalo, etikečių! Šių pavadinimų anksčiau nežinojau ir nebuvau susipažinęs su kompiuteriu TRS-80, todėl man atrodė, kad Radio Shack yra garso kasečių gamintojas, pvz., BASF, Sony ar TDK, o TRS-80 buvo atkūrimo laikas. Kodėl gi ne?

Kompiuterio Tandy/Radio Shack TRS-80

Labai tikėtina, kad aptariamas garso įrašas, kurį kaip pavyzdį pateikiau straipsnio pradžioje, buvo padarytas tokiu kompiuteriu:

Kaip atkūriau duomenis nežinomu formatu iš magnetinės juostos

Paaiškėjo, kad šis kompiuteris ir jo atmainos (Model I/Model III/Model IV ir kt.) vienu metu buvo labai populiarūs (žinoma, ne Rusijoje). Pastebėtina, kad jų naudojamas procesorius taip pat buvo Z80. Šį kompiuterį galite rasti internete daug informacijos. Devintajame dešimtmetyje kompiuterinė informacija buvo platinama žurnalai. Šiuo metu yra keletas emuliatoriai kompiuteriai įvairioms platformoms.

Atsisiunčiau emuliatorių trs80gp ir pirmą kartą galėjau pamatyti, kaip veikia šis kompiuteris. Žinoma, kompiuteris nepalaikė spalvų išvesties, ekrano skiriamoji geba buvo tik 128x48 pikseliai, tačiau buvo daug plėtinių ir modifikacijų, galinčių padidinti ekrano skiriamąją gebą. Taip pat buvo daugybė šio kompiuterio operacinių sistemų parinkčių ir BASIC kalbos diegimo parinkčių (kuri, skirtingai nei ZX Spectrum, kai kuriuose modeliuose net nebuvo „perkelta“ į ROM ir bet kurią parinktį buvo galima įkelti iš diskelio, kaip ir pati OS)

as irgi radau naudingumas konvertuoti garso įrašus į CAS formatą, kurį palaiko emuliatoriai, tačiau naudojant juos kažkodėl nepavyko nuskaityti įrašų iš mano kasečių.

Išsiaiškinęs CAS failo formatą (kuris pasirodė esąs tik po bitų duomenų kopija iš juostos, kurią jau turėjau po ranka, išskyrus antraštę su sinchronizavimo baitu), padariau keli mano programos pakeitimai ir pavyko išvesti veikiantį CAS failą, kuris veikė emuliatoriuje (TRS-80 Model III):

Kaip atkūriau duomenis nežinomu formatu iš magnetinės juostos

Sukūriau naujausią konvertavimo programos versiją su automatiniu pirmojo impulso ir atstumo tarp atskaitos impulsų nustatymu kaip GEM paketą, šaltinio kodas pasiekiamas adresu GitHub.

išvada

Kelias, kurį nuėjome, buvo žavinga kelionė į praeitį, ir aš džiaugiuosi, kad galiausiai radau atsakymą. Be kita ko, aš:

  • Išsiaiškinau duomenų išsaugojimo formatą ZX Spectrum ir ištyriau integruotas ROM procedūras, skirtas duomenims iš garso kasečių įrašyti / nuskaityti.
  • Susipažinau su kompiuteriu TRS-80 ir jo rūšimis, studijavau operacinę sistemą, pažiūrėjau pavyzdines programas ir netgi turėjau galimybę atlikti derinimą mašininiuose koduose (juk visa Z80 mnemonika man pažįstama)
  • Parašė visavertę garso įrašų konvertavimo į CAS formatą programą, kuri gali nuskaityti duomenis, kurių neatpažįsta „oficiali“ programa

Šaltinis: www.habr.com

Добавить комментарий