1

(135 replies, posted in Sinclair)

utz wrote:

@Hikaru: I don't quite understand your sample code. Is the .lvNN part executed every time, and then it will jump to .lv00-.lvXY, from where it'll loop back to .lvNN? Is beeper turned off before executing .lvNN? Wouldn't that give an overall very low volume?

I tried to illustrate the way the channels are calculated alone, throwing out loop counters etc. In the actual engine, there's only one .lv_jmp JR, which is located near the beginning of the loop and preceded by the fixed-position OUT. There's two OUTs each loop iteration. The placement of the second OUT varies according to the .lv routine (this is the only difference between them), i.e. each .lv routine sets a certain 'volume level' using PWM. Logically, they are divided into intervals of 11-13 T states for this purpose, made up from just what's in there, hence why it's so noisy esp. on contended machines. Overall there's 8 'levels' like that, each of the channels uses 4. I don't remember the reason for double-digit label naming, probably had 16 levels in there at some point

2

(135 replies, posted in Sinclair)

utz wrote:

The only thing I could think of right now would be to implement it via a digi core (like in fluidcore, zbmod, etc). But that would be pretty inefficient, as you'd need 4 shifts to derive a suitable volume value from H. So pointing to a predefined table of wave data would be much faster (but at the cost of using up an extra register, of course).

I've made a simple engine like that last year. Example here (not optimized for contention whatsoever). Schematically it uses a number of routines like this:

.lvNN
    ld a,#C0
    add hl,de
    and h
    exx
    add hl,de
    add h
    rra
    rrca
    rrca
    and #38
    ld (.lv_jmp),a
.lv_jmp=$+1
    jr $+2
.lv00
    ...
.lv01
    ...

interspersed with OUT (#FE),A and such like

As far as efficiency goes, it depends. With wave tables, you're doing additions on table values, one addition per each channel. This method OTOH allows summing up 'the H's' themselves first and then derive the resulting level from that, so in the end it's probably more or less the same thing

3

(9 replies, posted in Sinclair)

These engines (SpecialFX, Music Synth and so on) are all based on the same principle of playing with the duty cycle, i.e. the 0/1 balance of a period, on a small enough scale where modifying the cycle is still perceived more as a change in volume rather than the timbre. The reason they employ this two-part delay scheme is reducing volume-dependent timing fluctuations/cross detune. One delay counter is decreased while the other one is increased at the same time as the envelope decays. This allows the length of the pulse to remain the same throughout regardless of volume, so that whichever cross-detune issues remain are frequency related.

4

(7 replies, posted in Sinclair)

Hmm, yes, I was neither right nor wrong with that part. To clarify, the problem comes from the fact that: 'a muted channel always outputs a 0', i.e. that H isn't actually zeroed at rest notes, only the duty values/frequencies (less special-case code). Doing the comparison backwards sets this specific situation straight with no additional code. Apart from that, both versions are legit and interchangeable

5

(7 replies, posted in Sinclair)

Cheers guys. For now I continue to tweak/debug the routine when I have the time. Some gritty details-

It turned out that the sound got somewhat worse with the newer 'optimized' sound loop used in the test .SNA. In particular, there's a kind of annoying tingling-buzz noise, or something, in SpecEmu, qualitatively reminiscent of Sample Tracker. Reverting to an older version of the loop (2 x 5T slower) seems to help reduce that to an extent. OTOH I'm not perceiving much difference between the two with other emulators like Fuse or Spectaculator. Is it SpecEmu acting up (which I doubt tbh) or the szzt is real? Could it be caused by asynchronous channel updates in the newer loop? I'm not really sure what to make out of it. In case anyone's curious, [new (noisy)] [old (better)]

There's something else I don't quite understand the implications of. This engine has the channel counters updated twice less often than Tritone does (frequency values are 12bit in both cases, although not in the test .SNA version), but it attempts to 'make up' for it by having the corresponding frequencies multiplied by 2 before they are used, so in the end you get about the same note. Hopefully. I have a feeling that something inevitably gets lost in the process - some sort of granularity maybe - I can't quite pinpoint what it is. As a matter of fact I'm using the 12bit Tritone format with just minor modifications, finding it rather convenient. Hope it doesn't prove detrimental / hit me back in some way much later like it tends to happen with these things

I've found that the more correct way of doing the 'Tritone comparison' in the sound loop is LD 'A,DUTY: CP H', i.e. backwards. The way it is originally I guess you could say the duty cycle is actually increased, starting from 50%, together with the value, despite the fact that it is perceived as 'thinner' sound. Normally this isn't a problem, if not for one thing. A muted channel outputs a '0' at all times, whereas what is perceived as '0's of the playing channel is actually represented by '1's in the output. This in turn leads to a kind of interference/noise of its own wherever the two overlap. It's barely noticeable in Tritone but here it (used to be) more evident, for whatever reason

6

(65 replies, posted in Sinclair)

Nice. At first I thought it was an attempt at PDM what's with the OUTI's and all, then I realized it was a clever way to set the PWM 'level balance' depending on where you begin in the memory. Overall, this technique is strangely underused. My Vvv (the saw wave player) is based on a similar idea albeit realized with a set of routines

Looking forward to your other entries, too smile

7

(7 replies, posted in Sinclair)

https://drive.google.com/open?id=0B-KEe … XFDQzVmVm8

Been working on this on and off this week, it's basically a Tritone-like 1-bit engine. 4 channels of the same type, identical (albeit more pronounced) drums, similar data format

Was considering the idea of maybe having something finished by Sunday for Utz's compo but at this point it is probably safe to say that it's not happening. As such, it's more of an illustration for this post, i.e. the fact that the 8T alignment rule doesn't always seem to hold even if the loop is a multiple of that (two iterations of 160T each in this case). The 'contention vibrato' I think is evident esp. on high notes

There's a few more problems with it, like e.g. how the 'quiet' drum noise setting became just too quiet now, I'll probably revisit that part later. I'm not good when it comes to PC programming so can't say anything about converters and whatnot. Perhaps I'll figure something out eventually

I had another engine that I wanted to show, with 2channels of - for the lack of a better word - 'realtime' saw waves but it is even less finished atm, particularly the data processing routines became bloated as fvck despite the fact that it was originally intended as a rather simple engine feature-wise

A suggestion for reducing row transition noise in 'pulse interleaved' engines: streamline your data fetcher and throw in a few OUT #FE's in there to keep the beeper afloat so to speak. If the outer loop in your sound generation code uses A', chances are the beeper state is preserved in A so you can try something like EXA: XOR #10: OUT (#FE),A: EXA for a better effect. (I haven't actually tried any of this though!)

Edit: thinking about it, clicks are just as likely to happen (e.g. when approaching 0% and 100%), so probably not worth it

9

(3 replies, posted in Sinclair)

This is more of a design question but let's say you had to combine frequency tables and detune. How would you go about it?

I can think of these kinda options,

1. Persistent detune values. Once a detune value is read, it is stored and kept until you run into another one. All subsequent notes in the corresponding channel have their frequencies added with the currenty stored detune value
2. Immediate frequencies. Like when a note normally takes one byte but if a certain bit is set (there's typically a few spare ones), it should instead be interpreted as the MSB of a 2-byte frequency value

I suppose the first approach results in smaller data wherever detuned notes are used frequently, it is perhaps more suitable for SpecialFX-like engines, whereas with the second one it is vice versa