Bibliothèque son avec callback qu'on peut modifier avec ajout facile de filtres pour un exécutable indépendant
Publié : ven. 25/juil./2025 9:30
Objectif : ne pas importer de librairie C qui rend l'application dépendante d'une lib externe et pouvoir générer un exécutable non dépendant en respect de la philosophie Purebasic.
Faisabilité : créer dans Purebasic une bibliothèque avec son callback (comme portaudio et le temps réel) qu'on puisse modifier avec la possibilité d'y mettre des filtres sur mesures (passe haut, etc).
Inspiration pour la faisabilité: Voici un exemple qui fonctionne
Il s'agit d'un filtre passe bas IIR. Il faut modifier le chemin du wav à la ligne 253.
Faisabilité : créer dans Purebasic une bibliothèque avec son callback (comme portaudio et le temps réel) qu'on puisse modifier avec la possibilité d'y mettre des filtres sur mesures (passe haut, etc).
Inspiration pour la faisabilité: Voici un exemple qui fonctionne
Il s'agit d'un filtre passe bas IIR. Il faut modifier le chemin du wav à la ligne 253.
Code : Tout sélectionner
EnableExplicit
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; sndfile ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; --- Constantes libsndfile ---
#SFM_READ = $10 ; Mode lecture pour sf_open (0x10)
#SFM_WRITE = $20
#SFM_RDWR = $30
; --- Importation des fonctions libsndfile ---
ImportC "/opt/homebrew/Cellar/libsndfile/1.2.2_1/lib/libsndfile.1.0.37.dylib"
sf_open(filename.p-utf8, mode.l, *sfinfo)
; sf_open(filename.s, mode.l, *sfinfo) ; filename.s = PureBasic String
sf_strerror(*sndfile)
sf_close(*sndfile)
sf_read_float(*sndfile, *ptr_float, frames.q)
sf_readf_float(*sndfile, *ptr_float, frames.q)
EndImport
; --- Structure SF_INFO de libsndfile ---
Structure SF_INFO Align #PB_Structure_AlignC
frames.i ; sf_count_t (nombre de frames) 8
samplerate.l ; int (fréquence d'échantillonnage) 4
channels.l ; int (nombre de canaux) 4
format.l ; int (format du fichier, ex: WAV, FLAC, etc.) 4
sections.l ; int (nombre de sections) 4
seekable.l ; int (indique si le fichier est seekable) 4
EndStructure
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; portaudio ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ==== Constantes ====
#paContinue = 0
#SampleRate = 44100
#FramesPerBuffer = 64
#TwoPi = 2 * #PI
#paFloat32 = $1
#paInt16 = $8
#paComplete = 1
Enumeration
#paNoError = 0
#paNotInitialized = -10000
#paUnanticipatedHostError
#paInvalidChannelCount
#paInvalidSampleRate
#paInvalidDevice
#paInvalidFlag
#paSampleFormatNotSupported
#paBadIODeviceCombination
#paInsufficientMemory
#paBufferTooBig
#paBufferTooSmall
#paNullCallback
#paBadStreamPtr
#paTimedOut
#paInternalError
#paDeviceUnavailable
#paIncompatibleHostApiSpecificStreamInfo
#paStreamIsStopped
#paStreamIsNotStopped
#paInputOverflowed
#paOutputUnderflowed
#paHostApiNotFound
#paInvalidHostApi
#paCanNotReadFromACallbackStream
#paCanNotWriteToACallbackStream
#paCanNotReadFromAnOutputOnlyStream
#paCanNotWriteToAnInputOnlyStream
#paIncompatibleStreamHostApi
#paBadBufferPtr
EndEnumeration
ImportC "/opt/homebrew/Cellar/portaudio/19.7.0/lib/libportaudio.dylib"
Pa_Initialize()
Pa_Terminate()
Pa_GetDeviceCount()
Pa_GetDeviceInfo(i) ;const PaDeviceInfo * Pa_GetDeviceInfo (PaDeviceIndex device)
Pa_GetHostApiInfo(q);const PaHostApiInfo * Pa_GetHostApiInfo (PaHostApiIndex hostApi)
Pa_GetErrorText(i) ;const char * Pa_GetErrorText (PaError errorCode)
Pa_StartStream(*stream)
Pa_StopStream(*stream)
Pa_CloseStream(*stream)
Pa_IsStreamActive(*stream)
; typedef unsigned long PaSampleFormat;
; PaError Pa_OpenDefaultStream (PaStream **stream, int numInputChannels, int numOutputChannels, PaSampleFormat sampleFormat, double sampleRate, unsigned long framesPerBuffer, PaStreamCallback *streamCallback, void *userData)
Pa_OpenDefaultStream(*stream, inputChannels, outputChannels.l, sampleFormat.q, sampleRate.d, framesPerBuffer.q, *callback, *userData)
Pa_Sleep(ms)
EndImport
Structure PaDeviceInfo Align #PB_Structure_AlignC
structVersion.l ;4 bytes
*name ; 8 bytes
hostApi.l ;4 bytes. int
maxInputChannels.l ;4 bytes
maxOutputChannels.l ;4 bytes
defaultLowInputLatency.d ; 8 bytes
defaultLowOutputLatency.d ; 8 bytes
defaultHighInputLatency.d ; 8 bytes
defaultHighOutputLatency.d ; 8 bytes
defaultSampleRate.d ; 8 bytes
EndStructure
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#FRAMES_PER_BUFFER = 512
Structure CanalRealTimeProcessing
x.d[2]; Entrées
y.d[2]; Sorties
EndStructure
Structure PaData
*file
info.SF_INFO
stop.b
pause.b ; Nouveau : état de pause
*cg.CanalRealTimeProcessing
*cd.CanalRealTimeProcessing
EndStructure
; ---------- procédure iir ----------
Procedure.f iir (input.f, *c.CanalRealTimeProcessing)
Protected ta.l, i.l, yn.f
Static Dim coeffA.d(1)
Static Dim coeffB.d(1)
Static init.b = #False
If Not init
coeffA(0) = 1.0 : coeffA(1) = -0.866788439500
coeffB(0) = 0.066605780250 : coeffB(1) = 0.066605780250
init = #True
EndIf
ta = ArraySize(coeffA()) + 1; taille (+1 car purebasic)
; Debug ta
; Décalage des entrées
For i = ta - 1 To 1 Step -1
; Debug i
*c\x[i] = *c\x[i - 1]
Next i
; Nouvel échantillon d'entrée
*c\x[0] = input
; Calcul de la sortie
yn = 0.0
; Partie feedforward (numérateur)
For i = 0 To ta-1
yn + coeffB(i) * *c\x[i]
Next i
; Partie feedback (dénominateur)
For i = 1 To ta-1; commence à 1
yn - coeffA(i) * *c\y[i]
Next i
; Décalage de la sortie
For i = ta-1 To 1 Step -1
*c\y[i] = *c\y[i - 1]
Next i
; Sauvegarder la sortie
*c\y[0] = yn
ProcedureReturn yn
EndProcedure
; === Callback audio ===
ProcedureC.l audio_callback(*inputBuffer, *outputBuffer, framesPerBuffer.q, *timeInfo, statusFlags.q, *userData)
Protected *audioData.PaData = *userData
Protected *audioOut.FLOAT = *outputBuffer
Protected num_read.q
Protected i.l, totalSamples.l, filtre.f, input.f
totalSamples = framesPerBuffer * *audioData\info\channels
If *audioData\stop
For i = 0 To totalSamples - 1
PokeF(*audioOut + i * SizeOf(Float), 0.0)
Next
ProcedureReturn #paComplete
EndIf
; Nouveau : si en pause, remplir de silence
If *audioData\pause
For i = 0 To totalSamples - 1
PokeF(*audioOut + i * SizeOf(Float), 0.0)
Next
ProcedureReturn #paContinue
EndIf
totalSamples = framesPerBuffer * *audioData\info\channels
num_read = sf_read_float(*audioData\file, *audioOut, totalSamples)
; Debug Str(totalSamples) + " " + Str(num_read)
; canal de droite à 0
; For i = 0 To num_read - 1 Step 2
; PokeF(*audioOut + (i + 0) * SizeOf(Float), 0)
; Next
For i = 0 To num_read - 1 Step 2
; canal gauche
input = PeekF(*audioOut + (i + 0) * SizeOf(Float))
filtre = iir(input.f, *audioData\cg)
PokeF(*audioOut + (i + 0) * SizeOf(Float), filtre)
; canal droit
input = PeekF(*audioOut + (i + 1) * SizeOf(Float))
filtre = iir(input.f, *audioData\cg)
PokeF(*audioOut + (i + 1) * SizeOf(Float), filtre)
Next i
; fin de fichier wav : remplir le reste par des 0
If num_read < framesPerBuffer
For i = num_read To totalSamples - 1
PokeF(*audioOut + i * SizeOf(Float), 0.0)
Next
ProcedureReturn #paComplete
EndIf
ProcedureReturn #paContinue
EndProcedure
; ================== Main ==================
; Define wavfile.s = OpenFileRequester("Sélectionnez un fichier WAV", "", "Fichiers WAV (*.wav)|*.wav", 0)
; If wavfile = ""
; End
; EndIf
; Define audioData.PaData
; audioData\info\format = 0; sndfile le recommande avant ouverture
; audioData\file = sf_open(wavfile, #SFM_READ, @audioData\info)
; If audioData\file = 0
; MessageRequester("Erreur", "Impossible d'ouvrir le fichier.")
; End
; EndIf
Define wavfile$="/Users/uio/Music/_.wav"
Define audioData.PaData
Define canalGauche.CanalRealTimeProcessing
Define canalDroit.CanalRealTimeProcessing
audiodata\cg = @canalGauche
audiodata\cd = @canalDroit
audioData\info\format = 0; sndfile le recommande avant ouverture
audioData\file = sf_open(wavfile$, #SFM_READ, @audioData\info)
If audioData\file = 0
MessageRequester("Erreur", "Impossible d'ouvrir le fichier.")
End
EndIf
If Pa_Initialize() <> 0
MessageRequester("Erreur", "Erreur initialisation PortAudio")
sf_close(audioData\file)
End
EndIf
Define *stream
If Pa_OpenDefaultStream(@*stream, 0, audioData\info\channels, #paFloat32, audioData\info\samplerate, #FRAMES_PER_BUFFER, @audio_callback(), @audioData) <> 0
MessageRequester("Erreur", "Erreur ouverture flux audio")
Pa_Terminate()
sf_close(audioData\file)
End
EndIf
If Pa_StartStream(*stream) <> 0
MessageRequester("Erreur", "Erreur démarrage flux")
Pa_CloseStream(*stream)
Pa_Terminate()
sf_close(audioData\file)
End
EndIf
; === Fenêtre avec boutons Stop et Pause ===
OpenWindow(0, 0, 0, 350, 100, "Lecteur WAV", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ButtonGadget(0, 50, 30, 80, 30, "Arrêter")
ButtonGadget(1, 140, 30, 80, 30, "Pause")
ButtonGadget(2, 230, 30, 80, 30, "Reprendre")
; === Boucle lecture audio + GUI ===
Repeat
Select WaitWindowEvent(10)
Case #PB_Event_Gadget
Select EventGadget()
Case 0 ; Bouton Arrêter
audioData\stop = #True
Case 1 ; Bouton Pause
audioData\pause = #True
Case 2 ; Bouton Reprendre
audioData\pause = #False
EndSelect
Case #PB_Event_CloseWindow
audioData\stop = #True
Break
EndSelect
If Pa_IsStreamActive(*stream) = 0
Break
EndIf
Pa_Sleep(10)
ForEver
; === Nettoyage ===
Pa_StopStream(*stream)
Pa_CloseStream(*stream)
Pa_Terminate()
sf_close(audioData\file)
; MessageRequester("Info", "Lecture terminée.")