Hoe ek data in 'n onbekende formaat van magnetiese band herwin het

voorgeskiedenis

Omdat ek 'n liefhebber van retro-hardeware was, het ek een keer 'n ZX Spectrum+ by 'n verkoper in die Verenigde Koninkryk gekoop. By die rekenaar self ingesluit, het ek verskeie oudiokassette met speletjies ontvang (in die oorspronklike verpakking met instruksies), asook programme wat op kassette opgeneem is sonder spesiale merke. Verbasend genoeg was data van 40 jaar oue kassette goed leesbaar en ek kon byna al die speletjies en programme daarvan aflaai.

Hoe ek data in 'n onbekende formaat van magnetiese band herwin het

Op sommige kassette het ek egter opnames gevind wat duidelik nie deur die ZX Spectrum-rekenaar gemaak is nie. Hulle het heeltemal anders geklink en, anders as die opnames van die genoemde rekenaar, het hulle nie begin met 'n kort BASIC selflaaiprogram, wat gewoonlik in die opnames van alle programme en speletjies voorkom nie.

Vir ’n geruime tyd het dit by my gespook – ek wou baie graag uitvind wat in hulle versteek is. As jy die oudiosein as 'n reeks grepe kan lees, kan jy soek vir karakters of enigiets wat die oorsprong van die sein aandui. ’n Soort retro-argeologie.

Noudat ek al die pad gegaan het en na die etikette van die kassette self gekyk het, glimlag ek omdat

die antwoord was heeltyd reg voor my oë
Op die etiket van die linkerkasset is die naam van die TRS-80-rekenaar, en net onder die naam van die vervaardiger: "Vervaardig deur Radio Shack in die VSA"

(As jy die intrige tot die einde wil hou, moenie onder die bederf gaan nie)

Vergelyking van oudio seine

Eerstens, kom ons digitaliseer die klankopnames. Jy kan luister hoe dit klink:


En soos gewoonlik klink die opname vanaf die ZX Spectrum-rekenaar:


In beide gevalle is daar aan die begin van die opname 'n sg vlieënier toon - 'n klank van dieselfde frekwensie (in die eerste opname is dit baie kort <1 sekonde, maar is onderskeibaar). Die loodstoon dui aan die rekenaar om voor te berei om data te ontvang. As 'n reël herken elke rekenaar slegs sy "eie" loodstoon aan die vorm van die sein en sy frekwensie.

Dit is nodig om iets te sê oor die seinvorm self. Byvoorbeeld, op die ZX Spectrum is sy vorm reghoekig:

Hoe ek data in 'n onbekende formaat van magnetiese band herwin het

Wanneer 'n vlieëniertoon bespeur word, vertoon die ZX Spectrum afwisselende rooi en blou strepies op die grens van die skerm om aan te dui dat die sein herken is. Pilot toon eindig sinchro pols, wat die rekenaar aandui om data te begin ontvang. Dit word gekenmerk deur 'n korter duur (in vergelyking met die loodstoon en daaropvolgende data) (sien figuur)

Nadat die sinkroniseringspuls ontvang is, teken die rekenaar elke styging/daling van die sein aan, en meet die duur daarvan. As die tydsduur minder as 'n sekere limiet is, word bis 1 na die geheue geskryf, anders 0. Die bisse word in grepe versamel en die proses word herhaal totdat N grepe ontvang is. Die nommer N word gewoonlik uit die kopskrif van die afgelaaide lêer geneem. Die laaivolgorde is soos volg:

  1. vlieënier toon
  2. kopskrif (vaste lengte), bevat die grootte van die afgelaaide data (N), lêernaam en tipe
  3. vlieënier toon
  4. die data self

Om seker te maak dat die data korrek gelaai is, lees die ZX Spectrum die sg pariteit byte (pariteitgreep), wat bereken word wanneer 'n lêer gestoor word deur alle grepe van die geskrewe data te XOR. Wanneer 'n lêer gelees word, bereken die rekenaar die pariteitgreep uit die ontvangde data en, as die resultaat van die gestoorde een verskil, vertoon die foutboodskap "R Tape loading error". Streng gesproke kan die rekenaar hierdie boodskap vroeër uitreik as dit, tydens lees, nie 'n pols herken nie (gemis of die duur daarvan stem nie ooreen met sekere perke nie)

So, kom ons kyk nou hoe 'n onbekende sein lyk:

Hoe ek data in 'n onbekende formaat van magnetiese band herwin het

Dit is die loodstoon. Die vorm van die sein verskil aansienlik, maar dit is duidelik dat die sein bestaan ​​uit die herhaling van kort pulse van 'n sekere frekwensie. By 'n steekproeffrekwensie van 44100 Hz is die afstand tussen die "pieke" ongeveer 48 monsters (wat ooreenstem met 'n frekwensie van ~918 Hz). Kom ons onthou hierdie syfer.

Kom ons kyk nou na die datafragment:

Hoe ek data in 'n onbekende formaat van magnetiese band herwin het

As ons die afstand tussen individuele pulse meet, blyk dit dat die afstand tussen "lang" pulse steeds ~48 monsters is, en tussen kortes - ~24. As ek 'n bietjie vorentoe kyk, sal ek sê dat dit op die ou end geblyk het dat "verwysings"-pulse met 'n frekwensie van 918 Hz voortdurend volg, van die begin tot die einde van die lêer. Dit kan aanvaar word dat wanneer data oorgedra word, as 'n bykomende puls tussen die verwysingspulse teëgekom word, ons dit as bis 1 beskou, anders 0.

Wat van die sinchronisasie-puls? Kom ons kyk na die begin van die data:

Hoe ek data in 'n onbekende formaat van magnetiese band herwin het

Die loodstoon eindig en die data begin onmiddellik. 'n Bietjie later, nadat ons verskeie verskillende oudio-opnames ontleed het, kon ons ontdek dat die eerste greep data altyd dieselfde is (10100101b, A5h). Die rekenaar kan begin om data te lees nadat dit dit ontvang het.

U kan ook aandag gee aan die verskuiwing van die eerste verwysingspuls onmiddellik na die laaste 1ste in die sinchronisasiegreep. Dit is baie later ontdek in die proses om 'n dataherkenningsprogram te ontwikkel, toe die data aan die begin van die lêer nie stabiel gelees kon word nie.

Kom ons probeer nou 'n algoritme beskryf wat 'n oudiolêer sal verwerk en data sal laai.

Laai data

Kom ons kyk eers na 'n paar aannames om die algoritme eenvoudig te hou:

  1. Ons sal slegs lêers in WAV-formaat oorweeg;
  2. Die oudiolêer moet met 'n loodstoon begin en moet nie stilte aan die begin bevat nie
  3. Die bronlêer moet 'n monstertempo van 44100 Hz hê. In hierdie geval is die afstand tussen die verwysingspulse van 48 monsters reeds bepaal en hoef ons dit nie programmaties te bereken nie;
  4. Die voorbeeldformaat kan enige (8/16 bisse/swewende punt) wees - aangesien ons dit tydens lees kan omskakel na die gewenste een;
  5. Ons neem aan dat die bronlêer genormaliseer word deur amplitude, wat die resultaat behoort te stabiliseer;

Die leesalgoritme sal soos volg wees:

  1. Ons lees die lêer in die geheue, en skakel terselfdertyd die voorbeeldformaat om na 8 bisse;
  2. Bepaal die posisie van die eerste puls in die oudiodata. Om dit te doen, moet jy die nommer van die monster met die maksimum amplitude bereken. Vir eenvoud, sal ons dit een keer met die hand bereken. Kom ons stoor dit na die veranderlike prev_pos;
  3. Voeg 48 by die posisie van die laaste puls (pos := prev_pos + 48)
  4. Aangesien die verhoging van die posisie met 48 nie waarborg dat ons by die posisie van die volgende verwysingspuls sal uitkom nie (banddefekte, onstabiele werking van die bandaandrywingmeganisme, ens.), moet ons die posisie van die pospuls aanpas. Om dit te doen, neem 'n klein stukkie data (pos-8;pos+8) en vind die maksimum amplitudewaarde daarop. Die posisie wat ooreenstem met die maksimum sal in pos gestoor word. Hier is 8 = 48/6 'n eksperimenteel verkry konstante, wat waarborg dat ons die korrekte maksimum sal bepaal en nie ander impulse wat naby mag wees, sal beïnvloed nie. In baie slegte gevalle, wanneer die afstand tussen pulse baie minder as of groter as 48 is, kan jy 'n gedwonge soektog na 'n puls implementeer, maar binne die bestek van die artikel sal ek dit nie in die algoritme beskryf nie;
  5. By die vorige stap sou dit ook nodig wees om te kontroleer dat die verwysingspuls enigsins gevind is. Dit wil sê, as jy bloot na die maksimum soek, waarborg dit nie dat die impuls in hierdie segment teenwoordig is nie. In my nuutste implementering van die leesprogram kyk ek na die verskil tussen die maksimum en minimum amplitudewaardes op 'n segment, en as dit 'n sekere limiet oorskry, tel ek die teenwoordigheid van 'n impuls. Die vraag is ook wat om te doen as die verwysingspuls nie gevind word nie. Daar is 2 opsies: óf die data het geëindig en stilte volg, óf dit moet as 'n leesfout beskou word. Ons sal dit egter weglaat om die algoritme te vereenvoudig;
  6. By die volgende stap moet ons die teenwoordigheid van 'n datapuls (bis 0 of 1) bepaal, hiervoor neem ons die middel van die segment (prev_pos;pos) middle_pos gelyk aan middle_pos := (prev_pos+pos)/2 en in een of ander omgewing van middle_pos op die segment (middle_pos-8;middle_pos +8) kom ons bereken die maksimum en minimum amplitude. As die verskil tussen hulle meer as 10 is, skryf ons bietjie 1 in die resultaat, anders 0. 10 is 'n konstante wat eksperimenteel verkry word;
  7. Stoor die huidige posisie in prev_pos (prev_pos := pos)
  8. Herhaal vanaf stap 3 totdat ons die hele lêer gelees het;
  9. Die resulterende bis-skikking moet as 'n stel grepe gestoor word. Aangesien ons nie die sinchronisasiegreep in ag geneem het tydens lees nie, is die aantal bisse dalk nie 'n veelvoud van 8 nie, en die vereiste bisverskuiwing is ook onbekend. In die eerste implementering van die algoritme het ek nie geweet van die bestaan ​​van die sinchronisasiegreep nie en daarom het ek eenvoudig 8 lêers met verskillende getalle offsetbis gestoor. Een van hulle het korrekte data bevat. In die finale algoritme verwyder ek eenvoudig alle bisse tot A5h, wat my in staat stel om dadelik die korrekte uitvoerlêer te kry

Algoritme in Ruby, vir diegene wat belangstel
Ek het Ruby gekies as die taal vir die skryf van die program, want... Ek programmeer die meeste van die tyd daarop. Die opsie is nie hoëprestasie nie, maar die taak om die leesspoed so vinnig as moontlik te maak, is nie die moeite werd nie.

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

Gevolg

Nadat ek verskeie variante van die algoritme en konstantes probeer het, was ek gelukkig om iets uiters interessant te kry:

Hoe ek data in 'n onbekende formaat van magnetiese band herwin het

Dus, te oordeel aan die karakterstringe, het ons 'n program om grafieke te teken. Daar is egter geen sleutelwoorde in die programteks nie. Alle sleutelwoorde word as grepe geënkodeer (elke waarde > 80h). Nou moet ons uitvind watter rekenaar uit die 80's programme in hierdie formaat kon stoor.

Trouens, dit is baie soortgelyk aan 'n BASIESE program. Die ZX Spectrum-rekenaar stoor programme in ongeveer dieselfde formaat in die geheue en stoor programme op band. Net vir ingeval, ek het die sleutelwoorde daarteen nagegaan tafel. Die resultaat was egter natuurlik negatief.

Ek het ook die BASIESE sleutelwoorde van die gewilde Atari, Commodore 64 en verskeie ander rekenaars van daardie tyd nagegaan, waarvoor ek dokumentasie kon vind, maar sonder sukses - my kennis van die tipe retro-rekenaars het geblyk nie so wyd te wees nie.

Toe besluit ek om te gaan die lys, en toe val my blik op die naam van die vervaardiger Radio Shack en die TRS-80-rekenaar. Dit is die name wat op die etikette van die kassette wat op my tafel gelê het geskryf was! Ek het nie voorheen hierdie name geken nie en was nie vertroud met die TRS-80-rekenaar nie, so dit het vir my gelyk of Radio Shack 'n oudiokassetvervaardiger soos BASF, Sony of TDK was, en die TRS-80 was die afspeeltyd. Hoekom nie?

Rekenaar Tandy/Radio Shack TRS-80

Dit is baie waarskynlik dat die betrokke klankopname, wat ek as voorbeeld aan die begin van die artikel gegee het, op 'n rekenaar soos hierdie gemaak is:

Hoe ek data in 'n onbekende formaat van magnetiese band herwin het

Dit het geblyk dat hierdie rekenaar en sy variëteite (Model I/Model III/Model IV, ens.) op 'n tyd baie gewild was (natuurlik nie in Rusland nie). Dit is opmerklik dat die verwerker wat hulle gebruik het ook Z80 was. Vir hierdie rekenaar kan jy op die internet vind baie inligting. In die 80's is rekenaarinligting versprei in tydskrifte. Op die oomblik is daar verskeie emulators rekenaars vir verskillende platforms.

Ek het die emulator afgelaai trs80gp en vir die eerste keer kon ek sien hoe hierdie rekenaar werk. Natuurlik het die rekenaar nie kleuruitvoer ondersteun nie; die skermresolusie was slegs 128x48 pixels, maar daar was baie uitbreidings en wysigings wat die skermresolusie kon verhoog. Daar was ook baie opsies vir bedryfstelsels vir hierdie rekenaar en opsies vir die implementering van die BASIC-taal (wat, anders as die ZX Spectrum, in sommige modelle nie eens in ROM "geflits" is nie en enige opsie van 'n disket gelaai kon word, net soos die bedryfstelsel self)

Ek het ook gevind nut om oudio-opnames in CAS-formaat om te skakel, wat deur emulators ondersteun word, maar om een ​​of ander rede was dit nie moontlik om opnames van my kassette te lees deur dit te gebruik nie.

Nadat ek die CAS-lêerformaat uitgepluis het (wat blykbaar net 'n bietjie-vir-bietjie kopie was van die data van die band wat ek reeds byderhand gehad het, behalwe vir die kopskrif met die teenwoordigheid van 'n sinchronisasiegreep), het ek 'n 'n paar veranderinge aan my program en kon 'n werkende CAS-lêer uitvoer wat in die emulator gewerk het (TRS-80 Model III):

Hoe ek data in 'n onbekende formaat van magnetiese band herwin het

Ek het die nuutste weergawe van die omskakelingshulpmiddel ontwerp met outomatiese bepaling van die eerste puls en die afstand tussen verwysingspulse as 'n GEM-pakket, die bronkode is beskikbaar by GitHub.

Gevolgtrekking

Die pad wat ons afgelê het, was 'n fassinerende reis na die verlede, en ek is bly dat ek uiteindelik die antwoord gevind het. Ek het onder andere:

  • Ek het die formaat vir die stoor van data in die ZX Spectrum uitgepluis en die ingeboude ROM-roetines bestudeer vir die stoor/lees van data vanaf oudiokassette
  • Ek het kennis gemaak met die TRS-80-rekenaar en sy variëteite, die bedryfstelsel bestudeer, na voorbeeldprogramme gekyk en selfs die geleentheid gehad om ontfouting in masjienkodes te doen (al die Z80-mnemonics is immers aan my bekend)
  • Het 'n volwaardige program geskryf om oudio-opnames na CAS-formaat om te skakel, wat data kan lees wat nie deur die "amptelike" program herken word nie

Bron: will.com

Voeg 'n opmerking