1 (edited by introspec 2015-12-05 02:57:08)

Topic: Squeeker by zilogat0r

I keep forgetting to write about Squeeker engine by Zilogat0r. Many thanks to Shiru for pushing me to study it earlier this year. In case you have not heard of it before, I am attaching the track by Factor6 - see the attachment called "squeekf6.z80".

It has a very unusual sound loop, which suggests a nice solution to the common problem of overbearing mid- and high-tones, while the basses are low. This is what the sound loop looks like:

mxb    exx ; (1)
    xor a
    ld bc,#0400
    ld sp,#80c4
mxa    rla
    pop de
    pop hl
    add hl,de
    push hl
    pop hl
    djnz mxa

    ld c,a ; (2)
    ld a,20
    add a,h
    ld a,15
    adc a,c
    out (254),a
    exx

    dec hl ; (3)
    ld a,h
    or l
    jr nz,mxb

OK. Part (3) is the easiest: HL' stores the number of times the sound loop repeats, i.e. this is simply the tempo control. Easy. Part (1) is also not too bad. The SP is directed at the buffer at #80c4 that stores 8 words - the full data describing the state of 4 available channels. For each channel 2 words are stored: first, the word which is added to the channel current state counter (i.e., the period of the wave) and the second word is the actual state. Two words are read off the stack, added together and then the second word is re-saved back onto the stack (PUSH HL : POP HL). This is done for all 4 channels, i.e. we have four independent 16-bit channels.

However, part (2) looks very strange at first. Note how A is used to accumulate values of the overflow bit C during part (1), except that RLA is done BEFORE, not after the first addition happens, so the overflow produced by the fourth channel is IGNORED. What? Now, watch my hands:

    ld c,a ; save into C the number 0..7 with states of first three channels
    ld a,20 ; add 20 to H, so it overflows when H>255-20
    add a,h
    ld a,15 ; discard the result in A, put 15 into A instead
    adc a,c ; add the overflow we had after adding 20 to H and also the contents of C
    out (254),a

In the end, what we end up outputting into the port is the number 15+0..7(depending on overflow bits of the first 3 channels)+1(if H in the fourth channel is above 255-20). If all the additions are 0, we end up outputting something into the tape port and onto the border too. However, as long as at least one of the channels 1..3 produces a pin, or if the duty cycle of the channel 4 is sufficiently advanced, we end up setting bit 4 of the port 254, i.e. producing sound.

So, effectively, we have here a very rare beast: a mixed synthesis beeper engine. The first three channels work as the standard "pin-based" generators, whereas channel 4 produces much wider square pulses. This makes it possible to use channel 4 for bass, which sounds solid and strong. Cool idea!

Can one do better? I guess one can. One can align the engine to 8t, slightly speed it up, and also actually switch to the kind of mixing which I used in the "Octode XL", i.e. the form of accumulating PWM. After all, with "Octode XL" it was possible to choose "volumes" of individual channels, so it is pretty similar to what we have in Zilogat0r's engine.

This is what it would sound like if one would try to reproduce the original sound using "Octode XL" style of mixing.

This is what it would sound like if one would actually use the new possibilities given by the "Octode XL" style of mixing.

Post's attachments

squeekf6.z80 36.86 kb, 10 downloads since 2015-12-05 

You don't have the permssions to download the attachments of this post.

Re: Squeeker by zilogat0r

Aww yes! Thanks a lot, introspec. I love the sound of this engine, and with your explanation, I can finally make a converter for the engine (though it'll probably have to wait till I come back from my traditional end-of-the-year album recording session, and provided Shiru doesn't beat me to it with a 1tracker plugin).

Judging by sound, I suspect Spectone-1 (http://www.worldofspectrum.org/infoseekid.cgi?id=0007879, the engine that can be heard eventually when leaving the menu screen with key A and going further on from there) might operate on a similar principle?

Re: Squeeker by zilogat0r

utz, what did you think about my hacks of this engine? I cannot decide if it is worth it, the sound becomes more muffled, but it is also louder. I believe Shiru was messing with the engine to include it into his tracker and this was at some point during the summer, so it may well be that he has some kind of support for it already.

I just popped into a debugger and, no, "Spectone-1" is a very different beast. It is a 4-channel engine which uses short pins, with the code that bears more than a passing resemblance of ZX10. Can you also hear characteristic "pops", just like in Special FX? This is because the sound loop is infinite and tempo is controlled via interrupts. This is the code:

l_fa3b: xor a

    dec c ; channel 1
    jr nz, l_fa67
    ld c, ixl
    or 0 ; volume for channel 1

l_fa43: dec b ; channel 2
    jr nz, l_fa6a
    ld b, ixh
    or 3 ; volume for channel 2

l_fa4a: dec e ; channel 3
    jr nz, l_fa6d
    ld e, iyl
    or 0 ; volume for channel 3

l_fa51: dec d ; channel 4
    jr nz, l_fa70
    ld d, iyh
    or 0 ; volume for channel 4

l_fa58: or l ; previous volume
    ld l, a ; save for next iteration
    jp z, l_fa73
    dec l     ; if non-zero, decrement it
    ld a, 16
l_fa60: xor 31
    out (254), a
    jp l_fa3b

l_fa67: jp l_fa43
l_fa6a: jp l_fa4a
l_fa6d: jp l_fa51
l_fa70: jp l_fa58
l_fa73: jp l_fa60

The mixing is a little bit similar to one of my early sketches for "Octode XL" (which is not very surprising, because I messed with "Octode" and "Octode" was clearly inspired by ZX10, just like this engine). Note how it tries to control the volume by using OR volume, but also note that it is not consistent in that channel volumes are not properly added. It also has a mechanism for carrying pins to next iteration, but once again, not fully consistently.

Re: Squeeker by zilogat0r

Interesting, thanks for clearing this up. Might explain why my attempts of contacting the guys behind this have failed so far big_smile I still wonder how those envelopes on the third/fourth screen are done though? Do they shift the volume on interrupts?

Regarding your Squeeker hack, well, I should probably say I absolutely love the sound of the original, there's something very special to it. Sorry, can't describe it better. Leaving that aside, your "like_original" version sounds quite nice to my ears. In the "like_octode_xl" version on the other hand, the mids are too metallic for my taste. But then again, as you know my taste is rather, eh, peculiar wink

Re: Squeeker by zilogat0r

No, I do not like it too much myself. It is "cleaner", but the balance does get distorted.
Just wanted to compare my perception with someone elses, thanks... smile

6 (edited by garvalf 2015-12-07 17:30:14)

Re: Squeeker by zilogat0r

it sounds great. I think I like the "This is what it would sound like if one would try to reproduce the original sound using "Octode XL" style of mixing" version the best.

I'd like to have a tool (like a perl script) for creating the data. But there are also several awesome Utz's engines to test as well so there is no hurry...

Re: Squeeker by zilogat0r

Btw the "squeekf6.z80" version is obsolete - there is a more current one with a slight modification:

mxb  exx
     xor a
     ld bc,#0400
     ld sp,#80c4
mxa  rl c
     pop de
     pop hl
     add hl,de
     push hl
     pop hl
     ld a,40
     add a,h
     djnz mxa
    
     ld a,15
     adc a,c
     out (254),a
     exx
     dec hl
     ld a,h
     or l
     jr nz,mxb

8 (edited by utz 2016-01-13 14:03:53)

Re: Squeeker by zilogat0r

One thing I don't understand about this routine: It seems the buffer has to be cleared before every row, otherwise there will be random drop-outs. Any idea why that is? I would much prefer not to spend ~74 t-states on that.

Edit: Ah, I get it now, it's because the "add counters" from rested channels will cause a continuous overflow, thus keeping the output high at all times. Still looking for an elegant solution to this though.

Re: Squeeker by zilogat0r

Got a first beta of the XM converter ready. It's functional but highly unoptimized, so I'll probably make more changes to it later. Anyway, it's using the original core of the 2012 version of Squeeker. Per-row speed control has been added. Data format and loader have been changed completely.
The converter is standalone, no Perl required.

download (win/*nix binaries and source included)

10

Re: Squeeker by zilogat0r

Yesterday's release had a botched XM template. Fixed that and also improved the loader code somewhat, so please re-download.

Re: Squeeker by zilogat0r

very cool, I'm working on a tune with this new tool. Thank you!

Re: Squeeker by zilogat0r

about the global duty, I guess the default value is 32, isn't it?

13

Re: Squeeker by zilogat0r

Yes, and that's also what the template is based on. It seemed like a good compromise, as the engine started to overload at around 40.

Re: Squeeker by zilogat0r

by the way, the sample tune sounds very good, I guess it's your composition...

15

Re: Squeeker by zilogat0r

Hehe, thanks wink It's all due to how great this engine sounds, though big_smile

Re: Squeeker by zilogat0r

Not working for me I'm afraid utz sad

I'm on Windows 8, 64bit

Just tried the xmtosqueeker.exe and downloaded a .dll it asked for but it still won't start up.
libgcc_s_sjlj-1.dll

Net research also advised to copy this .dll to \windows\SysWOW64
libgcc_s_dw2-1.dll

17

Re: Squeeker by zilogat0r

And another build attempt. Same download link as before, hope it finally works now!

18 (edited by Zilog 2016-01-18 17:24:05)

Re: Squeeker by zilogat0r

Interesting reading, guys! And a great reverse-engineering attempt. But, just few notes to it:

- why is the loop djnzed, not unrolled, etc? it's because there's no need for extra speed, slow iteration means longer spike (more audible pin-based channels), and it was originally written with code-size in my mind, for Hellboj^HPRG (who loved Hubbard's JSW so much that i decided he deserves this gift:). summer 2000...

- channel counter preset - it helps to fight stuck-one in the bass channel (resting pin channel would never produce this), but more importantnly, it helps fight the aliasing - imagine playing two tones simultaneously, just one octave apart. pre-setting channels with different starting values (0,1,2 or maybe 0, 2, 4?) will produce desired harmonics, otherwise spikes of the higher tone would cover up the spikes of the lower tone completely. it's audible in the JSW song in one pattern - data are correctly there, but the original routine (without preset AFAIK) plays like there's no progression in one of the channels - all the spikes for that particular tone were just aliased out.

Now, i'll play with your converter, Utz!

Z.

Post's attachments

hell.tap 1.44 kb, 5 downloads since 2016-01-18 

You don't have the permssions to download the attachments of this post.

Re: Squeeker by zilogat0r

utz wrote:

Got a first beta of the XM converter ready. It's functional but highly unoptimized, so I'll probably make more changes to it later. Anyway, it's using the original core of the 2012 version of Squeeker. Per-row speed control has been added. Data format and loader have been changed completely.
The converter is standalone, no Perl required.

download (win/*nix binaries and source included)


Btw. interesting row-oriented partial-change format. Is it really more efficient than mine? (channel-oriented, pattern is always made up of four particular channels)? mine was designed for LZ-based compression (pattern definitions were similar enough, and some tracks, such as bass or accompanion were quite reusable)... nice anyway. looking forward to hearing something new with my engine inside.

Btw., nowadays I wouldn't use 16bit phase-accumulator technique. Instead, I'll use 8bit mixing with postscaling , congruential "lower bits" of the scale-divider, and pwm-threshold for pwm-based volumes. The same 8bit "top-octave" frequency source would ensure proper tuning with sufdficiently enough values (128..255), and the postscaler allows to "modulate" the sound in different ways, producing one carry in the end as well...

Z.

20

Re: Squeeker by zilogat0r

Zilog wrote:

Is it really more efficient than mine?

Not at all, yours is certainly more efficient! The idea was more to smoothen row transitions by speeding up the data loading. I think that worked out reasonably well, though as you can probably tell from the code it's not optimized at all yet.

8bit mixing with postscaling? Sounds intriguing, but to be honest I don't quite understand how that would work. Could you explain a bit more about this idea? In any case, I'd love to see a new engine from you, given the unique approach you took in Squeeker smile

Re: Squeeker by zilogat0r

utz wrote:
Zilog wrote:

Is it really more efficient than mine?

Not at all, yours is certainly more efficient! The idea was more to smoothen row transitions by speeding up the data loading. I think that worked out reasonably well, though as you can probably tell from the code it's not optimized at all yet.

8bit mixing with postscaling? Sounds intriguing, but to be honest I don't quite understand how that would work. Could you explain a bit more about this idea? In any case, I'd love to see a new engine from you, given the unique approach you took in Squeeker smile

Well, the key idea is, why do all the people use 8bit counters (DEC counter8; JR NZ 10T_padding; LD counter,divider; LD channel,spike) - this means the higher tone, the less accurate its frequency (divider has just few valid bits). this takes 26T/channel AFAIK.

But, there's different approach - phase accumulator instead of divider, there's no need for conditional jumps, you just add to 8bit "accumulator", ala
LD A,frequency; ADD A,L; LD L,A (no need to reload counters, carry is immediately available, you even can use ADD HL,DE to accomplish this, when the frequency of two channels is very different - bass carry-contributions from L to H could be neglected then etc.).

Carry then feeds (SBC A,A; AND scaling_val; ADD A,scaler8) another register, "scaler", where's accumulated scaling value, typically some power-of-two, repeatedly added and we can control octave this wat, ensuring that the frequency would be in perfect align with other octaves, because we add 128, 64, 32 or similar "nice" numbers. And, when the numbers are even a little bit biased, let's say by 7, 5 or so, then, PWM control degrades for very low widhts to "intermittent" pins, giving nice fades. You could even produce harmonics with "masking" of that scaler, in one channel.

You can think of it like a <#80-#FF>-range phase accumulator as "top-octave" generator, with secondary 8bit scaler fed by its pins, doing the "downscaling" stuff - volumes, timbre, intermittent PWM stuff.

I have this idea for more than 10 years, but still no time to implement it properly (some proof-of-concept has been made on PIC12F628 :-), where it takes 7 mcycles (7us for 4MHz xtal) per channel...

regards,
Zilogat0r

22

Re: Squeeker by zilogat0r

Ah, I see. That's indeed a promising concept. It'd be especially interesting to attempt an implementation that uses it for harmonics.

What was the PIC implementation used for, just a stand-alone thingy? Do you still have sources for it?

23

Re: Squeeker by zilogat0r

The xm2squeek package has been updated, it now enables you to set the duty cycle per pattern.