Audio Saving problem (and clicks on Mac)

BlitzMax Forums/BlitzMax Programming/Audio Saving problem (and clicks on Mac)

Casaber(Posted 2016) [#1]
So I got the strength and some time to sit down to code and I thought I'll try learn Bmax some more.
I found some nice examples here in the different forums.

Got something running, but the save does not work for me on Mac.
I get a 44 bytes file and this error message:
2016-03-01 16:30:05.342 record[6725:348438] -[NSColorSpaceColor _boolForRegID:]: unrecognized selector sent to instance 0x38a250
2016-03-01 16:30:05.343 record[6725:348438] -[NSColorSpaceColor _boolForRegID:]: unrecognized selector sent To instance 0x38a250

Also another and perhaps bigger problem;
While using other examples (not this one, this one works perfectly except saving..
I discovered clicks on Mac (Windows does not has this issue at all, but OSX does). And I finally bumped into this; http://www.blitzbasic.com/Community/posts.php?topic=87431
It might be the same issue I´m not sure. If so are this thread still relevant or is it incorporated in the newest Bmax download by now?

If it's still relevant, I´m really not that savvy grasping those steps mentioned in the thread so how would I do that exactly?

* RES renamed properly into BYTES in all occasions now.
* Wrappable timer check.

EnableOpenALAudio; SetAudioDriver "OpenAL" ; Graphics 800,600
Global Device% ; Device=alcOpenDevice(Null) ; Global CapDev%
CapDev = alcCaptureOpenDevice(Null,44100,AL_FORMAT_MONO16,44100*2*1*5) ; alcCaptureStart CapDev
Global Num% , Sum% , RecTime% , RecordNow%=1 , Channels%=1 , Bytes%=2 , Space%= 44100 * Channels * Bytes% * 240 , music:TSound
Global TestSample:TAudioSample=CreateAudioSample(Space,44100,SF_MONO16LE)

Repeat
    If RecTime - MilliSecs() < 0 ' Wrappable MillSec (was If RecTime < MilliSecs() )
       RecTime = MilliSecs() +500 ;alcGetIntegerv(CapDev,ALC_CAPTURE_SAMPLES,4,Varptr(Num))
       If (Sum + (Num*Channels*Bytes)) < Space Then alcCaptureSamples(CapDev,TestSample.Samples+Sum,Num) ; Sum = Sum + (Num * Channels* Bytes)
    EndIf
    If KeyHit(Key_R) And RecordNow = 0 Then RecordNow = 1 ; alcCaptureStart(CapDev) ; Sum = 0 ' Record from mic
    If KeyHit(Key_P) And RecordNow = 0 Then Music = LoadSound(TestSample) ; PlaySound Music    ' Playback
    If KeyHit(Key_S) And RecordNow = 1 Then RecordNow = 0 ; alcCaptureStop CapDev                   ' Stop
    Flip 0
Until KeyHit(Key_Escape)
alcCloseDevice CapDev ; alcCloseDevice Device
SaveWAV
End

Function SaveWAV()
   Local name$ = RequestFile$("Filename...","Audio:wav",True) , SndBank:TBank,FileStream:TStream , Bits = 16
   SndBank = CreateStaticBank(TestSample.samples,Sum) ; FileStream = WriteStream(name$)
      If FileStream Then
         fileStream.WriteString "RIFF"                               ' Fixed header (always 4 bytes)
         fileStream.WriteInt Sum + 40                              ' File size
         fileStream.WriteString "WAVE"                             ' Fixed header (always 4 bytes)
         fileStream.WriteString "fmt "                                ' Fixed header (always 4 bytes)
         fileStream.WriteInt 16                                         ' Size of WAVE section chunk
         fileStream.WriteShort 1                                       ' WAVE type format 
         fileStream.WriteShort Channels                            ' Mono/stereo 
         fileStream.WriteInt Testsample.Hertz                    ' Sample-Rate 44100
         fileStream.WriteInt Testsample.Hertz * Channels * Bytes ' Bytes/sec
         fileStream.WriteShort Channels * Bytes                ' Bytes/sample
         fileStream.WriteShort Bits                                    ' 8 or 16 bits
         fileStream.WriteString "data"                               ' Fixed header (always 4 bytes)
         fileStream.WriteInt Sum                                      ' Size of sample data
         SndBank.Write FileStream,0,Sum ; CloseStream FileStream
   EndIf
End Function



Floyd(Posted 2016) [#2]
Just guessing here, but all of those fileStream.Write commands write a total of 44 bytes.
That means the final SndBank.Write is not writing anything. Is Sum zero?

As a reality check you could add one line of code to SaveWAV()
         Print "Sum = " + Sum
         SndBank.Write FileStream,0,Sum ; CloseStream FileStream

If Sum is zero then you have to figure out why. One possible source of trouble is Millisecs(), which may have wrapped around to a negative value if your computer has not been rebooted in a very long time.

You are doing

If RecTime < MilliSecs()

which can fail if MilliSec() is negative. The right way to test is
If RecTime - MilliSecs() < 0
That will work even if MilliSecs() has wrapped around.


grable(Posted 2016) [#3]
Res doesnt exist, so it will be 0, thus the line
Sum = Sum + (Num * Channels* Res)
Will always be zero too.

You should get into the habit of at least using Strict so this kinda thing never happens ;)


Casaber(Posted 2016) [#4]
@Floyd I did exactly that before posting and ya that's the 44 bytes yup. I didn't come much longer though with that. I got that nasty error message and it's all new to me what it means.

@Grable Ah thanks for making me notice that, ya, I renamed res into bytes, that's why,I did several versions and somewhere the mix of the two made into this one. Thats certainly one bit of the puzzle. I edited the source now to be true to each variables name. I´m in the process of learning to use SUPERSTRICT and see how that feels.

Still the same error, but it saved 11MB this time. I could play it in VLC.
BMax still gives this error on exit
2016-03-01 18:45:04.938 untitled10[7018:369474] -[NSColorSpaceColor _boolForRegID:]: unrecognized selector sent to instance 0x273270
2016-03-01 18:45:04.938 untitled10[7018:369474] -[NSColorSpaceColor _boolForRegID:]: unrecognized selector sent to instance 0x273270

I narrowed the error down to these, but have no clue what it is
Local name$ = RequestFile$("Filename...","Audio:wav",True) , SndBank:TBank,FileStream:TStream , Bits = 16
SndBank = CreateStaticBank(TestSample.samples,Sum) ; FileStream = WriteStream(name$)


Casaber(Posted 2016) [#5]
I narrowed it down further, it was the requestfile, how to fix that?


Casaber(Posted 2016) [#6]
I must say that I´ve had alot od errors with GUI alot with Mac
either they appear in the background,a and does not really distrurb the exits,
or they mess everything up all together.

But I´ve had alot of issues with BMax GUI + Mac . I think the GUI is unstable somehow. Or I´ve missed something vital.

How about that update procdure that fixes glitches on Mac?
I'm ready to try that now if someone wants to guide me I'll do my best.


grable(Posted 2016) [#7]
There is no usage of NSColorSpaceColor in any of the *.m files though, so i dont know where that comes from.
And looking at the class here, it doesnt have this method (and never did, as far i can tell).
EDIT: Not the same class name i noticed, but i cant find anything on NSColorSpaceColor, only NSColorSpace.

I guess it boils down to what version of BlitzMax your running and on which OSX version.
Try getting hold of Brucey, he also uses a mac and im sure he would know if its related to that. Even though hes more focused on bmx-ng these days, he still tries to be compatible with vanilla.


Casaber(Posted 2016) [#8]
Okay ya, I won´t bother him as bmx-ng is sounds like the better thing to work on. I don´t need GUI.

I use the latest version of Bmax that comes precompiled v6.1 I think?
It says 1.50 in the aboutbox but i know its higher. I did that misstake once already. I got that latest "Mac fixed version".

I think sincerly think BMax is faulty there somewhere when it comes to GUI. Alot to fix. This is not the the 1st weird error.

EDIT for the record I use Yosemite 10.10.5 right now. And the latest Bmax that's downloadable from blitzbasic.com accounts.


Casaber(Posted 2016) [#9]
I should probably add that it's only Mac this happens on. Windows haven't had those errors.

Though they share odd behaviours.. such as the restore button which always stops working suddenly as it should.
This happens both on Mac and PC in all the demos I´ve tried. I havn't been able to rule it our as a bug yet.
It works, it works, and then.. it doesn't. That's the pattern. It might stretch the window vertically but not horisontally, or not at all.


Casaber(Posted 2016) [#10]
About the click, this is what I use as a try bed now. I think you need a Mac to hear the clicking, or else its the buffersize that needs to be increase.
With Mac's it does not matter if you increase the buffers, you get regular clicking at each buffer swap. Somethings terribly wrong.


' List all audiodrivers on the system
' For Local a$=EachIn AudioDrivers() ; Print a ; Next

Rem This is what I get on the Mac for example
OpenAL Default
OpenAL Built-in Output
OpenAL
FreeAudio
FreeAudio CoreAudio
EndRem

SuperStrict
Graphics w,h ; EnableOpenALAudio ; SetAudioDriver "OpenAL"
Const s:Int = 1024 , w:Int = 1280 , h:Int = 800 ' If something clicks, try increase buffersize (s)
Global src:Int , b:Int[3] , al:Short[s] , format:Int = AL_FORMAT_MONO16 , buf:Byte Ptr = Byte Ptr al , hz:Double = 1.0
Global a:Double = 0.0 , buffer:Int , val:Int,d:Int , c:Int , vol:Double = 0.0 , i:Int

' Init OpenAl (Tripple buffering, queue buffers and start playing)
' Samplerate = 44.1Khz, bits = 16, Channels = Mono, Buffersize = 1024 samples

d=alcOpenDevice(Null) ; C=alcCreateContext(d,Null) ; alcMakeContextCurrent C ; alGenBuffers 3,b ; alGenSources 1,Varptr src
alBufferData b[0],format,buf,s*2,44100 ; alBufferData b[1],format,buf,s*2,44100 ; alBufferData b[2],format,buf,s*2,44100
alSourceQueueBuffers src,1,Varptr b[0] ; alSourceQueueBuffers src,1,Varptr b[1] ; alSourceQueueBuffers src,1,Varptr b[2] ; alSourcePlay src

' Clear buffer
For Local i:Int = 0 Until s ; AL[i] = 0 ; Next

Repeat
Cls
' -------------------------------------------------------------------------
alGetSourcei src,AL_BUFFERS_PROCESSED,Varptr val
	While val 
		alSourceUnqueueBuffers src,1,Varptr buffer
		For i = 0 Until s ; AL[i] = vol*Sin(a)*$7fff ; hz:+((0.5*2^(MouseX()/(0.125*w)))-hz)*0.0002 ; a:+hz ; vol:+((1.0-(Double(MouseY())/h))-vol)*0.0002 ; Next
		alBufferData buffer,format,buf,s*2,44100 ; alSourceQueueBuffers src,1,Varptr buffer ; val:-1
	Wend
alGetSourcei src,AL_SOURCE_STATE,Varptr val ; If val <> AL_PLAYING Then alSourcePlay src
' -------------------------------------------------------------------------

SetColor 0,255,0 ; For i = 0 Until s Step 2 ; Plot i,10+700*(Float((AL[i]+$7fff) Mod $ffff)/$ffff) ; Next
Flip 1
Until KeyHit(KEY_ESCAPE) Or AppTerminate()
alcDestroyContext C ; alcCloseDevice d



dw817(Posted 2016) [#11]
Interesting program, Casaber. Sounds like something out of Star Trek.

Are you trying to achieve a pure tone ?


Casaber(Posted 2016) [#12]
@dw817 I found lots of examples on the forum and one was one of those Russian exotic instruments what's the name? Theremins.
Those where always used for those old Horror movies and ya Star trek, so you couldn't be more right.

I´m trying to have pure tone (any tone really) without the glitching happening. It's the glitching that's the problem,
It's pretty intense how deep it seem to go. I hope it's possible to fix. Becuase BMax would be so nice to use for crossplatforms.

You didn´t hear any cracks on Windows did you?


dw817(Posted 2016) [#13]
Lots of clicking. I'm beginning to wonder if going the route of building a sound this way is different on each PC.

I've got some book chapters to write (every Tuesday) but once I finish w that I'm going to look to see how a .WAV file is formed and see if I can't build something in there.


BlitzMan(Posted 2016) [#14]
@dw817 can not wait to see your book guy.It will be Biblical im sure.


dw817(Posted 2016) [#15]
Heh, it's already done. Well several books actually, science fiction story, Future Barrier (completing 2nd book), The Dream Machine (done), and Nancy Principle, 2 books complete. On Tuesdays I work on Dream Diary though which is not a book per se.

You can find that here.

READING LINK.


BlitzMan(Posted 2016) [#16]
See the link.It is more small thought`s than story.Everyone has a book in them.Mine is everyone dies on this planet ,and i start again with EVE.ie:MEGAN FOX


Casaber(Posted 2016) [#17]
@dw817 I can give you a boost learning wav, lemme get down the code just as a guide for the contents of the header, sorry about the layout at the right... I tried, I never got the hang of tabs.

Function SaveWAV()
   Local name$ = RequestFile$("Filename...","Audio:wav",True) , SndBank:TBank,FileStream:TStream , Bits = 16
   SndBank = CreateStaticBank(TestSample.samples,Sum) ; FileStream = WriteStream(name$)
      If FileStream Then
         fileStream.WriteString "RIFF"                                            ' Fixed header (12 bytes total chunk)
       * fileStream.WriteInt Sum + 40                                             ' Chunk size (Overall)
         fileStream.WriteString "WAVE"                                            ' Fixed header
         ' ----------------------------------------------------------------------------------------
         fileStream.WriteString "fmt "                                            ' Fixed header (always 4 bytes) (24 bytes total chunk) 
         fileStream.WriteInt 16                                                   ' Chunk size
         fileStream.WriteShort 1                                                  ' PCM aka WAVE audio format = 1 (change to 3 for WAVE_FORMAT_IEEE_FLOAT)
    >  * fileStream.WriteShort Channels                                           ' 1=Mono/2=stereo (number of channels)
    >  * fileStream.WriteInt Testsample.Hertz                                     ' e.g. 44100 / 48000 / 96000 / 192000 / anything (samplerate) (samples per second) 
       * fileStream.WriteInt Testsample.Hertz * Channels * Bytes                  ' Bytes/sec aka (also called Byterate) You calculate this one, using two of the other fields:  samplerate * blockalign.
       * fileStream.WriteShort Channels * Bytes                                   ' Bytes/sample (also called blockalign) You calculate this one, using two of the other fields:  channels * (bitspersample / 8)
    >  * fileStream.WriteShort Bits                                               ' Bits per sample (8/16/32) (bit depth)    
         ' ----------------------------------------------------------------------------------------
         fileStream.WriteString "data"                                            ' Fixed header (always 4 bytes) (8 bytes + actual wave total chunk)
       * fileStream.WriteInt Sum                                                  ' Bytes of sample data which is about to come
         SndBank.Write FileStream,0,Sum ; CloseStream FileStream ' Audiodata
   EndIf


This is the common wav model it uses 3 chunks (44 bytes in total) + data
You can have inbetween extra chunks but its EXTREMELY rare, you can safely ignore those.

Everything is pretty static, I pointed out the 7 things (3 really as the rest are calculated and not interesting, but I marked them as they do change) The rest in the 44 byte header are just.. static info no need to know or change.

And to create waves, we want to look at the end of course, the actual data.

How data is stored:
8-bit audio are stored as -128 to 127 as unsigned bytes 0-255.
16-bit audio are stored as -32768 to 32767 but actually -32760 to 32760 (this is the only one that are concerned with endianness)
32-bit audio are stored as -1.0f to 1.0f (32bit floats) (this one needs one extra step to be activated using the field in the fmt chunk to 3 (WAVE_FORMAT_IEEE_FLOAT)
Without it you get instead 32bit integers with similiar properties of 16bit as you would think, including Endianess. Integers are always better quality than floats when you use bits to the fullest.


If stereo is used then right ones are stored, then left ones.

Hope that helps !!


Casaber(Posted 2016) [#18]
Wow I got the tabs just right, that was a world first. Pure accident.


Casaber(Posted 2016) [#19]
I tried today with 3 buffers, and with 8 buffers, to see if it should fix the clicking by adding a multitude of buffers, neither the size or the number changes the fact that it makes Macs into a click factory.

I tell you this is what complexity gives you. OS and everything inbetween stops you from moving 90KB lousy bytes per second.
Right now using 44100 * 2 bytes which comes just below 90KB per second.

Actually MIDI showed it even better, it needs only 4KB per second to reach proffessional timing quality which has been okay in studios since 1983 and ya still is by todays standards in many respects.
It´s nice when its working. Very nice. But it's not easy to move even 4KBytes evenly through the system, It's just barely managable, nothing obviously easy about it.

Now the next goal; moving 90KB per second through the system and allowing a base for realtime synthesis.
OpenAL is not absolutely ideal for this but I know it's well good enough to manage it by experience.

So I'm starting to think there's something missing, but I don´t know where. OS or Bmax, or something inbetween.

I changed this back to 3 buffers as that seem about ideal. And 2048byte buffers is abit large but it seem needed right now till things got fixed.


SuperStrict ; SetGraphicsDriver GLMax2DDriver()
Const s:Int = 1024*2 , w:Int = 1280 , h:Int = 800
Global src:Int , buffers:Int[3] , AL:Short[s] , format:Int = AL_FORMAT_MONO16 , buf:Byte Ptr = Byte Ptr AL , hz:Double = 1.0
Global  a:Double = 0.0 , buffer:Int , val:Int,ALDev:Int , C:Int , st:Float , v:Float , cntr:Double , vol:Double = 0.0
GLGraphics w,h,0 ; glewInit ; glFlush ; glOrtho 0,w,h,0,-100,100 ; glEnable GL_ALPHA_TEST ; glAlphaFunc GL_NOTEQUAL,0 ; glEnable GL_BLEND

' Init OpenAl (Open device, create context, and activate it, add 3 buffers, and 1 src)
ALDev=alcOpenDevice(Null) ; C=alcCreateContext(ALDev,Null) ; alcMakeContextCurrent C ; alGenBuffers 3,buffers ; alGenSources 1,Varptr src

' Clear buffer
For Local i:Int = 0 Until s ; AL[i] = 0 ; Next

' Create 3 buffers and queue them and start playing
alBufferData buffers[0],format,buf,s*2,44100 ; alBufferData buffers[1],format,buf,s*2,44100 ; alBufferData buffers[2],format,buf,s*2,44100
alSourceQueueBuffers src,1,Varptr buffers[0] ; alSourceQueueBuffers src,1,Varptr buffers[1] ; alSourceQueueBuffers src,1,Varptr buffers[2] ; alSourcePlay src

Repeat
	glClear GL_COLOR_BUFFER_BIT
	alGetSourcei src,AL_BUFFERS_PROCESSED,Varptr val ' Get val
	While val
		alSourceUnqueueBuffers src,1,Varptr buffer ' Change buffer queue

		' Create waveform in buffer
		For Local i:Int = 0 Until s ; AL[i] = vol*Sin(a)*$7fff ; cntr = 0.5 * 2 ^ (MouseX() / (0.125*w)) ; hz:+(cntr-hz)*0.0002 ; a:+hz ; vol:+((1.0 - (Double(MouseY()) / h))-vol) * 0.0002 ; Next

		alBufferData buffer,format,buf,s*2,44100 ; alSourceQueueBuffers src,1,Varptr buffer ; val:-1 ' 
	Wend
If val =< 0 Then alGetSourcei src,AL_SOURCE_STATE,Varptr val ; If val <> AL_PLAYING Then alSourcePlay src ' 
st = Float(w)/8 ; For Local i:Int = 0 Until s ; v = Float((AL[i]+$7fff) Mod $ffff)/$ffff ; glBegin GL_POINTS ; glVertex2d i,10+700*v ; glEnd ; Next ' Draw 

Flip 1
Until KeyHit(KEY_ESCAPE) Or AppTerminate()
alcDestroyContext C ; alcCloseDevice ALDev



Casaber(Posted 2016) [#20]
I think I changed my mind I think the best bet would be to code directly to the OS underlaying. Because not much are needed. Under 10 calls per OS of those you
would want to support. And you can direct to a common and ideal one for each system then. That would be the best strategy. Even if this one works, one would get picky
and would want to reach one step further. And that would probably spell the word problems.


Casaber(Posted 2016) [#21]
If anyone wants a example that almost works.
A datatype other than SHORT (unsigned 16bit) and INT (signed 32bit) is needed
like a 16bit signed. But that's for another day. It kinda works without it. strangely enough.

Instructions:
Select some wave you want with the mouse, and then press right mouse button to stamp out a 10 seconds wav of that very waveform of your fine selection.

' Create waveform and save it (WAV 16 bit, 44100, mono)

Graphics 800,600
Local samplerate:Int = 44100             ' Samplerate = 44.1Khz, we want 44100 samples To pass To the ears per second.
Local seconds:Double = 10                ' 10 seconds long.
Local SAMPLES:Int = samplerate * seconds ' How many samples would that take, not bytes though, it's samples, we take account to actual bytes per sample later.
Local waveform:Int[SAMPLES]              ' Create a suiting buffer to hold the samples.
Local t:Double , i:Int                   ' Some variables To work with.

' Parameters we'd want to be able to change
Local hz:Double = 440.0      '  Hz aka pitch aka frequency, this could be anything, 20 - 20000 is human normal perception. 
Local volume:Int = 32000     ' Volume aka amplitude, this could be anything that bitdepth allows. Use close max values for best quality, think "big shapes", you want things big.

Repeat
Cls

hz = MouseX()*10

' Create the waveform
For i = 0 Until SAMPLES
    t = Double(i) / Double(samplerate)
      waveform[i] = volume * Sin(hz * t * 360.0) ' 360 degrees instead of 2 * pi radians.
Next

' Draw the waveform, fit it on the screen
For i = 0 Until 44100
Plot i/(44100/800),300 + (waveform[i]/(32000.0/300.0)) ' 800 and 300 (half of 600) is what we want to fit the entire wave inside.
Next

Flip 1
Until MouseDown(2)

Local name$ = "sound.wav", SndBank:TBank,FileStream:TStream , Bits = 16 , Bytes = 2 , Channels = 1 , Sum = Samples * 2 ' 16 bit each sample is 2 bytes in size then
SndBank = CreateStaticBank(waveform,sum) ; FileStream = WriteStream(name$)

      If FileStream Then
         fileStream.WriteString "RIFF"                                            ' -
         fileStream.WriteInt Sum + 40                                             ' Manipulates size
         fileStream.WriteString "WAVE"                                            ' -
         ' ----------------------------------------------------------------------------------------
         fileStream.WriteString "fmt "                                            ' -
         fileStream.WriteInt 16                                                   ' -
         fileStream.WriteShort 1                                                  ' 1 for integer WAV (bit choices are then 8/16/32/any), or 3 for float WAV (bit choices are then 32/64).
         fileStream.WriteShort Channels                                           ' Number of channels
         fileStream.WriteInt Samplerate                                           ' Samplerate
         fileStream.WriteInt Samplerate * Channels * Bytes                        ' Byterate
         fileStream.WriteShort Channels * Bytes                                   ' Bytes per sample "Block align"
         fileStream.WriteShort Bits                                               ' Bitdepth (avoid 24bit etc try use those above instead, nothing wrong with wasting space for effeciency and quality)
         ' ----------------------------------------------------------------------------------------
         fileStream.WriteString "data"                                            ' -
         fileStream.WriteInt Sum                                                  ' Manipulates size
         SndBank.Write FileStream,0,sum ; CloseStream FileStream                  ' Audiodata
EndIf
End



Casaber(Posted 2016) [#22]
VLC did not eat the 32bit out of the box, I can't remember if that's whats' expected.

But it does play 32bit float out of the box.
I warn you, put the volume down before, I mean it.


' Create waveform and save it (WAV 32 bit, 44100, mono)

Graphics 800,600
Local samplerate:Int = 44100             ' Samplerate = 44.1Khz, we want 44100 samples To pass To the ears per second.
Local seconds:Double = 10                ' 10 seconds long.
Local SAMPLES:Int = samplerate * seconds ' How many samples would that take, not bytes though, it's samples, we take account to actual bytes per sample later.
Local waveform:Float[SAMPLES]              ' Create a suiting buffer to hold the samples.
Local t:Double , i:Int                   ' Some variables To work with.

' Parameters we'd want to be able to change
Local hz:Double = 440.0      '  Hz aka pitch aka frequency, this could be anything, 20 - 20000 is human normal perception. 
Local volume:Int = 32000     ' Volume aka amplitude, this could be anything that bitdepth allows. Use close max values for best quality, think "big shapes", you want things big.

Repeat
Cls

hz = MouseX()*10

' Create the waveform
For i = 0 Until SAMPLES
    t = Double(i) / Double(samplerate)
      waveform[i] = volume * Sin(hz * t * 360.0) ' 360 degrees instead of 2 * pi radians.
Next

' Draw the waveform, fit it on the screen
For i = 0 Until 44100
Plot i/(44100/800),300 + (waveform[i]/(32000.0/300.0)) ' 800 and 300 (half of 600) is what we want to fit the entire wave inside.
Next

Flip 1
Until MouseDown(2)

Local Bits = 32
Local name$ = "sound.wav", SndBank:TBank,FileStream:TStream , Bytes = Bits/8 , Channels = 1 , Sum = Samples * Bytes
SndBank = CreateStaticBank(waveform,sum) ; FileStream = WriteStream(name$)

      If FileStream Then
         fileStream.WriteString "RIFF"                                            ' -
         fileStream.WriteInt Sum + 40                                             ' Manipulates size
         fileStream.WriteString "WAVE"                                            ' -
         ' ----------------------------------------------------------------------------------------
         fileStream.WriteString "fmt "                                            ' -
         fileStream.WriteInt 16                                                   ' -
         fileStream.WriteShort 3                                                  ' 1 for integer WAV (bit choices are then 8/16/32/any), or 3 for float WAV (bit choices are then 32/64).
         fileStream.WriteShort Channels                                           ' Number of channels
         fileStream.WriteInt Samplerate                                           ' Samplerate
         fileStream.WriteInt Samplerate * Channels * Bytes                        ' Byterate
         fileStream.WriteShort Channels * Bytes                                   ' Bytes per sample "Block align"
         fileStream.WriteShort Bits                                               ' Bitdepth (avoid 24bit etc try use those above instead, nothing wrong with wasting space for effeciency and quality)
         ' ----------------------------------------------------------------------------------------
         fileStream.WriteString "data"                                            ' -
         fileStream.WriteInt Sum                                                  ' Manipulates size
         SndBank.Write FileStream,0,sum ; CloseStream FileStream                  ' Audiodata
EndIf
End



Casaber(Posted 2016) [#23]
I´m not trying 64bit before I get my headphones and heraing back.


dw817(Posted 2016) [#24]
Hi Casaber. This is some good work here. If you want, have a CONSTANT variable or something in the beginning to allow the person trying this to adjust the volume in the code.

Say, "Const Volume=10" from a scale of 1-255.


Casaber(Posted 2016) [#25]
repost repost


Casaber(Posted 2016) [#26]
Thanks. What about this, I skipped the 16bit version becuase it needs conversion (a one liner but my brain needs to work for that one liner)

Here's a retake on the 32bit one, a m ore friendly one. EXCEPT on the ears.
Sinewaves have never been kind to ears.

Added on earprotector constant.- To save mankind. I´m nice today.




dw817(Posted 2016) [#27]
That volume protection helps a little, Casaber. Unfortunately it is also bending the .WAV pitch a little. Just a tiny bit.

You can see this with frequency: 2450. Try 10% max volume, then 100% max volume and listen closely to the difference. And yes, adjust the volume so it does not hurt your ears.

I have very thick headphones I wear most of the time while working so you want to be especially careful if you have some similar method of listening to audio on the computer.

Additionally you might add CTRL-P keystroke that plays the sound.

To test to make sure it doesn't CLICK, have the sound play like 3 times in a row, and at a length of less than half a second. This might help you debug any glitches in it too instead of having to manually play the .WAV each time w a separate program.


Casaber(Posted 2016) [#28]
To make the volume really help I took it away and replace it with *100.
I'm all flabbergized why it does not work. Becuase it shaves 90% off still sounds the same.

*100 worked though so.. that's a relief. It's me, I just can't see why it didn't work that well. (It diminshes the visuals WAY before it diminishes the audiable).

Some stupid bug. But it's not part of the code, it does not make it unstable or anything.
A very isolated bug. Hate those kind of things but at least.. it's isolated.

I won´t do a amazing demo out of this, I just wanted to show off the mechanics how to make a wave.


Casaber(Posted 2016) [#29]
The fun part would be to do some useful soundeffects.
I won´t probably be happening just yet, I have a stackful of work before coding again.

But I´m already visualising all kinds of things, I´m thinking.. something modular, something so that anyone with.or-without-audio-knowledge can try out different things on their leasure. Just toying around.

Like adding different waves (and samples from the outside world), and getting the feel of things, adding vibrato, tremolo pitchbends, animated filters. Learn what those are. Just to create some small but still.. very alive sound effects that could be used inside games and such..

That would be fun.


Casaber(Posted 2016) [#30]
@dw817 I wonder how you do your stuff with graphics. You know.. Imagine.. If you could do the same with sounds..
I wonder what would help to aid you (or anyone) to do something like that. Designing sounds in a new intuitive way or something.


Casaber(Posted 2016) [#31]
So I did the one liner during breakfast as it really bugged me, but it seem like I did the conversion all for nothing as it works just using short as is (I found out later).

Local temp:Int
Local b:Short = 0
Local c:Short = 0

For temp=-32768 To 32767 Step 10

' Convert signed 32bit into unsigned 16bit using sweat and blood
' b = (65536 - ( (temp ~ $0000ffff) + 1 )) & $ffff ' The last & is not needed, it just looks good. 
b = 65536 - ( (temp ~ $0000ffff) + 1 )
Print b

' Without effort
c=temp
Print c

Delay 1

If c<>b Then End
Next

WaitKey
End




Casaber(Posted 2016) [#32]
In Bmax could you somehow cast a whole array of ints into a whole array of shorts before sending them to the bank/stream?

or would you need to create an additional array of type short?

That would happen here in that case somewhere:
SndBank = CreateStaticBank(waveform,sum

In case there is not such thing. Here's the 16bit wav, fixed the other away. Nothing fancy, it's just a working 16bit example aswell.



Casaber(Posted 2016) [#33]
One thing about wav (generally, not a particular code)

In the structure there is this
fileStream.WriteInt Sum + 40 ' Manipulates size

The problem is some says the actual 4 bytecounter should not be included in the length, some says. It's an easy misstake.

From the offical WAV:
"A 32-bit unsigned value identifying the size of ckData. This size value does
not include the size of the ckID or ckSize fields or the pad byte at the end of ckData."

Minimal wav header is 44bytes. So total filesize - 8 would be the correct one. (it's filesize - 4 now when adding 40, as header is itself 44 bytes.
Meaning all wav examples above with +40 should be +36. Nasty bug to find as all players work either way.

The correct one would be
fileStream.WriteInt Sum + 36

I will change at least the two latest sources accordingly. So there will be a 16 & a 32bit example in good condition.


Casaber(Posted 2016) [#34]
I did a function out of this but it got ugly because of the 8bit 16bit 32bit and 64bit handling. Very ugly code I´m not pleased with it at all.
It's mostly about the barrier of handling multiple datatypes (or arrays of them) elegantly using a single function. It exploded.. like..several lines that should no
be needed really. Very messy. I'll have a look what I can do about it later. I think right now; nothing.


Casaber(Posted 2016) [#35]
Don't use the 32bit before I've fixed it, it has an datatype issue, that is what makes it overly loud aswell.

The 16bit is THE one to use. Most ppl won´t be needing 32bit and 64bit anyways.

If you by chance wants to use 8 bit, you need to remember one thing, and that is to pad it at the very end if datasize happens to be odd. This padbyte should not be included anywhere in the header so it's nothing to think about, it's just a matter of putting an extra 0 byte at the end if your samplelength is odd). It's just that WAV hates odd filesizes.


dw817(Posted 2016) [#36]
Looks good, or perhaps I should say, sounds good. :)As mentioned, Casaber, to test the click, maybe have the audio shortened to a half second or smaller, then play it 3 times consecutively with a slight pause between to see if there are any clicks.


Casaber(Posted 2016) [#37]
Good thinking. I got a few ideas to try out now, lets see how those work out. I guess it will take some time to test thing throughly first. I really want to get back on
track with the graphics library as there is where BMax shines right out of the box, but I forgot something, you COULD play samples (already rendered)
very quickly when using the right methods right out of the box, and it works on both platforms. Without any clicky problems. It's just realtime changes in the buffering that clicks and that could be fixed later on separatly.

It handles samples actually very quickly. OpenAL is wonderful there. So a Soundfontplayer is still on.

But implemmenting this wav was no mistake it's nice to have a solid short and sweet wav loader/saver implemented before doing soundfonts, or if I could find a better public domain format but I don´t think there is one.


Midimaster(Posted 2016) [#38]
...or if I could find a better public domain format but I don´t think there is one.


There is an OGG-Saver:

Import axe.oggsaver
Local Sample:TAudioSample
....
SaveOGG(Sample, SampleName + ".ogg")
 



Casaber(Posted 2016) [#39]
MIDImaster Thanks, but Ogg does not support all the features I need, I really need something with more than multiple loop points.
I could add loop info chunks to wav to get that aswell, but I thought I shall split the sound usage into two groups;

One for as a raw medium of storage. (wav and ogg would fit here)

And then pick a more flexible format for instruments to avoid to need to grow an fileformat of my own.

EMU added alot of overkill features in SF2 but It's nice as instrumentholders. Multisamples could be held easly in one etc. I will certainly not use all the features before realtime synthesis works but that's no problem.
http://freepats.zenvoid.org/sf2/sfspec24.pdf

In my eyes Ogg is more on pair with WAV. Does Bmax handles FLACs inside OGGs?


Midimaster(Posted 2016) [#40]
I did not know, that WAV can save more informations than sample values. Never heard about multiple loop points.

Bmax uses the Ogg Vorbis format, which compresses with losses. But you can define compression rate/quality in OGG format.