Tech Flashback: The CD+Graphics Format (CD+G)

I think by now, everyone knows I like to go on a salvage. This includes going regularly into thrift shops to see what’s new (or old) and what could be had for a reasonable price. This week had been rather interesting, as I was reminded of something I’d always wanted to know more about.

The Compact Disc and CD+G

The humble compact disc, better known as the CD, is a familiar format using a 12cm round polycarbonate disc of 1.2mm thickness for storing music (in the form of CD-DA), video (in the form of Video CD) and data (in the form of CD-ROM). With its large capacity for its time, it was used for a wide variety of other purposes including interactive multimedia usage through CDi (a format I’ve never experienced), as storage for various game consoles including the Playstation and as the recording for Karaoke machines (CD+G and CD+EG).

The CD+Graphics format, better known as CD+G, was a favourite amongst earlier karaoke machines. Using CDs that were backwards compatible with regular CD-DA, when inserted into a player supporting CD+G, the disc was able to present low-resolution graphics (commonly lyrics, timed to the music). This format has always intrigued me – as I have never previously owned a CD+G player or a CD+G disc.

It seems the CD+G format is capable of drawing a 300 x 216 pixel area with 288 x 192 pixels of active video surrounded by a single-coloured border. Many resources point to the CD+G Revealed document as documentation of how the system works, essentially using the six unused subcode channels to carry six bits per frame of data which is formatted into 24-byte packets which contain commands for a CD+G player to interpret. This document contradicts Wikipedia in claiming that the central 294 x 204 pixel area is displayed, however this does make the border half-a-tile in size. The document does contain a number of errors, later discovered, so a firm specification is likely only available from Philips/Sony with a payment.

A CD-DA disc stores 24 bytes (6 stereo samples) of data in each frame, with 8 bytes of error correction code and 1 byte of subcode with each “bit” being denoted a channel name (P through to W). The first two bits (P/Q) are used for audio navigation and timing, thus are not used by CD+G. As a result, 6-bits of data can be conveyed in each audio frame. At a sample rate of 44100Hz, this translates to a total of 7350 frames/second or 7350 bytes of subcode per second. This is approximately 35.28MB in 80-minutes, which is not much data at all. As each CD+G packet costs 24-bytes, and the visible area has about 49 x 17 tiles (using the larger value) for 833 total tiles, it would take around 2.72 seconds to fill the screen with a two-colour image. More time would be needed for extra colours or for system information and effects.

It was also interesting to note that each frame containing 33-bytes of data is actually coded up to 17-bits per byte (EFM + merging bits) resulting in 561 bits to which a 27-bit sync word is added to, resulting in a frame occupying 588 bits delivering 192 bits of audio.

A Thrifty Find

This week, while leafing through the CD stack at a local thrift shop, I came across my first CD+G in the wild. The title was a very uninspiring Pop Hits 2000 Plus Volume 1, seemingly made for the Vibes karaoke machine, which sounds like something a low-cost toy company would have.

Looking at the rear proved just that – as the disc seems to have been “produced” by Funtastic, which is a company that specialises in toys. It curiously says “Sony Factory Inc” – I’m not sure Sony would want anything to do with this, but that added even more to the allure.

The disc was nothing special – a standard pressed disc from DEX Audio.

For the cost of just 50c, I couldn’t say no. It was something I wanted to play with for a while. Another interesting find was this disc from back in 2005 – when Nokia was still on the top of the mobile phone world …

It’s no CD+G but it’s interesting to see Nokia’s name attached to something that’s not quite “mobile phone” …

… but of course, you could vote for the tracks using your phone. The Nokia Connecting Beats Competition 2005 disc was apparently given out for free as a promotional disc and listeners would vote for an upcoming artist to receive sponsorship. I wonder how that all went … but enough of this 50c diversion.

Playing CD+G

While there is a number of different free and paid-for software that can play CD+G files (which I won’t go into), the first step is necessarily to rip the disc so as to end up with an audio file and a CD+G file. Luckily, Karaokeware has a free CDG Rip tool which allows us to do just this. Unfortunately, it doesn’t seem to work with my virtual drive due to its insistence on using spindle-speed setting commands, and will require a physical drive capable of reading raw subcode (most modern drives would fit in this category).

Once ripped, it is possible to render the CD+G by opening the audio file in VLC, with the sidecar .cdg file automatically detected and rendered.

Unfortunately, it seems that VLC renders the CD+G in an awful quality resulting in indistinct edges and colour bleed which I suspect should not be happening. I found that it is possible to convert .cdg files to video using ffmpeg, although the durations will not be correct as it does not seem to respect the length of the .cdg file to determine the output duration. The resulting text is a lot more pleasing – here is a demo of part of one of the tracks on the disc as an animated GIF:

We can see the “slowness” in the way the tiles load and the blockyness of the text, along with the limited colours that is the charm of CD+G karaoke. Very retro. Also, it seems that the packaging did not mean Sony Factory at all, but instead meant Song Factory which seems to be an outfit that specialises in recreations of popular songs for karaoke use.

Interpreting CD+G Subcode Data

The first thing I did was instinctively open up the .cdg file in a hex editor to take a look.

Sure enough, there were CDG packets – visible starting with 0x09 periodically. These were spaced apart quite a distance with intervening 0x00 nulls as a way to time the actions on the screen. Looking through a hex editor was hardly instructive, so I set to work with the document and wrote myself a quick parser that would chew on the .cdg file and spit out the data. The parser is in the appendix – but basically it spits out the byte number, the time from the beginning, the command number, the command description and the command arguments.

I made a slight exception for the tile commands which instead spits out the tile graphics as zeroes and ones, along with the decimal value of that line. As we can see, it seems customary to start by loading the colour table and resetting the screen to clear it then drawing graphics – but it’s noted that commands are often repeated without consequence just to guard against error as subcode itself is not protected by the error correction in the same way.

By now, I realised that the CD+G Revealed document seems to have some errors – for example, the Memory Preset command second field should be the repeat counter – this always had 4 for some reason. Likewise, I was finding all of the Tile Blocks were being loaded to co-ordinates 0, 14 which was completely silly. Instead, I realised that in the case of tile blocks – there were two extra bytes that weren’t accounted for and the initial 22, 57 seemed to be a constant that had no explanation. This required cross-referencing with some other applications to figure out – as I was banging my head on this for a while.

CDGFix is one of the more popular apps out there to fix corrupted discs and it had a feature that allowed us to render a CD+G file step-by-step for troubleshooting. The free edition, however, didn’t have much editing or diagnostic capability, not at the “command” level, but I wasn’t going to pay as I had just the one 50c disc.

Instead, I came across Daryl’s CDG Editor which is free (as in beer), and did exactly what I wanted. In this case, it was able to show the tile block and co-ordinates – which is when I realised that I wasn’t even seeing the right tile block because I was two bytes out – the co-ordinate bytes.

Had I gone looking, I might not have written my quick and dirty parser at all – but it was still instructive to try and work it out on my own.

Creating CD+G data

Part of the reason I wanted to learn more about CD+G was that it was a very simple system, but it’s also possible to render without any equipment just in software. This makes it a potential retro-art form in a way. As a result, I wanted to create CD+G subcode streams that could be rendered – not just read/edit existing ones.

I started small just to make something that converted text into CD+G subcode. In order to ease this task, I chose a font that would be 6×12 pixels, the same dimensions as a CD+G tile. For this, I found Terminus, distributed as a .BDF. The next step was to get this into a C-array of sorts so that I could index into it with ASCII characters and generate the tiles – for this I found a Python script from littlevgl called bdf_font_converter.py inside the lv_utils project. Massaging the output slightly and writing some fixed header/trailer CD+G codes, it was possible to create a program that took text in and rendered a CD+G subcode data-stream out – although ignoring parity calculations for now. The code is in the appendix that follows this posting.

Here’s an example of my first trial – printing the CD+G Revealed document:

It was also proven that VLC is not capable of rendering such fine details properly – look at the mess it produces:

It seems that respecting the borders is necessary, as any tiles rendered into the border are invisible. From there, I also found it good to insert null packets just to slow down the printing to something more digestible.

While I haven’t experimented with colour or tiling larger graphics together at this stage, it was still very good to see that I could create something that several CD+G renderers would happily render.

Conclusion

It seems that CD+G is a simple format that uses the unused subcode bits in a CD-DA to convey short and simple commands to compatible players to render low-resolution graphics in up to 16-colours. The format didn’t seem too popular, mainly finding a niche in karaoke being more simple to implement, but even this seems to have been easily supplanted first by VCD, then DVD and so-on, as these formats offered full-motion-video flexibility with the display of more elaborate graphics. As a result, CD+G remains relatively obscure compared to the other mainstream CD formats, almost being completely irrelevant save for those who have ripped libraries of CD+G discs which can still be played through various software on computers today.

Appendix: CD+G Parser Code

The following code was based on information from the CD+G Revealed resource which has proven to be slightly inaccurate and missing the definition for Define Transparent Colour. As a result, it has been slightly changed with proper parsing for the Tile/Tile XOR commands, however, the accuracy of decoding cannot be guaranteed especially as I do not look at the parity check either as the information on this appears to be missing. Better to use commercial utilities (e.g. CDG Fix and CDG Editor) if you’re serious – this is just something I hacked together within 30 minutes including poor style and magic numbers everywhere just to more easily read .cdg files.

// Gough's Quick and Dirty CD+G Parser v0.02
// March 2019 - goughlui.com
// Based on CD+G Specification from https://jbum.com//cdg_revealed.html
// But deviates as I found CD+G uses 24-byte packets and there were
// errors in the interpetation of the Tile/XOR Tile commands.
// Bad style comes for free, with magic numbers and all!
// No guarantees are provided! No error checking!
// Reads CD+G Subcodes from STDIN, output to STDOUT.

#include <stdio.h>

int main (void) {
  int scdata = getchar();
  int cdgblock[22];
  int elapsed = 0;
  
  while(scdata!=EOF) {
    if((scdata&0x3F)==0x09) { // CD+G Cmd Follows
      scdata=getchar();
      elapsed++;
      for(int i=0;i<22;i++) {
        cdgblock[i]=getchar();
        elapsed++;
      }
      printf("%d,%f,%d,",elapsed,(float)elapsed/7350,scdata);
      switch(scdata&0x3F) {
        case 1:
          printf("Memory Preset Cmd,%d,%d\r\n",cdgblock[0]&0x0F,\
          cdgblock[1]&0x0F);
          break;
        case 2:
          printf("Border Preset Cmd,%d\r\n",cdgblock[0]&0x0F);
          break;
        case 6:
        case 38:
          if((scdata&0x3F)==6) {
            printf("Tile Block Cmd,");
          } else {
            printf("Tile Block XOR Cmd,");
          }
          printf("%d,%d,%d,%d,%d,%d\r\n",cdgblock[0]&0x3F,cdgblock[1]&0x3F,\
          cdgblock[2]&0x3F,cdgblock[3]&0x3F,cdgblock[4]&0x3F,cdgblock[5]&0x3F);
          for(int j=6;j<18;j++) {
            printf("\t\t%d%d%d%d%d%d-%d\r\n",(cdgblock[j]>>5)&1,\
            (cdgblock[j]>>4)&1,(cdgblock[j]>>3)&1,(cdgblock[j]>>2)&1,\
            (cdgblock[j]>>1)&1,cdgblock[j]&1,cdgblock[j]);
          }
          break;
        case 20:
        case 24:
          if((scdata&0x3F)==20) {
            printf("Scroll Preset Cmd,");
          } else {
            printf("Scroll Copy Cmd,");
          }
          printf("%d,%d,%d,%d,%d\r\n",cdgblock[0]&0x0F,\
          cdgblock[1]&0x30>>4,cdgblock[1]&0x07,cdgblock[2]&0x30>>4,\
          cdgblock[2]&0x0F);
          break;
        case 28:
          printf("Define Transparent Colour Cmd,%d\r\n",\
          cdgblock[0]&0x3F);
          // No Known Field Definition for This Command!!!
          break;
        case 30:
        case 31:
          if((scdata&0x3F)==30) {
            printf("Load Colour Table Low Cmd");
          } else {
            printf("Load Colour Table High Cmd");
          }
          for(int j=0;j<16;j=j+2) {
            printf(",%d,%d,%d",(cdgblock[j]&0x3D)>>2,\
            (((cdgblock[j]&0x03)<<6)|(cdgblock[j+1]&0xF0))>>4,\
            cdgblock[j+1]&0xF);
          }
          printf("\r\n");
          break;
        default:
          printf("Unknown Cmd\r\n");
      }
    }
    // End of CD+G Cmd, or No Cmd, so skip byte
    scdata=getchar();
    elapsed++;
  }
  printf("%d,%f,-1,End of File\r\n",elapsed,(float)elapsed/7350);
  return(0);
}

Appendix: CD+G Creation Code (minus font array)

I also wrote a very ugly and hacky code to convert text to CD+G subcode based on the Terminus 6×12 font converted into a C-array using this utility. The code minus the font array data is shown below, taking input from stdin and providing output to stdout. The code does not compute parity and just pads the rest of the packet with nulls.

// Quick and Dirty CD+G Text Writer v0.01
// Uses Terminus 6x12 Font from http://terminus-font.sourceforge.net/
// BDF Converter from https://github.com/littlevgl/lv_utils
// Based on CD+G Specification from https://jbum.com//cdg_revealed.html
// March 2019 - goughlui.com
// Bad style comes for free, with magic numbers and all!
// No guarantees are provided! No error checking!
// Reads text from STDIN, output CD+G codes to STDOUT.

// fontindex = array of pointers arranged by ASCII code into fontdata
// fontdata = actual font data bytes
// omitted in this paste for brevity

#include <stdio.h>

int main (void) {
  int cchar=getchar();
  int xco=1;
  int yco=1;
  int col0=0;
  int col1=1;
  int i;

  // Set colour pallettes
  printf("%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",\
  0x09,0x1E,0x00,0x00,0x3F,0x3F,0x00,0x00,0x3F,0x3F,0x00,0x00,\
  0x3F,0x3F,0x00,0x00,0x3F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00);
  printf("%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",\
  0x09,0x1F,0x00,0x00,0x3F,0x3F,0x00,0x00,0x3F,0x3F,0x00,0x00,\
  0x3F,0x3F,0x00,0x00,0x3F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00);
  // Recall memory for Background and Border
  printf("%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",\
  0x09,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);
  printf("%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",\
  0x09,0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);

  while(cchar!=EOF) {
    i=fontindex[cchar];
    printf("%c%c%c%c%c%c%c%c",0x09,0x06,22,57,col0,col1,yco,xco);
    for(int j=0;j<12;j++) {
      printf("%c",fontdata[i+j]>>2);
    }
    printf("%c%c%c%c",0x00,0x00,0x00,0x00);
    for(int j=0;j<720;j++) { // Option to slow down printing to 10cps
      printf("%c",0x00);
    }
    xco++;
    if(xco>48) {
      xco=1;
      yco++;
    }
    if(yco>16) {
      yco=1;
    } else {
      cchar=getchar();
    }
  }
  return(0);
}

Full code can be downloaded in this ZIP file including the demo .cdg created for demonstrating the format.

About lui_gough

I'm a bit of a nut for electronics, computing, photography, radio, satellite and other technical hobbies. Click for more about me!
This entry was posted in Audio, Computing, Tech Flashback and tagged , , , , , . Bookmark the permalink.

4 Responses to Tech Flashback: The CD+Graphics Format (CD+G)

  1. Gerry says:

    I was always fascinated by the CD+G format, too. I used Erik Deppe’s (free) CD+G Creator (http://users.telenet.be/erik.deppe/cdgcreator.htm) to build CDs displaying pictures while playing music (no karaoke functionality) for use on a hardware CD+G player (Commodore Amiga CDTV).

    Another interesting format I was looking around the internet for years without success – until literally a minute ago – is the CD+MIDI, the CD that contains hidden MIDI information (https://www.journaldulapin.com/2019/01/19/playing-a-cd-midi-the-cd-that-contains-hidden-midi/).

  2. Peter Finn says:

    Hi all. First up, I am not a tech head or code writer. Just a 58 year old mid level computer skills who has a juke box which is capable of playing VCD’s (video cd’s) successfully (all be it not great quality pictures). I (naively) bought some CD+G karaoke cd’s with the hope that I could insert them in the jukebox and have a bit of fun. They are the “Funtastic Vibes” brand as noted above. Question – is there anything I can do (simply) to successfully use these CD+G discs and have the karaoke words show on my tv display ? They play the music ok in the jukebox, but don’t display any words. Any help would be appreciated. Thanks, Peter

    • lui_gough says:

      You’re probably best to rip them using CDGRip as mentioned earlier. Then you could perhaps use ffmpeg to transcode them which will require some command line knowledge – if using Windows, I find Zeranoe’s FFmpeg builds to be the easiest to obtain. The transcoding settings will depend on your intended VCD format – e.g. by using (ffmpeg -i xyz.cdg -i xyz.wav -target pal-vcd out.mpg). Then you will need to use a VCD authoring software, e.g. Nero Burning ROM, to burn the .mpg files to disc in the appropriate VCD format.

      – Gough

      • Peter Finn says:

        Many thanks for your quick reply, didn’t expect it. Thanks for the advice, it’s beyond me, but my son in law may be able to assist, given we now have a direction. Kind regards, Peter Finn

Leave a Reply to Peter Finn Cancel reply