Play YRG, RSF files from SD Card

This simple Arduino sketch is for playing YRG and RSF files from SD card root folder using AY-3-8910/12 YM2149F chips or emulator (in parallel mode) from this site. Sketch contains 2MHz generator for AY/YM chip.
(Updated 12.12.2015 updated RSF to v3)

Connections:

Arduino pins to AY/YM chip:

A0 – DA0, A1 – DA1, A2 – DA2, A3 – DA3, A4 – DA4, 5 – DA5, 6 – DA6, 7 – DA7

3 – BC1, 2 – BDIR

9 – frequency generator 2MHz

SD card:

MOSI – 11, MISO – 12, CLK(SCK) – 13, CS – 4

 

#include <SPI.h>
#include <SD.h>

const int CS_Pin = 4;
//                 PC0 PC1 PC2 PC3 PC4 PD5 PD6 PD7
const byte ad[8] = {A0, A1, A2, A3, A4,  5,  6,  7 }; // connect to DA0,1,...,7
const int pinBC1 = 3;
const int pinBDIR = 2;
const byte freqOutputPin = 9;   // OCR1A output pin for ATmega328, 2MHz output frequency for AY/YM chip


//Port at which pins BC1/BDIR is
#define __BCPORT__ PORTD
#define __BC1__ 3  // PORT PIN (PD3)
#define __BDIR__ 2 // PORT PIN (PD2)

void initFrequencyGenerator()
{ // Set Timer 1 CTC mode OCR1A toggles on compare match
    TCCR1A = 0x40;
    TCCR1B = 0x09; // prescaller
    // This value determines the output frequency: 0 - 16MHz, 1 - 8MHz, 2 - 4MHz, 3 - 2MHz, 4 - 1MHz
    OCR1A = 3;
}


File dataFile;
File entry,root;

void setup()
{
  //init pins
  for(byte i=0; i < 8; i++) pinMode(ad[i], OUTPUT);

  pinMode(CS_Pin, OUTPUT);

  pinMode(pinBC1, OUTPUT);
  pinMode(pinBDIR, OUTPUT);

  //inactive mode
  digitalWrite(pinBC1, LOW);
  digitalWrite(pinBDIR, LOW);

  Serial.begin(57600);

  // see if the card is present and can be initialized:
  if (!SD.begin(CS_Pin))
  {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized.");

  root = SD.open("/",FILE_READ);
  
  initFrequencyGenerator();
  pinMode(freqOutputPin, OUTPUT);
}



void send_data(byte address, byte data) {
// WRITE REGISTER NUMBER
  //write address to DA0-DA7 pins
  PORTC |= address & 0x1F; // DA0-DA4
  PORTD |= address & 0xE0; // DA5-DA7
  //validate addess
  //set BC1+BDIR bits, latch address mode
  __BCPORT__ |= (1 << __BDIR__) + (1 << __BC1__);
  asm("nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop"); //set+hold address delay 558ns (400+100 min)
  //clear BC1+BDIR bits, inactive mode
  __BCPORT__ &= ~((1 << __BDIR__) + (1 << __BC1__));
  // reset pins to tristate mode
  PORTC &= ~(address & 0x1F);
  PORTD &= ~(address & 0xE0);

// WRITE REGISTER DATA
  //write data to pins
  PORTC |= data & 0x1F;
  PORTD |= data & 0xE0;
  //validate data
  //set BDIR bit, write to reg mode
  __BCPORT__ |= ( 1 << __BDIR__); 
  asm("nop\nnop\nnop\nnop\nnop"); //310ns delay (250min-500max) nop=62ns on 16MHz
  //clear BDIR bit, inactive mode
  __BCPORT__ &= ~( 1 << __BDIR__); 
  // reset pins to tristate mode
  PORTC &= ~(data & 0x1F);
  PORTD &= ~(data & 0xE0);
}


byte buf[128];
byte buf2[14];
unsigned long t;

void play_yrg()
{
   while(dataFile.available())
   {
     // read 16 x16 regs
     byte bframes = dataFile.read(buf,128)/16;

     for (byte frame = 0; frame < bframes; frame++)
     {
       // send diff registers from current frame
       for (byte reg = 0; reg < 14; reg++)
       {
           if (reg == 13 && buf[frame*16+reg]==255) break;
           if(reg == 13 || buf2[reg] != buf[frame*16+reg]) send_data(reg, buf[frame*16+reg]);
       }
       memcpy(buf2,&buf[frame*16],14);
       //delay(20);
       while(millis() - t < 20); 
       t = millis();
     }
   }
}

void play_rsf()
{
   //unsigned long frames, loopframe, offset;
   word freq, offset;
   byte val, zeroes, ptr = 0, count, mask2, mask1, delay_time, skip;
   
   if( dataFile.read(buf,4) <= 0 ) return; // short file

   if(buf[0] != 'R' || buf[1] != 'S' || buf[2] != 'F') return; //not RSF
   
   switch(buf[3])
  { // reading RSF HEADER v3 only supported!
     case 3: // RSF ver.3
           if( dataFile.read(buf,14) == 0 ) return; // short file
           memcpy(&freq,&buf[0],sizeof(word));
           memcpy(&offset,&buf[2],sizeof(word));
           //memcpy(&frames,&buf[4],sizeof(unsigned long));
           //memcpy(&loopframe,&buf[8],sizeof(unsigned long));
           break;
     default:
           return;
   }

   if( freq > 1000 ) return; // frequency is too fast
   
   delay_time = 1000 / freq;
  
   // skip text info
   dataFile.seek(offset);
   
   // play song data
   if( (count = dataFile.read(buf,128)) <= 0 ) return;

   for(;;) {   
         if(ptr > count>>1)
         { // half buffer is already played move it and load more
           byte msize = count - ptr;
           memmove(buf,&buf[ptr], msize);
           if( (count = dataFile.read(&buf[msize],ptr)) == 0 ) return;
           count += msize;
           ptr = 0;
         }
         skip = 1;
         val = buf[ptr++];
       
         switch(val)
         {
           case 255:
               break;
           case 254:
               skip = buf[ptr++];
               if(ptr >= count) return;
               break;
           default:
               mask2 = val;
               mask1 = buf[ptr++];
               byte reg = 0;
               while( mask1 != 0)
               {
                   if(mask1 & 1) send_data(reg,buf[ptr++]);
                   mask1 >>= 1;
                   reg++;
               }
               reg = 8;
               while( mask2 != 0)
               {
                   if(mask2 & 1) send_data(reg,buf[ptr++]);
                   mask2 >>= 1;
                   reg++;
               }
         }             

         //delay(delay_time);
         while(millis() - t < delay_time * skip);
         t = millis();
   }
}

void loop()
{
 byte file_type;
  // reset AY
 for(int i=0;i<14;i++) send_data(i, 0);
 memset(buf2,0,14);

 for(;;) { // find file
    file_type = 0;
    if(entry) entry.close();
    entry = root.openNextFile();
    if(!entry) { // end of files on SD
       root.rewindDirectory();
       entry = root.openNextFile();
    }
    
    if(entry.isDirectory()) continue;
    
    if(strcasestr(entry.name(),".yrg")) {
      file_type = 1;
      break;
    }
    if(strcasestr(entry.name(),".rsf")) {
      file_type = 2;
      break;
    }
 }
 dataFile = SD.open(entry.name(),FILE_READ);
 Serial.print("File: ");
 Serial.println(entry.name());

 t = millis();
 
 switch(file_type)
 {
    case 1:
        play_yrg();
        break;
    case 2:
        play_rsf();
        break;
 }
 
 dataFile.close();
 entry.close();
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *