Historia e mëparshme
Duke qenë një adhurues i harduerit retro, një herë blera nga një shitës nga Britania e Madhe një ZX Spectrum+. Bashkë me kompjuterin, mora disa kaseta audio me lojra (në paketimin origjinal me instrukcione), si dhe programe të regjistruara në kasetat pa ndonjë shënim të veçantë. Për habinë time, të dhënat nga kasetat 40-vjeçare lexoheshin mirë dhe arrita të ngarkoja pothuajse të gjitha lojrat dhe programet nga ato.

Megjithatë, në disa kaseta pashë regjistrime që ishin bërë qartë jo nga kompjuteri ZX Spectrum. Ato kishin një tingull krejt ndryshe dhe, në dallim nga regjistrimet e përmendura, nuk fillonin me përfalësin e shkurtuar BASIC, i cili zakonisht është i pranishëm në regjistrimet e të gjitha programeve dhe lojrave.
Për disa kohë kjo më shqetësonte — isha shumë i etur për të mësuar se çfarë fshihej brenda. Sikur të ishte e mundur të lexoja sinjalin audio si një sekuencë bajtesh, do të mund të kërkoja për simbole ose diçka që tregon origjinën e sinjalit. Një lloj retro-arkeologjie.
Tani, kur kam kaluar të gjithë këtë rrugë dhe shoh etiketat e kasetave vetë, qesh, sepse
përgjigjja ishte përpara syve për gjithë këtë kohë
Në etiketën e kasetës së majtë — emri i kompjuterit TRS-80, dhe pak më poshtë emri i prodhuesit: “Prodhimi nga Radio Shack në SHBA”
(Nëse doni të ruani intrigën deri në fund, mos hyni nën spoiler)
Krahasimi i sinjaleve audio
Së pari, le të digitalizojmë regjistrimet audio. Mund të dëgjojmë si tingëllon:
Dhe si tingëllon zakonisht një regjistrim nga kompjuteri ZX Spectrum:
Në të dy rastet, në fillim të regjistrimit është i pranishëm i ashtuquajturi ton pilot — një tingull i një frekuence (në regjistrimin e parë ai është shumë i shkurtër <1 sek, megjithatë i dallueshëm). Ton pilot shërben si sinjal për kompjuterin që duhet të përgatitet për marrjen e të dhënave. Zakonisht çdo kompjuter njeh vetëm “tonin e tij” pilot sipas formës së sinjalit dhe frekuencës së tij.
Duhet thënë për vetë formën e sinjalit. Për shembull, në ZX Spectrum, forma e tij është katrore:

Kur zbulohet ton pilot, ZX Spectrum shfaq në ekranin e tij një rresht të alternuar të vijave të kuqe dhe blu, duke treguar se sinjali është njohur. Ton pilot përfundon me një impuls sinkronizimi, i cili sinjalizon kompjuterin se duhet të fillojë të pranojë të dhëna. Ai karakterizohet nga një kohëzgjatje më e vogël (krahasuar me tonin pilot dhe të dhënat që vijojnë) (shihni figurën)
Pasi që sinjali sinkron kërkohet, kompjuteri regjistron çdo ngritje/shkëputje të sinjalit, duke matur kohëzgjatjen e tij. Nëse kohëzgjatja është më e vogël se një kufi i caktuar, regjistrohet një bit 1 në memorje, përndryshe 0. Bitët mblidhen në byte dhe procesi përsëritet derisa të kenë ardhur N byte. Numri N, zakonisht, merret nga headeri i skedarit në ngarkim. Renditja e ngarkesës është si më poshtë:
- ton pilot
- header (me gjatësi fikse), përmban madhësinë e të dhënave në ngarkim (N), emrin dhe llojin e skedarit
- ton pilot
- të dhënat e vetë
Për të siguruar që të dhënat janë ngarkuar saktë, ZX Spectrum lexon byte-in e fundit të quajtur byte pariteti çka llogaritet gjatë ruajtjes së skedarit me operacionin XOR mbi të gjitha byte-të e të dhënave të regjistruara. Gjatë leximit të skedarit, kompjuteri llogarit byte-in e paritetit nga të dhënat e marra dhe, nëse rezultati ndryshon nga ai i ruajtur, jep një mesazh gabimi "R Tape loading error". Në mënyrë strikte, kompjuteri mund të japë këtë mesazh edhe më parë, nëse gjatë leximit nuk mund të identifikojë sinjalin (mungesë ose kohëzgjatja e tij nuk përputhet me kufijtë e caktuar)
Tani le të shohim se si duket sinjali i panjohur:

Ky është ton piloti. Forma e sinjalit ndryshon ndjeshëm, por është e dukshme që sinjali përbëhet nga impulset e shkurtra të përsëritura me një frekuencë të caktuar. Me frekuencën e mostrimit prej 44100 Hz, distanca midis "majave" është rreth 48 mostra (që përkon me një frekuencë ~918 Hz) Le ta mbajmë mend këtë numër.
Tani le të shohim një fragment me të dhëna:

Nëse matim distancën midis impulsojve të veçantë, do të dalë se midis impulsojve "të gjatë" distanca ende është ~48 mostra, ndërsa midis atyre të shkurtra -- ~24. Pak përpara, do të thosha se në fund rezultoi se impulsoj të "referencës" me frekuencë 918 Hz ndjekin në mënyrë të vazhdueshme, nga fillimi deri në fund të skedarit. Mund të supozojmë që gjatë transferimit të të dhënave, nëse ndërmjet impulsojve referencë shfaqet një impuls shtesë, e konsiderojmë atë si bit 1, përndryshe 0.
Çfarë ndodh me sinjalin sinkron? Le të shohim fillimin e të dhënave:

Toni pilot përfundon dhe menjëherë fillojnë të dhënat. Pak më vonë, duke analizuar disa regjistrime të ndryshme audio, u zbulua se bajti i parë i të dhënave është gjithmonë i njëjtë (10100101b, A5h). Ndoshta, kompjuteri fillon të lexojë të dhënat pasi të ketë marrë këtë.
Mund të vihet re gjithashtu zhvendosja e impulsit të parë referues menjëherë pas 1-shit të fundit në sinhronbajt. Këtë arritëm ta zbulojmë ndjeshëm më vonë në procesin e zhvillimit të programit për njohjen e të dhënave, kur të dhënat në fillim të skedarit nuk kishin mundur të lexoheshin qëndruese.
Tani do të përpiqemi të përshkruajmë algoritmin që do të përpunojë skedarin audio dhe do të ngarkojë të dhënat.
Ngarkimi i të dhënave
Fillimisht do të shqyrtojmë disa supozime për të mos e komplikuar algoritmin:
- Do të shqyrtojmë vetëm skedarët në formatin WAV;
- Skedari audio duhet të fillojë me ton pilot dhe nuk duhet të përmbajë heshtje në fillim
- Skedari burimor duhet të ketë frekuencë mostrimi prej 44100 Hz. Në këtë rast, distanca midis impulsave referues në 48 mostra tashmë është e përcaktuar dhe ne nuk kemi nevojë ta llogarisim atë programatikisht;
- Formati i mostrave mund të jetë çfarëdo (8/16 bit/për pikë fluturuese) — sepse gjatë leximit mund ta konvertojmë në formatin e nevojshëm;
- Supozojmë se skedari burimor është normalizuar sipas amplitudës, që duhet të stabilizojë rezultatin;
Algoritmi i leximit do të jetë si vjen:
- Lexojmë skedarin në memorie, duke konvertuar njëkohësisht formatin e mostrave në 8 bit;
- Përcaktojmë pozita e impulsit të parë në të dhënat audio. Për këtë është e nevojshme të llogarisim numrin e mostrës me amplitudë maksimale. Për thjeshtësi, do ta llogarisim një herë manualisht. Do ta ruajmë në variablën prev_pos;
- Shtojmë 48 në pozita e impulsit të fundit (pos := prev_pos + 48)
- Rritja e pozita 48 nuk garanton që ne do të arrijmë në pozitarin e ardhshëm të impulsit (defektet e shiritit, funksionimi jo stabil i mekanizmit të shtrirjes së shiritit dhe të tjera), prandaj duhet të korrigjojmë pozitarin e impulsit pos. Për këtë merrni një segment të vogël të të dhënave (pos-8; pos+8) dhe gjeni maksimumin e vlerës së amplitudës në të. Pozita që korrespondon me maksimumin do të ruhet në pos. Këtu 8 = 48/6 - një konstantë e marrë eksperimentalisht, e cila garanton që ne do të përcaktojmë maksimumin e saktë dhe nuk do të prekim impulsat e tjerë që mund të jenë afër. Në raste shumë të këqija, kur distanca midis impulsit është shumë më e vogël ose më e madhe se 48, mund të realizoni një kërkim të detyruar të impulsit, por në kuadër të këtij artikulli nuk do ta përshkruaj këtë në algoritëm;
- Në hapin e mëparshëm është gjithashtu e nevojshme të kontrollohet nëse impulsi referues është gjetur. Pra, nëse thjesht kërkoni maksimumin, kjo nuk garanton që impulsi është prezent në këtë segment. Në versionin tim të fundit të programit të leximit, unë kontrolloj diferencën midis maksimumit dhe minimumit të vlerës së amplitudës në segment dhe nëse ajo tejkalon një kufi të caktuar, e konsideroj si praninë e impulsit. Pyetja është gjithashtu se çfarë duhet bërë nëse impulsi referues nuk është gjetur. Këtu ka dy mundësi: ose të dhënat kanë përfunduar dhe më pas pason heshtja, ose kjo duhet të konsiderohet si një gabim leximi. Megjithatë, le t'i lëmë këto për thjeshtimin e algoritmit;
- Në hapin tjetër duhet të përcaktojmë praninë e impulsit të të dhënave (bit 0 ose 1), për të cilin do të marrim mesataren e segmentit (prev_pos; pos) middle_pos e barabartë me middle_pos := (prev_pos + pos) / 2 dhe në një afërsi të caktuar të middle_pos në segmentin (middle_pos-8; middle_pos+8) do të llogarisim maksimumin dhe minimumin e amplitudës. Nëse diferenca midis tyre është më e madhe se 10, regjistrojmë në rezultat bitin 1, përndryshe 0. 10 - një konstantë e marrë eksperiencë;
- Ruajmë pozitarin aktual në prev_pos (prev_pos := pos)
- Përsërisim duke filluar nga hapi 3, derisa të lexojmë të gjithë skedarin;
- Marrje e bitëve duhet të ruhet si një grup bajtësh. Duke pasur parasysh se nuk e kemi marrë parasysh bajtin sinkronizues gjatë leximit, numri i bitëve mund të mos jetë i shumëfishueshëm me 8, si dhe nuk dihet ndjesia e nevojshme në bitë. Në implementimin e parë të algoritmit nuk dija për ekzistencën e bajtëve sinkronizues dhe kështu thjesht ruaja 8 skedarë me numra të ndryshëm të bitëve mbetje. Një prej tyre kishte të dhëna të sakta. Në algoritmin përfundimtar, thjesht fshij të gjitha bitët deri në A5h, çka lejon të marrësh menjëherë një skedar të saktë në dalje.
Algoritmi në Ruby, për ata që janë të interesuar.
Si gjuhë programimi, zgjodha Ruby, pasi kaloj shumicën e kohës duke programuar me të. Ky variant nuk është me performancë të lartë, megjithatë, detyra për të bërë shpejtësinë e leximit sa më të shpejtë nuk është e rëndësishme.
# Используем 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*")
Rezultati
Pas disa provave me variante të algoritmit dhe konstantave, munda të merrja diçka jashtëzakonisht interesante:

Pra, sipas vargjeve karakteresh, kemi një program për ndërtimin e grafikëve. Megjithatë, në tekstin e programit mungojnë fjalët kyçe. Të gjitha fjalët kyçe janë koduar në formën e bajtëve (vlera e secilës > 80h). Tani duhet të zbuloj se cili kompjuter nga vitet '80 mund të ruante programet në këtë format.
Në të vërtetë, kjo është shumë e ngjashme me një program në gjuhën BASIC. Në një format të ngjashëm, kompjuteri ZX Spectrum ruan në kujtesë dhe ruan programet në kasetë. Nga pala tjetër, kontrollova fjalët kyçe në përputhje me Megjithatë, rezultati, është e qartë, qe negativ.
Gjithashtu kontrollova fjalët kyçe BASIC të kompjuterëve të njohur të asaj kohe si Atari, Commodore 64 dhe disa të tjerë për të cilët arrita të gjej dokumentacion, por pa sukses - njohuritë e mia për llojet e kompjuterëve retro nuk ishin aq të mëdha.
Atëherë vendosa të shkoj në , dhe aty vura re emrin e prodhuesit Radio Shack dhe kompjuterin TRS-80. Këto emra ishin shkruar në etiketat e kasetave që kisha mbi tavolinë! Nuk e dija më parë këto emra dhe nuk isha i njohur me kompjuterin TRS-80, kështu që më dukej se Radio Shack është prodhuesi i kasetave audio, si BASF, Sony ose TDK, dhe TRS-80 - kohëzgjatja e riprodhimit. Pse jo?
Kompjuteri Tandy/Radio Shack TRS-80
Shumë është e mundur që regjistrimi audio i marrëzi që kam sjellë si shembull në fillim të artikullit, është bërë në një kompjuter të tillë:

