Creating ZX SOUND

Started by Baggey, February 01, 2021, 08:17:48

Previous topic - Next topic

Baggey

So for my Emulation ive been writting. I need to create a Sound effect BUFFER for it.

In Theory what i think i need is to be able to play a silent sound loop continuously. And when the speaker or Pizzo is triggered. Create a pip by writing a one into the buffer playing continuously. Or by finding the Byte for the write sweet spot of the Pip/Tone etc.

Sort off creating some sort of like white noise.

So on the fly bits/bytes are written into the buffer using a pointer. where audio is then heard.
Being able to create width of pulses by the number of bits/bytes written.

Would anybody have any ideas of how this could be achived in BlitzMax?  :o

Baggey
Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Derron

Maybe ask IWasAdam -- he is experienced in writing to audio buffers.

Aside of that. Audio.soloud module - and then write to the audio buffer / memory block as you already described.


bye
Ron

Scaremonger

Hi,
Strange to see that you are working on a ZX Sound system. I was recently looking at some old BBC Basic programs and thought I'd experiment and create an "Envelope" command.. I haven't achieved my aim yet, but here is some test code that I am slowly improving when I get a chance... Please be warned that there are several bugs in it at the moment.

Basically what you need to do is create a sample audio using a wave pattern and then play it! I think you will probably need to generate a Square Wave instead of a Sine Wave, but I am no expert in these matters.

Hope this help, or at least brings a smile :)


SuperStrict
Rem ISSUES
* Time signature are not working properly. 2/2 should be twice as fast as 4/4 but it isn't!
* I'm generating more samples than the array size.
- Have added a limiter, but this is not a fix!
End Rem

Type CScale ' in Pitch
Const C1:Int = 5
Const C1_SHARP:Int = 9
Const D1_FLAT:Int = 9
Const D1:Int = 13
Const D1_SHARP:Int = 17
Const E1_FLAT:Int = 17
Const E1:Int = 21
Const F1:Int = 25
Const F1_SHARP:Int = 29
Const G1_FLAT:Int = 29
Const G1:Int = 33
Const G1_SHARP:Int = 37
Const A1_FLAT:Int = 37
Const A1:Int = 41
Const A1_SHARP:Int = 45
Const B1_FLAT:Int = 45
Const B1:Int = 49

Const C2:Int = 53 ' MIDDLE C
Const C2_SHARP:Int = 57
Const D2_FLAT:Int = 57
Const D2:Int = 61
Const D2_SHARP:Int = 65
Const E2_FLAT:Int = 65
Const E2:Int = 69
Const F2:Int = 73
Const F2_SHARP:Int = 77
Const G2_FLAT:Int = 77
Const G2:Int = 81
Const G2_SHARP:Int = 85
Const A2_FLAT:Int = 85
Const A2:Int = 89
Const A2_SHARP:Int = 93
Const B2_FLAT:Int = 93
Const B2:Int = 97

Const C3:Int = 101
Const C3_SHARP:Int = 105
Const D3_FLAT:Int = 105
Const D3:Int = 109
Const D3_SHARP:Int = 113
Const E3_FLAT:Int = 113
Const E3:Int = 117
Const F3:Int = 121
Const F3_SHARP:Int = 125
Const G3_FLAT:Int = 125
Const G3:Int = 129
Const G3_SHARP:Int = 133
Const A3_FLAT:Int = 133
Const A3:Int = 137
Const A3_SHARP:Int = 141
Const B3_FLAT:Int = 141
Const B3:Int = 145

Const C4:Int = 149
Const C4_SHARP:Int = 153
Const D4_FLAT:Int = 153
Const D4:Int = 157
Const D4_SHARP:Int = 161
Const E4_FLAT:Int = 161
Const E4:Int = 165
Const F4:Int = 169
Const F4_SHARP:Int = 173
Const G4_FLAT:Int = 173
Const G4:Int = 177
Const G4_SHARP:Int = 181
Const A4_FLAT:Int = 181
Const A4:Int = 185
Const A4_SHARP:Int = 189
Const B4_FLAT:Int = 189
Const B4:Int = 193

Const C5:Int = 197
Const C5_SHARP:Int = 201
Const D5_FLAT:Int = 201
Const D5:Int = 205
Const D5_SHARP:Int = 209
Const E5_FLAT:Int = 209
Const E5:Int = 213
Const F5:Int = 217
Const F5_SHARP:Int = 221
Const G5_FLAT:Int = 221
Const G5:Int = 225
Const G5_SHARP:Int = 229
Const A5_FLAT:Int = 229
Const A5:Int = 233
Const A5_SHARP:Int = 237
Const B5_FLAT:Int = 237
Const B5:Int = 241
Private ' Prevent create
Method New() ; End Method
End Type

