Hur jag återställde data i ett okänt format från magnetband

förhistoria

Eftersom jag är en älskare av retrohårdvara köpte jag en gång en ZX Spectrum+ från en säljare i Storbritannien. Med själva datorn fick jag flera ljudkassetter med spel (i originalförpackningen med instruktioner), samt program inspelade på kassetter utan speciella märkningar. Överraskande nog var data från 40 år gamla kassetter läsbara och jag kunde ladda ner nästan alla spel och program från dem.

Hur jag återställde data i ett okänt format från magnetband

På vissa kassetter hittade jag dock inspelningar som helt klart inte gjordes av ZX Spectrum-datorn. De lät helt annorlunda och till skillnad från inspelningarna från den nämnda datorn startade de inte med en kort BASIC bootloader, som vanligtvis finns i inspelningarna av alla program och spel.

Under en tid förföljde detta mig - jag ville verkligen ta reda på vad som gömde sig i dem. Om du kunde läsa ljudsignalen som en sekvens av bytes, kan du leta efter tecken eller något som indikerar signalens ursprung. En sorts retroarkeologi.

Nu när jag har gått hela vägen och tittat på etiketterna på själva kassetterna ler jag eftersom

svaret låg mitt framför mina ögon hela tiden
På etiketten på den vänstra kassetten står namnet på TRS-80-datorn och precis under namnet på tillverkaren: "Tillverkad av Radio Shack i USA"

(Om du vill behålla intrigen till slutet, gå inte under spoilern)

Jämförelse av ljudsignaler

Låt oss först och främst digitalisera ljudinspelningarna. Du kan lyssna på hur det låter:


Och som vanligt låter inspelningen från ZX Spectrum-datorn:


I båda fallen finns i början av inspelningen en sk pilotton - ett ljud med samma frekvens (i den första inspelningen är det mycket kort <1 sekund, men är urskiljbart). Pilottonen signalerar till datorn att förbereda sig för att ta emot data. Som regel känner varje dator bara igen sin "egen" pilotton genom formen på signalen och dess frekvens.

Det är nödvändigt att säga något om själva signalformen. Till exempel, på ZX Spectrum är dess form rektangulär:

Hur jag återställde data i ett okänt format från magnetband

När en pilotton detekteras visar ZX Spectrum alternerande röda och blå staplar på skärmens kant för att indikera att signalen har identifierats. Pilottonen slutar synkropuls, som signalerar datorn att börja ta emot data. Den kännetecknas av en kortare varaktighet (jämfört med pilottonen och efterföljande data) (se figur)

Efter att synkpulsen har tagits emot registrerar datorn varje ökning/fall av signalen och mäter dess varaktighet. Om varaktigheten är mindre än en viss gräns skrivs bit 1 till minnet, annars 0. Bitarna samlas in i byte och processen upprepas tills N byte tas emot. Siffran N tas vanligtvis från rubriken på den nedladdade filen. Laddningssekvensen är som följer:

  1. pilotton
  2. header (fast längd), innehåller storleken på nedladdade data (N), filnamn och typ
  3. pilotton
  4. själva datan

För att säkerställa att datan laddas korrekt läser ZX Spectrum av sk paritetsbyte (paritetsbyte), som beräknas när en fil sparas genom att XORing av alla bytes av skrivna data. När du läser en fil, beräknar datorn paritetsbyten från mottagna data och, om resultatet skiljer sig från den sparade, visar felmeddelandet "R Tape loading error". Strängt taget kan datorn avge detta meddelande tidigare om den vid läsning inte kan känna igen en puls (missad eller dess varaktighet inte motsvarar vissa gränser)

Så låt oss nu se hur en okänd signal ser ut:

Hur jag återställde data i ett okänt format från magnetband

Detta är pilottonen. Signalens form är avsevärt annorlunda, men det är tydligt att signalen består av upprepade korta pulser med en viss frekvens. Vid en samplingsfrekvens på 44100 Hz är avståndet mellan "topparna" ungefär 48 sampel (vilket motsvarar en frekvens på ~918 Hz). Låt oss komma ihåg denna siffra.

Låt oss nu titta på datafragmentet:

Hur jag återställde data i ett okänt format från magnetband

Om vi ​​mäter avståndet mellan enskilda pulser visar det sig att avståndet mellan "långa" pulser fortfarande är ~48 sampel och mellan korta - ~24. Ser jag lite framåt kommer jag att säga att det till slut visade sig att "referens"-pulser med en frekvens på 918 Hz följer kontinuerligt, från början till slutet av filen. Det kan antas att vid sändning av data, om en ytterligare puls påträffas mellan referenspulserna, betraktar vi den som bit 1, annars 0.

