Hogyan állítottam vissza adatokat ismeretlen formátumban mágnesszalagról

őstörténet

Mivel a retro hardverek szerelmese, egyszer vásároltam egy ZX Spectrum+-t egy eladótól az Egyesült Királyságban. Magához a számítógéphez mellékelve kaptam több hangkazettát játékokkal (eredeti csomagolásban, használati utasítással), valamint kazettára rögzített műsorokat külön jelölés nélkül. Meglepő módon a 40 éves kazetták adatai jól olvashatóak voltak, és szinte az összes játékot, programot le tudtam tölteni róluk.

Hogyan állítottam vissza adatokat ismeretlen formátumban mágnesszalagról

Néhány kazettán azonban olyan felvételeket találtam, amelyek egyértelműen nem a ZX Spectrum számítógéppel készültek. Teljesen más a hangzásuk, és az említett számítógépről készült felvételekkel ellentétben nem egy rövid BASIC bootloaderrel indultak, ami általában minden program és játék felvételén megtalálható.

Egy ideig ez kísértett – nagyon szerettem volna megtudni, mi rejtőzik bennük. Ha az audiojelet bájtok sorozataként tudná olvasni, kereshetne karaktereket vagy bármit, ami a jel eredetét jelzi. Egyfajta retro-régészet.

Most, hogy végigmentem és megnéztem magukat a kazetták címkéit, elmosolyodom, mert

a válasz végig a szemem előtt volt
A bal oldali kazetta címkéjén a TRS-80 számítógép neve, közvetlenül alatta pedig a gyártó neve: „A Radio Shack az USA-ban gyártotta”

(Ha a végéig meg akarod őrizni az intrikát, ne menj a spoiler alá)

Hangjelek összehasonlítása

Először is digitalizáljuk a hangfelvételeket. Meghallgathatod, hogyan hangzik:


És szokás szerint a ZX Spectrum számítógépről készült felvétel hangzik:


Mindkét esetben a felvétel elején van egy ún pilótahang - azonos frekvenciájú hang (az első felvételen nagyon rövid <1 másodperc, de jól megkülönböztethető). A pilótahang jelzi a számítógépnek, hogy készüljön fel az adatok fogadására. Általában minden számítógép csak a „saját” pilothangját ismeri fel a jel alakja és frekvenciája alapján.

Mondani kell valamit magáról a jelformáról. Például a ZX Spectrumon az alakja téglalap alakú:

Hogyan állítottam vissza adatokat ismeretlen formátumban mágnesszalagról

Pilótahang észlelésekor a ZX Spectrum váltakozó piros és kék sávokat jelenít meg a képernyő szélén, jelezve, hogy a jel felismerése megtörtént. Pilot hang véget ér szinkron impulzus, amely jelzi a számítógépnek, hogy kezdje meg az adatok fogadását. Rövidebb időtartam jellemzi (a pilótahanghoz és az azt követő adatokhoz képest) (lásd az ábrát)

A szinkronimpulzus vétele után a számítógép rögzíti a jel minden egyes emelkedését/esését, és méri annak időtartamát. Ha az időtartam egy bizonyos határnál kisebb, az 1. bit a memóriába kerül, ellenkező esetben a 0. A biteket bájtokba gyűjtik, és a folyamatot addig ismétlik, amíg N bájt meg nem érkezik. Az N számot általában a letöltött fájl fejlécéből veszik. A betöltési sorrend a következő:

  1. pilótahang
  2. fejléc (fix hosszúságú), tartalmazza a letöltött adatok méretét (N), a fájl nevét és típusát
  3. pilótahang
  4. magát az adatot

Az adatok helyes betöltésére a ZX Spectrum beolvassa az ún paritás bájt (paritás bájt), amely egy fájl mentésekor az írott adatok összes bájtjának XOR-ával kerül kiszámításra. Fájl beolvasásakor a számítógép a kapott adatokból kiszámítja a paritásbájtot, és ha az eredmény eltér a mentetttől, megjelenik az „R Tape loading error” hibaüzenet. Szigorúan véve a számítógép korábban kiadhatja ezt az üzenetet, ha olvasás közben nem tud felismerni egy impulzust (kimaradt vagy időtartama nem felel meg bizonyos határoknak)

