526

(166 replies, posted in Sinclair)

Filebrowser shows "./". Permissions shouldn't be an issue, at least not up to the user's home directory.

I'll do a bit of hunting for the editor bug as well. For now I found some suspicious looking code in song_editor.h:493

if(curSelectionWidth=module.columns)

Is that what you intended?

527

(166 replies, posted in Sinclair)

Great! Not that I'll be using the "chopped view" much, but it's always good to see ongoing development.
Alright, managed to compile it. The following changes were necessary:

makefile:1 CPPFLAGS=-I./angelscript -I./angelscript/include -I./angelscript_jit -I/usr/include/SDL -I/usr/include -std=c++11 -fpermissive -static
makefile:72 gme/Spc_Filter.o: gme/Spc_Filter.cpp
main.cpp:10 #include <dirent.h>
main.cpp:18 //remove
main.cpp:1200 mkdir(backupDirectory) -> mkdir(backupDirectory, S_IRWXU);

As usual, angelscript/lib needs to be rebuild and put into system lib directory. Used version 2.31.2, seems to work fine.

A couple of bugs:
internal filebrowser can only access files/directories inside the exec dir
pattern display will blank when using chopped view and current row < 24

528

(25 replies, posted in Sinclair)

Ah, didn't know that was him yikes Been in touch with him via email in the meantime. Really looking forward to his sound driver. Might try to write a native tracker around it if I can find the time.

Part 12: Synthesizing Basic Waveforms: Rectangle, Triangle, Saw

In this chapter, I'm going to explain how to synthesize different waveforms without the use of samples or wavetables.


For generating waveforms other than a rectangle/pulse on a 1-bit output, we need to be able to output multiple volume levels. In part 10, we have looked at some methods for outputting PCM samples and wavetables. We concluded that in the 1-bit domain, time is directly related to volume. The longer we keep our 1-bit output "on" within a fixed-length frame, the higher the volume produced by the speaker cone will be. We can use this knowledge to write a very efficient rendering loop that will generate 8 volume levels with just 3 output commands:

  calculate 3-bit volume
  output (volume & 1) for t cycles
  output (volume & 2) for 2t cycles
  output (volume & 4) for 4t cycles and loop

As you can see, the trick here is to double the amount of cycles taken after each consecutive output command in the loop. An implementation of this for the ZX Spectrum beeper could look like this:

  ld c,#fe
  ld hl,0
  ld de,frequency_divider
loop
  add hl,de         ;11                   ;update frequency counter as usual
  ;...              ;y                    ;do some magic to calculate 3-bit volume
  ;...              ;z                    ;put it in bit 4-6 of register A
                                          ;so now bit 4 of A = volume & 1
  out (c),a         ;12: x+10+11+y+z=64   ;output to beeper
  rrca              ;4                    ;now bit 4 of A = volume & 2
  out (c),a         ;12: 4+12=16          ;output
  ds 4              ;16                   ;timing
  rrca              ;4                    ;now bit 4 of A = volume & 4
  out (c),a         ;12: 16+4+12=32       ;output
  ;...              ;x                    ;update timer etc.
  jp loop           ;10                   ;loop

If you count the cycles, you'll notice that this loop takes exactly 112 cycles. Which means we can easily add a second channel in the same manner, which brings the total cycle count to 224 - perfect for a ZX beeper routine. Side note: If necessary, you can cheat a little and reduce the 64-cycle output to 56 cycles, without much impact on the sound.


Anyway, we will use this framework as the basis for our waveform generation. So let's talk about the "magic" part.

The easiest of the basic waveforms is the saw wave. How so, you may ask? Well, the saw wave is actually right in front of your nose. Look at the first command in the sound loop - ADD HL,DE. Say we set the frequency divider in DE to 0x100. What happens to the H register? It is incremented by 1 each sound loop iteration, before wrapping around to 0 eventually. Ok, by now you might have guessed where this is going. If you haven't, then plot it out on a piece of paper - the value of H goes on the y-axis, and the number of loop iterations goes on the x-axis. Any questions? As you can see, our saw wave is actually generated for free while we update our frequency counter (thanks to Shiru for pointing this out to me). We just need to put it into A, and rotate once to get it into the right position.

  add hl,de         ;update frequency counter
  ld a,h            ;now 3-bit volume is in bit 5-7 of A
  rrca              ;now it's in bit 4-6
  out (c),a         ;output as above
  ...

Doing a triangle wave is a little more tricky. In fact, being the lousy mathematician that I am, it took me quite a while to figure this out. Ok, here's how it's done. We've already got the first half of our triangle wave done - it's the same as the saw wave. The second half is where the trouble starts - instead of increasing the volume further as we do for the saw wave, we want to decrease it again. So we could do something ugly like

  add hl,de
  ld a,h            ;check if we've passed the half-way point of the saw
  rla               ;aka H >= 0x80
  jp c,_invert_volume
  ...
reentry
  rrca
  out (c),a
  ...
  jp loop
_invert_volume
  ;A = -H

There's a more elegant way that does the same thing without the need for conditional jumps.

  add hl,de
  ld a,h
  rla
  sbc a,a          ;if h >= 0x80, A = 0xff, else A = 0
  xor h            ;0 xor H = H, 0xff xor H = -H - 1
  out (c),a        ;result is already in bit 4-6, no need to rotate
  ...

We can simply ignore the off-by-one error on H >= 0x80, since we don't care about the lower 4 bits anyway.

Last but not least, a word about rectangle waves. Of course, rectangle waves happen naturally on a 1-bit output, unless you force it to do something else. Which we are doing in this case, so how do we get things back to "normal"? Well, to get a square wave, we simply have to remove the XOR H from the previous code example. Which means that with just two bytes of self-modifying code, we can create a routine that will render saw, triangle, or square waves on demand:

  add hl,de
  ld a,h
  rla
  
  ;saw    |     tri     |    rect
  rra     |   sbc a,a   |   sbc a,a
  rrca    |   xor h     |   nop

  out (c),a
  ...

You'll notice that even with timer updates, register swapping, etc. you'll still have some free cycles left. Which should, of course, be put to some good use - see part 11 if you need some inspiration.

530

(22 replies, posted in Sinclair)

You actually deserve some credit on this, since it was you who brought to my attention that we get a saw wave for free from the frequency calculation wink Triangle is basically the same thing, just have to negate the channel accu hi-byte if it's >#80.

531

(135 replies, posted in Sinclair)

Hmmf, tried mixing 4 triangle waves. It works, but it sounds rather awful. Not sure why that is. Of course, with the volume level reduced to two bits per channel, the sound will be a bit more distorted. But it shouldn't create all that high frequency fizz. And even when pairing notes (same note on ch1/2 and ch3/4), it's still much more noticable than in pytha, even though the core of this one is faster (200t vs 216t in pytha). Any ideas? Is it maybe because the level changes less often? It'd be great to be able to use the "one counter update per sound loop iteration" trick on this. Because then we could modulate our triangle osc with another triangle osc, which means we'd have primitive FM synthesis running. Well, guess I'm dreaming too much.

Edit: Attached proof-of-concept single channel FM example. It's incredibly simple, 2 ops only, so no change over time. Works but sounds terrible, unfortunately.

Edit2: Did some more tests. One thing I tried was to apply a simple low-pass filter (actual_vol = (last_vol + current_vol) / 2) - no luck. Also tried a very fast routine, mixing 2 triangles in 112t. Even that creates significant parasite tone. So either there's something wrong with my algorithm, or 4 levels is simply not enough to render a passable triangle wave.

Edit3: Sure enough, I can reproduce the effect with a hand-drawn sample in Milkytracker. So the noise is definately caused by the reduced number of volume levels. Damn! I'm afraid that means FM synthesis just isn't going to happen, at least not on 48K.

532

(22 replies, posted in Sinclair)

