Yes, it works! Thanks a ton, krue!
977 2015-07-28 11:41:57
Re: 1tracker v0.47 (166 replies, posted in Sinclair)
Ah, thanks krue. Alas, I only have a 32 bit system - any idea what changes I'd need to make? At the moment gcc chokes on scriptbuilder.cpp, with some weird errors like
angelscript/scriptbuilder/scriptbuilder.cpp:808:83: error: invalid conversion from ‘int*’ to ‘asUINT* {aka unsigned int*}’ [-fpermissive]
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
^
In file included from angelscript/scriptbuilder/scriptbuilder.h:25:0,
from angelscript/scriptbuilder/scriptbuilder.cpp:1:
./angelscript/include/angelscript.h:764:24: note: initializing argument 3 of ‘virtual asETokenClass asIScriptEngine::ParseToken(const char*, size_t, asUINT*) const’
virtual asETokenClass ParseToken(const char *string, size_t stringLength = 0, asUINT *tokenLength = 0) const = 0;978 2015-07-27 23:34:44
Topic: List of 1-bit routines and editors for Sinclair ZX machines (7 replies, posted in Sinclair)
Here's a list of multi-channel routines for the ZX Spectrum and ZX-81. Who will be the first to use them all? ![]()
ZX Spectrum
Cross-Platform Multi-Engine Editors:
1tracker
Beepola
bintracker
anteater (utz)
2ch square wave, click drums, simple PWM, compact size
XM converter, 1tracker
beepertoy (utz)
meta-engine with multiple cores, including Squeeker, Tritone, and Octode emulation, wavetable synthesis, lo-pass/hi-pass filters
source, editor n/a
BeepTracker (Alone Coder)
5ch pin pulse, envelopes/volume control, fx, non-interrupting sample drums
native editor
betaphase (utz)
experimental 3ch phaser type engine, slides, noise mode
bintracker
BM-1 aka BeepModular-1 (utz)
experimental 2ch engine with JIT code modification, can emulate almost any other beeper engine
source, editor n/a
BuzzKick (Shiru)
Improved clone of FuzzClick (SpecialFX) with sampled drums.
1tracker
BT'man (Alone Coder)
5ch custom synthesis, volume control, fx
native editor
Ear Shaver (Shiru)
2ch Earth Shaker or PuInt Synthesis, pwm drums
1tracker
Earth Shaker (Michael Batty)
1,5ch custom synthesis, click drums
1tracker
fluidcore (utz)
4ch wavetable synthesis
XM converter
Fuzz Click see SpecialFX
Huby (Shiru)
2ch PFM, synth drum, compact size
XM converter, 1tracker, Beepola
LSengine '89 (Lyndon Sharp)
2ch custom synthesis, non-interrupting sampled drums
XM converter, 1tracker
LSengine '91 (Lyndon Sharp)
2ch custom synthesis, non-interrupting sampled drums
1tracker
LSMB (Lyndon Sharp)
Lyndon Sharp's Wham! The Music Box clone with sampled drums
1tracker
Music Box, the see Wham
Music Studio, the (Saša Pušica)
2ch custom synthesis, interrupting synth drums
native editor, Beepola, 1tracker
Music Synth (Simon C. Tillson)
2ch PFM, envelopes, interrupting synth drums
native editor, native editor (tracker edition)
nanobeep (utz)
2ch custom synthesis, click drum, compact size (56-77 bytes)
XM converter, 1tracker
nanobeep2 (utz)
2ch square wave, slightly less compact than original nanobeep (64-99 bytes depending on configuration), but more feature-rich
source, editor n/a
ntropic (utz)
2ch square wave, 1ch noise, click drum, compact size
XM converter, 1tracker
Octode (Shiru)
8ch PFM, click drums
XM converter, 1tracker
Octode XL (introspec)
8ch PFM, volume control, click drums
XM converter, 1tracker
Octode 2k15 (utz)
8ch PFM, click drums
XM converter, 1tracker
Octode 2k16 (utz)
8ch square wave (digital), click drums
XM converter, 1tracker
Octode PWM (utz)
8ch custom synthesis, click drums
XM converter, 1tracker
Orfeus see SpecialFX
Oleg Origin's Engines (Oleg Origin)
various engines with different capabilities
no public release available yet
Phaser1 (Shiru)
1ch square wave, 1ch dual generator square wave, fx, interrupting sample/synth drums
native editor, 1tracker, Beepola
Phaser2 (Shiru)
2ch dual generator square wave, fx, non-interrupting synth drums
1tracker
Phaser3 (Shiru)
Even more awesome than Phaser2.
1tracker
PhaserX (utz)
2ch Phaser type engine with a drawbar organ effect and click drums. Ch2 ops can be decoupled to form 2 Squeeker type channels.
bintracker
PhaseSqueek (utz)
Powerful Phaser/Squeeker hybrid with 2-4 channels, noise, drawbar organ fx, fx tables, click drums
bintracker
PlipPlop (✝Jonathan Smith)
1ch custom synthesis, click drums
Beepola
povver (utz)
3ch pulse wave with simple volume envelopes, noise mode, click drums
1tracker
poww (utz)
2ch custom synthesis, click drums
1tracker
prdr (Shiru)
2ch custom synthesis, pitch slides
1tracker
Pytha (utz)
2ch tri/rect/saw/noise, click drums
1tracker, bintracker
qaop (utz)
2ch wavetable synthesis, click drums
XM converter
Qchan (Shiru)
4ch PFM, envelopes/volume control, click drums
XM converter, Beepola
quattropic (utz)
4ch square wave, variable pulse width, noise, pitch slides
XM converter, 1tracker
rawp (utz)
2ch wavetable synthesis, click drums
XM converter
SampleTracker (CBM)
3ch digi
native editor
Savage (Jason C. Brooke)
2ch square wave, variable pulse width, fx, click drums
Creador Musical (native editor), Beepola
Savage HD (introspec)
2ch square wave, variable pulse width, fx, click drums
editor n/a, can patch Beepola output against source
Squat (Shiru)
4ch OR synthesis, noise mode, sampled drums
1tracker
Squeeker (Zilogat0r)
4ch OR synthesis, variable pulse width
native editor, XM converter
Squeeker Plus (utz)
4ch OR synthesis, pulse width envelopes, noise mode, slides, click drums
1tracker
SpecialFX (✝Jonathan Smith)
2ch PFM, envelopes, click drums
Orfeus (native editor), Beepola
Spectone-1 (Zoltan Janosy)
4ch custom synthesis, envelopes
editor n/a
Stocker (Shiru)
2ch PFM, full envelopes/volume control, click drums
Vortex Tracker converter, 1tracker
StringKS (utz)
2ch Karplus-Strong string synthesis, volume control, pwm drums
source
tbeepr (introspec)
2ch custom synthesis, variable pulse width, duty cycle sweep, interrupting click drums
1tracker, source
Tim Follin 3ch (Tim Follin)
3ch PFM, volumes
1tracker
Tritone (Shiru)
3ch square wave, variable pulse width, click drums
XM converter, Beepola, 1tracker
Tritone Digi (Shiru)
3ch square wave, variable pulse width, pwm drums
1tracker
Tritone FX (utz)
3ch square wave, variable pulse width, noise, tick-based fx, click drums
source, editor n/a
Vibra (utz)
2ch tone, 1ch noise, vibrato, slides
1tracker
Wham (Mark Alexander)
2ch square wave, interrupting synth drums
native editor, native editor (tracker edition), Beepola
wtbeep (utz)
3ch tone with 32 selectable waveforms, click drums
1tracker
wtfx (utz)
2ch wavetable synthesis, tick-based fx
editor n/a, source
xtone (utz)
6ch square wave, variable pulse width, click drums
XM converter, 1tracker
yawp (utz)
3ch wavetable synthesis
XM converter
YU The Music Box see Music Studio, The
zbmod (utz)
3ch digi/samples
XM converter
ZX-3 (✝Ján Deák)
3ch PFM, envelopes
native editor, 1tracker
ZX-7 (✝Ján Deák)
8ch PFM
native editor, 1tracker
ZX-10 (✝Ján Deák)
4ch PFM, envelopes
1tracker
ZX-16 (✝Ján Deák)
16ch PFM, pitch slides
XM converter, MIDI converter
ZX81
1k2b (utz)
2ch square wave, click drums
XM converter
979 2015-07-25 14:54:46
Re: 1tracker v0.47 (166 replies, posted in Sinclair)
Yes, that did the trick, thanks. Btw took me a while to figure out how to operate the instrument editor, you might want to add those keys to the help page.
Unfortunately I'm very busy at the moment so it'll be a while before I get around to really digging into this, but nevertheless a huge thank you, Shiru.
980 2015-07-25 11:41:46
Re: 1tracker v0.47 (166 replies, posted in Sinclair)
Yippee! Yes, Phaser2! I'm very, very happy right now.
However, not completely happy because unfortunately I get an "not a valid win32 executable" error when trying to run this (XP SP3).
Ok, I'll see if I can compile it from source for Linux. Though without krue's compile guide I think it'll be tricky...
edit: not getting very far:
angelscript/scriptarray/scriptarray.cpp:15:34: error: ‘asAllocMem’ was not declared in this scope
static asALLOCFUNC_t userAlloc = asAllocMem;
^
angelscript/scriptarray/scriptarray.cpp:16:34: error: ‘asFreeMem’ was not declared in this scope
static asFREEFUNC_t userFree = asFreeMem;
^
angelscript/scriptarray/scriptarray.cpp: In member function ‘void CScriptArray::Precache()’:
angelscript/scriptarray/scriptarray.cpp:1525:11: error: ‘class asIScriptFunction’ has no member named ‘GetParam’
func->GetParam(0, ¶mTypeId, &flags);
^
makefile:6: recipe for target 'angelscript/scriptarray/scriptarray.o' failed
make: *** [angelscript/scriptarray/scriptarray.o] Error 1981 2015-07-23 22:08:43
Re: Beepolyator - convertor from midi to zx16 (6 replies, posted in Sinclair)
Haha awesome, great job! So that's how you made that kickass pink floyd cover track... and I was fooled into thinking it was Octode XL ![]()
btw what about this? Also, welcome to the new 1-bit forum, good to have you aboard again.
ed: A small issue - you might want to restore HL' on exit (original zx16 misses this, too).
982 2015-07-20 22:52:04
Re: Tutorial: How to Write a 1-Bit Music Routine (56 replies, posted in General Discussion)
Thanks Shiru, that's a great explanation.
Now there's one thing that I still don't understand myself - if you or someone else has the time, please do explain: How do engines like e.g. Zilog's Squeeker or the infamous Spectone-1 demo (the one in the "hidden" part that can be reached with key A) work?
983 2015-07-19 14:55:49
Re: 1-Bit Forum Music Compo 2015 (12 replies, posted in General Discussion)
Ok folks, I just updated the rules - submissions to the Code category don't need to be anonymous if you're worried about people stealing your code.
984 2015-07-16 19:36:22
Re: Tutorial: How to Write a 1-Bit Music Routine (56 replies, posted in General Discussion)
Part 7: Variable Pulse Width
Alright, if you've come this far, you should be able to write a pretty decent basic 1-bit sound routine. But the real fun of 1-bit coding has only started. From this point on, coding for 1-bit sounds becomes somewhat of an art form - you've got to use your creativity and imagination in order to build a routine that does something out of the ordinary.
I'm by no means an assembly expert, and don't understand half of what all these crazy beeper engines out there are doing. So I can only share those few tricks and techniques that I have so far discovered/reverse-engineered/been told about.
Ok, let's talk about variable pulse width. Varying the pulse width has a number of useful effects, most importantly the ability to produce more interesting timbres when used in conjunction with the pulse interleaving method. (In conjunction with PFM, it can be used to create volume envelopes, but this is not what this part of the tutorial is about.)
Imagine a classic pulse interleaving routine with 16-bit counters, as explained in part 4. The basic procedure for updating a channel's state and counters is:
counter := base + counter
IF carry THEN
state := state XOR ch_toggle
ENDIF
OUTPUT stateThis will output a square wave with a 50:50 duty cycle, because half of the time the output is 0, and the other half it's 1.
Well, there is another of way of doing this.
counter := base + counter ld hl,nnnn \ add hl,bc \ ld b,h \ ld c,l
IF counter < 8000h THEN ld a,h \ cp #80
state := off ld a,0 \ jr nc,skip
ELSE
state := on ld a,#10
ENDIF skip:
OUTPUT state out (#fe),aSo, instead of waiting until the counter wraps from FFFFh to 0h, we now check if it has wrapped from FFFFh to 0h or from 7FFFh to 8000h. So in effect we change the state twice as often, but we will still get a 50:50 square wave. Now what happens if we compare against a value other than 8000h? You probably can guess: Yes, that will change our duty cycle. So, to get a 25:75 square wave for example, we'd compare against 4000h, for 12.5:87:5 we compare against 2000h, and so forth. Simple, right?
If only we wouldn't have to deal with that ugly conditional jump that ruins our timing. Well, in Z80 asm there's a handy trick. It is used in Shiru's Tritone, for example.
ld hl,nnnn \ add hl,bc \ ld b,h \ ld c,l ;do the counter math as usual
ld a,h \ cp nn ;compare against our chosen value
sbc a,a ;A will become 0 if there was no carry, else it becomes FFh
and #10 ;ANDing 10h will leave A set to either 0 or 10h, depending the on previous result
out (#fe),a985 2015-07-15 23:27:16
Re: Tutorial: How to Write a 1-Bit Music Routine (56 replies, posted in General Discussion)
Part 6: Drums
We got ourselves a nicely working 1-bit routine now, but something is missing. Now what could that be? Oh right, we need some drums!
As usual, there are several approaches to realize drum sounds. The by far most common one is the "interrupting click drum" method. The idea is that in order to play drum sounds, you briefly pause playback of the tone channels and squeeze the drums in between the notes. In order for listeners to not realize that tone playback has been interrupted, the drum sounds need to be quite short, typically in the range of a few hundred up to a couple of thousand t-states.
There are countless ways of actually producing the drum sounds - pretty much anything that makes noise goes. I'll only post a very primitive example here to get you started, the rest is entirely up to your creativity ![]()
We'll need 3 variables:
data_pointer - a pointer into ROM or another array of arbitrary values. On ZX Spectrum, we'll use HL.
timer - a counter to keep track of the drum length. We'll use B on the Speccy.
state - our good friend, the output state. Let's use the accumulator A.
and a constant which equals the state being on/1, let's call it ch_on.
Now we do the following:
PSEUDOCODE ZX ASM
drumloop:
state := (data_pointer) AND ch_on ld a,(hl) \ and #10
OUTPUT state out (#fe),a
INCREMENT data_pointer inc hl
DECREMENT timer
IF timer != 0 THEN djnz drumloop
GOTO drumloop
ELSE
EXIT ret
ENDIFThis will create a very short noise burst - for better sound, you may want to add some bogus commands for wasting a few cycles in the loop. You would typically trigger this code at some point during reading in data for the next sound loop.
One last thing, you will need to adjust the main soundloop timer. Otherwise you will get an unwanted groove effect every time a drum is played. So you need to count the number of t-states your drum code takes to execute. Divide this number by the amount of t-states your sound loop takes, and subtract the result from the main timer every time you trigger a drum.
Another approach to creating drums is the "PWM sample" method. PWM (pulse-width modulation) samples are a distant relative of the more widely known PCM (WAV) samples. In PCM data, each data value (also known as sample) represents the relative volume at a given time. However, for 1-bit devices, volume is rather meaningless as you have only two volume states - nothing (off, 0) or full blast (on, 1). So instead, in PWM data each sample represents the time taken until the 1-bit output state will be toggled again. Sounds a bit confusing? Well, you can also think of PWM data as a sequence of frequencies. So, think about how a kickdrum sounds: It starts at a very high frequency, then quickly drops and ends with a somewhat longer low tone. So, as a PWM sample, we could create something like this:
db #80, #80, #70, #70, #60, #60, #60, #50 ;high start and quick drop
db #50, #50, #50, #40, #40, #40, #40, #40
db #30, #30, #30, #30, #30, #30, #20, #20
db #20, #20, #20, #20, #20, #20, #20, #10
db #10, #10, #10, #10, #10, #10, #10, #08
db #08, #08, #08, #08, #08, #08, #08, #08
db #04, #04, #04, #04, #04, #04, #04, #04 ;slow low end
db #04, #04, #04, #04, #04, #04, #04, #04
db #02, #02, #02, #02, #02, #02, #02, #02
db #02, #02, #02, #02, #02, #02, #02, #02
db #02, #02, #02, #02, #02, #02, #02, #02
db #00 ;end markerStill confused? Well, luckily there's a utility that you can use to convert PCM to PWM. It's called pcm2pwm and can be downloaded here.
Now, how to play back this data? It couldn't be simpler. We need 3 variables:
data_pointer - a pointer that points to the memory location of the PWM data. We'll use HL in our ZX Spectrum Z80 asm example.
counter - a counter that is fed with the sample values. We'll use B.
state - the output state. We'll use A' (the "shadow" accumulator).
Also, we need the ch_toggle constant as usual.
PSEUDOCODE ZX ASM
state := on ld a,#10
drumloop:
counter := (data_pointer) ex af,af' \ ld b,(hl)
IF counter == 0 THEN xor a \ or b
EXIT ret z
ENDIF
ex af,af'
innerloop:
OUTPUT state out (#fe),a
DECREMENT counter
IF counter != 0 THEN djnz innerloop
GOTO innerloop
ELSE
INCREMENT data_pointer inc hl
state := state XOR ch_toggle xor #10
GOTO drumloop jr drumloop
ENDIF You can call this code inbetween notes, just like with the interrupting click drum method. However, this will lead to the usual problems - the drum sound needs to be very short, and you need to correct the main soundloop timer. A much better way to use PWM samples is to treat them like an extra channel, and trigger them within the soundloop alongside with the regular tone channels. The above code should be easy to adjust, so I'll leave that to you ![]()
Edit 15-12-01: Link to new version of pcm2pwm added.
986 2015-07-15 23:22:54
Re: Tutorial: How to Write a 1-Bit Music Routine (56 replies, posted in General Discussion)
Part 5: Improving the Sound
You've followed this tutorial series and have come up with a little 1-bit sound routine of your own design. Only problem - it still sounds crap - notes are detuned, there are clicks and crackles all over the place, and worse of all, you hear a constant high-pitched whistle over the music. So, it's time to address some of the most common sources of unwanted noise in 1-bit sound routines, and how to deal with them.
Timing Issues
In order to keep the pitch stable, you need to make sure that the timing of your sound routine is as accurate as possible, ie. that each iteration of the sound loop takes exactly the same time to execute.
Let's take a look again at the first example from part 1. We can see that that code is not well timed at all:
soundLoop:
xor a ;4
dec b ;4
jr nz,skip1 ;12/7
ld a,#10 ;7
ld b,c ;4
skip1:
out (#fe),a ;11
xor a ;4
dec d ;4
jr nz,skip2 ;12/7
ld a,#10 ;7
ld d,e ;4
skip2:
out (#fe),a ;11
dec hl ;6
ld a,h \ or l ;8
jr nz,soundLoop ;12
;78/92tDue to the two jumps, there are four different paths through the core (no jump taken, jump 1 taken, jump 2 taken, both jumps taking), and the sound loop length thus varies up to 12 t-states - that's more than 15% of the sound loop length, and therefore clearly unacceptable. We need to make sure that the sound loop will always take the same amount of time regardless of the code path taken. One possible solution would be to introduce an additional time-wasting jump:
soundLoop:
xor a ;4
dec b ;4
jr nz,wait1 ;12/7----
ld a,#10 ;7
ld b,c ;4
nop ;4-------7+7+4+4=22
skip1:
out (#fe),a ;11
xor a ;4
dec d ;4
jr nz,wait2 ;12/7
ld a,#10 ;7
ld d,e ;4
nop ;4
skip2:
out (#fe),a ;11
dec hl ;6
ld a,h \ or l ;8
jr nz,soundLoop ;12
;100/100t
wait1:
jp skip1 ;12+10=22
wait2:
jp skip2There are other possibilities, but I'll leave that for another part of this tutorial.
Row Transition Noise
A common moment for unwanted noise to occur is ironically not during the sound loop, but between notes - the moment when you're reading in new data and updating your counters etc. This is called row transition noise.
Row transition noise is very difficult to avoid. Your focus should therefore be on reducing transition noise rather than trying eliminating it. The key to this is to read in data as fast and efficiently as possible. Not much else can be said about this, except: Make sure you optimize your code. For starters, WikiTI has an excellent article on optimizing Z80 code.
Theoretically, there is a way for eliminating transition noise, though in practise very few existing beeper engines use it (Jan Deak's ZX-16 being a notable example). That way is to do parallel computation, ie. read in data while the sound loop is running. Obviously this is not only rather difficult, but also it is usually only feasible on faster machines - on ZX Spectrum, it will most likely slow down your sound loop too much.
Which brings us to another problem...
Discretion Noise
Discretion noise, also known as parasite tone, commonly takes the form of a high-pitched whistling, whining, or hissing. It inevitably occurs when mixing software channels into a 1-bit output and cannot be avoided. It is usually not a big deal when doing PFM, but can be a major hassle with Pulse Interleaving. The solution is to push the parasite tone's frequency above the audible range. In other words, if you hear discretion noise, your sound loop is too slow. As a rule of thumb, on ZX Spectrum (3,5 MHz) your sound loop should not exceed 250 t-states.
Let's take a look at the asm example from part 4 again. At the end of the sound loop, there is a relative jump back to the start (jr nz,soundLoop). A better solution would be to use an absolute jump (jp nz,soundLoop) instead, because an absolute jump always takes 10 t-states, but a relative jump takes 12 if the jump is actually taken, which we assume to be the case here.
Also, leading up to the jump we have
dec ix
ld a,ixh \ or ixl
jr nz,soundLoopwhich takes a whopping 38 t-states. It may be a good idea to replace it with
dec ixl
jr nz,soundLoop
dec ixh
jp nz,soundLoopThis will take only 20 t-states except when the first jump is not taken. It will introduce a timing shift every 256 sound loop iterations, but this is usually not a major problem, as it happens at a frequency below audible range.
I'll cover some more tricks for speeding up synthesis in one of the following parts.
IO Contention
This section addresses a problem that is specific to the ZX Spectrum. You can most likely skip this section if you're targetting another platform.
IO Contention is an issue that occurs on all older Spectrum models up to and including the +2. The implication is that in certain circumstances, writing values to the ULA will introduce an additional delay in the program execution. You don't need to understand the full details of this, but if you are curious you can read all about IO contention here.
What's important to know is that delay caused by IO contention affects our sound loop timing. Which is bad, as I've explained above. For sound cores with only one OUT command the solution is rather trivial: You just need to make sure that the number of t-states your sound loop takes is a multiple of 8. For ideal sound in cores with multiple OUTs however, the timing distance between each OUT command must be a multiple of 8. Naturally this is pretty tricky to achieve (and chances are your core will sound ok without observing this), but keep it in mind as a general guideline.
Edit 15-12-01: Added/changed info as suggested by introspec.
987 2015-07-14 21:39:32
Re: Tutorial: How to Write a 1-Bit Music Routine (56 replies, posted in General Discussion)
Part 4: 16-Bit Counting
If you have tried the methods in Part 1, you might have noticed that it produces a lot of detuned notes at higher frequencies. This is because 8-bit values are too small to properly represent the common range of musical notes.
So, in order to increase the usable tonal range of your engine, you should use 16-bit values for your frequency counters. However, this poses another problem: As 1-bit DACs are usually hooked up to slow 8-bit CPUs, 16-bit maths are generally rather slow in execution. So simply decrementing our frequency counters like in Part 1 will most likely be too slow. We therefore need to use a trick to speed up counting.
The trick, in this case, is to add up counters repeatedly and check for carry (ie, see if the result was >FFFFh.)
I'll explain how this works for the Pulse Interleaving method. We need the following variables:
base1 - the base frequency of channel 1. We'll put this in memory on ZX Spectrum.
base2 - the base frequency of channel 2. We'll put this in memory on ZX Spectrum.
counter1 - the actual frequency counter of channel 1. We'll use BC on the Spectrum.
counter2 - the actual frequency counter of channel 2. We'll use DE on the Spectrum.
state1 - output state of channel 1. Let's use IYh.
state2 - output state of channel 2. Let's use IYl.
timer - note length counter. We'll use IX.
and our usual ch_toggle constant.
disable interrupts di
state1 := 0 ld iy,0
state2 := 0
counter1 := 0 ld bc,0
counter2 := 0 ld de,0
soundLoop: soundLoop:
counter1 := base1 + counter1 ld hl,nnnn \ add hl,bc \ ld b,h \ ld c,l ;nnnn = base1
IF previous operation resulted in carry jr nc,skip1
state1 := state1 XOR ch_toggle ld a,iyh \ xor #10 \ ld iyh,a
ENDIF skip1:
OUTPUT state1 ld a,iyh \ out (#fe),a
counter2 := base2 + counter2 ld hl,nnnn \ add hl,de \ ex de,hl ;nnnn = base2
IF previous operation resulted in carry jr nc,skip2
state2 := state2 XOR ch_toggle ld a,iyl \ xor #10 \ ld iyl,a
ENDIF skip2:
OUTPUT state2 ld a,iyl \ out (#fe),a
DECREMENT timer dec ix
IF timer == 0 then ld a,ixh \ or ixl
GOTO soundLoop jr nz,soundLoop
ELSE
ENABLE INTERRUPTS ei
EXIT ret
ENDIF
Of course the above asm code can be optimized further, but that I will leave to you, the programmer ![]()
Beware that in order to calculate the counter values, you will need to adapt the formula from Part 3. Simply change it to
fn = f0 * (a)^n.
988 2015-07-14 21:39:05
Re: Tutorial: How to Write a 1-Bit Music Routine (56 replies, posted in General Discussion)
Part 3: Calculating Note Counters
So you've got your sound loop and your data loader all set up, but now you need to actually fill in some music data. For that, you will need to know how
to calculate the note/frequency counter values.
Basically, all you need to know is the magic formula
fn = int(f0 / (a)^n) where...
... f0 is the base note counter value you want to use
... n is the distance to the base counter value in halftones
... fn is the frequency of the note n halftones away from the base note
... a is the twelth root of 2, approx. 1.059463094
Unless you want concert pitch, it doesn't really matter so much which base value (f0) you use, so you might as well use something high, e.g. 254.
Now, to calculate the value for the base note plus one halftone, you do:
f1 = int(254 / 1.059463094^1) = 239
For the base value plus two halftones, it's
f2 = int(254 / 1.059463094^2) = 226
As you can see, the lower the note, the higher the counter value. This makes sense because we decrement these values in the sound loop. A higher value means it takes longer for the counter to reach zero and reset, therefore the output is activated/toggled less frequently - which of course results in a lower frequency.
989 2015-07-14 21:38:19
Re: Tutorial: How to Write a 1-Bit Music Routine (56 replies, posted in General Discussion)
Part 2: Adding a Loader/Wrapper
In Part 1, I talked about the two different synthesis methods commonly found in 1-bit/beeper engines. Now, in order to transform these synthesis cores into an actualy 1-bit player, you'll need to add some code to load in the desired frequency values.
First, you should think about the layout of your music data. It's common to use a two-part structure. The first part is the song sequence, which is actually a list of references to the pattern data which follows in part 2. So, an example music data file could look like this:
PSEUDOCODE ZX ASM
sequence { sequence:
pattern00 dw pattern00
pattern01 dw pattern01
pattern02 dw pattern02
pattern03 dw pattern03
sequence_end dw #0000
}
pattern00 { pattern00:
1st note ch1, 1st note ch2 db nn,db nn
2nd note ch1, 2nd note ch2 db nn,db nn
3rd note ch1, 3rd note ch2 db nn,db nn
... ...
pattern_end db #ff
}
pattern01 { pattern01:
... ...
}
... ...Reading in data like this is theoretically quite trivial, but may get a bit confusing if you have to write the loader in assembly language.
For the following example, we'll use the usual counter1, counter2, backup1, backup2, and timer variables from the example in part 1. In addition, we'll need
two pointers:
seq - will be our pointer to the song sequence
pat - will be our pointer to the current pattern.
Now, you'll need to
init: init:
seq := sequence+0 ld hl,sequence \ push hl
read_sequence:
pat := (seq) pop hl \ ld e,(hl) \ inc hl \ ld d,(hl)
IF pat == sequence_end THEN xor a \ or d
exit ret z
ENDIF
INCREMENT seq inc hl \ push hl \ push de
read_pattern:
counter1 := (pat) pop de \ ld a,(de) \ ld b,a
IF counter1 == pattern_end THEN cp #ff
GOTO read_sequence jr z,read_sequence
ENDIF
backup1 := counter1 ld c,b
INCREMENT pat inc de
counter2 := (pat) ld a,(de)
INCREMENT pat inc de \ push de
backup2 := counter2 ld h,a \ ld l,h
CALL soundLoop call soundLoop
GOTO read_pattern jr read_pattern
Note that this doesn't set the timer - I think you can figure out yourself how to do that.
990 2015-07-14 21:37:23
Topic: Tutorial: How to Write a 1-Bit Music Routine (56 replies, posted in General Discussion)
Hiya folks, in this tutorial I'm going explain how you can write your own multi-channel music routines for 1-bit devices. If you have any questions or suggestions, feel free to post in this thead anytime ![]()
There are various 1-bit synthesis methods. I'm going to demonstrate only the two most common ones here. For explanation I'll mostly use my own flavour of pseudo code, and parallel to that I'll give real-life Z80 asm examples, as it would be done on a ZX Spectrum computer.
Index
Part 1: The Basics
Part 2: Adding a Loader/Wrapper
Part 3: Calculating Note Counters
Part 4: 16-Bit Counting
Part 5: Improving the Sound
Part 6: Drums
Part 7: Variable Pulse Width
Part 8: Why PFM Engines Have No Bass
Part 9: More Soundloop Tweaks
Part 10: Simple PCM/Wavetable Synthesis
Part 11: Sound Tricks - Noise, Phasing, SID Sound, Earth Shaker, Duty Modulation
Part 12: Synthesizing Basic Waveforms: Rectangle, Triangle, Saw
Part 1: The Basics
Method 1 uses a synthesis procedure called Pulse Frequency Modulation (PFM) at it's heart. Because of the thin, razor-like pulses it produces, PFM is also known as the "pin pulse method". It is used in many engines like Octode, Qchan, Special FX/Fuzz Click, or Huby. The approach allows for mixing of many software channels even on slow hardware, but usually does not reproduce bass frequencies very well.
Ok, let's take a look at how the method works. Assume we have the following variables:
counter1 - a counter which holds the frequency value for channel 1. On Spectrum, let's use register B.
counter2 - a counter which holds the frequency value for channel 2. On Spectrum, let's use register D.
backup1 - a copy of the initial value of counter1. On Spectrum, let's use register C.
backup2 - a copy of the initial value of counter2. On Spectrum, let's use register E.
state - the output state of channel 1, can be off (0) or on (1). On Spectrum, we'll use A.
timer - a counter which holds the note length. Let's use HL for that.
So, in order to synthesize our two software channels, we do the following:
PSEUDOCODE ZX SPECTRUM ASM
DISABLE INTERRUPTS di # running interrupts will throw off timing
soundLoop: soundLoop:
state := off xor a
DECREMENT counter1 dec b
IF counter1 == 0 THEN jr nz,skip1
state := on ld a,#10
counter1 := backup1 ld b,c
ENDIF skip1:
OUTPUT state1 out (#fe),a
state := off xor a
DECREMENT counter2 dec d
IF counter2 == 0 THEN jr nz,skip2
state := on ld a,#10
counter2 := backup2 ld d,e
ENDIF skip2:
OUTPUT state2 out (#fe),a
DECREMENT timer dec hl
IF timer != 0 THEN ld a,h \ or l
GOTO soundLoop jr nz,soundLoop
ELSE
ENABLE INTERRUPTS ei
EXIT ret
ENDIFMethod 2 is called Pulse Interleaving, or XOR method. It is used in engines like Tritone, Savage, Wham! The Music Box, and Phaser1. This method will generate a more classic chiptune sound with full square waves and good bass. The drawback of this approach however is that it is more limiting in terms of the number of channels that can be generated.
Assume we have the same variables as in example 1. However, this time we'll use the H register to keep track of state1, and L to keep track of state2. That means we can't use HL as our timer anymore. Well, luckily we have IX at our disposal as well.
In addition, we need a constant which holds a value that will toggle any output state between off and on. We'll call it ch_toggle. On Spectrum, a value of 10h or 18h will do the trick.
PSEUDOCODE ZX SPECTRUM ASM
state1 := off ld h,0
state2 := off ld l,0
DISABLE INTERRUPTS di
soundLoop: soundLoop:
DECREMENT counter1 dec b
ld a,h ; load state1
IF counter1 == 0 THEN jr nz,skip1
state1 := state1 XOR ch_toggle xor #10 \ ld h,a
counter1 := backup1 ld b,c
ENDIF skip1:
OUTPUT state1 out (#fe),a
DECREMENT counter2 dec d
ld a,l ; load state2
IF counter2 == 0 THEN jr nz,skip2
state2 := state2 XOR ch_toggle xor #10 \ ld l,a
counter2 := backup2 ld d,e
ENDIF skip2:
OUTPUT state2 out (#fe),a
DECREMENT timer dec ix
IF timer != 0 THEN ld a,ixh \ or ixl
GOTO soundLoop jr nz,soundLoop
ELSE
ENABLE INTERRUPTS ei
EXIT ret
ENDIFA note those willing to try this on ZX Spectrum - beware that the code needs to run from uncontended RAM. Running from contended RAM (ie. any address below 8000h) will completely destroy the routine's timing, producing random screeching noises instead of recognizable tones.
And that's all for part one, if you have any questions feel free to post them here.
991 2015-07-13 16:14:18
Re: 1-Bit Forum Music Compo 2015 (12 replies, posted in General Discussion)
Yeah, good to have you aboard. Also, welcome to teh Forum ![]()
992 2015-07-12 19:32:10
Topic: 1-Bit Forum Music Compo 2015 (12 replies, posted in General Discussion)
Here we go, the 1-Bit Forum Music Competition 2015 is open for submissions now! There are five categories, of course you're welcome to submit works to each of them.
General Rules
Any editor/routine is allowed, you can even write a new one if you like.
Works must be unreleased, and may not be published before the end of the compo.
Original works only, covers are not allowed.
Submissions must be anonymous (ie. no author names in source, binary, or mp3 tags).
Submit works in emulator format (.tap, .sna, .xex, etc.), and also provide mp3 renders for all your works.
For each entry, provide a seperate ID/readme file with info on author, title, tools used, platform, and how to run your entry on emulator/hardware.
Categories that receive less than 3 entries may be dropped or merged into the Wild category.
By submitting, you agree that your works will be made available for free download after the contest.
Only registered members of the 1-Bit Forum may vote.
UPDATE: NEW DEADLINE: Sept. 13th, 2015
Send your submissions to 1bitcompo AT randomflux DOT info.
Please don't send mp3 renders directly, but upload them to a file hoster instead and provide a link in your submission email.
CATEGORIES:
1-bit Classic
Write music for ZX Spectrum beeper and/or Atari 8bit GTIA. Maximum 2 submissions per author.
1-bit Alternative
Write music for any 1-bit platform of your choice, including but not limited to PC Speaker, Apple hooter, ZX81, calculators, Apogee, Channel F.
Maximum 1 submission per platform per author.
1-bit Wild
The category for "fake" 1-bit, made with VSTis, DSSIs, samples, etc. Anything goes as long as it sounds 1-bit. Max. 1 work per author.
1-bit 1K
Write a 1-bit tune that fits into one kilobyte. Works on any platform are accepted, maximum 2 submissions per author.
Maximum uncompressed binary size (ie. size of the player code + song data) = 1024 byte. Headers/loaders etc. don't count towards the file size.
Please submit an additional .bin file along with the usual tap/sna/xex.
1-bit Code
Write a new 1-bit routine for a platform of your choice. You can submit as many entries as you want to this category. Submissions don't need to be anonymous, in case you're worried someone's going to steal the code.
Submission format is plaintext .asm. Provide a short track demonstrating the capabilities of your routine (binary/emulator format + mp3 render). In the info file, state the specifications of your routine. Providing an editor/converter is not required, however it may positively influence your score if you do.
993 2015-07-12 13:04:29
Re: plans for a 1-bit forum contest (16 replies, posted in General Discussion)
Yes, that's indeed the point. Well, one of them at least ![]()
994 2015-07-12 12:08:49
Re: found on the web (1 replies, posted in General Discussion)
Yes, Jredd rulez. Even though this one is a bit too much on the happy side for me ![]()
995 2015-07-12 12:07:58
Re: plans for a 1-bit forum contest (16 replies, posted in General Discussion)
So I take it we'll have at least 3 entries for the 1K category? Alright then, I'll add it to the compo!
996 2015-07-10 10:57:57
Re: plans for a 1-bit forum contest (16 replies, posted in General Discussion)
I don't know if having several categories would be successful, but I'd like to give it a try
I'd give a rather generous deadline, so people have enough time to dig a bit into the non-Spectrum tools.
Was even thinking about adding another category for 1K size limited tunes, so the "tiny" engines like Huby and ntropic get some love. Though that might indeed be overkill...
997 2015-07-07 22:45:20
Re: 1bit web radio (10 replies, posted in Sinclair)
Cool! And I should do some more conversions as well. I just never get around to it, my life is mostly code, eat, sleep, repeat these days.
998 2015-07-07 22:43:16
Re: testing some new tricks... (7 replies, posted in Sinclair)
@Shiru, I'd say yours is less noisy at low volumes, though still more noisy than I expected. I'm also surprised that the timbre changes just as much as with the phase shifting approach. Seems with the right ratios, some very interesting timbre effects can be created.
@AtariTufty: I'd love to put it in ntropic, along with proper 16-bit frequency counters. However in order to do so I'd need to build a few new registers into the Z80... So it'll probably really just become a new engine eventually.
999 2015-07-07 11:19:46
Topic: plans for a 1-bit forum contest (16 replies, posted in General Discussion)
Yeah, it's about time and I'm intending to launch it shortly, but before I do that I'd like to hear if you folks have any special requests/ideas for it.
My idea is to have 3 categories - Speccy+GTIA (think it makes sense to combine it into one category due to similarities in sound), Alternative Platforms (bascially anything that is not Speccy or Atari 8bit), and Code (aka write a new 1-bit engine). Well, that's the plan so far. What do you folks think? Considering Shiru's recent release, maybe we should add a "fake1bit" category as well?
1,000 2015-07-07 11:03:44
Re: testing some new tricks... (7 replies, posted in Sinclair)
@garvalf: It's an entirely new engine. Or rather, it will be eventually, as it still needs quite a bit of improvement and I don't really have the time to work on it these days.
@Shiru: Hehe no, this is quite different. Imagine you have two output states per channel. Now, if you inverse the phases of those outputs, you will hear - nothing. Which is logical, because one of the outputs will always be low and the other will always be high, so in combination the output state for that channel doesn't change. Well, I guess you can already deduce where this is going
Shift the phase for one of the ouputs (ie start counting not at 0, but at #4000 or whatever) and voila. It also works with duty cycles other than 50:50, though then volume control gets more limited.
Overall I think the method you propose might be better sounding, but I somehow like this little trick ![]()