読者です 読者をやめる 読者になる 読者になる

フアミコン兄さんの朝までアップアップ

twitterで流してしまうにはもったいないネタをこちらへ。お問い合わせはtwitterID:@fcneetまで。

手のひらサイズのS98プレイヤーを作る

4周遅れくらいですが、僕も手のひらサイズのS98プレイヤーを作りました。

 

_DSC0022

これは何?

 SDカードに入れたS98ファイルをスタンドアロンで再生できるボードです。YMF288(OPL3)の実チップを使ってOPN3-L用の楽曲を再生できます。ボード上にマイコン(ATMEGA328)、Sdカードスロット(基板裏面)、YMF288、DAC、アンプ、操作ボタン、表示用LCDを搭載しています。

 

 

 機能

  • SDカード(FAT32対応)内のS98ファイルを再生可能
  • OPN3-L用の楽曲データに対応します(PCMパートは非対応)
  • S98v3対応(ただしPCMパート入りデータは非対応、今の所はFMパートのみの再生も不可)
  • 操作ボタンを使って再生・停止・曲送り・頭出しが可能(現時点では未実装)
  • LCDにファイル名・ステータスを表示可能

回路図・ソースコード

YMF288のピンアサイン、サンプルコードはVainDreams別館様を参考にさせていただきました。

 

回路図はこちら

 

ソースコードにビルドには以下のライブラリが必要です。

 

/*
  YMF288 driver for YMF288 Player Unit
   License: GNU General Public License V3

 (C) Copyright 2015 TaisukeWatanabe(@fcneet)

  【参考資料】
  ・YMF288-M ピン配列&回路 (http://d.hatena.ne.jp/m_yanase/20131007/1381151650)
  ・YMF288変換基板+Arduinoで簡易S98プレーヤを作る(その1~2)
    (http://d.hatena.ne.jp/m_yanase/20130910/1378810438)
  ・S98v3フォーマット(http://www.vesta.dti.ne.jp/~tsato/soft_s98v3.html
  ・S98プレイヤを作ってみる(http://risky-safety.org/zinnia/sdl/works/fmgen/s98sdl/
*/

#include <SD.h>
#include <TimerOne.h>
#include "ring_buffer.h"

// リングバッファのサイズ
//
#define BUF_SIZE 256

static const int data_pins[] = { 0,1,2,3,4,5,6,7 };  // D0-D7
static const int RST = 8;                           // Reset
static const int A0_ = 17;                           // A0
static const int A1_ = 18;                           // A1
static const int WR  = 15;                           // Read Enable
static const int CS  = 14;                           // Chip Select

// SDカード CS Pin定義
const int chipSelect = 10;

// S98ファイルヘッダ構造体
//
struct S98Header {
  char Magic[3];
  char Format[1];
  unsigned long Timer;
  unsigned long Timer2;
  unsigned long Compress;
  unsigned long NamePtr;
  unsigned long DataPtr;
  unsigned long LoopPtr;
  unsigned long DeviceCount;
  unsigned long DeviceInfo;
};

// クラス OPN3L_MDL 宣言
//
class OPN3L_MDL{
public:
  // コンストラクタ
  OPN3L_MDL(){
  }
  // モジュールリセット
  void reset(){
    digitalWrite(RST, LOW);  // モジュールリセット
    delayMicroseconds(100); // min. 25us
    digitalWrite(RST, HIGH);
    delay(100);
  }
  // レジスタライト
  void reg_write(unsigned char ifadr, unsigned char adr, unsigned char dat){
    law_write(ifadr, adr, dat);
    switch(adr){  // データライト後のWait
      case 0x28:{ // FM Address 0x28
        delayMicroseconds(26);  // min: 24us wait
        break;
      }
      case 0x10:{ // RHYTHM Address 0x10
        delayMicroseconds(24);  // min: 22us wait
        break;
      }
      default:{  // Other Address
        delayMicroseconds(3);  // min.1.9us wait
      }  
    }
  }
private:
  // データバスセット
  void write_data(unsigned char dat){      
    PORTD = (PORTD&0B00000000) | dat;  // ポートD0-D7へ出力データセット
  }
 
  // データライト
  void law_write(unsigned char ifadr, unsigned char adr, unsigned char dat)
  {
    PORTC = (PORTC &  ~0B01000);  // A0 LOW(Address Bus set)
    PORTC = (PORTC &  ~0B10000) | (ifadr << 4);  // A1 Set
    
    write_data(adr);         // Address set
    PORTC = (PORTC & ~0B00001);  // CS LOW
    delayMicroseconds(1);    //  min: 200ns wait
    PORTC = (PORTC |  0B00001);  // CS HIGH
    delayMicroseconds(3);    //  min: 1.9us wait
    
    PORTC = (PORTC |  0B01000);  // A0 HIGH(Address Bus set)
    write_data(dat);         // Data set
    PORTC = (PORTC & ~0B00001);  // CS LOW
    delayMicroseconds(1);  //  min: 200ns wait
    PORTC = (PORTC |  0B00001);  // CS HIGH
  }   
};

// グローバル変数
//
OPN3L_MDL mdl;             // GMC-MOD01制御クラス
File dir;
File dataFile;             // SDカードアクセスクラス
S98Header header;          // S98形式ヘッダ格納用構造体
rbuffer_t rbuf;            // リングバッファ構造体
char buf[BUF_SIZE+1];      // リングバッファのバッファ領域
int loop_count = 0;        // Sync waitカウント用変数

void attachTimerOne()
{
  if(header.Timer==0)
    Timer1.attachInterrupt(S98Play, 990 * 10);
  else
    Timer1.attachInterrupt(S98Play, 980 * header.Timer);  
}

// Setup関数
//
void setup(){
  int i;

  for(i=0; i<8; i++)  // Arduino Pin初期化
    pinMode(data_pins[i], OUTPUT);
  pinMode(A0_, OUTPUT);
  pinMode(A1_, OUTPUT);
  pinMode(WR, OUTPUT);
  pinMode(CS, OUTPUT);
  pinMode(RST, OUTPUT);

  mdl.reset();
 
  //  SDカードアクセス初期化
  pinMode(SS, OUTPUT);
  if (!SD.begin(chipSelect)) {
    while(1);
  }
 
  dir = SD.open("/");
  dataFile = dir.openNextFile();
  if (!dataFile)
  {
    while(1);
  }
 
  // S98ヘッダ情報取得
  dataFile.seek(0x0);
  dataFile.read(&header, sizeof(header));

  // リングバッファ初期化&データ先頭へシーク
  rbuffer_init( &rbuf, buf, BUF_SIZE+1 );
  dataFile.seek(header.DataPtr);
 
  digitalWrite(WR, LOW);

  // タイマ割込宣言
  // 1sync期間毎に割込を掛けるよう設定
  Timer1.initialize();
  attachTimerOne();
}

// Loop関数
//
void loop(){
}

// S98データ演奏関数
//
void S98Play(){
  int n,s,i;
  char devadr, dat, adrdat[2];
  char buf_tmp[BUF_SIZE];
 
  // リングバッファ内データサイズのチェック&データ積み込み
  if(rbuffer_size(&rbuf)<(BUF_SIZE/4)){
    dataFile.read(buf_tmp, (BUF_SIZE-rbuffer_size(&rbuf)));
    rbuffer_write(&rbuf, buf_tmp, (BUF_SIZE-rbuffer_size(&rbuf)));
  }

  // Sync wait中はwaitカウントを減じて関数を抜ける
  if(loop_count!=0){
    loop_count--;
    return;
  }
 
  do{
    // リングバッファのデータが3byte未満の時は関数を抜ける
    if(rbuffer_size(&rbuf)<3)
      return;

    rbuffer_read(&rbuf, &devadr, 1);
    switch(devadr){
      case 0x00:{
        rbuffer_read(&rbuf, adrdat, 2);
        mdl.reg_write( 0B0, adrdat[0], adrdat[1] );
        break;
      }
      case 0x01:{
        rbuffer_read(&rbuf, adrdat, 2);
        mdl.reg_write( 0B0, adrdat[0], adrdat[1] );
        break;
      }
      case 0xFF:{  // 1 sync wait
        loop_count = 0;
        return;
      }
      case 0xFE:{  // n sync wait
        n = s = 0; i = 0;
        do{
          ++i;
          rbuffer_read(&rbuf, &dat, 1);
          n |= (dat & 0x7f) << s;
          s += 7;
        } while(dat & 0x80);
        n += 2;
        loop_count = n-1;
        return;
      }
      case 0xFD:{  // データ末尾
        delayMicroseconds(2000 * 1000);
        mdl.reset();
        dataFile.close();
        dataFile = dir.openNextFile();
        if(!dataFile)
        {
          dir.rewindDirectory();
          dataFile = dir.openNextFile();
        }
        // S98ヘッダ情報取得
        dataFile.seek(0x0);
        dataFile.read(&header, sizeof(header));

        // リングバッファ初期化&データ先頭へシーク
        rbuffer_init( &rbuf, buf, BUF_SIZE+1 );
        dataFile.seek(header.DataPtr);
        Timer1.detachInterrupt();
        attachTimerOne();
        return;
      }
    }
  }while(1);
}

備考・今後の予定

前に作ったラズパイ用OPN(YM2151)ボードも何とかしなければ(´・ω・`)