diff --git a/examples/piano/main.go b/examples/piano/main.go index dd7a92d..dd8b220 100644 --- a/examples/piano/main.go +++ b/examples/piano/main.go @@ -1,6 +1,7 @@ package main import "math" +import "time" import "errors" import "github.com/faiface/beep" import "github.com/faiface/beep/speaker" @@ -15,7 +16,7 @@ const sampleRate = 44100 const bufferSize = 256 var tuning = music.EqualTemparment { A4: 440 } var waveform = 0 -var playing = map[music.Note] *beep.Ctrl { } +var playing = map[music.Note] *toneStreamer { } func main () { speaker.Init(sampleRate, bufferSize) @@ -59,17 +60,27 @@ func stopNote (note music.Note) { if _, is := playing[note]; !is { return } speaker.Lock() - playing[note].Streamer = nil + playing[note].Release() delete(playing, note) speaker.Unlock() } func playNote (note music.Note) { - streamer, _ := Tone(sampleRate, int(tuning.Tune(note)), waveform, 0.3) + streamer, _ := Tone ( + sampleRate, + int(tuning.Tune(note)), + waveform, + 0.3, + ADSR { + Attack: 100 * time.Millisecond, + Decay: 400 * time.Millisecond, + Sustain: 0.7, + Release: 500 * time.Millisecond, + }) stopNote(note) speaker.Lock() - playing[note] = &beep.Ctrl { Streamer: streamer } + playing[note] = streamer speaker.Unlock() speaker.Play(playing[note]) } @@ -80,30 +91,62 @@ func playNote (note music.Note) { type toneStreamer struct { position float64 delta float64 + waveform int gain float64 + + adsr ADSR + released bool + complete bool + + adsrPhase int + adsrPosition float64 + adsrDeltas [4]float64 +} + +type ADSR struct { + Attack time.Duration + Decay time.Duration + Sustain float64 + Release time.Duration } func Tone ( - sr beep.SampleRate, - freq int, + sampleRate beep.SampleRate, + frequency int, waveform int, gain float64, + adsr ADSR, ) ( - beep.Streamer, + *toneStreamer, error, ) { - if int(sr) / freq < 2 { + if int(sampleRate) / frequency < 2 { return nil, errors.New ( "tone generator: samplerate must be at least " + "2 times greater then frequency") } + tone := new(toneStreamer) - tone.position = 0.0 tone.waveform = waveform - steps := float64(sr) / float64(freq) + tone.position = 0.0 + steps := float64(sampleRate) / float64(frequency) tone.delta = 1.0 / steps tone.gain = gain + + if adsr.Attack < time.Millisecond { adsr.Attack = time.Millisecond } + if adsr.Decay < time.Millisecond { adsr.Decay = time.Millisecond } + if adsr.Release < time.Millisecond { adsr.Release = time.Millisecond } + tone.adsr = adsr + + attackSteps := adsr.Attack.Seconds() * float64(sampleRate) + decaySteps := adsr.Decay.Seconds() * float64(sampleRate) + releaseSteps := adsr.Release.Seconds() * float64(sampleRate) + tone.adsrDeltas[0] = 1 / attackSteps + tone.adsrDeltas[1] = 1 / decaySteps + tone.adsrDeltas[2] = 0 + tone.adsrDeltas[3] = 1 / releaseSteps + return tone, nil } @@ -127,17 +170,59 @@ func (tone *toneStreamer) nextSample () (sample float64) { 28.32 * tone.position * tone.position + 15.62 * tone.position * tone.position * tone.position } + + adsrGain := 0.0 + switch tone.adsrPhase { + case 0: adsrGain = tone.adsrPosition + if tone.adsrPosition > 1 { + tone.adsrPosition = 0 + tone.adsrPhase = 1 + } + + case 1: adsrGain = 1 + tone.adsrPosition * (tone.adsr.Sustain - 1) + if tone.adsrPosition > 1 { + tone.adsrPosition = 0 + tone.adsrPhase = 2 + } + + case 2: adsrGain = tone.adsr.Sustain + if tone.released { + tone.adsrPhase = 3 + } + + case 3: adsrGain = (1 - tone.adsrPosition) * tone.adsr.Sustain + if tone.adsrPosition > 1 { + tone.adsrPosition = 0 + tone.complete = true + } + } + + sample *= adsrGain * adsrGain + + tone.adsrPosition += tone.adsrDeltas[tone.adsrPhase] _, tone.position = math.Modf(tone.position + tone.delta) return } func (tone *toneStreamer) Stream (buf [][2]float64) (int, bool) { + if tone.complete { + return 0, false + } + for i := 0; i < len(buf); i++ { - sample := tone.nextSample() * tone.gain + sample := 0.0 + if !tone.complete { + sample = tone.nextSample() * tone.gain + } buf[i] = [2]float64{sample, sample} } return len(buf), true } + func (tone *toneStreamer) Err () error { return nil } + +func (tone *toneStreamer) Release () { + tone.released = true +}