prehistory
Bilang mahilig sa retro hardware, minsan akong bumili ng ZX Spectrum+ mula sa isang nagbebenta sa UK. Kasama sa computer mismo, nakatanggap ako ng ilang mga audio cassette na may mga laro (sa orihinal na packaging na may mga tagubilin), pati na rin ang mga programa na naitala sa mga cassette na walang mga espesyal na marka. Nakapagtataka, ang data mula sa 40-taong-gulang na mga cassette ay nababasa nang mabuti at nagawa kong i-download ang halos lahat ng mga laro at programa mula sa kanila.
Gayunpaman, sa ilang mga cassette nakita ko ang mga pag-record na malinaw na hindi ginawa ng ZX Spectrum computer. Ang mga ito ay ganap na naiiba at, hindi katulad ng mga pag-record mula sa nabanggit na computer, hindi sila nagsimula sa isang maikling BASIC bootloader, na kadalasang naroroon sa mga pag-record ng lahat ng mga programa at laro.
For some time this haunted me - Gusto ko talagang malaman kung ano ang nakatago sa kanila. Kung mababasa mo ang audio signal bilang isang sequence ng mga byte, maaari kang maghanap ng mga character o anumang bagay na nagpapahiwatig ng pinagmulan ng signal. Isang uri ng retro-archaeology.
Ngayon na napunta na ako sa lahat ng paraan at tiningnan ang mga label ng mga cassette mismo, napangiti ako dahil
ang sagot ay nasa harap ng aking mga mata
Sa label ng kaliwang cassette ay ang pangalan ng TRS-80 computer, at sa ibaba lamang ng pangalan ng tagagawa: "Ginawa ng Radio Shack sa USA"
(Kung nais mong panatilihin ang intriga hanggang sa huli, huwag pumunta sa ilalim ng spoiler)
Paghahambing ng mga signal ng audio
Una sa lahat, i-digitize natin ang mga audio recording. Maaari mong pakinggan kung ano ang tunog nito:
At gaya ng dati ang pag-record mula sa ZX Spectrum computer ay tumutunog:
Sa parehong mga kaso, sa simula ng pag-record mayroong isang tinatawag na pilot tone - isang tunog ng parehong dalas (sa unang pag-record ito ay napakaikli <1 segundo, ngunit nakikilala). Ang pilot tone ay nagpapahiwatig sa computer na maghanda upang makatanggap ng data. Bilang isang tuntunin, kinikilala lamang ng bawat computer ang "sariling" pilot tone nito sa pamamagitan ng hugis ng signal at dalas nito.
Kinakailangang sabihin ang tungkol sa hugis ng signal mismo. Halimbawa, sa ZX Spectrum ang hugis nito ay hugis-parihaba:
Kapag may nakitang pilot tone, ang ZX Spectrum ay nagpapakita ng mga alternating red at blue bar sa border ng screen upang ipahiwatig na ang signal ay nakilala. Nagtatapos ang tono ng piloto synchro pulse, na senyales sa computer na magsimulang tumanggap ng data. Ito ay nailalarawan sa pamamagitan ng isang mas maikling tagal (kumpara sa tono ng piloto at kasunod na data) (tingnan ang figure)
Matapos matanggap ang pulso ng pag-sync, itinatala ng computer ang bawat pagtaas/pagbagsak ng signal, sinusukat ang tagal nito. Kung ang tagal ay mas mababa sa isang tiyak na limitasyon, ang bit 1 ay isinusulat sa memorya, kung hindi ay 0. Ang mga bit ay kinokolekta sa mga byte at ang proseso ay paulit-ulit hanggang sa N bytes ay matanggap. Karaniwang kinukuha ang numerong N mula sa header ng na-download na file. Ang pagkakasunud-sunod ng paglo-load ay ang mga sumusunod:
- pilot tone
- header (fixed length), naglalaman ng laki ng na-download na data (N), pangalan ng file at uri
- pilot tone
- ang data mismo
Upang matiyak na ang data ay na-load nang tama, binabasa ng ZX Spectrum ang tinatawag na parity byte (parity byte), na kinakalkula kapag nagse-save ng file sa pamamagitan ng XORing ng lahat ng byte ng nakasulat na data. Kapag nagbabasa ng isang file, kinakalkula ng computer ang parity byte mula sa natanggap na data at, kung ang resulta ay naiiba sa na-save, ipinapakita ang mensahe ng error na "R Tape loading error". Sa mahigpit na pagsasalita, maaaring mailabas ng computer ang mensaheng ito nang mas maaga kung, kapag nagbabasa, hindi nito nakikilala ang isang pulso (napalampas o ang tagal nito ay hindi tumutugma sa ilang mga limitasyon)
Kaya, tingnan natin ngayon kung ano ang hitsura ng hindi kilalang signal:
Ito ang tono ng piloto. Ang hugis ng signal ay makabuluhang naiiba, ngunit ito ay malinaw na ang signal ay binubuo ng paulit-ulit na maikling pulso ng isang tiyak na dalas. Sa dalas ng sampling na 44100 Hz, ang distansya sa pagitan ng "mga taluktok" ay humigit-kumulang 48 sample (na tumutugma sa dalas ng ~918 Hz). Tandaan natin ang figure na ito.
Tingnan natin ngayon ang fragment ng data:
Kung susukatin natin ang distansya sa pagitan ng mga indibidwal na pulso, lumalabas na ang distansya sa pagitan ng "mahaba" na mga pulso ay ~48 mga sample pa rin, at sa pagitan ng mga maikli - ~24. Sa unahan ng kaunti, sasabihin ko na sa huli ay lumabas na ang mga "reference" na pulso na may dalas na 918 Hz ay ββpatuloy na sumusunod, mula sa simula hanggang sa dulo ng file. Maaaring ipagpalagay na kapag nagpapadala ng data, kung ang isang karagdagang pulso ay nakatagpo sa pagitan ng mga reference pulse, itinuturing namin ito bilang bit 1, kung hindi man 0.
Paano ang pulso ng pag-sync? Tingnan natin ang simula ng data:
Ang pilot tone ay nagtatapos at ang data ay magsisimula kaagad. Maya-maya, pagkatapos suriin ang ilang magkakaibang audio recording, natuklasan namin na ang unang byte ng data ay palaging pareho (10100101b, A5h). Maaaring magsimulang magbasa ng data ang computer pagkatapos nitong matanggap ito.
Maaari mo ring bigyang-pansin ang paglilipat ng unang reference pulse kaagad pagkatapos ng huling 1st sa sync byte. Natuklasan ito nang maglaon sa proseso ng pagbuo ng isang programa sa pagkilala ng data, kapag ang data sa simula ng file ay hindi mababasa nang matatag.
Ngayon subukan nating ilarawan ang isang algorithm na magpoproseso ng audio file at maglo-load ng data.
Loading data
Una, tingnan natin ang ilang mga pagpapalagay upang mapanatiling simple ang algorithm:
- Isasaalang-alang lamang namin ang mga file sa WAV na format;
- Ang audio file ay dapat magsimula sa isang pilot tone at hindi dapat maglaman ng katahimikan sa simula
- Ang source file ay dapat may sampling rate na 44100 Hz. Sa kasong ito, ang distansya sa pagitan ng reference pulses ng 48 samples ay natukoy na at hindi namin kailangang kalkulahin ito sa programmatically;
- Ang sample na format ay maaaring maging anumang (8/16 bits/floating point) - dahil kapag nagbabasa ay maaari nating i-convert ito sa nais;
- Ipinapalagay namin na ang source file ay na-normalize ng amplitude, na dapat patatagin ang resulta;
Ang algorithm sa pagbabasa ay ang mga sumusunod:
- Binabasa namin ang file sa memorya, sa parehong oras na nagko-convert ng sample na format sa 8 bits;
- Tukuyin ang posisyon ng unang pulso sa data ng audio. Upang gawin ito, kailangan mong kalkulahin ang bilang ng sample na may pinakamataas na amplitude. Para sa pagiging simple, kakalkulahin namin ito nang manu-mano nang isang beses. I-save natin ito sa variable prev_pos;
- Magdagdag ng 48 sa posisyon ng huling pulso (pos := prev_pos + 48)
- Dahil ang pagtaas ng posisyon ng 48 ay hindi ginagarantiyahan na makakarating tayo sa posisyon ng susunod na reference pulse (mga depekto sa tape, hindi matatag na operasyon ng mekanismo ng tape drive, atbp.), Kailangan nating ayusin ang posisyon ng pos pulse. Upang gawin ito, kumuha ng isang maliit na piraso ng data (pos-8; pos+8) at hanapin ang pinakamataas na halaga ng amplitude dito. Ang posisyon na naaayon sa maximum ay maiimbak sa pos. Narito ang 8 = 48/6 ay isang eksperimento na nakuha na pare-pareho, na ginagarantiyahan na matutukoy namin ang tamang maximum at hindi makakaapekto sa iba pang mga impulses na maaaring nasa malapit. Sa napakasamang mga kaso, kapag ang distansya sa pagitan ng mga pulso ay mas mababa sa o mas malaki kaysa sa 48, maaari mong ipatupad ang isang sapilitang paghahanap para sa isang pulso, ngunit sa loob ng saklaw ng artikulo ay hindi ko ilalarawan ito sa algorithm;
- Sa nakaraang hakbang, kakailanganin din na suriin na ang reference pulse ay natagpuan sa lahat. Iyon ay, kung hahanapin mo lang ang maximum, hindi nito ginagarantiyahan na ang salpok ay naroroon sa segment na ito. Sa aking pinakabagong pagpapatupad ng programa sa pagbabasa, sinusuri ko ang pagkakaiba sa pagitan ng maximum at minimum na mga halaga ng amplitude sa isang segment, at kung lumampas ito sa isang tiyak na limitasyon, binibilang ko ang pagkakaroon ng isang salpok. Ang tanong ay kung ano ang gagawin kung hindi natagpuan ang reference pulse. Mayroong 2 opsyon: maaaring natapos na ang data at sumunod ang katahimikan, o dapat itong ituring na error sa pagbabasa. Gayunpaman, aalisin namin ito upang gawing simple ang algorithm;
- Sa susunod na hakbang, kailangan nating matukoy ang pagkakaroon ng isang pulso ng data (bit 0 o 1), para dito kinukuha natin ang gitna ng segment (prev_pos;pos) middle_pos na katumbas ng middle_pos := (prev_pos+pos)/2 at sa ilang kapitbahayan ng middle_pos sa segment (middle_pos-8;middle_pos +8) kalkulahin natin ang maximum at minimum amplitude. Kung ang pagkakaiba sa pagitan ng mga ito ay higit sa 10, isinusulat namin ang bit 1 sa resulta, kung hindi man 0. 10 ay isang pare-pareho na nakuha sa eksperimento;
- I-save ang kasalukuyang posisyon sa prev_pos (prev_pos := pos)
- Ulitin simula sa hakbang 3 hanggang mabasa namin ang buong file;
- Ang resultang bit array ay dapat na i-save bilang isang set ng mga byte. Dahil hindi namin isinasaalang-alang ang sync byte kapag nagbabasa, ang bilang ng mga bit ay maaaring hindi maramihang 8, at ang kinakailangang bit offset ay hindi rin alam. Sa unang pagpapatupad ng algorithm, hindi ko alam ang tungkol sa pagkakaroon ng sync byte at samakatuwid ay nag-save lamang ng 8 mga file na may iba't ibang bilang ng mga offset bit. Ang isa sa mga ito ay naglalaman ng tamang data. Sa panghuling algorithm, inaalis ko lang ang lahat ng mga bit hanggang sa A5h, na nagpapahintulot sa akin na agad na makuha ang tamang output file
Algorithm sa Ruby, para sa mga interesado
Pinili ko si Ruby bilang wika para sa pagsulat ng programa, dahil... Pino-program ko ito sa halos lahat ng oras. Ang pagpipilian ay hindi mataas ang pagganap, ngunit ang gawain ng paggawa ng bilis ng pagbabasa nang mabilis hangga't maaari ay hindi katumbas ng halaga.
# ΠΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ 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*")
Resulta
Ang pagkakaroon ng pagsubok ng ilang mga variant ng algorithm at constants, ako ay mapalad na makakuha ng isang bagay na lubhang kawili-wili:
Kaya, sa paghusga sa mga string ng character, mayroon kaming isang programa para sa paglalagay ng mga graph. Gayunpaman, walang mga keyword sa teksto ng programa. Ang lahat ng mga keyword ay naka-encode bilang mga byte (bawat halaga > 80h). Ngayon kailangan nating malaman kung aling computer mula sa 80s ang makakapag-save ng mga programa sa format na ito.
Sa katunayan, ito ay halos kapareho sa isang BASIC na programa. Ang ZX Spectrum na computer ay nag-iimbak ng mga programa sa humigit-kumulang sa parehong format sa memorya at nagse-save ng mga programa sa tape. Kung sakali, sinuri ko ang mga keyword laban
Sinuri ko rin ang BASIC na mga keyword ng sikat na Atari, Commodore 64 at ilang iba pang mga computer noong panahong iyon, kung saan nakahanap ako ng dokumentasyon, ngunit walang tagumpay - ang aking kaalaman sa mga uri ng mga retro na computer ay naging hindi masyadong malawak.
Pagkatapos ay nagpasya akong pumunta
Computer Tandy/Radio Shack TRS-80
Malamang na ang audio recording na pinag-uusapan, na ibinigay ko bilang isang halimbawa sa simula ng artikulo, ay ginawa sa isang computer na tulad nito:
Ito ay lumabas na ang computer na ito at ang mga uri nito (Model I/Model III/Model IV, atbp.) ay napakapopular sa isang pagkakataon (siyempre, hindi sa Russia). Kapansin-pansin na ang ginamit nilang processor ay Z80 din. Para sa computer na ito maaari mong mahanap sa Internet
Nagdownload ako ng emulator
Nakahanap din ako
Nang malaman ang format ng CAS file (na naging isang bit-by-bit na kopya lamang ng data mula sa tape na nasa kamay ko na, maliban sa header na may presensya ng isang sync byte), gumawa ako ng ilang mga pagbabago sa aking programa at nakapag-output ng gumaganang CAS file na nagtrabaho sa emulator (TRS-80 Model III):
Idinisenyo ko ang pinakabagong bersyon ng utility ng conversion na may awtomatikong pagtukoy ng unang pulso at ang distansya sa pagitan ng mga reference pulse bilang isang pakete ng GEM, ang source code ay available sa
Konklusyon
Ang landas na aming tinahak ay naging isang kamangha-manghang paglalakbay sa nakaraan, at ako ay natutuwa na sa huli ay natagpuan ko ang sagot. Sa iba pang mga bagay, ako:
- Naisip ko ang format para sa pag-save ng data sa ZX Spectrum at pinag-aralan ang built-in na ROM routines para sa pag-save/pagbasa ng data mula sa mga audio cassette
- Nakilala ko ang TRS-80 na computer at ang mga uri nito, pinag-aralan ang operating system, tumingin sa mga sample na programa at kahit na nagkaroon ng pagkakataon na mag-debug sa mga code ng makina (pagkatapos ng lahat, pamilyar sa akin ang lahat ng Z80 mnemonics)
- Sumulat ng isang ganap na utility para sa pag-convert ng mga audio recording sa CAS na format, na maaaring magbasa ng data na hindi kinikilala ng "opisyal" na utility
Pinagmulan: www.habr.com