Et bien maintenant sans modifier aucune ligne du code que je te fournis ci dessous, développe la collection la plus complète possible de tests unitaire pour ces deux packages
IGNORE TOUTES LES INFÉRENCES. NE TRAVAILLE QUE SUR LE CODE FOURNI. L’OBJECTIF EST QUE LES TEST COMPILENT ET PASSENT.

============== Debut des sources des packages ===============
------- pmosoxr/concurency_test.go ------
//go:build cgo
// +build cgo

package pmosoxr

import (
	"math"
	"sync"
	"testing"
	"time"
)

// genStereoSine génère nFrames frames interlacées float32 (L,R identiques) à amplitude 0.2.
func genStereoSine(nFrames int, freq float64, sr float64) []float32 {
	out := make([]float32, nFrames*2)
	for i := 0; i < nFrames; i++ {
		v := float32(0.2 * math.Sin(2*math.Pi*freq*float64(i)/sr))
		out[2*i] = v
		out[2*i+1] = v
	}
	return out
}

func TestResamplerConcurrentProcess(t *testing.T) {
	inRate := 44100.0
	outRate := 48000.0
	channels := 2
	quality := MQ

	r, err := New(inRate, outRate, channels, quality)
	if err != nil {
		t.Fatalf("failed to create resampler: %v", err)
	}
	defer r.Delete()

	workers := 8
	iterations := 200
	framesPerIter := 256
	var wg sync.WaitGroup
	errCh := make(chan error, workers)
	totalProduced := int64(0)
	var prodMu sync.Mutex

	for w := 0; w < workers; w++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for i := 0; i < iterations; i++ {
				in := genStereoSine(framesPerIter, 440.0+float64(id), inRate)
				ratio := outRate / inRate
				outFrames := int(float64(framesPerIter)*ratio) + 64
				out := make([]float32, outFrames*2)
				consumed, produced, perr := r.Process(in, out)
				if perr != nil {
					errCh <- perr
					return
				}
				if consumed < 0 || produced < 0 {
					errCh <- &testError{"negative sample count"}
					return
				}
				if produced > len(out) {
					errCh <- &testError{"produced > out buffer"}
					return
				}
				prodMu.Lock()
				totalProduced += int64(produced)
				prodMu.Unlock()
				time.Sleep(1 * time.Millisecond)
			}
		}(w)
	}

	wg.Wait()
	close(errCh)

	for e := range errCh {
		t.Fatalf("resampler error: %v", e)
	}

	if totalProduced == 0 {
		t.Fatal("no samples produced")
	}
}

type testError struct {
	s string
}

func (e *testError) Error() string { return e.s }
-----------------
------- pmosoxr/quality.go ------
package pmosoxr

/*
#cgo CFLAGS: -I${SRCDIR}/../C/include

#include <soxr.h>
#include <stdlib.h>

static soxr_quality_spec_t q_spec(int q) {
    switch(q) {
        case 0: return soxr_quality_spec(SOXR_QQ, 0);
        case 1: return soxr_quality_spec(SOXR_LQ, 0);
        case 2: return soxr_quality_spec(SOXR_MQ, 0);
        case 3: return soxr_quality_spec(SOXR_HQ, 0);
        case 4: return soxr_quality_spec(SOXR_VHQ, 0);
        default: return soxr_quality_spec(SOXR_MQ, 0);
    }
}
*/
import "C"

type Quality int

const (
	QQ Quality = iota
	LQ
	MQ
	HQ
	VHQ
)

func (q Quality) toC() C.soxr_quality_spec_t {
	return C.q_spec(C.int(q))
}
-----------------
------- pmosoxr/soxr.go ------
//go:build cgo
// +build cgo

package pmosoxr

/*
#cgo CFLAGS: -I${SRCDIR}/../C/include
#cgo LDFLAGS: ${SRCDIR}/../C/lib/libsoxr.a -lomp

#include <stdlib.h>
#include <soxr.h>
*/
import "C"

import (
	"errors"
	"sync"
	"unsafe"
)

type Resampler struct {
	handle   C.soxr_t
	channels int
	mu       sync.Mutex
	deleted  bool
}

func New(inRate, outRate float64, channels int, q Quality) (*Resampler, error) {
	if channels != 2 {
		return nil, errors.New("only stereo supported")
	}

	var err C.soxr_error_t
	qspec := q.toC()

	handle := C.soxr_create(
		C.double(inRate),
		C.double(outRate),
		C.uint(channels),
		&err,
		nil,
		&qspec,
		nil,
	)
	if handle == nil {
		if err != nil {
			return nil, errors.New(C.GoString(err))
		}
		return nil, errors.New("soxr_create failed without error message")
	}
	return &Resampler{handle: handle, channels: channels}, nil
}

func (r *Resampler) Process(in []float32, out []float32) (consumedSamples int, producedSamples int, err error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if r.deleted {
		return 0, 0, errors.New("resampler deleted")
	}
	if r.handle == nil {
		return 0, 0, errors.New("resampler not initialized")
	}
	if len(in)%r.channels != 0 || len(out)%r.channels != 0 {
		return 0, 0, errors.New("buffer size not divisible by channel count")
	}

	var idone, odone C.size_t
	var inPtr C.soxr_in_t
	var outPtr C.soxr_out_t

	if len(in) > 0 {
		inPtr = C.soxr_in_t(unsafe.Pointer(&in[0]))
	}
	if len(out) > 0 {
		outPtr = C.soxr_out_t(unsafe.Pointer(&out[0]))
	}

	st := C.soxr_process(
		r.handle,
		inPtr, C.size_t(len(in)/r.channels),
		&idone,
		outPtr, C.size_t(len(out)/r.channels),
		&odone,
	)
	if st != nil {
		return 0, 0, errors.New(C.GoString(st))
	}
	return int(idone) * r.channels, int(odone) * r.channels, nil
}

func (r *Resampler) Flush(out []float32) (int, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if r.deleted {
		return 0, errors.New("resampler deleted")
	}
	if r.handle == nil {
		return 0, errors.New("resampler not initialized")
	}

	var odone C.size_t
	if len(out) == 0 {
		return 0, nil
	}
	st := C.soxr_process(r.handle, nil, 0, nil,
		C.soxr_out_t(unsafe.Pointer(&out[0])), C.size_t(len(out)/r.channels), &odone)
	if st != nil {
		return 0, errors.New(C.GoString(st))
	}
	return int(odone) * r.channels, nil
}

func (r *Resampler) Delete() {
	r.mu.Lock()
	defer r.mu.Unlock()

	if !r.deleted && r.handle != nil {
		C.soxr_delete(r.handle)
		r.handle = nil
		r.deleted = true
	}
}
-----------------
------- pmostream/audio.go ------
package pmostream

import (
	"fmt"
	"os"
	"strings"

	"github.com/gopxl/beep"
	"github.com/gopxl/beep/flac"
	"github.com/gopxl/beep/mp3"
	"github.com/gopxl/beep/vorbis"
	"github.com/gopxl/beep/wav"
)

type streamerWithCloser struct {
	beep.Streamer
	closer func() error
}

func (s *streamerWithCloser) Close() error {
	if s.closer != nil {
		return s.closer()
	}
	return nil
}

func LoadAudio(uri string) (beep.Streamer, beep.Format, error) {
	f, err := os.Open(uri)
	if err != nil {
		return nil, beep.Format{}, err
	}

	var streamer beep.Streamer
	var format beep.Format

	lowerURI := strings.ToLower(uri)
	switch {
	case strings.HasSuffix(lowerURI, ".flac"):
		streamer, format, err = flac.Decode(f)
	case strings.HasSuffix(lowerURI, ".wav"):
		streamer, format, err = wav.Decode(f)
	case strings.HasSuffix(lowerURI, ".mp3"):
		streamer, format, err = mp3.Decode(f)
	case strings.HasSuffix(lowerURI, ".ogg"):
		streamer, format, err = vorbis.Decode(f)
	default:
		f.Close()
		return nil, beep.Format{}, fmt.Errorf("unsupported format: %s", uri)
	}

	if err != nil {
		f.Close()
		return nil, beep.Format{}, err
	}

	return &streamerWithCloser{
		Streamer: streamer,
		closer:   f.Close,
	}, format, nil
}
-----------------
------- pmostream/buffer.go ------
package pmostream

import (
	"sync"
	"time"
)

// AudioBuffer est un buffer circulaire thread-safe pour l'audio
type AudioBuffer struct {
	mu       sync.Mutex
	cond     *sync.Cond
	chunks   [][]byte
	size     int
	format   SampleFormat
	readPos  int
	writePos int
	closed   bool
}

// NewAudioBuffer crée un nouveau buffer circulaire
func NewAudioBuffer(size int, format SampleFormat) *AudioBuffer {
	ab := &AudioBuffer{
		chunks: make([][]byte, size),
		size:   size,
		format: format,
	}
	ab.cond = sync.NewCond(&ab.mu)
	return ab
}

