October 27, 2021, 08:10:00

Author Topic: Creating ZX SOUND  (Read 7093 times)

Offline Scaremonger

  • Full Member
  • ***
  • Posts: 233
    • ITSpeedway - Ramblings of a geek!
Re: Creating ZX SOUND
« Reply #45 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.

Offline Steve Elliott

  • Hero Member
  • *****
  • Posts: 3237
  • elgol
Re: Creating ZX SOUND
« Reply #46 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
Windows 10 64-bit, 16Gb RAM, Intel i5 3.2 GHz, Nvidia GeForce GTX 1050 (2Gb)
MacOS Big Sur 64-bit, 8Gb RAM, Intel i5 2.3 Ghz, Intel Iris Plus Graphics 640 1536 MB
Linux Mint 19.3 64-bit, 16Gb RAM, Intel i5 3.2 GHz, Nvidia GeForce GTX 1050 (2Gb)
Raspberry pi 3, pi 4, pi 400, BBC B, C64, ZX Spectrum

Offline Baggey

  • Full Member
  • ***
  • Posts: 198
Re: Creating ZX SOUND
« Reply #47 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
Currently Running a PC that just Aint fast enough!?
ZX Spectrum 48k, NEXT, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip.

Jesus was only famous because of his DAD.    Resistance Is Futile! The code will be assimulated!

Offline Baggey

  • Full Member
  • ***
  • Posts: 198
Re: Creating ZX SOUND
« Reply #48 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
« Last Edit: February 12, 2021, 17:26:29 by Baggey »
Currently Running a PC that just Aint fast enough!?
ZX Spectrum 48k, NEXT, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip.

Jesus was only famous because of his DAD.    Resistance Is Futile! The code will be assimulated!

Offline Steve Elliott

  • Hero Member
  • *****
  • Posts: 3237
  • elgol
Re: Creating ZX SOUND
« Reply #49 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.
« Last Edit: February 15, 2021, 17:07:35 by Steve Elliott »
Windows 10 64-bit, 16Gb RAM, Intel i5 3.2 GHz, Nvidia GeForce GTX 1050 (2Gb)
MacOS Big Sur 64-bit, 8Gb RAM, Intel i5 2.3 Ghz, Intel Iris Plus Graphics 640 1536 MB
Linux Mint 19.3 64-bit, 16Gb RAM, Intel i5 3.2 GHz, Nvidia GeForce GTX 1050 (2Gb)
Raspberry pi 3, pi 4, pi 400, BBC B, C64, ZX Spectrum

Offline Baggey

  • Full Member
  • ***
  • Posts: 198
Re: Creating ZX SOUND
« Reply #50 on: February 12, 2021, 18:05:49 »
So, Thought i'd reflect a bit and step back.

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



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

Interestingly it's not a TRUE square wave either it's a Saw tooth!? So thats time to go full on and time to decay off.
Im looking at the waveform of the title screen music and that has 3 milli Secs as well.
Currently Running a PC that just Aint fast enough!?
ZX Spectrum 48k, NEXT, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip.

Jesus was only famous because of his DAD.    Resistance Is Futile! The code will be assimulated!

Offline Baggey

  • Full Member
  • ***
  • Posts: 198
Re: Creating ZX SOUND
« Reply #51 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
« Last Edit: February 12, 2021, 19:03:43 by Baggey »
Currently Running a PC that just Aint fast enough!?
ZX Spectrum 48k, NEXT, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip.

Jesus was only famous because of his DAD.    Resistance Is Futile! The code will be assimulated!

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: Creating ZX SOUND
« Reply #52 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.
« Last Edit: February 12, 2021, 18:30:13 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Baggey

  • Full Member
  • ***
  • Posts: 198
Re: Creating ZX SOUND
« Reply #53 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:


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
« Last Edit: February 12, 2021, 18:30:55 by Baggey »
Currently Running a PC that just Aint fast enough!?
ZX Spectrum 48k, NEXT, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip.

Jesus was only famous because of his DAD.    Resistance Is Futile! The code will be assimulated!

Offline Scaremonger

  • Full Member
  • ***
  • Posts: 233
    • ITSpeedway - Ramblings of a geek!
Re: Creating ZX SOUND
« Reply #54 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()


Offline iWasAdam

  • Hero Member
  • *****
  • Posts: 2484
Re: Creating ZX SOUND
« Reply #55 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?

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: Creating ZX SOUND
« Reply #56 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.    
« Last Edit: February 16, 2021, 05:42:53 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Baggey

  • Full Member
  • ***
  • Posts: 198
Re: Creating ZX SOUND
« Reply #57 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
Currently Running a PC that just Aint fast enough!?
ZX Spectrum 48k, NEXT, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip.

Jesus was only famous because of his DAD.    Resistance Is Futile! The code will be assimulated!

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: Creating ZX SOUND
« Reply #58 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
« Last Edit: February 18, 2021, 02:20:25 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: Creating ZX SOUND
« Reply #59 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.  
« Last Edit: February 18, 2021, 09:54:20 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal