ماقبل تاریخ
از آنجایی که عاشق سخت افزار یکپارچهسازی با سیستمعامل بودم، یک بار یک ZX Spectrum+ از فروشنده ای در بریتانیا خریدم. همراه با خود رایانه، چندین نوار کاست صوتی با بازی (در بسته بندی اصلی با دستورالعمل) و همچنین برنامه های ضبط شده روی نوارهای بدون علامت خاص دریافت کردم. در کمال تعجب، داده های کاست های 40 ساله به خوبی قابل خواندن بود و من توانستم تقریباً همه بازی ها و برنامه ها را از آنها دانلود کنم.
با این حال، در برخی کاستها، ضبطهایی پیدا کردم که به وضوح توسط کامپیوتر ZX Spectrum ساخته نشده بودند. صدای آنها کاملاً متفاوت بود و برخلاف ضبط های رایانه ذکر شده، با یک بوت لودر BASIC کوتاه که معمولاً در ضبط همه برنامه ها و بازی ها وجود دارد، شروع نمی کردند.
برای مدتی این من را آزار می داد - من واقعاً می خواستم بفهمم چه چیزی در آنها پنهان شده است. اگر میتوانید سیگنال صوتی را به صورت دنبالهای از بایت بخوانید، میتوانید به دنبال کاراکترها یا هر چیزی که منشأ سیگنال را نشان میدهد بگردید. نوعی رترو باستان شناسی.
حالا که تمام راه را رفتهام و به برچسبهای کاستها نگاه میکنم، لبخند میزنم چون
جواب در تمام مدت جلوی چشمانم بود
روی برچسب نوار کاست سمت چپ، نام کامپیوتر TRS-80 و دقیقاً زیر نام سازنده قرار دارد: "ساخت شده توسط Radio Shack در ایالات متحده آمریکا"
(اگر می خواهید فتنه را تا آخر نگه دارید زیر اسپویلر نروید)
مقایسه سیگنال های صوتی
اول از همه، بیایید صداهای ضبط شده را دیجیتالی کنیم. می توانید به صدای آن گوش دهید:
و طبق معمول صدای ضبط شده از رایانه ZX Spectrum به گوش می رسد:
در هر دو مورد، در ابتدای ضبط به اصطلاح وجود دارد لحن خلبان - صدایی با همان فرکانس (در ضبط اول بسیار کوتاه <1 ثانیه است، اما قابل تشخیص است). صدای خلبان به کامپیوتر سیگنال می دهد که برای دریافت داده ها آماده شود. به عنوان یک قاعده، هر کامپیوتر تنها صدای خلبان "خود" خود را با شکل سیگنال و فرکانس آن تشخیص می دهد.
لازم است در مورد خود شکل سیگنال چیزی بگوییم. به عنوان مثال، در ZX Spectrum شکل آن مستطیل است:
هنگامی که یک آهنگ پایلوت تشخیص داده می شود، طیف ZX نوارهای قرمز و آبی متناوب را در حاشیه صفحه نمایش می دهد تا نشان دهد که سیگنال شناسایی شده است. لحن خلبان به پایان می رسد پالس همگام، که به رایانه سیگنال می دهد تا شروع به دریافت داده کند. با مدت زمان کوتاه تر (در مقایسه با صدای خلبان و داده های بعدی) مشخص می شود (شکل را ببینید)
پس از دریافت پالس همگام سازی، کامپیوتر هر افزایش/افت سیگنال را ثبت می کند و مدت زمان آن را اندازه گیری می کند. اگر مدت زمان کمتر از حد معینی باشد، بیت 1 در حافظه نوشته می شود، در غیر این صورت 0. بیت ها به بایت جمع آوری می شوند و این فرآیند تا زمانی که N بایت دریافت شود تکرار می شود. عدد N معمولا از سربرگ فایل دانلود شده گرفته می شود. ترتیب بارگذاری به شرح زیر است:
- لحن خلبان
- هدر (طول ثابت)، شامل اندازه داده های دانلود شده (N)، نام و نوع فایل است
- لحن خلبان
- خود داده ها
برای اطمینان از بارگذاری صحیح داده ها، ZX Spectrum به اصطلاح می خواند بایت برابری (بایت برابری)، که هنگام ذخیره یک فایل با XOR کردن تمام بایت های داده های نوشته شده محاسبه می شود. هنگام خواندن یک فایل، کامپیوتر بایت برابری را از داده های دریافتی محاسبه می کند و اگر نتیجه با ذخیره شده متفاوت باشد، پیغام خطا "R Tape loading error" را نمایش می دهد. به بیان دقیق، اگر رایانه هنگام خواندن نتواند پالس را تشخیص دهد (از دست رفته یا مدت زمان آن با محدودیت های خاصی مطابقت ندارد) می تواند زودتر این پیام را صادر کند.
بنابراین، اکنون ببینیم یک سیگنال ناشناخته چگونه به نظر می رسد:
این لحن خلبان است. شکل سیگنال به طور قابل توجهی متفاوت است، اما واضح است که سیگنال شامل تکرار پالس های کوتاه با یک فرکانس خاص است. در فرکانس نمونه برداری 44100 هرتز، فاصله بین "پیک ها" تقریبا 48 نمونه است (که با فرکانس ~918 هرتز مطابقت دارد). بیایید این شکل را به خاطر بسپاریم.
بیایید اکنون به قطعه داده نگاه کنیم:
اگر فاصله بین پالس های فردی را اندازه گیری کنیم، معلوم می شود که فاصله بین پالس های "بلند" هنوز 48 نمونه و بین پالس های کوتاه - 24 است. با کمی نگاه کردن به آینده، می گویم که در پایان معلوم شد که پالس های "مرجع" با فرکانس 918 هرتز به طور مداوم از ابتدا تا انتهای فایل دنبال می شوند. می توان فرض کرد که هنگام انتقال داده، اگر یک پالس اضافی بین پالس های مرجع مواجه شد، آن را بیت 1 و در غیر این صورت 0 در نظر می گیریم.
در مورد پالس همگام سازی چطور؟ بیایید به ابتدای داده ها نگاه کنیم:
صدای خلبان به پایان می رسد و داده ها بلافاصله شروع می شوند. کمی بعد، پس از تجزیه و تحلیل چندین ضبط صوتی مختلف، ما توانستیم کشف کنیم که اولین بایت داده همیشه یکسان است (10100101b، A5h). ممکن است کامپیوتر بعد از دریافت داده ها شروع به خواندن کند.
همچنین می توانید به جابجایی اولین پالس مرجع بلافاصله پس از آخرین 1 در بایت همگام سازی توجه کنید. خیلی دیرتر در فرآیند توسعه یک برنامه تشخیص داده ها کشف شد، زمانی که داده های ابتدای فایل به طور پایدار قابل خواندن نبود.
اکنون بیایید سعی کنیم الگوریتمی را توصیف کنیم که یک فایل صوتی را پردازش کرده و داده ها را بارگذاری می کند.
در حال بارگیری داده ها
ابتدا، اجازه دهید به چند فرض برای ساده نگه داشتن الگوریتم نگاه کنیم:
- ما فقط فایل ها را با فرمت WAV در نظر خواهیم گرفت.
- فایل صوتی باید با لحن پایلوت شروع شود و در ابتدا حاوی سکوت نباشد
- فایل منبع باید دارای نرخ نمونه برداری 44100 هرتز باشد. در این حالت، فاصله بین پالس های مرجع 48 نمونه از قبل تعیین شده است و نیازی به محاسبه آن به صورت برنامه ای نداریم.
- قالب نمونه می تواند هر (8/16 بیت / نقطه شناور) باشد - زیرا هنگام خواندن می توانیم آن را به مورد دلخواه تبدیل کنیم.
- ما فرض می کنیم که فایل منبع با دامنه نرمال شده است، که باید نتیجه را تثبیت کند.
الگوریتم خواندن به صورت زیر خواهد بود:
- ما فایل را به حافظه می خوانیم، در همان زمان فرمت نمونه را به 8 بیت تبدیل می کنیم.
- موقعیت اولین پالس را در داده های صوتی تعیین کنید. برای این کار باید تعداد نمونه را با حداکثر دامنه محاسبه کنید. برای سادگی، یک بار آن را به صورت دستی محاسبه می کنیم. بیایید آن را در متغیر prev_pos ذخیره کنیم.
- 48 را به موقعیت آخرین پالس اضافه کنید (pos := prev_pos + 48)
- از آنجایی که افزایش موقعیت به میزان 48 تضمین نمی کند که به موقعیت پالس مرجع بعدی برسیم (نقایص نوار، عملکرد ناپایدار مکانیزم درایو نوار و غیره)، باید موقعیت پالس pos را تنظیم کنیم. برای انجام این کار، یک داده کوچک (pos-8;pos+8) بردارید و حداکثر مقدار دامنه را روی آن بیابید. موقعیت مربوط به حداکثر در pos ذخیره می شود. در اینجا 8 = 48/6 یک ثابت تجربی به دست آمده است، که تضمین می کند که ما حداکثر صحیح را تعیین می کنیم و بر سایر تکانه هایی که ممکن است نزدیک باشند تأثیر نمی گذارد. در موارد بسیار بد، زمانی که فاصله بین پالس ها بسیار کمتر یا بیشتر از 48 باشد، می توانید جستجوی اجباری برای یک پالس را اجرا کنید، اما در محدوده مقاله من این را در الگوریتم شرح نمی دهم.
- در مرحله قبل، بررسی اینکه آیا پالس مرجع اصلاً پیدا شده است نیز ضروری است. یعنی اگر به سادگی به دنبال حداکثر باشید، این تضمین نمی کند که تکانه در این بخش وجود داشته باشد. در آخرین اجرای برنامه خواندن، تفاوت بین مقادیر حداکثر و حداقل دامنه را در یک قطعه بررسی می کنم و اگر از حد معینی فراتر رفت، وجود یک ضربه را حساب می کنم. همچنین سوال این است که اگر پالس مرجع پیدا نشد چه باید کرد. 2 گزینه وجود دارد: یا داده ها به پایان رسیده است و سکوت به دنبال دارد، یا این باید به عنوان یک خطای خواندن در نظر گرفته شود. با این حال، برای ساده کردن الگوریتم، این مورد را حذف می کنیم.
- در مرحله بعد، باید وجود یک پالس داده (بیت 0 یا 1) را تعیین کنیم، برای این کار وسط قطعه (prev_pos;pos) middle_pos را برابر Middle_pos := (prev_pos+pos)/2 می گیریم و در برخی از همسایگی های middle_pos در قطعه (middle_pos-8;middle_pos +8) بیایید دامنه حداکثر و حداقل را محاسبه کنیم. اگر اختلاف بین آنها بیش از 10 باشد، بیت 1 را در نتیجه می نویسیم، در غیر این صورت 0. 10 ثابتی است که به طور تجربی به دست می آید.
- موقعیت فعلی را در prev_pos ذخیره کنید (prev_pos := pos)
- از مرحله 3 شروع کنید تا کل فایل را بخوانیم.
- آرایه بیت حاصل باید به صورت مجموعه ای از بایت ها ذخیره شود. از آنجایی که هنگام خواندن بایت همگامسازی را در نظر نگرفتیم، ممکن است تعداد بیتها مضرب 8 نباشد و مقدار بیت مورد نیاز نیز ناشناخته است. در اولین اجرای الگوریتم، من از وجود بایت همگام سازی اطلاعی نداشتم و بنابراین به سادگی 8 فایل را با تعداد بیت های افست مختلف ذخیره کردم. یکی از آنها حاوی داده های صحیح بود. در الگوریتم نهایی، من به سادگی تمام بیت ها را تا A5h حذف می کنم، که به من امکان می دهد بلافاصله فایل خروجی صحیح را دریافت کنم.
الگوریتم در روبی، برای علاقه مندان
من Ruby را به عنوان زبان نوشتن برنامه انتخاب کردم، زیرا ... من بیشتر اوقات روی آن برنامه ریزی می کنم. این گزینه کارایی بالایی ندارد، اما وظیفه افزایش سرعت خواندن تا حد امکان ارزش آن را ندارد.
# Используем 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*")
نتیجه
با امتحان چندین نوع الگوریتم و ثابت، خوش شانس بودم که چیز بسیار جالبی به دست آوردم:
بنابراین، با قضاوت بر اساس رشته کاراکترها، ما برنامه ای برای رسم نمودارها داریم. با این حال، هیچ کلمه کلیدی در متن برنامه وجود ندارد. همه کلمات کلیدی به صورت بایت کدگذاری می شوند (هر مقدار > 80 ساعت). اکنون باید دریابیم که کدام رایانه از دهه 80 می تواند برنامه ها را در این قالب ذخیره کند.
در واقع شباهت زیادی به برنامه بیسیک دارد. کامپیوتر ZX Spectrum برنامه ها را تقریباً با همان فرمت در حافظه ذخیره می کند و برنامه ها را روی نوار ذخیره می کند. فقط در مورد، من کلمات کلیدی را بررسی کردم
من همچنین کلمات کلیدی BASIC Atari ، Commodore 64 و چندین رایانه دیگر را در آن زمان بررسی کردم ، که توانستم اسنادی را برای آنها پیدا کنم ، اما بدون موفقیت - معلوم شد که دانش من در مورد انواع رایانه های قدیمی چندان گسترده نیست.
بعد تصمیم گرفتم برم
کامپیوتر Tandy/Radio Shack TRS-80
به احتمال بسیار زیاد صدای ضبط شده مورد نظر که در ابتدای مقاله به عنوان مثال آورده بودم، بر روی رایانه ای مانند این ساخته شده است:
معلوم شد که این کامپیوتر و انواع آن (Model I/Model III/Model IV و غیره) زمانی بسیار محبوب بودند (البته نه در روسیه). قابل ذکر است که پردازنده ای که استفاده کردند نیز Z80 بود. برای این کامپیوتر می توانید در اینترنت پیدا کنید
شبیه ساز رو دانلود کردم
من هم پیدا کردم
با فهمیدن فرمت فایل CAS (که معلوم شد فقط یک کپی بیت به بیت از داده های نواری است که قبلاً در دست داشتم، به جز هدر با حضور یک بایت همگام سازی)، یک تغییرات کمی در برنامه من انجام شد و توانستم یک فایل CAS کارآمد را که در شبیه ساز کار می کرد (TRS-80 Model III) خروجی بگیرم:
من آخرین نسخه ابزار تبدیل را با تعیین خودکار اولین پالس و فاصله بین پالس های مرجع به عنوان یک بسته GEM طراحی کردم، کد منبع در دسترس است
نتیجه
مسیری که ما طی کردهایم سفری جذاب به گذشته بود و خوشحالم که در پایان به پاسخ آن رسیدم. از جمله من:
- من فرمت ذخیره داده ها را در ZX Spectrum کشف کردم و روال های ROM داخلی را برای ذخیره/خواندن داده ها از کاست های صوتی مطالعه کردم.
- من با کامپیوتر TRS-80 و انواع آن آشنا شدم، سیستم عامل را مطالعه کردم، به برنامه های نمونه نگاه کردم و حتی این فرصت را داشتم که در کدهای ماشین اشکال زدایی کنم (به هر حال، تمام حافظه های Z80 برای من آشنا هستند)
- یک ابزار کامل برای تبدیل صداهای ضبط شده به فرمت CAS نوشت که می تواند داده هایی را بخواند که توسط ابزار "رسمی" شناسایی نشده اند.
منبع: www.habr.com