// Available retourne le nombre de chunks disponibles pour lecture
func (ab *AudioBuffer) Available() int {
	ab.mu.Lock()
	defer ab.mu.Unlock()
	return ab.availableLocked()
}

// Write ajoute un chunk dans le buffer (écrase le plus ancien si plein)
func (ab *AudioBuffer) Write(chunk []byte) {
	ab.mu.Lock()
	defer ab.mu.Unlock()
	if ab.closed {
		return
	}

	ab.chunks[ab.writePos] = chunk
	ab.writePos = (ab.writePos + 1) % ab.size
	if ab.writePos == ab.readPos {
		// Écrase le plus ancien
		ab.readPos = (ab.readPos + 1) % ab.size
	}

	ab.cond.Broadcast()
}

// Read lit un chunk du buffer, ou nil si vide ou fermé
func (ab *AudioBuffer) Read() []byte {
	ab.mu.Lock()
	defer ab.mu.Unlock()

	if ab.availableLocked() == 0 {
		return nil
	}

	chunk := ab.chunks[ab.readPos]
	ab.chunks[ab.readPos] = nil
	ab.readPos = (ab.readPos + 1) % ab.size
	return chunk
}

// WaitForData attend qu'au moins n chunks soient disponibles ou timeout/closed
func (ab *AudioBuffer) WaitForData(n int, timeout time.Duration) bool {
	ab.mu.Lock()
	defer ab.mu.Unlock()

	if n <= 0 {
		n = 1
	}

	deadline := time.Now().Add(timeout)

	for ab.availableLocked() < n && !ab.closed {
		if timeout <= 0 {
			ab.cond.Wait()
		} else {
			remaining := time.Until(deadline)
			if remaining <= 0 {
				return false
			}

			// Wait avec timeout en utilisant Wait + Broadcast classique
			timer := time.NewTimer(remaining)
			done := make(chan struct{})

			go func() {
				ab.cond.Wait()
				close(done)
			}()

			ab.mu.Unlock()
			select {
			case <-done:
				// réveillé par Broadcast
				if !timer.Stop() {
					<-timer.C
				}
			case <-timer.C:
				ab.mu.Lock()
				return false
			}
			ab.mu.Lock()
		}
	}

	return ab.availableLocked() >= n && !ab.closed
}

// Close ferme le buffer et réveille tous les Waiters
func (ab *AudioBuffer) Close() {
	ab.mu.Lock()
	defer ab.mu.Unlock()
	if !ab.closed {
		ab.closed = true
		ab.cond.Broadcast()
	}
}

// availableLocked retourne le nombre de chunks disponibles, doit être appelé avec ab.mu locké
func (ab *AudioBuffer) availableLocked() int {
	if ab.writePos >= ab.readPos {
		return ab.writePos - ab.readPos
	}
	return ab.size - ab.readPos + ab.writePos
}
-----------------
------- pmostream/conversion.go ------
package pmostream

import (
	"encoding/binary"
	"math"
)

func convertFloat64ToFloat32(samples [][2]float64) []float32 {
	if len(samples) == 0 {
		return nil
	}
	result := make([]float32, len(samples)*2)
	for i, s := range samples {
		result[2*i] = float32(clamp(s[0], -1.0, 1.0))
		result[2*i+1] = float32(clamp(s[1], -1.0, 1.0))
	}
	return result
}

func convertFloat64ToPCM(samples [][2]float64) []byte {
	if len(samples) == 0 {
		return nil
	}
	buf := make([]byte, len(samples)*4)
	for i, s := range samples {
		l := int16(clamp(s[0], -1.0, 1.0) * 32767.0)
		r := int16(clamp(s[1], -1.0, 1.0) * 32767.0)
		binary.LittleEndian.PutUint16(buf[4*i:], uint16(uint16(l)))
		binary.LittleEndian.PutUint16(buf[4*i+2:], uint16(uint16(r)))
	}
	return buf
}

func float32ToPCM(samples []float32) []byte {
	if len(samples) == 0 {
		return nil
	}
	buf := make([]byte, len(samples)*2)
	for i, v := range samples {
		val := int16(clamp(float64(v), -1.0, 1.0) * 32767.0)
		binary.LittleEndian.PutUint16(buf[2*i:], uint16(val))
	}
	return buf
}

func float32ToBytes(samples []float32) []byte {
	if len(samples) == 0 {
		return nil
	}
	buf := make([]byte, len(samples)*4)
	for i, v := range samples {
		binary.LittleEndian.PutUint32(buf[i*4:], math.Float32bits(v))
	}
	return buf
}

func bytesToFloat32(data []byte) []float32 {
	if len(data)%4 != 0 {
		return nil
	}
	result := make([]float32, len(data)/4)
	for i := range result {
		result[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[i*4:]))
	}
	return result
}

func pcmToFloat32(data []byte) []float32 {
	if len(data)%2 != 0 {
		return nil
	}
	result := make([]float32, len(data)/2)
	for i := 0; i < len(result); i++ {
		val := int16(binary.LittleEndian.Uint16(data[2*i:]))
		result[i] = float32(val) / 32768.0
	}
	return result
}

func clamp(val, min, max float64) float64 {
	if val < min {
		return min
	}
	if val > max {
		return max
	}
	return val
}
-----------------
------- pmostream/core.go ------
package pmostream

import (
	"fmt"
	"math"
	"sync"
	"sync/atomic"
	"time"

	"gargoton.petite-maison-orange.fr/eric/pmomusic/pmosoxr"
	"github.com/gopxl/beep"
	"github.com/gopxl/beep/effects"
)

type SampleFormat int

const (
	Float32 SampleFormat = iota
	PCM16
)

type HiFiConfig struct {
	TargetSampleRate beep.SampleRate
	BufferSeconds    int
	ChunkSize        int
	ResampleQuality  pmosoxr.Quality
	Volume           float64
	Format           SampleFormat
}

func DefaultHiFiConfig() HiFiConfig {
	return HiFiConfig{
		TargetSampleRate: 48000,
		BufferSeconds:    5,
		ChunkSize:        1024,
		ResampleQuality:  pmosoxr.HQ,
		Volume:           0.0,
		Format:           PCM16,
	}
}

type AudioProcessor struct {
	config       HiFiConfig
	streamer     beep.Streamer
	format       beep.Format
	resampler    *pmosoxr.Resampler
	volume       *effects.Volume
	volumeValue  float64
	buffer       *AudioBuffer
	masterBuffer *MasterBuffer
	processMutex sync.Mutex
	running      atomic.Bool
	wg           sync.WaitGroup
	resampleBuf  []float32
	volumeMu     sync.Mutex
	streamDone   atomic.Bool
}

func NewAudioProcessor(streamer beep.Streamer, format beep.Format, config HiFiConfig, master *MasterBuffer) (*AudioProcessor, error) {
	if streamer == nil && master == nil {
		return nil, fmt.Errorf("streamer cannot be nil if no master buffer is provided")
	}

	if format.NumChannels != 2 {
		return nil, fmt.Errorf("only stereo format is supported")
	}

	var vol *effects.Volume
	if streamer != nil {
		vol = &effects.Volume{
			Streamer: streamer,
			Base:     2,
			Volume:   config.Volume,
			Silent:   false,
		}
	}

	var resampler *pmosoxr.Resampler
	var err error
	if streamer != nil && format.SampleRate != config.TargetSampleRate {
		resampler, err = pmosoxr.New(float64(format.SampleRate), float64(config.TargetSampleRate), 2, config.ResampleQuality)
		if err != nil {
			return nil, fmt.Errorf("failed to create resampler: %w", err)
		}
	}

	sampleSize := 4
	if config.Format == PCM16 {
		sampleSize = 2
	}
	bytesPerSecond := int(config.TargetSampleRate) * sampleSize * 2 // stéréo
	bufferSize := (bytesPerSecond * config.BufferSeconds) / config.ChunkSize
	if bufferSize < 1 {
		bufferSize = 1
	}

	ap := &AudioProcessor{
		config:       config,
		streamer:     streamer,
		format:       format,
		resampler:    resampler,
		volume:       vol,
		volumeValue:  config.Volume,
		buffer:       NewAudioBuffer(bufferSize, config.Format),
		masterBuffer: master,
	}
	ap.running.Store(true)
	return ap, nil
}

func (p *AudioProcessor) GetBuffer() *AudioBuffer {
	return p.buffer
}

func (p *AudioProcessor) SetVolume(volume float64) {
	p.volumeMu.Lock()
	defer p.volumeMu.Unlock()
	p.volumeValue = volume
	if p.volume != nil {
		p.volume.Volume = volume
	}
}

func (p *AudioProcessor) Stop() {
	p.running.Store(false)
}

