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

Háttér

Régi vasárutálok kedvelőjeként egyszer egy brit eladótól szereztem be egy ZX Spectrum+ készüléket. A számítógépen kívül néhány játékot tartalmazó audiokazettát (eredeti csomagolásban, útmutatókkal), valamint olyan programokat tartalmazó kazettákat kaptam, amelyek nem voltak külön megjelölve. Meglepetésemre a 40 éves kazetták adatai szépen olvashatók voltak, és majdnem az összes játékot és programot sikerült betöltenem róluk.

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

Ellenben néhány kazettán olyan felvételeket találtam, amelyeket nyilvánvalóan nem a ZX Spectrum készített. Ezek teljesen másképp szóltak, és a fent említett számítógép felvételeivel ellentétben nem kezdődtek egy rövid BASIC betöltővel, amely általában jelen van minden program és játék felvételénél.

Egy ideig ez nem hagyott nyugodni – nagyon kíváncsi voltam, mi rejlik bennük. Ha sikerült volna az audiosignálot bájtok sorozataként elolvasni, lehetett volna keresni benne karaktereket vagy valamit, ami jelzi a jel eredetét. Olyasmi, mint egy retro-arkológia.

Most, hogy végigjártam az utat és ránézek a kazetták címkéjére, mosolygok, mert

a válasz szinte végig a szemem előtt volt
A bal oldali kazetta címkén az TRS-80 számítógép neve szerepel, és kicsivel lejjebb a gyártó neve: „Manufactured by Radio Shack in USA”

(Ha szeretné megőrizni a meglepetést a végéig, ne tekintse meg a spoiler-t)

Audiovizuális jelsorok összehasonlítása

Első lépésként digitalizáljuk az audiokazettákat. Itt meghallgathatja, hogyan hangzik:


És így szól egy felvétel a ZX Spectrum számítógépről:


Mindkét esetben a felvétel elején megjelenik az úgynevezett pilottónus – egy frekvencia hangja (az első felvételen nagyon rövid <1 másodperc, de észlelhető). A pilottónus jelez 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” pilottónusát ismeri fel a jel formájáról és frekvenciájáról.

Meg kell említeni a jel formáját is. Például, a ZX Spectrum esetében a formája téglalap alakú:

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

A pilottónus észlelésénél a ZX Spectrum váltakozó piros-kék csíkokat jelenít meg a képernyő szélén, jelezve, hogy a jelet észlelték. A pilottónus véget ér szinkronimpulzussal, amely jelzi a számítógépnek, hogy el kell kezdenie az adatok fogadását. Jellemzője, hogy rövidebb (a pilot tonushoz és a későbbi adatokhoz képest) a hossza (lásd az ábrát)

Miután a szinkronimpulzus megérkezett, a számítógép rögzíti a jel minden emelkedését / süllyedését, mérve annak hosszát. Ha a hossza a megadott küszöb alatt van, akkor a memóriába 1-es bitet ír, különben 0-t. A bitek byte-okba gyűlnek, és a folyamat folytatódik, amíg meg nem kapunk N byte-ot. Az N számot általában a betöltött fájl fejlécéből nyerik. A betöltési szekvencia a következő:

  1. pilottónus
  2. fejléc (meghatározott hosszúságú), tartalmazza a betöltendő adatok méretét (N), a fájl nevét és típusát
  3. pilottónus
  4. maguk az adatok

Annak érdekében, hogy megbizonyosodjunk arról, hogy az adatok helyesen lettek betöltve, a ZX Spectrum az utolsó byte-t a úgynevezett paritás byte (parity byte), amely a fájl mentésekor XOR művelettel számítódik az összes rögzített byte felett. A fájl olvasásakor a számítógép újraszámítja a paritás byte-ot a kapott adatok alapján, és ha az eredmény eltér a rögzítettektől, hibaüzenetet ad ki: „R Tape loading error”. Szigorúan véve a számítógép ezt az üzenetet korábban is kiadhatja, ha az olvasás során nem tudja az impulzusokat azonosítani (hiányzik, vagy a hossza nem felel meg a megadott határoknak)

Nézzük most meg, hogyan néz ki egy ismeretlen jel:

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

Ez a pilot tonus. A jel formája jelentősen eltér, de látható, hogy a jel ismétlődő rövid impulzusokból áll, meghatározott frekvenciával. 44100 Hz-es mintavételezési frekvencia mellett a „csúcsok” közötti távolság körülbelül 48 mintának felel meg (ami körülbelül 918 Hz frekvenciát jelent) Ezt a számot jegyezzük meg.

Nézzük most meg az adatokkal kapcsolatos szakaszt:

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

