The BBC only had limited sound, with only 3 channels through a sound generator chip (plus a 'noise' channel, which you could use for percussion if you were clever). So that made it more interesting to generate sound effects and tunes. I'd managed to extract the game music from a few games - I remember Wizzy's Mansion being really quite fun, and that was the music that I left playing on the start up menu on the BBC boot disc.
Along with a few tunes that I'd got from games, I wrote a converter that tool RISC OS !Maestro files and could produce a sequence for the BBC to play. Because !Maestro was pretty simple and (generally) restricted numbers of channels, it wasn't too bad to convert down to use on the BBC. I also wrote converters for !Coconizer and some of the SoundTracker formats to extract the sequences from it.
In many cases what I ended up with was a very simple sequence of data for the note and duration. These could be played pretty easily, but I wanted to make something that could play general tunes. I knocked up a simple bit of a assembler that would run through a sequence from memory playing it, in the background. That's not so hard, but I wrote a sequencer program that would let me create the files that the assembler played. It was a lot harder, and not very good.
It did give me a way to store all the little bits that I had created from the other formats. It wasn't that useful, though.
Eventually I made a little library that I could stick on the end of any program to replace the sound effects, and which would produce the same sequence, but in a SoundTracker format. The samples attached to the file would be mathematically generated, so they were mostly sine wave based, but you could use any formula, in order to make a repeating effect. A few of my compositions ended up as little Sound Tracker files that can play on most players - although you probably wouldn't want to hear my rendition of Baa Baa Black Sheep!
On the other hand, I managed to convert things like the theme from Wizzy's Mansion to SoundTracker format. Which means that I can also turn it into an MP3 . It's kinda fun to know that the SoundTracker was created on the BBC for playback on RISC OS, but is actually being converted on Linux into an MP3 for playback on whatever you're now using.
Whilst I was reading the Acorn FidoNet echoes I remember seeing a few people with 'DJF' signatures on their mails. I asked a few questions and it turned out that this was 'David's Jingle Format', which was a short sequence to allow you to embed a tune in an email signature. The idea was that the mail reader could play the jingle when you opened the message, providing a way of identifying who it was from. I thought it was quite neat.
The format had support for multiple channels and could be of an arbitrary length, although to be used in a signature you really needed to keep it quite short. I spoke to the author - a David Fletcher, no relation to me! - and looked at their player. I wanted to produce a player that could play the jingles as well, and so I knocked up a desktop tool that could play them, and control the instruments that they were played with and volume (although that wasn't part of the definition).
I adapted some of the !Maestro conversion tools so that they could convert the music in the Maestro files into DJF. Sometimes they worked well, and other times they were quite bad. Mostly you could locate the section you were interested in and insert it into your signature. For many years I kept my DJF signature on my postings, even when they were going to non-RISC OS groups. My signature was the intro to "Script For A Jester's Tear" by Marillion.
The !DJF-JRF player that I wrote could be invoked to play jingles within arbitrary files, which was how I'd made !ReadMail invoke it to play tunes when you opened mails. It was really quite silly - I can't imagine anyone doing that these days, but at the time I thought it quite neat.
I also wrote a command line (assembler) version of the player, in 252 bytes. I think I sent it in to Acorn User for one of their < 256 byte programming challenges. My limited version didn't support multiple channels, but did play my signature if you didn't specify a DJF tune on the command line.
Later, I updated the
!DJF-JRF application to be able to be invoked from
within an IRC CTCP message through the WimpCTCP protocol. If you sent a CTCP
DJF tune' message, the IRC client would pass it on to the
application and it would play the tune. Like much of WimpCTCP, it wasn't used
by many people, but it amused me nonetheless.
Thomas Olsson wrote the AMPlayer module to play MP3s way back, around the time I started at RISCOS Ltd. It's a quite optimised and flexible audio MPEG playback system, which you can mostly use as a fire-and-forget interface for playing music. At University, Chris had been playing MP3s using the reference decoder that someone had ported. It ran in a TaskWindow (which was nice) but it was very slow. AMPlayer was a huge improvement, and on a StrongARM only took about 10% of the CPU time for many MP3s, leaving you with enough to do other things (like work!).
Whilst we were at RISCOS Ltd, we reported a few problems and gave a few suggestions, but it wasn't until I started at Picsel that I had more of a play with it. Initially I started out with the front end, and added some bits like a queue window and played with some of the automatic queueing. Thomas gave me access to the source, and I had a look at some of the bits that had been issues in the past.
Things like the ID3 support were improved, because they were easy to handle, and there were fixes to handle variable bit rate files which were becoming more common. ID3v2 skipping, and later decoding, were added so that the new files that were turning up with these headers could be decoded. The module can actually decode pretty much all the ID3v2 tags on a file, both at the head, inline, and as trailers, even the compressed tags (because it uses the ZLib module to do the decoding). It copes reasonably well with the broken iTunes format ID3v2 tags that were being created for some time, too.
The AMPlayer module is my main example of a module that handles the system changing around it gracefully. After support had been added, I updated the code to manage the sound services properly. That meant that if you killed off the module, AMPlayer would switch to using the regular 16bit SoundDMA interface, or the 8bit one if you didn't have that. If the SoundDMA module was killed, AMPlayer would go to sleep and the sound would (obviously) stop. When SoundDMA came back (if you restarted it), the music would continue from where it left off. Similarly, if returned, AMPlayer would register itself there.
Additionally, the audio buffer code was reworked so that you could resize the dynamic area in the Task Manager to increase or reduce its memory usage, and everything would continue without much noticeable effect. Depending on the size of the change and where the sound playback was in the buffer, there might be a delay (only to the system - the playback would be unaffected) whilst the memory was moved around so that the buffer could be changed in size.
If there's one change that I made that I was unsure about it was the change to the SWI API. The initial API was well thought out, but hadn't used a consistent set of flags across every call. This meant that some calls couldn't easily be extended, and I wanted to be able to extend every call. On a couple of occasions we'd wanted to be able to play multiple MP3 files at once. AMPlayer isn't capable of handling multiple MP3s itself, because it uses static variables all over the place. It would be possible to rewrite large chunks of the code but I felt that that wasn't necessary.
RISC OS has always (as far as I can tell) had the ability to multiply instantiate a module. Indeed, the module when it is first started is just the first (and current) instance of the module. The 'current' instance is the one which will be called for SWI calls. The use of instantiation was, I think, intended for applications such as !Paint being in a module and started multiple times as an application, but with only a single copy of the code in memory. FileCore uses multiple instantiation so that it can re-instantiate itself for each filesystem which registers itself, but this introduces its own set of problems.
Anyhow, re-instantiating a module means that the module re-initialises and gets a new, separate, workspace. The module is called for services in exactly the same way for each instance, but SWI calls and *Commands only go to the current instance. You can change which instance that is through a SWI OS_Module call. Whilst you can direct commands at a given instance of the module (eg *AMPlayer%InstName:AMPlay filename), you cannot direct SWIs to a particular instance.
In order to make AMPlayer able to play multiple MP3s at
once, I felt that using multiple instantiation was a pretty good fit. The
lack of any way to control the instances through SWI calls was an issue,
which is where the flags change came in. If every SWI for the module had a flags
R0, and a flag it indicated 'direct this call to another
instance', you could then do magic in the module's handler to make it appear
that the call had been dispatched to a given instance. Really that just
means changing the private workspace pointer, but it is magic nonetheless!
That meant changing the API slightly so that every call had a flags register. A few applications broke because of this, and that made me sad, but only for a very little bit because those applications were updated quickly and we had a useful feature. On a StrongARM you could get away with playing 4 MP3s simultaneously before the machine began to be unusable. You could do things, but it was very slow because of all the work going on in the background.
A few APIs were updated - for example, instead of using SWI AMPlayer_Play to play a track on the current instance, it could be directed to start a new instance and begin decoding the file - and not to start playing. That allowed you to queue up a track to start so that it had buffered output and was ready to play as soon as you unpaused it.
I reworked quite a bit of the frame reading data so that if there wasn't enough data available it would back out gracefully, rather than having partially consumed frame data. At first this was because some people had reported that streaming an MP3 through PipeFS would tend to generate pops and squeaks if there wasn't enough data. Later, it was improved more because any form of streaming would require that we handle the case well.
Between myself and Robin Watts, we added the streaming interface. I have a feeling that I wrote the original implementation and Robin replaced some of the bits that caused problems when he wrote a Shoutcast streaming application. The streaming interface wasn't as nice to use as I'd have liked, and later an AMPlayerBuffers module was written which handled a lot of the more fiddly bits.
Along with Robin's work on the Shoutcast streaming, between us we added a way to record metadata changes within the stream. Normally Shoutcast would stream the MP3 with metadata updates every so many bytes (yes, bytes - it wasn't on frame boundaries because then the streamer could worry about just getting the data to the other end, not what the data was). Because the metadata related to the stream position, you had to record the updated metadata until such time as the decoder reached it. Only then would it be appropriate to update the live metadata. Actually you also should hold on to the data until it was played, but I don't remember if that was done or not.
There were other little bits and pieces, like the
AMPlayer$Volume variable would directly affect the playback
volume when you changed it, not on the start of the next track. Seeking
on the command line with (for example) *AMLocate +0:25 could
be used to quickly skip ahead. 'User output' was added, so that the module
could be used to decode to a user function instead of feeding the sound
system. This made it very simple to decode MP3 data into 16-bit stereo
Thomas had previously added plug in support so that you could manipulate the frequency buffers before they were actually processed. Thomas had written a simple example module to use the plug ins interface, which could apply a simple scale factor to the tables. He produced a simple 3 band equaliser application which would change the sound of the track. I rewrote the equaliser to control all 16 bands, with curve fitting between 'locked' bands that you could modify. It looked rather pretty in the interface. It wasn't all that practical, however.
Because the frequency bands which have been decoded are already psychoacoustically modelled, there may not be enough data available to reproduce the frequencies you want to hear. When encoded, the encoder will make decisions based on how it is configured and the bit rate it has available about what to encode. The psychoacoustic modelling it applies may mean that it doesn't include certain frequencies because they are masked by other frequencies. This improves the compression by omitting that data, and reduces the fidelity at some frequencies.
By changing the frequency balance, applying a different equalisation scaling as I was, those frequencies that were being masked become more prominent. But the encoded audio just doesn't have enough data at those frequencies. What you get instead is the muffled, masked version that was reconstructed from the lower frequencies. So... it doesn't really work. But it does change the sound, albeit never in quite the way you'd like.
Anyhow, Robin did a whole load of improvements to optimise the speed, and later added a Floating Point implementation, so that it could run on the ARM7500FE (which had hardware floating point support).
I also rewrote all the documentation in the 'new' PRMinXML format so that it was far more presentable. It came out quite well, and was a lot easier for me to maintain.
I think I made a good contribution to the AMPlayer module, and it was quite widely used by people.
AMPlayer came with its own front end - which I'd updated a little, but I wanted a bit more integration with the system. Firstly, the !AMPlayer application could queue tracks so that the music would continue whenever a track finished, but that only worked if you were in the desktop. What if you were in the middle of playing Doom and your music stopped ? Surely you don't want to mess around returning to the desktop to find something else to listen to ? That's almost as bad as having to turn the LP (or tape) over!
For the same reason, I also wanted to be able to control the playback without hunting for some front end. Some keyboards have special buttons for play, pause, rewind, fast forward, etc, and it seemed logical to use them - I'd got such a keyboard, too. Often if you get a phone call - or someone wants to talk to you - you need a quick way to pause things. Or sometimes you need to turn the volume down a bit .
Pause icon. The ControlAMPlayer module started out by controlling the volume and being able to pause and play, using the numeric keypad. This was great, but it provided no feedback. Really you want a volume indicator, and an indication that you've just done something (like pause or play) - especially as the thing that you were playing might be silent, so you wouldn't be able to tell that you'd actually done anything until the sound started (or didn't!).
Volume control icon. I drew some little sprites to indicate the various actions, and a bunch of volume indications icons which could be plotted when you did things - a little pause icon, or an indication of the volume level when you changed the volume. As ControlAMPlayer ran entirely on callbacks, plotting stuff to the screen had to be done with care - you can't just plot sprites and expect them to end up on the screen.
You have to be careful of many things. Many things I still wasn't careful of - being in the middle of a print job being one that springs to mind, but of the other things, being in a TaskWindow, having graphics window enabled, and having VDU output disabled all matter. Additionally the VDU system tracks the positions that operations took place so as subsequent ones may need to know them (for example a 'line' operation needs to know there the previous 'move' was), so they need to be restored. My preservation rules didn't cover all the possible cases, but they did work reasonably well most of the time.
Really the whole 'on screen display' function should be handled by another module, and I had planned to create one that took all the things that I learnt from ControlAMPlayer and did it properly and safely for generic clients. As with many things, I never got around to it - as ControlAMPlayer did a good job of its operations and nothing else needed it just yet, there was no need to do it 'right now'.
Another major part of ControlAMPlayer was the automatic queueing of tracks. As each track started the module would look for the next one to play in the directory, alphabetically. So if you were playing numbered tracks you would hear them all in order - which is ideal if you're me and you listen to albums, and every track is numbered in the directory. If there were no more tracks to play in the directory, ControlAMPlayer will look in the parent directory for a track to play after the directory it was playing - and recurse into the next directory if there is one.
The effect was exactly what I wanted - continuous music, and continuous
ordered playing. Because my albums are also numbered by release (eg
02Fugazi, if you happen to
know Marillion), this meant that you could step through all releases by an
artist for hours without touching the controls (except maybe to pause). It
did have another great effect. If you ran out of the artist,
ControlAMPlayer would keep going into the next artist.
So if you ran of (as happened to me) Zero 7 (Trip-hop, ambient), it rolled into Zeromancer (Industrial metal). That's quite a contrast, but it's good for me - I'd have never have discovered the 'Eurotrash' album without it . Happy Justin. It's also great to be able to say that I got into listening to Zeromancer through Zero 7. It just throws people who know the two .
You could also force a skip to the next track - so that when Royksopp's "49%" (or any other track that you really didn't like) played you could move swiftly on.
Two other little things that made playing music much easier - and more interesting - the 'open directory', and 'queue album'. Pressing 'Enter' on the keypad would open the directory that contained the track currently playing, which lets you select another track, or move to somewhere else to find other music. I had collected lyrics alongside all the music in my collection, so this also let me find the text file containing the words if I was confused, or wanted to update them with corrections. Otherwise how would I have got Heart And Soul to have the two parts properly ? And without the right words for the two parts how are you meant to sing along ?
'Queue album' was a slight variant on the regular queueing. If you used a shift-double-click on a file in the Filer, you would get that track queued to be played after the current track. On the other hand if you used shift-ctrl-double-click you would get an 'queue album' set up. This meant that when the current directory completed and ControlAMPlayer would begin to traverse up the directory tree, it would instead play the specified track. This meant that you could queue up one level of different artists, which still meant less fiddling with finding music and more actually doing things.
ControlAMPlayer might be quite specific to me, but it is a great example (in my mind anyhow) of how you should build things. AMPlayer provides a rich interface for playing audio MPEG files (and streams, if you go there). Upon that, people build the interface that they want to use. Quite a few people built decent desktop front ends, some which were skinned, others focused more heavily on support for indexed libraries and searching. My own solution avoided the desktop interface entirely and focused on doing what it needed without that.
!AMInfo pops up track information and the album cover when the track changes. After I'd got the ImageFileGadget working nicely, I wanted something to use it in - other than an image viewer. I'd already written !ImgViewer, which was pretty simple in that it just viewed images. I had often wanted to be able to have a little pop up window show me the track that was playing, like many of the other applications did - Moose does it for me on Windows, showing me what is playing on my Squeezebox (although I'm not sure that at the time I had it).
The !AMInfo application was really simple. It polls every so often and checks
what the file currently playing is. If it is different to the one it saw last time
it uses the ID3 information from AMPlayer to populate the
track, album and artist fields in the window, and then loads in any
CoverFront/jpg' file from the directory and attaches it to the
ImageFileGadget. The window is opened at the bottom left, just above the
Iconbar. After the window's been shown for a few seconds, the
application then slides it off the left of the screen
It's not particularly complicated, but it did a quite cute job. Huh - apparently it would also open a second window with the lyrics in as text area if you clicked on it. I don't remember that! And it seems it would pop up if you held the mouse at the left of the screen - using the edge notification that we added for RISC OS 4.
ControlAMPlayer made it very easy to have music playing all the time, which was the way that I liked it. However, I'm a little obsessive (no... really, I am!) and I wanted to keep track of what I'd listened to. I had a little TaskWindow task which would keep track of the music and write the details to a file as the tracks changed.
This meant that I could look back and go 'oh that's what I was listening to then!'... if I wanted. Later I discovered Last.FM, which provides a web based service to track the music you've listened to, and records little graphs and tables of the stuff you've got. It's all quite pretty, and gives you some nice info - if you can submit the details to it.
The client that submits the played music has to be specific to the player and the system that you're using. As there was no support for AMPlayer and RISC OS, I began to write a client which would poll AMPlayer for track details, and submit the details to the server. Unfortunately the Last.FM documentation of the scrobbler (the thing that records the music) was pretty poor and I found some problems with the implementation which were slightly different to the documentation.
I rewrote their API documentation and passed the new document back to them so that other people would be able to write clients without the same pain I had. I believe parts of my documentation - or at least the bits that were problematic for me - made it into their own rewrite, but in any case I did have a few people thanked me for the extra documentation.
It's not a particularly complicated protocol, although it is made a little more fiddly by the fact that their servers appeared to sometimes believe that they had received all the data when actually only part had arrived. That in itself was bizarre - and took me a while to track down. Eventually I used packet dumps to show that the HTTP requests that were being sent were valid but when there was a slow connection their server would send a response before all the data had even been sent successfully - usually complaining that the message was truncated.
The client had to buffer up played track details ready for submission, and attempt to send to the server periodically. If the server wasn't there, or reported that it was too busy, the client backed off and would try again later. Not complex, but nice to see in action.
Many other devices have Last.FM client, so whilst I've moved away from using AMPlayer and RISC OS, I've still got a Last.FM profile which tracks my music. Since working in a real office, the amount of music I get to listen to is significantly less though.