ماقبل تاریخ
از آنجایی که من طرفدار سختافزارهای قدیمی هستم، یک بار یک ZX Spectrum+ از یک فروشنده بریتانیایی خریدم. به همراه خود کامپیوتر، چندین کاست صوتی حاوی بازی (در بستهبندی اصلی به همراه دستورالعملها) و همچنین برنامههای ضبط شده روی نوارها بدون هیچ علامت خاصی دریافت کردم. در کمال تعجب، دادههای نوارهای ۴۰ ساله به راحتی قابل خواندن بودند و من توانستم تقریباً تمام بازیها و برنامههای آنها را دانلود کنم.

با این حال، در برخی از نوارها، ضبطهایی پیدا کردم که مشخصاً توسط کامپیوتر ZX Spectrum ساخته نشده بودند. صدای آنها کاملاً متفاوت بود و برخلاف ضبطهای کامپیوتر فوقالذکر، با بارگذار کوتاه BASIC که معمولاً در ضبطهای همه برنامهها و بازیها وجود دارد، شروع نمیشد.
این موضوع مدتی مرا درگیر خود کرد - واقعاً میخواستم بدانم چه چیزی درون آنها پنهان است. اگر میتوانستم سیگنال صوتی را به صورت دنبالهای از بایتها بخوانم، میتوانستم نمادها یا هر چیزی را که منشأ سیگنال را نشان میدهد، جستجو کنم. نوعی باستانشناسی پسگرد.
حالا که همه راه را رفتهام و به برچسب خود کاستها نگاه کردهام، لبخند میزنم چون
جواب تمام مدت درست جلوی چشمانم بود
برچسب روی کاست سمت چپ نام کامپیوتر TRS-80 است و درست زیر آن نام سازنده نوشته شده است: «ساخته شده توسط Radio Shack در آمریکا»
(اگر میخواهید هیجان داستان را تا انتها حفظ کنید، روی اسپویل کلیک نکنید.)
مقایسه سیگنالهای صوتی
اول، بیایید ضبطهای صوتی را دیجیتالی کنیم. میتوانید به صدای آنها گوش دهید:
و طبق معمول، صدای ضبط شده از یک کامپیوتر ZX Spectrum به گوش میرسد:
در هر دو مورد، در ابتدای ضبط، به اصطلاح وجود دارد صدای خلبان — یک صدای تکفرکانسی (در اولین ضبط، بسیار کوتاه است، کمتر از ۱ ثانیه، اما هنوز قابل تشخیص است). صدای راهنما به کامپیوتر سیگنال میدهد تا برای دریافت دادهها آماده شود. معمولاً هر کامپیوتر فقط صدای راهنمای خود را بر اساس شکل و فرکانس سیگنال تشخیص میدهد.
شایان ذکر است که شکل سیگنال نیز خود به خود تغییر میکند. برای مثال، در ZX Spectrum، شکل آن مستطیلی است:

وقتی صدای بوق آزمایشی تشخیص داده میشود، ZX Spectrum نوارهای قرمز و آبی متناوبی را در حاشیه صفحه نمایش میدهد که نشان میدهد سیگنال شناسایی شده است. صدای بوق آزمایشی پایان مییابد. پالس همگامسازیکه به کامپیوتر سیگنال میدهد تا شروع به دریافت دادهها کند. این سیگنال با مدت زمان کوتاهتری (در مقایسه با صدای راهنما و دادههای بعدی) مشخص میشود (شکل را ببینید).
پس از دریافت پالس همگامسازی، کامپیوتر هر افزایش/کاهش سیگنال را ثبت میکند و مدت زمان آن را اندازهگیری میکند. اگر مدت زمان کمتر از یک حد مشخص باشد، یک بیت ۱ در حافظه نوشته میشود؛ در غیر این صورت، یک بیت ۰ نوشته میشود. بیتها به بایتها جمعآوری میشوند و این فرآیند تا زمانی که N بایت دریافت شود، تکرار میشود. عدد N معمولاً از سرآیند فایلی که بارگذاری میشود گرفته میشود. ترتیب بارگذاری به شرح زیر است:
- صدای خلبان
- هدر (با طول ثابت)، شامل اندازه دادههایی که باید دانلود شوند (N)، نام و نوع فایل
- صدای خلبان
- خود دادهها
برای اطمینان از بارگذاری صحیح دادهها، ZX Spectrum به اصطلاح ... را میخواند. بایت برابری (بایت برابری) که هنگام ذخیره یک فایل با XOR کردن تمام بایتهای دادههای نوشته شده محاسبه میشود. هنگام خواندن یک فایل، کامپیوتر بایت برابری را از دادههای دریافتی محاسبه میکند و اگر نتیجه با نتیجه ذخیره شده متفاوت باشد، پیام خطای "خطای بارگذاری نوار R" را نمایش میدهد. به طور دقیق، اگر کامپیوتر نتواند پالسی را در حین خواندن تشخیص دهد (وجود نداشته باشد یا مدت زمان آن از محدودیتهای خاصی تجاوز نکند)، ممکن است این پیام را زودتر نمایش دهد.
بنابراین، حالا بیایید ببینیم یک سیگنال ناشناخته چگونه به نظر میرسد:

این یک صدای آزمایشی است. شکل موج به طور قابل توجهی متفاوت است، اما واضح است که سیگنال از تکرار پالسهای کوتاه با فرکانس خاص تشکیل شده است. در نرخ نمونهبرداری ۴۴۱۰۰ هرتز، فاصله بین "قلهها" تقریباً ۴۸ نمونه است (مربوط به فرکانس حدود ۹۱۸ هرتز). بیایید این شکل را به خاطر بسپاریم.
حالا بیایید به قطعه داده نگاه کنیم:

اگر فاصله بین پالسهای منفرد را اندازهگیری کنیم، متوجه میشویم که فاصله بین پالسهای «بلند» هنوز حدود ۴۸ نمونه است، در حالی که بین پالسهای کوتاه حدود ۲۴ نمونه است. کمی جلوتر میروم و میگویم که معلوم شد پالسهای «مرجع» با فرکانس ۹۱۸ هرتز به طور مداوم از ابتدا تا انتهای فایل دنبال میشوند. میتوان فرض کرد که در حین انتقال داده، اگر یک پالس اضافی بین پالسهای مرجع رخ دهد، آن را به عنوان بیت ۱ و در غیر این صورت به عنوان بیت ۰ در نظر میگیریم.
در مورد پالس همگامسازی چطور؟ بیایید به شروع دادهها نگاه کنیم:

صدای آزمایشی پایان مییابد و دادهها بلافاصله شروع میشوند. کمی بعد، پس از تجزیه و تحلیل چندین ضبط صوتی مختلف، مشخص شد که اولین بایت داده همیشه یکسان است (10100101b، A5h). شاید کامپیوتر پس از دریافت دادهها، شروع به خواندن آنها میکند.
همچنین میتوانید متوجه تغییر در اولین پالس مرجع بلافاصله پس از آخرین ۱ در بایت همگامسازی شوید. این موضوع مدتها بعد در طول توسعه برنامه تشخیص داده، زمانی که دادههای ابتدای فایل قابل خواندن قابل اعتماد نبودند، کشف شد.
حالا بیایید سعی کنیم الگوریتمی را که فایل صوتی را پردازش کرده و دادهها را بارگذاری میکند، شرح دهیم.
در حال بارگیری داده ها
بیایید ابتدا چند فرض را در نظر بگیریم تا الگوریتم ساده بماند:
- ما فقط فایلها را با فرمت WAV در نظر خواهیم گرفت؛
- فایل صوتی باید با صدای آزمایشی شروع شود و در ابتدا نباید سکوت داشته باشد.
- فایل منبع باید فرکانس نمونهبرداری ۴۴۱۰۰ هرتز داشته باشد. در این حالت، فاصله بین پالسهای مرجع ۴۸ نمونه از قبل تعیین شده است و نیازی به محاسبه آن به صورت برنامهنویسی نداریم؛
- قالب نمونه میتواند هر (۸/۱۶ بیت/ممیز شناور) باشد - زیرا هنگام خواندن میتوانیم آن را به قالب مورد نیاز تبدیل کنیم.
- ما فرض میکنیم که فایل منبع از نظر دامنه نرمالسازی شده است، که باید نتیجه را پایدار کند؛
الگوریتم خواندن به شرح زیر خواهد بود:
- ما فایل را در حافظه میخوانیم و همزمان قالب نمونه را به ۸ بیت تبدیل میکنیم؛
- ما موقعیت اولین پالس را در دادههای صوتی تعیین میکنیم. برای انجام این کار، باید شماره نمونه را با حداکثر دامنه محاسبه کنیم. برای سادگی، آن را یک بار به صورت دستی محاسبه میکنیم. آن را در متغیر prev_pos ذخیره خواهیم کرد.
- عدد ۴۸ را به موقعیت آخرین پالس اضافه کن (pos := prev_pos + 48)
- از آنجایی که افزایش موقعیت به میزان ۴۸ واحد تضمین نمیکند که ما به پالس مرجع بعدی خواهیم رسید (به دلیل نقص نوار، عملکرد ناپایدار درایو نوار و غیره)، باید موقعیت پالس pos را تنظیم کنیم. برای انجام این کار، یک بخش داده کوچک (pos-8;pos+8) را میگیریم و حداکثر مقدار دامنه را پیدا میکنیم. موقعیت مربوط به حداکثر را در pos ذخیره میکنیم. در اینجا، ۸ = ۴۸/۶ یک ثابت تجربی به دست آمده است که تضمین میکند ما حداکثر صحیح را پیدا خواهیم کرد و بر سایر پالسهایی که ممکن است در نزدیکی باشند تأثیری نخواهد گذاشت. در موارد بسیار ضعیف، زمانی که فاصله بین پالسها به طور قابل توجهی کمتر یا بیشتر از ۴۸ واحد باشد، میتوانیم یک جستجوی پالس اجباری را پیادهسازی کنیم، اما من این الگوریتم را برای اهداف این مقاله شرح نمیدهم.
- در مرحله قبل، همچنین لازم بود بررسی شود که آیا پالس مرجع واقعاً پیدا شده است یا خیر. یعنی صرفاً جستجوی حداکثر، تضمین نمیکند که پالسی در یک بخش مشخص وجود داشته باشد. در آخرین پیادهسازی برنامه خواندن، تفاوت بین حداکثر و حداقل مقادیر دامنه در یک بخش را بررسی میکنم و اگر از حد مشخصی فراتر رود، وجود پالس را میشمارم. سوال دیگر این است که اگر پالس مرجع پیدا نشد، چه باید کرد. دو گزینه وجود دارد: یا دادهها به پایان رسیده و سکوت برقرار است، یا این باید به عنوان یک خطای خواندن در نظر گرفته شود. با این حال، برای سادهسازی الگوریتم، از این مورد صرف نظر میکنیم.
- مرحله بعدی تعیین وجود یک پالس داده (بیت ۰ یا ۱) است. برای انجام این کار، نقطه میانی قطعه (prev_pos;pos) middle_pos را برابر با middle_pos := (prev_pos+pos)/2 در نظر میگیریم و حداکثر و حداقل دامنهها را در مجاورت middle_pos روی قطعه (middle_pos-8;middle_pos+8) محاسبه میکنیم. اگر اختلاف بین آنها بیشتر از ۱۰ باشد، بیت ۱ را در نتیجه مینویسیم؛ در غیر این صورت، ۰ را مینویسیم. ۱۰ یک ثابت است که به صورت تجربی به دست میآید؛
- موقعیت فعلی را در prev_pos ذخیره کن (prev_pos := pos)
- ما از مرحله ۳ شروع میکنیم تا زمانی که کل فایل را خوانده باشیم؛
- بیتمپ حاصل باید به صورت مجموعهای از بایتها ذخیره شود. از آنجایی که هنگام خواندن، بایت همگامسازی را در نظر نگرفتیم، تعداد بیتها ممکن است مضربی از ۸ نباشد و همچنین مقدار جابجایی بیت مورد نیاز نیز ناشناخته است. در اولین پیادهسازی الگوریتم، من از بایت همگامسازی اطلاعی نداشتم و بنابراین به سادگی هشت فایل با تعداد بیتهای جابجایی مختلف ذخیره کردم. یکی از آنها حاوی دادههای صحیح بود. در الگوریتم نهایی، من به سادگی تمام بیتها را تا A5h حذف میکنم که به من امکان میدهد بلافاصله فایل خروجی صحیح را بدست آورم.
یک الگوریتم در روبی، برای علاقهمندان
من روبی را به عنوان زبان برنامهنویسی انتخاب کردم چون بیشتر وقتم را صرف برنامهنویسی با آن میکنم. این زبان گزینهی با کارایی بالایی نیست، اما به حداکثر رساندن سرعت خواندن هدف من نیست.
# Используем 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*")
نتیجه
بعد از امتحان کردن چندین نوع الگوریتم و ثابتها، خوشبختانه به نتیجهی فوقالعاده جالبی رسیدم:

بنابراین، با توجه به رشتههای کاراکتری، ما برنامهای برای رسم نمودار داریم. با این حال، متن برنامه فاقد کلمات کلیدی است. همه کلمات کلیدی به صورت بایت کدگذاری شدهاند (هر مقدار > 80h). حال باید بفهمیم کدام رایانه از دهه 80 میتوانست برنامهها را با این فرمت ذخیره کند.
در واقع، خیلی شبیه یک برنامهی بیسیک است. کامپیوتر ZX Spectrum تقریباً از همان فرمت برای ذخیرهی برنامهها در حافظه و ضبط آنها روی نوار استفاده میکند. محض احتیاط، من کلمات کلیدی را با ... مقایسه کردم. با این حال، نتیجه آشکارا منفی بود.
من همچنین کلمات کلیدی BASIC را برای کامپیوترهای محبوب آن زمان، آتاری، کمودور ۶۴ و چند مورد دیگر که میتوانستم مستنداتشان را پیدا کنم، بررسی کردم، اما موفق نشدم - دانش من از انواع کامپیوترهای قدیمی چندان گسترده نبود.
بعد تصمیم گرفتم برم دنبالش و بعد نگاهم به نام سازنده - Radio Shack - و کامپیوتر TRS-80 افتاد. اینها نامهایی بودند که روی برچسب نوارهای کاست روی میزم نوشته شده بودند! من قبلاً نه این نامها را شنیده بودم و نه با کامپیوتر TRS-80 آشنایی داشتم، بنابراین حدس زدم که Radio Shack یک تولیدکننده نوار کاست است، مثل BASF، Sony یا TDK، و TRS-80 زمان پخش آن بود. چرا که نه؟
کامپیوتر تندی/رادیو شک TRS-80
به احتمال زیاد صدای ضبط شده مورد نظر، که در ابتدای مقاله به عنوان مثال ارائه دادم، در رایانهای مانند این ضبط شده است:

مشخص شده که این کامپیوتر و انواع آن (مدل I/مدل III/مدل IV و غیره) در زمان خود (البته نه در روسیه) بسیار محبوب بودهاند. شایان ذکر است که پردازندهای که آنها استفاده میکردند نیز Z80 بود. اطلاعات مربوط به این کامپیوتر را میتوان به صورت آنلاین یافت. در دهه ۸۰ میلادی، اطلاعات مربوط به کامپیوتر در ... پخش شد. در حال حاضر چندین مورد وجود دارد کامپیوتر برای پلتفرمهای مختلف
من شبیه ساز رو دانلود کردم و برای اولین بار، توانستم ببینم که این کامپیوتر چگونه کار میکند. البته، کامپیوتر از خروجی رنگی پشتیبانی نمیکرد و وضوح صفحه نمایش فقط ۱۲۸x۴۸ پیکسل بود، اما افزونهها و اصلاحات متعددی وجود داشت که میتوانست وضوح صفحه نمایش را افزایش دهد. همچنین انواع سیستم عامل برای این کامپیوتر و پیادهسازیهایی از زبان بیسیک (که برخلاف ZX Spectrum، در برخی مدلها حتی به حافظه ROM هم منتقل نمیشد؛ هر نوع زبانی را میتوان از روی فلاپی دیسک بوت کرد، درست مانند خود سیستم عامل).
من هم پیدا کردم برای تبدیل فایلهای صوتی ضبط شده به فرمت CAS که توسط شبیهسازها پشتیبانی میشود، اما به دلایلی نمیتوانستم فایلهای ضبط شده از کاستهایم را با آنها بخوانم.
با فهمیدن فرمت فایل CAS (که معلوم شد فقط یک کپی بیت به بیت از دادههای نواری است که از قبل داشتم، به جز هدر با بایت همگامسازی)، چند تغییر در برنامهام ایجاد کردم و توانستم یک فایل CAS کارآمد در خروجی دریافت کنم که در شبیهساز (TRS-80 Model III) کار میکرد:

من آخرین نسخه از ابزار تبدیل را با تشخیص خودکار اولین پالس و فاصله بین پالسهای مرجع به عنوان یک بسته GEM طراحی کردهام، کد منبع در آدرس زیر موجود است. .
نتیجه
سفری که تا اینجا طی کردم، سفری جذاب به گذشته بوده است و خوشحالم که بالاخره جواب را پیدا کردم. از جمله موارد دیگر، من:
- من فرمت ذخیره دادهها در ZX Spectrum را کشف کردم و روالهای داخلی ROM برای ذخیره/خواندن دادهها از کاستهای صوتی را مطالعه کردم.
- من با کامپیوتر TRS-80 و انواع آن آشنا شدم، سیستم عامل را مطالعه کردم، برنامههای نمونه را بررسی کردم و حتی این فرصت را داشتم که کدهای ماشین را اشکالزدایی کنم (به هر حال، من با تمام نشانههای Z80 به خوبی آشنا هستم).
- من یک ابزار کامل برای تبدیل ضبطهای صوتی به فرمت CAS نوشتم که میتواند دادههایی را که توسط ابزار "رسمی" شناخته نمیشوند، بخواند.
منبع: www.habr.com
