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 :)

Code: [Select]
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.

Code: [Select]
'# 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
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
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
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.

Code: [Select]
'# 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
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"
Code: [Select]
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"
Code: [Select]
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
Title screen "The Blue Danube"
Code: [Select]
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:

Code: [Select]
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:

Code: [Select]
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.

Quote
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:

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

Code: [Select]
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"
Code: [Select]
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

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.

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

[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
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:

Code: [Select]
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.
Code: [Select]
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:
Code: [Select]
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
  1.                      
  2.   Case 15 ' RRCA
  3.         TempCARRY=(A & 1) <> 0
  4.         A=(A Shr 1)
  5.         If TempCARRY=1 Then A=(A | 128)
  6.         '
  7.         fSIGN = fSIGN ' Not affected           
  8.         fZERO = fZERO ' Not affected
  9.         fFIVE= ((A & 32) <> 0)
  10.         fHALFCARRY = 0
  11.         fTHREE = ((A & 8) <> 0)
  12.         fPARITY = fPARITY ' Not affected
  13.         fADDSUB = 0
  14.         fCARRY = TempCARRY
  15.         SetFLAGS()
  16.         PC:+1 ; TCycles:+4
  17.   Case 16 ' DJNZ,d
  18.         DisJump=(twosum[PC1]+2) ; B:-1 ; BC=(B Shl 8)+C
  19.         If B<>0 Then
  20.           PC:+DisJump ; TCycles:+13
  21.         Else
  22.           PC:+2 ; TCycles:+8
  23.         End If
  24.   Case 47 ' CPL
  25.         A=(A ~ 255)
  26.         fSIGN = fSIGN ' Not affected
  27.         fZERO = fZERO ' Not affected
  28.         fFIVE = ((A & 32) <> 0)
  29.         fHALFCARRY = 1
  30.         fTHREE = ((A & 8) <> 0)
  31.         fPARITY = fPARITY 'Not affected
  32.         fADDSUB = 1
  33.         fCARRY = fCARRY ' Not affected
  34.         SetFLAGS()
  35.         PC:+1 ; TCycles:+4
  36.  

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
  1.   Method Or8(InB:Byte)
  2.               ' All Flags affected.
  3.               ' Sign Set if Bit 7 is One
  4.               ' Zero Set if Answer is zero
  5.               ' Halfcarry is Reset
  6.               ' Parity Set if EVEN number of One's
  7.               ' AddSub is Reset
  8.               ' Carry is Reset
  9.               A=(A | InB)
  10.               '
  11.               fSIGN = (A & 128) <> 0
  12.               fZERO = (A = 0)
  13.               fFIVE = (A & 32) <> 0
  14.               fHALFCARRY = 0
  15.               fTHREE = (A & 8) <> 0
  16.               fPARITY = parity[A]
  17.               fADDSUB = 0
  18.               fCARRY = 0
  19.               SetFLAGS()
  20.   End Method
  21.                
  22.   Method Xor8(InB:Byte)
  23.                ' All Flags affected.
  24.                ' Sign Set if Bit 7 is One
  25.                ' Zero Set if Answer is zero
  26.                ' Halfcarry is Reset
  27.                ' Parity Set if EVEN number of One's
  28.                ' AddSub is Reset
  29.                ' Carry is Reset
  30.                A=(A ~ InB)
  31.                '
  32.                fSIGN = (A & 128) <> 0
  33.                fZERO = (A = 0)
  34.                fFIVE = (A & 32) <> 0
  35.                fHALFCARRY = 0
  36.                fTHREE = (A & 8) <> 0
  37.                fPARITY = parity[A]
  38.                fADDSUB = 0
  39.                fCARRY = 0
  40.                SetFLAGS()                      
  41.   End Method
  42.  

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.

Code: [Select]
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
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:

Code: [Select]
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:
Code: [Select]
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)

Code: [Select]
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
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:

Code: [Select]
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:-

Code: [Select]
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  :)

Code: [Select]
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
  1. SuperStrict
  2.  
  3. ' Music Data (Blue Danube from Manic Miner)
  4. 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]
  5. Print( "DATA LENGTH: "+Len(data) )
  6. Local lenOfdata:Int=Len(data)
  7. Print "Length of Data="+LenOfdata
  8. Print
  9. Print "Press Enter" ; Input
  10.  
  11. Global cursor:Int = 0
  12. Global duration:Int
  13. Global note:Byte[2]
  14. Global RepeatLoop:Byte=True
  15.  
  16. ReadMusic()
  17.  
  18. Print
  19. Print "is the Data Length some how counting 0? ie (0 to 285) = 286"
  20. Print
  21. Print "Address 34187 - Address 33902 = "+(34187-33902)
  22. Print "cursor on next run = "+cursor+"  Byte will be "+data[cursor]
  23.  
  24. Function ReadMusic()
  25. While RepeatLoop
  26.         If data[cursor]=255 Then
  27.                 Print cursor+") "+data[cursor] ; cursor=0 ; Exit ' Reached the end?
  28.         Else
  29.         ' Read music data
  30.         duration = data[cursor]
  31.         note[0]  = data[cursor+1]
  32.         note[1]  = data[cursor+2]
  33.         Print cursor+") "+duration+", "+note[0]+", "+note[1]
  34.         cursor :+ 3
  35.         End If
  36. Wend
  37. End Function
  38.  
  39. Rem
  40.         19      37596   FN37596 LD A,(IY+0) PLAY MUSIC ' Let A=PEEK(IY+0)
  41.          7      37599           CP 255                 ' If (A-255)=0 Then fZERO=TRUE
  42.         11      37601           RET Z                  ' If fZERO=TRUE Then Return/Exit/End/Gosub
  43. 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
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
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
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
Code: [Select]
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 :))

Code: [Select]
' 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
  1. Global Duration:INT=1000
  2. Const SamplesPerSecond:INT=11025
  3.  
  4. Global AudioSample:TAudioSample=CreateAudioSample( duration, SamplesPerSecond, SF_MONO8 )
  5. OUT_Piezo_(Duration,80)
  6.  
  7. Function OUT_Piezo_(Duration%, Hertz%)
  8.                         Local w%
  9.                         For Local K%=0 Until Duration-1  
  10.                                 w=w+1          
  11.                                 If (w Mod Hertz)< Hertz/2
  12.                                          AudioSample.samples[K] = 255
  13.                                 Else
  14.                                         AudioSample.samples[K] = 0
  15.                                 EndIf
  16.                         Next
  17. End Function
  18.  
  19. Global audio:TSound=LoadSound( AudioSample)
  20. While Not (AppTerminate() Or KeyDown(key_space))
  21.  
  22.  
  23. If MouseHit(1)
  24. PlaySound audio
  25. EndIf
  26.  

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
Code: [Select]
#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:

Code: [Select]
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:

Code: [Select]
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
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.

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

Code: [Select]
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

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

microsecs.c
Code: [Select]
#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 );
}


Code: [Select]
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.

Code: [Select]
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

Code: [Select]
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

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

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
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!

(http://)

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
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
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
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:
(http://www.midimaster.de/images/HoerenKlein.png)

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 :)
Code: [Select]
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
  1. SuperStrict
  2.  
  3. Graphics 800,600
  4.  
  5.  
  6. 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]
  7. Global NextTime%, ReadPos%
  8.  
  9.  
  10. Repeat
  11.         Wait
  12.         If Data[ReadPos]=255 End
  13. Until AppTerminate()
  14.  
  15.  
  16. Function Wait()
  17.         If NextTime<MilliSecs()
  18.                 Print ReadPos + " " + data[ReadPos] + " " + data[ReadPos+1] + " " + data[ReadPos+2]
  19.                 Local Duration%=Data[ReadPos]*5
  20.                 NextTime=MilliSecs()+Duration
  21.                 MakeSound Duration, Data[ReadPos+1], Data[ReadPos+2]
  22.  
  23.                 ReadPos=ReadPos+3
  24.         EndIf  
  25. End Function
  26.  
  27.  
  28. Function MakeSound(Duration%,PulseA%, PulseB%)
  29.         Const SAMPLERATE% = 11000'Hz
  30.         Local SampleLen%, Half%
  31.         Local SampleA:TAudioSample, SampleB:TAudioSample, AudioA:TSound, AudioB:TSound
  32.        
  33.         SampleLen = SAMPLERATE*Duration/1000
  34.         If PulseA>0    
  35.                         SampleA = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )
  36.                         Half = PulseA/2
  37.                         For Local n:Int=0 Until SampleLen-1
  38.                                 If (n Mod PulseA) < half
  39.                                         SampleA.samples[n] = 200
  40.                                 Else
  41.                                         SampleA.samples[n] = 55
  42.                                 End If
  43.                         Next
  44.                         AudioA = LoadSound( SampleA )
  45.         EndIf
  46.         If PulseB>0            
  47.                         SampleB = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )         
  48.                         Half = PulseB/2
  49.                         For Local n:Int=0 Until SampleLen-1
  50.                                 If (n Mod PulseB)<half
  51.                                         SampleB.samples[n] = 200
  52.                                 Else
  53.                                         SampleB.samples[n] = 55
  54.                                 End If
  55.                         Next           
  56.                         AudioB = LoadSound( SampleB )
  57.         EndIf
  58.         If PulseA>0    
  59.                 PlaySound AudioA
  60.         EndIf
  61.         If PulseB>0
  62.                 PlaySound AudioB
  63.         EndIf
  64. 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
  1.      SuperStrict
  2.      
  3.     Graphics 800,600
  4.      
  5.      
  6.     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]
  7.      
  8.     SoundChip.StartSong data
  9.  
  10.     Repeat
  11.             If soundChip.run()=False End
  12.     Until AppTerminate()
  13.      
  14.      
  15. Type SoundChip
  16.         Const SAMPLERATE% = 22050,  DURATION_FAKTOR:Float=5
  17.         Const SINUS:Int=1, SQUARE:Int=2, SAWTOOTH:Int=3
  18.         Const WAVE_FORM:Int =SQUARE
  19.         Global SongData:Int[], NextTime:Int, ReadPos:Int
  20.  
  21.        
  22.         Function StartSong:Int(Data:Int[])
  23.                         SongData=Data
  24.                         NextTime=MilliSecs()
  25.                         ReadPos=0
  26.         End Function
  27.  
  28.        
  29.         Function Run:Int()
  30.                 If NextTime<MilliSecs()
  31.                         If Data[ReadPos]=255 Return False      
  32.                         Local Duration%=Data[ReadPos]*DURATION_FAKTOR
  33.                         Local PulseA:Int=Data[ReadPos+1]
  34.                         Local PulseB:Int=Data[ReadPos+2]
  35.  
  36.                         NextTime=MilliSecs()+Duration
  37.                         If PulseA<>0 Or PulseB<>0
  38.                                 Select WAVE_FORM
  39.                                         Case SINUS
  40.                                                 MakeSinus  Duration, PulseA, PulseB
  41.                                         Case SQUARE
  42.                                                 MakeSquare Duration, PulseA, PulseB                                    
  43.                                         Case SAWTOOTH
  44.                                                 MakeSaw    Duration, PulseA, PulseB
  45.                                 End Select
  46.                         EndIf
  47.                         ReadPos=ReadPos+3
  48.                 EndIf  
  49.                 Return True
  50.         End Function
  51.  
  52.  
  53.  
  54.  
  55.         Function MakeSinus(Duration:Int, PulseA:Int, PulseB:Int)
  56.                        
  57.                         Local SampleLen:Int= SAMPLERATE*Duration/1000
  58.                         Local FaktorA:Float = 360.0/PulseA
  59.                         Local FaktorB:Float = 360.0/PulseB
  60.  
  61.                         Local Sample:TAudioSample = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )
  62.  
  63.                         For Local n:Int=0 Until SampleLen
  64.                                 Local ValueA:Int, ValueB:Int
  65.  
  66.                                 If PulseA=0
  67.                                         ValueA=0
  68.                                 Else
  69.                                         ValueA=60* Sin(FaktorA*(n Mod PulseA))
  70.                                 EndIf
  71.                                 If PulseB=0
  72.                                         ValueB=0
  73.                                 Else
  74.                                         ValueB=60* Sin(FaktorB*(n Mod PulseB))
  75.                                 EndIf
  76.  
  77.                                 Sample.samples[n] = 127+ValueA+ValueB
  78.                         Next
  79.                         Local Audio:TSound = LoadSound( Sample )
  80.                         PlaySound Audio
  81.         End Function
  82.  
  83.  
  84.  
  85.        
  86.         Function MakeSaw(Duration:Int, PulseA:Int, PulseB:Int)
  87.                        
  88.                         Local SampleLen:Int= SAMPLERATE*Duration/1000
  89.                         Local FaktorA:Float = 120.0/PulseA
  90.                         Local FaktorB:Float = 120.0/PulseB
  91.  
  92.                         Local Sample:TAudioSample = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )
  93.  
  94.                         For Local n:Int=0 Until SampleLen
  95.                                 Local ValueA:Int, ValueB:Int
  96.  
  97.                                 If PulseA=0
  98.                                         ValueA=0
  99.                                 Else
  100.                                         ValueA=FaktorA*(n Mod PulseA) -60
  101.                                 EndIf
  102.  
  103.                                 If PulseB=0
  104.                                         ValueB=0
  105.                                 Else
  106.                                         ValueB=FaktorB*(n Mod PulseB) -60
  107.                                 EndIf
  108.  
  109.                                 Sample.samples[n] = 127+ValueA+ValueB
  110.                         Next
  111.                         Local Audio:TSound = LoadSound( Sample )
  112.                         PlaySound Audio
  113.         End Function
  114.  
  115.  
  116.  
  117.         Function MakeSquare(Duration:Int, PulseA:Int, PulseB:Int)
  118.                        
  119.                         Local SampleLen:Int= SAMPLERATE*Duration/1000
  120.                         Local HalfA:Int = PulseA/2
  121.                         Local HalfB:Int = PulseB/2
  122.  
  123.                         Local Sample:TAudioSample = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )
  124.  
  125.                         For Local n:Int=0 Until SampleLen
  126.                                 Local ValueA:Int, ValueB:Int
  127.  
  128.                                 If PulseA=0
  129.                                         ValueA=0
  130.                                 ElseIf (n Mod PulseA) < HalfA
  131.                                         ValueA=60
  132.                                 Else
  133.                                         ValueA=-60
  134.                                 EndIf
  135.  
  136.                                 If PulseB=0
  137.                                         ValueB=0
  138.                                 ElseIf (n Mod PulseB) < HalfB
  139.                                         ValueB=60
  140.                                 Else
  141.                                         ValueB=-60
  142.                                 EndIf
  143.                                 Sample.samples[n] = 127+ValueA+ValueB
  144.                         Next
  145.                         Local Audio:TSound = LoadSound( Sample )
  146.                         PlaySound Audio
  147.         End Function
  148.  
  149. End Type
  150.  
  151.    
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:
Code: [Select]
'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:
Code: [Select]
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
  1. SuperStrict
  2. Graphics 800,600
  3. Global counter%, Zeit%
  4.  
  5. ' as a workaround I use the registers as real BYTE
  6. Global A:Byte, B:Byte=1, C:Byte, D:Byte=0, E:Byte=0
  7.  
  8. '!!!!! the Data length is 286 so a BYTE variable would never reach the last DATA=255 to get the STOP command
  9. ' workaround: define IY as INT
  10. Global IY:Int
  11.  
  12. ' Music Data
  13. 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]
  14. Print( "DATA LENGTH: "+Len(data) )
  15.  
  16. CALL_37596()
  17.  
  18.  
  19. Print "samples=" + counter
  20. Repeat
  21. Until AppTerminate()
  22. End
  23.  
  24.  
  25. Function CALL_37596:Int()
  26.  
  27.         While True
  28.                 A = data[IY]                                    '37596  LD A,(IY+0)     Pick up the next byte of tune data from the table at 33902
  29.                 If A=255                                                '37599  CP 255  Has the tune finished?
  30.                         Return True                                     '37601  RET Z   Return (with the zero flag set) if so
  31.                 End If
  32.                 C = A                                                   '37602  LD C,A  Copy the first byte of data for this note (which determines the duration) to C
  33.                 B = 0                                                   '37603  LD B,0  Initialise B, which will be used as a delay counter in the note-producing loop
  34.                 A = 0                                                   '37605  XOR A   Set A=0 (for no apparent reasaon)
  35.                 D = data[IY+1]                                  '37606  LD D,(IY+1)     Pick up the second byte of data for this note
  36.                 E = data[IY+2]                                  '37615  LD E,(IY+2)     Pick up the third byte of data for this note
  37.                 Repeat                                                  ' until C=0 (at 37645)
  38.                
  39.                
  40. 'workaround: normally B runs from 255 downto 0
  41. ' this makes the song playing faster:  
  42.                         B=150
  43.                         Repeat                                          ' until B=0 (at 37642)                         
  44.                                 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)
  45.                                 D :- 1                                  '37626  DEC D
  46.                                 If D=0                                  '37627  JR NZ,37634
  47.                                         D = data[IY+1]          '37629  LD D,(IY+1)
  48.                                         A = A~24                        '37632  XOR 24
  49.                                 End If
  50.                                 E :- 1                                  '37634  DEC E
  51.                                 If E=0                                  '37635  JR NZ,37642
  52.                                         E = data[IY+2]          '37637  LD E,(IY+2)
  53.                                         'A = A~24                       '37640  XOR 24
  54.                                 End If
  55.                                 B :- 1                                  '37642  DJNZ 37624
  56.  
  57.                         Until B=0                                       ' ""    ""
  58.                         C :- 1                                          '37644  DEC C
  59.  
  60.                 Until C=0                                               '37645  JR NZ,37624
  61.                 IY :+ 3                                                 '37667  INC IY  Move IY along to the data for the next note in the tune
  62. Print "Step in DATA=" +  iy                             '37669  INC IY
  63.                                                                                 '37671  INC IY
  64.  
  65.  
  66.         Wend                                                            '37673  JR 37596        Jump back to play the next note
  67. End Function
  68.  
  69.  
  70.  
  71. Function OUT_ONCE(Port%, Value%)
  72.         Const SAMPLE_RATE%=44100
  73.         Global NextSample:TAudioSample
  74.         If Counter=0
  75.                 Print "Sample created"
  76.                 NextSample = CreateAudioSample(750000, SAMPLE_RATE, SF_MONO8 )
  77.         EndIf
  78.         Counter=Counter + 1
  79.  
  80.         Select Value & 8
  81.                 Case 0
  82.                         NextSample.Samples[Counter]=50 
  83.                 Case 8
  84.                         NextSample.Samples[Counter]=200
  85.         End Select
  86.  
  87.         If counter=730000
  88.                 Print "play TSound"
  89.                 Local Audio:TSound = LoadSound( NextSample )
  90.                 PlaySound Audio
  91.         EndIf
  92. End Function
  93.  
  94.  


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
  1. ...
  2. 'workaround: now you can set B back to  255 (or remove this code line)
  3.         B=255
  4.         Repeat                                  ' until B=0 (at 37642)                         
  5.         OUT_CHUNKS(254,A)                       '37624  OUT (254),A    
  6. ...
  7.  
  8. Function OUT_CHUNKS(Port%, Value%)
  9.         Const SAMPLE_RATE%=44100
  10.         Global NextSample:TAudioSample, PlayTime%
  11.         If Counter=0
  12.                 Print "Sample created"
  13.                 NextSample = CreateAudioSample(4410, SAMPLE_RATE, SF_MONO8 )
  14.         EndIf
  15.  
  16.         Select Value & 8
  17.                 Case 0
  18.                         NextSample.Samples[Counter]=50 
  19.                 Case 8
  20.                         NextSample.Samples[Counter]=200
  21.         End Select
  22.         Counter=Counter + 1
  23.         If counter=4408
  24.                 NextSample.Samples[4408]=0     
  25.                 NextSample.Samples[4409]=0     
  26.                 Local Audio:TSound = LoadSound( NextSample )
  27.                 Repeat
  28.                         Delay 1
  29.                 Until PlayTime<MilliSecs()
  30.                 Print "play TSound"
  31.                 PlayTime=MilliSecs()+100
  32.                 PlaySound Audio
  33.                 Counter=0
  34.         EndIf
  35. End Function
  36.  
  37.  
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

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
  1. ...
  2. 'workaround: now you can set B back to  255 (or remove this code line)
  3.         B=255
  4.         Repeat                                  ' until B=0 (at 37642)                         
  5.         OUT_CHUNKS(254,A)                       '37624  OUT (254),A    
  6. ...
  7.  
  8. Function OUT_CHUNKS(Port%, Value%)
  9.         Const SAMPLE_RATE%=44100
  10.         Global NextSample:TAudioSample, PlayTime%
  11.         If Counter=0
  12.                 Print "Sample created"
  13.                 NextSample = CreateAudioSample(4410, SAMPLE_RATE, SF_MONO8 )
  14.         EndIf
  15.  
  16.         Select Value & 8
  17.                 Case 0
  18.                         NextSample.Samples[Counter]=50 
  19.                 Case 8
  20.                         NextSample.Samples[Counter]=200
  21.         End Select
  22.         Counter=Counter + 1
  23.         If counter=4408
  24.                 NextSample.Samples[4408]=0     
  25.                 NextSample.Samples[4409]=0     
  26.                 Local Audio:TSound = LoadSound( NextSample )
  27.                 Repeat
  28.                         Delay 1
  29.                 Until PlayTime<MilliSecs()
  30.                 Print "play TSound"
  31.                 PlayTime=MilliSecs()+100
  32.                 PlaySound Audio
  33.                 Counter=0
  34.         EndIf
  35. End Function
  36.  
  37.  

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
  1. ' ManicMiner Player Using BlitzMax 1.5
  2.  
  3. SuperStrict
  4.  
  5. Graphics 800,600
  6.  
  7. Global RunSpeed:Int=16 ' Using Blitzmax 1.5 Gives roughly a 50Hz Frame Delay!
  8.  
  9. ' Registers 16 Bit
  10. Global IY:Short, BC:Short, DE:Short, HL:Short
  11.  
  12. ' Registers 8 Bit
  13. Global A:Byte, B:Byte, C:Byte, D:Byte, E:Byte, H:Byte, L:Byte
  14.  
  15. ' Control Registers
  16. Global TCycles:Int
  17. Global fZERO:Byte
  18. Global ENTER:Byte=0
  19. Global Counter:Int
  20.  
  21. ' Music Data
  22. 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]
  23. Print
  24. Print "Length of MusicData = "+(Len MusicData)
  25.  
  26. Init()
  27.  
  28. Function MainLoop()
  29.     Local starttime:Int = MilliSecs()
  30.        
  31.     While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)
  32.        
  33.                                 Run()
  34.         Local endtime:Int = MilliSecs()
  35.         Local diff:Int = endtime - starttime
  36.        
  37.         Local pausedelay:Int = RunSPEED ' About 50Hz ie one TV frame
  38.                        
  39.         If pausedelay > 0 Then
  40.             Delay(pausedelay)
  41.         Else
  42.             pausedelay = 0
  43.         End If
  44.         starttime = endtime+pausedelay
  45.     Wend
  46. End Function
  47.  
  48. Function Call_37596:Byte()
  49.  
  50.                 While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)
  51.  
  52.                 ' LD A,(IY+0)           '  Get next Byte of Tune Data from 33902
  53.                                 A=MusicData[IY]  ; TCycles:+19
  54.                 ' CP 255                                '  Has the tune finished?
  55.                                 If (A-255)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7
  56.                 ' RET Z         '        Return if ZERO flag is Set
  57.                                 If fZERO=1 Then
  58.                                                 TCycles:+11 ; Return fZERO
  59.                                         Else
  60.                                                 TCycles:+5
  61.                                 End If
  62.                 '       LD C,A                                          ' Copy the first byte of MusicData for this note (which determines the duration) into C
  63.                                 C=A ; BC=(B Shl 8)+C ; TCycles:+4
  64.                 ' LD B,0                                                ' Load B with 0, which will be used as a delay counter in the note-producing loop              
  65.                                 B=0 ; BC=(B Shl 8)+C ; TCycles:+7      
  66.                 ' XOR A                         ' XOR A  Equivalent to LOAD A with 0 ( Incerting a delay of 4 TCycles )
  67.                                 A=(A ~ A) ; TCycles:+4
  68.                 ' LD D,(IY+1)                           ' Get second byte of MusicData for this note
  69.                                 D=MusicData[IY+1] ; DE=(D Shl 8)+E ; TCycles:+19
  70.                 ' LD A,D                                                ' Load A with D
  71.                                 A=D ; TCycles:+4
  72.                 '              
  73.                 ' FIRST CALL
  74.                 '
  75.                 ' CALL 37675                            ' Update on screen piano Key
  76.                                 TCycles:+17             ' 17 Clock Cycles for the call
  77.                                 ' Code there is irelevant, but time must br acounted for!
  78.                 '       LD (HL),80                        ' Set the Atribute colour of piano key 80 (INK 0: PAPER 2: BRIGHT 1)
  79.                                 TCycles:+10             ' 10 Clock Cycles for the LD(HL),40
  80.                 '       LD E,(IY+2)                             ' Get Third byte of MusicData for this note
  81.                                 E=MusicData[IY+2] ; D=(D Shl 8)+E ; TCycles:+19
  82.                 ' LD A,E                                        '       Load A with E
  83.                                 A=E ; TCycles:+4
  84.                 '
  85.                 ' SECOND CALL
  86.                 '
  87.                 '       CALL 37675                              ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
  88.                                 A=Call_37675()  ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
  89.                                 TCycles:+17             ' 17 Clock Cycles for the call
  90.                                 ' Code there is irelevant, but time must br acounted for!
  91.                 ' LD (HL),40
  92.                                 TCycles:+10             ' 10 Clock Cycles for the LD(HL),40
  93.                 Repeat ' C LOOP until C=0
  94.                         ' Part of the DJNZ
  95.                                 Repeat ' B LOOP until B=0
  96.                                 'For Local DJNZ:Int=B To 0 Step -1 ' until B=0
  97.                                                 'Print "B="+B
  98.                                                 ' OUT (254),A           ' Turn Piezo on or off
  99.                                                         OUT_CHUNKS(254,A) ; TCycles:+11
  100.                                                 ' DEC D                                 ' Decrease D by One
  101.                                                         D:-1 ; DE=(D Shl 8)+E ; TCycles:+4
  102.                                                         If D=0 Then fZERO=1 Else fZERO=0 ' If D=0 then set Zero flag
  103.                                                 ' JR NZ,37634
  104.                                                         If fZERO=1 Then
  105.                                                                         ' LD D,(IY+1)
  106.                                                                                 D = MusicData[IY+1]     ; DE=(D Shl 8)+E ; TCycles:+19
  107.                                                                         ' XOR 24
  108.                                                                                 A = (A ~ 24)                                                                   
  109.                                                                                 TCycles:+7 ' Condition not met
  110.                                                                 Else
  111.                                                                                 TCycles:+12 ' Condition met
  112.                                                         End If
  113.                                                 '       DEC E
  114.                                                         E:-1 ; DE=(D Shl 8)+E ; TCycles:+4
  115.                                                         If E=0 Then fZERO=1 Else fZERO=0 ' If E=0 then set Zero flag
  116.                                                 ' JR NZ,37642
  117.                                                         If fZERO=1 Then
  118.                                                                         ' LD E,(IY+1)
  119.                                                                                 E = MusicData[IY+2] ; TCycles:+19
  120.                                                                         ' XOR 24
  121.                                                                                 A = (A ~ 24)
  122.                                                                                 TCycles:+7 ' Condition not met
  123.                                                                 Else
  124.                                                                                 TCycles:+12 ' Condition met
  125.                                                         End If
  126.                                                 ' DJNZ
  127.                                                                 'DisJump=(twosum[PC1]+2) ; B:-1 ; BC=(B Shl 8)+C
  128.                                                                 If B<>0 Then  
  129.                                                                                 TCycles:+13 ' Condition met
  130.                                                                         Else
  131.                                                                                 TCycles:+8 ' Condition not met
  132.                                                                 End If
  133.                                                                 B:-1 ; BC=(B Shl 8)+C
  134.                                                 '
  135.                                 Until (B=0) ' B=0
  136.                                 ' DEC C ' Decrease C by one
  137.                                         C:-1 ; BC=(B Shl 8)+C ; TCycles:+4
  138.                                         If C=0 Then fZERO=1 Else fZERO=0 ' If E=0 then set Zero flag
  139.                         Until (fZERO=1)
  140.                 ' Check KEYBOARD and JOYSTICK
  141.                         CALL_37687() ; TCycles:+17      ' Check wether Enter or Fire is being pressed Code ignored but timing needed!
  142.                 '       RET NZ                                                          '        Return if ZERO flag is ReSet, if it is
  143.                                 If fZERO=0 Then
  144.                                                 TCycles:+11 ; Return fZERO
  145.                                         Else
  146.                                                 TCycles:+5
  147.                                 End If
  148.                 ' Pick up Keys AGAIN! This time we are Painting the keys WHITE Updating them
  149.                 ' Code is irrelevant but the timming is!
  150.                 ' LD A,(IY+1)           '  Get 2nd Byte of Tune Data from 33902
  151.                         A=MusicData[IY+1]  ; TCycles:+19
  152.                 '       CALL 37675                              ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
  153.                         A=Call_37675()  ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
  154.                         TCycles:+17             ' 17 Clock Cycles for the call
  155.                         ' Code there is irelevant, but time must be acounted for!
  156.                 ' LD (HL),56
  157.                                 TCycles:+10             ' 10 Clock Cycles for the LD(HL),40. Paints Piano key WHITE
  158.                 ' LD A,(IY+2)           '  Get 3rd Byte of Tune Data from 33902
  159.                         A=MusicData[IY+2]  ; TCycles:+19
  160.                 '       CALL 37675                              ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
  161.                         A=Call_37675()  ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
  162.                         TCycles:+17             ' 17 Clock Cycles for the call
  163.                         ' Code there is irelevant, but time must be acounted for!
  164.                 ' LD (HL),56
  165.                         TCycles:+10             ' 10 Clock Cycles for the LD(HL),40    
  166.                 ' INC IY                                        ' Increase IY by one
  167.                         IY:+1 ; TCycles:+10
  168.                 ' INC IY                                        ' Increase IY by one
  169.                         IY:+1 ; TCycles:+10
  170.                 ' INC IY                                        ' Increase IY by one
  171.                         IY:+1 ; TCycles:+10
  172.                 ' JR 37596
  173.                         TCycles:+12
  174.                 Wend
  175. End Function
  176.  
  177. ' Check Wether ENTER Or FIRE is being pressed
  178. Function Call_37687:Byte()
  179.                  Print "Checking Keyboard"
  180.                 ' LD A,(33881)
  181.                         A=0     ; TCycles:+13                                   ' Load A with Peek(33881). This Value will be 0 for this Purpose
  182.                 '       OR A                                                                                    ' Is Joystick Connected
  183.                         A=(A | A)
  184.                         'Print "A is "+A ; Repeat Until KeyDown(Key_SPACE)
  185.                         If A=0 Then fZERO=1 Else fZERO=0 ; TCycles:+4
  186.                         'Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
  187.                 ' JR Z,37698                                                            ' Jump Forwrad if not
  188.                         If fZERO=0 Then
  189.                                         ' IN A,(31)                                             ' Kempston Joystick PORT
  190.                                                 A=0 ; TCycles:+11               ' Assume FIRE is not being pressed, So we PASS back a 0!
  191.                                         ' BIT 4,A                                                       ' Was FIRE pressed?
  192.                                                 Local Ans:Byte
  193.                                                 Ans=(16 & A)                            ' Test Bit 4 ie, 2^4=16
  194.                                                 If Ans=0 Then fZERO=1 Else fZERO=0 ; TCycles:+8
  195.                                         ' RET NZ                                                        ' Return the ZERO FLAG if FIRE Pressed, Which it won't be
  196.                                                 If fZERO=1 Then
  197.                                                                 TCycles:+11 ; Return fZERO
  198.                                                         Else
  199.                                                                 TCycles:+5
  200.                                                 End If
  201.                         End If
  202.                         ' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
  203.                 ' LD BC,49140                                   ' Load BC ready to Read the Key Buffer for Keys_H_to_ENT
  204.                         BC=49150 ; TCycles:+10
  205.                         ' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
  206.                 ' IN A,(C)
  207.                         If KeyDown(Key_ENTER) Then
  208.                                         A=254 ; ENTER=1
  209.                                         'Print "A is "+A
  210.                                         'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
  211.                                 Else
  212.                                         A=255 ; ENTER=0
  213.                                         'Print "A is "+A
  214.                                         'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
  215.                         End If                                         
  216.                         TCycles:+11             ' if ENTER then A=254 else A=255
  217.                 ' AND 1
  218.                         A=(A & 1)
  219.                         If A=0 Then fZERO=1 Else fZERO=0
  220.                         TCycles:+7
  221.                 ' CP 1                          '  Has ENTER been pressed?
  222.                         If (A-1)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7
  223.                 '       RET
  224.                         TCycles:+10
  225. End Function
  226.  
  227. ' Update Keyboard Piano key and Value of A
  228. Function Call_37675:Byte()
  229.                 '       SUB 8
  230.                         A=A-8 ; TCycles:+7
  231.                         Local TempCARRY:Byte
  232.                 ' RRCA 
  233.                         TempCARRY=(A & 1) <> 0
  234.                         If TempCARRY=0 Then A=(A Shr 1) Else A=((A Shr 1) | 128)
  235.                         TCycles:+4
  236.                 ' RRCA 
  237.                         TempCARRY=(A & 1) <> 0
  238.                         If TempCARRY=0 Then A=(A Shr 1) Else A=((A Shr 1) | 128)
  239.                         TCycles:+4
  240.                 ' RRCA 
  241.                         TempCARRY:Byte=(A & 1) <> 0
  242.                         If TempCARRY=0 Then A=(A Shr 1) Else A=((A Shr 1) | 128)
  243.                         TCycles:+4     
  244.                 ' CPL
  245.                         A=(A ~ 255) ; TCycles:+4
  246.                 ' OR 224
  247.                         A=(A | 224) ; TCycles:+7
  248.                 ' LD L,A
  249.                         L=A ; TCycles:+4
  250.                 ' LD H,89
  251.                         H=89 ; TCycles:+7
  252.                 ' RET
  253.                         TCycles:+10 ; Return A
  254. End Function
  255.  
  256. Function OUT_CHUNKS(Port%, Value%)
  257.  
  258.         Const SAMPLE_RATE%=44100
  259.         Global NextSample:TAudioSample, PlayTime:Int
  260.         If Counter=0
  261.                 Print "Sample created"
  262.                 NextSample = CreateAudioSample(4410, SAMPLE_RATE, SF_MONO8 )
  263.                                                                         Print
  264.                                                                         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)
  265.                                                                         Print "OUT_CHUNKS CALLED!"
  266.         EndIf
  267.  
  268.         Select Value & 8
  269.                 Case 0
  270.                         NextSample.Samples[Counter]=50
  271.                 Case 8
  272.                         NextSample.Samples[Counter]=200
  273.         End Select
  274.         Counter=Counter + 1
  275.         If counter=4408
  276.                 NextSample.Samples[4408]=0    
  277.                 NextSample.Samples[4409]=0    
  278.                 Local Audio:TSound = LoadSound( NextSample )
  279.                 Repeat
  280.                   ' Delay 1
  281.                 Until PlayTime<MilliSecs()
  282.                 Print "play TSound"
  283.                 PlayTime=MilliSecs()+100
  284.                 PlaySound Audio
  285.                 Counter=0
  286.         EndIf
  287. End Function
  288.  
  289. Function Init()
  290.                 ' Reset Registers
  291.                 IY=0 ; A=0 ; B=0 ; C=0
  292.                 ' START SpecBlitz
  293.                 MainLoop()
  294. End Function
  295.  
  296. Function run()
  297.                 Call_37596()
  298.                 If fZERO=1 And Enter=0 Then
  299.                                 Print
  300.                                 Print "TUNE FINISHED"
  301.                                 Print "IY="+IY+" ... MusicData[IY]="+musicDATA[IY]
  302.                                 End
  303.                 End If
  304.                 'Print Key_Enter
  305.                 If ENTER=1 Then Print "ENTER was Pressed!" ; End
  306. 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
  1. Function OUT(port%, value%)
  2.      Global SwapIt=1-SwapIt
  3.      If SwapIt=1 Return
  4.      ....
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
  1. Function SlowDown()
  2.      Global Realtime:INT
  3.      If RealTime=0
  4.           RealTime=Millisecs()
  5.      Endif
  6.      If TCycles>3500
  7.           TCyles=TCyles-3500
  8.           RealTime=RealTime+1
  9.      Endif
  10.      While RealTime>Millisecs()
  11.           Delay 1
  12.      wend
  13. End Function
  14.  

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?