Cheers guys wink
Ok, here's an updated version in action: http://irrlichtproject.de/transfer/pytha-demo2.mp3
While still not clean (of course, it's beeper, damnit big_smile), there's a slight but noticable improvement I'd say. Btw just to be clear, both recordings are 100% raw, no EQ or filtering applied.

533

(22 replies, posted in Sinclair)

Ok, here's a hardware recording: http://irrlichtproject.de/transfer/pytha-demo.mp3
Could be cleaner, yes, but still it's nothing a little equalizing can't solve, I think. I'll have another go at shaving off a few more cycles, though, which should push that HF noise a bit more towards the inaudible range.

534

(22 replies, posted in Sinclair)

Hmm, that shouldn't be the case. There is some noise on the pure triangle, and the modulation effect has a pretty sharp sound, but it should be tolerable (unless my ears are getting bad). Are you running on hardware or Fuse emulator? Most other emulators have problems with this type of fast-switching synthesis.

Border masking would cost a lot of cycles, so unfortunately it's pretty much impossible.

535

(22 replies, posted in Sinclair)

Whoops, just noticed that I miscalculated the timing, 220 cycles instead of 224. Got a version which runs in 216, but the improvement to the sound is barely noticable so I'll leave the engine as it is for the time being.

536

(22 replies, posted in Sinclair)

Yep, still totally retired from making new beeper engines... ah, damnit.
Alright then, say hello to Pytha, the first beeper engine that can generate triangle waves in realtime, without wavetable lookup. Just two channels, both of them do the same thing. In addition to triangle waves, there's also saw and pulse (rectangle). There's also an LFO-controlled modulator thingy, which does some weird sweep/resonant filter fx. With the LFO turned off, the modulator can also be used to set the duty cycle for the pulse waveform. All the base waveforms can be turned into noise as well. And of course, there's the usual click drums, with variable starting pitch and slide speed for the kick, and variable volume for noise. The LFO runs at a fixed rate. Tried making that variable, but unfortunately it creates too much noise. It'd also be nice to shave a few cycles from the synth loop for better sound. There's actually 10 cycles wasted on timing, however they are in a really bad spot so it's hard to reduce them to 2.

Anyway, won't make an XM converter for this, for obvious reasons. Got a working MDAL config for it, though, just need to polish up a few things on that. For now, the source code is in the github repo, as usual. Short demo tune is attached.

537

(20 replies, posted in Sinclair)

Not sure if I'll be able to make it. But I'll try.

538

(135 replies, posted in Sinclair)

Another 1-bit puzzle cracked: How to generate a triangle wave without wavetable lookup. There are some killer modulation effects that you can do with this as well, but I still need to find a few free t-states in order to implement those for 2 channels.

Edit: Ok, got it. Here's a shorter and faster version, 11 t-states free which will be enough to pull of the modulation effects.

sloop
    add hl,de        ;11
    
    ld a,0           ;7    ;timing
    nop              ;4    ;timing
    
    ld a,h           ;4
    rlca             ;4
    sbc a,a          ;4
    xor h            ;4
    
    rrca             ;4
    out (c),a        ;12__64
    rrca             ;4
    out (c),a        ;12__16
    ds 4             ;16
    rrca             ;4
    out (c),a        ;12__32
    
    jp sloop         ;10

Edit2: Example with modulation effect attached.

539

(25 replies, posted in Sinclair)

Yeah, I think SID is here to stay wink Port contention will be there in 48k mode, though it can probably be disabled with a setting.
I'm curious how good the beeper sound will be. I expect it to be at least on par with what garvalf recorded from his MiST.

540

(25 replies, posted in Sinclair)

Awesome! Can't wait to see the full documentation for the Next. Guess I'll be writing a sound driver or two for this thing.

541

(25 replies, posted in Sinclair)

Thanks for the info. Seems that either way the Next will be totally overpowered in terms of sound capabilities yikes It's basically a chip orchestra-in-a-box at this point. Especially pleased to hear that SAA support is on the list. Now we just need a decent tracker for all of this...

542

(25 replies, posted in Sinclair)

Thanks!

Did you have this discussion before they upgraded to the SLX16? Maybe at least OPL2 will be possible after all, who knows wink On the other hand, I'll probably stick to the beeper anyways. I need those limitations, with FM I'd just be stuck tweaking sounds all day.

Made by one of the most talented chip artists on the planet wink

Ha, neat. They even got a Sharp Pocket PC!

The Commodorians are actually blessed as far as live music coding goes, since they've got defMON wink

545

(10 replies, posted in Sinclair)

Not sure if Shiru has developed this idea any further yet. He's done a few ports of ZX beeper engines though, as you've probably seen. I've mostly been busy working on MDAL myself, though I've also done a Z80 emulation core in the meantime (not for AVR though, just a C++ experiment to see if I could pull it off in general). I'm sure we'll come back to this at a later point though.

546

(25 replies, posted in Sinclair)

Yeah, programming natively on the thing is what I'm most looking forward to. As far as sound goes, I think the current specs are great already. And there's always the chance for an updated firmware later on wink

Also what? The Lyndon Sharp is active? I've been trying to get in touch with the man for ages, to no avail. He briefly popped up on WorldofSpectrum a while ago, but nobody gave a damn (unsurprisingly, as many people with a serious interest in Speccy things have left that place after the new management took over), so he disappeared from there again. If you could manage to reach out to him and lure him here, that'd be awesome. Not sure if he even knows that there are people actively using his beeper sound routine nowadays...

547

(25 replies, posted in Sinclair)

Holy moly, over 720,000 GBP! Don't think anybody saw that coming. Well deserved though, imo this really is the Speccy of the future.

Haven't tried tasm_on_calc, but judging from the readme, it might not be very useful. It only implements a small subset of commands, and a lot of important stuff like logic, 16-bit arithmetic, relative jumps, and data directives aren't implemented at all. That said, the 84+ can be programmed natively in machine code. So you can always write your asm code on paper, and translate it by hand wink Quite tedious, but it's doable for small projects.

Houstontracker takes up most of the available RAM. However, on the 84+, you can always stuff things into the Flash archive to make room in the RAM. So it's certainly possible to use the same calc for music tracking and asm experiments.

Z80 is sometimes used in the Sega Genesis/Megadrive as a sound co-processor. It's also used for sound generation in a number of other home computers without dedicated sound hardware.

You don't really need an in-depth understanding of the ULA. I suppose my wording was a bit confusing: As far as sound goes, the ULA only converts the digital output from the Z80 into an analogue signal. The actual sound synthesis is done by the Z80.

Basically, you mostly interact with the ULA either by writing to VRAM (0x4000 - 0x5bff) in order to change graphics, or by writing to port 0xfe in order to change the border color, and the state of the 1-bit signal. Bit 4 is the relevant bit for controlling the beeper state.

The tricky part of running graphics and audio at the same time is this: the Z80 shares the data bus with the ULA in the lower 16 KB of the Spectrum's RAM (0x4000 - 0x7fff), and the ULA gets precedence over the CPU. So when the CPU is executing a read or write operation that concerns the first 16K of RAM, it may have to wait a variable amount of cycles. This is called "memory contention". Now here's the catch: When synthesizing 1-bit music, you usually need to use cycle-exact timing. However, the amount of delay caused by the memory contention is very difficult to predict, so cycle-exact code becomes nearly impossible when generating graphics at the same time. There are some tricks to make it appear as if graphics and music are running at the same time, but actual synchronous gfx+msx generation is considered very challenging (though not impossible).

Well, that certainly means taking the hard route wink On the other hand, I can completely understand your desire to stay away from "big" computers. In this case you may want to familiarize yourself with MRS (the Memory Resident System). It's one of the most advanced assemblers that run natively on the 48K, and it should work just fine in conjunction with the divSD.

Regarding the 48K sound, it actually is generated by the ULA chip, which also does the graphics. The ULA sends the signal to both the on-board speaker and the MIC output (and since the latter isn't fully seperated from the EAR port it's audible there as well, at a lower volume). Pretty much all the recordings available online are taken from the MIC port.

Like what I've seen so far of your works on vimeo, nice and dark. Will have to check out more of that wink