SyntaxBomb - Indie Coders

Languages & Coding => BlitzMax / BlitzMax NG => Topic started by: Baggey on February 01, 2021, 08:17:48

Title: Creating ZX SOUND
Post by: Baggey on February 01, 2021, 08:17:48
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
Title: Re: Creating ZX SOUND
Post by: Derron on February 01, 2021, 10:26:43
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
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 01, 2021, 17:57:15
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

Title: Re: Creating ZX SOUND
Post by: Baggey on February 02, 2021, 06:15:25
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
Title: Re: Creating ZX SOUND
Post by: Derron on February 02, 2021, 08:49:14
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
Title: Re: Creating ZX SOUND
Post by: 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...
Title: Re: Creating ZX SOUND
Post by: 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



Title: Re: Creating ZX SOUND
Post by: Baggey on February 03, 2021, 10:23:33
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
Title: Re: Creating ZX SOUND
Post by: Baggey on February 03, 2021, 10:56:17
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
Title: Re: Creating ZX SOUND
Post by: Baggey on February 03, 2021, 11:07:22
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

(https://www.jlwranglerforums.com/forum/attachments/27c5932b4197271a08b337e75dc7c17a-gif.304658/)

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
Title: Re: Creating ZX SOUND
Post by: 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...
Title: Re: Creating ZX SOUND
Post by: Baggey on February 03, 2021, 14:10:39
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 (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

Title: Re: Creating ZX SOUND
Post by: Baggey on February 03, 2021, 18:09:26
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/qxHd7Q803Sw)   https://youtu.be/Bsd5b2FuYhM (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
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 04, 2021, 23:09:05
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.
Title: Re: Creating ZX SOUND
Post by: Baggey on February 05, 2021, 07:01:11
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
Title: Re: Creating ZX SOUND
Post by: col on February 05, 2021, 09:59:51
'Sounds' interesting - pardon the pun ;D

Does this snippet of code help you at all?https://skoolkit.ca/disassemblies/manic_miner/asm/37596.html (https://skoolkit.ca/disassemblies/manic_miner/asm/37596.html)
Title: Re: Creating ZX SOUND
Post by: Baggey on February 05, 2021, 16:45:59
Ok. Very interesting site. Manic Miner Disassembly! Thankyou Col


37596 LD A,(IY+0) Pick up the next byte of tune data from the table at 33902 "THE START OF OUR DATA STRING"
37599 CP 255         Has the tune finished?
37601 RET Z         Return (with the zero flag set) if so
37602 LD C,A         Copy the first byte of data for this note (which determines the duration) to C
37603 LD B,0         Initialise B, which will be used as a delay counter in the note-producing loop
37605 XOR A         Set A=0 (for no apparent reasaon)
37606 LD D,(IY+1) Pick up the second byte of data for this note
37609 LD A,D         Copy it to A
37610 CALL 37675 Calculate the attribute file address for the corresponding piano key
37613 LD (HL),80         Set the attribute byte for the piano key to 80 (INK 0: PAPER 2: BRIGHT 1)
37615 LD E,(IY+2) Pick up the third byte of data for this note
37618 LD A,E         Copy it to A
37619 CALL 37675 Calculate the attribute file address for the corresponding piano key
37622 LD (HL),40         Set the attribute byte for the piano key to 40 (INK 0: PAPER 5: BRIGHT 0)
37624 OUT (254),A Produce a sound based on the frequency parameters in the second and third bytes of data for this note (copied into D and E)
37626 DEC D
37627 JR NZ,37634
37629 LD D,(IY+1)
37632 XOR 24
37634 DEC E
37635 JR NZ,37642
37637 LD E,(IY+2)
37640 XOR 24
37642 DJNZ 37624
37644 DEC C
37645 JR NZ,37624
37647 CALL 37687 Check whether ENTER or the fire button is being pressed
37650 RET NZ         Return (with the zero flag reset) if it is
37651 LD A,(IY+1) Pick up the second byte of data for this note
37654 CALL 37675 Calculate the attribute file address for the corresponding piano key
37657 LD (HL),56         Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)
37659 LD A,(IY+2) Pick up the third byte of data for this note
37662 CALL 37675 Calculate the attribute file address for the corresponding piano key
37665 LD (HL),56         Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)
37667 INC IY         Move IY along to the data for the next note in the tune
37669 INC IY
37671 INC IY
37673 JR 37596         Jump back to play the next note


"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

Ive read through the Music playing code. And in a nut shell I think, from the Speakers point of view. We are literaly pulsing on and off. Within four loops.

Just looking at the first three bytes that are picked up.

There Are 4 Loops in play.

In a nut shell C=80 first byte and Pitch/Duration ie an outer loop executed 80 times.

B=0 The second loop which will always be executed 256 times and always starts at zero. Next inner loop of which the 2nd and 3rd Bytes are counted down.

The Third Loop which is D=128 (on first run) and the second byte is another timed loop within this.
The Fourth Loop which is E=129 (on first run) and the third byte.

Now the Third byte effects the initial condition of the speaker being on. When it goes through the Call 37675 it will come back with A=240 (on first Run).

This Long sentence may not be right! But I think this could give an out of sync delay of the (on/off). Depending on how different D and E are on First run? This would mean putting the third byte of every three bytes in the call and calculating the A value. On each start of the Note. But i think it could be ignored and assumed to be on unless its a silent note or pause. The human Ear might not hear this mistake as it would be so fast!? If the Third does need to be calculated its Subtracted by 8 then Rotated three bits to the right 3 times looping around on its self. Then Complementing the 1's and 0's. Then Oring with 224. So it might seem it does.

So Bit 5 of A is a 1 so when the OUT(254),240 is used the Piezo is turned on as bit 5 is a 1. So speaker is now on.

Now, D=128 and E=129 they are both decreased by one. When D=0 it will become D=128 again and the speaker turned off! and if E=0 it will become E=129 again and speaker turned off also.  NOTE the XOR 24 does the Speaker and the Mic bits not sure why?

The D and E loop are played exacatly 256 times or until B=0. Which will loop for 256 times again playing D and E. Then Fist Outer Loop decreases C=80 by 1 and does it all over again until C=0. This is the Pitch Loop.

Then we pick up the next three bytes in the TUNE and do the exact same thing all over again untill we hit 255 the tune terminator or start playing whole tune again. 

One iteration of this should produce the first note.  :o

Hope ive not made any errors and this makes sence. These loops and Z80 instructions can be done in Blitzmax.
But how the loops relate to timming of Playing through sound channels at sample's per second etc. God only knows  ;D

Kind Regards Baggey


Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 06, 2021, 09:53:37
@col : Thanks, that is so useful.

@baggy : Your run through looks good. Can't wait to get some free time to work on it.
Title: Re: Creating ZX SOUND
Post by: col on February 06, 2021, 15:18:03
If you wanted a truly authentic reproduction of the tune I would think you could do it with some math.

Using the Z80 manual you could get the TState timings for each instruction. Knowing the 3.5Mhz that the Z80 is running at you can work out the on/off rate of the piezo speaker for each frequency.

The Xor 24 is probably to do with a Speccy side effect. As you may (or may not) know the Ear (speaker) and Mic are both accessed through port 254. The value 24 in 8bit binary in 00011000 with the 5th bit (0001) being the speaker and the 4th bit (1000) for the Mic - The side effect is that if you output to both of these at the same time then it increased the volume of the output.
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 06, 2021, 16:38:21

Quote from: col on February 06, 2021, 15:18:03
Using the Z80 manual you could get the TState timings for each instruction. Knowing the 3.5Mhz that the Z80 is running at you can work out the on/off rate of the piezo speaker for each frequency.
Thanks; I have already looked up the number of cycles for the OUT instruction on this site (https://spectrumforeveryone.com/technical/z80-processor-instructions/) but hadn't considered the calling routine itself. @Baggey will probably have a seperate method within his emulator.

Quote from: col on February 06, 2021, 15:18:03
The value 24 in 8bit binary in 00011000 with the 5th bit (0001) being the speaker and the 4th bit (1000) for the Mic. The side effect is that if you output to both of these at the same time then it increased the volume of the output.

Interesting that the microphone affects the volume.

Bits xxxxx111 appear to control the border color too.


Title: Re: Creating ZX SOUND
Post by: Baggey on February 06, 2021, 18:47:38
Quote from: Scaremonger on February 06, 2021, 16:38:21

[quoteBits xxxxx111 appear to control the border color too.

The Border color is affected by the first 3 bits. The colour of the border is what's on the keys of the spectrum Keyboard. Incidently xxxxx111=7 is WHITE.

Just had a look over at that site. And he or she has written some of the opcodes differently which is interesting. As ive been thinking of doing this with the 6502. Which was in the "BBC_B". As the litreture for the 6502 is Horrendously documented  :-[ There's still alot of Missing opcodes there thou.

I haven't counted all of the Z80 Opcodes In my Emulator. Which i might call "SpecBlitz" or "SpecMax". But there can be upto 1792 instructions  :o feels like im in the region of at least 1300 right now! I think some of these have never been used as there undocumented.

Good luck with producing some sound. I could have a look at writing a small routine in Blitmax. Just to spit out the value of A for a given value of E if that helps?

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 07, 2021, 00:46:42
Quote from: Baggey on February 06, 2021, 18:47:38
I could have a look at writing a small routine in Blitmax. Just to spit out the value of A for a give value of E if that helps?

I have done this tonight using the actual ZXSpectrum code converted in-place:


SuperStrict
' Manic Miner Audio Output

' 16 Bit Regsiters
Global BC16:Short
Global DE16:Short
Global HL16:Short
Global BC:Byte Ptr = Varptr BC16
Global DE:Byte Ptr = Varptr DE16
Global HL:Byte Ptr = Varptr HL16
Const B:Byte=1, C:Byte=0, D:Byte=1, E:Byte=0, H:Byte=1, L:Byte=0

' 8 Bit Registers
Global A:Byte
Global IY:Byte, IX:Byte

' FLAGS
Global F:Byte[8]
Const CF:Byte=0
Const ZR:Byte=1

' Music Data
Global data:Byte[] = [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]

' Direct Code Conversion
Function CALL_37596:Int()

While True
A = data[IY] '37596 LD A,(IY+0) Pick up the next byte of tune data from the table at 33902
If A=255 '37599 CP 255 Has the tune finished?
Return True '37601 RET Z Return (with the zero flag set) if so
End If
BC[C] = A '37602 LD C,A Copy the first byte of data for this note (which determines the duration) to C
BC[B] = 0 '37603 LD B,0 Initialise B, which will be used as a delay counter in the note-producing loop
A = 0 '37605 XOR A Set A=0 (for no apparent reasaon)
DE[D] = data[IY+1] '37606 LD D,(IY+1) Pick up the second byte of data for this note
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = DE[D] '37609 LD A,D Copy it to A
'CALL_37675() '37610 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,80) '37613 LD (HL),80 Set the attribute byte for the piano key to 80 (INK 0: PAPER 2: BRIGHT 1)
DE[E] = data[IY+2] '37615 LD E,(IY+2) Pick up the third byte of data for this note
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = DE[E] '37618 LD A,E Copy it to A
'CALL_37675() '37619 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,40) '37622 LD (HL),40 Set the attribute byte for the piano key to 40 (INK 0: PAPER 5: BRIGHT 0)
'Print( "A ---"+Right(Hex(A),2)+"  BC-"+Right(Hex(BC16),4)+"  DE-"+Right(Hex(DE16),4)+"  HL-"+Right(Hex(HL16),4) )
Repeat ' until C=0 (at 37645)
Repeat ' until B=0 (at 37642)
_OUT(254,A) '37624 OUT (254),A Produce a sound based on the frequency parameters in the second and third bytes of data for this note (copied into D and E)
DE[D] :- 1 '37626 DEC D
If DE[D]=0 '37627 JR NZ,37634
DE[D] = data[IY+1] '37629 LD D,(IY+1)
A = A~24 '37632 XOR 24
End If
DE[E] :- 1 '37634 DEC E
If DE[E]=0 '37635 JR NZ,37642
DE[E] = data[IY+2] '37637 LD E,(IY+2)
A = A~24 '37640 XOR 24
End If
'Print( "A ---"+Right(Hex(A),2)+"  BC-"+Right(Hex(BC16),4)+"  DE-"+Right(Hex(DE16),4)+"  HL-"+Right(Hex(HL16),4) )
BC[B] :- 1 '37642 DJNZ 37624
Until BC[B]=0 ' ""    ""
BC[C] :- 1 '37644 DEC C
Until C=0 '37645 JR NZ,37624
'##### KEYBOARD INTERACTION
'If CALL_37687() '37647 CALL 37687 Check whether ENTER or the fire button is being pressed
' Return False '37650 RET NZ Return (with the zero flag reset) if it is
'End If
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = data[IY+1] '37651 LD A,(IY+1) Pick up the second byte of data for this note
'CALL_37675() '37654 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,56) '37657 LD (HL),56 Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = data[IY+2] '37659 LD A,(IY+2) Pick up the third byte of data for this note
'CALL_37675() '37662 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,56) '37665 LD (HL),56 Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)
IY :+ 3 '37667 INC IY Move IY along to the data for the next note in the tune
'37669 INC IY
'37671 INC IY
Wend '37673 JR 37596 Jump back to play the next note
End Function

' Direct Code Conversion
' Calculates which on-screen piano key will be shown pressed
' We dont need this, so it has been commented out
'Function CALL_37675()
' A :- 8 '37675 SUB 8 Compute the piano key index (K) based on the frequency parameter (F), and store it in bits 0-4 of A: K=31-INT((F-8)/8)
' _RRCA() '37677 RRCA
' _RRCA() '37678 RRCA
' _RRCA() '37679 RRCA
' A  = ~A '37680 CPL
' A :| 224 '37681 OR 224 A=224+K; this is the LSB
' HL[L] = A '37683 LD L,A Set HL to the attribute file address for the piano key
' HL[H] = 89 '37684 LD H,89
' Return '37686 RET
'End Function

' Keyboard input
' Not required, so has been commented out
'Function CALL_37687:Int()
' Return False
'End Function

' Z80 Mnumonic Rotate Bits Right with Carry
'Function _RRCA()
' Local x:Byte = A & $01
' A = ( A Shr 1 | ( A & $01 ) Shl 7 )
' F[CF] = x
'End Function

' Z80 Mnumonic
Function _OUT( port:Byte, value:Byte)
If port=254
' bitmap of value is xxxSMBBB
' S=Sound M=Microphone B=Border
Print( value )
End If
End Function

' Play Music
IY = 0 ' Set pointer to start of music
CALL_37596()


Some of the code is not actually needed for the music, for example this piece (and the function it calls) appear to update an onscreen "Piano" to show the keys being played' so you'll find that commented out.

37609 LD A,D         Copy it to A
37610 CALL 37675 Calculate the attribute file address for the corresponding piano key
37613 LD (HL),80         Set the attribute byte for the piano key to 80 (INK 0: PAPER 2: BRIGHT 1)

37618 LD A,E         Copy it to A
37619 CALL 37675 Calculate the attribute file address for the corresponding piano key
37622 LD (HL),40         Set the attribute byte for the piano key to 40 (INK 0: PAPER 5: BRIGHT 0)

37651 LD A,(IY+1) Pick up the second byte of data for this note
37654 CALL 37675 Calculate the attribute file address for the corresponding piano key
37657 LD (HL),56         Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)

37659 LD A,(IY+2) Pick up the third byte of data for this note
37662 CALL 37675 Calculate the attribute file address for the corresponding piano key
37665 LD (HL),56         Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)


The routine that interrupts the music when a key is pressed is also commented out:

37647 CALL 37687 Check whether ENTER or the fire button is being pressed
37650 RET NZ         Return (with the zero flag reset) if it is


Looking through the code in I think I know why there are two values after the duration. The code is attempting to simulate pressing two keys at once. The inner loop repeats 256 times and decreases both of these values (Register D and E), turning off/on the speaker to mimic a frequency... Pretty impressive.

PS. It doesn't play the music yet.
Title: Re: Creating ZX SOUND
Post by: Baggey on February 07, 2021, 06:36:33
Well Impressed!

So you dived straight into the Z80 Opcodes ;D Writing an Emulator is a great way of learning the Z80 Opcodes.

Im currently rewriting my opcodes as functions in the quest for more speed rather than using long SELECT/CASE statements.

So Here are RRCA, DJNZ you can ignore the flags! for just simulating the Music Play.

I see you have already got round The "Jump relative instructions"
NOTE the DJNZ and JRNZ Instructions have two differnt timings on wether a TRUE condition or a FALSE condition are met. Again the timing is so fast this might not be recognized by the human ear thou! Oh and when the DJNZ starts which is initialized by B=0 because it started at ZERO it will count 256 times in the loop. Which will cause a small timing addition to the simulated loop. As there are many of these iterations of the loop. So this maybe needed to be be taken into account in the overall timing.
IY and IX are 16 bit register's but their being peeked for a byte ie, LET E=PEEK (IY+1), so it's looking at the address of (IY+1) seeing what's in that memory location and returning the byte to E. There are many UNDOCUMENTED codes on the Z80 and the IX,IY register's are even split into 8 Bit register's and become IYH, IYL, IXH, IXL But that's something else again ;D

Code (Blitzmax) Select
                     
  Case 15 ' RRCA
TempCARRY=(A & 1) <> 0
A=(A Shr 1)
If TempCARRY=1 Then A=(A | 128)
'
fSIGN = fSIGN ' Not affected
fZERO = fZERO ' Not affected
fFIVE= ((A & 32) <> 0)
fHALFCARRY = 0
fTHREE = ((A & 8) <> 0)
fPARITY = fPARITY ' Not affected
fADDSUB = 0
fCARRY = TempCARRY
SetFLAGS()
PC:+1 ; TCycles:+4
  Case 16 ' DJNZ,d
DisJump=(twosum[PC1]+2) ; B:-1 ; BC=(B Shl 8)+C
If B<>0 Then
  PC:+DisJump ; TCycles:+13
Else
  PC:+2 ; TCycles:+8
End If
  Case 47 ' CPL
A=(A ~ 255)
fSIGN = fSIGN ' Not affected
fZERO = fZERO ' Not affected
fFIVE = ((A & 32) <> 0)
fHALFCARRY = 1
fTHREE = ((A & 8) <> 0)
fPARITY = fPARITY 'Not affected
fADDSUB = 1
fCARRY = fCARRY ' Not affected
SetFLAGS()
        PC:+1 ; TCycles:+4


Here is the code for OR and XOR in Method's which are Function's. Again the flags can be ignored for Music play.

Code (Blitzmax) Select

  Method Or8(InB:Byte)
      ' All Flags affected.
      ' Sign Set if Bit 7 is One
      ' Zero Set if Answer is zero
      ' Halfcarry is Reset
              ' Parity Set if EVEN number of One's
      ' AddSub is Reset
      ' Carry is Reset
      A=(A | InB)
      '
      fSIGN = (A & 128) <> 0
      fZERO = (A = 0)
      fFIVE = (A & 32) <> 0
      fHALFCARRY = 0
      fTHREE = (A & 8) <> 0
      fPARITY = parity[A]
      fADDSUB = 0
      fCARRY = 0
      SetFLAGS()
  End Method

  Method Xor8(InB:Byte)
       ' All Flags affected.
       ' Sign Set if Bit 7 is One
       ' Zero Set if Answer is zero
       ' Halfcarry is Reset
       ' Parity Set if EVEN number of One's
       ' AddSub is Reset
       ' Carry is Reset
       A=(A ~ InB)
       '
       fSIGN = (A & 128) <> 0
       fZERO = (A = 0)
       fFIVE = (A & 32) <> 0
       fHALFCARRY = 0
       fTHREE = (A & 8) <> 0
       fPARITY = parity[A]
       fADDSUB = 0
       fCARRY = 0
       SetFLAGS()
  End Method


With the Out (254),A Instruction. Just for playing sound the first three bits are for the border. Which can be ignored unless you want some sort of visual effect  8)
The Tcycles are how long the instruction takes. Again how the timing relates to the Samples/rate within the Simulation is beyond me. That's why i started the thread to bring Musical life to my ZX Emulator.

I think the two pulsed loop is a way of simulating two channels at once. Because when you listen to the Tune it sounds kinda distorted or same note out of sync.

Also thinking of timming again.
The Check Routine wether ENTER is being pressed this is going to cause a timming delay.
I think as the CPU runs at 3.5Mhz. One TCycle 'time state' is callculated and then if an instruction takes, say 12,7, or 4 etc this can be taken into account as a pause in the loop.
But once worked out it becomes part of the loop permenantly. Maybe getting sound going. "All be it distorted or out of tune!" and then tweeking the loops for instruction timming might be necessary. Again these pause delays will then become permenant.

37647 CALL 37687 Check whether ENTER or the fire button is being pressed
37650 RET NZ         Return (with the zero flag reset) if it is


Thinking again. if the Z80 CPU runs at 3.5Mhz that's 3.5x10'6. This means that 1 Tcycle will take appronimatly 285 nSes that's 285x10'-9. So every instruction in the LOOPS will need this number setup as a CONSTANT and then multiplied by the individual TCycles of each instruction. Therefore this time can then be worked out on how this effects pitch/duration of the note at Samples/second.

For Example, RRCA has 4 Clock Cycles then 4x285x10'-9=1.14uSecs or 1.14x10'-6 in RealTime. So this needs to be taken into account when working out let's say the bandwith of the note to sound in tune. I try to steer away from the MATH but i think this is one's going to need it. Damm those Cobweb's

Hope that sort of makes's sence with the timming of the Z80 code but again how that effects the music it's self?  :-X

I do belive this is going to work.  :o Happy coding. ^-^

Kind Regards Baggey

Title: Re: Creating ZX SOUND
Post by: col on February 07, 2021, 10:06:56
To save bending your brain too much on the math there is a super handy z80 extension for vscode (if you use it) that will calculate the tstates for you... It can be found here:
https://github.com/theNestruo/z80-asm-meter-vscode (https://github.com/theNestruo/z80-asm-meter-vscode)
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 07, 2021, 10:34:14
Quote from: col on February 07, 2021, 10:06:56
To save bending your brain too much on the math there is a super handy z80 extension for vscode (if you use it) that will calculate the tstates for you...

I've recently started looking at vscode on Linux after finding out that there is a BlitzMax extension in development. I'll take a look at that one.
Title: Re: Creating ZX SOUND
Post by: iWasAdam on February 07, 2021, 12:34:44
Very impressed with how all of this is progressing. Excellent work and something to very proud of - emulating chips ;)
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 10, 2021, 07:07:26
Just in case anyone is following this and is interested in the TSTATE Cycle data:


TSTATES POINTER
19 37596 FN37596 LD A,(IY+0) PLAY MUSIC
7 37599 CP 255
11 37601 RET Z
4 37602 LD C,A
7 37603 LD B,0
4 37605 XOR A
19 37606 LD D,(IY+1)
4 37609 LD A,D
17 37610 CALL 37675
10 37613 LD (HL),80
19 37615 LD E,(IY+2)
4 37618 LD A,E
17 37619 CALL 37675
10 37622 LD (HL),40
11 37624 LOOP OUT (254),A
4 37626 DEC D
12 37627 JR NZ,37634
19 37629 LD D,(IY+1)
7 37632 XOR 24
4 37634 DEC E
12 37635 JR NZ,37642
19 37637 LD E,(IY+2)
7 37640 XOR 24
13 37642 DJNZ 37624
4 37644 DEC C
12 37645 JR NZ,37624
17 37647 CALL 37687
11 37650 RET NZ
19 37651 LD A,(IY+1)
17 37654 CALL 37675
10 37657 LD (HL),56
19 37659 LD A,(IY+2)
17 37662 CALL 37675
10 37665 LD (HL),56
10 37667 INC IY
10 37669 INC IY
10 37671 INC IY
12 37673 JR 37596
7 37675 FN37675 SUB 8 IDENTIFY KEY FROM NOTE
4 37677 RRCA
4 37678 RRCA
4 37679 RRCA
4 37680 CPL
7 37681 OR 224
4 37683 LD L,A
7 37684 LD H,89
10 37686 RET
13 37687 FN37687 LD A,(33881) KEYBOARD INPUT
7 37690 OR A
12 37691 JR Z,37698
13 37693 IN A,(31)
8 37695 BIT 4,A
11 37697 RET NZ
10 37698 LD BC,49150
12 37701 IN A,(C)
7 37703 AND 1
7 37705 CP 1
10 37707 RET
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 10, 2021, 08:04:14
I'm not sure if I have found a bug in the original code or what, but the music data doesn't seem to be correct after byte 210.

The total length of the music data without the endstop (255) is 285 bytes. The music data is in triplets but 284 is not equally divisable by 3...

This code should explain what I mean:

SuperStrict

' Music Data (Blue Danube from Manic Miner)
Global data:Byte[] = [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]
Print( "DATA LENGTH: "+Len(data) )

Local cursor:Int = 0
Local duration:Int
Local note:Byte[2]
Repeat
If data[cursor]=255 cursor=0 ' Reached the end? - Start again
' Read music data
duration = data[cursor]
note[0]  = data[cursor+1]
note[1]  = data[cursor+2]
Print( cursor+") "+duration+", "+note[0]+", "+note[1] )
cursor :+ 3
Until False


If you look at the output, you'll see <CURSOR>) <DURATION>, <NOTE-A>, <NOTE-B>. We have already established the first number is the note duration (see below); however if you look at the output after cursor 210 something strange is happening which will mess up the tune.

100 is a Minim (Half Note)
80 is a Dotted Crochet (1/8 + 1/4)
50 is a Crotchet (Quarter note)
25 is a Quaver (Eighth note)


204) 50, 38, 48
207) 50, 38, 48
210) 50, 0, 0
213) 114, 115, 50   <- This is Wrong
216) 114, 115, 50
219) 96, 97, 50
222) 76, 77, 50
225) 76, 153, 50
228) 76, 77, 50
Title: Re: Creating ZX SOUND
Post by: col on February 10, 2021, 08:27:49
I would say the data has been copied wrong. Take a look at the original music data and you can see the culprit at address 34115.

Music data:
https://skoolkit.ca/disassemblies/manic_miner/asm/33902.html (https://skoolkit.ca/disassemblies/manic_miner/asm/33902.html)
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 10, 2021, 12:36:12
Quote from: col on February 10, 2021, 08:27:49
I would say the data has been copied wrong. Take a look at the original music data and you can see the culprit at address 34115.

Music data:
https://skoolkit.ca/disassemblies/manic_miner/asm/33902.html (https://skoolkit.ca/disassemblies/manic_miner/asm/33902.html)

@col: Awesome thanks.

The correct Music array should be:


Global data:Byte[] = [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,50,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]


Si...
Title: Re: Creating ZX SOUND
Post by: Baggey on February 10, 2021, 13:59:19
Hi Guy's,

Ive just Wrote Two small routine's to spit out the Music Data, Directly from the memory of the emulator. So no typo errors!! It comes from the "Manic.z80" file.
I Remember you saying the Data seemed to be strange towards the end!

I do apologize for typing it in wrong! :-[ The code should now be spot on!   ;D

Firstly is the byte held at each memory address location:-

Building Spectrum emulator
Compiling:ZXio.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 11429 bytes.
Compiling:Z80.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 279826 bytes.
Compiling:video.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 5916 bytes.
Compiling:videoMemory.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 9359 bytes.
Compiling:OO.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 143179 bytes.
Compiling:CB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 107153 bytes.
Compiling:ED.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 39351 bytes.
Compiling:DD.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 46811 bytes.
Compiling:FD.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 55618 bytes.
Compiling:DDCB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 114656 bytes.
Compiling:FDCB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 159967 bytes.
Compiling:Spectrum emulator.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 61839 bytes.
Linking:Spectrum emulator.exe
Executing:Spectrum emulator.exe
start ADDRESS
PEEK(33902)=80
PEEK(33903)=128
PEEK(33904)=129
PEEK(33905)=80
PEEK(33906)=102
PEEK(33907)=103
PEEK(33908)=80
PEEK(33909)=86
PEEK(33910)=87
PEEK(33911)=50
PEEK(33912)=86
PEEK(33913)=87
PEEK(33914)=50
PEEK(33915)=171
PEEK(33916)=203
PEEK(33917)=50
PEEK(33918)=43
PEEK(33919)=51
PEEK(33920)=50
PEEK(33921)=43
PEEK(33922)=51
PEEK(33923)=50
PEEK(33924)=171
PEEK(33925)=203
PEEK(33926)=50
PEEK(33927)=51
PEEK(33928)=64
PEEK(33929)=50
PEEK(33930)=51
PEEK(33931)=64
PEEK(33932)=50
PEEK(33933)=171
PEEK(33934)=203
PEEK(33935)=50
PEEK(33936)=128
PEEK(33937)=129
PEEK(33938)=50
PEEK(33939)=128
PEEK(33940)=129
PEEK(33941)=50
PEEK(33942)=102
PEEK(33943)=103
PEEK(33944)=50
PEEK(33945)=86
PEEK(33946)=87
PEEK(33947)=50
PEEK(33948)=96
PEEK(33949)=86
PEEK(33950)=50
PEEK(33951)=171
PEEK(33952)=192
PEEK(33953)=50
PEEK(33954)=43
PEEK(33955)=48
PEEK(33956)=50
PEEK(33957)=43
PEEK(33958)=48
PEEK(33959)=50
PEEK(33960)=171
PEEK(33961)=192
PEEK(33962)=50
PEEK(33963)=48
PEEK(33964)=68
PEEK(33965)=50
PEEK(33966)=48
PEEK(33967)=68
PEEK(33968)=50
PEEK(33969)=171
PEEK(33970)=192
PEEK(33971)=50
PEEK(33972)=136
PEEK(33973)=137
PEEK(33974)=50
PEEK(33975)=136
PEEK(33976)=137
PEEK(33977)=50
PEEK(33978)=114
PEEK(33979)=115
PEEK(33980)=50
PEEK(33981)=76
PEEK(33982)=77
PEEK(33983)=50
PEEK(33984)=76
PEEK(33985)=77
PEEK(33986)=50
PEEK(33987)=171
PEEK(33988)=192
PEEK(33989)=50
PEEK(33990)=38
PEEK(33991)=48
PEEK(33992)=50
PEEK(33993)=38
PEEK(33994)=48
PEEK(33995)=50
PEEK(33996)=171
PEEK(33997)=192
PEEK(33998)=50
PEEK(33999)=48
PEEK(34000)=68
PEEK(34001)=50
PEEK(34002)=48
PEEK(34003)=68
PEEK(34004)=50
PEEK(34005)=171
PEEK(34006)=192
PEEK(34007)=50
PEEK(34008)=136
PEEK(34009)=137
PEEK(34010)=50
PEEK(34011)=136
PEEK(34012)=137
PEEK(34013)=50
PEEK(34014)=114
PEEK(34015)=115
PEEK(34016)=50
PEEK(34017)=76
PEEK(34018)=77
PEEK(34019)=50
PEEK(34020)=76
PEEK(34021)=77
PEEK(34022)=50
PEEK(34023)=171
PEEK(34024)=203
PEEK(34025)=50
PEEK(34026)=38
PEEK(34027)=51
PEEK(34028)=50
PEEK(34029)=38
PEEK(34030)=51
PEEK(34031)=50
PEEK(34032)=171
PEEK(34033)=203
PEEK(34034)=50
PEEK(34035)=51
PEEK(34036)=64
PEEK(34037)=50
PEEK(34038)=51
PEEK(34039)=64
PEEK(34040)=50
PEEK(34041)=171
PEEK(34042)=203
PEEK(34043)=50
PEEK(34044)=128
PEEK(34045)=129
PEEK(34046)=50
PEEK(34047)=128
PEEK(34048)=129
PEEK(34049)=50
PEEK(34050)=102
PEEK(34051)=103
PEEK(34052)=50
PEEK(34053)=86
PEEK(34054)=87
PEEK(34055)=50
PEEK(34056)=64
PEEK(34057)=65
PEEK(34058)=50
PEEK(34059)=128
PEEK(34060)=171
PEEK(34061)=50
PEEK(34062)=32
PEEK(34063)=43
PEEK(34064)=50
PEEK(34065)=32
PEEK(34066)=43
PEEK(34067)=50
PEEK(34068)=128
PEEK(34069)=171
PEEK(34070)=50
PEEK(34071)=43
PEEK(34072)=51
PEEK(34073)=50
PEEK(34074)=43
PEEK(34075)=51
PEEK(34076)=50
PEEK(34077)=128
PEEK(34078)=171
PEEK(34079)=50
PEEK(34080)=128
PEEK(34081)=129
PEEK(34082)=50
PEEK(34083)=128
PEEK(34084)=129
PEEK(34085)=50
PEEK(34086)=102
PEEK(34087)=103
PEEK(34088)=50
PEEK(34089)=86
PEEK(34090)=87
PEEK(34091)=50
PEEK(34092)=64
PEEK(34093)=65
PEEK(34094)=50
PEEK(34095)=128
PEEK(34096)=152
PEEK(34097)=50
PEEK(34098)=32
PEEK(34099)=38
PEEK(34100)=50
PEEK(34101)=32
PEEK(34102)=38
PEEK(34103)=50
PEEK(34104)=128
PEEK(34105)=152
PEEK(34106)=50
PEEK(34107)=38
PEEK(34108)=48
PEEK(34109)=50
PEEK(34110)=38
PEEK(34111)=48
PEEK(34112)=50
PEEK(34113)=0
PEEK(34114)=0
PEEK(34115)=50
PEEK(34116)=114
PEEK(34117)=115
PEEK(34118)=50
PEEK(34119)=114
PEEK(34120)=115
PEEK(34121)=50
PEEK(34122)=96
PEEK(34123)=97
PEEK(34124)=50
PEEK(34125)=76
PEEK(34126)=77
PEEK(34127)=50
PEEK(34128)=76
PEEK(34129)=153
PEEK(34130)=50
PEEK(34131)=76
PEEK(34132)=77
PEEK(34133)=50
PEEK(34134)=76
PEEK(34135)=77
PEEK(34136)=50
PEEK(34137)=76
PEEK(34138)=153
PEEK(34139)=50
PEEK(34140)=91
PEEK(34141)=92
PEEK(34142)=50
PEEK(34143)=86
PEEK(34144)=87
PEEK(34145)=50
PEEK(34146)=51
PEEK(34147)=205
PEEK(34148)=50
PEEK(34149)=51
PEEK(34150)=52
PEEK(34151)=50
PEEK(34152)=51
PEEK(34153)=52
PEEK(34154)=50
PEEK(34155)=51
PEEK(34156)=205
PEEK(34157)=50
PEEK(34158)=64
PEEK(34159)=65
PEEK(34160)=50
PEEK(34161)=102
PEEK(34162)=103
PEEK(34163)=100
PEEK(34164)=102
PEEK(34165)=103
PEEK(34166)=50
PEEK(34167)=114
PEEK(34168)=115
PEEK(34169)=100
PEEK(34170)=76
PEEK(34171)=77
PEEK(34172)=50
PEEK(34173)=86
PEEK(34174)=87
PEEK(34175)=50
PEEK(34176)=128
PEEK(34177)=203
PEEK(34178)=25
PEEK(34179)=128
PEEK(34180)=0
PEEK(34181)=25
PEEK(34182)=128
PEEK(34183)=129
PEEK(34184)=50
PEEK(34185)=128
PEEK(34186)=203
PEEK(34187)=255
end ADDRESS




Secondly A CUT and PASTE STRING  :)

Building Spectrum emulator
Compiling:ZXio.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 11429 bytes.
Compiling:Z80.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 279855 bytes.
Compiling:video.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 5916 bytes.
Compiling:videoMemory.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 9359 bytes.
Compiling:OO.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 143179 bytes.
Compiling:CB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 107153 bytes.
Compiling:ED.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 39351 bytes.
Compiling:DD.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 46811 bytes.
Compiling:FD.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 55618 bytes.
Compiling:DDCB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 114656 bytes.
Compiling:FDCB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 159967 bytes.
Compiling:Spectrum emulator.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 61839 bytes.
Linking:Spectrum emulator.exe
Executing:Spectrum emulator.exe
Start ADDRESS 33902
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,50,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
End ADDRESS 34187


Looking very forward to hearing this. Thought id post a Screen shot of the ZX emulator.

Kudos if you can make this work!

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on February 10, 2021, 16:40:56
Hi Si,

Tried running you're routine for checking data but got error's from the output window LOL. Not sure wether it's the version of Blitzmax im using thou?

So i changed it slightly to test the new Data and it tally's now with 285 Bytes. Except the Len(data)=286 Counting ZERO i presume?

Code (Blitzmax) Select
SuperStrict

' Music Data (Blue Danube from Manic Miner)
Global data:Int[] = [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,50,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]
Print( "DATA LENGTH: "+Len(data) )
Local lenOfdata:Int=Len(data)
Print "Length of Data="+LenOfdata
Print
Print "Press Enter" ; Input

Global cursor:Int = 0
Global duration:Int
Global note:Byte[2]
Global RepeatLoop:Byte=True

ReadMusic()

Print
Print "is the Data Length some how counting 0? ie (0 to 285) = 286"
Print
Print "Address 34187 - Address 33902 = "+(34187-33902)
Print "cursor on next run = "+cursor+"  Byte will be "+data[cursor]

Function ReadMusic()
While RepeatLoop
If data[cursor]=255 Then
Print cursor+") "+data[cursor] ; cursor=0 ; Exit ' Reached the end?
Else
' Read music data
duration = data[cursor]
note[0]  = data[cursor+1]
note[1]  = data[cursor+2]
Print cursor+") "+duration+", "+note[0]+", "+note[1]
cursor :+ 3
End If
Wend
End Function

Rem
19 37596 FN37596 LD A,(IY+0) PLAY MUSIC ' Let A=PEEK(IY+0)
7 37599         CP 255                 ' If (A-255)=0 Then fZERO=TRUE
11 37601         RET Z                  ' If fZERO=TRUE Then Return/Exit/End/Gosub
end rem


Some of the Updating of key press routines may need to be taken into account for the timming of the note playing. If i can help in anyway just let me know.  ;D

Again Hope that help's.

Kind Regards Baggey

Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 10, 2021, 17:17:40
Cool, we are getting there.

Because the frequency is not actually passed to the speaker; I have been playing around with how we get the timings for the audio and come up with a way that counts the duration between calls to OUT(254,A) and then generates an audio sample to match the frequency of the calls 

The idea is that your Emulator will be running at the same speed as the ZX Spectrum 48K (3.500MHz), so the "pulses" to the Speaker should match the frequency we need to make the correct sound. The note values in the data appear to have been calculated relative to the Spectrum CPU speed and the TStates of the music code loop which is why the values do not match the desired frequency.

My first run-through made a series of strange crackles, so I have a bit of work to do, but will post when I have ironed out a few bugs.




Title: Re: Creating ZX SOUND
Post by: Baggey on February 10, 2021, 17:35:24
Quote from: Scaremonger on February 10, 2021, 17:17:40
Cool, we are getting there.

Because the frequency is not actually passed to the speaker; I have been playing around with how we get the timings for the audio and come up with a way that counts the duration between calls to OUT(254,A) and then generates an audio sample to match the frequency of the calls 

The idea is that your Emulator will be running at the same speed as the ZX Spectrum 48K (3.500MHz), so the "pulses" to the Speaker should match the frequency we need to make the correct sound. The note values in the data appear to have been calculated relative to the Spectrum CPU speed and the TStates of the music code loop which is why the values do not match the desired frequency.

My first run-through made a series of strange crackles, so I have a bit of work to do, but will post when I have ironed out a few bugs.

Cool. Since this started, ive been brushing up on my Maths. Been Looking into Ampliude/Pressure, Sin functions, Propogation down the line etc. Music theory is more complicated than i imagend. Ive since realised a Duration of sound can have many samples of the wave it's self sort of added together. Even been looking at Audacity to look at The waves in realtime to get a better understanding of what's happening. notes played simultaneously adding together. Which start to go in to Trignometric Functions 1st, 2nd and 3rd Harmonics :o

Thought id stop there.

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: iWasAdam on February 10, 2021, 19:53:28
Have a look at QasarBeach
https://adamstrange.itch.io/qasarbeach (https://adamstrange.itch.io/qasarbeach)

It will allow you to visualize any kind of waveform - allow you to draw waveforms, etc and view/hear the results in realtime.

You can also create sounds, load sounds and generally bugger them up to your hearts content.

Sound theory is simple, but doing it in realtime over multiple voices is more of a challenge, but I'll help where you need it ;)
Title: Re: Creating ZX SOUND
Post by: Baggey on February 11, 2021, 14:18:42
Quote from: iWasAdam on February 10, 2021, 19:53:28
Have a look at QasarBeach
https://adamstrange.itch.io/qasarbeach (https://adamstrange.itch.io/qasarbeach)

It will allow you to visualize any kind of waveform - allow you to draw waveforms, etc and view/hear the results in realtime.

You can also create sounds, load sounds and generally bugger them up to your hearts content.

Sound theory is simple, but doing it in realtime over multiple voices is more of a challenge, but I'll help where you need it ;)

Hi Adam,
Off Topic slightly! Tried Quasr Beach theres alot to go through. Love the retro feel. I need more time to read through and work out how to play something.  :-[

But Ohh my god! Ive just come across Viva Mortis. Absolutley Superb.  8)

I love Retro Stuff Almost reminds me of TikiTaka. By the way whats MX2. Ahh Just realised Monkey2.

Kind Regards Baggey

Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 11, 2021, 17:19:13
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

Using your idea above, I have put together this little bit of code to create a small Square Wave and allow you to turn it on/off (SPACE) and change the pitch (UP/DOWN).

buzzer-1.bmx

SuperStrict

' Simple Square Wave Player

' Create a Buzzer
AppTitle = "BUZZER.1"
Global buzzer:TChannel = CreateBuzzer()

' Create one "wave" of the supplied frequency
Function CreateBuzzer:TChannel()
Const amplitude:Float= 1.0 ' Volume 0.0 to 1.0
Const frequency:Int  = 261.63 'Hz = C4 = Middle-C
Const samplerate:Int = 44000
Local sample:TAudioSample = CreateAudioSample( frequency, 44000, SF_MONO8 )
Print( "Sample length "+sample.length )
Local half:Float = frequency/2.0
For Local n:Int = 0 Until sample.length
Local level:Float = n Mod frequency
If level<half
sample.samples[n] =  amplitude
Else
sample.samples[n] = -amplitude
EndIf
Next
' Cue a buzz sound ready to be played
Return CueSound( LoadSound( sample, True ) )
End Function

Local state:Int = False, rate:Float = 1.0
Local text:String

Graphics 200,50
Repeat
Cls
text = "SPACE = ON/OFF"
DrawText( text,(GraphicsWidth()-TextWidth(text))/2,(GraphicsHeight()-TextHeight(text))/3 )
text = "UP/DOWN = PITCH"
DrawText( text,(GraphicsWidth()-TextWidth(text))/2,(GraphicsHeight()-TextHeight(text))/3*2 )
If KeyHit( KEY_SPACE )
state = Not state
If state
buzzer.setpaused( False )
Else
buzzer.setpaused( True )
EndIf
EndIf
If KeyHit( KEY_UP )
rate :+ 0.1
buzzer.setrate( rate )
EndIf
If KeyHit( KEY_DOWN)
rate :- 0.1
buzzer.setrate( rate )
EndIf
Flip
Until KeyHit( KEY_ESCAPE )


I have no idea what sample rate to use for the spectrum but I decided to start with Middle C (C4, 261.63 Hz) for the frequency.

I guess the sample rate should probably be relative to the speed of the Spectrum CPU (3500000 Hz) but until I figure out what to use, I stuck 44000 in there.

Next step is to use the Wave and an oscillating function (similar to how the Manic Miner turns the speaker on and off repeatedly) to see if I can reproduce different notes.
Title: Re: Creating ZX SOUND
Post by: Baggey on February 11, 2021, 19:05:51
Hi Si,

Ive been messing with this. I couldn't get the Function to work however! I see you've passed it as a Tchannel. Shall have a play later with that idea. Functions and pointers arn't my strong point :))


' CreateSquareWave

'SuperStrict

Graphics 640,480

' Higher the Duration the BASSier Tone is
Global Duration:Byte=128 ' Or total samples per One Cycle

' Max Frequency for Ear so dosent need to be higher!?
' However the Lower it is the duller or Bassier sound we get.
Const SamplesPerSecond:Float=22000
'Global Hertz:Float=HumanEarMAX

Global AudioSample:TAudioSample=CreateAudioSample( duration, SamplesPerSecond, SF_MONO8 )

Function OUT_Piezo_(Duration:Byte, Hertz:Short)

AudioSample:TAudioSample=CreateAudioSample( Length, SamplesPerSecond, SF_MONO8 )

For Local K:Float=0 Until Duration
' ZX Spectrum could only play a Square Wave
' So a Work around For a very crude Square Wave!
If K<(Duration/2) Then AudioSample.samples[K] = 1 Else AudioSample.samples[K] = 0
Next

End Function

Global audio:TSound=LoadSound( AudioSample, True )
channel=CueSound(audio)

While Not (AppTerminate() Or KeyDown(key_space))


If MouseHit(1) PlaySound audio,channel


Local Pan# = MouseX () / (GraphicsWidth () / 2.0)
Local Vol# = 1 - (MouseY () *.0023)
Local Rate#=Pan#

' Not needed for Specy, As it was always MONO! But Pan#=0 for CENTER
'Pan#=0 ;

SetChannelPan channel,0

' Turning Piezo ON/OFF  Vol=0 for OFF and Vol=1 for ON
SetChannelVolume channel,Vol#

SetChannelRate channel,rate#

'StopChannel channel
'Audiosample.length=Duration
'Audiosample.hertz=hertz
'Hertz=Rnd (0.0, 44000)
'OUT_Piezo_(Duration,Hertz)


Cls
DrawText "Left Click to Play", 240, 200
DrawText "Pan   : " + pan, 240, 220
DrawText "Volume: " + vol, 240, 240

Flip

Wend


If you wobble the mouse around and up/down it sounds like galaxians and pleiads even Pacman dying. LOL  :))

I think im out of my depth here! But hey it's Great Fun  :)

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on February 11, 2021, 19:43:08
There are a lot of bugs in your code....
All  values are INTEGER!!!

To create a Square-Signal the both values are 0 and 255!!!
128 is the Zero-Signal
255 is full positiv +127
0 is full negativ   -128

The Hertz is the Frequency to alter from 0 to 255
Code (BlitzMax) Select

Global Duration:INT=1000
Const SamplesPerSecond:INT=11025

Global AudioSample:TAudioSample=CreateAudioSample( duration, SamplesPerSecond, SF_MONO8 )
OUT_Piezo_(Duration,80)

Function OUT_Piezo_(Duration%, Hertz%)
Local w%
For Local K%=0 Until Duration-1 
w=w+1
If (w Mod Hertz)< Hertz/2
AudioSample.samples[K] = 255
Else
AudioSample.samples[K] = 0
EndIf
Next
End Function

Global audio:TSound=LoadSound( AudioSample)
While Not (AppTerminate() Or KeyDown(key_space))


If MouseHit(1)
PlaySound audio
EndIf


this code is only for first tests and pointing you in a new direction....
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 11, 2021, 22:02:49
The ZX Spectrum code sends ON / OFF pulses to the Buzzer, but not the frequency or duration.

I have timed and counted calls to OUT(254,A) and plan to use this information to create the square wave; however I was getting values less than a millisecond and BlitzMax doesn't have a more accurate function, so I've written one in C.

microsecs.c

#include "time.h"
#include <sys/time.h>

long MicroSecs(void) {
struct timeval tv;
gettimeofday( &tv, NULL );
return (( (long)tv.tv_sec )*1000000 )+( tv.tv_usec );
}


To use it, simply add this to the top of your Blitzmax code:


Import "microsecs.c"
Extern
Function MicroSecs:Long()
EndExtern


I may add another version that returns a double at a later time, but this will do for now.

So far, my OUT() emulator looks like this:


Function _OUT( port:Byte, value:Byte)
Global savestate:Int=False
Global savetime:Long, counter:Int

If port=254
Local speaker:Int = (value&$10)
counter :+ 1
If speaker = savestate Return ' State has not changed
Local now:Long = MicroSecs()
If speaker ' Speaker turned ON, play last wave
Local duration:Long = now-savetime
duration = Min(duration,500) ' Allow for clock creep
'Print( "- duration: "+duration+" us" )
Print( "CREATE SAMPLE "+counter+", "+ duration+"us")
savetime = now
counter = 0
End If
savestate = speaker
End If
End Function


I've not thoroughly tested it yet, but I'll have another play when I get time.

Si...
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 11, 2021, 22:20:37
Quote from: Midimaster on February 11, 2021, 19:43:08
All  values are INTEGER!!!
To create a Square-Signal the both values are 0 and 255!!!
128 is the Zero-Signal
255 is full positiv +127
0 is full negativ   -128

I was using amplitude of -1.0 to +1.0 in my code and it was probably the cause of the weird clicks that I got as a result. Thanks for clearing that one up @midimaster.

Quote from: Midimaster on February 11, 2021, 19:43:08
The Hertz is the Frequency to alter from 0 to 255

Interesting... So a middle C at 261.63 Hz would need to be converted in some way?
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 11, 2021, 22:56:13
This is my conversion so far...

It makes a sound and there seems to be something deep in the noise, but its still got a serious problem in there somewhere.

I would appreciate if any of you guys can spot anything.

(You will need "millisecs.c" that I posted in an earlier message to run this).


SuperStrict
' Manic Miner Audio Output

Import "microsecs.c"
Extern
Function MicroSecs:Long()
EndExtern

' ZX Spectrum 16K/48K = 3.500 Mhz
' ZX Spectrum 128K    = 3.547 Mhz
Const CLOCKSPEED:Float = 3.500 'Mhz
Const __CPUCYCLE__:Float = (1.0/(CLOCKSPEED)) 'in Microseconds
Print( "1 Cycle   = "+(__CPUCYCLE__)+" microseconds")
Print( "108 Cycles= "+(__CPUCYCLE__*108)+" microseconds")

' 16 Bit Regsiters
Global BC16:Short
Global DE16:Short
Global HL16:Short
Global BC:Byte Ptr = Varptr BC16
Global DE:Byte Ptr = Varptr DE16
Global HL:Byte Ptr = Varptr HL16
Const B:Byte=1, C:Byte=0, D:Byte=1, E:Byte=0, H:Byte=1, L:Byte=0

' 8 Bit Registers
Global A:Byte
Global IY:Byte, IX:Byte

' FLAGS
Global F:Byte[8]
Const CF:Byte=0
Const ZR:Byte=1

' Music Data
Global data:Byte[] = [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,50,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]

' Direct Code Conversion
Function CALL_37596:Int()
Local __TSTATES__:Int

While True
A = data[IY] '37596 LD A,(IY+0) Pick up the next byte of tune data from the table at 33902
If A=255 '37599 CP 255 Has the tune finished?
Return True '37601 RET Z Return (with the zero flag set) if so
End If
BC[C] = A '37602 LD C,A Copy the first byte of data for this note (which determines the duration) to C
BC[B] = 0 '37603 LD B,0 Initialise B, which will be used as a delay counter in the note-producing loop
A = 0 '37605 XOR A Set A=0 (for no apparent reasaon)
DE[D] = data[IY+1] '37606 LD D,(IY+1) Pick up the second byte of data for this note
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = DE[D] '37609 LD A,D Copy it to A
'CALL_37675() '37610 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,80) '37613 LD (HL),80 Set the attribute byte for the piano key to 80 (INK 0: PAPER 2: BRIGHT 1)
DE[E] = data[IY+2] '37615 LD E,(IY+2) Pick up the third byte of data for this note
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = DE[E] '37618 LD A,E Copy it to A
'CALL_37675() '37619 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,40) '37622 LD (HL),40 Set the attribute byte for the piano key to 40 (INK 0: PAPER 5: BRIGHT 0)
'Print( "A ---"+Right(Hex(A),2)+"  BC-"+Right(Hex(BC16),4)+"  DE-"+Right(Hex(DE16),4)+"  HL-"+Right(Hex(HL16),4) )
Repeat ' until C=0 (at 37645)
Repeat ' until B=0 (at 37642)
_OUT(254,A) '37624 OUT (254),A Produce a sound based on the frequency parameters in the second and third bytes of data for this note (copied into D and E)
__TSTATES__ = 0
DE[D] :- 1 '37626 DEC D
If DE[D]=0 '37627 JR NZ,37634
DE[D] = data[IY+1] '37629 LD D,(IY+1)
A = A~24 '37632 XOR 24
__TSTATES__ :+ 26
End If
DE[E] :- 1 '37634 DEC E
If DE[E]=0 '37635 JR NZ,37642
DE[E] = data[IY+2] '37637 LD E,(IY+2)
A = A~24 '37640 XOR 24
__TSTATES__ :+ 26
End If
BC[B] :- 1 '37642 DJNZ 37624

' SLOW DOWN BLITZMAX SO CODE RUNS AT SAME SPEED AS ZX SPECTRUM
__TSTATES__ :+ 56
'Print(microsecs())
Local wait:Long = MicroSecs()+Long(__TSTATES__*__CPUCYCLE__)
'Print("wait "+wait)
'Print("tstates "+__TSTATES__)
While wait>MicroSecs() ; Wend

'Print( "A ---"+Right(Hex(A),2)+"  BC-"+Right(Hex(BC16),4)+"  DE-"+Right(Hex(DE16),4)+"  HL-"+Right(Hex(HL16),4) )
Until BC[B]=0 ' ""    ""
BC[C] :- 1 '37644 DEC C



Until C=0 '37645 JR NZ,37624
'##### KEYBOARD INTERACTION
'If CALL_37687() '37647 CALL 37687 Check whether ENTER or the fire button is being pressed
' Return False '37650 RET NZ Return (with the zero flag reset) if it is
'End If
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = data[IY+1] '37651 LD A,(IY+1) Pick up the second byte of data for this note
'CALL_37675() '37654 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,56) '37657 LD (HL),56 Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = data[IY+2] '37659 LD A,(IY+2) Pick up the third byte of data for this note
'CALL_37675() '37662 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,56) '37665 LD (HL),56 Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)
IY :+ 3 '37667 INC IY Move IY along to the data for the next note in the tune
'37669 INC IY
'37671 INC IY
Wend '37673 JR 37596 Jump back to play the next note
End Function

' Direct Code Conversion
' Calculates which on-screen piano key will be shown pressed
' We dont need this, so it has been commented out
'Function CALL_37675()
' A :- 8 '37675 SUB 8 Compute the piano key index (K) based on the frequency parameter (F), and store it in bits 0-4 of A: K=31-INT((F-8)/8)
' _RRCA() '37677 RRCA
' _RRCA() '37678 RRCA
' _RRCA() '37679 RRCA
' A  = ~A '37680 CPL
' A :| 224 '37681 OR 224 A=224+K; this is the LSB
' HL[L] = A '37683 LD L,A Set HL to the attribute file address for the piano key
' HL[H] = 89 '37684 LD H,89
' Return '37686 RET
'End Function

' Keyboard input
' Not required, so has been commented out
'Function CALL_37687:Int()
' Return False
'End Function

' Z80 Mnumonic Rotate Bits Right with Carry
'Function _RRCA()
' Local x:Byte = A & $01
' A = ( A Shr 1 | ( A & $01 ) Shl 7 )
' F[CF] = x
'End Function

' Z80 Mnumonic


' bitmap of value is xxxSMBBB
' S=Sound M=Microphone B=Border

Function _OUT( port:Byte, value:Byte)
Global savestate:Int=False
Global savetime:Long, counter:Int

If port=254
Local speaker:Int = (value&$10)
counter :+ 1
If speaker = savestate Return ' State has not changed
Local now:Long = MicroSecs()
If speaker ' Speaker turned ON, play last wave
Local duration:Int = now-savetime
duration = Min(duration,500)*10
'Print( "- duration: "+duration+" us" )
Print( "CREATE SAMPLE "+counter+" Hz, "+ duration)
Buzz( duration, counter )
savetime = now
counter = 0
End If
savestate = speaker
End If
End Function

' Create one "wave" of the supplied frequency
Function Buzz:TChannel( duration:Int, frequency:Int )
'Const amplitude:Float= 1.0 ' Volume 0.0 to 1.0
'Const frequency:Int  = 261.63 'Hz = C4 = Middle-C
Const SAMPLERATE:Int = 11025

Local sample:TAudioSample = CreateAudioSample( duration, SAMPLERATE, SF_MONO8 )
Local half:Int = frequency/2
Local w%
For Local n:Int=0 Until sample.length -1
w=w+1         
If (w Mod frequency)< half
sample.samples[n] = 255
Else
sample.samples[n] = 0
EndIf
Next
Local audio:TSound = LoadSound( sample )
PlaySound( audio )

End Function

' Play Music
Print( "Starting" )
IY = 0 ' Set pointer to start of music
CALL_37596()
Title: Re: Creating ZX SOUND
Post by: Midimaster on February 12, 2021, 02:16:32
If you feed the function with real frequencies you need to inverse them to be able to manipulate pulse lengths.

Dont know the correct factor, but this shows the way (yellow line):


....
Function Buzz:TChannel( duration:Int, frequency:Int )
   Const SAMPLERATE:Int = 11025
        frequency=11025/frequency
   Local sample:TAudioSample = CreateAudioSample( duration, SAMPLERATE, SF_MONO8 )
   Local half:Int = frequency/2
   For Local n:Int=0 Until sample.length -1   
               If (n Mod frequency)< half
                    sample.samples[n] = 255
               Else
                     sample.samples[n] =0
               Endif
   Next
   ....
Title: Re: Creating ZX SOUND
Post by: Baggey on February 12, 2021, 14:37:37
Quote from: Scaremonger on February 11, 2021, 22:02:49

To use it, simply add this to the top of your Blitzmax code:

microsecs.c

#include "time.h"
#include <sys/time.h>

long MicroSecs(void) {
struct timeval tv;
gettimeofday( &tv, NULL );
return (( (long)tv.tv_sec )*1000000 )+( tv.tv_usec );
}




Import "microsecs.c"
Extern
Function MicroSecs:Long()
EndExtern


Hi Si, Not interfaced with C before. Ive worked out you use the C snipet of your code and save as microsecs.c file. Ive saved it the local directory and in BlitzMax tmp folder.
Ive then used the 2nd piece of code to import "microsecs.C". But when i compile i get the following.

Building Import microsecs
Compiling:microsecs.c
Build Error: failed to compile C:/Spectrum7/Sound Stuff/microsecs.c
Process complete


I think i need to do something else but i dunno what.  :-[

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on February 12, 2021, 15:04:37
Just wondering if anyone else is following this and is interested in Emulation or Creating Sound. He or She may be interested in some of the math.

The Z80 runs at 3.5Mhz and to emulate it and produce sound on the fly so to speak. Timming is crucial.

So ive added a little info program so others can see how to interpret, how to calculate the timming of each Instruction according to it's Tstates.
sometimes, something that seems simple in principle can get bogged down in the math. Just Trying to create a sound on the fly or even white noise in BlitzMax dosent seem to be that easy?

Thanks to everyone who's having ago.

All ideas welcome  ;D


Const ClockSpeedDOUBLE:Double = 3.5*10^6 ' ie, 3500000  or ( 3.5 EXP 6 Calculator )
Const ClockSpeedFLOAT:Float   = 3.5*10^6 ' ie, 3500000  or ( 3.5 EXP 6 Calculator )
Const ClockSpeed=3500000
Const Z80_CPU_Cycle:Float=0.000000285 ' Cycles per second. Most instructions take a minimum of 4 TStates
Global OpCode_Time:Float

Print
Print "ClockSpeed as a DOUBLE="+ClockSpeedDOUBLE
Print
Print " Or"
Print
Print "ClockSpeed as a  FLOAT="+ClockSpeedFLOAT
Print
Print
Print "BLITZ MAX Dosen't make the numbers nice!"
Print
Print " Clock Speed is = "+ClockSpeed+" Hz ... Maybe more familiar as 3,500,000 Hz or 3.5 Mhz"
Print
Print " Which is 3.5*10^6 or ( 3.5 EXP 6 Calculator )"
Print
Print
Print " So, One Z80 CPU takes 1/3.5*10^6 or ( 1/3.5 EXP 6 Calulator )"
Print "ie,"
Print " One CPU cycle = 0.000000285 Secs ... 0.285 uSecs or micro Seconds ... 0.285*10^(-6) or ( 0.285 EXP -6 Calulator )"
Print "                                     Or 285 nSecs or nano Seconds  ... 285*10^(-9)   or (  285 EXP -6 Calculator )"
Print
Print "So, Blitzmax notation "
Print
Const __CPUCYCLE__:Float = (1/ClockSpeedFloat)
Print " 1 CPU cycle   = "+(__CPUCYCLE__)+"  Which as i say isn't nice!"
Print
Print "or, Z80_CPU_Cycle = 0.000000285"
Print
Print "Therefore,"
Print
Print "108 Cycles= "+(__CPUCYCLE__*108)
Print
Print "Which is 0.000030857 Secs ... or 30.857uSecs micro Seconds ... 30.857*10^(-6) or ( 30.857 EXP -6 Calculator )"
Print
Print "Consider a NOP Instruction 4 Tstates"
Print
Print "4 Cycles= "+(__CPUCYCLE__*4)+" microseconds"
Print
Print "Which is 0.000001142 Secs ... or 1.142uSecs micro Seconds ... 1.142*10^(-6) or ( 1.142 EXP -6 Calculator )"
Print
Print "Anyone wishing to work out how many Seconds a TCycle takes, or instruction can take."
Print "We can use the following Formulae"
Print
Print " Tstates of Instruction * 1 / 3.5*10^6 = Time Taken in Seconds"
Print
Print "ie,  Our NOP Instruction has   4 * 1 / 3.5*10^6 = Time Taken in Seconds   or   BlitzMax Time Taken = 4*(1/(3.5*10^6))"
Print
Print "Time for NOP OpCode = "+4*(1/(3.5*10^6))+" ... Which is 1.142*10^6 Seconds"
Print
Print "Incidently the Brackets are there forcing a Mathamatical rule called BIDMAS. To make BlitzMax Enforce this RULE"
Print "the Brackets are there. Meaning anything representing the letter is done first! or within the Brackets"
Print "Otherwise are Sum is going to be wrong!"
Print
Print " B = Brackets"
Print " I = Indicies"
Print " D = Division"
Print " M = Multiplication"
Print " A = Addition"
Print " S = Subtraction"
Print "                                Time Taken to execute = Tstates for Instruction *  (1/(3.5*10^6))"


Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 12, 2021, 16:17:19
Quote from: Baggey on February 12, 2021, 15:04:37

Print "   1 CPU cycle   = "+(__CPUCYCLE__)+"  Which as i say isn't nice!"


Ha ha... Yes, that notation is something I use when I want something to stand out and I wouldn't usually use it. I used it in the Manic Miner code because I wanted to separate the Z80 code from Blitzmax and Control code.
Title: Re: Creating ZX SOUND
Post by: Steve Elliott on February 12, 2021, 16:44:39
Quote
Just wondering if anyone else is following this and is interested in Emulation or Creating Sound.

I was creating some sound this morning in BASIC (my ZX Spectrum arrived from Ebay).   ;D
Title: Re: Creating ZX SOUND
Post by: Baggey on February 12, 2021, 17:18:19
Quote from: Scaremonger on February 12, 2021, 16:17:19
Quote from: Baggey on February 12, 2021, 15:04:37

Print "   1 CPU cycle   = "+(__CPUCYCLE__)+"  Which as i say isn't nice!"


Ha ha... Yes, that notation is something I use when I want something to stand out and I wouldn't usually use it. I used it in the Manic Miner code because I wanted to separate the Z80 code from Blitzmax and Control code.

Hey Si, I was not! By any means on about your Code/Notation!  :-[  I was on about the notaion BlitzMax gives which from an engineering point of view is not easy to read! 

ie, THIS "3.08571434e-005" which isnt being taken to the nearset milli, micro, nano or pico! ;D

Kindest Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on February 12, 2021, 17:24:40
Quote from: Steve Elliott on February 12, 2021, 16:44:39
Quote
Just wondering if anyone else is following this and is interested in Emulation or Creating Sound.

I was creating some sound this morning in BASIC (my ZX Spectrum arrived from Ebay).   ;D

Hope you have some fun! Your going to need to Re-Cap it thou. If you run it for a long time! it's over 30 years old now! Come to think of it so am I   8) :))

Did you get a Rubber key or a +?

Baggey
Title: Re: Creating ZX SOUND
Post by: Steve Elliott on February 12, 2021, 17:54:05
Quote
Hope you have some fun! Your going to need to Re-Cap it thou. If you run it for a long time! it's over 30 years old now! Come to think of it so am I   8) :))

Did you get a Rubber key or a +?

Will do, I've begun learning Z80 Assembly using an Emulator (my childhood Speccy no longer works).  No need, this new/old Spectrum has already been refurbished and came with a modern power supply.  Re-capped, new keyboard membrane, composite output rather than RF (or scart with the supplied converter) and no longer runs hot because of a modern regulator.  It actually looks almost brand new.

This is a 48K rubber key Spectrum (which was my very first computer).  But I also have a ZX Spectrum Next arriving around August.  It's 100% compatable with hardware and software of the original because it's not an emulator, but hardware re-created using a FPGA.  It has 3 sound chips, 256 colour graphics, turbo modes (it can run at 3.5Mhz, 7, 14 or 28Mhz) hardware sprites, hardware scrolling, Copper Chip, 2Mb RAM, SD Card Storage, 2 built-in Joystick Ports etc.  When using all of the Next features it can behave like a ZX Spectrum crossed with an Amiga.  Or you can plug in a tape recorder, load your old games from tape and run them at 3.5Mhz.

https://www.specnext.com/

[Edit]
Looking at your sig you already have a Next lol?  Cool project btw, sorry I can't help you with it.
Title: Re: Creating ZX SOUND
Post by: Baggey on February 12, 2021, 18:05:49
So, Thought i'd reflect a bit and step back.

Taken a look at one full sinewave of the Output Piezo which is this!

(//)

Ive Analysed "Had to check my spelling i had Analize first"  ???  Anyway's the Piezo output down to what i think in the Music world is One sample. Please correct me if im wrong. There is a pattern all notes pip's on to off, of the Piezo are taking Roughly 3 milli Secs. For one cycle of the wave to happen. Analysing the very first note/sound from the in game music.

Interestingly it's not a TRUE square wave either it's a Saw tooth!? So thats time to go full on and time to decay off.
Im looking at the waveform of the title screen music and that has 3 milli Secs as well.
Title: Re: Creating ZX SOUND
Post by: Baggey on February 12, 2021, 18:20:50
Quote from: Steve Elliott on February 12, 2021, 17:54:05
Quote
Will do, I've begun learning Z80 Assembly using an Emulator


My ultimate Goal for my Emulator or SpecBlitz is to have a code window where you type your program in a language that is a cross between Basic and Machine code. So it's Understandable and fast! If i run it at full screen and no time delay you can't play the games  :))

It's Aimed at us lot wanting to relive are child hood writing games in BASIC running at Machine code speed's.

If you ever typed a game in, in Basic and ran it, it was slow! SpecBlitz is running at probably this speed difference. With one press of the button and your in Full screen MODE and you can not tell your on a Pc anymore. Apart from the fact your sat in front of one. But there is the recreated usb Keyboard?

Sorry to deviated, i know this is suppoused to be about creating ZX Sound.

Going back to learning Z80 Machine code. Ive used these Book's the most :-
Bernard Babani BP 152 "An Introduction to Z80 Machine Code"
Programming the Z80 Sybex RODNAY ZAKS
The original Sinclair basic Programming book

Also, This site many times "https://clrhome.org/table/"

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on February 12, 2021, 18:21:15
Quote from: Baggey on February 12, 2021, 18:05:49
So, Thought i'd reflect a bit and step back.

Taken a look at one full sinewave of the Output Piezo which is this!
Interestingly it's not a TRUE square wave either it's a Saw tooth!? So thats time to go full on and time to decay off.
Im looking at the waveform of the title screen music and that has 3 milli Secs as well.

This it not a square and not a sawtooth! This is a typical resulting of two combined signals. I would guess the second frequency is double of the first. You need a FFT Analyse to find out the two frequencies


A signal like yours does not show or prove any "attack" or "decay". This are ADSR-Envelopes and only visible over a long period of f.e. 500msec.
Title: Re: Creating ZX SOUND
Post by: Baggey on February 12, 2021, 18:26:43
Quote from: Midimaster on February 12, 2021, 18:21:15
Quote from: Baggey on February 12, 2021, 18:05:49
So, Thought i'd reflect a bit and step back.

Taken a look at one full sinewave of the Output Piezo which is this!
Interestingly it's not a TRUE square wave either it's a Saw tooth!? So thats time to go full on and time to decay off.
Im looking at the waveform of the title screen music and that has 3 milli Secs as well.

This it not a square and not a sawtooth! This is a typical resulting of two sawtoth signals result. I wouzld guess the second frequency is double of the first.

If you do not want to install a professional audio analyzing you can use my "Hoeren&Sehen"-Maschine, which is a audio analyser for children: http://www.midimaster.de/download/hoerensehen_demo.exe. Here you can combine to waves and watch the resulting signal:
(https://www.syntaxbomb.com/proxy.php?request=http%3A%2F%2Fwww.midimaster.de%2Fimages%2FHoerenKlein.png&hash=d7ce02036fd8bbb789ad108cb87cc42ba62bf79a)

A signal like yours does not show or prove any "attack" or "decay". This are ADSR-Envelopes and only visible over a long period of f.e. 500msec.

Thankyou for the advise. With respect Id love to install Librarys and .Dlls even link to .c files etc. But unless someone shows me step by step im stuck with Blitzmax 1.5 Working out of the box so to Speak. Im only a hobby Programmer. Im Literally flying by the seat of my Pants.  :-[ :o

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 15, 2021, 18:30:57

I have fixed a few bugs and sorted out some timing issues. The test code below now plays notes instead of noise, but they are not quite what I expected and certainly not the Blue Danube.

I think the issue now lies in the frequency calculation (@midimaster suggested the pulses needed converting ), but I havn't had time to take a look at it just yet.

This is my test code so far if anyone wants to take a peek at the Buzz() code :)

SuperStrict
' Manic Miner Audio Output

Import "microsecs.c"
Extern
Function MicroSecs:Long()
EndExtern

' ZX Spectrum 16K/48K = 3.500 Mhz
' ZX Spectrum 128K    = 3.547 Mhz
'
Const CLOCKSPEED:Float = 3.500 'Mhz
Const __CPUCYCLE__:Float = CLOCKSPEED/1000000 'in Microseconds

'                   3.500 Mhz = 3500000 CYCLES/SECOND
'     3,500,000 CYCLES/SECOND = 3,500,000 CYCLES/1,000ms (or 1,000,000us)
'3,500,000 CYCLES/1,000,000us = 35 CYCLES PER 10 us
'              35 CYCLES/10us = 1 CYCLE / 3.5 us
'                  108 CYCLES = 378 us

Print( "1 Cycle   = "+(__CPUCYCLE__)+" microseconds")
Print( "108 Cycles= "+(__CPUCYCLE__*108)+" microseconds")

' 16 Bit Regsiters
Global BC16:Short
Global DE16:Short
Global HL16:Short
Global BC:Byte Ptr = Varptr BC16
Global DE:Byte Ptr = Varptr DE16
Global HL:Byte Ptr = Varptr HL16
Const B:Byte=1, C:Byte=0, D:Byte=1, E:Byte=0, H:Byte=1, L:Byte=0

' 8 Bit Registers
Global A:Byte
Global IY:Byte, IX:Byte

' FLAGS
Global F:Byte[8]
Const CF:Byte=0
Const ZR:Byte=1

' Music Data
Global data:Byte[] = [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,50,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]
Print( "DATA LENGTH: "+Len(data) )

' Direct Code Conversion
Function CALL_37596:Int()
Local __TSTATES__:Int, __TIMING__:Long

While True
A = data[IY] '37596 LD A,(IY+0) Pick up the next byte of tune data from the table at 33902
If A=255 '37599 CP 255 Has the tune finished?
Return True '37601 RET Z Return (with the zero flag set) if so
End If
BC[C] = A '37602 LD C,A Copy the first byte of data for this note (which determines the duration) to C
BC[B] = 0 '37603 LD B,0 Initialise B, which will be used as a delay counter in the note-producing loop
A = 0 '37605 XOR A Set A=0 (for no apparent reasaon)
DE[D] = data[IY+1] '37606 LD D,(IY+1) Pick up the second byte of data for this note
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = DE[D] '37609 LD A,D Copy it to A
'CALL_37675() '37610 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,80) '37613 LD (HL),80 Set the attribute byte for the piano key to 80 (INK 0: PAPER 2: BRIGHT 1)
DE[E] = data[IY+2] '37615 LD E,(IY+2) Pick up the third byte of data for this note
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = DE[E] '37618 LD A,E Copy it to A
'CALL_37675() '37619 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,40) '37622 LD (HL),40 Set the attribute byte for the piano key to 40 (INK 0: PAPER 5: BRIGHT 0)
'Print( "A ---"+Right(Hex(A),2)+"  BC-"+Right(Hex(BC16),4)+"  DE-"+Right(Hex(DE16),4)+"  HL-"+Right(Hex(HL16),4) )
Repeat ' until C=0 (at 37645)
Repeat ' until B=0 (at 37642)

Local __LOOPSTART__:Long = MicroSecs()

_OUT(254,A) '37624 OUT (254),A Produce a sound based on the frequency parameters in the second and third bytes of data for this note (copied into D and E)
__TSTATES__ = 0
__TIMING__  = MicroSecs()
DE[D] :- 1 '37626 DEC D
If DE[D]=0 '37627 JR NZ,37634
DE[D] = data[IY+1] '37629 LD D,(IY+1)
A = A~24 '37632 XOR 24
__TSTATES__ :+ 26
End If
DE[E] :- 1 '37634 DEC E
If DE[E]=0 '37635 JR NZ,37642
DE[E] = data[IY+2] '37637 LD E,(IY+2)
'A = A~24 '37640 XOR 24
__TSTATES__ :+ 26
End If
BC[B] :- 1 '37642 DJNZ 37624

' IDENTIFY HOW LONG BLITZMAX IS TAKING
Local BlitzmaxNG:Long = MicroSecs() - __LOOPSTART__
'Print( "Blitzmax took: "+BlitzmaxNG+ " microsecs" )
'If BlitzmaxNG>5 DebugStop

' SLOW DOWN BLITZMAX SO CODE RUNS AT SAME SPEED AS ZX SPECTRUM
__TSTATES__ :+ 56
Local ZXSpectrum:Long = Long( Float(__TSTATES__)*Float(__CPUCYCLE__)*1000000.0)
'Print( "Spekky took: "+ZXSpectrum+" microseconds" )
'Print( " "+__TSTATES__+" t-states at "+Float(__CPUCYCLE__)+" us" )
Local wait:Long = MicroSecs() + ZXSpectrum - BlitzmaxNG
While wait>MicroSecs() ; Wend

'Print( "A ---"+Right(Hex(A),2)+"  BC-"+Right(Hex(BC16),4)+"  DE-"+Right(Hex(DE16),4)+"  HL-"+Right(Hex(HL16),4) )
Until BC[B]=0 ' ""    ""
BC[C] :- 1 '37644 DEC C

Until C=0 '37645 JR NZ,37624
'##### KEYBOARD INTERACTION
'If CALL_37687() '37647 CALL 37687 Check whether ENTER or the fire button is being pressed
' Return False '37650 RET NZ Return (with the zero flag reset) if it is
'End If
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = data[IY+1] '37651 LD A,(IY+1) Pick up the second byte of data for this note
'CALL_37675() '37654 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,56) '37657 LD (HL),56 Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)
'##### UPDATE AN ONSCREEN KEYBOARD TO SHOW NOTE
'A = data[IY+2] '37659 LD A,(IY+2) Pick up the third byte of data for this note
'CALL_37675() '37662 CALL 37675 Calculate the attribute file address for the corresponding piano key
'mem(HL,56) '37665 LD (HL),56 Set the attribute byte for the piano key back to 56 (INK 0: PAPER 7: BRIGHT 0)
IY :+ 3 '37667 INC IY Move IY along to the data for the next note in the tune
'37669 INC IY
'37671 INC IY
Wend '37673 JR 37596 Jump back to play the next note
End Function

' Direct Code Conversion
' Calculates which on-screen piano key will be shown pressed
' We dont need this, so it has been commented out
'Function CALL_37675()
' A :- 8 '37675 SUB 8 Compute the piano key index (K) based on the frequency parameter (F), and store it in bits 0-4 of A: K=31-INT((F-8)/8)
' _RRCA() '37677 RRCA
' _RRCA() '37678 RRCA
' _RRCA() '37679 RRCA
' A  = ~A '37680 CPL
' A :| 224 '37681 OR 224 A=224+K; this is the LSB
' HL[L] = A '37683 LD L,A Set HL to the attribute file address for the piano key
' HL[H] = 89 '37684 LD H,89
' Return '37686 RET
'End Function

' Keyboard input
' Not required, so has been commented out
'Function CALL_37687:Int()
' Return False
'End Function

' Z80 Mnumonic Rotate Bits Right with Carry
'Function _RRCA()
' Local x:Byte = A & $01
' A = ( A Shr 1 | ( A & $01 ) Shl 7 )
' F[CF] = x
'End Function

' Z80 Mnumonic


' bitmap of value is xxxSMBBB
' S=Sound M=Microphone B=Border

Function _OUT( port:Byte, value:Byte)
Global savestate:Int=False
Global savetime:Long=MicroSecs() ' Initialise to now to stop initial jump
Global counter:Int ' Count pulses from the game code

If port=254
Local speaker:Int = (value&$10)
counter :+ 1
If speaker = savestate Return ' State has not changed
Local now:Long = MicroSecs() ' Microseconds
If speaker ' Speaker turned ON, play last wave
Local duration:Int = now-savetime ' Microseconds
Print( "CREATE SAMPLE: "+duration+" us / "+counter+" oscillations"  )
Buzz( duration, counter )
savetime = now
counter = 0
End If
savestate = speaker
End If
End Function

' Create one "wave" of the supplied frequency
' Duration is in MicroSeconds
' Pulses is a count of the Oscillations sent to the speaker duing the time

Function Buzz:TChannel( duration:Int, pulses:Int )
Const SAMPLERATE:Int = 8000 'Hz

' CAP Duration
duration = Min( Duration, 20000 )

Local samples:Int = SAMPLERATE*duration/100000
Local frequency:Int = (SAMPLERATE/pulses) /4
Print "FREQ: "+frequency
' Create Audio Sample
Local sample:TAudioSample = CreateAudioSample( samples, SAMPLERATE, SF_MONO8 )
Print( "Sample Length: "+ sample.length )

' CREATE SQUARE WAVE WITH "pulses" OSCILLATIONS
Local half:Int = frequency/2
For Local n:Int=0 Until sample.length
If (n Mod frequency)<half
sample.samples[n] = 255
Else
sample.samples[n] = 0
End If
Next

'DebugLog( "SAMPLES: "+sample.length )
'sample.samples[sample.length-1]=128 ' End in silence

' PLAY THE AUDIO SAMPLE
Local audio:TSound = LoadSound( sample )
Local before:Long = MicroSecs()
Local ch:TChannel = PlaySound( audio )
While ch.playing() ; Wend
Print( "Playing time: "+ (MicroSecs()-before) + " us" )

End Function

' Play Music
Print( "Starting" )
IY = 0 ' Set pointer to start of music
CALL_37596()

Title: Re: Creating ZX SOUND
Post by: iWasAdam on February 15, 2021, 20:10:03
Whatever your base frequency. Do the following;
X2 = octave higher
/2 = octave lower
Or
x2 = next octave
X4 = next 2 octaves
X0.5 = prev octave
X0.25 = prev 2 octave

In essence, to get the 12 notes per octave you use divisions of 12. It's never quite that simple. I use a pre calculated lookup table

Ok let's assume you have a single wave that is 128frames - I use floats with 0 being the mid point, -1 an 1being the lower and upper volume.
This makes sin creations, volume, all sorts of stuff very fast and simple.

You then have a ring buffer, and you feed the wave data to the buffer adjusting the values foe volume float to int etc.

To play actual notes you do the following;

Each voice has a position. This is a double. And updates at a value of one - that means your wave is transferred at a single frame = pos+ 1

Let's assume that the 1 is called offset and is also a double

To output an active higher, you set the offset to 2. Pos=pos+offset
The output 2 octaves higher, offset = 4
You get the general concept?
Title: Re: Creating ZX SOUND
Post by: Midimaster on February 16, 2021, 03:13:42
At first you need to know, how the data structure is and how to play this data in an "normal" BlitzMax programm. Trying to find out by doing this in the emulator is not a good idea.

So I wrote a stand alone BlitzMax code, which is now able to play these DATA[] Arrays. With this base you now can try to integrate it into your emulator.

What I found out:
The DATA[] is organized in packages of 3 Bytes.

The first byte is the timing: The numbers of samples of the next sound
The second byte is the sound represented by a pulse width of a rectangle wave
The third byte is another sound generator for a second simultan sound. Often it is very closed to the first pulse. This brings the typical "old school" sound. sometime it is far away. This brings an interval related to the first pulse.

This code plays the datas:
Code (BlitzMax) Select
SuperStrict

Graphics 800,600


Global data%[] = [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,50,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]
Global NextTime%, ReadPos%


Repeat
Wait
If Data[ReadPos]=255 End
Until AppTerminate()


Function Wait()
If NextTime<MilliSecs()
Print ReadPos + " " + data[ReadPos] + " " + data[ReadPos+1] + " " + data[ReadPos+2]
Local Duration%=Data[ReadPos]*5
NextTime=MilliSecs()+Duration
MakeSound Duration, Data[ReadPos+1], Data[ReadPos+2]

ReadPos=ReadPos+3
EndIf
End Function


Function MakeSound(Duration%,PulseA%, PulseB%)
Const SAMPLERATE% = 11000'Hz
Local SampleLen%, Half%
Local SampleA:TAudioSample, SampleB:TAudioSample, AudioA:TSound, AudioB:TSound

SampleLen = SAMPLERATE*Duration/1000
If PulseA>0
SampleA = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )
Half = PulseA/2
For Local n:Int=0 Until SampleLen-1
If (n Mod PulseA) < half
SampleA.samples[n] = 200
Else
SampleA.samples[n] = 55
End If
Next
AudioA = LoadSound( SampleA )
EndIf
If PulseB>0
SampleB = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )
Half = PulseB/2
For Local n:Int=0 Until SampleLen-1
If (n Mod PulseB)<half
SampleB.samples[n] = 200
Else
SampleB.samples[n] = 55
End If
Next
AudioB = LoadSound( SampleB )
EndIf
If PulseA>0
PlaySound AudioA
EndIf
If PulseB>0
PlaySound AudioB
EndIf
End Function



and here is the same as a "class". You can "adjust the emulator in the CONST-values. The emulator is able to play song as SINUS, SAW or SQUARE:
Code (BlitzMax) Select
     SuperStrict
     
    Graphics 800,600
     
     
    Global data:Int[] = [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,50,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]
     
    SoundChip.StartSong data

    Repeat
            If soundChip.run()=False End
    Until AppTerminate()
     
     
Type SoundChip
Const SAMPLERATE% = 22050,  DURATION_FAKTOR:Float=5
Const SINUS:Int=1, SQUARE:Int=2, SAWTOOTH:Int=3
Const WAVE_FORM:Int =SQUARE
Global SongData:Int[], NextTime:Int, ReadPos:Int


Function StartSong:Int(Data:Int[])
SongData=Data
NextTime=MilliSecs()
ReadPos=0
End Function


Function Run:Int()
If NextTime<MilliSecs()
If Data[ReadPos]=255 Return False
Local Duration%=Data[ReadPos]*DURATION_FAKTOR
Local PulseA:Int=Data[ReadPos+1]
Local PulseB:Int=Data[ReadPos+2]

NextTime=MilliSecs()+Duration
If PulseA<>0 Or PulseB<>0
Select WAVE_FORM
Case SINUS
MakeSinus  Duration, PulseA, PulseB
Case SQUARE
MakeSquare Duration, PulseA, PulseB
Case SAWTOOTH
MakeSaw    Duration, PulseA, PulseB
End Select
EndIf
ReadPos=ReadPos+3
EndIf 
Return True
End Function




Function MakeSinus(Duration:Int, PulseA:Int, PulseB:Int)

Local SampleLen:Int= SAMPLERATE*Duration/1000
Local FaktorA:Float = 360.0/PulseA
Local FaktorB:Float = 360.0/PulseB

Local Sample:TAudioSample = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )

For Local n:Int=0 Until SampleLen
Local ValueA:Int, ValueB:Int

If PulseA=0
ValueA=0
Else
ValueA=60* Sin(FaktorA*(n Mod PulseA))
EndIf
If PulseB=0
ValueB=0
Else
ValueB=60* Sin(FaktorB*(n Mod PulseB))
EndIf

Sample.samples[n] = 127+ValueA+ValueB
Next
Local Audio:TSound = LoadSound( Sample )
PlaySound Audio
End Function




Function MakeSaw(Duration:Int, PulseA:Int, PulseB:Int)

Local SampleLen:Int= SAMPLERATE*Duration/1000
Local FaktorA:Float = 120.0/PulseA
Local FaktorB:Float = 120.0/PulseB

Local Sample:TAudioSample = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )

For Local n:Int=0 Until SampleLen
Local ValueA:Int, ValueB:Int

If PulseA=0
ValueA=0
Else
ValueA=FaktorA*(n Mod PulseA) -60
EndIf

If PulseB=0
ValueB=0
Else
ValueB=FaktorB*(n Mod PulseB) -60
EndIf

Sample.samples[n] = 127+ValueA+ValueB
Next
Local Audio:TSound = LoadSound( Sample )
PlaySound Audio
End Function



Function MakeSquare(Duration:Int, PulseA:Int, PulseB:Int)

Local SampleLen:Int= SAMPLERATE*Duration/1000
Local HalfA:Int = PulseA/2
Local HalfB:Int = PulseB/2

Local Sample:TAudioSample = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )

For Local n:Int=0 Until SampleLen
Local ValueA:Int, ValueB:Int

If PulseA=0
ValueA=0
ElseIf (n Mod PulseA) < HalfA
ValueA=60
Else
ValueA=-60
EndIf

If PulseB=0
ValueB=0
ElseIf (n Mod PulseB) < HalfB
ValueB=60
Else
ValueB=-60
EndIf
Sample.samples[n] = 127+ValueA+ValueB
Next
Local Audio:TSound = LoadSound( Sample )
PlaySound Audio
End Function

End Type

   

Title: Re: Creating ZX SOUND
Post by: Baggey on February 17, 2021, 17:58:48
Hi All,

Just realised your using BlitzmaxNG for the microsecs routine.

Im not having the time id like at the moment as im back to work. Waiting for the weekend so i can look at the machinecode routine's again. From the Z80 side of things the value of A is returned back differently when its passed to the Piano Key colour update routine.

What i mean is every third byte in the music data string if it's pulled directly is wrong.

The value for A that is passed to the Buzz routine will always be wrong unless you put the third byte that is Picked up. Into "Function CALL_37675()". I think the first dry run i did ,the 3rd Byte is 129 but will be returned as 240 when passed to OUT(254),240. This needs to be taken into account on every third byte pickup! Or it wont sound right  ;D

Note Every different value that is passed back were only interested in Bit5 for Piezo on or off.

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on February 18, 2021, 01:32:57
I'm now trying to understand your machine code:
'37626 DEC D
'37627 JR NZ,37634
'37629 LD D,(IY+1)
'37632 XOR 24

'37634 DEC E
'37635 JR NZ,37642
'37637 LD E,(IY+2)
'37640 XOR 24


this part is for defining, whether sound D or sound E are positive or negative at a certain moment. The registers are filled with the start value, then a register (Which??) is "XOR"-ed to give the OUT-Function the instruction do exactly the opposite of what it did before. (by the way: this means, that we should not calculate Half% for swapping our pulse. The sample-pulse must have the double length)

The individual countdown reduces  the D and E register, until it start again with filling them with the start value.

This works perfect if only one sound is used. f.e. if the pulse is 128 the coutdown reduces the value until 0. during this time the speaker would get a "ON". the the register is refilled. the XOR switches and for the next 128 pulses the speaker get a OFF.

What I cannot believe is that this would work if two sounds are processed. In your code D and E use the same register for "XOR"-ing. This means whenever any of the two sound-registers is refilled, the speaker switch the state. This would cause a wrong "combination wave form".   This means that there is not longer a individual ON/OFF state for each of the two sounds:
DE[D] :- 1 '37626 DEC D
If DE[D]=0 '37627 JR NZ,37634
   DE[D] = data[IY+1] '37629 LD D,(IY+1)
   A = A~24 '37632 XOR 24
   __TSTATES__ :+ 26
End If
DE[E] :- 1 '37634 DEC E
If DE[E]=0 '37635 JR NZ,37642
   DE[E] = data[IY+2] '37637 LD E,(IY+2)
   'A = A~24 '37640 XOR 24
   __TSTATES__ :+ 26
End If


As I see you saw the same problem and commented out the second A=A~24. It would make more sense if both sound would swap a different Bit individual. Are you sure, that this code is transferd correctly from machne code to BlitzMax?

also the "24" is strange, as this seems to be a combination of speaker and microphone...?

Your use of the OUT-function is completely wrong. In the machine code the OUT fires Bytes directly to the speaker. During a single note the state often changes from 0 to 1. Not each change to 1 should cause a new Tsound. You could do so, if you can feed an audio stream in BlitzMax like it is possible with OpenAL.

Without OpenAL you cannot simulate the OUT-Function. You have to stay one level higher and only there you can create TSounds

TSound is not made for very short audios below 100msec. especially "music"-TSounds below 100msec will produce artifacts and below 10msec only noise remains.

But at the moment OUT is called more that 3.000 times a second. And i guess inside the OUT the changes from 1 to 0 happen more than 100 times a second. fe.e a 250Hz tone needs 500 changes per second, one change every 2msec
Title: Re: Creating ZX SOUND
Post by: Midimaster on February 18, 2021, 09:40:38
To get any results and find bugs in the main function I modified your OUT-function. You can simulate a AUDIO-OUT-STREAM, when you collect all OUT-calls in one single TAudioSample until all work is done. At the end you play this TAudioSample to control the correctness.

With this I was able to find a lot of bugs in the main code. Your code never ended because the IY (as a BYTE) never reaches the 287.element of the DATA[]-field. So I changed it in a workaround to INTEGER.

Also it looks like your idea of handling the BC DE as 16bit registers failt. When you use them separately as 4 single BYTES the  function works as expected.

The delay counter B always runs from 255 down to 0. I changed this to 150 to make the song playing faster.

Also I rised the sampling rate to 44100 to make the song sounding higher.

use this as a base for further inverstigations. This is a runable example:
Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global counter%, Zeit%

' as a workaround I use the registers as real BYTE
Global A:Byte, B:Byte=1, C:Byte, D:Byte=0, E:Byte=0

'!!!!! the Data length is 286 so a BYTE variable would never reach the last DATA=255 to get the STOP command
' workaround: define IY as INT
Global IY:Int

' Music Data
Global data%[] = [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,50,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]
Print( "DATA LENGTH: "+Len(data) )

CALL_37596()


Print "samples=" + counter
Repeat
Until AppTerminate()
End


Function CALL_37596:Int()

While True
A = data[IY] '37596 LD A,(IY+0) Pick up the next byte of tune data from the table at 33902
If A=255 '37599 CP 255 Has the tune finished?
Return True '37601 RET Z Return (with the zero flag set) if so
End If
C = A '37602 LD C,A Copy the first byte of data for this note (which determines the duration) to C
B = 0 '37603 LD B,0 Initialise B, which will be used as a delay counter in the note-producing loop
A = 0 '37605 XOR A Set A=0 (for no apparent reasaon)
D = data[IY+1] '37606 LD D,(IY+1) Pick up the second byte of data for this note
E = data[IY+2] '37615 LD E,(IY+2) Pick up the third byte of data for this note
Repeat ' until C=0 (at 37645)


'workaround: normally B runs from 255 downto 0
' this makes the song playing faster:
B=150
Repeat ' until B=0 (at 37642)
OUT_ONCE(254,A) '37624 OUT (254),A Produce a sound based on the frequency parameters in the second and third bytes of data for this note (copied into D and E)
D :- 1 '37626 DEC D
If D=0 '37627 JR NZ,37634
D = data[IY+1] '37629 LD D,(IY+1)
A = A~24 '37632 XOR 24
End If
E :- 1 '37634 DEC E
If E=0 '37635 JR NZ,37642
E = data[IY+2] '37637 LD E,(IY+2)
'A = A~24 '37640 XOR 24
End If
B :- 1 '37642 DJNZ 37624

Until B=0 ' ""    ""
C :- 1 '37644 DEC C

Until C=0 '37645 JR NZ,37624
IY :+ 3 '37667 INC IY Move IY along to the data for the next note in the tune
Print "Step in DATA=" +  iy '37669 INC IY
'37671 INC IY


Wend '37673 JR 37596 Jump back to play the next note
End Function



Function OUT_ONCE(Port%, Value%)
Const SAMPLE_RATE%=44100
Global NextSample:TAudioSample
If Counter=0
Print "Sample created"
NextSample = CreateAudioSample(750000, SAMPLE_RATE, SF_MONO8 )
EndIf
Counter=Counter + 1

Select Value & 8
Case 0
NextSample.Samples[Counter]=50
Case 8
NextSample.Samples[Counter]=200
End Select

If counter=730000
Print "play TSound"
Local Audio:TSound = LoadSound( NextSample )
PlaySound Audio
EndIf
End Function



The next step would now be to cut the single TAudioSample in chunks of 100msecs to let the sound begin sooner.

With this changes you will get 100msec chunks. It is reacting faster but you will get more artifacts:
Code (BlitzMax) Select

...
'workaround: now you can set B back to  255 (or remove this code line)
B=255
Repeat ' until B=0 (at 37642)
OUT_CHUNKS(254,A) '37624 OUT (254),A
...

Function OUT_CHUNKS(Port%, Value%)
Const SAMPLE_RATE%=44100
Global NextSample:TAudioSample, PlayTime%
If Counter=0
Print "Sample created"
NextSample = CreateAudioSample(4410, SAMPLE_RATE, SF_MONO8 )
EndIf

Select Value & 8
Case 0
NextSample.Samples[Counter]=50
Case 8
NextSample.Samples[Counter]=200
End Select
Counter=Counter + 1
If counter=4408
NextSample.Samples[4408]=0
NextSample.Samples[4409]=0
Local Audio:TSound = LoadSound( NextSample )
Repeat
Delay 1
Until PlayTime<MilliSecs()
Print "play TSound"
PlayTime=MilliSecs()+100
PlaySound Audio
Counter=0
EndIf
End Function


Title: Re: Creating ZX SOUND
Post by: Baggey on February 18, 2021, 20:16:46
WOW, Just had a listen  :o

How have you arrived at 750,000 is there a mathimatical explanation.

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on February 19, 2021, 00:49:22
If you want to write code it is always a good way not to write the complete code idea and then start testing. Better is to work step by step down into the problem and test each step before diving deeper into the final code.

In your case this means first to understand the DATA[] field and write a BlitzMax Code which  produces the song as expected. Then you know, what the goal is. And you can verify, that the DATA[] field really contains good datas. Here I recognized that your example ("Jig") is valid. In this step I did not define BYTE variables, but INTEGERs.

The Data[] field of "Danube" has 286 Byte which means 95 tones + one STOP command. Each of this tone make a variable C count down to 0. Depending on the tone C varies between 50-80 representing the length of the tone. Inside each of this 50-80 loops there is another inner loop, where B runs from 255 downto 0. In total this can be upto 1.938.000 samples.
95*80*255= 1.938.000

to investigate the total number of samples you let the code run without doing anything with audio, but only count the calls of the OUT-function. In my test code with B=max 150 the result was 736.543. It is always a good idea to insert a lot of PRINT command, so you can follow, what happens and what is the state of all this loops.

Already at this moment I recognized that Scaremongers code was infinite and never reached the song end. Defining IY as Byte allows not a song length>255.

Now, when you know that you will need 736.543 samples you create a Byte Array that will be big enough to contain all these loops. During testing it is never a good idea to create an array of 736.543 bytes if you will need this number. Think big! Take 750.000 (or 2.000.000!!). Later you can state it more precisely.

The target is always not to produce additional bugs, because of beeing too stingy.

If you have been able to get running your problem in a more tolerant "surrounding", you can now dive deeper and modify your code towards the real target. But you will see, that these steps are now very easy and fast to reach. One of this steps is to check whether all variables can be changed to BYTE. Next step can be to simulate this 16bit registers. and so on...

f.e. the step to use Chunks instead of one big TAudioSample needed only 5 min after knowing that the prior code was already working. At the end you will code faster if you divide your problem in more little stages.





Title: Re: Creating ZX SOUND
Post by: Scaremonger on February 20, 2021, 10:37:50
@Midimaster: Nice one. I like the approach to build up the sound and then play it in chunks.
Title: Re: Creating ZX SOUND
Post by: Baggey on February 20, 2021, 16:35:31
Quote from: Midimaster on February 18, 2021, 09:40:38

The next step would be now to cut into single TAudioSample chunks of 100msecs. It is reacting faster but you will get more artifacts:
Code (BlitzMax) Select

...
'workaround: now you can set B back to  255 (or remove this code line)
B=255
Repeat ' until B=0 (at 37642)
OUT_CHUNKS(254,A) '37624 OUT (254),A
...

Function OUT_CHUNKS(Port%, Value%)
Const SAMPLE_RATE%=44100
Global NextSample:TAudioSample, PlayTime%
If Counter=0
Print "Sample created"
NextSample = CreateAudioSample(4410, SAMPLE_RATE, SF_MONO8 )
EndIf

Select Value & 8
Case 0
NextSample.Samples[Counter]=50
Case 8
NextSample.Samples[Counter]=200
End Select
Counter=Counter + 1
If counter=4408
NextSample.Samples[4408]=0
NextSample.Samples[4409]=0
Local Audio:TSound = LoadSound( NextSample )
Repeat
Delay 1
Until PlayTime<MilliSecs()
Print "play TSound"
PlayTime=MilliSecs()+100
PlaySound Audio
Counter=0
EndIf
End Function



WOW! We are really getting there  8)

Haven't had enough time to review everything! But had a good day coding and Playing with your routines. The closest ive got is, to using your OUT_CHUNKS Code successfully. Within my Emulator "SpecBlitz". Thanks to MIDI MASTER  ;D

Love the idea of the Sound Chip Type. But the Scretchy sound of Manic Miner just isnt there thou. Ive played with some off the values and got Bassier sounds and Higher Pitched sounds as well. the saw wave example is an excellent twist to the Zx Spectrum. It's almost like using Band Pass filters. Not sure how to alter the Sound Chip type to achieve the scretchy Sound of Manic Miner Thou.

So ive created a mini ZX Emulator to use your OUT_CHUNKS routine. This Routine is Directly Runable In BlitzMax 1.5 and here it is :-

Code (Blitzmax) Select
' ManicMiner Player Using BlitzMax 1.5

SuperStrict

Graphics 800,600

Global RunSpeed:Int=16 ' Using Blitzmax 1.5 Gives roughly a 50Hz Frame Delay!

' Registers 16 Bit
Global IY:Short, BC:Short, DE:Short, HL:Short

' Registers 8 Bit
Global A:Byte, B:Byte, C:Byte, D:Byte, E:Byte, H:Byte, L:Byte

' Control Registers
Global TCycles:Int
Global fZERO:Byte
Global ENTER:Byte=0
Global Counter:Int

' Music Data
Global MusicData:Int[] = [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,50,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]
Print
Print "Length of MusicData = "+(Len MusicData)

Init()

Function MainLoop()
    Local starttime:Int = MilliSecs()
   
    While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)
       
Run()
        Local endtime:Int = MilliSecs()
        Local diff:Int = endtime - starttime
       
        Local pausedelay:Int = RunSPEED ' About 50Hz ie one TV frame

        If pausedelay > 0 Then
            Delay(pausedelay)
        Else
            pausedelay = 0
        End If
        starttime = endtime+pausedelay
    Wend
End Function

Function Call_37596:Byte()

While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)

' LD A,(IY+0) '  Get next Byte of Tune Data from 33902
A=MusicData[IY]  ; TCycles:+19
' CP 255 '  Has the tune finished?
If (A-255)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7
' RET Z         ' Return if ZERO flag is Set
If fZERO=1 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If
' LD C,A ' Copy the first byte of MusicData for this note (which determines the duration) into C
C=A ; BC=(B Shl 8)+C ; TCycles:+4
' LD B,0 ' Load B with 0, which will be used as a delay counter in the note-producing loop
B=0 ; BC=(B Shl 8)+C ; TCycles:+7
' XOR A          ' XOR A  Equivalent to LOAD A with 0 ( Incerting a delay of 4 TCycles )
A=(A ~ A) ; TCycles:+4
' LD D,(IY+1) ' Get second byte of MusicData for this note
D=MusicData[IY+1] ; DE=(D Shl 8)+E ; TCycles:+19
' LD A,D ' Load A with D
A=D ; TCycles:+4
'
' FIRST CALL
'
' CALL 37675 ' Update on screen piano Key
TCycles:+17 ' 17 Clock Cycles for the call
' Code there is irelevant, but time must br acounted for!
' LD (HL),80   ' Set the Atribute colour of piano key 80 (INK 0: PAPER 2: BRIGHT 1)
TCycles:+10  ' 10 Clock Cycles for the LD(HL),40
' LD E,(IY+2) ' Get Third byte of MusicData for this note
E=MusicData[IY+2] ; D=(D Shl 8)+E ; TCycles:+19
' LD A,E   ' Load A with E
A=E ; TCycles:+4
'
' SECOND CALL
'
' CALL 37675 ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
A=Call_37675() ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
TCycles:+17 ' 17 Clock Cycles for the call
' Code there is irelevant, but time must br acounted for!
' LD (HL),40
TCycles:+10  ' 10 Clock Cycles for the LD(HL),40
Repeat ' C LOOP until C=0
' Part of the DJNZ
Repeat ' B LOOP until B=0
'For Local DJNZ:Int=B To 0 Step -1 ' until B=0
'Print "B="+B
' OUT (254),A ' Turn Piezo on or off
OUT_CHUNKS(254,A) ; TCycles:+11
' DEC D ' Decrease D by One
D:-1 ; DE=(D Shl 8)+E ; TCycles:+4
If D=0 Then fZERO=1 Else fZERO=0 ' If D=0 then set Zero flag
' JR NZ,37634
If fZERO=1 Then
' LD D,(IY+1)
D = MusicData[IY+1] ; DE=(D Shl 8)+E ; TCycles:+19
' XOR 24
A = (A ~ 24)
TCycles:+7 ' Condition not met
Else
TCycles:+12 ' Condition met
End If
' DEC E
E:-1 ; DE=(D Shl 8)+E ; TCycles:+4
If E=0 Then fZERO=1 Else fZERO=0 ' If E=0 then set Zero flag
' JR NZ,37642
If fZERO=1 Then
' LD E,(IY+1)
E = MusicData[IY+2] ; TCycles:+19
' XOR 24
A = (A ~ 24)
TCycles:+7 ' Condition not met
Else
TCycles:+12 ' Condition met
End If
' DJNZ
'DisJump=(twosum[PC1]+2) ; B:-1 ; BC=(B Shl 8)+C
If B<>0 Then 
TCycles:+13 ' Condition met
Else
TCycles:+8 ' Condition not met
End If
B:-1 ; BC=(B Shl 8)+C
'
Until (B=0) ' B=0
' DEC C ' Decrease C by one
C:-1 ; BC=(B Shl 8)+C ; TCycles:+4
If C=0 Then fZERO=1 Else fZERO=0 ' If E=0 then set Zero flag
Until (fZERO=1)
' Check KEYBOARD and JOYSTICK
CALL_37687() ; TCycles:+17 ' Check wether Enter or Fire is being pressed Code ignored but timing needed!
' RET NZ          ' Return if ZERO flag is ReSet, if it is
If fZERO=0 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If
' Pick up Keys AGAIN! This time we are Painting the keys WHITE Updating them
' Code is irrelevant but the timming is!
' LD A,(IY+1) '  Get 2nd Byte of Tune Data from 33902
A=MusicData[IY+1]  ; TCycles:+19
' CALL 37675 ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
A=Call_37675() ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
TCycles:+17 ' 17 Clock Cycles for the call
' Code there is irelevant, but time must be acounted for!
' LD (HL),56
TCycles:+10  ' 10 Clock Cycles for the LD(HL),40. Paints Piano key WHITE
' LD A,(IY+2) '  Get 3rd Byte of Tune Data from 33902
A=MusicData[IY+2]  ; TCycles:+19
' CALL 37675 ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
A=Call_37675() ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
TCycles:+17 ' 17 Clock Cycles for the call
' Code there is irelevant, but time must be acounted for!
' LD (HL),56
TCycles:+10  ' 10 Clock Cycles for the LD(HL),40
' INC IY ' Increase IY by one
IY:+1 ; TCycles:+10
' INC IY ' Increase IY by one
IY:+1 ; TCycles:+10
' INC IY ' Increase IY by one
IY:+1 ; TCycles:+10
' JR 37596
TCycles:+12
Wend
End Function

' Check Wether ENTER Or FIRE is being pressed
Function Call_37687:Byte()
Print "Checking Keyboard"
' LD A,(33881)
A=0 ; TCycles:+13 ' Load A with Peek(33881). This Value will be 0 for this Purpose
' OR A ' Is Joystick Connected
A=(A | A)
'Print "A is "+A ; Repeat Until KeyDown(Key_SPACE)
If A=0 Then fZERO=1 Else fZERO=0 ; TCycles:+4
'Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
' JR Z,37698 ' Jump Forwrad if not
If fZERO=0 Then
' IN A,(31) ' Kempston Joystick PORT
A=0 ; TCycles:+11 ' Assume FIRE is not being pressed, So we PASS back a 0!
' BIT 4,A ' Was FIRE pressed?
Local Ans:Byte
Ans=(16 & A) ' Test Bit 4 ie, 2^4=16
If Ans=0 Then fZERO=1 Else fZERO=0 ; TCycles:+8
' RET NZ ' Return the ZERO FLAG if FIRE Pressed, Which it won't be
If fZERO=1 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If
End If
' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
' LD BC,49140 ' Load BC ready to Read the Key Buffer for Keys_H_to_ENT
BC=49150 ; TCycles:+10
' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
' IN A,(C)
If KeyDown(Key_ENTER) Then
A=254 ; ENTER=1
'Print "A is "+A
'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
Else
A=255 ; ENTER=0
'Print "A is "+A
'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
End If
TCycles:+11 ' if ENTER then A=254 else A=255
' AND 1
A=(A & 1)
If A=0 Then fZERO=1 Else fZERO=0
TCycles:+7
' CP 1 '  Has ENTER been pressed?
If (A-1)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7
' RET
TCycles:+10
End Function

' Update Keyboard Piano key and Value of A
Function Call_37675:Byte()
' SUB 8
A=A-8 ; TCycles:+7
Local TempCARRY:Byte
' RRCA
TempCARRY=(A & 1) <> 0
If TempCARRY=0 Then A=(A Shr 1) Else A=((A Shr 1) | 128)
TCycles:+4
' RRCA
TempCARRY=(A & 1) <> 0
If TempCARRY=0 Then A=(A Shr 1) Else A=((A Shr 1) | 128)
TCycles:+4
' RRCA
TempCARRY:Byte=(A & 1) <> 0
If TempCARRY=0 Then A=(A Shr 1) Else A=((A Shr 1) | 128)
TCycles:+4
' CPL
A=(A ~ 255) ; TCycles:+4
' OR 224
A=(A | 224) ; TCycles:+7
' LD L,A
L=A ; TCycles:+4
' LD H,89
H=89 ; TCycles:+7
' RET
TCycles:+10 ; Return A
End Function

Function OUT_CHUNKS(Port%, Value%)

        Const SAMPLE_RATE%=44100
        Global NextSample:TAudioSample, PlayTime:Int
        If Counter=0
                Print "Sample created"
                NextSample = CreateAudioSample(4410, SAMPLE_RATE, SF_MONO8 )
Print
Print "IY="+(RSet(IY, 3).Replace(" ", "0"))+" ... MusicData[IY]="+(RSet$(musicDATA[IY+2],3).Replace(" ","0"))+" ... A Register="+(RSet$(A,3).Replace(" ","0"))+" ... BIT4="+((A & 16) <> 0)
Print "OUT_CHUNKS CALLED!"
        EndIf

        Select Value & 8
                Case 0
                        NextSample.Samples[Counter]=50
                Case 8
                        NextSample.Samples[Counter]=200
        End Select
        Counter=Counter + 1
        If counter=4408
                NextSample.Samples[4408]=0     
                NextSample.Samples[4409]=0     
                Local Audio:TSound = LoadSound( NextSample )
                Repeat
                  ' Delay 1
                Until PlayTime<MilliSecs()
                Print "play TSound"
                PlayTime=MilliSecs()+100
                PlaySound Audio
                Counter=0
        EndIf
End Function

Function Init()
' Reset Registers
IY=0 ; A=0 ; B=0 ; C=0
' START SpecBlitz
MainLoop()
End Function

Function run()
Call_37596()
If fZERO=1 And Enter=0 Then
Print
Print "TUNE FINISHED"
Print "IY="+IY+" ... MusicData[IY]="+musicDATA[IY]
End
End If
'Print Key_Enter
If ENTER=1 Then Print "ENTER was Pressed!" ; End
EndFunction


If you read through my mini Emulator. There is a Delay routine that slows down BlitzMax 1.5 to the ZX Spectrum speed. Ive put in all the Tstate time Cycles of each Instruction. And just can't see away through the tree's  ??? On how to integrate this into your counter timer loop within OUT_CHUNKS. Anyone?

Conclusion
I really dont have a clue on how to use the TSound functions to create what i need hence the start of "Creating ZX SOUND" thread :-[

The code as is need's to be left intact! And only the "OUT_CHUNKS" routine using the Global TCycles somehow or the "Sound chip type" Routine to reference time within the Emulator. Using the Tcycles as a reference count. Being able todo this should allow other games or programs to use the "OUT_CHUNKS" function for sound as well. Rather than being unique to Manic Miner it's self.

Is there away the sound can be sped up or slowed down only using a GLOBAL DELAY? I see you used a DELAY instruction in there?

Side Tracking a bit BlitzMax 1.5 Really Dosen't explain how to use the TSOUND functions Clearly in Help >:( Any pointers on the Syntax to make the Penny drop would be much appriciated!

However, enjoy the Mini Zx Emulator and OUT_CHUNKS Playing the BLUE DANUBE from Manic Miner.

Thankyou to all who are helping and giving idea's.

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on February 20, 2021, 18:49:53
HOW TCYCLES IN OUT()?
I would think the OUT command in Z80 is no software function, which would consum time. It is the last step to hardware and only the call needs the 11 TCycles you already added.

MY DELAY
The circumstance that OUT is now a function is only "Windows"-related to simulate the hardware. You should calculte 0msec for it. The reason why I added a DELAY is also only windowsrelated. We have to care about that the next PlaySound exactly starts 100msec after the former PlaySound had started.
If your TCycles-timer ever will work perfect my DELAY will have no longer any effect. (but keep activated the code line for "security reasons".)


WOULD OUT_CHUNK WORK FOR EVERY Z80-CODE?
Yes! My OUT_CHUNK (please rename it as "OUT") is a possible way to simulate the hardware speaker with a BlitzMax TSound. You can "feed" it with any Value and it will always do what the Z80-speaker would have done. The only restrictions are...

1. The sound always starts with an latency of 100msec
2. Sounds shorter than 100msec are not played
3. The last 100msec of a sequence will be cutted away.

You can optimize this behavoir (2. and 3.), when you use a TimerEvent() based function (f.e. every 10msec) which periodically will have a look on the TAudioSample, whether it now should be played now. In this method the OUT()-function would only fill the TAudioSample but is not longer responisble for playing it.

TUNING THE SONG AND RISING SPEED
you can adjust the tuning of the music by changing Const SAMPLE_RATE towards lower values. F.e. 22.000 makes it sounding deeper. You can speed up the song by Changing the line B=255. f.e. B=125 plays it with double speed. If you need to do this inside the OUT, you could try to "forget" OUT calls. f.e.
Code (BlitzMax) Select
Function OUT(port%, value%)
     Global SwapIt=1-SwapIt
     If SwapIt=1 Return
     ....

This would make the song playing 2 times faster

A HINT FOR TCYLCES
You are doing right in collecting the TCYCLES for simulating the processor speed of the Z80. you add them, and when they reached a crititical sum (f.e. 3500) you could pause the BlitzMax-Code for 1msec with a DELAY 1. This way you would not need a high resolution timer and the speed would be controlled 1.000 times a second.

Code (BlitzMax) Select
Function SlowDown()
     Global Realtime:INT
     If RealTime=0
          RealTime=Millisecs()
     Endif
     If TCycles>3500
          TCyles=TCyles-3500
          RealTime=RealTime+1
     Endif
     While RealTime>Millisecs()
          Delay 1
     wend
End Function


This would interupt the simultator without slowing down the whole windows (and the other processes)
Title: Re: Creating ZX SOUND
Post by: Baggey on February 20, 2021, 21:00:52
Oh my god,

I feel like a "kid in a sweet shop" or in a "toy shop" not sure what translate's best but Kudos! Does that translate as well!

Daz ist gut!

I have four hours of play tomorrow until next weekend  :'(

For anyone who may be interseted this has been a long journey of passion! interest and tinasity if that's a word :-X

As i explained earlier in another thread writing an emulator from scratch takes a long time. If i ever get it working fully i promise to mention the people on the way "Kevin Picone" who helped me to get started. Midi Master and Scaremonger

A thread for Sound help!
https://www.underwaredesign.com/forums/index.php?topic=3946.0

My OLD Emulator which aint so OLD now! ;)
https://www.underwaredesign.com/forums/index.php?topic=3890.0

Again i think this deserves:-

(https://www.jlwranglerforums.com/forum/attachments/27c5932b4197271a08b337e75dc7c17a-gif.304658/)

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on March 27, 2021, 19:51:58
Im Interested in trying this with OpenAL but can't seem to get anything to work ive installed the .ExE filen for openAL.
But get this error anyone?

Building untitled1
Compiling:untitled1.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 3331 bytes.
Linking:untitled1.exe
Executing:untitled1.exe
No access to OpenAL capture Device !
Process complete


Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Scaremonger on March 27, 2021, 22:20:44
You should keep an eye on this link.

https://www.syntaxbomb.com/index.php/topic,8377.0.html (https://www.syntaxbomb.com/index.php/topic,8377.0.html)

@Midimaster is creating a pretty awesome ringbuffer.

Title: Re: Creating ZX SOUND
Post by: Baggey on April 02, 2021, 17:31:51
Finally somemore time to tinker.

So, Thought id have ago at doing JetSet Willy Theme tune. In the Z80 Emulation format.
This runs in BlitzMax 1.5

' JetSet Willy Player Using BlitzMax 1.5

SuperStrict

Graphics 800,600

Global RunSpeed:Int=16 ' Using Blitzmax 1.5 Gives roughly a 50Hz Frame Delay!

' Registers 16 Bit
Global IY:Short, BC:Short, DE:Short, HL:Short, AF:Short

' Registers 8 Bit
Global A:Byte, B:Byte, C:Byte, D:Byte, E:Byte, H:Byte, L:Byte, F:Byte

' Registers 16 Bit
Global AF_:Short

' Alternate 8 Bit
Global A_:Byte, F_:Byte

' Control Registers
Global TCycles:Int
Global fZERO:Byte
Global fCARRY:Byte=0
Global ENTER:Byte=0
Global Counter:Int

' Temp Values
Global TempA:Short
Global Ans:Short
Global sum:Byte
Global Copy_of_bit7:Byte

' Music Data
Global MusicData:Int[] = [81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,76,60,51,76,60,51,76,57,45,76,57,45,81,64,45,81,60,51,81,60,54,91,64,54,102,81,60,81,60,51,81,60,51,40,60,40,40,54,45,81,54,45,81,54,45,40,54,40,40,60,51,81,60,51,38,60,79,45,76,60,45,40,64,51,81,64,51,45,64,54,32,64,54,61,121,61,255]
Print
Print "Length of MusicData = "+(Len MusicData)

Init()

Function MainLoop()
    Local starttime:Int = MilliSecs()
   
    While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)
       
Run()
        Local endtime:Int = MilliSecs()
        Local diff:Int = endtime - starttime
       
        Local pausedelay:Int = RunSPEED ' About 50Hz ie one TV frame

        If pausedelay > 0 Then
            Delay(pausedelay)
        Else
            pausedelay = 0
        End If
        starttime = endtime+pausedelay
    Wend
End Function

Function Call_38562:Byte()

While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)

' LD A,(HL) '  Get next Byte of Tune Data from 34299. In this case 0
A=MusicData[HL]  ; TCycles:+19

' CP 255 '  Has the tune finished?
sum=A-255
Print "A-255="+sum
If (A-255)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7

' RET Z         ' Return if ZERO flag is Set
If fZERO=1 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If

' LD BC,100 ' Load BC with 100. B=0 (short note Counter). C=100 (Short Note Counter)
B=0 ; C=100 ; BC=(B Shl 8)+C ; TCycles:+10

' XOR A          ' XOR A  Equivalent to LOAD A with 0 ( Incerting a delay of 4 TCycles )
' Border Colour and Speaker state
A=(A ~ A) ; TCycles:+4

' LD E,(HL) ' Load E with byte of MusicData During the short note loop
E=MusicData[HL] ; DE=(D Shl 8)+E ; TCycles:+19

' LD D,E ' Load D with (Pitch delay counter)
D=E ; DE=(D Shl 8)+E ; TCycles:+4
'
Repeat ' C is OUTER LOOP Until C=0
' Part of the DJNZ
'
Repeat ' B is iner LOOP until B=0
'For Local DJNZ:Int=B To 0 Step -1 ' until B=0
'Print "B="+B

' OUT (254),A ' Produce a short note ( 0.003s ) Whose pitch is determined by E
OUT_254(254,A) ; TCycles:+11

' DEC D ' Decrease D by One
D:-1 ; DE=(D Shl 8)+E ; TCycles:+4
If D=0 Then fZERO=1 Else fZERO=0 ' If D=0 then set Zero flag

' JR NZ,38580
If fZERO=1 Then
' LD D,E
D = E ; DE=(D Shl 8)+E ; TCycles:+19
' XOR 24
A = (A ~ 24)
TCycles:+7 ' Condition not met
Else
TCycles:+12 ' Condition met
End If

' DJNZ
'DisJump=(twosum[PC1]+2) ; B:-1 ; BC=(B Shl 8)+C
If B<>0 Then 
TCycles:+13 ' Condition met
Else
TCycles:+8 ' Condition not met
End If
B:-1 ; BC=(B Shl 8)+C
'
Until (B=0) ' B=0

' EX AF,AF_ ' Exchange AF with AF_ Alternate registers
TempA=A ; A=TempA ; TCycles:+7

' LD A,C   ' Load A with C
A=C ; TCycles:+4

' CP 50 '  Is the short note counter down to 50 yet?
If (A-50)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7

' JR NZ,38590 ' Jump if not otherwise
If fZERO=1 Then
' RL E ' Double value in E
Copy_of_bit7=(E & 128) <> 0
Ans=(E Shl 1)
Ans = Ans & 255
'
'fSIGN=(Ans & 128) <> 0
fZERO = (Ans=0)
'fFIVE = (Ans & 32) <> 0
'fHALFCARRY = 0
'fTHREE = (Ans & 8) <> 0
'fPARITY=parity[Ans]
'fADDSUB = 0
fCARRY = Copy_of_bit7
'SetFLAGS()
E=Ans

' XOR 24
A = (A ~ 24)
TCycles:+7 ' Condition not met
Else
TCycles:+12 ' Condition met
End If

' EX AF,AF_ ' Exchange AF with AF_ Alternate registers
TempA=A ; A=TempA ; TCycles:+7


' DEC C ' Decrease C by one
C:-1 ; BC=(B Shl 8)+C ; TCycles:+4
If C=0 Then fZERO=1 Else fZERO=0 ' If E=0 then set Zero flag


' JR NZ ' Jump back until we've finished playing 50 notes
'
Until (C=0) ' C=0

' Check KEYBOARD and JOYSTICK
CALL_38601() ; TCycles:+17 ' Check wether Enter or Fire is being pressed Code ignored but timing needed!

' RET NZ          ' Return if ZERO flag is ReSet, if it is
If fZERO=0 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If

' INC HL ' Increase HL by one
HL:+1 ; TCycles:+10

' JR 38562
TCycles:+12
Wend
End Function

' Check Wether ENTER Or FIRE is being pressed
Function Call_38601:Byte()
Print "Checking Keyboard"
' LD A,(34254)
A=0 ; TCycles:+13 ' Load A with Peek(34254). This Value will be 0 for this Purpose
' OR A ' Is Joystick Connected
A=(A | A)
'Print "A is "+A ; Repeat Until KeyDown(Key_SPACE)
If A=0 Then fZERO=1 Else fZERO=0 ; TCycles:+4
'Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
' JR Z,38612 ' Jump Forwrad if not
If fZERO=0 Then
' IN A,(31) ' Kempston Joystick PORT
A=0 ; TCycles:+11 ' Assume FIRE is not being pressed, So we PASS back a 0!
' BIT 4,A ' Was FIRE pressed?
Local Ans:Byte
Ans=(16 & A) ' Test Bit 4 ie, 2^4=16
If Ans=0 Then fZERO=1 Else fZERO=0 ; TCycles:+8
' RET NZ ' Return the ZERO FLAG if FIRE Pressed, Which it won't be
If fZERO=1 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If
End If
' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
' LD BC,45054 ' Load BC ready to Read the Key Buffer for Keys_H_to_ENT and 6-7-8-9-0
BC=45054 ; TCycles:+10
' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
' IN A,(C)
If KeyDown(Key_ENTER) Then
A=254 ; ENTER=1
'Print "A is "+A
'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
Else
A=255 ; ENTER=0
'Print "A is "+A
'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
End If
TCycles:+11 ' if ENTER then A=254 else A=255
' AND 1
A=(A & 1)
If A=0 Then fZERO=1 Else fZERO=0
TCycles:+7
' CP 1 '  Has ENTER been pressed?
If (A-1)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7
' RET
TCycles:+10
End Function

Function OUT_254(Port:Byte, Value:Byte)
' The three lines of code speed the song up ie Twice the speed!
'Global SwapIt:Byte
      'SwapIt=1-SwapIt
      'If SwapIt=1 Return
'
        Const SAMPLE_RATE:Short=44100
        Global NextSample:TAudioSample, PlayTime:Int
        If Counter=0
                Print "Sample created"
                NextSample = CreateAudioSample(4410, SAMPLE_RATE, SF_MONO8 )
Print
Print "HL="+(RSet(HL, 3).Replace(" ", "0"))+" ... MusicData[HL]="+(RSet$(musicDATA[HL],3).Replace(" ","0"))+" ... A Register="+(RSet$(A,3).Replace(" ","0"))+" ... BIT4="+((A & 16) <> 0)
Print "OUT_254 Called!"
        EndIf

        Select Value & 8
                Case 0
                        NextSample.Samples[Counter]=50
                Case 8
                        NextSample.Samples[Counter]=200
        End Select
        Counter=Counter + 1
        If counter=4408
                NextSample.Samples[4408]=0     
                'NextSample.Samples[4409]=0     
                Local Audio:TSound = LoadSound( NextSample )
                Repeat
                  ' Delay 1
                Until PlayTime<MilliSecs()
                Print "play TSound"
                PlayTime=MilliSecs()+100
                PlaySound Audio
                Counter=0
        EndIf
End Function

Function Init()
' Reset Registers
IY=0 ; A=0 ; B=0 ; C=0 ; D=0 ; E=0 ; F=0 ; TempA=0
' START SpecBlitz
MainLoop()
End Function

Function run()
Call_38562()
If fZERO=1 And Enter=0 Then
Print
Print "TUNE FINISHED"
Print "HL="+HL+" ... MusicData[HL]="+musicDATA[HL]
End
End If
'Print Key_Enter
If ENTER=1 Then Print "ENTER was Pressed!" ; End
EndFunction


Something wrong here, in the loops as i think each note is being played 3 times. Anyway you can here the Midnight Sonata in there. But something's not quite write?

Been staring at this most of the day and can't see my mistake.

Kind Rgards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on April 02, 2021, 17:57:20
Did you check the quality of the DATA[]-field with my StandAlone-Player from post#56?

How does it sound for you here?


And... Is it the correct number of values?

The size needs to be (3*x+1), so 100 would fit perfect, but there are 101 elements. The player crashs.

Do you have a link, how the music should sound? What is "midnight sonata"? Beethoven?



Title: Re: Creating ZX SOUND
Post by: Baggey on April 03, 2021, 09:29:16
POST HAS BEEN UPDATED

Will play somemore latter on. Ill check post 56. It's Beethoven i believe.

Ive now checked post #56 and it sounds nothing like it should.

The machine code routine that plays the tune in Manic Miner is slightly different to the machine code that plays the tune in Jet Set Willy. Out 254 would do the same thing to the speaker though!?

I think Manic Miner use's 3 bytes to make the tune and Jet Set willy uses 2 bytes (2*X+1). I think it's all to do with timing in the Loops themselves. Looking forward to experimenting with the Ring buffer wrapper. Hope it works with BlitzMax 1.5 and is easy to install.  :D

Latter on today ill check ive ripped the music data properly!

For now it should sound like this. https://youtu.be/l0lHXNqXFvg

Kind Regards Baggey

Title: Re: Creating ZX SOUND
Post by: Baggey on April 03, 2021, 10:45:26
JetSet Willy Tune Data actually in memory:-

So ripped data from Memory in my SpecBlitz Emulator so data is spot on!  ;D

There are 99 Bytes of Data. With the last byte being 255 terminator for the loop. If we count the Zero we have 100.

Will have a look at the Z80 Machine code emulation as i think ive got a loop wrong!

Building Spectrum emulator
Compiling:ZXio.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 11441 bytes.
Compiling:Z80.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 277502 bytes.
Compiling:video.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 5916 bytes.
Compiling:videoMemory.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 9359 bytes.
Compiling:OO.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 143179 bytes.
Compiling:CB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 107819 bytes.
Compiling:ED.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 39351 bytes.
Compiling:DD.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 46811 bytes.
Compiling:FD.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 55618 bytes.
Compiling:DDCB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 115762 bytes.
Compiling:FDCB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 159967 bytes.
Compiling:Spectrum emulator.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 61637 bytes.
Linking:Spectrum emulator.exe
Executing:Spectrum emulator.exe
start ADDRESS
PEEK(34299)=81
PEEK(34300)=60
PEEK(34301)=51
PEEK(34302)=81
PEEK(34303)=60
PEEK(34304)=51
PEEK(34305)=81
PEEK(34306)=60
PEEK(34307)=51
PEEK(34308)=81
PEEK(34309)=60
PEEK(34310)=51
PEEK(34311)=81
PEEK(34312)=60
PEEK(34313)=51
PEEK(34314)=81
PEEK(34315)=60
PEEK(34316)=51
PEEK(34317)=81
PEEK(34318)=60
PEEK(34319)=51
PEEK(34320)=81
PEEK(34321)=60
PEEK(34322)=51
PEEK(34323)=76
PEEK(34324)=60
PEEK(34325)=51
PEEK(34326)=76
PEEK(34327)=60
PEEK(34328)=51
PEEK(34329)=76
PEEK(34330)=57
PEEK(34331)=45
PEEK(34332)=76
PEEK(34333)=57
PEEK(34334)=45
PEEK(34335)=81
PEEK(34336)=64
PEEK(34337)=45
PEEK(34338)=81
PEEK(34339)=60
PEEK(34340)=51
PEEK(34341)=81
PEEK(34342)=60
PEEK(34343)=54
PEEK(34344)=91
PEEK(34345)=64
PEEK(34346)=54
PEEK(34347)=102
PEEK(34348)=81
PEEK(34349)=60
PEEK(34350)=81
PEEK(34351)=60
PEEK(34352)=51
PEEK(34353)=81
PEEK(34354)=60
PEEK(34355)=51
PEEK(34356)=40
PEEK(34357)=60
PEEK(34358)=40
PEEK(34359)=40
PEEK(34360)=54
PEEK(34361)=45
PEEK(34362)=81
PEEK(34363)=54
PEEK(34364)=45
PEEK(34365)=81
PEEK(34366)=54
PEEK(34367)=45
PEEK(34368)=40
PEEK(34369)=54
PEEK(34370)=40
PEEK(34371)=40
PEEK(34372)=60
PEEK(34373)=51
PEEK(34374)=81
PEEK(34375)=60
PEEK(34376)=51
PEEK(34377)=38
PEEK(34378)=60
PEEK(34379)=45
PEEK(34380)=76
PEEK(34381)=60
PEEK(34382)=45
PEEK(34383)=40
PEEK(34384)=64
PEEK(34385)=51
PEEK(34386)=81
PEEK(34387)=64
PEEK(34388)=51
PEEK(34389)=45
PEEK(34390)=64
PEEK(34391)=54
PEEK(34392)=32
PEEK(34393)=64
PEEK(34394)=54
PEEK(34395)=61
PEEK(34396)=121
PEEK(34397)=61
PEEK(34398)=255
end ADDRESS


And as a Cut and Paste string :-

Building Spectrum emulator
Compiling:ZXio.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 11441 bytes.
Compiling:Z80.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 277089 bytes.
Compiling:video.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 5916 bytes.
Compiling:videoMemory.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 9359 bytes.
Compiling:OO.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 143179 bytes.
Compiling:CB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 107819 bytes.
Compiling:ED.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 39351 bytes.
Compiling:DD.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 46811 bytes.
Compiling:FD.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 55618 bytes.
Compiling:DDCB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 115762 bytes.
Compiling:FDCB.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
4 passes, 159967 bytes.
Compiling:Spectrum emulator.bmx
flat assembler  version 1.69.14  (1572863 kilobytes memory)
3 passes, 61637 bytes.
Linking:Spectrum emulator.exe
Executing:Spectrum emulator.exe
Start ADDRESS 34299
81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,76,60,51,76,60,51,76,57,45,76,57,45,81,64,45,81,60,51,81,60,54,91,64,54,102,81,60,81,60,51,81,60,51,40,60,40,40,54,45,81,54,45,81,54,45,40,54,40,40,60,51,81,60,51,38,60,45,76,60,45,40,64,51,81,64,51,45,64,54,32,64,54,61,121,61,255,
End ADDRESS 34398


Kind Regards Baggey

Title: Re: Creating ZX SOUND
Post by: Midimaster on April 03, 2021, 11:29:06
The audio sample compared with the datas I would guess that each byte is a new sound and the lenght of all is the same (fix).
Title: Re: Creating ZX SOUND
Post by: Baggey on April 03, 2021, 17:43:39
Quote from: Midimaster on April 03, 2021, 11:29:06
The audio sample compared with the datas I would guess that each byte is a new sound and the lenght of all is the same (fix).

Done it! the Out Function is fine. There where three errors in my Emulation of the Z80 code.

Sometimes it pays to have a break and walk away, and then come back to the code. As i found some silly errors quite quickly after a good walk and some fresh air  ^-^

id not emulated the RL E properly which just need doubling and the Exchange EX AF,AF_ was getting the same value and there was an extra XOR 24 ie on off of the speaker that wasn't needed  ;D in one off the loops.

I hope the wrapper will be able to play faster sounds than 100 mSecs as some sounds arnt being played quick enough i think. ie not heard.
Ive noticed with this tune a slight ringing? At the end of every note.

So here is executable code to play JetSet Willy. Midnight sonata by Beethoven :-


' JetSet Willy Player Using BlitzMax 1.5

SuperStrict

Graphics 800,600

Global RunSpeed:Int=16 ' Using Blitzmax 1.5 Gives roughly a 50Hz Frame Delay!

' Registers 16 Bit
Global BC:Short, DE:Short, HL:Short

' Registers 8 Bit
Global A:Byte, B:Byte, C:Byte, D:Byte, E:Byte, H:Byte, L:Byte

' Alternate 8 Bit
Global A_:Byte

' Control Registers
Global TCycles:Int
Global fZERO:Byte
Global ENTER:Byte=0
Global Counter:Int

' Temp Values
Global TempA:Byte
Global Ans:Short

' Music Data
Global MusicData:Int[] = [81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,76,60,51,76,60,51,76,57,45,76,57,45,81,64,45,81,60,51,81,60,54,91,64,54,102,81,60,81,60,51,81,60,51,40,60,40,40,54,45,81,54,45,81,54,45,40,54,40,40,60,51,81,60,51,38,60,45,76,60,45,40,64,51,81,64,51,45,64,54,32,64,54,61,121,61,255]
Print
Print "Length of MusicData = "+(Len MusicData)

Init()

Function MainLoop()
    Local starttime:Int = MilliSecs()
   
    While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)
       
Run()
        Local endtime:Int = MilliSecs()
        Local diff:Int = endtime - starttime
       
        Local pausedelay:Int = RunSPEED ' About 50Hz ie one TV frame

        If pausedelay > 0 Then
            Delay(pausedelay)
        Else
            pausedelay = 0
        End If
        starttime = endtime+pausedelay
    Wend
End Function

Function Call_38562:Byte()

While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)

' LD A,(HL) '  Get next Byte of Tune Data from 34299. In this case 0
A=MusicData[HL]  ; TCycles:+19

' CP 255 '  Has the tune finished?
If (A-255)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7

' RET Z         ' Return if ZERO flag is Set
If fZERO=1 Then
TCycles:+11 ; Return fZERO ' Exit Function End of Tune
Else
TCycles:+5
End If

' LD BC,100 ' Load BC with 100. B=0 (short note Counter). C=100 (Short Note Counter)
B=0 ; C=100 ; BC=(B Shl 8)+C ; TCycles:+10

' XOR A         ' XOR A  Equivalent to LOAD A with 0 ( Incerting a delay of 4 TCycles )
A=(A ~ A) ; TCycles:+4 ' Border Colour and Speaker state

' LD E,(HL) ' Load E with byte of MusicData During the short note loop
E=MusicData[HL] ; DE=(D Shl 8)+E ; TCycles:+19

' LD D,E ' Load D with (Pitch delay counter)
D=E ; DE=(D Shl 8)+E ; TCycles:+4
'
Repeat ' C is OUTER LOOP Until C=0
' Part of the DJNZ
'
Repeat ' B is iner LOOP until B=0
'Print "B="+B
'
' OUT (254),A ' Produce a short note ( 0.003s ) Whose pitch is determined by E
OUT_254(254,A) ; TCycles:+11

' DEC D ' Decrease D by One
D:-1 ; DE=(D Shl 8)+E ; TCycles:+4
If D=0 Then fZERO=1 Else fZERO=0 ' If D=0 then set Zero flag

' JR NZ,38580
If fZERO=1 Then
' LD D,E
D=E ; DE=(D Shl 8)+E ; TCycles:+19
' XOR 24
A=(A ~ 24)
TCycles:+7 ' Condition not met
Else
TCycles:+12 ' Condition met
End If

' DJNZ
If B<>0 Then 
TCycles:+13 ' Condition met
Else
TCycles:+8 ' Condition not met
End If
B:-1 ; BC=(B Shl 8)+C
'
Until (B=0) ' B=0

' EX AF,AF_ ' Exchange AF with AF_ Alternate registers
'Print "A Before exchange ="+A
TempA=A_ ; A_=A ; A=TempA ; TCycles:+7 ' For this emulation focus on A
'Print "A after exchange ="+A

' LD A,C   ' Load A with C
A=C ; TCycles:+4

' CP 50 '  Is the short note counter down to 50 yet?
If (A-50)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7

' JR NZ,38590 ' Jump if not otherwise
If fZERO=1 Then
' RL E ' Double value in E ' Rotating left is same as * by 2
Print "E="+E
E:+E ; DE=(D Shl 8)+E
Print "E Doubled ="+E
TCycles:+7 ' Condition not met
Else
TCycles:+12 ' Condition met
End If

' EX AF,AF_ ' Exchange AF with AF_ Alternate registers
TempA=A_ ; A_=A ; A=TempA ; TCycles:+7

' DEC C ' Decrease C by one
C:-1 ; BC=(B Shl 8)+C ; TCycles:+4
If C=0 Then fZERO=1 Else fZERO=0 ' If C=0 then set Zero flag

' JR NZ ' Jump back until we've finished playing 50 notes
'
Until (C=0) ' C=0

' Check KEYBOARD and JOYSTICK
CALL_38601() ; TCycles:+17 ' Check wether Enter or Fire is being pressed Code ignored but timing needed!

' RET NZ     ' Return if ZERO flag is ReSet, if it is
If fZERO=0 Then
TCycles:+11 ; Return fZERO ' Enter or fire was pressed
Else
TCycles:+5
End If

' INC HL ' Increase HL by one
HL:+1 ; TCycles:+10

' JR 38562
TCycles:+12
Wend
End Function

' Check Wether ENTER Or FIRE is being pressed
Function Call_38601:Byte()
Print "Checking Keyboard"
' LD A,(34254)
A=0 ; TCycles:+13 ' Load A with Peek(34254). This Value will be 0 for this Purpose

' OR A ' Is Joystick Connected
A=(A | A)
'Print "A is "+A ; Repeat Until KeyDown(Key_SPACE)
If A=0 Then fZERO=1 Else fZERO=0 ; TCycles:+4
'Print "GOT HERE!" ' For Testing Key Press FLAG Loop!

' JR Z,38612 ' Jump Forwrad if not
If fZERO=0 Then
' IN A,(31) ' Kempston Joystick PORT
A=0 ; TCycles:+11 ' Assume FIRE is not being pressed, So we PASS back a 0!
' BIT 4,A ' Was FIRE pressed?
Local Ans:Byte
Ans=(16 & A) ' Test Bit 4 ie, 2^4=16
If Ans=0 Then fZERO=1 Else fZERO=0 ; TCycles:+8
' RET NZ ' Return the ZERO FLAG if FIRE Pressed, Which it won't be
If fZERO=1 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If
End If
' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!

' LD BC,45054 ' Load BC ready to Read the Key Buffer for Keys_H_to_ENT and 6-7-8-9-0
BC=45054 ; TCycles:+10
' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!

' IN A,(C)
If KeyDown(Key_ENTER) Then
A=254 ; ENTER=1
'Print "A is "+A
'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
Else
A=255 ; ENTER=0
'Print "A is "+A
'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
End If
TCycles:+11 ' if ENTER then A=254 else A=255

' AND 1
A=(A & 1)
If A=0 Then fZERO=1 Else fZERO=0
TCycles:+7

' CP 1 '  Has ENTER been pressed?
If (A-1)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7

' RET
TCycles:+10
End Function

Function OUT_254(Port:Byte, Value:Byte)
' The three lines of code speed the song up ie Twice the speed!
'Global SwapIt:Byte
    'SwapIt=1-SwapIt
    'If SwapIt=1 Return
'
        Const SAMPLE_RATE:Short=44100
        Global NextSample:TAudioSample, PlayTime:Int
        If Counter=0
                Print "Sample created"
                NextSample = CreateAudioSample(4410, SAMPLE_RATE, SF_MONO8 )
Print
Print "HL="+(RSet(HL, 3).Replace(" ", "0"))+" ... MusicData[HL]="+(RSet$(musicDATA[HL],3).Replace(" ","0"))+" ... A Register="+(RSet$(A,3).Replace(" ","0"))+" ... BIT4="+((A & 16) <> 0)
Print "OUT_254 Called!"
        EndIf

        Select Value & 8
                Case 0
                        NextSample.Samples[Counter]=50
                Case 8
                        NextSample.Samples[Counter]=200
        End Select
        Counter=Counter + 1
        If counter=4408
                NextSample.Samples[4408]=0     
                'NextSample.Samples[4409]=0     
                Local Audio:TSound = LoadSound( NextSample )
                Repeat
                  ' Delay 1
                Until PlayTime<MilliSecs()
                Print "play TSound"
                PlayTime=MilliSecs()+100
                PlaySound Audio
                Counter=0
        EndIf
End Function

Function Init()
' Reset Registers
A=0 ; B=0 ; C=0 ; D=0 ; E=0 ; A_=0 ; TempA=0
' START SpecBlitz
MainLoop()
End Function

Function run()
Call_38562()
If fZERO=1 And Enter=0 Then
Print
Print "TUNE FINISHED"
Print "HL="+HL+" ... MusicData[HL]="+musicDATA[HL]
End
End If
'Print Key_Enter
If ENTER=1 Then Print "ENTER was Pressed!" ; End
EndFunction


Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on April 03, 2021, 18:15:20
you can simply speed up if you reduce the value of C here:
' LD BC,100 ' Load BC with 100. B=0 (short note Counter). C=100 (Short Note Counter)
B=0 ; C=30 ; BC=(B Shl 8)+C ; TCycles:+10


I changed C=100 to C=30

Try also 10 or 5 or 3 to see, how special effects can be made....
did you already succeed in motor sound, etc...?

by the way...

here is the update OnlyPlayer-Version for testing 1Byte-Song-Data:
Code (BlitzMax) Select
    SuperStrict
     
    Graphics 800,600
   
Global Data:Int[] = [81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,81,60,51,76,60,51,76,60,51,76,57,45,76,57,45,81,64,45,81,60,51,81,60,54,91,64,54,102,81,60,81,60,51,81,60,51,40,60,40,40,54,45,81,54,45,81,54,45,40,54,40,40,60,51,81,60,51,38,60,45,76,60,45,40,64,51,81,64,51,45,64,54,32,64,54,61,121,61,255]

    Global NextTime%, ReadPos%
     
     
    Repeat
            Wait
            If Data[ReadPos]=255 End
    Until AppTerminate()
     
     
    Function Wait()
            If NextTime<MilliSecs()
                    Print ReadPos + " " + data[ReadPos] + " " + data[ReadPos+1] + " " + data[ReadPos+2]
Local duration%=100
                    NextTime=MilliSecs()+Duration
                   MakeSound Duration, Data[ReadPos], 0
     
                    ReadPos=ReadPos+1
            EndIf 
    End Function
     
     
    Function MakeSound(Duration%,PulseA%, PulseB%)
            Const SAMPLERATE% = 22000'Hz
            Local SampleLen%, Half%
            Local SampleA:TAudioSample, SampleB:TAudioSample, AudioA:TSound, AudioB:TSound
           
            SampleLen = SAMPLERATE*Duration/1000
            If PulseA>0   
                            SampleA = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )
                            Half = PulseA/2
                            For Local n:Int=0 Until SampleLen-1
                                    If (n Mod PulseA) < half
                                            SampleA.samples[n] = 200
                                    Else
                                            SampleA.samples[n] = 55
                                    End If
                            Next
                            AudioA = LoadSound( SampleA )
            EndIf
            If PulseB>0           
                            SampleB = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )         
                            Half = PulseB/2
                            For Local n:Int=0 Until SampleLen-1
                                    If (n Mod PulseB)<half
                                            SampleB.samples[n] = 200
                                    Else
                                            SampleB.samples[n] = 55
                                    End If
                            Next           
                            AudioB = LoadSound( SampleB )
            EndIf
            If PulseA>0   
                    PlaySound AudioA
            EndIf
            If PulseB>0
                    PlaySound AudioB
            EndIf
    End Function
Title: Re: Creating ZX SOUND
Post by: Baggey on April 03, 2021, 19:10:01
Quote from: Midimaster on April 03, 2021, 18:15:20
did you already succeed in motor sound, etc...?

Not quite sure what you mean by "motor sound, etc...?"

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on April 04, 2021, 00:13:22
oh i wanted to know, whether the emulator also is able to play the ingame sounds like crashs or thing you can hear at the end of the youtube moonlight sonata example you sended some posts ago? (in reply #70)
Title: Re: Creating ZX SOUND
Post by: Baggey on April 04, 2021, 08:47:17
Quote from: Midimaster on April 04, 2021, 00:13:22
oh i wanted to know, whether the emulator also is able to play the ingame sounds like crashs or thing you can hear at the end of the youtube moonlight sonata example you sended some posts ago? (in reply #70)

Not all? It sometimes clicks and bashes or it plays the in game music but not all of it. It's like playing some of the notes or the sounds are to quick! Im trying to put together a mini avi file of a couple of games but don't know how to post it yet ??? Im also still ironing out some underlying bugs. The more functionality im getting the bugs are getting harder to find.

Im probably a good 98% there. And id also like to add more features to it as well.

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on July 09, 2021, 19:35:50
So, This isn't an update or anything!

Im still working on this.

Im almost 98% there with the emulator. im not good with trying to get an upload with how it's working at the moment.

Ive Found that there are undocumented NOP opCodes that no person seems to of mentioned anywhere? Only those who have sucessful Emulator's will know what im on about!

i am dry running and coming up against these's at the mo! BATMAN, COMMANDO, and Remember CHEQURED FLAG! seem to rely on these.

Sound with OUT CHUNKS no matter how i alter either works or dosent or seems to repeat it's self?

Have a week off and will be playing with various routine's!

Hopefully im almost at the point off all CODE working  8)

I would like to be able to upload an AVI for this thread to show in game sounds and emulation running if thou's in charge would let me!  :D

Oh also finding that at certain times there are write's to ROM which need to be ignored! As when an Interrupt happens that uses ROM code could be Currupted! So adding a feature to not write to any PART of memory below 16384! or just ignore the WRITE!

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on July 12, 2021, 09:40:12
So been Reading this Thread, I Havent read all of it yet.  :-X

https://www.syntaxbomb.com/worklogs/done!-a-new-audio-out-approach-in-blitzmax-freeaudio-ringbuffer/ from Reply #28 I think.

QuoteSilence at the end?
this is the dis-advantage of ring buffers. they will play the last datas of the buffer over and over again. So you have to fill the buffer in a last round with 0 to prevent this.

Now 16bit Stereo and Manipulation
Here is the next step. Now you can stream SF_STEREO16 and you can manipulate the samples by using a RAMStream. This is an executable example. use the TestC.ogg from attachment. (the poor quality you can hear is in the ogg file, not because of the algo). you can move the mouse to pan between LEFT and RIGHT.

What are the possible Chunk-times?

Any chunk time must be a possible divider of the 1000msec. f.e. 20 or 25 or 40, but not 30!
But the result must again be a possible divider of the samples frequency.

In our example the chunk-time for refreshing must stay at 20msec, because only this value is without crackling. This is caused by the fact that the prime number in 22050Hz is 441 and this a 1/50 of 22050Hz. ---> 1/50 from 100msec= 20msec.
Also a possible chunk-times is 40msec = 882samples

This means with 44100Hz you could also use 10msec, 20msec and 40msec

This means with 48000Hz or 24000Hz you are more flexible: 1, 2, 4, 5, 8, 10, 20, 25 and 40msec

My Latest Code Version of OUT_254 Implementing Midi's idea for OUT_CHUNKS

Code (BlitzMax) Select
Function OUT_254(Port:Byte, Value:Byte)

If (Port & 1) = 0 Then ZXio.Border = (Value & 7) 'Border Colour

' The three lines of code speed the song up ie Twice the speed!
'Global SwapIt:Byte
      'SwapIt=1-SwapIt
      'If SwapIt=1 Return
'
'If Port=254 Then

        Const SAMPLE_RATE:Int= 24000 ' 22050 ' 96000
Global NextSample:TAudioSample, PlayTime:Int


        If MusicCounter=0
                'Print "Sample created"
                NextSample = CreateAudioSample(883, SAMPLE_RATE, SF_mono8 )
'Print
'Print "IY="+(RSet(IY, 3).Replace(" ", "0"))+" ... MusicData[IY]="+(RSet$(musicDATA[IY+2],3).Replace(" ","0"))+" ... A Register="+(RSet$(A,3).Replace(" ","0"))+" ... BIT4="+((A & 16) <> 0)
'Print "OUT_CHUNKS CALLED!"
        End If

' This is checking EAR/MIC Bit
'Note if bit 8 is on or off this is normal volume
' if bit 16 id on or off is like the Loud button
'Value=(Value & 24)
' NextSample.Samples[MusicCounter]=0
' If Value = 16 Then NextSample.Samples[MusicCounter]=200
' If Value = 8 Then NextSample.Samples[MusicCounter]=128
'If Value = 24 Then NextSample.Samples[MusicCounter]=250
'If Port=254 Then
        'If (Value & 16) Then
         '   NextSample.Samples[MusicCounter] = 250
        'Else
         '   NextSample.Samples[MusicCounter]=0
        'End If

Local out_value:Byte=(Value & 24)
Print "Play Sound!   Out value "+out_value
       
        Select out_value
' Note the other Case values are todo with the EAR/MIC socket, Which is Effecting the Sound produced!?
              Case 0
                        NextSample.Samples[MusicCounter]=50
                Case 8
                        NextSample.Samples[MusicCounter]=200
Case 16
                        NextSample.Samples[MusicCounter]=200
Case 24
NextSample.Samples[MusicCounter]=200

'Default
' NextSample.Samples[MusicCounter]=0

        End Select

        MusicCounter:+1

        If MusicCounter=880 Then ' Why 800 or 880 Sound Samples to Sound Better?
                NextSample.Samples[881]=0         
                Local Audio:TSound = LoadSound( NextSample )
                Repeat
                   'Delay 1
                Until PlayTime<MilliSecs()
                'Print "play TSound"
                PlayTime=MilliSecs()+5 ' Played with this Helps with Fast Sounds and long Sounds? Between 1 and 100 or starts to cause Emulator Lag
                PlaySound Audio
'Stopsound audio
                MusicCounter=0

        End If
'End If
End Function


In the Above CODE 22050 sounds good but changing to 24000 gives higher pitched sounds to be in the Sample. Changing to 44100 sounds Tinney? The 22050 sounds BASSIER if that's a word.

In the Case Select statements the Mic and Ear together amplify the Sound slightly by changing Volume minimally not sure how to achieve this effect.
it sort of pluse's or Amplify's the Sound heard.

If i Play Manic Miner with the Above code the in game music is sort off okay but the Title Music is Ringing/Echoing or Reapeating Awfully.

Now,

If i change the Music Counter to 4408. Title Music sort off works but then i lose the quick in game Sounds.

So Im going to Look into the RingBuffer idea as im Using BlitzMax 1.5 Vanilla.

The Emulator "SpecBlitz or BlitzSpec" is Almost Working!  8). There is a game called Movie by Imagine. FUSE runs it with Flashing RED screen and so does Spectulator. Mine Runs it Almost!

Im Currently Stopping ROM write's as this currupts Imterrupt routine's Acessing the Rom code. Also there are a Lot of Special NOP opcode's not Documented.
They appear as two byte codes but don't work in that Manner. ::)

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on July 12, 2021, 12:05:58
with the new ringbuffer approach  (starting reading from post #59 please!!!) you would use exact the same (or little slower) sample rate the old vintage computers had. The sample rate should fit to the timing rate of OUT_254()-calls.

A too high sample rate would result in pauses (or crackles), a too low sample rate would cause a little latency, when long songs are played. So better try at first slower sample rates.


The Ringbuffer-Approach needs a threaded BlitzMax build.


The commands coming in at your OUT_254() function need to be translated putten in an array, then send to the RingBuffer.Send AudioArray() function.


I will write a executable example here, which transfers the old CHUNK_OUT approach to the new RingBuffer-approach. Give me 2 days...

Title: Re: Creating ZX SOUND
Post by: Derron on July 12, 2021, 12:15:28
Quote from: Midimaster on July 12, 2021, 12:05:58
The Ringbuffer-Approach needs a threaded BlitzMax build.

the threaded stuff could be done in C (eg callbacks into blitzmax from threads) and so work in "non threaded" builds (vanilla) too.


bye
Ron
Title: Re: Creating ZX SOUND
Post by: Midimaster on July 14, 2021, 07:55:02
Ok, I adapted my FreeAudio-Ringbuffer to BlitzMax 1.50 and added some security functions, which cares about audio timing and prevent the buffer overrun.

This is how you would call the new approach:
Code (BlitzMax) Select

Import "RingBufferClassVanilla.bmx"                         ' import the BlitzMax Ringbuffer
RingBufferClass.SetDriver("FreeAudio DirectSound")          ' needs to be this driver on Windows
Global RingBuffer:RingBufferClass = New RingBufferClass     ' define the ringbuffer
RingBuffer.CreateNew(40000, SF_MONO8)                       '  40000Hz and Mono 8bit
Local WatchThread:TThread=CreateThread(WatchLoop, "")       ' define a multi-thread function

' this is the function that cares about the ringbuffer DO NOT change anything
Function  WatchLoop:Object(data:Object)
        Repeat
                Delay 3
                RingBuffer.Watch
        Forever
End Function


'This is how you would send one single sample to the ringbuffer:
RingBuffer.SendONE 200    ' sends a value of 200 to the buffer





This is how your OUT_254 function would look now:

Code (BlitzMax) Select
SuperStrict
Import "RingBufferClassVanilla.bmx"

Graphics 800,600

RingBufferClass.SetDriver("FreeAudio DirectSound")
Global RingBuffer:RingBufferClass = New RingBufferClass
RingBuffer.CreateNew(40000, SF_MONO8)
Local WatchThread:TThread=CreateThread(WatchLoop, "")

Global A:Byte, B:Byte=1, C:Byte, D:Byte=0, E:Byte=0
Global IY:Int

' Music Data
Global data%[] = [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,50,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]
Print( "DATA LENGTH: "+Len(data) )

CALL_37596()

Repeat
Delay 5
Until AppTerminate()
End


Function  WatchLoop:Object(data:Object)
        Repeat
                Delay 3
                RingBuffer.Watch
        Forever
End Function



Function CALL_37596:Int()
While True
A = data[IY]
If A=255
Return True
End If
C = A 
B = 0
A = 0
D = data[IY+1]
E = data[IY+2]
Repeat             
Repeat                     
OUT_RING_ONE(254,A)    ' <-------------- NEW APPROACH
D :- 1
If D=0
D = data[IY+1]
A = A~24 
End If
E :- 1
If E=0
E = data[IY+2]                       
End If
B :- 1 
Until B=0
C :- 1
Until C=0
IY :+ 3
Print "Step in DATA=" +  iy
Wend
End Function




Function OUT_RING_ONE(Port%, Value%)
Select Value & 8
Case 0
RingBuffer.SendONE 50
Case 8
RingBuffer.SendONE 200
End Select
End Function



And this is the new RingBufferClass for BlitzMax 1.50:


Code (BlitzMax) Select
Type RingBufferClass
' RingBufferClassVanilla.bmx
' A permant running Audio-Output using FreeAudio
' --------------------------------------------------
' Author: Midimaster at www.midimaster.com
' V1.1 2021-07-14
' see examples of use at https://www.syntaxbomb.com/index.php/topic,8377.0.html
' -------------------------------------------------------------------
' minimal example:
'   RingBufferClass.SetDriver("FreeAudio....")
'   Global RingBuffer:RingBufferClass = New RingBufferClass
'   RingBuffer.CreateNew(HERTZ, FORMAT)
'   SendSize:INT  = RingBuffer.IntervalSamples()
'   SendTime:INT = RingBuffer.IntervalTime()
'   Local WatchThread:TThread=CreateThread(WatchLoop, "")
'   Global WriteTime = MilliSecs()
'   Function SendSamples
' If WriteTime>MilliSecs() Return
' WriteTime =WriteTime + SendTime
' Local AudioArray:Int[SendSize]
' For Local i:Int=0 To SendSize-1
' AudioArray[i] = any value....
' Next
' RingBuffer.Send AudioArray
'   End Function

Global MyDriver$
Global BufferMutex:TMutex=CreateMutex()

Field CHANNELS:Int, CHUNK_SIZE:Int, BUFFER_SAMPLES:Int, BUFFER_SIZE:Int
Field FORMAT:Int, CHUNK_TIME:Int, HERTZ:Int, BITS:Int
Field WritePointer:Int, ReadPointer:Int, RingPointer:Int
Field WriteTime:Int, WatchTime:Int, InFormat%

Field RingBuffer:TAudioSample, InBuffer:TAudioSample, Sound:TSound
Field RingStream:TStream, InBufferStream:TStream



Function SetDriver(Driver$)
' PUBLIC: Use this to...
' select one of the audio drivers. It needs to be FreeAudio
' on Windows needs to be FreeAudio DirectSound
If MyDriver<>"" Return
If Driver.contains("FreeAudio")=False
Notify "wrong AudioDriver"
End
EndIf
MyDriver = Driver
SetAudioDriver(MyDriver)
End Function



Method CreateNew(Hertz%, UserFormat%=SF_STEREO16LE , Latency%=8)
' PUBLIC: Use this to define the Ringbuffer...
' HERTZ should be a multiple of 1000 for CHUNK_TIME=10, 20, 40 or 50
' HERTZ can also be 44100 when CHUNK_TIME=20 or 40
'
' UserFormat can be SF_MONO8 or SF_STEREO8 or SF_MONO16LE or SF_STEREO16LE
'
' LATENCY can be from 1 to 32
' 2=extrem small, 4=normal size,  8-32..=secure sizes
'
If MyDriver=""
Notify "No AudioDriver selected"
End
EndIf
Self.HERTZ=Hertz
Self.FORMAT=SF_STEREO16LE
Self.InFormat=UserFormat
DefineBuffer Latency
ClearBuffer
WatchTime=MilliSecs()
PlaySound Sound
End Method



Method IntervalSamples:Int()
' PUBLIC: Use this to...
' inform how many samples you should send each call
If  (InFormat=SF_MONO8) Or (InFormat=SF_MONO16LE)
Return CHUNK_SIZE/4
Else
Return CHUNK_SIZE/2
EndIf
End Method



Method IntervalTime:Int()
' PUBLIC: Use this to...
' inform how long you should wait between calls (in msecs)
Return CHUNK_TIME
End Method




Method SendOne(Value:Int)
' PUBLIC: Use this to...
' send one single sample value to the ringbuffer
Global ShortCollector:Short[IntervalSamples()*2]
    Global CollektorCounter:Int

Select InFormat
Case SF_MONO8
Value=(Value-128)*128
ShortCollector[CollektorCounter]=Value
ShortCollector[CollektorCounter+1]=Value
CollektorCounter=CollektorCounter+2
Case SF_STEREO8
Value=(Value-128)*128
ShortCollector[CollektorCounter]=Value
CollektorCounter=CollektorCounter+1
Case SF_MONO16LE
ShortCollector[CollektorCounter]=Value
ShortCollector[CollektorCounter+1]=Value
CollektorCounter=CollektorCounter+2
Case SF_STEREO16LE
ShortCollector[CollektorCounter]=Value
CollektorCounter=CollektorCounter+1
End Select
If CollektorCounter=IntervalSamples()*2
CheckBufferOverRun
Transfer ShortCollector
          CollektorCounter=0
EndIf
End Method




Method Send(AudioArray:Int[])
' PUBLIC:  Use this to...
' send a couple of samples value to the ringbuffer
Local ShortArray:Short[]
Local i%, v%
Select InFormat
Case SF_MONO8
ShortArray= New Short[AudioArray.Length*2]
For i=0 To AudioArray.Length-1
v=AudioArray[i]
V=V-128
ShortArray[2*i]=v*128
ShortArray[2*i+1]=v*128
Next
Case SF_STEREO8
ShortArray= New Short[AudioArray.Length]
For i=0 To AudioArray.Length-1
v=AudioArray[i]
V=V-128
ShortArray[i]=v*128
Next
Case SF_MONO16LE
ShortArray= New Short[AudioArray.Length*2]
For i=0 To AudioArray.Length-1
ShortArray[2*i]=AudioArray[i]
ShortArray[2*i+1]=AudioArray[i]
Next
Case SF_STEREO16LE
ShortArray= New Short[AudioArray.Length]
For i=0 To AudioArray.Length-1
ShortArray[i]=AudioArray[i]
Next
End Select
CheckBufferOverRun
Transfer ShortArray
End Method


'
'   E N D   O F   T H E   P U B L I C   F U N C T I O N S
'
' ***************************************************************************
'
'    I N T E R N A L   F U N C T I O N S :

Method Watch()
' private: called by the thread WatchLoop(), cares about transfering ringbuffer content to the audio device
If WatchTime<MilliSecs()
WatchTime = WatchTime + CHUNK_TIME
SendOneChunk
EndIf
End Method

Method DefineBuffer(Latency%)
' private
CHUNK_TIME=20
BITS=16
CHANNELS=2
CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
BUFFER_SAMPLES = 4*Latency * HERTZ * CHUNK_TIME             /1000
BUFFER_SIZE    = BUFFER_SAMPLES * BITS/8 *CHANNELS

RingBuffer     = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
InBuffer       = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
Local fa_sound:Int  =  fa_CreateSound( BUFFER_SAMPLES-1, BITS, CHANNELS, HERTZ, RingBuffer.Samples, $80000000 )
Sound          = TFreeAudioSound.CreateWithSound( fa_sound, Null)
RingStream     = CreateRamStream(RingBuffer.Samples , Buffer_size,True,True)
InBufferStream = CreateRamStream(InBuffer.Samples,Buffer_size,True,True)
RingPointer    =  BUFFER_SIZE/4
End Method

Method SendOneChunk()
' private
LockMutex BufferMutex
Local ReadPointerMod% = ReadPointer Mod BUFFER_SIZE
InBufferStream.Seek ReadPointerMod
RingStream.Seek RingPointer
Local i%
If ReadPointer + CHUNK_SIZE > WritePointer
Local Maxi%=WritePointer-ReadPointer
For i = 0 To Maxi-1
RingStream.WriteByte InBufferStream.ReadByte()
Next
For i = Maxi To CHUNK_SIZE-1
RingStream.WriteByte 0
Next
ReadPointer=ReadPointer + Maxi
Else
For i = 0 To CHUNK_SIZE-1
RingStream.WriteByte InBufferStream.ReadByte()
Next
ReadPointer=ReadPointer + CHUNK_SIZE
EndIf
RingPointer=(RingPointer + CHUNK_SIZE) Mod (BUFFER_SIZE)
UnlockMutex BufferMutex
End Method

Method ClearBuffer()
' private
For Local i:Int =0 To BUFFER_SIZE
RingBuffer.Samples[i]=0
Next
End Method

Method Transfer(ShortArray:Short[])
' private
LockMutex BufferMutex
Local WritePointerMod% = WritePointer Mod BUFFER_SIZE
InBufferStream.Seek WritePointerMod
   Local i%
If ShortArray.Length*2 + (WritePointerMod) <= BUFFER_SIZE
For i=0 To ShortArray.Length-1
InBufferStream.WriteShort ShortArray[i]
Next
Else

Local Maxi% = BUFFER_SIZE-WritePointerMod

For i=0 To Maxi/2-1
InBufferStream.WriteShort ShortArray[i]
Next
InBufferStream.Seek 0
For i=Maxi/2 To ShortArray.Length-1
InBufferStream.WriteShort ShortArray[i]
Next
EndIf
WritePointer=WritePointer + ShortArray.Length*2

UnlockMutex BufferMutex
End Method

Method CheckBufferOverRun()
' private  cares about buffer overruns and report if you send to fast
Local grade%
Local diff%=WritePointer-Readpointer
diff=diff*100/BUFFER_SIZE

If diff>80
Print "RINGBUFFER: Prevent Buffer Overrun! Wait for " + IntervalTime() + "msec"

Delay IntervalTime()
'Print WritePointer + " " + Readpointer + " " + (WritePointer-Readpointer) + " " + BUFFER_SIZE
'Print diff + "%"
EndIf
End Method
End Type

Title: Re: Creating ZX SOUND
Post by: Baggey on July 14, 2021, 09:06:17
Oh my! Using Blitzmax 1.5

Ive Saved the RingBufferClass to  "C:\Blitzmax\tmp\RingBufferClassVanilla.bmx"

Ive Locked as a build file "C:\Blitzmax\tmp\Music.bmx"

When i Compile i get theses errors from the Output window?

Building Music.tmp
Compiling:RingBufferClassVanilla.bmx
Compile Error: Identifier 'TMutex' not found
[C:/BlitzMax/tmp/RingBufferClassVanilla.bmx;29;13]
Build Error: failed to compile C:/BlitzMax/tmp/RingBufferClassVanilla.bmx
Process complete

Do i need some other files? as Identifier Tmutex isnt there.  :-[

Kind Regards Baggey



Title: Re: Creating ZX SOUND
Post by: Midimaster on July 14, 2021, 09:20:04
you have to build as "Threaded Build", because the Ringbuffer uses MultiThreading
Title: Re: Creating ZX SOUND
Post by: Baggey on July 14, 2021, 09:32:16
Quote from: Midimaster on July 14, 2021, 09:20:04
you have to build as "Threaded Build", because the Ringbuffer uses MultiThreading

Okay havent got a Clue what Multithreading is  :o

Ive Selected 'Program/Buid Options and Checked the "Threaded Build" also still have "Quick Build" Checked as usual.

It Works!  8)

So need to Play with this now as the Music Loop being played hasn't got all the Z80 Emulation behind it. It Should Sound "Screechy"
Thankyou so much for this new Approach. I shall in due course adapt to the Emulator and see how this effects music and in game bullets and jumps etc...  ;D

Kind Regards Baggey

Title: Re: Creating ZX SOUND
Post by: Baggey on July 14, 2021, 20:30:45
Thanks to MidiMaster,

I have been playing around, with a new way of Playing ZX Sound's using "Mini Z80 MachineCode Emulator" to send Bytes to MidiMasters "RingBuffer".

Note, The Z80 Mock up code is the full Machine Code Routine in Manic Miner. So we can hear the Cllasic Screechy! 'Manic Miner' Sound as origonaly Played/heard. 8)

This is Executable Code in Blitzmax 1.5 A.K.A "Vanilla" You will need to use the drop down Menu  Program-Build Options- and Check/Tick "Threaded Build" to compile.

The "RingBufferClassVanilla.bmx" file needs to be in the same Folder as this Code.

Code (Blitzmax) Select
' ManicMiner Player Using BlitzMax 1.5

SuperStrict

' Add Code from the RingBufferClass
Import "RingBufferClassVanilla.bmx"

Graphics 800,600

' MidiMasters Stuff
' RingBufferClass Stuff
RingBufferClass.SetDriver("FreeAudio DirectSound")
  Global RingBuffer:RingBufferClass = New RingBufferClass
  RingBuffer.CreateNew(40000, SF_MONO8)
  Local WatchThread:TThread=CreateThread(WatchLoop, "")
' RingBufferClass Stuff

' RingBufferClass Functions
Function OUT_RING_ONE(Port%, Value%)
            Select Value & 8
                    Case 0
                            RingBuffer.SendONE 50
                    Case 8
                            RingBuffer.SendONE 200
            End Select
End Function

Function  WatchLoop:Object(data:Object)
            Repeat
                    Delay 3
                    RingBuffer.Watch
            Forever
End Function
' RingBufferClass Functions
' MidiMasters Stuff END

Global RunSpeed:Int=16 ' Using Blitzmax 1.5 Gives roughly a 50Hz Frame Delay!

' Registers 16 Bit
Global IY:Short, BC:Short, DE:Short, HL:Short

' Registers 8 Bit
Global A:Byte, B:Byte, C:Byte, D:Byte, E:Byte, H:Byte, L:Byte

' Control Registers
Global TCycles:Int
Global fZERO:Byte
Global ENTER:Byte=0
Global Counter:Int

' Music Data
Global MusicData:Int[] = [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,50,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]
Print
Print "Length of MusicData = "+(Len MusicData)

Init()

Function MainLoop()
    Local starttime:Int = MilliSecs()
   
    While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)
       
Run()
        Local endtime:Int = MilliSecs()
        Local diff:Int = endtime - starttime
       
        Local pausedelay:Int = RunSPEED ' About 50Hz ie one TV frame

        If pausedelay > 0 Then
            Delay(pausedelay)
        Else
            pausedelay = 0
        End If
        starttime = endtime+pausedelay
    Wend
End Function

Function Call_37596:Byte()

While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)

' LD A,(IY+0) '  Get next Byte of Tune Data from 33902
A=MusicData[IY]  ; TCycles:+19
' CP 255 '  Has the tune finished?
If (A-255)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7
' RET Z         ' Return if ZERO flag is Set
If fZERO=1 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If
' LD C,A ' Copy the first byte of MusicData for this note (which determines the duration) into C
C=A ; BC=(B Shl 8)+C ; TCycles:+4
' LD B,0 ' Load B with 0, which will be used as a delay counter in the note-producing loop
B=0 ; BC=(B Shl 8)+C ; TCycles:+7
' XOR A          ' XOR A  Equivalent to LOAD A with 0 ( Incerting a delay of 4 TCycles )
A=(A ~ A) ; TCycles:+4
' LD D,(IY+1) ' Get second byte of MusicData for this note
D=MusicData[IY+1] ; DE=(D Shl 8)+E ; TCycles:+19
' LD A,D ' Load A with D
A=D ; TCycles:+4
'
' FIRST CALL
'
' CALL 37675 ' Update on screen piano Key
TCycles:+17 ' 17 Clock Cycles for the call
' Code there is irelevant, but time must br acounted for!
' LD (HL),80   ' Set the Atribute colour of piano key 80 (INK 0: PAPER 2: BRIGHT 1)
TCycles:+10  ' 10 Clock Cycles for the LD(HL),40
' LD E,(IY+2) ' Get Third byte of MusicData for this note
E=MusicData[IY+2] ; D=(D Shl 8)+E ; TCycles:+19
' LD A,E   ' Load A with E
A=E ; TCycles:+4
'
' SECOND CALL
'
' CALL 37675 ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
A=Call_37675() ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
TCycles:+17 ' 17 Clock Cycles for the call
' Code there is irelevant, but time must br acounted for!
' LD (HL),40
TCycles:+10  ' 10 Clock Cycles for the LD(HL),40
Repeat ' C LOOP until C=0
' Part of the DJNZ
Repeat ' B LOOP until B=0
'For Local DJNZ:Int=B To 0 Step -1 ' until B=0
'Print "B="+B
OUT_RING_ONE(254,A) ; TCycles:+11
' DEC D ' Decrease D by One
D:-1 ; DE=(D Shl 8)+E ; TCycles:+4
If D=0 Then fZERO=1 Else fZERO=0 ' If D=0 then set Zero flag
' JR NZ,37634
If fZERO=1 Then
' LD D,(IY+1)
D = MusicData[IY+1] ; DE=(D Shl 8)+E ; TCycles:+19
' XOR 24
A = (A ~ 24)
TCycles:+7 ' Condition not met
Else
TCycles:+12 ' Condition met
End If
' DEC E
E:-1 ; DE=(D Shl 8)+E ; TCycles:+4
If E=0 Then fZERO=1 Else fZERO=0 ' If E=0 then set Zero flag
' JR NZ,37642
If fZERO=1 Then
' LD E,(IY+1)
E = MusicData[IY+2] ; TCycles:+19
' XOR 24
A = (A ~ 24)
TCycles:+7 ' Condition not met
Else
TCycles:+12 ' Condition met
End If
' DJNZ
'DisJump=(twosum[PC1]+2) ; B:-1 ; BC=(B Shl 8)+C
If B<>0 Then 
TCycles:+13 ' Condition met
Else
TCycles:+8 ' Condition not met
End If
B:-1 ; BC=(B Shl 8)+C
'
Until (B=0) ' B=0
' DEC C ' Decrease C by one
C:-1 ; BC=(B Shl 8)+C ; TCycles:+4
If C=0 Then fZERO=1 Else fZERO=0 ' If E=0 then set Zero flag
Until (fZERO=1)
' Check KEYBOARD and JOYSTICK
CALL_37687() ; TCycles:+17 ' Check wether Enter or Fire is being pressed Code ignored but timing needed!
' RET NZ          ' Return if ZERO flag is ReSet, if it is
If fZERO=0 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If
' Pick up Keys AGAIN! This time we are Painting the keys WHITE Updating them
' Code is irrelevant but the timming is!
' LD A,(IY+1) '  Get 2nd Byte of Tune Data from 33902
A=MusicData[IY+1]  ; TCycles:+19
' CALL 37675 ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
A=Call_37675() ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
TCycles:+17 ' 17 Clock Cycles for the call
' Code there is irelevant, but time must be acounted for!
' LD (HL),56
TCycles:+10  ' 10 Clock Cycles for the LD(HL),40. Paints Piano key WHITE
' LD A,(IY+2) '  Get 3rd Byte of Tune Data from 33902
A=MusicData[IY+2]  ; TCycles:+19
' CALL 37675 ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
A=Call_37675() ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
TCycles:+17 ' 17 Clock Cycles for the call
' Code there is irelevant, but time must be acounted for!
' LD (HL),56
TCycles:+10  ' 10 Clock Cycles for the LD(HL),40
' INC IY ' Increase IY by one
IY:+1 ; TCycles:+10
' INC IY ' Increase IY by one
IY:+1 ; TCycles:+10
' INC IY ' Increase IY by one
IY:+1 ; TCycles:+10
' JR 37596
TCycles:+12
Wend
End Function

' Check Wether ENTER Or FIRE is being pressed
Function Call_37687:Byte()
Print "Checking Keyboard"
' LD A,(33881)
A=0 ; TCycles:+13 ' Load A with Peek(33881). This Value will be 0 for this Purpose
' OR A ' Is Joystick Connected
A=(A | A)
'Print "A is "+A ; Repeat Until KeyDown(Key_SPACE)
If A=0 Then fZERO=1 Else fZERO=0 ; TCycles:+4
'Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
' JR Z,37698 ' Jump Forwrad if not
If fZERO=0 Then
' IN A,(31) ' Kempston Joystick PORT
A=0 ; TCycles:+11 ' Assume FIRE is not being pressed, So we PASS back a 0!
' BIT 4,A ' Was FIRE pressed?
Local Ans:Byte
Ans=(16 & A) ' Test Bit 4 ie, 2^4=16
If Ans=0 Then fZERO=1 Else fZERO=0 ; TCycles:+8
' RET NZ ' Return the ZERO FLAG if FIRE Pressed, Which it won't be
If fZERO=1 Then
TCycles:+11 ; Return fZERO
Else
TCycles:+5
End If
End If
' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
' LD BC,49140 ' Load BC ready to Read the Key Buffer for Keys_H_to_ENT
BC=49150 ; TCycles:+10
' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
' IN A,(C)
If KeyDown(Key_ENTER) Then
A=254 ; ENTER=1
'Print "A is "+A
'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
Else
A=255 ; ENTER=0
'Print "A is "+A
'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
End If
TCycles:+11 ' if ENTER then A=254 else A=255
' AND 1
A=(A & 1)
If A=0 Then fZERO=1 Else fZERO=0
TCycles:+7
' CP 1 '  Has ENTER been pressed?
If (A-1)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7
' RET
TCycles:+10
End Function

' Update Keyboard Piano key and Value of A
Function Call_37675:Byte()
' SUB 8
A=A-8 ; TCycles:+7
Local TempCARRY:Byte
' RRCA
TempCARRY=(A & 1) <> 0
If TempCARRY=0 Then A=(A Shr 1) Else A=((A Shr 1) | 128)
TCycles:+4
' RRCA
TempCARRY=(A & 1) <> 0
If TempCARRY=0 Then A=(A Shr 1) Else A=((A Shr 1) | 128)
TCycles:+4
' RRCA
TempCARRY:Byte=(A & 1) <> 0
If TempCARRY=0 Then A=(A Shr 1) Else A=((A Shr 1) | 128)
TCycles:+4
' CPL
A=(A ~ 255) ; TCycles:+4
' OR 224
A=(A | 224) ; TCycles:+7
' LD L,A
L=A ; TCycles:+4
' LD H,89
H=89 ; TCycles:+7
' RET
TCycles:+10 ; Return A
End Function

Function Init()
' Reset Registers
IY=0 ; A=0 ; B=0 ; C=0
' START SpecBlitz
MainLoop()
End Function

Function run()
Call_37596()
If fZERO=1 And Enter=0 Then
Print
Print "TUNE FINISHED"
Print "IY="+IY+" ... MusicData[IY]="+musicDATA[IY]
End
End If
'Print Key_Enter
If ENTER=1 Then Print "ENTER was Pressed!" ; End
EndFunction


So Hopefully This is a Much quicker way of playing Sounds So in Game Jigs, Explosions, Bullets, and really Quick sound Effects will work now!
Not just for Manic Miner but for all Beeper Sound produced by the Piezo Speaker in the ZX Spectrum. Hopefully this will progress to the AY Chip It's self. ::)

Next Step to Integrate this Mock up Code into "SpecBlitz" now! Finggers crossed.  :D

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on July 15, 2021, 05:24:30
You need not to stay at the 40.000Hz and can also adjust them in the definition...
  RingBuffer.CreateNew(40000, SF_MONO8)
... to reach the original pitch of the vintage computers.

I also see, that you alreay added a counter TCycles for simulating the processor speed. If this is adjusted correct and works also the RingBuffer should work without using its "Emergency Break"
Code (BlitzMax) Select
Method CheckBufferOverRun()
' private  cares about buffer overruns and report if you send to fast
Local grade%
Local diff%=WritePointer-Readpointer
diff=diff*100/BUFFER_SIZE

If diff>80
Print "RINGBUFFER: Prevent Buffer Overrun! Wait for " + IntervalTime() + "msec"

Delay IntervalTime()
'Print WritePointer + " " + Readpointer + " " + (WritePointer-Readpointer) + " " + BUFFER_SIZE
'Print diff + "%"
EndIf
End Method


Please have a look on the DEBUG window. Whenever you see the notice..
RINGBUFFER: Prevent Buffer Overrun! Wait for 20 msec
...you are feeding the Ringbuffer to fast.

Title: Re: Creating ZX SOUND
Post by: Baggey on July 16, 2021, 17:59:37
I am getting more sucess with the RingBuffer. I have theme tunes working. ie Game Music and in Game Jigs.

However, Not all explosions or bullets are heard and some in game music seems like some of the byte's being sent get lost or missed somehow?

I see you've mentioned writing to the buffer to fast can happen. I supouse this could be why in game bullets and explosions aren't being recognized.

I am getting this message sometimes. RINGBUFFER: Prevent "Buffer Overrun! Wait for 20 msec"

Ive already tried ajusting the 40Khz down to 22,100Hz but then emulation runs very slow and laggy.

Im going to try and not use the CheckBufferOverRun() Function and see what effect that has.

Ive created a you tube channel last night  :o  and waiting for confirmation to use it i think  ::)

I want to try and post a video to demonstrate what's happening with the Sound when i run full throttle and at FullScreen 3.5Mhz speed.

Im also still trying to iron out some rather stuborn bugs as well!

Hopefully soon  :o

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on July 16, 2021, 18:39:22
The datas were written in the 70th exactly for the speed of the vintage hardware. If you now run your emulator on a extrem fast computer you have to care about the simulation of the vintage processor speed.

A first sign for you, that your emulator runs to fast is now my message "Prevent Buffer Overrun! Wait for 20 msec"

When you see this you can be sure, that your simulator runs to fast compared with the vintage original hardware. In a perfect tuned simulator you would not see this message.



Reducing the kHz now only has the effect, that the songs sound lower. First try to find the best fitting kHz (39kHz or 38kHz or ...) and then have a look on the overrun messages.

With the new overrun protection no sounds can be lost. Because the simulator would prefer waiting  for 20msec instead of loosing datas. Switching of the overrun protection serves nothing: Sounds will get lost, because the problem of the to high simulator speed is still there.

Could you show the code where you reduce the processor speed to the vintage speed. Or is this secret? I'm sure you forgot some TCycles-commands. What about this NOPs?

3.5Mhz would mean 3500 TCyles per millisecs. but are you sure that all 3500000 TCycles had really been avaiable for the vintage computers in those days too? Maybe the system needed 10%-30% of the performance also when a game was running?






Title: Re: Creating ZX SOUND
Post by: iWasAdam on July 16, 2021, 18:51:30
Quote
I am getting this message sometimes. RINGBUFFER: Prevent "Buffer Overrun! Wait for 20 msec"
Ive already tried ajusting the 40Khz down to 22,100Hz but then emulation runs very slow and laggy.
Im going to try and not use the CheckBufferOverRun() Function and see what effect that has.

That would suggest something is wrong with the ring buffer. Plus also the code itself might be doing something funky.

As an example a general ringbuffer should support up to 32 channels at the same time with no slowdown!

if you look at the spectrum, the sound is just a simple square wave.

The speed of your system should have no bearing as you should be syncing into the reatime clock and working out timing from there.

if you are looking at the AY chip then you will need to do some serious research into FM synthesis.

Another thing to think about is separating the sound system from the main code in it's own thread system. That way it wont ever slow things down unless you are doing something very odd...
Title: Re: Creating ZX SOUND
Post by: Baggey on July 16, 2021, 21:22:06
Okay!  ???

Trying Bandicam to upload a video Currently ???

For 'Fook Sake' Don't Laugh. I feel like a 52 year old Kid trying to bash the "ROUND PEG" into the Star "HOLE"  ::) Things used to be so easy!

Nearly there. :-\ This is a 'Hobby'

Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on July 17, 2021, 00:16:42
Quote from: iWasAdam on July 16, 2021, 18:51:30
Quote
I am getting this message sometimes. RINGBUFFER: Prevent "Buffer Overrun! Wait for 20 msec"
Ive already tried ajusting the 40Khz down to 22,100Hz but then emulation runs very slow and laggy.
Im going to try and not use the CheckBufferOverRun() Function and see what effect that has.

That would suggest something is wrong with the ring buffer. Plus also the code itself might be doing something funky.

As an example a general ringbuffer should support up to 32 channels at the same time with no slowdown!

if you look at the spectrum, the sound is just a simple square wave.

The speed of your system should have no bearing as you should be syncing into the reatime clock and working out timing from there.

if you are looking at the AY chip then you will need to do some serious research into FM synthesis.

Another thing to think about is separating the sound system from the main code in it's own thread system. That way it wont ever slow things down unless you are doing something very odd...


Quote from: iWasAdam on July 16, 2021, 18:51:30
That would suggest something is wrong with the ring buffer.

@Adam

could you please read the complete topics before you fire your comments with no context to the theme?

The FreeAudioRingbuffer feeds the Audio-Device with the (user's) given HERTZ, CHANNELS and FORMAT. If then a user fires the double amout of datas every ringbuffer gets to the point of overrun. My FreeaudioRingBuffer reports this at the state of 80% to the DEBUG window to inform the user that the source datas are fired to fast. And it waits for 20msec until the buffer is again free enough to receive the additional datas.

Baggey wants to simulate with the FreeAudioRingbuffer an old Spectrum Hardware. On the old hardware the timing was always perfect, because the hardware speed was a given limitation. If he now fires the same datas on a modern computer, then they reach 10 to 100 times faster the "soundchip". So, when he now received the message "buffer overrun" this means that his simulator timing is faster than the original vintage timing was.

His problem is not that the FreeAudioRingbuffer slows down! He does not send 32 channels but one!!!! My FreeaudioRingbuffer is 100% able to handle 32 channels with 96kHz and 4byte-FLOATs.

But he sends SF_MONO8 with 40kHz and this means only 40000  bytes per second should be sended to the RingBuffer and not 400.000 each second!!!
So he receives the helpful message "Please send slower! This are to many for the rate you decided!"

Quoteif you look at the spectrum, the sound is just a simple square wave
What do you think we are doing here? We send only SQUARE WAVES. But in the tricky way of the 70th.

Quote...Another thing to think about is separating the sound system from the main code in it's own thread system.
What do you think we are doing here? FreeAudioRingBuffer is in it's own thread system.

Sometimes READING would help!
Title: Re: Creating ZX SOUND
Post by: Baggey on July 24, 2021, 14:16:06
So Finaly a post of SpecBlitz playing "Jet Set Willy".

Cant get Full screen video up  ??? This isnt really a showcase im still trying to get Sounds to work! And further mods todo to the SpecBlitz Emulator.

There are holes in the in game tune and when an effect of jump, collect item or being Killed. Video is about 4 Mins but ive tried to capture every sound effect that should or dosent happen  ;D

Im getting buffer Overrun at the mo will try code for more precise TCycles latter  ;)

https://youtu.be/d3Jwuf4gEUM

I will indue corse get some more vids up with other game sounds trying to demonstrate where sounds are or arent working.

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on July 24, 2021, 14:56:07
MidiMaster has a new Version of the RingBuffer Version 1.2

https://www.syntaxbomb.com/worklogs/done!-a-new-audio-out-approach-in-blitzmax-freeaudio-ringbuffer/msg347049187/#msg347049187

I Shall try this new code in SpecBlitz to see if it will iron out writing to fast to the Ring Buffer.

Hopefully we may hear Sound's that are being misssed by my incorrect timing or Buffer overrun's  ;D

Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on July 24, 2021, 16:50:14
Here's a Link to Highway Encounter about 4 mins

https://youtu.be/EnEIbv9tET8

I think all sounds in this game are being Sounded but i think there are small holes in the sound. This could be due to my Timing not quite right.

Not Running RingBuffer 1.2 yet and if i need to make my Timing more acurate thats a lot of CODE to alter  :o

Noticed theres a write to 16384 which isnt aloud when watching back video! Could be a another Bug ive just found  :-X

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on July 25, 2021, 10:00:02
Quote from: Baggey on July 24, 2021, 14:16:06
There are holes in the in game tune and when an effect of jump, collect item or being Killed. Video is about 4 Mins but ive tried to capture every sound effect that should or dosent happen  ;D

Im getting buffer Overrun at the mo will try code for more precise TCycles latter  ;)

https://youtu.be/d3Jwuf4gEUM

In your audio example you can hear both possible situations with the RingBuffer. And you can als ohear how comfortable the Overrun message works.

As long as the Ringbuffer fires the Overrund-Message the sounds are perfect. This is because the Ringbuffer takes command about the corrrect timing. Your app fires the datas to fast and the fingbuffer interrupts your app and forces it to send a optimal timing. So in this situation your timing control fails and the app is sending to often packets.

The second possible situation is that your app sends datas too slow. Now the number of datas reaching the ringbuffer are to low. you can hear this in the "stuttering". This is caused by little pauses where the buffer has no more data to process and it reacts with SILENCE.

There a some possible solutions or reasons for this:

1. The buffer works to fast. Set the HERTZ lower and the buffer expects less datas.

2. The sender sends to slow. Try to send the packets more often.

3. The processor speed simulation is not perfect.
Title: Re: Creating ZX SOUND
Post by: Baggey on August 01, 2021, 07:10:03
So, Yes it's sort of a showcase but it's more about the seemingly endless task of getting Sound to WORK!

Yesterday ive altered and tweeked the Maincode and Z80 timing control of SpecBlitz. Using Manic Miner as the Example, as that is what i started with.

My game Code is Running silky smooth now without any tearing to Willy and the Baddy on screen he jumps around and there seems to be no Throteling or slowing of the game play!

Using the Latest RingBufferClass with TCycles set at 3500 per milli second and a ring buffer of 44100Hz see Output window as i change the Tcycles in realtime slightly.

Flash routine is now out of sync but it's irelavent to the main code controling the sound data being sent! I'll change this latter. Oh HALT emulation will need adjusting too but it's not used in Manic Miner.

First Video is ManicMiner running on FUSE and me Running SpecBlitz at 44100Hz at 3500TCycles per second. Fuse Title Music seems fast and the in Game Jig seems fast too. Compared to SpecBlitz Sound. In game sounds of jumping, Diying and item collecting work as you'd expect! But Silence for quick snipets of jumping, Diying and item collecting?
I think thou, the in game play are very similair. Video is Roughly 2.5mins

https://youtu.be/1xTTLXFaNM4

So, Here's the thing ive now created a video changing SpecBlitz to 9600Hz at 3500TCycles per second. Music sound seems very Laggy now but in game sounds like dying, Jumping and item collecting are trying to work. Video is Roughly 1.0min

https://youtu.be/1S36JTL52E0

I Really have no idea what is happening  :-[ The silence at 44100hz seems like i should be sending data Quicker. As im not getting Buffer Overun. And at the lower 9600Hz scale it seems like im not sending it quick enough either. But in Game Emulation seems silcky smooth now :D


Sound seems to be a tough cookie to get right!? So Anyone got an idea as to what might be happening?


Id like to move on with Future Changes to come :-
     Creating 128K emulation
     An automatic file loader
     And Finally get the AY Chip going  :o

Oh i want to create a coding window with a cross between Basic and Machinecode ie a Special Assembler so SpecBlitz can be coded in Native Machine code but using A Basic like Syntax. The idea i think will get people coding in Machine code but using an understandable Basic Syntax.

Oh, Onemore. All programs will be able to be saved and run and instantly available again if there is a CRASH!  8)

However it seems like One is not quite hitting the sweet spot in my timing maybe  :-\

Kind Regards Baggey "up the CREEK without a PADDLE" when it comes to SOUND  :))




Title: Re: Creating ZX SOUND
Post by: iWasAdam on August 01, 2021, 07:41:34
ignoring the sound...

Can you set a base cpu frequency (fast, slow, exact, etc) and get the games to run at the required speed?
Title: Re: Creating ZX SOUND
Post by: Baggey on August 01, 2021, 07:56:04
Quote from: iWasAdam on August 01, 2021, 07:41:34
ignoring the sound...

Can you set a base cpu frequency (fast, slow, exact, etc) and get the games to run at the required speed?

yes, i can. In the first video in the Output window i changed the Tcycles from 3500 to 3506 and back down to 3500 again in realtime. The in game emulation as a result runs faster or slower but smoothly.

Baggey
Title: Re: Creating ZX SOUND
Post by: iWasAdam on August 01, 2021, 08:06:18
ok, so your actual z80 code is fine and all the ula etc is being dealt with. brilliant.

next question - sound - lets just assume I know nothing about how it is currently being done.
Q. With the sound. I am assuming you are sending data to a black box (Yep I know it's some form of ring buffer), in some form of square wave?

And when you activate the black box - that's when it slows down?

Title: Re: Creating ZX SOUND
Post by: Baggey on August 01, 2021, 08:42:29
Quote from: iWasAdam on August 01, 2021, 08:06:18
ok, so your actual z80 code is fine and all the ula etc is being dealt with. brilliant.

To the best off my Knowledge the Z80 code is working as it should or it would'nt be Emulating the game. So things like Flags, video Interupts, Keyboard interupts and Machine Interupts etc are functioning. I believe i am runing at a Base Frequency as you call it of 3500TCycles per milli second As close as.

Quote from: iWasAdam on August 01, 2021, 08:06:18
next question - sound - lets just assume I know nothing about how it is currently being done.
Q. With the sound. I am assuming you are sending data to a black box (Yep I know it's some form of ring buffer), in some form of square wave?

I am sending one Byte of information. When ever the Emulator interpret's an OUT r,(254) instruction. ie, this port concern's the Mic,Ear and border value. i strip the information by anding with 24. I then have the Mic bit and the EAR bit. The value being sent to the Ring buffer is one byte value of 50 or 200 depending of wether the Piezo/Speaker Moving to ON or setteling to OFF. So to speak.

For Example :-
Code (Blitzmax) Select
Select (value & 24)
            Case 0  ' OFF
                      RingBuffer.SendONE 50
                Case 8  ' EAR Socket ON ... ie PIEZO
                      RingBuffer.SendONE 200
Case 16 ' MIC Socket ON
                      RingBuffer.SendONE 50
Case 24 ' EAR and MIC SOCKET ON ... Should Amplify Sound Slightly ... ie, Louder Volume
      RingBuffer.SendONE 200
'Default
      'RingBuffer.SendONE 50
End Select


Quote from: iWasAdam on August 01, 2021, 08:06:18
And when you activate the black box - that's when it slows down?

Im not sure about the final part!  :-[

Kin Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on August 01, 2021, 08:42:50
At a first step the game speed has to be correct. If this is perfect you now can find out the best HERTZ for the ringbuffer. You  should find the same HERTZ the vintage speaker port worked with.
In this former times the speakers did not work with a common Hertz like 44100, but they used the computer's clock rate or a division of it. So we can only guess.

A sign, that you are using the correct HERTZ is the pitch of the music. It should also today on modern computers sound with exact the same pitch as it was on the vintage computers.

A too high HERTZ results in stuttering, a too low HERTZ will cause "BUFFER OVERRUN" messages. You can find out the best HERTZ by using the middle of your MINIMUM and MAXIMUM test HERTZ. So I suggest now to use (44100+9600)/2 = 26000 for a next experiment.

If you get STUTTERING you take 26000 as new MAXIMUM. So next eperiment is with (26000+9600)/2 =18000 Hertz

If you get BUFFER OVERRUN you take 26000 as new MINIMUM. So next eperiment is with (44100+26000)/2 =35000 Hertz

Better you test this with in-game-music instead of short sounds.
Title: Re: Creating ZX SOUND
Post by: Baggey on August 01, 2021, 08:58:35
Okay,

So it's like trying to find a UNITY point. Sweet Spot. if i had a scope or analyser maybe i could see the Frequency of the FUSE sound?

However, I shall try this division idea of 2. Moving closer to a frequency of compromise i suppose?

I have some Schematics of the Zx spectrum ill see where the Clk Cycles get divided. May be looking to deep but i suppose there's some sort of feed from a clock source feeding the PIEZO through an NPN, PNP or OP-AMP by a clk pulse. Being turned on by the OUT r,(254) letting through a clk pulse :o

Thanks to all in advise and helping!

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on August 01, 2021, 09:05:48
I checked the sound pitch in your videos with AUDACITY and found out, that 44.100 is still to low

The factor of pitch between the song in FUSE and your test with 44.100 is 1.45. This would mean that 44100*1.45 = 63.945.

So I would suggest a test with 64.000 HERTZ next

But this would cause more STUTTERING. It looks like the port 254 received the double amout of data on the vintage computers.

So please do a second test with 32.000 Hertz. I guess the pitch (and the song speed) will be to low, but the STUTTERING will be away and there are still no BUFFER OVERRUN messages.


If this is a hit, it would mean, that they did something with the speaker we still do not know. Or the game speed is not 3500 but higher.


Only one DELAY?

Did you now remove all DELAY commands except the one in AddCycles()?
Title: Re: Creating ZX SOUND
Post by: Midimaster on August 01, 2021, 09:19:14
The little running man in the game (FUSE-video at 0:38) need 16sec to do his way 3 times.

In your 44.100 simulation (FUSE-video at 1:57) he needs 18sec to do the same. This would mean the TCycles/msec have to be 3937 and not 3500.

What do you think?
Title: Re: Creating ZX SOUND
Post by: iWasAdam on August 01, 2021, 10:24:50
QuoteCode: BlitzMax
That's really helpful. it gives me an understanding of what is going on and how you are sending data/info to the 'blackbox' ringbuffer

Right - And I know someone is going to go nuts over this...
the speed of the ring buffer should have no effects on the sound output. E.G. The RingBuffer (I'm just going to call it RB from now on), will have a frequency that the audio driver is happy with. you send data to the RB and it outputs the result.

BUT... and it is a big one....

It may be that the internal operation of the RB is not operating in a way that the spectrum output would like.

Without testing the RB. My thought is the following:
you send the RB 50. it registers 50, outputs 50 and continues to output whatever 'other' value it likes. I would have thought that the RB would continue sending the value of 50, until it was told to send another different value.
So you send 50 first, then a 0 later and so on. this is what gives the square wave?
NOTE - sending 150 and 100 would actually give the same result as sending 50 and 0 - why - because it is the difference between the values that matters.
In the spectrums case- 0 is off and a louder volume is on
I did some checking and the beep command only has a freq and length. So the spectrum never really used volume - which is good to know

This would mean that for and z80 speed, the pitch comes directly from this speed. - It's also why a NTSC and PAL spoectrums have slightly different pitch frequencies - 60 htz vs 50 htz clock speeds.

You would need to test and check this though...?

Title: Re: Creating ZX SOUND
Post by: Midimaster on August 01, 2021, 11:58:05
Quote from: iWasAdam on August 01, 2021, 10:24:50
QuoteCode: BlitzMax
the speed of the ring buffer should have no effects on the sound output. E.G. The RingBuffer (I'm just going to call it RB from now on), will have a frequency that the audio driver is happy with. you send data to the RB and it outputs the result.

The FreeAudioRingBuffer has no "speed". It is a passiv element. Filled by the user's call RingBufferSendOne(), emptied by the FreeAudio-Driver
The FreeAudio-Driver has a "speed": the amount of data per msec is related to the Hertz we startet it.



Quote...It may be that the internal operation of the RB is not operating in a way that the spectrum output would like.
Without testing the RB. My thought is the following:
you send the RB 50. it registers 50, outputs 50 and continues to output whatever 'other' value it likes. I would have thought that the RB would continue sending the value of 50, until it was told to send another different value....

The current use of the ringbuffer is sending SILENCE (=0), when the buffer is empty. That fits to a lot of situation where you would use such a ringbuffer. It would also fit to a perfect adjusted Z80 if it sends exactly the amount of data the FreeAudio-Driver needs

But that is a good idea of you! We should try to send the LAST VALUE instead of silence. This would keep the speaker in the last state until a new OUT_254 comes in. This will prevent all timing problems, but of course will cause pitch fluctuations. Maybe it will sound ugly, maybe it will lead us exactly to the typical dirty sound of vintage computers.


QuoteThis would mean that for and z80 speed, the pitch comes directly from this speed. - It's also why a NTSC and PAL spoectrums have slightly different pitch frequencies - 60 htz vs 50 htz clock speeds.

That is correct! You got it! At Z80 Music pitch was related directly to the processor speed. That why it is so important to adjust also the HERTZ of the Freeaudio-Driver to the vintage situation. There was no 44100 in this times. The game pushes single BYTEs to the OUT_254 and the speaker reacts on this timing. We can simulate this with sending 0 and 255 to the ringbuffer, which gives a very loud signal.  Or we send 100 and 150, which produces a quiter signal. But the signal should dance around 0 Volt, which is 128 in SF_MONO8. Sending 0 and 50 to the ringbuffer leads to -32768 and -19968 sent to the FreeAudioDriver and this means a permanent DC at the speakers. A sound engeneer will tell  you that this is not the best way to handle speakers. In german we call this mid-point "Nulldurchgang" what means Zero-Level in english.

Often misunderstanding comes from the different nativ languages we speak.



Title: Re: Creating ZX SOUND
Post by: iWasAdam on August 01, 2021, 12:26:11
I do think that one thing should be thought about:
QuoteBut the signal should dance around 0 Volt

Not strictly true - it may actually sit at 0v-5v. I would have to look at the exact spectrum specs. but doing some research the spectrum used a 1bit speaker system. with frequency control giving limited pitch over 10 octaves.

My thought here would to do some primitive tests using video examples to compare the outputs of both.


The only other warning sign I get is to do with hrtz and speed and slowdown.
the sound system should be almost completely removed from the z80/spectrum core - just sending data as and when needed. each should do it's own thing in almost isolation from the other - there should be no real slowdown of speed. There would be differences in debug vs release versions as debug would have at least twice the overhead because if the inserted debug code.

In the above example of a single 1 bit channel emulation - you would have no audible difference in either debug or release as the speed changes would be almost none. You are only sending an on/off signal...

I usually have trouble with debug when using more that 3-4 16bit channels or realtime audio... and then only slowdowns. you should not be having any issues with speed in any situation - unless there is something 'odd' going on with the code.

BTW I run my ring buffer at 44100 - you should never need to do any housekeeping with this as it should 'just' work. Once set up you don't have to think about it, etc. There should no no messages about unfilled buffers, no intentional slowdowns, etc. The core buffer design is the most important part as everything is built on top of it.
Title: Re: Creating ZX SOUND
Post by: Midimaster on August 01, 2021, 14:17:25
We already did video comparation and I found out that Baggey would need 3937 cylcles/msec to run in the same speed like FUSE simulator. When we do this the little running man runs the same speed in both videos. (factor 1.12)

When comparing audio pitch it would be neccesary to run the FreeAudio-Device with 64.0kHz to reach the same tune. (factor 1.45). Running with 32.0kHz will feed the Device perfect, but the pitch is now too low. Filling a 64.0kHz Device with the given 3500 cyles/msec and auto-completing missing BYTE with doubling the last one will also cause in a real deeper pitch.

As the source code of my FreeAudioRingBuffer is Public Domain everybody is free to manipulate and adjust it to his needs. Also the fact that it has only 300 lines of BlitzMax code should help to easy change something.

I also already thought about offering an option to switch off the AUTO-DELAY (which prevents buffer overruns) in the next version.

Together with Baggey I developed a Z80-timing system that runs exactly with 3500 ticks/msec in every situation. It does not matter, whether we are in DEBUG or RELEASE mode or whether  other background processes need more time. Also the RingBuffer has no effect on the Z80-processor speed. This was a necessary first step to start investigations about finding out how we can/need do handle the audio timing.


Your access of always using 44.1kHz is perfect for a new app, because you are able to provide data in the needed speed. But Baggey situation is different. He has to handle with the given 3500 cycles. Sometimes during this game code there is a code line for the OUT_254 (speaker). This defines the speed of feeding the speaker. Now we have to find out, what is the best corresponding HERTZ  in FreeAudio-Driver related to this feeding.

You are welcome to offer code snipplets Baggey can add to his code.
Title: Re: Creating ZX SOUND
Post by: iWasAdam on August 01, 2021, 14:28:40
No probs - the only code issues I have is I don't use blitzNG - I moved to monkey2 and now have finished transition to Wonkey (now I've finished writing the editor)

There are significant differences with accessing c code (why I said don't attempt to run with prev code sample ;( ) with NG and Wonkey.

I was toying with moving back to NG - but found it was just too old to comfortably do what I needed. so whilst I can talk concepts and pseudo code, my code is firmly fixed with wonkey...
Title: Re: Creating ZX SOUND
Post by: Baggey on August 01, 2021, 18:26:09
Quote from: iWasAdam on August 01, 2021, 14:28:40
No probs - the only code issues I have is I don't use blitzNG - I moved to monkey2 and now have finished transition to Wonkey (now I've finished writing the editor)

There are significant differences with accessing c code (why I said don't attempt to run with prev code sample ;( ) with NG and Wonkey.

I was toying with moving back to NG - but found it was just too old to comfortably do what I needed. so whilst I can talk concepts and pseudo code, my code is firmly fixed with wonkey...

Finally!  ;D Back from the Beach and trying out some Ideas that you guy's have Bounced around "OUT!" this Evening 8)

Im Using BlitzMax 1.5 Vanilla. Ive tried moving to "blitzNG" but i get to many errors and when it finally compile's it dosent work as it should so i stuck with BlitMax 1.5  :-X

Kind Regards to all Baggey!
Title: Re: Creating ZX SOUND
Post by: Baggey on August 01, 2021, 19:21:46
QuoteWe already did video comparation and I found out that Baggey would need 3937 cylcles/msec to run in the same speed like FUSE simulator. When we do this the little running man runs the same speed in both videos. (factor 1.12)

Wow just tried the value of 3937. Title screen is Laggy with Buffer Overrun. However! Ingame music and game emulation seems good! maybe pitch slightly off. When i die, jump or the end game happens im getting a squeeky change in sound!  ;D

So data being sent is happening! and data being recieved is happening!

In my world "Im a SPARKY!" Their out of phase with each other! I think one is leading or lagging the other!  :-\

Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on August 07, 2021, 20:55:14
Okay the latest video for Zx Sound perfection.

In this video i show RingBuffer_1.2 in it's basic form running sound sent by SpecBlitz. I show title music which loses its notes at the end! In game jig which dosent play properly. then i demonstrate Man dying, jumping and end game which is silent!

Second part of video. I make changes to RingBuffer_1.2 to get more functionallity. I scroll through RingBuffer _1.2 altering code to make sound work better being sent from SpecBlitz. Main screen music loses end notes slightly! In game jig sounds good to the ear. In game effects coming through but not write. We can here jumping sort off and end game is audible but not write.

In various test's the best Result's are in SENDING LAST BYTE RECIEVED and also CHUNK_TIME=10 as low as possible the better!?

So again any ideas people? One idea im having is if the buffer is full i should pause Specblitz until the buffer is ready to recieve more bytes?

https://youtu.be/jUwV6YesEHs

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on August 07, 2021, 21:59:09
So, Some more Changes.

For reasons unknown ive lowerd HERTZ, CHUNKTIME is down to 5 and Short sounds have improved?? At the expense of longer playing tune's.
if i have a CHUNKTIME=5 i cant use 44100Hz it crashes.

https://youtu.be/Spju8wsYN5s

Kind Regards Baggey
Title: Re: Creating ZX SOUND
Post by: Midimaster on August 08, 2021, 02:57:29
The HERTZ of 44100 are completely wrong! compare the pitch of the song in your video, where FUSE plays the songs. This is massiv higher!!!

You can reach this pitch in yor app only if you rise the HERTZ to 63000.

With 44100 it is to slow and needs to much time.  Why do you test at 44100?

I checked length and pitch of the 4 sounds from your FUSE video in post #97

The Danube-Song  length in FUSE is 20.00 sec
The second music length in FUSE is 13.60 sec
The short Sound  length in FUSE is 00.35 sec
The long Sound   length in FUSE is 02.10 sec


Lets make some examination with the LONG.Mp3
(See attachment)

I made AUDACITY screenshots of FUSE-Samples compared to YOUR-Samples. This is this LONG.MP3 ( the sound appears when the GameOver comes and take the player away)

Here you see a first overview. LONG.MP3 (FUSE) on top  <->  LONGAFTERRINGBUFFER.MP3 (YOURs) at the bottom:
(https://www.syntaxbomb.com/blitzmax-blitzmax-ng/creating-zx-sound/?action=dlattach;attach=4736)
I needed to rise the HERTZ of YOURs to 63.000 to get the same length. Again 63.000!!!

The FUSE sound changes the pitch from low to high.
YOURs looks like sending packages.

Now lets have a deeper look into the samples:
(https://www.syntaxbomb.com/blitzmax-blitzmax-ng/creating-zx-sound/?action=dlattach;attach=4738)

The FUSE has impulses. The distances getting smaller from peak to peak. This causes the rising of the pitch.
YOURs is still a package

Lets look deeper:
(https://www.syntaxbomb.com/blitzmax-blitzmax-ng/creating-zx-sound/?action=dlattach;attach=4740)
The impulses on FUSE are SAWTOOTH


How did this happen? What does it mean?


This means that the Port254 can not only receive a fix HERTZ rate in the game, but can also handle variable frame rate. RingBuffer collected the low rate sended impulses and FreeAudio interpreted it as a short bulk of a 44100 HERTZ audiosample followed by a pause.

But the truth is, that we need to measure the microtime between the impulses and simulate the missing samples, when the rate of impulses is lower than 63000.

Keep the last impuls in mind and send it in a rate of 63000 until the next impuls reaches the PORT. This will result in a SQUARE signal. Not quite perfect but a next step.

So you need to write a function that receive PORT254 impulses and now sends in a given HERTZ rate the last value until a new one arrives.

63000 is our minimum HERTZ, because the "songs" really send 63000 impulses per second.
Title: Re: Creating ZX SOUND
Post by: Midimaster on August 14, 2021, 09:39:23
This compares to runing man sound of FUSE and YOURs after using the MicroTimer()

It sound better than ever before, but we have a strange pitch effect. YOURs is running faster, but the pitch is lower. Faster would always cause higher pitch. It looks like the original is sending faster, but making pauses between the notes. Do we have forgot someting in the game code?

This effect is only possible if the OUT_254 fires faster for a several time followed by a phase of firing nothing.

Both audacity tracks show exactly the same piece of music (same musical length)

First take is FUSE
below is yours:
(https://www.syntaxbomb.com/blitzmax-blitzmax-ng/creating-zx-sound/?action=dlattach;attach=4750)


Here you see a deeper view into the samples.

Original produces SAWTOOTH, we SQUARE:
(https://www.syntaxbomb.com/blitzmax-blitzmax-ng/creating-zx-sound/?action=dlattach;attach=4752)

I measured the distances:

Result A

In FUSE  the 5 single peaks have a distance of 1.5msec followed of a pause of 77msec

In YOURS  the 5 single peaks have a distance of 2.1msec followed of a pause of 58msec


Result B

In FUSE  the signal is nearly all the time a sequence of 0 and 255.
first (5sample)-package is starting wih 255, the next package with 0, then again the first....


In YOUR  the first signal is a sequence of 0 and 128 ,followed by a sequence of 128 and 0 followed by a sequence of 64 and 255 and so on... it looks chaotic, no system
Title: Re: Creating ZX SOUND
Post by: Baggey on August 14, 2021, 12:04:07
My values Dont seem to be centered around 0 Volts they seem to be V=sin(wt)+something? so to speak.

If Fuse is tailing of to zero like sawtooth then this may explain why im getting repeating of sounds when there is no more being sent! As were not going to zero?

I think when no more is being sent we need to create a sawtooth function. Maybe interpolate to zero very quickly or just zero. I suppose this is the time for the speaker to settle back to is ready state in the real world. Ie not a perfect square WAVE.

Here are the latest sounds from Manic Miner. Starting to sound like the real thing!

https://youtu.be/4T2oO8pX2dg

Baggey
Title: Re: Creating ZX SOUND
Post by: Baggey on August 14, 2021, 17:25:39
Okay, Altered the Timing slightly to compensate.

Wow sounds are working!  :o 8) But cant seem to get rid of the damm repeating! See current Vid.

Anyone any ideas?

Nearly there. https://youtu.be/JXaUDau4gBQ

Kind Regards Baggey