Lássuk tehát, hogyan néz ki egy ismeretlen jel:

Hogyan állítottam vissza adatokat ismeretlen formátumban mágnesszalagról

Ez a pilótahang. A jel alakja jelentősen eltér, de egyértelmű, hogy a jel bizonyos frekvenciájú rövid impulzusok ismétlődéséből áll. 44100 Hz-es mintavételezési frekvenciánál a „csúcsok” közötti távolság hozzávetőlegesen 48 minta (ami ~918 Hz-es frekvenciának felel meg) Emlékezzünk erre az ábrára.

Nézzük most az adatrészletet:

Hogyan állítottam vissza adatokat ismeretlen formátumban mágnesszalagról

Ha megmérjük az egyes impulzusok közötti távolságot, akkor kiderül, hogy a „hosszú” impulzusok közötti távolság még mindig ~48 minta, a rövidek között pedig ~24. Kicsit előretekintve elmondom, hogy a végén kiderült, hogy a fájl elejétől a végéig folyamatosan 918 Hz-es „referencia” impulzusok következnek. Feltételezhető, hogy adatátvitelkor, ha a referenciaimpulzusok között további impulzus is előfordul, azt 1-es bitnek, egyébként 0-nak tekintjük.

Mi a helyzet a szinkronimpulzussal? Nézzük az adatok elejét:

Hogyan állítottam vissza adatokat ismeretlen formátumban mágnesszalagról

A pilótahang véget ér, és az adatok azonnal elkezdődnek. Kicsit később, több különböző hangfelvétel elemzése után felfedezhettük, hogy az adatok első bájtja mindig ugyanaz (10100101b, A5h). A számítógép elkezdheti olvasni az adatokat, miután megkapta azokat.

Arra is figyelhet, hogy a szinkronizálási bájtban közvetlenül az utolsó 1. utáni első referenciaimpulzus eltolódik. Jóval később, egy adatfelismerő program fejlesztése során fedezték fel, amikor a fájl elején lévő adatokat nem lehetett stabilan kiolvasni.

Most próbáljunk meg leírni egy algoritmust, amely feldolgozza a hangfájlt és betölti az adatokat.

adatok betöltése

Először is nézzünk meg néhány feltételezést, hogy az algoritmus egyszerű legyen:

  1. Csak a WAV formátumú fájlokat vesszük figyelembe;
  2. Az audiofájlnak pilothanggal kell kezdődnie, és nem tartalmazhat csendet az elején
  3. A forrásfájl mintavételi frekvenciájának 44100 Hz-nek kell lennie. Ebben az esetben 48 minta referenciaimpulzusai közötti távolság már meg van határozva, és ezt nem kell programozottan kiszámítanunk;
  4. A minta formátuma tetszőleges lehet (8/16 bit/lebegőpont) - hiszen olvasáskor tudjuk konvertálni a kívántra;
  5. Feltételezzük, hogy a forrásfájlt az amplitúdó normalizálja, ami stabilizálja az eredményt;

Az olvasási algoritmus a következő lesz:

  1. A fájlt beolvassuk a memóriába, egyúttal a mintaformátumot 8 bitesre konvertáljuk;
  2. Határozza meg az első impulzus helyét az audioadatokban. Ehhez ki kell számítani a maximális amplitúdójú minta számát. Az egyszerűség kedvéért egyszer manuálisan számoljuk ki. Mentsük el a prev_pos változóba;
  3. Adjon hozzá 48-at az utolsó impulzus helyéhez (poz := prev_pos + 48)
  4. Mivel a pozíció 48-cal történő növelése nem garantálja, hogy a következő referenciaimpulzus pozíciójába kerülünk (szalaghibák, a szalagos meghajtó mechanizmusának instabil működése stb.), ezért a poziciós impulzus helyzetét módosítanunk kell. Ehhez vegyünk egy kis adatot (pos-8;pos+8), és keressük meg rajta a maximális amplitúdóértéket. A maximumnak megfelelő pozíció a pozícióban lesz tárolva. Itt a 8 = 48/6 egy kísérletileg kapott állandó, amely garantálja, hogy a helyes maximumot határozzuk meg, és nem befolyásolja a közelben esetlegesen előforduló egyéb impulzusokat. Nagyon rossz esetekben, amikor az impulzusok közötti távolság sokkal kisebb vagy nagyobb, mint 48, végrehajthat kényszerített impulzuskeresést, de a cikk keretein belül ezt nem írom le az algoritmusban;
  5. Az előző lépésnél azt is ellenőrizni kell, hogy a referenciaimpulzus egyáltalán megtalálható-e. Vagyis ha egyszerűen a maximumot keresed, az nem garantálja, hogy az impulzus jelen van ebben a szegmensben. A leolvasó program legújabb implementációjában egy szegmensen ellenőrzöm a maximális és minimális amplitúdóértékek közötti különbséget, és ha túllép egy bizonyos határt, akkor számolom az impulzus jelenlétét. A kérdés az is, hogy mi a teendő, ha nem található a referenciaimpulzus. 2 lehetőség van: vagy az adatok véget értek, és csend következik, vagy ez olvasási hibának tekintendő. Ezt azonban az algoritmus egyszerűsítése érdekében elhagyjuk;
  6. A következő lépésben meg kell határoznunk egy adatimpulzus jelenlétét (bit 0 vagy 1), ehhez vesszük a szegmens közepét (prev_pos;pos) middle_pos egyenlő middle_pos := (prev_pos+pos)/2 és a szegmens middle_pos valamelyik szomszédságában (middle_pos-8;middle_pos +8) számítsuk ki a maximális és minimális amplitúdót. Ha a különbség nagyobb, mint 10, akkor az 1-es bitet írjuk az eredménybe, ellenkező esetben 0. 10 egy kísérletileg kapott állandó;
  7. Az aktuális pozíció mentése a prev_pos fájlba (prev_pos := pos)
  8. Ismételje meg a 3. lépéstől kezdve, amíg el nem olvassuk a teljes fájlt;
  9. Az eredményül kapott bittömböt bájtok halmazaként kell elmenteni. Mivel olvasáskor nem vettük figyelembe a szinkron bájtot, előfordulhat, hogy a bitek száma nem lehet 8 többszöröse, és a szükséges biteltolás sem ismert. Az algoritmus első implementációjában nem tudtam a szinkronizálási bájt létezéséről, ezért egyszerűen elmentettem 8 fájlt különböző számú offset bittel. Az egyik helyes adatokat tartalmazott. Az utolsó algoritmusban egyszerűen eltávolítom az összes bitet A5h-ig, ami lehetővé teszi, hogy azonnal megkapjam a megfelelő kimeneti fájlt

Ruby algoritmus, az érdeklődők számára
A program írásához a Rubyt választottam, mert... Legtöbbször programozom rá. Az opció nem nagy teljesítményű, de az olvasási sebesség minél gyorsabbá tétele nem éri meg.

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

Eredmény

Az algoritmus és a konstansok több változatát kipróbálva szerencsém volt valami rendkívül érdekes dologgal:

Hogyan állítottam vissza adatokat ismeretlen formátumban mágnesszalagról

Tehát a karakterláncokból ítélve van egy programunk a grafikonok ábrázolására. A program szövegében azonban nincsenek kulcsszavak. Minden kulcsszó bájtban van kódolva (minden érték > 80 óra). Most meg kell találnunk, hogy a 80-as évek melyik számítógépe menthetett programokat ebben a formátumban.

Valójában nagyon hasonlít egy BASIC programhoz. A ZX Spectrum számítógép megközelítőleg azonos formátumban tárolja a programokat a memóriában, és szalagra menti a programokat. Minden esetre leellenőriztem a kulcsszavakat asztal. Az eredmény azonban nyilvánvalóan negatív lett.

