前置き
みなさん、よきMSXライフを送ってますか?
突然何言ってんだコイツ、と思った皆さんは正常だと思います。
ところで、MSXライフ送ってる人のうち、本当の実機を使ってる人は何%くらいなんでしょうかね? そんな自分も少し前までずっとエミュレータで、今更実機を触る事なんてもう無いんだろうなと思ってました。
しかしひょんな事から実家の押入れを掃除したら、出てきてしまったのです、あの思い出の実機が。懐かしさのあまり電源入れてみたら、電源装置は死亡しており、もはや電源は入りませんでした。
まぁそこは、結局は少々本体を改造して電源装置を別付けして動くようにしたんですけども。外付けのFDD装置ももはや怪しげ、それになんと言ってもフロッピーディスクなんて今時のPCにはもう無いし、何の役にも立たないのです。
思いついた事
時代が一周して、FDDなんて過去のもの、HDDですらSSDに代替されて過去のものになりつつある今、これからのMSX時代(そんなもんねぇよw)において確かなものって一体何なんでしょうか。
自分は実は、「カセットテープインタフェース」なのではないかと確信しております。WAVやMP3など音声フォーマットは未だに盛んだし、音声出力も当分廃れそうもありません。WindowsもMacも、44.1kHz(or 48kHz)の16bit音声出力なんて楽勝なはずです。
つまり?
MSXのカセットテープインタフェースを最新のPCでシミュレートすれば良い、という事なんです。
古のBLOADフォーマット(バイナリ)や、CLOADフォーマット(BASIC)などをPCでシミュレートして音声出力すれば、それをMSXで読ませればPC→MSXへのデータ伝送が可能です。
という事で作ってみた
個人的に使ってるPCはMacなので、事もあろうにObjective-Cで作ってみましたょ。
ざっくり概要を書くと、MSXのテープフォーマットは以下のような体裁なんです。
* ロングヘッダ (2,400Hz, 6.7秒程度)
* バイナリデータ 0xd0 × 10バイト
* ファイル名 6バイト
* 空白の時間 1秒程度
* ショートヘッダ (2,400Hz, 1.7秒程度)
* 開始アドレス(2byte), 終了アドレス(2byte), 実行開始アドレス(2byte)
* 転送データ (終了アドレス - 開始アドレス byte)
とまぁこんな感じです。
データそのものは、0: 1,200Hz, 1: 2,400Hzという組み合わせでビットを表し、1,200bpsの速度でゲロゲロゲロ...と鳴らし込んでやれば、MSXはテープからの信号かと勘違いして見事読み込んでくれるのです。
1,200/2,400Hzって...
色々実験した結果、Macの音声出力を11,025Hz/8bitとして作るのがもっとも精度良かったです。
11,025Hzということは、1秒間に11,025バイトのデータが基準であるという事。1,200Hzは、1秒間に1,200回High&Lowがある矩形波ですので、つまり11,025 / 1,200が1,200Hzの1波長分のバイト数という事になります。
1,200Hzの1波長はHigh&Lowの組み合わせなので、11,025/2,400のHighと、11,025/2,400のLowを連続して出してやると1,200Hzの音がサウンドとして出力されるというわけです。
こう言ったことを積み上げていき最終的に上記のフォーマットを再現してやれば、MacからバイナリデータをMSXに転送することができます。
ちょっとだけ具体的なソースの話
Macでは、CoreAudio
処理系のAudioUnit
を使ってみました。
ちょっと初期化部分だけ抜粋してみます。
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent component = AudioComponentFindNext(NULL, &desc);
if (component == NULL)
return NO;
AudioComponentInstanceNew(component, &_outputUnit);
if (self.outputUnit == NULL)
return NO;
//initialize the instance of AudioUnit with parameters
if (AudioUnitInitialize(self.outputUnit) != noErr)
return NO;
AURenderCallbackStruct callback;
callback.inputProc = outputCallback;
callback.inputProcRefCon = (__bridge void *)self;
if (AudioUnitSetProperty(self.outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &callback, sizeof(callback)) != noErr)
return NO;
AudioStreamBasicDescription outputFormat;
outputFormat.mSampleRate = self.samplingRate;
outputFormat.mFormatID = kAudioFormatLinearPCM;
outputFormat.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
outputFormat.mBitsPerChannel = self.bitsPerSample;
outputFormat.mChannelsPerFrame = 1;
outputFormat.mFramesPerPacket = 1;
outputFormat.mBytesPerFrame = outputFormat.mBitsPerChannel / 8 * outputFormat.mChannelsPerFrame;
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
if (AudioUnitSetProperty(self.outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outputFormat, sizeof(outputFormat)) != noErr)
return NO;
で、何を転送するのかな?
MacやLinux, Windows上でMSX用のプログラムをクロス開発するに決まってます。いやはや懐かしい、Z80のアセンブラはパズルみたいな様相を呈し、非常に面白いものです。
何となく、「最近コンピュータが面白くないな」「昔はもっと面白かったんだけどな」と感じたら、MSXでのプログラミングを改めて体験してみるのも悪くないです。
最後に
このテープ音声出力プログラムですが、ソース公開することを検討しています。
もし、少しでも反響があれば考えてみようかな、と。
もし、興味関心がある方いれば、ご連絡よろしくお願いします。内容はあまり綺麗にも書いてませんし、どうせ大して需要もないかなと思って今現在は個人的に閉じてこじんまりやっておりますw