Fastest way to do a little BEEP
Fastest way to do a little BEEP
Jarl, torpedos!
Hi, all!
I need to do a BEEP but with less instructions as possible, well, I mean, as fast as possible. E.g. a=beep tone, b=beep length
What would it be the shortest method? In assembler, please.
Hi, all!
I need to do a BEEP but with less instructions as possible, well, I mean, as fast as possible. E.g. a=beep tone, b=beep length
What would it be the shortest method? In assembler, please.
If something works, don't touch it !!!! at all !!!
Re: Fastest way to do a little BEEP
Via the ROM routine I'd guess?
Re: Fastest way to do a little BEEP
Is this for sound effects, or playing a tune?
If it's the latter, here's some Booty for you - this routine plays the whole tune but you can always play just one note at a time.
(It's not quite what you asked for though, each note is the same duration.)
If it's the latter, here's some Booty for you - this routine plays the whole tune but you can always play just one note at a time.
(It's not quite what you asked for though, each note is the same duration.)
Code: Select all
musicbeep:
halt
halt
halt
halt
ld hl,(musiccount)
ld h,0
add hl,hl
ld de,musictab
add hl,de
ld e,(hl)
inc hl
ld c,(hl)
ld b,0
ld d,b
ld a,e
cp 255
call z,resettune
or a
call nz,musiclp
ld hl,musiccount
inc (hl)
jr musicbeep
;
resettune:
xor a
ld (musiccount),a
ld de,(musictab)
ld c,d
ld b,0
ld d,b
ld a,e
ret
;
musiccount: db 0
;
musiclp: ld a,0
out (254),a
call delay
ld a,16
out (254),a
call delay
dec de
ld a,d
or e
jr nz,musiclp
ret
;
delay: push bc
delp: dec bc
ld a,b
or c
jr nz,delp
pop bc
ret
;
musictab:
DEFB 38,43
DEFB 34,48
DEFB 38,43
DEFB 0,0
DEFB 22,73
DEFB 0,0
DEFB 22,73
DEFB 0,0
DEFB 34,48
DEFB 30,53
DEFB 28,58
DEFB 46,35
DEFB 46,35
DEFB 43,38
DEFB 46,35
DEFB 0,0
DEFB 53,30
DEFB 46,35
DEFB 53,30
DEFB 0,0
DEFB 25,65
DEFB 0,0
DEFB 25,65
DEFB 0,0
DEFB 25,65
DEFB 22,73
DEFB 21,78
DEFB 34,48
DEFB 34,48
DEFB 32,50
DEFB 34,48
DEFB 0,0
DEFB 38,43
DEFB 43,38
DEFB 46,35
DEFB 43,38
DEFB 38,43
DEFB 34,48
DEFB 38,43
DEFB 34,48
DEFB 30,53
DEFB 28,58
DEFB 30,53
DEFB 28,58
DEFB 25,65
DEFB 22,73
DEFB 22,73
DEFB 20,80
DEFB 18,90
DEFB 16,103
DEFB 18,90
DEFB 22,73
DEFB 21,78
DEFB 25,65
DEFB 22,73
DEFB 28,58
DEFB 25,65
DEFB 30,53
DEFB 28,58
DEFB 0,0
DEFB 22,73
DEFB 0,0
DEFB 22,73
DEFB 0,0
DEFB 34,48
DEFB 30,53
DEFB 28,58
DEFB 46,35
DEFB 46,35
DEFB 34,48
DEFB 28,58
DEFB 46,35
DEFB 46,35
DEFB 34,48
DEFB 38,43
DEFB 0,0
DEFB 29,55
DEFB 0,0
DEFB 29,55
DEFB 0,0
DEFB 38,43
DEFB 34,48
DEFB 29,55
DEFB 53,30
DEFB 53,30
DEFB 38,43
DEFB 29,55
DEFB 53,30
DEFB 53,30
DEFB 38,43
DEFB 43,38
DEFB 0,0
DEFB 34,48
DEFB 0,0
DEFB 34,48
DEFB 0,0
DEFB 38,43
DEFB 43,38
DEFB 46,35
DEFB 43,38
DEFB 38,43
DEFB 34,48
DEFB 38,43
DEFB 34,48
DEFB 30,53
DEFB 28,58
DEFB 30,53
DEFB 28,58
DEFB 25,65
DEFB 22,73
DEFB 22,73
DEFB 20,80
DEFB 18,90
DEFB 16,103
DEFB 18,90
DEFB 22,73
DEFB 21,78
DEFB 25,65
DEFB 22,73
DEFB 28,58
DEFB 25,65
DEFB 30,53
DEFB 28,58
DEFB 0,0
DEFB 22,73
DEFB 0,0
DEFB 22,73
DEFB 0,0
DEFB 255,255
endmusic:
My Speccy site: thirdharmoniser.com
Re: Fastest way to do a little BEEP
Sorry, not from ROM routine, I need only a little "beep" and I wish I can change some register's value so it can change the frequency.
Not for sound effects, just a beep.
I've found this one:
But I'm not sure if there's a faster method, I'm going to test this.
Not for sound effects, just a beep.
I've found this one:
Code: Select all
beep:
ld c,%00010000
ld b,10 ; Beep length
beep0:
ld d,150 ; The more value, the more low
ld a,c
and %11111000
out (254),a
cpl
ld c,a
beep1:
dec d
jr nz,beep1
djnz beep0
If something works, don't touch it !!!! at all !!!
Re: Fastest way to do a little BEEP
Best to figure out your own way, really!
You set the speaker bit to 1, wait a bit, reset it to 0, wait a bit, and repeat, for as long as you want the sound to last. So your code looks like:
Repeat for duration:
- toggle speaker bit
- [Self-contained delay loop]
And repeat for duration.
You can just leave the OUT value in A and use XOR 16 on it to toggle the speaker bit whenever you do OUT (254),A
Or you can use the alternate A register A' instead to toggle the speaker bit, if you need to use A elsewhere in your delay counters, e.g.
EX AF,AF'
OUT (254),A
XOR 16
EX AF,AF'
Now where you have a counting loop wrapped around everything for duration, remember the higher the pitch, the shorter the internal delay loop, so the longer duration loop you'll have to ask for.
You set the speaker bit to 1, wait a bit, reset it to 0, wait a bit, and repeat, for as long as you want the sound to last. So your code looks like:
Repeat for duration:
- toggle speaker bit
- [Self-contained delay loop]
And repeat for duration.
You can just leave the OUT value in A and use XOR 16 on it to toggle the speaker bit whenever you do OUT (254),A
Or you can use the alternate A register A' instead to toggle the speaker bit, if you need to use A elsewhere in your delay counters, e.g.
EX AF,AF'
OUT (254),A
XOR 16
EX AF,AF'
Now where you have a counting loop wrapped around everything for duration, remember the higher the pitch, the shorter the internal delay loop, so the longer duration loop you'll have to ask for.
Last edited by Joefish on Fri Feb 11, 2022 7:42 pm, edited 6 times in total.
Re: Fastest way to do a little BEEP
P.S. not sure how this page came up - did someone just link to it? Looks handy...
viewtopic.php?f=6&t=70
viewtopic.php?f=6&t=70
Re: Fastest way to do a little BEEP
Slightly faster, but more registers used:
Code: Select all
ld de,#80 ; tone freq, lower value = lower tone
ld bc,#1000 ; length
loop:
add hl,de
ld a,h
and #10 ; optional, masks border colour
out (#fe),a
dec bc
ld a,b
or c
jr nz,loop
Re: Fastest way to do a little BEEP
Thanks, [mention]Joefish[/mention], very instructive!
And thanks too, [mention]utz[/mention], I'm going to test that.
At least I understand the following: if 1 is "sending 1 to 0xFE port", 0 is "sending 0 to 0xFE port", and . is "a pause":
Low tone:
1..........0..........1..........0..........1..........0..........
Sharp tone:
1...0...1...0...1...0...1...0...
And thanks too, [mention]utz[/mention], I'm going to test that.
At least I understand the following: if 1 is "sending 1 to 0xFE port", 0 is "sending 0 to 0xFE port", and . is "a pause":
Low tone:
1..........0..........1..........0..........1..........0..........
Sharp tone:
1...0...1...0...1...0...1...0...
If something works, don't touch it !!!! at all !!!
Re: Fastest way to do a little BEEP
Fine, [mention]utz[/mention], it's like mine but with less bytes, congrats.
But there's something odd... these routines make beeps not like ROM ones, but like the intro of Skool Daze:
https://www.youtube.com/watch?v=ULCjgUNz6bQ
I thought ROM beep would be the easiest way to do a beep, but I think there must be something else...
But there's something odd... these routines make beeps not like ROM ones, but like the intro of Skool Daze:
https://www.youtube.com/watch?v=ULCjgUNz6bQ
I thought ROM beep would be the easiest way to do a beep, but I think there must be something else...
If something works, don't touch it !!!! at all !!!
- lister_of_smeg
- Microbot
- Posts: 145
- Joined: Thu Nov 16, 2017 1:44 pm
Re: Fastest way to do a little BEEP
Do you have interrupts enabled?
Also, you'll get a little distortion with that method on models which are subject to I/O contention, because the loop will take longer when the ULA is drawing the screen than when drawing the border.
- lister_of_smeg
- Microbot
- Posts: 145
- Joined: Thu Nov 16, 2017 1:44 pm
Re: Fastest way to do a little BEEP
This is pretty short, but so are the beeps!
Code: Select all
; beep
; entry: d = pitch, e = duration
loop0: djnz loop1
xor (hl)
out (#fe), a
dec e
ret z
beep: ld b, d
loop1: ld hl, loop0
jp (hl)
- NEO SPECTRUMAN
- Microbot
- Posts: 110
- Joined: Tue Jan 26, 2021 10:27 pm
Re: Fastest way to do a little BEEP
Code: Select all
beep
de - pitch 1 lowest 32768 highest
c - lenght
loop add hl,de
sbc a
out ($FE),a
djnz loop
dec c
jp nz,loop
Code: Select all
click %)
de - pitch 1 lowest 32768 highest
b - lenght
loop add hl,de
sbc a
out ($FE),a
djnz loop
Code: Select all
without pitch %) (same size as beep)
c - lenght
loop ld a,r
out ($FE),a
djnz loop
dec c
jp nz,loop
Re: Fastest way to do a little BEEP
Make sure you run the beep code in un-contended memory, to avoid distortions.Bubu wrote: ↑Sat Feb 12, 2022 5:28 pm Fine, @utz, it's like mine but with less bytes, congrats.
But there's something odd... these routines make beeps not like ROM ones, but like the intro of Skool Daze:
https://www.youtube.com/watch?v=ULCjgUNz6bQ
I thought ROM beep would be the easiest way to do a beep, but I think there must be something else...
Ideally, make sure the loop code is a multiple of 8 clocks.
Re: Fastest way to do a little BEEP
That is barely noticeable, since it only happens in between transitions of contention and non-contention areas of the screen, usually 2 times in a full frame.lister_of_smeg wrote: ↑Sat Feb 12, 2022 8:20 pm ...
Also, you'll get a little distortion with that method on models which are subject to I/O contention, because the loop will take longer when the ULA is drawing the screen than when drawing the border.
- lister_of_smeg
- Microbot
- Posts: 145
- Joined: Thu Nov 16, 2017 1:44 pm
Re: Fastest way to do a little BEEP
Oh it's noticeable...RMartins wrote: ↑Sun Feb 13, 2022 11:23 pmThat is barely noticeable, since it only happens in between transitions of contention and non-contention areas of the screen, usually 2 times in a full frame.lister_of_smeg wrote: ↑Sat Feb 12, 2022 8:20 pm ...
Also, you'll get a little distortion with that method on models which are subject to I/O contention, because the loop will take longer when the ULA is drawing the screen than when drawing the border.
[media]https://youtu.be/gSd9d6qeBFE[/media]
How noticeable, or perhaps more importantly whether it matters it's noticeable depends on the individual of course.
Re: Fastest way to do a little BEEP
I never listened to such a difference.
There must be something wrong with that code or the emulator.
Either Interrupts or assuming that the same memory pages are uncontended in every machine or something like that.
Can you share the code, for us to take a look ?
Did you try that on a Real Spectrum ?
Does it behave the same ?
Re: Fastest way to do a little BEEP
Yes, that'll do it. But you have options. You could be sending 111111110000000011111111000000001111111100000000 instead and it'd make no difference. If that happens to be easier to program. That's what Utz is doing. HL is a running counter and so at some point a bit in the middle of H will start as 0, then swap to 1 for a while, then back to 0 again. That one bit is continually fed OUT. The routine doesn't wait to see if it's actually changed. It's quite an efficient way of doing things, particularly if you're not too fussed about precise frequencies. And your code might even be able to carry out some other repetitive task at the same time.
When you OUT the 0 or the 1 to the speaker, it holds that state until you OUT to it again. If you OUT the same state, nothing changes. Just as if the border is already red and you send OUT 254,2. The slightly annoying think about the speaker though is if you want a border other than black, you have to remember to have the border colour as well as the speaker bit in the byte you send to OUT 254.
Some other tricks, and these vary from slightly to very advanced:
1. By adding a random element to decide whether the bit switches from 0 to 1, you can generate white noise. Again, vary the spacing between changes to control the overall pitch. You can get pseudo-random numbers either with a suitable snippet of machine code, or by just taking bytes sequentially from some of the more irregular sections of the ROM.
2. By generating a regular 1..0..1..0..1..0..1..0 pattern but then adding a random element of variation to it (but not too much, so that some of the original pattern is still there) you can get a sort of machine noise whine that combines a tone and noise. But it's not always very pleasant.
3. If you can generate a pattern that goes 10101010 00000000 10101010 00000000 where the '1010' bit is switching faster than humans can hear ( > 20,000 cycles per second), but the slower changes from the 1010 to 0000 bits occur at a much lower rate, then what happens is the circuit in the Speccy can't move the speaker fast enough and instead it's kind of 'juggled' or 'hovers' at a half-on position. So effectively your pattern is ½ ½ ½ ½ ½ ½ ½ ½ 0 0 0 0 0 0 0 0 ½ ½ ½ ½ ½ ½ ½ ½ 0 0 0 0 0 0 0 0. This gives a sort of crude volume control, and is also the basis of how 2-channel music players work. They add two 'half-volume' channels together into one output that combines 0000s, 1111s, and ½½½½s.
4. Probably best you forget you read #3 for now.
5. If you're playing tunes, you need to disable the interrupts and make sure the code is running in the top 32K of memory so the timing isn't messed up. And you need to calculate the exact length of your program loops to calculate the exact frequency for the note you want to play (look up the frequencies of musical notes on the internet, and know that going from one note (e.g. 'C') to the same note in the next 'octave' is an exact doubling, or halving, of frequencies).
6. If you're only doing this for sound effects, note that timing is far less important. You can even combine something like a long, repetitive screen copy loop with a tone or noise generator that OUTs to the speaker bit at the end of each pass of the loop. It'll severely limit the range of precise frequencies you can generate, but it'll mean you're not wasting programming time in delay loops when you could be doing something useful.
- lister_of_smeg
- Microbot
- Posts: 145
- Joined: Thu Nov 16, 2017 1:44 pm
Re: Fastest way to do a little BEEP
It's just the code utz posted with a di on the front and ei; ret on the end. bc is set to #8000 rather than #1000.RMartins wrote: ↑Mon Feb 14, 2022 10:39 amI never listened to such a difference.
There must be something wrong with that code or the emulator.
Either Interrupts or assuming that the same memory pages are uncontended in every machine or something like that.
Can you share the code, for us to take a look ?
Did you try that on a Real Spectrum ?
Does it behave the same ?
And yes, the behaviour is the same on a real 48k Spectrum.
Re: Fastest way to do a little BEEP
lister_of_smeg wrote: ↑Mon Feb 14, 2022 1:57 pmIt's just the code utz posted with a di on the front and ei; ret on the end. bc is set to #8000 rather than #1000.RMartins wrote: ↑Mon Feb 14, 2022 10:39 am
I never listened to such a difference.
There must be something wrong with that code or the emulator.
Either Interrupts or assuming that the same memory pages are uncontended in every machine or something like that.
Can you share the code, for us to take a look ?
Did you try that on a Real Spectrum ?
Does it behave the same ?
And yes, the behaviour is the same on a real 48k Spectrum.
Code: Select all
ld de,#80 ; tone freq, lower value = lower tone
ld bc,#1000 ; length
loop:
add hl,de ; 11T
ld a,h ; 4T
and #10 ; 7T ; optional, masks border colour
out (#fe),a ; 11T
dec bc ; 6T
ld a,b ; 4T
or c ; 4T
jr nz,loop ; 12T
Depending on the frequency counter (de), it also doesn't seem to generate a square wave, for counter values that are not a submultiple of 256.
But all this is mostly irrelevant (if running in contended memory), because when you change the BC value to such a large value, you will surely be crossing the frame uncontended and contended memory area edges (when running from contended memory).
So if you are crossing between these areas, the time width of the loop will change, which basically changes the pitch of the sound.
This happens, because during contention, ULA steals 4 clock cycles for each 8 clock cycles.
There are other complications, like memory access pattern weave in this chopped time, but avoiding the ugly details here.
Which does not happen in non contended areas.
So this works almost like vibrato.
For this not to happen, don't play notes, in both areas and just play inside a specific frame area (contended/screen or uncontended/border, WHEN running in contended memory), whatever one you choose to play on.
TIP: No single note is supposed to be that long.
For code running in NON contended memory, see next post.
Last edited by RMartins on Mon Feb 14, 2022 6:36 pm, edited 2 times in total.
Re: Fastest way to do a little BEEP
The play loop NOT being a multiple of 8T is the most important factor if running in NON-contended memory, since all the other factors (related with contended memory don't apply).
Although the memory we are running from is NOT contended, the ULA port is still a source of contention.
So if the play loop is not a multiple of the duty-cycle (ULA access memory pattern), then every time we access it we will NOT be aligned with the ULA memory pattern, every single time, some times more, some times less, but almost always unaligned, so we take a variable delay every single time which makes the sound have noise.
But if the play loop is a multiple of 8T, we will take a hit on the first ULA access, but then we get synchronised with it, since we use a similar beat (a multiple of the ULA access pattern timing) so further ULA port accesses will be aligned or in sync, and we will never get an extra delay.
Although the memory we are running from is NOT contended, the ULA port is still a source of contention.
So if the play loop is not a multiple of the duty-cycle (ULA access memory pattern), then every time we access it we will NOT be aligned with the ULA memory pattern, every single time, some times more, some times less, but almost always unaligned, so we take a variable delay every single time which makes the sound have noise.
But if the play loop is a multiple of 8T, we will take a hit on the first ULA access, but then we get synchronised with it, since we use a similar beat (a multiple of the ULA access pattern timing) so further ULA port accesses will be aligned or in sync, and we will never get an extra delay.
- lister_of_smeg
- Microbot
- Posts: 145
- Joined: Thu Nov 16, 2017 1:44 pm
Re: Fastest way to do a little BEEP
Indeed, adding a RET Z after the OR C should fix the 8T alignment. Which is nice, because it wouldn't actually increase the size of the routine in memory.
This was precisely my point in the first place!
Re: Fastest way to do a little BEEP
Ah - good point. Because this code is continuously doing OUTs, every time around the loop might be subject to contention with the ULA and thus suffer a bit of unintended slowdown.
Whereas if you use a delay loop and only OUT a 0 or 1 when it needs to switch, you suffer far less contention / slowdown. Or if your loop takes an exact multiple of 8 clock cycles to run, it can align with the contention pattern and run at the same speed as it does when there's no contention.
So a multi-channel sound routine or one that tries to juggle the speaker state as a form of volume control MUST repeat at a multiple of 8 clock cycles, so that it can align with the pattern of memory contention...
Whereas if you use a delay loop and only OUT a 0 or 1 when it needs to switch, you suffer far less contention / slowdown. Or if your loop takes an exact multiple of 8 clock cycles to run, it can align with the contention pattern and run at the same speed as it does when there's no contention.
So a multi-channel sound routine or one that tries to juggle the speaker state as a form of volume control MUST repeat at a multiple of 8 clock cycles, so that it can align with the pattern of memory contention...
Re: Fastest way to do a little BEEP
Actually, my takeaway from years of experimentation is that this is rather debatable. In theory yes, 8t alignment should result in cleaner sound. In practice however, even if you account for border area and all, there is going to be some distortion, no matter what. So it becomes a question of "massaging" that distortion, so it becomes less distracting. A strict 224t sound loop, while theoretically optimal on the 48k, often results in a rather sharp, almost tonal distortion. Using slightly less than 224t on the other hand can cause the distortion noise to be a bit more soft and subtle, even when not aligning to 8t. A good example is Shiru's classic Tritone engine, which arguably has the most pleasant, mellow sound of all the post-2010 engines - using a 153t sound loop. All in all I'd say 8t alignment only really matters when handling very low volumes, like in a PCM player for example.
Re: Fastest way to do a little BEEP
Let us have a debate about it then!utz wrote: ↑Mon Feb 14, 2022 9:20 pm Actually, my takeaway from years of experimentation is that this is rather debatable. In theory yes, 8t alignment should result in cleaner sound. In practice however, even if you account for border area and all, there is going to be some distortion, no matter what. So it becomes a question of "massaging" that distortion, so it becomes less distracting. A strict 224t sound loop, while theoretically optimal on the 48k, often results in a rather sharp, almost tonal distortion. Using slightly less than 224t on the other hand can cause the distortion noise to be a bit more soft and subtle, even when not aligning to 8t. A good example is Shiru's classic Tritone engine, which arguably has the most pleasant, mellow sound of all the post-2010 engines - using a 153t sound loop. All in all I'd say 8t alignment only really matters when handling very low volumes, like in a PCM player for example.
You remember I've been going on about this contention alignment issue for years and years. Over these years, I can remember only one engine that started sounding worse when aligned: Zilogator's "Squeeker". All others improved their sound. Some very slightly (the engines that write to ports relatively rarely are quite insensitive to the contention, e.g. Tim Follin engines are mostly indifferent to it), some very noticeably (Savage engine writes to ports A LOT, so the fact that it is slightly misaligned makes it sound detuned). Specifically for Tritone, I wrote aligned version of Tritone and I am yet to see a track that would not be significantly improved by the alignment (because Tritone is yet another example of engine that writes to ports A LOT).
With this in mind, I am tempted to say that it would probably be a good idea to go back to "Squeeker" and try to figure out once and for all, what is so special about it. Most likely, it is sound mixing strategy somehow benefits from a slight detune...
Re: Fastest way to do a little BEEP
Hahaha, the whole time I was typing this up I was thinking "Uh oh, introspec better not read this"
Ultimately it's all down to subjective experience. Objectively speaking 8t aligned engines are of course going to sound cleaner, but whether that's always better is a matter of personal preference. In some cases, I prefer to have that slight fuzzyness from ignoring IO contention masking some of the other imperfections. That said, I don't think I've ever written a non-aligned beeper engine.
Another thing that comes into play here is hearing range, which apparently decreases past the age of 35 (can definitely confirm from my own experience). So that aligned version of Tritone might sound better to you because you're simply not hearing some of the high-pitched distortions.
Ultimately it's all down to subjective experience. Objectively speaking 8t aligned engines are of course going to sound cleaner, but whether that's always better is a matter of personal preference. In some cases, I prefer to have that slight fuzzyness from ignoring IO contention masking some of the other imperfections. That said, I don't think I've ever written a non-aligned beeper engine.
Another thing that comes into play here is hearing range, which apparently decreases past the age of 35 (can definitely confirm from my own experience). So that aligned version of Tritone might sound better to you because you're simply not hearing some of the high-pitched distortions.