mailto: blog -at- heyrick -dot- eu

SimpleSeq v0.10

SimpleSeq playing
SimpleSeq playing music.

Let's begin the tenth month with release zero-ten of SimpleSeq.

In this version, the following things have been done:

  • Now fully clears the music array when loading a new piece, so nothing is left lingering.
  • Tweaked the size of the note drawn in overview mode (and while playing) if there's only one instrument in order to have the grid lines remain visible.
  • When playing, a highlight bar moves across the bar to show what is actually being played (as shown in the picture above).
  • Setting a note in the editor (by pressing Space) now makes the note sound. I'm not sure when that stopped working...
  • You can now use ^I to insert a bar before the current bar, and ^D to delete the current bar. You can choose to apply this to the current channel only (leaving the others untouched), or to all channels.
  • When saving a file, the previous version is copied as the filename with a "/bak" suffix, so there's always the current version and the previous.
    Just in case. ;)
  • The file enumeration has been tweaked so it will now list ONLY files that are either SimpleSeq or MIDI. It also omits listing the backup files. Instead, it'll suffix " (*)" to the filename if there's a backup.
    While it's not made explicitly clear, you can, therefore, enter the filename and then add "/bak" to the end to load the backup copy, should you wish to revert to the previous version.
    Note that this happens every save, so saving twice would effectively render the backup file the same as the main file.

Here's the download:

Download (108.54K)
For RISC OS 5 machines with MIDI



It got up to 26°C today. The average is ten degrees lower. It was also plenty warm yesterday, so I tackled the brambles again.

The dumb thumbnail picture is a nod to how all the mainstream videos have pictures of the people pulling dumb looking "reaction faces". It's not just MrBeast or a dozen influencers I've never heard of, Dr. Becky and Sabine Hossenfelder are getting in on the crazy-face action.

And, yeah, 26°C isn't that hot, it's just unusual for this time of year. But a "meh" face is kind of boring.

It was a composite of a frame from the start of the video proper, and a photo of my holding an asian parasol that I quickly ran outside and took. I used the Background Eraser app to remove the background of the photo of me, and then spent a ten minutes putting the parasol back into the image. I then used the Polish photo tweaker app to merge the picture of me into the through-the-window shot, and paste in an emoji just because.
It was a bit of a rush job as I'd already uploaded the video. So the edges of me are a little fuzzy. But, then, Background Eraser does tend to make fuzzy images. It's not pixel perfect, it does weird things with resizing the source image. But it's a hell of a lot less bother than firing up the PC and cutting out the wanted parts in a photo editor.


ChooseBD colour determination

Talking of how things are made, I thought I'd explain how ChooseBD works out what colour to use for the pinboard text.

The secret is to look to see how big the JPEG is, and create a sprite that is a sixteenth of that size.

  SYS "XJPEG_FileInfo", 1, f$ TO ,, owid%, ohei%
  wid% = owid% / 16
  hei% = ohei% / 16
  sz% = (wid% * hei% * 4) + 512
  DIM spr% sz%
  spr%!0 = sz%
  spr%!8 = 16
  SYS "OS_SpriteOp", ( 9 + 256), spr%
  SYS "OS_SpriteOp", (15 + 256), spr%, "wrkspc", 0, wid%, hei%, 1 + (6 << 27) + (90 << 14) + (90 << 1)

SpriteOp 9 creates the sprite area. As we know we're going to be using full RGB, we can work out the size by simply multiplying the pixels by four, then adding in 512 bytes to cover headers and such. More than we need, but better that than not enough.
SpriteOp 15 creates the sprite "wrkspc" within the sprite area. We use a sprite mode word to specify type 6 (16m colours) with 90 dpi X/Y. The dpi isn't important, but the OS will sulk if you don't specify something.

