Hvordan jeg gendannede data i et ukendt format fra magnetbånd

forhistorie

Da jeg er en elsker af retro hardware, købte jeg engang en ZX Spectrum+ fra en sælger i Storbritannien. Med til selve computeren fik jeg flere lydkassetter med spil (i original emballage med instruktioner), samt programmer optaget på kassetter uden særlige markeringer. Overraskende nok var data fra 40 år gamle kassetter godt læsbare, og jeg var i stand til at downloade næsten alle spil og programmer fra dem.

Hvordan jeg gendannede data i et ukendt format fra magnetbånd

På nogle kassetter fandt jeg dog optagelser, der tydeligvis ikke var lavet af ZX Spectrum-computeren. De lød helt anderledes og i modsætning til optagelserne fra den nævnte computer startede de ikke med en kort BASIC bootloader, som normalt er til stede i optagelserne af alle programmer og spil.

I nogen tid forfulgte dette mig - jeg ville virkelig gerne finde ud af, hvad der gemte sig i dem. Hvis du kunne læse lydsignalet som en sekvens af bytes, kunne du kigge efter tegn eller andet, der angiver signalets oprindelse. En slags retro-arkæologi.

Nu hvor jeg er gået hele vejen og kigget på etiketterne på selve kassetterne, smiler jeg pga.

svaret var lige foran mine øjne hele tiden
På etiketten på venstre kassette står navnet på TRS-80 computeren, og lige under navnet på producenten: "Fremstillet af Radio Shack i USA"

(Hvis du vil beholde intrigen til slutningen, skal du ikke gå under spoileren)

Sammenligning af lydsignaler

Lad os først og fremmest digitalisere lydoptagelserne. Du kan lytte til, hvordan det lyder:


Og som sædvanlig lyder optagelsen fra ZX Spectrum-computeren:


I begge tilfælde er der i begyndelsen af ​​optagelsen en såkaldt pilot tone - en lyd med samme frekvens (i den første optagelse er den meget kort <1 sekund, men kan skelnes). Pilottonen signalerer computeren til at forberede sig på at modtage data. Som regel genkender hver computer kun sin "egen" pilottone ved signalets form og frekvens.

Det er nødvendigt at sige noget om selve signalformen. For eksempel på ZX Spectrum er dens form rektangulær:

Hvordan jeg gendannede data i et ukendt format fra magnetbånd

Når en pilottone detekteres, viser ZX Spectrum skiftevis røde og blå bjælker på skærmens kant for at angive, at signalet er blevet genkendt. Pilottonen slutter synkro puls, som signalerer computeren til at begynde at modtage data. Det er karakteriseret ved en kortere varighed (sammenlignet med pilottonen og efterfølgende data) (se figur)

Efter at synkroniseringsimpulsen er modtaget, registrerer computeren hver stigning/fald af signalet og måler dets varighed. Hvis varigheden er mindre end en vis grænse, skrives bit 1 til hukommelsen, ellers 0. Bittene opsamles i bytes, og processen gentages, indtil der modtages N bytes. Nummeret N er normalt taget fra overskriften på den downloadede fil. Indlæsningssekvensen er som følger:

  1. pilot tone
  2. header (fast længde), indeholder størrelsen på de downloadede data (N), filnavn og type
  3. pilot tone
  4. selve dataene

For at sikre, at dataene indlæses korrekt, aflæser ZX Spectrum den såkaldte paritetsbyte (paritetsbyte), som beregnes ved lagring af en fil ved at XORinge alle bytes af de skrevne data. Når du læser en fil, beregner computeren paritetsbyten ud fra de modtagne data, og hvis resultatet afviger fra den gemte, viser den fejlmeddelelsen "R Tape loading error". Strengt taget kan computeren udsende denne meddelelse tidligere, hvis den under læsning ikke kan genkende en puls (misset eller dens varighed svarer ikke til visse grænser)

Så lad os nu se, hvordan et ukendt signal ser ud:

Hvordan jeg gendannede data i et ukendt format fra magnetbånd

Dette er pilottonen. Signalets form er væsentligt anderledes, men det er tydeligt, at signalet består af gentagelse af korte impulser af en bestemt frekvens. Ved en samplingsfrekvens på 44100 Hz er afstanden mellem "toppene" cirka 48 samples (hvilket svarer til en frekvens på ~918 Hz). Lad os huske denne figur.

Lad os nu se på datafragmentet:

Hvordan jeg gendannede data i et ukendt format fra magnetbånd

Hvis vi måler afstanden mellem individuelle pulser, viser det sig, at afstanden mellem "lange" pulser stadig er ~48 prøver, og mellem korte - ~24. Ser jeg lidt fremad, vil jeg sige, at det til sidst viste sig, at "reference"-pulser med en frekvens på 918 Hz følger kontinuerligt, fra begyndelsen til slutningen af ​​filen. Det kan antages, at når der sendes data, hvis der stødes på en ekstra impuls mellem referenceimpulserne, betragter vi den som bit 1, ellers 0.

