Functional sound equalization live!
#1
Hi. I'll put it this way. It's all in the patterns. All of this is contained directly in the sound samples that you load via memsound. The whole science is that you have to create an empty sound signal (basically silence), which must have a sufficient frequency so that the resulting sound does not pulsate. You then modulate this empty signal with the volume of the source signal. Of course, if you use subsampling (this case) you can change the frequency. I will still work on the way to the depths, with the highest probability the intermediate sample will have to be calculated and added to the empty signal.

Just write a valid name of your audio file in the source code. Then run it and use the mouse wheel. Eq level = 100 percent means that the treble is fully equalized.

To be continued again sometime next time. I'm currently concreting the foundations of the fence (today the concrete hardens, so I hopped over to this forum), I don't have time for programming, I'm on my own. Have a nice day.

Edit: Source code updated (SIN is not need...)
Code: (Select All)
'small sound equalizing example writed by Petr Preclik 03-08-2022 (dd-mm-yyyy)
$NoPrefix
Dim a As MEM, a2 As MEM, j As Long, Eq As Double
Dim As Integer Il, Ir, Il2, Ir2
s = SndOpen("b.mp3") 'insert your music file name here
a = MemSound(s, 1)
a2 = MemSound(s, 2)
f = 1
Eq = 1.7
Print "Use the mouse wheel to reduce/add the treble of the sound!"

Do Until j = a.SIZE - 2
    MemGet a, a.OFFSET + j, Il
    MemGet a, a.OFFSET + j + 2, Ir
    MemGet a2, a2.OFFSET + j, Il2
    MemGet a2, a2.OFFSET + j + 2, Ir2

    j = j + 2

    frL = f * (Il / -32768) + f * ((Ir / 32768) / Eq) 'create new silent empty signal. Volume in our new empty signal is drived by volume in original sound file.
    frR = f * (Il2 / -32768) + f * ((Ir2 / 32768) / Eq)

    SndRaw frL, frR
    Do Until SndRawLen = 0
        Mouse Mw
        Eq = Eq + Mw
        Eq = MINMAX(1, 2, Eq)
        Locate 3
        sLevel = Int((2 - Eq) * 100)
        Print "Eq level:"; sLevel; "%  "
    Loop
Loop

Function MINMAX (Minimum_Value, Maximum_Value, Current_Value)
    MINMAX = Current_Value
    If Current_Value > Maximum_Value Then MINMAX = Maximum_Value
    If Current_Value < Minimum_Value Then MINMAX = Minimum_Value
End Function

Sub Mouse (Mw)
    Mw = 0
    While MouseInput
        Mw = MouseWheel / 10
    Wend
End Sub


Reply
#2
Hi Pete Smile
I get error #308 on line 13, is this program 32-bit only?
Reply
#3
Hi Jack, this can work also in 64 bit. I run and wrote it in QB64 ver 2.0.2 (none Phoenix edition), 32 bit.

Edit:

@Jack - My program above does not work in Phoenix QB64 due to changes with MemSound that I found out about today. It works correctly in QB64 2.02, but here I am attaching a modified version for Phoenix, so far with support for only one audio format - 32 bit. I will gradually patch the other programs so that they work in Phoenix.

Code: (Select All)
'small sound equalizing example writed by Petr Preclik 03-08-2022 (dd-mm-yyyy)
$NoPrefix
Dim a As MEM, j As Offset, Eq As Double
Dim As Single Il, Ir, Il2, Ir2
s = SndOpen("b.mp3") 'insert your music file name here
a = MemSound(s, 0)
If a.ELEMENTSIZE <> 8 Then Print "This program is now designed just for 32 bit sound": Sleep 2: End

Eq = 1.7
Print "Use the mouse wheel to reduce/add the treble of the sound!"

Do Until j = a.SIZE - a.ELEMENTSIZE * 2
    MemGet a, a.OFFSET + j, Il
    MemGet a, a.OFFSET + j + a.ELEMENTSIZE, Ir
    MemGet a, a.OFFSET + j + a.ELEMENTSIZE / 2, Il2
    MemGet a, a.OFFSET + j + a.ELEMENTSIZE * 1.5, Ir2

    j = j + a.ELEMENTSIZE

    frL = Il * EqS + (Il - Ir) * EqH 'create new silent empty signal. Volume in our new empty signal is drived by volume in original sound file.
    frR = Ir * EqS + (Il2 - Ir2) * EqH

    SndRaw frL * slevel, frR * slevel

    Do Until SndRawLen < 0.01
        Mouse Mw
        Eq = Eq + Mw
        Eq = MINMAX(1, 2, Eq)
        EqS = Eq - 1
        EqH = 1 - EqS
        Locate 3
        slevel = Int((2 - Eq) * 100)
        slevel = MINMAX(1, 100, slevel)
        Print "Eq level:"; slevel; "%  " ', EqS, EqH
    Loop
Loop

Function MINMAX (Minimum_Value, Maximum_Value, Current_Value)
    MINMAX = Current_Value
    If Current_Value > Maximum_Value Then MINMAX = Maximum_Value
    If Current_Value < Minimum_Value Then MINMAX = Minimum_Value
End Function

Sub Mouse (Mw)
    Mw = 0
    While MouseInput
        Mw = MouseWheel / 25
    Wend
End Sub


Reply
#4
On line 13 is MemGet, so you are probably using such a sound file that either the SndOpen command cannot open, or the path to the file is written incorrectly.


Reply
#5
(08-03-2022, 03:56 PM)Petr Wrote: Hi. I'll put it this way. It's all in the patterns. All of this is contained directly in the sound samples that you load via memsound. The whole science is that you have to create an empty sound signal (basically silence), which must have a sufficient frequency so that the resulting sound does not pulsate. You then modulate this empty signal with the volume of the source signal. Of course, if you use subsampling (this case) you can change the frequency. I will still work on the way to the depths, with the highest probability the intermediate sample will have to be calculated and added to the empty signal.

Just write a valid name of your audio file in the source code. Then run it and use the mouse wheel. Eq level = 100 percent means that the treble is fully equalized.

To be continued again sometime next time. I'm currently concreting the foundations of the fence (today the concrete hardens, so I hopped over to this forum), I don't have time for programming, I'm on my own. Have a nice day.

Edit: Source code updated (SIN is not need...)
Code: (Select All)
'small sound equalizing example writed by Petr Preclik 03-08-2022 (dd-mm-yyyy)
$NoPrefix
Dim a As MEM, a2 As MEM, j As Long, Eq As Double
Dim As Integer Il, Ir, Il2, Ir2
s = SndOpen("b.mp3") 'insert your music file name here
a = MemSound(s, 1)
a2 = MemSound(s, 2)
f = 1
Eq = 1.7
Print "Use the mouse wheel to reduce/add the treble of the sound!"

Do Until j = a.SIZE - 2
    MemGet a, a.OFFSET + j, Il
    MemGet a, a.OFFSET + j + 2, Ir
    MemGet a2, a2.OFFSET + j, Il2
    MemGet a2, a2.OFFSET + j + 2, Ir2

    j = j + 2

    frL = f * (Il / -32768) + f * ((Ir / 32768) / Eq) 'create new silent empty signal. Volume in our new empty signal is drived by volume in original sound file.
    frR = f * (Il2 / -32768) + f * ((Ir2 / 32768) / Eq)

    SndRaw frL, frR
    Do Until SndRawLen = 0
        Mouse Mw
        Eq = Eq + Mw
        Eq = MINMAX(1, 2, Eq)
        Locate 3
        sLevel = Int((2 - Eq) * 100)
        Print "Eq level:"; sLevel; "%  "
    Loop
Loop

Function MINMAX (Minimum_Value, Maximum_Value, Current_Value)
    MINMAX = Current_Value
    If Current_Value > Maximum_Value Then MINMAX = Maximum_Value
    If Current_Value < Minimum_Value Then MINMAX = Minimum_Value
End Function

Sub Mouse (Mw)
    Mw = 0
    While MouseInput
        Mw = MouseWheel / 10
    Wend
End Sub

This is fascinating. 

First, what is modulating a signal? I have seen that term over the years and just recently I read something with it, and said, "Dammit, I'm going to learn what this is, once and for all!" and promptly forgot about it by the time I was onto the next thing. Maybe if you explained like you would to a child, that'll work, LoL

At first I thought this empty signal might be the target frequency but phase-reversed, and fed back into the full sound, cancelling out that given frequency, and thus "equalizing" by means of subtraction, but I have no idea. So many questions -
* How do you give silence a frequency, if by definition it has no frequency? 
* In addition to my "modulate" question, what do mean by pulsate? 
* Why might this change the frequency and how do you correct for that? 
* And what is subsampling? 

This stuff goes way over my head, but I am interested in learning more about manipulating sound.
Reply
#6
Hi. My comment is misleading. That statement about sufficient signal frequency comes from another source code. I simply tried to create a 100 Hz carrier signal and then modulate it with sound. This is shown by the attached source. Try changing the SignalSpeed variable.

For clarification. SNDRATE is the audio sampling rate. That's 44100 samples per second. By definition of audio, the sampling rate of the audio must be twice as high as the maximum recorded frequency. For 44100 samples, the maximum possible frequency is 22050 Hz.
So if you needed to record sound with a frequency of, say, 88.2 Khz, you would do it like this: For 88.2 KHz, you need exactly as many samples as for 4 seconds of sound at a sampling rate of 44100 samples per second. So just record the sound within 4 seconds and then speed it up 4x for playback.

Subsampling - if you have audio whose sample rate contains 44100 samples per second, you can play it with a quarter of its samples at the cost of losing quality. In practice, you do this by playing every fourth sample four times. That's to meet 44100 samples per second. If you were to play each sample only once, the result would be a 4x accelerated sound.

This leads to another possibility - smooth regulation of sound speed, it can be regulated by the number of dropped samples and the number of their playback.


Frequency modulated as sound:
Code: (Select All)
'create signal (100 hz) and then modulate this frequency as sound:
$NoPrefix
SignalSpeed = 0.01 '100 Hz = 100 changes per 1 second. Periode time is 1 / 100
Audio = SndOpen("be happy.mp3")
Dim As MEM L, R
Dim As Integer LeftSignal, RightSignal
Dim As Long C
L = MemSound(Audio, 1)
R = MemSound(Audio, 2)

Do Until C = L.SIZE - 2
    MemGet L, L.OFFSET + C, LeftSignal
    MemGet R, R.OFFSET + C, RightSignal
    s = s + SignalSpeed
    SndRaw Sin(s) * (LeftSignal / 32768), Sin(s) * (RightSignal / 32768)
    C = C + 2
    Do While SndRawLen > 0
    Loop
Loop

Play audio in subsampled form:
Code: (Select All)
'play sound using 11025 samples, not 44100
$NoPrefix
Audio = SndOpen("be happy.mp3")
Dim As MEM L, R
Dim As Integer LeftSignal, RightSignal
Dim As Long C
L = MemSound(Audio, 1)
R = MemSound(Audio, 2)

Do Until C = L.SIZE - 8
    MemGet L, L.OFFSET + C, LeftSignal
    MemGet R, R.OFFSET + C, RightSignal
    For U = 1 To 4
        SndRaw LeftSignal / 32768, RightSignal / 32768
    Next
    C = C + 8 'as STEP 4, its INTEGER (2 byte), so 8
    Do While SndRawLen > 0
    Loop
Loop

and small upgrade previous code - play sound fast:

Code: (Select All)
'play sound using every 4'th sample
$NoPrefix
Audio = SndOpen("be happy.mp3")
Dim As MEM L, R
Dim As Integer LeftSignal, RightSignal
Dim As Long C
L = MemSound(Audio, 1)
R = MemSound(Audio, 2)

Do Until C = L.SIZE - 8
    MemGet L, L.OFFSET + C, LeftSignal
    MemGet R, R.OFFSET + C, RightSignal
    'For U = 1 To 4
    SndRaw LeftSignal / 32768, RightSignal / 32768
    'Next
    C = C + 8 'as STEP 4, its INTEGER (2 byte), so 8
    Do While SndRawLen > 0
    Loop
Loop

Big Grin


Reply
#7
Hi Czech Pete!

I wish you contacted me before pouring that concrete, because I wrote a program that builds a fence! The problem is, I coded it in FreeBASIC. It's currently malfunctioning beautifully at the U.S.-Mexican border!

I miss my days as a carpenter, getting a little too old for that sort of thing. I can still turn water in to wine, though, as you can probably tell from my decision to code anything in FreeBASIC. Oh well..

Best wishes on completing the fencing project and the sound program. Great to have you here at Phoenix.

Pete
Reply
#8
(08-06-2022, 03:27 PM)Pete Wrote: Hi Czech Pete!

I wish you contacted me before pouring that concrete, because I wrote a program that builds a fence! The problem is, I coded it in FreeBASIC. It's currently malfunctioning beautifully at the U.S.-Mexican border!

I miss my days as a carpenter, getting a little too old for that sort of thing. I can still turn water in to wine, though, as you can probably tell from my decision to code anything in FreeBASIC. Oh well..

Best wishes on completing the fencing project and the sound program. Great to have you here at Phoenix.

Pete

Got a link to your FreeBasic "fence" code? 
Is it something that could be ported to QB64?
Reply
#9
Big GrinBig GrinBig Grin
Reply
#10
Hello everyone in the forum. Attached is a functional program, it is a cornerstone on a possible path to an equalizer. This one is mainly focused on deep frequencies, but you can try to influence frequencies up to 5KHz with it.
I'm not educated in this field, I don't even have basic knowledge about music mixing procedures, everything I do here is logically derived and I read something somewhere.

I built it on the principle of signal subsampling. Example. You want to mix music and boost the frequency of 100 Hz. Good. So here's how I deduced it:
The basic signal has 44100 samples per second and the maximum captured frequency in such a signal is 22050 Hz. This means that for a frequency of 100 Hz we need 200 samples. So the program creates a signal by taking sample number 1 and sample number 201, looking at how much higher or lower the level of the samples is between them and calculating the missing 200 samples between them as a gradual increase or gradual difference of the signals. This creates a signal with a frequency of 100 Hz, which respects the original audio signal at its beginning and at its end.

The equalization itself is further implemented by choosing the mix ratio between this triangular signal and the original signal. These are added up and the result is then replayed. Basically, I had it in front of me in the past when I was creating WAVSAVE, but it didn't even occur to me at the time.

Before actually trying this program, I will first warn you about 4 things.
1) I tested the program only in a 32-bit IDE
2) I do not guarantee the correctness of this procedure, although it returns a fairly decent signal at deep frequencies
3) Turn down your speakers before trying, seriously loud production this time can destroy them.
4) Place correct music file name on row 2 in source code before compiling.

Program control is described in the program:

It's fully open, so you can even listen to the triangle signal itself and change the mixing level of the original signal. An equalizer should be made in a similar way, when the individual frequency curves will be mixed together with the original curve.

If someone comes up with a better solution, let them know.

Signal mixing with keys < and >
triangle signal volume (default ist 100%) can be set with keys + and -
frequency setting with keys q,w or Q,W

This source code is for QB64 2.02, I'm starting to convert it to Phoenix 3.5.0 today, 3/22/2023, I'll add it to this thread when the conversion is complete.


Code: (Select All)
$NoPrefix
s$ = "s.mp3" 'PLECE HERE CORRECT MUSIC FILE NAME!
s = SndOpen(s$)
Dim As MEM LS, RS 'arrays with default original MP3 values
Dim As Integer L1, R1, L2, R2, NL, NR 'L1, R1 - integers contains original signal values from MEMSOUND arrays, L2, R2 - the same as L1 and R1 but shifted right by KROK value, so if is created 100 Hz signal,
'                                                                        so L2 and R2 are shifted by 441 records in MEMSOUND array to right, NL, NR - for mixing both - new and original signal to new signal
LS = MemSound(s, 1)
RS = MemSound(s, 2)


'zajistit, aby pole LS a RS bylo delitelne hodnotou KROK
Dim As Offset MaxKrok, stp, krok 'varibles for reading MEMSOUND array
Dim As _Float PropocetL, PropocetR ' variables for calculating TRIANGLE signal
krok = 220 'Default is program set to creating 100 Hz (Bass) signal


MaxKrok = LS.SIZE 'maximal steps value for MEM functions

Screen NewImage(1200, 768, 256) 'graphis screen for visualising output signal

Do Until MaxKrok Mod krok * 2 = 0 'this loop ensures that when reading the field MEMSOUND to create a new signal, the field will not overflow and Memory out of range not occur.
    MaxKrok = MaxKrok - 1
Loop
'Default settings
mix = .25 'Original signal MIX level to output signal (is not volume level)
Volume = 1 'Created Signal (Created Signal) volume level

Locate 1: Print "Original Signal:"; Int(mix * 100); "%"; " Created Signal:"; Int((1 - mix) * 100); "%"; "           "
Locate 1, 90: Print "Created Signal Volume Level:"; Int(Volume * 100); "  "
Locate 2, 1: Print "Use the < and > keys to set the ratio between the original and generated signal, Q and W for freq. change, + and - for generated signal volume level."
Do Until stp >= MaxKrok - krok * 4 'read MEMSOUND array in full range
    MemGet LS, LS.OFFSET + stp, L1 'read left original signal
    MemGet RS, RS.OFFSET + stp, R1 'read right original signal

    MemGet LS, LS.OFFSET + stp + krok * 2, L2 'read left original signal shifted to right by krok's value
    MemGet RS, RS.OFFSET + stp + krok * 2, R2 'read right original signal shifted to right by krok's value

    stp = stp + krok * 2 '* 2 - values in MEMSOUND array are INTEGERS, 2 Bytes long

    PropocetL = (L2 - L1) / OffConv(krok) 'calculation of the size of the increase or decrease of the signal to create a triangular signal [LEFT]
    PropocetR = (R2 - R1) / OffConv(krok) 'calculation of the size of the increase or decrease of the signal to create a triangular signal [RIGHT]

    PrL = PropocetL
    PrR = PropocetR
    NL = L1 'reset this value (NL) to start value before create new triangle signal for left channel
    NR = R1 'reset this value (NR) to start value before create new triangle signal for right channel

    es = 0
    Locate 1, 60: Print "Frequency curve: [Hz] "; _SndRate / krok

    Locked = 0 'for the possibility of changing the frequency during playback
    Do Until es = krok 'it only reads the slice of the memsound field in which the new signal is formed
        'keyborad program setup
        k$ = InKey$
        Select Case k$
            Case ",", "<": mix = mix + .01: Locate 1: Print "Original Signal:"; Int(mix * 100); "%"; " Created Signal:"; Int(bmix * 100); "%"; "           "
            Case ".", ">": mix = mix - .01: Locate 1: Print "Original Signal:"; Int(mix * 100); "%"; " Created Signal:"; Int(bmix * 100); "%"; "           "
            Case "+": Volume = Volume + .1
                If Volume > 2 Then Volume = 2
                Locate 1, 90: Print "Created Signal Volume Level:"; Int(Volume * 100); "  " '            Volume - is created signal volume level
            Case "-": Volume = Volume - .1
                If Volume < 0 Then Volume = 0
                Locate 1, 90: Print "Created Signal Volume Level:"; Int(Volume * 100); "  "
            Case "Q", "q"
                If Locked = 0 Then
                    Locked = 1
                    krok = krok + 10
                    If krok > 550 Then krok = 550
                    MaxKrok = LS.SIZE
                    Do Until MaxKrok Mod krok * 2 = 0
                        MaxKrok = MaxKrok - 1
                    Loop
                    Locate 1, 60: Print "Frequency curve: [Hz] "; _SndRate / krok / 2
                    Exit Do
                End If
            Case "W", "w"
                If Locked = 0 Then
                    Locked = 1
                    krok = krok - 10
                    If krok < 1 Then krok = 1
                    MaxKrok = LS.SIZE
                    Do Until MaxKrok Mod krok * 2 = 0
                        MaxKrok = MaxKrok - 1
                    Loop
                    Locate 1, 60: Print "Frequency curve: [Hz] "; _SndRate / krok / 2
                    Exit Do
                End If
        End Select


        If mix < 0 Then mix = 0
        If mix > 1 Then mix = 1
        bmix = 1 - mix

        posuvX = posuvX + 1 'variable for shift curve on the screen, just for graphic, not for own sound function
        If posuvX = Width Then posuvX = 1
        Line (posuvX, 40)-(posuvX, Height), 0, BF

        MemGet LS, LS.OFFSET + (stp - krok * 2) + es * 2, L1 'krok and es varibles must be multiplied by two, because MEMGET reads INTEGER values.
        MemGet RS, RS.OFFSET + (stp - krok * 2) + es * 2, R1



        SL = L1 * mix / 32768 + (NL / 32768 * bmix * Volume) 'the same as for R1 but for left channel.
        SR = R1 * mix / 32768 + (NR / 32768 * bmix * Volume) 'R1 is original signal, mix is R1 percentage level in new signal, NR is created signal, bmix is percentage level for NR and volume is NR volume level.

        SndRaw SL, SR

        NL = NL + PrL 'signal calculation - fall or rise - formation of a triangular waveform  [LEFT]
        NR = NR + PrR 'signal calculation - fall or rise - formation of a triangular waveform [RIGHT]
        es = es + 1


        Line (posuvX, (Height / 2 - SL * 100))-(posuvX, (Height / 2 + SL * 100)), , BF 'draw output signal to screen

        Do Until SndRawLen = 0 'wait until all music samples are playing
        Loop
    Loop
Loop


Function OffConv& (InputVal As _Offset) 'convert offset value to normal long numeric value   -  tested in 32 bit IDE only
    Dim co As Long, m As _MEM
    m = _Mem(InputVal)
    _MemGet m, m.OFFSET, co
    _MemFree m
    OffConv& = co
End Function


Reply




Users browsing this thread: 6 Guest(s)