SuperStrictRem 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 RemType 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 MethodEnd TypeType 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 MethodEnd TypeType 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 MethodEnd 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 MethodEnd StructType 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=MN0017603Local music:TMusic = New TMusic( 62, 2, 2 )'# BAR 1music.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 2music.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 3music.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 4music.add( CScale.F2, CNote.quarter+CNote.eighth )music.add( CScale.D2, CNote.eighth, True )music.add( CScale.D2, CNote.half )'# BAR 5music.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 6music.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 7music.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 8music.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 9music.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 10music.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 11music.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 12music.add( CScale.F2, CNote.quarter+CNote.eighth )music.add( CScale.D2, CNote.eighth, True )music.add( CScale.D2, CNote.half )'# BAR 13music.add( CScale.D2, CNote.half )music.add( CScale.F2, CNote.half )'#music.generate()Graphics 320,200Repeat Cls DrawText( "P - Play", 0, 0 ) DrawText( "ESC - Exit", 0, TextHeight("8g") ) Flip If KeyHit( KEY_P ) music.play() Until KeyHit( KEY_ESCAPE )#cantinabandDefData CTempo.Ragtime, 2, 2 DefData CScale.A2, CNote.quarter, True
'# ZX Spectrum 16K/48K retro sound'# (c) Copyright Si Dunford, February 2021, All Rights Reserved'# Version 0.1SuperStrict' ZX Spectrum sample' https://worldofspectrum.org/ZXBasicManual/zxmanchap19.htmlPrint "Frere Gustav"BEEP 1,0 ; BEEP 1,2 ; BEEP .5,3 ; BEEP .5,2 ; BEEP 1,0BEEP 1,0 ; BEEP 1,2 ; BEEP .5,3 ; BEEP.5,2 ; BEEP 1,0BEEP 1,3 ; BEEP 1,5 ; BEEP 2,7BEEP 1,3 ; BEEP 1,5 ; BEEP 2,7BEEP .75,7 ; BEEP .25,8 ; BEEP .5,7 ; BEEP .5,5 ;BEEP .5,3 ; BEEP.5,2 ; BEEP 1,0BEEP .75,7 ; BEEP .25,8 ; BEEP .5,7 ; BEEP .5,5 ; BEEP .5,3 ; BEEP .5,2 ; BEEP 1,0BEEP 1,0 ; BEEP 1,-5 ; BEEP 2,0BEEP 1,0 ; BEEP 1,-5 ; BEEP 2,0' Wait until Enter pressedInputConst 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.0Function 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 ) WendEnd Function
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)
the simple way is to create a single sample say 128frames in lengthfill this with a square wave (< 64 sample=-1 > 64 sample =1)play this as a sound with a loop - thats your baseplay the sound through a channel to allow for direct manipulationuse pitch, pan, vol to do all the other stuff <-these are channel controlsSpectrum only used square waves128 used a sound chip - you can emulate it - but it's not so simple. basically 3 voices + noise
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.1SuperStrict' ZX Spectrum sample' https://worldofspectrum.org/ZXBasicManual/zxmanchap19.htmlPrint "Frere Gustav"BEEP 1,0 ; BEEP 1,2 ; BEEP .5,3 ; BEEP .5,2 ; BEEP 1,0BEEP 1,0 ; BEEP 1,2 ; BEEP .5,3 ; BEEP.5,2 ; BEEP 1,0BEEP 1,3 ; BEEP 1,5 ; BEEP 2,7BEEP 1,3 ; BEEP 1,5 ; BEEP 2,7BEEP .75,7 ; BEEP .25,8 ; BEEP .5,7 ; BEEP .5,5 ;BEEP .5,3 ; BEEP.5,2 ; BEEP 1,0BEEP .75,7 ; BEEP .25,8 ; BEEP .5,7 ; BEEP .5,5 ; BEEP .5,3 ; BEEP .5,2 ; BEEP 1,0BEEP 1,0 ; BEEP 1,-5 ; BEEP 2,0BEEP 1,0 ; BEEP 1,-5 ; BEEP 2,0' Wait until Enter pressedInputConst 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.0Function 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 ) WendEnd FunctionThere 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...
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...
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
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
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,255I 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".
SPECTRUM DATA MUSICAL NOTE NOTE PITCH FREQUENCY50,51,205, Crochet, E5, 16 660 Hz 50,64,65, Crochet, C5, 12 523 Hz50,102,103, Crochet, E4, 4 330 Hz100,102,103, Minim, E4, 4 330 Hz50,114,115, Crotchet, D4, 2 293 Hz100,76,77, Minim, A4, 9 440 Hz50,86,87, Crotchet, G4, 7 392 Hz50,128,203, Dotted Crochet, C4, 0 262 Hz25,128,0, Quaver, C4, 0 262 Hz25,128,129, Crotchet, C4, 0 262 Hz50,128,203, Dotted Crochet, C4, 0 262 Hz255
NOTE PITCH FREQ SPEC-DATAC4 0 262 Hz 128C4# 1 277 HzD4 2 294 Hz 114 D4# 3 311 HzE4 4 300 Hz 102F4 5 349 HzF4# 6 370 HzG4 7 392 Hz 86G4# 8 415 HzA4 9 440 Hz 76A4# 10 466 HzB4 11 494 HzC5 12 523 Hz 64
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: