code to generate wav files specifying waveform, pitch, ADSR, etc.?
#1
Does anyone have any QB64 / QuickBasic / QBasic code to generate a WAV file, specifying various sound synthesis parameters like waveform (sine, triangle, square, sawtooth, white noise, etc.), ADSR (attack / decay / sustain / release), as well as pitch/frequency and volume, (bonus would be generating a stereo sound file, controlling the pan position), and then play back 2 or more of the sound files simultaneously? 

Also it would be useful to append one WAV file to the end of another. 

This would be useful for sound effects for games as well as music applications. 

I've found the WAV file specification (still digesting) but it is a little daunting!

http://www.topherlee.com/software/pcm-tu...ormat.html
 
https://docs.fileformat.com/audio/wav/
 
https://m.youtube.com/watch?v=udbA7u1zYfc
 
https://www.videoproc.com/resource/wav-file.htm
 
https://www.fastmetrics.com/support/wav-file/amp/
 
http://www.thescarms.com/vbasic/tone.aspx
 
https://www.vbforums.com/showthread.php?...e-wav-file
 
https://www.vbforums.com/showthread.php?...a-wav-file
 
https://docs.microsoft.com/en-us/previou...0(v=vs.85)
 
https://www.vbforums.com/showthread.php?...a-wav-file
 
https://forums.codeguru.com/showthread.p...g-wav-file
 
https://forums.codeguru.com/showthread.p...-WAV-Files
 
https://rochars.github.io/wavefile/
 
https://gist.github.com/asanoboy/3979747
 
https://morioh.com/p/bced3b76866e
 
https://codereview.stackexchange.com/que...script-es6
 
https://blog.logrocket.com/audio-visuali...avascript/
 
https://www.tutorialspoint.com/read-and-...thon-wave#
 
https://ourcodeworld.com/articles/read/1...javascript
 
https://learningsolutionsmag.com/article...er-s-guide
 
https://onestepcode.com/modifying-wav-files-c/
 
https://codingdiksha.com/build-a-wav-fil...avascript/
Reply
#2
DSP is not easy. Look into "musicdsp.org", only about coming up with an oscillator, or filter which is not low-pass. I know how to create a basic sine oscillator, or a saw based on trig "TAN()" function but that's all. I'm still dumb with things like that trick using "EXP()" to fade sound in and out. Loading and creating a 44100Hz 16-bit mono wave file is not much hassle for me anymore. Doing it in stereo begins the headaches, also trying to use a larger bit depth and/or sampling rate. Generally the specs I just listed would be enough for sound effects in an arcade game except for audiophiles. Cannot play FLAC, MP3, OGG etc. directly with such a routine.

To come up with a wave file in which the PCM data of one is attached to the end of data of the other, must read the header of both files, determine the number of sample frames and make that adjustment in the header of the output wave buffer. If one file is mono and the other stereo well, mister, you will have more work to do. Which is what I mean. Keep it simple. However the "average" gamer doesn't care if somebody doesn't know how to program continuous music, and doesn't know how to kludge sound effects which aren't boring.

Just needing to compute sensible sound with "_SNDRAW" requires some technical knowhow.  For a polyphonic synthesizer must know how to add the voices, fade them in and out and make sure the level does not exceed absolute-value ONE (or it causes ugly digital distortion which might harm your hearing and/or your studio monitors). Must compute ahead of time for at least one second at the "current" sampling rate. A wave file could be imported, depending on it being 16-bit or 32-bit integer, then each sample frame would have to be converted to a single-prec float value from -1.0 to 1.0. Perhaps 32-bit float WAV could be set directly. A few wave files could be compressed or use obscure or weird codecs. Even fewer support three or more channels which could be difficult to program.

Somebody proved to me that it's possible to create a tracker with QB64, but it doesn't edit song documents! Playing one or more sounds away from "_SNDRAW" way requires working with an audio driver and with threads, which are other dimensions of complexity. Then expecting it to work in real time after editing... oh well charge for it if you could do it.

MIDI is another matter, it depends more on hardware and stuff installed on Windows such as the "synthesizer" ripped off some Japanese company. The code to do it in that OS is easy but it's not portable eg. to Linux.
Reply
#3
(07-23-2022, 07:51 AM)mnrvovrfc Wrote: DSP is not easy. Look into "musicdsp.org", only about coming up with an oscillator, or filter which is not low-pass. I know how to create a basic sine oscillator, or a saw based on trig "TAN()" function but that's all. I'm still dumb with things like that trick using "EXP()" to fade sound in and out. Loading and creating a 44100Hz 16-bit mono wave file is not much hassle for me anymore. Doing it in stereo begins the headaches, also trying to use a larger bit depth and/or sampling rate. Generally the specs I just listed would be enough for sound effects in an arcade game except for audiophiles. Cannot play FLAC, MP3, OGG etc. directly with such a routine.

To come up with a wave file in which the PCM data of one is attached to the end of data of the other, must read the header of both files, determine the number of sample frames and make that adjustment in the header of the output wave buffer. If one file is mono and the other stereo well, mister, you will have more work to do. Which is what I mean. Keep it simple. However the "average" gamer doesn't care if somebody doesn't know how to program continuous music, and doesn't know how to kludge sound effects which aren't boring.

Just needing to compute sensible sound with "_SNDRAW" requires some technical knowhow.  For a polyphonic synthesizer must know how to add the voices, fade them in and out and make sure the level does not exceed absolute-value ONE (or it causes ugly digital distortion which might harm your hearing and/or your studio monitors). Must compute ahead of time for at least one second at the "current" sampling rate. A wave file could be imported, depending on it being 16-bit or 32-bit integer, then each sample frame would have to be converted to a single-prec float value from -1.0 to 1.0. Perhaps 32-bit float WAV could be set directly. A few wave files could be compressed or use obscure or weird codecs. Even fewer support three or more channels which could be difficult to program.

Somebody proved to me that it's possible to create a tracker with QB64, but it doesn't edit song documents! Playing one or more sounds away from "_SNDRAW" way requires working with an audio driver and with threads, which are other dimensions of complexity. Then expecting it to work in real time after editing... oh well charge for it if you could do it.

MIDI is another matter, it depends more on hardware and stuff installed on Windows such as the "synthesizer" ripped off some Japanese company. The code to do it in that OS is easy but it's not portable eg. to Linux.

All I'm looking to do is generate some basic sounds - white noise, brown noise, sine, square, sawtooth, triangle, etc. waveforms, at a certain pitch and volume. Generate and write the WAV files (if not found in the program directory) and then play them back on demand asynchronously however QB64 does that. If we have sample code for the basic waveforms and writing the WAV file, then we really just have to tweak the code to get different variations... I have found tons of info on the WAV format, just been avoiding & procrastinating doing the actual WORK of reading and digesting it all, and giving it a solid effort, LoL. But once the basic functionality is working, you can build on it and the rest of the dominos will fall more easily.
Reply
#4
Found a program in my collection that generates melodies itself and then saves them as a WAV file.
Unfortunately, I don't know who it is from.
But maybe it will help you.

Tongue Big Grin

Code: (Select All)
Dim fm(88) As Double
Dim f As Double
Dim tn(88) As String
Dim in As String
Randomize Timer(.001)
Dim t As Double
Dim Left As _MEM, Right As _MEM




Print "Welcone to Random Tones and Chords Generator v0.1b"
Print
Input "Please specify Chord possibility in % (25):"; uaw$
Input "Please set music time [sec] for generating WAV file (120):"; uWAVLen
Input "Please set BPM (120):"; ubpm$
Input "Please set Pauses possibility in % (5):"; up$

If uWAVLen$ = "" Or Val(uWAVLen$) < 0 Or Val(uWAVLen$) > 1000 Then
    WAVLen = 120
Else WAVLen = Val(uWAVLen$)
End If
Print "Music Time:"; LTrim$(Str$(WAVLen)); "s"

If uaw$ = "" Or Val(uaw$) < 0 Or Val(uaw$) > 100 Then
    aw = 25
Else aw = Val(uaw$)
End If
Print "Chords possibility:"; aw; "%"

If ubpm$ = "" Or Val(ubpm$) < 0 Or Val(ubpm$) > 1000 Then
    bpm = 120
Else bpm = Val(ubpm$)
End If
Print "BPM:"; bpm


If up$ = "" Or Val(up$) < 0 Or Val(up$) > 100 Then
    pperc = 5
Else pperc = Val(up$)
End If
Print "Pauses:"; LTrim$(Str$(pperc)); "%"


Dim RawMusic(_Ceil(_SndRate * WAVLen)) As Single

Left = _Mem(RawMusic())
Right = _Mem(RawMusic()) 'mono signal, so both channels are the same


'bpm = 120
'interval 1 and 2 (semitones)
int1 = 4
int2 = 7
'interval possibility percent
'aw = 25
'tone minimum and maximum
tmin = 35
tmax = 47
'lengths min and max
lmin = 2
lmax = 4
'fill the tone names
For i = 1 To 88
    f = ((2 ^ ((i - 49) / 12)) * 440)
    fm(i) = CInt(f)
    Select Case i Mod 12
        Case 1: tn(i) = "A"
        Case 2: tn(i) = "A#"
        Case 3: tn(i) = "B"
        Case 4: tn(i) = "C"
        Case 5: tn(i) = "C#"
        Case 6: tn(i) = "D"
        Case 7: tn(i) = "D#"
        Case 8: tn(i) = "E"
        Case 9: tn(i) = "F"
        Case 10: tn(i) = "F#"
        Case 11: tn(i) = "G"
        Case 0: tn(i) = "G#"
    End Select
    'Select Case Int(i / 12)
    '    Case 0: tn(i) = tn(i) + "0"

    'End Select
    'DEBUG
    'Print f; tn(i)
    'If i > 24 Then Sleep
Next i

'delete all sharp tones
For i = 1 To 88
    If Mid$(tn(i), 2, 1) = "#" Then
        tn(i) = tn(i - 1)
        fm(i) = fm(i - 1)
    End If
Next i
'DEBUG fm(49)="A"
'Print fm(49)

'fill the lengths
For i = 1 To 7
    lm(i) = 2 ^ i / 2
    'DEBUG
    'Print lm(i)

Next i

Do
    'tones between tmin and tmax
    i = CInt((Rnd * (tmax - tmin)) + tmin)

    'Pauses
    If Rnd < pperc / 100 Then p = 1 Else p = 0

    lr = lm(CInt(Rnd * (lmax - lmin)) + lmin) 'Length out of the lenght fields between 2 and 4
    L = 1 / lr * 60 / bpm * 4 'Lenght is parts of a second from the length fields multiplied by bpm

    If Rnd < aw / 100 Then
        akk = 1
    Else akk = 0
    End If

    t = 0
    If akk = 1 And p = 0 Then
        Print "1/"; LTrim$(Str$(lr)); " Tone:"; tn(i); "+"; tn(i + int1); "+"; tn(i + 7)
    ElseIf akk = 0 And p = 0 Then Print "1/"; LTrim$(Str$(lr)); " Tone:"; tn(i)
    ElseIf p = 1 Then Print "1/"; LTrim$(Str$(lr)); " Pause"
    End If
    Do
        'queue some sound
        Do While t < L 'you may wish to adjust this
            sample1 = Sin(t * fm(i) * Atn(1) * 8) / 3 * 2
            sample1 = sample1 * Exp(-t * L) / 3 * 2

            t = t + 1 / _SndRate

            If akk = 1 Then
                sample2 = Sin(t * fm(i + int1) * Atn(1) * 8) / 3 * 2
                sample2 = sample2 * Exp(-t * L) / 3 * 2
                sample1 = (sample1 + sample2) / 2

                '_SndRaw sample2
                't = t + 1 / _SndRate
                sample3 = Sin(t * fm(i + int2) * Atn(1) * 8) / 3 * 2
                sample3 = sample3 * Exp(-t * L) / 3 * 2
                sample1 = (sample1 + sample3) / 2
                '_SndRaw sample
                't = t + 1 / _SndRate
            End If
            If p = 1 Then sample1 = 0
            _SndRaw sample1
            RawMusic(rwi) = sample1
            rwi = rwi + 1

            If rwi > UBound(RawMusic) Then
                Print "Sound generated, saving to file generated.wav"
                _Delay .5
                SAVESOUND8S Left, Right, "generated.wav"
                _MemFree Left
                _MemFree Right
                Erase RawMusic
                System
            End If
        Loop


    Loop While t < L 'play for l seconds

    Do While _SndRawLen > 0 'Finish any left over queued sound!
    Loop
    _SndRawDone


Loop While in = ""










Sub SAVESOUND8S (Left As _MEM, Right As _MEM, file As String) 'Left and Right memory blocks contains value -1 to 1 (_SNDRAW compatible)

    Size = OFFSET_to_I64(Left.SIZE) 'convertion is used for WAV file header, becuse offset value can not be used directly

    Type head8
        chunk As String * 4 '       4 bytes  (RIFF)
        size As Long '              4 bytes  (file size)
        fomat As String * 4 '       4 bytes  (WAVE)
        sub1 As String * 4 '        4 bytes  (fmt )
        subchunksize As Long '      4 bytes  (lo / hi), $00000010 for PCM audio
        format As Integer '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
        channels As Integer '       2 bytes  (1 = mono, 2 = stereo)
        rate As Long '              4 bytes  (sample rate, standard is 44100)
        ByteRate As Long '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
        Block As Integer '          2 bytes  (block align = number of channels * bits per sample /8)
        Bits As Integer '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
        subchunk2 As String * 4 '   4 bytes  ("data")  contains begin audio samples
        lenght As Long '            4 bytes  Data block size
    End Type '                     44 bytes  total
    Dim H8 As head8
    ch = FreeFile

    H8.chunk = "RIFF"
    H8.size = 44 + Size / 2

    H8.fomat = "WAVE"
    H8.sub1 = "fmt "
    H8.subchunksize = 16
    H8.format = 1
    H8.channels = 2
    H8.rate = 44100
    H8.ByteRate = 44100 * 2 * 8 / 8
    H8.Block = 2
    H8.Bits = 8
    H8.subchunk2 = "data"
    H8.lenght = Size / 2
    If _FileExists(file$) Then Kill file$

    Open file$ For Binary As #ch
    Put #ch, , H8

    Dim LeftChannel8 As _Byte, RightChannel8 As _Byte, RawLeft As Single, RawRight As Single
    Dim Recalc As _MEM, size As _Offset

    size = Left.SIZE 'now this value is for memory size used by SINGLE ARRAY - WAV 8bit need 1 byte for 1 channel and 1 sample

    'recalculate audiodata to file - byte - values

    Recalc = _MemNew(size / 2) 'this is value used by WAV - size is 4 byte per sample, we recording stereo (2 x 1 byte) therefore is this divided by 2

    start& = 0: LRO& = 0
    Do Until start& = Left.SIZE
        RawLeft = _MemGet(Left, Left.OFFSET + start&, Single)
        RawRight = _MemGet(Right, Right.OFFSET + start&, Single)

        LeftChannel8 = 128 - RawLeft * 128
        RightChannel8 = 128 - RawRight * 128

        _MemPut Recalc, Recalc.OFFSET + s&, LeftChannel8
        _MemPut Recalc, Recalc.OFFSET + s& + 1, RightChannel8
        s& = s& + 2
        start& = start& + 4
    Loop

    'write audio data to file

    WAVeRAW$ = Space$(s&)
    _MemGet Recalc, Recalc.OFFSET, WAVeRAW$
    Put #ch, , WAVeRAW$

    'erase memory
    _MemFree Recalc
    WAVeRAW$ = ""
    Close ch
End Sub

Function OFFSET_to_I64 (value As _Offset)
    Dim m As _MEM
    $If 32BIT Then
            DIM num AS LONG
            m = _MEM(num)
            _MEMPUT m,m.offset, value
            Offset_to_i64 = num
            _MEMFREE m
    $Else
        Dim num As _Integer64
        m = _Mem(num)
        _MemPut m, m.OFFSET, value
        OFFSET_to_I64 = num
        _MemFree m
    $End If
End Function
Reply
#5
(07-23-2022, 06:30 PM)Steffan-68 Wrote: Found a program in my collection that generates melodies itself and then saves them as a WAV file.
Unfortunately, I don't know who it is from.
But maybe it will help you. Tongue Big Grin

Surely any example is a start, so thank you! Hopefully others will begin playing with this stuff as well, multiple heads are better than one, for this kinda thing!
I'll give it a look when I'm back on the 'puter.
Reply
#6
I suspect anything _SND uses that stuff that you have to show license and source code so I did my experiment with Sound:
Code: (Select All)
'current file title: mouse circle sounds.bas
'original title squeaky mouse.bas SmallBASIC 2015-04-27 B+
'old on-line title: circles
'now more sound effects, color contrast, c=cls screen option
Const xmax = 800, ymax = 600
Screen _NewImage(xmax, ymax, 32)
_ScreenMove 200, 0
DefLng A-Z
Randomize Timer
While 1
    Locate 1, 1
    Print "press c to clear screen clutter"
    If InKey$ = "c" Then Cls

    While _MouseInput: Wend
    x = _MouseX
    y = _MouseY
    f = 37 + 3250 * y / ymax
    d = x / xmax * 18
    Sound f, d
    Circle (x, y), d, _RGB32(255 * y / ymax + 10, 255 * y / ymax + 10, 128)
    _Display
    _Limit 5
Wend

The help file says frequency can go up to 32000 but I got really screwy stuff happening so I maxed it out at 1/10 that range.

Oops! I posted this in wrong section, I meant it to go where you were experimenting with sounds using mouse, sorry!
b = b + ...
Reply




Users browsing this thread: 1 Guest(s)