Type CNote ' duration with regards to a beat
' English
Const breve:Int = 128
Const semibreve:Int = 64
Const minim:Int = 32
Const crotchet:Int = 16
Const quaver:Int = 8
Const semiquaver:Int = 4
Const demisemiquaver:Int = 2
Const hemidemisemiquaver:Int = 1
' USA
Const doublewhole:Int = 128
Const whole:Int = 64
Const half:Int = 32
Const quarter:Int = 16
Const eighth:Int = 8
Const sixteenth:Int = 4
Const thirtysecond:Int = 2
Const sixtyfourth:Int = 1
Private ' Prevent create
Method New() ; End Method
End Type

Type CTempo ' in BPM
Const Adagietto:Int = 75
Const Adagio:Int = 71
Const Allegretto:Int = 116
Const Allegrissimo:Int = 172
Const Allegro:Int = 138
Const Allegro_moderato:Int = 118
Const Allegro_vivace:Int = 176
Const Andante:Int = 92
Const Andante_moderato:Int = 102
Const Andantino:Int = 94
Const Grave:Int = 35
Const Larghetto:Int = 63
Const Larghissimo:Int = 24
Const Largo:Int = 50
Const Lento:Int = 52
Const Marcia_moderato:Int = 84
Const Moderato:Int = 114
Const Presto:Int = 184
Const Prestissimo:Int = 200
Const Ragtime:Int = 62
Const Vivace:Int = 166
Const Vivacissimo:Int =  174
Private ' Prevent create
Method New() ; End Method
End Type

' TEMPO is written as a fraction 3/4 or 2/2 etc.
' Largo / Allegro
' Top number is beats in a measure (Bar)
' Bottom number is which note (duration) is considered a beat
' x/4 means quarter notes get one beat
' x/2 means half notes get 1 beat

' Beats is used as BPM, and duration of a beat is 60000/BPM = beat (in ms)

Struct SNote
Field pitch:Int
Field duration:Float
Field detached:Int = False ' (short note / Dot under note on stave)
Method New( p:Int, d:Float, s:Int = False )
pitch = p
duration = d
detached = s
End Method
End Struct

Type TMusic

Private
Field _tempo:Int
Field _timesig:Int[2] ' Time signature (Beats in a measure/note duration)
Field _format:Int = SF_MONO8
Field _hertz:Int  = 5000 ' 44100 ' Speed we are going to playback

Field notes:SNote[][]
Field samples:Int[]
Field sample:TAudioSample

Public

Method New()
_tempo:Int = CTempo.Moderato
_timesig = [4,4]
End Method

Method New( bpm:Int )
_tempo:Int = bpm
_timesig = [4,4]
End Method

Method New( bpm:Int, top:Int, bot:Int )
_tempo:Int = bpm
_timesig = [top,bot]
End Method

Method generate()
' Calculate duration of a beat (at current tempo)
Local beat:Float = 60.0/Float(_tempo) ' Length of a beat in ms
Print( _tempo+" BPM" )
Print( "1 beat is "+beat+" seconds" )

' Calculate duration (in ms)
'Local measure:Float = beat * Float(_measure) '/ Float( _quarters)
'Local mpq:Float = measure / _quarters
'Print( "1 measure is "+measure+" seconds" )

' Calculate music duration (in note time)
Local duration:Int
For Local chord:Int = 0 Until notes.length
duration :+ notes[chord][0].duration
Next
Print( "Music duration is "+(duration/64)+" whole notes" )
'Print( "There are "+_timesig[0]+" beats in a bar" )
'Print( (Float(_timesig[1])/128.0*duration) + " beats" )

' Calculate the duration of the music (ms)
Local length:Float = Float(_timesig[1])/128.0*duration * beat
Print( "Music length is "+length+" seconds" )

' Calculate the number of audio samples
Local audiosamples:Int = length * _hertz
Print( "Contains "+audiosamples+" samples" )

samples = New Int[ audiosamples ]

Local pointer:Long
Local time:Int
For Local chord:Int = 0 Until notes.length

Local time:Int = Float(_timesig[1])/128.0*notes[chord][0].duration * beat *Float(_hertz)
Local chordstart:Long = pointer
Local note:Int = 0
'For note:Int = 0 Until notes[chord].length
pointer = chordstart
Local rest:Int = 0
Local busy:Int = 0
If notes[chord][note].pitch=0 ' Rest
rest = time
busy = 0
ElseIf notes[chord][note].detached
rest = time-Int(time/2)
busy = time/2
EndIf
If busy>0
For Local t:Int = 0 To busy
'Print( sample + "/"+audiosamples)
If pointer<audiosamples samples[pointer]:+Sin( t*256/(128-notes[chord][note].pitch) ) *128
pointer:+1
Next
End If
If rest>0
For Local t:Int = 0 To rest
If pointer<audiosamples samples[pointer]=0
pointer:+1
Next
End If

'Next
' Recalculate chord
'If notes[chord].length >1
' pointer = chordstart
' For Local t:Int = 0 To time
' samples[sample] :\ notes[chord].length
' Next
'End If
Next
Print( pointer+" samples generated" )

' Create an audio sample
Self.sample = CreateAudioSample( samples.length, _hertz, _format )
For Local n:Int = 0 Until samples.length
Self.sample.samples[n]=samples[n]
Next

End Method

Method add( pitch:Int, duration:Float, detached:Int=False )
Local note:SNote = New SNote( pitch, duration, detached )
notes :+ [ [note] ]
End Method

Method add( pitch:Int[], duration:Float, detached:Int=False )
Local chord:SNote[]
For Local n:Int = 0 Until pitch.length
Local note:SNote = New SNote( pitch[n], duration, detached )
chord :+ [ note ]
Next
notes :+ [ chord ]
End Method

Method rest( duration:Float )
Local note:SNote = New SNote( 0, duration )
notes :+ [ [note] ]
End Method

Method play:TChannel( loop:Int = False )
Local audio:TSound=LoadSound( sample, loop )
Return PlaySound( audio )
End Method

End Type

' https://www.musicnotes.com/sheetmusic/mtd.asp?ppn=MN0017603
Local music:TMusic = New TMusic( 62, 2, 2 )
'# BAR 1
music.add( [CScale.F2,CScale.A2], CNote.quarter, True )
music.add( CScale.D3, CNote.quarter, True )
music.add( CScale.A2, CNote.quarter, True )
music.add( CScale.D3, CNote.quarter, True )
'# BAR 2
music.add( CScale.A2, CNote.eighth, True )
music.add( CScale.D3, CNote.quarter, True )
music.add( CScale.A2, CNote.eighth, True )
music.rest( CNote.eighth )
music.add( CScale.G2_SHARP, CNote.eighth )
music.add( CScale.A2, CNote.quarter, True )
'# BAR 3
music.add( CScale.A2, CNote.eighth )
music.add( CScale.G2_SHARP, CNote.eighth )
music.add( CScale.A2, CNote.eighth )
music.add( CScale.G2, CNote.eighth )
music.rest( CNote.eighth )
music.add( CScale.F2_SHARP, CNote.eighth )
music.add( CScale.G2, CNote.eighth )
music.add( CScale.G2_FLAT, CNote.eighth )
'# BAR 4
music.add( CScale.F2, CNote.quarter+CNote.eighth )
music.add( CScale.D2, CNote.eighth, True )
music.add( CScale.D2, CNote.half )
'# BAR 5
music.add( CScale.A2, CNote.quarter, True )
music.add( CScale.D3, CNote.quarter, True )
music.add( CScale.A2, CNote.quarter, True )
music.add( CScale.D3, CNote.quarter, True )
'# BAR 6
music.add( CScale.A2, CNote.eighth, True )
music.add( CScale.D3, CNote.quarter, True )
music.add( CScale.A2, CNote.eighth, True )
music.rest( CNote.eighth )
music.add( CScale.G2_SHARP, CNote.eighth )
music.add( CScale.A2, CNote.quarter, True )
'# BAR 7
music.add( CScale.G2, CNote.eighth )
music.rest( CNote.eighth )
music.add( CScale.G2, CNote.quarter, True )
music.add( CScale.G2, CNote.eighth )
music.add( CScale.F2_SHARP, CNote.eighth )
music.add( CScale.G2, CNote.quarter, True )
'# BAR 8
music.add( CScale.C3, CNote.eighth )
music.add( CScale.B2, CNote.quarter )
music.add( CScale.A2, CNote.eighth, True)
music.add( CScale.A2, CNote.eighth )
music.add( CScale.G2, CNote.quarter+CNote.eighth )
'# BAR 9
music.add( CScale.A2, CNote.quarter, True )
music.add( CScale.D3, CNote.quarter, True )
music.add( CScale.A2, CNote.quarter, True )
music.add( CScale.D3, CNote.quarter, True )
'# BAR 10
music.add( CScale.A2, CNote.eighth, True )
music.add( CScale.D3, CNote.quarter, True )
music.add( CScale.A2, CNote.eighth, True )
music.rest( CNote.eighth )
music.add( CScale.G2_SHARP, CNote.eighth )
music.add( CScale.A2, CNote.quarter, True )
'# BAR 11
music.add( CScale.C3, CNote.eighth, True )
music.rest( CNote.eighth )
music.add( CScale.C3, CNote.quarter, True )
music.add( CScale.C3, CNote.eighth )
music.add( CScale.A2, CNote.eighth )
music.add( CScale.G2, CNote.quarter, True )
'# BAR 12
music.add( CScale.F2, CNote.quarter+CNote.eighth )
music.add( CScale.D2, CNote.eighth, True )
music.add( CScale.D2, CNote.half )
'# BAR 13
music.add( CScale.D2, CNote.half )
music.add( CScale.F2, CNote.half )
'#
music.generate()

Graphics 320,200
Repeat
Cls
DrawText( "P   - Play", 0, 0 )
DrawText( "ESC - Exit", 0, TextHeight("8g") )
Flip

If KeyHit( KEY_P ) music.play()

Until KeyHit( KEY_ESCAPE )

#cantinaband
DefData CTempo.Ragtime, 2, 2
DefData CScale.A2, CNote.quarter, True


Baggey

Thankyou!

But unfortunatly this is BlitzMax NG code. Which throws so many errors for me i dont use it  ???

I get to many ERRORS! "Unable to find overload for ???? Argument #1 is"int" but decleration is "Short". Even with all the Boxes unchecked.
Where as in Blitzmax my code runs nicely :o  So BlitzMax NG for me just dosent get used. Probably should be talked about else where.

On the otherhand ive got Manic Miner Working yesterday! Which ironically was my first game. Im still working through little bugs in my OPcodes. And need to start looking at interupts as there are many games that use a CALL to a keyboard rouine in rom. As some games just sit at the menu waiting for keyboard input.

Im using BlitzMax 1.5

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Derron

What you unchecked where the errors :D
If you kept it checked, then you get errors when passing the wrong parameter type. Uncheck it and they become warnings (compilation continues).

Why is it there?
In vanilla you could pass "long" into "int", you can pass "float" to "int" and so on.
This can lead to issues: eg. you once had "float" as parameter, now you change it in code to be "int" (so eg dollar:Float becomes dollarcent:Int). Now that you changed that you forgot to adjust it in the calling code. Vanilla would still accept it. NG will warn that there is something "different".

To come around the issue you need to manually cast the value first - you then "know" what happens.


Stuff like "passing a byte to an integer" should/could work as there is no potential information loss. all byte values fit into an integer. Same to say for "short" (fits into integer).
But passing an "integer" to a "short" _might_ lead to some problem (passing a value bigger than the short max - or a negative number).


Yes, you have to "repair" your code one time - but it will help. Not to say that you can - with NG easily have two functions
function MyFunc(i:int); print "int : "+i; End Function
function MyFunc(f:Float); print "float : "+f; End Function

MyFunc(1.5)
MyFunc(1)


The benefit of "NG" will be some more optimizations done by GCC automatically - so some stuff just will run a bit faster with NG (ymmv of course)


bye
Ron

Scaremonger

Hi,
I looked up the specification of the ZX Spectrum 16K/48K and found that it only has a simple speaker. Sound was generated directly by the CPU and it used the BEEP command to produce Square waves.
The Spectrum 128K had a dedicated sound chip. I might work on that at a later date after I get BBC Micro Envelopes working.

I think this is Vanilla Blitzmax, so you should be able to get it to work.


'# ZX Spectrum 16K/48K retro sound
'# (c) Copyright Si Dunford, February 2021, All Rights Reserved
'# Version 0.1
SuperStrict

' ZX Spectrum sample
' https://worldofspectrum.org/ZXBasicManual/zxmanchap19.html
Print "Frere Gustav"
BEEP 1,0 ; BEEP 1,2 ; BEEP .5,3 ; BEEP .5,2 ; BEEP 1,0
BEEP 1,0 ; BEEP 1,2 ; BEEP .5,3 ; BEEP.5,2 ; BEEP 1,0
BEEP 1,3 ; BEEP 1,5 ; BEEP 2,7
BEEP 1,3 ; BEEP 1,5 ; BEEP 2,7
BEEP .75,7 ; BEEP .25,8 ; BEEP .5,7 ; BEEP .5,5 ;BEEP .5,3 ;
BEEP.5,2 ; BEEP 1,0
BEEP .75,7 ; BEEP .25,8 ; BEEP .5,7 ; BEEP .5,5 ; BEEP .5,3 ; BEEP .5,2 ;
BEEP 1,0
BEEP 1,0 ; BEEP 1,-5 ; BEEP 2,0
BEEP 1,0 ; BEEP 1,-5 ; BEEP 2,0

' Wait until Enter pressed
Input

Const MIDDLE_C_FREQ:Float = 262.0 'Hz

' ZX Spectrum 16K/48K Beep command uses a Square Wave
' Duration is in Seconds and Pitch is in Semitones from Middle C (C4)
' Therefore PItch 0 = C4, 1=C4#, 2=D4, 3=D4#, 4=E4, 5=F4 etc
' I have estimated the samplerate to be 3000 and fixed the volume at 1.0
Function BEEP( duration:Float, pitch:Float )
' Create an Audio Sample
Local samplerate:Int = 3000 'Hz (Estimated)
Local amplitude:Float = 1.0 ' Volume
Local samples:Int = samplerate * duration
Local audiosample:TAudioSample = CreateAudioSample( samples, samplerate, SF_MONO8 )

' Convert Pitch to Frequency
Local frequency:Float = MIDDLE_C_FREQ * 2.0^(pitch/12.0)

' Generate a Square Wave
Local time:Float = 0.0
Local delta:Float = 1.0/samplerate
Local square:Float
For Local sample:Int = 0 Until samples
Local value:Float = Sin( 360 * frequency * time ) * amplitude *100
If value>=0
square = amplitude
Else
square = -amplitude
End If
audiosample.samples[sample] = square
time :+ delta
Next

' Play the sound sample
Local audio:TSound=LoadSound( audiosample, False ) ' Load but do not loop
Local channel:TChannel = PlaySound( audio )
While ChannelPlaying( channel )
Wend
End Function


There is probably a better way to produce the square wave than simply use a sine wave and flatten it, but this is a work in progress for me.

Regards,
Si...

iWasAdam

the simple way is to create a single sample say 128frames in length
fill this with a square wave (< 64 sample=-1 > 64 sample =1)
play this as a sound with a loop - thats your base

play the sound through a channel to allow for direct manipulation

use pitch, pan, vol to do all the other stuff <-these are channel controls


Spectrum only used square waves
128 used a sound chip - you can emulate it - but it's not so simple. basically 3 voices + noise




Baggey

Quote from: Derron on February 02, 2021, 08:49:14
The benefit of "NG" will be some more optimizations done by GCC automatically - so some stuff just will run a bit faster with NG (ymmv of course)

Hi, Slightly of this topic but Ive just tried NG again. Checking different boxes. Slow to start but waited and got the Emulator running in it. I have had loads of errors either Ints, Shorts and Bytes argument error's.

Im ironing out bugs in my Speccy emulator and it seems like data is being lost randomly. Ive read through my code now, more times than i can rememder.  :o
And think when some thing is being equated from passed variables coming from functions that are using other functions. Information is either not being passed by me OR HARD CODED commands doing it?

NG runs but crashes at some point but gives loads of errors whilst Max runs quite happly but not right.

when i started writing the emulator I used the registers as they are :Byte :Short and int: for doing math's anding out parts of the short for my byte's.
Would it be best to use ints all the way?

Sorry this is off topic for sound but the end goal is a Fully functioning "Zx Spectrum emulator" working in BlitzMax or Maybe NG?  ;D
I have it at the point of running games some very playable. Im starting not to find any errors in the Z80 code now. >:(

Anyway Sound is something to start and Interups are needed as keyboard dosent work in all games.

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Baggey

Quote from: iWasAdam on February 03, 2021, 08:44:23
the simple way is to create a single sample say 128frames in length
fill this with a square wave (< 64 sample=-1 > 64 sample =1)
play this as a sound with a loop - thats your base

play the sound through a channel to allow for direct manipulation

use pitch, pan, vol to do all the other stuff <-these are channel controls


Spectrum only used square waves
128 used a sound chip - you can emulate it - but it's not so simple. basically 3 voices + noise

Hi Adam, Im new to Blitmax and not familiar with all the commands, Do you have anycode samples to play with?

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Baggey

#9
Quote from: Scaremonger on February 03, 2021, 08:16:07
Hi,
I looked up the specification of the ZX Spectrum 16K/48K and found that it only has a simple speaker. Sound was generated directly by the CPU and it used the BEEP command to produce Square waves.
The Spectrum 128K had a dedicated sound chip. I might work on that at a later date after I get BBC Micro Envelopes working.

I think this is Vanilla Blitzmax, so you should be able to get it to work.


'# ZX Spectrum 16K/48K retro sound
'# (c) Copyright Si Dunford, February 2021, All Rights Reserved
'# Version 0.1
SuperStrict

' ZX Spectrum sample
' https://worldofspectrum.org/ZXBasicManual/zxmanchap19.html
Print "Frere Gustav"
BEEP 1,0 ; BEEP 1,2 ; BEEP .5,3 ; BEEP .5,2 ; BEEP 1,0
BEEP 1,0 ; BEEP 1,2 ; BEEP .5,3 ; BEEP.5,2 ; BEEP 1,0
BEEP 1,3 ; BEEP 1,5 ; BEEP 2,7
BEEP 1,3 ; BEEP 1,5 ; BEEP 2,7
BEEP .75,7 ; BEEP .25,8 ; BEEP .5,7 ; BEEP .5,5 ;BEEP .5,3 ;
BEEP.5,2 ; BEEP 1,0
BEEP .75,7 ; BEEP .25,8 ; BEEP .5,7 ; BEEP .5,5 ; BEEP .5,3 ; BEEP .5,2 ;
BEEP 1,0
BEEP 1,0 ; BEEP 1,-5 ; BEEP 2,0
BEEP 1,0 ; BEEP 1,-5 ; BEEP 2,0

' Wait until Enter pressed
Input

Const MIDDLE_C_FREQ:Float = 262.0 'Hz

' ZX Spectrum 16K/48K Beep command uses a Square Wave
' Duration is in Seconds and Pitch is in Semitones from Middle C (C4)
' Therefore PItch 0 = C4, 1=C4#, 2=D4, 3=D4#, 4=E4, 5=F4 etc
' I have estimated the samplerate to be 3000 and fixed the volume at 1.0
Function BEEP( duration:Float, pitch:Float )
' Create an Audio Sample
Local samplerate:Int = 3000 'Hz (Estimated)
Local amplitude:Float = 1.0 ' Volume
Local samples:Int = samplerate * duration
Local audiosample:TAudioSample = CreateAudioSample( samples, samplerate, SF_MONO8 )

' Convert Pitch to Frequency
Local frequency:Float = MIDDLE_C_FREQ * 2.0^(pitch/12.0)

' Generate a Square Wave
Local time:Float = 0.0
Local delta:Float = 1.0/samplerate
Local square:Float
For Local sample:Int = 0 Until samples
Local value:Float = Sin( 360 * frequency * time ) * amplitude *100
If value>=0
square = amplitude
Else
square = -amplitude
End If
audiosample.samples[sample] = square
time :+ delta
Next

' Play the sound sample
Local audio:TSound=LoadSound( audiosample, False ) ' Load but do not loop
Local channel:TChannel = PlaySound( audio )
While ChannelPlaying( channel )
Wend
End Function


There is probably a better way to produce the square wave than simply use a sine wave and flatten it, but this is a work in progress for me.

Regards,
Si...

OHHHHH. Loving this. Shall have a play around and now for 'Manic Miner' "The Blue Danube Waltz"  :P



The 48k Spectrum does produce sound using the CPU it OUT's to port 254. literaly by turning voltage on and off to the Piezo soldered to the PCB.! Very crude in deed.
ie, Z80 Opcode OUT(254),n Bit D4 is the bit for turning the speaker on and off. ie 1 for on and 0 for off. I think as long as all the Tstates for instructions are timed write and the Out(254),16 contains 16 we produce a PIP so to speak.

This is what im aiming for now and maybe away of being able to alter (length of play and amplitude) a little bit. Some other Spectrums had a dedicated AY chip as well. For now im trying to emulate just the Piezo. Looking at the Code in Memory i can see where the tune is stored in Bytes so probably just feeding these to the OUT command for emulation would be good. Seems easy but hay, Theres always a spaner in the works somewhere. That just aint going to play nicely ;D

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Scaremonger

I would suspect that those bytes in memory are related to the individual samples, so OUT(254,n) would be similar to the value in the sample at a particular point.

Is it possible for you to post the bytecodes for the audio so I can take a look?

Si...

Baggey

#11
Quote from: Scaremonger on February 03, 2021, 12:40:24
I would suspect that those bytes in memory are related to the individual samples, so OUT(254,n) would be similar to the value in the sample at a particular point.

Is it possible for you to post the bytecodes for the audio so I can take a look?

Si...

https://youtu.be/F7khL9Ms4ow

These are the audio codes for the in game music its a repeating loop.

In Music "jig"
128,114,102,96,86,102,86,86,81,96,81,81,86,102,86,86,128,114,102,96,86,102,86,86,81,96,81,81,86,86,86,86,128,114,102,96,86,102,86,86,81,96,81,81,86,102,86,86,128,114,102,96,86,102,86,64,86,102,128,102,86,86,86,86

Getting the title screen music code is going to take a little longer. Ive attached the above YouTube clip so the relevent pitch tone of notes can be obtained/heard.

Title screen "The Blue Danube"
80,128,129,80,102,103,80,86,87,50,86,87,50,171,203,50,43,51,50,43,51,50,171,203,50,51,64,50,51,64,50,171,203,50,128,129,50,128,129,50,102,103,50,86,87,50,96,86,50,171,192,50,43,48,50,43,48,50,171,192,50,48,68,50,48,68,50,171,192,50,136,137,50,136,137,50,114,115,50,76,77,50,76,77,50,171,192,50,38,48,50,38,48,50,171,192,50,48,68,50,48,68,50,171,192,50,136,137,50,136,137,50,114,115,50,76,77,50,76,77,50,171,203,50,38,51,50,38,51,50,171,203,50,51,64,50,51,64,50,171,203,50,128,129,50,128,129,50,102,103,50,86,87,50,64,65,50,128,171,50,32,43,50,32,43,50,128,171,50,43,51,50,43,51,50,128,171,50,128,129,50,128,129,50,102,103,50,86,87,50,64,65,50,128,152,50,32,38,50,32,38,50,128,152,50,38,48,50,38,48,50,0,0,114,115,50,114,115,50,96,97,50,76,77,50,76,153,50,76,77,50,76,77,50,76,153,50,91,92,50,86,87,50,51,205,50,51,52,50,51,52,50,51,205,50,64,65,50,102,103,100,102,103,50,114,115,100,76,77,50,86,87,50,128,203,25,128,0,25,128,129,50,128,203,255

I think the last byte "255" is a terminator not needed. I think the tune uses three bytes at a time to make the note. The first byte is the duration of the note, the second and third dermine the frequency. How that relates to OUT(254),16 I think now is just turning the Piezo on and off. I think the bytes are used in a loop to determine the length of the frequency the Piezo is turned on and off so to speak. So i think its like turning the speaker on for, Lets say a "1 second" duration, Pulsing this many "times a second".

If your interested i can look at the code executing these bytes to try and understand further.

Incidently the Z80 CPU runs at 3.5Mhz I think most instructions take about 5u Secs to execute. "Very roughly 5u Secs!"

Kind Regards Baggey

Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Baggey

#12
Ive Been Looking at many sites to day about creating sound's from computer chips!   :-X  WELL!  We are in LOCK DOWN people!    :o

Remebering all sorts of formuale from my youthfull day's.

So Been starting to alter and add to code example's ive been shown to help me on my Quest! Writing a "SPEC BLITZ" Emulator. Sort of like that name?

Anyway's Came across this. Imagine doing "Karaoke" to what i think is probably the earliest version of a Karaoke machine! Dare i say it. It's a C64 on a SID chip.

https://youtu.be/qxHd7Q803Sw   https://youtu.be/Bsd5b2FuYhM

Father for give my Sin's but ive Imagend. A ZX SPECTRUM 48K with a SID chip init  >:D  Damm the Blastfomy! You Frankenstien. Well anything is possible in software! HA! HA! HA!.

Sorry got lost in the moment. Enjoy the clip if you dare 1980's Karaoke if it ever existed.  ;D

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Scaremonger

#13
Quote from: Baggey on February 03, 2021, 14:10:39
Title screen "The Blue Danube"
80,128,129,80,102,103,80,86,87,50,86,87,50,171,203,50,43,51,50,43,51,50,171,203,50,51,64,50,51,64,50,171,203,50,128,129,50,128,129,50,102,103,50,86,87,50,96,86,50,171,192,50,43,48,50,43,48,50,171,192,50,48,68,50,48,68,50,171,192,50,136,137,50,136,137,50,114,115,50,76,77,50,76,77,50,171,192,50,38,48,50,38,48,50,171,192,50,48,68,50,48,68,50,171,192,50,136,137,50,136,137,50,114,115,50,76,77,50,76,77,50,171,203,50,38,51,50,38,51,50,171,203,50,51,64,50,51,64,50,171,203,50,128,129,50,128,129,50,102,103,50,86,87,50,64,65,50,128,171,50,32,43,50,32,43,50,128,171,50,43,51,50,43,51,50,128,171,50,128,129,50,128,129,50,102,103,50,86,87,50,64,65,50,128,152,50,32,38,50,32,38,50,128,152,50,38,48,50,38,48,50,0,0,114,115,50,114,115,50,96,97,50,76,77,50,76,153,50,76,77,50,76,77,50,76,153,50,91,92,50,86,87,50,51,205,50,51,52,50,51,52,50,51,205,50,64,65,50,102,103,100,102,103,50,114,115,100,76,77,50,86,87,50,128,203,25,128,0,25,128,129,50,128,203,255

I think the last byte "255" is a terminator not needed. I think the tune uses three bytes at a time to make the note. The first byte is the duration of the note, the second and third dermine the frequency. How that relates to OUT(254),16 I think now is just turning the Piezo on and off. I think the bytes are used in a loop to determine the length of the frequency the Piezo is turned on and off so to speak. So i think its like turning the speaker on for, Lets say a "1 second" duration, Pulsing this many "times a second".

Its fairly easy to see the pattern of "50" through the Blue Danube data and looking at the sheet music it is composed almost entirely of Crotchets (Quarter notes); however at the end of the section the tune has a slightly different structure so I used that to try to work out what it all means.

If you put the last 5 bars of the sheet music next to the Spectrum data and include the Spectrum Pitch for those notes and the frequencies, you get something that looks like this:


SPECTRUM DATA  MUSICAL NOTE     NOTE  PITCH  FREQUENCY
50,51,205,     Crochet,         E5,   16     660 Hz
50,64,65,      Crochet,         C5,   12     523 Hz
50,102,103,    Crochet,         E4,   4      330 Hz
100,102,103,   Minim,           E4,   4      330 Hz
50,114,115,    Crotchet,        D4,   2      293 Hz
100,76,77,     Minim,           A4,   9      440 Hz
50,86,87,      Crotchet,        G4,   7      392 Hz
50,128,203,    Dotted Crochet,  C4,   0      262 Hz
25,128,0,      Quaver,          C4,   0      262 Hz
25,128,129,    Crotchet,        C4,   0      262 Hz
50,128,203,    Dotted Crochet,  C4,   0      262 Hz
255


The Spectrum note lengths don't seem to be very accurate but it does seem to match; so as you suspected, the first of these digits is the note duration.
100 is a Minim (Half Note)
50 is a Crotchet (Quarter note)
25 is a Quaver (Eighth note)

The note frequencies are interesting though: The data appears to be in reverse to the Spectrum Pitch command and Frequency, but I cannot figure out how to convert it at the moment:


NOTE  PITCH  FREQ     SPEC-DATA
C4    0      262 Hz   128
C4#   1      277 Hz
D4    2      294 Hz   114
D4#   3      311 Hz
E4    4      300 Hz   102
F4    5      349 Hz
F4#   6      370 Hz
G4    7      392 Hz   86
G4#   8      415 Hz
A4    9      440 Hz   76
A4#   10     466 Hz
B4    11     494 Hz
C5    12     523 Hz   64


Will keep stabbing at it until I figure out how to play it.

Baggey

#14
Kudos  :P

This weekend ill have a try and strip out the Start screen Music data from Jet Set Willy. The "Moonlight Sonata" and in game music "In the Hall of the Mountain King".  ;D

Its one of the other games ive got up and running. They both dont seem to use the interupt system in Rom which ive also got to get working. I also need to get the Rope's working properly their swinging in the rooms only one way and the conveyer belts animate one way only  :o.

I think its to do with my shift LEFT instruction emulations. It goes right but not left?
and willy can jump anywhere in these rooms. Anyway ive digresed.

QuoteThe note frequencies are interesting though: The data appears to be in reverse to the Spectrum Pitch command and Frequency, but I cannot figure out how to convert it at the moment:

The 2 bytes of Frequency I think you say they appear reversed when a 16 bit register is loaded with a value it takes the 'LSB' Least significant byte first and then the 'MSB' Most significant byte next. ie a number of upto 65535 can be obtained. Including the zero thats 65536 posibility's so if you take the (first byte * 256) then add the second you have a number between ( 0 and 65535 ) .

Normally the BC register would be fed in a timed loop using "Instructions to grab data and OUT to the Piezo" with JRNZ. Creating a timed loop of samples of the note? until BC is 0.
Within this loop the speaker would be being pulsed on and off.

Therefore Let's say 50,51,205 ... 50=A Crochet as you say, then the Frequency. if the 2nd and 3rd bytes are indeed reversed (on / off) pulses=(205*256+51)=52531 , 1/52531=19Mhz if it were not reversed you'd get (on / off) pulses=(51*256+205)=13261 , 1/13261=75Mhz.

Have no idea if 19Mhz or 75Mhz come close to making any sense?  :o God im getting rusty at maths, it's uHz 10-6 used to have an engineering button that did it for me :))
Come to think of it the tune is played very screetchy. So the notes are fast.

Kind Regards Lee
Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!