Hur är det med synkpulsen? Låt oss titta på början av data:

Hur jag återställde data i ett okänt format från magnetband

Pilottonen slutar och data börjar omedelbart. Lite senare, efter att ha analyserat flera olika ljudinspelningar, kunde vi upptäcka att den första byten med data alltid är densamma (10100101b, A5h). Datorn kan börja läsa data efter att den tagit emot det.

Du kan också vara uppmärksam på förskjutningen av den första referenspulsen omedelbart efter den sista 1:an i synkbyten. Det upptäcktes mycket senare i processen att utveckla ett dataigenkänningsprogram, när data i början av filen inte kunde läsas stabilt.

Låt oss nu försöka beskriva en algoritm som kommer att bearbeta en ljudfil och ladda data.

Laddar data

Låt oss först titta på några antaganden för att hålla algoritmen enkel:

  1. Vi kommer endast att överväga filer i WAV-format;
  2. Ljudfilen måste börja med en pilotton och får inte innehålla tystnad i början
  3. Källfilen måste ha en samplingshastighet på 44100 Hz. I detta fall är avståndet mellan referenspulserna för 48 sampel redan bestämt och vi behöver inte beräkna det programmatiskt;
  4. Exempelformatet kan vara vilket som helst (8/16 bitar/flytande komma) - eftersom vi vid läsning kan konvertera det till önskat;
  5. Vi antar att källfilen är normaliserad med amplitud, vilket bör stabilisera resultatet;

Läsalgoritmen kommer att vara följande:

  1. Vi läser in filen i minnet och konverterar samtidigt provformatet till 8 bitar;
  2. Bestäm positionen för den första pulsen i ljuddata. För att göra detta måste du beräkna antalet prov med maximal amplitud. För enkelhetens skull kommer vi att beräkna det en gång manuellt. Låt oss spara den i variabeln prev_pos;
  3. Lägg till 48 till positionen för den sista pulsen (pos := prev_pos + 48)
  4. Eftersom en ökning av positionen med 48 inte garanterar att vi kommer till positionen för nästa referenspuls (banddefekter, instabil drift av banddrivmekanismen, etc.), måste vi justera positionen för pospulsen. För att göra detta, ta en liten bit data (pos-8;pos+8) och hitta det maximala amplitudvärdet på den. Positionen som motsvarar maximum kommer att lagras i pos. Här är 8 = 48/6 en experimentellt erhållen konstant, som garanterar att vi kommer att bestämma det korrekta maximumet och inte påverkar andra impulser som kan finnas i närheten. I mycket dåliga fall, när avståndet mellan pulserna är mycket mindre än eller större än 48, kan man implementera en påtvingad sökning efter en puls, men inom ramen för artikeln kommer jag inte att beskriva detta i algoritmen;
  5. Vid föregående steg skulle det också vara nödvändigt att kontrollera att referenspulsen överhuvudtaget hittades. Det vill säga, om du helt enkelt letar efter det maximala, garanterar inte detta att impulsen finns i detta segment. I min senaste implementering av läsprogrammet kontrollerar jag skillnaden mellan maximala och lägsta amplitudvärden på ett segment, och om det överskrider en viss gräns, räknar jag närvaron av en impuls. Frågan är också vad man ska göra om referenspulsen inte hittas. Det finns två alternativ: antingen har data avslutats och tystnad följer, eller så bör detta betraktas som ett läsfel. Vi kommer dock att utelämna detta för att förenkla algoritmen;
  6. I nästa steg måste vi bestämma närvaron av en datapuls (bit 0 eller 1), för detta tar vi mitten av segmentet (prev_pos;pos) middle_pos lika med middle_pos := (prev_pos+pos)/2 och i något område av middle_pos på segmentet (middle_pos-8;middle_pos +8) låt oss beräkna den maximala och minsta amplituden. Om skillnaden mellan dem är mer än 10, skriver vi bit 1 i resultatet, annars 0. 10 är en konstant som erhålls experimentellt;
  7. Spara den aktuella positionen i prev_pos (prev_pos := pos)
  8. Upprepa från steg 3 tills vi läser hela filen;
  9. Den resulterande bitarrayen måste sparas som en uppsättning byte. Eftersom vi inte tog hänsyn till synkbyten vid läsning, kanske antalet bitar inte är en multipel av 8, och den nödvändiga bitoffseten är också okänd. I den första implementeringen av algoritmen visste jag inte om existensen av synkroniseringsbyten och sparade därför helt enkelt 8 filer med olika antal offsetbitar. En av dem innehöll korrekta uppgifter. I den slutliga algoritmen tar jag helt enkelt bort alla bitar upp till A5h, vilket gör att jag omedelbart kan få rätt utdatafil

Algoritm i Ruby, för den som är intresserad
Jag valde Ruby som språk för att skriva programmet, eftersom... Jag programmerar på det för det mesta. Alternativet är inte högpresterande, men uppgiften att göra läshastigheten så snabb som möjligt är inte värt det.

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

Resultat

Efter att ha provat flera varianter av algoritmen och konstanterna hade jag turen att få något extremt intressant:

Hur jag återställde data i ett okänt format från magnetband

Så, att döma av teckensträngarna, har vi ett program för att rita grafer. Det finns dock inga nyckelord i programtexten. Alla nyckelord är kodade som byte (varje värde > 80h). Nu måste vi ta reda på vilken dator från 80-talet som kunde spara program i detta format.

Faktum är att det är väldigt likt ett BASIC-program. ZX Spectrum-datorn lagrar program i ungefär samma format i minnet och sparar program på band. För säkerhets skull kollade jag sökorden mot tabell. Resultatet var dock uppenbart negativt.

Jag kollade också BASIC nyckelorden för de populära Atari, Commodore 64 och flera andra datorer från den tiden, för vilka jag kunde hitta dokumentation, men utan framgång - min kunskap om typerna av retrodatorer visade sig inte vara så bred.

Sedan bestämde jag mig för att gå listan, och då föll min blick på namnet på tillverkaren Radio Shack och TRS-80-datorn. Det här är namnen som stod på etiketterna på kassetterna som låg på mitt bord! Jag kände inte till dessa namn tidigare och var inte bekant med TRS-80-datorn, så det verkade för mig som om Radio Shack var en ljudkassetttillverkare som BASF, Sony eller TDK, och TRS-80 var uppspelningstiden. Varför inte?

Dator Tandy/Radio Shack TRS-80

Det är mycket troligt att ljudinspelningen i fråga, som jag gav som exempel i början av artikeln, gjordes på en dator som denna:

Hur jag återställde data i ett okänt format från magnetband

Det visade sig att den här datorn och dess varianter (Model I/Model III/Model IV, etc.) var mycket populära på en gång (naturligtvis inte i Ryssland). Det är anmärkningsvärt att processorn de använde också var Z80. För denna dator kan du hitta på Internet mycket information. På 80-talet distribuerades datorinformation i tidningar. För tillfället finns det flera emulatorer datorer för olika plattformar.

Jag laddade ner emulatorn trs80gp och för första gången kunde jag se hur den här datorn fungerade. Datorn stödde naturligtvis inte färgutdata, skärmupplösningen var bara 128x48 pixlar, men det fanns många tillägg och modifieringar som kunde öka skärmupplösningen. Det fanns också många alternativ för operativsystem för den här datorn och alternativ för att implementera BASIC-språket (som, till skillnad från ZX Spectrum, i vissa modeller inte ens "flashades" till ROM och alla alternativ kunde laddas från en diskett, precis som själva operativsystemet)

Jag hittade också verktyg att konvertera ljudinspelningar till CAS-format, som stöds av emulatorer, men av någon anledning gick det inte att läsa inspelningar från mina kassetter med hjälp av dem.

Efter att ha räknat ut CAS-filformatet (som visade sig vara bara en bit-för-bit kopia av data från bandet som jag redan hade till hands, förutom rubriken med närvaron av en synkroniseringsbyte), gjorde jag en några ändringar i mitt program och kunde mata ut en fungerande CAS-fil som fungerade i emulatorn (TRS-80 Model III):

Hur jag återställde data i ett okänt format från magnetband

Jag designade den senaste versionen av konverteringsverktyget med automatisk bestämning av den första pulsen och avståndet mellan referenspulserna som ett GEM-paket, källkoden finns på Github.

Slutsats

Vägen vi har färdats visade sig vara en fascinerande resa in i det förflutna, och jag är glad att jag till slut hittade svaret. Jag har bland annat:

  • Jag kom på formatet för att spara data i ZX Spectrum och studerade de inbyggda ROM-rutinerna för att spara/läsa data från ljudkassetter
  • Jag bekantade mig med TRS-80-datorn och dess varianter, studerade operativsystemet, tittade på exempelprogram och fick till och med möjligheten att göra felsökning i maskinkoder (trots allt är alla Z80-mnemonics bekanta för mig)
  • Skrev ett fullfjädrat verktyg för att konvertera ljudinspelningar till CAS-format, som kan läsa data som inte känns igen av det "officiella" verktyget

Källa: will.com

Lägg en kommentar