Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can we emit sound without noise? #152

Open
renanbastos93 opened this issue Apr 11, 2024 · 1 comment
Open

How can we emit sound without noise? #152

renanbastos93 opened this issue Apr 11, 2024 · 1 comment

Comments

@renanbastos93
Copy link

I'd like to reproduce sound using a speaker like the example below because I want to simulate a piano.

package main

import (
	"fmt"
	"math"
	"os"
	"os/signal"
	"time"

	"github.com/gopxl/beep"
	"github.com/gopxl/beep/speaker"
)

const sampleRate = 44100

func main() {
	duration := 1 * time.Second // Duração do som (por exemplo, 1 segundo)

	// Configuração do formato do som
	format := beep.SampleRate(sampleRate)
	speaker.Init(format, format.N(time.Second/10))

	// Mapa de frequências para as notas musicais
	noteFrequencies := map[string]float64{
		"DO":  261.63,
		"RE":  293.66,
		"MI":  329.63,
		"FA":  349.23,
		"SOL": 392.00,
		"LA":  440.00,
		"SI":  493.88,

		// DO RE MI FA FA DO RE DO RE RE DO SOL FA MI MI DO RE MI FA FA
	}

	doremifa := []string{
		"DO", "RE", "MI", "FA", "FA", "DO", "RE", "DO",
		"RE", "RE", "DO", "SOL", "FA", "MI", "MI", "DO", "RE",
		"MI", "FA", "FA",
	}

	// Tocar as notas musicais
	for _, note := range doremifa {
		playNoteWithCleanSignal(note, noteFrequencies[note], duration)
	}

	fmt.Println("Finished playing notes.")
}

// playNoteWithCleanSignal toca uma nota musical específica com sinal limpo
func playNoteWithCleanSignal(note string, frequency float64, duration time.Duration) {
	// Gerar streamer para a nota especificada
	streamer := beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
		for i := range samples {
			t := float64(i) / sampleRate
			// Aplicar um envelope de amplitude para suavizar o início e o fim da nota
			amplitude := math.Sin(2*math.Pi*frequency*t) * envelope(float64(i), duration.Seconds()*sampleRate)
			samples[i][0] = amplitude * 0.3 // Ajuste de volume
			samples[i][1] = samples[i][0]   // Canal estéreo
		}
		return len(samples), true
	})

	// Tocar o som
	done := make(chan bool)
	speaker.Play(beep.Seq(streamer, beep.Callback(func() {
		done <- true
	})))

	fmt.Printf("Playing note %s (%.2f Hz) for %s...\n", note, frequency, duration)

	// Esperar até que a duração expire ou até que o usuário pressione Ctrl+C
	select {
	case <-time.After(duration):
	case <-interrupt():
	}

	fmt.Printf("Note %s finished playing.\n", note)
}

func envelope(i float64, length float64) float64 {
	attack := 0.1  // duração do ataque (em porcentagem da duração total)
	decay := 0.1   // duração da queda (em porcentagem da duração total)
	sustain := 0.7 // nível de sustentação (em porcentagem do nível máximo)
	release := 0.1 // duração da liberação (em porcentagem da duração total)

	attackSamples := int(attack * length)
	decaySamples := int(decay * length)
	sustainSamples := int(sustain * length)
	releaseSamples := int(release * length)

	if i < float64(attackSamples) {
		return float64(i) / float64(attackSamples)
	} else if i < float64(attackSamples+decaySamples) {
		return 1 - (i - float64(attackSamples)/float64(decaySamples)*(1-sustain))
	} else if i < float64(attackSamples+decaySamples+sustainSamples) {
		return sustain
	} else {
		return sustain - (i-float64(attackSamples)-float64(decaySamples)-float64(sustainSamples))/(float64(releaseSamples)*sustain)
	}
}

func interrupt() chan os.Signal {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	return c
}
@MarkKremer
Copy link
Contributor

Hello 👋

Let's debug the code :)

func playNoteWithCleanSignal(note string, frequency float64, duration time.Duration) {
	streamer := beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
		for i := range samples {
			t := float64(i) / sampleRate

The above snippet shows a StreamerFunc. How speaker and a lot of Beep's components work is that they only pull a small amount of samples (e.g. 512 samples) from the streamer at a time to save on memory. Therefor, StreamerFunc will be called multiple times and each time it receives a slice with a small amount of samples to fill. If i is used as the time for the sine wave, the sine wave will start over each time, and this could cause glitch noises. What you'll need to do is keep track of the total number of samples played over the course of multiple calls to StreamerFunc. If that makes sense.

Just so you know, generators.SineTone() exists. But if you want to add the envelop function it may be a bit more difficult.


amplitude := math.Sin(2*math.Pi*frequency*t) * envelope(float64(i), duration.Seconds()*sampleRate)

Try debugging your code first without the envelope. I think envelope doesn't work as well yet so first try to make the rest of the program work and not do everything at once.


done := make(chan bool)
speaker.Play(beep.Seq(streamer, beep.Callback(func() {
    done <- true
})))

This doesn't stop the sound from playing. This will send a signal on the done channel when the streamer is finished (but it never finishes), and done isn't used. So at the end all tones are still playing. Have a look at beep.Take() or make sure the StreamerFunc returns an end signal. Then you could use done in the select statement instead of time.After().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants