ebitengine / oto

♪ A low-level library to play sound on multiple platforms ♪
Apache License 2.0
1.59k stars 133 forks source link

Click sound when each sinewave in example finishes playing. #234

Closed bwmarrin closed 7 months ago

bwmarrin commented 7 months ago

Hi :) Thanks for making this library, I'm excited to use it to write a morse code (cw) training application.

I'm just now getting started and trying out sending some audio but I've run into an issue. Each time my app (and the example app in this repo) finishes a play of a sinewave there's a click sound. I don't notice this when the sinewave starts playing, just right as it stops.

I'm not audio genius and am quite new to this library so I am hoping there are some wizards in here that might have a solution to this? Having this ending click sound does make this unusable for my morse code goals since things would be very confusing with beeps and clicks all together! :(

I'm building the example inside WSL on Windows 10 and cross compiling it for Win 10, then just running the executable on the same Windows 10 machine under Windows (not WSL) if that all makes sense.

hajimehoshi commented 7 months ago

Could you show the minimal code to reproduce your issue? Thanks,

Asday commented 7 months ago

It's probably because you're not waiting for a zero crossing.

Sound is a wave, and if you stop the sound while the wave is far away from zero, you're going to hear a high frequency pop as the wave jumps to zero for the next sample.

Consider waiting to cut the signal until it crosses zero, or applying a very short fade out when cutting the signal.

bwmarrin commented 7 months ago

@hajimehoshi your simple sinewave example code in this project does it, as each tone ends, there's a pop/click sound.

@Asday is right. I did some google digging and learning on Sinewaves and apparently if you cut it off somewhere other than back at zero it ends with a somewhat harsh clickish sound. I was able to confirm this by doing some hackish code to find the ending sinewave point then truncate the bytes sent by the Read() func in the example to that point and pass that over to the oto player, and the click is gone. The way I did it though is kind of clunkly so I'd like to fine a cleaner solution so that the Read function itself is smart and ends at a zero point.

So, my next struggle since I'm not that smart :) How do I modify the example code so that it ends on a zero point? Is there some math that I can use to calculate the right amount of bytes or time length that a given freq/sample would be back at zero?

I'm going to dig around on the internet more and try to figure it out but if anyone happens to already know or have an example that'd be awesome and I would appreciate it :) Thanks.

hajimehoshi commented 7 months ago

@hajimehoshi your simple sinewave example code in this project does it, as each tone ends, there's a pop/click sound.

You mean an Ebitengine's example? (In this case, github.com/hajimehoshi/ebiten is the better place to discuss by the way) Then, as @Asday said, this is expected. The sounds abruptlly ends and a noisy sound could happen. If you want to avoid this, you should fade out the sound before terminating the game. For the sake of simplicity, the example doesn't handle such things.

So, my next struggle since I'm not that smart :) How do I modify the example code so that it ends on a zero point? Is there some math that I can use to calculate the right amount of bytes or time length that a given freq/sample would be back at zero?

Set the volume value between 0 and 1 for each samples. Start with 1 and decrease to 0 gradually. You can call SetVolume at every Update assuming you are using Ebitengine.

bwmarrin commented 7 months ago

I mean the example code here in this oto repo - https://github.com/ebitengine/oto/blob/main/example/main.go

I am not using Ebitengine game engine or any of it's examples - only this oto sound library.

Since I'm using this to make morse code (cw) the sinewaves I'd need are pretty small individually, 40ms to 100ms or so depending on the speed. I suppose having it fade in/out wouldn't hurt but it would have to be a very short fade of a couple or few ms. I think (with a lot of help from Google) I can figure out the maths/code to generate the sinewave byte data that does this, eventually, but if someone already had an example that'd be awesome :)

hajimehoshi commented 7 months ago

I mean the example code here in this oto repo - https://github.com/ebitengine/oto/blob/main/example/main.go

Thanks. This is the same thing: the example abrupts its wave so you would have to adjust the volume.