' *****************************************************************************' Multisine Generator (Log Spaced, Min–Max Phase Optimization)
' - Frequency range: 1 mHz → 10 kHz (logarithmic spacing)
' - User-selectable number of tones (1–32)
' - User-selectable uniform amplitude (example: 1.0)
' - Min–Max phase optimization to reduce the maximum absolute sample (peak)
' *****************************************************************************
USES "CONSOLE"
USES "iComplex"
%MAX_ENTRIES = 32
' -----------------------------
' Configuration
' -----------------------------
LOCAL nSamples AS LONG value = 1024 ' Increase for wider time window (benefits very low f)
LOCAL fs AS DOUBLE value = 50000 ' Must exceed 2 * fMax (Nyquist). Here fMax=10 kHz -> fs >= 20 kHz
LOCAL pi AS DOUBLE value = 4 * ATN(1)
' Min–Max optimizer settings
LOCAL MAX_ITERS AS LONG value = 200 ' Iteration cap
LOCAL TOL AS DOUBLE Value = 1e-9 ' Stop if improvement < TOL
' Frequency range (log spaced)
LOCAL fMin AS DOUBLE value = 0.001 ' 1 mHz
LOCAL fMax AS DOUBLE value = 10000 ' 10 kHz
' Generated signal
LOCAL Signal(nSamples) AS DOUBLE
' Generic variable
LOCAL i , j , n AS LONG value = 0
Console_SetCP(%CP_UTF8) ' Set Input codepage to UTF8
Console_SetOutputCP(%CP_UTF8) ' Set Output codepage to UTF8
' -----------------------------
' User input: tone count and amplitude
' -----------------------------
LOCAL activeCount AS LONG
PRINT $CRLF & "Enter number of output frequencies (1-32): " : activecount = VAL(Console_ReadLine())
IF activeCount < 1 OR activeCount > %MAX_ENTRIES THEN
PRINTL "Invalid value. Must be 1–32."
waitkey
stop
END IF
LOCAL userAmp AS DOUBLE
PRINT $CRLF & "Enter uniform amplitude for all tones (e.g., 1): " : UserAmp = VAL(Console_ReadLine())
IF userAmp <= 0 THEN userAmp = 1
' -----------------------------
' Data structures
' -----------------------------
TYPE tFreqEntry
f AS DOUBLE
amp AS DOUBLE
ph AS DOUBLE
END TYPE
LOCAL FreqTable(%MAX_ENTRIES) AS tFreqEntry
' -----------------------------
' Build logarithmically spaced frequency table
' f(k) = exp( log(fMin) + (k-1)/(N-1) * (log(fMax) - log(fMin)) )
' -----------------------------
IF activeCount = 1 THEN
FreqTable(1).f = fMin
FreqTable(1).amp = userAmp
FreqTable(1).ph = 0
ELSE
LOCAL dlogMin AS DOUBLE : dlogMin = LOG(fMin)
LOCAL dlogMax AS DOUBLE : dlogMax = LOG(fMax)
LOCAL dstep AS DOUBLE : dstep = (dlogMax - dlogMin) / (activeCount - 1)
FOR i = 1 TO activeCount
FreqTable(i).f = EXP(dlogMin + (i - 1) * dstep)
FreqTable(i).amp = userAmp
FreqTable(i).ph = 0 ' Start phases at 0 for optimization
NEXT
END IF
'
' -----------------------------
' Sanity checks
' -----------------------------
IF fs < 2 * fMax THEN
PRINTL "WARNING: fs < 2*fMax (Nyquist). Increase fs to at least " & (2 * fMax) & " Hz."
END IF
IF nSamples < 2 THEN
PRINTL "ERROR: nSamples must be >= 2"
END
END IF
' -----------------------------
' Run optimization, synthesize, and report
' -----------------------------
msgbox 0,"before"
CALL MinMaxOptimizePhases(Signal,nSamples)
' Compute final stats
'LOCAL dpeakVal AS DOUBLE value = 0
'LOCAL dsumSq AS DOUBLE value = 0
'LOCAL dRMS AS DOUBLE Value = 0
msgbox 0," heelo"
FOR i = 1 TO nSamples
IF ABS(Signal(i)) > dpeakVal THEN dpeakVal = ABS(Signal(i))
dsumSq = dsumSq + (Signal(i) * Signal(i))
NEXT
IF nSamples > 0 THEN
drms = SQR(dsumSq / nSamples)
ELSE
drms = 0
END IF
'
' -----------------------------
' Output table and stats
' -----------------------------
PRINTL ""
PRINTL "=== Multisine Configuration ==="
'PRINTL "Samples: " & nSamples & ", fs: " & fs & " Hz"
'PRINTL "Active tones: " & activeCount & ", Uniform amplitude: " & userAmp
'PRINTL "Frequency range: " & fMin & " Hz to " & fMax & " Hz (log-spaced)"
'
'PRINTL ""
'PRINTL "=== Frequency Table ==="
'FOR i = 1 TO activeCount
' PRINTL USING$("Tone ##: F=#########.######## Hz Amp=#.### Phi=#.###### rad", _
' i, FreqTable(i).f, FreqTable(i).amp, FreqTable(i).ph)
'NEXT
'
'PRINTL ""
'PRINTL "=== Signal Stats ==="
'PRINTL "Peak: " & FORMAT$(peakVal, "#.######")
'PRINTL "RMS : " & FORMAT$(rms, "#.######")
'IF rms > 0 THEN
' PRINTL "Crest Factor: " & FORMAT$(peakVal / rms, "#.######") & " (" & FORMAT$(20 * LOG(peakVal / rms) / LOG(10), "#.##") & " dB)"
'ELSE
' PRINTL "Crest Factor: N/A (RMS=0)"
'END IF
'
'MSGBOX 0, "Multisine (log-spaced) with Min–Max phase optimization complete." & $CRLF & _
' "Tones: " & activeCount & ", Amp: " & FORMAT$(userAmp, "#.###") & $CRLF & _
' "Peak=" & FORMAT$(peakVal, "#.####") & ", RMS=" & FORMAT$(rms, "#.####")
'
'
' -----------------------------
' Helper: compute signal samples given current phases
' -----------------------------
SUB ComputeSignal(byref Signal(),byval nSamples)
' LOCAL i , j AS LONG
' LOCAL t, theta AS DOUBLE
' LOCAL s AS DOUBLE
msgbox 0, "compute"
FOR i = 1 TO nSamples
t = (i - 1) / fs
' Direct real sum; complex not required for real signal
s = 0
FOR j = 1 TO activeCount
theta = 2 * pi * FreqTable(j).f * t + FreqTable(j).ph
s = s + FreqTable(j).amp * COS(theta)
NEXT
Signal(i) = s
NEXT
END SUB
'
'
'' -----------------------------
'' Helper: crest factor computation
'' -----------------------------
'FUNCTION CrestFactorDB() AS DOUBLE
' LOCAL i AS LONG
' LOCAL peakVal AS DOUBLE, sumSq AS DOUBLE
' peakVal = 0 : sumSq = 0
' FOR i = 1 TO nSamples
' IF ABS(Signal(i)) > peakVal THEN peakVal = ABS(Signal(i))
' sumSq = sumSq + Signal(i) * Signal(i)
' NEXT
' LOCAL rms AS DOUBLE : rms = SQR(sumSq / nSamples)
' IF rms = 0 THEN
' FUNCTION = 0
' ELSE
' FUNCTION = 20 * LOG(peakVal / rms) / LOG(10) ' CF in dB
' END IF
'END FUNCTION
-----------------------------
' Min–Max Phase Optimization
' Goal: minimize max |Signal| over the finite window by adjusting phases.
' Approach: Greedy coordinate updates focused at the current worst (peak) sample.
' For the current peak sample t*, for each tone k:
' s(t*) = R + A_k * cos(theta_k + phi_k), solve phi_k to minimize |s(t*)|.
' Optimal at that t*: cos(theta_k + phi_k) = clamp(-R / A_k, -1, 1).
' Iterate until improvement < TOL or MAX_ITERS reached.
' -----------------------------
SUB MinMaxOptimizePhases(byref Signal(), nSamples)
' LOCAL it AS LONG
' LOCAL i, k AS LONG
' LOCAL imax AS LONG
' LOCAL oldPeak, newPeak, sVal AS DOUBLE
' LOCAL tstar, theta_k AS DOUBLE
' LOCAL R, A AS DOUBLE
' LOCAL c AS DOUBLE
msgbox 0," MINMAX"
' Initial signal
CALL ComputeSignal(signal,nSamples)
' Measure initial peak
oldPeak = 0
FOR i = 1 TO nSamples
IF ABS(Signal(i)) > oldPeak THEN oldPeak = ABS(Signal(i))
NEXT
FOR it = 1 TO MAX_ITERS
' ' Find index of current worst sample
' imax = 1 : newPeak = ABS(Signal(1))
' FOR i = 2 TO nSamples
' sVal = ABS(Signal(i))
' IF sVal > newPeak THEN
' newPeak = sVal
' imax = i
' END IF
' NEXT
'
' ' If peak improvement is tiny, stop
' IF it > 1 THEN
' IF oldPeak - newPeak < TOL THEN EXIT FOR
' END IF
' oldPeak = newPeak
'
' ' Time at peak
' tstar = (imax - 1) / fs
'
' ' For each tone, adjust phase to reduce the peak at tstar
' FOR k = 1 TO activeCount
' ' Residual excluding tone k at tstar
' ' s(t*) = sum_j A_j cos(theta_j + phi_j)
' ' R = s(t*) - A_k cos(theta_k + phi_k)
' theta_k = 2 * pi * FreqTable(k).f * tstar + FreqTable(k).ph
' A = FreqTable(k).amp
' R = Signal(imax) - A * COS(theta_k)
'
' ' Best (local) choice at t* in the min-max sense:
' ' cos(theta_k + newPhi) = clamp( -R / A, -1, 1 )
' IF A > 0 THEN
' c = -R / A
' IF c > 1 THEN c = 1
' IF c < -1 THEN c = -1
'
' ' Choose phase giving that cosine at t*
' ' newPhi = arccos(c) - (2*pi*f_k*t*), but theta_k = 2*pi*f_k*t* + oldPhi,
' ' -> newPhi = arccos(c) - 2*pi*f_k*t*
' ' We'll directly update FreqTable(k).ph
' FreqTable(k).ph = ACOS(c) - (2 * pi * FreqTable(k).f * tstar)
' ' Optional: keep phase in [-pi, pi] for neatness (cos periodic, not required)
' IF FreqTable(k).ph > pi THEN FreqTable(k).ph = FreqTable(k).ph - 2 * pi
' IF FreqTable(k).ph < -pi THEN FreqTable(k).ph = FreqTable(k).ph + 2 * pi
' END IF
' NEXT
'
' ' Recompute signal after updating all phases
' CALL ComputeSignal()
' NEXT
END SUB