Hvad med synkroniseringspulsen? Lad os se på begyndelsen af ​​dataene:

Hvordan jeg gendannede data i et ukendt format fra magnetbånd

Pilottonen slutter, og dataene begynder med det samme. Lidt senere, efter at have analyseret flere forskellige lydoptagelser, kunne vi opdage, at den første byte af data altid er den samme (10100101b, A5h). Computeren begynder muligvis at læse data, efter den har modtaget dem.

Du kan også være opmærksom på forskydningen af ​​den første referenceimpuls umiddelbart efter den sidste 1. i sync-byten. Det blev opdaget meget senere i processen med at udvikle et datagenkendelsesprogram, da dataene i begyndelsen af ​​filen ikke kunne læses stabilt.

Lad os nu prøve at beskrive en algoritme, der vil behandle en lydfil og indlæse data.

Indlæser data

Lad os først se på et par antagelser for at holde algoritmen enkel:

  1. Vi vil kun overveje filer i WAV-format;
  2. Lydfilen skal begynde med en pilottone og må ikke indeholde tavshed i begyndelsen
  3. Kildefilen skal have en samplinghastighed på 44100 Hz. I dette tilfælde er afstanden mellem referenceimpulserne for 48 prøver allerede bestemt, og vi behøver ikke at beregne den programmatisk;
  4. Eksempelformatet kan være et hvilket som helst (8/16 bit/flydende komma) - da vi ved læsning kan konvertere det til det ønskede;
  5. Vi antager, at kildefilen er normaliseret af amplitude, hvilket burde stabilisere resultatet;

Læsealgoritmen vil være som følger:

  1. Vi læser filen ind i hukommelsen og konverterer samtidig prøveformatet til 8 bit;
  2. Bestem positionen af ​​den første impuls i lyddataene. For at gøre dette skal du beregne antallet af prøven med den maksimale amplitude. For nemheds skyld vil vi beregne det én gang manuelt. Lad os gemme det i prev_pos-variablen;
  3. Tilføj 48 til positionen for den sidste puls (pos := prev_pos + 48)
  4. Da en forøgelse af positionen med 48 ikke garanterer, at vi kommer til positionen for den næste referenceimpuls (bånddefekter, ustabil drift af bånddrevmekanismen osv.), er vi nødt til at justere positionen af ​​posimpulsen. For at gøre dette skal du tage et lille stykke data (pos-8;pos+8) og finde den maksimale amplitudeværdi på det. Positionen svarende til maksimum vil blive gemt i pos. Her er 8 = 48/6 en eksperimentelt opnået konstant, som garanterer, at vi vil bestemme det korrekte maksimum og ikke vil påvirke andre impulser, der måtte være i nærheden. I meget dårlige tilfælde, når afstanden mellem pulser er meget mindre end eller større end 48, kan du implementere en tvungen søgning efter en puls, men inden for artiklens rammer vil jeg ikke beskrive dette i algoritmen;
  5. Ved det foregående trin ville det også være nødvendigt at kontrollere, at referenceimpulsen overhovedet blev fundet. Det vil sige, at hvis du blot leder efter det maksimale, er dette ikke en garanti for, at impulsen er til stede i dette segment. I min seneste implementering af læseprogrammet kontrollerer jeg forskellen mellem de maksimale og minimale amplitudeværdier på et segment, og hvis det overskrider en vis grænse, tæller jeg tilstedeværelsen af ​​en impuls. Spørgsmålet er også, hvad man skal gøre, hvis referencepulsen ikke findes. Der er 2 muligheder: enten er dataene afsluttet, og tavshed følger, eller også skal dette betragtes som en læsefejl. Vi vil dog udelade dette for at forenkle algoritmen;
  6. På næste trin skal vi bestemme tilstedeværelsen af ​​en datapuls (bit 0 eller 1), for dette tager vi midten af ​​segmentet (prev_pos;pos) middle_pos lig med middle_pos := (prev_pos+pos)/2 og Lad os i et eller andet område af middle_pos på segmentet (middle_pos-8;middle_pos +8) beregne den maksimale og minimale amplitude. Hvis forskellen mellem dem er mere end 10, skriver vi bit 1 i resultatet, ellers er 0. 10 er en konstant opnået eksperimentelt;
  7. Gem den aktuelle position i prev_pos (prev_pos := pos)
  8. Gentag fra trin 3, indtil vi læser hele filen;
  9. Det resulterende bitarray skal gemmes som et sæt bytes. Da vi ikke tog hensyn til synkroniseringsbyten ved læsning, er antallet af bit muligvis ikke et multiplum af 8, og den nødvendige bitoffset er heller ikke kendt. I den første implementering af algoritmen kendte jeg ikke til eksistensen af ​​sync-byten og gemte derfor blot 8 filer med forskelligt antal offset-bits. En af dem indeholdt korrekte data. I den endelige algoritme fjerner jeg simpelthen alle bits op til A5h, hvilket giver mig mulighed for straks at få den korrekte outputfil

Algoritme i Ruby, for de interesserede
Jeg valgte Ruby som sprog for at skrive programmet, fordi... Jeg programmerer på det det meste af tiden. Muligheden er ikke højtydende, men opgaven med at gøre læsehastigheden så hurtig som muligt er ikke det værd.

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

Outcome

Efter at have prøvet flere varianter af algoritmen og konstanterne, var jeg heldig at få noget ekstremt interessant:

Hvordan jeg gendannede data i et ukendt format fra magnetbånd

Så ud fra tegnstrengene at dømme har vi et program til at plotte grafer. Der er dog ingen nøgleord i programteksten. Alle nøgleord er kodet som bytes (hver værdi > 80 timer). Nu skal vi finde ud af, hvilken computer fra 80'erne der kunne gemme programmer i dette format.

Faktisk minder det meget om et BASIC-program. ZX Spectrum-computeren gemmer programmer i omtrent det samme format i hukommelsen og gemmer programmer på bånd. For en sikkerheds skyld tjekkede jeg søgeordene op imod bord. Resultatet var dog åbenbart negativt.

Jeg tjekkede også BASIC søgeordene på de populære Atari, Commodore 64 og adskillige andre computere fra den tid, som jeg kunne finde dokumentation for, men uden held - min viden om typerne af retro-computere viste sig ikke at være så bred.

Så besluttede jeg at gå listen, og så faldt mit blik på navnet på producenten Radio Shack og TRS-80 computeren. Det er de navne, der var skrevet på etiketterne på de kassetter, der lå på mit bord! Jeg kendte ikke disse navne før og var ikke bekendt med TRS-80 computeren, så det forekom mig, at Radio Shack var en lydkassetteproducent som BASF, Sony eller TDK, og TRS-80 var afspilningstiden. Hvorfor ikke?

Computer Tandy/Radio Shack TRS-80

Det er meget sandsynligt, at den pågældende lydoptagelse, som jeg gav som eksempel i begyndelsen af ​​artiklen, er lavet på en computer som denne:

Hvordan jeg gendannede data i et ukendt format fra magnetbånd

Det viste sig, at denne computer og dens varianter (Model I/Model III/Model IV osv.) var meget populære på et tidspunkt (selvfølgelig ikke i Rusland). Det er bemærkelsesværdigt, at den processor, de brugte, også var Z80. Til denne computer kan du finde på internettet en masse information. I 80'erne blev computerinformationer distribueret i magasiner. I øjeblikket er der flere emulatorer computere til forskellige platforme.

Jeg downloadede emulatoren trs80gp og for første gang var jeg i stand til at se, hvordan denne computer fungerede. Computeren understøttede selvfølgelig ikke farveoutput, skærmopløsningen var kun 128x48 pixels, men der var mange udvidelser og ændringer, der kunne øge skærmopløsningen. Der var også mange muligheder for operativsystemer til denne computer og muligheder for at implementere BASIC-sproget (som i modsætning til ZX Spectrum i nogle modeller ikke engang blev "flashet" til ROM, og enhver mulighed kunne indlæses fra en diskette, ligesom selve OS)

fandt jeg også nytte at konvertere lydoptagelser til CAS-format, som understøttes af emulatorer, men af ​​en eller anden grund var det ikke muligt at læse optagelser fra mine kassetter ved hjælp af dem.

Efter at have fundet ud af CAS-filformatet (som viste sig kun at være en bit-for-bit kopi af dataene fra båndet, som jeg allerede havde ved hånden, bortset fra headeren med tilstedeværelsen af ​​en synkroniseringsbyte), lavede jeg en få ændringer til mit program og var i stand til at udlæse en fungerende CAS-fil, der fungerede i emulatoren (TRS-80 Model III):

Hvordan jeg gendannede data i et ukendt format fra magnetbånd

Jeg designede den seneste version af konverteringsværktøjet med automatisk bestemmelse af den første puls og afstanden mellem referenceimpulser som en GEM-pakke, kildekoden er tilgængelig på Github.

Konklusion

Vejen, vi har gået, viste sig at være en fascinerende rejse ind i fortiden, og jeg er glad for, at jeg til sidst fandt svaret. Jeg har blandt andet:

  • Jeg fandt ud af formatet til at gemme data i ZX Spectrum og studerede de indbyggede ROM-rutiner til at gemme/læse data fra lydkassetter
  • Jeg stiftede bekendtskab med TRS-80-computeren og dens varianter, studerede operativsystemet, så på eksempler på programmer og havde endda mulighed for at foretage fejlfinding i maskinkoder (trods alt er alle Z80-mnemonics velkendte for mig)
  • Skrev et fuldgyldigt værktøj til at konvertere lydoptagelser til CAS-format, som kan læse data, der ikke genkendes af det "officielle" værktøj

Kilde: www.habr.com

Tilføj en kommentar