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.

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
Hosting By