package audio import ( "log" "math" "sync" "git.atri.dad/atridad/muse/schema" ) const ( SampleRate = 44100 Bits = 16 Channels = 2 ) // Handles audio synthesis type Generator struct { sampleRate float64 bufferPool sync.Pool } // Creates a new audio generator func NewGenerator() *Generator { return &Generator{ sampleRate: float64(SampleRate), bufferPool: sync.Pool{ New: func() interface{} { return make([]float64, 0, 4096) }, }, } } // Renders the complete song to audio samples func (g *Generator) Generate(song schema.Song) ([]float64, error) { samplesPerBeat := (60.0 / float64(song.BPM)) * g.sampleRate log.Printf("Generating song at %.1f BPM, sample rate: %.0f, samples per beat: %.1f\n", float64(song.BPM), g.sampleRate, samplesPerBeat) totalBeats := song.CalculateTotalLength() log.Printf("Total song length: %.1f beats", totalBeats) totalSamples := int(float64(totalBeats) * samplesPerBeat) buffer := make([]float64, totalSamples*Channels) if totalSamples == 0 { log.Printf("Warning: Zero samples calculated for song") } var wg sync.WaitGroup var mu sync.Mutex for _, track := range song.Tracks { wg.Add(1) go func(track schema.Track) { defer wg.Done() patternLength := len(track.Pattern) if patternLength == 0 { return } for beat := 0; beat < int(totalBeats); beat++ { var note string if track.Loop { patternIndex := beat % patternLength note = track.Pattern[patternIndex] } else { if beat < patternLength { note = track.Pattern[beat] } else { note = "." } } if note == "." || note == "" { continue } startSample := int(float64(beat) * samplesPerBeat) endSample := startSample + int(samplesPerBeat) if startSample >= len(buffer)/Channels { continue } if endSample > len(buffer)/Channels { endSample = len(buffer) / Channels } freq := noteToFreq(note) amplitude := 0.5 duration := float64(endSample-startSample) / g.sampleRate audioData := g.generateOscillator(track.Instrument, freq, duration, amplitude*track.Volume, 0) if len(audioData) == 0 { continue } if len(track.Effects) > 0 { effectChain := CreateEffectChain(track.Effects, g.sampleRate) audioData = effectChain.Process(audioData) } pan := (track.Pan + 1) / 2 leftGain := math.Sqrt(1.0 - pan) rightGain := math.Sqrt(pan) mu.Lock() for i, sample := range audioData { if startSample+i >= len(buffer)/Channels { break } leftSample := sample * leftGain rightSample := sample * rightGain buffer[(startSample+i)*Channels] += leftSample buffer[(startSample+i)*Channels+1] += rightSample } mu.Unlock() } }(track) } wg.Wait() return buffer, nil }