Scott Hanselman

The Weekly Source Code 39 - Commodore 64 Emulator in Silverlight 3

March 27, '09 Comments [8] Posted in Mix | Open Source | Silverlight | Source Code | WPF
Sponsored By

C64 Application Icon I had the pleasure of interviewing Pete Brown this last week and talking about the Silverlight 3 Commodore 64 Emulator he's been working on. He just launched the CodePlex site a few minutes ago (my time), but I've had the code for a while to play with. You can read Tim Heuer's blog post for details on how to get started with Silverlight 3 Beta and the tools you'd need or see some video of the emulator in action.

Silverlight C64 Emulator

Keep in mind that this is a labor of love that Pete's doing, and the code has been written in "gett'er done" mode, so it won't win any awards for aesthetic. A lot of the code as been ported directly over from Open Source C++ in the Frodo Emulator or from Sharp C64.

It does have some pretty clever ideas, though, and I thought I'd take a look at those in this Weekly Source Code (which I promise to make more Weekly, starting now).

Dynamically Creating a Video Stream

Pete wanted the screen to draw as fast as possible, which is 50Hz (50 times a second). He was originally creating PNGs or Bitmaps and throwing it up on the screen as fast as possible, but then a member of the Silverlight team suggesting "making a video." What did he mean by "making a video?" He suggested actually using a Silverlight MediaElement (the "video player" control) and acting as a DataSource for a video. He's dynamically creating a movie that never ends.

This means the UI XAML is basically:

<MediaElement x:Name="VideoDisplay"
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Top"
Stretch="Uniform"
IsHitTestVisible="False"
Margin="4" />

And in the code behind he creates a VideoMediaStreamSource the had blogged about here, deriving from MediaStreamSource:

_video = new VideoMediaStreamSource(TheSID.Renderer.AudioStream, C64Display.DISPLAY_X, C64Display.DISPLAY_Y);

and it looks like:

private byte[][] _frames = new byte[2][];
public VideoMediaStreamSource(int frameWidth, int frameHeight)
{
_frameWidth = frameWidth;
_frameHeight = frameHeight;

_framePixelSize = frameWidth * frameHeight;
_frameBufferSize = _framePixelSize * BytesPerPixel;

// PAL is 50 frames per second
_frameTime = (int)TimeSpan.FromSeconds((double)1 / 50).Ticks;

_frames[0] = new byte[_frameBufferSize];
_frames[1] = new byte[_frameBufferSize];

_currentBufferFrame = 0;
_currentReadyFrame = 1;
}

public void Flip()
{
int f = _currentBufferFrame;
_currentBufferFrame = _currentReadyFrame;
_currentReadyFrame = f;
}

When he wants to write a pixel to his buffer, as he often does at the low level:

public void WritePixel(int position, Color color)
{
int offset = position * BytesPerPixel;

_frames[_currentBufferFrame][offset++] = color.B;
_frames[_currentBufferFrame][offset++] = color.G;
_frames[_currentBufferFrame][offset++] = color.R;
_frames[_currentBufferFrame][offset++] = color.A;

}

When it comes time to get a sample, the MediaSteamSource calls GetSampleAsync:

protected override void GetSampleAsync(MediaStreamType mediaStreamType)
{
if (mediaStreamType == MediaStreamType.Audio)
{
GetAudioSample();
}
else if (mediaStreamType == MediaStreamType.Video)
{
GetVideoSample();
}
}

He grabs a video frame from his buffer, he make a sample and reports he's done:

private void GetVideoSample()
{
_frameStream = new MemoryStream();
_frameStream.Write(_frames[_currentReadyFrame], 0, _frameBufferSize);

// Send out the next sample
MediaStreamSample msSamp = new MediaStreamSample(
_videoDesc,
_frameStream,
0,
_frameBufferSize,
_currentVideoTimeStamp,
_emptySampleDict);

_currentVideoTimeStamp += _frameTime;

ReportGetSampleCompleted(msSamp);
}

His app makes frames as fast as they can, putting them in the buffer at 50Hz, and the MediaElement requests frames from his VideoMediaStreamSource as fast as it can take them.

Emulating a 1541 Disk Drive

There's a file format in the world of C64 emulators that everyone has standardized on called .d64. The D64Drive.cs file contains the meat of the code to read these image files. "The *.D64 file format is a 1:1 copy of all sectors as they appear on a floppy disk."

Most of it looks like C/C++ code, because it once was. Some of it used to be "unsafe" C# code, writing with the unsafe keyword so the runtime could pin down pointers and use them directly.

I love it when there's things like byte[] magic. ;) Seems like every binary file format has them. In this case, we're looking for 0x43, 0x15, 0x41 and 0x64. Notice that 0x43 is "C", while the second and third bites are "1541" with the final "64" in there. ;)

private void open_close_d64_file(string d64name, Stream fileStream)
{
long size;
byte[] magic = new byte[4];

// Close old .d64, if open
if (the_file != null)
{
close_all_channels();
the_file.Dispose();
the_file = null;
}


// Open new .d64 file
if (fileStream != null)
{
//the_file = new FileStream(d64name, FileMode.Open, FileAccess.Read);
the_file = fileStream;

// Check length
size = the_file.Length;

// Check length
if (size < NUM_SECTORS * 256)
{
the_file.Dispose();
the_file = null;
return;
}

// x64 image?
the_file.Read(magic, 0, 4);
if (magic[0] == 0x43 && magic[1] == 0x15 && magic[2] == 0x41 && magic[3] == 0x64)
image_header = 64;
else
image_header = 0;

// Preset error info (all sectors no error)
Array.Clear(error_info, 0, error_info.Length);

// Load sector error info from .d64 file, if present
if (image_header == 0 && size == NUM_SECTORS * 257)
{
the_file.Seek(NUM_SECTORS * 256, SeekOrigin.Begin);
the_file.Read(error_info, 0, NUM_SECTORS);
}
}
}

This is all fun stuff, but as Pete said to me in an email:

"PS. My “real” code *never* looks like this. This is full of c++-isms and just plain “let me see if I can get this to work” junk."

So, take it for what it is. It's awesome.

References

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Friday, March 27, 2009 6:15:31 AM UTC
What is the C-64 equivalent of "but will it run Farcry?" ;) But seriously what an awesome project! I wish I had the time and know-how to do something this cool! :)
Joe Chung
Friday, March 27, 2009 8:54:13 AM UTC
Great job there dudes.

Such were the days when 16 colors was cool, 6510 Assembler was 'real' programming and, 'Micro Kid' by Level 42 was on a compact cassette loaded into the Sony Walkman. It's all too Retro!!!

Geek kids of today... you just don't know how lucky you are!
Andy B
Friday, March 27, 2009 5:09:11 PM UTC
Its awesome? Awesome to revive the Commodore 64? I think you guys really need to get a life... Whats next? Will you be whipping us back in the future with an 8-Track Player done in F# on a Cray Supercomputer?
Andy Foreman
Friday, March 27, 2009 5:26:31 PM UTC
Andy you totally shot down my next project! :P
Friday, March 27, 2009 5:39:56 PM UTC
@Andy

Get me a cray! :) (especially one as beautiful as the Jaguar, how cool is that that they took time to put a sprawling graphic across the entire set of racks)

I'll settle for a CX1 though.

You should check out the C64 / SID music and demo scenes. *tons* of cool stuff being done there today on a computer less powerful than modern phones. The C64 was the worlds best selling computer, we didn't have to revive it, it never left :)
Saturday, March 28, 2009 1:00:47 AM UTC
Doubly awesome. The C64 bit is awesome, but the use of a video stream in order to have pixel-level bitmap rasterization is really smart, I'm kicking myself for overlooking that possibility.
Saturday, March 28, 2009 3:36:08 AM UTC
@Jon

Pete Blois gave me the idea. You can just use the WriteableBitmap class if you'd like, but you'd have to manage sync timing yourself. It performs just as well, though.

Pete
Monday, March 30, 2009 8:50:49 AM UTC
Simply wonderful. Now I have to fire up my C64 emulator in my office... I mean, open Visual Studio and work! :P
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.