func (p *AudioProcessor) Close() error {
	p.Stop()
	p.wg.Wait()
	p.processMutex.Lock()
	defer p.processMutex.Unlock()

	if p.resampler != nil {
		p.resampler.Delete()
	}
	if closer, ok := p.streamer.(interface{ Close() error }); ok {
		return closer.Close()
	}
	return nil
}

// Process lit depuis le streamer ou le master buffer, applique resampling + volume et écrit dans le buffer
func (p *AudioProcessor) Process() error {
	if !p.running.Load() {
		return nil
	}

	p.wg.Add(1)
	defer p.wg.Done()

	chunkSize := p.config.ChunkSize
	if chunkSize <= 0 {
		chunkSize = 1024
	}

	for p.running.Load() {
		var samples [][2]float64

		// 1) Lire depuis le streamer
		if p.streamer != nil {
			samples = make([][2]float64, chunkSize)
			n, ok := p.streamer.Stream(samples)
			if !ok {
				p.streamDone.Store(true)
				break
			}
			samples = samples[:n]
			if n == 0 {
				time.Sleep(5 * time.Millisecond)
				continue
			}
		} else if p.masterBuffer != nil {
			// Lecture via ForkedBuffer
			chunks := p.masterBuffer.ReadAll()
			if len(chunks) == 0 {
				time.Sleep(5 * time.Millisecond)
				continue
			}

			samples = make([][2]float64, 0)
			for _, c := range chunks {
				var fs []float32
				if p.config.Format == Float32 {
					fs = bytesToFloat32(c)
				} else {
					fs = pcmToFloat32(c)
				}
				if len(fs)%2 != 0 {
					continue
				}
				for i := 0; i < len(fs); i += 2 {
					samples = append(samples, [2]float64{float64(fs[i]), float64(fs[i+1])})
				}
			}
			if len(samples) == 0 {
				time.Sleep(5 * time.Millisecond)
				continue
			}
		} else {
			time.Sleep(5 * time.Millisecond)
			continue
		}

		// 2) Resampler si nécessaire
		var processed []float32
		if p.resampler != nil {
			if len(p.resampleBuf) < len(samples)*2 {
				p.resampleBuf = make([]float32, len(samples)*2)
			}
			inBuf := convertFloat64ToFloat32(samples)
			_, np, err := p.resampler.Process(inBuf, p.resampleBuf)
			if err != nil {
				return err
			}
			processed = p.resampleBuf[:np]
		} else {
			processed = convertFloat64ToFloat32(samples)
		}

		// 3) Appliquer volume
		p.volumeMu.Lock()
		volumeFactor := float32(math.Pow(2, p.volumeValue))
		p.volumeMu.Unlock()
		for i := range processed {
			processed[i] *= volumeFactor
		}

		// 4) Convertir et écrire
		var chunk []byte
		if p.config.Format == Float32 {
			chunk = float32ToBytes(processed)
		} else {
			chunk = float32ToPCM(processed)
		}
		p.buffer.Write(chunk)
	}

	// Flush resampler à la fin uniquement
	if p.resampler != nil {
		flushBuf := make([]float32, 4096)
		for {
			_, np, err := p.resampler.Process(nil, flushBuf)
			if err != nil {
				return err
			}
			if np == 0 {
				break
			}

			processed := flushBuf[:np]
			p.volumeMu.Lock()
			volumeFactor := float32(math.Pow(2, p.volumeValue))
			p.volumeMu.Unlock()
			for i := range processed {
				processed[i] *= volumeFactor
			}

			var chunk []byte
			if p.config.Format == Float32 {
				chunk = float32ToBytes(processed)
			} else {
				chunk = float32ToPCM(processed)
			}
			p.buffer.Write(chunk)
		}
	}

	p.buffer.Close() // fermer uniquement à la fin
	return nil
}
-----------------
------- pmostream/fork_eq_test.go ------
//go:build cgo
// +build cgo

package pmostream

import (
	"math"
	"testing"
	"time"

	"github.com/gopxl/beep"
)

// TestForkedEQ vérifie que chaque fork peut avoir son propre ParametricEQ appliqué
func TestAudioProcessorWithMasterBuffer(t *testing.T) {
	stream := beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
		for i := range samples {
			v := 0.2 * math.Sin(2*math.Pi*440*float64(i)/44100.0)
			samples[i][0] = v
			samples[i][1] = v
		}
		return len(samples), true
	})

	format := beep.Format{SampleRate: 44100, NumChannels: 2, Precision: 2}
	config := DefaultHiFiConfig()
	master := NewMasterBuffer()

	proc, err := NewAudioProcessor(stream, format, config, master)
	if err != nil {
		t.Fatal(err)
	}

	go proc.Process()
	defer proc.Close()

	time.Sleep(50 * time.Millisecond)

	if proc.buffer.Available() == 0 {
		t.Fatal("buffer should contain data")
	}

	chunk := proc.buffer.Read()
	if chunk == nil || len(chunk) == 0 {
		t.Fatal("failed to read chunk from buffer")
	}
}
-----------------
------- pmostream/fork.go ------
package pmostream

import (
	"fmt"
	"sync"
	"time"

	"github.com/gopxl/beep"
)

// MasterBuffer est un buffer central qui permet de fork un flux vers plusieurs processors
type MasterBuffer struct {
	mu     sync.RWMutex
	cond   *sync.Cond
	chunks [][]byte
	closed bool
}

// NewMasterBuffer crée un buffer central
func NewMasterBuffer() *MasterBuffer {
	mb := &MasterBuffer{}
	mb.cond = sync.NewCond(&mb.mu)
	return mb
}

// Write ajoute un chunk au buffer central
func (mb *MasterBuffer) Write(chunk []byte) {
	if len(chunk) == 0 {
		return
	}

	mb.mu.Lock()
	defer mb.mu.Unlock()

	if mb.closed {
		return
	}

	data := make([]byte, len(chunk))
	copy(data, chunk)

	mb.chunks = append(mb.chunks, data)
	mb.cond.Broadcast()
}

// Fork crée un lecteur indépendant pour ce buffer
func (mb *MasterBuffer) Fork() *ForkedBuffer {
	mb.mu.RLock()
	defer mb.mu.RUnlock()

	return &ForkedBuffer{
		master: mb,
		index:  len(mb.chunks),
	}
}

// Close ferme le buffer et notifie tous les lecteurs
func (mb *MasterBuffer) Close() {
	mb.mu.Lock()
	defer mb.mu.Unlock()
	mb.closed = true
	mb.cond.Broadcast()
}

// ForkedBuffer permet à un processor forké de lire indépendamment
type ForkedBuffer struct {
	master *MasterBuffer
	index  int
}

func (fb *ForkedBuffer) ReadAll() [][]byte {
	fb.master.mu.Lock() // Lock au lieu de RLock
	defer fb.master.mu.Unlock()

	if fb.index >= len(fb.master.chunks) {
		return nil
	}

	result := make([][]byte, len(fb.master.chunks)-fb.index)
	for i := fb.index; i < len(fb.master.chunks); i++ {
		result[i-fb.index] = fb.master.chunks[i]
	}
	fb.index = len(fb.master.chunks)
	return result
}

func (fb *ForkedBuffer) WaitForData(timeoutMs int) bool {
	deadline := time.Now().Add(time.Duration(timeoutMs) * time.Millisecond)
	fb.master.mu.Lock()
	defer fb.master.mu.Unlock()

	for fb.index >= len(fb.master.chunks) && !fb.master.closed {
		remaining := time.Until(deadline)
		if remaining <= 0 {
			return false
		}
		fb.master.cond.Wait()
	}
	return fb.index < len(fb.master.chunks)
}

// ForkStreamer crée plusieurs streamers à partir d'un streamer source en utilisant un MasterBuffer
func ForkStreamer(streamer beep.Streamer, format beep.Format, config HiFiConfig, nForks int) ([]beep.Streamer, error) {
	if nForks < 1 {
		return nil, fmt.Errorf("nForks must be at least 1")
	}

	// Créer un MasterBuffer
	master := NewMasterBuffer()

	// Créer un processeur principal qui alimente le MasterBuffer
	mainProc, err := NewAudioProcessor(streamer, format, config, master)
	if err != nil {
		return nil, err
	}

	// Démarrer le traitement principal
	go mainProc.Process()

	// Créer des streamers forké
	forks := make([]beep.Streamer, nForks)
	for i := 0; i < nForks; i++ {
		forkedBuffer := master.Fork()
		forks[i] = &forkedStreamer{
			fb:     forkedBuffer,
			config: config,
		}
	}

	return forks, nil
}

// forkedStreamer implémente beep.Streamer pour lire depuis un ForkedBuffer
type forkedStreamer struct {
	fb     *ForkedBuffer
	config HiFiConfig
}

func (fs *forkedStreamer) Stream(samples [][2]float64) (n int, ok bool) {
	if !fs.fb.WaitForData(100) {
		return 0, true
	}

	chunks := fs.fb.ReadAll()
	if len(chunks) == 0 {
		return 0, true
	}

	// Concaténer tous les chunks
	var totalSize int
	for _, chunk := range chunks {
		totalSize += len(chunk)
	}

	combined := make([]byte, 0, totalSize)
	for _, chunk := range chunks {
		combined = append(combined, chunk...)
	}

	// Convertir en float32 selon le format
	var allData []float32
	if fs.config.Format == Float32 {
		allData = bytesToFloat32(combined)
	} else {
		allData = pcmToFloat32(combined)
	}

	if allData == nil {
		return 0, true
	}

	numSamples := len(allData) / 2
	if numSamples > len(samples) {
		numSamples = len(samples)
	}

	for i := 0; i < numSamples; i++ {
		if 2*i+1 < len(allData) {
			samples[i][0] = float64(allData[2*i])
			samples[i][1] = float64(allData[2*i+1])
		}
	}

	return numSamples, true
}

func (fs *forkedStreamer) Err() error {
	return nil
}

// ReadAll retourne tous les chunks disponibles dans le buffer central
func (mb *MasterBuffer) ReadAll() [][]byte {
	mb.mu.RLock()
	defer mb.mu.RUnlock()

	if mb.closed || len(mb.chunks) == 0 {
		return nil
	}

	// Créer une copie de tous les chunks
	result := make([][]byte, len(mb.chunks))
	for i, chunk := range mb.chunks {
		result[i] = make([]byte, len(chunk))
		copy(result[i], chunk)
	}

	return result
}
-----------------
------- pmostream/parameq.go ------
package pmostream

import (
	"math"
	"sync"

	"github.com/gopxl/beep"
)

// ParametricEQ applique un égaliseur paramétrique stéréo (Biquad peaking) sur un Streamer.
type ParametricEQ struct {
	input      beep.Streamer
	sampleRate float64
	mu         sync.Mutex

	// Coefficients Biquad
	a0, a1, a2, b1, b2 float64

	// États pour les deux canaux
	x1, x2, y1, y2 [2]float64
}

// EQParams définit les paramètres d'un filtre peaking
type EQParams struct {
	FreqHz float64 // fréquence centrale en Hz
	GainDB float64 // gain en dB
	Q      float64 // facteur de qualité
}

// NewParametricEQ construit un EQ peaking stéréo en interrogeant le streamer pour la fréquence
func NewParametricEQ(input beep.Streamer, params EQParams, sr beep.SampleRate) *ParametricEQ {
	eq := &ParametricEQ{
		input:      input,
		sampleRate: float64(sr),
	}

	eq.setParams(params)
	return eq
}

// setParams calcule les coefficients du Biquad
func (eq *ParametricEQ) setParams(p EQParams) {
	eq.mu.Lock()
	defer eq.mu.Unlock()

	A := math.Pow(10, p.GainDB/40) // conversion dB -> amplitude
	w0 := 2 * math.Pi * p.FreqHz / eq.sampleRate
	alpha := math.Sin(w0) / (2 * p.Q)

	a0 := 1 + alpha/A
	eq.a0 = 1
	eq.a1 = -2 * math.Cos(w0) / a0
	eq.a2 = (1 - alpha/A) / a0
	eq.b1 = 2 * math.Cos(w0) * -1 / a0
	eq.b2 = (1 - alpha*A) / a0
}

// Stream applique l'EQ sur un chunk stéréo float64 [][2]float64
func (eq *ParametricEQ) Stream(samples [][2]float64) (n int, ok bool) {
	eq.mu.Lock()
	defer eq.mu.Unlock()

	if len(samples) == 0 {
		return 0, true
	}

	for i := range samples {
		for ch := 0; ch < 2; ch++ {
			x := samples[i][ch]
			y := eq.a0*x + eq.a1*eq.x1[ch] + eq.a2*eq.x2[ch] - eq.b1*eq.y1[ch] - eq.b2*eq.y2[ch]

			eq.x2[ch] = eq.x1[ch]
			eq.x1[ch] = x
			eq.y2[ch] = eq.y1[ch]
			eq.y1[ch] = y

			samples[i][ch] = y
		}
	}
	return len(samples), true
}

// Close libère les ressources (pas nécessaire ici mais pour interface uniforme)
func (eq *ParametricEQ) Close() error {
	return nil
}

// Wrap permet d'utiliser ParametricEQ comme beep.Streamer
func (eq *ParametricEQ) Streamer() beep.Streamer {
	return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
		return eq.Stream(samples)
	})
}
-----------------
------- pmostream/streamer.go ------
package pmostream

import (
	"encoding/binary"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/gopxl/beep"
)

// StreamManager gère plusieurs AudioProcessor et clients HTTP
type StreamManager struct {
	processors map[string]*AudioProcessor
	mu         sync.RWMutex
}

// NewStreamManager crée un gestionnaire de flux audio
func NewStreamManager() *StreamManager {
	return &StreamManager{
		processors: make(map[string]*AudioProcessor),
	}
}

// AddProcessor ajoute un processeur audio et démarre sa boucle Process
func (m *StreamManager) AddProcessor(id string, processor *AudioProcessor) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, exists := m.processors[id]; exists {
		log.Printf("Processor with id %s already exists", id)
		return
	}
	m.processors[id] = processor

	go func() {
		if err := processor.Process(); err != nil {
			log.Printf("Processor error for %s: %v", id, err)
		}
	}()
}

// RemoveProcessor arrête et supprime un processeur audio
func (m *StreamManager) RemoveProcessor(id string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if processor, exists := m.processors[id]; exists {
		processor.Stop()
		processor.Close()
		delete(m.processors, id)
	}
}

// GetHandler retourne un handler HTTP pour streamer un flux audio
func (m *StreamManager) GetHandler(id string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		m.mu.RLock()
		processor, exists := m.processors[id]
		m.mu.RUnlock()

		if !exists {
			http.Error(w, "Stream not found", http.StatusNotFound)
			return
		}

		buffer := processor.GetBuffer()
		config := processor.config

		w.Header().Set("Content-Type", "audio/wav")
		w.Header().Set("Cache-Control", "no-cache")
		w.Header().Set("Connection", "keep-alive")
		w.Header().Set("Access-Control-Allow-Origin", "*")

		flusher, ok := w.(http.Flusher)
		if !ok {
			http.Error(w, "Streaming not supported", http.StatusInternalServerError)
			return
		}

		// Écrire l'en-tête WAV pour streaming
		if err := writeWavHeader(w, config.TargetSampleRate, config.Format); err != nil {
			log.Printf("Failed to write WAV header: %v", err)
			return
		}
		flusher.Flush()

		clientDone := r.Context().Done()

		for {
			select {
			case <-clientDone:
				log.Printf("Client disconnected: %s", id)
				return
			default:
			}

			// Attendre les données avec timeout
			if !buffer.WaitForData(1, 100*time.Millisecond) {
				continue
			}

			chunk := buffer.Read()
			if chunk == nil {
				continue
			}

			// Envoyer le chunk au client
			if _, err := w.Write(chunk); err != nil {
				log.Printf("Write error: %v", err)
				return
			}
			flusher.Flush()
		}
	}
}

// writeWavHeader écrit un en-tête WAV pour streaming
func writeWavHeader(w http.ResponseWriter, sampleRate beep.SampleRate, format SampleFormat) error {
	var audioFormat uint16 = 1 // PCM
	var bitsPerSample uint16 = 16

	if format == Float32 {
		audioFormat = 3 // IEEE_FLOAT
		bitsPerSample = 32
	}

	numChannels := uint16(2)
	blockAlign := numChannels * bitsPerSample / 8
	byteRate := uint32(sampleRate) * uint32(blockAlign)

	header := make([]byte, 44)
	copy(header[0:4], "RIFF")
	binary.LittleEndian.PutUint32(header[4:8], 0xFFFFFFFF) // Taille inconnue pour streaming
	copy(header[8:12], "WAVE")
	copy(header[12:16], "fmt ")
	binary.LittleEndian.PutUint32(header[16:20], 16)
	binary.LittleEndian.PutUint16(header[20:22], audioFormat)
	binary.LittleEndian.PutUint16(header[22:24], numChannels)
	binary.LittleEndian.PutUint32(header[24:28], uint32(sampleRate))
	binary.LittleEndian.PutUint32(header[28:32], byteRate)
	binary.LittleEndian.PutUint16(header[32:34], blockAlign)
	binary.LittleEndian.PutUint16(header[34:36], bitsPerSample)
	copy(header[36:40], "data")
	binary.LittleEndian.PutUint32(header[40:44], 0xFFFFFFFF) // Taille inconnue pour streaming

	_, err := w.Write(header)
	return err
}
-----------------
============== Fin des sources des packages ===============
============== Analyse des bugs par chatgpt ===============