Ha megmérjük a távolságot az egyes impulzusok között, kiderül, hogy a „hosszú” impulzusok között a távolság továbbra is körülbelül 48 minta, míg a rövidek között ~24. Egy kicsit előreugorva elmondhatom, hogy végül kiderült, hogy a 918 Hz-es referenciájú impulzusok folyamatosan követik egymást, a fájl elejétől a végéig. Fel lehet tételezni, hogy az adatok átvitele során, ha egy extra impulzus találkozik a referenciapulzusok között, azt 1-es bitnek tekintjük, különben 0-nak.

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

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

A pilot tone ends and the data immediately begins. A bit later, after analyzing several different audio recordings, it was found that the first byte of data is always the same (10100101b, A5h). It is possible that the computer starts reading the data after it receives it.

Attention can also be drawn to the shift of the first reference pulse immediately after the last '1' in the sync byte. It was discovered much later in the process of developing the data recognition program when the data at the beginning of the file could not be read stably.

Now let's try to describe the algorithm that will process the audio file and load the data.

Adatok betöltése

First, let's consider several assumptions to simplify the algorithm:

  1. We will consider files only in WAV format;
  2. The audio file must start with a pilot tone and should not contain silence at the beginning;
  3. The source file must have a sampling rate of 44100 Hz. In this case, the distance between reference pulses of 48 samples is already defined, and we do not need to calculate it programmatically;
  4. The sample format can be anything (8/16 bits/floating point) — as we can convert it to the required format when reading;
  5. We assume that the original file is normalized in amplitude, which should stabilize the result;

The reading algorithm will be as follows:

  1. Read the file into memory while simultaneously converting the sample format to 8 bits;
  2. Determine the position of the first pulse in the audio data. To do this, we need to calculate the sample number with the maximum amplitude. For simplicity, let's count it manually once. We will save it in the variable prev_pos;
  3. Add 48 to the position of the last pulse (pos := prev_pos + 48)
  4. Mivel a pozíció 48-ra történő növelése nem garantálja, hogy elérjük a következő referenciális impulzus pozícióját (szalaghibák, instabil szalagszállító mechanika stb.), ki kell igazítani az impulzus pozíciót (pos). Ehhez egy kisebb adatszakaszt (pos-8; pos+8) fogunk venni, és megkeressük rajta az amplitúdó értékének maximumát. A maximumhoz tartozó pozíciót megőrizzük pos-ként. Itt 8 = 48/6 – kísérleti úton kapott állandó, amely garantálja, hogy a helyes maximumot határozzuk meg, és nem érintjük más impulzusokat, amelyek a közelben lehetnek. Nagyon rossz esetekben, amikor az impulzusok közötti távolság jelentősen kevesebb vagy több mint 48, lehet kényszerített impulzuskeresést végrehajtani, de ezt a cikk keretein belül nem írom le az algoritmusban.
  5. Az előző lépésben azt is meg kell vizsgálni, hogy a referenciális impulzus valóban megtalálható-e. Vagyis ha csak a maximumot keressük, az nem garantálja, hogy az impulzus jelen van a megadott szakaszban. A legutolsó megvalósításomban a leolvasó programban ellenőrzöm a maximális és minimális amplitúdó érték közötti különbséget a szakaszon, és ha az átlép egy bizonyos határt, akkor az impulzus létezését elismerem. A kérdés az is, hogy mi a teendő, ha a referenciális impulzus nem található. Itt két lehetőség van: vagy az adatok véget értek, és aztán csend következik, vagy ezt olvasási hibának kell tekinteni. Ezt azonban leegyszerűsítjük az algoritmus számára.
  6. A következő lépésben meg kell határoznunk az adatok impulzusának (0 vagy 1 bit) létezését, ehhez a szakasz közepét (prev_pos; pos) middle_pos-ként vesszük, amely egyenlő middle_pos := (prev_pos+pos)/2 és a middle_pos körüli területen a szakaszon (middle_pos-8; middle_pos+8) számítjuk az amplitúdó maximumát és minimumát. Ha a különbség közöttük meghaladja a 10-et, akkor 1-es bitet rögzítünk az eredménybe, különben 0-t. A 10 egy kísérleti úton kapott állandó.
  7. Megőrizzük az aktuális pozíciót prev_pos-ként (prev_pos := pos).
  8. Ismételjük a 3. lépéstől kezdve, amíg az egész fájlt be nem olvassuk.
  9. A kapott bitmasszát byte-okként kell menteni. Mivel olvasásnál nem vettük figyelembe a szinkron byte-ot, a bitek száma nem feltétlenül lesz 8-mal osztható, és a bitekben szükséges eltolás is ismeretlen. Az algoritmus első megvalósításakor nem tudtam a szinkron byte létezéséről, és ezért egyszerűen 8 fájlt mentettem különböző bitek eltolásával. Az egyik helyes adatokat tartalmazott. A végső algoritmusban egyszerűen eltávolítom az összes bitet az A5h-ig, ami lehetővé teszi, hogy helyes fájlt kapjak az outputon.

Ruby algoritmus, akit érdekel
A program írásához a Ruby nyelvet választottam, mivel a legtöbb időt ezen programozom. A megoldás nem nagy teljesítményű, azonban nem célom a maximálisan gyors olvasási sebesség elérése.

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

Több algoritmus- és állandóságváltozat kipróbálása után sikerült valami rendkívül érdekeset kapnom:

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

Tehát, a karakterláncok alapján, van egy programunk grafikonok készítésére. Azonban a program szövegében nincsenek kulcsszavak. Minden kulcsszó byte-ként van kódolva (minden érték > 80h). Most meg kell állapítanunk, hogy melyik 80-as évekbeli számítógép tudott ilyen formátumban programokat menteni.

Valójában ez nagyon hasonló egy BASIC nyelvű programhoz. Körülbelül ilyen formátumban tárolja a ZX Spectrum számítógép a programokat a memóriájában és menti azokat kazettára. Minden esetre ellenőriztem a kulcsszavakat, hogy megfelelnek-e a táblázatnak.. Az eredmény azonban nyilvánvalóan negatív volt.

Ezen kívül ellenőriztem a korabeli népszerű Atari, Commodore 64 és néhány más számítógép BASIC kulcsszavait, amire sikerült dokumentációt találni, de sajnos eredménytelenül – tudásom a retro számítógépek fajtáiról nem volt olyan széleskörű.

Akkor úgy döntöttem, hogy megnézem a lista, és itt a szemem a Radio Shack gyártó és TRS-80 számítógép nevére esett. Ezek a nevek voltak feltüntetve a kazetták címkéjén, amelyek az asztalomon hevertek! Korábban nem tudtam ezeket a neveket, és nem voltam ismerős a TRS-80 számítógéppel, ezért azt hittem, hogy a Radio Shack egy audiokazetta gyártó, mint a BASF, Sony vagy TDK, a TRS-80 pedig a lejátszás időtartama. Miért ne?

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

Nagyon valószínű, hogy az említett audioregistráció, amelyet példaként hoztam fel a cikk elején, egy ilyen számítógépen készült:

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

Kiderült, hogy ez a számítógép és annak különböző változatai (Model I/Model III/Model IV stb.) nagyon népszerűek voltak a maguk idejében (persze, nem Oroszországban). Érdekes, hogy a benne használt processzor is Z80 volt. Erről a számítógépről az Interneten található sok információ.Az 80-as években a számítógépről szóló információk folyóiratokbanterjedtek. Jelenleg több emulátor létezik a számítógéphez különböző platformokon.

Letöltöttem az emulátort trs80gp és először láthattam, hogyan működött 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 mindössze 128x48 pixel volt, de számos bővítmény és módosítás létezett, amelyek növelhették a képernyő felbontását. Emellett számos operációs rendszer és BASIC nyelv megvalósításának különböző változatai voltak ehhez a számítógéphez (amely, ellentétben a ZX Spectrum-mal, egyes modellekben nem volt „beépítve” az EPROMembe, és bármely variant a lemezről betölthető volt, ahogyan mag az OS is).

Ezen kívül találtam segédprogramot eszközöket audiovideók CAS formátumba konvertálására, amelyeket az emulátorok támogatnak, azonban valamilyen okból nem tudtam a kazettáimról felvételeket olvasni velük.

Miután megértettem a CAS fájl formátumát (ami egyszerűen a lényeg copy-bitje volt az adatokkal, amelyek már a kezemben voltak, azzal a kivétellel, hogy a fejléc tartalmazta a szinkron byte-ot), néhány változtatást végeztem a programomban, és sikerült egy működő CAS fájlt létrehoznom, amely működött az emulátorban (TRS-80 Model III):

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

A legutolsó verziót, amely automatikusan meghatározza az első impulzust és a horgony impulzusok közötti távolságot, GEM csomag formájában készítettem el, a forráskód elérhető a Githubot.

Zárás

A megtett út izgalmas utazás volt a múltba, és örülök, hogy végül megtaláltam a megoldást. Ezen kívül:

  • Megértettem a ZX Spectrum adatok mentési formátumát, és tanulmányoztam a beépített rutint a kazetták audió adatainak mentésére/olvasására az EPROMembe.
  • Megismerkedtem a TRS-80 számítógéppel és annak különböző változataival, tanulmányoztam az operációs rendszert, megnéztem a programpéldákat, és még lehetőségem nyílt gépi kódban debuggolni (és a Z80 mnemonikák minden jól ismertek számomra).
  • Készítettem egy teljes értékű segédprogramot, amely képes hangfelvételeket CAS formátumban konvertálni, és olyan adatokat olvasni, amelyeket az "hivatalos" segédprogram nem ismer fel.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster