Hoe ik gegevens in een onbekend formaat van magneetband heb hersteld

prehistorie

Als liefhebber van retro-hardware heb ik ooit een ZX Spectrum+ gekocht bij een verkoper in Groot-Brittannië. Bij de computer zelf ontving ik verschillende audiocassettes met games (in de originele verpakking met instructies), evenals programma's opgenomen op cassettes zonder speciale markeringen. Verrassend genoeg waren de gegevens van 40 jaar oude cassettes goed leesbaar en kon ik er bijna alle games en programma's van downloaden.

Hoe ik gegevens in een onbekend formaat van magneetband heb hersteld

Op sommige cassettes vond ik echter opnames die duidelijk niet door de ZX Spectrum-computer waren gemaakt. Ze klonken heel anders en begonnen, in tegenstelling tot de opnames van de genoemde computer, niet met een korte BASIC-bootloader, die meestal aanwezig is in de opnames van alle programma's en games.

Dit achtervolgde me een tijdje - ik wilde heel graag weten wat erin verborgen zat. Als je het audiosignaal zou kunnen lezen als een reeks bytes, zou je kunnen zoeken naar tekens of iets anders dat de oorsprong van het signaal aangeeft. Een soort retro-archeologie.

Nu ik helemaal ben gegaan en naar de labels van de cassettes zelf heb gekeken, glimlach ik omdat

het antwoord lag de hele tijd vlak voor mijn ogen
Op het label van de linker cassette staat de naam van de TRS-80 computer, en net daaronder de naam van de fabrikant: “Manufactured by Radio Shack in USA”

(Als je de intriges tot het einde wilt behouden, ga dan niet onder de spoiler)

Vergelijking van audiosignalen

Laten we eerst de audio-opnames digitaliseren. Je kunt luisteren hoe het klinkt:


En zoals gewoonlijk klinkt de opname van de ZX Spectrum-computer:


In beide gevallen is er aan het begin van de opname een zogenaamde piloot toon - een geluid met dezelfde frequentie (in de eerste opname is het zeer kort <1 seconde, maar wel te onderscheiden). De piloottoon geeft aan dat de computer zich klaarmaakt om gegevens te ontvangen. In de regel herkent elke computer alleen zijn ‘eigen’ piloottoon aan de vorm van het signaal en de frequentie ervan.

Het is noodzakelijk om iets te zeggen over de signaalvorm zelf. Op de ZX Spectrum is de vorm bijvoorbeeld rechthoekig:

Hoe ik gegevens in een onbekend formaat van magneetband heb hersteld

Wanneer een piloottoon wordt gedetecteerd, geeft de ZX Spectrum afwisselend rode en blauwe balken weer op de rand van het scherm om aan te geven dat het signaal is herkend. De piloottoon eindigt synchro-puls, wat aangeeft dat de computer gegevens begint te ontvangen. Het wordt gekenmerkt door een kortere duur (vergeleken met de piloottoon en daaropvolgende gegevens) (zie afbeelding)

Nadat de synchronisatiepuls is ontvangen, registreert de computer elke stijging/daling van het signaal en meet de duur ervan. Als de duur korter is dan een bepaalde limiet, wordt bit 1 naar het geheugen geschreven, anders 0. De bits worden verzameld in bytes en het proces wordt herhaald totdat N bytes zijn ontvangen. Het nummer N wordt meestal uit de header van het gedownloade bestand gehaald. De laadvolgorde is als volgt:

  1. piloot toon
  2. header (vaste lengte), bevat de grootte van de gedownloade gegevens (N), bestandsnaam en type
  3. piloot toon
  4. de gegevens zelf

Om er zeker van te zijn dat de gegevens correct worden geladen, leest de ZX Spectrum het zogenaamde pariteitsbyte (pariteitsbyte), die wordt berekend bij het opslaan van een bestand door alle bytes van de geschreven gegevens te XORen. Bij het lezen van een bestand berekent de computer de pariteitsbyte uit de ontvangen gegevens en geeft, als het resultaat afwijkt van het opgeslagen resultaat, de foutmelding “R Tape Loading Error” weer. Strikt genomen kan de computer dit bericht eerder afgeven als hij tijdens het lezen een puls niet kan herkennen (gemist of de duur ervan komt niet overeen met bepaalde limieten)

Laten we nu eens kijken hoe een onbekend signaal eruit ziet:

Hoe ik gegevens in een onbekend formaat van magneetband heb hersteld

Dit is de piloottoon. De vorm van het signaal is aanzienlijk anders, maar het is duidelijk dat het signaal bestaat uit herhalende korte pulsen van een bepaalde frequentie. Bij een bemonsteringsfrequentie van 44100 Hz is de afstand tussen de “pieken” ongeveer 48 monsters (wat overeenkomt met een frequentie van ~918 Hz). Laten we dit cijfer onthouden.

Laten we nu naar het gegevensfragment kijken:

Hoe ik gegevens in een onbekend formaat van magneetband heb hersteld

Als we de afstand tussen individuele pulsen meten, blijkt dat de afstand tussen “lange” pulsen nog steeds ~48 samples bedraagt, en tussen korte pulsen – ~24. Als ik een beetje vooruitkijk, zal ik zeggen dat het uiteindelijk bleek dat er continu "referentiepulsen" met een frequentie van 918 Hz volgen, van het begin tot het einde van het bestand. Er kan worden aangenomen dat we bij het verzenden van gegevens, als er tussen de referentiepulsen een extra puls wordt aangetroffen, deze als bit 1 beschouwen, anders als 0.

Hoe zit het met de synchronisatiepuls? Laten we eens kijken naar het begin van de gegevens:

Hoe ik gegevens in een onbekend formaat van magneetband heb hersteld

De piloottoon eindigt en de data beginnen onmiddellijk. Even later, na analyse van verschillende audio-opnamen, konden we ontdekken dat de eerste byte aan gegevens altijd hetzelfde is (10100101b, A5h). Het kan zijn dat de computer gegevens begint te lezen nadat deze deze heeft ontvangen.

Je kunt ook letten op de verschuiving van de eerste referentiepuls onmiddellijk na de laatste 1e in de synchronisatiebyte. Het werd veel later ontdekt tijdens het ontwikkelen van een dataherkenningsprogramma, toen de gegevens aan het begin van het bestand niet stabiel konden worden gelezen.

Laten we nu proberen een algoritme te beschrijven dat een audiobestand verwerkt en gegevens laadt.

Data laden

Laten we eerst eens kijken naar een paar aannames om het algoritme eenvoudig te houden:

  1. We nemen alleen bestanden in WAV-formaat in overweging;
  2. Het audiobestand moet beginnen met een pilottoon en mag aan het begin geen stilte bevatten
  3. Het bronbestand moet een bemonsteringsfrequentie van 44100 Hz hebben. In dit geval is de afstand tussen de referentiepulsen van 48 monsters al bepaald en hoeven we deze niet programmatisch te berekenen;
  4. Het voorbeeldformaat kan elk zijn (8/16 bits/zwevende komma) - omdat we het bij het lezen naar het gewenste formaat kunnen converteren;
  5. We gaan ervan uit dat het bronbestand genormaliseerd is op basis van amplitude, wat het resultaat zou moeten stabiliseren;

Het leesalgoritme is als volgt:

  1. We lezen het bestand in het geheugen en converteren tegelijkertijd het voorbeeldformaat naar 8 bits;
  2. Bepaal de positie van de eerste puls in de audiogegevens. Om dit te doen, moet u het nummer van het monster met de maximale amplitude berekenen. Voor de eenvoud berekenen we het eenmalig handmatig. Laten we het opslaan in de variabele prev_pos;
  3. Voeg 48 toe aan de positie van de laatste puls (pos := prev_pos + 48)
  4. Omdat het verhogen van de positie met 48 niet garandeert dat we de positie van de volgende referentiepuls zullen bereiken (banddefecten, onstabiele werking van het bandaandrijfmechanisme, enz.), moeten we de positie van de pos-puls aanpassen. Om dit te doen, neemt u een klein stukje gegevens (pos-8;pos+8) en zoekt u daarop de maximale amplitudewaarde. De positie die overeenkomt met het maximum wordt opgeslagen in pos. Hier is 8 = 48/6 een experimenteel verkregen constante, die garandeert dat we het juiste maximum zullen bepalen en geen invloed zullen hebben op andere impulsen die mogelijk in de buurt zijn. In zeer slechte gevallen, wanneer de afstand tussen de pulsen veel kleiner of groter is dan 48, kun je een geforceerde zoektocht naar een puls implementeren, maar binnen de reikwijdte van het artikel zal ik dit niet in het algoritme beschrijven;
  5. Bij de vorige stap zou het ook nodig zijn om te controleren of de referentiepuls überhaupt werd gevonden. Dat wil zeggen: als je simpelweg op zoek gaat naar het maximale, garandeert dit niet dat de impuls aanwezig is in dit segment. In mijn nieuwste implementatie van het leesprogramma controleer ik het verschil tussen de maximale en minimale amplitudewaarden op een segment, en als deze een bepaalde limiet overschrijdt, tel ik de aanwezigheid van een impuls. De vraag is ook wat te doen als de referentiepuls niet wordt gevonden. Er zijn 2 opties: óf de gegevens zijn beëindigd en er volgt stilte, óf dit moet als een leesfout worden beschouwd. We zullen dit echter weglaten om het algoritme te vereenvoudigen;
  6. Bij de volgende stap moeten we de aanwezigheid van een datapuls (bit 0 of 1) bepalen, hiervoor nemen we het midden van het segment (prev_pos;pos) middle_pos gelijk aan middle_pos := (prev_pos+pos)/2 en laten we in een bepaalde buurt van middle_pos op het segment (middle_pos-8;middle_pos +8) de maximale en minimale amplitude berekenen. Als het verschil tussen beide groter is dan 10, schrijven we bit 1 in het resultaat, anders 0. 10 is een experimenteel verkregen constante;
  7. Sla de huidige positie op in prev_pos (prev_pos := pos)
  8. Herhaal dit vanaf stap 3 totdat we het hele bestand hebben gelezen;
  9. De resulterende bitarray moet worden opgeslagen als een set bytes. Omdat we bij het lezen geen rekening hebben gehouden met de synchronisatiebyte, is het aantal bits mogelijk geen veelvoud van 8 en is ook de vereiste bit-offset onbekend. Bij de eerste implementatie van het algoritme wist ik niet van het bestaan ​​van de synchronisatiebyte en bewaarde daarom eenvoudigweg 8 bestanden met verschillende aantallen offsetbits. Eén ervan bevatte correcte gegevens. In het uiteindelijke algoritme verwijder ik eenvoudigweg alle bits tot A5h, waardoor ik onmiddellijk het juiste uitvoerbestand krijg

Algoritme in Ruby, voor geïnteresseerden
Ik heb Ruby gekozen als taal voor het schrijven van het programma, omdat... Ik programmeer er meestal op. De optie levert geen hoge prestaties, maar de taak om de leessnelheid zo snel mogelijk te maken is het niet waard.

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

Resultaat

Nadat ik verschillende varianten van het algoritme en de constanten had geprobeerd, had ik het geluk iets buitengewoon interessants te krijgen:

Hoe ik gegevens in een onbekend formaat van magneetband heb hersteld

Dus, afgaande op de tekenreeksen, hebben we een programma voor het plotten van grafieken. Er zijn echter geen trefwoorden in de programmatekst. Alle trefwoorden worden gecodeerd als bytes (elke waarde > 80h). Nu moeten we uitzoeken welke computer uit de jaren 80 programma's in dit formaat kon opslaan.

In feite lijkt het sterk op een BASIC-programma. De ZX Spectrum-computer slaat programma's in ongeveer hetzelfde formaat op in het geheugen en slaat programma's op tape op. Voor de zekerheid heb ik de zoekwoorden gecontroleerd tafel. Het resultaat was echter duidelijk negatief.

Ik controleerde ook de BASIC-sleutelwoorden van de populaire Atari, Commodore 64 en verschillende andere computers uit die tijd, waarvoor ik documentatie kon vinden, maar zonder succes - mijn kennis van de soorten retrocomputers bleek niet zo breed te zijn.

Toen besloot ik te gaan de lijst, en toen viel mijn blik op de naam van de fabrikant Radio Shack en de TRS-80-computer. Dit zijn de namen die op de etiketten van de cassettes stonden die op mijn tafel lagen! Ik kende deze namen niet eerder en was niet bekend met de TRS-80 computer, dus het leek mij dat Radio Shack een fabrikant van audiocassettes was zoals BASF, Sony of TDK, en dat de TRS-80 de afspeeltijd was. Waarom niet?

Computer Tandy/Radio Shack TRS-80

Het is zeer waarschijnlijk dat de betreffende audio-opname, die ik aan het begin van het artikel als voorbeeld gaf, op een computer als deze is gemaakt:

Hoe ik gegevens in een onbekend formaat van magneetband heb hersteld

Het bleek dat deze computer en zijn varianten (Model I/Model III/Model IV, etc.) ooit erg populair waren (natuurlijk niet in Rusland). Het is opmerkelijk dat de processor die ze gebruikten ook Z80 was. Voor deze computer kunt u op internet vinden veel informatie. In de jaren tachtig werd computerinformatie verspreid tijdschriften. Op dit moment zijn er meerdere emulators computers voor verschillende platforms.

Ik heb de emulator gedownload trs80gp en voor het eerst kon ik zien hoe deze computer werkte. Uiteraard ondersteunde de computer geen kleuruitvoer; de schermresolutie was slechts 128x48 pixels, maar er waren veel uitbreidingen en aanpassingen die de schermresolutie konden verhogen. Er waren ook veel opties voor besturingssystemen voor deze computer en opties voor het implementeren van de BASIC-taal (die, in tegenstelling tot de ZX Spectrum, in sommige modellen niet eens in ROM was "geflashed" en elke optie kon van een diskette worden geladen, net als het besturingssysteem zelf)

Ik heb ook gevonden nut om audio-opnamen om te zetten naar CAS-formaat, dat wordt ondersteund door emulators, maar om de een of andere reden was het niet mogelijk om opnames van mijn cassettes te lezen met behulp ervan.

Nadat ik het CAS-bestandsformaat had ontdekt (dat slechts een bit-voor-bit kopie bleek te zijn van de gegevens van de tape die ik al bij de hand had, met uitzondering van de header met de aanwezigheid van een synchronisatiebyte), maakte ik een enkele wijzigingen in mijn programma en ik kon een werkend CAS-bestand uitvoeren dat werkte in de emulator (TRS-80 Model III):

Hoe ik gegevens in een onbekend formaat van magneetband heb hersteld

Ik heb de nieuwste versie van het conversieprogramma met automatische bepaling van de eerste puls en de afstand tussen referentiepulsen ontworpen als GEM-pakket, de broncode is beschikbaar op GitHub.

Conclusie

Het pad dat we hebben afgelegd bleek een fascinerende reis naar het verleden, en ik ben blij dat ik uiteindelijk het antwoord heb gevonden. Ik:

  • Ik ontdekte het formaat voor het opslaan van gegevens in de ZX Spectrum en bestudeerde de ingebouwde ROM-routines voor het opslaan/lezen van gegevens van audiocassettes
  • Ik maakte kennis met de TRS-80-computer en zijn varianten, bestudeerde het besturingssysteem, bekeek voorbeeldprogramma's en kreeg zelfs de kans om te debuggen in machinecodes (alle Z80-ezelsbruggetjes zijn mij immers bekend)
  • Schreef een volwaardig hulpprogramma voor het converteren van audio-opnamen naar CAS-formaat, dat gegevens kan lezen die niet worden herkend door het "officiële" hulpprogramma

Bron: www.habr.com

Voeg een reactie