Music - beeper engines: Binary modulation (part 1).

Info Guide #12
Binary Modulation - Part 1
 Binary modulation: news from the beeper
utz
translated by Lord Vader

   Last time I outlined the general trends
development on the 1-bit music scene. B 
this time I'll go more into technique and 
I will write more specifically. 

               Digitization

   Most of the first half2016
I experimented with digital synthesis
sampled sound.
   The general principle is essentially the same as
and in the 'channel alternation' method(pulse-
interleaving), used in such movements─
kah, like "WHAM! The Music Studio" (author
Mark Alexander), Savage Engine (author 
Jason C. Brooke), Tritone (by Shiru). 
The output bit switches at the frequency you─
higher than it can move with full amplitude
loudspeaker diffuser, and it’s like that─
at once settles into some average
position. In this way you can simulate
a whole set of “volumes” required for mi─
caching of several channels. The simplest
the implementation is presented below.

(example 1)
loop
 add hl,de;HL - channel 1 adder,
                ;DE - channel 1 frequency
 ld a,h
 cp #80 ;compare the adder with
       ;required fill factor
 sbc a,a;fill A with the carry flag
out (#fe),a;output to beeper

 add ix,bc;IX - channel 2 adder,
                ;BC - channel 2 frequency
ld a,ixh;...
 cp #80
 sbc a,a
 out (#fe),a

 jr loop

   The above code mixes two means─
cores with fill factor 0.5 each
(that is, each of the channels will be
nii "1" exactly half the time, as in
AY ). Naturally, in this code you can set 
arbitrary coefficient.
   The problem with this synthesis is that
processing allchannels allocated little time─
menu, which limits the number of channels.
As this time increases, it becomes─
being noticeable (i.e. falls within the audible range─
range) parasitic noise at sampling frequency─
tions. On the Spectrum, the frequency period is usually
sampling is set to 224 clock cycles
(i.e. duration1 line). It helps
align all commandsOUT to boundary 8
cycles (since these commands are original─
nom 'rubber' Spectrum are subject to tor─
a possibility that can be avoided if they
aligned by8 clock cycles). When I'm just on─
I started writing beeper engines, I didn’t know that
and got a pretty lousy sound.

   So now you know how to mix
two channels with meanders, but how to apply
this knowledge is for the real sampled
sound? To begin with, you can count these channels
not by channels, but by signal levels. In the example
above there are already 3 signal levels: 0 (both
channel outputs 0),1 (one of the two outputs
1) and 2 (both output 1). Thus, at
we have already got some sampled
sound (whichis simply a follow─
intensity of signal levels changing with
fixed frequency - for example44.1
kHz ). However, it is clear that we need several─
only more signal levels than3, by─
example, standard65536 levels from standard─
new 16-bit wav file. That’s where I appeared─
serious problems arise.
   We'll come down from heaven to earth - it's unlikely
it will be possible to achieve65536 signal levels
on the beeper. As I already mentioned, mixing
must fit within224 clock cycles, and each
instructionOUT is aligned to 8 clock cycles, so
absolute maximum is 224/8=28
signal levels [actually 29 - approx.
lane]. In theory. In practice there are 
and other limiting factors, reduce─
limiting the maximum achievable quantity
levels. For example, the fastest team
OUT is already running11 clock cycles. In addition, 
for several mixing channels required─
we need to carry out side calculations on─example, channel adder update,
note length counters, etc. My early ex─
The experiments were similar to the following code:

(example 2)
 ld c,#fe
loop
 add ix,de;add frequency DE
                ;to adder IX
sbc a,a;adc a,b;HL indicates in
                     ;256-byte sample,
                  ;aligned to 256 bytes
 add a,l;when the adder overflows
               ;take a step in the sample
 ld l,a
ld a,(hl);in sample bytes - bits,
       ;which we will push into the beeper
out (c),a;%00010000 = level 1,
               ;%00110000 = level 2 etc:
        ;the number of "1s" in a byte sets the level
rlca
 out (c),a
rlca
 out (c),a
 rlca
 ...
 jr loop

   This code more or less works, leaving─
leaving plenty of room for improvement.
Its main problem is uneven distribution
distribution of OUTs by execution time
cycle. You can try and distribute
OUTs evenly throughout the entire cycle (see, on─
example, my beeper engines qaop, yawp and
wtfx). However, later it will become clear to you that 
none of this helps much unless you
you won’t start sacrificing the number of levels
signal. What's next?
   Firstly, we can be a little optimistic─
create a lookup based on the table (sample) by taking it
index directly from high byte
channel adder, for example:

(example 3)
 add hl,de
 ld c,h ;BC indicates the sample
ld a,(bc)

   I learned this trick from Sorchard with pho─
roomsworldofdragon.org. At first I'm a catfish─
I have no idea whether this will workmagic? After all
this approach will lead to frequency limitation─
resolution up to8 bits, which,
as we know, it’s bad! Well, not really. Bits
index in the table should also be added
to these bits, and in fact in the example2
frequency resolution is24 bits -
which is already too much. With optimized bow─
pom(as in example 3) we get 16-bit─
new resolution, which is exactly what we need
necessary.
   Secondly, there is no need to update
all channel adders in one
cycle pass. Just a simple conclusion is enough─
correct (combined of all
channels) signal level in a given passage─
de cycle. (Respect to Alone Coder for this
trick.)

   So, we have done all possible optimizations─
tions, but our code still sucks. What
now? If, for example, you write graphically─
bad code, and it turns out to be slow,
then first of all you unfold the cycle─
ly. We will do something similar for ours
sound code - namely, let's write a section─
New 'core' for each signal level:

(example 4)
 ld l,0
ldb,#ff;ld b,1 for CMOS Z80, because
 ;"out (c),0" works there as out (c),#FF
 ld c,#fe

org #8100;aligned to 256 bytes
coreO ;volume 0
 out (c),0; turn off the beeper bit
 ...  ;updating channel counters
 ...  ;calculate the level for
  ;next iteration
 ...  ;H = #81 + signal level
jp (hl)

org #8200
core1 ;volume 1
 out (c),b; turn on the beeper bit
 ... ;spend 4 clock cycles
 out (c),0;turn off the beeper bit
         ;(it was turned on for exactly 16 cycles)
 ...  ;updatechannel meters
 ...  ;calculate the level for
              ;next iteration
 ...  ;H = #81 + signal level
jp (hl)

org #8300
core2 ;volume 2
 out (c),b; turn on the beeper bit
 ... ;spend 20 cycles
 out (c),0;turn off the beeper bit
         ;(it was turned on for exactly 32 clock cycles)
 ...  ;updating channel counters
 ...  ;calculate the level for
              ;next iteration
 ...  ;H = #81 + signal level
jp (hl)

org #8400
coreЗ
 ...

   And so on and so forth. to you at─
gotta play a littleTetris, parting─
Laying code for updating counters, etc., but that’s all
solvable. But now we can reach as many as 8
signal levels! However, another one appears
problem - for level1 we cannot re─
switch the beeper bit quickly enough -
minimum delay between switchings
is11 clock cycles (OUT (C),0:OUT (#FE),
A). But11 clock cycles is not very good, because 
in this case we can run into a torus─
Possibility of I/O cycles One of the solutions
is that we simply forget about
this is because at signal level1 ULA-tor─
may not affect the sound much. Other
method - we assume that at signal level0
we still output the pulse in16 cycles
to the beeper (we always output an impulse and never
do not output a shorter pulse). At the same time, in
example 4 core1 will become coreO, etc. This
approach works well on real hardware
- but, unfortunately, not in emulators. Therefore
my beeper engine zbmod (which plays
samples of unlimited length in3 channels with
21 volume levels) just has two
versions - one for real hardware, where
level 0 corresponds to an impulse of 16
cycles on the beeper bit, another for emulation─
ditch, where at a volume level1 the
alignment of output cycles by8 clock cycles.
 Disadvantage of the above 'multi-core─
the method is obvious - it eats a huge amount of food─
quantity of memory. And what’s worse, memory is lost
wasted - due to the need to level
pieces of code of 256 bytes. Of course you can
fill the lost pieces with something useful─
nom. In zbmod, for example, there is code that─
ry loads the next track data into
operating time of the main loop - just below
I will offer another idea for filling these
pieces. But before that I’ll tell you about something else
method of creating16 pure signal levels
- using only6 OUT commands and without
excessive memory consumption by code, as in
example4.

   Attention: I came up with this code, compare─
very recent and not sufficiently tested─shaft it. However, I think he will 
a good addition to this article. 

   So.
   Of course, you know that in3 bits you can─
You need to encode8 numbers (0..7) . What if
we will apply this observation to our levels
signal?

(example 5)
 ld c,#fe
loop
 ... ;updating everything you need
               ;for 40 cycles
 out (c),x;switch the beeper bit
                 ;through 64t
 ... ;do something else for 20t
 out (c),x;switch the beeper bit
                 ;through 32t
 ... ;something on 4t
out (c),x;output after 16 clock cycles -
                 ;start channel 1 output

 ... ;something at 52t
out (c),x;output via 64t
... ;something at 20t
out (c),x;output via 32t
 ... ;something on 4t
out (c),x;output via 16t -
                 ;start channel 2 output
 jr loop

   This code gives us2 * 2^3 = 16 levels
signal. And the entire cycle is executed in exactly
2*(64+32+16) = 224 clock cycles (randomly so─
it turned out). Cool!
   This focus does not yet have an official
name, let's call it"n-bit ladder".

   By the way, there is one problem that I
still not resolved. When the output cycle
sampled sound for a while
displays counts in a row at a high volume─
stu, the average level on the dynamics too
increases, creating an unpleasant effect
overload.  This can be heard, for example─
measures, in the demo melody from the engine
Octode2k16 (which sums 8 channels 
meander and displays all possible sums
through9 different cycle options).
 I'm guessing thisoccurs due to
that the speaker diffuser cannot keep up
return to neutral state and
so the volumes are added up and increased─
tsya.I even tried to use this trick
to create a sound similar to AY-envelope─
tions, but unfortunately I was not able to reliably
reproduce this effect. If you have
Any ideas about this -
I'll be glad to hear about them.

                 Filters

   Above I promised to put it to good use
holes in the coded 'multi-core' code
engine. What about... filters? Low-pass filter, High-pass filter -
This is all the domain of the DSP, yes. And of course, we
You shouldn’t even hope to cut it down, even if you accept it─
creative filter... although...we are ALREADY playing Sam─
flies on a beeper, so it might interfere with us
Z80 low speed only. And it turns out
that low-pass filters and high-pass filters do not require special functions at all─
numerical resources.
   Formula for the simplest low-pass filter with infinite─
The new impulse response is:

 y[i] = y[i-1] + a·(x[i] - y[i-1])

   Where i is the reference number, x is the input
(unfiltered) signal, y - output
(filtered) signal anda - a certain co─
coefficient from 0 to 1. Smaller value a
correspondsgreater filtering effect─
that one In a 'multi-core' engine(see example 4)
this formula is easily implemented as follows
way:

(example 6)
 ;H = #81 + previous level
              ;iterations (i.e. y[i-1])
 ld a,#81
 add a,h
 ld h,a ;H = y[i-1];
 ... ;updating adders
 ... ;A = next level
               ;iterations, i.e. x[i]
 sub h ;A = x[i] - y[i-1]
 srl a ;A = 0.5·(x[i] - y[i-1])
 add a,h;A = y[i-1]+a·(x[i]-y[i-1])=
               ;= y[i]

   Pretty simple, right? Let it be
not the best in the world, but still real
LOW PASS FILTER.
   By the way, I usually do 2 shifts (rrca:
rrca:and #3f), as a compromise between─
depending on code speed and sound quality.

   HPFare made a little more complicated. Can you─
honor the result of the low-pass filtery[i] from the input sig─
nalx[i], but you can take the following formula:

 y[i] = a·(y[i-1] + x[i] - x[i-1])

   One way or another, the calculation for the Z80 requires
one operation more than low-pass filter. But this is not
main problem. The main problem is
is that, unlike the case of a low-pass filter, the calculation
The high filter produces negative numbers. This means
you will have to either add 'cores' (core-1,
core-2, etc.), which will output
same volume levels ascore1,core2
etc. - it’s impossible to output to a beeper
negative signal levels! - or check─
change the result of calculations to negative─
ity, executingcpl:inc if necessary.
Unfortunately, I can’t think of anything nicer
I couldn’t, but I’m sure there is a beautiful method.
[The high-pass filter simply removes the constant component─ 
and gives readings around zero. Correct─ 
The correct way here would be this: to the result 
The high filter should be increased by half as much as possible 
output engine count and trim the re─ 
filling - approx. ] 

   The action of suchfilters you can use─
play in my beeper engine Beepertoy.

             Squeeker method

   Having experimented with playing se─
mplov on beeper for several months─
Man, I'm a little bored. As can be seen fromat─
measure 4, this kind of engines is not so much slo─
It's so painful to write. Because
having written several such engines ( Beeper─
toy, fluidcore, Octode2k16, zbmod ), I re─ 
I wanted to do something new.
   I always liked ZilogatOr's engine
called squeeker. He won't like it
lovers of clean and clear sound. However,
when I listen to engines like
Fuzz Click (aka Special FX) or engines 
Follina, I think they sound like 
dirty distorted rock guitar. And nothing
imitates this guitar better than the old one
good squeeker.  The only thing that makes me
stopped me from writing music in this
engine - lack of a normal editor.
Well, more precisely, it was written in BASIC, and
it's even worse than writing music in assembly─
lere.
   But ZilogatOr sent me some of his
engine several years ago, and recently I
finally was able to make a converter for it from
formatXM.
 And how does this engine work? Very
simple: first the states are calculated (0
or1 ) channels using a coefficient─
filling element - similar toexample 1
by itself. However, squeeker does not output further
state of each channelOUT' by turn─
di, creating the illusion of several levels
volume. Instead of this state of all ka─
nals are combined usingOR. And so
there is only1 OUT command in the loop.

(Example 7)
loop
 ld b,0; here we will accumulate
             ;channel states, initially 0
ld de,xxxx;channel frequency 1
add hl,de;channel 1 accumulator
 ld a,h
add a,#20;fill factor
 rl b ; transfer is remembered in B

ld de,xxxx;thenже самое для канала 2
     add ix,de
     ld a,ixh
     add a,#20
     rl b

     ld de,xxxx;и для канала 3
     add iy,de
     ld a,iyh
     add a,#20
     rl b

     ld a,b   ;взяли все 3 бита
     add a,#f;если B был 0, то бит 4
            ;останется нулём и после этого
             ;- иначе установится
     out (#fe),a;и наконец!
     jr loop

   На первый взгляд, это всё выглядит глу─
пой идеей. И если вам важен чистый звук,то
так оно и есть. Но если вам нравится рок и
тяжёлый митол,то это - замечательная идея.
Кроме  того, для экзотических железок, где
звук  тупо  генерируется на одной фиксиро─
ванной частоте (например, компьютеры Sharp
Pocket  или консоль Fairchild Channel F ),This method allows you to get rid of this
spurious hardware tone, as opposed to
engines similar toexample 1.
 What are the overall advantages and disadvantages─
what about this method? To begin with, the main thing
the advantage lies in the only command─
deOUT for the entire cycle. And since now it’s not
you have to take care of mixing ka─
nals on the speaker diaphragm, output cycle
can be done slower.300-400 cycles per
such a cycle is in the order of things, and you can even
during this cycle output some
graphics. In addition, as you add ka─
cash in such an engine, the volume of each
the individual will not decrease, unlike
from the method described at the beginning of the article. Dan─
This fact makes this method suitable
for a combination of AY sound and beeper, which is about─
showcased in squeekAY.
   The main disadvantage of the method is that the channels are mo─
gut to block each other. With 4 channel
It is better not to use coefficients in the engine
fill more than#20, otherwise dropouts will begin─
density of the sound of any channels. With a coefficient─
There are fewer ents, such occurrences are also rare
are observed, but much less frequently than might be expected
appear as a result of studying the code.

   So, having understood this method and on─
writingXM converter for it, I took it and made it─
lal engine called Squeeker Plus, in
to which I added drums, noise and envelopes.
Envelopes? Well, they're actually envelopes
for fill factors. Still in
The squeeker method does not produce clean means─
games, and therefore you can simulate levels
volume by changing the fill coefficients─
opinions, exactly the same as it is done in PFM-
engines: Qchan, Fuzz Click, Stocker, etc.
[PFM (pulse-frequency modulation) - method 
representation of an analog signal at ─ 
pulse power of fixed duration 
and amplitude, only the distance changes 
between such impulses - approx. lane ] 

Share your thoughts about the article