Playing YRG, RSF files from SD Card (with emulator)

This simple Arduino sketch is for playing YRG and RSF files from SD card root folder using  emulator (in serial mode) from this site. (Updated 12.12.2015 to RSFv3)

Connections:

Arduino pins to emulator pins:

1 (TX Pin) -> RX pin (emulator)

SD card:

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

 

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

const int CS_Pin = 4;

File dataFile;
File entry,root;

void setup()
{
  pinMode(CS_Pin, OUTPUT);

  // see if the card is present and can be initialized:
  if (!SD.begin(CS_Pin)) return;

  root = SD.open("/",FILE_READ);  

  Serial.begin(57600);
}


#define BUF_MAX 16*15
static byte buf[BUF_MAX];
static byte buf2[29];
unsigned long t;


/// PLAY YRG FILE ==============================
void play_yrg()
{
   byte regbuf[28];
   byte bframes;
   
   while(dataFile.available())
   {
     // read 16 x16 regs
     bframes = dataFile.read(buf,BUF_MAX)/16;
     for (byte frame = 0; frame < bframes; frame++)
     {
       // send diff registers from current frame
       byte buf_p = 0;
       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]) {
             regbuf[buf_p++] = reg;
             regbuf[buf_p++] = buf[frame*16+reg];
           }
       }
       Serial.write(regbuf,buf_p);
       memcpy(buf2,&buf[frame*16],14);
       //delay(20);
       while(millis() - t < 20); 
       t = millis();
     }
   }
}

/// PLAY RSF FILE ==============================
void play_rsf()
{
   //unsigned long frames, loopframe,curframe=0;
   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,BUF_MAX)) <= 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;
         }
         val = buf[ptr++];
         skip = 1;       
         switch(val)
         {
           case 255:
               break;
           case 254:
               skip = buf[ptr++];
               if(ptr >= count) return;
               break;
           default:
               mask2 = val;
               mask1 = buf[ptr++];
               byte reg = 0, reg_p = 0;
               while( mask1 != 0)
               {
                   if(mask1 & 1) {
                     buf2[reg_p++] = reg;
                     buf2[reg_p++] = buf[ptr++];
                   }
                   mask1 >>= 1;
                   reg++;
               }
               reg = 8;
               while(mask2 != 0)
               {
                   if(mask2 & 1) {
                     buf2[reg_p++] = reg;
                     buf2[reg_p++] = buf[ptr++];
                   }
                   mask2 >>= 1;
                   reg++;
               }
               Serial.write(buf2,reg_p);
         }             
         //curframe += skip;
         while(millis() - t < delay_time * skip); // delay
         t = millis();
   }
}

void loop()
{
 byte file_type;
  // reset AY
 memset(buf2,0,29);
 buf2[0] = 255;
 for(byte i = 0; i < 14; i++) buf2[i*2+1]=i;
 Serial.write(buf2,29);
 memset(buf2,0,29);

 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);

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

 dataFile.close();
 entry.close();
}

 

Optimized sketch used SdFat library

#include <SdFat.h>
#undef USE_ARDUINO_SPI_LIBRARY
#define USE_ARDUINO_SPI_LIBRARY 0

#define CS_Pin 4
#define BAUD_RATE 57600
#define BAUD_PRESCALE (((F_CPU/(BAUD_RATE*16UL)))-1)

SdFat SD;
SdFile dataFile;

void serialwrite(byte* buf,byte bsize)
{
  for(byte i=0 ; i < bsize; i++)
  {
    while ((UCSR0A & (1 << UDRE0)) == 0) {}; // Do nothing until UDR is ready for more data to be written to it
    UDR0 = buf[i]; // Send out the byte value in the variable "ByteToSend"
  }
}


void setup()
{
  if (!SD.begin(CS_Pin, SPI_FULL_SPEED)) return;//SD.initErrorHalt();

  // initialize serial interface
  UCSR0B |= (1<<RXEN0)  | (1<<TXEN0);
  UCSR0C |= (1<<UCSZ00) | (1<<UCSZ01);
  UBRR0H = BAUD_PRESCALE >> 8;
  UBRR0L = BAUD_PRESCALE;
}

#define BUF_MAX 16*5

static byte buf[BUF_MAX];
static byte buf2[29];
static unsigned long t;


/// PLAY YRG FILE ==============================
void play_yrg()
{
   byte regbuf[28];
   byte bframes;
   
   while((bframes = dataFile.read(buf,BUF_MAX)/16) > 0)
   {
     for (byte frame = 0; frame < bframes; frame++)
     {
       // send diff registers from current frame
       byte buf_p = 0;
       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]) {
             regbuf[buf_p++] = reg;
             regbuf[buf_p++] = buf[frame*16+reg];
           }
       }
       serialwrite(regbuf,buf_p);
       memcpy(buf2,&buf[frame*16],14);
       
       while(millis() - t < 20); //delay(20);
       t = millis();
     }
   }
}

/// PLAY RSF FILE ==============================
void play_rsf()
{
   //unsigned long frames, loopframe,curframe=0;
   word freq, offset;
   byte val, 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.seekSet(offset);

   // play song data
   if( (count = dataFile.read(buf,BUF_MAX)) <= 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;
         }
         val = buf[ptr++];
         skip = 1;       
         switch(val)
         {
           case 255:
               break;
           case 254:
               skip = buf[ptr++];
               break;
           default:
               mask2 = val;
               mask1 = buf[ptr++];
               byte reg = 0, reg_p = 0;
               while( mask1 != 0)
               {
                   if(mask1 & 1) {
                     buf2[reg_p++] = reg;
                     buf2[reg_p++] = buf[ptr++];
                   }
                   mask1 >>= 1;
                   reg++;
               }
               reg = 8;
               while(mask2 != 0)
               {
                   if(mask2 & 1) {
                     buf2[reg_p++] = reg;
                     buf2[reg_p++] = buf[ptr++];
                   }
                   mask2 >>= 1;
                   reg++;
               }
               serialwrite(buf2,reg_p);
         }             

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

///=====================================================
void loop()
{
 byte file_type;
 char fname[12]; // using short filenames
 
  // reset AY
 memset(buf2,0,29);
 buf2[0] = 255;
 for(byte i = 0; i < 14; i++) buf2[i*2+1]=i;
 serialwrite(buf2,29);
 memset(buf2,0,29);

 for(;;) { // find file
    file_type = 0;

    if(!dataFile.openNext(SD.vwd(), O_READ)) dataFile.openRoot(dataFile.volume()); // end of files on SD
    
    if(dataFile.isDir()) {
      dataFile.close();
      continue;
    }
    
    dataFile.getFilename(fname);
    
    if(strcasestr(fname,".yrg")) {
      file_type = 1;
      break;
    }
    if(strcasestr(fname,".rsf")) {
      file_type = 2;
      break;
    }
 }

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

 dataFile.close();
}

connect

9 thoughts on “Playing YRG, RSF files from SD Card (with emulator)

      1. Hi,

        It seems I need some more help.
        All I get is some “click click click click” or a machine-like noise. With rsf I get click (2 / second), with yrg it’s faster and sounds like a machine working.

        I’ve got a pair of Atmega8, I managed to flash them with this command:

        avrdude -p atmega8 -c USBasp -U flash:w:AY_Emul_244_2ch_speaker.hex -U eeprom:w:Conf_serial_25MHz_1_75Mhz.hex -U lfuse:w:0xCE:m -U hfuse:w:0xCF:m

        then it seems it flashed correctly, I got at the end:

        /…/
        avrdude: verifying …
        avrdude: 1 bytes of hfuse verified
        avrdude: safemode: Fuses OK (H:FF, E:CF, L:CE)
        avrdude done. Thank you.

        I’m using a 25.000H4J crystal, and I’ve double checked the connexions (like on your sketch, I only connected vcc and ground on the sd card reader because I supposed it was necessary to make it work. I’ve only used the sd.h version, not the optimized one. I’ve downloaded the sample songs pack in rsf format from your website (http://www.avray.ru/music_collections/, the last one with 7 songs). I’ve put the tracks at the root of a 1 gb sd card (which i’ve already used for some arduino tests with this sd card reader, it’s in fat16 format).

        When I power the arduino (nano), I get the click click / noise sound and the Tx led is synchronised to the sound (it blinks quite fast)

        I’ve tried to compile the other sketch with the sdfat lib from greiman’s github, but it’s doesn’t even compile, maybe the version used is another one?

        Any idea why I don’t get music?

        1. Hi, if TX pin led flases fast, it look like serial data comes ok. But, you use SPEAKER version, which is for ZX-Spectrum speaker port. Also, I’ve not understand which emulator pin you connected to Arduino, it should be RX pin on emulator and TX pin on Arduino. Not all Atmega8 can work with 25MHz crystall, try 24MHz and 20MHz crystalls, may be them will work, if not, double check all connections.
          Check also that config for emulator in same folder.

          1. Thank you, it’s working fine now.
            I made a couple of mistake, first my SD card reader was underpowered (3V instead of 5V), then I had only connected VCC + GND on the atmega8, there is also AVCC + the other GND which I’ve forgotten. It seems the arduino can power both the atmega chip and the SD card with its 5V output, I don’t know if it drains too much current though.
            I don’t know either if AREF is needed, some sketches show it connected to AVCC as well, but it works without so far.

            Now I need to make and wire a proper board for the AY player, fill the SD cards with nice chiptunes, and it will be awesome! Thanks again for the help, and for the whole AVR-AY project.

            1. Thanks! And congratulations!
              You need to connect only VCC and GND, no AVCC & AREF needed, also you may use one of two GND by your choice (which is convenient to use in a circuit). And yes, arduino can drive emulator as it use not more than 40mA

              1. I was trying to record some music from the emulator, and found the output was quite harsh because the separation of the channel A and C is total. I found a simple way to soften it a bit by adding a 680 ohm resistor (or similar) between pins PB1 and PB2. It should also be possible to add a potentiometer to make it even more customizable!

Leave a Reply

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