Sana oldin
Retro-uskunalar ishqibozi sifatida men bir marta Buyuk Britaniyadagi sotuvchidan ZX Spectrum+ sotib oldim. Kompyuterning o'zi bilan birga men o'yinlar bilan bir nechta audio kassetalarni oldim (ko'rsatmalar bilan original qadoqda), shuningdek, maxsus belgilarsiz kasetlarda yozilgan dasturlar. Ajablanarlisi shundaki, 40 yoshli kassetalardagi ma'lumotlar yaxshi o'qilishi mumkin edi va men ulardan deyarli barcha o'yinlar va dasturlarni yuklab olishga muvaffaq bo'ldim.
Biroq, ba'zi kasetlarda men ZX Spectrum kompyuteri tomonidan aniqlanmagan yozuvlarni topdim. Ular butunlay boshqacha yangradi va yuqorida aytib o'tilgan kompyuterdagi yozuvlardan farqli o'laroq, ular odatda barcha dasturlar va o'yinlarning yozuvlarida mavjud bo'lgan qisqa BASIC yuklovchi bilan boshlanmadi.
Bir muncha vaqt bu meni hayratda qoldirdi - men ularda nima yashiringanini bilishni juda xohlardim. Agar siz audio signalni baytlar ketma-ketligi sifatida o'qiy olsangiz, signalning kelib chiqishini ko'rsatadigan belgilar yoki har qanday narsani qidirib topishingiz mumkin. Retro-arxeologiyaning bir turi.
Endi men hamma yo'lni bosib o'tib, kassetalarning yorliqlarini ko'rib chiqdim, chunki men tabassum qilaman
javob hamma vaqt ko'zim oldida edi
Chap kassetaning yorlig'ida TRS-80 kompyuterining nomi va ishlab chiqaruvchi nomi ostida: "AQShda Radio Shack tomonidan ishlab chiqarilgan"
(Agar siz intrigani oxirigacha saqlamoqchi bo'lsangiz, spoylerga tushmang)
Ovozli signallarni taqqoslash
Avvalo, audioyozuvlarni raqamlashtiramiz. Bu qanday eshitilishini tinglashingiz mumkin:
Odatdagidek ZX Spectrum kompyuteridan yozuv eshitiladi:
Ikkala holatda ham, yozuvning boshida, deb ataladigan narsa bor uchuvchi ohang - bir xil chastotali tovush (birinchi yozuvda u juda qisqa <1 soniya, lekin ajralib turadi). Uchuvchi ohang kompyuterga ma'lumotlarni qabul qilishga tayyorlanish uchun signal beradi. Qoida tariqasida, har bir kompyuter signalning shakli va chastotasi bo'yicha faqat o'zining "o'z" uchuvchi ohangini taniydi.
Signal shaklining o'zi haqida biror narsa aytish kerak. Masalan, ZX Spectrumda uning shakli to'rtburchaklar shaklida bo'ladi:
Uchuvchi ohang aniqlanganda, ZX Spectrum signal tan olinganligini ko'rsatish uchun ekran chegarasida o'zgaruvchan qizil va ko'k chiziqlarni ko'rsatadi. Uchuvchi ohang tugaydi sinxron puls, bu kompyuterga ma'lumotlarni qabul qilishni boshlash uchun signal beradi. U qisqaroq muddat bilan tavsiflanadi (uchuvchi ohang va keyingi ma'lumotlarga nisbatan) (rasmga qarang)
Sinxronlash pulsi qabul qilingandan so'ng, kompyuter signalning har bir ko'tarilishi / tushishini qayd etadi, uning davomiyligini o'lchaydi. Agar davomiylik ma'lum chegaradan kam bo'lsa, 1-bit xotiraga yoziladi, aks holda 0. Bitlar baytlarga yig'iladi va jarayon N bayt olinmaguncha takrorlanadi. N raqami odatda yuklab olingan faylning sarlavhasidan olinadi. Yuklash ketma-ketligi quyidagicha:
- uchuvchi ohang
- sarlavha (belgilangan uzunlik), yuklab olingan ma'lumotlarning hajmini (N), fayl nomini va turini o'z ichiga oladi
- uchuvchi ohang
- ma'lumotlarning o'zi
Ma'lumotlar to'g'ri yuklanganligiga ishonch hosil qilish uchun ZX Spectrum deb ataladigan narsani o'qiydi paritet bayt (parite bayti), bu yozma ma'lumotlarning barcha baytlarini XORlash orqali faylni saqlashda hisoblanadi. Faylni o'qiyotganda, kompyuter olingan ma'lumotlardan paritet baytni hisoblab chiqadi va agar natija saqlanganidan farq qilsa, "R Tape loading error" xato xabarini ko'rsatadi. To'g'ridan-to'g'ri aytganda, agar kompyuter o'qish paytida pulsni taniy olmasa (o'tkazib yuborilgan yoki uning davomiyligi ma'lum chegaralarga to'g'ri kelmasa) ushbu xabarni oldinroq berishi mumkin.
Keling, noma'lum signal qanday ko'rinishini ko'rib chiqaylik:
Bu uchuvchi ohang. Signalning shakli sezilarli darajada farq qiladi, ammo signal ma'lum bir chastotaning qisqa impulslarini takrorlashdan iboratligi aniq. Namuna olish chastotasi 44100 Gts bo'lganida, "cho'qqilar" orasidagi masofa taxminan 48 ta namunani tashkil qiladi (bu ~ 918 Gts chastotaga to'g'ri keladi) Keling, ushbu raqamni eslaylik.
Endi ma'lumotlar fragmentini ko'rib chiqamiz:
Agar individual impulslar orasidagi masofani o'lchaydigan bo'lsak, "uzoq" impulslar orasidagi masofa hali ham ~ 48 namuna, qisqa bo'lganlar orasida - ~ 24 ekanligi ma'lum bo'ladi. Bir oz oldinga qarab, shuni aytamanki, oxir-oqibat 918 Gts chastotali "mos yozuvlar" impulslari faylning boshidan oxirigacha doimiy ravishda kuzatib borishi ma'lum bo'ldi. Taxmin qilish mumkinki, ma'lumotlarni uzatishda, agar mos yozuvlar impulslari orasida qo'shimcha impuls uchrasa, biz uni bit 1, aks holda 0 deb hisoblaymiz.
Sinxronlash pulsi haqida nima deyish mumkin? Keling, ma'lumotlarning boshiga qaraylik:
Uchuvchi ohang tugaydi va ma'lumotlar darhol boshlanadi. Biroz vaqt o'tgach, bir nechta turli xil audio yozuvlarni tahlil qilgandan so'ng, biz ma'lumotlarning birinchi bayti har doim bir xil ekanligini aniqladik (10100101b, A5h). Kompyuter ma'lumotlarni qabul qilgandan keyin o'qishni boshlashi mumkin.
Sinxronlash baytidagi oxirgi 1-dan so'ng darhol birinchi mos yozuvlar pulsining siljishiga ham e'tibor berishingiz mumkin. U ancha keyinroq ma'lumotlarni aniqlash dasturini ishlab chiqish jarayonida, faylning boshidagi ma'lumotlarni barqaror o'qish mumkin bo'lmaganda aniqlangan.
Endi audio faylga ishlov beradigan va ma'lumotlarni yuklaydigan algoritmni tasvirlashga harakat qilaylik.
Ma'lumotlar yuklanmoqda
Birinchidan, algoritmni sodda saqlash uchun bir nechta taxminlarni ko'rib chiqaylik:
- Biz faqat WAV formatidagi fayllarni ko'rib chiqamiz;
- Audio fayl uchuvchi ohang bilan boshlanishi kerak va boshida sukunat bo'lmasligi kerak
- Manba fayli 44100 Gts namuna olish tezligiga ega bo'lishi kerak. Bunday holda, 48 ta namunaning mos yozuvlar pulslari orasidagi masofa allaqachon aniqlangan va biz uni dasturiy tarzda hisoblashimiz shart emas;
- Namuna formati har qanday bo'lishi mumkin (8/16 bit / suzuvchi nuqta) - chunki o'qish paytida biz uni kerakli formatga o'zgartirishimiz mumkin;
- Biz manba fayli amplituda bilan normallashtirilgan deb hisoblaymiz, bu esa natijani barqarorlashtirishi kerak;
O'qish algoritmi quyidagicha bo'ladi:
- Biz faylni xotiraga o'qiymiz, shu bilan birga namuna formatini 8 bitga aylantiramiz;
- Ovoz ma'lumotlaridagi birinchi impulsning o'rnini aniqlang. Buning uchun siz maksimal amplitudali namunaning sonini hisoblashingiz kerak. Oddiylik uchun biz uni bir marta qo'lda hisoblaymiz. Keling, uni prev_pos o'zgaruvchisiga saqlaymiz;
- Oxirgi pulsning holatiga 48 qo'shing (pos := prev_pos + 48)
- Pozitsiyani 48 ga oshirish biz keyingi mos yozuvlar impulsining holatiga (lenta nuqsonlari, lenta qo'zg'alish mexanizmining beqaror ishlashi va boshqalar) etib borishimizga kafolat bermasligi sababli, biz pos impulsining holatini sozlashimiz kerak. Buning uchun kichik ma'lumot qismini (pos-8;pos+8) oling va undagi maksimal amplituda qiymatini toping. Maksimalga mos keladigan pozitsiya posda saqlanadi. Bu erda 8 = 48/6 eksperimental ravishda olingan konstanta bo'lib, biz to'g'ri maksimalni aniqlashimizni va yaqin atrofdagi boshqa impulslarga ta'sir qilmasligimizni kafolatlaydi. Juda yomon holatlarda, impulslar orasidagi masofa 48 dan ancha kam yoki undan katta bo'lsa, siz impulsni majburiy qidirishni amalga oshirishingiz mumkin, ammo maqola doirasida men buni algoritmda tasvirlamayman;
- Oldingi bosqichda, shuningdek, mos yozuvlar pulsning umuman topilganligini tekshirish kerak bo'ladi. Ya'ni, agar siz shunchaki maksimalni qidirsangiz, bu impulsning ushbu segmentda mavjudligini kafolatlamaydi. O'qish dasturini so'nggi amalga oshirishda men segmentdagi maksimal va minimal amplituda qiymatlari o'rtasidagi farqni tekshiraman va agar u ma'lum chegaradan oshsa, impuls mavjudligini hisoblayman. Savol, shuningdek, mos yozuvlar pulsi topilmasa, nima qilish kerakligi. Ikkita variant mavjud: yoki ma'lumotlar tugadi va sukunat davom etadi yoki bu o'qish xatosi deb hisoblanishi kerak. Biroq, algoritmni soddalashtirish uchun biz buni o'tkazib yuboramiz;
- Keyingi bosqichda biz ma'lumotlar impulsi mavjudligini aniqlashimiz kerak (bit 0 yoki 1), buning uchun segmentning o'rtasini (prev_pos;pos) middle_pos ga teng bo'lgan middle_pos := (prev_pos+pos)/2 va segmentdagi Middle_pos ning ba'zi bir mahallasida (middle_pos-8;middle_pos +8) maksimal va minimal amplitudani hisoblaymiz. Agar ular orasidagi farq 10 dan ortiq bo'lsa, natijaga 1 bitni yozamiz, aks holda 0. 10 - tajriba yo'li bilan olingan doimiy;
- Joriy pozitsiyani oldingi_postda saqlang (oldingi_post := pos)
- 3-bosqichdan boshlab butun faylni o'qiguncha takrorlang;
- Olingan bit massivi baytlar to'plami sifatida saqlanishi kerak. Biz o'qishda sinxronlash baytini hisobga olmaganimiz sababli, bitlar soni 8 ga ko'paytirilmasligi mumkin va kerakli bit ofset ham noma'lum. Algoritmni birinchi amalga oshirishda men sinxronlash bayti mavjudligi haqida bilmasdim va shuning uchun turli xil ofset bitlari bilan 8 ta faylni saqlab qoldim. Ulardan biri to'g'ri ma'lumotlarni o'z ichiga olgan. Yakuniy algoritmda men A5h gacha bo'lgan barcha bitlarni olib tashlayman, bu menga to'g'ri chiqish faylini darhol olish imkonini beradi.
Qiziqqanlar uchun Ruby-dagi algoritm
Men dasturni yozish uchun til sifatida Ruby ni tanladim, chunki... Men ko'pincha uni dasturlashtiraman. Variant yuqori unumdorlikka ega emas, lekin o'qish tezligini imkon qadar tezroq qilish vazifasi bunga loyiq emas.
# ΠΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ 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*")
natija
Algoritm va konstantalarning bir nechta variantini sinab ko'rganimdan so'ng, men juda qiziqarli narsani olish baxtiga muyassar bo'ldim:
Shunday qilib, belgilar qatoriga qarab, bizda grafiklarni chizish uchun dastur mavjud. Biroq, dastur matnida kalit so'zlar mavjud emas. Barcha kalit so'zlar bayt sifatida kodlangan (har bir qiymat > 80h). Endi biz 80-yillardagi qaysi kompyuter dasturlarni ushbu formatda saqlashi mumkinligini aniqlashimiz kerak.
Aslida, u BASIC dasturiga juda o'xshaydi. ZX Spectrum kompyuteri dasturlarni taxminan bir xil formatda xotirada saqlaydi va dasturlarni lentaga saqlaydi. Har holda, men kalit so'zlarni qarshi tekshirdim
Men shuningdek, o'sha davrdagi mashhur Atari, Commodore 64 va boshqa bir nechta kompyuterlarning BASIC kalit so'zlarini tekshirdim, ular uchun hujjatlarni topa oldim, ammo muvaffaqiyatga erishmadi - retro kompyuterlar turlari haqidagi bilimim unchalik keng emas edi.
Keyin men borishga qaror qildim
Kompyuter Tandy/Radio Shack TRS-80
Maqolaning boshida misol tariqasida keltirgan ushbu audioyozuv kompyuterda shunday qilingan bo'lishi mumkin:
Ma'lum bo'lishicha, ushbu kompyuter va uning navlari (Model I/Model III/Model IV va boshqalar) bir vaqtlar juda mashhur bo'lgan (albatta, Rossiyada emas). E'tiborlisi, ular ishlatgan protsessor ham Z80 edi. Ushbu kompyuter uchun siz Internetda topishingiz mumkin
Men emulyatorni yuklab oldim
Men ham topdim
CAS fayl formatini aniqlab, (bu menda allaqachon mavjud bo'lgan lentadagi ma'lumotlarning bir oz nusxasi bo'lib chiqdi, sinxronlash bayti mavjud bo'lgan sarlavhadan tashqari) Mening dasturimga bir nechta o'zgarishlar kiritildi va emulyatorda ishlaydigan ishlaydigan CAS faylini chiqara oldim (TRS-80 Model III):
Men birinchi pulsni va mos yozuvlar pulslari orasidagi masofani GEM to'plami sifatida avtomatik aniqlash bilan konversiya yordam dasturining so'nggi versiyasini ishlab chiqdim, manba kodi quyidagi manzilda mavjud.
xulosa
Biz bosib o'tgan yo'l o'tmishga bo'lgan maroqli sayohat bo'lib chiqdi va yakunda javob topganimdan xursandman. Boshqa narsalar qatorida men:
- Men ZX Spectrum-da ma'lumotlarni saqlash formatini aniqladim va audio kassetalardan ma'lumotlarni saqlash/o'qish uchun o'rnatilgan ROM tartiblarini o'rgandim.
- Men TRS-80 kompyuteri va uning turlari bilan tanishdim, operatsion tizimni o'rgandim, namunaviy dasturlarni ko'rib chiqdim va hatto mashina kodlarida disk raskadrovka qilish imkoniyatiga ega bo'ldim (axir, barcha Z80 mnemonikalari menga tanish)
- "Rasmiy" yordam dasturi tomonidan tan olinmagan ma'lumotlarni o'qiy oladigan audio yozuvlarni CAS formatiga o'tkazish uchun to'liq yordamchi dastur yozildi.
Manba: www.habr.com