Az akkori népszerű Atari, Commodore 64 és több más számítógép BASIC kulcsszavait is leellenőriztem, amelyekhez sikerült dokumentációt találni, de sikertelenül - a retro számítógépek típusaira vonatkozó ismereteim nem bizonyultak olyan szélesnek.

Aztán úgy döntöttem, hogy megyek a listát, majd a pillantásom a gyártó Radio Shack nevére és a TRS-80 számítógépre esett. Ezek a nevek voltak felírva az asztalomon heverő kazetták címkéire! Korábban nem ismertem ezeket a neveket, és nem ismertem a TRS-80 számítógépet, ezért úgy tűnt számomra, hogy a Radio Shack egy hangkazetta-gyártó, mint a BASF, a Sony vagy a TDK, és a TRS-80 a lejátszási idő. Miért ne?

Számítógép Tandy/Radio Shack TRS-80

Nagyon valószínű, hogy a szóban forgó hangfelvétel, amelyet a cikk elején példaként hoztam fel, egy ilyen számítógépen készült:

Hogyan állítottam vissza adatokat ismeretlen formátumban mágnesszalagról

Kiderült, hogy ez a számítógép és fajtái (I. modell/III. modell/IV. modell stb.) egy időben nagyon népszerűek voltak (persze nem Oroszországban). Figyelemre méltó, hogy az általuk használt processzor is Z80 volt. Ehhez a számítógéphez megtalálható az interneten sok információ. A 80-as években a számítógépes információkat terjesztették folyóiratok. Jelenleg több is van emulátorok számítógépek különböző platformokhoz.

Letöltöttem az emulátort trs80gp és most először láthattam, hogyan működik ez a számítógép. Természetesen a számítógép nem támogatta a színes kimenetet, a képernyő felbontása csak 128x48 pixel volt, de számos bővítés és módosítás volt, ami növelhette a képernyő felbontását. Ezen a számítógépen számos operációs rendszerre és a BASIC nyelv implementálására is lehetőség volt (amely a ZX Spectrum-mal ellentétben bizonyos modellekben nem is került ROM-ba, és bármilyen opció betölthető volt hajlékonylemezről, mint pl. maga az operációs rendszer)

én is találtam hasznosság hangfelvételeket CAS formátumba konvertálni, amit az emulátorok támogatnak, de ezek segítségével valamiért nem lehetett felvételeket olvasni a kazettáimról.

Miután kitaláltam a CAS fájlformátumot (ami csak egy bitenkénti másolata a nálam lévő szalagról, kivéve a fejlécet a szinkronizálási bájt jelenlétével), elkészítettem egy néhány módosítást végeztem a programon, és egy működő CAS-fájlt tudtam kiadni, amely működött az emulátorban (TRS-80 Model III):

Hogyan állítottam vissza adatokat ismeretlen formátumban mágnesszalagról

A konvertáló segédprogram legújabb verzióját az első impulzus és a referenciaimpulzusok távolságának automatikus meghatározásával GEM-csomagként terveztem, a forráskód elérhető a címen. GitHub.

Következtetés

Az út, amelyet bejártunk, lenyűgöző utazásnak bizonyult a múltba, és örülök, hogy végül megtaláltam a választ. Többek között én:

  • Kitaláltam a ZX Spectrum adatmentési formátumát, és tanulmányoztam a beépített ROM-rutinokat a hangkazetták adatainak mentéséhez/olvasásához.
  • Megismerkedtem a TRS-80 számítógéppel és fajtáival, tanulmányoztam az operációs rendszert, megnéztem a mintaprogramokat, és még gépi kódokban is volt lehetőségem hibakeresésre (végül is az összes Z80-as mnemonika ismerős számomra)
  • Készített egy teljes értékű segédprogramot a hangfelvételek CAS formátumba konvertálására, amely képes olyan adatokat olvasni, amelyeket a „hivatalos” segédprogram nem ismer fel

Forrás: will.com

Hozzászólás