Doli që kompjuteri i këtij lloji (Model I/Model III/Model IV dhe të tjerë) ishte shumë i njohur në kohën e tij (sigurisht, jo në Rusi). E veçanta është se procesori që përdorej në të — gjithashtu Z80. Për këtë kompjuter, mund të gjeni në Internet . Në vitet '80, informacioni për kompjuterin shpërndahej në . Aktualisht ekzistojnë disa kompjuterash për platforma të ndryshme.
Unë shkarkova emulatorin dhe arrita të shihja për herë të parë sesi funksiononte ky kompjuter. Sigurisht, kompjuteri nuk mbështeste daljen me ngjyra, rezolucioni i ekranit ishte vetëm 128x48 piksel, por kishte shumë zgjerime dhe modifikime që mund të rrisnin rezolucionin e ekranit. Po ashtu, kishte shumë variante sistemesh operativ për këtë kompjuter dhe variante të implementimit të gjuhës BASIC (e cila, ndryshe nga ZX Spectrum, në disa modele madje nuk ishte «e instaluar» në EEPROM dhe çdo variant mund të ngarkohej nga disku, ashtu si edhe vetë OS)
Po ashtu, kam gjetur për të konvertuar regjistrimet audio në formatin CAS, i cili mbështetet nga emulatorët, megjithatë nuk arrita të lexoj me ndihmën e tyre regjistrimet nga kasetat e mia për disa arsye.
Pas analizës së formatit të skedarit CAS (i cili rezultoi të ishte thjesht një kopje bit për bit të të dhënave nga banda, të cilat tashmë i kisha në duar, përveç titullit me praninë e bajtdhenësit të sinkronizuar), unë bëra disa ndryshime në programin tim dhe arrita të marr një skedar CAS funksional që funksionoi në emulator (TRS-80 Model III):

Versioni më i fundit i utilitarit për konvertim me identifikim automatik të impulsit të parë dhe distancës ndërmjet impulsive mbështetjeve e kam organizuar si një paketë GEM, kodi burimor është në dispozicion në .
Përfundim
Udhëtimi që kalova rezultoi të jetë një aventurë e mrekullueshme në të kaluarën, dhe jam i lumtur që në fund gjeta zgjidhjen. Përveç kësaj, unë:
- Analizova formatin e ruajtjes së të dhënave në ZX Spectrum dhe studioja programet e integruara në EEPROM për ruajtjen/leximin e të dhënave nga kasetat audio
- Njoha kompjuterin TRS-80 dhe llojet e tij, shqyrtova sistemin operativ, pashë shembuj programesh dhe madje pata mundësinë të merrem me debugimin në kodet makinerik (gjithsesi, të gjitha mnemonikët Z80 janë të njohur për mua)
- Kam krijuar një utilitar të plotë për konvertimin e regjistrimeve audio në formatin CAS, i cili mund të lexojë të dhëna që nuk njihen nga utilitari "zyrtar".
Burimi: habr.com
