-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #141 from gopxl/transition-effect
Add effects.Transition and a cross-fade example
- Loading branch information
Showing
2 changed files
with
144 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package effects | ||
|
||
import ( | ||
"math" | ||
|
||
"github.com/gopxl/beep" | ||
) | ||
|
||
// TransitionFunc defines a function used in a transition to describe the progression curve | ||
// from one value to the next. The input 'percent' always ranges from 0.0 to 1.0, where 0.0 | ||
// represents the starting point and 1.0 represents the end point of the transition. | ||
// | ||
// The returned value from TransitionFunc is expected to be in the normalized range of [0.0, 1.0]. | ||
// However, it may exceed this range, providing flexibility to generate curves with momentum. | ||
// The Transition() function then maps this normalized output to the actual desired range. | ||
type TransitionFunc func(percent float64) float64 | ||
|
||
// TransitionLinear transitions the gain linearly from the start to end value. | ||
func TransitionLinear(percent float64) float64 { | ||
return percent | ||
} | ||
|
||
// TransitionEqualPower transitions the gain of a streamer in such a way that the total perceived volume stays | ||
// constant if mixed together with another streamer doing the inverse transition. | ||
// | ||
// See https://www.oreilly.com/library/view/web-audio-api/9781449332679/ch03.html#s03_2 for more information. | ||
func TransitionEqualPower(percent float64) float64 { | ||
return math.Cos((1.0 - percent) * 0.5 * math.Pi) | ||
} | ||
|
||
// Transition gradually adjusts the gain of the source streamer 's' from 'startGain' to 'endGain' | ||
// over the entire duration of the stream, defined by the number of samples 'len'. | ||
// The transition is defined by the provided 'transitionFunc' function, which determines the | ||
// gain at each point during the transition. | ||
func Transition(s beep.Streamer, len int, startGain, endGain float64, transitionfunc TransitionFunc) *TransitionStreamer { | ||
return &TransitionStreamer{ | ||
s: s, | ||
len: len, | ||
startGain: startGain, | ||
endGain: endGain, | ||
transitionFunc: transitionfunc, | ||
} | ||
} | ||
|
||
type TransitionStreamer struct { | ||
s beep.Streamer | ||
pos int | ||
len int | ||
startGain, endGain float64 | ||
transitionFunc TransitionFunc | ||
} | ||
|
||
// Stream fills samples with the gain-adjusted samples of the source streamer. | ||
func (t *TransitionStreamer) Stream(samples [][2]float64) (n int, ok bool) { | ||
n, ok = t.s.Stream(samples) | ||
|
||
for i := 0; i < n; i++ { | ||
pos := t.pos + i | ||
progress := float64(pos) / float64(t.len) | ||
if progress < 0 { | ||
progress = 0 | ||
} else if progress > 1 { | ||
progress = 1 | ||
} | ||
value := t.transitionFunc(progress) | ||
gain := t.startGain + (t.endGain-t.startGain)*value | ||
|
||
samples[i][0] *= gain | ||
samples[i][1] *= gain | ||
} | ||
|
||
t.pos += n | ||
|
||
return | ||
} | ||
|
||
// Err propagates the original Streamer's errors. | ||
func (t *TransitionStreamer) Err() error { | ||
return t.s.Err() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package effects_test | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/gopxl/beep" | ||
"github.com/gopxl/beep/effects" | ||
"github.com/gopxl/beep/generators" | ||
"github.com/gopxl/beep/speaker" | ||
) | ||
|
||
// Cross-fade between two sine tones. | ||
func ExampleTransition() { | ||
sampleRate := beep.SampleRate(44100) | ||
|
||
s1, err := generators.SineTone(sampleRate, 261.63) | ||
if err != nil { | ||
panic(err) | ||
} | ||
s2, err := generators.SineTone(sampleRate, 329.628) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
crossFades := beep.Seq( | ||
// Play s1 normally for 3 seconds | ||
beep.Take(sampleRate.N(time.Second*3), s1), | ||
// Play s1 and s2 together. s1 transitions from a gain of 1.0 (normal volume) | ||
// to 0.0 (silent) whereas s2 does the opposite. The equal power transition | ||
// function helps keep the overall volume constant. | ||
beep.Mix( | ||
effects.Transition( | ||
beep.Take(sampleRate.N(time.Second*2), s1), | ||
sampleRate.N(time.Second*2), | ||
1.0, | ||
0.0, | ||
effects.TransitionEqualPower, | ||
), | ||
effects.Transition( | ||
beep.Take(sampleRate.N(time.Second*2), s2), | ||
sampleRate.N(time.Second*2), | ||
0.0, | ||
1.0, | ||
effects.TransitionEqualPower, | ||
), | ||
), | ||
// Play the rest of s2 normally for 3 seconds | ||
beep.Take(sampleRate.N(time.Second*3), s2), | ||
) | ||
|
||
err = speaker.Init(sampleRate, sampleRate.N(time.Second/30)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
done := make(chan struct{}) | ||
speaker.Play(beep.Seq( | ||
crossFades, | ||
beep.Callback(func() { | ||
done <- struct{}{} | ||
}), | ||
)) | ||
<-done | ||
} |