Extending BBC BASIC

Sunday, 1st February 2009

BBC BASIC may have originated with the 8-bit home computer era, but it's still being updated and its most up-to-date incarnation - BBC BASIC for Windows - has a wealth of features that are unavailable on the Z80 version.

The BBC BASIC graphics API is primarily accessed via the multi-purpose PLOT statement. PLOT is followed by three arguments - the type of graphics operation being carried out followed by an X and a Y coordinate. For example, to draw a line between (20,30) and (100,120) you could do this:

PLOT 4,20,30   : REM Move graphics cursor to (20,30)
PLOT 5,100,120 : REM Draw a line to (100,120)

This results in needing to remember a lot of different plot codes (there is a logic to how they are formed but I still need to consult a list of codes from time to time). All implementations of BBC BASIC feature two helper statements to aid the user:

  • MOVE x,y (equivalent to PLOT 4,x,y)
  • DRAW x,y (equivalent to PLOT 5,x,y)

More recent versions, such as BBC BASIC for Windows, also implements the following helper statements, amongst others:

  • CIRCLE x,y,r (equivalent to MOVE x,y : PLOT 145,r,0)
  • ELLIPSE x,y,w,h (equivalent to MOVE x,y : PLOT 0,w,0 : PLOT 193,0,h)
  • FILL x,y (equivalent to PLOT 133,x,y)
  • RECTANGLE FILL x,y,w,h (equivalent to MOVE x,y : PLOT 97,w,h)

This is all very well, but the BBC BASIC (Z80) interpreter is a sealed box as far as I am concerned. I can ask it to perform tasks for me ("evaluate this expression") and it can ask me to perform tasks for it ("output this character to the display") but I can't modify its behaviour.

BASIC ROM User Guide for the BBC Microcomputer and Acorn Electron coverOr, so I thought - until I read through the copy of BASIC ROM User Guide that a friend had rescued and sent to me. It has a section on adding statements, which it achieves by using a clever - but simple - trick.

When BBC BASIC encounters a statement it doesn't recognise it triggers the Mistake error. On the BBC Micro the error handler is vectored, meaning that it loads the address of the error handling routine from RAM first instead of jumping to a fixed address. This allows the user to override the normal error handler, detect the Mistake condition and try and parse the erroneous statement themselves. If they can't handle the statement either control is passed back to BBC BASIC's usual error handler, otherwise the error condition is cleared and execution continues as normal.

BBC BASIC (Z80) follows the same procedure but with one major difference - the error handler routine is not vectored. Unfortunately, the only practical workaround I can think of is to patch the interpreter's error handler routine directly. Richard Russell somehow managed to add support for additional commands to the Z88 version via a patch that runs from RAM, but I haven't been able to work out how he managed to do that yet.

Demonstration of ELLIPSE FILLThe first series of additional statements I added were the graphics helper statements listed above, WAIT (which pauses execution for a certain number of centi-seconds) and SWAP which exchanges the contents of two variables. These are all relatively simple statements to implement as they do not affect the state of BBC BASIC in any other way; they perform a single, simple task then exit.

One of the more useful additions to more recent versions of BBC BASIC is the WHILE...ENDWHILE loop structure. A limitation of BBC BASIC (Z80)'s statement blocks is that their contents must be executed at least once, hence IF statements must fit on one line, multi-line procedures or functions should be placed at the end of the file after an END statement and REPEAT...UNTIL loops - where the looping conditional is at the end of the block, rather than the start - are provided. If a WHILE condition evaluates to FALSE, control needs to resume at the matching ENDWHILE. This is an interesting technical challenge, as it needs to handle nested WHILE...ENDWHILE stataments when searching through the code to find the terminating ENDWHILE, but appears to work pretty well now.

Another useful recent addition is EXIT (in three variations - EXIT FOR, EXIT REPEAT and EXIT WHILE) which breaks out of a loop structure. This has the same technical challenges as the WHILE...ENDWHILE loop structure (searching for the matching loop terminator) with the additional difficulty of unwinding the stack to the correct position.

By combining WHILE loops and EXIT WHILE you can simulate multi-line IF statement blocks, so

IF <condition> THEN <statements>

becomes

WHILE <condition>
  <statements>
EXIT WHILE:ENDWHILE

These additions are not without their downsides. Most of the statements supported natively by BBC BASIC (Z80) are represented by single-byte tokens, whereas these extensions are stored as ASCII text. This makes them take up more room in the source file and slower to execute (searching for and handling strings is a much more complex operation than searching for bytes). Using them makes your programs incompatible with other versions of BBC BASIC (Z80). I personally feel that these disadvantages are far outweighed by the advantage of easier to read code, however.

To round the entry off, have a fractal. smile.gif

BBC BASIC for the TI-83+/TI-84+ beta release

Wednesday, 21st January 2009

Work commitments have prevented me from doing much on my own projects recently, but zipping up a few files to get BBC BASIC tested is not a time-consuming process so I've started to release test builds.

The documentation is available online. It's generated by a little tool I hacked together to turn a MediaWiki database into a CHM file.

I've had a few issues with the TI-84+ hardware, such as LCD corruption, difficulty in getting key presses to register and crashes when USB devices are unplugged. I think I've fixed the LCD and key issues by dropping the CPU speed down to 6MHz when the full 15MHz is not required, but am still stumped by the USB issues. Unfortunately documentation on the USB hardware is rather thin on the ground and I don't own a TI-84+ for testing.

The package comes with a few demo programs from the CP/M release and a few I cobbled together myself. Most recently I've tried putting together a few little graphics demos.

There's still a fair amount of work to go on this project (especially optimising - some of the code is extremely inefficient) but it feels nice to have something out there for people to try. smile.gif

C# emu2413

Thursday, 8th January 2009

This is fairly embarrassing; somebody sent me an email that was flagged as spam which I accidentally deleted. So if you sent me an email and I haven't replied, I'm not deliberately being rude; could you send it again? embarrass.gif



After encountering strange crashes (not .NET exceptions, full out crashes) with emu2413 I decided to port it to straight C# instead from its existing C incarnation (emu2413.h.cs and emu2413.c.cs). Even though the original was macro-heavy it was relatively simple to port, and so there's no dependency on an unmanaged DLL to generate FM sound any more. However, the C# version is significantly slower (Cogwheel now takes about 50% extra CPU time when FM sound is enabled), possibly due to many extraneous method calls that were macros in the original.

However, the emulator still crashes when FM sound is enabled. And I have no idea why, as it only happens in Release mode and outside the IDE. The Debug build works fine inside and outside the IDE, and Release mode works fine when run within the IDE. sad.gif

Controller input updates to Cogwheel

Monday, 5th January 2009

I hope you all had a good Christmas and New Year period!

I received an Xbox 360 controller for Christmas, so have done a bit of work on Cogwheel to add support for it. (You can download a copy of the latest version 1.0.2.0 with SlimDX here).

The first issue to deal with was the D-pad on the Xbox 360 controller. When treated as a conventional joystick or DirectInput device the D-pad state is returned via the point-of-view (POV) hat. The joystick input source class couldn't raise events generated by the POV hat so support for that had to be added. This now allows other controllers that used the POV hat for slightly bizarre reasons (eg the faceplate buttons on the PlayStation controller when using PPJoy) to work too.

The second issue was the slightly odd way that the Xbox 360's DirectInput driver returns the state of the triggers - as a single axis, with one trigger moving the axis in one direction, the other trigger moving it in the other. You cannot differentiate between both triggers being held and both being released, as both states return 0. To get around this, I've added support for XInput devices, where all buttons and triggers operate independently.

The Xbox 360 controller now shows up twice in the UI - once as an XInput device and again as a conventional joystick. Fortunately, you can check if a device is an XInput device by the presence of IG_ in its device ID. Here's some C# code that can be used to check with a joystick is an XInput device or not.

using System.Globalization;
using System.Management;
using System.Text.RegularExpressions;

namespace CogwheelSlimDX.JoystickInput {
    
    /// <summary>
    /// Provides methods for retrieving the state from a joystick.
    /// </summary>
    public class Joystick {

        /* ... */

        /// <summary>
        /// Gets the vendor identifier of the <see cref="Joystick"/>.
        /// </summary>
        public ushort VendorId { get; private set; }

        /// <summary>
        /// Gets the product identifier of the <see cref="Joystick"/>.
        /// </summary>
        public ushort ProductId { get; private set; }

        /* ... */

        /// <summary>
        /// Determines whether the device is an XInput device or not. Returns true if it is, false if it isn't.
        /// </summary>
        public bool IsXInputDevice {
            get {
                var ParseIds = new Regex(@"([VP])ID_([\da-fA-F]{4})"); // Used to grab the VID/PID components from the device ID string.

                // Iterate over all PNP devices.
                using (var QueryPnp = new ManagementObjectSearcher(@"\\.\root\cimv2", string.Format("Select * FROM Win32_PNPEntity"), new EnumerationOptions() { BlockSize = 20 })) {
                    foreach (var PnpDevice in QueryPnp.Get()) {

                        // Check if the DeviceId contains the tell-tale "IG_".
                        var DeviceId = (string)PnpDevice.Properties["DeviceID"].Value;
                        if (DeviceId.Contains("IG_")) {

                            // Check the VID/PID components against the joystick's.
                            var Ids = ParseIds.Matches(DeviceId);
                            if (Ids.Count == 2) {
                                ushort? VId = null, PId = null;
                                foreach (Match M in Ids) {
                                    ushort Value = ushort.Parse(M.Groups[2].Value, NumberStyles.HexNumber);
                                    switch (M.Groups[1].Value) {
                                        case "V": VId = Value; break;
                                        case "P": PId = Value; break;
                                    }
                                }
                                if (VId.HasValue && this.VendorId == VId && PId.HasValue && this.ProductId == PId) return true;
                            }
                        }
                    }
                }
                return false;
            }
        }

        /* ... */
    }
}

When the joysticks are enumerated they are only added to the input manager if they are not XInput devices.



To round up the entry, here's a screenshot of a minesweeper clone I've been working on in BBC BASIC.

2009.01.03.01.gif

You can view/download the code here and it will run in the shareware version of BBC BASIC for Windows. The code has been deliberately uglified (cramming multiple statements onto a single line, few comments, trimmed whitespace) to try and keep it within the shareware version's 8KB limit as this is a good limit to keep in mind for the TI-83+ version too.

Sega Master System emulation in Silverlight

Monday, 15th December 2008

I've had to quickly learn Silverlight for work recently, which has been an interesting experience. I've had to write new code, which is fine but doesn't really excite me as far as Silverlight is concerned - it doesn't really matter which language new code is developed in, as long as it gets the job done.

What does interest me more is that Silverlight is ".NET in your browser", and I'm a big fan of .NET technology with a handful of .NET-based projects under my belt. Silverlight therefore gives me the opportunity to run these projects within the browser, which is a fun idea. smile.gif

To this end, I've turned Cogwheel, a Sega 8-bit system emulator, into a Silverlight application. It took about an hour and a half, which was not as bad as I'd expected! (Skip to the bottom for instructions for the demo).

Raster graphics

Silverlight's raster graphics support is somewhat lacking. You can display raster graphics in Image elements, but - as far as I can see - that's about it. If you wish to generate and display images dynamically via primitive pixel-pushing, you're out of luck as far as Silverlight's class library is concerned.

Thankfully, Ian Griffiths has developed a class named PngGenerator that can speedily encode a PNG from an array of Colors that can then be displayed in an Image. Cogwheel's rasteriser returns pixel data as an array of integers so there's a small amount of overhead to convert these but other than that it's easy to push pixels, albeit in a fairly roundabout manner.

Render loop

The render loop is based around an empty Storyboard that invokes an Action every time it completes then restarts itself.

using System;
using System.Windows;
using System.Windows.Media.Animation;

namespace Cogwheel.Silverlight {

    public static class RenderLoop {

        public static void AttachRenderLoop(this FrameworkElement c, Action update) {
            var Board = new Storyboard();
            c.Resources.Add("RenderLoop", Board);
            Board.Completed += (sender, e) => {
                if (update != null) update();
                Board.Begin();
            };
            Board.Begin();
        }

        public static void DetachRenderLoop(this FrameworkElement c) {
            var Board = (Storyboard)c.Resources["RenderLoop"];
            Board.Stop();
            c.Resources.Remove("RenderLoop");
        }
    
    }
}

I'm not sure if this is the best way to do it, but it works well enough and is easy to use - just grab any FrameworkElement (in my case the Page UserControl) and call AttachRenderLoop:

private void UserControl_Loaded(object sender, RoutedEventArgs e) {
    this.UserControlRoot.AttachRenderLoop(() => {
        /* Update/render loop in here. */
    });
}

Missing .NET framework class library features

This is the big one; Silverlight does not cover the entire .NET framework class library, and so bits of it are missing. Fortunately this can be resolved, the difficulty depending on how you want the functionality of the original app to be affected.

Missing types you're not interested in.

These are the easiest to deal with, and this includes attributes and interfaces that the existing code uses that you're not especially interested in. For example, Cogwheel uses some of .NET's serialisation features for save states - a feature I wasn't intending on implementing in the Silverlight version. The [Serializable] and [NonSerialized] attributes are not available in Silverlight, nor is the IDeserializationCallback interface. To get the project to compile some dummy types were created.

namespace System {

    class SerializableAttribute : Attribute { }
    class NonSerializedAttribute : Attribute { }
    
    interface IDeserializationCallback {
        void OnDeserialization(object sender);
    }

}

Missing types or methods that you don't mind partially losing.

Cogwheel features some zip file handling code that uses System.IO.Compression.DeflateStream, a class not available in Silverlight. Rather than remove the zip classes entirely (which would require modifications to other files that relied on them) it was easier to use conditional compilation to skip over the DeflateStream where required.

switch (this.Method) {
    case CompressionMethod.Store:
        CompressingStream = CompressedStream;
        break;
    
    #if !SILVERLIGHT
    case CompressionMethod.Deflate:
        CompressingStream = new DeflateStream(CompressedStream, CompressionMode.Compress, true);
        break;
    #endif
    
    default:
        throw new NotSupportedException();
}

Missing instance methods.

C# 3.0 adds support for extension methods - user-defined methods that can be used to extend the functionality of existing classes that you cannot modify directly. Silverlight is missing a number of instance methods on certain classes, such as string.ToLowerInvariant();. By using extension methods the missing methods can be restored.

namespace System {

    public static class Extensions {
        public static string ToLowerInvariant(this string s) { return s.ToLower(CultureInfo.InvariantCulture); }
        public static string ToUpperInvariant(this string s) { return s.ToUpper(CultureInfo.InvariantCulture); }
    }

}

Missing static methods.

These are the most work to fix as extension methods only work on instance methods, not static methods. This requires a change at the place the method is called as well as the code for the method itself.

I've got around this by creating new static classes with Ex appended to the name then using using to alias the types. For example, Silverlight lacks the Array.ConvertAll method.

namespace System {

    static class ArrayEx {
        public static TOut[] ConvertAll<TIn, TOut>(TIn[] input, Func<TIn, TOut> fn) {
            TOut[] result = new TOut[input.Length];
            for (int i = 0; i < input.Length; i++) {
                result[i] = fn(input[i]);
            }
            return result;
        }
    }

}

First, a replacement method is written with Ex appended to the class name. Secondly, any file that contains a reference to the method has this added to the top:

#if SILVERLIGHT
using ArrayEx = System.ArrayEx;
#else
using System.IO.Compression;
using ArrayEx = System.Array;
#endif

Finally, anywhere in the code that calls Array.ConvertAll is modified to call ArrayEx.ConvertAll instead. When compiling for Silverlight it calls the new routine, otherwise it calls the regular Array.Convert.

Demo

The links below launch the emulator with the selected ROM image.

To run your own ROM image, click on the folder image in the bottom-right corner of the browser window to bring up a standard open file dialog.

Zip files are not handled correctly, but if you type *.* into the filename box, right-click a zip file, pick Open, then select the ROM from inside that it should work (it does on Vista at any rate).

The cursor keys act as you'd expect; Ctrl or Z is button 1/Start; Alt, Shift or X is 2 (Alt brings up the menu in IE). Space is Pause if you use an SMS ROM and Start if you use a Game Gear ROM. Keys don't work at all in Opera for some reason, but they should work fine in IE 8 and Firefox 3. You may need to click on the application first!

Issues

There are a number of issues I have yet to address. Performance is an obvious one; it's a little choppy even with 100% usage of one the cores on a Core 2 Duo. Sound is missing, and I'm not sure what Opera's doing with keys. Other than that, I thought it was a fun experiment. smile.gif

Once I've tidied it up a bit I'll merge the source code with the existing source repository.

Page 12 of 45 18 9 10 11 12 13 14 15 1645

Older postsNewer postsLatest posts RSSSearchBrowse by dateIndexTags