The next task is to switch to redirecting output to the sprite.

  SYS "OS_SpriteOp", (62 + 256), spr%, "wrkspc" TO ,,,sasz%
  DIM save% sasz%
  SYS "OS_SpriteOp", (60 + 256), spr%, "wrkspc", save% TO , r1%, r2%, r3%

Here, the save area size is read and then the sprite is redirected.

Next, if the scaling ratio is set to original:smaller we can get SpriteExtend to render directly from the JPEG file into the sprite (using sprite redirection).

  DIM scl% 16
  scl%!0 = wid%
  scl%!4 = hei%
  scl%!8 = owid%
  scl%!12= ohei%
  SYS "JPEG_PlotFileScaled", f$, 0, 0, scl%, 0
  SYS "OS_SpriteOp", (60 + 256), r1%, r2%, r3%

That final SpriteOp undoes the VDU redirection, so everything is normal now, and we have an image like this in memory:

A little version of a big image.

It might seem small, but it's all we need.

Now, for speed, we're going to call OS_SpriteOp by number to save having BASIC keep on looking up the SWI name, and we're also going to refer to the sprite by address rather than name to save having the OS keep on looking up the sprite by name.
It does make a difference. On my ARMv7 Pi 2, it takes 4.1 seconds by name or 2.9 seconds by reference. That's quite a difference.

Now, the original image is 3390×2714, or 9.2 million pixels. Our rescaled image is only 211×169, or 35,659 pixels. It's much smaller but it's enough to determine what we need.

Here's the scanner:

  SYS "OS_SpriteOp", (24 + 256), spr%, "wrkspc" TO ,,addr%

  red% = 0
  green% = 0
  blue% = 0

  FOR yl% = 0 TO (hei% - 1)
    FOR xl% = 0 TO (wid% - 1)
      SYS &2E, (41 + 512), spr%, addr%, xl%, yl% TO ,,,,,col%
      red%   += (  col%        AND 255)
      green% += ( (col% >>  8) AND 255)
      blue%  += ( (col% >> 16) AND 255)

The first line gets us an address for the sprite, and the SYS &2E is the numerical way of calling OS_SpriteOp.

What we're doing is simply whizzing through the image to see what colour every pixel is, and this (in the form &00BBGGRR) is split into red, green, and blue parts, which are cumulative. There's no need to worry about overflow as all pixels returning 255 adds up to only 9,093,045. A 32 bit value lets us count up to 4,294,967,295. Performing this calculation on the full size image would only add up to 2,346,117,300 so we don't have to worry about overshooting.

For that image, our final results are 5,244,322 red, 5,809,786 green, and 6,906,236 blue.

  val% = (red% / (wid% * hei%) ) OR
         (green% / (wid% * hei%) ) OR
         (blue% / (wid% * hei%) )

I've split this line C-like as it's long. What we're doing is dividing each of the red, green, and blue by the number of pixels to give us an average for each colour.
This would be 147 for red, 163 for green, and 194 for blue (because there's lots of blue).
This is then merged together (using OR) to arrive at a value of 243. It's not quite correct as we're not applying any weighting to the colours, but it'll do for what we need it for.

Essentially we've converted the image to a value of 243 that represents the intensity of the image. It isn't a grey dot because we ORed the values, we didn't add them and divide by three for an average (that would be something like 168 off the top of my head).

Then, if the intensity is over 140 we'll go with white on black, otherwise it's black on white.

You could shave off a few centiseconds (to 2.6s) by rewriting the loop like this:

  R% = 0 : G% = 0 : B% = 0
  H% = hei% - 1 : W% = wid% - 1
  S% = spr% : A% = addr%
  FOR Y% = 0 TO H% : FOR X% = 0 TO W%
  SYS &2E,553,S%,A%,X%,Y% TO ,,,,,C%
  R%+=(C% AND 255)

And you could shave off nearly a second (2.1s) by ditching OS_SpriteOp completely and poking around the sprite memory. As we created a 16M colour sprite, we know the arrangement of each word.

  R% = 0 : G% = 0 : B% = 0
  E% = hei% * wid% * 4
  O% = addr% + 40
  FOR L% = 0 TO E% STEP 4
    C% = O%!L%
    R%+=(C% AND 255)

However, this is one of the larger images. The rest are smaller so take less time, and using legal OS calls is better (in karmic sense) than directly poking around memory regions.



Your comments:

Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.

rob, 1st October 2023, 23:24
If you're going to be iterating over every pixel anyway, you could calculate the intensity separately for each possible icon location, assuming they are positioned on a grid, to cater for images with large bright and dark areas.  
Rick, 2nd October 2023, 06:53
Possible, but it's an all-or-nothing setting, you can't set the text colour per icon. 
Accordingly, images with large light and dark areas may get things wrong. 
I can't balance it as while I put my icons on the lower left, that's just me. Others may prefer the lower right...
Rob, 2nd October 2023, 12:37
Ah, I wasn't sure if that were possible or not. Fair enough.
David Pilling, 2nd October 2023, 12:57
If resources were limited you could access the JPEG data directly - it is in 8x8 blocks anyway. 
If you wanted to make RISC OS do more of the work - is there a SWI for optimised palette conversion - Col Trans maybe or Change FSI. Alas scaling will be simple scaling just omitting rows and columns - otherwise you could set the destination as a 1 x 1 pixel. 
Another thought is pick 20 pixels at random, how representative would they be of thousands of pixels. The scaling is this kind of thing, because there will be pathological images where the pixels chucked away are not typical. 
Out on t'internet they compete to consume resources for this task - lets fire up a browser... 
The solution above is fine, just that it begs the question 'can you over think this'. 
Zerosquare, 2nd October 2023, 15:19
I don't understand what you're doing with that ORing of color values. Bitwise OR can be very sensitive to small variations, which is something you don't want in that case. 
Example: 126 OR 127 = 127, and 128 OR 129 = 129, but 127 OR 128 = 255.
Rick, 2nd October 2023, 19:48
Zerosquare: It's to bias it towards light. 
Obviously there are pathological cases that will upset a normal averaging routine, for example, (R+G+B)รท3 would say an image that is full intensity green is only 33% grey. Perhaps using colour weighting, but as David suggests, it's easy to overthink things. ;) 
I'm not looking for a "grey pixel to represent this image", I'm looking for a value to tell me if it's light or dark (to use the opposite for the text colour). 
David: resources aren't limited, the reason I don't support sprite backdrops isn't because of a lack of memory, it's because of a lack of patience. I wanted something that could run quickly. Pasting a 1/16th JPEG into a sprite for examination met that criteria. 
Why 1/16th? Because SpriteExtend is lame and does zero dithering, it simply loops plotting a pixel and then skipping a few. If you zoom up the little image, you'll see the jaggies are quite evident. 
I'm not sure quite what would happen if I told it to render to a 1 pixel sized image. Prolly crash. ;) 
Thing is, as I said to Rob earlier, there are plenty of cases where no algorithm would be ideal. Consider, for instance, a black and white checkerboard. 

Add a comment (v0.11) [help?] . . . try the comment feed!
Your name
Your email (optional)
Validation Are you real? Please type 37260 backwards.
Your comment
French flagSpanish flagJapanese flag
«   October 2023   »

Advent Calendar 2023
(YouTube playlist)

(Felicity? Marte? Find out!)

Last 5 entries

List all b.log entries

Return to the site index



Search Rick's b.log!

PS: Don't try to be clever.
It's a simple substring match.


Last read at 13:07 on 2023/12/04.

QR code

Valid HTML 4.01 Transitional
Valid CSS
Valid RSS 2.0


© 2023 Rick Murray
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted.
RIPA notice: No consent is given for interception of page transmission.


Have you noticed the watermarks on pictures?
Next entry - 2023/10/03
Return to top of page