Code: [Select]
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

Code: [Select]
' 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!

Code: [Select]
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 :-

Code: [Select]
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
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 :-

Code: [Select]
' 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:
Code: [Select]
' 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
  1.     SuperStrict
  2.      
  3.     Graphics 800,600
  4.    
  5. 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]
  6.  
  7.     Global NextTime%, ReadPos%
  8.      
  9.      
  10.     Repeat
  11.             Wait
  12.             If Data[ReadPos]=255 End
  13.     Until AppTerminate()
  14.      
  15.      
  16.     Function Wait()
  17.             If NextTime<MilliSecs()
  18.                     Print ReadPos + " " + data[ReadPos] + " " + data[ReadPos+1] + " " + data[ReadPos+2]
  19.                                         Local duration%=100
  20.                     NextTime=MilliSecs()+Duration
  21.                    MakeSound Duration, Data[ReadPos], 0
  22.      
  23.                     ReadPos=ReadPos+1
  24.             EndIf  
  25.     End Function
  26.      
  27.      
  28.     Function MakeSound(Duration%,PulseA%, PulseB%)
  29.             Const SAMPLERATE% = 22000'Hz
  30.             Local SampleLen%, Half%
  31.             Local SampleA:TAudioSample, SampleB:TAudioSample, AudioA:TSound, AudioB:TSound
  32.            
  33.             SampleLen = SAMPLERATE*Duration/1000
  34.             If PulseA>0    
  35.                             SampleA = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )
  36.                             Half = PulseA/2
  37.                             For Local n:Int=0 Until SampleLen-1
  38.                                     If (n Mod PulseA) < half
  39.                                             SampleA.samples[n] = 200
  40.                                     Else
  41.                                             SampleA.samples[n] = 55
  42.                                     End If
  43.                             Next
  44.                             AudioA = LoadSound( SampleA )
  45.             EndIf
  46.             If PulseB>0            
  47.                             SampleB = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )        
  48.                             Half = PulseB/2
  49.                             For Local n:Int=0 Until SampleLen-1
  50.                                     If (n Mod PulseB)<half
  51.                                             SampleB.samples[n] = 200
  52.                                     Else
  53.                                             SampleB.samples[n] = 55
  54.                                     End If
  55.                             Next          
  56.                             AudioB = LoadSound( SampleB )
  57.             EndIf
  58.             If PulseA>0    
  59.                     PlaySound AudioA
  60.             EndIf
  61.             If PulseB>0
  62.                     PlaySound AudioB
  63.             EndIf
  64.     End Function
  65.  
Title: Re: Creating ZX SOUND
Post by: Baggey on April 03, 2021, 19:10:01
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
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.

Quote
Silence 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
  1. Function OUT_254(Port:Byte, Value:Byte)
  2.                                
  3.         If (Port & 1) = 0 Then ZXio.Border = (Value & 7) 'Border Colour
  4.                                
  5.                 ' The three lines of code speed the song up ie Twice the speed!
  6.                 'Global SwapIt:Byte
  7.                 'SwapIt=1-SwapIt
  8.                 'If SwapIt=1 Return
  9.                 '
  10.                 'If Port=254 Then
  11.  
  12.         Const SAMPLE_RATE:Int= 24000 ' 22050 ' 96000
  13.         Global NextSample:TAudioSample, PlayTime:Int
  14.  
  15.  
  16.         If MusicCounter=0
  17.                 'Print "Sample created"
  18.                 NextSample = CreateAudioSample(883, SAMPLE_RATE, SF_mono8 )
  19.                 'Print
  20.                 '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)
  21.                 'Print "OUT_CHUNKS CALLED!"
  22.         End If
  23.  
  24.         ' This is checking EAR/MIC Bit 
  25.         'Note if bit 8 is on or off this is normal volume
  26.         ' if bit 16 id on or off is like the Loud button        
  27.         'Value=(Value & 24)
  28.         '       NextSample.Samples[MusicCounter]=0
  29.         '       If Value = 16 Then NextSample.Samples[MusicCounter]=200
  30.         '       If Value = 8 Then NextSample.Samples[MusicCounter]=128
  31.         'If Value = 24 Then NextSample.Samples[MusicCounter]=250
  32.         'If Port=254 Then
  33.         'If (Value & 16) Then
  34.          '   NextSample.Samples[MusicCounter] = 250
  35.         'Else
  36.          '   NextSample.Samples[MusicCounter]=0
  37.         'End If
  38.  
  39.         Local out_value:Byte=(Value & 24)
  40.         Print "Play Sound!   Out value "+out_value
  41.        
  42.         Select out_value
  43.                 ' Note the other Case values are todo with the EAR/MIC socket, Which is Effecting the Sound produced!?                         
  44.                 Case 0
  45.                         NextSample.Samples[MusicCounter]=50
  46.                 Case 8
  47.                         NextSample.Samples[MusicCounter]=200
  48.                 Case 16
  49.                         NextSample.Samples[MusicCounter]=200
  50.                 Case 24
  51.                         NextSample.Samples[MusicCounter]=200
  52.  
  53.                 'Default
  54.                 '                               NextSample.Samples[MusicCounter]=0
  55.  
  56.         End Select
  57.  
  58.         MusicCounter:+1
  59.  
  60.         If MusicCounter=880 Then ' Why 800 or 880 Sound Samples to Sound Better?
  61.                 NextSample.Samples[881]=0          
  62.                 Local Audio:TSound = LoadSound( NextSample )
  63.                 Repeat
  64.                    'Delay 1
  65.                 Until PlayTime<MilliSecs()
  66.                 'Print "play TSound"
  67.                 PlayTime=MilliSecs()+5 ' Played with this Helps with Fast Sounds and long Sounds? Between 1 and 100 or starts to cause Emulator Lag
  68.                 PlaySound Audio
  69.                 'Stopsound audio
  70.                 MusicCounter=0
  71.                                                                
  72.         End If
  73.         'End If
  74. End Function
  75.  

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
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
  1. Import "RingBufferClassVanilla.bmx"                         ' import the BlitzMax Ringbuffer
  2. RingBufferClass.SetDriver("FreeAudio DirectSound")          ' needs to be this driver on Windows
  3. Global RingBuffer:RingBufferClass = New RingBufferClass     ' define the ringbuffer
  4. RingBuffer.CreateNew(40000, SF_MONO8)                       '  40000Hz and Mono 8bit
  5. Local WatchThread:TThread=CreateThread(WatchLoop, "")       ' define a multi-thread function
  6.  
  7. ' this is the function that cares about the ringbuffer DO NOT change anything
  8. Function  WatchLoop:Object(data:Object)
  9.         Repeat
  10.                 Delay 3
  11.                 RingBuffer.Watch
  12.         Forever
  13. End Function
  14.  
  15.  
  16. 'This is how you would send one single sample to the ringbuffer:
  17. RingBuffer.SendONE 200    ' sends a value of 200 to the buffer
  18.  




This is how your OUT_254 function would look now:

Code: BlitzMax
  1. SuperStrict
  2. Import "RingBufferClassVanilla.bmx"
  3.  
  4. Graphics 800,600
  5.  
  6. RingBufferClass.SetDriver("FreeAudio DirectSound")
  7. Global RingBuffer:RingBufferClass = New RingBufferClass
  8. RingBuffer.CreateNew(40000, SF_MONO8)
  9. Local WatchThread:TThread=CreateThread(WatchLoop, "")
  10.  
  11. Global A:Byte, B:Byte=1, C:Byte, D:Byte=0, E:Byte=0
  12. Global IY:Int
  13.  
  14. ' Music Data
  15. 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]
  16. Print( "DATA LENGTH: "+Len(data) )
  17.  
  18. CALL_37596()
  19.  
  20. Repeat
  21.         Delay 5
  22. Until AppTerminate()
  23. End
  24.  
  25.  
  26. Function  WatchLoop:Object(data:Object)
  27.         Repeat
  28.                 Delay 3
  29.                 RingBuffer.Watch
  30.         Forever
  31. End Function
  32.  
  33.  
  34.  
  35. Function CALL_37596:Int()
  36.         While True
  37.                 A = data[IY]
  38.                 If A=255
  39.                         Return True
  40.                 End If
  41.                 C = A  
  42.                 B = 0
  43.                 A = 0
  44.                 D = data[IY+1]
  45.                 E = data[IY+2]
  46.                 Repeat              
  47.                         Repeat                    
  48.                                 OUT_RING_ONE(254,A)    ' <-------------- NEW APPROACH
  49.                                 D :- 1
  50.                                 If D=0
  51.                                         D = data[IY+1]
  52.                                         A = A~24  
  53.                                 End If
  54.                                 E :- 1
  55.                                 If E=0
  56.                                         E = data[IY+2]                        
  57.                                 End If
  58.                                 B :- 1                         
  59.                         Until B=0
  60.                         C :- 1                 
  61.                 Until C=0
  62.                 IY :+ 3
  63.                 Print "Step in DATA=" +  iy
  64.         Wend
  65. End Function
  66.  
  67.  
  68.  
  69.  
  70. Function OUT_RING_ONE(Port%, Value%)
  71.         Select Value & 8
  72.                 Case 0
  73.                         RingBuffer.SendONE 50
  74.                 Case 8
  75.                         RingBuffer.SendONE 200
  76.         End Select
  77. End Function
  78.  


And this is the new RingBufferClass for BlitzMax 1.50:


Code: BlitzMax
  1. Type RingBufferClass
  2.         ' RingBufferClassVanilla.bmx
  3.         ' A permant running Audio-Output using FreeAudio
  4.         ' --------------------------------------------------
  5.         ' Author: Midimaster at www.midimaster.com
  6.         ' V1.1 2021-07-14
  7.         ' see examples of use at https://www.syntaxbomb.com/index.php/topic,8377.0.html
  8.         ' -------------------------------------------------------------------
  9.         ' minimal example:
  10.         '   RingBufferClass.SetDriver("FreeAudio....")
  11.         '   Global RingBuffer:RingBufferClass = New RingBufferClass
  12.         '   RingBuffer.CreateNew(HERTZ, FORMAT)
  13.         '   SendSize:INT  = RingBuffer.IntervalSamples()
  14.         '   SendTime:INT = RingBuffer.IntervalTime()
  15.         '   Local WatchThread:TThread=CreateThread(WatchLoop, "")
  16.         '   Global WriteTime = MilliSecs()
  17.         '   Function SendSamples
  18.         '               If WriteTime>MilliSecs() Return
  19.         '               WriteTime =WriteTime + SendTime
  20.         '               Local AudioArray:Int[SendSize]
  21.         '               For Local i:Int=0 To SendSize-1
  22.         '                       AudioArray[i] = any value....
  23.         '               Next
  24.         '               RingBuffer.Send AudioArray
  25.         '   End Function
  26.  
  27.         Global MyDriver$
  28.         Global BufferMutex:TMutex=CreateMutex()
  29.  
  30.         Field CHANNELS:Int, CHUNK_SIZE:Int, BUFFER_SAMPLES:Int, BUFFER_SIZE:Int
  31.         Field FORMAT:Int, CHUNK_TIME:Int, HERTZ:Int, BITS:Int
  32.         Field WritePointer:Int, ReadPointer:Int, RingPointer:Int
  33.         Field WriteTime:Int, WatchTime:Int, InFormat%
  34.  
  35.         Field RingBuffer:TAudioSample, InBuffer:TAudioSample, Sound:TSound
  36.         Field RingStream:TStream, InBufferStream:TStream
  37.        
  38.  
  39.  
  40.         Function SetDriver(Driver$)
  41.         ' PUBLIC: Use this to...
  42.                 ' select one of the audio drivers. It needs to be FreeAudio
  43.                 ' on Windows needs to be FreeAudio DirectSound
  44.                 If MyDriver<>"" Return
  45.                 If Driver.contains("FreeAudio")=False
  46.                         Notify "wrong AudioDriver"
  47.                         End
  48.                 EndIf
  49.                 MyDriver = Driver
  50.                 SetAudioDriver(MyDriver)
  51.         End Function
  52.        
  53.  
  54.        
  55.         Method CreateNew(Hertz%, UserFormat%=SF_STEREO16LE , Latency%=8)
  56.         ' PUBLIC: Use this to define the Ringbuffer...
  57.                         ' HERTZ should be a multiple of 1000 for CHUNK_TIME=10, 20, 40 or 50
  58.                         ' HERTZ can also be 44100 when CHUNK_TIME=20 or 40
  59.                         '
  60.                         ' UserFormat can be SF_MONO8 or SF_STEREO8 or SF_MONO16LE or SF_STEREO16LE
  61.                         '
  62.                         ' LATENCY can be from 1 to 32
  63.                         ' 2=extrem small, 4=normal size,  8-32..=secure sizes
  64.                         '      
  65.                         If MyDriver=""
  66.                                         Notify "No AudioDriver selected"
  67.                                         End
  68.                         EndIf
  69.                         Self.HERTZ=Hertz
  70.                         Self.FORMAT=SF_STEREO16LE
  71.                         Self.InFormat=UserFormat
  72.                         DefineBuffer Latency
  73.                         ClearBuffer
  74.                         WatchTime=MilliSecs()
  75.                         PlaySound Sound
  76.         End Method
  77.  
  78.  
  79.  
  80.         Method IntervalSamples:Int()
  81.         ' PUBLIC: Use this to...
  82.                 ' inform how many samples you should send each call    
  83.                 If  (InFormat=SF_MONO8) Or (InFormat=SF_MONO16LE)
  84.                         Return CHUNK_SIZE/4
  85.                 Else
  86.                         Return CHUNK_SIZE/2
  87.                 EndIf
  88.         End Method
  89.  
  90.  
  91.        
  92.         Method IntervalTime:Int()
  93.         ' PUBLIC: Use this to...
  94.                 ' inform how long you should wait between calls (in msecs)     
  95.                 Return CHUNK_TIME
  96.         End Method      
  97.  
  98.  
  99.  
  100.        
  101.         Method SendOne(Value:Int)
  102.         ' PUBLIC: Use this to...
  103.                 ' send one single sample value to the ringbuffer
  104.                 Global ShortCollector:Short[IntervalSamples()*2]
  105.             Global CollektorCounter:Int
  106.  
  107.                 Select InFormat
  108.                         Case SF_MONO8                                    
  109.                                 Value=(Value-128)*128
  110.                                 ShortCollector[CollektorCounter]=Value
  111.                                 ShortCollector[CollektorCounter+1]=Value
  112.                                 CollektorCounter=CollektorCounter+2
  113.                         Case SF_STEREO8                                          
  114.                                 Value=(Value-128)*128
  115.                                 ShortCollector[CollektorCounter]=Value
  116.                                 CollektorCounter=CollektorCounter+1
  117.                         Case SF_MONO16LE
  118.                                 ShortCollector[CollektorCounter]=Value
  119.                                 ShortCollector[CollektorCounter+1]=Value
  120.                                 CollektorCounter=CollektorCounter+2
  121.                         Case SF_STEREO16LE
  122.                                 ShortCollector[CollektorCounter]=Value
  123.                                 CollektorCounter=CollektorCounter+1
  124.                 End Select
  125.                 If CollektorCounter=IntervalSamples()*2
  126.                         CheckBufferOverRun
  127.                         Transfer ShortCollector
  128.                 CollektorCounter=0
  129.                 EndIf
  130.         End Method
  131.  
  132.  
  133.  
  134.  
  135.          Method Send(AudioArray:Int[])
  136.         ' PUBLIC:  Use this to...
  137.                 ' send a couple of samples value to the ringbuffer
  138.                 Local ShortArray:Short[]
  139.                 Local i%, v%
  140.                 Select InFormat
  141.                         Case SF_MONO8                                    
  142.                                         ShortArray= New Short[AudioArray.Length*2]
  143.                                         For i=0 To AudioArray.Length-1
  144.                                                 v=AudioArray[i]
  145.                                                 V=V-128
  146.                                                 ShortArray[2*i]=v*128
  147.                                                 ShortArray[2*i+1]=v*128
  148.                                         Next
  149.                         Case SF_STEREO8
  150.                                         ShortArray= New Short[AudioArray.Length]
  151.                                         For i=0 To AudioArray.Length-1
  152.                                                 v=AudioArray[i]
  153.                                                 V=V-128
  154.                                                 ShortArray[i]=v*128
  155.                                         Next
  156.                         Case SF_MONO16LE
  157.                                         ShortArray= New Short[AudioArray.Length*2]
  158.                                         For i=0 To AudioArray.Length-1
  159.                                                 ShortArray[2*i]=AudioArray[i]
  160.                                                 ShortArray[2*i+1]=AudioArray[i]
  161.                                         Next
  162.                         Case SF_STEREO16LE
  163.                                          ShortArray= New Short[AudioArray.Length]
  164.                                         For i=0 To AudioArray.Length-1
  165.                                                 ShortArray[i]=AudioArray[i]
  166.                                         Next
  167.                 End Select
  168.                 CheckBufferOverRun
  169.                 Transfer ShortArray
  170.         End Method     
  171.  
  172.  
  173. '
  174. '   E N D   O F   T H E   P U B L I C   F U N C T I O N S
  175. '
  176. ' ***************************************************************************
  177. '
  178. '    I N T E R N A L   F U N C T I O N S :
  179.  
  180.         Method Watch()
  181.         ' private: called by the thread WatchLoop(), cares about transfering ringbuffer content to the audio device
  182.                 If WatchTime<MilliSecs()
  183.                         WatchTime = WatchTime + CHUNK_TIME
  184.                         SendOneChunk
  185.                 EndIf
  186.         End Method
  187.                
  188.          Method DefineBuffer(Latency%)
  189.          ' private
  190.                         CHUNK_TIME=20
  191.                         BITS=16
  192.                         CHANNELS=2
  193.                         CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
  194.                         BUFFER_SAMPLES = 4*Latency * HERTZ * CHUNK_TIME             /1000
  195.                         BUFFER_SIZE    = BUFFER_SAMPLES * BITS/8 *CHANNELS
  196.                        
  197.                         RingBuffer     = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
  198.                         InBuffer       = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
  199.                         Local fa_sound:Int  =  fa_CreateSound( BUFFER_SAMPLES-1, BITS, CHANNELS, HERTZ, RingBuffer.Samples, $80000000 )
  200.                         Sound          = TFreeAudioSound.CreateWithSound( fa_sound, Null)
  201.                         RingStream     = CreateRamStream(RingBuffer.Samples , Buffer_size,True,True)
  202.                         InBufferStream = CreateRamStream(InBuffer.Samples,Buffer_size,True,True)
  203.                         RingPointer    =  BUFFER_SIZE/4
  204.         End Method
  205.  
  206.          Method SendOneChunk()
  207.          ' private
  208.                 LockMutex BufferMutex
  209.                         Local ReadPointerMod% = ReadPointer Mod BUFFER_SIZE
  210.                         InBufferStream.Seek ReadPointerMod
  211.                         RingStream.Seek RingPointer
  212.                         Local i%
  213.                         If ReadPointer + CHUNK_SIZE > WritePointer
  214.                                 Local Maxi%=WritePointer-ReadPointer
  215.                                 For i = 0 To Maxi-1
  216.                                                 RingStream.WriteByte InBufferStream.ReadByte()
  217.                                 Next
  218.                                 For i = Maxi To CHUNK_SIZE-1
  219.                                                 RingStream.WriteByte 0
  220.                                 Next
  221.                                 ReadPointer=ReadPointer + Maxi
  222.                         Else
  223.                                 For i = 0 To CHUNK_SIZE-1
  224.                                         RingStream.WriteByte InBufferStream.ReadByte()
  225.                                 Next
  226.                                 ReadPointer=ReadPointer + CHUNK_SIZE
  227.                         EndIf
  228.                         RingPointer=(RingPointer + CHUNK_SIZE) Mod (BUFFER_SIZE)
  229.                 UnlockMutex BufferMutex
  230.         End Method
  231.  
  232.          Method ClearBuffer()
  233.          ' private
  234.                         For Local i:Int =0 To BUFFER_SIZE
  235.                                 RingBuffer.Samples[i]=0
  236.                         Next   
  237.         End Method
  238.        
  239.          Method Transfer(ShortArray:Short[])
  240.          ' private
  241.                 LockMutex BufferMutex
  242.                 Local WritePointerMod% = WritePointer Mod BUFFER_SIZE
  243.                 InBufferStream.Seek WritePointerMod
  244.            Local i%
  245.                 If ShortArray.Length*2 + (WritePointerMod) <= BUFFER_SIZE
  246.                         For i=0 To ShortArray.Length-1
  247.                                 InBufferStream.WriteShort ShortArray[i]
  248.                         Next                   
  249.                 Else
  250.                
  251.                         Local Maxi% = BUFFER_SIZE-WritePointerMod
  252.                        
  253.                         For i=0 To Maxi/2-1
  254.                                 InBufferStream.WriteShort ShortArray[i]
  255.                         Next
  256.                         InBufferStream.Seek 0
  257.                         For i=Maxi/2 To ShortArray.Length-1
  258.                                 InBufferStream.WriteShort ShortArray[i]
  259.                         Next
  260.                 EndIf
  261.                 WritePointer=WritePointer + ShortArray.Length*2
  262.  
  263.                 UnlockMutex BufferMutex
  264.         End Method
  265.        
  266.         Method CheckBufferOverRun()
  267.         ' private  cares about buffer overruns and report if you send to fast
  268.                 Local grade%
  269.                         Local diff%=WritePointer-Readpointer
  270.                         diff=diff*100/BUFFER_SIZE
  271.                        
  272.                         If diff>80
  273.                                 Print "RINGBUFFER: Prevent Buffer Overrun! Wait for " + IntervalTime() + "msec"
  274.  
  275.                                 Delay IntervalTime()
  276.                                 'Print WritePointer + " " + Readpointer + " " + (WritePointer-Readpointer) + " " + BUFFER_SIZE
  277.                                 'Print diff + "%"
  278.                         EndIf
  279.         End Method
  280. End Type
  281.  
  282.  
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
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
  1. ' ManicMiner Player Using BlitzMax 1.5
  2.  
  3. SuperStrict
  4.  
  5. ' Add Code from the RingBufferClass
  6. Import "RingBufferClassVanilla.bmx"
  7.  
  8. Graphics 800,600
  9.  
  10. ' MidiMasters Stuff
  11. ' RingBufferClass Stuff
  12.         RingBufferClass.SetDriver("FreeAudio DirectSound")
  13.   Global RingBuffer:RingBufferClass = New RingBufferClass
  14.   RingBuffer.CreateNew(40000, SF_MONO8)
  15.   Local WatchThread:TThread=CreateThread(WatchLoop, "")
  16. ' RingBufferClass Stuff
  17.  
  18. ' RingBufferClass Functions
  19. Function OUT_RING_ONE(Port%, Value%)
  20.             Select Value & 8
  21.                     Case 0
  22.                             RingBuffer.SendONE 50
  23.                     Case 8
  24.                             RingBuffer.SendONE 200
  25.             End Select
  26. End Function
  27.  
  28. Function  WatchLoop:Object(data:Object)
  29.             Repeat
  30.                     Delay 3
  31.                     RingBuffer.Watch
  32.             Forever
  33. End Function
  34. ' RingBufferClass Functions
  35. ' MidiMasters Stuff END
  36.  
  37. Global RunSpeed:Int=16 ' Using Blitzmax 1.5 Gives roughly a 50Hz Frame Delay!
  38.  
  39. ' Registers 16 Bit
  40. Global IY:Short, BC:Short, DE:Short, HL:Short
  41.  
  42. ' Registers 8 Bit
  43. Global A:Byte, B:Byte, C:Byte, D:Byte, E:Byte, H:Byte, L:Byte
  44.  
  45. ' Control Registers
  46. Global TCycles:Int
  47. Global fZERO:Byte
  48. Global ENTER:Byte=0
  49. Global Counter:Int
  50.  
  51. ' Music Data
  52. 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]
  53. Print
  54. Print "Length of MusicData = "+(Len MusicData)
  55.  
  56. Init()
  57.  
  58. Function MainLoop()
  59.     Local starttime:Int = MilliSecs()
  60.        
  61.     While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)
  62.        
  63.                                 Run()
  64.         Local endtime:Int = MilliSecs()
  65.         Local diff:Int = endtime - starttime
  66.        
  67.         Local pausedelay:Int = RunSPEED ' About 50Hz ie one TV frame
  68.                        
  69.         If pausedelay > 0 Then
  70.             Delay(pausedelay)
  71.         Else
  72.             pausedelay = 0
  73.         End If
  74.         starttime = endtime+pausedelay
  75.     Wend
  76. End Function
  77.  
  78. Function Call_37596:Byte()
  79.  
  80.                 While Not AppTerminate() And Not KeyHit(KEY_ESCAPE)
  81.  
  82.                 ' LD A,(IY+0)           '  Get next Byte of Tune Data from 33902
  83.                                 A=MusicData[IY]  ; TCycles:+19
  84.                 ' CP 255                                '  Has the tune finished?
  85.                                 If (A-255)=0 Then fZERO=1 Else fZERO=0 ; TCycles:+7
  86.                 ' RET Z         '        Return if ZERO flag is Set
  87.                                 If fZERO=1 Then
  88.                                                 TCycles:+11 ; Return fZERO
  89.                                         Else
  90.                                                 TCycles:+5
  91.                                 End If
  92.                 '       LD C,A                                          ' Copy the first byte of MusicData for this note (which determines the duration) into C
  93.                                 C=A ; BC=(B Shl 8)+C ; TCycles:+4
  94.                 ' LD B,0                                                ' Load B with 0, which will be used as a delay counter in the note-producing loop              
  95.                                 B=0 ; BC=(B Shl 8)+C ; TCycles:+7      
  96.                 ' XOR A                         ' XOR A  Equivalent to LOAD A with 0 ( Incerting a delay of 4 TCycles )
  97.                                 A=(A ~ A) ; TCycles:+4
  98.                 ' LD D,(IY+1)                           ' Get second byte of MusicData for this note
  99.                                 D=MusicData[IY+1] ; DE=(D Shl 8)+E ; TCycles:+19
  100.                 ' LD A,D                                                ' Load A with D
  101.                                 A=D ; TCycles:+4
  102.                 '              
  103.                 ' FIRST CALL
  104.                 '
  105.                 ' CALL 37675                            ' Update on screen piano Key
  106.                                 TCycles:+17             ' 17 Clock Cycles for the call
  107.                                 ' Code there is irelevant, but time must br acounted for!
  108.                 '       LD (HL),80                        ' Set the Atribute colour of piano key 80 (INK 0: PAPER 2: BRIGHT 1)
  109.                                 TCycles:+10             ' 10 Clock Cycles for the LD(HL),40
  110.                 '       LD E,(IY+2)                             ' Get Third byte of MusicData for this note
  111.                                 E=MusicData[IY+2] ; D=(D Shl 8)+E ; TCycles:+19
  112.                 ' LD A,E                                        '       Load A with E
  113.                                 A=E ; TCycles:+4
  114.                 '
  115.                 ' SECOND CALL
  116.                 '
  117.                 '       CALL 37675                              ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
  118.                                 A=Call_37675()  ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
  119.                                 TCycles:+17             ' 17 Clock Cycles for the call
  120.                                 ' Code there is irelevant, but time must br acounted for!
  121.                 ' LD (HL),40
  122.                                 TCycles:+10             ' 10 Clock Cycles for the LD(HL),40
  123.                 Repeat ' C LOOP until C=0
  124.                         ' Part of the DJNZ
  125.                                 Repeat ' B LOOP until B=0
  126.                                 'For Local DJNZ:Int=B To 0 Step -1 ' until B=0
  127.                                                 'Print "B="+B
  128.                                                         OUT_RING_ONE(254,A) ; TCycles:+11
  129.                                                 ' DEC D                                 ' Decrease D by One
  130.                                                         D:-1 ; DE=(D Shl 8)+E ; TCycles:+4
  131.                                                         If D=0 Then fZERO=1 Else fZERO=0 ' If D=0 then set Zero flag
  132.                                                 ' JR NZ,37634
  133.                                                         If fZERO=1 Then
  134.                                                                         ' LD D,(IY+1)
  135.                                                                                 D = MusicData[IY+1]     ; DE=(D Shl 8)+E ; TCycles:+19
  136.                                                                         ' XOR 24
  137.                                                                                 A = (A ~ 24)                                                                   
  138.                                                                                 TCycles:+7 ' Condition not met
  139.                                                                 Else
  140.                                                                                 TCycles:+12 ' Condition met
  141.                                                         End If
  142.                                                 '       DEC E
  143.                                                         E:-1 ; DE=(D Shl 8)+E ; TCycles:+4
  144.                                                         If E=0 Then fZERO=1 Else fZERO=0 ' If E=0 then set Zero flag
  145.                                                 ' JR NZ,37642
  146.                                                         If fZERO=1 Then
  147.                                                                         ' LD E,(IY+1)
  148.                                                                                 E = MusicData[IY+2] ; TCycles:+19
  149.                                                                         ' XOR 24
  150.                                                                                 A = (A ~ 24)
  151.                                                                                 TCycles:+7 ' Condition not met
  152.                                                                 Else
  153.                                                                                 TCycles:+12 ' Condition met
  154.                                                         End If
  155.                                                 ' DJNZ
  156.                                                                 'DisJump=(twosum[PC1]+2) ; B:-1 ; BC=(B Shl 8)+C
  157.                                                                 If B<>0 Then  
  158.                                                                                 TCycles:+13 ' Condition met
  159.                                                                         Else
  160.                                                                                 TCycles:+8 ' Condition not met
  161.                                                                 End If
  162.                                                                 B:-1 ; BC=(B Shl 8)+C
  163.                                                 '
  164.                                 Until (B=0) ' B=0
  165.                                 ' DEC C ' Decrease C by one
  166.                                         C:-1 ; BC=(B Shl 8)+C ; TCycles:+4
  167.                                         If C=0 Then fZERO=1 Else fZERO=0 ' If E=0 then set Zero flag
  168.                         Until (fZERO=1)
  169.                 ' Check KEYBOARD and JOYSTICK
  170.                         CALL_37687() ; TCycles:+17      ' Check wether Enter or Fire is being pressed Code ignored but timing needed!
  171.                 '       RET NZ                                                          '        Return if ZERO flag is ReSet, if it is
  172.                                 If fZERO=0 Then
  173.                                                 TCycles:+11 ; Return fZERO
  174.                                         Else
  175.                                                 TCycles:+5
  176.                                 End If
  177.                 ' Pick up Keys AGAIN! This time we are Painting the keys WHITE Updating them
  178.                 ' Code is irrelevant but the timming is!
  179.                 ' LD A,(IY+1)           '  Get 2nd Byte of Tune Data from 33902
  180.                         A=MusicData[IY+1]  ; TCycles:+19
  181.                 '       CALL 37675                              ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
  182.                         A=Call_37675()  ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
  183.                         TCycles:+17             ' 17 Clock Cycles for the call
  184.                         ' Code there is irelevant, but time must be acounted for!
  185.                 ' LD (HL),56
  186.                                 TCycles:+10             ' 10 Clock Cycles for the LD(HL),40. Paints Piano key WHITE
  187.                 ' LD A,(IY+2)           '  Get 3rd Byte of Tune Data from 33902
  188.                         A=MusicData[IY+2]  ; TCycles:+19
  189.                 '       CALL 37675                              ' Note A is returned with the value for the OUT(254),A! Also Update's on screen piano Key
  190.                         A=Call_37675()  ' A IS RETURNED ALTERD CRUCIAL FOR THE VALUE OF A!
  191.                         TCycles:+17             ' 17 Clock Cycles for the call
  192.                         ' Code there is irelevant, but time must be acounted for!
  193.                 ' LD (HL),56
  194.                         TCycles:+10             ' 10 Clock Cycles for the LD(HL),40    
  195.                 ' INC IY                                        ' Increase IY by one
  196.                         IY:+1 ; TCycles:+10
  197.                 ' INC IY                                        ' Increase IY by one
  198.                         IY:+1 ; TCycles:+10
  199.                 ' INC IY                                        ' Increase IY by one
  200.                         IY:+1 ; TCycles:+10
  201.                 ' JR 37596
  202.                         TCycles:+12
  203.                 Wend
  204. End Function
  205.  
  206. ' Check Wether ENTER Or FIRE is being pressed
  207. Function Call_37687:Byte()
  208.                  Print "Checking Keyboard"
  209.                 ' LD A,(33881)
  210.                         A=0     ; TCycles:+13                                   ' Load A with Peek(33881). This Value will be 0 for this Purpose
  211.                 '       OR A                                                                                    ' Is Joystick Connected
  212.                         A=(A | A)
  213.                         'Print "A is "+A ; Repeat Until KeyDown(Key_SPACE)
  214.                         If A=0 Then fZERO=1 Else fZERO=0 ; TCycles:+4
  215.                         'Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
  216.                 ' JR Z,37698                                                            ' Jump Forwrad if not
  217.                         If fZERO=0 Then
  218.                                         ' IN A,(31)                                             ' Kempston Joystick PORT
  219.                                                 A=0 ; TCycles:+11               ' Assume FIRE is not being pressed, So we PASS back a 0!
  220.                                         ' BIT 4,A                                                       ' Was FIRE pressed?
  221.                                                 Local Ans:Byte
  222.                                                 Ans=(16 & A)                            ' Test Bit 4 ie, 2^4=16
  223.                                                 If Ans=0 Then fZERO=1 Else fZERO=0 ; TCycles:+8
  224.                                         ' RET NZ                                                        ' Return the ZERO FLAG if FIRE Pressed, Which it won't be
  225.                                                 If fZERO=1 Then
  226.                                                                 TCycles:+11 ; Return fZERO
  227.                                                         Else
  228.                                                                 TCycles:+5
  229.                                                 End If
  230.                         End If
  231.                         ' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
  232.                 ' LD BC,49140                                   ' Load BC ready to Read the Key Buffer for Keys_H_to_ENT
  233.                         BC=49150 ; TCycles:+10
  234.                         ' Print "GOT HERE!" ' For Testing Key Press FLAG Loop!
  235.                 ' IN A,(C)
  236.                         If KeyDown(Key_ENTER) Then
  237.                                         A=254 ; ENTER=1
  238.                                         'Print "A is "+A
  239.                                         'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
  240.                                 Else
  241.                                         A=255 ; ENTER=0
  242.                                         'Print "A is "+A
  243.                                         'Print "Key_ENTER" ; Repeat Until KeyDown(Key_SPACE)
  244.                         End