951

(164 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.

952

(164 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, &paramTypeId, &flags);
           ^
makefile:6: recipe for target 'angelscript/scriptarray/scriptarray.o' failed
make: *** [angelscript/scriptarray/scriptarray.o] Error 1

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 wink
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).

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?

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.

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 state

This 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),a

So, 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),a
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 wink

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
  ENDIF

This 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 marker

Still 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 wink

Edit 15-12-01: Link to new version of pcm2pwm added.

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/92t

Due 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 skip2

There 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,soundLoop

which 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,soundLoop

This 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.

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 wink

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.

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.

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.

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 wink

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
  ENDIF


Method 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
  ENDIF

A 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.

Yeah, good to have you aboard. Also, welcome to teh Forum wink

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.

Yes, that's indeed the point. Well, one of them at least wink

966

(1 replies, posted in General Discussion)

Yes, Jredd rulez. Even though this one is a bit too much on the happy side for me wink

So I take it we'll have at least 3 entries for the 1K category? Alright then, I'll add it to the compo!

I don't know if having several categories would be successful, but I'd like to give it a try wink 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...

969

(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.

970

(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.

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?

972

(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 wink 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 wink

973

(7 replies, posted in Sinclair)

Trying to tackle the good old "volume control" problem. Think I might have found something...

974

(130 replies, posted in Sinclair)

Now now, 2nd place, not bad wink Congratz!

975

(3 replies, posted in Sinclair)

wink Iirc it might have been based on Skyscrap, but if so it's a heavily modified version. That .FZX is probably one big mess though.